Compare commits

..

294 Commits

Author SHA1 Message Date
61182deb96 Bump version to 0.94.0 (#12987) 2024-05-28 12:04:09 -07:00
4ab2c3238a Disable reedline patch for 0.94.0 (#12986)
Disable crates.io git patch for reedline for 0.94.0 release.
2024-05-28 18:53:51 +00:00
6012af2412 Fix panic when redirecting nothing (#12970)
# Description
Fixes #12969 where the parser can panic if a redirection is applied to
nothing / an empty command.

# Tests + Formatting
Added a test.
2024-05-27 10:03:06 +08:00
f74dd33ba9 Fix touch --reference using PWD from the environment (#12976)
This PR fixes `touch --reference path` so that it resolves `path` using
PWD from the engine state.
2024-05-26 20:24:00 +03:00
a1fc41db22 Fix path type using PWD from the environment (#12975)
This PR fixes the `path type` command so that it resolves relative paths
using PWD from the engine state.

As a bonus, it also fixes the issue of `path type` returning an empty
string instead of an error when it fails.
2024-05-26 20:23:52 +03:00
f38f88d42c Fixes . expanded incorrectly as external argument (#12950)
This PR fixes a bug where `.` is expanded into an empty string when used
as an argument to external commands. Fixes
https://github.com/nushell/nushell/issues/12948.

---------

Co-authored-by: Ian Manske <ian.manske@pm.me>
2024-05-26 07:06:17 +08:00
0c5a67f4e5 make polars plugin use mimalloc (#12967)
# Description
@maxim-uvarov did a ton of research and work with the dply-rs author and
ritchie from polars and found out that the allocator matters on macos
and it seems to be what was messing up the performance of polars plugin.
ritchie suggested to use jemalloc but i switched it to mimalloc to match
nushell and it seems to run better.

## Before (default allocator)
note - using 1..10 vs 1..100 since it takes so long. also notice how
high the `max` timings are compared to mimalloc below.
```nushell
❯ 1..10 | each {timeit {polars open Data7602DescendingYearOrder.csv | polars group-by year | polars agg (polars col geo_count | polars sum) | polars collect | null}} |   | {mean: ($in | math avg), min: ($in | math min), max: ($in | math max), stddev: ($in | into int | into float | math stddev | into int | $'($in)ns' | into duration)}
╭────────┬─────────────────────────╮
│ mean   │ 4sec 999ms 605µs 995ns  │
│ min    │ 983ms 627µs 42ns        │
│ max    │ 13sec 398ms 135µs 791ns │
│ stddev │ 3sec 476ms 479µs 939ns  │
╰────────┴─────────────────────────╯
❯ use std bench
❯ bench { polars open Data7602DescendingYearOrder.csv | polars group-by year | polars agg (polars col geo_count | polars sum) | polars collect | null } -n 10
╭───────┬────────────────────────╮
│ mean  │ 6sec 220ms 783µs 983ns │
│ min   │ 1sec 184ms 997µs 708ns │
│ max   │ 18sec 882ms 81µs 708ns │
│ std   │ 5sec 350ms 375µs 697ns │
│ times │ [list 10 items]        │
╰───────┴────────────────────────╯
```

## After (using mimalloc)
```nushell
❯ 1..100 | each {timeit {polars open Data7602DescendingYearOrder.csv | polars group-by year | polars agg (polars col geo_count | polars sum) | polars collect | null}} |   | {mean: ($in | math avg), min: ($in | math min), max: ($in | math max), stddev: ($in | into int | into float | math stddev | into int | $'($in)ns' | into duration)}
╭────────┬───────────────────╮
│ mean   │ 103ms 728µs 902ns │
│ min    │ 97ms 107µs 42ns   │
│ max    │ 149ms 430µs 84ns  │
│ stddev │ 5ms 690µs 664ns   │
╰────────┴───────────────────╯
❯ use std bench
❯ bench { polars open Data7602DescendingYearOrder.csv | polars group-by year | polars agg (polars col geo_count | polars sum) | polars collect | null } -n 100
╭───────┬───────────────────╮
│ mean  │ 103ms 620µs 195ns │
│ min   │ 97ms 541µs 166ns  │
│ max   │ 130ms 262µs 166ns │
│ std   │ 4ms 948µs 654ns   │
│ times │ [list 100 items]  │
╰───────┴───────────────────╯
```

## After (using jemalloc - just for comparison)
```nushell
❯ 1..100 | each {timeit {polars open Data7602DescendingYearOrder.csv | polars group-by year | polars agg (polars col geo_count | polars sum) | polars collect | null}} |   | {mean: ($in | math avg), min: ($in | math min), max: ($in | math max), stddev: ($in | into int | into float | math stddev | into int | $'($in)ns' | into duration)}

╭────────┬───────────────────╮
│ mean   │ 113ms 939µs 777ns │
│ min    │ 108ms 337µs 333ns │
│ max    │ 166ms 467µs 458ns │
│ stddev │ 6ms 175µs 618ns   │
╰────────┴───────────────────╯
❯ use std bench
❯ bench { polars open Data7602DescendingYearOrder.csv | polars group-by year | polars agg (polars col geo_count | polars sum) | polars collect | null } -n 100
╭───────┬───────────────────╮
│ mean  │ 114ms 363µs 530ns │
│ min   │ 108ms 804µs 833ns │
│ max   │ 143ms 521µs 459ns │
│ std   │ 5ms 88µs 56ns     │
│ times │ [list 100 items]  │
╰───────┴───────────────────╯
```

## After (using parquet + mimalloc)
```nushell
❯ 1..100 | each {timeit {polars open data.parquet | polars group-by year | polars agg (polars col geo_count | polars sum) | polars collect | null}} |   | {mean: ($in | math avg), min: ($in | math min), max: ($in | math max), stddev: ($in | into int | into float | math stddev | into int | $'($in)ns' | into duration)}
╭────────┬──────────────────╮
│ mean   │ 34ms 255µs 492ns │
│ min    │ 31ms 787µs 250ns │
│ max    │ 76ms 408µs 416ns │
│ stddev │ 4ms 472µs 916ns  │
╰────────┴──────────────────╯
❯ use std bench
❯ bench { polars open data.parquet | polars group-by year | polars agg (polars col geo_count | polars sum) | polars collect | null } -n 100
╭───────┬──────────────────╮
│ mean  │ 34ms 897µs 562ns │
│ min   │ 31ms 518µs 542ns │
│ max   │ 65ms 943µs 625ns │
│ std   │ 3ms 450µs 741ns  │
│ times │ [list 100 items] │
╰───────┴──────────────────╯
```

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

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

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

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

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2024-05-25 09:10:01 -05:00
95977faf2d Do not propagate glob creation error for external args (#12955)
# Description
Instead of returning an error, this PR changes `expand_glob` in
`run_external.rs` to return the original string arg if glob creation
failed. This makes it so that, e.g.,
```nushell
^echo `[`
^echo `***`
```
no longer fail with a shell error. (This follows from #12921.)
2024-05-25 08:59:36 +08:00
c5d716951f Allow byte streams with unknown type to be compatiable with binary (#12959)
# Description
Currently, this pipeline doesn't work `open --raw file | take 100`,
since the type of the byte stream is `Unknown`, but `take` expects
`Binary` streams. This PR changes commands that expect
`ByteStreamType::Binary` to also work with `ByteStreamType::Unknown`.
This was done by adding two new methods to `ByteStreamType`:
`is_binary_coercible` and `is_string_coercible`. These return true if
the type is `Unknown` or matches the type in the method name.
2024-05-24 17:54:38 -07:00
b06f31d3c6 Make from json --objects streaming (#12949)
# Description

Makes the `from json --objects` command produce a stream, and read
lazily from an input stream to produce its output.

Also added a helper, `PipelineData::get_type()`, to make it easier to
construct a wrong type error message when matching on `PipelineData`. I
expect checking `PipelineData` for either a string value or an `Unknown`
or `String` typed `ByteStream` will be very, very common. I would have
liked to have a helper that just returns a readable stream from either,
but that would either be a bespoke enum or a `Box<dyn BufRead>`, which
feels like it wouldn't be so great for performance. So instead, taking
the approach I did here is probably better - having a function that
accepts the `impl BufRead` and matching to use it.

# User-Facing Changes

- `from json --objects` no longer collects its input, and can be used
for large datasets or streams that produce values over time.

# Tests + Formatting
All passing.

# After Submitting
- [ ] release notes

---------

Co-authored-by: Ian Manske <ian.manske@pm.me>
2024-05-24 23:37:50 +00:00
84b7a99adf Revert "Polars lazy refactor (#12669)" (#12962)
This reverts commit 68adc4657f.

# Description

Reverts the lazyframe refactor (#12669) for the next release, since
there are still a few lingering issues. This temporarily solves #12863
and #12828. After the release, the lazyframes can be added back and
cleaned up.
2024-05-24 18:09:26 -05:00
7d11c28eea Revert "Remove std::env::set_current_dir() call from EngineState::merge_env()" (#12954)
Reverts nushell/nushell#12922
2024-05-24 11:09:59 -05:00
bf07806b1b Use cwd in grid (#12947)
# Description
Fixes #12946. The `grid` command does not use the cwd when trying to get
the icon or color for a file/path.
2024-05-23 20:38:47 +00:00
0b5a4c0d95 explore refactoring+clarification (#12940)
Another very boring PR cleaning up and documenting some of `explore`'s
innards. Mostly renaming things that I found confusing or vague when
reading through the code, also adding some comments.
2024-05-23 08:51:39 -05:00
f53aa6fcbf fix std help (#12943)
# Description
Fixes: #12941

~~The issue is cause by some columns(is_builtin, is_plugin, is_custom,
is_keyword) are removed in #10023~~
Edit: I'm wrong

# Tests + Formatting
Added one test for `std help`
2024-05-23 08:51:02 -05:00
2612a167e3 Remove list support in with-env (#12939)
# Description
Following from #12523, this PR removes support for lists of environments
variables in the `with-env` command. Rather, only records will be
supported now.

# After Submitting
Update examples using the list form in the docs and book.
2024-05-23 13:53:55 +08:00
c7097ca937 explore cleanup: remove+move binary viewer config (#12920)
Small change, removing 4 more configuration options from `explore`'s
binary viewer:

1. `show_index`
2. `show_data`
3. `show_ascii`
4. `show_split`

These controlled whether the 3 columns in the binary viewer (index, hex
data, ASCII) and the pipe separator (`|`) in between them are shown. I
don't think we need this level of configurability until the `explore`
command is more mature, and maybe even not then; we can just show them
all.

I think it's very unlikely that anyone is using these configuration
points.

Also, the row offset (e.g. how many rows we have scrolled down) was
being stored in config/settings when it's arguably not config; more like
internal state of the binary viewer. I moved it to a more appropriate
location and renamed it.
2024-05-22 20:06:14 -07:00
58cf0c56f8 add some completion tests (#12908)
# Description
```nushell
❯ ls
╭───┬───────┬──────┬──────┬──────────╮
│ # │ name  │ type │ size │ modified │
├───┼───────┼──────┼──────┼──────────┤
│ 0 │ a.txt │ file │  0 B │ now      │
╰───┴───────┴──────┴──────┴──────────╯

❯ ls a.
NO RECORDS FOUND
```

There is a completion issue on previous version, I think @amtoine have
reproduced it before. But currently I can't reproduce it on latest main.
To avoid such regression, I added some tests for completion.

---------

Co-authored-by: Antoine Stevan <44101798+amtoine@users.noreply.github.com>
2024-05-23 10:47:06 +08:00
6c649809d3 Rewrite run_external.rs (#12921)
This PR is a complete rewrite of `run_external.rs`. The main goal of the
rewrite is improving readability, but it also fixes some bugs related to
argument handling and the PATH variable (fixes
https://github.com/nushell/nushell/issues/6011).

I'll discuss some technical details to make reviewing easier.

## Argument handling

Quoting arguments for external commands is hard. Like, *really* hard.
We've had more than a dozen issues and PRs dedicated to quoting
arguments (see Appendix) but the current implementation is still buggy.

Here's a demonstration of the buggy behavior:

```nu
let foo = "'bar'"
^touch $foo            # This creates a file named `bar`, but it should be `'bar'`
^touch ...[ "'bar'" ]  # Same
```

I'll describe how this PR deals with argument handling.

First, we'll introduce the concept of **bare strings**. Bare strings are
**string literals** that are either **unquoted** or **quoted by
backticks** [^1]. Strings within a list literal are NOT considered bare
strings, even if they are unquoted or quoted by backticks.

When a bare string is used as an argument to external process, we need
to perform tilde-expansion, glob-expansion, and inner-quotes-removal, in
that order. "Inner-quotes-removal" means transforming from
`--option="value"` into `--option=value`.

## `.bat` files and CMD built-ins

On Windows, `.bat` files and `.cmd` files are considered executable, but
they need `CMD.exe` as the interpreter. The Rust standard library
supports running `.bat` files directly and will spawn `CMD.exe` under
the hood (see
[documentation](https://doc.rust-lang.org/std/process/index.html#windows-argument-splitting)).
However, other extensions are not supported [^2].

Nushell also supports a selected number of CMD built-ins. The problem
with CMD is that it uses a different set of quoting rules. Correctly
quoting for CMD requires using
[Command::raw_arg()](https://doc.rust-lang.org/std/os/windows/process/trait.CommandExt.html#tymethod.raw_arg)
and manually quoting CMD special characters, on top of quoting from the
Nushell side. ~~I decided that this is too complex and chose to reject
special characters in CMD built-ins instead [^3]. Hopefully this will
not affact real-world use cases.~~ I've implemented escaping that works
reasonably well.

## `which-support` feature

The `which` crate is now a hard dependency of `nu-command`, making the
`which-support` feature essentially useless. The `which` crate is
already a hard dependency of `nu-cli`, and we should consider removing
the `which-support` feature entirely.

## Appendix

Here's a list of quoting-related issues and PRs in rough chronological
order.

* https://github.com/nushell/nushell/issues/4609
* https://github.com/nushell/nushell/issues/4631
* https://github.com/nushell/nushell/issues/4601
  * https://github.com/nushell/nushell/pull/5846
* https://github.com/nushell/nushell/issues/5978
  * https://github.com/nushell/nushell/pull/6014
* https://github.com/nushell/nushell/issues/6154
  * https://github.com/nushell/nushell/pull/6161
* https://github.com/nushell/nushell/issues/6399
  * https://github.com/nushell/nushell/pull/6420
  * https://github.com/nushell/nushell/pull/6426
* https://github.com/nushell/nushell/issues/6465
* https://github.com/nushell/nushell/issues/6559
  * https://github.com/nushell/nushell/pull/6560

[^1]: The idea that backtick-quoted strings act like bare strings was
introduced by Kubouch and briefly mentioned in [the language
reference](https://www.nushell.sh/lang-guide/chapters/strings_and_text.html#backtick-quotes).

[^2]: The documentation also said "running .bat scripts in this way may
be removed in the future and so should not be relied upon", which is
another reason to move away from this. But again, quoting for CMD is
hard.

[^3]: If anyone wants to try, the best resource I found on the topic is
[this](https://daviddeley.com/autohotkey/parameters/parameters.htm).
2024-05-23 02:05:27 +00:00
64afb52ffa Fix leftover wrong column name (#12937)
# Description

Small fixup for https://github.com/nushell/nushell/pull/12930
2024-05-22 21:24:22 +00:00
ac4125f8ed fix range semantic in detect_columns, str substring, str index-of (#12894)
# Description
Fixes: https://github.com/nushell/nushell/issues/7761

It's still unsure if we want to change the `range semantic` itself, but
it's good to keep range semantic consistent between nushell commands.

# User-Facing Changes
### Before
```nushell
❯ "abc" | str substring 1..=2
b
```
### After
```nushell
❯ "abc" | str substring 1..=2
bc
```

# Tests + Formatting
Adjust tests to fit new behavior
2024-05-22 20:00:58 +03:00
7ede90cba5 Remove std::env::set_current_dir() call from EngineState::merge_env() (#12922)
As discussed in https://github.com/nushell/nushell/pull/12749, we no
longer need to call `std::env::set_current_dir()` to sync `$env.PWD`
with the actual working directory. This PR removes the call from
`EngineState::merge_env()`.
2024-05-22 19:58:27 +03:00
75689ec98a Small improvements to debug profile (#12930)
<!--
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.
-->

1. With the `-l` flag, `debug profile` now collects files and line
numbers of profiled pipeline elements

![profiler_lines](https://github.com/nushell/nushell/assets/25571562/b400a956-d958-4aff-aa4c-7e65da3f78fa)

2. Error from the profiled closure will be reported instead of silently
ignored.

![profiler_lines_error](https://github.com/nushell/nushell/assets/25571562/54f7ad7a-06a3-4d56-92c2-c3466917bee8)


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

New `--lines(-l)` flag to `debug profile`. The command will also fail if
the profiled closure fails, so technically it is a breaking change.

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

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

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

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

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

---------

Co-authored-by: Ian Manske <ian.manske@pm.me>
2024-05-22 19:56:51 +03:00
7de513a4e0 Implement streaming I/O for CSV and TSV commands (#12918)
# Description

Implements streaming for:

- `from csv`
- `from tsv`
- `to csv`
- `to tsv`

via the new string-typed ByteStream support.

# User-Facing Changes
Commands above. Also:

- `to csv` and `to tsv` now have `--columns <List(String)>`, to provide
the exact columns desired in the output. This is required for them to
have streaming output, because otherwise collecting the entire list is
necessary to determine the output columns. If we introduce
`TableStream`, this may become less necessary.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
- [ ] release notes

---------

Co-authored-by: Ian Manske <ian.manske@pm.me>
2024-05-22 16:55:24 +00:00
758c5d447a Add support for the ps command on FreeBSD, NetBSD, and OpenBSD (#12892)
# Description

I feel like it's a little sad that BSDs get to enjoy almost everything
other than the `ps` command, and there are some tests that rely on this
command, so I figured it would be fun to patch that and make it work.

The different BSDs have diverged from each other somewhat, but generally
have a similar enough API for reading process information via
`sysctl()`, with some slightly different args.

This supports FreeBSD with the `freebsd` module, and NetBSD and OpenBSD
with the `netbsd` module. OpenBSD is a fork of NetBSD and the interface
has some minor differences but many things are the same.

I had wanted to try to support DragonFlyBSD too, but their Rust version
in the latest release is only 1.72.0, which is too old for me to want to
try to compile rustc up to 1.77.2... but I will revisit this whenever
they do update it. Dragonfly is a fork of FreeBSD, so it's likely to be
more or less the same - I just don't want to enable it without testing
it.

Fixes #6862 (partially, we probably won't be adding `zfs list`)

# User-Facing Changes
`ps` added for FreeBSD, NetBSD, and OpenBSD.

# Tests + Formatting
The CI doesn't run tests for BSDs, so I'm not entirely sure if
everything was already passing before. (Frankly, it's unlikely.) But
nothing appears to be broken.

# After Submitting
- [ ] release notes?
- [ ] DragonflyBSD, whenever they do update Rust to something close
enough for me to try it
2024-05-22 08:13:45 -07:00
d7e75c0b70 Bump shadow-rs from 0.27.1 to 0.28.0 (#12932)
Bumps [shadow-rs](https://github.com/baoyachi/shadow-rs) from 0.27.1 to
0.28.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/baoyachi/shadow-rs/releases">shadow-rs's
releases</a>.</em></p>
<blockquote>
<h2>fix cargo clippy</h2>
<p><a
href="https://redirect.github.com/baoyachi/shadow-rs/issues/160">#160</a></p>
<p>Thx <a href="https://github.com/qartik"><code>@​qartik</code></a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ba9f8b0c2b"><code>ba9f8b0</code></a>
Update Cargo.toml</li>
<li><a
href="d1b724c1e7"><code>d1b724c</code></a>
Merge pull request <a
href="https://redirect.github.com/baoyachi/shadow-rs/issues/160">#160</a>
from qartik/patch-1</li>
<li><a
href="505108d5d6"><code>505108d</code></a>
Allow missing_docs for deprecated CLAP_VERSION constant</li>
<li>See full diff in <a
href="https://github.com/baoyachi/shadow-rs/compare/v0.27.1...v0.28.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=shadow-rs&package-manager=cargo&previous-version=0.27.1&new-version=0.28.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-05-22 15:59:33 +08:00
3cf150727c Bump actions/checkout from 4.1.5 to 4.1.6 (#12934)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.5
to 4.1.6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v4.1.6</h2>
<h2>What's Changed</h2>
<ul>
<li>Check platform to set archive extension appropriately by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1732">actions/checkout#1732</a></li>
<li>Update for 4.1.6 release by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1733">actions/checkout#1733</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4.1.5...v4.1.6">https://github.com/actions/checkout/compare/v4.1.5...v4.1.6</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h2>v4.1.6</h2>
<ul>
<li>Check platform to set archive extension appropriately by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1732">actions/checkout#1732</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="a5ac7e51b4"><code>a5ac7e5</code></a>
Update for 4.1.6 release (<a
href="https://redirect.github.com/actions/checkout/issues/1733">#1733</a>)</li>
<li><a
href="24ed1a3528"><code>24ed1a3</code></a>
Check platform for extension (<a
href="https://redirect.github.com/actions/checkout/issues/1732">#1732</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/checkout/compare/v4.1.5...v4.1.6">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.5&new-version=4.1.6)](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-05-22 11:22:02 +08:00
f83439fdda Add completer for std help (#12929)
# Description

While each of the `help <subcommands>` in `std` had completers, there
wasn't one for the main `help` command.

This adds all internals and custom commands (as with `help commands`) as
possible completions.

# User-Facing Changes

`help ` + <kbd>Tab</kbd> will now suggest completions for both the `help
<subcommands>` as well as all internal and custom commands.

# Tests + Formatting

Note: Cannot add tests for completion functions since they are
module-internal and not visible to test cases, that I can see.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-05-21 10:31:14 -05:00
1cdc39bc2a Update mimalloc to 0.1.42 (#12919)
# Description

This update fixes mimalloc for NetBSD

# Tests + Formatting

Tests are passing just fine
2024-05-21 07:47:24 +02:00
db37bead64 Remove unused dependencies (#12917)
- **Remove unused `pathdiff` dep in `nu-cli`**
- **Remove unused `serde_json` dep on `nu-protocol`**
- Unnecessary after moving the plugin file to msgpack (still a
dev-dependency)
2024-05-21 01:09:28 +00:00
6e050f5634 explore: consolidate padding config, handle ByteStream, tweak naming+comments (#12915)
Some minor changes to `explore`, continuing on my mission to simplify
the command in preparation for a larger UX overhaul:

1. Consolidate padding configuration. I don't think we need separate
config points for the (optional) index column and regular data columns
in the normal pager, they can share padding configuration. Likewise, in
the binary viewer all 3 columns (index, data, ASCII) had their
left+right padding configured independently.
2. Update `explore` so we use the binary viewer for the new `ByteStream`
type. `cat foo.txt | into binary | explore` was not using the binary
viewer after the `ByteStream` changes.
3. Tweak the naming of a few helper functions, add a comment

I've put the changes in separate commits to make them easier to review.

---------

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2024-05-20 22:03:21 +02:00
905e3d0715 Remove dataframes crate and feature (#12889)
# Description
Removes the old `nu-cmd-dataframe` crate in favor of the polars plugin.
As such, this PR also removes the `dataframe` feature, related CI, and
full releases of nushell.
2024-05-20 17:22:08 +00:00
4f69ba172e add math min and math max to bench command (#12913)
# Description

This PR adds min and max to the bench command.
```nushell
❯ use std bench
❯ bench { dply -c 'parquet("./data.parquet") | group_by(year) | summarize(count = n(), sum = sum(geo_count)) | show()' | complete | null } --rounds 100 --verbose
100 / 100
╭───────┬───────────────────╮
│ mean  │ 71ms 358µs 850ns  │
│ min   │ 66ms 457µs 583ns  │
│ max   │ 120ms 338µs 167ns │
│ std   │ 6ms 553µs 949ns   │
│ times │ [list 100 items]  │
╰───────┴───────────────────╯
```

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

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

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

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

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2024-05-20 10:08:03 -05:00
c98960d053 Take owned Read and Write (#12909)
# Description
As @YizhePKU pointed out, the [Rust API
guidelines](https://rust-lang.github.io/api-guidelines/interoperability.html#generic-readerwriter-functions-take-r-read-and-w-write-by-value-c-rw-value)
recommend that generic functions take readers and writers by value and
not by reference. This PR changes `copy_with_interupt` and few other
places to take owned `Read` and `Write` instead of mutable references.
2024-05-20 15:10:36 +02:00
c61075e20e Add string/binary type color to ByteStream (#12897)
# Description

This PR allows byte streams to optionally be colored as being
specifically binary or string data, which guarantees that they'll be
converted to `Binary` or `String` appropriately on `into_value()`,
making them compatible with `Type` guarantees. This makes them
significantly more broadly usable for command input and output.

There is still an `Unknown` type for byte streams coming from external
commands, which uses the same behavior as we previously did where it's a
string if it's UTF-8.

A small number of commands were updated to take advantage of this, just
to prove the point. I will be adding more after this merges.

# User-Facing Changes
- New types in `describe`: `string (stream)`, `binary (stream)`
- These commands now return a stream if their input was a stream:
  - `into binary`
  - `into string`
  - `bytes collect`
  - `str join`
  - `first` (binary)
  - `last` (binary)
  - `take` (binary)
  - `skip` (binary)
- Streams that are explicitly binary colored will print as a streaming
hexdump
  - example:
    ```nushell
    1.. | each { into binary } | bytes collect
    ```

# Tests + Formatting
I've added some tests to cover it at a basic level, and it doesn't break
anything existing, but I do think more would be nice. Some of those will
come when I modify more commands to stream.

# After Submitting
There are a few things I'm not quite satisfied with:

- **String trimming behavior.** We automatically trim newlines from
streams from external commands, but I don't think we should do this with
internal commands. If I call a command that happens to turn my string
into a stream, I don't want the newline to suddenly disappear. I changed
this to specifically do it only on `Child` and `File`, but I don't know
if this is quite right, and maybe we should bring back the old flag for
`trim_end_newline`
- **Known binary always resulting in a hexdump.** It would be nice to
have a `print --raw`, so that we can put binary data on stdout
explicitly if we want to. This PR doesn't change how external commands
work though - they still dump straight to stdout.

Otherwise, here's the normal checklist:

- [ ] release notes
- [ ] docs update for plugin protocol changes (added `type` field)

---------

Co-authored-by: Ian Manske <ian.manske@pm.me>
2024-05-20 00:35:32 +00:00
baeba19b22 Make get_full_help take &dyn Command (#12903)
# Description
Changes `get_full_help` to take a `&dyn Command` instead of multiple
arguments (`&Signature`, `&Examples` `is_parser_keyword`). All of these
arguments can be gathered from a `Command`, so there is no need to pass
the pieces to `get_full_help`.

This PR also fixes an issue where the search terms are not shown if
`--help` is used on a command.
2024-05-19 19:56:33 +02:00
474293bf1c Clear environment for child Commands (#12901)
# Description
There is a bug when `hide-env` is used on environment variables that
were present at shell startup. Namely, child processes still inherit the
hidden environment variable. This PR fixes #12900, fixes #11495, and
fixes #7937.

# Tests + Formatting
Added a test.
2024-05-19 15:35:07 +00:00
cc9f41e553 Use CommandType in more places (#12832)
# Description
Kind of a vague title, but this PR does two main things:
1. Rather than overriding functions like `Command::is_parser_keyword`,
this PR instead changes commands to override `Command::command_type`.
The `CommandType` returned by `Command::command_type` is then used to
automatically determine whether `Command::is_parser_keyword` and the
other `is_{type}` functions should return true. These changes allow us
to remove the `CommandType::Other` case and should also guarantee than
only one of the `is_{type}` functions on `Command` will return true.
2. Uses the new, reworked `Command::command_type` function in the `scope
commands` and `which` commands.


# User-Facing Changes
- Breaking change for `scope commands`: multiple columns (`is_builtin`,
`is_keyword`, `is_plugin`, etc.) have been merged into the `type`
column.
- Breaking change: the `which` command can now report `plugin` or
`keyword` instead of `built-in` in the `type` column. It may also now
report `external` instead of `custom` in the `type` column for known
`extern`s.
2024-05-18 23:37:31 +00:00
580c60bb82 Preserve metadata in more places (#12848)
# Description
This PR makes some commands and areas of code preserve pipeline
metadata. This is in an attempt to make the issue described in #12599
and #9456 less likely to occur. That is, reading and writing to the same
file in a pipeline will result in an empty file. Since we preserve
metadata in more places now, there will be a higher chance that we
successfully detect this error case and abort the pipeline.
2024-05-17 17:59:32 +00:00
c10aa2cf09 collect: don't require a closure (#12788)
# Description

This changes the `collect` command so that it doesn't require a closure.
Still allowed, optionally.

Before:

```nushell
open foo.json | insert foo bar | collect { save -f foo.json }
```

After:

```nushell
open foo.json | insert foo bar | collect | save -f foo.json
```

The closure argument isn't really necessary, as collect values are also
supported as `PipelineData`.

# User-Facing Changes
- `collect` command changed

# Tests + Formatting
Example changed to reflect.

# After Submitting
- [ ] release notes
- [ ] we may want to deprecate the closure arg?
2024-05-17 18:46:03 +02:00
e3db6ea04a Exclude polars from ensure_plugins_built(), for performance reasons (#12896)
# Description

We have been building `nu_plugin_polars` unnecessarily during `cargo
test`, which is very slow. All of its tests are run within its own
crate, which happens during the plugins CI phase.

This should speed up the CI a bit.
2024-05-17 15:04:59 +00:00
59f7c523fa Fix the way the output of table is printed in print() (#12895)
# Description

Forgot that I fixed this already on my branch, but when printing without
a display output hook, the implicit call to `table` gets its output
mangled with newlines (since #12774). This happens when running `nu -c`
or a script file.

Here's that fix in one PR so it can be merged easily.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-05-17 07:18:18 -07:00
8adf3406e5 allow define it as a variable inside closure (#12888)
# Description
Fixes: #12690 

The issue is happened after
https://github.com/nushell/nushell/pull/12056 is merged. It will raise
error if user doesn't supply required parameter when run closure with
do.
And parser adds a `$it` parameter when parsing closure or block
expression.

I believe the previous behavior is because we allow such syntax on
previous version(0.44):
```nushell
let x = { print $it }
```
But it's no longer allowed after 0.60.  So I think they can be removed.

# User-Facing Changes
```nushell
let tmp = {
  let it = 42
  print $it
}

do -c $tmp
```
should be possible again.

# Tests + Formatting
Added 1 test
2024-05-17 00:03:13 +00:00
6891267b53 Support ByteStreams in bytes starts-with and bytes ends-with (#12887)
# Description
Restores `bytes starts-with` so that it is able to work with byte
streams once again. For parity/consistency, this PR also adds byte
stream support to `bytes ends-with`.

# User-Facing Changes
- `bytes ends-with` now supports byte streams.

# Tests + Formatting
Re-enabled tests for `bytes starts-with` and added tests for `bytes
ends-with`.
2024-05-17 07:59:08 +08:00
aec41f3df0 Add Span merging functions (#12511)
# Description
This PR adds a few functions to `Span` for merging spans together:
- `Span::append`: merges two spans that are known to be in order.
- `Span::concat`: returns a span that encompasses all the spans in a
slice. The spans must be in order.
- `Span::merge`: merges two spans (no order necessary).
- `Span::merge_many`: merges an iterator of spans into a single span (no
order necessary).

These are meant to replace the free-standing `nu_protocol::span`
function.

The spans in a `LiteCommand` (the `parts`) should always be in order
based on the lite parser and lexer. So, the parser code sees the most
usage of `Span::append` and `Span::concat` where the order is known. In
other code areas, `Span::merge` and `Span::merge_many` are used since
the order between spans is often not known.
2024-05-16 22:34:49 +00:00
2a09dccc11 Bytestream touchup (#12886)
# Description
Adds some docs and a small fix to `Chunks`.
2024-05-16 21:15:20 +00:00
1c00a6ca5e sync up with reedline changes (#12881)
# Description

sync-up nushell to reedline's latest minor changes. Not quite sure why
itertools downgraded to 0.11.0 when nushell and reedline have it set to
0.12.0.
2024-05-16 22:26:03 +02:00
6fd854ed9f Replace ExternalStream with new ByteStream type (#12774)
# Description
This PR introduces a `ByteStream` type which is a `Read`-able stream of
bytes. Internally, it has an enum over three different byte stream
sources:
```rust
pub enum ByteStreamSource {
    Read(Box<dyn Read + Send + 'static>),
    File(File),
    Child(ChildProcess),
}
```

This is in comparison to the current `RawStream` type, which is an
`Iterator<Item = Vec<u8>>` and has to allocate for each read chunk.

Currently, `PipelineData::ExternalStream` serves a weird dual role where
it is either external command output or a wrapper around `RawStream`.
`ByteStream` makes this distinction more clear (via `ByteStreamSource`)
and replaces `PipelineData::ExternalStream` in this PR:
```rust
pub enum PipelineData {
    Empty,
    Value(Value, Option<PipelineMetadata>),
    ListStream(ListStream, Option<PipelineMetadata>),
    ByteStream(ByteStream, Option<PipelineMetadata>),
}
```

The PR is relatively large, but a decent amount of it is just repetitive
changes.

This PR fixes #7017, fixes #10763, and fixes #12369.

This PR also improves performance when piping external commands. Nushell
should, in most cases, have competitive pipeline throughput compared to,
e.g., bash.
| Command | Before (MB/s) | After (MB/s) | Bash (MB/s) |
| -------------------------------------------------- | -------------:|
------------:| -----------:|
| `throughput \| rg 'x'` | 3059 | 3744 | 3739 |
| `throughput \| nu --testbin relay o> /dev/null` | 3508 | 8087 | 8136 |

# User-Facing Changes
- This is a breaking change for the plugin communication protocol,
because the `ExternalStreamInfo` was replaced with `ByteStreamInfo`.
Plugins now only have to deal with a single input stream, as opposed to
the previous three streams: stdout, stderr, and exit code.
- The output of `describe` has been changed for external/byte streams.
- Temporary breaking change: `bytes starts-with` no longer works with
byte streams. This is to keep the PR smaller, and `bytes ends-with`
already does not work on byte streams.
- If a process core dumped, then instead of having a `Value::Error` in
the `exit_code` column of the output returned from `complete`, it now is
a `Value::Int` with the negation of the signal number.

# After Submitting
- Update docs and book as necessary
- Release notes (e.g., plugin protocol changes)
- Adapt/convert commands to work with byte streams (high priority is
`str length`, `bytes starts-with`, and maybe `bytes ends-with`).
- Refactor the `tee` code, Devyn has already done some work on this.

---------

Co-authored-by: Devyn Cairns <devyn.cairns@gmail.com>
2024-05-16 07:11:18 -07:00
1b8eb23785 allow passing float value to custom command (#12879)
# Description
Fixes: #12691 

In `parse_short_flag`, it only checks special cases for
`SyntaxShape::Int`, `SyntaxShape::Number` to allow a flag to be a
number. This pr adds `SyntaxShape::Float` to allow a flag to be float
number.

# User-Facing Changes
This is possible after this pr:
```nushell
def spam [val: float] { $val }; 
spam -1.4
```

# Tests + Formatting
Added 1 test
2024-05-16 10:50:29 +02:00
e20113a0eb Remove stack debug assert (#12861)
# Description
In order for `Stack::unwrap_unique` to work as intended, we currently
manually track all references to the parent stack and ensure that they
are cleared before calling `Stack::unwrap_unique` in the REPL. We also
only call `Stack::unwrap_unique` after all code from the current REPL
entry has finished executing. Since `Value`s cannot store `Stack`
references, then this should have worked in theory. However, we forgot
to account for threads. `run-external` (and maybe the plugin writers)
can spawn threads that clone the `Stack`, holding on to references of
the parent stack. These threads are not waited/joined upon, and so may
finish after the eval has already returned. This PR removes the
`Stack::unwrap_unique` function and associated debug assert that was
[causing
panics](https://gist.github.com/cablehead/f3d2608a1629e607c2d75290829354f7)
like @cablehead found.

# After Submitting
Make values cheaper to clone as a more robust solution to the
performance issues with cloning the stack.

---------

Co-authored-by: Wind <WindSoilder@outlook.com>
2024-05-15 22:59:10 +00:00
6f3dbc97bb fixed syntax shape requirements for --quantiles option for polars summary (#12878)
Fix for #12730

All of the code expected a list of floats, but the syntax shape expected
a table. Resolved by changing the syntax shape to list of floats.

cc: @maxim-uvarov
2024-05-15 16:55:07 -05:00
06fe7d1e16 Remove usages of Call::positional_nth (#12871)
# Description
Following from #12867, this PR replaces usages of `Call::positional_nth`
with existing spans. This removes several `expect`s from the code.

Also remove unused `positional_nth_mut` and `positional_iter_mut`
2024-05-15 19:59:42 +02:00
b08135d877 Fixed small error in the help-examples for the get command (#12877)
# Description

Another small error in Help, this time for the `get` command example.

# User-Facing Changes

Help only
2024-05-15 19:49:08 +02:00
72b880662b Fixed a nitpick usage-help error - closure v. block (#12876)
# Description

So minor, but had to be fixed sometime. `help each while` used the term
"block" in the "usage", but the argument type is a closure.

# User-Facing Changes

help-only
2024-05-15 18:16:59 +02:00
defed3001d make it clearer what is being loaded with --log-level info (#12875)
# Description

A common question we get is what config files are loaded when and with
what parameters. It's for this reason that I wrote [this
gist](https://gist.github.com/fdncred/b87b784f04984dc31a150baed9ad2447).
Another way to figure this out is to use `nu --log-level info`. This
will show some performance timings but will also show what is being
loaded when. For the most part the `[INFO]` lines show the performance
timings and the `[WARN]` lines show the files.

This PR tries to make things a little bit clearer when using the
`--log-level info` parameter.

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

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

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

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

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2024-05-15 09:44:09 -05:00
0cfbdc909e Fix sys panic (#12846)
# Description
This should fix #10155 where the `sys` command can panic due to date
math in certain cases / on certain systems.

# User-Facing Changes
The `boot_time` column now has a date value instead of a formatted date
string. This is technically a breaking change.
2024-05-15 15:40:04 +08:00
a7807735b1 Add a passing test for interactivity on slow pipelines (#12865)
# Description

This PR adds a single test to assert interactivity on slow pipelines

Currently the timeout is set to 6 seconds, as the test can sometimes
take ~3secs to run on my local m1 mac air, which I don't think is an
indication of a slow pipeline, but rather slow test start up time...
2024-05-15 01:48:27 +00:00
155934f783 make better messages for incomplete string (#12868)
# Description
Fixes: #12795

The issue is caused by an empty position of `ParseError::UnexpectedEof`.
So no detailed message is displayed.
To fix the issue, I adjust the start of span to `span.end - 1`. In this
way, we can make sure that it never points to an empty position.

After lexing item, I also reorder the unclosed character checking . Now
it will be checking unclosed opening delimiters first.

# User-Facing Changes
After this pr, it outputs detailed error message for incomplete string
when running scripts.

## Before
```
❯ nu -c "'ab"
Error: nu::parser::unexpected_eof

  × Unexpected end of code.
   ╭─[source:1:4]
 1 │ 'ab
   ╰────
> ./target/debug/nu -c "r#'ab"
Error: nu::parser::unexpected_eof

  × Unexpected end of code.
   ╭─[source:1:6]
 1 │ r#'ab
   ╰────
```
## After
```
> nu -c "'ab"
Error: nu::parser::unexpected_eof

  × Unexpected end of code.
   ╭─[source:1:3]
 1 │ 'ab
   ·   ┬
   ·   ╰── expected closing '
   ╰────
> ./target/debug/nu -c "r#'ab"
Error: nu::parser::unexpected_eof

  × Unexpected end of code.
   ╭─[source:1:5]
 1 │ r#'ab
   ·     ┬
   ·     ╰── expected closing '#
   ╰────
```


# Tests + Formatting
Added some tests for incomplete string.

---------

Co-authored-by: Ian Manske <ian.manske@pm.me>
2024-05-15 01:14:11 +00:00
9bf4d3ece6 Bump rust-embed from 8.3.0 to 8.4.0 (#12870)
Bumps [rust-embed](https://github.com/pyros2097/rust-embed) from 8.3.0
to 8.4.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pyrossh/rust-embed/blob/master/changelog.md">rust-embed's
changelog</a>.</em></p>
<blockquote>
<h2>[8.4.0] - 2024-05-11</h2>
<ul>
<li>Re-export RustEmbed as Embed <a
href="https://redirect.github.com/pyrossh/rust-embed/pull/245/files">#245</a>.
Thanks to <a href="https://github.com/pyrossh">pyrossh</a></li>
<li>Do not build glob matchers repeatedly when include-exclude feature
is enabled <a
href="https://redirect.github.com/pyrossh/rust-embed/pull/244/files">#244</a>.
Thanks to <a href="https://github.com/osiewicz">osiewicz</a></li>
<li>Add <code>metadata_only</code> attribute <a
href="https://redirect.github.com/pyrossh/rust-embed/pull/241/files">#241</a>.
Thanks to <a href="https://github.com/ddfisher">ddfisher</a></li>
<li>Replace <code>expect</code> with a safer alternative that returns
<code>None</code> instead <a
href="https://redirect.github.com/pyrossh/rust-embed/pull/240/files">#240</a>.
Thanks to <a href="https://github.com/costinsin">costinsin</a></li>
<li>Eliminate unnecessary <code>to_path</code> call <a
href="https://redirect.github.com/pyrossh/rust-embed/pull/239/files">#239</a>.
Thanks to <a href="https://github.com/smoelius">smoelius</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/pyros2097/rust-embed/commits">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rust-embed&package-manager=cargo&previous-version=8.3.0&new-version=8.4.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-05-15 09:06:09 +08:00
cb64c78a3b Bump interprocess from 2.0.1 to 2.1.0 (#12869)
Bumps [interprocess](https://github.com/kotauskas/interprocess) from
2.0.1 to 2.1.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/kotauskas/interprocess/releases">interprocess's
releases</a>.</em></p>
<blockquote>
<h2>2.1.0 – listeners are now iterators</h2>
<ul>
<li>Fixes <a
href="https://redirect.github.com/kotauskas/interprocess/issues/49">#49</a></li>
<li>Adds <code>Iterator</code> impl on local socket listeners (closes <a
href="https://redirect.github.com/kotauskas/interprocess/issues/64">#64</a>)</li>
<li>Miscellaneous documentation fixes</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="b79d363615"><code>b79d363</code></a>
Thank you Windows, very cool</li>
<li><a
href="2ab1418db7"><code>2ab1418</code></a>
Move a bunch of goalposts</li>
<li><a
href="d26ed39bd2"><code>d26ed39</code></a>
Use macro in Windows <code>UnnamedPipe</code> builder</li>
<li><a
href="f9528885e4"><code>f952888</code></a>
I'm not adding a <code>build.rs</code> to silence a warning</li>
<li><a
href="5233ffb7c9"><code>5233ffb</code></a>
Fix <a
href="https://redirect.github.com/kotauskas/interprocess/issues/49">#49</a>
and add test</li>
<li><a
href="85d3e1861a"><code>85d3e18</code></a>
Complete half-done move to <code>os</code> module in tests</li>
<li><a
href="7715dbdd49"><code>7715dbd</code></a>
Listeners are now iterators</li>
<li><a
href="8a47261ddc"><code>8a47261</code></a>
Bump version</li>
<li>See full diff in <a
href="https://github.com/kotauskas/interprocess/compare/2.0.1...2.1.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=interprocess&package-manager=cargo&previous-version=2.0.1&new-version=2.1.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-05-15 09:05:55 +08:00
c3da44cbb7 Fix char panic (#12867)
# Description
The `char` command can panic due to a failed `expect`: `char --integer
...[77 78 79]`

This PR fixes the panic for the `--integer` flag and also the
`--unicode` flag.

# After Submitting
Check other commands and places where similar bugs can occur due to
usages of `Call::positional_nth` and related methods.
2024-05-14 21:10:06 +00:00
aa46bc97b3 Search terms for compact command (#12864)
# Description

There was a question in Discord today about how to remove empty rows
from a table. The user found the `compact` command on their own, but I
realized that there were no search terms on the command. I've added
'empty' and 'remove', although I subsequently figured out that 'empty'
is found in the "usage" anyway. That said, I don't think it hurts to
have good search terms behind it regardless.

# User-Facing Changes

Just the help

# Tests + Formatting

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

# After Submitting
2024-05-14 09:21:50 -05:00
2ed77aef1d Fix panic when exploring empty dictionary (#12860)
- fixes #12841 

# Description
Add boundary checks to ensure that the row and column chosen in
RecordView are not over the length of the possible row and columns. If
we are out of bounds, we default to Value::nothing.

# Tests + Formatting
Tests ran and formatting done
2024-05-14 14:13:49 +00:00
cd381b74e0 Fix improperly escaped strings in stor insert (#12820)
- fixes #12764 

Replaced the custom logic with values_to_sql method that is already used
in crate::database.
This will ensure that handling of parameters is the same between sqlite
and stor.
2024-05-13 20:22:39 -05:00
98369985b1 Allow custom value operations to work on eager and lazy dataframes interchangeably. (#12819)
Fixes Bug #12809 

The example that @maxim-uvarov posted now works as expected:

<img width="1223" alt="Screenshot 2024-05-09 at 16 21 01"
src="https://github.com/nushell/nushell/assets/56345/a4df62e3-e432-4c09-8e25-9a6c198741a3">
2024-05-13 18:17:31 -05:00
aaf973bbba Add Stack::stdout_file and Stack::stderr_file to capture stdout/-err of external commands (#12857)
# Description
In this PR I added two new methods to `Stack`, `stdout_file` and
`stderr_file`. These two modify the inner `StackOutDest` and set a
`File` into the `stdout` and `stderr` respectively. Different to the
`push_redirection` methods, these do not require to hold a guard up all
the time but require ownership of the stack.

This is primarly useful for applications that use `nu` as a language but
not the `nushell`.

This PR replaces my first attempt #12851 to add a way to capture
stdout/-err of external commands. Capturing the stdout without having to
write into a file is possible with crates like
[`os_pipe`](https://docs.rs/os_pipe), an example for this is given in
the doc comment of the `stdout_file` command and can be executed as a
doctest (although it doesn't validate that you actually got any data).

This implementation takes `File` as input to make it easier to implement
on different operating systems without having to worry about
`OwnedHandle` or `OwnedFd`. Also this doesn't expose any use `os_pipe`
to not leak its types into this API, making it depend on it.

As in my previous attempt, @IanManske guided me here.

# User-Facing Changes
This change has no effect on `nushell` and therefore no user-facing
changes.

# Tests + Formatting
This only exposes a new way of using already existing code and has
therefore no further testing. The doctest succeeds on my machine at
least (x86 Windows, 64 Bit).

# After Submitting
All the required documentation is already part of this PR.
2024-05-13 18:48:38 +00:00
905ec88091 Update PR template (#12838)
# Description
Updates the command listed in the PR template to test the standard
library, following from #11151.
2024-05-13 08:45:44 -05:00
c4dca5fe03 Merged tests to produce a single binary (#12826)
This PR should close #7147 

# Description
Merged src/tests into /tests to produce a single binary.

![image](https://github.com/nushell/nushell/assets/94604837/84726469-d447-4619-b6d1-2d1415d0f42e)

# User-Facing Changes
No user facing changes

# Tests + Formatting
Moved tests. Tollkit check pr pass.

# After Submitting

---------

Co-authored-by: Ian Manske <ian.manske@pm.me>
2024-05-13 13:37:53 +00:00
c70c43aae9 Add example and search term for 'repeat' to the fill command (#12844)
# Description

It's commonly forgotten or overlooked that a lot of `std repeat`
functionality can be handled with the built-in `fill`. Added 'repeat` as
a search term for `fill` to improve discoverability.

Also replaced one of the existing examples with one `fill`ing an empty
string, a la `repeat`. There were 6 examples already, and 3 of them
pretty much were variations on the same theme, so I repurposed one of
those rather than adding a 7th.

# User-Facing Changes

Changes to `help` only

# Tests + Formatting

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

# After Submitting

I assume the "Commands" doc is auto-generated from the `help`, but I'll
double-check that assumption.
2024-05-12 20:55:07 -05:00
30fc832035 Fix custom converters with save (#12833)
# Description
Fixes #10429 where `save` fails if a custom command is used as the file
format converter.

# Tests + Formatting
Added a test.
2024-05-12 13:19:28 +02:00
075535f869 remove --not flag for 'str contains' (#12837)
# Description
This PR resolves an inconsistency between different `str` subcommands,
notably `str contains`, `str starts-with` and `str ends-with`. Only the
`str contains` command has the `--not` flag and a desicion was made in
this #12781 PR to remove the `--not` flag and use the `not` operator
instead.

Before:
`"blob" | str contains --not o`
After:
`not ("blob" | str contains o)` OR `"blob" | str contains o | not $in`

> Note, you can currently do all three, but the first will be broken
after this PR is merged.

# User-Facing Changes
- remove `--not(-n)` flag from `str contains` command
  - This is a breaking change!

# Tests + Formatting
- [x] Added tests
- [x] Ran `cargo fmt --all`
- [x] Ran `cargo clippy --workspace -- -D warnings -D
clippy::unwrap_used`
- [x] Ran `cargo test --workspace`
- [ ] Ran `cargo run -- -c "use std testing; testing run-tests --path
crates/nu-std"`
    - I was unable to get this working.
```
Error: nu::parser::export_not_found

  × Export not found.
   ╭─[source:1:9]
 1 │ use std testing; testing run-tests --path crates/nu-std
   ·         ───┬───
   ·            ╰── could not find imports
   ╰────
```
^ I still can't figure out how to make this work 😂 

# After Submitting
Requires update of documentation
2024-05-11 23:13:36 +00:00
cab86f49c0 Fix pipe redirection into complete (#12818)
# Description
Fixes #12796 where a combined out and err pipe redirection (`o+e>|`)
into `complete` still provides separate `stdout` and `stderr` columns in
the record. Now, the combined output will be in the `stdout` column.
This PR also fixes a similar error with the `e>|` pipe redirection.

# Tests + Formatting
Added two tests.
2024-05-11 15:32:00 +00:00
b9a7faad5a Implement PWD recovery (#12779)
This PR has two parts. The first part is the addition of the
`Stack::set_pwd()` API. It strips trailing slashes from paths for
convenience, but will reject otherwise bad paths, leaving PWD in a good
state. This should reduce the impact of faulty code incorrectly trying
to set PWD.
(https://github.com/nushell/nushell/pull/12760#issuecomment-2095393012)

The second part is implementing a PWD recovery mechanism. PWD can become
bad even when we did nothing wrong. For example, Unix allows you to
remove any directory when another process might still be using it, which
means PWD can just "disappear" under our nose. This PR makes it possible
to use `cd` to reset PWD into a good state. Here's a demonstration:

```sh
mkdir /tmp/foo
cd /tmp/foo

# delete "/tmp/foo" in a subshell, because Nushell is smart and refuse to delete PWD
nu -c 'cd /; rm -r /tmp/foo'

ls          # Error:   × $env.PWD points to a non-existent directory
            # help: Use `cd` to reset $env.PWD into a good state

cd /
pwd         # prints /
```

Also, auto-cd should be working again.
2024-05-10 11:06:33 -05:00
70c01bbb26 Fix raw strings as external argument (#12817)
# Description
As discovered by @YizhePKU in a
[comment](https://github.com/nushell/nushell/pull/9956#issuecomment-2103123797)
in #9956, raw strings are not parsed properly when they are used as an
argument to an external command. This PR fixes that.

# Tests + Formatting
Added a test.
2024-05-10 07:50:31 +08:00
72d3860d05 Refactor the CLI code a bit (#12782)
# Description
Refactors the code in `nu-cli`, `main.rs`, `run.rs`, and few others.
Namely, I added `EngineState::generate_nu_constant` function to
eliminate some duplicate code. Otherwise, I changed a bunch of areas to
return errors instead of calling `std::process::exit`.

# User-Facing Changes
Should be none.
2024-05-10 07:29:27 +08:00
1b2e680059 Fix syntax highlighting for not (#12815)
# Description
Fixes #12813 where a panic occurs when syntax highlighting `not`. Also
fixes #12814 where syntax highlighting for `not` no longer works.

# User-Facing Changes
Bug fix.
2024-05-10 07:09:44 +08:00
7271ad7909 Pass Stack ref to Completer::fetch (#12783)
# Description
Adds an additional `&Stack` parameter to `Completer::fetch` so that the
completers don't have to store a `Stack` themselves. I also removed
unnecessary `EngineState`s from the completers, since the same
`EngineState` is available in the `working_set.permanent_state` also
passed to `Completer::fetch`.
2024-05-09 13:38:24 +08:00
3b3f48202c Refactor message printing in rm (#12799)
# Description
Changes the iterator in `rm` to be an iterator over
`Result<Option<String>, ShellError>` (an optional message or error)
instead of an iterator over `Value`. Then, the iterator is consumed and
each message is printed. This allows the
`PipelineData::print_not_formatted` method to be removed.
2024-05-09 13:36:47 +08:00
948b299e65 Fix/simplify cwd in benchmarks (#12812)
# Description
The benchmarks currently panic when trying to set the initial CWD. This
is because the code that sets the CWD also tries to get the CWD.
2024-05-08 19:16:57 -07:00
ba6f38510c Shrink Value by boxing Range/Closure (#12784)
# Description
On 64-bit platforms the current size of `Value` is 56 bytes. The
limiting variants were `Closure` and `Range`. Boxing the two reduces the
size of Value to 48 bytes. This is the minimal size possible with our
current 16-byte `Span` and any 24-byte `Vec` container which we use in
several variants. (Note the extra full 8-bytes necessary for the
discriminant or other smaller values due to the 8-byte alignment of
`usize`)

This is leads to a size reduction of ~15% for `Value` and should overall
be beneficial as both `Range` and `Closure` are rarely used compared to
the primitive types or even our general container types.

# User-Facing Changes
Less memory used, potential runtime benefits.

(Too late in the evening to run the benchmarks myself right now)
2024-05-09 08:10:58 +08:00
92831d7efc feat: add an echo command to nu_plugin_example (#12754)
# Description

This PR adds a new `echo` command to the `nu_plugin_example` plugin that
simply [streams all of its input to its
output](https://github.com/nushell/nushell/pull/12754/files#diff-de9fcf086b8c373039dadcc2bcb664c6014c0b2af8568eab68c0b6666ac5ccceR47).

```
: "hi" | example echo
hi
```

The motivation for adding it is to have a convenient command to exercise
interactivity on slow pipelines.

I'll follow up on that front with [another
PR](https://github.com/cablehead/nushell/pull/1/files)

# Tests + Formatting

https://github.com/nushell/nushell/pull/12754/files#diff-de9fcf086b8c373039dadcc2bcb664c6014c0b2af8568eab68c0b6666ac5ccceR51-R55
2024-05-08 12:45:44 -07:00
5466da3b52 cleanup osc calls for shell_integration (#12810)
# Description

This PR is a continuation of #12629 and meant to address [Reilly's
stated
issue](https://github.com/nushell/nushell/pull/12629#issuecomment-2099660609).

With this PR, nushell should work more consistently with WezTerm on
Windows. However, that means continued scrolling with typing if osc133
is enabled. If it's possible to run WezTerm inside of vscode, then
having osc633 enabled will also cause the display to scroll with every
character typed. I think the cause of this is that reedline paints the
entire prompt on each character typed. We need to figure out how to fix
that, but that's in reedline.

For my purposes, I keep osc133 and osc633 set to true and don't use
WezTerm on Windows.

Thanks @rgwood for reporting the issue. I found several logic errors.
It's often good to come back to PRs and look at them with fresh eyes. I
think this is pretty close to logically correct now. However, I'm
approaching burn out on ansi escape codes so i could've missed
something.

Kudos to [escape-artist](https://github.com/rgwood/escape-artist) for
helping me debug an ansi escape codes that are actually being sent to
the terminal. It was an invaluable tool.

# 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-05-08 13:34:04 -05:00
3b26c08dab Refactor parse command (#12791)
# Description
- Switches the `excess` in the `ParserStream` and
`ParseStreamerExternal` types from a `Vec` to a `VecDeque`
- Removes unnecessary clones to `stream_helper`
- Other simplifications and loop restructuring
- Merges the `ParseStreamer` and `ParseStreamerExternal` types into a
common `ParseIter`
- `parse` now streams for list values
2024-05-08 06:50:58 -05:00
e462b6cd99 Make the message when running a plugin exe directly clearer (#12806)
# Description

This changes the message that shows up when running a plugin executable
directly rather than as a plugin to direct the user to run `plugin add
--help`, which should have enough information to figure out what's going
on. The message previously just vaguely suggested that the user needs to
run the plugin "from within Nushell", which is not really enough - it
has to be added with `plugin add` to be used as a plugin.

Also fix docs for `plugin add` to mention `plugin use` rather than
`register` (oops)
2024-05-07 20:12:32 -07:00
f851b61cb7 Bump softprops/action-gh-release from 2.0.4 to 2.0.5 (#12803)
Bumps
[softprops/action-gh-release](https://github.com/softprops/action-gh-release)
from 2.0.4 to 2.0.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/softprops/action-gh-release/releases">softprops/action-gh-release's
releases</a>.</em></p>
<blockquote>
<h2>v2.0.5</h2>
<ul>
<li>Factor in file names with spaces when upserting files <a
href="https://redirect.github.com/softprops/action-gh-release/pull/446">#446</a>
via <a
href="https://github.com/MystiPanda"><code>@​MystiPanda</code></a></li>
<li>Improvements to error handling <a
href="https://redirect.github.com/softprops/action-gh-release/pull/449">#449</a>
via <a href="https://github.com/till"><code>@​till</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md">softprops/action-gh-release's
changelog</a>.</em></p>
<blockquote>
<h2>2.0.5</h2>
<ul>
<li>Factor in file names with spaces when upserting files <a
href="https://redirect.github.com/softprops/action-gh-release/pull/446">#446</a>
via <a
href="https://github.com/MystiPanda"><code>@​MystiPanda</code></a></li>
<li>Improvements to error handling <a
href="https://redirect.github.com/softprops/action-gh-release/pull/449">#449</a>
via <a href="https://github.com/till"><code>@​till</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="69320dbe05"><code>69320db</code></a>
update changelog</li>
<li><a
href="9771ccf55f"><code>9771ccf</code></a>
update changelog rebuild dist</li>
<li><a
href="0a76e4214a"><code>0a76e42</code></a>
Fix: error handling (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/449">#449</a>)</li>
<li><a
href="3989e4b325"><code>3989e4b</code></a>
document impl detail</li>
<li><a
href="72e945e627"><code>72e945e</code></a>
update changelog</li>
<li><a
href="40bf9ec7aa"><code>40bf9ec</code></a>
fmt and build</li>
<li><a
href="998623f0c3"><code>998623f</code></a>
fix: support space in file name (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/446">#446</a>)</li>
<li><a
href="0979303f02"><code>0979303</code></a>
Fix failure (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/447">#447</a>)</li>
<li><a
href="9b795e5782"><code>9b795e5</code></a>
Update README.md (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/432">#432</a>)</li>
<li>See full diff in <a
href="https://github.com/softprops/action-gh-release/compare/v2.0.4...v2.0.5">compare
view</a></li>
</ul>
</details>
<br />

<details>
<summary>Most Recent Ignore Conditions Applied to This Pull
Request</summary>

| Dependency Name | Ignore Conditions |
| --- | --- |
| softprops/action-gh-release | [< 0.2, > 0.1.13] |
</details>


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=softprops/action-gh-release&package-manager=github_actions&previous-version=2.0.4&new-version=2.0.5)](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-05-08 10:35:18 +08:00
cad22bb833 Bump actions/checkout from 4.1.4 to 4.1.5 (#12804)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.4
to 4.1.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v4.1.5</h2>
<h2>What's Changed</h2>
<ul>
<li>Update NPM dependencies by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1703">actions/checkout#1703</a></li>
<li>Bump github/codeql-action from 2 to 3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1694">actions/checkout#1694</a></li>
<li>Bump actions/setup-node from 1 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1696">actions/checkout#1696</a></li>
<li>Bump actions/upload-artifact from 2 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1695">actions/checkout#1695</a></li>
<li>README: Suggest <code>user.email</code> to be
<code>41898282+github-actions[bot]@users.noreply.github.com</code> by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1707">actions/checkout#1707</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4.1.4...v4.1.5">https://github.com/actions/checkout/compare/v4.1.4...v4.1.5</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="44c2b7a8a4"><code>44c2b7a</code></a>
README: Suggest <code>user.email</code> to be
`41898282+github-actions[bot]<a
href="https://github.com/users"><code>@​users</code></a>.norepl...</li>
<li><a
href="8459bc0c7e"><code>8459bc0</code></a>
Bump actions/upload-artifact from 2 to 4 (<a
href="https://redirect.github.com/actions/checkout/issues/1695">#1695</a>)</li>
<li><a
href="3f603f6d5e"><code>3f603f6</code></a>
Bump actions/setup-node from 1 to 4 (<a
href="https://redirect.github.com/actions/checkout/issues/1696">#1696</a>)</li>
<li><a
href="fd084cde18"><code>fd084cd</code></a>
Bump github/codeql-action from 2 to 3 (<a
href="https://redirect.github.com/actions/checkout/issues/1694">#1694</a>)</li>
<li><a
href="9c1e94e0ad"><code>9c1e94e</code></a>
Update NPM dependencies (<a
href="https://redirect.github.com/actions/checkout/issues/1703">#1703</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/checkout/compare/v4.1.4...v4.1.5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.4&new-version=4.1.5)](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-05-08 10:35:08 +08:00
7a86b98f61 Migrate to a new PWD API (part 2) (#12749)
Refer to #12603 for part 1.

We need to be careful when migrating to the new API, because the new API
has slightly different semantics (PWD can contain symlinks). This PR
handles the "obviously safe" part of the migrations. Namely, it handles
two specific use cases:

* Passing PWD into `canonicalize_with()`
* Passing PWD into `EngineState::merge_env()`

The first case is safe because symlinks are canonicalized away. The
second case is safe because `EngineState::merge_env()` only uses PWD to
call `std::env::set_current_dir()`, which shouldn't affact Nushell. The
commit message contains detailed stats on the updated files.

Because these migrations touch a lot of files, I want to keep these PRs
small to avoid merge conflicts.
2024-05-07 18:17:49 +03:00
b9331d1b08 Add sys users command (#12787)
# Description
Add a new `sys users` command which returns a table of the users of the
system. This is the same table that is currently present as
`(sys).host.sessions`. The same table has been removed from the recently
added `sys host` command.

# User-Facing Changes
Adds a new command. (The old `sys` command is left as is.)
2024-05-07 07:52:02 -05:00
c54d223ea0 Fix list spread syntax highlighting (#12793)
# Description
I broke syntax highlighting for list spreads in #12529. This should fix
#12792 😅. I just copied the code for highlighting record
spreads.
2024-05-07 13:41:47 +08:00
eccc558a4e describe refactor (#12770)
# Description

Refactors `describe` a bit. Namely, I added a `Description` enum to get
rid of `compact_primitive_description` and its awkward `Value` pattern
matching.
2024-05-06 23:20:46 +00:00
1038c64f80 Add sys subcommands (#12747)
# Description
Adds subcommands to `sys` corresponding to each column of the record
returned by `sys`. This is to alleviate the fact that `sys` now returns
a regular record, meaning that it must compute every column which might
take a noticeable amount of time. The subcommands, on the other hand,
only need to compute and return a subset of the data which should be
much faster. In fact, it should be as fast as before, since this is how
the lazy record worked (it would compute only each column as necessary).

I choose to add subcommands instead of having an optional cell-path
parameter on `sys`, since the cell-path parameter would:
- increase the code complexity (can access any value at any row or
nested column)
- prevents discovery with tab-completion
- hinders type checking and allows users to pass potentially invalid
columns

# User-Facing Changes
Deprecates `sys` in favor of the new `sys` subcommands.
2024-05-06 23:20:27 +00:00
68adc4657f Polars lazy refactor (#12669)
This moves to predominantly supporting only lazy dataframes for most
operations. It removes a lot of the type conversion between lazy and
eager dataframes based on what was inputted into the command.

For the most part the changes will mean:
* You will need to run `polars collect` after performing operations
* The into-lazy command has been removed as it is redundant.
* When opening files a lazy frame will be outputted by default if the
reader supports lazy frames

A list of individual command changes can be found
[here](https://hackmd.io/@nucore/Bk-3V-hW0)

---------

Co-authored-by: Ian Manske <ian.manske@pm.me>
2024-05-06 23:19:11 +00:00
97fc190cc5 allow raw string to be used inside subexpression, list, and closure (#12776)
# Description
Fixes: #12744

This pr is moving raw string lex logic into `lex_item` function, so we
can use raw string inside subexpression, list, closure.
```nushell
> [r#'abc'#]
╭───┬─────╮
│ 0 │ abc │
╰───┴─────╯
> (r#'abc'#)
abc
> do {r#'aa'#}
aa
```

# Tests + Formatting
Done

# After Submitting
NaN
2024-05-06 15:53:58 -05:00
f9d4fa2c40 Add SOPs for dealing with adding deps/crates (#12771)
Time to expand our developer documentation, as some of this is still
tribal knowledge or things could otherwise slip through the cracks and
are costly to fix later.
2024-05-06 22:14:00 +02:00
460a1c8f87 Allow ls works inside dir with [] brackets (#12625)
# Description
Fixes: #12429

To fix the issue, we need to pass the `input pattern` itself to
`glob_from` function, but currently on latest main, nushell pass
`expanded path of input pattern` to `glob_from` function.
It causes globbing failed if expanded path includes `[]` brackets.

It's a pity that I have to duplicate `nu_engine::glob_from` function
into `ls`, because `ls` might convert from `NuGlob::NotExpand` to
`NuGlob::Expand`, in that case, `nu_engine::glob_from` won't work if
user want to ls for a directory which includes tilde:
```
mkdir "~abc"
ls "~abc"
```
So I need to duplicate `glob_from` function and pass original
`expand_tilde` information.

# User-Facing Changes
Nan

# Tests + Formatting
Done

# After Submitting
Nan
2024-05-06 14:01:32 +08:00
e879d4ecaf ListStream touchup (#12524)
# Description

Does some misc changes to `ListStream`:
- Moves it into its own module/file separate from `RawStream`.
- `ListStream`s now have an associated `Span`.
- This required changes to `ListStreamInfo` in `nu-plugin`. Note sure if
this is a breaking change for the plugin protocol.
- Hides the internals of `ListStream` but also adds a few more methods.
- This includes two functions to more easily alter a stream (these take
a `ListStream` and return a `ListStream` instead of having to go through
the whole `into_pipeline_data(..)` route).
  -  `map`: takes a `FnMut(Value) -> Value`
  - `modify`: takes a function to modify the inner stream.
2024-05-05 16:00:59 +00:00
3143ded374 Tango migration (#12469)
# Description

This PR migrates the benchmark suit to Tango. Its different compared to
other framework because it require 2 binaries, to run to do A/B
benchmarking, this is currently limited to Linux, Max, (Windows require
rustc nightly flag), by switching between two suits it can reduce noise
and run the code "almost" concurrently. I have have been in contact with
the maintainer, and bases this on the dev branch, as it had a newer API
simular to criterion. This framework compared to Divan also have a
simple file dump system if we want to generate graphs, do other analysis
on later. I think overall this crate is very nice, a lot faster to
compile and run then criterion, that's for sure.
2024-05-05 15:53:48 +00:00
ce3bc470ba improve NUON documentation (#12717)
# Description
this PR
- moves the documentation from `lib.rs` to `README.md` while still
including it in the lib file, so that both the [crates.io
page](https://crates.io/crates/nuon) and the
[documentation](https://docs.rs/nuon/latest/nuon/) show the top-level
doc
- mention that comments are allowed in NUON
- add a JSON-NUON example
- put back the formatting of NOTE blocks in the doc

# User-Facing Changes

# Tests + Formatting

# After Submitting
2024-05-05 15:34:22 +02:00
2f8e397365 Refactor flattening to reduce intermediate allocations (#12756)
# Description
Our current flattening code creates a bunch of intermediate `Vec`s for
each function call. These intermediate `Vec`s are then usually appended
to the current `output` `Vec`. By instead passing a mutable reference of
the `output` `Vec` to each flattening function, this `Vec` can be
reused/appended to directly thereby eliminating the need for
intermediate `Vec`s in most cases.
2024-05-05 10:43:20 +02:00
9181fca859 Update interprocess to 2.0.1 (#12769)
Fixes #12755

See https://github.com/kotauskas/interprocess/issues/63 and
https://github.com/kotauskas/interprocess/pull/62
2024-05-05 00:51:08 +02:00
0bfbe8c372 Specify the required minimum chrono version (#12766)
See #12765 and h/t to @FMOtalleb in
https://discord.com/channels/601130461678272522/855947301380947968/1236286905843454032

`Duration/TimeDelta::try_milliseconds` was added in `0.4.34`
https://github.com/chronotope/chrono/releases/tag/v0.4.34
2024-05-04 20:16:20 +02:00
349d02ced0 Pin base64 to the fixed patch version (#12762)
Followup to #12757

Always ensure that the `Cargo.toml` specifies the full minimum version
required to have the correct behavior or used features. Otherwise a
missing semver specifier is equal to `0` and could downgrade.
2024-05-04 17:16:40 +02:00
8eefb7313e Minimize future false positive typos (#12751)
# Description

Make typos config more strict: ignore false positives where they occur.

1. Ignore only files with typos
2. Add regexp-s with context
3. Ignore variable names only in Rust code
4. Ignore only 1 "identifier"
5. Check dot files

🎁 Extra bonus: fix typos!!
2024-05-04 15:00:44 +00:00
3ae6fe2114 Enable columns with spaces for into_sqlite by adding quotes to column names (#12759)
# Description
Spaces were causing an issue with into_sqlite when they appeared in
column names.

This is because the column names were not properly wrapped with
backticks that allow sqlite to properly interpret the column.

The issue has been addressed by adding backticks to the column names of
into sqlite. The output of the column names when using open is
unchanged, and the column names appear without backticks as expected.

fixes #12700 

# User-Facing Changes
N/A

# Tests + Formatting
Formatting has been respected.

Repro steps from the issue have been done, and ran multiple times. New
values get added to the correct columns as expected.
2024-05-04 08:12:44 -05:00
1e71cd4777 Bump base64 to 0.22.1 (#12757)
# Description
Bumps `base64` to 0.22.1 which fixes the alphabet used for binhex
encoding and decoding. This required updating some test expected output.

Related to PR #12469 where `base64` was also bumped and ran into the
failing tests.

# User-Facing Changes
Bug fix, but still changes binhex encoding and decoding output.

# Tests + Formatting
Updated test expected output.
2024-05-04 15:56:16 +03:00
0d6fbdde4a Fix PWD cannot point to root paths (#12761)
PR https://github.com/nushell/nushell/pull/12603 made it so that PWD can
never contain a trailing slash. However, a root path (such as `/` or
`C:\`) technically counts as "having a trailing slash", so now `cd /`
doesn't work.

I feel dumb for missing such an obvious edge case. Let's just merge this
quickly before anyone else finds out...

EDIT: It appears I'm too late.
2024-05-04 13:05:54 +03:00
709b2479d9 Fix trailing slash in PWD set by cd (#12760)
# Description

Fixes #12758.

#12662 introduced a bug where calling `cd` with a path with a trailing
slash would cause `PWD` to be set to a path including a trailing slash,
which is not allowed. This adds a helper to `nu_path` to remove this,
and uses it in the `cd` command to clean it up before setting `PWD`.

# Tests + Formatting
I added some tests to make sure we don't regress on this in the future.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-05-04 12:38:37 +03:00
35a0f7a369 fix: prevent relative directory traversal from crashing (#12438)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

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

- fixes #11922
- fixes #12203

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

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->
This is a rewrite for some parts of the recursive completion system. The
Rust `std::path` structures often ignores things like a trailing `.`
because for a complete path, it implies the current directory. We are
replacing the use of some of these structs for Strings.

A side effect is the slashes being normalized in Windows. For example if
we were to type `foo/bar/b`, it would complete it to `foo\bar\baz`
because a backward slash is the main separator in windows.

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

Relative paths are preserved. `..`s in the paths won't eagerly show
completions from the parent path. For example, `asd/foo/../b` will now
complete to `asd/foo/../bar` instead of `asd/bar`.

# 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-05-03 20:17:50 -05:00
a1287f7b3f add more tests to the polars plugin (#12719)
# Description

I added some more tests to our mighty `polars` ~~, yet I don't know how
to add expected results in some of them. I would like to ask for help.~~

~~My experiments are in the last commit: [polars:
experiments](f7e5e72019).
Without those experiments `cargo test` goes well.~~
 
UPD. I moved out my unsuccessful test experiments into a separate
[branch](https://github.com/maxim-uvarov/nushell/blob/polars-tests-broken2/).
So, this branch seems ready for a merge.

@ayax79, maybe you'll find time for me please? It's not urgent for sure.

P.S. I'm very new to git. Please feel free to give me any suggestions on
how I should use it better
2024-05-03 20:14:55 -05:00
406df7f208 Avoid taking unnecessary ownership of intermediates (#12740)
# Description

Judiciously try to avoid allocations/clone by changing the signature of
functions

- **Don't pass str by value unnecessarily if only read**
- **Don't require a vec in `Sandbox::with_files`**
- **Remove unnecessary string clone**
- **Fixup unnecessary borrow**
- **Use `&str` in shape color instead**
- **Vec -> Slice**
- **Elide string clone**
- **Elide `Path` clone**
- **Take &str to elide clone in tests**

# User-Facing Changes
None

# Tests + Formatting
This touches many tests purely in changing from owned to borrowed/static
data
2024-05-04 00:53:15 +00:00
e6f473695c Fix typo (#12752) 2024-05-03 16:14:13 -05:00
eff7f33086 Report errors that occur on file operations in ls (#12033)
Currently errors just create empty entries inside of resulting
dataframes.

This changeset is meant to help debug #12004, though generally speaking
I do think it's worth having ways to make errors be visible in this kind
of pipeline be visible

An example of what this looks like

<img width="954" alt="image"
src="https://github.com/nushell/nushell/assets/1408472/2c3c9167-2aaf-4f87-bab5-e8302d7a1170">
2024-05-03 10:12:43 -05:00
bdb6daa4b5 Migrate to a new PWD API (#12603)
This is the first PR towards migrating to a new `$env.PWD` API that
returns potentially un-canonicalized paths. Refer to PR #12515 for
motivations.

## New API: `EngineState::cwd()`

The goal of the new API is to cover both parse-time and runtime use
case, and avoid unintentional misuse. It takes an `Option<Stack>` as
argument, which if supplied, will search for `$env.PWD` on the stack in
additional to the engine state. I think with this design, there's less
confusion over parse-time and runtime environments. If you have access
to a stack, just supply it; otherwise supply `None`.

## Deprecation of other PWD-related APIs

Other APIs are re-implemented using `EngineState::cwd()` and properly
documented. They're marked deprecated, but their behavior is unchanged.
Unused APIs are deleted, and code that accesses `$env.PWD` directly
without using an API is rewritten.

Deprecated APIs:

* `EngineState::current_work_dir()`
* `StateWorkingSet::get_cwd()`
* `env::current_dir()`
* `env::current_dir_str()`
* `env::current_dir_const()`
* `env::current_dir_str_const()`

Other changes:

* `EngineState::get_cwd()` (deleted)
* `StateWorkingSet::list_env()` (deleted)
* `repl::do_run_cmd()` (rewritten with `env::current_dir_str()`)

## `cd` and `pwd` now use logical paths by default

This pulls the changes from PR #12515. It's currently somewhat broken
because using non-canonicalized paths exposed a bug in our path
normalization logic (Issue #12602). Once that is fixed, this should
work.

## Future plans

This PR needs some tests. Which test helpers should I use, and where
should I put those tests?

I noticed that unquoted paths are expanded within `eval_filepath()` and
`eval_directory()` before they even reach the `cd` command. This means
every paths is expanded twice. Is this intended?

Once this PR lands, the plan is to review all usages of the deprecated
APIs and migrate them to `EngineState::cwd()`. In the meantime, these
usages are annotated with `#[allow(deprecated)]` to avoid breaking CI.

---------

Co-authored-by: Jakub Žádník <kubouch@gmail.com>
2024-05-03 14:33:09 +03:00
f32ecc641f Remove some macros (#12742)
# Description
Replaces some macros with regular functions or other code.
2024-05-03 10:35:37 +02:00
eff2f1b3b0 Update PLATFORM_SUPPORT regarding feature flags (#12741)
This was out of date after removing `extra` and moving towards the
polars plugin
2024-05-03 08:49:44 +02:00
72f3942c37 Upgrade to interprocess 2.0.0 (#12729)
# Description

This fixes #12724. NetBSD confirmed to work with this change.

The update also behaves a bit better in some ways - it automatically
unlinks and reclaims sockets on Unix, and doesn't try to flush/sync the
socket on Windows, so I was able to remove that platform-specific logic.

They also have a way to split the socket so I could just use one socket
now, but I haven't tried to do that yet. That would be more of a
breaking change but I think it's more straightforward.

# User-Facing Changes

- Hopefully more platforms work

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-05-02 22:31:33 -07:00
bc6d934fa1 add support for cell-paths to NUON (#12718)
# Description
_cell paths_ can be easily serialized back and forth to NUON with the
leading `$.` syntax.

# User-Facing Changes
```nushell
$.foo.bar.0 | to nuon
```
and
```nushell
"$.foo.bar.0" | from nuon
```
are now possible

# Tests + Formatting
a new `cell_path` test has been added to `nuon`

# After Submitting
2024-05-03 09:25:19 +08:00
944ebac1c2 Eliminate dead code in nu-explore (#12735)
# Description
Nightly clippy found some unused fields leading me down a rabbit hole of
dead code hidden behind `pub`

Generally removing any already dead code or premature configurability
that is not exposed to the user.

# User-Facing Changes

None in effect.

Removed some options from the `$env.config.explore.hex-dump` record that
were only read into a struct but never used and also not validated.
2024-05-03 08:36:58 +08:00
847646e44e Remove lazy records (#12682)
# Description
Removes lazy records from the language, following from the reasons
outlined in #12622. Namely, this should make semantics more clear and
will eliminate concerns regarding maintainability.

# User-Facing Changes
- Breaking change: `lazy make` is removed.
- Breaking change: `describe --collect-lazyrecords` flag is removed.
- `sys` and `debug info` now return regular records.

# After Submitting
- Update nushell book if necessary.
- Explore new `sys` and `debug info` APIs to prevent them from taking
too long (e.g., subcommands or taking an optional column/cell-path
argument).
2024-05-03 08:36:10 +08:00
ad6deadf24 Flush on every plugin Data message (#12728)
# Description

This helps to ensure data produced on a stream is immediately available
to the consumer of the stream. The BufWriter introduced for performance
reasons in 0.93 exposed the behavior that data messages wouldn't make it
to the other side until they filled the buffer in @cablehead's
[`nu_plugin_from_sse`](https://github.com/cablehead/nu_plugin_from_sse).

I had originally not flushed on every `Data` message because I figured
that it isn't really critical that the other side sees those messages
immediately, since they're not used for control and they are flushed
when waiting for acknowledgement or when the buffer is too full anyway.

Increasing the amount of data that can be sent with a single underlying
write increases performance, but this interferes with some plugins that
want to use streams in a more real-time way. In the future I would like
to make this configurable, maybe even per-command, so that a command can
decide what the priority is. But for now I think this is reasonable.

In the worst case, this decreases performance by about 40%, when sending
very small values (just numbers). But for larger values, this PR
actually increases performance by about 20%, because I've increased the
buffer size about 2x to 16,384 bytes. The previous value of 8,192 bytes
was too small to fit a full buffer coming from an external command, so
doubling it makes sense, and now a write of a buffer from an external
command can be done in exactly one write call, which I think makes
sense. I'm doing this at the same time because flushing each data
message would make it very likely that each individual data message from
an external stream would require exactly two writes rather than
approximately one (amortized).

Again, hopefully the tradeoff isn't too bad, and if it is I'll just make
it configurable.

# User-Facing Changes

- Performance of plugin streams will be a bit different
- Plugins that expect to send streams in real-time will work again

# Tests + Formatting

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-05-02 23:51:16 +00:00
be6137d136 Fix clippy::wrong_self_convention in polars plugin (#12737)
Expected `into_` for `fn(self) -> T`
2024-05-02 19:31:51 +02:00
b88d8726d0 Rework for new clippy lints (#12736)
- **Clippy lint `assigning_clones`**
- **Clippy lint `legacy_numeric_constants`**
- **`clippy::float_equality_without_abs`**
- **`nu-table`: clippy::zero_repeat_side_effects**

---------

Co-authored-by: Ian Manske <ian.manske@pm.me>
2024-05-02 19:29:03 +02:00
0805f1fd90 overhaul shell_integration to enable individual control over ansi escape sequences (#12629)
# Description

This PR overhauls the shell_integration system by allowing individual
control over which ansi escape sequences are used. As we continue to
broaden our support for more ansi escape sequences, we can't really have
an all-or-nothing strategy. Some ansi escapes cause problems in certain
operating systems or terminals. We should allow the user to choose which
escapes they want.

TODO:
* Gather feedback
* Should osc7, osc9_9 and osc633p be mutually exclusive?
* Is the naming convention for these settings too nerdy osc2, osc7, etc?

closes #11301

# User-Facing Changes
shell_integration is no longer a boolean value. This is what is
supported in the default_config.nu
```nushell
  shell_integration: {
    # osc2 abbreviates the path if in the home_dir, sets the tab/window title, shows the running command in the tab/window title
    osc2: true
    # osc7 is a way to communicate the path to the terminal, this is helpful for spawning new tabs in the same directory
    osc7: true
    # osc8 is also implemented as the deprecated setting ls.show_clickable_links, it shows clickable links in ls output if your terminal supports it
    osc8: true
    # osc9_9 is from ConEmu and is starting to get wider support. It's similar to osc7 in that it communicates the path to the terminal
    osc9_9: false
    # osc133 is several escapes invented by Final Term which include the supported ones below.
    # 133;A - Mark prompt start
    # 133;B - Mark prompt end
    # 133;C - Mark pre-execution
    # 133;D;exit - Mark execution finished with exit code
    # This is used to enable terminals to know where the prompt is, the command is, where the command finishes, and where the output of the command is
    osc133: true
    # osc633 is closely related to osc133 but only exists in visual studio code (vscode) and supports their shell integration features
    # 633;A - Mark prompt start
    # 633;B - Mark prompt end
    # 633;C - Mark pre-execution
    # 633;D;exit - Mark execution finished with exit code
    # 633;E - NOT IMPLEMENTED - Explicitly set the command line with an optional nonce
    # 633;P;Cwd=<path> - Mark the current working directory and communicate it to the terminal
    # and also helps with the run recent menu in vscode
    osc633: true
    # reset_application_mode is escape \x1b[?1l and was added to help ssh work better
    reset_application_mode: true
  }
```

# 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-05-02 09:56:50 -04:00
8ed0d84d6a add raw-string literal support (#9956)
# Description

This PR adds raw string support by using `r#` at the beginning of single
quoted strings and `#` at the end.

Notice that escapes do not process, even within single quotes,
parentheses don't mean anything, $variables don't mean anything. It's
just a string.
```nushell
❯ echo r#'one\ntwo (blah) ($var)'#
one\ntwo (blah) ($var)
```
Notice how they work without `echo` or `print` and how they work without
carriage returns.
```nushell
❯ r#'adsfa'#
adsfa
❯ r##"asdfa'@qpejq'##
asdfa'@qpejq
❯ r#'asdfasdfasf
∙ foqwejfqo@'23rfjqf'#
```
They also have a special configurable color in the repl. (use single
quotes though)

![image](https://github.com/nushell/nushell/assets/343840/8780e21d-de4c-45b3-9880-2425f5fe10ef)

They should work like rust raw literals and allow `r##`, `r###`,
`r####`, etc, to help with having one or many `#`'s in the middle of
your raw-string.

They should work with `let` as well.

```nushell
r#'some\nraw\nstring'# | str upcase
```

closes https://github.com/nushell/nushell/issues/5091
# 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 -A
clippy::needless_collect -A clippy::result_large_err` to check that
you're using the standard code style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- -c "use std testing; testing run-tests --path
crates/nu-std"` to run the tests for the standard library

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

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

---------

Co-authored-by: WindSoilder <WindSoilder@outlook.com>
Co-authored-by: Ian Manske <ian.manske@pm.me>
2024-05-02 09:36:37 -04:00
b5741ef14b Remove accidentally committed file (#12734)
Remove a small (20kb) SQLite database that was accidentally added as
part of https://github.com/nushell/nushell/pull/12692
2024-05-02 06:14:36 -07:00
cc91e36cf8 Upgrade Nu to v0.93.0 for nightly and release workflow (#12721) 2024-05-02 11:11:53 +08:00
0a01e7c33e Bump rmp-serde from 1.2.0 to 1.3.0 (#12711)
Bumps [rmp-serde](https://github.com/3Hren/msgpack-rust) from 1.2.0 to
1.3.0.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="52de9be5a3"><code>52de9be</code></a>
Bump</li>
<li><a
href="c1b19aa3a8"><code>c1b19aa</code></a>
Update README</li>
<li><a
href="454e0c5e18"><code>454e0c5</code></a>
Smaller integer for depth</li>
<li><a
href="143897c5ab"><code>143897c</code></a>
Update README</li>
<li><a
href="7ddcd2ea4a"><code>7ddcd2e</code></a>
Decoder/inspector example</li>
<li><a
href="f9f02d8397"><code>f9f02d8</code></a>
Simplify Marker match by reusing discriminant</li>
<li><a
href="926682d1d6"><code>926682d</code></a>
Smaller write len</li>
<li><a
href="06414f584d"><code>06414f5</code></a>
Handle OOM when writing</li>
<li><a
href="80e00b3187"><code>80e00b3</code></a>
Bump</li>
<li><a
href="6dd81ee985"><code>6dd81ee</code></a>
Hack to use bytes</li>
<li>Additional commits viewable in <a
href="https://github.com/3Hren/msgpack-rust/compare/rmp-serde/v1.2.0...rmp-serde/v1.3.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rmp-serde&package-manager=cargo&previous-version=1.2.0&new-version=1.3.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-05-01 17:26:00 -07:00
ac882d7013 Add toolkit release-pkg windows for Windows release pkg builds (#12727)
# Description

We have often had issues with winget during release, and have to fix the
Windows installers and then create new packages.

This runs `release-pkg.nu` for all eight Windows packages we release:
std and full, aarch64 and x86_64, and zip and msi variants.

It requires the cross compiling toolchain for MSVC to be installed,
since Rust generally needs that to build things on Windows. Use the
Visual Studio Installer to do so.

If there's ever a need, this can be extended for other platforms too.
2024-05-01 16:51:44 -07:00
3d340657b5 explore: adopt anyhow, support CustomValue, remove help system (#12692)
This PR:
1. Adds basic support for `CustomValue` to `explore`. Previously `open
foo.db | explore` didn't really work, now we "materialize" the whole
database to a `Value` before loading it
2. Adopts `anyhow` for error handling in `explore`. Previously we were
kind of rolling our own version of `anyhow` by shoving all errors into a
`std::io::Error`; I think this is much nicer. This was necessary because
as part of 1), collecting input is now fallible...
3. Removes a lot of `explore`'s fancy command help system.
- Previously each command (`:help`, `:try`, etc.) had a sophisticated
help system with examples etc... but this was not very visible to users.
You had to know to run `:help :try` or view a list of commands with
`:help :`
- As discussed previously, we eventually want to move to a less modal
approach for `explore`, without the Vim-like commands. And so I don't
think it's worth keeping this command help system around (it's
intertwined with other stuff, and making these changes would have been
harder if keeping it).
4. Rename the `--reverse` flag to `--tail`. The flag scrolls to the end
of the data, which IMO is described better by "tail"
5. Does some renaming+commenting to clear up things I found difficult to
understand when navigating the `explore` code


I initially thought 1) would be just a few lines, and then this PR blew
up into much more extensive changes 😅


## Before
The whole database was being displayed as a single Nuon/JSON line 🤔 

![image](https://github.com/nushell/nushell/assets/26268125/6383f43b-fdff-48b4-9604-398438ad1499)


## After
The database gets displayed like a record

![image](https://github.com/nushell/nushell/assets/26268125/2f00ed7b-a3c4-47f4-a08c-98d07efc7bb4)


## Future work

It is sort of annoying that we have to load a whole SQLite database into
memory to make this work; it will be impractical for large databases.
I'd like to explore improvements to `CustomValue` that can make this
work more efficiently.
2024-05-01 17:34:37 -05:00
bc18cc12d5 change wix install method from perMachine to perUser (#12720)
# Description

This PR:
* Updates to the latest cargo-wix
* Changes install method from perMachine to perUser
* Updates HKCU Path vs HKLM Path
* Updates Windows Terminal Fragment Json to be compatible with [their
spec](https://learn.microsoft.com/en-us/windows/terminal/json-fragment-extensions).
* Updates License year from 2022 to 2024

The result of these changes makes our Windows installer no longer prompt
with a UAC dialog and installs the binaries into
`%LocalAppData%\Programs\nu\bin`.

All of this is an attempt to make WinGet releases less error prone.

# 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-05-01 17:31:16 -05:00
2970d48d41 Make bytes build accept integer values as individual bytes (#12685)
# Description
This creates an option for building binary data from byte integers.
Previously I think you could only do this by formatting the integers to
hex and using `decode hex`.

One potentially confusing thing is that this is different from the `into
binary` behavior. But since this doesn't support any of the other `into
binary` behaviors, it might be okay.

# User-Facing Changes
- `bytes build` accepts single byte arguments as integers

# Tests + Formatting
Example added.

# After Submitting
- [ ] release notes
2024-05-01 17:29:33 -05:00
f184a77fe1 Path expansion no longer removes trailing slashes (#12662)
This PR changes `nu_path::expand_path_with()` to no longer remove
trailing slashes. It also fixes bugs in the current implementation due
to ineffective tests (Fixes #12602).
2024-05-01 17:28:54 -05:00
b22d131279 Prevent each from swallowing errors when eval_block returns a ListStream (#12412)
<!--
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.
-->

Prior, it seemed that nested errors would not get detected and shown.
This PR fixes that.

Resolves #10176:
```
~/CodingProjects/nushell> [[1,2]] | each {|x| $x | each {|y| error make {msg: "oh noes"} } }                        05/04/2024 21:34:08
Error: nu:🐚:eval_block_with_input

  × Eval block failed with pipeline input
   ╭─[entry #1:1:3]
 1 │ [[1,2]] | each {|x| $x | each {|y| error make {msg: "oh noes"} } }
   ·   ┬
   ·   ╰── source value
   ╰────

Error:   × oh noes
   ╭─[entry #1:1:36]
 1 │ [[1,2]] | each {|x| $x | each {|y| error make {msg: "oh noes"} } }
   ·                                    ─────┬────
   ·                                         ╰── originates from here
   ╰────
```

Resolves #11224:
```
~/CodingProjects/nushell> [0] | each { |_|                                                                          05/04/2024 21:35:40
:::     [0] | each { |_|
:::         non-existent-command
:::     }
::: }
Error: nu:🐚:eval_block_with_input

  × Eval block failed with pipeline input
   ╭─[entry #1:2:6]
 1 │ [0] | each { |_|
 2 │     [0] | each { |_|
   ·      ┬
   ·      ╰── source value
 3 │         non-existent-command
   ╰────

Error: nu:🐚:external_command

  × External command failed
   ╭─[entry #1:3:9]
 2 │     [0] | each { |_|
 3 │         non-existent-command
   ·         ──────────┬─────────
   ·                   ╰── executable was not found
 4 │     }
   ╰────
  help: No such file or directory (os error 2)
```

# 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-05-01 17:24:54 -05:00
52d99cc60c Change environment variables to be case-preserving (#12701)
This PR changes `$env` to be **case-preserving** instead of
case-sensitive. That is, it preserves the case of the environment
variable when it is first assigned, but subsequent retrieval and update
ignores the case.

Notably, both `$env.PATH` and `$env.Path` can now be used to read or set
the environment variable, but child processes will always see the
correct case based on the platform.

Fixes #11268.

---

This feature was surprising simple to implement, because most of the
infrastructure to support case-insensitive cell path access already
exists. The `get` command extracts data using a cell path in a
case-insensitive way (!), but accepts a `--sensitive` flag. (I think
this should be flipped around?)
2024-05-01 17:22:34 -05:00
21ebdfe8d7 Bump version to 0.93.1 (#12710)
# Description

Next patch/dev release, `0.93.1`
2024-05-01 17:19:20 -05:00
cb6d495e02 Bump actions/checkout from 4.1.3 to 4.1.4 (#12712)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.3
to 4.1.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v4.1.4</h2>
<h2>What's Changed</h2>
<ul>
<li>Disable <code>extensions.worktreeConfig</code> when disabling
<code>sparse-checkout</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1692">actions/checkout#1692</a></li>
<li>Add dependabot config by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1688">actions/checkout#1688</a></li>
<li>Bump word-wrap from 1.2.3 to 1.2.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1643">actions/checkout#1643</a></li>
<li>Bump the minor-actions-dependencies group with 2 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1693">actions/checkout#1693</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4.1.3...v4.1.4">https://github.com/actions/checkout/compare/v4.1.3...v4.1.4</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h2>v4.1.4</h2>
<ul>
<li>Disable <code>extensions.worktreeConfig</code> when disabling
<code>sparse-checkout</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1692">actions/checkout#1692</a></li>
<li>Add dependabot config by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1688">actions/checkout#1688</a></li>
<li>Bump the minor-actions-dependencies group with 2 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1693">actions/checkout#1693</a></li>
<li>Bump word-wrap from 1.2.3 to 1.2.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1643">actions/checkout#1643</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="0ad4b8fada"><code>0ad4b8f</code></a>
Prep Release v4.1.4 (<a
href="https://redirect.github.com/actions/checkout/issues/1704">#1704</a>)</li>
<li><a
href="43045ae669"><code>43045ae</code></a>
Disable <code>extensions.worktreeConfig</code> when disabling
<code>sparse-checkout</code> (<a
href="https://redirect.github.com/actions/checkout/issues/1692">#1692</a>)</li>
<li><a
href="37b082107b"><code>37b0821</code></a>
Bump the minor-actions-dependencies group with 2 updates (<a
href="https://redirect.github.com/actions/checkout/issues/1693">#1693</a>)</li>
<li><a
href="9839dc14a0"><code>9839dc1</code></a>
Add dependabot config (<a
href="https://redirect.github.com/actions/checkout/issues/1688">#1688</a>)</li>
<li><a
href="9b4c13b0bf"><code>9b4c13b</code></a>
Bump word-wrap from 1.2.3 to 1.2.5 (<a
href="https://redirect.github.com/actions/checkout/issues/1643">#1643</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/checkout/compare/v4.1.3...v4.1.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.3&new-version=4.1.4)](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-05-01 11:13:32 +08:00
3c022e334f Fix Windows Terminal profile installation (#12714)
# Description

The command used to edit the Windows Terminal profiling was failing due
to the `open file | save -f file` metadata safeguard introduced in this
version. With this, the installer is now fixed again.
2024-04-30 17:36:40 -07:00
734a3b5f2c Bump crate-ci/typos from 1.20.10 to 1.21.0 (#12713) 2024-05-01 00:32:01 +00:00
3b220e07e3 Bump version to 0.93.0 (#12709)
# Description

Bump version to `0.93.0`
2024-04-30 15:51:13 -07:00
16799a1d78 Bump reedline to 0.32.0 (#12708)
# Description

Follow `reedline` release to `0.32.0`, disable crates.io git patch
2024-04-30 15:32:54 -07:00
38bf3f6e1b Update the bundled readme in release archives (#12688)
Reflect the deprecation of `register`

cc @devyn

---------

Co-authored-by: Devyn Cairns <devyn.cairns@gmail.com>
2024-04-30 00:42:21 -07:00
e83123dcca Add fs feature to nix dependency (#12702)
# Description
Caught a compilation error using `cargo hack` -- `nu-utils` will not
compile without the `fs` feature enabled for `nix`.
2024-04-29 22:50:39 +00:00
648486400c Fix missing local socket feature (#12698)
# Description

So sorry to do this during the pre-release freeze, but my plugin crate
split PR broke local socket mode, because `nu-plugin-protocol` didn't
have the compile feature to advertise the `LocalSocket` protocol
feature.

This is a very simple, configuration-only bugfix that I think really
needs to be merged before the release, or else local socket mode won't
work at all.

# Tests + Formatting

There's an oversight in my testing that caused this to not be caught:
the engine really did have the feature, but it just wasn't advertising
it, so for `stress_internals` it was still able to use it successfully.
Post-release I'll try to make sure this is properly handled somehow.
2024-04-29 15:02:56 +08:00
59ee96c70d fixes a rust-analyzer warning (#12694)
# Description

Minor change but fixes a few rust-analyzer warnings.

# 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-04-28 04:52:05 -05:00
365f5954ee restore query web --as-table to working order (#12693)
# Description

This PR fixes a problem introduced with PR
https://github.com/nushell/nushell/pull/12236. That PR accidentally
stopped `--as-table` from working.

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

It works again.

![image](https://github.com/nushell/nushell/assets/343840/b517507f-6b92-4e39-a389-5c69907d77c0)

# 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-04-28 04:21:26 -05:00
24ecb84d97 update wix to include nu_plugin_polars (#12687)
# Description

Update wix/msi to include nu_plugin_polars. 🤞🏻 

# 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-04-27 16:00:12 -05:00
0c4d5330ee Split the plugin crate (#12563)
# Description

This breaks `nu-plugin` up into four crates:

- `nu-plugin-protocol`: just the type definitions for the protocol, no
I/O. If someone wanted to wire up something more bare metal, maybe for
async I/O, they could use this.
- `nu-plugin-core`: the shared stuff between engine/plugin. Less stable
interface.
- `nu-plugin-engine`: everything required for the engine to talk to
plugins. Less stable interface.
- `nu-plugin`: everything required for the plugin to talk to the engine,
what plugin developers use. Should be the most stable interface.

No changes are made to the interface exposed by `nu-plugin` - it should
all still be there. Re-exports from `nu-plugin-protocol` or
`nu-plugin-core` are used as required. Plugins shouldn't ever have to
use those crates directly.

This should be somewhat faster to compile as `nu-plugin-engine` and
`nu-plugin` can compile in parallel, and the engine doesn't need
`nu-plugin` and plugins don't need `nu-plugin-engine` (except for test
support), so that should reduce what needs to be compiled too.

The only significant change here other than splitting stuff up was to
break the `source` out of `PluginCustomValue` and create a new
`PluginCustomValueWithSource` type that contains that instead. One bonus
of that is we get rid of the option and it's now more type-safe, but it
also means that the logic for that stuff (actually running the plugin
for custom value ops) can live entirely within the `nu-plugin-engine`
crate.

# User-Facing Changes
- New crates.
- Added `local-socket` feature for `nu` to try to make it possible to
compile without that support if needed.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-27 12:08:12 -05:00
884d5312bb add tests to polars unique (#12683)
# Description

I would like to help with `polars` plugin development and add tests to
all the `polars` command's existing params.

Since I have never written any lines of Rust, even though the task of
creating tests is relatively simple, I would like to ask for feedback to
ensure I did everything correctly here.
2024-04-27 12:04:54 -05:00
76d1d70e83 Upgrade hustcer/setup-nu to v3.10 to fix macOS arm64 build errors (#12681)
Upgrade hustcer/setup-nu to
[v3.10](https://github.com/hustcer/setup-nu/releases/tag/v3.10) to fix
macOS arm64 build errors
Release Testing:
https://github.com/nushell/nightly/actions/runs/8856404620
2024-04-27 10:18:08 +08:00
02de69de92 Fix inconsistent print behavior (#12675)
# Description

I found a bunch of issues relating to the specialized reimplementation
of `print()` that's done in `nu-cli` and it just didn't seem necessary.
So I tried to unify the behavior reasonably. `PipelineData::print()`
already handles the call to `table` and it even has a `no_newline`
option.

One of the most major issues before was that we were using the value
iterator, and then converting to string, and then printing each with
newlines. This doesn't work well for an external stream, because its
iterator ends up creating `Value::binary()` with each buffer... so we
were doing lossy UTF-8 conversion on those and then printing them with
newlines, which was very weird:


![Screenshot_2024-04-26_02-02-29](https://github.com/nushell/nushell/assets/10729/131c2224-08ee-4582-8617-6ecbb3ce8da5)

You can see the random newline inserted in a break between buffers, but
this would be even worse if it were on a multibyte UTF-8 character. You
can produce this by writing a large amount of text to a text file, and
then doing `nu -c 'open file.txt'` - in my case I just wrote `^find .`;
it just has to be large enough to trigger a buffer break.

Using `print()` instead led to a new issue though, because it doesn't
abort on errors. This is so that certain commands can produce a stream
of errors and have those all printed. There are tests for e.g. `rm` that
depend on this behavior. I assume we want to keep that, so instead I
made my target `BufferedReader`, and had that fuse closed if an error
was encountered. I can't imagine we want to keep reading from a wrapped
I/O stream if an error occurs; more often than not the error isn't going
to magically resolve itself, it's not going to be a different error each
time, and it's just going to lead to an infinite stream of the same
error.

The test that broke without that was `open . | lines`, because `lines`
doesn't fuse closed on error. But I don't know if it's expected or not
for it to do that, so I didn't target that.

I think this PR makes things better but I'll keep looking for ways to
improve on how errors and streams interact, especially trying to
eliminate cases where infinite error loops can happen.

# User-Facing Changes
- **Breaking**: `BufferedReader` changes + no more public fields
- A raw I/O stream from e.g. `open` won't produce infinite errors
anymore, but I consider that to be a plus
- the implicit `print` on script output is the same as the normal one
now

# Tests + Formatting
Everything passes but I didn't add anything specific.
2024-04-27 00:25:11 +00:00
533603b72c Add deprecation warning to describe --collect-lazyrecords (#12667)
# Description
Missed a spot for lazy record deprecation. This adds a warning for the
`--collect-lazyrecords` flag on `describe`.
2024-04-26 16:36:30 +00:00
1ecbb3e09f Make exit code available in catch block (#12648)
# Description
Bandaid fix for #12643, where it is not possible to get the exit code of
a failed external command while also having the external command inherit
nushell's stdout and stderr. This changes `try` so that the exit code of
external command is available in the `catch` block via the usual
`$env.LAST_EXIT_CODE`.

# Tests + Formatting
Added one test.

# After Submitting
Rework I/O redirection and possibly exit codes.
2024-04-26 16:35:08 +00:00
d23a3737c0 make grid throw an error when not enough columns (#12672)
<!--
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.
-->

Resolves #12654. 

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

`grid` can now throw an error.

# 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 relevant test.
2024-04-26 06:33:00 -05:00
822c434c12 Add a bit more delay before ps calls in plugin persistence tests (#12673)
# Description
I've found that sometimes on Linux, this test fails to find the created
process even after it should definitely be running.

Trying to add a little delay.
2024-04-26 06:24:57 -05:00
d126793290 Add plugin error propagation on write/flush (#12670)
# Description
Yet another attempt to fix the `stress_internals::test_wrong_version()`
test...

This time I think it's probably because we are getting a broken pipe
write error when we try to send `Hello` or perhaps something after it,
because the pipe has already been closed by the reader when it saw the
invalid version. In that case, an error should be available in state. It
probably makes more sense to send that back to the user rather than an
unhelpful I/O error.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-26 06:23:58 -05:00
adf38c7c76 Msgpack commands (#12664)
# Description

I thought about bringing `nu_plugin_msgpack` in, but that is MPL with a
clause that prevents other licenses, so rather than adapt that code I
decided to take a crack at just doing it straight from `rmp` to `Value`
without any `rmpv` in the middle. It seems like it's probably faster,
though I can't say for sure how much with the plugin overhead.

@IanManske I started on a `Read` implementation for `RawStream` but just
specialized to `from msgpack` here, but I'm thinking after release maybe
we can polish it up and make it a real one. It works!

# User-Facing Changes
New commands:

- `from msgpack`
- `from msgpackz`
- `to msgpack`
- `to msgpackz`

# Tests + Formatting
Pretty thorough tests added for the format deserialization, with a
roundtrip for serialization. Some example tests too for both `from
msgpack` and `to msgpack`.

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


# After Submitting
- [ ] update release notes
2024-04-26 06:23:16 -05:00
79ebf0c0a9 Fix an into bits example (#12668)
# Description
One example for `into bits` says it uses binary value when it actually
uses a filesize. This lead to issue #11412, but I never got around to
fixing the example until this PR.
2024-04-25 19:38:28 -05:00
f234a0ea33 each signature fix (#12666)
# Description
Removes the second `Int` parameter from the closure in the signature of
`each`. This parameter doesn't exist / isn't supported.
2024-04-25 22:47:30 +02:00
2466a39574 Fix example wording in seq date (#12665)
# Description

The previous messages said that the command printed dates separated by
newlines. But the current iteration of `seq date` returns a list.

# User-Facing Changes

Minor wording edit.

---------

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2024-04-25 22:12:42 +02:00
1239e86320 Remove deprecated flags on run-external (#12659)
# Description
Removes the `run-external` flags that were deprecated in 0.92.0 with
#11934.
2024-04-25 14:17:21 +00:00
5b0546cfce Remove deprecated flags on commandline (#12658)
# Description
Removes the `commandline` flags and API that was deprecated in 0.91.0
with #11877.

# User-Facing Changes
Users need to migrate to the new `commandline` subcommands introduced in
0.91.0.
2024-04-25 14:16:42 +00:00
b6d765a2d8 nu-cmd-lang cleanup (#12609)
# Description
This PR does miscellaneous cleanup in some of the commands from
`nu-cmd-lang`.

# User-Facing Changes
None.

# After Submitting
Cleanup the other commands in `nu-cmd-lang`.
2024-04-25 14:16:12 +00:00
530162b4c4 Lazy record deprecation (#12656)
# Description
In this week's nushell meeting, we decided to go ahead with #12622 and
remove lazy records in 0.94.0. For 0.93.0, we will only deprecate `lazy
make`, and so this PR makes `lazy make` print a deprecation warning.

# User-Facing Changes
None, besides the deprecation warning.

# After Submitting
Remove lazy records.
2024-04-25 07:05:24 +08:00
25cbcb511d Rename plugin cache file ⇒ plugin registry file (#12634)
# Description
So far this seems like the winner of my poll on what the name should be.
I'll take this off draft once the poll expires, if this is indeed the
winner.
2024-04-24 17:40:39 -05:00
9996e4a1f8 Shrink the size of Expr (#12610)
# Description
Continuing from #12568, this PR further reduces the size of `Expr` from
64 to 40 bytes. It also reduces `Expression` from 128 to 96 bytes and
`Type` from 32 to 24 bytes.

This was accomplished by:
- for `Expr` with multiple fields (e.g., `Expr::Thing(A, B, C)`),
merging the fields into new AST struct types and then boxing this struct
(e.g. `Expr::Thing(Box<ABC>)`).
- replacing `Vec<T>` with `Box<[T]>` in multiple places. `Expr`s and
`Expression`s should rarely be mutated, if at all, so this optimization
makes sense.

By reducing the size of these types, I didn't notice a large performance
improvement (at least compared to #12568). But this PR does reduce the
memory usage of nushell. My config is somewhat light so I only noticed a
difference of 1.4MiB (38.9MiB vs 37.5MiB).

---------

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2024-04-24 15:46:35 +00:00
c52884b3c8 Fix (and test) for a deadlock that can happen while waiting for protocol info (#12633)
# Description

The local socket PR introduced a `Waitable` type, which could either
hold a value or be waited on until a value is available. Unlike a
channel, it would always return that value once set.

However, one issue with this design was that there was no way to detect
whether a value would ever be written. This splits the writer into a
different type `WaitableMut`, so that when it is dropped, waiting
threads can fail (because they'll never get a value).

# Tests + Formatting

A test has been added to `stress_internals` to make sure this fails in
the right way.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-24 08:44:04 -05:00
0f645b3bb6 Futher improve messages related to error propagation on plugin calls (#12646)
# Description
Trying to give as much context as possible. Now there should be a
spanned error with the call span if possible, and the propagated error
as an inner error if there was one in every case.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-24 08:39:04 -05:00
1e453020b6 update to latest reedline (#12644)
# Description

Update to latest reedline main branch 4cf8c75d for testing before
release.

# 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-04-24 07:40:04 -05:00
06c72df672 Bump serial_test from 3.0.0 to 3.1.0 (#12638) 2024-04-24 12:01:20 +00:00
9d65c47313 Bump rust-ini from 0.20.0 to 0.21.0 (#12637) 2024-04-24 11:55:20 +00:00
b576123b0a Accept filenames in other plugin management commands (#12639)
# Description

This allows the following commands to all accept a filename instead of a
plugin name:

- `plugin use`
- `plugin rm`
- `plugin stop`

Slightly complicated because of the need to also check against
`NU_PLUGIN_DIRS`, but I also fixed some issues with that at the same
time

Requested by @fdncred

# User-Facing Changes

The new commands are updated as described.

# Tests + Formatting

Tests for `NU_PLUGIN_DIRS` handling also made more robust.

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

# After Submitting

- [ ] Double check new docs to make sure they describe this capability
2024-04-24 06:28:45 -05:00
1633004643 Bump actions/checkout from 4.1.2 to 4.1.3 (#12635) 2024-04-24 14:10:34 +08:00
fbb4fad7ac Bump crate-ci/typos from 1.20.9 to 1.20.10 (#12636) 2024-04-24 00:26:06 +00:00
c9bc0c7d3e Fix error message propagation on plugin call failure (#12632)
# Description

This should fix the sometimes failing wrong version test for
stress_internals.

The plugin interface state stores an error if some kind of critical
error happened, and this error should be propagated to any future
operations on the interface, but this wasn't being propagated to plugin
calls that were already waiting.

During plugin registration, the wrong version error needs to be received
as a response to the `get_signature()` to show up properly, but this
would only happen if `get_signature()` started after the `Hello` was
already received and processed. That would be a race condition, which
this commit solves.

cc @sholderbach - this should fix the CI issue

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-23 17:30:51 -05:00
eec8645b9c update to latest reedline 455b9a3 (#12630)
# Description

This PR updates to the latest main branch in the reedline repo in order
to test the latest reedline changes.

# 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-04-23 10:54:14 -05:00
1f4131532d Deprecate register and add plugin use (#12607)
# Description

Adds a new keyword, `plugin use`. Unlike `register`, this merely loads
the signatures from the plugin cache file. The file is configurable with
the `--plugin-config` option either to `nu` or to `plugin use` itself,
just like the other `plugin` family of commands. At the REPL, one might
do this to replace `register`:

```nushell
> plugin add ~/.cargo/bin/nu_plugin_foo
> plugin use foo
```

This will not work in a script, because `plugin use` is a keyword and
`plugin add` does not evaluate at parse time (intentionally). This means
we no longer run random binaries during parse.

The `--plugins` option has been added to allow running `nu` with certain
plugins in one step. This is used especially for the `nu_with_plugins!`
test macro, but I'd imagine is generally useful. The only weird quirk is
that it has to be a list, and we don't really do this for any of our
other CLI args at the moment.

`register` now prints a deprecation parse warning.

This should fix #11923, as we now have a complete alternative to
`register`.

# User-Facing Changes

- Add `plugin use` command
- Deprecate `register`
- Add `--plugins` option to `nu` to replace a common use of `register`

# Tests + Formatting

I think I've tested it thoroughly enough and every existing test passes.
Testing nu CLI options and alternate config files is a little hairy and
I wish there were some more generic helpers for this, so this will go on
my TODO list for refactoring.

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

# After Submitting

- [ ] Update plugins sections of book
- [ ] Release notes
2024-04-23 06:37:50 -05:00
5c7f7883c8 Add ErrSpan extension trait for Result (#12626)
# Description
This adds an extension trait to `Result` that wraps errors in `Spanned`,
saving the effort of calling `.map_err(|err| err.into_spanned(span))`
every time. This will hopefully make it even more likely that someone
will want to use a spanned `io::Error` and make it easier to remove the
impl for `From<io::Error> for ShellError` because that doesn't have span
information.
2024-04-23 10:39:55 +02:00
b0acc1d890 Avoid panic when pipe a variable to a custom command which have recursive call (#12491)
# Description
Fixes: #11351

And comment here is also fixed:
https://github.com/nushell/nushell/issues/11351#issuecomment-1996191537

The panic can happened if we pipe a variable to a custom command which
recursively called itself inside another block.

TBH, I think I figure out how it works to panic, but I'm not sure if
there is a potention issue if nushell don't mutate a block in such case.

# User-Facing Changes
Nan

# Tests + Formatting
Done

# After Submitting
Done

---------

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2024-04-23 06:10:35 +08:00
bed236362a bump reedline to latest commit point on main (#12621)
https://github.com/nushell/reedline/pull/781

This will allow others to test and make sure that this reedline fix
works on other terminals and platforms...
2024-04-22 11:48:30 -07:00
797a90520c Improve the "input and output are the same file" error text (#12619)
# Description
Continuing from #12601, this PR improves the error message help text and
adds an example to `collect`.
2024-04-22 09:00:38 -05:00
20bf3c587f Update ratatui to deduplicate syn in build (#12606)
`ratatui` introduced a dependency on `stability` which until recently
used `syn 1.x`, with this update all our crates in the default `cargo
build` path are using `syn 2.x`
2024-04-22 14:48:33 +02:00
bae6d694ca Refactor using ClosureEval types (#12541)
# Description
Adds two new types in `nu-engine` for evaluating closures: `ClosureEval`
and `ClosureEvalOnce`. This removed some duplicate code and centralizes
our logic for setting up, running, and cleaning up closures. For
example, in the future if we are able to reduce the cloning necessary to
run a closure, then we only have to change the code related to these
types.

`ClosureEval` and `ClosureEvalOnce` are designed with a builder API.
`ClosureEval` is used to run a closure multiple times whereas
`ClosureEvalOnce` is used for a one-shot closure.

# User-Facing Changes
Should be none, unless I messed up one of the command migrations.
Actually, this will fix any unreported environment bugs for commands
that didn't reset the env after running a closure.
2024-04-22 14:15:09 +08:00
83720a9f30 Make the same file error more likely to appear (#12601)
# Description
When saving to a file we currently try to check if the data source in
the pipeline metadata is the same as the file we are saving to. If so,
we create an error, since reading and writing to a file at the same time
is currently not supported/handled gracefully. However, there are still
a few instances where this error is not properly triggered, and so this
PR attempts to reduce these cases. Inspired by #12599.

# Tests + Formatting
Added a few tests.

# After Submitting
Some commands still do not properly preserve metadata (e.g., `str trim`)
and so prevent us from detecting this error.
2024-04-22 01:12:13 +00:00
a60381a932 Added commands for working with the plugin cache. (#12576)
# Description
This pull request provides three new commands:
`polars store-ls` - moved from `polars ls`. It provides the list of all
object stored in the plugin cache
`polars store-rm` - deletes a cached object
`polars store-get` - gets an object from the cache. 

The addition of `polars store-get` required adding a reference_count to
cached entries. `polars get` is the only command that will increment
this value. `polars rm` will remove the value despite it's count. Calls
to PolarsPlugin::custom_value_dropped will decrement the value.

The prefix store- was chosen due to there already being a `polars cache`
command. These commands were not made sub-commands as there isn't a way
to display help for sub commands in plugins (e.g. `polars store`
displaying help) and I felt the store- seemed fine anyways.

The output of `polars store-ls` now shows the reference count for each
object.

# User-Facing Changes
polars ls has now moved to polars store-ls

---------

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-21 19:43:43 -05:00
aad3ac11da update toolkit register pluginstoolkit add plugins (#12613)
# Description

Updates `toolkit.nu` to use `plugin add`.

There's no need to spawn a separate `nu` to do `register` anymore, since
`plugin add` is not a keyword
2024-04-21 18:23:12 -05:00
8b7696f4c1 stress_internals: exit(1) on io error (#12612)
# Description

The `stress_internals` tests can fail sometimes, but usually not on the
CI, because Nushell exits while the plugin is still trying to read or
maybe write something, leading to a broken pipe.

`nu-plugin` already exits with 1 without printing a message on a
protocol-level I/O error, so this just doing the same thing.

I think there's probably a way to correct the plugin handling so that we
wait for plugins to shut down before exiting and this doesn't happen,
but this is the quick fix in the meantime.
2024-04-21 23:48:09 +02:00
a900166e27 fix typo in the documentation of nuon::ToStyle (#12608)
follow-up to
- https://github.com/nushell/nushell/pull/12591

cc/ @fdncred 

# Description
there was a typo in the doc of `nuon::ToStyle`.

# User-Facing Changes

# Tests + Formatting

# After Submitting
2024-04-21 10:53:53 -05:00
2595f31541 Overhaul the plugin cache file with a new msgpack+brotli format (#12579)
# Description

- Plugin signatures are now saved to `plugin.msgpackz`, which is
brotli-compressed MessagePack.
- The file is updated incrementally, rather than writing all plugin
commands in the engine every time.
- The file always contains the result of the `Signature` call to the
plugin, even if commands were removed.
- Invalid data for a particular plugin just causes an error to be
reported, but the rest of the plugins can still be parsed

# User-Facing Changes

- The plugin file has a different filename, and it's not a nushell
script.
- The default `plugin.nu` file will be automatically migrated the first
time, but not other plugin config files.
- We don't currently provide any utilities that could help edit this
file, beyond `plugin add` and `plugin rm`
  - `from msgpackz`, `to msgpackz` could also help
- New commands: `plugin add`, `plugin rm`

# Tests + Formatting

Tests added for the format and for the invalid handling.

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

# After Submitting

- [ ] Check for documentation changes
- [ ] Definitely needs release notes
2024-04-21 07:36:26 -05:00
6cba7c6b40 Small refactor in cal (#12604)
Removed some unnecessary code found when looking at #12597

- **Simplify day of the week selection**
- **Use `Record` directly instead of `IndexMap`**
2024-04-21 10:41:26 +02:00
3b1d405b96 Remove the Value::Block case (#12582)
# Description
`Value` describes the types of first-class values that users and scripts
can create, manipulate, pass around, and store. However, `Block`s are
not first-class values in the language, so this PR removes it from
`Value`. This removes some unnecessary code, and this change should be
invisible to the user except for the change to `scope modules` described
below.

# User-Facing Changes
Breaking change: the output of `scope modules` was changed so that
`env_block` is now `has_env_block` which is a boolean value instead of a
`Block`.

# After Submitting
Update the language guide possibly.
2024-04-21 07:03:33 +02:00
5fd34320e9 add search_term "str extract" to parse command (#12600)
# Description

For a long time, I was searching for the `str extract` command to
extract regexes from strings. I often painfully used `str replace -r
'(.*)(pattern_to_find)(.*)' '$2'` for such purposes.
Only this morning did I realize that `parse` is what I needed for so
many times, which I had only used for parsing data in tables.
2024-04-21 07:01:42 +02:00
5e52bd77e0 Change cal --week-start examples + error message (#12597)
This PR fixes the example for `cal --week-start` and adds the list of
expected values to the error message.
2024-04-20 22:39:46 +02:00
cf8fcef9bf set the type of default NU_LIB_DIRS and NU_PLUGIN_DIRS to list<string> (#12573)
# Description
Fix: #12489

I believe the issue it's because the default value of `NU_LIB_DIRS` and
`NU_PLUGIN_DIRS` is a string, but it should be a list.

So if users don't set up these values in `env.nu`, we will get a
problem.
2024-04-20 10:04:41 -05:00
47867a58df Ab/version details (#12593)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

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

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

Add version details in `version`'s output. The intended use is for
third-party tools to be able to quickly check version numbers without
having to the parsing from `(version).version` or `$env.NU_VERSION`.

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

This adds 4 new values to the record from `version`:

```
...
│ major              │ 0                                               │
│ minor              │ 92                                              │
│ patch              │ 3                                               │
│ pre                │ a-value                                         │
...
```

`pre` is optional and won't be present most of the time I think.

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

I ran the new command using `cargo run -- -c version`:

```
╭────────────────────┬─────────────────────────────────────────────────╮
│ version            │ 0.92.3                                          │
│ major              │ 0                                               │
│ minor              │ 92                                              │
│ patch              │ 3                                               │
│ branch             │                                                 │
│ commit_hash        │                                                 │
│ build_os           │ macos-aarch64                                   │
│ build_target       │ aarch64-apple-darwin                            │
│ rust_version       │ rustc 1.77.2 (25ef9e3d8 2024-04-09)             │
│ rust_channel       │ 1.77.2-aarch64-apple-darwin                     │
│ cargo_version      │ cargo 1.77.2 (e52e36006 2024-03-26)             │
│ build_time         │ 2024-04-20 15:09:36 +02:00                      │
│ build_rust_channel │ release                                         │
│ allocator          │ mimalloc                                        │
│ features           │ default, sqlite, system-clipboard, trash, which │
│ installed_plugins  │                                                 │
╰────────────────────┴─────────────────────────────────────────────────╯
```

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

After this is merged I would like to write docs somewhere for scripts
writer to advise using these members instead of the string values. Where
should I put said docs ?
2024-04-20 10:01:51 -05:00
b274ec19fd improve nu --lsp command tooltips (#12589)
# Description

This PR improves the `nu --lsp` tooltips by using nu code blocks around
the examples and a few other places.

This is what it looks like in Zed.
![Screenshot 2024-04-19 at 8 20
53 PM](https://github.com/nushell/nushell/assets/343840/20d51dcc-f3b2-4f2b-9d43-5817dd3913df)

Here it is in Helix.

![image](https://github.com/nushell/nushell/assets/343840/a9e7d6b9-cd21-4a5a-9c88-9af17a2b2363)

This coloring is far from perfect, but it's what the tree-sitter-nu
queries generate.

# 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-04-20 07:16:38 -05:00
be5ed3290c add "to nuon" enumeration of possible styles (#12591)
# Description
in order to change the style of the _serialized_ NUON data,
`nuon::to_nuon` takes three mutually exclusive arguments, `raw: bool`,
`tabs: Option<usize>` and `indent: Option<usize>` 🤔
this begs to use an enumeration with all possible alternatives, right?

this PR changes the signature of `nuon::to_nuon` to use `nuon::ToStyle`
which has three variants
- `Raw`: no newlines
- `Tabs(n: usize)`: newlines and `n` tabulations as indent
- `Spaces(n: usize)`: newlines and `n` spaces as indent

# User-Facing Changes
the signature of `nuon::to_nuon` changes from
```rust
to_nuon(
    input: &Value,
    raw: bool,
    tabs: Option<usize>,
    indent: Option<usize>,
    span: Option<Span>,
) -> Result<String, ShellError>
```
to
```rust
to_nuon(
    input: &Value,
    style: ToStyle,
    span: Option<Span>
) -> Result<String, ShellError>
```

# Tests + Formatting

# After Submitting
2024-04-20 11:40:52 +02:00
187b87c61c Don't allow skip on external stream (#12559)
# Description
Close: #12514

# User-Facing Changes
`^ls | skip 1` will raise an error
```nushell
❯ ^ls | skip 1
Error: nu:🐚:only_supports_this_input_type

  × Input type not supported.
   ╭─[entry #1:1:2]
 1 │ ^ls | skip 1
   ·  ─┬   ──┬─
   ·   │     ╰── only list, binary or range input data is supported
   ·   ╰── input type: raw data
   ╰────
```

# Tests + Formatting
Sorry I can't add it because of the issue:
https://github.com/nushell/nushell/issues/12558

# After Submitting
Nan
2024-04-19 14:54:59 +00:00
f2169c8022 Switch plugin msgpack protocol to named format (#12580)
# Description
In conflict with the documentation, the msgpack serializer for plugins
is actually using the compact format, which doesn't name struct fields,
and is instead dependent on their ordering, rendering them as tuples.
This is not a good idea for a robust protocol even if it makes the
serialization and deserialization faster.

I expect this to have some impact on performance, but I think the
robustness is probably worth it.

Deserialization always accepts either format, so this shouldn't cause
too many incompatibilities.

# User-Facing Changes
This does technically change the protocol, but it makes it reflect the
documentation. It shouldn't break deserialization, so plugins shouldn't
necessarily need a recompile.

Performance is likely worse and I should benchmark the difference.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-19 07:50:51 -05:00
9fb59a6f43 Removed the polars dtypes command (#12577)
# Description
The polars dtype command is largerly redundant since the introduction of
the schema command. The schema command also has the added benefit that
it's output can be used as a parameter to other schema commands:

```nushell
[[a b]; [5 6] [5 7]] | polars into-df -s ($df | polars schema
```

# User-Facing Changes
`polars dtypes` has been removed. Users should use `polars schema`
instead.

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-19 07:01:47 -05:00
55edef5dda create nuon crate from from nuon and to nuon (#12553)
# Description
playing with the NUON format in Rust code in some plugins, we agreed
with the team it was a great time to create a standalone NUON format to
allow Rust devs to use this Nushell file format.

> **Note**
> this PR almost copy-pastes the code from
`nu_commands/src/formats/from/nuon.rs` and
`nu_commands/src/formats/to/nuon.rs` to `nuon/src/from.rs` and
`nuon/src/to.rs`, with minor tweaks to make then standalone functions,
e.g. remove the rest of the command implementations

### TODO
- [x] add tests
- [x] add documentation

# User-Facing Changes
devs will have access to a new crate, `nuon`, and two functions,
`from_nuon` and `to_nuon`
```rust
from_nuon(
    input: &str,
    span: Option<Span>,
) -> Result<Value, ShellError>
```
```rust
to_nuon(
    input: &Value,
    raw: bool,
    tabs: Option<usize>,
    indent: Option<usize>,
    span: Option<Span>,
) -> Result<String, ShellError>
```

# Tests + Formatting
i've basically taken all the tests from
`crates/nu-command/tests/format_conversions/nuon.rs` and converted them
to use `from_nuon` and `to_nuon` instead of Nushell commands
- i've created a `nuon_end_to_end` to run both conversions with an
optional middle value to check that all is fine

> **Note** 
> the `nuon::tests::read_code_should_fail_rather_than_panic` test does
give different results locally and in the CI...
> i've left it ignored with comments to help future us :)

# After Submitting
mention that in the release notes for sure!!
2024-04-19 13:54:16 +02:00
fac2f43aa4 Add an example Nushell plugin written in Nushell itself (#12574)
# Description

As suggested by @fdncred.

It's neat that this is possible, but the particularly useful part of
this is that we can actually
test it because it doesn't have any external dependencies, unlike the
python plugin.

Right now this just implements exactly the same behavior as the python
plugin, but we could have it
exercise a few more things.

Also fixes a couple of bugs:

- `.nu` plugins were not run with `nu --stdin`, so they couldn't take
input.
- `register` couldn't be called if `--no-config-file` was set, because
it would error on trying to
  update the plugin file.

# User-Facing Changes

- `nu_plugin_nu_example` plugin added.
- `register` now works in `--no-config-file` mode.

# Tests + Formatting
Tests added for `nu_plugin_nu_example`.

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

# After Submitting

- [ ] Add the version bump to the release script just like for python
2024-04-19 09:53:30 +03:00
6d2cb4382a Fix circular source causing Nushell to crash (#12262)
# Description

EngineState now tracks the script currently running, instead of the
parent directory of the script. This also provides an easy way to expose
the current running script to the user (Issue #12195).

Similarly, StateWorkingSet now tracks scripts instead of directories.
`parsed_module_files` and `currently_parsed_pwd` are merged into one
variable, `scripts`, which acts like a stack for tracking the current
running script (which is on the top of the stack).

Circular import check is added for `source` operations, in addition to
module import. A simple testcase is added for circular source.

<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

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


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

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

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

It shouldn't have any user facing changes.
2024-04-19 09:38:08 +03:00
351bff8233 fix(shell_integration): set window title on startup (#12569)
Should close #10833 — though I'd imagine that should have already been
closed.

# Description

Very minor tweak, but it was quite noticeable when using Zellij which
relies on OSC 2 to set pane titles. Before the change:

![image](https://github.com/nushell/nushell/assets/6251883/b944bbce-2040-4886-9955-3c5b57d368e9)

Note that the default `Pane #1` is still showing for the untouched
shell, but running a command like `htop` or `ls` correctly sets the
title during / afterwards.

After this PR:

![image](https://github.com/nushell/nushell/assets/6251883/dd513cfe-923c-450f-b0f2-c66938b0d6f0)

There are now no-longer any unset titles — even if the shell hasn't been
touched.

**As an aside:** I feel quite strongly that (at least OSC 2) shell
integration should be enabled by default, as it is for every other Linux
shell I've used, but I'm not sure which issues that caused that the
default config refers to? Which terminals are broken by shell
integration, and could some of the shell integrations be turned on by
default after splitting things into sub-options as suggested in #11301 ?

# User-Facing Changes

You'll just have shell integrations working from right after the shell
has been launched, instead of needing to run something first.

# Tests + Formatting

Not quite sure how to test this one? Are there any other tests that
currently exist for shell integration? I couldn't quite track them
down...

# After Submitting

Let me know if you think this needs any user-facing docs changes!
2024-04-18 20:30:37 -05:00
7fe2e60af7 add ability to set metadata (#12564)
# Description

This PR adds the ability to set metadata. This is especially useful for
activating LS_COLORS when using table literals.


![image](https://github.com/nushell/nushell/assets/343840/feef6433-f592-43ea-890a-38cb2df35686)

You can also set the filepath metadata, although I'm not really user how
useful this is. We may end up removing this option entirely.
```nushell
❯ "crates" | metadata set --datasource-filepath $'(pwd)/crates' | metadata
╭────────┬───────────────────────────────────╮
│ source │ /Users/fdncred/src/nushell/crates │
╰────────┴───────────────────────────────────╯
```

No file paths are checked. You could also do this.
```nushell
❯ "crates" | metadata set --datasource-filepath $'a/b/c/d/crates' | metadata
╭────────┬────────────────╮
│ source │ a/b/c/d/crates │
╰────────┴────────────────╯
```

The command name and parameter names are still WIP. We could change
them.

There are currently 3 kinds of metadata in nushell.
```rust
pub enum DataSource {
    Ls,
    HtmlThemes,
    FilePath(PathBuf),
}
```

I've skipped adding `HtmlThemes` because it seems to be specific to our
`to html` command only.
2024-04-19 09:03:59 +08:00
999dfdf936 Fix the error output in the python plugin to match LabeledError (#12575)
# Description

Forgot to update this after updating the format of `LabeledError`.

# Tests + Formatting

Not applicable to the python plugin.
2024-04-18 20:01:35 -05:00
cc7b5c5a26 Only mark collected dataframes as from_lazy=false when collect is called from the collect command. (#12571)
I had previously changed NuLazyFrame::collect to set the NuDataFrame's
from_lazy field to false to prevent conversion back to a lazy frame. It
appears there are cases where this should happen. Instead, I am only
setting from_lazy=false inside the `polars collect` command.

[Related discord
message](https://discord.com/channels/601130461678272522/1227612017171501136/1230600465159421993)

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-18 17:10:38 -05:00
9a265847e2 Box ImportPattern in Expr (#12568)
# Description
Adds a `Box` around the `ImportPattern` in `Expr` which decreases the
size of `Expr` from 152 to 64 bytes (and `Expression` from 216 to 128
bytes). This seems to speed up parsing a little bit according to the
benchmarks (main is top, PR is bottom):
```
benchmarks                       fastest       │ slowest       │ median        │ mean          │ samples │ iters
benchmarks                       fastest       │ slowest       │ median        │ mean          │ samples │ iters
├─ parser_benchmarks                           │               │               │               │         │
├─ parser_benchmarks                           │               │               │               │         │
│  ├─ parse_default_config_file  2.287 ms      │ 4.532 ms      │ 2.311 ms      │ 2.437 ms      │ 100     │ 100
│  ├─ parse_default_config_file  2.255 ms      │ 2.781 ms      │ 2.281 ms      │ 2.312 ms      │ 100     │ 100
│  ╰─ parse_default_env_file     421.8 µs      │ 824.6 µs      │ 494.3 µs      │ 527.5 µs      │ 100     │ 100
│  ╰─ parse_default_env_file     402 µs        │ 486.6 µs      │ 414.8 µs      │ 416.2 µs      │ 100     │ 100

```
2024-04-18 17:57:01 +02:00
b088f395dc Update crate feature flags (#12566)
# Description
Remove unused/effect-less features, make sure we show all relevant
features in `version`

# User-Facing Changes
- **Remove unused feature `wasi`**
- will cause failure to build should you enable it. Otherwise no effect
- **Include feat `system-clipboard` in `version`**
2024-04-18 16:33:41 +02:00
6ccd547d81 Add ListItem type for Expr::List (#12529)
# Description
This PR adds a `ListItem` enum to our set of AST types. It encodes the
two possible expressions inside of list expression: a singular item or a
spread. This is similar to the existing `RecordItem` enum. Adding
`ListItem` allows us to remove the existing `Expr::Spread` case which
was previously used for list spreads. As a consequence, this guarantees
(via the type system) that spreads can only ever occur inside lists,
records, or as command args.

This PR also does a little bit of cleanup in relevant parser code.
2024-04-18 13:21:05 +02:00
57b0c722c6 Upgrading nu-cmd-dataframe to polars 0.39 (#12554)
#Description
Upgrading nu-cmd-dataframe to polars 0.39

---------

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-17 12:50:17 -05:00
13160b3ec3 Replace subtraction of Instants and Durations with saturating subtractions (#12549)
# Description
Duration can not be negative, and an underflow causes a panic.

This should fix #12539 as from what I can tell that bug was caused in
`nu-explore:📟:events` from subtracting durations, but I figured
this might be more widespread, and saturating to zero generally makes
sense.

I also added the relevant clippy lint to try to prevent this from
happening in the future. I can't think of a reason we would ever want to
subtract durations without checking first.

cc @fdncred

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-17 07:25:16 -05:00
410f3c5c8a Upgrading nu_plugin_polars to polars 0.39.1 (#12551)
# Description
Upgrading nu_plugin_polars to polars 0.39.1

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-17 06:35:09 -05:00
b296d6ee3c Improve safety of get_unchecked_str in nu_system::macos (#12550)
# Description
The implementation of this function had a few issues before:

- It didn't check that the `cp` pointer is actually ahead of the `start`
pointer, so `len` could potentially underflow and wrap around, which
would be a violation of memory safety
- It used `Vec::from_raw_parts` even though the buffer is borrowed, not
owned. Although `std::mem::forget` is used later to ensure the
destructor doesn't run, there is a risk that the destructor would run if
a panic happened during `String::from_utf8_unchecked`, which would lead
to a `free()` of a pointer we don't own
2024-04-17 17:10:05 +08:00
9a739d9f0d Bump rmp-serde from 1.1.2 to 1.2.0 (#12547)
Bumps [rmp-serde](https://github.com/3Hren/msgpack-rust) from 1.1.2 to
1.2.0.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/3Hren/msgpack-rust/commits/rmp-serde/v1.2.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rmp-serde&package-manager=cargo&previous-version=1.1.2&new-version=1.2.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-04-17 16:44:54 +08:00
8cba6678b3 Bump crate-ci/typos from 1.20.7 to 1.20.9 (#12543) 2024-04-17 01:26:46 +00:00
055aae9f5c Impl FusedIterator for record iterators (#12542)
This is good practice as all our iterators will never return a value
after reaching `None`

The benefit should be minimal as only `Iterator::fuse` is directly
specialized and itself rarely used (sometimes in `itertools` adaptors)

Thus it is mostly a documentation thing
2024-04-17 00:34:16 +02:00
a67dad3d15 Minor housekeeping in the parser (#12540)
- **Move lone `check_name` to the alias place**
- **Restrict visibility of `check_call` helper**
2024-04-17 00:33:50 +02:00
43814dcb0f use abbreviated string instead of debug string for DatetimeParseErrors (#12517)
Resolves #12444. Prevents debug string from being printed out.

---------

Co-authored-by: sholderbach <sholderbach@users.noreply.github.com>
2024-04-16 23:19:03 +02:00
cc781a1ecd Make group-by return errors in closure (#12508)
# Description
When a closure if provided to `group-by`, errors that occur in the
closure are currently ignored. That is, `group-by` will fall back and
use the `"error"` key if an error occurs. For example, the code snippet
below will group all `ls` entries under the `"error"` column.
```nushell
ls | group-by { get nope } 
```

This PR changes `group-by` to instead bubble up any errors triggered
inside the closure. In addition, this PR also does some refactoring and
cleanup inside `group-by`.

# User-Facing Changes
Errors are now returned from the closure provided to `group-by` instead
of falling back to the `"error"` group/key.
2024-04-16 21:52:21 +02:00
a7a5ec31be Fixing NuLazyFrame/NuDataFrame conversion issues (#12538)
# Description

@maxim-uvarov brought up another case where converting back and forth
between eager and lazy dataframes was not working correctly:

```
> [[a b]; [6 2] [1 4] [4 1]] | polars into-lazy | polars append -c ([[a b]; [6 2] [1 4] [4 1]] | polars into-df)
Error: nu:🐚:cant_convert

  × Can't convert to NuDataFrame.
   ╭─[entry #1:1:49]
 1 │ [[a b]; [6 2] [1 4] [4 1]] | polars into-lazy | polars append -c ([[a b]; [6 2] [1 4] [4 1]] | polars into-df)
   ·                                                 ──────┬──────
   ·                                                       ╰── can't convert NuLazyFrameCustomValue to NuDataFrame
   ╰────
```

This pull request fixes this case and glaringly obvious similar cases I
could find.

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-16 11:16:37 -05:00
48e4448e55 Add a panic unwind handler during plugin calls (#12526)
# Description
If a panic happens during a plugin call, because it always happens
outside of the main thread, it currently just hangs Nushell because the
plugin stays running without ever producing a response to the call.

This adds a panic handler that calls `exit(1)` after the unwind finishes
to the plugin runner. The panic error is still printed to stderr as
always, and waiting for the unwind to finish helps to ensure that
anything on the stack with `Drop` behavior that needed to run still
runs, at least on that thread.

# User-Facing Changes
Panics now look like this, which is what they looked like before the
plugin behavior was moved to a separate thread:

```
thread 'plugin runner (primary)' panicked at crates/nu_plugin_example/src/commands/main.rs:45:9:
Test panic
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Error: nu:🐚:plugin_failed_to_decode

  × Plugin failed to decode: Failed to receive response to plugin call

```

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-16 15:00:32 +00:00
e6fbf7d01d Unify working_set.error usage. (#12531)
# Description
A little refactor that use `working_set.error` rather than
`working_set.parse_errors.push`, which is reported here:
https://github.com/nushell/nushell/pull/12238

> Inconsistent error reporting. Usage of both working_set.error() and
working_set.parse_errors.push(). Using ParseError::Expected for an
invalid variable name when there's ParseError::VariableNotValid (from
parser.rs:5237). Checking variable names manually when there's
is_variable() (from parser.rs:2905).

# User-Facing Changes
NaN

# Tests + Formatting
Done
2024-04-16 15:47:10 +02:00
62555c997e remove useless path.rs (#12534)
# Description
Sorry for introducing a useless file in previous pr, I have renamed it
from `glob.rs` to `path.rs`, but forget removing it.
2024-04-16 06:41:43 -05:00
03317ff92e Python plugin: remove unnecessary fields from signature (#12533)
# Description
Remove a couple of legacy fields (`input_type`, `output_type`), and
`var_id` which is optional and not required for deserialization.

I think until I document this in the plugin protocol ref, most people
will probably be using this example to get started, so it should be as
correct as possible

# After Submitting
- [ ] TODO: document `Signature` in plugin protocol reference
2024-04-16 06:40:04 -05:00
1661bb68f9 Cleaning up to_pipe_line_data and cache_and_to_value, making them part of CustomValueSupport (#12528)
# Description

This is just some cleanup. I moved to_pipeline_data and to_cache_value
to the CustomValueSupport trait, where I should've put them to begin
with.

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-16 06:35:52 -05:00
c9e9b138eb Improve with-env robustness (#12523)
# Description
Work for #7149

- **Error `with-env` given uneven count in list form**
- **Fix `with-env` `CantConvert` to record**
- **Error `with-env` when given protected env vars**
- **Deprecate list/table input of vars to `with-env`**
- **Remove examples for deprecated input**

# User-Facing Changes

## Deprecation of the following forms

```
> with-env [MYENV "my env value"] { $env.MYENV }
my env value

> with-env [X Y W Z] { $env.X }
Y

> with-env [[X W]; [Y Z]] { $env.W }
Z
```

## recommended standardized form

```
# Set by key-value record
> with-env {X: "Y", W: "Z"} { [$env.X $env.W] }
╭───┬───╮
│ 0 │ Y │
│ 1 │ Z │
╰───┴───╯
```

## (Side effect) Repeated definitions in an env shorthand are now
disallowed

```
> FOO=bar FOO=baz $env
Error: nu:🐚:column_defined_twice

  × Record field or table column used twice: FOO
   ╭─[entry #1:1:1]
 1 │ FOO=bar FOO=baz $env
   · ─┬─     ─┬─
   ·  │       ╰── field redefined here
   ·  ╰── field first defined here
   ╰────
```
2024-04-16 19:08:58 +08:00
5f818eaefe Ensure that lazy frames converted via to-lazy are not converted back to eager frames later in the pipeline. (#12525)
# Description
@maxim-uvarov discovered the following error:
```
> [[a b]; [6 2] [1 4] [4 1]] | polars into-lazy | polars sort-by a | polars unique --subset [a]
Error:   × Error using as series
   ╭─[entry #1:1:68]
 1 │ [[a b]; [6 2] [1 4] [4 1]] | polars into-lazy | polars sort-by a | polars unique --subset [a]
   ·                                                                    ──────┬──────
   ·                                                                          ╰── dataframe has more than one column
   ╰────
 ```
 
During investigation, I discovered the root cause was that the lazy frame was incorrectly converted back to a eager dataframe. In order to keep this from happening, I explicitly set that the dataframe did not come from an eager frame. This causes the conversion logic to not attempt to convert the dataframe later in the pipeline.

---------

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-15 18:29:42 -05:00
078ba5aabe Disallow setting the PWD via load-env input (#12522)
# Description
Fixes #12520


# User-Facing Changes
Breaking change:

Any operation parsing input with `PWD` to set the environment will now
fail with `ShellError::AutomaticEnvVarSetManually`

Furthermore transactions containing the special env-vars will be
rejected before executing any modifications. Prevoiusly this was
changing valid variables before while leaving valid variables after the
violation untouched.

## `PWD` handling.

Now failing

```
{PWD: "/trolling"} | load-env
``` 

already failing 

```
load-env {PWD: "/trolling"}
``` 

## Error management



```
> load-env {MY_VAR1: foo, PWD: "/trolling", MY_VAR2: bar}
Error: nu:🐚:automatic_env_var_set_manually

  × PWD cannot be set manually.
   ╭─[entry #1:1:2]
 1 │  load-env {MY_VAR1: foo, PWD: "/trolling", MY_VAR2: bar}
   ·  ────┬───
   ·      ╰── cannot set 'PWD' manually
   ╰────
  help: The environment variable 'PWD' is set automatically by Nushell and cannot be set manually.
```

### Before:
```
> $env.MY_VAR1
foo
> $env.MY_VAR2
Error: nu:🐚:name_not_found
....
```
### After:
```
> $env.MY_VAR1
Error: nu:🐚:name_not_found
....
> $env.MY_VAR2
Error: nu:🐚:name_not_found
....
```

# After Submitting
We need to check if any integrations rely on this hack.
2024-04-15 21:09:58 +02:00
c06ef201b7 Local socket mode and foreground terminal control for plugins (#12448)
# Description

Adds support for running plugins using local socket communication
instead of stdio. This will be an optional thing that not all plugins
have to support.

This frees up stdio for use to make plugins that use stdio to create
terminal UIs, cc @amtoine, @fdncred.

This uses the [`interprocess`](https://crates.io/crates/interprocess)
crate (298 stars, MIT license, actively maintained), which seems to be
the best option for cross-platform local socket support in Rust. On
Windows, a local socket name is provided. On Unixes, it's a path. The
socket name is kept to a relatively small size because some operating
systems have pretty strict limits on the whole path (~100 chars), so on
macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash
includes the plugin filename and timestamp to be unique enough.

This also adds an API for moving plugins in and out of the foreground
group, which is relevant for Unixes where direct terminal control
depends on that.

TODO:

- [x] Generate local socket path according to OS conventions
- [x] Add support for passing `--local-socket` to the plugin executable
instead of `--stdio`, and communicating over that instead
- [x] Test plugins that were broken, including
[amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore)
- [x] Automatically upgrade to using local sockets when supported,
falling back if it doesn't work, transparently to the user without any
visible error messages
  - Added protocol feature: `LocalSocket`
- [x] Reset preferred mode to `None` on `register`
- [x] Allow plugins to detect whether they're running on a local socket
and can use stdio freely, so that TUI plugins can just produce an error
message otherwise
  - Implemented via `EngineInterface::is_using_stdio()`
- [x] Clean up foreground state when plugin command exits on the engine
side too, not just whole plugin
- [x] Make sure tests for failure cases work as intended
  - `nu_plugin_stress_internals` added

# User-Facing Changes
- TUI plugins work
- Non-Rust plugins could optionally choose to use this
- This might behave differently, so will need to test it carefully
across different operating systems

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
- [ ] Document local socket option in plugin contrib docs
- [ ] Document how to do a terminal UI plugin in plugin contrib docs
- [ ] Document: `EnterForeground` engine call
- [ ] Document: `LeaveForeground` engine call
- [ ] Document: `LocalSocket` protocol feature
2024-04-15 18:28:18 +00:00
67e7eec7da Add Record::into_columns (#12324)
# Description
Add `Record::into_columns` to complement `Record::columns` and
`Record::into_values`.
2024-04-14 22:43:47 +02:00
af72a18785 Improve error messages for plugin protocol by removing #[serde(untagged)] (#12510)
# Description

In the plugin protocol, I had used `#[serde(untagged)]` on the `Stream`
variant to make it smaller and include all of the stream messages at the
top level, but unfortunately this causes serde to make really unhelpful
errors if anything fails to decode anywhere:

```
Error: nu:🐚:plugin_failed_to_decode

  × Plugin failed to decode: data did not match any variant of untagged enum PluginOutput
```

If you are trying to develop something using the plugin protocol
directly, this error is incredibly unhelpful. Even as a user, this
basically just says 'something is wrong'. With this change, the errors
are much better:

```
Error: nu:🐚:plugin_failed_to_decode

  × Plugin failed to decode: unknown variant `PipelineDatra`, expected one of `Error`, `Signature`, `Ordering`, `PipelineData` at line 2 column 37
```

The only downside is it means I have to duplicate all of the
`StreamMessage` variants manually, but there's only 4 of them and
they're small.

This doesn't actually change the protocol at all - everything is still
identical on the wire.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-14 15:55:18 +00:00
50b2dac8d7 Set Up Config in LSP Mode (#12454)
# Description

When starting the LSP server, the configuration file and environment
file are used to configure the LSP engine unless --no-config-file is
provided.

This PR provides an improvement that is related to #10794 

CC: @fdncred
2024-04-14 07:32:30 -05:00
2ae9ad8676 Copy-on-write for record values (#12305)
# Description
This adds a `SharedCow` type as a transparent copy-on-write pointer that
clones to unique on mutate.

As an initial test, the `Record` within `Value::Record` is shared.

There are some pretty big wins for performance. I'll post benchmark
results in a comment. The biggest winner is nested access, as that would
have cloned the records for each cell path follow before and it doesn't
have to anymore.

The reusability of the `SharedCow` type is nice and I think it could be
used to clean up the previous work I did with `Arc` in `EngineState`.
It's meant to be a mostly transparent clone-on-write that just clones on
`.to_mut()` or `.into_owned()` if there are actually multiple
references, but avoids cloning if the reference is unique.

# User-Facing Changes
- `Value::Record` field is a different type (plugin authors)

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
- [ ] use for `EngineState`
- [ ] use for `Value::List`
2024-04-14 01:42:03 +00:00
b508d1028c Fixes #12482 by pointing help links for ndjson to a non-spam source (take 2) (#12509)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

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

* Fixes #12482
* Initial PR failed due to CI issues at the time. Subsequent rebase
failed, so creating new PR.

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

Use https://github.com/ndjson/ndjson-spec for help links instead of
former spam site

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

Link changed for `help to ndjson` and `help from ndjson`.

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

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


# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2024-04-13 18:59:43 -05:00
040f10e19d Added a PluginTest method that will call custom_value_to_base_value (#12502)
# Description

Added a method for getting the base value for a PluginCustomValue. 

cc: @devyn

---------

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-13 13:03:44 -05:00
10a9a17b8c Two consecutive calls to into-lazy should not fail (#12505)
# Description

From @maxim-uvarov's
[post](https://discord.com/channels/601130461678272522/1227612017171501136/1228656319704203375).

When calling `to-lazy` back to back in a pipeline, an error should not
occur:

```
> [[a b]; [6 2] [1 4] [4 1]] | polars into-lazy | polars into-lazy
Error: nu:🐚:cant_convert

  × Can't convert to NuDataFrame.
   ╭─[entry #1:1:30]
 1 │ [[a b]; [6 2] [1 4] [4 1]] | polars into-lazy | polars into-lazy
   ·                              ────────┬───────
   ·                                      ╰── can't convert NuLazyFrameCustomValue to NuDataFrame
   ╰────
 ```

This pull request ensures that custom value's of NuLazyFrameCustomValue are properly converted when passed in.

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-13 13:00:46 -05:00
b9dd47ebb7 Polars 0.38 upgrade (#12506)
# Description
Polars 0.38 upgrade for both the dataframe crate and the polars plugin.

---------

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-13 13:00:04 -05:00
211d9c685c Fix clippy lint (#12504)
Just fixes a clippy lint.
2024-04-13 16:19:32 +00:00
0110345755 making ls and du supports rest parameters. (#12327)
# Description
Close: #12147
Close: #11796 

About the change: it make pattern handling into a function:
`ls_for_one_pattern`(for ls), `du_for_one_pattern`(for du). Then
iterates on user input pattern, call these core function, and chaining
these iterator to one pipelinedata.
2024-04-13 15:03:17 +00:00
56cdee1fd8 Refactor first and last (#12478)
# Description

- Refactors `first` and `last` using `Vec::truncate` and `Vec::drain`.
- `std::mem::take` was also used to eliminate a few `Value` clones.
- The `NeedsPositiveValue` error now uses the span of the `rows`
argument instead of the call head span.
- `last` now errors on an empty stream to match `first` which does
error.
-  Made metadata preservation more consistent.

# User-Facing Changes
Breaking change: `last` now errors on an empty stream to match `first`
which does error.
2024-04-13 14:58:54 +00:00
1bded8572c Ensure that two columns named index don't exist when converting a Dataframe to a nu Value. (#12501)
# Description
@maxim-uvarov discovered an issue with the current implementation. When
executing [[index a]; [1 1]] | polars into-df, a plugin_failed_to_decode
error occurs. This happens because a Record is created with two columns
named "index" as an index column is added during conversion. This pull
request addresses the problem by not adding an index column if there is
already a column named "index" in the dataframe.

---------

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-13 06:33:29 -05:00
f975c9923a Handle relative paths correctly on polars to-(parquet|jsonl|arrow|etc) commands (#12486)
# Description

All polars commands that output a file were not handling relative paths
correctly.

A command like
``` [[a b]; [6 2] [1 4] [4 1]] | polars into-df | polars to-parquet foo.json``` 
was outputting the foo.json to the directory of the plugin executable. 

This pull request pulls in nu-path and using it for resolving the file paths.

Related discussion
https://discord.com/channels/601130461678272522/1227612017171501136/1227889870358183966

# User-Facing Changes
None

# Tests + Formatting
Done, added tests for each of the polars to-* commands.

---------

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-12 19:30:37 -05:00
b7fb0af967 bump nushell to latest reedline (#12497)
# Description

This bumps nushell to the latest reedline main brach which includes the
new bashism !term.

# 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-04-12 16:56:40 -04:00
3a11b9d69d Also use old mac workers for plugins job (#12495)
Repeat from #12493

Now we see failures on main
2024-04-12 15:43:36 -04:00
50fb8243c8 Added a short flag -c to polars append --col (#12487)
# Description
`dfr append --col` had a short version -c. This polar requests adds the
short flag back.

Reference Conversation:
https://discord.com/channels/601130461678272522/1227612017171501136/1227902980628676688

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-12 10:55:36 -05:00
741e3c3d8f Return value instead of stream from kill (#12480)
# Description
The `kill` command returns a stream with a single value. This PR changes
it to simply return the value.

# User-Facing Changes
Technically a breaking change.
2024-04-12 10:44:27 -05:00
3eb9c2a565 drop refactor (#12479)
# Description
Refactors `drop` using `Vec::truncate` and adds a `NeedsPositiveValue`
error.

# User-Facing Changes
Breaking change: `drop` now errors if the number of rows/columns is
negative.
2024-04-12 10:44:00 -05:00
de41345bf2 better logging for shell_integration ansi escapes + better plugin perf logging (#12494)
# Description

This PR just adds better logging for shell_integration and tweaks the
ansi escapes so they're closer to where the action happens. I also added
some perf log entries to help better understand plugin file load and
eval performance.

# 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-04-12 10:11:41 -05:00
b9c2f9ee56 displaying span information, creation time, and size with polars ls (#12472)
# Description
`polars ls` is already different that `dfr ls`. Currently it just shows
the cache key, columns, rows, and type. I have added:
- creation time
- size
- span contents
-  span start and end

<img width="1471" alt="Screenshot 2024-04-10 at 17 27 06"
src="https://github.com/nushell/nushell/assets/56345/545918b7-7c96-4c25-bc01-b9e2b659a408">

# Tests + Formatting
Done

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-12 09:23:46 -05:00
872945ae8e Bump version to 0.92.3 (#12476) 2024-04-12 08:00:43 -05:00
cd8f04041c Switch the CI to use macos-13 instead of macos-latest, due to failures (#12493)
# Description

The CI has been failing lately on macOS, most likely because it's
running out of memory now that
the `polars` plugin has been included and it's quite large on top of
`dfr`.

This PR just makes use use the `macos-13` runner instead of
`macos-latest` because the latter has
only half as much RAM because it's running on the Apple M1 platform.

---------

Co-authored-by: sholderbach <sholderbach@users.noreply.github.com>
2024-04-12 13:37:35 +02:00
3a39e3df22 Bump our Rust version to stable (#12471)
This was prompted by CVE-2024-24576

- https://nvd.nist.gov/vuln/detail/CVE-2024-24576
- https://blog.rust-lang.org/2024/04/09/cve-2024-24576.html
-
https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/

Affected is launching commands on Windows with arbitrary arguments,
which is the case for Nushell's external invocation on Windows

Rust has fixed this quoting vulnerability in 1.77.2 (latest stable at
time of commit)

We will thus use this version for our builds and recommend all our
packaging/distribution maintainers to use this version of Rust when
building Nushell.
2024-04-10 23:41:38 +02:00
39156930f5 fix std log (#12470)
related to
- https://github.com/nushell/nushell/pull/12196

# Description
while i'm 100% okey with the original intent behind
https://github.com/nushell/nushell/pull/12196, i think the PR did
introduce two unintended things:
- extra parentheses that make the `log.nu` module look like Lisp lol
- a renaming of the `NU_LOG_LEVEL` environment variable to
`NU_log-level`. this breaks previous usage of `std log` and, as it's not
mentionned at all in the PR, i thought it was not intentional 😋

# User-Facing Changes
users can now control `std log` with `$env.NU_LOG_LEVEL`

# Tests + Formatting
the "log" tests have been fixed as well.

# After Submitting
2024-04-10 17:30:58 -04:00
83674909f1 Lex whitespace in input-output types. (#12339)
# Description
Fixes #12264.

# User-Facing Changes


Multiple input-output types can break across lines like command params.

# Tests + Formatting

 E2E parser tests
2024-04-10 16:28:54 +02:00
18ddf95d44 Force timeit to not capture stdout (#12465)
# Description
Fixes:  #11996

After this change `let t = timeit ^ls` will list current directory to
stdout.
```
❯ let t = timeit ^ls
CODE_OF_CONDUCT.md      Cargo.lock              Cross.toml              README.md               aaa                     benches                 devdocs                 here11                  scripts                 target                  toolkit.nu              wix
CONTRIBUTING.md         Cargo.toml              LICENSE                 a.txt                   assets                  crates                  docker                  rust-toolchain.toml     src                     tests                   typos.toml
```

If user don't want such behavior, he can redirect the stdout to `std
null-stream` easily
```
> use std
> let t = timeit { ^ls o> (std null-device) }
```

# User-Facing Changes
NaN

# Tests + Formatting
Done

# After Submitting
Nan

---------

Co-authored-by: Ian Manske <ian.manske@pm.me>
2024-04-10 13:31:29 +00:00
81c61f3243 Showing full help when running the polars command (#12462)
Displays the full help message for all sub commands.

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-10 07:26:33 -05:00
efc1cfa939 Move dataframes support to a plugin (#12220)
WIP

This PR covers migration crates/nu-cmd-dataframes to a new plugin
./crates/nu_plugin_polars

## TODO List

Other:
- [X] Fix examples
- [x] Fix Plugin Test Harness
- [X] Move Cache to Mutex<BTreeMap>
- [X] Logic for disabling/enabling plugin GC based off whether items are
cached.
- [x] NuExpression custom values
- [X] Optimize caching (don't cache every object creation). 
- [x] Fix dataframe operations (in NuDataFrameCustomValue::operations)
- [x] Added plugin_debug! macro that for checking an env variable
POLARS_PLUGIN_DEBUG

Fix duplicated commands:
- [x] There are two polars median commands, one for lazy and one for
expr.. there should only be one that works for both. I temporarily
called on polars expr-median (inside expressions_macros.rs)
- [x] polars quantile (lazy, and expr). the expr one is temporarily
expr-median
- [x] polars is-in (renamed one series-is-in)

Commands:
- [x] AppendDF
- [x] CastDF
- [X] ColumnsDF
- [x] DataTypes
- [x] Summary
- [x] DropDF
- [x] DropDuplicates
- [x] DropNulls
- [x] Dummies
- [x] FilterWith
- [X] FirstDF
- [x] GetDF
- [x] LastDF
- [X] ListDF
- [x] MeltDF
- [X] OpenDataFrame
- [x] QueryDf
- [x] RenameDF
- [x] SampleDF
- [x] SchemaDF
- [x] ShapeDF
- [x] SliceDF
- [x] TakeDF
- [X] ToArrow
- [x] ToAvro
- [X] ToCSV
- [X] ToDataFrame
- [X] ToNu
- [x] ToParquet
- [x] ToJsonLines
- [x] WithColumn
- [x] ExprAlias
- [x] ExprArgWhere
- [x] ExprCol
- [x] ExprConcatStr
- [x] ExprCount
- [x] ExprLit
- [x] ExprWhen
- [x] ExprOtherwise
- [x] ExprQuantile
- [x] ExprList
- [x] ExprAggGroups
- [x] ExprCount
- [x] ExprIsIn
- [x] ExprNot
- [x] ExprMax
- [x] ExprMin
- [x] ExprSum
- [x] ExprMean
- [x] ExprMedian
- [x] ExprStd
- [x] ExprVar
- [x] ExprDatePart
- [X] LazyAggregate
- [x] LazyCache
- [X] LazyCollect
- [x] LazyFetch
- [x] LazyFillNA
- [x] LazyFillNull
- [x] LazyFilter
- [x] LazyJoin
- [x] LazyQuantile
- [x] LazyMedian
- [x] LazyReverse
- [x] LazySelect
- [x] LazySortBy
- [x] ToLazyFrame
- [x] ToLazyGroupBy
- [x] LazyExplode
- [x] LazyFlatten
- [x] AllFalse
- [x] AllTrue
- [x] ArgMax
- [x] ArgMin
- [x] ArgSort
- [x] ArgTrue
- [x] ArgUnique
- [x] AsDate
- [x] AsDateTime
- [x] Concatenate
- [x] Contains
- [x] Cumulative
- [x] GetDay
- [x] GetHour
- [x] GetMinute
- [x] GetMonth
- [x] GetNanosecond
- [x] GetOrdinal
- [x] GetSecond
- [x] GetWeek
- [x] GetWeekDay
- [x] GetYear
- [x] IsDuplicated
- [x] IsIn
- [x] IsNotNull
- [x] IsNull
- [x] IsUnique
- [x] NNull
- [x] NUnique
- [x] NotSeries
- [x] Replace
- [x] ReplaceAll
- [x] Rolling
- [x] SetSeries
- [x] SetWithIndex
- [x] Shift
- [x] StrLengths
- [x] StrSlice
- [x] StrFTime
- [x] ToLowerCase
- [x] ToUpperCase
- [x] Unique
- [x] ValueCount

---------

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-09 19:31:43 -05:00
cbbccaa722 Bump crate-ci/typos from 1.20.3 to 1.20.7 (#12456) 2024-04-10 00:28:30 +00:00
d735607ac8 Isolate tests from user config (#12437)
# Description
This is an attempt to isolate the unit tests from whatever might be in
the user's config. If the
user's config is broken in some way or incompatible with this version
(for example, especially if
there are plugins that aren't built for this version), tests can
spuriously fail.

This makes tests more reliably pass the same way they would on CI even
if the user has config, and
should also make them run faster.

I think this is _good enough_, but I still think we should have a
specific config dir env variable for nushell specifically (rather than
having to use `XDG_CONFIG_HOME`, which would mess with other things) and
then we can just have `nu-test-support` set that to a temporary dir
containing the shipped default config files.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-10 06:27:46 +08:00
d7ba8872bf Rename IoStream to OutDest (#12433)
# Description
I spent a while trying to come up with a good name for what is currently
`IoStream`. Looking back, this name is not the best, because it:
1. Implies that it is a stream, when it all it really does is specify
the output destination for a stream/pipeline.
2. Implies that it handles input and output, when it really only handles
output.

So, this PR renames `IoStream` to `OutDest` instead, which should be
more clear.
2024-04-09 16:48:32 +00:00
6536fa5ff7 Ensure currently_parsed_cwd is set for config files (#12338)
<!--
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.
-->

Fixes #7849, #11465 based on @kubouch's suggestion in
https://github.com/nushell/nushell/issues/11465#issuecomment-1883847806.

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

Can source files relative to `env.nu` or `config.nu` like in #6150.

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

Adds test that previously failed.

# 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-04-09 10:06:41 -04:00
14b0ff3f05 Add --no-newline option to nu (#12410)
# Description
I have `nu` set as my shell in my editor, which allows me to easily pipe
selections of text to things like `str pascal-case` or even more complex
string operation pipelines, which I find super handy. However, the only
annoying thing is that I pretty much always have to add `| print -n` at
the end, because `nu` adds a newline when it prints the resulting value.

This adds a `--no-newline` option to stop that from happening, and then
you don't need to pipe to `print -n` anymore, you can just have your
shell command for your editor contain that flag.

# User-Facing Changes
- Add `--no-newline` command line option

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-09 10:04:00 -04:00
00b3a07efe Add GetSpanContents engine call (#12439)
# Description
This allows plugins to view the source code of spans.

Requested by @ayax79 for implementing `polars ls`. Note that this won't
really help you find the location of the span. I'm planning to add
another engine call that will return information more similar to what
shows up in the miette diagnostics, with filename / line number / some
context, but I'll want to refactor some of the existing logic to make
that happen, so it was easier to just do this first. I hope this is
enough to at least have something somewhat useful show up for `polars
ls`.

# User-Facing Changes
- Example plugin: added `example view span` command

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
- [ ] Add to plugin protocol reference
2024-04-09 10:02:17 -04:00
9a2a6ab52c Update MSRV following rust-toolchain.toml (#12455)
Also update the `rust-version` in `Cargo.toml` following the update to
`rust-toolchain.toml` in #12258

# Testing

Added a CI check to verify any future PRs trying to update one will also
have to update the other. (using `std-lib-and-python-virtualenv` job as
this already includes a fresh `nu` binary for a little toml munching
script)
2024-04-09 08:25:45 -04:00
40f72e80c3 try to be a bit more precise with repl logging (#12449)
# Description

This PR tries to be a bit more precise with the repl logging when
starting nushell with `nu --log-level debug`. It adds a few more `perf`
lines and changes some of the text of others.

# 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-04-08 05:36:54 -05:00
56c9587cc3 Bump similar from 2.4.0 to 2.5.0 (#12375) 2024-04-07 21:50:11 +00:00
625a9144cf Bump h2 from 0.3.24 to 0.3.26 (#12413) 2024-04-07 21:48:18 +00:00
773dafa8ac Fix negative value file size for "into filesize" (issue #12396) (#12443)
# Description
Add support for using negative values file size for `into filesize`.
This will help in sorting the file size if negative values are also
passed.

**Before**

![image](https://github.com/nushell/nushell/assets/43441496/e115b4b3-7526-4379-8dc0-f4f4e44839a1)
**After**

![image](https://github.com/nushell/nushell/assets/43441496/4a75fb40-ebe6-46eb-b9d2-55f37db7a6fa)

# User-Facing Changes
- User can now sort negative filesize also

# Tests + Formatting
- 🟢 toolkit fmt
- 🟢 toolkit clippy
- 🟢 toolkit test
- 🟢 toolkit test stdlib

# After Submitting

---------

Co-authored-by: Priyank Singh <priyank.singh@soroco.com>
2024-04-07 16:50:11 +00:00
e234f3ea7b Fix typo in help stor import (#12442)
Changed `export` for `import`

<!--
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
`help stor import` showed a help string that was probably copy-pasted
from `stor export`
<!--
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
Now `help stor import` shows a correct description of the operation that
it is doing
<!-- 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-04-07 08:09:24 -05:00
67c8b0db69 Mention print in the echo help text (#12436)
# Description
Edits the `echo` help text to mention the `print` command.

---------

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2024-04-06 20:24:00 -05:00
6b4cbe7a98 explain refactor (#12432)
# Description
Refactors the `explain` command. Part of this is fixing the `type`
column which used to always be `string` for each row.
2024-04-06 18:55:22 -05:00
70520000d2 Use nu-cmd-lang default context for plugin tests (#12434)
# Description
@ayax79 added `nu-cmd-lang` as a dep for `nu-plugin-test-support` in
order to get access to `let`. Since we have the dep anyway now, we might
as well just add all of the lang commands - there aren't very many of
them and it would be less confusing than only `let` working.

# User-Facing Changes
- Can use some more core nu language features in plugin tests, like
loops and `do`

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
- [ ] Might need to change something about the plugin testing section of
the book, since I think it says something about there only being the
plugin command itself available
2024-04-06 23:28:08 +00:00
01c79bbefc Implement De-/Serialize for Record manually (#12365)
# Description
This decouples the serialized representation of `Record` from its
internal implementation. It now gets treated as a map type in `serde`.

This has several benefits:
- more efficient representation (not showing inner fields)
- human readable e.g. as a JSON record
- no breaking changes when refactoring the `Record` internals in the
future (see #12326, or potential introduction of `indexmap::IndexMap`
for large N)
- we now deny the creation of invalid records a non-cooperating plugin
could produce
  - guaranteed key-value correspondence
  - checking for unique keys

# Breaking change to the plugin protocol:
Now expects a record/map directly as the `Record.val` field instead of a
serialization of it.
2024-04-07 07:21:03 +08:00
db1dccc762 Don't check if stderr empty in test_xdg_config_symlink (#12435)
<!--
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.
-->

Fixes a problem introduced in
https://github.com/nushell/nushell/pull/12420 where one of the config
path tests (`test_xdg_config_symlink`) fails if, on MacOS, a developer
already has config files in the default location for that platform.

This happened because the test is making sure the
`xdg_config_home_invalid` error isn't reported, but to do that, it
asserts that stderr is empty, which it is not if in the case mentioned
above, because Nushell warns that the default location
(`~/.config`/`~/Library/Application Support`) is not empty but
`XDG_CONFIG_HOME` is empty.

If someone with a Mac could test this, that'd be great.

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

None, this is for contributors.

# 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-04-06 18:01:16 -04:00
1c5bd21ebc Added let command to PluginTest (#12431)
# Description
The `let` command is needed for many example tests. This pull request
adds the `let` command to the EngineState of Test Plugin.

cc: @devyn 

# User-Facing Changes
No user changes. Plugin tests can now have examples with the let
keyword.

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-06 20:11:41 +00:00
fe99729cdb Prevent panic on date overflow (#12427)
# Description
Fixes #12095 where date math using `chrono` can panic on overflow. It
looks like there's only one place that needed fixing.
2024-04-06 15:16:49 +00:00
03667bdf8c Fix merging child stack into parent (#12426)
# Description
Fixes #12423 where changes to mutable variables are not properly
persisted after a REPL entry.
2024-04-06 15:03:22 +00:00
eb36dbb091 Fix #12416 by canonicalizing XDG_CONFIG_HOME before comparing to config_dir() (#12420)
<!--
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.
-->

Fixes #12416. Turns out that in src/main.rs, `XDG_CONFIG_HOME` wasn't
being canonicalized before being compared to `nu_path::config_dir()` to
check if `XDG_CONFIG_HOME` was set to an invalid value. This has been
rectified now.

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

Setting `XDG_CONFIG_HOME` to a symlink should work now.

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

I manually tested it and the error has disappeared:

New behavior (this branch):

![image](https://github.com/nushell/nushell/assets/45539777/062d1cc5-551c-431c-b138-d3da8de018bd)

Old behavior (main):

![image](https://github.com/nushell/nushell/assets/45539777/22c4b5a3-3fd0-4ab6-9cf0-ae25488645ba)

Thanks to a pointer from Devyn, I've now added tests to make sure the
`xdg_config_home_invalid` error doesn't pop up when `XDG_CONFIG_HOME` is
a symlink (and does when it's actually invalid).

Turns out two of the tests in `test_config_path` tried modifying
`XDG_CONFIG_HOME` using `playground.with_env` but used `nu!`, so the
subprocess didn't actually use the modified value of `XDG_CONFIG_HOME`.
When I added them, I was unaware that the `.with_env` didn't actually
modify the environment. This has now been rectified.

# 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-04-06 09:09:03 -05:00
e211e96d33 Fix nushell#10591: encode returns error with utf-16le and utf-16be encodings (nushell#10591) (#12411)
# Description

This closes (nushell#10591)

The Command encode's help text says that utf-16le and utf-16be encodings
are not supported, however you could still use these encodings and they
didn't work properly, since they returned the bytes UTF-8 encoded:
```bash
"䆺ש" | encode utf-16
Length: 5 (0x5) bytes | printable whitespace ascii_other non_ascii
00000000: e4 86 ba d7 a9 ×××××
 ```
# User-Facing Changes

The Command encode's help text was updated and now when trying to encode with utf-16le and utf-16be returns an error:
![screenshot](https://github.com/nushell/nushell/assets/119532691/c346dc57-8b42-4dfc-93d5-638b0041d89f)

# Tests + Formatting

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-06 09:07:55 -05:00
7a7d43344e Range refactor (#12405)
# Description
Currently, `Range` is a struct with a `from`, `to`, and `incr` field,
which are all type `Value`. This PR changes `Range` to be an enum over
`IntRange` and `FloatRange` for better type safety / stronger compile
time guarantees.

Fixes: #11778 Fixes: #11777 Fixes: #11776 Fixes: #11775 Fixes: #11774
Fixes: #11773 Fixes: #11769.

# User-Facing Changes
Hopefully none, besides bug fixes.

Although, the `serde` representation might have changed.
2024-04-06 09:04:56 -05:00
75fedcc8dd prevent select (negative number) from hanging shell (#12393)
<!--
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
Resolves #11756.
Resolves #12346. 

As per description, shell no longer hangs:
```
~/CodingProjects/nushell> [1 2 3] | select (-2) 
Error: nu:🐚:cant_convert

  × Can't convert to cell path.
   ╭─[entry #1:1:18]
 1 │ [1 2 3] | select (-2)
   ·                  ──┬─
   ·                    ╰── can't convert negative number to cell path
   ╰────
```


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

Added relevant test 🚀 

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

Possibly support `get` `get`ting negative numbers, as per #12346
discussion. Alternatively, we can consider adding a cellpath for
negative indexing?
2024-04-06 09:03:05 -05:00
ed4927441f Bump rust-embed from 8.2.0 to 8.3.0 (#12374) 2024-04-06 13:59:01 +00:00
6fba5f5ec7 Bump shadow-rs from 0.26.1 to 0.27.1 (#12372) 2024-04-06 13:58:16 +00:00
12b897b149 Make auto-cd check for permissions (#12342)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

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

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

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->
I was playing around with auto-cd and realised it didn't check for
permissions before cd'ing. This PR fixes that.

```
~/CodingProjects/nushell> /root                                                                           
Error: nu:🐚:io_error

  × I/O error
  help: Cannot change directory to /root: You are neither the owner, in the group, nor the super user and do not have permission
```

This PR also refactors some of the filesystem utilities to nu-utils,
specifically the permissions checking and users.

# 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-04-06 08:56:46 -05:00
16cbed7d6e Fix some of the tests in tests::shell (#12417)
# Description
Some of the tests in `tests::shell` were using `sh` unnecessarily, and
had `#[cfg(not(windows))]` when they should be testable on Windows if
`sh` is not used.

I also found that they were using `.expect()` incorrectly, under the
assumption that that would check their output, when really an
`assert_eq!` on the output is needed to do that. So these tests weren't
even really working properly before.

# User-Facing Changes
None

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-06 11:57:47 +08:00
0e36c43c64 Add BufWriter to ChildStdin on the plugin interface (#12419)
# Description
This speeds up writing messages to the plugin, because otherwise every
individual piece of the messages (not even the entire message) is
written with one syscall, leading to a lot of back and forth with the
kernel.

I learned this by running `strace` to debug something and saw a ton of
`write()` calls.

```nushell
# Before
1..10 | each { timeit { example seq 1 10000 | example sum } } | math avg
269ms 779µs 149ns
# After
> 1..10 | each { timeit { example seq 1 10000 | example sum } } | math avg
39ms 636µs 643ns
```

# User-Facing Changes
- Performance improvement

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-06 11:52:27 +08:00
2562e306b6 Improve handling of custom values in plugin examples (#12409)
# Description
Requested by @ayax79. This makes the custom value behavior more correct,
by calling the methods on the plugin to handle the custom values in
examples rather than the methods on the custom values themselves. This
helps for handle-type custom values (like what he's doing with
dataframes).

- Equality checking in `PluginTest::test_examples()` changed to use
`PluginInterface::custom_value_partial_cmp()`
- Base value rendering for `PluginSignature` changed to use
`Plugin::custom_value_to_base_value()`
- Had to be moved closer to `serve_plugin` for this reason, so the test
for writing signatures containing custom values was removed
- That behavior should still be tested to some degree, since if custom
values are not handled, signatures will fail to parse, so all of the
other tests won't work.

# User-Facing Changes

- `Record::sort_cols()` method added to share functionality required by
`PartialCmp`, and it might also be slightly faster
- Otherwise, everything should mostly be the same but better. Plugins
that don't implement special handling for custom values will still work
the same way, because the default implementation is just a pass-through
to the `CustomValue` methods.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-05 21:57:20 -05:00
c82dfce246 Fix deadlock on PluginCustomValue drop (#12418)
# Description
Because the plugin interface reader thread can be responsible for
sending a drop notification, it's possible for it to end up in a
deadlock where it's waiting for the response to the drop notification
call.

I decided that the best way to address this is to just discard the
response and not wait for it. It's not really important to synchronize
with the response to `Dropped`, so this is probably faster anyway.

cc @ayax79, this is your issue where polars is getting stuck

# User-Facing Changes
- A bug fix
- Custom value plugin: `custom-value handle update` command

# Tests + Formatting

Tried to add a test with a long pipeline with a lot of drops and run it
over and over to reproduce the deadlock.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-05 21:57:00 -05:00
82b7548c0c Tweak release workflow after 0.92.1 lessons (#12401)
Encountered repeated build failure that vanished after clearing the
existing build caches.

- Don't `fail-fast` (this is highly annoying, if a severe problem is
detected, there is still the possibility to intervene manually)
- Don't cache the build process, we do it rarely so any potential
speed-up is offset by the uncertainty if this affects the artefact
2024-04-06 10:20:01 +08:00
0884d1a5ce Fix testing.nu import of std log (#12392)
# Description

`use std/log.nu` does not work, have to `use std log`

# User-Facing Changes

Fix the testing script. Bug fix.
2024-04-05 20:29:19 -05:00
00b576b7f1 Fix stop suggesting --trash when already enabled (issue #12361) (#12362)
fixes #12361

Looking at the condition, `TRASH_SUPPORTED && (trash || (rm_always_trash
&& !permanent))`, this code path seems only to run when `--trash` is
enabled and `--permanent` is disabled.

This suggests that the `--trash` suggestion is a mistake and should have
suggested `--permanent`.
2024-04-05 20:28:40 -05:00
88ff622b16 Make view source more robust (#12359)
<!--
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.
-->

Resolves #11800.

```
~/CodingProjects/nushell> def "url expand" [$urls:any = []]: [string -> string, list -> table] {    
:::   let urls = ($in | default $urls)
:::   def expand-link [] {
:::     http head --redirect-mode manual $in | where name == location | get value.0
:::   }
:::   match ($urls | describe) {
:::     string => { $urls | expand-link }
:::     $type if ($type =~ list) => { $urls | wrap link | insert expanded {|url| $url.link | expand-link}}
:::   }
::: }; view source "url expand"
def "url expand" [ $urls: any = [] ]: [string -> string, list<any> -> table] {
  let urls = ($in | default $urls)
  def expand-link [] {
    http head --redirect-mode manual $in | where name == location | get value.0
  }
  match ($urls | describe) {
    string => { $urls | expand-link }
    $type if ($type =~ list) => { $urls | wrap link | insert expanded {|url| $url.link | expand-link}}
  }
}
```

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

`view source` now 
- adds quotes to commands with spaces
- shows default argument values
- shows type signatures

# 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-04-05 20:28:15 -05:00
394487b3a7 Bump version to 0.92.2 (#12402) 2024-04-05 10:24:00 -04:00
acc3ca9de7 Update list of supported formats in dfr open error message. (#12408)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

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

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

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->
The error message when using `dfr open --type` shows an outdated list of
supported formats.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
User is now informed that jsonl and avro formats are supported.

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

# 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.
-->
No doc changes.
2024-04-05 06:47:08 -05:00
1032 changed files with 41719 additions and 30158 deletions

View File

@ -26,7 +26,7 @@ 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
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the tests for the standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows

View File

@ -19,7 +19,7 @@ jobs:
# Prevent sudden announcement of a new advisory from failing ci:
continue-on-error: true
steps:
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.6
- uses: rustsec/audit-check@v1.4.1
with:
token: ${{ secrets.GITHUB_TOKEN }}

12
.github/workflows/check-msrv.nu vendored Normal file
View File

@ -0,0 +1,12 @@
let toolchain_spec = open rust-toolchain.toml | get toolchain.channel
let msrv_spec = open Cargo.toml | get package.rust-version
# This check is conservative in the sense that we use `rust-toolchain.toml`'s
# override to ensure that this is the upper-bound for the minimum supported
# rust version
if $toolchain_spec != $msrv_spec {
print -e "Mismatching rust compiler versions specified in `Cargo.toml` and `rust-toolchain.toml`"
print -e $"Cargo.toml: ($msrv_spec)"
print -e $"rust-toolchain.toml: ($toolchain_spec)"
exit 1
}

View File

@ -10,7 +10,7 @@ env:
NUSHELL_CARGO_PROFILE: ci
NU_LOG_LEVEL: DEBUG
# If changing these settings also change toolkit.nu
CLIPPY_OPTIONS: "-D warnings -D clippy::unwrap_used"
CLIPPY_OPTIONS: "-D warnings -D clippy::unwrap_used -D clippy::unchecked_duration_subtraction"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
@ -24,77 +24,55 @@ jobs:
# Pinning to Ubuntu 20.04 because building on newer Ubuntu versions causes linux-gnu
# builds to link against a too-new-for-many-Linux-installs glibc version. Consider
# revisiting this when 20.04 is closer to EOL (April 2025)
platform: [windows-latest, macos-latest, ubuntu-20.04]
feature: [default, dataframe]
include:
- feature: default
flags: ""
- feature: dataframe
flags: "--features=dataframe"
exclude:
- platform: windows-latest
feature: dataframe
- platform: macos-latest
feature: dataframe
#
# Using macOS 13 runner because 14 is based on the M1 and has half as much RAM (7 GB,
# instead of 14 GB) which is too little for us right now. Revisit when `dfr` commands are
# removed and we're only building the `polars` plugin instead
platform: [windows-latest, macos-13, ubuntu-20.04]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.6
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with:
rustflags: ""
- name: cargo fmt
run: cargo fmt --all -- --check
# If changing these settings also change toolkit.nu
- name: Clippy
run: cargo clippy --workspace ${{ matrix.flags }} --exclude nu_plugin_* -- $CLIPPY_OPTIONS
run: cargo clippy --workspace --exclude nu_plugin_* -- $CLIPPY_OPTIONS
# In tests we don't have to deny unwrap
- name: Clippy of tests
run: cargo clippy --tests --workspace ${{ matrix.flags }} --exclude nu_plugin_* -- -D warnings
run: cargo clippy --tests --workspace --exclude nu_plugin_* -- -D warnings
- name: Clippy of benchmarks
run: cargo clippy --benches --workspace ${{ matrix.flags }} --exclude nu_plugin_* -- -D warnings
run: cargo clippy --benches --workspace --exclude nu_plugin_* -- -D warnings
tests:
strategy:
fail-fast: true
matrix:
platform: [windows-latest, macos-latest, ubuntu-20.04]
feature: [default, dataframe]
include:
# linux CI cannot handle clipboard feature
- default-flags: ""
- platform: ubuntu-20.04
# linux CI cannot handle clipboard feature
- platform: ubuntu-20.04
default-flags: "--no-default-features --features=default-no-clipboard"
- feature: default
flags: ""
- feature: dataframe
flags: "--features=dataframe"
exclude:
- platform: windows-latest
feature: dataframe
- platform: macos-latest
feature: dataframe
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.6
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with:
rustflags: ""
- name: Tests
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }} ${{ matrix.flags }}
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }}
- name: Check for clean repo
shell: bash
run: |
@ -117,12 +95,10 @@ jobs:
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.6
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with:
rustflags: ""
- name: Install Nushell
run: cargo install --path . --locked --no-default-features
@ -130,6 +106,9 @@ jobs:
- name: Standard library tests
run: nu -c 'use crates/nu-std/testing.nu; testing run-tests --path crates/nu-std'
- name: Ensure that Cargo.toml MSRV and rust-toolchain.toml use the same version
run: nu .github/workflows/check-msrv.nu
- name: Setup Python
uses: actions/setup-python@v5
with:
@ -158,17 +137,19 @@ jobs:
strategy:
fail-fast: true
matrix:
platform: [windows-latest, macos-latest, ubuntu-20.04]
# Using macOS 13 runner because 14 is based on the M1 and has half as much RAM (7 GB,
# instead of 14 GB) which is too little for us right now.
#
# Failure occurring with clippy for rust 1.77.2
platform: [windows-latest, macos-13, ubuntu-20.04]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.6
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with:
rustflags: ""
- name: Clippy
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS

View File

@ -27,7 +27,7 @@ jobs:
# if: github.repository == 'nushell/nightly'
steps:
- name: Checkout
uses: actions/checkout@v4.1.2
uses: actions/checkout@v4.1.6
if: github.repository == 'nushell/nightly'
with:
ref: main
@ -36,10 +36,10 @@ jobs:
token: ${{ secrets.WORKFLOW_TOKEN }}
- name: Setup Nushell
uses: hustcer/setup-nu@v3.9
uses: hustcer/setup-nu@v3.10
if: github.repository == 'nushell/nightly'
with:
version: 0.91.0
version: 0.93.0
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
- name: Prepare for Nightly Release
@ -84,46 +84,35 @@ jobs:
include:
- target: aarch64-apple-darwin
os: macos-latest
target_rustflags: ''
- target: x86_64-apple-darwin
os: macos-latest
target_rustflags: ''
- target: x86_64-pc-windows-msvc
extra: 'bin'
os: windows-latest
target_rustflags: ''
- target: x86_64-pc-windows-msvc
extra: msi
os: windows-latest
target_rustflags: ''
- target: aarch64-pc-windows-msvc
extra: 'bin'
os: windows-latest
target_rustflags: ''
- target: aarch64-pc-windows-msvc
extra: msi
os: windows-latest
target_rustflags: ''
- target: x86_64-unknown-linux-gnu
os: ubuntu-20.04
target_rustflags: ''
- target: x86_64-unknown-linux-musl
os: ubuntu-20.04
target_rustflags: ''
- target: aarch64-unknown-linux-gnu
os: ubuntu-20.04
target_rustflags: ''
- target: armv7-unknown-linux-gnueabihf
os: ubuntu-20.04
target_rustflags: ''
- target: riscv64gc-unknown-linux-gnu
os: ubuntu-latest
target_rustflags: ''
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.6
with:
ref: main
fetch-depth: 0
@ -134,25 +123,23 @@ jobs:
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with:
rustflags: ''
- name: Setup Nushell
uses: hustcer/setup-nu@v3.9
uses: hustcer/setup-nu@v3.10
with:
version: 0.91.0
version: 0.93.0
- name: Release Nu Binary
id: nu
run: nu .github/workflows/release-pkg.nu
env:
RELEASE_TYPE: standard
OS: ${{ matrix.os }}
REF: ${{ github.ref }}
TARGET: ${{ matrix.target }}
_EXTRA_: ${{ matrix.extra }}
TARGET_RUSTFLAGS: ${{ matrix.target_rustflags }}
- name: Create an Issue for Release Failure
if: ${{ failure() }}
@ -174,7 +161,7 @@ jobs:
# REF: https://github.com/marketplace/actions/gh-release
# Create a release only in nushell/nightly repo
- name: Publish Archive
uses: softprops/action-gh-release@v2.0.4
uses: softprops/action-gh-release@v2.0.5
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
with:
prerelease: true
@ -184,122 +171,6 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
full:
name: Full
needs: prepare
strategy:
fail-fast: false
matrix:
target:
- aarch64-apple-darwin
- x86_64-apple-darwin
- x86_64-pc-windows-msvc
- aarch64-pc-windows-msvc
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
- aarch64-unknown-linux-gnu
extra: ['bin']
include:
- target: aarch64-apple-darwin
os: macos-latest
target_rustflags: '--features=dataframe'
- target: x86_64-apple-darwin
os: macos-latest
target_rustflags: '--features=dataframe'
- target: x86_64-pc-windows-msvc
extra: 'bin'
os: windows-latest
target_rustflags: '--features=dataframe'
- target: x86_64-pc-windows-msvc
extra: msi
os: windows-latest
target_rustflags: '--features=dataframe'
- target: aarch64-pc-windows-msvc
extra: 'bin'
os: windows-latest
target_rustflags: '--features=dataframe'
- target: aarch64-pc-windows-msvc
extra: msi
os: windows-latest
target_rustflags: '--features=dataframe'
- target: x86_64-unknown-linux-gnu
os: ubuntu-20.04
target_rustflags: '--features=dataframe'
- target: x86_64-unknown-linux-musl
os: ubuntu-20.04
target_rustflags: '--features=dataframe'
- target: aarch64-unknown-linux-gnu
os: ubuntu-20.04
target_rustflags: '--features=dataframe'
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v4.1.2
with:
ref: main
fetch-depth: 0
- name: Update Rust Toolchain Target
run: |
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with:
rustflags: ''
- name: Setup Nushell
uses: hustcer/setup-nu@v3.9
with:
version: 0.91.0
- name: Release Nu Binary
id: nu
run: nu .github/workflows/release-pkg.nu
env:
RELEASE_TYPE: full
OS: ${{ matrix.os }}
REF: ${{ github.ref }}
TARGET: ${{ matrix.target }}
_EXTRA_: ${{ matrix.extra }}
TARGET_RUSTFLAGS: ${{ matrix.target_rustflags }}
- name: Create an Issue for Release Failure
if: ${{ failure() }}
uses: JasonEtco/create-an-issue@v2.9.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
update_existing: true
search_existing: open
filename: .github/AUTO_ISSUE_TEMPLATE/nightly-build-fail.md
- name: Set Outputs of Short SHA
id: vars
run: |
echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
sha_short=$(git rev-parse --short HEAD)
echo "sha_short=${sha_short:0:7}" >> $GITHUB_OUTPUT
# REF: https://github.com/marketplace/actions/gh-release
# Create a release only in nushell/nightly repo
- name: Publish Archive
uses: softprops/action-gh-release@v2.0.4
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
with:
draft: false
prerelease: true
name: Nu-nightly-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.sha_short }}
tag_name: nightly-${{ steps.vars.outputs.sha_short }}
body: |
This is a NIGHTLY build of Nushell.
It is NOT recommended for production use.
files: ${{ steps.nu.outputs.archive }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
cleanup:
name: Cleanup
# Should only run in nushell/nightly repo
@ -310,14 +181,14 @@ jobs:
- name: Waiting for Release
run: sleep 1800
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.6
with:
ref: main
- name: Setup Nushell
uses: hustcer/setup-nu@v3.9
uses: hustcer/setup-nu@v3.10
with:
version: 0.91.0
version: 0.93.0
# Keep the last a few releases
- name: Delete Older Releases

View File

@ -9,7 +9,6 @@
# Instructions for manually creating an MSI for Winget Releases when they fail
# Added 2022-11-29 when Windows packaging wouldn't work
# Updated again on 2023-02-23 because msis are still failing validation
# Update on 2023-10-18 to use RELEASE_TYPE env var to determine if full or not
# To run this manual for windows here are the steps I take
# checkout the release you want to publish
# 1. git checkout 0.86.0
@ -17,28 +16,26 @@
# 2. $env:CARGO_TARGET_DIR = ""
# 2. hide-env CARGO_TARGET_DIR
# 3. $env.TARGET = 'x86_64-pc-windows-msvc'
# 4. $env.TARGET_RUSTFLAGS = ''
# 5. $env.GITHUB_WORKSPACE = 'D:\nushell'
# 6. $env.GITHUB_OUTPUT = 'D:\nushell\output\out.txt'
# 7. $env.OS = 'windows-latest'
# 8. $env.RELEASE_TYPE = '' # There is full and '' for normal releases
# 4. $env.GITHUB_WORKSPACE = 'D:\nushell'
# 5. $env.GITHUB_OUTPUT = 'D:\nushell\output\out.txt'
# 6. $env.OS = 'windows-latest'
# make sure 7z.exe is in your path https://www.7-zip.org/download.html
# 9. $env.Path = ($env.Path | append 'c:\apps\7-zip')
# 7. $env.Path = ($env.Path | append 'c:\apps\7-zip')
# make sure aria2c.exe is in your path https://github.com/aria2/aria2
# 10. $env.Path = ($env.Path | append 'c:\path\to\aria2c')
# 8. $env.Path = ($env.Path | append 'c:\path\to\aria2c')
# make sure you have the wixtools installed https://wixtoolset.org/
# 11. $env.Path = ($env.Path | append 'C:\Users\dschroeder\AppData\Local\tauri\WixTools')
# 9. $env.Path = ($env.Path | append 'C:\Users\dschroeder\AppData\Local\tauri\WixTools')
# You need to run the release-pkg twice. The first pass, with _EXTRA_ as 'bin', makes the output
# folder and builds everything. The second pass, that generates the msi file, with _EXTRA_ as 'msi'
# 12. $env._EXTRA_ = 'bin'
# 13. source .github\workflows\release-pkg.nu
# 14. cd ..
# 15. $env._EXTRA_ = 'msi'
# 16. source .github\workflows\release-pkg.nu
# 10. $env._EXTRA_ = 'bin'
# 11. source .github\workflows\release-pkg.nu
# 12. cd ..
# 13. $env._EXTRA_ = 'msi'
# 14. source .github\workflows\release-pkg.nu
# After msi is generated, you have to update winget-pkgs repo, you'll need to patch the release
# by deleting the existing msi and uploading this new msi. Then you'll need to update the hash
# on the winget-pkgs PR. To generate the hash, run this command
# 17. open target\wix\nu-0.74.0-x86_64-pc-windows-msvc.msi | hash sha256
# 15. open target\wix\nu-0.74.0-x86_64-pc-windows-msvc.msi | hash sha256
# Then, just take the output and put it in the winget-pkgs PR for the hash on the msi
@ -48,31 +45,15 @@ let os = $env.OS
let target = $env.TARGET
# Repo source dir like `/home/runner/work/nushell/nushell`
let src = $env.GITHUB_WORKSPACE
let flags = $env.TARGET_RUSTFLAGS
let dist = $'($env.GITHUB_WORKSPACE)/output'
let version = (open Cargo.toml | get package.version)
print $'Debugging info:'
print { version: $version, bin: $bin, os: $os, releaseType: $env.RELEASE_TYPE, target: $target, src: $src, flags: $flags, dist: $dist }; hr-line -b
# Rename the full release name so that we won't break the existing scripts for standard release downloading, such as:
# curl -s https://api.github.com/repos/chmln/sd/releases/latest | grep browser_download_url | cut -d '"' -f 4 | grep x86_64-unknown-linux-musl
const FULL_RLS_NAMING = {
x86_64-apple-darwin: 'x86_64-darwin-full',
aarch64-apple-darwin: 'aarch64-darwin-full',
x86_64-unknown-linux-gnu: 'x86_64-linux-gnu-full',
x86_64-pc-windows-msvc: 'x86_64-windows-msvc-full',
x86_64-unknown-linux-musl: 'x86_64-linux-musl-full',
aarch64-unknown-linux-gnu: 'aarch64-linux-gnu-full',
aarch64-pc-windows-msvc: 'aarch64-windows-msvc-full',
riscv64gc-unknown-linux-gnu: 'riscv64-linux-gnu-full',
armv7-unknown-linux-gnueabihf: 'armv7-linux-gnueabihf-full',
}
print { version: $version, bin: $bin, os: $os, target: $target, src: $src, dist: $dist }; hr-line -b
# $env
let USE_UBUNTU = $os starts-with ubuntu
let FULL_NAME = $FULL_RLS_NAMING | get -i $target | default 'unknown-target-full'
print $'(char nl)Packaging ($bin) v($version) for ($target) in ($src)...'; hr-line -b
if not ('Cargo.lock' | path exists) { cargo generate-lockfile }
@ -91,23 +72,23 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
'aarch64-unknown-linux-gnu' => {
sudo apt-get install gcc-aarch64-linux-gnu -y
$env.CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER = 'aarch64-linux-gnu-gcc'
cargo-build-nu $flags
cargo-build-nu
}
'riscv64gc-unknown-linux-gnu' => {
sudo apt-get install gcc-riscv64-linux-gnu -y
$env.CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER = 'riscv64-linux-gnu-gcc'
cargo-build-nu $flags
cargo-build-nu
}
'armv7-unknown-linux-gnueabihf' => {
sudo apt-get install pkg-config gcc-arm-linux-gnueabihf -y
$env.CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER = 'arm-linux-gnueabihf-gcc'
cargo-build-nu $flags
cargo-build-nu
}
_ => {
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
# Actually just for x86_64-unknown-linux-musl target
if $USE_UBUNTU { sudo apt install musl-tools -y }
cargo-build-nu $flags
cargo-build-nu
}
}
}
@ -116,7 +97,7 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
# Build for Windows without static-link-openssl feature
# ----------------------------------------------------------------------------
if $os in ['windows-latest'] {
cargo-build-nu $flags
cargo-build-nu
}
# ----------------------------------------------------------------------------
@ -134,9 +115,15 @@ print $'(char nl)All executable files:'; hr-line
print (ls -f ($executable | into glob)); sleep 1sec
print $'(char nl)Copying release files...'; hr-line
"To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
"To use the included Nushell plugins, register the binaries with the `plugin add` command to tell Nu where to find the plugin.
Then you can use `plugin use` to load the plugin into your session.
For example:
> register ./nu_plugin_query" | save $'($dist)/README.txt' -f
> plugin add ./nu_plugin_query
> plugin use query
For more information, refer to https://www.nushell.sh/book/plugins.html
" | save $'($dist)/README.txt' -f
[LICENSE ...(glob $executable)] | each {|it| cp -rv $it $dist } | flatten
print $'(char nl)Check binary release version detail:'; hr-line
@ -156,7 +143,7 @@ cd $dist; print $'(char nl)Creating release archive...'; hr-line
if $os in ['macos-latest'] or $USE_UBUNTU {
let files = (ls | get name)
let dest = if $env.RELEASE_TYPE == 'full' { $'($bin)-($version)-($FULL_NAME)' } else { $'($bin)-($version)-($target)' }
let dest = $'($bin)-($version)-($target)'
let archive = $'($dist)/($dest).tar.gz'
mkdir $dest
@ -171,7 +158,7 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
} else if $os == 'windows-latest' {
let releaseStem = if $env.RELEASE_TYPE == 'full' { $'($bin)-($version)-($FULL_NAME)' } else { $'($bin)-($version)-($target)' }
let releaseStem = $'($bin)-($version)-($target)'
print $'(char nl)Download less related stuffs...'; hr-line
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v608/less.exe -o less.exe
@ -186,7 +173,7 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
# Wix need the binaries be stored in target/release/
cp -r ($'($dist)/*' | into glob) target/release/
ls target/release/* | print
cargo install cargo-wix --version 0.3.4
cargo install cargo-wix --version 0.3.8
cargo wix --no-build --nocapture --package nu --output $wixRelease
# Workaround for https://github.com/softprops/action-gh-release/issues/280
let archive = ($wixRelease | str replace --all '\' '/')
@ -208,19 +195,11 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
}
}
def 'cargo-build-nu' [ options: string ] {
if ($options | str trim | is-empty) {
if $os == 'windows-latest' {
cargo build --release --all --target $target
} else {
cargo build --release --all --target $target --features=static-link-openssl
}
def 'cargo-build-nu' [] {
if $os == 'windows-latest' {
cargo build --release --all --target $target
} else {
if $os == 'windows-latest' {
cargo build --release --all --target $target $options
} else {
cargo build --release --all --target $target --features=static-link-openssl $options
}
cargo build --release --all --target $target --features=static-link-openssl
}
}

View File

@ -18,6 +18,7 @@ jobs:
name: Std
strategy:
fail-fast: false
matrix:
target:
- aarch64-apple-darwin
@ -33,165 +34,64 @@ jobs:
include:
- target: aarch64-apple-darwin
os: macos-latest
target_rustflags: ''
- target: x86_64-apple-darwin
os: macos-latest
target_rustflags: ''
- target: x86_64-pc-windows-msvc
extra: 'bin'
os: windows-latest
target_rustflags: ''
- target: x86_64-pc-windows-msvc
extra: msi
os: windows-latest
target_rustflags: ''
- target: aarch64-pc-windows-msvc
extra: 'bin'
os: windows-latest
target_rustflags: ''
- target: aarch64-pc-windows-msvc
extra: msi
os: windows-latest
target_rustflags: ''
- target: x86_64-unknown-linux-gnu
os: ubuntu-20.04
target_rustflags: ''
- target: x86_64-unknown-linux-musl
os: ubuntu-20.04
target_rustflags: ''
- target: aarch64-unknown-linux-gnu
os: ubuntu-20.04
target_rustflags: ''
- target: armv7-unknown-linux-gnueabihf
os: ubuntu-20.04
target_rustflags: ''
- target: riscv64gc-unknown-linux-gnu
os: ubuntu-latest
target_rustflags: ''
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.6
- name: Update Rust Toolchain Target
run: |
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain and cache
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with:
cache: false
rustflags: ''
- name: Setup Nushell
uses: hustcer/setup-nu@v3.9
uses: hustcer/setup-nu@v3.10
with:
version: 0.91.0
version: 0.93.0
- name: Release Nu Binary
id: nu
run: nu .github/workflows/release-pkg.nu
env:
RELEASE_TYPE: standard
OS: ${{ matrix.os }}
REF: ${{ github.ref }}
TARGET: ${{ matrix.target }}
_EXTRA_: ${{ matrix.extra }}
TARGET_RUSTFLAGS: ${{ matrix.target_rustflags }}
# REF: https://github.com/marketplace/actions/gh-release
- name: Publish Archive
uses: softprops/action-gh-release@v2.0.4
if: ${{ startsWith(github.ref, 'refs/tags/') }}
with:
draft: true
files: ${{ steps.nu.outputs.archive }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
full:
name: Full
strategy:
fail-fast: false
matrix:
target:
- aarch64-apple-darwin
- x86_64-apple-darwin
- x86_64-pc-windows-msvc
- aarch64-pc-windows-msvc
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
- aarch64-unknown-linux-gnu
extra: ['bin']
include:
- target: aarch64-apple-darwin
os: macos-latest
target_rustflags: '--features=dataframe'
- target: x86_64-apple-darwin
os: macos-latest
target_rustflags: '--features=dataframe'
- target: x86_64-pc-windows-msvc
extra: 'bin'
os: windows-latest
target_rustflags: '--features=dataframe'
- target: x86_64-pc-windows-msvc
extra: msi
os: windows-latest
target_rustflags: '--features=dataframe'
- target: aarch64-pc-windows-msvc
extra: 'bin'
os: windows-latest
target_rustflags: '--features=dataframe'
- target: aarch64-pc-windows-msvc
extra: msi
os: windows-latest
target_rustflags: '--features=dataframe'
- target: x86_64-unknown-linux-gnu
os: ubuntu-20.04
target_rustflags: '--features=dataframe'
- target: x86_64-unknown-linux-musl
os: ubuntu-20.04
target_rustflags: '--features=dataframe'
- target: aarch64-unknown-linux-gnu
os: ubuntu-20.04
target_rustflags: '--features=dataframe'
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v4.1.2
- name: Update Rust Toolchain Target
run: |
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with:
rustflags: ''
- name: Setup Nushell
uses: hustcer/setup-nu@v3.9
with:
version: 0.91.0
- name: Release Nu Binary
id: nu
run: nu .github/workflows/release-pkg.nu
env:
RELEASE_TYPE: full
OS: ${{ matrix.os }}
REF: ${{ github.ref }}
TARGET: ${{ matrix.target }}
_EXTRA_: ${{ matrix.extra }}
TARGET_RUSTFLAGS: ${{ matrix.target_rustflags }}
# REF: https://github.com/marketplace/actions/gh-release
- name: Publish Archive
uses: softprops/action-gh-release@v2.0.4
uses: softprops/action-gh-release@v2.0.5
if: ${{ startsWith(github.ref, 'refs/tags/') }}
with:
draft: true

View File

@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v4.1.2
uses: actions/checkout@v4.1.6
- name: Check spelling
uses: crate-ci/typos@v1.20.3
uses: crate-ci/typos@v1.21.0

View File

@ -55,7 +55,6 @@ It is good practice to cover your changes with a test. Also, try to think about
Tests can be found in different places:
* `/tests`
* `src/tests`
* command examples
* crate-specific tests
@ -72,11 +71,6 @@ Read cargo's documentation for more details: https://doc.rust-lang.org/cargo/ref
cargo run
```
- Build and run with dataframe support.
```nushell
cargo run --features=dataframe
```
- Run Clippy on Nushell:
```nushell
@ -94,11 +88,6 @@ Read cargo's documentation for more details: https://doc.rust-lang.org/cargo/ref
cargo test --workspace
```
along with dataframe tests
```nushell
cargo test --workspace --features=dataframe
```
or via the `toolkit.nu` command:
```nushell
use toolkit.nu test
@ -241,7 +230,7 @@ You can help us to make the review process a smooth experience:
- Choose what simplifies having confidence in the conflict resolution and the review. **Merge commits in your branch are OK** in the squash model.
- Feel free to notify your reviewers or affected PR authors if your change might cause larger conflicts with another change.
- During the rollup of multiple PRs, we may choose to resolve merge conflicts and CI failures ourselves. (Allow maintainers to push to your branch to enable us to do this quickly.)
## License
We use the [MIT License](https://github.com/nushell/nushell/blob/main/LICENSE) in all of our Nushell projects. If you are including or referencing a crate that uses the [GPL License](https://www.gnu.org/licenses/gpl-3.0.en.html#license-text) unfortunately we will not be able to accept your PR.

1823
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,8 @@ homepage = "https://www.nushell.sh"
license = "MIT"
name = "nu"
repository = "https://github.com/nushell/nushell"
rust-version = "1.74.1"
version = "0.92.1"
rust-version = "1.77.2"
version = "0.94.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -31,7 +31,7 @@ members = [
"crates/nu-cmd-base",
"crates/nu-cmd-extra",
"crates/nu-cmd-lang",
"crates/nu-cmd-dataframe",
"crates/nu-cmd-plugin",
"crates/nu-command",
"crates/nu-color-config",
"crates/nu-explore",
@ -40,6 +40,9 @@ members = [
"crates/nu-pretty-hex",
"crates/nu-protocol",
"crates/nu-plugin",
"crates/nu-plugin-core",
"crates/nu-plugin-engine",
"crates/nu-plugin-protocol",
"crates/nu-plugin-test-support",
"crates/nu_plugin_inc",
"crates/nu_plugin_gstat",
@ -47,23 +50,28 @@ members = [
"crates/nu_plugin_query",
"crates/nu_plugin_custom_values",
"crates/nu_plugin_formats",
"crates/nu_plugin_polars",
"crates/nu_plugin_stress_internals",
"crates/nu-std",
"crates/nu-table",
"crates/nu-term-grid",
"crates/nu-test-support",
"crates/nu-utils",
"crates/nuon",
]
[workspace.dependencies]
alphanumeric-sort = "1.5"
ansi-str = "0.8"
base64 = "0.22"
anyhow = "1.0.82"
base64 = "0.22.1"
bracoxide = "0.1.2"
brotli = "5.0"
byteorder = "1.5"
bytesize = "1.3"
calamine = "0.24.0"
chardetng = "0.1.17"
chrono = { default-features = false, version = "0.4" }
chrono = { default-features = false, version = "0.4.34" }
chrono-humanize = "0.2.3"
chrono-tz = "0.8"
crossbeam-channel = "0.5.8"
@ -85,6 +93,7 @@ heck = "0.5.0"
human-date-parser = "0.1.1"
indexmap = "2.2"
indicatif = "0.17"
interprocess = "2.1.0"
is_executable = "1.0"
itertools = "0.12"
libc = "0.2"
@ -95,7 +104,7 @@ lscolors = { version = "0.17", default-features = false }
lsp-server = "0.7.5"
lsp-types = "0.95.0"
mach2 = "0.4"
md5 = { version = "0.10", package = "md-5"}
md5 = { version = "0.10", package = "md-5" }
miette = "7.2"
mime = "0.3"
mime_guess = "2.0"
@ -109,9 +118,10 @@ num-traits = "0.2"
omnipath = "0.1"
once_cell = "1.18"
open = "5.1"
os_pipe = "1.1"
os_pipe = { version = "1.1", features = ["io_safety"] }
pathdiff = "0.2"
percent-encoding = "2"
pretty_assertions = "1.4"
print-positions = "0.6"
procfs = "0.16.0"
pwd = "1.3"
@ -121,13 +131,15 @@ quickcheck_macros = "1.0"
rand = "0.8"
ratatui = "0.26"
rayon = "1.10"
reedline = "0.31.0"
reedline = "0.32.0"
regex = "1.9.5"
rmp = "0.8"
rmp-serde = "1.3"
ropey = "1.6.1"
roxmltree = "0.19"
rstest = { version = "0.18", default-features = false }
rusqlite = "0.31"
rust-embed = "8.2.0"
rust-embed = "8.4.0"
same-file = "1.0"
serde = { version = "1.0", default-features = false }
serde_json = "1.0"
@ -162,24 +174,22 @@ windows = "0.54"
winreg = "0.52"
[dependencies]
nu-cli = { path = "./crates/nu-cli", version = "0.92.1" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.92.1" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.92.1" }
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.92.1", features = [
"dataframe",
], optional = true }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.92.1" }
nu-command = { path = "./crates/nu-command", version = "0.92.1" }
nu-engine = { path = "./crates/nu-engine", version = "0.92.1" }
nu-explore = { path = "./crates/nu-explore", version = "0.92.1" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.92.1" }
nu-parser = { path = "./crates/nu-parser", version = "0.92.1" }
nu-path = { path = "./crates/nu-path", version = "0.92.1" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.92.1" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.92.1" }
nu-std = { path = "./crates/nu-std", version = "0.92.1" }
nu-system = { path = "./crates/nu-system", version = "0.92.1" }
nu-utils = { path = "./crates/nu-utils", version = "0.92.1" }
nu-cli = { path = "./crates/nu-cli", version = "0.94.0" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.94.0" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.94.0" }
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.94.0", optional = true }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.94.0" }
nu-command = { path = "./crates/nu-command", version = "0.94.0" }
nu-engine = { path = "./crates/nu-engine", version = "0.94.0" }
nu-explore = { path = "./crates/nu-explore", version = "0.94.0" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.94.0" }
nu-parser = { path = "./crates/nu-parser", version = "0.94.0" }
nu-path = { path = "./crates/nu-path", version = "0.94.0" }
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.94.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.94.0" }
nu-std = { path = "./crates/nu-std", version = "0.94.0" }
nu-system = { path = "./crates/nu-system", version = "0.94.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.94.0" }
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
@ -187,7 +197,7 @@ crossterm = { workspace = true }
ctrlc = { workspace = true }
log = { workspace = true }
miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] }
mimalloc = { version = "0.1.37", default-features = false, optional = true }
mimalloc = { version = "0.1.42", default-features = false, optional = true }
serde_json = { workspace = true }
simplelog = "0.12"
time = "0.3"
@ -208,18 +218,21 @@ nix = { workspace = true, default-features = false, features = [
] }
[dev-dependencies]
nu-test-support = { path = "./crates/nu-test-support", version = "0.92.1" }
nu-test-support = { path = "./crates/nu-test-support", version = "0.94.0" }
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.94.0" }
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.94.0" }
assert_cmd = "2.0"
dirs-next = { workspace = true }
divan = "0.1.14"
pretty_assertions = "1.4"
tango-bench = "0.5"
pretty_assertions = { workspace = true }
rstest = { workspace = true, default-features = false }
serial_test = "3.0"
serial_test = "3.1"
tempfile = { workspace = true }
[features]
plugin = [
"nu-plugin",
"nu-plugin-engine",
"nu-cmd-plugin",
"nu-cli/plugin",
"nu-parser/plugin",
"nu-command/plugin",
@ -237,7 +250,6 @@ default-no-clipboard = [
"mimalloc",
]
stable = ["default"]
wasi = ["nu-cmd-lang/wasi"]
# NOTE: individual features are also passed to `nu-cmd-lang` that uses them to generate the feature matrix in the `version` command
# Enable to statically link OpenSSL (perl is required, to build OpenSSL https://docs.rs/openssl/latest/openssl/);
@ -245,15 +257,16 @@ wasi = ["nu-cmd-lang/wasi"]
static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"]
mimalloc = ["nu-cmd-lang/mimalloc", "dep:mimalloc"]
system-clipboard = ["reedline/system_clipboard", "nu-cli/system-clipboard"]
system-clipboard = [
"reedline/system_clipboard",
"nu-cli/system-clipboard",
"nu-cmd-lang/system-clipboard",
]
# Stable (Default)
which-support = ["nu-command/which-support", "nu-cmd-lang/which-support"]
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"]
# Dataframe feature for nushell
dataframe = ["dep:nu-cmd-dataframe", "nu-cmd-lang/dataframe"]
# SQLite commands for nushell
sqlite = ["nu-command/sqlite", "nu-cmd-lang/sqlite"]
@ -292,4 +305,4 @@ bench = false
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
[[bench]]
name = "benchmarks"
harness = false
harness = false

View File

@ -1,95 +1,39 @@
use nu_cli::{eval_source, evaluate_commands};
use nu_parser::parse;
use nu_plugin::{Encoder, EncodingType, PluginCallResponse, PluginOutput};
use nu_plugin_core::{Encoder, EncodingType};
use nu_plugin_protocol::{PluginCallResponse, PluginOutput};
use nu_protocol::{
engine::{EngineState, Stack},
eval_const::create_nu_constant,
PipelineData, Span, Spanned, Value, NU_VARIABLE_ID,
PipelineData, Span, Spanned, Value,
};
use nu_std::load_standard_library;
use nu_utils::{get_default_config, get_default_env};
use std::path::{Path, PathBuf};
use std::rc::Rc;
fn main() {
// Run registered benchmarks.
divan::main();
}
use std::hint::black_box;
use tango_bench::{benchmark_fn, tango_benchmarks, tango_main, IntoBenchmarks};
fn load_bench_commands() -> EngineState {
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context())
}
fn canonicalize_path(engine_state: &EngineState, path: &Path) -> PathBuf {
let cwd = engine_state.current_work_dir();
if path.exists() {
match nu_path::canonicalize_with(path, cwd) {
Ok(canon_path) => canon_path,
Err(_) => path.to_owned(),
}
} else {
path.to_owned()
}
}
fn get_home_path(engine_state: &EngineState) -> PathBuf {
nu_path::home_dir()
.map(|path| canonicalize_path(engine_state, &path))
.unwrap_or_default()
}
fn setup_engine() -> EngineState {
let mut engine_state = load_bench_commands();
let home_path = get_home_path(&engine_state);
let cwd = std::env::current_dir()
.unwrap()
.into_os_string()
.into_string()
.unwrap();
// parsing config.nu breaks without PWD set, so set a valid path
engine_state.add_env_var(
"PWD".into(),
Value::string(home_path.to_string_lossy(), Span::test_data()),
);
engine_state.add_env_var("PWD".into(), Value::string(cwd, Span::test_data()));
let nu_const = create_nu_constant(&engine_state, Span::unknown())
.expect("Failed to create nushell constant.");
engine_state.set_variable_const_val(NU_VARIABLE_ID, nu_const);
engine_state.generate_nu_constant();
engine_state
}
fn bench_command(bencher: divan::Bencher, scaled_command: String) {
bench_command_with_custom_stack_and_engine(
bencher,
scaled_command,
Stack::new(),
setup_engine(),
)
}
fn bench_command_with_custom_stack_and_engine(
bencher: divan::Bencher,
scaled_command: String,
stack: nu_protocol::engine::Stack,
mut engine: EngineState,
) {
load_standard_library(&mut engine).unwrap();
let commands = Spanned {
span: Span::unknown(),
item: scaled_command,
};
bencher
.with_inputs(|| engine.clone())
.bench_values(|mut engine| {
evaluate_commands(
&commands,
&mut engine,
&mut stack.clone(),
PipelineData::empty(),
None,
)
.unwrap();
})
}
fn setup_stack_and_engine_from_command(command: &str) -> (Stack, EngineState) {
let mut engine = setup_engine();
let commands = Spanned {
@ -104,266 +48,13 @@ fn setup_stack_and_engine_from_command(command: &str) -> (Stack, EngineState) {
&mut stack,
PipelineData::empty(),
None,
false,
)
.unwrap();
(stack, engine)
}
// FIXME: All benchmarks live in this 1 file to speed up build times when benchmarking.
// When the *_benchmarks functions were in different files, `cargo bench` would build
// an executable for every single one - incredibly slowly. Would be nice to figure out
// a way to split things up again.
#[divan::bench]
fn load_standard_lib(bencher: divan::Bencher) {
let engine = setup_engine();
bencher
.with_inputs(|| engine.clone())
.bench_values(|mut engine| {
load_standard_library(&mut engine).unwrap();
})
}
#[divan::bench_group]
mod record {
use super::*;
fn create_flat_record_string(n: i32) -> String {
let mut s = String::from("let record = {");
for i in 0..n {
s.push_str(&format!("col_{}: {}", i, i));
if i < n - 1 {
s.push_str(", ");
}
}
s.push('}');
s
}
fn create_nested_record_string(depth: i32) -> String {
let mut s = String::from("let record = {");
for _ in 0..depth {
s.push_str("col: {");
}
s.push_str("col_final: 0");
for _ in 0..depth {
s.push('}');
}
s.push('}');
s
}
#[divan::bench(args = [1, 10, 100, 1000])]
fn create(bencher: divan::Bencher, n: i32) {
bench_command(bencher, create_flat_record_string(n));
}
#[divan::bench(args = [1, 10, 100, 1000])]
fn flat_access(bencher: divan::Bencher, n: i32) {
let (stack, engine) = setup_stack_and_engine_from_command(&create_flat_record_string(n));
bench_command_with_custom_stack_and_engine(
bencher,
"$record.col_0 | ignore".to_string(),
stack,
engine,
);
}
#[divan::bench(args = [1, 2, 4, 8, 16, 32, 64, 128])]
fn nest_access(bencher: divan::Bencher, depth: i32) {
let (stack, engine) =
setup_stack_and_engine_from_command(&create_nested_record_string(depth));
let nested_access = ".col".repeat(depth as usize);
bench_command_with_custom_stack_and_engine(
bencher,
format!("$record{} | ignore", nested_access),
stack,
engine,
);
}
}
#[divan::bench_group]
mod table {
use super::*;
fn create_example_table_nrows(n: i32) -> String {
let mut s = String::from("let table = [[foo bar baz]; ");
for i in 0..n {
s.push_str(&format!("[0, 1, {i}]"));
if i < n - 1 {
s.push_str(", ");
}
}
s.push(']');
s
}
#[divan::bench(args = [1, 10, 100, 1000])]
fn create(bencher: divan::Bencher, n: i32) {
bench_command(bencher, create_example_table_nrows(n));
}
#[divan::bench(args = [1, 10, 100, 1000])]
fn get(bencher: divan::Bencher, n: i32) {
let (stack, engine) = setup_stack_and_engine_from_command(&create_example_table_nrows(n));
bench_command_with_custom_stack_and_engine(
bencher,
"$table | get bar | math sum | ignore".to_string(),
stack,
engine,
);
}
#[divan::bench(args = [1, 10, 100, 1000])]
fn select(bencher: divan::Bencher, n: i32) {
let (stack, engine) = setup_stack_and_engine_from_command(&create_example_table_nrows(n));
bench_command_with_custom_stack_and_engine(
bencher,
"$table | select foo baz | ignore".to_string(),
stack,
engine,
);
}
}
#[divan::bench_group]
mod eval_commands {
use super::*;
#[divan::bench(args = [100, 1_000, 10_000])]
fn interleave(bencher: divan::Bencher, n: i32) {
bench_command(
bencher,
format!("seq 1 {n} | wrap a | interleave {{ seq 1 {n} | wrap b }} | ignore"),
)
}
#[divan::bench(args = [100, 1_000, 10_000])]
fn interleave_with_ctrlc(bencher: divan::Bencher, n: i32) {
let mut engine = setup_engine();
engine.ctrlc = Some(std::sync::Arc::new(std::sync::atomic::AtomicBool::new(
false,
)));
load_standard_library(&mut engine).unwrap();
let commands = Spanned {
span: Span::unknown(),
item: format!("seq 1 {n} | wrap a | interleave {{ seq 1 {n} | wrap b }} | ignore"),
};
bencher
.with_inputs(|| engine.clone())
.bench_values(|mut engine| {
evaluate_commands(
&commands,
&mut engine,
&mut nu_protocol::engine::Stack::new(),
PipelineData::empty(),
None,
)
.unwrap();
})
}
#[divan::bench(args = [1, 5, 10, 100, 1_000])]
fn for_range(bencher: divan::Bencher, n: i32) {
bench_command(bencher, format!("(for $x in (1..{}) {{ sleep 50ns }})", n))
}
#[divan::bench(args = [1, 5, 10, 100, 1_000])]
fn each(bencher: divan::Bencher, n: i32) {
bench_command(
bencher,
format!("(1..{}) | each {{|_| sleep 50ns }} | ignore", n),
)
}
#[divan::bench(args = [1, 5, 10, 100, 1_000])]
fn par_each_1t(bencher: divan::Bencher, n: i32) {
bench_command(
bencher,
format!("(1..{}) | par-each -t 1 {{|_| sleep 50ns }} | ignore", n),
)
}
#[divan::bench(args = [1, 5, 10, 100, 1_000])]
fn par_each_2t(bencher: divan::Bencher, n: i32) {
bench_command(
bencher,
format!("(1..{}) | par-each -t 2 {{|_| sleep 50ns }} | ignore", n),
)
}
}
#[divan::bench_group()]
mod parser_benchmarks {
use super::*;
#[divan::bench()]
fn parse_default_config_file(bencher: divan::Bencher) {
let engine_state = setup_engine();
let default_env = get_default_config().as_bytes();
bencher
.with_inputs(|| nu_protocol::engine::StateWorkingSet::new(&engine_state))
.bench_refs(|working_set| parse(working_set, None, default_env, false))
}
#[divan::bench()]
fn parse_default_env_file(bencher: divan::Bencher) {
let engine_state = setup_engine();
let default_env = get_default_env().as_bytes();
bencher
.with_inputs(|| nu_protocol::engine::StateWorkingSet::new(&engine_state))
.bench_refs(|working_set| parse(working_set, None, default_env, false))
}
}
#[divan::bench_group()]
mod eval_benchmarks {
use super::*;
#[divan::bench()]
fn eval_default_env(bencher: divan::Bencher) {
let default_env = get_default_env().as_bytes();
let fname = "default_env.nu";
bencher
.with_inputs(|| (setup_engine(), nu_protocol::engine::Stack::new()))
.bench_values(|(mut engine_state, mut stack)| {
eval_source(
&mut engine_state,
&mut stack,
default_env,
fname,
PipelineData::empty(),
false,
)
})
}
#[divan::bench()]
fn eval_default_config(bencher: divan::Bencher) {
let default_env = get_default_config().as_bytes();
let fname = "default_config.nu";
bencher
.with_inputs(|| (setup_engine(), nu_protocol::engine::Stack::new()))
.bench_values(|(mut engine_state, mut stack)| {
eval_source(
&mut engine_state,
&mut stack,
default_env,
fname,
PipelineData::empty(),
false,
)
})
}
}
// generate a new table data with `row_cnt` rows, `col_cnt` columns.
fn encoding_test_data(row_cnt: usize, col_cnt: usize) -> Value {
let record = Value::test_record(
@ -375,76 +66,424 @@ fn encoding_test_data(row_cnt: usize, col_cnt: usize) -> Value {
Value::list(vec![record; row_cnt], Span::test_data())
}
#[divan::bench_group()]
mod encoding_benchmarks {
use super::*;
#[divan::bench(args = [(100, 5), (10000, 15)])]
fn json_encode(bencher: divan::Bencher, (row_cnt, col_cnt): (usize, usize)) {
let test_data = PluginOutput::CallResponse(
0,
PluginCallResponse::value(encoding_test_data(row_cnt, col_cnt)),
);
let encoder = EncodingType::try_from_bytes(b"json").unwrap();
bencher
.with_inputs(Vec::new)
.bench_values(|mut res| encoder.encode(&test_data, &mut res))
}
#[divan::bench(args = [(100, 5), (10000, 15)])]
fn msgpack_encode(bencher: divan::Bencher, (row_cnt, col_cnt): (usize, usize)) {
let test_data = PluginOutput::CallResponse(
0,
PluginCallResponse::value(encoding_test_data(row_cnt, col_cnt)),
);
let encoder = EncodingType::try_from_bytes(b"msgpack").unwrap();
bencher
.with_inputs(Vec::new)
.bench_values(|mut res| encoder.encode(&test_data, &mut res))
}
fn bench_command(
name: &str,
command: &str,
stack: Stack,
engine: EngineState,
) -> impl IntoBenchmarks {
let commands = Spanned {
span: Span::unknown(),
item: command.to_string(),
};
[benchmark_fn(name, move |b| {
let commands = commands.clone();
let stack = stack.clone();
let engine = engine.clone();
b.iter(move || {
let mut stack = stack.clone();
let mut engine = engine.clone();
#[allow(clippy::unit_arg)]
black_box(
evaluate_commands(
&commands,
&mut engine,
&mut stack,
PipelineData::empty(),
None,
false,
)
.unwrap(),
);
})
})]
}
#[divan::bench_group()]
mod decoding_benchmarks {
use super::*;
#[divan::bench(args = [(100, 5), (10000, 15)])]
fn json_decode(bencher: divan::Bencher, (row_cnt, col_cnt): (usize, usize)) {
let test_data = PluginOutput::CallResponse(
0,
PluginCallResponse::value(encoding_test_data(row_cnt, col_cnt)),
);
let encoder = EncodingType::try_from_bytes(b"json").unwrap();
let mut res = vec![];
encoder.encode(&test_data, &mut res).unwrap();
bencher
.with_inputs(|| {
let mut binary_data = std::io::Cursor::new(res.clone());
binary_data.set_position(0);
binary_data
})
.bench_values(|mut binary_data| -> Result<Option<PluginOutput>, _> {
encoder.decode(&mut binary_data)
})
}
#[divan::bench(args = [(100, 5), (10000, 15)])]
fn msgpack_decode(bencher: divan::Bencher, (row_cnt, col_cnt): (usize, usize)) {
let test_data = PluginOutput::CallResponse(
0,
PluginCallResponse::value(encoding_test_data(row_cnt, col_cnt)),
);
let encoder = EncodingType::try_from_bytes(b"msgpack").unwrap();
let mut res = vec![];
encoder.encode(&test_data, &mut res).unwrap();
bencher
.with_inputs(|| {
let mut binary_data = std::io::Cursor::new(res.clone());
binary_data.set_position(0);
binary_data
})
.bench_values(|mut binary_data| -> Result<Option<PluginOutput>, _> {
encoder.decode(&mut binary_data)
})
}
fn bench_eval_source(
name: &str,
fname: String,
source: Vec<u8>,
stack: Stack,
engine: EngineState,
) -> impl IntoBenchmarks {
[benchmark_fn(name, move |b| {
let stack = stack.clone();
let engine = engine.clone();
let fname = fname.clone();
let source = source.clone();
b.iter(move || {
let mut stack = stack.clone();
let mut engine = engine.clone();
let fname: &str = &fname.clone();
let source: &[u8] = &source.clone();
black_box(eval_source(
&mut engine,
&mut stack,
source,
fname,
PipelineData::empty(),
false,
));
})
})]
}
/// Load the standard library into the engine.
fn bench_load_standard_lib() -> impl IntoBenchmarks {
[benchmark_fn("load_standard_lib", move |b| {
let engine = setup_engine();
b.iter(move || {
let mut engine = engine.clone();
load_standard_library(&mut engine)
})
})]
}
fn create_flat_record_string(n: i32) -> String {
let mut s = String::from("let record = {");
for i in 0..n {
s.push_str(&format!("col_{}: {}", i, i));
if i < n - 1 {
s.push_str(", ");
}
}
s.push('}');
s
}
fn create_nested_record_string(depth: i32) -> String {
let mut s = String::from("let record = {");
for _ in 0..depth {
s.push_str("col: {");
}
s.push_str("col_final: 0");
for _ in 0..depth {
s.push('}');
}
s.push('}');
s
}
fn create_example_table_nrows(n: i32) -> String {
let mut s = String::from("let table = [[foo bar baz]; ");
for i in 0..n {
s.push_str(&format!("[0, 1, {i}]"));
if i < n - 1 {
s.push_str(", ");
}
}
s.push(']');
s
}
fn bench_record_create(n: i32) -> impl IntoBenchmarks {
bench_command(
&format!("record_create_{n}"),
&create_flat_record_string(n),
Stack::new(),
setup_engine(),
)
}
fn bench_record_flat_access(n: i32) -> impl IntoBenchmarks {
let setup_command = create_flat_record_string(n);
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
bench_command(
&format!("record_flat_access_{n}"),
"$record.col_0 | ignore",
stack,
engine,
)
}
fn bench_record_nested_access(n: i32) -> impl IntoBenchmarks {
let setup_command = create_nested_record_string(n);
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
let nested_access = ".col".repeat(n as usize);
bench_command(
&format!("record_nested_access_{n}"),
&format!("$record{} | ignore", nested_access),
stack,
engine,
)
}
fn bench_table_create(n: i32) -> impl IntoBenchmarks {
bench_command(
&format!("table_create_{n}"),
&create_example_table_nrows(n),
Stack::new(),
setup_engine(),
)
}
fn bench_table_get(n: i32) -> impl IntoBenchmarks {
let setup_command = create_example_table_nrows(n);
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
bench_command(
&format!("table_get_{n}"),
"$table | get bar | math sum | ignore",
stack,
engine,
)
}
fn bench_table_select(n: i32) -> impl IntoBenchmarks {
let setup_command = create_example_table_nrows(n);
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
bench_command(
&format!("table_select_{n}"),
"$table | select foo baz | ignore",
stack,
engine,
)
}
fn bench_eval_interleave(n: i32) -> impl IntoBenchmarks {
let engine = setup_engine();
let stack = Stack::new();
bench_command(
&format!("eval_interleave_{n}"),
&format!("seq 1 {n} | wrap a | interleave {{ seq 1 {n} | wrap b }} | ignore"),
stack,
engine,
)
}
fn bench_eval_interleave_with_ctrlc(n: i32) -> impl IntoBenchmarks {
let mut engine = setup_engine();
engine.ctrlc = Some(std::sync::Arc::new(std::sync::atomic::AtomicBool::new(
false,
)));
let stack = Stack::new();
bench_command(
&format!("eval_interleave_with_ctrlc_{n}"),
&format!("seq 1 {n} | wrap a | interleave {{ seq 1 {n} | wrap b }} | ignore"),
stack,
engine,
)
}
fn bench_eval_for(n: i32) -> impl IntoBenchmarks {
let engine = setup_engine();
let stack = Stack::new();
bench_command(
&format!("eval_for_{n}"),
&format!("(for $x in (1..{n}) {{ 1 }}) | ignore"),
stack,
engine,
)
}
fn bench_eval_each(n: i32) -> impl IntoBenchmarks {
let engine = setup_engine();
let stack = Stack::new();
bench_command(
&format!("eval_each_{n}"),
&format!("(1..{n}) | each {{|_| 1 }} | ignore"),
stack,
engine,
)
}
fn bench_eval_par_each(n: i32) -> impl IntoBenchmarks {
let engine = setup_engine();
let stack = Stack::new();
bench_command(
&format!("eval_par_each_{n}"),
&format!("(1..{}) | par-each -t 2 {{|_| 1 }} | ignore", n),
stack,
engine,
)
}
fn bench_eval_default_config() -> impl IntoBenchmarks {
let default_env = get_default_config().as_bytes().to_vec();
let fname = "default_config.nu".to_string();
bench_eval_source(
"eval_default_config",
fname,
default_env,
Stack::new(),
setup_engine(),
)
}
fn bench_eval_default_env() -> impl IntoBenchmarks {
let default_env = get_default_env().as_bytes().to_vec();
let fname = "default_env.nu".to_string();
bench_eval_source(
"eval_default_env",
fname,
default_env,
Stack::new(),
setup_engine(),
)
}
fn encode_json(row_cnt: usize, col_cnt: usize) -> impl IntoBenchmarks {
let test_data = Rc::new(PluginOutput::CallResponse(
0,
PluginCallResponse::value(encoding_test_data(row_cnt, col_cnt)),
));
let encoder = Rc::new(EncodingType::try_from_bytes(b"json").unwrap());
[benchmark_fn(
format!("encode_json_{}_{}", row_cnt, col_cnt),
move |b| {
let encoder = encoder.clone();
let test_data = test_data.clone();
b.iter(move || {
let mut res = Vec::new();
encoder.encode(&*test_data, &mut res).unwrap();
})
},
)]
}
fn encode_msgpack(row_cnt: usize, col_cnt: usize) -> impl IntoBenchmarks {
let test_data = Rc::new(PluginOutput::CallResponse(
0,
PluginCallResponse::value(encoding_test_data(row_cnt, col_cnt)),
));
let encoder = Rc::new(EncodingType::try_from_bytes(b"msgpack").unwrap());
[benchmark_fn(
format!("encode_msgpack_{}_{}", row_cnt, col_cnt),
move |b| {
let encoder = encoder.clone();
let test_data = test_data.clone();
b.iter(move || {
let mut res = Vec::new();
encoder.encode(&*test_data, &mut res).unwrap();
})
},
)]
}
fn decode_json(row_cnt: usize, col_cnt: usize) -> impl IntoBenchmarks {
let test_data = PluginOutput::CallResponse(
0,
PluginCallResponse::value(encoding_test_data(row_cnt, col_cnt)),
);
let encoder = EncodingType::try_from_bytes(b"json").unwrap();
let mut res = vec![];
encoder.encode(&test_data, &mut res).unwrap();
[benchmark_fn(
format!("decode_json_{}_{}", row_cnt, col_cnt),
move |b| {
let res = res.clone();
b.iter(move || {
let mut binary_data = std::io::Cursor::new(res.clone());
binary_data.set_position(0);
let _: Result<Option<PluginOutput>, _> =
black_box(encoder.decode(&mut binary_data));
})
},
)]
}
fn decode_msgpack(row_cnt: usize, col_cnt: usize) -> impl IntoBenchmarks {
let test_data = PluginOutput::CallResponse(
0,
PluginCallResponse::value(encoding_test_data(row_cnt, col_cnt)),
);
let encoder = EncodingType::try_from_bytes(b"msgpack").unwrap();
let mut res = vec![];
encoder.encode(&test_data, &mut res).unwrap();
[benchmark_fn(
format!("decode_msgpack_{}_{}", row_cnt, col_cnt),
move |b| {
let res = res.clone();
b.iter(move || {
let mut binary_data = std::io::Cursor::new(res.clone());
binary_data.set_position(0);
let _: Result<Option<PluginOutput>, _> =
black_box(encoder.decode(&mut binary_data));
})
},
)]
}
tango_benchmarks!(
bench_load_standard_lib(),
// Data types
// Record
bench_record_create(1),
bench_record_create(10),
bench_record_create(100),
bench_record_create(1_000),
bench_record_flat_access(1),
bench_record_flat_access(10),
bench_record_flat_access(100),
bench_record_flat_access(1_000),
bench_record_nested_access(1),
bench_record_nested_access(2),
bench_record_nested_access(4),
bench_record_nested_access(8),
bench_record_nested_access(16),
bench_record_nested_access(32),
bench_record_nested_access(64),
bench_record_nested_access(128),
// Table
bench_table_create(1),
bench_table_create(10),
bench_table_create(100),
bench_table_create(1_000),
bench_table_get(1),
bench_table_get(10),
bench_table_get(100),
bench_table_get(1_000),
bench_table_select(1),
bench_table_select(10),
bench_table_select(100),
bench_table_select(1_000),
// Eval
// Interleave
bench_eval_interleave(100),
bench_eval_interleave(1_000),
bench_eval_interleave(10_000),
bench_eval_interleave_with_ctrlc(100),
bench_eval_interleave_with_ctrlc(1_000),
bench_eval_interleave_with_ctrlc(10_000),
// For
bench_eval_for(1),
bench_eval_for(10),
bench_eval_for(100),
bench_eval_for(1_000),
bench_eval_for(10_000),
// Each
bench_eval_each(1),
bench_eval_each(10),
bench_eval_each(100),
bench_eval_each(1_000),
bench_eval_each(10_000),
// Par-Each
bench_eval_par_each(1),
bench_eval_par_each(10),
bench_eval_par_each(100),
bench_eval_par_each(1_000),
bench_eval_par_each(10_000),
// Config
bench_eval_default_config(),
// Env
bench_eval_default_env(),
// Encode
// Json
encode_json(100, 5),
encode_json(10000, 15),
// MsgPack
encode_msgpack(100, 5),
encode_msgpack(10000, 15),
// Decode
// Json
decode_json(100, 5),
decode_json(10000, 15),
// MsgPack
decode_msgpack(100, 5),
decode_msgpack(10000, 15)
);
tango_main!();

View File

@ -5,25 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
edition = "2021"
license = "MIT"
name = "nu-cli"
version = "0.92.1"
version = "0.94.0"
[lib]
bench = false
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.1" }
nu-command = { path = "../nu-command", version = "0.92.1" }
nu-test-support = { path = "../nu-test-support", version = "0.92.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.0" }
nu-command = { path = "../nu-command", version = "0.94.0" }
nu-test-support = { path = "../nu-test-support", version = "0.94.0" }
rstest = { workspace = true, default-features = false }
tempfile = { workspace = true }
[dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.92.1" }
nu-engine = { path = "../nu-engine", version = "0.92.1" }
nu-path = { path = "../nu-path", version = "0.92.1" }
nu-parser = { path = "../nu-parser", version = "0.92.1" }
nu-protocol = { path = "../nu-protocol", version = "0.92.1" }
nu-utils = { path = "../nu-utils", version = "0.92.1" }
nu-color-config = { path = "../nu-color-config", version = "0.92.1" }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.0" }
nu-engine = { path = "../nu-engine", version = "0.94.0" }
nu-path = { path = "../nu-path", version = "0.94.0" }
nu-parser = { path = "../nu-parser", version = "0.94.0" }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.0", optional = true }
nu-protocol = { path = "../nu-protocol", version = "0.94.0" }
nu-utils = { path = "../nu-utils", version = "0.94.0" }
nu-color-config = { path = "../nu-color-config", version = "0.94.0" }
nu-ansi-term = { workspace = true }
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
@ -37,12 +39,11 @@ miette = { workspace = true, features = ["fancy-no-backtrace"] }
lscolors = { workspace = true, default-features = false, features = ["nu-ansi-term"] }
once_cell = { workspace = true }
percent-encoding = { workspace = true }
pathdiff = { workspace = true }
sysinfo = { workspace = true }
unicode-segmentation = { workspace = true }
uuid = { workspace = true, features = ["v4"] }
which = { workspace = true }
[features]
plugin = []
system-clipboard = ["reedline/system_clipboard"]
plugin = ["nu-plugin-engine"]
system-clipboard = ["reedline/system_clipboard"]

View File

@ -1,5 +1,4 @@
use nu_engine::command_prelude::*;
use unicode_segmentation::UnicodeSegmentation;
#[derive(Clone)]
pub struct Commandline;
@ -11,45 +10,12 @@ impl Command for Commandline {
fn signature(&self) -> Signature {
Signature::build("commandline")
.input_output_types(vec![
(Type::Nothing, Type::Nothing),
(Type::String, Type::String),
])
.switch(
"cursor",
"Set or get the current cursor position",
Some('c'),
)
.switch(
"cursor-end",
"Set the current cursor position to the end of the buffer",
Some('e'),
)
.switch(
"append",
"appends the string to the end of the buffer",
Some('a'),
)
.switch(
"insert",
"inserts the string into the buffer at the cursor position",
Some('i'),
)
.switch(
"replace",
"replaces the current contents of the buffer (default)",
Some('r'),
)
.optional(
"cmd",
SyntaxShape::String,
"the string to perform the operation with",
)
.input_output_types(vec![(Type::Nothing, Type::String)])
.category(Category::Core)
}
fn usage(&self) -> &str {
"View or modify the current command line input buffer."
"View the current command line input buffer."
}
fn search_terms(&self) -> Vec<&str> {
@ -59,126 +25,11 @@ impl Command for Commandline {
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
let span = cmd.span();
let cmd = cmd.coerce_into_string()?;
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
if call.has_flag(engine_state, stack, "cursor")? {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError {
error: "`--cursor (-c)` is deprecated".into(),
msg: "Setting the current cursor position by `--cursor (-c)` is deprecated"
.into(),
span: Some(call.arguments_span()),
help: Some("Use `commandline set-cursor`".into()),
inner: vec![],
},
);
match cmd.parse::<i64>() {
Ok(n) => {
repl.cursor_pos = if n <= 0 {
0usize
} else {
repl.buffer
.grapheme_indices(true)
.map(|(i, _c)| i)
.nth(n as usize)
.unwrap_or(repl.buffer.len())
}
}
Err(_) => {
return Err(ShellError::CantConvert {
to_type: "int".to_string(),
from_type: "string".to_string(),
span,
help: Some(format!(r#"string "{cmd}" does not represent a valid int"#)),
})
}
}
} else if call.has_flag(engine_state, stack, "append")? {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError {
error: "`--append (-a)` is deprecated".into(),
msg: "Appending the string to the end of the buffer by `--append (-a)` is deprecated".into(),
span: Some(call.arguments_span()),
help: Some("Use `commandline edit --append (-a)`".into()),
inner: vec![],
},
);
repl.buffer.push_str(&cmd);
} else if call.has_flag(engine_state, stack, "insert")? {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError {
error: "`--insert (-i)` is deprecated".into(),
msg: "Inserts the string into the buffer at the cursor position by `--insert (-i)` is deprecated".into(),
span: Some(call.arguments_span()),
help: Some("Use `commandline edit --insert (-i)`".into()),
inner: vec![],
},
);
let cursor_pos = repl.cursor_pos;
repl.buffer.insert_str(cursor_pos, &cmd);
repl.cursor_pos += cmd.len();
} else {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError {
error: "`--replace (-r)` is deprecated".into(),
msg: "Replacing the current contents of the buffer by `--replace (-p)` or positional argument is deprecated".into(),
span: Some(call.arguments_span()),
help: Some("Use `commandline edit --replace (-r)`".into()),
inner: vec![],
},
);
repl.buffer = cmd;
repl.cursor_pos = repl.buffer.len();
}
Ok(Value::nothing(call.head).into_pipeline_data())
} else {
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
if call.has_flag(engine_state, stack, "cursor-end")? {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError {
error: "`--cursor-end (-e)` is deprecated".into(),
msg: "Setting the current cursor position to the end of the buffer by `--cursor-end (-e)` is deprecated".into(),
span: Some(call.arguments_span()),
help: Some("Use `commandline set-cursor --end (-e)`".into()),
inner: vec![],
},
);
repl.cursor_pos = repl.buffer.len();
Ok(Value::nothing(call.head).into_pipeline_data())
} else if call.has_flag(engine_state, stack, "cursor")? {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError {
error: "`--cursor (-c)` is deprecated".into(),
msg: "Getting the current cursor position by `--cursor (-c)` is deprecated"
.into(),
span: Some(call.arguments_span()),
help: Some("Use `commandline get-cursor`".into()),
inner: vec![],
},
);
let char_pos = repl
.buffer
.grapheme_indices(true)
.chain(std::iter::once((repl.buffer.len(), "")))
.position(|(i, _c)| i == repl.cursor_pos)
.expect("Cursor position isn't on a grapheme boundary");
Ok(Value::string(char_pos.to_string(), call.head).into_pipeline_data())
} else {
Ok(Value::string(repl.buffer.to_string(), call.head).into_pipeline_data())
}
}
let repl = engine_state.repl_state.lock().expect("repl state mutex");
Ok(Value::string(repl.buffer.clone(), call.head).into_pipeline_data())
}
}

View File

@ -107,7 +107,7 @@ impl Command for History {
file: history_path.display().to_string(),
span: head,
})?
.into_pipeline_data(ctrlc)),
.into_pipeline_data(head, ctrlc)),
HistoryFileFormat::Sqlite => Ok(history_reader
.and_then(|h| {
h.search(SearchQuery::everything(SearchDirection::Forward, None))
@ -122,7 +122,7 @@ impl Command for History {
file: history_path.display().to_string(),
span: head,
})?
.into_pipeline_data(ctrlc)),
.into_pipeline_data(head, ctrlc)),
}
}
} else {

View File

@ -36,16 +36,6 @@ For more information on input and keybindings, check:
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
Ok(Value::string(
get_full_help(
&Keybindings.signature(),
&Keybindings.examples(),
engine_state,
stack,
self.is_parser_keyword(),
),
call.head,
)
.into_pipeline_data())
Ok(Value::string(get_full_help(self, engine_state, stack), call.head).into_pipeline_data())
}
}

View File

@ -12,7 +12,7 @@ impl Command for KeybindingsDefault {
fn signature(&self) -> Signature {
Signature::build(self.name())
.category(Category::Platform)
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
.input_output_types(vec![(Type::Nothing, Type::table())])
}
fn usage(&self) -> &str {

View File

@ -14,7 +14,7 @@ impl Command for KeybindingsList {
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
.input_output_types(vec![(Type::Nothing, Type::table())])
.switch("modifiers", "list of modifiers", Some('m'))
.switch("keycodes", "list of keycodes", Some('k'))
.switch("modes", "list of edit modes", Some('o'))

View File

@ -1,13 +1,18 @@
use crate::completions::{CompletionOptions, SortBy};
use nu_protocol::{engine::StateWorkingSet, levenshtein_distance, Span};
use nu_protocol::{
engine::{Stack, StateWorkingSet},
levenshtein_distance, Span,
};
use reedline::Suggestion;
// Completer trait represents the three stages of the completion
// fetch, filter and sort
pub trait Completer {
#[allow(clippy::too_many_arguments)]
fn fetch(
&mut self,
working_set: &StateWorkingSet,
stack: &Stack,
prefix: Vec<u8>,
span: Span,
offset: usize,

View File

@ -4,16 +4,14 @@ use crate::{
};
use nu_parser::FlatShape;
use nu_protocol::{
engine::{CachedFile, EngineState, StateWorkingSet},
engine::{CachedFile, Stack, StateWorkingSet},
Span,
};
use reedline::Suggestion;
use std::sync::Arc;
use super::SemanticSuggestion;
pub struct CommandCompletion {
engine_state: Arc<EngineState>,
flattened: Vec<(Span, FlatShape)>,
flat_shape: FlatShape,
force_completion_after_space: bool,
@ -21,14 +19,11 @@ pub struct CommandCompletion {
impl CommandCompletion {
pub fn new(
engine_state: Arc<EngineState>,
_: &StateWorkingSet,
flattened: Vec<(Span, FlatShape)>,
flat_shape: FlatShape,
force_completion_after_space: bool,
) -> Self {
Self {
engine_state,
flattened,
flat_shape,
force_completion_after_space,
@ -37,13 +32,14 @@ impl CommandCompletion {
fn external_command_completion(
&self,
working_set: &StateWorkingSet,
prefix: &str,
match_algorithm: MatchAlgorithm,
) -> Vec<String> {
let mut executables = vec![];
// os agnostic way to get the PATH env var
let paths = self.engine_state.get_path_env_var();
let paths = working_set.permanent_state.get_path_env_var();
if let Some(paths) = paths {
if let Ok(paths) = paths.as_list() {
@ -52,7 +48,10 @@ impl CommandCompletion {
if let Ok(mut contents) = std::fs::read_dir(path.as_ref()) {
while let Some(Ok(item)) = contents.next() {
if self.engine_state.config.max_external_completion_results
if working_set
.permanent_state
.config
.max_external_completion_results
> executables.len() as i64
&& !executables.contains(
&item
@ -114,7 +113,7 @@ impl CommandCompletion {
if find_externals {
let results_external = self
.external_command_completion(&partial, match_algorithm)
.external_command_completion(working_set, &partial, match_algorithm)
.into_iter()
.map(move |x| SemanticSuggestion {
suggestion: Suggestion {
@ -161,6 +160,7 @@ impl Completer for CommandCompletion {
fn fetch(
&mut self,
working_set: &StateWorkingSet,
_stack: &Stack,
_prefix: Vec<u8>,
span: Span,
offset: usize,
@ -266,6 +266,8 @@ pub fn is_passthrough_command(working_set_file_contents: &[CachedFile]) -> bool
#[cfg(test)]
mod command_completions_tests {
use super::*;
use nu_protocol::engine::EngineState;
use std::sync::Arc;
#[test]
fn test_find_non_whitespace_index() {

View File

@ -7,8 +7,8 @@ use nu_engine::eval_block;
use nu_parser::{flatten_pipeline_element, parse, FlatShape};
use nu_protocol::{
debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet},
BlockId, PipelineData, Span, Value,
engine::{Closure, EngineState, Stack, StateWorkingSet},
PipelineData, Span, Value,
};
use reedline::{Completer as ReedlineCompleter, Suggestion};
use std::{str, sync::Arc};
@ -22,10 +22,10 @@ pub struct NuCompleter {
}
impl NuCompleter {
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
pub fn new(engine_state: Arc<EngineState>, stack: Arc<Stack>) -> Self {
Self {
engine_state,
stack: stack.reset_stdio().capture(),
stack: Stack::with_parent(stack).reset_out_dest().capture(),
}
}
@ -52,8 +52,15 @@ impl NuCompleter {
};
// Fetch
let mut suggestions =
completer.fetch(working_set, prefix.clone(), new_span, offset, pos, &options);
let mut suggestions = completer.fetch(
working_set,
&self.stack,
prefix.clone(),
new_span,
offset,
pos,
&options,
);
// Sort
suggestions = completer.sort(suggestions, prefix);
@ -63,15 +70,15 @@ impl NuCompleter {
fn external_completion(
&self,
block_id: BlockId,
closure: &Closure,
spans: &[String],
offset: usize,
span: Span,
) -> Option<Vec<SemanticSuggestion>> {
let block = self.engine_state.get_block(block_id);
let block = self.engine_state.get_block(closure.block_id);
let mut callee_stack = self
.stack
.gather_captures(&self.engine_state, &block.captures);
.captures_to_stack_preserve_out_dest(closure.captures.clone());
// Line
if let Some(pos_arg) = block.signature.required_positional.first() {
@ -96,9 +103,8 @@ impl NuCompleter {
PipelineData::empty(),
);
match result {
Ok(pd) => {
let value = pd.into_value(span);
match result.and_then(|data| data.into_value(span)) {
Ok(value) => {
if let Value::List { vals, .. } = value {
let result =
map_value_completions(vals.iter(), Span::new(span.start, span.end), offset);
@ -175,11 +181,8 @@ impl NuCompleter {
// Variables completion
if prefix.starts_with(b"$") || most_left_var.is_some() {
let mut completer = VariableCompletion::new(
self.engine_state.clone(),
self.stack.clone(),
most_left_var.unwrap_or((vec![], vec![])),
);
let mut completer =
VariableCompletion::new(most_left_var.unwrap_or((vec![], vec![])));
return self.process_completion(
&mut completer,
@ -210,13 +213,10 @@ impl NuCompleter {
// We got no results for internal completion
// now we can check if external completer is set and use it
if let Some(block_id) = config.external_completer {
if let Some(external_result) = self.external_completion(
block_id,
&spans,
fake_offset,
new_span,
) {
if let Some(closure) = config.external_completer.as_ref() {
if let Some(external_result) =
self.external_completion(closure, &spans, fake_offset, new_span)
{
return external_result;
}
}
@ -227,8 +227,6 @@ impl NuCompleter {
|| (flat_idx == 0 && working_set.get_span_contents(new_span).is_empty())
{
let mut completer = CommandCompletion::new(
self.engine_state.clone(),
&working_set,
flattened.clone(),
// flat_idx,
FlatShape::String,
@ -256,10 +254,7 @@ impl NuCompleter {
|| prev_expr_str == b"overlay use"
|| prev_expr_str == b"source-env"
{
let mut completer = DotNuCompletion::new(
self.engine_state.clone(),
self.stack.clone(),
);
let mut completer = DotNuCompletion::new();
return self.process_completion(
&mut completer,
@ -270,10 +265,7 @@ impl NuCompleter {
pos,
);
} else if prev_expr_str == b"ls" {
let mut completer = FileCompletion::new(
self.engine_state.clone(),
self.stack.clone(),
);
let mut completer = FileCompletion::new();
return self.process_completion(
&mut completer,
@ -291,7 +283,6 @@ impl NuCompleter {
match &flat.1 {
FlatShape::Custom(decl_id) => {
let mut completer = CustomCompletion::new(
self.engine_state.clone(),
self.stack.clone(),
*decl_id,
initial_line,
@ -307,10 +298,7 @@ impl NuCompleter {
);
}
FlatShape::Directory => {
let mut completer = DirectoryCompletion::new(
self.engine_state.clone(),
self.stack.clone(),
);
let mut completer = DirectoryCompletion::new();
return self.process_completion(
&mut completer,
@ -322,10 +310,7 @@ impl NuCompleter {
);
}
FlatShape::Filepath | FlatShape::GlobPattern => {
let mut completer = FileCompletion::new(
self.engine_state.clone(),
self.stack.clone(),
);
let mut completer = FileCompletion::new();
return self.process_completion(
&mut completer,
@ -338,8 +323,6 @@ impl NuCompleter {
}
flat_shape => {
let mut completer = CommandCompletion::new(
self.engine_state.clone(),
&working_set,
flattened.clone(),
// flat_idx,
flat_shape.clone(),
@ -360,9 +343,9 @@ impl NuCompleter {
}
// Try to complete using an external completer (if set)
if let Some(block_id) = config.external_completer {
if let Some(closure) = config.external_completer.as_ref() {
if let Some(external_result) = self.external_completion(
block_id,
closure,
&spans,
fake_offset,
new_span,
@ -372,10 +355,7 @@ impl NuCompleter {
}
// Check for file completion
let mut completer = FileCompletion::new(
self.engine_state.clone(),
self.stack.clone(),
);
let mut completer = FileCompletion::new();
out = self.process_completion(
&mut completer,
&working_set,
@ -560,7 +540,7 @@ mod completer_tests {
result.err().unwrap()
);
let mut completer = NuCompleter::new(engine_state.into(), Stack::new());
let mut completer = NuCompleter::new(engine_state.into(), Arc::new(Stack::new()));
let dataset = [
("sudo", false, "", Vec::new()),
("sudo l", true, "l", vec!["ls", "let", "lines", "loop"]),

View File

@ -7,40 +7,65 @@ use nu_protocol::{
Span,
};
use nu_utils::get_ls_colors;
use std::{
ffi::OsStr,
path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP},
use std::path::{
is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR,
};
#[derive(Clone, Default)]
pub struct PathBuiltFromString {
parts: Vec<String>,
isdir: bool,
}
fn complete_rec(
partial: &[String],
partial: &[&str],
built: &PathBuiltFromString,
cwd: &Path,
options: &CompletionOptions,
dir: bool,
isdir: bool,
) -> Vec<PathBuf> {
) -> Vec<PathBuiltFromString> {
let mut completions = vec![];
if let Ok(result) = cwd.read_dir() {
for entry in result.filter_map(|e| e.ok()) {
let entry_name = entry.file_name().to_string_lossy().into_owned();
let path = entry.path();
if let Some((&base, rest)) = partial.split_first() {
if (base == "." || base == "..") && (isdir || !rest.is_empty()) {
let mut built = built.clone();
built.parts.push(base.to_string());
built.isdir = true;
return complete_rec(rest, &built, cwd, options, dir, isdir);
}
}
if !dir || path.is_dir() {
match partial.first() {
Some(base) if matches(base, &entry_name, options) => {
let partial = &partial[1..];
if !partial.is_empty() || isdir {
completions.extend(complete_rec(partial, &path, options, dir, isdir));
if entry_name.eq(base) {
break;
}
let mut built_path = cwd.to_path_buf();
for part in &built.parts {
built_path.push(part);
}
let Ok(result) = built_path.read_dir() else {
return completions;
};
for entry in result.filter_map(|e| e.ok()) {
let entry_name = entry.file_name().to_string_lossy().into_owned();
let entry_isdir = entry.path().is_dir();
let mut built = built.clone();
built.parts.push(entry_name.clone());
built.isdir = entry_isdir;
if !dir || entry_isdir {
match partial.split_first() {
Some((base, rest)) => {
if matches(base, &entry_name, options) {
if !rest.is_empty() || isdir {
completions
.extend(complete_rec(rest, &built, cwd, options, dir, isdir));
} else {
completions.push(path)
completions.push(built);
}
}
None => completions.push(path),
_ => {}
}
None => {
completions.push(built);
}
}
}
@ -48,33 +73,23 @@ fn complete_rec(
completions
}
#[derive(Debug)]
enum OriginalCwd {
None,
Home(PathBuf),
Some(PathBuf),
// referencing a single local file
Local(PathBuf),
Home,
Prefix(String),
}
impl OriginalCwd {
fn apply(&self, p: &Path) -> String {
let mut ret = match self {
Self::None => p.to_string_lossy().into_owned(),
Self::Some(base) => pathdiff::diff_paths(p, base)
.unwrap_or(p.to_path_buf())
.to_string_lossy()
.into_owned(),
Self::Home(home) => match p.strip_prefix(home) {
Ok(suffix) => format!("~{}{}", SEP, suffix.to_string_lossy()),
_ => p.to_string_lossy().into_owned(),
},
Self::Local(base) => Path::new(".")
.join(pathdiff::diff_paths(p, base).unwrap_or(p.to_path_buf()))
.to_string_lossy()
.into_owned(),
fn apply(&self, mut p: PathBuiltFromString) -> String {
match self {
Self::None => {}
Self::Home => p.parts.insert(0, "~".to_string()),
Self::Prefix(s) => p.parts.insert(0, s.clone()),
};
if p.is_dir() {
let mut ret = p.parts.join(MAIN_SEPARATOR_STR);
if p.isdir {
ret.push(SEP);
}
ret
@ -116,79 +131,67 @@ pub fn complete_item(
};
get_ls_colors(ls_colors_env_str)
});
let mut cwd = cwd_pathbuf.clone();
let mut prefix_len = 0;
let mut original_cwd = OriginalCwd::None;
let mut components_vec: Vec<Component> = Path::new(&partial).components().collect();
// Path components that end with a single "." get normalized away,
// so if the partial path ends in a literal "." we must add it back in manually
if partial.ends_with('.') && partial.len() > 1 {
components_vec.push(Component::Normal(OsStr::new(".")));
};
let mut components = components_vec.into_iter().peekable();
let mut cwd = match components.peek().cloned() {
let mut components = Path::new(&partial).components().peekable();
match components.peek().cloned() {
Some(c @ Component::Prefix(..)) => {
// windows only by definition
components.next();
if let Some(Component::RootDir) = components.peek().cloned() {
components.next();
};
[c, Component::RootDir].iter().collect()
cwd = [c, Component::RootDir].iter().collect();
prefix_len = c.as_os_str().len();
original_cwd = OriginalCwd::Prefix(c.as_os_str().to_string_lossy().into_owned());
}
Some(c @ Component::RootDir) => {
components.next();
PathBuf::from(c.as_os_str())
// This is kind of a hack. When joining an empty string with the rest,
// we add the slash automagically
cwd = PathBuf::from(c.as_os_str());
prefix_len = 1;
original_cwd = OriginalCwd::Prefix(String::new());
}
Some(Component::Normal(home)) if home.to_string_lossy() == "~" => {
components.next();
original_cwd = OriginalCwd::Home(home_dir().unwrap_or(cwd_pathbuf.clone()));
home_dir().unwrap_or(cwd_pathbuf)
}
Some(Component::CurDir) => {
components.next();
original_cwd = match components.peek().cloned() {
Some(Component::Normal(_)) | None => OriginalCwd::Local(cwd_pathbuf.clone()),
_ => OriginalCwd::Some(cwd_pathbuf.clone()),
};
cwd_pathbuf
}
_ => {
original_cwd = OriginalCwd::Some(cwd_pathbuf.clone());
cwd_pathbuf
cwd = home_dir().unwrap_or(cwd_pathbuf);
prefix_len = 1;
original_cwd = OriginalCwd::Home;
}
_ => {}
};
let mut partial = vec![];
let after_prefix = &partial[prefix_len..];
let partial: Vec<_> = after_prefix
.strip_prefix(is_separator)
.unwrap_or(after_prefix)
.split(is_separator)
.filter(|s| !s.is_empty())
.collect();
for component in components {
match component {
Component::Prefix(..) => unreachable!(),
Component::RootDir => unreachable!(),
Component::CurDir => {}
Component::ParentDir => {
if partial.pop().is_none() {
cwd.pop();
}
}
Component::Normal(c) => partial.push(c.to_string_lossy().into_owned()),
}
}
complete_rec(partial.as_slice(), &cwd, options, want_directory, isdir)
.into_iter()
.map(|p| {
let path = original_cwd.apply(&p);
let style = ls_colors.as_ref().map(|lsc| {
lsc.style_for_path_with_metadata(
&path,
std::fs::symlink_metadata(&path).ok().as_ref(),
)
complete_rec(
partial.as_slice(),
&PathBuiltFromString::default(),
&cwd,
options,
want_directory,
isdir,
)
.into_iter()
.map(|p| {
let path = original_cwd.apply(p);
let style = ls_colors.as_ref().map(|lsc| {
lsc.style_for_path_with_metadata(&path, std::fs::symlink_metadata(&path).ok().as_ref())
.map(lscolors::Style::to_nu_ansi_term_style)
.unwrap_or_default()
});
(span, escape_path(path, want_directory), style)
})
.collect()
});
(span, escape_path(path, want_directory), style)
})
.collect()
}
// Fix files or folders with quotes or hashes

View File

@ -6,14 +6,13 @@ use nu_engine::eval_call;
use nu_protocol::{
ast::{Argument, Call, Expr, Expression},
debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet},
engine::{Stack, StateWorkingSet},
PipelineData, Span, Type, Value,
};
use nu_utils::IgnoreCaseExt;
use std::{collections::HashMap, sync::Arc};
use std::collections::HashMap;
pub struct CustomCompletion {
engine_state: Arc<EngineState>,
stack: Stack,
decl_id: usize,
line: String,
@ -21,10 +20,9 @@ pub struct CustomCompletion {
}
impl CustomCompletion {
pub fn new(engine_state: Arc<EngineState>, stack: Stack, decl_id: usize, line: String) -> Self {
pub fn new(stack: Stack, decl_id: usize, line: String) -> Self {
Self {
engine_state,
stack: stack.reset_stdio().capture(),
stack,
decl_id,
line,
sort_by: SortBy::None,
@ -35,7 +33,8 @@ impl CustomCompletion {
impl Completer for CustomCompletion {
fn fetch(
&mut self,
_: &StateWorkingSet,
working_set: &StateWorkingSet,
_stack: &Stack,
prefix: Vec<u8>,
span: Span,
offset: usize,
@ -47,7 +46,7 @@ impl Completer for CustomCompletion {
// Call custom declaration
let result = eval_call::<WithoutDebug>(
&self.engine_state,
working_set.permanent_state,
&mut self.stack,
&Call {
decl_id: self.decl_id,
@ -75,55 +74,53 @@ impl Completer for CustomCompletion {
// Parse result
let suggestions = result
.map(|pd| {
let value = pd.into_value(span);
match &value {
Value::Record { val, .. } => {
let completions = val
.get("completions")
.and_then(|val| {
val.as_list()
.ok()
.map(|it| map_value_completions(it.iter(), span, offset))
})
.unwrap_or_default();
let options = val.get("options");
.and_then(|data| data.into_value(span))
.map(|value| match &value {
Value::Record { val, .. } => {
let completions = val
.get("completions")
.and_then(|val| {
val.as_list()
.ok()
.map(|it| map_value_completions(it.iter(), span, offset))
})
.unwrap_or_default();
let options = val.get("options");
if let Some(Value::Record { val: options, .. }) = &options {
let should_sort = options
.get("sort")
.and_then(|val| val.as_bool().ok())
.unwrap_or(false);
if let Some(Value::Record { val: options, .. }) = &options {
let should_sort = options
.get("sort")
.and_then(|val| val.as_bool().ok())
.unwrap_or(false);
if should_sort {
self.sort_by = SortBy::Ascending;
}
custom_completion_options = Some(CompletionOptions {
case_sensitive: options
.get("case_sensitive")
.and_then(|val| val.as_bool().ok())
.unwrap_or(true),
positional: options
.get("positional")
.and_then(|val| val.as_bool().ok())
.unwrap_or(true),
match_algorithm: match options.get("completion_algorithm") {
Some(option) => option
.coerce_string()
.ok()
.and_then(|option| option.try_into().ok())
.unwrap_or(MatchAlgorithm::Prefix),
None => completion_options.match_algorithm,
},
});
if should_sort {
self.sort_by = SortBy::Ascending;
}
completions
custom_completion_options = Some(CompletionOptions {
case_sensitive: options
.get("case_sensitive")
.and_then(|val| val.as_bool().ok())
.unwrap_or(true),
positional: options
.get("positional")
.and_then(|val| val.as_bool().ok())
.unwrap_or(true),
match_algorithm: match options.get("completion_algorithm") {
Some(option) => option
.coerce_string()
.ok()
.and_then(|option| option.try_into().ok())
.unwrap_or(MatchAlgorithm::Prefix),
None => completion_options.match_algorithm,
},
});
}
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
_ => vec![],
completions
}
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
_ => vec![],
})
.unwrap_or_default();

View File

@ -8,25 +8,16 @@ use nu_protocol::{
levenshtein_distance, Span,
};
use reedline::Suggestion;
use std::{
path::{Path, MAIN_SEPARATOR as SEP},
sync::Arc,
};
use std::path::{Path, MAIN_SEPARATOR as SEP};
use super::SemanticSuggestion;
#[derive(Clone)]
pub struct DirectoryCompletion {
engine_state: Arc<EngineState>,
stack: Stack,
}
#[derive(Clone, Default)]
pub struct DirectoryCompletion {}
impl DirectoryCompletion {
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
Self {
engine_state,
stack,
}
pub fn new() -> Self {
Self::default()
}
}
@ -34,22 +25,24 @@ impl Completer for DirectoryCompletion {
fn fetch(
&mut self,
working_set: &StateWorkingSet,
stack: &Stack,
prefix: Vec<u8>,
span: Span,
offset: usize,
_: usize,
_pos: usize,
options: &CompletionOptions,
) -> Vec<SemanticSuggestion> {
let AdjustView { prefix, span, .. } = adjust_if_intermediate(&prefix, working_set, span);
// Filter only the folders
#[allow(deprecated)]
let output: Vec<_> = directory_completion(
span,
&prefix,
&self.engine_state.current_work_dir(),
&working_set.permanent_state.current_work_dir(),
options,
self.engine_state.as_ref(),
&self.stack,
working_set.permanent_state,
stack,
)
.into_iter()
.map(move |x| SemanticSuggestion {

View File

@ -1,39 +1,31 @@
use crate::completions::{file_path_completion, Completer, CompletionOptions, SortBy};
use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet},
engine::{Stack, StateWorkingSet},
Span,
};
use reedline::Suggestion;
use std::{
path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR},
sync::Arc,
};
use std::path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR};
use super::SemanticSuggestion;
#[derive(Clone)]
pub struct DotNuCompletion {
engine_state: Arc<EngineState>,
stack: Stack,
}
#[derive(Clone, Default)]
pub struct DotNuCompletion {}
impl DotNuCompletion {
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
Self {
engine_state,
stack,
}
pub fn new() -> Self {
Self::default()
}
}
impl Completer for DotNuCompletion {
fn fetch(
&mut self,
_: &StateWorkingSet,
working_set: &StateWorkingSet,
stack: &Stack,
prefix: Vec<u8>,
span: Span,
offset: usize,
_: usize,
_pos: usize,
options: &CompletionOptions,
) -> Vec<SemanticSuggestion> {
let prefix_str = String::from_utf8_lossy(&prefix).replace('`', "");
@ -49,26 +41,25 @@ impl Completer for DotNuCompletion {
let mut is_current_folder = false;
// Fetch the lib dirs
let lib_dirs: Vec<String> =
if let Some(lib_dirs) = self.engine_state.get_env_var("NU_LIB_DIRS") {
lib_dirs
.as_list()
.into_iter()
.flat_map(|it| {
it.iter().map(|x| {
x.to_path()
.expect("internal error: failed to convert lib path")
})
let lib_dirs: Vec<String> = if let Some(lib_dirs) = working_set.get_env_var("NU_LIB_DIRS") {
lib_dirs
.as_list()
.into_iter()
.flat_map(|it| {
it.iter().map(|x| {
x.to_path()
.expect("internal error: failed to convert lib path")
})
.map(|it| {
it.into_os_string()
.into_string()
.expect("internal error: failed to convert OS path")
})
.collect()
} else {
vec![]
};
})
.map(|it| {
it.into_os_string()
.into_string()
.expect("internal error: failed to convert OS path")
})
.collect()
} else {
vec![]
};
// Check if the base_dir is a folder
// rsplit_once removes the separator
@ -84,7 +75,8 @@ impl Completer for DotNuCompletion {
partial = base_dir_partial;
} else {
// Fetch the current folder
let current_folder = self.engine_state.current_work_dir();
#[allow(deprecated)]
let current_folder = working_set.permanent_state.current_work_dir();
is_current_folder = true;
// Add the current folder and the lib dirs into the
@ -103,8 +95,8 @@ impl Completer for DotNuCompletion {
&partial,
&search_dir,
options,
self.engine_state.as_ref(),
&self.stack,
working_set.permanent_state,
stack,
);
completions
.into_iter()

View File

@ -9,25 +9,16 @@ use nu_protocol::{
};
use nu_utils::IgnoreCaseExt;
use reedline::Suggestion;
use std::{
path::{Path, MAIN_SEPARATOR as SEP},
sync::Arc,
};
use std::path::{Path, MAIN_SEPARATOR as SEP};
use super::SemanticSuggestion;
#[derive(Clone)]
pub struct FileCompletion {
engine_state: Arc<EngineState>,
stack: Stack,
}
#[derive(Clone, Default)]
pub struct FileCompletion {}
impl FileCompletion {
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
Self {
engine_state,
stack,
}
pub fn new() -> Self {
Self::default()
}
}
@ -35,10 +26,11 @@ impl Completer for FileCompletion {
fn fetch(
&mut self,
working_set: &StateWorkingSet,
stack: &Stack,
prefix: Vec<u8>,
span: Span,
offset: usize,
_: usize,
_pos: usize,
options: &CompletionOptions,
) -> Vec<SemanticSuggestion> {
let AdjustView {
@ -47,14 +39,15 @@ impl Completer for FileCompletion {
readjusted,
} = adjust_if_intermediate(&prefix, working_set, span);
#[allow(deprecated)]
let output: Vec<_> = complete_item(
readjusted,
span,
&prefix,
&self.engine_state.current_work_dir(),
&working_set.permanent_state.current_work_dir(),
options,
self.engine_state.as_ref(),
&self.stack,
working_set.permanent_state,
stack,
)
.into_iter()
.map(move |x| SemanticSuggestion {

View File

@ -1,7 +1,7 @@
use crate::completions::{Completer, CompletionOptions};
use nu_protocol::{
ast::{Expr, Expression},
engine::StateWorkingSet,
engine::{Stack, StateWorkingSet},
Span,
};
use reedline::Suggestion;
@ -23,10 +23,11 @@ impl Completer for FlagCompletion {
fn fetch(
&mut self,
working_set: &StateWorkingSet,
_stack: &Stack,
prefix: Vec<u8>,
span: Span,
offset: usize,
_: usize,
_pos: usize,
options: &CompletionOptions,
) -> Vec<SemanticSuggestion> {
// Check if it's a flag

View File

@ -3,30 +3,20 @@ use crate::completions::{
};
use nu_engine::{column::get_columns, eval_variable};
use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet},
engine::{Stack, StateWorkingSet},
Span, Value,
};
use reedline::Suggestion;
use std::{str, sync::Arc};
use std::str;
#[derive(Clone)]
pub struct VariableCompletion {
engine_state: Arc<EngineState>, // TODO: Is engine state necessary? It's already a part of working set in fetch()
stack: Stack,
var_context: (Vec<u8>, Vec<Vec<u8>>), // tuple with $var and the sublevels (.b.c.d)
}
impl VariableCompletion {
pub fn new(
engine_state: Arc<EngineState>,
stack: Stack,
var_context: (Vec<u8>, Vec<Vec<u8>>),
) -> Self {
Self {
engine_state,
stack,
var_context,
}
pub fn new(var_context: (Vec<u8>, Vec<Vec<u8>>)) -> Self {
Self { var_context }
}
}
@ -34,10 +24,11 @@ impl Completer for VariableCompletion {
fn fetch(
&mut self,
working_set: &StateWorkingSet,
stack: &Stack,
prefix: Vec<u8>,
span: Span,
offset: usize,
_: usize,
_pos: usize,
options: &CompletionOptions,
) -> Vec<SemanticSuggestion> {
let mut output = vec![];
@ -54,7 +45,7 @@ impl Completer for VariableCompletion {
if !var_str.is_empty() {
// Completion for $env.<tab>
if var_str == "$env" {
let env_vars = self.stack.get_env_vars(&self.engine_state);
let env_vars = stack.get_env_vars(working_set.permanent_state);
// Return nested values
if sublevels_count > 0 {
@ -68,9 +59,7 @@ impl Completer for VariableCompletion {
self.var_context.1.clone().into_iter().skip(1).collect();
if let Some(val) = env_vars.get(&target_var_str) {
for suggestion in
nested_suggestions(val.clone(), nested_levels, current_span)
{
for suggestion in nested_suggestions(val, &nested_levels, current_span) {
if options.match_algorithm.matches_u8_insensitive(
options.case_sensitive,
suggestion.suggestion.value.as_bytes(),
@ -112,13 +101,12 @@ impl Completer for VariableCompletion {
if var_str == "$nu" {
// Eval nu var
if let Ok(nuval) = eval_variable(
&self.engine_state,
&self.stack,
working_set.permanent_state,
stack,
nu_protocol::NU_VARIABLE_ID,
nu_protocol::Span::new(current_span.start, current_span.end),
) {
for suggestion in
nested_suggestions(nuval, self.var_context.1.clone(), current_span)
for suggestion in nested_suggestions(&nuval, &self.var_context.1, current_span)
{
if options.match_algorithm.matches_u8_insensitive(
options.case_sensitive,
@ -136,12 +124,11 @@ impl Completer for VariableCompletion {
// Completion other variable types
if let Some(var_id) = var_id {
// Extract the variable value from the stack
let var = self.stack.get_var(var_id, Span::new(span.start, span.end));
let var = stack.get_var(var_id, Span::new(span.start, span.end));
// If the value exists and it's of type Record
if let Ok(value) = var {
for suggestion in
nested_suggestions(value, self.var_context.1.clone(), current_span)
for suggestion in nested_suggestions(&value, &self.var_context.1, current_span)
{
if options.match_algorithm.matches_u8_insensitive(
options.case_sensitive,
@ -211,7 +198,11 @@ impl Completer for VariableCompletion {
// Permanent state vars
// for scope in &self.engine_state.scope {
for overlay_frame in self.engine_state.active_overlays(&removed_overlays).rev() {
for overlay_frame in working_set
.permanent_state
.active_overlays(&removed_overlays)
.rev()
{
for v in &overlay_frame.vars {
if options.match_algorithm.matches_u8_insensitive(
options.case_sensitive,
@ -244,39 +235,21 @@ impl Completer for VariableCompletion {
// Find recursively the values for sublevels
// if no sublevels are set it returns the current value
fn nested_suggestions(
val: Value,
sublevels: Vec<Vec<u8>>,
val: &Value,
sublevels: &[Vec<u8>],
current_span: reedline::Span,
) -> Vec<SemanticSuggestion> {
let mut output: Vec<SemanticSuggestion> = vec![];
let value = recursive_value(val, sublevels);
let value = recursive_value(val, sublevels).unwrap_or_else(Value::nothing);
let kind = SuggestionKind::Type(value.get_type());
match value {
Value::Record { val, .. } => {
// Add all the columns as completion
for (col, _) in val.into_iter() {
for col in val.columns() {
output.push(SemanticSuggestion {
suggestion: Suggestion {
value: col,
description: None,
style: None,
extra: None,
span: current_span,
append_whitespace: false,
},
kind: Some(kind.clone()),
});
}
output
}
Value::LazyRecord { val, .. } => {
// Add all the columns as completion
for column_name in val.column_names() {
output.push(SemanticSuggestion {
suggestion: Suggestion {
value: column_name.to_string(),
value: col.clone(),
description: None,
style: None,
extra: None,
@ -311,56 +284,36 @@ fn nested_suggestions(
}
// Extracts the recursive value (e.g: $var.a.b.c)
fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
fn recursive_value(val: &Value, sublevels: &[Vec<u8>]) -> Result<Value, Span> {
// Go to next sublevel
if let Some(next_sublevel) = sublevels.clone().into_iter().next() {
if let Some((sublevel, next_sublevels)) = sublevels.split_first() {
let span = val.span();
match val {
Value::Record { val, .. } => {
for item in *val {
// Check if index matches with sublevel
if item.0.as_bytes().to_vec() == next_sublevel {
// If matches try to fetch recursively the next
return recursive_value(item.1, sublevels.into_iter().skip(1).collect());
}
if let Some((_, value)) = val.iter().find(|(key, _)| key.as_bytes() == sublevel) {
// If matches try to fetch recursively the next
recursive_value(value, next_sublevels)
} else {
// Current sublevel value not found
Err(span)
}
// Current sublevel value not found
return Value::nothing(span);
}
Value::LazyRecord { val, .. } => {
for col in val.column_names() {
if col.as_bytes().to_vec() == next_sublevel {
return recursive_value(
val.get_column_value(col).unwrap_or_default(),
sublevels.into_iter().skip(1).collect(),
);
}
}
// Current sublevel value not found
return Value::nothing(span);
}
Value::List { vals, .. } => {
for col in get_columns(vals.as_slice()) {
if col.as_bytes().to_vec() == next_sublevel {
return recursive_value(
Value::list(vals, span)
.get_data_by_key(&col)
.unwrap_or_default(),
sublevels.into_iter().skip(1).collect(),
);
if col.as_bytes() == *sublevel {
let val = val.get_data_by_key(&col).ok_or(span)?;
return recursive_value(&val, next_sublevels);
}
}
// Current sublevel value not found
return Value::nothing(span);
Err(span)
}
_ => return val,
_ => Ok(val.clone()),
}
} else {
Ok(val.clone())
}
val
}
impl MatchAlgorithm {

View File

@ -1,18 +1,20 @@
use crate::util::eval_source;
#[cfg(feature = "plugin")]
use nu_path::canonicalize_with;
use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet},
report_error, HistoryFileFormat, PipelineData,
};
#[cfg(feature = "plugin")]
use nu_protocol::{ParseError, Spanned};
use nu_protocol::{engine::StateWorkingSet, report_error, ParseError, PluginRegistryFile, Spanned};
use nu_protocol::{
engine::{EngineState, Stack},
report_error_new, HistoryFileFormat, PipelineData,
};
#[cfg(feature = "plugin")]
use nu_utils::utils::perf;
use std::path::PathBuf;
#[cfg(feature = "plugin")]
const PLUGIN_FILE: &str = "plugin.nu";
const PLUGIN_FILE: &str = "plugin.msgpackz";
#[cfg(feature = "plugin")]
const OLD_PLUGIN_FILE: &str = "plugin.nu";
const HISTORY_FILE_TXT: &str = "history.txt";
const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
@ -20,40 +22,149 @@ const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
#[cfg(feature = "plugin")]
pub fn read_plugin_file(
engine_state: &mut EngineState,
stack: &mut Stack,
plugin_file: Option<Spanned<String>>,
storage_path: &str,
) {
let start_time = std::time::Instant::now();
let mut plug_path = String::new();
// Reading signatures from signature file
// The plugin.nu file stores the parsed signature collected from each registered plugin
add_plugin_file(engine_state, plugin_file, storage_path);
use nu_protocol::ShellError;
use std::path::Path;
let plugin_path = engine_state.plugin_signatures.clone();
if let Some(plugin_path) = plugin_path {
let plugin_filename = plugin_path.to_string_lossy();
plug_path = plugin_filename.to_string();
if let Ok(contents) = std::fs::read(&plugin_path) {
eval_source(
engine_state,
stack,
&contents,
&plugin_filename,
PipelineData::empty(),
false,
);
}
let span = plugin_file.as_ref().map(|s| s.span);
// Check and warn + abort if this is a .nu plugin file
if plugin_file
.as_ref()
.and_then(|p| Path::new(&p.item).extension())
.is_some_and(|ext| ext == "nu")
{
report_error_new(
engine_state,
&ShellError::GenericError {
error: "Wrong plugin file format".into(),
msg: ".nu plugin files are no longer supported".into(),
span,
help: Some("please recreate this file in the new .msgpackz format".into()),
inner: vec![],
},
);
return;
}
let mut start_time = std::time::Instant::now();
// Reading signatures from plugin registry file
// The plugin.msgpackz file stores the parsed signature collected from each registered plugin
add_plugin_file(engine_state, plugin_file.clone(), storage_path);
perf(
&format!("read_plugin_file {}", &plug_path),
"add plugin file to engine_state",
start_time,
file!(),
line!(),
column!(),
engine_state.get_config().use_ansi_coloring,
);
start_time = std::time::Instant::now();
let plugin_path = engine_state.plugin_path.clone();
if let Some(plugin_path) = plugin_path {
// Open the plugin file
let mut file = match std::fs::File::open(&plugin_path) {
Ok(file) => file,
Err(err) => {
if err.kind() == std::io::ErrorKind::NotFound {
log::warn!("Plugin file not found: {}", plugin_path.display());
// Try migration of an old plugin file if this wasn't a custom plugin file
if plugin_file.is_none() && migrate_old_plugin_file(engine_state, storage_path)
{
let Ok(file) = std::fs::File::open(&plugin_path) else {
log::warn!("Failed to load newly migrated plugin file");
return;
};
file
} else {
return;
}
} else {
report_error_new(
engine_state,
&ShellError::GenericError {
error: format!(
"Error while opening plugin registry file: {}",
plugin_path.display()
),
msg: "plugin path defined here".into(),
span,
help: None,
inner: vec![err.into()],
},
);
return;
}
}
};
// Abort if the file is empty.
if file.metadata().is_ok_and(|m| m.len() == 0) {
log::warn!(
"Not reading plugin file because it's empty: {}",
plugin_path.display()
);
return;
}
// Read the contents of the plugin file
let contents = match PluginRegistryFile::read_from(&mut file, span) {
Ok(contents) => contents,
Err(err) => {
log::warn!("Failed to read plugin registry file: {err:?}");
report_error_new(
engine_state,
&ShellError::GenericError {
error: format!(
"Error while reading plugin registry file: {}",
plugin_path.display()
),
msg: "plugin path defined here".into(),
span,
help: Some(
"you might try deleting the file and registering all of your \
plugins again"
.into(),
),
inner: vec![],
},
);
return;
}
};
perf(
&format!("read plugin file {}", plugin_path.display()),
start_time,
file!(),
line!(),
column!(),
engine_state.get_config().use_ansi_coloring,
);
start_time = std::time::Instant::now();
let mut working_set = StateWorkingSet::new(engine_state);
nu_plugin_engine::load_plugin_file(&mut working_set, &contents, span);
if let Err(err) = engine_state.merge_delta(working_set.render()) {
report_error_new(engine_state, &err);
return;
}
perf(
&format!("load plugin file {}", plugin_path.display()),
start_time,
file!(),
line!(),
column!(),
engine_state.get_config().use_ansi_coloring,
);
}
}
#[cfg(feature = "plugin")]
@ -62,23 +173,39 @@ pub fn add_plugin_file(
plugin_file: Option<Spanned<String>>,
storage_path: &str,
) {
let working_set = StateWorkingSet::new(engine_state);
let cwd = working_set.get_cwd();
use std::path::Path;
if let Some(plugin_file) = plugin_file {
if let Ok(path) = canonicalize_with(&plugin_file.item, cwd) {
engine_state.plugin_signatures = Some(path)
} else {
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
report_error(&working_set, &e);
let working_set = StateWorkingSet::new(engine_state);
if let Ok(cwd) = engine_state.cwd_as_string(None) {
if let Some(plugin_file) = plugin_file {
let path = Path::new(&plugin_file.item);
let path_dir = path.parent().unwrap_or(path);
// Just try to canonicalize the directory of the plugin file first.
if let Ok(path_dir) = canonicalize_with(path_dir, &cwd) {
// Try to canonicalize the actual filename, but it's ok if that fails. The file doesn't
// have to exist.
let path = path_dir.join(path.file_name().unwrap_or(path.as_os_str()));
let path = canonicalize_with(&path, &cwd).unwrap_or(path);
engine_state.plugin_path = Some(path)
} else {
// It's an error if the directory for the plugin file doesn't exist.
report_error(
&working_set,
&ParseError::FileNotFound(
path_dir.to_string_lossy().into_owned(),
plugin_file.span,
),
);
}
} else if let Some(mut plugin_path) = nu_path::config_dir() {
// Path to store plugins signatures
plugin_path.push(storage_path);
let mut plugin_path = canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path);
plugin_path.push(PLUGIN_FILE);
let plugin_path = canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path);
engine_state.plugin_path = Some(plugin_path);
}
} else if let Some(mut plugin_path) = nu_path::config_dir() {
// Path to store plugins signatures
plugin_path.push(storage_path);
let mut plugin_path = canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path);
plugin_path.push(PLUGIN_FILE);
let plugin_path = canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path);
engine_state.plugin_signatures = Some(plugin_path);
}
}
@ -91,6 +218,10 @@ pub fn eval_config_contents(
let config_filename = config_path.to_string_lossy();
if let Ok(contents) = std::fs::read(&config_path) {
// Set the current active file to the config file.
let prev_file = engine_state.file.take();
engine_state.file = Some(config_path.clone());
eval_source(
engine_state,
stack,
@ -100,17 +231,18 @@ pub fn eval_config_contents(
false,
);
// Restore the current active file.
engine_state.file = prev_file;
// Merge the environment in case env vars changed in the config
match nu_engine::env::current_dir(engine_state, stack) {
match engine_state.cwd(Some(stack)) {
Ok(cwd) => {
if let Err(e) = engine_state.merge_env(stack, cwd) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
report_error_new(engine_state, &e);
}
}
Err(e) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
report_error_new(engine_state, &e);
}
}
}
@ -127,3 +259,132 @@ pub(crate) fn get_history_path(storage_path: &str, mode: HistoryFileFormat) -> O
history_path
})
}
#[cfg(feature = "plugin")]
pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -> bool {
use nu_protocol::{
PluginExample, PluginIdentity, PluginRegistryItem, PluginRegistryItemData, PluginSignature,
ShellError,
};
use std::collections::BTreeMap;
let start_time = std::time::Instant::now();
let Ok(cwd) = engine_state.cwd_as_string(None) else {
return false;
};
let Some(config_dir) = nu_path::config_dir().and_then(|mut dir| {
dir.push(storage_path);
nu_path::canonicalize_with(dir, &cwd).ok()
}) else {
return false;
};
let Ok(old_plugin_file_path) = nu_path::canonicalize_with(OLD_PLUGIN_FILE, &config_dir) else {
return false;
};
let old_contents = match std::fs::read(&old_plugin_file_path) {
Ok(old_contents) => old_contents,
Err(err) => {
report_error_new(
engine_state,
&ShellError::GenericError {
error: "Can't read old plugin file to migrate".into(),
msg: "".into(),
span: None,
help: Some(err.to_string()),
inner: vec![],
},
);
return false;
}
};
// Make a copy of the engine state, because we'll read the newly generated file
let mut engine_state = engine_state.clone();
let mut stack = Stack::new();
if eval_source(
&mut engine_state,
&mut stack,
&old_contents,
&old_plugin_file_path.to_string_lossy(),
PipelineData::Empty,
false,
) != 0
{
return false;
}
// Now that the plugin commands are loaded, we just have to generate the file
let mut contents = PluginRegistryFile::new();
let mut groups = BTreeMap::<PluginIdentity, Vec<PluginSignature>>::new();
for decl in engine_state.plugin_decls() {
if let Some(identity) = decl.plugin_identity() {
groups
.entry(identity.clone())
.or_default()
.push(PluginSignature {
sig: decl.signature(),
examples: decl
.examples()
.into_iter()
.map(PluginExample::from)
.collect(),
})
}
}
for (identity, commands) in groups {
contents.upsert_plugin(PluginRegistryItem {
name: identity.name().to_owned(),
filename: identity.filename().to_owned(),
shell: identity.shell().map(|p| p.to_owned()),
data: PluginRegistryItemData::Valid { commands },
});
}
// Write the new file
let new_plugin_file_path = config_dir.join(PLUGIN_FILE);
if let Err(err) = std::fs::File::create(&new_plugin_file_path)
.map_err(|e| e.into())
.and_then(|file| contents.write_to(file, None))
{
report_error_new(
&engine_state,
&ShellError::GenericError {
error: "Failed to save migrated plugin file".into(),
msg: "".into(),
span: None,
help: Some("ensure `$nu.plugin-path` is writable".into()),
inner: vec![err],
},
);
return false;
}
if engine_state.is_interactive {
eprintln!(
"Your old plugin.nu file has been migrated to the new format: {}",
new_plugin_file_path.display()
);
eprintln!(
"The plugin.nu file has not been removed. If `plugin list` looks okay, \
you may do so manually."
);
}
perf(
"migrate old plugin file",
start_time,
file!(),
line!(),
column!(),
engine_state.get_config().use_ansi_coloring,
);
true
}

View File

@ -1,12 +1,12 @@
use log::info;
use miette::Result;
use nu_engine::{convert_env_values, eval_block};
use nu_parser::parse;
use nu_protocol::{
debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet},
report_error, PipelineData, Spanned, Value,
report_error, PipelineData, ShellError, Spanned, Value,
};
use std::sync::Arc;
/// Run a command (or commands) given to us by the user
pub fn evaluate_commands(
@ -15,13 +15,10 @@ pub fn evaluate_commands(
stack: &mut Stack,
input: PipelineData,
table_mode: Option<Value>,
) -> Result<Option<i64>> {
no_newline: bool,
) -> Result<(), ShellError> {
// Translate environment variables from Strings to Values
if let Some(e) = convert_env_values(engine_state, stack) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
std::process::exit(1);
}
convert_env_values(engine_state, stack)?;
// Parse the source code
let (block, delta) = {
@ -40,7 +37,6 @@ pub fn evaluate_commands(
if let Some(err) = working_set.parse_errors.first() {
report_error(&working_set, err);
std::process::exit(1);
}
@ -48,29 +44,27 @@ pub fn evaluate_commands(
};
// Update permanent state
if let Err(err) = engine_state.merge_delta(delta) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
}
engine_state.merge_delta(delta)?;
// Run the block
let exit_code = match eval_block::<WithoutDebug>(engine_state, stack, &block, input) {
Ok(pipeline_data) => {
let mut config = engine_state.get_config().clone();
if let Some(t_mode) = table_mode {
config.table_mode = t_mode.coerce_str()?.parse().unwrap_or_default();
}
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
}
Err(err) => {
let working_set = StateWorkingSet::new(engine_state);
let pipeline = eval_block::<WithoutDebug>(engine_state, stack, &block, input)?;
report_error(&working_set, &err);
std::process::exit(1);
if let PipelineData::Value(Value::Error { error, .. }, ..) = pipeline {
return Err(*error);
}
if let Some(t_mode) = table_mode {
Arc::make_mut(&mut engine_state.config).table_mode =
t_mode.coerce_str()?.parse().unwrap_or_default();
}
if let Some(status) = pipeline.print(engine_state, stack, no_newline, false)? {
if status.code() != 0 {
std::process::exit(status.code())
}
};
}
info!("evaluate {}:{}:{}", file!(), line!(), column!());
Ok(exit_code)
Ok(())
}

View File

@ -1,93 +1,59 @@
use crate::util::eval_source;
use log::{info, trace};
use miette::{IntoDiagnostic, Result};
use nu_engine::{convert_env_values, current_dir, eval_block};
use nu_engine::{convert_env_values, eval_block};
use nu_parser::parse;
use nu_path::canonicalize_with;
use nu_protocol::{
ast::Call,
debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet},
report_error, Config, PipelineData, ShellError, Span, Value,
report_error, PipelineData, ShellError, Span, Value,
};
use nu_utils::stdout_write_all_and_flush;
use std::sync::Arc;
/// Main function used when a file path is found as argument for nu
/// Entry point for evaluating a file.
///
/// If the file contains a main command, it is invoked with `args` and the pipeline data from `input`;
/// otherwise, the pipeline data is forwarded to the first command in the file, and `args` are ignored.
pub fn evaluate_file(
path: String,
args: &[String],
engine_state: &mut EngineState,
stack: &mut Stack,
input: PipelineData,
) -> Result<()> {
// Translate environment variables from Strings to Values
if let Some(e) = convert_env_values(engine_state, stack) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
std::process::exit(1);
}
) -> Result<(), ShellError> {
// Convert environment variables from Strings to Values and store them in the engine state.
convert_env_values(engine_state, stack)?;
let cwd = current_dir(engine_state, stack)?;
let cwd = engine_state.cwd_as_string(Some(stack))?;
let file_path = canonicalize_with(&path, cwd).unwrap_or_else(|e| {
let working_set = StateWorkingSet::new(engine_state);
report_error(
&working_set,
&ShellError::FileNotFoundCustom {
msg: format!("Could not access file '{}': {:?}", path, e.to_string()),
span: Span::unknown(),
},
);
std::process::exit(1);
});
let file_path =
canonicalize_with(&path, cwd).map_err(|err| ShellError::FileNotFoundCustom {
msg: format!("Could not access file '{path}': {err}"),
span: Span::unknown(),
})?;
let file_path_str = file_path.to_str().unwrap_or_else(|| {
let working_set = StateWorkingSet::new(engine_state);
report_error(
&working_set,
&ShellError::NonUtf8Custom {
msg: format!(
"Input file name '{}' is not valid UTF8",
file_path.to_string_lossy()
),
span: Span::unknown(),
},
);
std::process::exit(1);
});
let file_path_str = file_path
.to_str()
.ok_or_else(|| ShellError::NonUtf8Custom {
msg: format!(
"Input file name '{}' is not valid UTF8",
file_path.to_string_lossy()
),
span: Span::unknown(),
})?;
let file = std::fs::read(&file_path)
.into_diagnostic()
.unwrap_or_else(|e| {
let working_set = StateWorkingSet::new(engine_state);
report_error(
&working_set,
&ShellError::FileNotFoundCustom {
msg: format!(
"Could not read file '{}': {:?}",
file_path_str,
e.to_string()
),
span: Span::unknown(),
},
);
std::process::exit(1);
});
let file = std::fs::read(&file_path).map_err(|err| ShellError::FileNotFoundCustom {
msg: format!("Could not read file '{file_path_str}': {err}"),
span: Span::unknown(),
})?;
engine_state.file = Some(file_path.clone());
engine_state.start_in_file(Some(file_path_str));
let parent = file_path.parent().unwrap_or_else(|| {
let working_set = StateWorkingSet::new(engine_state);
report_error(
&working_set,
&ShellError::FileNotFoundCustom {
msg: format!("The file path '{file_path_str}' does not have a parent"),
span: Span::unknown(),
},
);
std::process::exit(1);
});
let parent = file_path
.parent()
.ok_or_else(|| ShellError::FileNotFoundCustom {
msg: format!("The file path '{file_path_str}' does not have a parent"),
span: Span::unknown(),
})?;
stack.add_env_var(
"FILE_PWD".to_string(),
@ -104,17 +70,19 @@ pub fn evaluate_file(
let source_filename = file_path
.file_name()
.expect("internal error: script missing filename");
.expect("internal error: missing filename");
let mut working_set = StateWorkingSet::new(engine_state);
trace!("parsing file: {}", file_path_str);
let block = parse(&mut working_set, Some(file_path_str), &file, false);
// If any parse errors were found, report the first error and exit.
if let Some(err) = working_set.parse_errors.first() {
report_error(&working_set, err);
std::process::exit(1);
}
// Look for blocks whose name starts with "main" and replace it with the filename.
for block in working_set.delta.blocks.iter_mut().map(Arc::make_mut) {
if block.signature.name == "main" {
block.signature.name = source_filename.to_string_lossy().to_string();
@ -124,131 +92,49 @@ pub fn evaluate_file(
}
}
let _ = engine_state.merge_delta(working_set.delta);
// Merge the changes into the engine state.
engine_state.merge_delta(working_set.delta)?;
if engine_state.find_decl(b"main", &[]).is_some() {
let args = format!("main {}", args.join(" "));
let pipeline_data =
eval_block::<WithoutDebug>(engine_state, stack, &block, PipelineData::empty());
let pipeline_data = match pipeline_data {
Err(ShellError::Return { .. }) => {
// allows early exists before `main` is run.
return Ok(());
}
x => x,
}
.unwrap_or_else(|e| {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
std::process::exit(1);
});
let result = pipeline_data.print(engine_state, stack, true, false);
match result {
Err(err) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
std::process::exit(1);
}
Ok(exit_code) => {
if exit_code != 0 {
std::process::exit(exit_code as i32);
// Check if the file contains a main command.
let exit_code = if engine_state.find_decl(b"main", &[]).is_some() {
// Evaluate the file, but don't run main yet.
let pipeline =
match eval_block::<WithoutDebug>(engine_state, stack, &block, PipelineData::empty()) {
Ok(data) => data,
Err(ShellError::Return { .. }) => {
// Allow early return before main is run.
return Ok(());
}
Err(err) => return Err(err),
};
// Print the pipeline output of the last command of the file.
if let Some(status) = pipeline.print(engine_state, stack, true, false)? {
if status.code() != 0 {
std::process::exit(status.code())
}
}
if !eval_source(
// Invoke the main command with arguments.
// Arguments with whitespaces are quoted, thus can be safely concatenated by whitespace.
let args = format!("main {}", args.join(" "));
eval_source(
engine_state,
stack,
args.as_bytes(),
"<commandline>",
input,
true,
) {
std::process::exit(1);
}
} else if !eval_source(engine_state, stack, &file, file_path_str, input, true) {
std::process::exit(1);
)
} else {
eval_source(engine_state, stack, &file, file_path_str, input, true)
};
if exit_code != 0 {
std::process::exit(exit_code)
}
info!("evaluate {}:{}:{}", file!(), line!(), column!());
Ok(())
}
pub(crate) fn print_table_or_error(
engine_state: &mut EngineState,
stack: &mut Stack,
mut pipeline_data: PipelineData,
config: &mut Config,
) -> Option<i64> {
let exit_code = match &mut pipeline_data {
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
_ => None,
};
// Change the engine_state config to use the passed in configuration
engine_state.set_config(config.clone());
if let PipelineData::Value(Value::Error { error, .. }, ..) = &pipeline_data {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &**error);
std::process::exit(1);
}
if let Some(decl_id) = engine_state.find_decl("table".as_bytes(), &[]) {
let command = engine_state.get_decl(decl_id);
if command.get_block_id().is_some() {
print_or_exit(pipeline_data, engine_state, config);
} else {
// The final call on table command, it's ok to set redirect_output to false.
let call = Call::new(Span::new(0, 0));
let table = command.run(engine_state, stack, &call, pipeline_data);
match table {
Ok(table) => {
print_or_exit(table, engine_state, config);
}
Err(error) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &error);
std::process::exit(1);
}
}
}
} else {
print_or_exit(pipeline_data, engine_state, config);
}
// Make sure everything has finished
if let Some(exit_code) = exit_code {
let mut exit_code: Vec<_> = exit_code.into_iter().collect();
exit_code
.pop()
.and_then(|last_exit_code| match last_exit_code {
Value::Int { val: code, .. } => Some(code),
_ => None,
})
} else {
None
}
}
fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, config: &Config) {
for item in pipeline_data {
if let Value::Error { error, .. } = item {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &*error);
std::process::exit(1);
}
let out = item.to_expanded_string("\n", config) + "\n";
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{err}"));
}
}

View File

@ -32,4 +32,6 @@ pub use validation::NuValidator;
#[cfg(feature = "plugin")]
pub use config_files::add_plugin_file;
#[cfg(feature = "plugin")]
pub use config_files::migrate_old_plugin_file;
#[cfg(feature = "plugin")]
pub use config_files::read_plugin_file;

View File

@ -12,50 +12,49 @@ impl NuHelpCompleter {
}
fn completion_helper(&self, line: &str, pos: usize) -> Vec<Suggestion> {
let full_commands = self.0.get_signatures_with_examples(false);
let folded_line = line.to_folded_case();
//Vec<(Signature, Vec<Example>, bool, bool)> {
let mut commands = full_commands
.iter()
.filter(|(sig, _, _, _, _)| {
sig.name.to_folded_case().contains(&folded_line)
|| sig.usage.to_folded_case().contains(&folded_line)
|| sig
.search_terms
.iter()
let mut commands = self
.0
.get_decls_sorted(false)
.into_iter()
.filter_map(|(_, decl_id)| {
let decl = self.0.get_decl(decl_id);
(decl.name().to_folded_case().contains(&folded_line)
|| decl.usage().to_folded_case().contains(&folded_line)
|| decl
.search_terms()
.into_iter()
.any(|term| term.to_folded_case().contains(&folded_line))
|| sig.extra_usage.to_folded_case().contains(&folded_line)
|| decl.extra_usage().to_folded_case().contains(&folded_line))
.then_some(decl)
})
.collect::<Vec<_>>();
commands.sort_by(|(a, _, _, _, _), (b, _, _, _, _)| {
let a_distance = levenshtein_distance(line, &a.name);
let b_distance = levenshtein_distance(line, &b.name);
a_distance.cmp(&b_distance)
});
commands.sort_by_cached_key(|decl| levenshtein_distance(line, decl.name()));
commands
.into_iter()
.map(|(sig, examples, _, _, _)| {
.map(|decl| {
let mut long_desc = String::new();
let usage = &sig.usage;
let usage = decl.usage();
if !usage.is_empty() {
long_desc.push_str(usage);
long_desc.push_str("\r\n\r\n");
}
let extra_usage = &sig.extra_usage;
let extra_usage = decl.extra_usage();
if !extra_usage.is_empty() {
long_desc.push_str(extra_usage);
long_desc.push_str("\r\n\r\n");
}
let sig = decl.signature();
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
if !sig.named.is_empty() {
long_desc.push_str(&get_flags_section(Some(&*self.0.clone()), sig, |v| {
long_desc.push_str(&get_flags_section(Some(&*self.0.clone()), &sig, |v| {
v.to_parsable_string(", ", &self.0.config)
}))
}
@ -93,13 +92,14 @@ impl NuHelpCompleter {
}
}
let extra: Vec<String> = examples
let extra: Vec<String> = decl
.examples()
.iter()
.map(|example| example.example.replace('\n', "\r\n"))
.collect();
Suggestion {
value: sig.name.clone(),
value: decl.name().into(),
description: Some(long_desc),
style: None,
extra: Some(extra),

View File

@ -28,7 +28,7 @@ impl NuMenuCompleter {
Self {
block_id,
span,
stack: stack.reset_stdio().capture(),
stack: stack.reset_out_dest().capture(),
engine_state,
only_buffer_difference,
}
@ -59,8 +59,7 @@ impl Completer for NuMenuCompleter {
let res = eval_block::<WithoutDebug>(&self.engine_state, &mut self.stack, block, input);
if let Ok(values) = res {
let values = values.into_value(self.span);
if let Ok(values) = res.and_then(|data| data.into_value(self.span)) {
convert_to_suggestions(values, line, pos, self.only_buffer_difference)
} else {
Vec::new()

View File

@ -1,4 +1,10 @@
use crate::prompt_update::{POST_PROMPT_MARKER, PRE_PROMPT_MARKER};
use crate::prompt_update::{
POST_PROMPT_MARKER, PRE_PROMPT_MARKER, VSCODE_POST_PROMPT_MARKER, VSCODE_PRE_PROMPT_MARKER,
};
use nu_protocol::{
engine::{EngineState, Stack},
Value,
};
#[cfg(windows)]
use nu_utils::enable_vt_processing;
use reedline::{
@ -10,7 +16,8 @@ use std::borrow::Cow;
/// Nushell prompt definition
#[derive(Clone)]
pub struct NushellPrompt {
shell_integration: bool,
shell_integration_osc133: bool,
shell_integration_osc633: bool,
left_prompt_string: Option<String>,
right_prompt_string: Option<String>,
default_prompt_indicator: Option<String>,
@ -18,12 +25,20 @@ pub struct NushellPrompt {
default_vi_normal_prompt_indicator: Option<String>,
default_multiline_indicator: Option<String>,
render_right_prompt_on_last_line: bool,
engine_state: EngineState,
stack: Stack,
}
impl NushellPrompt {
pub fn new(shell_integration: bool) -> NushellPrompt {
pub fn new(
shell_integration_osc133: bool,
shell_integration_osc633: bool,
engine_state: EngineState,
stack: Stack,
) -> NushellPrompt {
NushellPrompt {
shell_integration,
shell_integration_osc133,
shell_integration_osc633,
left_prompt_string: None,
right_prompt_string: None,
default_prompt_indicator: None,
@ -31,6 +46,8 @@ impl NushellPrompt {
default_vi_normal_prompt_indicator: None,
default_multiline_indicator: None,
render_right_prompt_on_last_line: false,
engine_state,
stack,
}
}
@ -106,7 +123,19 @@ impl Prompt for NushellPrompt {
.to_string()
.replace('\n', "\r\n");
if self.shell_integration {
if self.shell_integration_osc633 {
if self.stack.get_env_var(&self.engine_state, "TERM_PROGRAM")
== Some(Value::test_string("vscode"))
{
// We're in vscode and we have osc633 enabled
format!("{VSCODE_PRE_PROMPT_MARKER}{prompt}{VSCODE_POST_PROMPT_MARKER}").into()
} else if self.shell_integration_osc133 {
// If we're in VSCode but we don't find the env var, but we have osc133 set, then use it
format!("{PRE_PROMPT_MARKER}{prompt}{POST_PROMPT_MARKER}").into()
} else {
prompt.into()
}
} else if self.shell_integration_osc133 {
format!("{PRE_PROMPT_MARKER}{prompt}{POST_PROMPT_MARKER}").into()
} else {
prompt.into()

View File

@ -1,9 +1,9 @@
use crate::NushellPrompt;
use log::trace;
use nu_engine::get_eval_subexpression;
use nu_engine::ClosureEvalOnce;
use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet},
report_error, Config, PipelineData, Value,
engine::{EngineState, Stack},
report_error_new, Config, PipelineData, Value,
};
use reedline::Prompt;
@ -23,10 +23,37 @@ pub(crate) const TRANSIENT_PROMPT_INDICATOR_VI_NORMAL: &str =
"TRANSIENT_PROMPT_INDICATOR_VI_NORMAL";
pub(crate) const TRANSIENT_PROMPT_MULTILINE_INDICATOR: &str =
"TRANSIENT_PROMPT_MULTILINE_INDICATOR";
// Store all these Ansi Escape Markers here so they can be reused easily
// According to Daniel Imms @Tyriar, we need to do these this way:
// <133 A><prompt><133 B><command><133 C><command output>
pub(crate) const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
pub(crate) const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
pub(crate) const PRE_EXECUTION_MARKER: &str = "\x1b]133;C\x1b\\";
#[allow(dead_code)]
pub(crate) const POST_EXECUTION_MARKER_PREFIX: &str = "\x1b]133;D;";
#[allow(dead_code)]
pub(crate) const POST_EXECUTION_MARKER_SUFFIX: &str = "\x1b\\";
// OSC633 is the same as OSC133 but specifically for VSCode
pub(crate) const VSCODE_PRE_PROMPT_MARKER: &str = "\x1b]633;A\x1b\\";
pub(crate) const VSCODE_POST_PROMPT_MARKER: &str = "\x1b]633;B\x1b\\";
#[allow(dead_code)]
pub(crate) const VSCODE_PRE_EXECUTION_MARKER: &str = "\x1b]633;C\x1b\\";
#[allow(dead_code)]
//"\x1b]633;D;{}\x1b\\"
pub(crate) const VSCODE_POST_EXECUTION_MARKER_PREFIX: &str = "\x1b]633;D;";
#[allow(dead_code)]
pub(crate) const VSCODE_POST_EXECUTION_MARKER_SUFFIX: &str = "\x1b\\";
#[allow(dead_code)]
pub(crate) const VSCODE_COMMANDLINE_MARKER: &str = "\x1b]633;E\x1b\\";
#[allow(dead_code)]
// "\x1b]633;P;Cwd={}\x1b\\"
pub(crate) const VSCODE_CWD_PROPERTY_MARKER_PREFIX: &str = "\x1b]633;P;Cwd=";
#[allow(dead_code)]
pub(crate) const VSCODE_CWD_PROPERTY_MARKER_SUFFIX: &str = "\x1b\\";
pub(crate) const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
fn get_prompt_string(
prompt: &str,
@ -34,17 +61,13 @@ fn get_prompt_string(
engine_state: &EngineState,
stack: &mut Stack,
) -> Option<String> {
let eval_subexpression = get_eval_subexpression(engine_state);
stack
.get_env_var(engine_state, prompt)
.and_then(|v| match v {
Value::Closure { val, .. } => {
let block = engine_state.get_block(val.block_id);
let mut stack = stack.captures_to_stack(val.captures);
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
let ret_val =
eval_subexpression(engine_state, &mut stack, block, PipelineData::empty());
let result = ClosureEvalOnce::new(engine_state, stack, *val)
.run_with_input(PipelineData::Empty);
trace!(
"get_prompt_string (block) {}:{}:{}",
file!(),
@ -52,28 +75,9 @@ fn get_prompt_string(
column!()
);
ret_val
result
.map_err(|err| {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
})
.ok()
}
Value::Block { val: block_id, .. } => {
let block = engine_state.get_block(block_id);
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
let ret_val = eval_subexpression(engine_state, stack, block, PipelineData::empty());
trace!(
"get_prompt_string (block) {}:{}:{}",
file!(),
line!(),
column!()
);
ret_val
.map_err(|err| {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
report_error_new(engine_state, &err);
})
.ok()
}
@ -103,20 +107,34 @@ pub(crate) fn update_prompt(
stack: &mut Stack,
nu_prompt: &mut NushellPrompt,
) {
let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, stack);
let configured_left_prompt_string =
match get_prompt_string(PROMPT_COMMAND, config, engine_state, stack) {
Some(s) => s,
None => "".to_string(),
};
// Now that we have the prompt string lets ansify it.
// <133 A><prompt><133 B><command><133 C><command output>
let left_prompt_string = if config.shell_integration {
if let Some(prompt_string) = left_prompt_string {
let left_prompt_string = if config.shell_integration_osc633 {
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
// We're in vscode and we have osc633 enabled
Some(format!(
"{PRE_PROMPT_MARKER}{prompt_string}{POST_PROMPT_MARKER}"
"{VSCODE_PRE_PROMPT_MARKER}{configured_left_prompt_string}{VSCODE_POST_PROMPT_MARKER}"
))
} else if config.shell_integration_osc133 {
// If we're in VSCode but we don't find the env var, but we have osc133 set, then use it
Some(format!(
"{PRE_PROMPT_MARKER}{configured_left_prompt_string}{POST_PROMPT_MARKER}"
))
} else {
left_prompt_string
configured_left_prompt_string.into()
}
} else if config.shell_integration_osc133 {
Some(format!(
"{PRE_PROMPT_MARKER}{configured_left_prompt_string}{POST_PROMPT_MARKER}"
))
} else {
left_prompt_string
configured_left_prompt_string.into()
};
let right_prompt_string = get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, stack);

View File

@ -1,6 +1,7 @@
use crate::{menus::NuMenuCompleter, NuHelpCompleter};
use crossterm::event::{KeyCode, KeyModifiers};
use log::trace;
use nu_ansi_term::Style;
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
use nu_engine::eval_block;
use nu_parser::parse;
@ -158,21 +159,14 @@ fn add_menu(
}
}
macro_rules! add_style {
// first arm match add!(1,2), add!(2,3) etc
($name:expr, $record: expr, $span:expr, $config: expr, $menu:expr, $f:expr) => {
$menu = match extract_value($name, $record, $span) {
Ok(text) => {
let style = match text {
Value::String { val, .. } => lookup_ansi_color_style(&val),
Value::Record { .. } => color_record_to_nustyle(&text),
_ => lookup_ansi_color_style("green"),
};
$f($menu, style)
}
Err(_) => $menu,
};
};
fn get_style(record: &Record, name: &str, span: Span) -> Option<Style> {
extract_value(name, record, span)
.ok()
.map(|text| match text {
Value::String { val, .. } => lookup_ansi_color_style(val),
Value::Record { .. } => color_record_to_nustyle(text),
_ => lookup_ansi_color_style("green"),
})
}
// Adds a columnar menu to the editor engine
@ -215,46 +209,21 @@ pub(crate) fn add_columnar_menu(
let span = menu.style.span();
if let Value::Record { val, .. } = &menu.style {
add_style!(
"text",
val,
span,
config,
columnar_menu,
ColumnarMenu::with_text_style
);
add_style!(
"selected_text",
val,
span,
config,
columnar_menu,
ColumnarMenu::with_selected_text_style
);
add_style!(
"description_text",
val,
span,
config,
columnar_menu,
ColumnarMenu::with_description_text_style
);
add_style!(
"match_text",
val,
span,
config,
columnar_menu,
ColumnarMenu::with_match_text_style
);
add_style!(
"selected_match_text",
val,
span,
config,
columnar_menu,
ColumnarMenu::with_selected_match_text_style
);
if let Some(style) = get_style(val, "text", span) {
columnar_menu = columnar_menu.with_text_style(style);
}
if let Some(style) = get_style(val, "selected_text", span) {
columnar_menu = columnar_menu.with_selected_text_style(style);
}
if let Some(style) = get_style(val, "description_text", span) {
columnar_menu = columnar_menu.with_description_text_style(style);
}
if let Some(style) = get_style(val, "match_text", span) {
columnar_menu = columnar_menu.with_match_text_style(style);
}
if let Some(style) = get_style(val, "selected_match_text", span) {
columnar_menu = columnar_menu.with_selected_match_text_style(style);
}
}
let marker = menu.marker.to_expanded_string("", config);
@ -313,30 +282,15 @@ pub(crate) fn add_list_menu(
let span = menu.style.span();
if let Value::Record { val, .. } = &menu.style {
add_style!(
"text",
val,
span,
config,
list_menu,
ListMenu::with_text_style
);
add_style!(
"selected_text",
val,
span,
config,
list_menu,
ListMenu::with_selected_text_style
);
add_style!(
"description_text",
val,
span,
config,
list_menu,
ListMenu::with_description_text_style
);
if let Some(style) = get_style(val, "text", span) {
list_menu = list_menu.with_text_style(style);
}
if let Some(style) = get_style(val, "selected_text", span) {
list_menu = list_menu.with_selected_text_style(style);
}
if let Some(style) = get_style(val, "description_text", span) {
list_menu = list_menu.with_description_text_style(style);
}
}
let marker = menu.marker.to_expanded_string("", config);
@ -520,46 +474,21 @@ pub(crate) fn add_ide_menu(
let span = menu.style.span();
if let Value::Record { val, .. } = &menu.style {
add_style!(
"text",
val,
span,
config,
ide_menu,
IdeMenu::with_text_style
);
add_style!(
"selected_text",
val,
span,
config,
ide_menu,
IdeMenu::with_selected_text_style
);
add_style!(
"description_text",
val,
span,
config,
ide_menu,
IdeMenu::with_description_text_style
);
add_style!(
"match_text",
val,
span,
config,
ide_menu,
IdeMenu::with_match_text_style
);
add_style!(
"selected_match_text",
val,
span,
config,
ide_menu,
IdeMenu::with_selected_match_text_style
);
if let Some(style) = get_style(val, "text", span) {
ide_menu = ide_menu.with_text_style(style);
}
if let Some(style) = get_style(val, "selected_text", span) {
ide_menu = ide_menu.with_selected_text_style(style);
}
if let Some(style) = get_style(val, "description_text", span) {
ide_menu = ide_menu.with_description_text_style(style);
}
if let Some(style) = get_style(val, "match_text", span) {
ide_menu = ide_menu.with_match_text_style(style);
}
if let Some(style) = get_style(val, "selected_match_text", span) {
ide_menu = ide_menu.with_selected_match_text_style(style);
}
}
let marker = menu.marker.to_expanded_string("", config);
@ -650,30 +579,15 @@ pub(crate) fn add_description_menu(
let span = menu.style.span();
if let Value::Record { val, .. } = &menu.style {
add_style!(
"text",
val,
span,
config,
description_menu,
DescriptionMenu::with_text_style
);
add_style!(
"selected_text",
val,
span,
config,
description_menu,
DescriptionMenu::with_selected_text_style
);
add_style!(
"description_text",
val,
span,
config,
description_menu,
DescriptionMenu::with_description_text_style
);
if let Some(style) = get_style(val, "text", span) {
description_menu = description_menu.with_text_style(style);
}
if let Some(style) = get_style(val, "selected_text", span) {
description_menu = description_menu.with_selected_text_style(style);
}
if let Some(style) = get_style(val, "description_text", span) {
description_menu = description_menu.with_description_text_style(style);
}
}
let marker = menu.marker.to_expanded_string("", config);

View File

@ -1,3 +1,9 @@
use crate::prompt_update::{
POST_EXECUTION_MARKER_PREFIX, POST_EXECUTION_MARKER_SUFFIX, PRE_EXECUTION_MARKER,
RESET_APPLICATION_MODE, VSCODE_CWD_PROPERTY_MARKER_PREFIX, VSCODE_CWD_PROPERTY_MARKER_SUFFIX,
VSCODE_POST_EXECUTION_MARKER_PREFIX, VSCODE_POST_EXECUTION_MARKER_SUFFIX,
VSCODE_PRE_EXECUTION_MARKER,
};
use crate::{
completions::NuCompleter,
nu_highlight::NoOpHighlighter,
@ -14,16 +20,19 @@ use nu_cmd_base::{
util::{get_editor, get_guaranteed_cwd},
};
use nu_color_config::StyleComputer;
use nu_engine::{convert_env_values, env_to_strings};
#[allow(deprecated)]
use nu_engine::{convert_env_values, current_dir_str, env_to_strings};
use nu_parser::{lex, parse, trim_quotes_str};
use nu_protocol::{
config::NuCursorShape,
engine::{EngineState, Stack, StateWorkingSet},
eval_const::create_nu_constant,
report_error_new, HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned,
Value, NU_VARIABLE_ID,
Value,
};
use nu_utils::{
filesystem::{have_permission, PermissionResult},
utils::perf,
};
use nu_utils::utils::perf;
use reedline::{
CursorConfig, CwdAwareHinter, DefaultCompleter, EditCommand, Emacs, FileBackedHistory,
HistorySessionId, Reedline, SqliteBackedHistory, Vi,
@ -39,16 +48,6 @@ use std::{
};
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>
// These first two have been moved to prompt_update to get as close as possible to the prompt.
// const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
// const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
const PRE_EXECUTE_MARKER: &str = "\x1b]133;C\x1b\\";
// This one is in get_command_finished_marker() now so we can capture the exit codes properly.
// const CMD_FINISHED_MARKER: &str = "\x1b]133;D;{}\x1b\\";
const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
/// The main REPL loop, including spinning up the prompt itself.
pub fn evaluate_repl(
engine_state: &mut EngineState,
@ -63,7 +62,7 @@ pub fn evaluate_repl(
// so that it may be read by various reedline plugins. During this, we
// can't modify the stack, but at the end of the loop we take back ownership
// from the Arc. This lets us avoid copying stack variables needlessly
let mut unique_stack = stack;
let mut unique_stack = stack.clone();
let config = engine_state.get_config();
let use_color = config.use_ansi_coloring;
@ -71,11 +70,23 @@ pub fn evaluate_repl(
let mut entry_num = 0;
let nu_prompt = NushellPrompt::new(config.shell_integration);
// Let's grab the shell_integration configs
let shell_integration_osc2 = config.shell_integration_osc2;
let shell_integration_osc7 = config.shell_integration_osc7;
let shell_integration_osc9_9 = config.shell_integration_osc9_9;
let shell_integration_osc133 = config.shell_integration_osc133;
let shell_integration_osc633 = config.shell_integration_osc633;
let nu_prompt = NushellPrompt::new(
shell_integration_osc133,
shell_integration_osc633,
engine_state.clone(),
stack.clone(),
);
let start_time = std::time::Instant::now();
// Translate environment variables from Strings to Values
if let Some(e) = convert_env_values(engine_state, &unique_stack) {
if let Err(e) = convert_env_values(engine_state, &unique_stack) {
report_error_new(engine_state, &e);
}
perf(
@ -111,11 +122,29 @@ pub fn evaluate_repl(
engine_state.merge_env(&mut unique_stack, cwd)?;
}
let hostname = System::host_name();
if shell_integration_osc2 {
run_shell_integration_osc2(None, engine_state, &mut unique_stack, use_color);
}
if shell_integration_osc7 {
run_shell_integration_osc7(
hostname.as_deref(),
engine_state,
&mut unique_stack,
use_color,
);
}
if shell_integration_osc9_9 {
run_shell_integration_osc9_9(engine_state, &mut unique_stack, use_color);
}
if shell_integration_osc633 {
run_shell_integration_osc633(engine_state, &mut unique_stack, use_color);
}
engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64);
// Regenerate the $nu constant to contain the startup time and any other potential updates
let nu_const = create_nu_constant(engine_state, Span::unknown())?;
engine_state.set_variable_const_val(NU_VARIABLE_ID, nu_const);
engine_state.generate_nu_constant();
if load_std_lib.is_none() && engine_state.get_config().show_banner {
eval_source(
@ -144,7 +173,7 @@ pub fn evaluate_repl(
let temp_file_cloned = temp_file.clone();
let mut nu_prompt_cloned = nu_prompt.clone();
let iteration_panic_state = catch_unwind(AssertUnwindSafe(move || {
let iteration_panic_state = catch_unwind(AssertUnwindSafe(|| {
let (continue_loop, current_stack, line_editor) = loop_iteration(LoopContext {
engine_state: &mut current_engine_state,
stack: current_stack,
@ -153,6 +182,7 @@ pub fn evaluate_repl(
temp_file: &temp_file_cloned,
use_color,
entry_num: &mut entry_num,
hostname: hostname.as_deref(),
});
// pass the most recent version of the line_editor back
@ -229,6 +259,7 @@ struct LoopContext<'a> {
temp_file: &'a Path,
use_color: bool,
entry_num: &'a mut usize,
hostname: Option<&'a str>,
}
/// Perform one iteration of the REPL loop
@ -247,6 +278,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
temp_file,
use_color,
entry_num,
hostname,
} = ctx;
let cwd = get_guaranteed_cwd(engine_state, &stack);
@ -355,12 +387,13 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
.with_completer(Box::new(NuCompleter::new(
engine_reference.clone(),
// STACK-REFERENCE 2
Stack::with_parent(stack_arc.clone()),
stack_arc.clone(),
)))
.with_quick_completions(config.quick_completions)
.with_partial_completions(config.partial_completions)
.with_ansi_colors(config.use_ansi_coloring)
.with_cursor_config(cursor_config);
perf(
"reedline builder",
start_time,
@ -382,6 +415,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
} else {
line_editor.disable_hints()
};
perf(
"reedline coloring/style_computer",
start_time,
@ -398,8 +432,9 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
report_error_new(engine_state, &e);
Reedline::create()
});
perf(
"reedline menus",
"reedline adding menus",
start_time,
file!(),
line!(),
@ -421,6 +456,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
} else {
line_editor
};
perf(
"reedline buffer_editor",
start_time,
@ -437,6 +473,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
warn!("Failed to sync history: {}", e);
}
}
perf(
"sync_history",
start_time,
@ -450,6 +487,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
start_time = std::time::Instant::now();
// Changing the line editor based on the found keybindings
line_editor = setup_keybindings(engine_state, line_editor);
perf(
"keybindings",
start_time,
@ -473,6 +511,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
&mut Stack::with_parent(stack_arc.clone()),
nu_prompt,
);
perf(
"update_prompt",
start_time,
@ -494,22 +533,43 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
.with_highlighter(Box::<NoOpHighlighter>::default())
// CLEAR STACK-REFERENCE 2
.with_completer(Box::<DefaultCompleter>::default());
let shell_integration = config.shell_integration;
let mut stack = Stack::unwrap_unique(stack_arc);
// Let's grab the shell_integration configs
let shell_integration_osc2 = config.shell_integration_osc2;
let shell_integration_osc7 = config.shell_integration_osc7;
let shell_integration_osc9_9 = config.shell_integration_osc9_9;
let shell_integration_osc133 = config.shell_integration_osc133;
let shell_integration_osc633 = config.shell_integration_osc633;
let shell_integration_reset_application_mode = config.shell_integration_reset_application_mode;
// TODO: we may clone the stack, this can lead to major performance issues
// so we should avoid it or making stack cheaper to clone.
let mut stack = Arc::unwrap_or_clone(stack_arc);
perf(
"line_editor setup",
start_time,
file!(),
line!(),
column!(),
use_color,
);
let line_editor_input_time = std::time::Instant::now();
match input {
Ok(Signal::Success(s)) => {
let hostname = System::host_name();
let history_supports_meta = matches!(
engine_state.history_config().map(|h| h.file_format),
Some(HistoryFileFormat::Sqlite)
);
if history_supports_meta {
prepare_history_metadata(&s, &hostname, engine_state, &mut line_editor);
prepare_history_metadata(&s, hostname, engine_state, &mut line_editor);
}
// For pre_exec_hook
start_time = Instant::now();
// Right before we start running the code the user gave us, fire the `pre_execution`
// hook
if let Some(hook) = config.hooks.pre_execution.clone() {
@ -530,22 +590,80 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
}
}
perf(
"pre_execution_hook",
start_time,
file!(),
line!(),
column!(),
use_color,
);
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
repl.cursor_pos = line_editor.current_insertion_point();
repl.buffer = line_editor.current_buffer_contents().to_string();
drop(repl);
if shell_integration {
run_ansi_sequence(PRE_EXECUTE_MARKER);
if shell_integration_osc633 {
if stack.get_env_var(engine_state, "TERM_PROGRAM")
== Some(Value::test_string("vscode"))
{
start_time = Instant::now();
run_ansi_sequence(VSCODE_PRE_EXECUTION_MARKER);
perf(
"pre_execute_marker (633;C) ansi escape sequence",
start_time,
file!(),
line!(),
column!(),
use_color,
);
} else if shell_integration_osc133 {
start_time = Instant::now();
run_ansi_sequence(PRE_EXECUTION_MARKER);
perf(
"pre_execute_marker (133;C) ansi escape sequence",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
} else if shell_integration_osc133 {
start_time = Instant::now();
run_ansi_sequence(PRE_EXECUTION_MARKER);
perf(
"pre_execute_marker (133;C) ansi escape sequence",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
// Actual command execution logic starts from here
let start_time = Instant::now();
let cmd_execution_start_time = Instant::now();
match parse_operation(s.clone(), engine_state, &stack) {
Ok(operation) => match operation {
ReplOperation::AutoCd { cwd, target, span } => {
do_auto_cd(target, cwd, &mut stack, engine_state, span);
run_finaliziation_ansi_sequence(
&stack,
engine_state,
use_color,
shell_integration_osc633,
shell_integration_osc133,
);
}
ReplOperation::RunCommand(cmd) => {
line_editor = do_run_cmd(
@ -553,16 +671,25 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
&mut stack,
engine_state,
line_editor,
shell_integration,
shell_integration_osc2,
*entry_num,
)
use_color,
);
run_finaliziation_ansi_sequence(
&stack,
engine_state,
use_color,
shell_integration_osc633,
shell_integration_osc133,
);
}
// as the name implies, we do nothing in this case
ReplOperation::DoNothing => {}
},
Err(ref e) => error!("Error parsing operation: {e}"),
}
let cmd_duration = start_time.elapsed();
let cmd_duration = cmd_execution_start_time.elapsed();
stack.add_env_var(
"CMD_DURATION_MS".into(),
@ -581,23 +708,45 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
}
}
if shell_integration {
do_shell_integration_finalize_command(hostname, engine_state, &mut stack);
if shell_integration_osc2 {
run_shell_integration_osc2(None, engine_state, &mut stack, use_color);
}
if shell_integration_osc7 {
run_shell_integration_osc7(hostname, engine_state, &mut stack, use_color);
}
if shell_integration_osc9_9 {
run_shell_integration_osc9_9(engine_state, &mut stack, use_color);
}
if shell_integration_osc633 {
run_shell_integration_osc633(engine_state, &mut stack, use_color);
}
if shell_integration_reset_application_mode {
run_shell_integration_reset_application_mode();
}
flush_engine_state_repl_buffer(engine_state, &mut line_editor);
}
Ok(Signal::CtrlC) => {
// `Reedline` clears the line content. New prompt is shown
if shell_integration {
run_ansi_sequence(&get_command_finished_marker(&stack, engine_state));
}
run_finaliziation_ansi_sequence(
&stack,
engine_state,
use_color,
shell_integration_osc633,
shell_integration_osc133,
);
}
Ok(Signal::CtrlD) => {
// When exiting clear to a new line
if shell_integration {
run_ansi_sequence(&get_command_finished_marker(&stack, engine_state));
}
run_finaliziation_ansi_sequence(
&stack,
engine_state,
use_color,
shell_integration_osc633,
shell_integration_osc133,
);
println!();
return (false, stack, line_editor);
}
@ -610,14 +759,19 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
// e.g. https://github.com/nushell/nushell/issues/6452
// Alternatively only allow that expected failures let the REPL loop
}
if shell_integration {
run_ansi_sequence(&get_command_finished_marker(&stack, engine_state));
}
run_finaliziation_ansi_sequence(
&stack,
engine_state,
use_color,
shell_integration_osc633,
shell_integration_osc133,
);
}
}
perf(
"processing line editor input",
start_time,
line_editor_input_time,
file!(),
line!(),
column!(),
@ -625,7 +779,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
);
perf(
"finished repl loop",
"time between prompts in line editor loop",
loop_start_time,
file!(),
line!(),
@ -641,15 +795,16 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
///
fn prepare_history_metadata(
s: &str,
hostname: &Option<String>,
hostname: Option<&str>,
engine_state: &EngineState,
line_editor: &mut Reedline,
) {
if !s.is_empty() && line_editor.has_last_command_context() {
#[allow(deprecated)]
let result = line_editor
.update_last_command_context(&|mut c| {
c.start_timestamp = Some(chrono::Utc::now());
c.hostname.clone_from(hostname);
c.hostname = hostname.map(str::to_string);
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
c
@ -716,7 +871,8 @@ fn parse_operation(
) -> Result<ReplOperation, ErrReport> {
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
// Check if this is a single call to a directory, if so auto-cd
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
#[allow(deprecated)]
let cwd = nu_engine::env::current_dir_str(engine_state, stack).unwrap_or_default();
let mut orig = s.clone();
if orig.starts_with('`') {
orig = trim_quotes_str(&orig).to_string()
@ -756,16 +912,27 @@ fn do_auto_cd(
},
);
}
let path = nu_path::canonicalize_with(path, &cwd)
.expect("internal error: cannot canonicalize known path");
path.to_string_lossy().to_string()
};
if let PermissionResult::PermissionDenied(reason) = have_permission(path.clone()) {
report_error_new(
engine_state,
&ShellError::IOError {
msg: format!("Cannot change directory to {path}: {reason}"),
},
);
return;
}
stack.add_env_var("OLDPWD".into(), Value::string(cwd.clone(), Span::unknown()));
//FIXME: this only changes the current scope, but instead this environment variable
//should probably be a block that loads the information from the state in the overlay
stack.add_env_var("PWD".into(), Value::string(path.clone(), Span::unknown()));
if let Err(err) = stack.set_cwd(&path) {
report_error_new(engine_state, &err);
return;
};
let cwd = Value::string(cwd, span);
let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS");
@ -810,8 +977,9 @@ fn do_run_cmd(
// we pass in the line editor so it can be dropped in the case of a process exit
// (in the normal case we don't want to drop it so return it as-is otherwise)
line_editor: Reedline,
shell_integration: bool,
shell_integration_osc2: bool,
entry_num: usize,
use_color: bool,
) -> Reedline {
trace!("eval source: {}", s);
@ -836,29 +1004,8 @@ fn do_run_cmd(
}
}
if shell_integration {
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
match cwd.coerce_into_string() {
Ok(path) => {
// Try to abbreviate string for windows title
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
path.replace(&p.as_path().display().to_string(), "~")
} else {
path
};
let binary_name = s.split_whitespace().next();
if let Some(binary_name) = binary_name {
run_ansi_sequence(&format!(
"\x1b]2;{maybe_abbrev_path}> {binary_name}\x07"
));
}
}
Err(e) => {
warn!("Could not coerce working directory to string {e}");
}
}
}
if shell_integration_osc2 {
run_shell_integration_osc2(Some(s), engine_state, stack, use_color);
}
eval_source(
@ -875,57 +1022,139 @@ fn do_run_cmd(
///
/// Output some things and set environment variables so shells with the right integration
/// can have more information about what is going on (after we have run a command)
/// can have more information about what is going on (both on startup and after we have
/// run a command)
///
fn do_shell_integration_finalize_command(
hostname: Option<String>,
fn run_shell_integration_osc2(
command_name: Option<&str>,
engine_state: &EngineState,
stack: &mut Stack,
use_color: bool,
) {
run_ansi_sequence(&get_command_finished_marker(stack, engine_state));
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
match cwd.coerce_into_string() {
Ok(path) => {
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
// https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences
if stack.get_env_var(engine_state, "TERM_PROGRAM")
== Some(Value::test_string("vscode"))
{
// If we're in vscode, run their specific ansi escape sequence.
// This is helpful for ctrl+g to change directories in the terminal.
run_ansi_sequence(&format!("\x1b]633;P;Cwd={}\x1b\\", path));
#[allow(deprecated)]
if let Ok(path) = current_dir_str(engine_state, stack) {
let start_time = Instant::now();
// Try to abbreviate string for windows title
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
path.replace(&p.as_path().display().to_string(), "~")
} else {
path
};
let title = match command_name {
Some(binary_name) => {
let split_binary_name = binary_name.split_whitespace().next();
if let Some(binary_name) = split_binary_name {
format!("{maybe_abbrev_path}> {binary_name}")
} else {
// Otherwise, communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
run_ansi_sequence(&format!(
"\x1b]7;file://{}{}{}\x1b\\",
percent_encoding::utf8_percent_encode(
&hostname.unwrap_or_else(|| "localhost".to_string()),
percent_encoding::CONTROLS
),
if path.starts_with('/') { "" } else { "/" },
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
));
maybe_abbrev_path.to_string()
}
// Try to abbreviate string for windows title
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
path.replace(&p.as_path().display().to_string(), "~")
} else {
path
};
// Set window title too
// https://tldp.org/HOWTO/Xterm-Title-3.html
// ESC]0;stringBEL -- Set icon name and window title to string
// ESC]1;stringBEL -- Set icon name to string
// ESC]2;stringBEL -- Set window title to string
run_ansi_sequence(&format!("\x1b]2;{maybe_abbrev_path}\x07"));
}
Err(e) => {
warn!("Could not coerce working directory to string {e}");
}
None => maybe_abbrev_path.to_string(),
};
// Set window title too
// https://tldp.org/HOWTO/Xterm-Title-3.html
// ESC]0;stringBEL -- Set icon name and window title to string
// ESC]1;stringBEL -- Set icon name to string
// ESC]2;stringBEL -- Set window title to string
run_ansi_sequence(&format!("\x1b]2;{title}\x07"));
perf(
"set title with command osc2",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
}
fn run_shell_integration_osc7(
hostname: Option<&str>,
engine_state: &EngineState,
stack: &mut Stack,
use_color: bool,
) {
#[allow(deprecated)]
if let Ok(path) = current_dir_str(engine_state, stack) {
let start_time = Instant::now();
// Otherwise, communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
run_ansi_sequence(&format!(
"\x1b]7;file://{}{}{}\x1b\\",
percent_encoding::utf8_percent_encode(
hostname.unwrap_or("localhost"),
percent_encoding::CONTROLS
),
if path.starts_with('/') { "" } else { "/" },
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
));
perf(
"communicate path to terminal with osc7",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
}
fn run_shell_integration_osc9_9(engine_state: &EngineState, stack: &mut Stack, use_color: bool) {
#[allow(deprecated)]
if let Ok(path) = current_dir_str(engine_state, stack) {
let start_time = Instant::now();
// Otherwise, communicate the path as OSC 9;9 from ConEmu (often used for spawning new tabs in the same dir)
run_ansi_sequence(&format!(
"\x1b]9;9;{}{}\x1b\\",
if path.starts_with('/') { "" } else { "/" },
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
));
perf(
"communicate path to terminal with osc9;9",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
}
fn run_shell_integration_osc633(engine_state: &EngineState, stack: &mut Stack, use_color: bool) {
#[allow(deprecated)]
if let Ok(path) = current_dir_str(engine_state, stack) {
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
// https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
let start_time = Instant::now();
// If we're in vscode, run their specific ansi escape sequence.
// This is helpful for ctrl+g to change directories in the terminal.
run_ansi_sequence(&format!(
"{}{}{}",
VSCODE_CWD_PROPERTY_MARKER_PREFIX, path, VSCODE_CWD_PROPERTY_MARKER_SUFFIX
));
perf(
"communicate path to terminal with osc633;P",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
}
}
fn run_shell_integration_reset_application_mode() {
run_ansi_sequence(RESET_APPLICATION_MODE);
}
@ -1072,12 +1301,47 @@ fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> Option<SetCursorSty
}
}
fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String {
fn get_command_finished_marker(
stack: &Stack,
engine_state: &EngineState,
shell_integration_osc633: bool,
shell_integration_osc133: bool,
) -> String {
let exit_code = stack
.get_env_var(engine_state, "LAST_EXIT_CODE")
.and_then(|e| e.as_i64().ok());
format!("\x1b]133;D;{}\x1b\\", exit_code.unwrap_or(0))
if shell_integration_osc633 {
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
// We're in vscode and we have osc633 enabled
format!(
"{}{}{}",
VSCODE_POST_EXECUTION_MARKER_PREFIX,
exit_code.unwrap_or(0),
VSCODE_POST_EXECUTION_MARKER_SUFFIX
)
} else if shell_integration_osc133 {
// If we're in VSCode but we don't find the env var, just return the regular markers
format!(
"{}{}{}",
POST_EXECUTION_MARKER_PREFIX,
exit_code.unwrap_or(0),
POST_EXECUTION_MARKER_SUFFIX
)
} else {
// We're not in vscode, so we don't need to do anything special
"\x1b[0m".to_string()
}
} else if shell_integration_osc133 {
format!(
"{}{}{}",
POST_EXECUTION_MARKER_PREFIX,
exit_code.unwrap_or(0),
POST_EXECUTION_MARKER_SUFFIX
)
} else {
"\x1b[0m".to_string()
}
}
fn run_ansi_sequence(seq: &str) {
@ -1088,6 +1352,73 @@ fn run_ansi_sequence(seq: &str) {
}
}
fn run_finaliziation_ansi_sequence(
stack: &Stack,
engine_state: &EngineState,
use_color: bool,
shell_integration_osc633: bool,
shell_integration_osc133: bool,
) {
if shell_integration_osc633 {
// Only run osc633 if we are in vscode
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
let start_time = Instant::now();
run_ansi_sequence(&get_command_finished_marker(
stack,
engine_state,
shell_integration_osc633,
shell_integration_osc133,
));
perf(
"post_execute_marker (633;D) ansi escape sequences",
start_time,
file!(),
line!(),
column!(),
use_color,
);
} else if shell_integration_osc133 {
let start_time = Instant::now();
run_ansi_sequence(&get_command_finished_marker(
stack,
engine_state,
shell_integration_osc633,
shell_integration_osc133,
));
perf(
"post_execute_marker (133;D) ansi escape sequences",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
} else if shell_integration_osc133 {
let start_time = Instant::now();
run_ansi_sequence(&get_command_finished_marker(
stack,
engine_state,
shell_integration_osc633,
shell_integration_osc133,
));
perf(
"post_execute_marker (133;D) ansi escape sequences",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
}
// Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo'
#[cfg(windows)]
static DRIVE_PATH_REGEX: once_cell::sync::Lazy<fancy_regex::Regex> =
@ -1153,3 +1484,136 @@ fn are_session_ids_in_sync() {
engine_state.history_session_id
);
}
#[cfg(test)]
mod test_auto_cd {
use super::{do_auto_cd, parse_operation, ReplOperation};
use nu_protocol::engine::{EngineState, Stack};
use std::path::Path;
use tempfile::tempdir;
/// Create a symlink. Works on both Unix and Windows.
#[cfg(any(unix, windows))]
fn symlink(original: impl AsRef<Path>, link: impl AsRef<Path>) -> std::io::Result<()> {
#[cfg(unix)]
{
std::os::unix::fs::symlink(original, link)
}
#[cfg(windows)]
{
if original.as_ref().is_dir() {
std::os::windows::fs::symlink_dir(original, link)
} else {
std::os::windows::fs::symlink_file(original, link)
}
}
}
/// Run one test case on the auto-cd feature. PWD is initially set to
/// `before`, and after `input` is parsed and evaluated, PWD should be
/// changed to `after`.
#[track_caller]
fn check(before: impl AsRef<Path>, input: &str, after: impl AsRef<Path>) {
// Setup EngineState and Stack.
let mut engine_state = EngineState::new();
let mut stack = Stack::new();
stack.set_cwd(before).unwrap();
// Parse the input. It must be an auto-cd operation.
let op = parse_operation(input.to_string(), &engine_state, &stack).unwrap();
let ReplOperation::AutoCd { cwd, target, span } = op else {
panic!("'{}' was not parsed into an auto-cd operation", input)
};
// Perform the auto-cd operation.
do_auto_cd(target, cwd, &mut stack, &mut engine_state, span);
let updated_cwd = engine_state.cwd(Some(&stack)).unwrap();
// Check that `updated_cwd` and `after` point to the same place. They
// don't have to be byte-wise equal (on Windows, the 8.3 filename
// conversion messes things up),
let updated_cwd = std::fs::canonicalize(updated_cwd).unwrap();
let after = std::fs::canonicalize(after).unwrap();
assert_eq!(updated_cwd, after);
}
#[test]
fn auto_cd_root() {
let tempdir = tempdir().unwrap();
let root = if cfg!(windows) { r"C:\" } else { "/" };
check(&tempdir, root, root);
}
#[test]
fn auto_cd_tilde() {
let tempdir = tempdir().unwrap();
let home = nu_path::home_dir().unwrap();
check(&tempdir, "~", home);
}
#[test]
fn auto_cd_dot() {
let tempdir = tempdir().unwrap();
check(&tempdir, ".", &tempdir);
}
#[test]
fn auto_cd_double_dot() {
let tempdir = tempdir().unwrap();
let dir = tempdir.path().join("foo");
std::fs::create_dir_all(&dir).unwrap();
check(dir, "..", &tempdir);
}
#[test]
fn auto_cd_triple_dot() {
let tempdir = tempdir().unwrap();
let dir = tempdir.path().join("foo").join("bar");
std::fs::create_dir_all(&dir).unwrap();
check(dir, "...", &tempdir);
}
#[test]
fn auto_cd_relative() {
let tempdir = tempdir().unwrap();
let foo = tempdir.path().join("foo");
let bar = tempdir.path().join("bar");
std::fs::create_dir_all(&foo).unwrap();
std::fs::create_dir_all(&bar).unwrap();
let input = if cfg!(windows) { r"..\bar" } else { "../bar" };
check(foo, input, bar);
}
#[test]
fn auto_cd_trailing_slash() {
let tempdir = tempdir().unwrap();
let dir = tempdir.path().join("foo");
std::fs::create_dir_all(&dir).unwrap();
let input = if cfg!(windows) { r"foo\" } else { "foo/" };
check(&tempdir, input, dir);
}
#[test]
fn auto_cd_symlink() {
let tempdir = tempdir().unwrap();
let dir = tempdir.path().join("foo");
std::fs::create_dir_all(&dir).unwrap();
let link = tempdir.path().join("link");
symlink(&dir, &link).unwrap();
let input = if cfg!(windows) { r".\link" } else { "./link" };
check(&tempdir, input, link);
}
#[test]
#[should_panic(expected = "was not parsed into an auto-cd operation")]
fn auto_cd_nonexistent_directory() {
let tempdir = tempdir().unwrap();
let dir = tempdir.path().join("foo");
let input = if cfg!(windows) { r"foo\" } else { "foo/" };
check(&tempdir, input, dir);
}
}

View File

@ -4,7 +4,7 @@ use nu_color_config::{get_matching_brackets_style, get_shape_color};
use nu_engine::env;
use nu_parser::{flatten_block, parse, FlatShape};
use nu_protocol::{
ast::{Argument, Block, Expr, Expression, PipelineRedirection, RecordItem},
ast::{Block, Expr, Expression, PipelineRedirection, RecordItem},
engine::{EngineState, Stack, StateWorkingSet},
Config, Span,
};
@ -37,6 +37,7 @@ impl Highlighter for NuHighlighter {
let str_word = String::from_utf8_lossy(str_contents).to_string();
let paths = env::path_str(&self.engine_state, &self.stack, *span).ok();
#[allow(deprecated)]
let res = if let Ok(cwd) =
env::current_dir_str(&self.engine_state, &self.stack)
{
@ -86,29 +87,8 @@ impl Highlighter for NuHighlighter {
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
.to_string();
macro_rules! add_colored_token_with_bracket_highlight {
($shape:expr, $span:expr, $text:expr) => {{
let spans = split_span_by_highlight_positions(
line,
$span,
&matching_brackets_pos,
global_span_offset,
);
spans.iter().for_each(|(part, highlight)| {
let start = part.start - $span.start;
let end = part.end - $span.start;
let text = (&next_token[start..end]).to_string();
let mut style = get_shape_color($shape.to_string(), &self.config);
if *highlight {
style = get_matching_brackets_style(style, &self.config);
}
output.push((style, text));
});
}};
}
let mut add_colored_token = |shape: &FlatShape, text: String| {
output.push((get_shape_color(shape.to_string(), &self.config), text));
output.push((get_shape_color(shape.as_str(), &self.config), text));
};
match shape.1 {
@ -128,23 +108,32 @@ impl Highlighter for NuHighlighter {
FlatShape::Operator => add_colored_token(&shape.1, next_token),
FlatShape::Signature => add_colored_token(&shape.1, next_token),
FlatShape::String => add_colored_token(&shape.1, next_token),
FlatShape::RawString => add_colored_token(&shape.1, next_token),
FlatShape::StringInterpolation => add_colored_token(&shape.1, next_token),
FlatShape::DateTime => add_colored_token(&shape.1, next_token),
FlatShape::List => {
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
}
FlatShape::Table => {
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
}
FlatShape::Record => {
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
}
FlatShape::Block => {
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
}
FlatShape::Closure => {
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
FlatShape::List
| FlatShape::Table
| FlatShape::Record
| FlatShape::Block
| FlatShape::Closure => {
let span = shape.0;
let shape = &shape.1;
let spans = split_span_by_highlight_positions(
line,
span,
&matching_brackets_pos,
global_span_offset,
);
for (part, highlight) in spans {
let start = part.start - span.start;
let end = part.end - span.start;
let text = next_token[start..end].to_string();
let mut style = get_shape_color(shape.as_str(), &self.config);
if highlight {
style = get_matching_brackets_style(style, &self.config);
}
output.push((style, text));
}
}
FlatShape::Filepath => add_colored_token(&shape.1, next_token),
@ -310,20 +299,6 @@ fn find_matching_block_end_in_expr(
global_span_offset: usize,
global_cursor_offset: usize,
) -> Option<usize> {
macro_rules! find_in_expr_or_continue {
($inner_expr:ident) => {
if let Some(pos) = find_matching_block_end_in_expr(
line,
working_set,
$inner_expr,
global_span_offset,
global_cursor_offset,
) {
return Some(pos);
}
};
}
if expression.span.contains(global_cursor_offset) && expression.span.start >= global_span_offset
{
let expr_first = expression.span.start;
@ -353,6 +328,7 @@ fn find_matching_block_end_in_expr(
Expr::Directory(_, _) => None,
Expr::GlobPattern(_, _) => None,
Expr::String(_) => None,
Expr::RawString(_) => None,
Expr::CellPath(_) => None,
Expr::ImportPattern(_) => None,
Expr::Overlay(_) => None,
@ -360,9 +336,8 @@ fn find_matching_block_end_in_expr(
Expr::MatchBlock(_) => None,
Expr::Nothing => None,
Expr::Garbage => None,
Expr::Spread(_) => None,
Expr::Table(hdr, rows) => {
Expr::Table(table) => {
if expr_last == global_cursor_offset {
// cursor is at table end
Some(expr_first)
@ -371,15 +346,19 @@ fn find_matching_block_end_in_expr(
Some(expr_last)
} else {
// cursor is inside table
for inner_expr in hdr {
find_in_expr_or_continue!(inner_expr);
}
for row in rows {
for inner_expr in row {
find_in_expr_or_continue!(inner_expr);
}
}
None
table
.columns
.iter()
.chain(table.rows.iter().flat_map(AsRef::as_ref))
.find_map(|expr| {
find_matching_block_end_in_expr(
line,
working_set,
expr,
global_span_offset,
global_cursor_offset,
)
})
}
}
@ -392,36 +371,45 @@ fn find_matching_block_end_in_expr(
Some(expr_last)
} else {
// cursor is inside record
for expr in exprs {
match expr {
RecordItem::Pair(k, v) => {
find_in_expr_or_continue!(k);
find_in_expr_or_continue!(v);
}
RecordItem::Spread(_, record) => {
find_in_expr_or_continue!(record);
}
}
}
None
exprs.iter().find_map(|expr| match expr {
RecordItem::Pair(k, v) => find_matching_block_end_in_expr(
line,
working_set,
k,
global_span_offset,
global_cursor_offset,
)
.or_else(|| {
find_matching_block_end_in_expr(
line,
working_set,
v,
global_span_offset,
global_cursor_offset,
)
}),
RecordItem::Spread(_, record) => find_matching_block_end_in_expr(
line,
working_set,
record,
global_span_offset,
global_cursor_offset,
),
})
}
}
Expr::Call(call) => {
for arg in &call.arguments {
let opt_expr = match arg {
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 {
find_in_expr_or_continue!(inner_expr);
}
}
None
}
Expr::Call(call) => call.arguments.iter().find_map(|arg| {
arg.expr().and_then(|expr| {
find_matching_block_end_in_expr(
line,
working_set,
expr,
global_span_offset,
global_cursor_offset,
)
})
}),
Expr::FullCellPath(b) => find_matching_block_end_in_expr(
line,
@ -431,12 +419,15 @@ fn find_matching_block_end_in_expr(
global_cursor_offset,
),
Expr::BinaryOp(lhs, op, rhs) => {
find_in_expr_or_continue!(lhs);
find_in_expr_or_continue!(op);
find_in_expr_or_continue!(rhs);
None
}
Expr::BinaryOp(lhs, op, rhs) => [lhs, op, rhs].into_iter().find_map(|expr| {
find_matching_block_end_in_expr(
line,
working_set,
expr,
global_span_offset,
global_cursor_offset,
)
}),
Expr::Block(block_id)
| Expr::Closure(block_id)
@ -461,14 +452,17 @@ fn find_matching_block_end_in_expr(
}
}
Expr::StringInterpolation(inner_expr) => {
for inner_expr in inner_expr {
find_in_expr_or_continue!(inner_expr);
}
None
}
Expr::StringInterpolation(exprs) => exprs.iter().find_map(|expr| {
find_matching_block_end_in_expr(
line,
working_set,
expr,
global_span_offset,
global_cursor_offset,
)
}),
Expr::List(inner_expr) => {
Expr::List(list) => {
if expr_last == global_cursor_offset {
// cursor is at list end
Some(expr_first)
@ -476,11 +470,15 @@ fn find_matching_block_end_in_expr(
// cursor is at list start
Some(expr_last)
} else {
// cursor is inside list
for inner_expr in inner_expr {
find_in_expr_or_continue!(inner_expr);
}
None
list.iter().find_map(|item| {
find_matching_block_end_in_expr(
line,
working_set,
item.expr(),
global_span_offset,
global_cursor_offset,
)
})
}
}
};

View File

@ -4,7 +4,7 @@ use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token,
use nu_protocol::{
debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet},
print_if_stream, report_error, report_error_new, PipelineData, ShellError, Span, Value,
report_error, report_error_new, PipelineData, ShellError, Span, Value,
};
#[cfg(windows)]
use nu_utils::enable_vt_processing;
@ -39,9 +39,8 @@ fn gather_env_vars(
init_cwd: &Path,
) {
fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) {
let working_set = StateWorkingSet::new(engine_state);
report_error(
&working_set,
report_error_new(
engine_state,
&ShellError::GenericError {
error: format!("Environment variable was not captured: {env_str}"),
msg: "".into(),
@ -71,9 +70,8 @@ fn gather_env_vars(
}
None => {
// Could not capture current working directory
let working_set = StateWorkingSet::new(engine_state);
report_error(
&working_set,
report_error_new(
engine_state,
&ShellError::GenericError {
error: "Current directory is not a valid utf-8 path".into(),
msg: "".into(),
@ -208,9 +206,48 @@ pub fn eval_source(
fname: &str,
input: PipelineData,
allow_return: bool,
) -> bool {
) -> i32 {
let start_time = std::time::Instant::now();
let exit_code = match evaluate_source(engine_state, stack, source, fname, input, allow_return) {
Ok(code) => code.unwrap_or(0),
Err(err) => {
report_error_new(engine_state, &err);
1
}
};
stack.add_env_var(
"LAST_EXIT_CODE".to_string(),
Value::int(exit_code.into(), Span::unknown()),
);
// reset vt processing, aka ansi because illbehaved externals can break it
#[cfg(windows)]
{
let _ = enable_vt_processing();
}
perf(
&format!("eval_source {}", &fname),
start_time,
file!(),
line!(),
column!(),
engine_state.get_config().use_ansi_coloring,
);
exit_code
}
fn evaluate_source(
engine_state: &mut EngineState,
stack: &mut Stack,
source: &[u8],
fname: &str,
input: PipelineData,
allow_return: bool,
) -> Result<Option<i32>, ShellError> {
let (block, delta) = {
let mut working_set = StateWorkingSet::new(engine_state);
let output = parse(
@ -224,104 +261,40 @@ pub fn eval_source(
}
if let Some(err) = working_set.parse_errors.first() {
set_last_exit_code(stack, 1);
report_error(&working_set, err);
return false;
return Ok(Some(1));
}
(output, working_set.render())
};
if let Err(err) = engine_state.merge_delta(delta) {
set_last_exit_code(stack, 1);
report_error_new(engine_state, &err);
return false;
}
engine_state.merge_delta(delta)?;
let b = if allow_return {
let pipeline = if allow_return {
eval_block_with_early_return::<WithoutDebug>(engine_state, stack, &block, input)
} else {
eval_block::<WithoutDebug>(engine_state, stack, &block, input)
}?;
let status = if let PipelineData::ByteStream(..) = pipeline {
pipeline.print(engine_state, stack, false, false)?
} else {
if let Some(hook) = engine_state.get_config().hooks.display_output.clone() {
let pipeline = eval_hook(
engine_state,
stack,
Some(pipeline),
vec![],
&hook,
"display_output",
)?;
pipeline.print(engine_state, stack, false, false)
} else {
pipeline.print(engine_state, stack, true, false)
}?
};
match b {
Ok(pipeline_data) => {
let config = engine_state.get_config();
let result;
if let PipelineData::ExternalStream {
stdout: stream,
stderr: stderr_stream,
exit_code,
..
} = pipeline_data
{
result = print_if_stream(stream, stderr_stream, false, exit_code);
} else if let Some(hook) = config.hooks.display_output.clone() {
match eval_hook(
engine_state,
stack,
Some(pipeline_data),
vec![],
&hook,
"display_output",
) {
Err(err) => {
result = Err(err);
}
Ok(val) => {
result = val.print(engine_state, stack, false, false);
}
}
} else {
result = pipeline_data.print(engine_state, stack, true, false);
}
match result {
Err(err) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
return false;
}
Ok(exit_code) => {
set_last_exit_code(stack, exit_code);
}
}
// reset vt processing, aka ansi because illbehaved externals can break it
#[cfg(windows)]
{
let _ = enable_vt_processing();
}
}
Err(err) => {
set_last_exit_code(stack, 1);
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
return false;
}
}
perf(
&format!("eval_source {}", &fname),
start_time,
file!(),
line!(),
column!(),
engine_state.get_config().use_ansi_coloring,
);
true
}
fn set_last_exit_code(stack: &mut Stack, exit_code: i64) {
stack.add_env_var(
"LAST_EXIT_CODE".to_string(),
Value::int(exit_code, Span::unknown()),
);
Ok(status.map(|status| status.code()))
}
#[cfg(test)]

View File

@ -0,0 +1 @@
mod nu_highlight;

View File

@ -0,0 +1,7 @@
use nu_test_support::nu;
#[test]
fn nu_highlight_not_expr() {
let actual = nu!("'not false' | nu-highlight | ansi strip");
assert_eq!(actual.out, "not false");
}

View File

@ -1,11 +1,15 @@
pub mod support;
use nu_cli::NuCompleter;
use nu_engine::eval_block;
use nu_parser::parse;
use nu_protocol::engine::StateWorkingSet;
use nu_protocol::{debugger::WithoutDebug, engine::StateWorkingSet, PipelineData};
use reedline::{Completer, Suggestion};
use rstest::{fixture, rstest};
use std::path::PathBuf;
use std::{
path::{PathBuf, MAIN_SEPARATOR},
sync::Arc,
};
use support::{
completions_helpers::{new_partial_engine, new_quote_engine},
file, folder, match_suggestions, new_engine,
@ -21,7 +25,7 @@ fn completer() -> NuCompleter {
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
// Instantiate a new completer
NuCompleter::new(std::sync::Arc::new(engine), stack)
NuCompleter::new(Arc::new(engine), Arc::new(stack))
}
#[fixture]
@ -35,7 +39,7 @@ fn completer_strings() -> NuCompleter {
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
// Instantiate a new completer
NuCompleter::new(std::sync::Arc::new(engine), stack)
NuCompleter::new(Arc::new(engine), Arc::new(stack))
}
#[fixture]
@ -55,7 +59,7 @@ fn extern_completer() -> NuCompleter {
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
// Instantiate a new completer
NuCompleter::new(std::sync::Arc::new(engine), stack)
NuCompleter::new(Arc::new(engine), Arc::new(stack))
}
#[fixture]
@ -78,14 +82,14 @@ fn custom_completer() -> NuCompleter {
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
// Instantiate a new completer
NuCompleter::new(std::sync::Arc::new(engine), stack)
NuCompleter::new(Arc::new(engine), Arc::new(stack))
}
#[test]
fn variables_dollar_sign_with_varialblecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let target_dir = "$ ";
let suggestions = completer.complete(target_dir, target_dir.len());
@ -137,7 +141,7 @@ fn dotnu_completions() {
let (_, _, engine, stack) = new_engine();
// Instantiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
// Test source completion
let completion_str = "source-env ".to_string();
@ -177,7 +181,7 @@ fn dotnu_completions() {
#[ignore]
fn external_completer_trailing_space() {
// https://github.com/nushell/nushell/issues/6378
let block = "let external_completer = {|spans| $spans}";
let block = "{|spans| $spans}";
let input = "gh alias ".to_string();
let suggestions = run_external_completion(block, &input);
@ -216,10 +220,10 @@ fn file_completions() {
let (dir, dir_str, engine, stack) = new_engine();
// Instantiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
// Test completions for the current folder
let target_dir = format!("cp {dir_str}");
let target_dir = format!("cp {dir_str}{MAIN_SEPARATOR}");
let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values
@ -264,7 +268,7 @@ fn partial_completions() {
let (dir, _, engine, stack) = new_partial_engine();
// Instantiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
// Test completions for a folder's name
let target_dir = format!("cd {}", file(dir.join("pa")));
@ -288,6 +292,8 @@ fn partial_completions() {
// Create the expected values
let expected_paths: Vec<String> = vec![
file(dir.join("partial_a").join("have_ext.exe")),
file(dir.join("partial_a").join("have_ext.txt")),
file(dir.join("partial_a").join("hello")),
file(dir.join("partial_a").join("hola")),
file(dir.join("partial_b").join("hello_b")),
@ -306,6 +312,8 @@ fn partial_completions() {
// Create the expected values
let expected_paths: Vec<String> = vec![
file(dir.join("partial_a").join("anotherfile")),
file(dir.join("partial_a").join("have_ext.exe")),
file(dir.join("partial_a").join("have_ext.txt")),
file(dir.join("partial_a").join("hello")),
file(dir.join("partial_a").join("hola")),
file(dir.join("partial_b").join("hello_b")),
@ -333,7 +341,54 @@ fn partial_completions() {
let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values
let expected_paths: Vec<String> = vec![file(dir.join("final_partial").join("somefile"))];
let expected_paths: Vec<String> = vec![
file(
dir.join("partial_a")
.join("..")
.join("final_partial")
.join("somefile"),
),
file(
dir.join("partial_b")
.join("..")
.join("final_partial")
.join("somefile"),
),
file(
dir.join("partial_c")
.join("..")
.join("final_partial")
.join("somefile"),
),
];
// Match the results
match_suggestions(expected_paths, suggestions);
// Test completion for all files under directories whose names begin with "pa"
let file_str = file(dir.join("partial_a").join("have"));
let target_file = format!("rm {file_str}");
let suggestions = completer.complete(&target_file, target_file.len());
// Create the expected values
let expected_paths: Vec<String> = vec![
file(dir.join("partial_a").join("have_ext.exe")),
file(dir.join("partial_a").join("have_ext.txt")),
];
// Match the results
match_suggestions(expected_paths, suggestions);
// Test completion for all files under directories whose names begin with "pa"
let file_str = file(dir.join("partial_a").join("have_ext."));
let file_dir = format!("rm {file_str}");
let suggestions = completer.complete(&file_dir, file_dir.len());
// Create the expected values
let expected_paths: Vec<String> = vec![
file(dir.join("partial_a").join("have_ext.exe")),
file(dir.join("partial_a").join("have_ext.txt")),
];
// Match the results
match_suggestions(expected_paths, suggestions);
@ -343,7 +398,7 @@ fn partial_completions() {
fn command_ls_with_filecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let target_dir = "ls ";
let suggestions = completer.complete(target_dir, target_dir.len());
@ -371,13 +426,20 @@ fn command_ls_with_filecompletion() {
".hidden_folder/".to_string(),
];
match_suggestions(expected_paths, suggestions);
let target_dir = "ls custom_completion.";
let suggestions = completer.complete(target_dir, target_dir.len());
let expected_paths: Vec<String> = vec!["custom_completion.nu".to_string()];
match_suggestions(expected_paths, suggestions)
}
#[test]
fn command_open_with_filecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let target_dir = "open ";
let suggestions = completer.complete(target_dir, target_dir.len());
@ -405,6 +467,13 @@ fn command_open_with_filecompletion() {
".hidden_folder/".to_string(),
];
match_suggestions(expected_paths, suggestions);
let target_dir = "open custom_completion.";
let suggestions = completer.complete(target_dir, target_dir.len());
let expected_paths: Vec<String> = vec!["custom_completion.nu".to_string()];
match_suggestions(expected_paths, suggestions)
}
@ -412,7 +481,7 @@ fn command_open_with_filecompletion() {
fn command_rm_with_globcompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let target_dir = "rm ";
let suggestions = completer.complete(target_dir, target_dir.len());
@ -447,7 +516,7 @@ fn command_rm_with_globcompletion() {
fn command_cp_with_globcompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let target_dir = "cp ";
let suggestions = completer.complete(target_dir, target_dir.len());
@ -482,7 +551,7 @@ fn command_cp_with_globcompletion() {
fn command_save_with_filecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let target_dir = "save ";
let suggestions = completer.complete(target_dir, target_dir.len());
@ -517,7 +586,7 @@ fn command_save_with_filecompletion() {
fn command_touch_with_filecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let target_dir = "touch ";
let suggestions = completer.complete(target_dir, target_dir.len());
@ -552,7 +621,7 @@ fn command_touch_with_filecompletion() {
fn command_watch_with_filecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let target_dir = "watch ";
let suggestions = completer.complete(target_dir, target_dir.len());
@ -587,7 +656,7 @@ fn command_watch_with_filecompletion() {
fn file_completion_quoted() {
let (_, _, engine, stack) = new_quote_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let target_dir = "open ";
let suggestions = completer.complete(target_dir, target_dir.len());
@ -625,7 +694,7 @@ fn flag_completions() {
let (_, _, engine, stack) = new_engine();
// Instantiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
// Test completions for the 'ls' flags
let suggestions = completer.complete("ls -", 4);
@ -660,10 +729,10 @@ fn folder_with_directorycompletions() {
let (dir, dir_str, engine, stack) = new_engine();
// Instantiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
// Test completions for the current folder
let target_dir = format!("cd {dir_str}");
let target_dir = format!("cd {dir_str}{MAIN_SEPARATOR}");
let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values
@ -689,7 +758,7 @@ fn variables_completions() {
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
// Instantiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
// Test completions for $nu
let suggestions = completer.complete("$nu.", 4);
@ -795,7 +864,7 @@ fn alias_of_command_and_flags() {
let alias = r#"alias ll = ls -l"#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let suggestions = completer.complete("ll t", 4);
#[cfg(windows)]
@ -814,7 +883,7 @@ fn alias_of_basic_command() {
let alias = r#"alias ll = ls "#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let suggestions = completer.complete("ll t", 4);
#[cfg(windows)]
@ -836,7 +905,7 @@ fn alias_of_another_alias() {
let alias = r#"alias lf = ll -f"#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let suggestions = completer.complete("lf t", 4);
#[cfg(windows)]
@ -847,12 +916,14 @@ fn alias_of_another_alias() {
match_suggestions(expected_paths, suggestions)
}
fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
let completer = format!("$env.config.completions.external.completer = {completer}");
// Create a new engine
let (dir, _, mut engine_state, mut stack) = new_engine();
let (_, delta) = {
let (block, delta) = {
let mut working_set = StateWorkingSet::new(&engine_state);
let block = parse(&mut working_set, None, block.as_bytes(), false);
let block = parse(&mut working_set, None, completer.as_bytes(), false);
assert!(working_set.parse_errors.is_empty());
(block, working_set.render())
@ -860,18 +931,15 @@ fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
assert!(engine_state.merge_delta(delta).is_ok());
assert!(
eval_block::<WithoutDebug>(&engine_state, &mut stack, &block, PipelineData::Empty).is_ok()
);
// Merge environment into the permanent state
assert!(engine_state.merge_env(&mut stack, &dir).is_ok());
let latest_block_id = engine_state.num_blocks() - 1;
// Change config adding the external completer
let mut config = engine_state.get_config().clone();
config.external_completer = Some(latest_block_id);
engine_state.set_config(config);
// Instantiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);
let mut completer = NuCompleter::new(Arc::new(engine_state), Arc::new(stack));
completer.complete(input, input.len())
}
@ -880,7 +948,7 @@ fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
fn unknown_command_completion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let target_dir = "thiscommanddoesnotexist ";
let suggestions = completer.complete(target_dir, target_dir.len());
@ -943,7 +1011,7 @@ fn flagcompletion_triggers_after_cursor_piped(mut completer: NuCompleter) {
fn filecompletions_triggers_after_cursor() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let suggestions = completer.complete("cp test_c", 3);
@ -1052,7 +1120,7 @@ fn alias_offset_bug_7648() {
let alias = r#"alias ea = ^$env.EDITOR /tmp/test.s"#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
// Issue #7648
// Nushell crashes when an alias name is shorter than the alias command
@ -1071,7 +1139,7 @@ fn alias_offset_bug_7754() {
let alias = r#"alias ll = ls -l"#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
// Issue #7754
// Nushell crashes when an alias name is shorter than the alias command

View File

@ -3,14 +3,11 @@ use nu_parser::parse;
use nu_protocol::{
debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet},
eval_const::create_nu_constant,
PipelineData, ShellError, Span, Value, NU_VARIABLE_ID,
PipelineData, ShellError, Span, Value,
};
use nu_test_support::fs;
use reedline::Suggestion;
use std::path::PathBuf;
const SEP: char = std::path::MAIN_SEPARATOR;
use std::path::{PathBuf, MAIN_SEPARATOR};
fn create_default_context() -> EngineState {
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context())
@ -20,20 +17,17 @@ fn create_default_context() -> EngineState {
pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
// Target folder inside assets
let dir = fs::fixtures().join("completions");
let mut dir_str = dir
let dir_str = dir
.clone()
.into_os_string()
.into_string()
.unwrap_or_default();
dir_str.push(SEP);
// Create a new engine with default context
let mut engine_state = create_default_context();
// Add $nu
let nu_const =
create_nu_constant(&engine_state, Span::test_data()).expect("Failed creating $nu");
engine_state.set_variable_const_val(NU_VARIABLE_ID, nu_const);
engine_state.generate_nu_constant();
// New stack
let mut stack = Stack::new();
@ -77,12 +71,11 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
pub fn new_quote_engine() -> (PathBuf, String, EngineState, Stack) {
// Target folder inside assets
let dir = fs::fixtures().join("quoted_completions");
let mut dir_str = dir
let dir_str = dir
.clone()
.into_os_string()
.into_string()
.unwrap_or_default();
dir_str.push(SEP);
// Create a new engine with default context
let mut engine_state = create_default_context();
@ -113,12 +106,11 @@ pub fn new_quote_engine() -> (PathBuf, String, EngineState, Stack) {
pub fn new_partial_engine() -> (PathBuf, String, EngineState, Stack) {
// Target folder inside assets
let dir = fs::fixtures().join("partial_completions");
let mut dir_str = dir
let dir_str = dir
.clone()
.into_os_string()
.into_string()
.unwrap_or_default();
dir_str.push(SEP);
// Create a new engine with default context
let mut engine_state = create_default_context();
@ -165,7 +157,7 @@ pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
// append the separator to the converted path
pub fn folder(path: PathBuf) -> String {
let mut converted_path = file(path);
converted_path.push(SEP);
converted_path.push(MAIN_SEPARATOR);
converted_path
}

View File

@ -0,0 +1,2 @@
mod commands;
mod completions;

View File

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

View File

@ -5,8 +5,8 @@ use nu_parser::parse;
use nu_protocol::{
cli_error::{report_error, report_error_new},
debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet},
BlockId, PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
engine::{Closure, EngineState, Stack, StateWorkingSet},
PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
};
use std::sync::Arc;
@ -153,11 +153,11 @@ pub fn eval_hook(
// If it returns true (the default if a condition block is not specified), the hook should be run.
let do_run_hook = if let Some(condition) = val.get("condition") {
let other_span = condition.span();
if let Ok(block_id) = condition.coerce_block() {
match run_hook_block(
if let Ok(closure) = condition.as_closure() {
match run_hook(
engine_state,
stack,
block_id,
closure,
None,
arguments.clone(),
other_span,
@ -259,25 +259,8 @@ pub fn eval_hook(
stack.remove_var(*var_id);
}
}
Value::Block { val: block_id, .. } => {
run_hook_block(
engine_state,
stack,
*block_id,
input,
arguments,
source_span,
)?;
}
Value::Closure { val, .. } => {
run_hook_block(
engine_state,
stack,
val.block_id,
input,
arguments,
source_span,
)?;
run_hook(engine_state, stack, val, input, arguments, source_span)?;
}
other => {
return Err(ShellError::UnsupportedConfigValue {
@ -289,11 +272,8 @@ pub fn eval_hook(
}
}
}
Value::Block { val: block_id, .. } => {
output = run_hook_block(engine_state, stack, *block_id, input, arguments, span)?;
}
Value::Closure { val, .. } => {
output = run_hook_block(engine_state, stack, val.block_id, input, arguments, span)?;
output = run_hook(engine_state, stack, val, input, arguments, span)?;
}
other => {
return Err(ShellError::UnsupportedConfigValue {
@ -310,20 +290,20 @@ pub fn eval_hook(
Ok(output)
}
fn run_hook_block(
fn run_hook(
engine_state: &EngineState,
stack: &mut Stack,
block_id: BlockId,
closure: &Closure,
optional_input: Option<PipelineData>,
arguments: Vec<(String, Value)>,
span: Span,
) -> Result<PipelineData, ShellError> {
let block = engine_state.get_block(block_id);
let block = engine_state.get_block(closure.block_id);
let input = optional_input.unwrap_or_else(PipelineData::empty);
let mut callee_stack = stack
.gather_captures(engine_state, &block.captures)
.captures_to_stack_preserve_out_dest(closure.captures.clone())
.reset_pipes();
for (idx, PositionalArg { var_id, .. }) in

View File

@ -1,9 +1,8 @@
use nu_protocol::{
ast::RangeInclusion,
engine::{EngineState, Stack, StateWorkingSet},
report_error, Range, ShellError, Span, Value,
engine::{EngineState, Stack},
Range, ShellError, Span, Value,
};
use std::path::PathBuf;
use std::{ops::Bound, path::PathBuf};
pub fn get_init_cwd() -> PathBuf {
std::env::current_dir().unwrap_or_else(|_| {
@ -14,45 +13,29 @@ pub fn get_init_cwd() -> PathBuf {
}
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf {
nu_engine::env::current_dir(engine_state, stack).unwrap_or_else(|e| {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
crate::util::get_init_cwd()
})
engine_state
.cwd(Some(stack))
.unwrap_or(crate::util::get_init_cwd())
}
type MakeRangeError = fn(&str, Span) -> ShellError;
pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> {
let start = match &range.from {
Value::Int { val, .. } => isize::try_from(*val).unwrap_or_default(),
Value::Nothing { .. } => 0,
_ => {
return Err(|msg, span| ShellError::TypeMismatch {
err_message: msg.to_string(),
span,
})
match range {
Range::IntRange(range) => {
let start = range.start().try_into().unwrap_or(0);
let end = match range.end() {
Bound::Included(v) => (v + 1) as isize,
Bound::Excluded(v) => v as isize,
Bound::Unbounded => isize::MAX,
};
Ok((start, end))
}
};
let end = match &range.to {
Value::Int { val, .. } => {
if matches!(range.inclusion, RangeInclusion::Inclusive) {
isize::try_from(*val).unwrap_or(isize::max_value())
} else {
isize::try_from(*val).unwrap_or(isize::max_value()) - 1
}
}
Value::Nothing { .. } => isize::max_value(),
_ => {
return Err(|msg, span| ShellError::TypeMismatch {
err_message: msg.to_string(),
span,
})
}
};
Ok((start, end))
Range::FloatRange(_) => Err(|msg, span| ShellError::TypeMismatch {
err_message: msg.to_string(),
span,
}),
}
}
const HELP_MSG: &str = "Nushell's config file can be found with the command: $nu.config-path. \

View File

@ -1,75 +0,0 @@
[package]
authors = ["The Nushell Project Developers"]
description = "Nushell's dataframe commands based on polars."
edition = "2021"
license = "MIT"
name = "nu-cmd-dataframe"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-dataframe"
version = "0.92.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
bench = false
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.92.1" }
nu-parser = { path = "../nu-parser", version = "0.92.1" }
nu-protocol = { path = "../nu-protocol", version = "0.92.1" }
# Potential dependencies for extras
chrono = { workspace = true, features = ["std", "unstable-locales"], default-features = false }
chrono-tz = { workspace = true }
fancy-regex = { workspace = true }
indexmap = { workspace = true }
num = { version = "0.4", optional = true }
serde = { workspace = true, features = ["derive"] }
# keep sqlparser at 0.39.0 until we can update polars
sqlparser = { version = "0.39.0", optional = true }
polars-io = { version = "0.37", features = ["avro"], optional = true }
polars-arrow = { version = "0.37", optional = true }
polars-ops = { version = "0.37", optional = true }
polars-plan = { version = "0.37", features = ["regex"], optional = true }
polars-utils = { version = "0.37", optional = true }
[dependencies.polars]
features = [
"arg_where",
"checked_arithmetic",
"concat_str",
"cross_join",
"csv",
"cum_agg",
"dtype-categorical",
"dtype-datetime",
"dtype-struct",
"dtype-i8",
"dtype-i16",
"dtype-u8",
"dtype-u16",
"dynamic_group_by",
"ipc",
"is_in",
"json",
"lazy",
"object",
"parquet",
"random",
"rolling_window",
"rows",
"serde",
"serde-lazy",
"strings",
"temporal",
"to_dummies",
]
default-features = false
optional = true
version = "0.37"
[features]
dataframe = ["num", "polars", "polars-io", "polars-arrow", "polars-ops", "polars-plan", "polars-utils", "sqlparser"]
default = []
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.1" }

View File

@ -1,104 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame};
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct DataTypes;
impl Command for DataTypes {
fn name(&self) -> &str {
"dfr dtypes"
}
fn usage(&self) -> &str {
"Show dataframe data types."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Dataframe dtypes",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr dtypes",
result: Some(
NuDataFrame::try_from_columns(
vec![
Column::new(
"column".to_string(),
vec![Value::test_string("a"), Value::test_string("b")],
),
Column::new(
"dtype".to_string(),
vec![Value::test_string("i64"), Value::test_string("i64")],
),
],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let mut dtypes: Vec<Value> = Vec::new();
let names: Vec<Value> = df
.as_ref()
.get_column_names()
.iter()
.map(|v| {
let dtype = df
.as_ref()
.column(v)
.expect("using name from list of names from dataframe")
.dtype();
let dtype_str = dtype.to_string();
dtypes.push(Value::string(dtype_str, call.head));
Value::string(*v, call.head)
})
.collect();
let names_col = Column::new("column".to_string(), names);
let dtypes_col = Column::new("dtype".to_string(), dtypes);
NuDataFrame::try_from_columns(vec![names_col, dtypes_col], None)
.map(|df| PipelineData::Value(df.into_value(call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(DataTypes {})])
}
}

View File

@ -1,68 +0,0 @@
use crate::dataframe::values::NuDataFrame;
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct ListDF;
impl Command for ListDF {
fn name(&self) -> &str {
"dfr ls"
}
fn usage(&self) -> &str {
"Lists stored dataframes."
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Creates a new dataframe and shows it in the dataframe list",
example: r#"let test = ([[a b];[1 2] [3 4]] | dfr into-df);
ls"#,
result: None,
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let mut vals: Vec<(String, Value)> = vec![];
for overlay_frame in engine_state.active_overlays(&[]) {
for var in &overlay_frame.vars {
if let Ok(value) = stack.get_var(*var.1, call.head) {
let name = String::from_utf8_lossy(var.0).to_string();
vals.push((name, value));
}
}
}
let vals = vals
.into_iter()
.filter_map(|(name, value)| {
NuDataFrame::try_from_value(value).ok().map(|df| (name, df))
})
.map(|(name, df)| {
Value::record(
record! {
"name" => Value::string(name, call.head),
"columns" => Value::int(df.as_ref().width() as i64, call.head),
"rows" => Value::int(df.as_ref().height() as i64, call.head),
},
call.head,
)
})
.collect::<Vec<Value>>();
let list = Value::list(vals, call.head);
Ok(list.into_pipeline_data())
}
}

View File

@ -1,79 +0,0 @@
use crate::dataframe::values::NuDataFrame;
use nu_engine::command_prelude::*;
use polars::prelude::{IpcWriter, SerWriter};
use std::{fs::File, path::PathBuf};
#[derive(Clone)]
pub struct ToArrow;
impl Command for ToArrow {
fn name(&self) -> &str {
"dfr to-arrow"
}
fn usage(&self) -> &str {
"Saves dataframe to arrow file."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("file", SyntaxShape::Filepath, "file path to save dataframe")
.input_output_type(Type::Custom("dataframe".into()), Type::Any)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Saves dataframe to arrow file",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr to-arrow test.arrow",
result: None,
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let file_name: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
let mut file = File::create(&file_name.item).map_err(|e| ShellError::GenericError {
error: "Error with file name".into(),
msg: e.to_string(),
span: Some(file_name.span),
help: None,
inner: vec![],
})?;
IpcWriter::new(&mut file)
.finish(df.as_mut())
.map_err(|e| ShellError::GenericError {
error: "Error saving file".into(),
msg: e.to_string(),
span: Some(file_name.span),
help: None,
inner: vec![],
})?;
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
Ok(PipelineData::Value(
Value::list(vec![file_value], call.head),
None,
))
}

View File

@ -1,109 +0,0 @@
use crate::dataframe::values::NuDataFrame;
use nu_engine::command_prelude::*;
use polars_io::{
avro::{AvroCompression, AvroWriter},
SerWriter,
};
use std::{fs::File, path::PathBuf};
#[derive(Clone)]
pub struct ToAvro;
impl Command for ToAvro {
fn name(&self) -> &str {
"dfr to-avro"
}
fn usage(&self) -> &str {
"Saves dataframe to avro file."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.named(
"compression",
SyntaxShape::String,
"use compression, supports deflate or snappy",
Some('c'),
)
.required("file", SyntaxShape::Filepath, "file path to save dataframe")
.input_output_type(Type::Custom("dataframe".into()), Type::Any)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Saves dataframe to avro file",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr to-avro test.avro",
result: None,
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn get_compression(call: &Call) -> Result<Option<AvroCompression>, ShellError> {
if let Some((compression, span)) = call
.get_flag_expr("compression")
.and_then(|e| e.as_string().map(|s| (s, e.span)))
{
match compression.as_ref() {
"snappy" => Ok(Some(AvroCompression::Snappy)),
"deflate" => Ok(Some(AvroCompression::Deflate)),
_ => Err(ShellError::IncorrectValue {
msg: "compression must be one of deflate or snappy".to_string(),
val_span: span,
call_span: span,
}),
}
} else {
Ok(None)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let file_name: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
let compression = get_compression(call)?;
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
let file = File::create(&file_name.item).map_err(|e| ShellError::GenericError {
error: "Error with file name".into(),
msg: e.to_string(),
span: Some(file_name.span),
help: None,
inner: vec![],
})?;
AvroWriter::new(file)
.with_compression(compression)
.finish(df.as_mut())
.map_err(|e| ShellError::GenericError {
error: "Error saving file".into(),
msg: e.to_string(),
span: Some(file_name.span),
help: None,
inner: vec![],
})?;
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
Ok(PipelineData::Value(
Value::list(vec![file_value], call.head),
None,
))
}

View File

@ -1,80 +0,0 @@
use crate::dataframe::values::NuDataFrame;
use nu_engine::command_prelude::*;
use polars::prelude::{JsonWriter, SerWriter};
use std::{fs::File, io::BufWriter, path::PathBuf};
#[derive(Clone)]
pub struct ToJsonLines;
impl Command for ToJsonLines {
fn name(&self) -> &str {
"dfr to-jsonl"
}
fn usage(&self) -> &str {
"Saves dataframe to a JSON lines file."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("file", SyntaxShape::Filepath, "file path to save dataframe")
.input_output_type(Type::Custom("dataframe".into()), Type::Any)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Saves dataframe to JSON lines file",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr to-jsonl test.jsonl",
result: None,
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let file_name: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
let file = File::create(&file_name.item).map_err(|e| ShellError::GenericError {
error: "Error with file name".into(),
msg: e.to_string(),
span: Some(file_name.span),
help: None,
inner: vec![],
})?;
let buf_writer = BufWriter::new(file);
JsonWriter::new(buf_writer)
.finish(df.as_mut())
.map_err(|e| ShellError::GenericError {
error: "Error saving file".into(),
msg: e.to_string(),
span: Some(file_name.span),
help: None,
inner: vec![],
})?;
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
Ok(PipelineData::Value(
Value::list(vec![file_value], call.head),
None,
))
}

View File

@ -1,79 +0,0 @@
use crate::dataframe::values::NuDataFrame;
use nu_engine::command_prelude::*;
use polars::prelude::ParquetWriter;
use std::{fs::File, path::PathBuf};
#[derive(Clone)]
pub struct ToParquet;
impl Command for ToParquet {
fn name(&self) -> &str {
"dfr to-parquet"
}
fn usage(&self) -> &str {
"Saves dataframe to parquet file."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("file", SyntaxShape::Filepath, "file path to save dataframe")
.input_output_type(Type::Custom("dataframe".into()), Type::Any)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Saves dataframe to parquet file",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr to-parquet test.parquet",
result: None,
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let file_name: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
let file = File::create(&file_name.item).map_err(|e| ShellError::GenericError {
error: "Error with file name".into(),
msg: e.to_string(),
span: Some(file_name.span),
help: None,
inner: vec![],
})?;
ParquetWriter::new(file)
.finish(df.as_mut())
.map_err(|e| ShellError::GenericError {
error: "Error saving file".into(),
msg: e.to_string(),
span: Some(file_name.span),
help: None,
inner: vec![],
})?;
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
Ok(PipelineData::Value(
Value::list(vec![file_value], call.head),
None,
))
}

View File

@ -1,116 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame, NuExpression};
use nu_engine::command_prelude::*;
use polars::prelude::{lit, DataType};
#[derive(Clone)]
pub struct ExprIsIn;
impl Command for ExprIsIn {
fn name(&self) -> &str {
"dfr is-in"
}
fn usage(&self) -> &str {
"Creates an is-in expression."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required(
"list",
SyntaxShape::List(Box::new(SyntaxShape::Any)),
"List to check if values are in",
)
.input_output_type(
Type::Custom("expression".into()),
Type::Custom("expression".into()),
)
.category(Category::Custom("expression".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Creates a is-in expression",
example: r#"let df = ([[a b]; [one 1] [two 2] [three 3]] | dfr into-df);
$df | dfr with-column (dfr col a | dfr is-in [one two] | dfr as a_in)"#,
result: Some(
NuDataFrame::try_from_columns(
vec![
Column::new(
"a".to_string(),
vec![
Value::test_string("one"),
Value::test_string("two"),
Value::test_string("three"),
],
),
Column::new(
"b".to_string(),
vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)],
),
Column::new(
"a_in".to_string(),
vec![
Value::test_bool(true),
Value::test_bool(true),
Value::test_bool(false),
],
),
],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn search_terms(&self) -> Vec<&str> {
vec!["check", "contained", "is-contain", "match"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let list: Vec<Value> = call.req(engine_state, stack, 0)?;
let expr = NuExpression::try_from_pipeline(input, call.head)?;
let values =
NuDataFrame::try_from_columns(vec![Column::new("list".to_string(), list)], None)?;
let list = values.as_series(call.head)?;
if matches!(list.dtype(), DataType::Object(..)) {
return Err(ShellError::IncompatibleParametersSingle {
msg: "Cannot use a mixed list as argument".into(),
span: call.head,
});
}
let expr: NuExpression = expr.into_polars().is_in(lit(list)).into();
Ok(PipelineData::Value(expr.into_value(call.head), None))
}
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
use crate::dataframe::eager::WithColumn;
use crate::dataframe::expressions::alias::ExprAlias;
use crate::dataframe::expressions::col::ExprCol;
#[test]
fn test_examples() {
test_dataframe(vec![
Box::new(ExprIsIn {}),
Box::new(ExprAlias {}),
Box::new(ExprCol {}),
Box::new(WithColumn {}),
])
}
}

View File

@ -1,62 +0,0 @@
mod alias;
mod arg_where;
mod col;
mod concat_str;
mod datepart;
mod expressions_macro;
mod is_in;
mod lit;
mod otherwise;
mod quantile;
mod when;
use nu_protocol::engine::StateWorkingSet;
pub(crate) use crate::dataframe::expressions::alias::ExprAlias;
use crate::dataframe::expressions::arg_where::ExprArgWhere;
pub(super) use crate::dataframe::expressions::col::ExprCol;
pub(super) use crate::dataframe::expressions::concat_str::ExprConcatStr;
pub(crate) use crate::dataframe::expressions::datepart::ExprDatePart;
pub(crate) use crate::dataframe::expressions::expressions_macro::*;
pub(super) use crate::dataframe::expressions::is_in::ExprIsIn;
pub(super) use crate::dataframe::expressions::lit::ExprLit;
pub(super) use crate::dataframe::expressions::otherwise::ExprOtherwise;
pub(super) use crate::dataframe::expressions::quantile::ExprQuantile;
pub(super) use crate::dataframe::expressions::when::ExprWhen;
pub fn add_expressions(working_set: &mut StateWorkingSet) {
macro_rules! bind_command {
( $command:expr ) => {
working_set.add_decl(Box::new($command));
};
( $( $command:expr ),* ) => {
$( working_set.add_decl(Box::new($command)); )*
};
}
// Dataframe commands
bind_command!(
ExprAlias,
ExprArgWhere,
ExprCol,
ExprConcatStr,
ExprCount,
ExprLit,
ExprWhen,
ExprOtherwise,
ExprQuantile,
ExprList,
ExprAggGroups,
ExprCount,
ExprIsIn,
ExprNot,
ExprMax,
ExprMin,
ExprSum,
ExprMean,
ExprMedian,
ExprStd,
ExprVar,
ExprDatePart
);
}

View File

@ -1,101 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame, NuExpression};
use nu_engine::command_prelude::*;
use polars::prelude::{lit, QuantileInterpolOptions};
#[derive(Clone)]
pub struct ExprQuantile;
impl Command for ExprQuantile {
fn name(&self) -> &str {
"dfr quantile"
}
fn usage(&self) -> &str {
"Aggregates the columns to the selected quantile."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required(
"quantile",
SyntaxShape::Number,
"quantile value for quantile operation",
)
.input_output_type(
Type::Custom("expression".into()),
Type::Custom("expression".into()),
)
.category(Category::Custom("expression".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Quantile aggregation for a group-by",
example: r#"[[a b]; [one 2] [one 4] [two 1]]
| dfr into-df
| dfr group-by a
| dfr agg (dfr col b | dfr quantile 0.5)"#,
result: Some(
NuDataFrame::try_from_columns(
vec![
Column::new(
"a".to_string(),
vec![Value::test_string("one"), Value::test_string("two")],
),
Column::new(
"b".to_string(),
vec![Value::test_float(4.0), Value::test_float(1.0)],
),
],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn search_terms(&self) -> Vec<&str> {
vec!["statistics", "percentile", "distribution"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let value = input.into_value(call.head);
let quantile: f64 = call.req(engine_state, stack, 0)?;
let expr = NuExpression::try_from_value(value)?;
let expr: NuExpression = expr
.into_polars()
.quantile(lit(quantile), QuantileInterpolOptions::default())
.into();
Ok(PipelineData::Value(
NuExpression::into_value(expr, call.head),
None,
))
}
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
use crate::dataframe::lazy::aggregate::LazyAggregate;
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
#[test]
fn test_examples() {
test_dataframe(vec![
Box::new(ExprQuantile {}),
Box::new(LazyAggregate {}),
Box::new(ToLazyGroupBy {}),
])
}
}

View File

@ -1,73 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame, NuLazyFrame};
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct LazyCollect;
impl Command for LazyCollect {
fn name(&self) -> &str {
"dfr collect"
}
fn usage(&self) -> &str {
"Collect lazy dataframe into eager dataframe."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("lazyframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "drop duplicates",
example: "[[a b]; [1 2] [3 4]] | dfr into-lazy | dfr collect",
result: Some(
NuDataFrame::try_from_columns(
vec![
Column::new(
"a".to_string(),
vec![Value::test_int(1), Value::test_int(3)],
),
Column::new(
"b".to_string(),
vec![Value::test_int(2), Value::test_int(4)],
),
],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let lazy = NuLazyFrame::try_from_pipeline(input, call.head)?;
let eager = lazy.collect(call.head)?;
let value = Value::custom(Box::new(eager), call.head);
Ok(PipelineData::Value(value, None))
}
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(LazyCollect {})])
}
}

View File

@ -1,153 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame, NuExpression, NuLazyFrame};
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct LazyExplode;
impl Command for LazyExplode {
fn name(&self) -> &str {
"dfr explode"
}
fn usage(&self) -> &str {
"Explodes a dataframe or creates a explode expression."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.rest(
"columns",
SyntaxShape::String,
"columns to explode, only applicable for dataframes",
)
.input_output_types(vec![
(
Type::Custom("expression".into()),
Type::Custom("expression".into()),
),
(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
),
])
.category(Category::Custom("lazyframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Explode the specified dataframe",
example: "[[id name hobbies]; [1 Mercy [Cycling Knitting]] [2 Bob [Skiing Football]]] | dfr into-df | dfr explode hobbies | dfr collect",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"id".to_string(),
vec![
Value::test_int(1),
Value::test_int(1),
Value::test_int(2),
Value::test_int(2),
]),
Column::new(
"name".to_string(),
vec![
Value::test_string("Mercy"),
Value::test_string("Mercy"),
Value::test_string("Bob"),
Value::test_string("Bob"),
]),
Column::new(
"hobbies".to_string(),
vec![
Value::test_string("Cycling"),
Value::test_string("Knitting"),
Value::test_string("Skiing"),
Value::test_string("Football"),
]),
], None).expect("simple df for test should not fail")
.into_value(Span::test_data()),
)
},
Example {
description: "Select a column and explode the values",
example: "[[id name hobbies]; [1 Mercy [Cycling Knitting]] [2 Bob [Skiing Football]]] | dfr into-df | dfr select (dfr col hobbies | dfr explode)",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"hobbies".to_string(),
vec![
Value::test_string("Cycling"),
Value::test_string("Knitting"),
Value::test_string("Skiing"),
Value::test_string("Football"),
]),
], None).expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
]
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
explode(call, input)
}
}
pub(crate) fn explode(call: &Call, input: PipelineData) -> Result<PipelineData, ShellError> {
let value = input.into_value(call.head);
if NuDataFrame::can_downcast(&value) {
let df = NuLazyFrame::try_from_value(value)?;
let columns: Vec<String> = call
.positional_iter()
.filter_map(|e| e.as_string())
.collect();
let exploded = df
.into_polars()
.explode(columns.iter().map(AsRef::as_ref).collect::<Vec<&str>>());
Ok(PipelineData::Value(
NuLazyFrame::from(exploded).into_value(call.head)?,
None,
))
} else {
let expr = NuExpression::try_from_value(value)?;
let expr: NuExpression = expr.into_polars().explode().into();
Ok(PipelineData::Value(
NuExpression::into_value(expr, call.head),
None,
))
}
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::{build_test_engine_state, test_dataframe_example};
use super::*;
use crate::dataframe::lazy::aggregate::LazyAggregate;
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
#[test]
fn test_examples_dataframe() {
let mut engine_state = build_test_engine_state(vec![Box::new(LazyExplode {})]);
test_dataframe_example(&mut engine_state, &LazyExplode.examples()[0]);
}
#[ignore]
#[test]
fn test_examples_expression() {
let mut engine_state = build_test_engine_state(vec![
Box::new(LazyExplode {}),
Box::new(LazyAggregate {}),
Box::new(ToLazyGroupBy {}),
]);
test_dataframe_example(&mut engine_state, &LazyExplode.examples()[1]);
}
}

View File

@ -1,143 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame, NuExpression};
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct LazyFillNA;
impl Command for LazyFillNA {
fn name(&self) -> &str {
"dfr fill-nan"
}
fn usage(&self) -> &str {
"Replaces NaN values with the given expression."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required(
"fill",
SyntaxShape::Any,
"Expression to use to fill the NAN values",
)
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("lazyframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Fills the NaN values with 0",
example: "[1 2 NaN 3 NaN] | dfr into-df | dfr fill-nan 0",
result: Some(
NuDataFrame::try_from_columns(
vec![Column::new(
"0".to_string(),
vec![
Value::test_int(1),
Value::test_int(2),
Value::test_int(0),
Value::test_int(3),
Value::test_int(0),
],
)],
None,
)
.expect("Df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Fills the NaN values of a whole dataframe",
example: "[[a b]; [0.2 1] [0.1 NaN]] | dfr into-df | dfr fill-nan 0",
result: Some(
NuDataFrame::try_from_columns(
vec![
Column::new(
"a".to_string(),
vec![Value::test_float(0.2), Value::test_float(0.1)],
),
Column::new(
"b".to_string(),
vec![Value::test_int(1), Value::test_int(0)],
),
],
None,
)
.expect("Df for test should not fail")
.into_value(Span::test_data()),
),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let fill: Value = call.req(engine_state, stack, 0)?;
let value = input.into_value(call.head);
if NuExpression::can_downcast(&value) {
let expr = NuExpression::try_from_value(value)?;
let fill = NuExpression::try_from_value(fill)?.into_polars();
let expr: NuExpression = expr.into_polars().fill_nan(fill).into();
Ok(PipelineData::Value(
NuExpression::into_value(expr, call.head),
None,
))
} else {
let val_span = value.span();
let frame = NuDataFrame::try_from_value(value)?;
let columns = frame.columns(val_span)?;
let dataframe = columns
.into_iter()
.map(|column| {
let column_name = column.name().to_string();
let values = column
.into_iter()
.map(|value| {
let span = value.span();
match value {
Value::Float { val, .. } => {
if val.is_nan() {
fill.clone()
} else {
value
}
}
Value::List { vals, .. } => {
NuDataFrame::fill_list_nan(vals, span, fill.clone())
}
_ => value,
}
})
.collect::<Vec<Value>>();
Column::new(column_name, values)
})
.collect::<Vec<Column>>();
Ok(PipelineData::Value(
NuDataFrame::try_from_columns(dataframe, None)?.into_value(call.head),
None,
))
}
}
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(LazyFillNA {})])
}
}

View File

@ -1,93 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame, NuExpression, NuLazyFrame};
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct LazyFillNull;
impl Command for LazyFillNull {
fn name(&self) -> &str {
"dfr fill-null"
}
fn usage(&self) -> &str {
"Replaces NULL values with the given expression."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required(
"fill",
SyntaxShape::Any,
"Expression to use to fill the null values",
)
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("lazyframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Fills the null values by 0",
example: "[1 2 2 3 3] | dfr into-df | dfr shift 2 | dfr fill-null 0",
result: Some(
NuDataFrame::try_from_columns(
vec![Column::new(
"0".to_string(),
vec![
Value::test_int(0),
Value::test_int(0),
Value::test_int(1),
Value::test_int(2),
Value::test_int(2),
],
)],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let fill: Value = call.req(engine_state, stack, 0)?;
let value = input.into_value(call.head);
if NuExpression::can_downcast(&value) {
let expr = NuExpression::try_from_value(value)?;
let fill = NuExpression::try_from_value(fill)?.into_polars();
let expr: NuExpression = expr.into_polars().fill_null(fill).into();
Ok(PipelineData::Value(
NuExpression::into_value(expr, call.head),
None,
))
} else {
let lazy = NuLazyFrame::try_from_value(value)?;
let expr = NuExpression::try_from_value(fill)?.into_polars();
let lazy = NuLazyFrame::new(lazy.from_eager, lazy.into_polars().fill_null(expr));
Ok(PipelineData::Value(lazy.into_value(call.head)?, None))
}
}
}
#[cfg(test)]
mod test {
use super::super::super::series::Shift;
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(LazyFillNull {}), Box::new(Shift {})])
}
}

View File

@ -1,126 +0,0 @@
use super::explode::explode;
use crate::dataframe::values::{Column, NuDataFrame};
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct LazyFlatten;
impl Command for LazyFlatten {
fn name(&self) -> &str {
"dfr flatten"
}
fn usage(&self) -> &str {
"An alias for dfr explode."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.rest(
"columns",
SyntaxShape::String,
"columns to flatten, only applicable for dataframes",
)
.input_output_types(vec![
(
Type::Custom("expression".into()),
Type::Custom("expression".into()),
),
(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
),
])
.category(Category::Custom("lazyframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Flatten the specified dataframe",
example: "[[id name hobbies]; [1 Mercy [Cycling Knitting]] [2 Bob [Skiing Football]]] | dfr into-df | dfr flatten hobbies | dfr collect",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"id".to_string(),
vec![
Value::test_int(1),
Value::test_int(1),
Value::test_int(2),
Value::test_int(2),
]),
Column::new(
"name".to_string(),
vec![
Value::test_string("Mercy"),
Value::test_string("Mercy"),
Value::test_string("Bob"),
Value::test_string("Bob"),
]),
Column::new(
"hobbies".to_string(),
vec![
Value::test_string("Cycling"),
Value::test_string("Knitting"),
Value::test_string("Skiing"),
Value::test_string("Football"),
]),
], None).expect("simple df for test should not fail")
.into_value(Span::test_data()),
)
},
Example {
description: "Select a column and flatten the values",
example: "[[id name hobbies]; [1 Mercy [Cycling Knitting]] [2 Bob [Skiing Football]]] | dfr into-df | dfr select (dfr col hobbies | dfr flatten)",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"hobbies".to_string(),
vec![
Value::test_string("Cycling"),
Value::test_string("Knitting"),
Value::test_string("Skiing"),
Value::test_string("Football"),
]),
], None).expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
]
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
explode(call, input)
}
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::{build_test_engine_state, test_dataframe_example};
use super::*;
use crate::dataframe::lazy::aggregate::LazyAggregate;
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
#[test]
fn test_examples_dataframe() {
let mut engine_state = build_test_engine_state(vec![Box::new(LazyFlatten {})]);
test_dataframe_example(&mut engine_state, &LazyFlatten.examples()[0]);
}
#[ignore]
#[test]
fn test_examples_expression() {
let mut engine_state = build_test_engine_state(vec![
Box::new(LazyFlatten {}),
Box::new(LazyAggregate {}),
Box::new(ToLazyGroupBy {}),
]);
test_dataframe_example(&mut engine_state, &LazyFlatten.examples()[1]);
}
}

View File

@ -1,87 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame, NuLazyFrame};
use nu_engine::command_prelude::*;
use polars::prelude::{lit, QuantileInterpolOptions};
#[derive(Clone)]
pub struct LazyQuantile;
impl Command for LazyQuantile {
fn name(&self) -> &str {
"dfr quantile"
}
fn usage(&self) -> &str {
"Aggregates the columns to the selected quantile."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required(
"quantile",
SyntaxShape::Number,
"quantile value for quantile operation",
)
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("lazyframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "quantile value from columns in a dataframe",
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr quantile 0.5",
result: Some(
NuDataFrame::try_from_columns(
vec![
Column::new("a".to_string(), vec![Value::test_float(4.0)]),
Column::new("b".to_string(), vec![Value::test_float(2.0)]),
],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let value = input.into_value(call.head);
let quantile: f64 = call.req(engine_state, stack, 0)?;
let lazy = NuLazyFrame::try_from_value(value)?;
let lazy = NuLazyFrame::new(
lazy.from_eager,
lazy.into_polars()
.quantile(lit(quantile), QuantileInterpolOptions::default())
.map_err(|e| ShellError::GenericError {
error: "Dataframe Error".into(),
msg: e.to_string(),
help: None,
span: None,
inner: vec![],
})?,
);
Ok(PipelineData::Value(lazy.into_value(call.head)?, None))
}
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(LazyQuantile {})])
}
}

View File

@ -1,54 +0,0 @@
use crate::dataframe::values::{NuDataFrame, NuLazyFrame, NuSchema};
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct ToLazyFrame;
impl Command for ToLazyFrame {
fn name(&self) -> &str {
"dfr into-lazy"
}
fn usage(&self) -> &str {
"Converts a dataframe into a lazy dataframe."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.named(
"schema",
SyntaxShape::Record(vec![]),
r#"Polars Schema in format [{name: str}]. CSV, JSON, and JSONL files"#,
Some('s'),
)
.input_output_type(Type::Any, Type::Custom("dataframe".into()))
.category(Category::Custom("lazyframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Takes a dictionary and creates a lazy dataframe",
example: "[[a b];[1 2] [3 4]] | dfr into-lazy",
result: None,
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let maybe_schema = call
.get_flag(engine_state, stack, "schema")?
.map(|schema| NuSchema::try_from(&schema))
.transpose()?;
let df = NuDataFrame::try_from_iter(input.into_iter(), maybe_schema)?;
let lazy = NuLazyFrame::from_dataframe(df);
let value = Value::custom(Box::new(lazy), call.head);
Ok(PipelineData::Value(value, None))
}
}

View File

@ -1,36 +0,0 @@
mod eager;
mod expressions;
mod lazy;
mod series;
mod stub;
mod utils;
mod values;
pub use eager::add_eager_decls;
pub use expressions::add_expressions;
pub use lazy::add_lazy_decls;
pub use series::add_series_decls;
use nu_protocol::engine::{EngineState, StateWorkingSet};
pub fn add_dataframe_context(mut engine_state: EngineState) -> EngineState {
let delta = {
let mut working_set = StateWorkingSet::new(&engine_state);
working_set.add_decl(Box::new(stub::Dfr));
add_series_decls(&mut working_set);
add_eager_decls(&mut working_set);
add_expressions(&mut working_set);
add_lazy_decls(&mut working_set);
working_set.render()
};
if let Err(err) = engine_state.merge_delta(delta) {
eprintln!("Error creating dataframe command context: {err:?}");
}
engine_state
}
#[cfg(test)]
mod test_dataframe;

View File

@ -1,148 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame};
use nu_engine::command_prelude::*;
use polars::prelude::{DataType, IntoSeries};
use polars_ops::prelude::{cum_max, cum_min, cum_sum};
enum CumType {
Min,
Max,
Sum,
}
impl CumType {
fn from_str(roll_type: &str, span: Span) -> Result<Self, ShellError> {
match roll_type {
"min" => Ok(Self::Min),
"max" => Ok(Self::Max),
"sum" => Ok(Self::Sum),
_ => Err(ShellError::GenericError {
error: "Wrong operation".into(),
msg: "Operation not valid for cumulative".into(),
span: Some(span),
help: Some("Allowed values: max, min, sum".into()),
inner: vec![],
}),
}
}
fn to_str(&self) -> &'static str {
match self {
CumType::Min => "cumulative_min",
CumType::Max => "cumulative_max",
CumType::Sum => "cumulative_sum",
}
}
}
#[derive(Clone)]
pub struct Cumulative;
impl Command for Cumulative {
fn name(&self) -> &str {
"dfr cumulative"
}
fn usage(&self) -> &str {
"Cumulative calculation for a series."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("type", SyntaxShape::String, "rolling operation")
.switch("reverse", "Reverse cumulative calculation", Some('r'))
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Cumulative sum for a series",
example: "[1 2 3 4 5] | dfr into-df | dfr cumulative sum",
result: Some(
NuDataFrame::try_from_columns(
vec![Column::new(
"0_cumulative_sum".to_string(),
vec![
Value::test_int(1),
Value::test_int(3),
Value::test_int(6),
Value::test_int(10),
Value::test_int(15),
],
)],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let cum_type: Spanned<String> = call.req(engine_state, stack, 0)?;
let reverse = call.has_flag(engine_state, stack, "reverse")?;
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
if let DataType::Object(..) = series.dtype() {
return Err(ShellError::GenericError {
error: "Found object series".into(),
msg: "Series of type object cannot be used for cumulative operation".into(),
span: Some(call.head),
help: None,
inner: vec![],
});
}
let cum_type = CumType::from_str(&cum_type.item, cum_type.span)?;
let mut res = match cum_type {
CumType::Max => cum_max(&series, reverse),
CumType::Min => cum_min(&series, reverse),
CumType::Sum => cum_sum(&series, reverse),
}
.map_err(|e| ShellError::GenericError {
error: "Error creating cumulative".into(),
msg: e.to_string(),
span: Some(call.head),
help: None,
inner: vec![],
})?;
let name = format!("{}_{}", series.name(), cum_type.to_str());
res.rename(&name);
NuDataFrame::try_from_series(vec![res.into_series()], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(Cumulative {})])
}
}

View File

@ -1,90 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame};
use nu_engine::command_prelude::*;
use polars::prelude::{DatetimeMethods, IntoSeries};
#[derive(Clone)]
pub struct GetDay;
impl Command for GetDay {
fn name(&self) -> &str {
"dfr get-day"
}
fn usage(&self) -> &str {
"Gets day from date."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns day from a date",
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
let df = ([$dt $dt] | dfr into-df);
$df | dfr get-day"#,
result: Some(
NuDataFrame::try_from_columns(
vec![Column::new(
"0".to_string(),
vec![Value::test_int(4), Value::test_int(4)],
)],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let casted = series.datetime().map_err(|e| ShellError::GenericError {
error: "Error casting to datetime type".into(),
msg: e.to_string(),
span: Some(call.head),
help: None,
inner: vec![],
})?;
let res = casted.day().into_series();
NuDataFrame::try_from_series(vec![res], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(explore_refactor_IntoDatetime)]
mod test {
use super::super::super::super::super::IntoDatetime;
use super::super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(GetDay {}), Box::new(IntoDatetime {})])
}
}

View File

@ -1,90 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame};
use nu_engine::command_prelude::*;
use polars::prelude::{DatetimeMethods, IntoSeries};
#[derive(Clone)]
pub struct GetHour;
impl Command for GetHour {
fn name(&self) -> &str {
"dfr get-hour"
}
fn usage(&self) -> &str {
"Gets hour from date."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns hour from a date",
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
let df = ([$dt $dt] | dfr into-df);
$df | dfr get-hour"#,
result: Some(
NuDataFrame::try_from_columns(
vec![Column::new(
"0".to_string(),
vec![Value::test_int(16), Value::test_int(16)],
)],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let casted = series.datetime().map_err(|e| ShellError::GenericError {
error: "Error casting to datetime type".into(),
msg: e.to_string(),
span: Some(call.head),
help: None,
inner: vec![],
})?;
let res = casted.hour().into_series();
NuDataFrame::try_from_series(vec![res], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(explore_refactor_IntoDatetime)]
mod test {
use super::super::super::super::super::IntoDatetime;
use super::super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(GetHour {}), Box::new(IntoDatetime {})])
}
}

View File

@ -1,90 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame};
use nu_engine::command_prelude::*;
use polars::prelude::{DatetimeMethods, IntoSeries};
#[derive(Clone)]
pub struct GetMinute;
impl Command for GetMinute {
fn name(&self) -> &str {
"dfr get-minute"
}
fn usage(&self) -> &str {
"Gets minute from date."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns minute from a date",
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
let df = ([$dt $dt] | dfr into-df);
$df | dfr get-minute"#,
result: Some(
NuDataFrame::try_from_columns(
vec![Column::new(
"0".to_string(),
vec![Value::test_int(39), Value::test_int(39)],
)],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let casted = series.datetime().map_err(|e| ShellError::GenericError {
error: "Error casting to datetime type".into(),
msg: e.to_string(),
span: Some(call.head),
help: None,
inner: vec![],
})?;
let res = casted.minute().into_series();
NuDataFrame::try_from_series(vec![res], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(explore_refactor_IntoDatetime)]
mod test {
use super::super::super::super::super::IntoDatetime;
use super::super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(GetMinute {}), Box::new(IntoDatetime {})])
}
}

View File

@ -1,90 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame};
use nu_engine::command_prelude::*;
use polars::prelude::{DatetimeMethods, IntoSeries};
#[derive(Clone)]
pub struct GetMonth;
impl Command for GetMonth {
fn name(&self) -> &str {
"dfr get-month"
}
fn usage(&self) -> &str {
"Gets month from date."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns month from a date",
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
let df = ([$dt $dt] | dfr into-df);
$df | dfr get-month"#,
result: Some(
NuDataFrame::try_from_columns(
vec![Column::new(
"0".to_string(),
vec![Value::test_int(8), Value::test_int(8)],
)],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let casted = series.datetime().map_err(|e| ShellError::GenericError {
error: "Error casting to datetime type".into(),
msg: e.to_string(),
span: Some(call.head),
help: None,
inner: vec![],
})?;
let res = casted.month().into_series();
NuDataFrame::try_from_series(vec![res], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(explore_refactor_IntoDatetime)]
mod test {
use super::super::super::super::super::IntoDatetime;
use super::super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(GetMonth {}), Box::new(IntoDatetime {})])
}
}

View File

@ -1,90 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame};
use nu_engine::command_prelude::*;
use polars::prelude::{DatetimeMethods, IntoSeries};
#[derive(Clone)]
pub struct GetNanosecond;
impl Command for GetNanosecond {
fn name(&self) -> &str {
"dfr get-nanosecond"
}
fn usage(&self) -> &str {
"Gets nanosecond from date."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns nanosecond from a date",
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
let df = ([$dt $dt] | dfr into-df);
$df | dfr get-nanosecond"#,
result: Some(
NuDataFrame::try_from_columns(
vec![Column::new(
"0".to_string(),
vec![Value::test_int(0), Value::test_int(0)],
)],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let casted = series.datetime().map_err(|e| ShellError::GenericError {
error: "Error casting to datetime type".into(),
msg: e.to_string(),
span: Some(call.head),
help: None,
inner: vec![],
})?;
let res = casted.nanosecond().into_series();
NuDataFrame::try_from_series(vec![res], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(explore_refactor_IntoDatetime)]
mod test {
use super::super::super::super::super::IntoDatetime;
use super::super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(GetNanosecond {}), Box::new(IntoDatetime {})])
}
}

View File

@ -1,90 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame};
use nu_engine::command_prelude::*;
use polars::prelude::{DatetimeMethods, IntoSeries};
#[derive(Clone)]
pub struct GetOrdinal;
impl Command for GetOrdinal {
fn name(&self) -> &str {
"dfr get-ordinal"
}
fn usage(&self) -> &str {
"Gets ordinal from date."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns ordinal from a date",
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
let df = ([$dt $dt] | dfr into-df);
$df | dfr get-ordinal"#,
result: Some(
NuDataFrame::try_from_columns(
vec![Column::new(
"0".to_string(),
vec![Value::test_int(217), Value::test_int(217)],
)],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let casted = series.datetime().map_err(|e| ShellError::GenericError {
error: "Error casting to datetime type".into(),
msg: e.to_string(),
span: Some(call.head),
help: None,
inner: vec![],
})?;
let res = casted.ordinal().into_series();
NuDataFrame::try_from_series(vec![res], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(explore_refactor_IntoDatetime)]
mod test {
use super::super::super::super::super::IntoDatetime;
use super::super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(GetOrdinal {}), Box::new(IntoDatetime {})])
}
}

View File

@ -1,90 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame};
use nu_engine::command_prelude::*;
use polars::prelude::{DatetimeMethods, IntoSeries};
#[derive(Clone)]
pub struct GetSecond;
impl Command for GetSecond {
fn name(&self) -> &str {
"dfr get-second"
}
fn usage(&self) -> &str {
"Gets second from date."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns second from a date",
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
let df = ([$dt $dt] | dfr into-df);
$df | dfr get-second"#,
result: Some(
NuDataFrame::try_from_columns(
vec![Column::new(
"0".to_string(),
vec![Value::test_int(18), Value::test_int(18)],
)],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let casted = series.datetime().map_err(|e| ShellError::GenericError {
error: "Error casting to datetime type".into(),
msg: e.to_string(),
span: Some(call.head),
help: None,
inner: vec![],
})?;
let res = casted.second().into_series();
NuDataFrame::try_from_series(vec![res], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(explore_refactor_IntoDatetime)]
mod test {
use super::super::super::super::super::IntoDatetime;
use super::super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(GetSecond {}), Box::new(IntoDatetime {})])
}
}

View File

@ -1,90 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame};
use nu_engine::command_prelude::*;
use polars::prelude::{DatetimeMethods, IntoSeries};
#[derive(Clone)]
pub struct GetWeek;
impl Command for GetWeek {
fn name(&self) -> &str {
"dfr get-week"
}
fn usage(&self) -> &str {
"Gets week from date."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns week from a date",
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
let df = ([$dt $dt] | dfr into-df);
$df | dfr get-week"#,
result: Some(
NuDataFrame::try_from_columns(
vec![Column::new(
"0".to_string(),
vec![Value::test_int(32), Value::test_int(32)],
)],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let casted = series.datetime().map_err(|e| ShellError::GenericError {
error: "Error casting to datetime type".into(),
msg: e.to_string(),
span: Some(call.head),
help: None,
inner: vec![],
})?;
let res = casted.week().into_series();
NuDataFrame::try_from_series(vec![res], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(explore_refactor_IntoDatetime)]
mod test {
use super::super::super::super::super::IntoDatetime;
use super::super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(GetWeek {}), Box::new(IntoDatetime {})])
}
}

View File

@ -1,90 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame};
use nu_engine::command_prelude::*;
use polars::prelude::{DatetimeMethods, IntoSeries};
#[derive(Clone)]
pub struct GetWeekDay;
impl Command for GetWeekDay {
fn name(&self) -> &str {
"dfr get-weekday"
}
fn usage(&self) -> &str {
"Gets weekday from date."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns weekday from a date",
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
let df = ([$dt $dt] | dfr into-df);
$df | dfr get-weekday"#,
result: Some(
NuDataFrame::try_from_columns(
vec![Column::new(
"0".to_string(),
vec![Value::test_int(2), Value::test_int(2)],
)],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let casted = series.datetime().map_err(|e| ShellError::GenericError {
error: "Error casting to datetime type".into(),
msg: e.to_string(),
span: Some(call.head),
help: None,
inner: vec![],
})?;
let res = casted.weekday().into_series();
NuDataFrame::try_from_series(vec![res], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(explore_refactor_IntoDatetime)]
mod test {
use super::super::super::super::super::IntoDatetime;
use super::super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(GetWeekDay {}), Box::new(IntoDatetime {})])
}
}

View File

@ -1,90 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame};
use nu_engine::command_prelude::*;
use polars::prelude::{DatetimeMethods, IntoSeries};
#[derive(Clone)]
pub struct GetYear;
impl Command for GetYear {
fn name(&self) -> &str {
"dfr get-year"
}
fn usage(&self) -> &str {
"Gets year from date."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns year from a date",
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
let df = ([$dt $dt] | dfr into-df);
$df | dfr get-year"#,
result: Some(
NuDataFrame::try_from_columns(
vec![Column::new(
"0".to_string(),
vec![Value::test_int(2020), Value::test_int(2020)],
)],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let casted = series.datetime().map_err(|e| ShellError::GenericError {
error: "Error casting to datetime type".into(),
msg: e.to_string(),
span: Some(call.head),
help: None,
inner: vec![],
})?;
let res = casted.year().into_series();
NuDataFrame::try_from_series(vec![res], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(explore_refactor_IntoDatetime)]
mod test {
use super::super::super::super::super::IntoDatetime;
use super::super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(GetYear {}), Box::new(IntoDatetime {})])
}
}

View File

@ -1,104 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame};
use nu_engine::command_prelude::*;
use polars::prelude::{is_in, IntoSeries};
#[derive(Clone)]
pub struct IsIn;
impl Command for IsIn {
fn name(&self) -> &str {
"dfr is-in"
}
fn usage(&self) -> &str {
"Checks if elements from a series are contained in right series."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("other", SyntaxShape::Any, "right series")
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Checks if elements from a series are contained in right series",
example: r#"let other = ([1 3 6] | dfr into-df);
[5 6 6 6 8 8 8] | dfr into-df | dfr is-in $other"#,
result: Some(
NuDataFrame::try_from_columns(
vec![Column::new(
"is_in".to_string(),
vec![
Value::test_bool(false),
Value::test_bool(true),
Value::test_bool(true),
Value::test_bool(true),
Value::test_bool(false),
Value::test_bool(false),
Value::test_bool(false),
],
)],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?.as_series(call.head)?;
let other_value: Value = call.req(engine_state, stack, 0)?;
let other_span = other_value.span();
let other_df = NuDataFrame::try_from_value(other_value)?;
let other = other_df.as_series(other_span)?;
let mut res = is_in(&df, &other)
.map_err(|e| ShellError::GenericError {
error: "Error finding in other".into(),
msg: e.to_string(),
span: Some(call.head),
help: None,
inner: vec![],
})?
.into_series();
res.rename("is_in");
NuDataFrame::try_from_series(vec![res.into_series()], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(IsIn {})])
}
}

View File

@ -1,95 +0,0 @@
mod date;
pub use date::*;
mod string;
pub use string::*;
mod masks;
pub use masks::*;
mod indexes;
pub use indexes::*;
mod all_false;
mod all_true;
mod arg_max;
mod arg_min;
mod cumulative;
mod n_null;
mod n_unique;
mod rolling;
mod shift;
mod unique;
mod value_counts;
use nu_protocol::engine::StateWorkingSet;
pub use all_false::AllFalse;
pub use all_true::AllTrue;
pub use arg_max::ArgMax;
pub use arg_min::ArgMin;
pub use cumulative::Cumulative;
pub use n_null::NNull;
pub use n_unique::NUnique;
pub use rolling::Rolling;
pub use shift::Shift;
pub use unique::Unique;
pub use value_counts::ValueCount;
pub fn add_series_decls(working_set: &mut StateWorkingSet) {
macro_rules! bind_command {
( $command:expr ) => {
working_set.add_decl(Box::new($command));
};
( $( $command:expr ),* ) => {
$( working_set.add_decl(Box::new($command)); )*
};
}
// Series commands
bind_command!(
AllFalse,
AllTrue,
ArgMax,
ArgMin,
ArgSort,
ArgTrue,
ArgUnique,
AsDate,
AsDateTime,
Concatenate,
Contains,
Cumulative,
GetDay,
GetHour,
GetMinute,
GetMonth,
GetNanosecond,
GetOrdinal,
GetSecond,
GetWeek,
GetWeekDay,
GetYear,
IsDuplicated,
IsIn,
IsNotNull,
IsNull,
IsUnique,
NNull,
NUnique,
NotSeries,
Replace,
ReplaceAll,
Rolling,
SetSeries,
SetWithIndex,
Shift,
StrLengths,
StrSlice,
StrFTime,
ToLowerCase,
ToUpperCase,
Unique,
ValueCount
);
}

View File

@ -1,116 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame, NuExpression, NuLazyFrame};
use nu_engine::command_prelude::*;
use polars_plan::prelude::lit;
#[derive(Clone)]
pub struct Shift;
impl Command for Shift {
fn name(&self) -> &str {
"dfr shift"
}
fn usage(&self) -> &str {
"Shifts the values by a given period."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("period", SyntaxShape::Int, "shift period")
.named(
"fill",
SyntaxShape::Any,
"Expression used to fill the null values (lazy df)",
Some('f'),
)
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe or lazyframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Shifts the values by a given period",
example: "[1 2 2 3 3] | dfr into-df | dfr shift 2 | dfr drop-nulls",
result: Some(
NuDataFrame::try_from_columns(
vec![Column::new(
"0".to_string(),
vec![Value::test_int(1), Value::test_int(2), Value::test_int(2)],
)],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let value = input.into_value(call.head);
if NuLazyFrame::can_downcast(&value) {
let df = NuLazyFrame::try_from_value(value)?;
command_lazy(engine_state, stack, call, df)
} else {
let df = NuDataFrame::try_from_value(value)?;
command_eager(engine_state, stack, call, df)
}
}
}
fn command_eager(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
df: NuDataFrame,
) -> Result<PipelineData, ShellError> {
let period: i64 = call.req(engine_state, stack, 0)?;
let series = df.as_series(call.head)?.shift(period);
NuDataFrame::try_from_series(vec![series], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
fn command_lazy(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
lazy: NuLazyFrame,
) -> Result<PipelineData, ShellError> {
let shift: i64 = call.req(engine_state, stack, 0)?;
let fill: Option<Value> = call.get_flag(engine_state, stack, "fill")?;
let lazy = lazy.into_polars();
let lazy: NuLazyFrame = match fill {
Some(fill) => {
let expr = NuExpression::try_from_value(fill)?.into_polars();
lazy.shift_and_fill(lit(shift), expr).into()
}
None => lazy.shift(shift).into(),
};
Ok(PipelineData::Value(lazy.into_value(call.head)?, None))
}
#[cfg(test)]
mod test {
use super::super::super::eager::DropNulls;
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(Shift {}), Box::new(DropNulls {})])
}
}

View File

@ -1,147 +0,0 @@
use crate::dataframe::{
utils::extract_strings,
values::{Column, NuDataFrame, NuLazyFrame},
};
use nu_engine::command_prelude::*;
use polars::prelude::{IntoSeries, UniqueKeepStrategy};
#[derive(Clone)]
pub struct Unique;
impl Command for Unique {
fn name(&self) -> &str {
"dfr unique"
}
fn usage(&self) -> &str {
"Returns unique values from a dataframe."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.named(
"subset",
SyntaxShape::Any,
"Subset of column(s) to use to maintain rows (lazy df)",
Some('s'),
)
.switch(
"last",
"Keeps last unique value. Default keeps first value (lazy df)",
Some('l'),
)
.switch(
"maintain-order",
"Keep the same order as the original DataFrame (lazy df)",
Some('k'),
)
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe or lazyframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Returns unique values from a series",
example: "[2 2 2 2 2] | dfr into-df | dfr unique",
result: Some(
NuDataFrame::try_from_columns(
vec![Column::new("0".to_string(), vec![Value::test_int(2)])],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Creates a is unique expression from a column",
example: "col a | unique",
result: None,
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let value = input.into_value(call.head);
if NuLazyFrame::can_downcast(&value) {
let df = NuLazyFrame::try_from_value(value)?;
command_lazy(engine_state, stack, call, df)
} else {
let df = NuDataFrame::try_from_value(value)?;
command_eager(engine_state, stack, call, df)
}
}
}
fn command_eager(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
df: NuDataFrame,
) -> Result<PipelineData, ShellError> {
let series = df.as_series(call.head)?;
let res = series.unique().map_err(|e| ShellError::GenericError {
error: "Error calculating unique values".into(),
msg: e.to_string(),
span: Some(call.head),
help: Some("The str-slice command can only be used with string columns".into()),
inner: vec![],
})?;
NuDataFrame::try_from_series(vec![res.into_series()], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
fn command_lazy(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
lazy: NuLazyFrame,
) -> Result<PipelineData, ShellError> {
let last = call.has_flag(engine_state, stack, "last")?;
let maintain = call.has_flag(engine_state, stack, "maintain-order")?;
let subset: Option<Value> = call.get_flag(engine_state, stack, "subset")?;
let subset = match subset {
Some(value) => Some(extract_strings(value)?),
None => None,
};
let strategy = if last {
UniqueKeepStrategy::Last
} else {
UniqueKeepStrategy::First
};
let lazy = lazy.into_polars();
let lazy: NuLazyFrame = if maintain {
lazy.unique(subset, strategy).into()
} else {
lazy.unique_stable(subset, strategy).into()
};
Ok(PipelineData::Value(lazy.into_value(call.head)?, None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(Unique {})])
}
}

View File

@ -1,44 +0,0 @@
use nu_engine::{command_prelude::*, get_full_help};
#[derive(Clone)]
pub struct Dfr;
impl Command for Dfr {
fn name(&self) -> &str {
"dfr"
}
fn usage(&self) -> &str {
"Operate with data in a dataframe format."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("dfr")
.category(Category::Custom("dataframe".into()))
.input_output_types(vec![(Type::Nothing, Type::String)])
}
fn extra_usage(&self) -> &str {
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
Ok(Value::string(
get_full_help(
&Dfr.signature(),
&Dfr.examples(),
engine_state,
stack,
self.is_parser_keyword(),
),
call.head,
)
.into_pipeline_data())
}
}

View File

@ -1,97 +0,0 @@
use super::{
eager::{SchemaDF, ToDataFrame},
expressions::ExprCol,
lazy::{LazyCollect, LazyFillNull, ToLazyFrame},
};
use nu_cmd_lang::Let;
use nu_engine::{command_prelude::*, eval_block};
use nu_parser::parse;
use nu_protocol::{debugger::WithoutDebug, engine::StateWorkingSet};
pub fn test_dataframe(cmds: Vec<Box<dyn Command + 'static>>) {
if cmds.is_empty() {
panic!("Empty commands vector")
}
// The first element in the cmds vector must be the one tested
let examples = cmds[0].examples();
let mut engine_state = build_test_engine_state(cmds.clone());
for example in examples {
test_dataframe_example(&mut engine_state, &example);
}
}
pub fn build_test_engine_state(cmds: Vec<Box<dyn Command + 'static>>) -> Box<EngineState> {
let mut engine_state = Box::new(EngineState::new());
let delta = {
// Base functions that are needed for testing
// Try to keep this working set small to keep tests running as fast as possible
let mut working_set = StateWorkingSet::new(&engine_state);
working_set.add_decl(Box::new(Let));
working_set.add_decl(Box::new(ToDataFrame));
working_set.add_decl(Box::new(ToLazyFrame));
working_set.add_decl(Box::new(LazyCollect));
working_set.add_decl(Box::new(ExprCol));
working_set.add_decl(Box::new(SchemaDF));
working_set.add_decl(Box::new(LazyFillNull));
// Adding the command that is being tested to the working set
for cmd in cmds.clone() {
working_set.add_decl(cmd);
}
working_set.render()
};
engine_state
.merge_delta(delta)
.expect("Error merging delta");
engine_state
}
pub fn test_dataframe_example(engine_state: &mut Box<EngineState>, example: &Example) {
// Skip tests that don't have results to compare to
if example.result.is_none() {
return;
}
let start = std::time::Instant::now();
let (block, delta) = {
let mut working_set = StateWorkingSet::new(engine_state);
let output = parse(&mut working_set, None, example.example.as_bytes(), false);
if let Some(err) = working_set.parse_errors.first() {
panic!("test parse error in `{}`: {:?}", example.example, err)
}
(output, working_set.render())
};
engine_state
.merge_delta(delta)
.expect("Error merging delta");
let mut stack = Stack::new().capture();
let result =
eval_block::<WithoutDebug>(engine_state, &mut stack, &block, PipelineData::empty())
.unwrap_or_else(|err| panic!("test eval error in `{}`: {:?}", example.example, err))
.into_value(Span::test_data());
println!("input: {}", example.example);
println!("result: {result:?}");
println!("done: {:?}", start.elapsed());
// Note. Value implements PartialEq for Bool, Int, Float, String and Block
// If the command you are testing requires to compare another case, then
// you need to define its equality in the Value struct
if let Some(expected) = example.result.clone() {
if result != expected {
panic!("the example result is different to expected value: {result:?} != {expected:?}")
}
}
}

View File

@ -1,14 +0,0 @@
mod nu_dataframe;
mod nu_expression;
mod nu_lazyframe;
mod nu_lazygroupby;
mod nu_schema;
mod nu_when;
pub mod utils;
pub use nu_dataframe::{Axis, Column, NuDataFrame};
pub use nu_expression::NuExpression;
pub use nu_lazyframe::NuLazyFrame;
pub use nu_lazygroupby::NuLazyGroupBy;
pub use nu_schema::{str_to_dtype, NuSchema};
pub use nu_when::NuWhen;

View File

@ -1,79 +0,0 @@
use super::NuDataFrame;
use nu_protocol::{ast::Operator, CustomValue, ShellError, Span, Value};
// CustomValue implementation for NuDataFrame
impl CustomValue for NuDataFrame {
fn typetag_name(&self) -> &'static str {
"dataframe"
}
fn typetag_deserialize(&self) {
unimplemented!("typetag_deserialize")
}
fn clone_value(&self, span: nu_protocol::Span) -> Value {
let cloned = NuDataFrame {
df: self.df.clone(),
from_lazy: false,
};
Value::custom(Box::new(cloned), span)
}
fn type_name(&self) -> String {
self.typetag_name().to_string()
}
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
let vals = self.print(span)?;
Ok(Value::list(vals, span))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
self
}
fn follow_path_int(
&self,
_self_span: Span,
count: usize,
path_span: Span,
) -> Result<Value, ShellError> {
self.get_value(count, path_span)
}
fn follow_path_string(
&self,
_self_span: Span,
column_name: String,
path_span: Span,
) -> Result<Value, ShellError> {
let column = self.column(&column_name, path_span)?;
Ok(column.into_value(path_span))
}
fn partial_cmp(&self, other: &Value) -> Option<std::cmp::Ordering> {
match other {
Value::Custom { val, .. } => val
.as_any()
.downcast_ref::<Self>()
.and_then(|other| self.is_equal(other)),
_ => None,
}
}
fn operation(
&self,
lhs_span: Span,
operator: Operator,
op: Span,
right: &Value,
) -> Result<Value, ShellError> {
self.compute_with_value(lhs_span, operator, op, right)
}
}

View File

@ -1,147 +0,0 @@
use super::NuExpression;
use nu_protocol::{
ast::{Comparison, Math, Operator},
CustomValue, ShellError, Span, Type, Value,
};
use polars::prelude::Expr;
use std::ops::{Add, Div, Mul, Rem, Sub};
// CustomValue implementation for NuDataFrame
impl CustomValue for NuExpression {
fn typetag_name(&self) -> &'static str {
"expression"
}
fn typetag_deserialize(&self) {
unimplemented!("typetag_deserialize")
}
fn clone_value(&self, span: nu_protocol::Span) -> Value {
let cloned = NuExpression(self.0.clone());
Value::custom(Box::new(cloned), span)
}
fn type_name(&self) -> String {
self.typetag_name().to_string()
}
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
self.to_value(span)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
self
}
fn operation(
&self,
lhs_span: Span,
operator: Operator,
op: Span,
right: &Value,
) -> Result<Value, ShellError> {
compute_with_value(self, lhs_span, operator, op, right)
}
}
fn compute_with_value(
left: &NuExpression,
lhs_span: Span,
operator: Operator,
op: Span,
right: &Value,
) -> Result<Value, ShellError> {
let rhs_span = right.span();
match right {
Value::Custom { val: rhs, .. } => {
let rhs = rhs.as_any().downcast_ref::<NuExpression>().ok_or_else(|| {
ShellError::DowncastNotPossible {
msg: "Unable to create expression".into(),
span: rhs_span,
}
})?;
match rhs.as_ref() {
polars::prelude::Expr::Literal(..) => {
with_operator(operator, left, rhs, lhs_span, right.span(), op)
}
_ => Err(ShellError::TypeMismatch {
err_message: "Only literal expressions or number".into(),
span: right.span(),
}),
}
}
_ => {
let rhs = NuExpression::try_from_value(right.clone())?;
with_operator(operator, left, &rhs, lhs_span, right.span(), op)
}
}
}
fn with_operator(
operator: Operator,
left: &NuExpression,
right: &NuExpression,
lhs_span: Span,
rhs_span: Span,
op_span: Span,
) -> Result<Value, ShellError> {
match operator {
Operator::Math(Math::Plus) => apply_arithmetic(left, right, lhs_span, Add::add),
Operator::Math(Math::Minus) => apply_arithmetic(left, right, lhs_span, Sub::sub),
Operator::Math(Math::Multiply) => apply_arithmetic(left, right, lhs_span, Mul::mul),
Operator::Math(Math::Divide) => apply_arithmetic(left, right, lhs_span, Div::div),
Operator::Math(Math::Modulo) => apply_arithmetic(left, right, lhs_span, Rem::rem),
Operator::Math(Math::FloorDivision) => apply_arithmetic(left, right, lhs_span, Div::div),
Operator::Comparison(Comparison::Equal) => Ok(left
.clone()
.apply_with_expr(right.clone(), Expr::eq)
.into_value(lhs_span)),
Operator::Comparison(Comparison::NotEqual) => Ok(left
.clone()
.apply_with_expr(right.clone(), Expr::neq)
.into_value(lhs_span)),
Operator::Comparison(Comparison::GreaterThan) => Ok(left
.clone()
.apply_with_expr(right.clone(), Expr::gt)
.into_value(lhs_span)),
Operator::Comparison(Comparison::GreaterThanOrEqual) => Ok(left
.clone()
.apply_with_expr(right.clone(), Expr::gt_eq)
.into_value(lhs_span)),
Operator::Comparison(Comparison::LessThan) => Ok(left
.clone()
.apply_with_expr(right.clone(), Expr::lt)
.into_value(lhs_span)),
Operator::Comparison(Comparison::LessThanOrEqual) => Ok(left
.clone()
.apply_with_expr(right.clone(), Expr::lt_eq)
.into_value(lhs_span)),
_ => Err(ShellError::OperatorMismatch {
op_span,
lhs_ty: Type::Custom(left.typetag_name().into()).to_string(),
lhs_span,
rhs_ty: Type::Custom(right.typetag_name().into()).to_string(),
rhs_span,
}),
}
}
fn apply_arithmetic<F>(
left: &NuExpression,
right: &NuExpression,
span: Span,
f: F,
) -> Result<Value, ShellError>
where
F: Fn(Expr, Expr) -> Expr,
{
let expr: NuExpression = f(left.as_ref().clone(), right.as_ref().clone()).into();
Ok(expr.into_value(span))
}

View File

@ -1,50 +0,0 @@
use super::NuLazyFrame;
use nu_protocol::{record, CustomValue, ShellError, Span, Value};
// CustomValue implementation for NuDataFrame
impl CustomValue for NuLazyFrame {
fn typetag_name(&self) -> &'static str {
"lazyframe"
}
fn typetag_deserialize(&self) {
unimplemented!("typetag_deserialize")
}
fn clone_value(&self, span: nu_protocol::Span) -> Value {
let cloned = NuLazyFrame {
lazy: self.lazy.clone(),
from_eager: self.from_eager,
schema: self.schema.clone(),
};
Value::custom(Box::new(cloned), span)
}
fn type_name(&self) -> String {
self.typetag_name().to_string()
}
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
let optimized_plan = self
.as_ref()
.describe_optimized_plan()
.unwrap_or_else(|_| "<NOT AVAILABLE>".to_string());
Ok(Value::record(
record! {
"plan" => Value::string(self.as_ref().describe_plan(), span),
"optimized_plan" => Value::string(optimized_plan, span),
},
span,
))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
self
}
}

View File

@ -1,188 +0,0 @@
mod custom_value;
use super::{NuDataFrame, NuExpression};
use core::fmt;
use nu_protocol::{PipelineData, ShellError, Span, Value};
use polars::prelude::{Expr, IntoLazy, LazyFrame, Schema};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
// Lazyframe wrapper for Nushell operations
// Polars LazyFrame is behind and Option to allow easy implementation of
// the Deserialize trait
#[derive(Default)]
pub struct NuLazyFrame {
pub lazy: Option<LazyFrame>,
pub schema: Option<Schema>,
pub from_eager: bool,
}
// Mocked serialization of the LazyFrame object
impl Serialize for NuLazyFrame {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_none()
}
}
// Mocked deserialization of the LazyFrame object
impl<'de> Deserialize<'de> for NuLazyFrame {
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(NuLazyFrame::default())
}
}
impl fmt::Debug for NuLazyFrame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NuLazyframe")
}
}
// Referenced access to the real LazyFrame
impl AsRef<LazyFrame> for NuLazyFrame {
fn as_ref(&self) -> &polars::prelude::LazyFrame {
// The only case when there cannot be a lazy frame is if it is created
// using the default function or if created by deserializing something
self.lazy.as_ref().expect("there should always be a frame")
}
}
impl AsMut<LazyFrame> for NuLazyFrame {
fn as_mut(&mut self) -> &mut polars::prelude::LazyFrame {
// The only case when there cannot be a lazy frame is if it is created
// using the default function or if created by deserializing something
self.lazy.as_mut().expect("there should always be a frame")
}
}
impl From<LazyFrame> for NuLazyFrame {
fn from(lazy_frame: LazyFrame) -> Self {
Self {
lazy: Some(lazy_frame),
from_eager: false,
schema: None,
}
}
}
impl NuLazyFrame {
pub fn new(from_eager: bool, lazy: LazyFrame) -> Self {
Self {
lazy: Some(lazy),
from_eager,
schema: None,
}
}
pub fn from_dataframe(df: NuDataFrame) -> Self {
let lazy = df.as_ref().clone().lazy();
Self {
lazy: Some(lazy),
from_eager: true,
schema: Some(df.as_ref().schema()),
}
}
pub fn into_value(self, span: Span) -> Result<Value, ShellError> {
if self.from_eager {
let df = self.collect(span)?;
Ok(Value::custom(Box::new(df), span))
} else {
Ok(Value::custom(Box::new(self), span))
}
}
pub fn into_polars(self) -> LazyFrame {
self.lazy.expect("lazyframe cannot be none to convert")
}
pub fn collect(self, span: Span) -> Result<NuDataFrame, ShellError> {
self.lazy
.expect("No empty lazy for collect")
.collect()
.map_err(|e| ShellError::GenericError {
error: "Error collecting lazy frame".into(),
msg: e.to_string(),
span: Some(span),
help: None,
inner: vec![],
})
.map(|df| NuDataFrame {
df,
from_lazy: !self.from_eager,
})
}
pub fn try_from_value(value: Value) -> Result<Self, ShellError> {
if Self::can_downcast(&value) {
Ok(Self::get_lazy_df(value)?)
} else if NuDataFrame::can_downcast(&value) {
let df = NuDataFrame::try_from_value(value)?;
Ok(NuLazyFrame::from_dataframe(df))
} else {
Err(ShellError::CantConvert {
to_type: "lazy or eager dataframe".into(),
from_type: value.get_type().to_string(),
span: value.span(),
help: None,
})
}
}
pub fn try_from_pipeline(input: PipelineData, span: Span) -> Result<Self, ShellError> {
let value = input.into_value(span);
Self::try_from_value(value)
}
pub fn get_lazy_df(value: Value) -> Result<Self, ShellError> {
let span = value.span();
match value {
Value::Custom { val, .. } => match val.as_any().downcast_ref::<Self>() {
Some(expr) => Ok(Self {
lazy: expr.lazy.clone(),
from_eager: false,
schema: None,
}),
None => Err(ShellError::CantConvert {
to_type: "lazy frame".into(),
from_type: "non-dataframe".into(),
span,
help: None,
}),
},
x => Err(ShellError::CantConvert {
to_type: "lazy frame".into(),
from_type: x.get_type().to_string(),
span: x.span(),
help: None,
}),
}
}
pub fn can_downcast(value: &Value) -> bool {
if let Value::Custom { val, .. } = value {
val.as_any().downcast_ref::<Self>().is_some()
} else {
false
}
}
pub fn apply_with_expr<F>(self, expr: NuExpression, f: F) -> Self
where
F: Fn(LazyFrame, Expr) -> LazyFrame,
{
let df = self.lazy.expect("Lazy frame must not be empty to apply");
let expr = expr.into_polars();
let new_frame = f(df, expr);
Self {
from_eager: self.from_eager,
lazy: Some(new_frame),
schema: None,
}
}
}

View File

@ -1,44 +0,0 @@
use super::NuLazyGroupBy;
use nu_protocol::{record, CustomValue, ShellError, Span, Value};
// CustomValue implementation for NuDataFrame
impl CustomValue for NuLazyGroupBy {
fn typetag_name(&self) -> &'static str {
"lazygroupby"
}
fn typetag_deserialize(&self) {
unimplemented!("typetag_deserialize")
}
fn clone_value(&self, span: nu_protocol::Span) -> Value {
let cloned = NuLazyGroupBy {
group_by: self.group_by.clone(),
schema: self.schema.clone(),
from_eager: self.from_eager,
};
Value::custom(Box::new(cloned), span)
}
fn type_name(&self) -> String {
self.typetag_name().to_string()
}
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
Ok(Value::record(
record! {
"LazyGroupBy" => Value::string("apply aggregation to complete execution plan", span)
},
span,
))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
self
}
}

View File

@ -1,113 +0,0 @@
mod custom_value;
use core::fmt;
use nu_protocol::{PipelineData, ShellError, Span, Value};
use polars::prelude::{LazyGroupBy, Schema};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
// Lazyframe wrapper for Nushell operations
// Polars LazyFrame is behind and Option to allow easy implementation of
// the Deserialize trait
#[derive(Default)]
pub struct NuLazyGroupBy {
pub group_by: Option<LazyGroupBy>,
pub schema: Option<Schema>,
pub from_eager: bool,
}
// Mocked serialization of the LazyFrame object
impl Serialize for NuLazyGroupBy {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_none()
}
}
// Mocked deserialization of the LazyFrame object
impl<'de> Deserialize<'de> for NuLazyGroupBy {
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(NuLazyGroupBy::default())
}
}
impl fmt::Debug for NuLazyGroupBy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NuLazyGroupBy")
}
}
// Referenced access to the real LazyFrame
impl AsRef<LazyGroupBy> for NuLazyGroupBy {
fn as_ref(&self) -> &polars::prelude::LazyGroupBy {
// The only case when there cannot be a lazy frame is if it is created
// using the default function or if created by deserializing something
self.group_by
.as_ref()
.expect("there should always be a frame")
}
}
impl AsMut<LazyGroupBy> for NuLazyGroupBy {
fn as_mut(&mut self) -> &mut polars::prelude::LazyGroupBy {
// The only case when there cannot be a lazy frame is if it is created
// using the default function or if created by deserializing something
self.group_by
.as_mut()
.expect("there should always be a frame")
}
}
impl From<LazyGroupBy> for NuLazyGroupBy {
fn from(group_by: LazyGroupBy) -> Self {
Self {
group_by: Some(group_by),
from_eager: false,
schema: None,
}
}
}
impl NuLazyGroupBy {
pub fn into_value(self, span: Span) -> Value {
Value::custom(Box::new(self), span)
}
pub fn into_polars(self) -> LazyGroupBy {
self.group_by.expect("GroupBy cannot be none to convert")
}
pub fn try_from_value(value: Value) -> Result<Self, ShellError> {
let span = value.span();
match value {
Value::Custom { val, .. } => match val.as_any().downcast_ref::<NuLazyGroupBy>() {
Some(group) => Ok(Self {
group_by: group.group_by.clone(),
schema: group.schema.clone(),
from_eager: group.from_eager,
}),
None => Err(ShellError::CantConvert {
to_type: "lazy groupby".into(),
from_type: "custom value".into(),
span,
help: None,
}),
},
x => Err(ShellError::CantConvert {
to_type: "lazy groupby".into(),
from_type: x.get_type().to_string(),
span: x.span(),
help: None,
}),
}
}
pub fn try_from_pipeline(input: PipelineData, span: Span) -> Result<Self, ShellError> {
let value = input.into_value(span);
Self::try_from_value(value)
}
}

View File

@ -1,41 +0,0 @@
use super::NuWhen;
use nu_protocol::{CustomValue, ShellError, Span, Value};
// CustomValue implementation for NuDataFrame
impl CustomValue for NuWhen {
fn typetag_name(&self) -> &'static str {
"when"
}
fn typetag_deserialize(&self) {
unimplemented!("typetag_deserialize")
}
fn clone_value(&self, span: nu_protocol::Span) -> Value {
let cloned = self.clone();
Value::custom(Box::new(cloned), span)
}
fn type_name(&self) -> String {
self.typetag_name().to_string()
}
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
let val: String = match self {
NuWhen::Then(_) => "whenthen".into(),
NuWhen::ChainedThen(_) => "whenthenthen".into(),
};
let value = Value::string(val, span);
Ok(value)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
self
}
}

View File

@ -1,77 +0,0 @@
mod custom_value;
use core::fmt;
use nu_protocol::{ShellError, Span, Value};
use polars::prelude::{col, when, ChainedThen, Then};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Clone)]
pub enum NuWhen {
Then(Box<Then>),
ChainedThen(ChainedThen),
}
// Mocked serialization of the LazyFrame object
impl Serialize for NuWhen {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_none()
}
}
// Mocked deserialization of the LazyFrame object
impl<'de> Deserialize<'de> for NuWhen {
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(NuWhen::Then(Box::new(when(col("a")).then(col("b")))))
}
}
impl fmt::Debug for NuWhen {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NuWhen")
}
}
impl From<Then> for NuWhen {
fn from(then: Then) -> Self {
NuWhen::Then(Box::new(then))
}
}
impl From<ChainedThen> for NuWhen {
fn from(chained_when: ChainedThen) -> Self {
NuWhen::ChainedThen(chained_when)
}
}
impl NuWhen {
pub fn into_value(self, span: Span) -> Value {
Value::custom(Box::new(self), span)
}
pub fn try_from_value(value: Value) -> Result<Self, ShellError> {
let span = value.span();
match value {
Value::Custom { val, .. } => match val.as_any().downcast_ref::<Self>() {
Some(expr) => Ok(expr.clone()),
None => Err(ShellError::CantConvert {
to_type: "when expression".into(),
from_type: "non when expression".into(),
span,
help: None,
}),
},
x => Err(ShellError::CantConvert {
to_type: "when expression".into(),
from_type: x.get_type().to_string(),
span: x.span(),
help: None,
}),
}
}
}

View File

@ -1,4 +0,0 @@
#[cfg(feature = "dataframe")]
pub mod dataframe;
#[cfg(feature = "dataframe")]
pub use dataframe::*;

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT"
name = "nu-cmd-extra"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
version = "0.92.1"
version = "0.94.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -13,13 +13,13 @@ version = "0.92.1"
bench = false
[dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.92.1" }
nu-engine = { path = "../nu-engine", version = "0.92.1" }
nu-json = { version = "0.92.1", path = "../nu-json" }
nu-parser = { path = "../nu-parser", version = "0.92.1" }
nu-pretty-hex = { version = "0.92.1", path = "../nu-pretty-hex" }
nu-protocol = { path = "../nu-protocol", version = "0.92.1" }
nu-utils = { path = "../nu-utils", version = "0.92.1" }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.0" }
nu-engine = { path = "../nu-engine", version = "0.94.0" }
nu-json = { version = "0.94.0", path = "../nu-json" }
nu-parser = { path = "../nu-parser", version = "0.94.0" }
nu-pretty-hex = { version = "0.94.0", path = "../nu-pretty-hex" }
nu-protocol = { path = "../nu-protocol", version = "0.94.0" }
nu-utils = { path = "../nu-utils", version = "0.94.0" }
# Potential dependencies for extras
heck = { workspace = true }
@ -37,6 +37,6 @@ extra = ["default"]
default = []
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.1" }
nu-command = { path = "../nu-command", version = "0.92.1" }
nu-test-support = { path = "../nu-test-support", version = "0.92.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.0" }
nu-command = { path = "../nu-command", version = "0.94.0" }
nu-test-support = { path = "../nu-test-support", version = "0.94.0" }

View File

@ -29,16 +29,6 @@ impl Command for Bits {
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
Ok(Value::string(
get_full_help(
&Bits.signature(),
&Bits.examples(),
engine_state,
stack,
self.is_parser_keyword(),
),
call.head,
)
.into_pipeline_data())
Ok(Value::string(get_full_help(self, engine_state, stack), call.head).into_pipeline_data())
}
}

View File

@ -30,8 +30,8 @@ impl Command for BitsInto {
(Type::Duration, Type::String),
(Type::String, Type::String),
(Type::Bool, Type::String),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.allow_variants_without_examples(true) // TODO: supply exhaustive examples
.rest(
@ -64,7 +64,7 @@ impl Command for BitsInto {
vec![
Example {
description: "convert a binary value into a string, padded to 8 places with 0s",
example: "01b | into bits",
example: "0x[1] | into bits",
result: Some(Value::string("00000001",
Span::test_data(),
)),
@ -118,22 +118,12 @@ fn into_bits(
let cell_paths = call.rest(engine_state, stack, 0)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
match input {
PipelineData::ExternalStream { stdout: None, .. } => {
Ok(Value::binary(vec![], head).into_pipeline_data())
}
PipelineData::ExternalStream {
stdout: Some(stream),
..
} => {
// TODO: in the future, we may want this to stream out, converting each to bytes
let output = stream.into_bytes()?;
Ok(Value::binary(output.item, head).into_pipeline_data())
}
_ => {
let args = Arguments { cell_paths };
operate(action, args, input, call.head, engine_state.ctrlc.clone())
}
if let PipelineData::ByteStream(stream, ..) = input {
// TODO: in the future, we may want this to stream out, converting each to bytes
Ok(Value::binary(stream.into_bytes()?, head).into_pipeline_data())
} else {
let args = Arguments { cell_paths };
operate(action, args, input, call.head, engine_state.ctrlc.clone())
}
}

View File

@ -15,7 +15,7 @@ impl Command for Fmt {
fn signature(&self) -> nu_protocol::Signature {
Signature::build("fmt")
.input_output_types(vec![(Type::Number, Type::Record(vec![]))])
.input_output_types(vec![(Type::Number, Type::record())])
.category(Category::Conversions)
}

View File

@ -1,4 +1,4 @@
use nu_engine::{command_prelude::*, get_eval_block_with_early_return};
use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce};
use nu_protocol::engine::Closure;
#[derive(Clone)]
@ -10,7 +10,7 @@ impl Command for EachWhile {
}
fn usage(&self) -> &str {
"Run a block on each row of the input list until a null is found, then create a new list with the results."
"Run a closure on each row of the input list until a null is found, then create a new list with the results."
}
fn search_terms(&self) -> Vec<&str> {
@ -67,116 +67,58 @@ impl Command for EachWhile {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let capture_block: Closure = call.req(engine_state, stack, 0)?;
let head = call.head;
let closure: Closure = call.req(engine_state, stack, 0)?;
let metadata = input.metadata();
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
let block = engine_state.get_block(capture_block.block_id).clone();
let mut stack = stack.captures_to_stack(capture_block.captures);
let orig_env_vars = stack.env_vars.clone();
let orig_env_hidden = stack.env_hidden.clone();
let span = call.head;
let eval_block_with_early_return = get_eval_block_with_early_return(&engine_state);
match input {
PipelineData::Empty => Ok(PipelineData::Empty),
PipelineData::Value(Value::Range { .. }, ..)
| PipelineData::Value(Value::List { .. }, ..)
| PipelineData::ListStream { .. } => Ok(input
// TODO: Could this be changed to .into_interruptible_iter(ctrlc) ?
.into_iter()
.map_while(move |x| {
// with_env() is used here to ensure that each iteration uses
// a different set of environment variables.
// Hence, a 'cd' in the first loop won't affect the next loop.
stack.with_env(&orig_env_vars, &orig_env_hidden);
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
| PipelineData::ListStream(..) => {
let mut closure = ClosureEval::new(engine_state, stack, closure);
Ok(input
.into_iter()
.map_while(move |value| {
match closure
.run_with_value(value)
.and_then(|data| data.into_value(head))
{
Ok(value) => (!value.is_nothing()).then_some(value),
Err(_) => None,
}
}
match eval_block_with_early_return(
&engine_state,
&mut stack,
&block,
x.into_pipeline_data(),
) {
Ok(v) => {
let value = v.into_value(span);
if value.is_nothing() {
None
} else {
Some(value)
})
.fuse()
.into_pipeline_data(head, engine_state.ctrlc.clone()))
}
PipelineData::ByteStream(stream, ..) => {
let span = stream.span();
if let Some(chunks) = stream.chunks() {
let mut closure = ClosureEval::new(engine_state, stack, closure);
Ok(chunks
.map_while(move |value| {
let value = value.ok()?;
match closure
.run_with_value(value)
.and_then(|data| data.into_value(span))
{
Ok(value) => (!value.is_nothing()).then_some(value),
Err(_) => None,
}
}
Err(_) => None,
}
})
.fuse()
.into_pipeline_data(ctrlc)),
PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::empty()),
PipelineData::ExternalStream {
stdout: Some(stream),
..
} => Ok(stream
.into_iter()
.map_while(move |x| {
// with_env() is used here to ensure that each iteration uses
// a different set of environment variables.
// Hence, a 'cd' in the first loop won't affect the next loop.
stack.with_env(&orig_env_vars, &orig_env_hidden);
let x = match x {
Ok(x) => x,
Err(_) => return None,
};
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
}
match eval_block_with_early_return(
&engine_state,
&mut stack,
&block,
x.into_pipeline_data(),
) {
Ok(v) => {
let value = v.into_value(span);
if value.is_nothing() {
None
} else {
Some(value)
}
}
Err(_) => None,
}
})
.fuse()
.into_pipeline_data(ctrlc)),
})
.fuse()
.into_pipeline_data(head, engine_state.ctrlc.clone()))
} else {
Ok(PipelineData::Empty)
}
}
// This match allows non-iterables to be accepted,
// which is currently considered undesirable (Nov 2022).
PipelineData::Value(x, ..) => {
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
}
eval_block_with_early_return(
&engine_state,
&mut stack,
&block,
x.into_pipeline_data(),
)
PipelineData::Value(value, ..) => {
ClosureEvalOnce::new(engine_state, stack, closure).run_with_value(value)
}
}
.map(|x| x.set_metadata(metadata))
.map(|data| data.set_metadata(metadata))
}
}

View File

@ -58,7 +58,7 @@ fn horizontal_rotate_value(
Value::Record { val: record, .. } => {
let rotations = by.map(|n| n % record.len()).unwrap_or(1);
let (mut cols, mut vals): (Vec<_>, Vec<_>) = record.into_iter().unzip();
let (mut cols, mut vals): (Vec<_>, Vec<_>) = record.into_owned().into_iter().unzip();
if !cells_only {
match direction {
HorizontalDirection::Right => cols.rotate_right(rotations),

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