Compare commits

...

81 Commits

Author SHA1 Message Date
JT
6cc4ef6c70 bump to 0.71, use 1.63 toolchain (#7061) 2022-11-09 06:54:00 +13:00
JT
517173bb8c Update rust-toolchain.toml 2022-11-09 06:06:33 +13:00
JT
e415be6c0e Revert back to 1.63 because of macOS build issues 2022-11-09 06:05:53 +13:00
59332562bb Update contributing guide and PR template (#7008)
* Update contributing guide

* Refactor pull request template

* Reword PR template a bit

* Update CONTRIBUTING.md

Co-authored-by: Reilly Wood <26268125+rgwood@users.noreply.github.com>

* Reformulate

* Make "Before Submitting" a top-level header

* Add review requirement to After Submitting

* Reformulate

* Update .github/pull_request_template.md

Co-authored-by: Dan Davison <dandavison7@gmail.com>

* Reformulate contributing guide

Co-authored-by: Reilly Wood <26268125+rgwood@users.noreply.github.com>
Co-authored-by: Dan Davison <dandavison7@gmail.com>
2022-11-07 22:57:29 +01:00
3d8d7787de Pin reedline to 0.14.0 release (#7050)
See release notes:
https://github.com/nushell/reedline/releases/tag/v0.14.0
2022-11-07 21:31:15 +01:00
611fe41788 Make the example names unique across workspace (#7046)
Avoids name collision in the target directory when running test
compilation.

cc @rgwood
2022-11-07 09:00:21 +01:00
a6118eed8d Revert "Fix for escaping backslashes in interpolated strings (fixes #6737) (#7020)" (#7038)
This reverts commit d4798d6ee1.
2022-11-06 16:17:00 -06:00
d4798d6ee1 Fix for escaping backslashes in interpolated strings (fixes #6737) (#7020)
Co-authored-by: Gavin Foley <gavinmfoley@gmail.com>
2022-11-07 08:57:28 +13:00
5ea245badf Make example binaries proper cargo examples (#7019)
Should not be built by default with `cargo build`
Instead are compiled with `cargo test` to avoid bitrot
Run with `cargo run -p ... --example ...`
2022-11-06 11:39:27 -08:00
5ee7847035 Add accidentally missing tests of some command examples (#7035)
* Add missing tests of examples

* Fix broken tests due to externals not being in working set

* Comment out `where` test due to bug
2022-11-06 09:53:25 -08:00
8a812cf03c added some search-terms to the platform category (#7021)
* added some search-terms to the `platform` category

* removed the redundant `sleep` search-term

* removed the redundant `gradients` search-term
2022-11-06 18:11:04 +01:00
9a1cedfd08 Add expected result to test (#7031) 2022-11-06 18:09:56 +01:00
766d1ef374 Update reedline (#7023)
Fixes #6991
2022-11-06 09:37:36 +01:00
beec658872 New "display_output" hook. (#6915)
* New "display_output" hook.

* Fix unrelated "clippy" complaint in nu-tables crate.

* Fix code-formattng and style issues in "display_output" hook

* Enhance eval_hook to return PipelineData.

This allows a hook (including display_output) to return a value.

Co-authored-by: JT <547158+jntrnr@users.noreply.github.com>
2022-11-06 13:46:40 +13:00
b90d701f89 Rename column name from command to name for consistency (#7007) 2022-11-05 10:46:30 +13:00
be5d71ea47 Run a round of clippy --fix to fix a ton of lints (#7006)
Signed-off-by: Alex Saveau <saveau.alexandre@gmail.com>

Signed-off-by: Alex Saveau <saveau.alexandre@gmail.com>
2022-11-04 15:11:17 -05:00
f1bde69131 Fix panic when encountering ENOTTY. (#7001) 2022-11-05 09:06:04 +13:00
36ae384fb3 Improve do command docs (#6975) 2022-11-05 07:50:56 +13:00
2c4048eb43 Refactor ansi stripping into nu-utils functions (#6966)
Allows use of slightly optimized variants that check if they have to use
the heavier vte parser. Tries to avoid unnnecessary allocations. Initial
performance characteristics proven out in #4378.

Also reduces boilerplate with right-ward drift.
2022-11-05 07:49:45 +13:00
b9195c2668 fix: fixcd (#6799)
* fix: fixcd

try to fix

Log: try to fix the bug with can enter a permisson error fold

* change wording

* fat

* fmt

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-11-05 07:38:39 +13:00
bb968304da bump rust-toolchain to 1.64 (#7005)
* bump rust-toolchain to 1.64

* 1.64 clippy
2022-11-04 10:27:23 -05:00
ca9bf19041 highlight term on PipelineData::Value() (#6997) 2022-11-04 08:42:16 -05:00
JT
ecfee4c542 Remove unnecessary clone in par-each (#6995)
* Remove unnecessary clone in par-each

* clippy
2022-11-04 18:07:28 +13:00
acb34561eb category tweak (#6982) 2022-11-02 12:17:17 -05:00
43aec8cdbe Fix $in in blocks given to any and all (#6951)
* Fix $in in blocks given to `any` and `all` (closes #6917)

* Fix help message typos

* Fix tests ($in doesn't work in examples?!)

* Fix formatting
2022-11-01 11:36:54 -07:00
e46d610f77 Refactor: finish refactor on commands which take optional cell paths. (#6961)
* refactor on conversions module

* finish refactor on strings command

* simplify code

* rename from ArgumentsCp to CellPathOnlyArgs

* fmt code

* refactor on hash relative commands
2022-11-01 12:40:11 +01:00
1d95861a09 Remove inadvertent dep on original ansi_term (#6965)
Is default feature in `lscolors`. Not needed for function as we use
crossterm backend in this case.
2022-11-01 12:27:20 +13:00
f48de73236 change str distance to output value::int (#6963)
* change str distance to output value::int

* update the test
2022-10-31 10:28:04 -05:00
0b4daa66b0 tweak upsert help text (#6962)
* tweak upsert help text

* more tweaks
2022-10-31 08:18:11 -05:00
412952182f Update reedline to latest dev (#6953)
- Reedline now properly supports the `sqlite-dynlib` feature flag
- Reorganized examples allow removal of `gethostname` as reedline
dev-dependency
2022-10-30 22:08:07 +01:00
014d36b17a Use nt-api 4 on Windows (#6949)
* Bump nushell-sytem dep to ntapi 0.4

0.3.7 trigger a warning about code being incompatible
with future rust versions. This is resolved in 0.4
https://github.com/MSxDOS/ntapi/issues/11

* Upgrade Cargo.lock for ntapi 0.4
2022-10-30 14:29:41 +01:00
f44f3a8af1 Fix double cache read in CI (#6948) 2022-10-30 08:24:10 +01:00
457514590d Refactor: introduce general operate commands to reduce duplicate code (#6879)
* make format filesize more flexible

* make code simpler

* finish refactor on bytes commands

* finish refactor on str commands

* fimplify code

* rename from column_paths to cell_paths
2022-10-29 16:29:46 -05:00
843d8c2242 Make default for mv safer, require -f to overwrite (#6904)
* fix:  "saner" default for mv

fixes #6747

As highlighted in the issue, the default behavior of nu currently
is to overwrite the destination file without notice.
This is not a "standard" expectation users that want this behavior
can create a dedicated alias.

* fix: 📝 edit the comment

* fix:  updated the tests

* fix: 🚧 use --force for case test
2022-10-29 22:16:29 +02:00
ce4ae00a6f Remove unused dependencies (#6945)
* Remove unused dependencies

Inspired by #6938 ran `cargo +nightly udeps --features extra`.
Removes 2 crates and should remove an unnecessary intra-workspace
dependency which might open up further opportunities for compilation.

* Make windows-only dependency conditional in toml

`omnipath` is only used on Windows and already behind a `#[cfg]` block
in the code. Made the dependency in `Cargo.toml` conditional as well.

* Make `nu-pretty-hex` example a proper example

This allows making `rand` a dev-dependency in this crate.
2022-10-29 21:19:12 +02:00
4f7f6a2932 Friendly error message for access beyond end (#6944)
Adds `ShellError::AccessEmptyContent`
2022-10-29 19:47:50 +02:00
7039602e4d Move nu-test-support into dev deps on nu-command (#6940)
This reduces the number of dependencies to build for `cargo build` or
`cargo run` by around 19.

Will not speed up CI as we need to `cargo test` but should help for
`cargo install` or users just building from source.
Time saved on my machine ~0.8 secs so likely unnoticable in noise.

Larger future goal is reducing longer dependency chains to allow more
parallel compilation.
2022-10-29 19:39:27 +02:00
acb7aff6fb Fix feature flag for open test (#6935) 2022-10-28 20:25:19 -07:00
8940ee6c3f enable ability to upsert into a list like update (#6932) 2022-10-28 18:13:14 -05:00
3b26b4355e Fix bug with alias handling when searching for matching brackets (#6931)
* [4325] - fix slicing

* [4325] - fix style
2022-10-28 16:21:24 -05:00
b56e603c58 Update PR template to mention user-facing changes (#6923)
* Update PR template to mention user-facing changes

* remove checkboxes
2022-10-28 09:14:08 -07:00
66c2a36123 table: Show truncated record differently (#6884)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-10-28 14:00:10 +02:00
8838815737 Update nix crate to 0.25 and narrow features (#6924)
Avoids compiling the crate twice due to incompatible versions from
dependencies. This avoids binary bloat before linking as well.
Narrow our feature selection to the used modules/functions to save
compile time. On my machine reduces `nix` crate compile time from 
around 9 secs to 0.9 secs.
2022-10-28 01:07:13 +02:00
834522d002 Fix each while behavior when printing. (#6897)
Fixes #6895

Warning: `Iterator::map_while` does not return a `FusedIterator`!
Depending on the consuming adaptor or code (e.g. for loop) the iteration
may be stopped but this is not guaranteed.

Adds a test example but Examples handle iterators like
`FusedIterator` and thus don't catch the regression

Cleanup the message on another test
2022-10-27 23:45:45 +02:00
f281cd5aa3 Update merge to also take single records (#6919) 2022-10-27 09:00:26 -07:00
5add5cbd12 Further edits to help messages (#6913) 2022-10-26 09:36:42 -07:00
902aad6016 fix description of build-string's second example (#6912) 2022-10-26 09:36:31 -05:00
13f87857cf docs: 📝 add "map" to each's search terms (#6903) 2022-10-25 21:24:27 +02:00
e0cc2c9112 Make get 1 error message better (#6892) 2022-10-24 18:22:57 -07:00
92ab8b831b Reduce required dependencies for diagnostics (#6648)
Disable backtrace on miette
- gimli crate requires several seconds
Disable diagnostics on wax
- depends on an outdated miette version

Builds fine, no observable loss in diagnostics quality of life

Removes 10 crates that have to be compiled.
2022-10-24 21:42:32 +02:00
6a7a60429f Remove unnecessary #[allow(...)] annotations (#6870)
* Remove unnecessary `#[allow]` annots

Reduce the number of lint exceptions that are not necessary with the
current state of the code (or more recent toolchain)

* Remove dead code from `FileStructure` in nu-command

* Replace `allow(unused)` with relevant feature switch

* Deal with `needless_collect` with annotations

* Change hack for needless_collect in `from json`

This change obviates the need for `allow(needless_collect)`
Removes a pessimistic allocation for empty strings, but increases
allocation size to `Value`

Probably not really worth it.

* Revert "Deal with `needless_collect` with annotations"

This reverts commit 05aca98445.

The previous state seems to better from a performance perspective as a
`Vec<String>` is lighter weight than `Vec<Value>`
2022-10-24 20:12:16 +02:00
79fd7d54b2 Wrap open parse errors from from commands (#6877)
* Wrap `open` parse errors from `from` commands

Minimal fix for #6843

This propagates the underlying errors from the called `from` commands
and adds a top-level error with the full path and the understood file
extension and resulting called command.

* Repoint inner span for `from ...` to `open`

* Add actionable message: refer to help or use --raw
2022-10-24 20:09:19 +02:00
ebca840d91 Add support to render right prompt on last line of the prompt (#6781)
* Add support to render right prompt on last line of the prompt

* reset reedline to main branch

* update reedline to fix right prompt to be rendered correctly

* reset reedline to main branch again
2022-10-23 16:18:26 +02:00
17b2bcc125 Support range in str substring (#6867) 2022-10-23 11:42:17 +02:00
24a98f8999 Mildly edited a small handful of help messages (#6868)
* Edited a handful of help messages

* Remove line break as instructed by clippy
2022-10-23 02:02:52 -04:00
e49b359848 Bumps Windows 0.37 -> 0.42. (#6865) 2022-10-22 17:59:44 -07:00
c9fb381d69 feat: coredump (#6791)
fix issue in https://github.com/nushell/nushell/issues/5903
return Error to pipeline_data, if match error, then return error
directly
2022-10-22 20:24:58 +02:00
8224ec49bc Highlight matching brackets / parentheses (#6655)
* [4325] - wip

* [4325] - hightlight only matched symbol

* [4325] - cleanup

* [4325] - match bracket while typing

* [4325] - fix clippy

* [4325] - add bracket highlight configuration

* [4325] - fix working with non-ascii
2022-10-22 11:55:45 -05:00
fe7e87ee02 Fixes for ps on Linux (#6858)
* Fix ps returning nothing when process dies

* Fix ps CPU calcs on Linux, remove unused thread code
2022-10-22 11:54:46 -05:00
a3dce8ff19 table -e Fix stackoverflow (cause endless empty list) (#6847)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-10-22 11:52:32 -05:00
89f3cbf318 Try not to use verbatim paths for UNC shares (#6824) 2022-10-22 11:51:52 -05:00
3f555a6836 add more helpful error for calling a decl that exists in a module (#6752)
* add more helpful error for calling a decl that exists in a module

* accord to suggestions

* make error more helpful
2022-10-22 11:41:31 -05:00
4fdf5c663c Prepend directory to the binary tarball (#6701)
* Prepend directory to the binary tarball

According to https://www.gnu.org/software/tar/manual/html_section/transform.html, use
`--transform` parameter to prepend directory to each file name.

Closes #6676

* Don't depend on GNU tar
2022-10-22 11:39:11 -05:00
1ec41a0ab4 Expose reedline EditCommand::Complete command (#6863)
This should have been done in 5eee33c7e4
2022-10-22 11:32:07 -05:00
ab0a6b6ca6 path: fix error message (#6860)
Closes #6819
2022-10-22 06:42:46 -05:00
e3bf6fdfc0 Print command help in base from+to commands (#6856) 2022-10-21 10:08:57 -07:00
46c0d29c08 table/ Fix paging indexing (#6850)
* table/ Fix paging indexing

close #6840

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Add test for pagging with row_overlapping

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-10-21 18:02:25 +02:00
76ccd5668a Remove perf flag to streamline logging configuration (#6834) 2022-10-21 10:20:21 -05:00
c6436eb32f rm: don't update target_exists every time in the loop (#6837) 2022-10-21 07:42:29 -07:00
b2c29117d9 table -e align key to 2nd line (#6842)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-10-21 06:29:55 -05:00
ffb1dfb012 Update ci workflow actions, fix #6713 (#6841)
* Update ci workflow actions, fix #6713

* Upgrade actions/setup-python to v4
2022-10-21 15:25:02 +08:00
88c6fa9933 Update reedline to fix history search filtering (#6835)
https://github.com/nushell/nushell/pull/6802#issuecomment-1285826499
2022-10-21 09:04:54 +02:00
60df45a390 Add missing shape_directory to default_config.nu (#6836)
Closes #6832
2022-10-20 21:25:09 -07:00
10aa86272b Allow captured stderr saving to file (#6793)
* support redirect stderr to file

* fix test

* fix test

* fix test
2022-10-20 07:56:44 -05:00
d37e6ba3b5 nu-table: Check perf improvements (#6710)
* nu-table: Checkout to test vte parsing

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Bump tabled version

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-10-20 07:52:15 -05:00
5eee33c7e4 Tab inline completion (#6802)
* Make Tab insert (partial) completion instead of select next menu item

* Use reedline feature branch

Co-authored-by: JT <547158+jntrnr@users.noreply.github.com>
2022-10-20 23:39:48 +13:00
5e748ae8fc make ++ append lists (#6766)
* make `++` append lists

* fmt

* fix for database
2022-10-20 23:28:18 +13:00
50e53e788a Simplify and reduce allocations in pipeline data loop (#6790) 2022-10-20 23:22:07 +13:00
7336e1df1a rm: fix error span when targets doesn't exists (#6815)
Closes #6810
2022-10-20 10:00:25 +02:00
a724a8fe7d bump to dev version 0.70.1 (#6814) 2022-10-20 18:04:10 +13:00
JT
c731a4e275 Update Cargo.toml 2022-10-19 11:44:04 +13:00
231 changed files with 4281 additions and 3417 deletions

View File

@ -1,21 +1,26 @@
# Description
(description of your pull request here)
(Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience.)
# Tests
# Major Changes
Make sure you've done the following:
If you're considering making any major change to nushell, before starting work on it, seek feedback from regular contributors and get approval for the idea from the core team either on [Discord](https://discordapp.com/invite/NtAbbGn) or [GitHub issue](https://github.com/nushell/nushell/issues/new/choose).
Making sure we're all on board with the change saves everybody's time.
Thanks!
- [ ] Add tests that cover your changes, either in the command examples, the crate/tests folder, or in the /tests folder.
- [ ] Try to think about corner cases and various ways how your changes could break. Cover them with tests.
- [ ] If adding tests is not possible, please document in the PR body a minimal example with steps on how to reproduce so one can verify your change works.
# Tests + Formatting
Make sure you've done the following, if applicable:
- Add tests that cover your changes (either in the command examples, the crate/tests folder, or in the /tests folder)
- Try to think about corner cases and various ways how your changes could break. Cover those in the tests
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 --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
- [ ] `cargo test --workspace --features=extra` to check that all the tests pass
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# Documentation
# After Submitting
- [ ] If your PR touches a user-facing nushell feature then make sure that there is an entry in the documentation (https://github.com/nushell/nushell.github.io) for the feature, and update it if necessary.
* Help us keep the docs up to date: If your PR affects the user experience of Nushell (adding/removing a command, changing an input/output type, etc.), make sure the changes are reflected in the documentation (https://github.com/nushell/nushell.github.io) after the PR is merged.

View File

@ -20,29 +20,19 @@ jobs:
NUSHELL_CARGO_TARGET: ci
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
# makes ci use rust-toolchain.toml
# with:
# profile: minimal
# toolchain: ${{ matrix.rust }}
# override: true
# components: rustfmt, clippy
- uses: Swatinem/rust-cache@v1
with:
key: "v2" # increment this to bust the cache if needed
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
- name: Rustfmt
uses: actions-rs/cargo@v1
uses: actions-rs/cargo@v1.0.1
with:
command: fmt
args: --all -- --check
- name: Clippy
uses: actions-rs/cargo@v1
uses: actions-rs/cargo@v1.0.1
with:
command: clippy
args: --features=extra --workspace --exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
@ -72,22 +62,13 @@ jobs:
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
# makes ci use rust-toolchain.toml
# with:
# profile: minimal
# toolchain: ${{ matrix.rust }}
# override: true
- uses: Swatinem/rust-cache@v1
with:
key: ${{ matrix.style }}v3 # increment this to bust the cache if needed
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
- name: Tests
uses: actions-rs/cargo@v1
uses: actions-rs/cargo@v1.0.1
with:
command: test
args: --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
@ -108,28 +89,19 @@ jobs:
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
# makes ci use rust-toolchain.toml
# with:
# profile: minimal
# toolchain: ${{ matrix.rust }}
# override: true
- uses: Swatinem/rust-cache@v1
with:
key: "2" # increment this to bust the cache if needed
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
- name: Install Nushell
uses: actions-rs/cargo@v1
uses: actions-rs/cargo@v1.0.1
with:
command: install
args: --path=. --profile ci --no-default-features
- name: Setup Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: "3.10"
@ -159,24 +131,19 @@ jobs:
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
# makes ci use rust-toolchain.toml
# with:
# profile: minimal
# toolchain: ${{ matrix.rust }}
# override: true
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
- name: Clippy
uses: actions-rs/cargo@v1
uses: actions-rs/cargo@v1.0.1
with:
command: clippy
args: --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
- name: Tests
uses: actions-rs/cargo@v1
uses: actions-rs/cargo@v1.0.1
with:
command: test
args: --profile ci --package nu_plugin_*

View File

@ -90,10 +90,16 @@ if ($ver | str trim | is-empty) {
cd $dist; $'(char nl)Creating release archive...'; hr-line
if $os in ['ubuntu-latest', 'macos-latest'] {
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
let files = (ls | get name)
let dest = $'($bin)-($version)-($target)'
let archive = $'($dist)/($dest).tar.gz'
let archive = $'($dist)/($bin)-($version)-($target).tar.gz'
tar czf $archive *
mkdir $dest
$files | each {|it| mv $it $dest } | ignore
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls $dest
tar -czf $archive $dest
print $'archive: ---> ($archive)'; ls $archive
echo $'::set-output name=archive::($archive)'

4
.gitignore vendored
View File

@ -22,6 +22,10 @@ debian/nu/
# VSCode's IDE items
.vscode/*
# Visual Studio Extension SourceGear Rust items
VSWorkspaceSettings.json
unstable_cargo_features.txt
# Helix configuration folder
.helix/*
.helix

View File

@ -1,8 +1,23 @@
# Contributing
Welcome to Nushell!
Welcome to Nushell and thank you for considering contributing!
To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)!
## Review Process
First of all, before diving into the code, if you want to create a new feature, change something significantly, and especially if the change is user-facing, it is a good practice to first get an approval from the core team before starting to work on it.
This saves both your and our time if we realize the change needs to go another direction before spending time on it.
So, please, reach out and tell us what you want to do.
This will significantly increase the chance of your PR being accepted.
The review process can be summarized as follows:
1. You want to make some change to Nushell that is more involved than simple bug-fixing.
2. Go to [Discord](https://discordapp.com/invite/NtAbbGn) or a [GitHub issue](https://github.com/nushell/nushell/issues/new/choose) and chat with some core team members and/or other contributors about it.
3. After getting a green light from the core team, implement the feature, open a pull request (PR) and write a concise but comprehensive description of the change.
4. If your PR includes any use-facing features (such as adding a flag to a command), clearly list them in the PR description.
5. Then, core team members and other regular contributors will review the PR and suggest changes.
6. When we all agree, the PR will be merged.
7. If your PR includes any user-facing features, make sure the changes are also reflected in [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged.
8. Congratulate yourself, you just improved Nushell! :-)
## Developing

325
Cargo.lock generated
View File

@ -12,15 +12,6 @@ dependencies = [
"regex",
]
[[package]]
name = "addr2line"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
@ -79,29 +70,21 @@ dependencies = [
[[package]]
name = "ansi-str"
version = "0.4.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "132f03d534a71638c1be79251027872d5dda24aa5eeeb6886960351f3087bdde"
checksum = "84252a7e1a0df81706ce70bbad85ed1e4916448a4093ccd52dd98c6a44a477cd"
dependencies = [
"ansitok",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "ansitok"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2c6eb31f539d8fc1df948eb26452d6c781be4c9883663e7acb258644b71d5b1"
checksum = "220044e6a1bb31ddee4e3db724d29767f352de47445a6cd75e1a173142136c83"
dependencies = [
"nom 7.1.1",
"vte",
]
[[package]]
@ -240,21 +223,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
dependencies = [
"addr2line",
"cc",
"cfg-if 1.0.0",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "bare-metal"
version = "0.2.5"
@ -403,12 +371,6 @@ version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
[[package]]
name = "byte-order"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021a13e4bf34a5679ada4609a01337ae82f2c4c97493b9d8cbf8aa9af9bd0f4"
[[package]]
name = "byte-unit"
version = "4.0.14"
@ -914,7 +876,7 @@ version = "3.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d91974fbbe88ec1df0c24a4f00f99583667a7e2e6272b2b92d294d81e462173"
dependencies = [
"nix 0.25.0",
"nix",
"winapi 0.3.9",
]
@ -1045,12 +1007,6 @@ dependencies = [
"rust_decimal",
]
[[package]]
name = "dunce"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541"
[[package]]
name = "dyn-clone"
version = "1.0.9"
@ -1426,16 +1382,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "gethostname"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e"
dependencies = [
"libc",
"winapi 0.3.9",
]
[[package]]
name = "getopts"
version = "0.2.21"
@ -1490,12 +1436,6 @@ dependencies = [
"syn",
]
[[package]]
name = "gimli"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
[[package]]
name = "git2"
version = "0.15.0"
@ -1929,9 +1869,9 @@ checksum = "47631885425c482fcf2dc4b182fc973c3c5b81a8f43a028055559bd24cccfa6e"
[[package]]
name = "json_to_table"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fe97d58ab96691e90f26c10ecc1fc098a4c072714d2d126cc8a2234b89f3456"
checksum = "408f09e42613f7a9902ecf2f30bcd053cf9f5668d0b1493ae1669070338d661b"
dependencies = [
"serde_json",
"tabled",
@ -2034,9 +1974,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.134"
version = "0.2.135"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb"
checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
[[package]]
name = "libgit2-sys"
@ -2156,7 +2096,6 @@ version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "074bff749d092e2e818fe954952102f88e21f67fc69f4d350621aab15a1810f1"
dependencies = [
"ansi_term",
"crossterm 0.24.0",
]
@ -2267,18 +2206,6 @@ dependencies = [
"nom 1.2.4",
]
[[package]]
name = "miette"
version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c90329e44f9208b55f45711f9558cec15d7ef8295cc65ecd6d4188ae8edc58c"
dependencies = [
"miette-derive 4.7.1",
"once_cell",
"thiserror",
"unicode-width",
]
[[package]]
name = "miette"
version = "5.3.0"
@ -2286,8 +2213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28d6092d7e94a90bb9ea8e6c26c99d5d112d49dda2afdb4f7ea8cf09e1a5a6d"
dependencies = [
"atty",
"backtrace",
"miette-derive 5.3.0",
"miette-derive",
"once_cell",
"owo-colors",
"supports-color",
@ -2299,17 +2225,6 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "miette-derive"
version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b5bc45b761bcf1b5e6e6c4128cd93b84c218721a8d9b894aa0aff4ed180174c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "miette-derive"
version = "5.3.0"
@ -2467,18 +2382,6 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "nix"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
"libc",
"memoffset",
]
[[package]]
name = "nix"
version = "0.25.0"
@ -2544,15 +2447,6 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "ntapi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "ntapi"
version = "0.4.0"
@ -2564,7 +2458,7 @@ dependencies = [
[[package]]
name = "nu"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"assert_cmd",
"atty",
@ -2575,8 +2469,8 @@ dependencies = [
"is_executable",
"itertools",
"log",
"miette 5.3.0",
"nix 0.24.2",
"miette",
"nix",
"nu-ansi-term",
"nu-cli",
"nu-color-config",
@ -2618,7 +2512,7 @@ dependencies = [
[[package]]
name = "nu-cli"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"atty",
"chrono",
@ -2628,7 +2522,7 @@ dependencies = [
"is_executable",
"lazy_static",
"log",
"miette 5.3.0",
"miette",
"nu-ansi-term",
"nu-color-config",
"nu-command",
@ -2641,14 +2535,13 @@ dependencies = [
"percent-encoding",
"reedline",
"rstest",
"strip-ansi-escapes",
"sysinfo",
"thiserror",
]
[[package]]
name = "nu-color-config"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"nu-ansi-term",
"nu-json",
@ -2659,7 +2552,7 @@ dependencies = [
[[package]]
name = "nu-command"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"Inflector",
"alphanumeric-sort",
@ -2689,6 +2582,7 @@ dependencies = [
"is-root",
"itertools",
"lazy_static",
"libc",
"log",
"lscolors",
"md-5",
@ -2735,7 +2629,6 @@ dependencies = [
"sha2",
"shadow-rs",
"sqlparser",
"strip-ansi-escapes",
"sysinfo",
"terminal_size 0.2.1",
"thiserror",
@ -2749,26 +2642,25 @@ dependencies = [
"uuid",
"wax",
"which",
"windows",
"windows 0.42.0",
"winreg",
]
[[package]]
name = "nu-engine"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"chrono",
"nu-glob",
"nu-path",
"nu-protocol",
"nu-utils",
"strip-ansi-escapes",
"sysinfo",
]
[[package]]
name = "nu-glob"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"doc-comment",
"tempdir",
@ -2776,7 +2668,7 @@ dependencies = [
[[package]]
name = "nu-json"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"fancy-regex",
"lazy_static",
@ -2789,13 +2681,13 @@ dependencies = [
[[package]]
name = "nu-parser"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"bytesize",
"chrono",
"itertools",
"log",
"miette 5.3.0",
"miette",
"nu-engine",
"nu-path",
"nu-plugin",
@ -2806,32 +2698,30 @@ dependencies = [
[[package]]
name = "nu-path"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"dirs-next",
"dunce",
"omnipath",
"pwd",
]
[[package]]
name = "nu-plugin"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"bincode",
"byte-order",
"criterion",
"nu-engine",
"nu-protocol",
"rmp",
"rmp-serde",
"rmpv",
"serde",
"serde_json",
]
[[package]]
name = "nu-pretty-hex"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"heapless",
"nu-ansi-term",
@ -2840,16 +2730,15 @@ dependencies = [
[[package]]
name = "nu-protocol"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"byte-unit",
"chrono",
"chrono-humanize",
"fancy-regex",
"indexmap",
"miette 5.3.0",
"miette",
"nu-json",
"nu-path",
"nu-utils",
"num-format",
"serde",
@ -2861,16 +2750,17 @@ dependencies = [
[[package]]
name = "nu-system"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"atty",
"chrono",
"errno",
"libc",
"libproc",
"log",
"mach2",
"nix 0.24.2",
"ntapi 0.3.7",
"nix",
"ntapi",
"once_cell",
"procfs",
"winapi 0.3.9",
@ -2878,28 +2768,28 @@ dependencies = [
[[package]]
name = "nu-table"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"atty",
"json_to_table",
"nu-ansi-term",
"nu-protocol",
"nu-utils",
"serde_json",
"strip-ansi-escapes",
"tabled",
]
[[package]]
name = "nu-term-grid"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"strip-ansi-escapes",
"nu-utils",
"unicode-width",
]
[[package]]
name = "nu-test-support"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"getset",
"hamcrest2",
@ -2907,18 +2797,18 @@ dependencies = [
"nu-glob",
"nu-path",
"nu-utils",
"num-bigint 0.4.3",
"num-format",
"tempfile",
]
[[package]]
name = "nu-utils"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"crossterm_winapi",
"lscolors",
"num-format",
"strip-ansi-escapes",
"sys-locale",
]
@ -2934,7 +2824,7 @@ dependencies = [
[[package]]
name = "nu_plugin_example"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"nu-plugin",
"nu-protocol",
@ -2942,7 +2832,7 @@ dependencies = [
[[package]]
name = "nu_plugin_gstat"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"git2",
"nu-engine",
@ -2952,7 +2842,7 @@ dependencies = [
[[package]]
name = "nu_plugin_inc"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"nu-plugin",
"nu-protocol",
@ -2961,7 +2851,7 @@ dependencies = [
[[package]]
name = "nu_plugin_query"
version = "0.70.0"
version = "0.71.0"
dependencies = [
"gjson",
"nu-engine",
@ -3020,7 +2910,6 @@ dependencies = [
"autocfg",
"num-integer",
"num-traits",
"serde",
]
[[package]]
@ -3136,13 +3025,10 @@ dependencies = [
]
[[package]]
name = "object"
version = "0.29.0"
name = "omnipath"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
dependencies = [
"memchr",
]
checksum = "e7461858c5ac9bde3fcdeedc17f58ed0469ec74f2d737b6369fc31c0b0ef576c"
[[package]]
name = "once_cell"
@ -3234,15 +3120,14 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "papergrid"
version = "0.6.0"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae9bed2481d5ab6e31056945f4704ca7348a3858148c30725b8946b7a7818498"
checksum = "1526bb6aa9f10ec339fb10360f22c57edf81d5678d0278e93bc12a47ffbe4b01"
dependencies = [
"ansi-str",
"ansitok",
"bytecount",
"fnv",
"strip-ansi-escapes",
"unicode-width",
]
@ -4070,14 +3955,13 @@ dependencies = [
[[package]]
name = "reedline"
version = "0.13.0"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "555a4285d183a94d140d749712a6600b10e70d7cd6aef91e3cfb65ba09b1d75e"
checksum = "75d91d2a38dbdaf0c748a96055c14aa4746be9add5f4aea4b1460e6e6492f770"
dependencies = [
"chrono",
"crossterm 0.24.0",
"fd-lock",
"gethostname",
"itertools",
"nu-ansi-term",
"rusqlite",
@ -4209,16 +4093,6 @@ dependencies = [
"serde",
]
[[package]]
name = "rmpv"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de8813b3a2f95c5138fe5925bfb8784175d88d6bff059ba8ce090aa891319754"
dependencies = [
"num-traits",
"rmp",
]
[[package]]
name = "roxmltree"
version = "0.14.1"
@ -4309,12 +4183,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "rustc-hash"
version = "1.1.0"
@ -4826,7 +4694,7 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8"
dependencies = [
"vte 0.10.1",
"vte",
]
[[package]]
@ -4951,7 +4819,7 @@ dependencies = [
"cfg-if 1.0.0",
"core-foundation-sys",
"libc",
"ntapi 0.4.0",
"ntapi",
"once_cell",
"rayon",
"winapi 0.3.9",
@ -4959,15 +4827,14 @@ dependencies = [
[[package]]
name = "tabled"
version = "0.9.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c8a1ea336f84dc7dfae1025b73904551b3c6a42347f4243387e990f94325895"
checksum = "56c3ee73732ffceaea7b8f6b719ce3bb17f253fa27461ffeaf568ebd0cdb4b85"
dependencies = [
"ansi-str",
"papergrid",
"tabled_derive",
"unicode-width",
"vte 0.11.0",
]
[[package]]
@ -5262,7 +5129,7 @@ dependencies = [
"once_cell",
"scopeguard",
"url",
"windows",
"windows 0.37.0",
]
[[package]]
@ -5453,12 +5320,6 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vec1"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fc1631c774f0f9570797191e01247cbefde789eebfbf128074cb934115a6133"
[[package]]
name = "version_check"
version = "0.9.4"
@ -5491,17 +5352,6 @@ dependencies = [
"vte_generate_state_changes",
]
[[package]]
name = "vte"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aae21c12ad2ec2d168c236f369c38ff332bc1134f7246350dca641437365045"
dependencies = [
"arrayvec 0.7.2",
"utf8parse",
"vte_generate_state_changes",
]
[[package]]
name = "vte_generate_state_changes"
version = "0.1.1"
@ -5635,14 +5485,12 @@ dependencies = [
"bstr",
"const_format",
"itertools",
"miette 4.7.1",
"nom 7.1.1",
"nom-supreme",
"pori",
"regex",
"smallvec",
"thiserror",
"vec1",
"walkdir",
]
@ -5723,6 +5571,21 @@ dependencies = [
"windows_x86_64_msvc 0.37.0",
]
[[package]]
name = "windows"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0286ba339aa753e70765d521bb0242cc48e1194562bfa2a2ad7ac8a6de28f5d5"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.42.0",
"windows_i686_gnu 0.42.0",
"windows_i686_msvc 0.42.0",
"windows_x86_64_gnu 0.42.0",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.42.0",
]
[[package]]
name = "windows-sys"
version = "0.36.1"
@ -5736,6 +5599,12 @@ dependencies = [
"windows_x86_64_msvc 0.36.1",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
[[package]]
name = "windows_aarch64_msvc"
version = "0.36.1"
@ -5748,6 +5617,12 @@ version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
[[package]]
name = "windows_i686_gnu"
version = "0.36.1"
@ -5760,6 +5635,12 @@ version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1"
[[package]]
name = "windows_i686_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
[[package]]
name = "windows_i686_msvc"
version = "0.36.1"
@ -5772,6 +5653,12 @@ version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c"
[[package]]
name = "windows_i686_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
[[package]]
name = "windows_x86_64_gnu"
version = "0.36.1"
@ -5784,6 +5671,18 @@ version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
[[package]]
name = "windows_x86_64_msvc"
version = "0.36.1"
@ -5796,6 +5695,12 @@ version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
[[package]]
name = "winreg"
version = "0.10.1"

View File

@ -10,7 +10,7 @@ license = "MIT"
name = "nu"
repository = "https://github.com/nushell/nushell"
rust-version = "1.60"
version = "0.70.0"
version = "0.71.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -36,23 +36,23 @@ chrono = { version = "0.4.21", features = ["serde"] }
crossterm = "0.24.0"
ctrlc = "3.2.1"
log = "0.4"
miette = "5.1.0"
miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
nu-ansi-term = "0.46.0"
nu-cli = { path="./crates/nu-cli", version = "0.70.0" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.70.0" }
nu-command = { path="./crates/nu-command", version = "0.70.0" }
nu-engine = { path="./crates/nu-engine", version = "0.70.0" }
nu-json = { path="./crates/nu-json", version = "0.70.0" }
nu-parser = { path="./crates/nu-parser", version = "0.70.0" }
nu-path = { path="./crates/nu-path", version = "0.70.0" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.70.0" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.70.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.70.0" }
nu-system = { path = "./crates/nu-system", version = "0.70.0" }
nu-table = { path = "./crates/nu-table", version = "0.70.0" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.70.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.70.0" }
reedline = { version = "0.13.0", features = ["bashisms", "sqlite"]}
nu-cli = { path="./crates/nu-cli", version = "0.71.0" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.71.0" }
nu-command = { path="./crates/nu-command", version = "0.71.0" }
nu-engine = { path="./crates/nu-engine", version = "0.71.0" }
nu-json = { path="./crates/nu-json", version = "0.71.0" }
nu-parser = { path="./crates/nu-parser", version = "0.71.0" }
nu-path = { path="./crates/nu-path", version = "0.71.0" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.71.0" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.71.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.71.0" }
nu-system = { path = "./crates/nu-system", version = "0.71.0" }
nu-table = { path = "./crates/nu-table", version = "0.71.0" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.71.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.71.0" }
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
rayon = "1.5.1"
is_executable = "1.0.1"
@ -69,11 +69,11 @@ signal-hook = { version = "0.3.14", default-features = false }
winres = "0.1"
[target.'cfg(target_family = "unix")'.dependencies]
nix = "0.24"
nix = { version = "0.25", default-features = false, features = ["signal", "process", "fs", "term"]}
atty = "0.2"
[dev-dependencies]
nu-test-support = { path="./crates/nu-test-support", version = "0.70.0" }
nu-test-support = { path="./crates/nu-test-support", version = "0.71.0" }
tempfile = "3.2.0"
assert_cmd = "2.0.2"
pretty_assertions = "1.0.0"

View File

@ -5,22 +5,22 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
edition = "2021"
license = "MIT"
name = "nu-cli"
version = "0.70.0"
version = "0.71.0"
[dev-dependencies]
nu-test-support = { path="../nu-test-support", version = "0.70.0" }
nu-command = { path = "../nu-command", version = "0.70.0" }
nu-test-support = { path="../nu-test-support", version = "0.71.0" }
nu-command = { path = "../nu-command", version = "0.71.0" }
rstest = {version = "0.15.0", default-features = false}
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.70.0" }
nu-path = { path = "../nu-path", version = "0.70.0" }
nu-parser = { path = "../nu-parser", version = "0.70.0" }
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
nu-utils = { path = "../nu-utils", version = "0.70.0" }
nu-engine = { path = "../nu-engine", version = "0.71.0" }
nu-path = { path = "../nu-path", version = "0.71.0" }
nu-parser = { path = "../nu-parser", version = "0.71.0" }
nu-protocol = { path = "../nu-protocol", version = "0.71.0" }
nu-utils = { path = "../nu-utils", version = "0.71.0" }
nu-ansi-term = "0.46.0"
nu-color-config = { path = "../nu-color-config", version = "0.70.0" }
reedline = { version = "0.13.0", features = ["bashisms", "sqlite"]}
nu-color-config = { path = "../nu-color-config", version = "0.71.0" }
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
atty = "0.2.14"
chrono = "0.4.21"
@ -30,9 +30,8 @@ fuzzy-matcher = "0.3.7"
is_executable = "1.0.1"
lazy_static = "1.4.0"
log = "0.4"
miette = { version = "5.1.0", features = ["fancy"] }
miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
percent-encoding = "2"
strip-ansi-escapes = "0.1.1"
sysinfo = "0.26.2"
thiserror = "1.0.31"

View File

@ -15,7 +15,6 @@ pub fn evaluate_commands(
engine_state: &mut EngineState,
stack: &mut Stack,
input: PipelineData,
is_perf_true: bool,
table_mode: Option<Value>,
) -> Result<Option<i64>> {
// Translate environment variables from Strings to Values
@ -68,9 +67,7 @@ pub fn evaluate_commands(
}
};
if is_perf_true {
info!("evaluate {}:{}:{}", file!(), line!(), column!());
}
info!("evaluate {}:{}:{}", file!(), line!(), column!());
Ok(exit_code)
}

View File

@ -60,7 +60,7 @@ impl CommandCompletion {
.matches_str(&x.to_string_lossy(), prefix)),
Some(true)
)
&& is_executable::is_executable(&item.path())
&& is_executable::is_executable(item.path())
{
if let Ok(name) = item.file_name().into_string() {
executables.push(name);

View File

@ -23,7 +23,6 @@ pub fn read_plugin_file(
stack: &mut Stack,
plugin_file: Option<Spanned<String>>,
storage_path: &str,
is_perf_true: bool,
) {
// Reading signatures from signature file
// The plugin.nu file stores the parsed signature collected from each registered plugin
@ -31,7 +30,7 @@ pub fn read_plugin_file(
let plugin_path = engine_state.plugin_signatures.clone();
if let Some(plugin_path) = plugin_path {
let plugin_filename = plugin_path.to_string_lossy().to_owned();
let plugin_filename = plugin_path.to_string_lossy();
if let Ok(contents) = std::fs::read(&plugin_path) {
eval_source(
@ -44,9 +43,7 @@ pub fn read_plugin_file(
}
}
if is_perf_true {
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
}
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
}
#[cfg(feature = "plugin")]
@ -80,7 +77,7 @@ pub fn eval_config_contents(
stack: &mut Stack,
) {
if config_path.exists() & config_path.is_file() {
let config_filename = config_path.to_string_lossy().to_owned();
let config_filename = config_path.to_string_lossy();
if let Ok(contents) = std::fs::read(&config_path) {
eval_source(

View File

@ -19,7 +19,6 @@ pub fn evaluate_file(
engine_state: &mut EngineState,
stack: &mut Stack,
input: PipelineData,
is_perf_true: bool,
) -> Result<()> {
// Translate environment variables from Strings to Values
if let Some(e) = convert_env_values(engine_state, stack) {
@ -54,9 +53,7 @@ pub fn evaluate_file(
std::process::exit(1);
}
if is_perf_true {
info!("evaluate {}:{}:{}", file!(), line!(), column!());
}
info!("evaluate {}:{}:{}", file!(), line!(), column!());
Ok(())
}

View File

@ -372,7 +372,7 @@ impl DescriptionMenu {
let description = self
.get_value()
.and_then(|suggestion| suggestion.description)
.unwrap_or_else(|| "".to_string())
.unwrap_or_default()
.lines()
.skip(self.skipped_rows)
.take(self.working_details.description_rows)
@ -610,7 +610,7 @@ impl Menu for DescriptionMenu {
let description_rows = self
.get_value()
.and_then(|suggestion| suggestion.description)
.unwrap_or_else(|| "".to_string())
.unwrap_or_default()
.lines()
.count();

View File

@ -17,6 +17,7 @@ pub struct NushellPrompt {
default_vi_insert_prompt_indicator: Option<String>,
default_vi_normal_prompt_indicator: Option<String>,
default_multiline_indicator: Option<String>,
render_right_prompt_on_last_line: bool,
}
impl Default for NushellPrompt {
@ -34,6 +35,7 @@ impl NushellPrompt {
default_vi_insert_prompt_indicator: None,
default_vi_normal_prompt_indicator: None,
default_multiline_indicator: None,
render_right_prompt_on_last_line: false,
}
}
@ -41,8 +43,13 @@ impl NushellPrompt {
self.left_prompt_string = prompt_string;
}
pub fn update_prompt_right(&mut self, prompt_string: Option<String>) {
pub fn update_prompt_right(
&mut self,
prompt_string: Option<String>,
render_right_prompt_on_last_line: bool,
) {
self.right_prompt_string = prompt_string;
self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
}
pub fn update_prompt_indicator(&mut self, prompt_indicator_string: Option<String>) {
@ -68,6 +75,7 @@ impl NushellPrompt {
prompt_indicator_string: Option<String>,
prompt_multiline_indicator_string: Option<String>,
prompt_vi: (Option<String>, Option<String>),
render_right_prompt_on_last_line: bool,
) {
let (prompt_vi_insert_string, prompt_vi_normal_string) = prompt_vi;
@ -78,6 +86,8 @@ impl NushellPrompt {
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
}
fn default_wrapped_custom_string(&self, str: String) -> String {
@ -162,4 +172,8 @@ impl Prompt for NushellPrompt {
prefix, history_search.term
))
}
fn right_prompt_on_last_line(&self) -> bool {
self.render_right_prompt_on_last_line
}
}

View File

@ -25,7 +25,6 @@ fn get_prompt_string(
config: &Config,
engine_state: &EngineState,
stack: &mut Stack,
is_perf_true: bool,
) -> Option<String> {
stack
.get_env_var(engine_state, prompt)
@ -44,14 +43,12 @@ fn get_prompt_string(
block,
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
);
if is_perf_true {
info!(
"get_prompt_string (block) {}:{}:{}",
file!(),
line!(),
column!()
);
}
info!(
"get_prompt_string (block) {}:{}:{}",
file!(),
line!(),
column!()
);
match ret_val {
Ok(ret_val) => Some(ret_val),
@ -90,17 +87,10 @@ pub(crate) fn update_prompt<'prompt>(
engine_state: &EngineState,
stack: &Stack,
nu_prompt: &'prompt mut NushellPrompt,
is_perf_true: bool,
) -> &'prompt dyn Prompt {
let mut stack = stack.clone();
let left_prompt_string = get_prompt_string(
PROMPT_COMMAND,
config,
engine_state,
&mut stack,
is_perf_true,
);
let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack);
// Now that we have the prompt string lets ansify it.
// <133 A><prompt><133 B><command><133 C><command output>
@ -116,45 +106,20 @@ pub(crate) fn update_prompt<'prompt>(
left_prompt_string
};
let right_prompt_string = get_prompt_string(
PROMPT_COMMAND_RIGHT,
config,
engine_state,
&mut stack,
is_perf_true,
);
let right_prompt_string =
get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack);
let prompt_indicator_string = get_prompt_string(
PROMPT_INDICATOR,
config,
engine_state,
&mut stack,
is_perf_true,
);
let prompt_indicator_string =
get_prompt_string(PROMPT_INDICATOR, config, engine_state, &mut stack);
let prompt_multiline_string = get_prompt_string(
PROMPT_MULTILINE_INDICATOR,
config,
engine_state,
&mut stack,
is_perf_true,
);
let prompt_multiline_string =
get_prompt_string(PROMPT_MULTILINE_INDICATOR, config, engine_state, &mut stack);
let prompt_vi_insert_string = get_prompt_string(
PROMPT_INDICATOR_VI_INSERT,
config,
engine_state,
&mut stack,
is_perf_true,
);
let prompt_vi_insert_string =
get_prompt_string(PROMPT_INDICATOR_VI_INSERT, config, engine_state, &mut stack);
let prompt_vi_normal_string = get_prompt_string(
PROMPT_INDICATOR_VI_NORMAL,
config,
engine_state,
&mut stack,
is_perf_true,
);
let prompt_vi_normal_string =
get_prompt_string(PROMPT_INDICATOR_VI_NORMAL, config, engine_state, &mut stack);
// apply the other indicators
nu_prompt.update_all_prompt_strings(
@ -163,12 +128,11 @@ pub(crate) fn update_prompt<'prompt>(
prompt_indicator_string,
prompt_multiline_string,
(prompt_vi_insert_string, prompt_vi_normal_string),
config.render_right_prompt_on_last_line,
);
let ret_val = nu_prompt as &dyn Prompt;
if is_perf_true {
info!("update_prompt {}:{}:{}", file!(), line!(), column!());
}
info!("update_prompt {}:{}:{}", file!(), line!(), column!());
ret_val
}

View File

@ -114,7 +114,7 @@ pub(crate) fn add_menus(
let res = eval_block(&engine_state, &mut temp_stack, &block, input, false, false)?;
if let PipelineData::Value(value, None) = res {
for menu in create_menus(&value, config)? {
for menu in create_menus(&value)? {
line_editor =
add_menu(line_editor, &menu, engine_state.clone(), stack, config)?;
}
@ -491,7 +491,7 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) {
KeyCode::Tab,
ReedlineEvent::UntilFound(vec![
ReedlineEvent::Menu("completion_menu".to_string()),
ReedlineEvent::MenuNext,
ReedlineEvent::Edit(vec![EditCommand::Complete]),
]),
);
@ -964,6 +964,7 @@ fn edit_from_record(
let char = extract_char(value, config)?;
EditCommand::MoveLeftBefore(char)
}
"complete" => EditCommand::Complete,
e => {
return Err(ShellError::UnsupportedConfigValue(
"reedline EditCommand".to_string(),

View File

@ -24,7 +24,6 @@ use std::{
sync::atomic::Ordering,
time::Instant,
};
use strip_ansi_escapes::strip;
use sysinfo::SystemExt;
// According to Daniel Imms @Tyriar, we need to do these this way:
@ -41,7 +40,6 @@ pub fn evaluate_repl(
engine_state: &mut EngineState,
stack: &mut Stack,
nushell_path: &str,
is_perf_true: bool,
prerun_command: Option<Spanned<String>>,
) -> Result<()> {
use reedline::{FileBackedHistory, Reedline, Signal};
@ -51,7 +49,7 @@ pub fn evaluate_repl(
if !atty::is(atty::Stream::Stdin) {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Nushell launched as interactive REPL but STDIN is not a TTY, either launch in a valid terminal or provide arguments to invoke a script!",
"Nushell launched as a REPL, but STDIN is not a TTY; either launch in a valid terminal or provide arguments to invoke a script!",
))
.into_diagnostic();
}
@ -60,14 +58,12 @@ pub fn evaluate_repl(
let mut nu_prompt = NushellPrompt::new();
if is_perf_true {
info!(
"translate environment vars {}:{}:{}",
file!(),
line!(),
column!()
);
}
info!(
"translate environment vars {}:{}:{}",
file!(),
line!(),
column!()
);
// Translate environment variables from Strings to Values
if let Some(e) = convert_env_values(engine_state, stack) {
@ -92,18 +88,14 @@ pub fn evaluate_repl(
},
);
if is_perf_true {
info!(
"load config initially {}:{}:{}",
file!(),
line!(),
column!()
);
}
info!(
"load config initially {}:{}:{}",
file!(),
line!(),
column!()
);
if is_perf_true {
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
}
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
let mut line_editor = Reedline::create();
@ -121,9 +113,7 @@ pub fn evaluate_repl(
engine_state.config.history_file_format,
);
if let Some(history_path) = history_path.as_deref() {
if is_perf_true {
info!("setup history {}:{}:{}", file!(), line!(), column!());
}
info!("setup history {}:{}:{}", file!(), line!(), column!());
let history: Box<dyn reedline::History> = match engine_state.config.history_file_format {
HistoryFileFormat::PlainText => Box::new(
@ -149,15 +139,7 @@ pub fn evaluate_repl(
if use_ansi {
println!("{}", banner);
} else {
let stripped_string = {
if let Ok(bytes) = strip(&banner) {
String::from_utf8_lossy(&bytes).to_string()
} else {
banner
}
};
println!("{}", stripped_string);
println!("{}", nu_utils::strip_ansi_string_likely(banner));
}
}
@ -173,14 +155,12 @@ pub fn evaluate_repl(
}
loop {
if is_perf_true {
info!(
"load config each loop {}:{}:{}",
file!(),
line!(),
column!()
);
}
info!(
"load config each loop {}:{}:{}",
file!(),
line!(),
column!()
);
let cwd = get_guaranteed_cwd(engine_state, stack);
@ -201,15 +181,11 @@ pub fn evaluate_repl(
let config = engine_state.get_config();
if is_perf_true {
info!("setup colors {}:{}:{}", file!(), line!(), column!());
}
info!("setup colors {}:{}:{}", file!(), line!(), column!());
let color_hm = get_color_config(config);
if is_perf_true {
info!("update reedline {}:{}:{}", file!(), line!(), column!());
}
info!("update reedline {}:{}:{}", file!(), line!(), column!());
let engine_reference = std::sync::Arc::new(engine_state.clone());
line_editor = line_editor
.with_highlighter(Box::new(NuHighlighter {
@ -266,18 +242,14 @@ pub fn evaluate_repl(
};
if config.sync_history_on_enter {
if is_perf_true {
info!("sync history {}:{}:{}", file!(), line!(), column!());
}
info!("sync history {}:{}:{}", file!(), line!(), column!());
if let Err(e) = line_editor.sync_history() {
warn!("Failed to sync history: {}", e);
}
}
if is_perf_true {
info!("setup keybindings {}:{}:{}", file!(), line!(), column!());
}
info!("setup keybindings {}:{}:{}", file!(), line!(), column!());
// Changing the line editor based on the found keybindings
line_editor = match create_keybindings(config) {
@ -301,14 +273,12 @@ pub fn evaluate_repl(
}
};
if is_perf_true {
info!("prompt_update {}:{}:{}", file!(), line!(), column!());
}
info!("prompt_update {}:{}:{}", file!(), line!(), column!());
// Right before we start our prompt and take input from the user,
// fire the "pre_prompt" hook
if let Some(hook) = config.hooks.pre_prompt.clone() {
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook) {
report_error_new(engine_state, &err);
}
}
@ -323,19 +293,16 @@ pub fn evaluate_repl(
}
let config = engine_state.get_config();
let prompt =
prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt, is_perf_true);
let prompt = prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt);
entry_num += 1;
if is_perf_true {
info!(
"finished setup, starting repl {}:{}:{}",
file!(),
line!(),
column!()
);
}
info!(
"finished setup, starting repl {}:{}:{}",
file!(),
line!(),
column!()
);
let input = line_editor.read_line(prompt);
let shell_integration = config.shell_integration;
@ -367,7 +334,7 @@ pub fn evaluate_repl(
// 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() {
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook) {
report_error_new(engine_state, &err);
}
}
@ -719,6 +686,7 @@ pub fn eval_env_change_hook(
eval_hook(
engine_state,
stack,
None,
vec![("$before".into(), before), ("$after".into(), after.clone())],
hook_value,
)?;
@ -744,15 +712,17 @@ pub fn eval_env_change_hook(
pub fn eval_hook(
engine_state: &mut EngineState,
stack: &mut Stack,
input: Option<PipelineData>,
arguments: Vec<(String, Value)>,
value: &Value,
) -> Result<(), ShellError> {
) -> Result<PipelineData, ShellError> {
let value_span = value.span()?;
let condition_path = PathMember::String {
val: "condition".to_string(),
span: value_span,
};
let mut output = PipelineData::new(Span::new(0, 0));
let code_path = PathMember::String {
val: "code".to_string(),
@ -762,7 +732,7 @@ pub fn eval_hook(
match value {
Value::List { vals, .. } => {
for val in vals {
eval_hook(engine_state, stack, arguments.clone(), val)?
eval_hook(engine_state, stack, None, arguments.clone(), val)?;
}
}
Value::Record { .. } => {
@ -778,6 +748,7 @@ pub fn eval_hook(
engine_state,
stack,
block_id,
None,
arguments.clone(),
block_span,
) {
@ -857,7 +828,9 @@ pub fn eval_hook(
.collect();
match eval_block(engine_state, stack, &block, input, false, false) {
Ok(_) => {}
Ok(pipeline_data) => {
output = pipeline_data;
}
Err(err) => {
report_error_new(engine_state, &err);
}
@ -872,7 +845,14 @@ pub fn eval_hook(
span: block_span,
..
} => {
run_hook_block(engine_state, stack, block_id, arguments, block_span)?;
run_hook_block(
engine_state,
stack,
block_id,
input,
arguments,
block_span,
)?;
}
other => {
return Err(ShellError::UnsupportedConfigValue(
@ -889,7 +869,17 @@ pub fn eval_hook(
span: block_span,
..
} => {
run_hook_block(engine_state, stack, *block_id, arguments, *block_span)?;
output = PipelineData::Value(
run_hook_block(
engine_state,
stack,
*block_id,
input,
arguments,
*block_span,
)?,
None,
);
}
other => {
return Err(ShellError::UnsupportedConfigValue(
@ -903,19 +893,20 @@ pub fn eval_hook(
let cwd = get_guaranteed_cwd(engine_state, stack);
engine_state.merge_env(stack, cwd)?;
Ok(())
Ok(output)
}
pub fn run_hook_block(
engine_state: &EngineState,
stack: &mut Stack,
block_id: BlockId,
optional_input: Option<PipelineData>,
arguments: Vec<(String, Value)>,
span: Span,
) -> Result<Value, ShellError> {
let block = engine_state.get_block(block_id);
let input = PipelineData::new(span);
let input = optional_input.unwrap_or_else(|| PipelineData::new(span));
let mut callee_stack = stack.gather_captures(&block.captures);

View File

@ -1,9 +1,10 @@
use log::trace;
use nu_ansi_term::Style;
use nu_color_config::get_shape_color;
use nu_color_config::{get_matching_brackets_style, get_shape_color};
use nu_parser::{flatten_block, parse, FlatShape};
use nu_protocol::ast::{Argument, Block, Expr, Expression};
use nu_protocol::engine::{EngineState, StateWorkingSet};
use nu_protocol::Config;
use nu_protocol::{Config, Span};
use reedline::{Highlighter, StyledText};
pub struct NuHighlighter {
@ -15,10 +16,12 @@ impl Highlighter for NuHighlighter {
fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
trace!("highlighting: {}", line);
let (shapes, global_span_offset) = {
let mut working_set = StateWorkingSet::new(&self.engine_state);
let mut working_set = StateWorkingSet::new(&self.engine_state);
let block = {
let (block, _) = parse(&mut working_set, None, line.as_bytes(), false, &[]);
block
};
let (shapes, global_span_offset) = {
let shapes = flatten_block(&working_set, &block);
(shapes, self.engine_state.next_span_start())
};
@ -26,6 +29,15 @@ impl Highlighter for NuHighlighter {
let mut output = StyledText::default();
let mut last_seen_span = global_span_offset;
let global_cursor_offset = _cursor + global_span_offset;
let matching_brackets_pos = find_matching_brackets(
line,
&working_set,
&block,
global_span_offset,
global_cursor_offset,
);
for shape in &shapes {
if shape.0.end <= last_seen_span
|| last_seen_span < global_span_offset
@ -44,166 +56,71 @@ impl Highlighter for NuHighlighter {
let next_token = line
[(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));
});
}};
}
macro_rules! add_colored_token {
($shape:expr, $text:expr) => {
output.push((get_shape_color($shape.to_string(), &self.config), $text))
};
}
match shape.1 {
FlatShape::Garbage => output.push((
// nushell Garbage
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::Nothing => output.push((
// nushell Nothing
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::Binary => {
// nushell ?
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Bool => {
// nushell ?
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Int => {
// nushell Int
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Float => {
// nushell Decimal
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Range => output.push((
// nushell DotDot ?
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::InternalCall => output.push((
// nushell InternalCommand
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::External => {
// nushell ExternalCommand
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::ExternalArg => {
// nushell ExternalWord
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Literal => {
// nushell ?
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Operator => output.push((
// nushell Operator
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::Signature => output.push((
// nushell ?
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::String => {
// nushell String
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::StringInterpolation => {
// nushell ???
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::DateTime => {
// nushell ???
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Garbage => add_colored_token!(shape.1, next_token),
FlatShape::Nothing => add_colored_token!(shape.1, next_token),
FlatShape::Binary => add_colored_token!(shape.1, next_token),
FlatShape::Bool => add_colored_token!(shape.1, next_token),
FlatShape::Int => add_colored_token!(shape.1, next_token),
FlatShape::Float => add_colored_token!(shape.1, next_token),
FlatShape::Range => add_colored_token!(shape.1, next_token),
FlatShape::InternalCall => add_colored_token!(shape.1, next_token),
FlatShape::External => add_colored_token!(shape.1, next_token),
FlatShape::ExternalArg => add_colored_token!(shape.1, next_token),
FlatShape::Literal => add_colored_token!(shape.1, next_token),
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::StringInterpolation => add_colored_token!(shape.1, next_token),
FlatShape::DateTime => add_colored_token!(shape.1, next_token),
FlatShape::List => {
// nushell ???
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
}
FlatShape::Table => {
// nushell ???
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
}
FlatShape::Record => {
// nushell ???
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
}
FlatShape::Block => {
// nushell ???
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
}
FlatShape::Filepath => output.push((
// nushell Path
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::Directory => output.push((
// nushell Directory
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::GlobPattern => output.push((
// nushell GlobPattern
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::Variable => output.push((
// nushell Variable
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::Flag => {
// nushell Flag
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Custom(..) => output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::Filepath => add_colored_token!(shape.1, next_token),
FlatShape::Directory => add_colored_token!(shape.1, next_token),
FlatShape::GlobPattern => add_colored_token!(shape.1, next_token),
FlatShape::Variable => add_colored_token!(shape.1, next_token),
FlatShape::Flag => add_colored_token!(shape.1, next_token),
FlatShape::Custom(..) => add_colored_token!(shape.1, next_token),
}
last_seen_span = shape.0.end;
}
@ -216,3 +133,297 @@ impl Highlighter for NuHighlighter {
output
}
}
fn split_span_by_highlight_positions(
line: &str,
span: &Span,
highlight_positions: &Vec<usize>,
global_span_offset: usize,
) -> Vec<(Span, bool)> {
let mut start = span.start;
let mut result: Vec<(Span, bool)> = Vec::new();
for pos in highlight_positions {
if start <= *pos && pos < &span.end {
if start < *pos {
result.push((Span { start, end: *pos }, false));
}
let span_str = &line[pos - global_span_offset..span.end - global_span_offset];
let end = span_str
.chars()
.next()
.map(|c| pos + get_char_length(c))
.unwrap_or(pos + 1);
result.push((Span { start: *pos, end }, true));
start = end;
}
}
if start < span.end {
result.push((
Span {
start,
end: span.end,
},
false,
));
}
result
}
fn find_matching_brackets(
line: &str,
working_set: &StateWorkingSet,
block: &Block,
global_span_offset: usize,
global_cursor_offset: usize,
) -> Vec<usize> {
const BRACKETS: &str = "{}[]()";
// calculate first bracket position
let global_end_offset = line.len() + global_span_offset;
let global_bracket_pos =
if global_cursor_offset == global_end_offset && global_end_offset > global_span_offset {
// cursor is at the end of a non-empty string -- find block end at the previous position
if let Some(last_char) = line.chars().last() {
global_cursor_offset - get_char_length(last_char)
} else {
global_cursor_offset
}
} else {
// cursor is in the middle of a string -- find block end at the current position
global_cursor_offset
};
// check that position contains bracket
let match_idx = global_bracket_pos - global_span_offset;
if match_idx >= line.len()
|| !BRACKETS.contains(get_char_at_index(line, match_idx).unwrap_or_default())
{
return Vec::new();
}
// find matching bracket by finding matching block end
let matching_block_end = find_matching_block_end_in_block(
line,
working_set,
block,
global_span_offset,
global_bracket_pos,
);
if let Some(pos) = matching_block_end {
let matching_idx = pos - global_span_offset;
if BRACKETS.contains(get_char_at_index(line, matching_idx).unwrap_or_default()) {
return if global_bracket_pos < pos {
vec![global_bracket_pos, pos]
} else {
vec![pos, global_bracket_pos]
};
}
}
Vec::new()
}
fn find_matching_block_end_in_block(
line: &str,
working_set: &StateWorkingSet,
block: &Block,
global_span_offset: usize,
global_cursor_offset: usize,
) -> Option<usize> {
for p in &block.pipelines {
for e in &p.expressions {
if e.span.contains(global_cursor_offset) {
if let Some(pos) = find_matching_block_end_in_expr(
line,
working_set,
e,
global_span_offset,
global_cursor_offset,
) {
return Some(pos);
}
}
}
}
None
}
fn find_matching_block_end_in_expr(
line: &str,
working_set: &StateWorkingSet,
expression: &Expression,
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;
let span_str = &line
[expression.span.start - global_span_offset..expression.span.end - global_span_offset];
let expr_last = span_str
.chars()
.last()
.map(|c| expression.span.end - get_char_length(c))
.unwrap_or(expression.span.start);
return match &expression.expr {
Expr::Bool(_) => None,
Expr::Int(_) => None,
Expr::Float(_) => None,
Expr::Binary(_) => None,
Expr::Range(..) => None,
Expr::Var(_) => None,
Expr::VarDecl(_) => None,
Expr::ExternalCall(..) => None,
Expr::Operator(_) => None,
Expr::UnaryNot(_) => None,
Expr::Keyword(..) => None,
Expr::ValueWithUnit(..) => None,
Expr::DateTime(_) => None,
Expr::Filepath(_) => None,
Expr::Directory(_) => None,
Expr::GlobPattern(_) => None,
Expr::String(_) => None,
Expr::CellPath(_) => None,
Expr::ImportPattern(_) => None,
Expr::Overlay(_) => None,
Expr::Signature(_) => None,
Expr::Nothing => None,
Expr::Garbage => None,
Expr::Table(hdr, rows) => {
if expr_last == global_cursor_offset {
// cursor is at table end
Some(expr_first)
} else if expr_first == global_cursor_offset {
// cursor is at table start
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
}
}
Expr::Record(exprs) => {
if expr_last == global_cursor_offset {
// cursor is at record end
Some(expr_first)
} else if expr_first == global_cursor_offset {
// cursor is at record start
Some(expr_last)
} else {
// cursor is inside record
for (k, v) in exprs {
find_in_expr_or_continue!(k);
find_in_expr_or_continue!(v);
}
None
}
}
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),
};
if let Some(inner_expr) = opt_expr {
find_in_expr_or_continue!(inner_expr);
}
}
None
}
Expr::FullCellPath(b) => find_matching_block_end_in_expr(
line,
working_set,
&b.head,
global_span_offset,
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::Block(block_id)
| Expr::RowCondition(block_id)
| Expr::Subexpression(block_id) => {
if expr_last == global_cursor_offset {
// cursor is at block end
Some(expr_first)
} else if expr_first == global_cursor_offset {
// cursor is at block start
Some(expr_last)
} else {
// cursor is inside block
let nested_block = working_set.get_block(*block_id);
find_matching_block_end_in_block(
line,
working_set,
nested_block,
global_span_offset,
global_cursor_offset,
)
}
}
Expr::StringInterpolation(inner_expr) => {
for inner_expr in inner_expr {
find_in_expr_or_continue!(inner_expr);
}
None
}
Expr::List(inner_expr) => {
if expr_last == global_cursor_offset {
// cursor is at list end
Some(expr_first)
} else if expr_first == global_cursor_offset {
// 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
}
}
};
}
None
}
fn get_char_at_index(s: &str, index: usize) -> Option<char> {
s[index..].chars().next()
}
fn get_char_length(c: char) -> usize {
c.to_string().len()
}

View File

@ -1,11 +1,11 @@
use log::trace;
use crate::repl::eval_hook;
use nu_engine::eval_block;
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
use nu_protocol::engine::StateWorkingSet;
use nu_protocol::CliError;
use nu_protocol::{
engine::{EngineState, Stack},
PipelineData, ShellError, Span, Value,
print_if_stream, PipelineData, ShellError, Span, Value,
};
#[cfg(windows)]
use nu_utils::enable_vt_processing;
@ -204,8 +204,6 @@ pub fn eval_source(
fname: &str,
input: PipelineData,
) -> bool {
trace!("eval_source");
let (block, delta) = {
let mut working_set = StateWorkingSet::new(engine_state);
let (output, err) = parse(
@ -232,7 +230,30 @@ pub fn eval_source(
match eval_block(engine_state, stack, &block, input, false, false) {
Ok(pipeline_data) => {
match pipeline_data.print(engine_state, stack, false, false) {
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) {
Err(err) => {
result = Err(err);
}
Ok(val) => {
result = val.print(engine_state, stack, false, false);
}
}
} else {
result = pipeline_data.print(engine_state, stack, false, false);
}
match result {
Err(err) => {
let working_set = StateWorkingSet::new(engine_state);

View File

@ -5,11 +5,11 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
edition = "2021"
license = "MIT"
name = "nu-color-config"
version = "0.70.0"
version = "0.71.0"
[dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
nu-protocol = { path = "../nu-protocol", version = "0.71.0" }
nu-ansi-term = "0.46.0"
nu-json = { path = "../nu-json", version = "0.70.0" }
nu-table = { path = "../nu-table", version = "0.70.0" }
nu-json = { path = "../nu-json", version = "0.71.0" }
nu-table = { path = "../nu-table", version = "0.71.0" }
serde = { version="1.0.123", features=["derive"] }

View File

@ -1,7 +1,9 @@
mod color_config;
mod matching_brackets_style;
mod nu_style;
mod shape_color;
pub use color_config::*;
pub use matching_brackets_style::*;
pub use nu_style::*;
pub use shape_color::*;

View File

@ -0,0 +1,30 @@
use crate::color_config::lookup_ansi_color_style;
use nu_ansi_term::Style;
use nu_protocol::Config;
pub fn get_matching_brackets_style(default_style: Style, conf: &Config) -> Style {
const MATCHING_BRACKETS_CONFIG_KEY: &str = "shape_matching_brackets";
match conf.color_config.get(MATCHING_BRACKETS_CONFIG_KEY) {
Some(int_color) => match int_color.as_string() {
Ok(int_color) => merge_styles(default_style, lookup_ansi_color_style(&int_color)),
Err(_) => default_style,
},
None => default_style,
}
}
fn merge_styles(base: Style, extra: Style) -> Style {
Style {
foreground: extra.foreground.or(base.foreground),
background: extra.background.or(base.background),
is_bold: extra.is_bold || base.is_bold,
is_dimmed: extra.is_dimmed || base.is_dimmed,
is_italic: extra.is_italic || base.is_italic,
is_underline: extra.is_underline || base.is_underline,
is_blink: extra.is_blink || base.is_blink,
is_reverse: extra.is_reverse || base.is_reverse,
is_hidden: extra.is_hidden || base.is_hidden,
is_strikethrough: extra.is_strikethrough || base.is_strikethrough,
}
}

View File

@ -5,25 +5,24 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
edition = "2021"
license = "MIT"
name = "nu-command"
version = "0.70.0"
version = "0.71.0"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-color-config = { path = "../nu-color-config", version = "0.70.0" }
nu-engine = { path = "../nu-engine", version = "0.70.0" }
nu-glob = { path = "../nu-glob", version = "0.70.0" }
nu-json = { path = "../nu-json", version = "0.70.0" }
nu-parser = { path = "../nu-parser", version = "0.70.0" }
nu-path = { path = "../nu-path", version = "0.70.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.70.0" }
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
nu-system = { path = "../nu-system", version = "0.70.0" }
nu-table = { path = "../nu-table", version = "0.70.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.70.0" }
nu-test-support = { path = "../nu-test-support", version = "0.70.0" }
nu-utils = { path = "../nu-utils", version = "0.70.0" }
nu-color-config = { path = "../nu-color-config", version = "0.71.0" }
nu-engine = { path = "../nu-engine", version = "0.71.0" }
nu-glob = { path = "../nu-glob", version = "0.71.0" }
nu-json = { path = "../nu-json", version = "0.71.0" }
nu-parser = { path = "../nu-parser", version = "0.71.0" }
nu-path = { path = "../nu-path", version = "0.71.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.71.0" }
nu-protocol = { path = "../nu-protocol", version = "0.71.0" }
nu-system = { path = "../nu-system", version = "0.71.0" }
nu-table = { path = "../nu-table", version = "0.71.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.71.0" }
nu-utils = { path = "../nu-utils", version = "0.71.0" }
nu-ansi-term = "0.46.0"
num-format = { version = "0.4.3" }
@ -55,7 +54,7 @@ is-root = "0.1.2"
itertools = "0.10.0"
lazy_static = "1.4.0"
log = "0.4.14"
lscolors = { version = "0.12.0", features = ["crossterm"]}
lscolors = { version = "0.12.0", features = ["crossterm"], default-features = false }
md5 = { package = "md-5", version = "0.10.0" }
meval = "0.2.0"
mime = "0.3.16"
@ -78,7 +77,6 @@ serde_yaml = "0.9.4"
sha2 = "0.10.0"
# Disable default features b/c the default features build Git (very slow to compile)
shadow-rs = { version = "0.16.1", default-features = false }
strip-ansi-escapes = "0.1.1"
sysinfo = "0.26.2"
terminal_size = "0.2.1"
thiserror = "1.0.31"
@ -88,8 +86,8 @@ unicode-segmentation = "1.8.0"
url = "2.2.1"
uuid = { version = "1.1.2", features = ["v4"] }
which = { version = "4.3.0", optional = true }
reedline = { version = "0.13.0", features = ["bashisms", "sqlite"]}
wax = { version = "0.5.0", features = ["diagnostics"] }
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
wax = { version = "0.5.0" }
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
sqlparser = { version = "0.23.0", features = ["serde"], optional = true }
@ -99,6 +97,7 @@ winreg = "0.10.1"
[target.'cfg(unix)'.dependencies]
umask = "2.0.0"
users = "0.11.0"
libc = "0.2"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
version = "2.1.3"
@ -136,9 +135,8 @@ features = [
]
[target.'cfg(windows)'.dependencies.windows]
version = "0.37.0"
version = "0.42.0"
features = [
"alloc",
"Win32_Foundation",
"Win32_Storage_FileSystem",
"Win32_System_SystemServices",
@ -155,6 +153,8 @@ database = ["sqlparser", "rusqlite"]
shadow-rs = { version = "0.16.1", default-features = false }
[dev-dependencies]
nu-test-support = { path = "../nu-test-support", version = "0.71.0" }
hamcrest2 = "0.3.0"
dirs-next = "2.0.0"
proptest = "1.0.0"

View File

@ -1,4 +1,4 @@
use super::{operate, BytesArgument};
use crate::input_handler::{operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
@ -10,12 +10,12 @@ struct Arguments {
added_data: Vec<u8>,
index: Option<usize>,
end: bool,
column_paths: Option<Vec<CellPath>>,
cell_paths: Option<Vec<CellPath>>,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
@ -62,12 +62,8 @@ impl Command for BytesAdd {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let added_data: Vec<u8> = call.req(engine_state, stack, 0)?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() {
None
} else {
Some(column_paths)
};
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let index: Option<usize> = call.get_flag(engine_state, stack, "index")?;
let end = call.has_flag("end");
@ -75,7 +71,7 @@ impl Command for BytesAdd {
added_data,
index,
end,
column_paths,
cell_paths,
};
operate(add, arg, input, call.head, engine_state.ctrlc.clone())
}
@ -118,7 +114,25 @@ impl Command for BytesAdd {
}
}
fn add(input: &[u8], args: &Arguments, span: Span) -> Value {
fn add(val: &Value, args: &Arguments, span: Span) -> Value {
match val {
Value::Binary {
val,
span: val_span,
} => add_impl(val, args, *val_span),
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Input's type is {}. This command only works with bytes.",
other.get_type()
),
span,
),
},
}
}
fn add_impl(input: &[u8], args: &Arguments, span: Span) -> Value {
match args.index {
None => {
if args.end {

View File

@ -1,4 +1,4 @@
use super::{operate, BytesArgument};
use crate::input_handler::{operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
@ -15,12 +15,12 @@ struct Arguments {
start: isize,
end: isize,
arg_span: Span,
column_paths: Option<Vec<CellPath>>,
cell_paths: Option<Vec<CellPath>>,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
@ -141,17 +141,13 @@ impl Command for BytesAt {
) -> Result<PipelineData, ShellError> {
let range: Value = call.req(engine_state, stack, 0)?;
let (start, end, arg_span) = parse_range(range, call.head)?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() {
None
} else {
Some(column_paths)
};
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let arg = Arguments {
start,
end,
arg_span,
column_paths,
cell_paths,
};
operate(at, arg, input, call.head, engine_state.ctrlc.clone())
}
@ -228,7 +224,25 @@ impl Command for BytesAt {
}
}
fn at(input: &[u8], arg: &Arguments, span: Span) -> Value {
fn at(val: &Value, args: &Arguments, span: Span) -> Value {
match val {
Value::Binary {
val,
span: val_span,
} => at_impl(val, args, *val_span),
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Input's type is {}. This command only works with bytes.",
other.get_type()
),
span,
),
},
}
}
fn at_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
let len: isize = input.len() as isize;
let start: isize = if arg.start < 0 {

View File

@ -1,4 +1,4 @@
use super::{operate, BytesArgument};
use crate::input_handler::{operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
@ -8,12 +8,12 @@ use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShap
struct Arguments {
pattern: Vec<u8>,
column_paths: Option<Vec<CellPath>>,
cell_paths: Option<Vec<CellPath>>,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
@ -53,15 +53,11 @@ impl Command for BytesEndsWith {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() {
None
} else {
Some(column_paths)
};
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let arg = Arguments {
pattern,
column_paths,
cell_paths,
};
operate(ends_with, arg, input, call.head, engine_state.ctrlc.clone())
}
@ -96,10 +92,24 @@ impl Command for BytesEndsWith {
}
}
fn ends_with(input: &[u8], Arguments { pattern, .. }: &Arguments, span: Span) -> Value {
Value::Bool {
val: input.ends_with(pattern),
span,
fn ends_with(val: &Value, args: &Arguments, span: Span) -> Value {
match val {
Value::Binary {
val,
span: val_span,
} => Value::Bool {
val: val.ends_with(&args.pattern),
span: *val_span,
},
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Input's type is {}. This command only works with bytes.",
other.get_type()
),
span,
),
},
}
}

View File

@ -1,4 +1,4 @@
use super::{operate, BytesArgument};
use crate::input_handler::{operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::ast::{Call, CellPath};
use nu_protocol::engine::{Command, EngineState, Stack};
@ -10,12 +10,12 @@ struct Arguments {
pattern: Vec<u8>,
end: bool,
all: bool,
column_paths: Option<Vec<CellPath>>,
cell_paths: Option<Vec<CellPath>>,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
@ -60,17 +60,13 @@ impl Command for BytesIndexOf {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() {
None
} else {
Some(column_paths)
};
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let arg = Arguments {
pattern,
end: call.has_flag("end"),
all: call.has_flag("all"),
column_paths,
cell_paths,
};
operate(index_of, arg, input, call.head, engine_state.ctrlc.clone())
}
@ -126,7 +122,25 @@ impl Command for BytesIndexOf {
}
}
fn index_of(input: &[u8], arg: &Arguments, span: Span) -> Value {
fn index_of(val: &Value, args: &Arguments, span: Span) -> Value {
match val {
Value::Binary {
val,
span: val_span,
} => index_of_impl(val, args, *val_span),
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Input's type is {}. This command only works with bytes.",
other.get_type()
),
span,
),
},
}
}
fn index_of_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
if arg.all {
search_all_index(input, &arg.pattern, arg.end, span)
} else {

View File

@ -1,4 +1,4 @@
use super::{operate, BytesArgument};
use crate::input_handler::{operate, CellPathOnlyArgs};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
@ -9,16 +9,6 @@ use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShap
#[derive(Clone)]
pub struct BytesLen;
struct Arguments {
column_paths: Option<Vec<CellPath>>,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
}
}
impl Command for BytesLen {
fn name(&self) -> &str {
"bytes length"
@ -49,13 +39,8 @@ impl Command for BytesLen {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() {
None
} else {
Some(column_paths)
};
let arg = Arguments { column_paths };
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let arg = CellPathOnlyArgs::from(cell_paths);
operate(length, arg, input, call.head, engine_state.ctrlc.clone())
}
@ -78,10 +63,24 @@ impl Command for BytesLen {
}
}
fn length(input: &[u8], _arg: &Arguments, span: Span) -> Value {
Value::Int {
val: input.len() as i64,
span,
fn length(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
match val {
Value::Binary {
val,
span: val_span,
} => Value::Int {
val: val.len() as i64,
span: *val_span,
},
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Input's type is {}. This command only works with bytes.",
other.get_type()
),
span,
),
},
}
}

View File

@ -11,11 +11,6 @@ mod replace;
mod reverse;
mod starts_with;
use nu_protocol::ast::CellPath;
use nu_protocol::{PipelineData, ShellError, Span, Value};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
pub use add::BytesAdd;
pub use at::BytesAt;
pub use build_::BytesBuild;
@ -28,71 +23,3 @@ pub use remove::BytesRemove;
pub use replace::BytesReplace;
pub use reverse::BytesReverse;
pub use starts_with::BytesStartsWith;
trait BytesArgument {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>>;
}
/// map input pipeline data, for each elements, if it's Binary, invoke relative `cmd` with `arg`.
fn operate<C, A>(
cmd: C,
mut arg: A,
input: PipelineData,
span: Span,
ctrlc: Option<Arc<AtomicBool>>,
) -> Result<PipelineData, ShellError>
where
A: BytesArgument + Send + Sync + 'static,
C: Fn(&[u8], &A, Span) -> Value + Send + Sync + 'static + Clone + Copy,
{
match arg.take_column_paths() {
None => input.map(
move |v| match v {
Value::Binary {
val,
span: val_span,
} => cmd(&val, &arg, val_span),
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Input's type is {}. This command only works with bytes.",
other.get_type()
),
span,
),
},
},
ctrlc,
),
Some(column_paths) => {
let arg = Arc::new(arg);
input.map(
move |mut v| {
for path in &column_paths {
let opt = arg.clone();
let r = v.update_cell_path(
&path.members,
Box::new(move |old| {
match old {
Value::Binary {val, span: val_span} => cmd(val, &opt, *val_span),
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Input's type is {}. This command only works with bytes.",
other.get_type()
),
span,
),
}}}),
);
if let Err(error) = r {
return Value::Error { error };
}
}
v
},
ctrlc,
)
}
}
}

View File

@ -1,4 +1,4 @@
use super::{operate, BytesArgument};
use crate::input_handler::{operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
@ -9,13 +9,13 @@ use nu_protocol::{
struct Arguments {
pattern: Vec<u8>,
end: bool,
column_paths: Option<Vec<CellPath>>,
cell_paths: Option<Vec<CellPath>>,
all: bool,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
@ -55,12 +55,8 @@ impl Command for BytesRemove {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() {
None
} else {
Some(column_paths)
};
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
if pattern_to_remove.item.is_empty() {
return Err(ShellError::UnsupportedInput(
@ -73,7 +69,7 @@ impl Command for BytesRemove {
let arg = Arguments {
pattern: pattern_to_remove,
end: call.has_flag("end"),
column_paths,
cell_paths,
all: call.has_flag("all"),
};
@ -135,7 +131,25 @@ impl Command for BytesRemove {
}
}
fn remove(input: &[u8], arg: &Arguments, span: Span) -> Value {
fn remove(val: &Value, args: &Arguments, span: Span) -> Value {
match val {
Value::Binary {
val,
span: val_span,
} => remove_impl(val, args, *val_span),
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Input's type is {}. This command only works with bytes.",
other.get_type()
),
span,
),
},
}
}
fn remove_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
let mut result = vec![];
let remove_all = arg.all;
let input_len = input.len();

View File

@ -1,4 +1,4 @@
use super::{operate, BytesArgument};
use crate::input_handler::{operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
@ -9,13 +9,13 @@ use nu_protocol::{
struct Arguments {
find: Vec<u8>,
replace: Vec<u8>,
column_paths: Option<Vec<CellPath>>,
cell_paths: Option<Vec<CellPath>>,
all: bool,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
@ -55,12 +55,8 @@ impl Command for BytesReplace {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 2)?;
let column_paths = if column_paths.is_empty() {
None
} else {
Some(column_paths)
};
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 2)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
if find.item.is_empty() {
return Err(ShellError::UnsupportedInput(
@ -72,7 +68,7 @@ impl Command for BytesReplace {
let arg = Arguments {
find: find.item,
replace: call.req::<Vec<u8>>(engine_state, stack, 1)?,
column_paths,
cell_paths,
all: call.has_flag("all"),
};
@ -126,7 +122,25 @@ impl Command for BytesReplace {
}
}
fn replace(input: &[u8], arg: &Arguments, span: Span) -> Value {
fn replace(val: &Value, args: &Arguments, span: Span) -> Value {
match val {
Value::Binary {
val,
span: val_span,
} => replace_impl(val, args, *val_span),
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Input's type is {}. This command only works with bytes.",
other.get_type()
),
span,
),
},
}
}
fn replace_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
let mut replaced = vec![];
let replace_all = arg.all;

View File

@ -1,4 +1,4 @@
use super::{operate, BytesArgument};
use crate::input_handler::{operate, CellPathOnlyArgs};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
@ -6,16 +6,6 @@ use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category;
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
struct Arguments {
column_paths: Option<Vec<CellPath>>,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
}
}
#[derive(Clone)]
pub struct BytesReverse;
@ -50,13 +40,8 @@ impl Command for BytesReverse {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let column_paths = if column_paths.is_empty() {
None
} else {
Some(column_paths)
};
let arg = Arguments { column_paths };
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let arg = CellPathOnlyArgs::from(cell_paths);
operate(reverse, arg, input, call.head, engine_state.ctrlc.clone())
}
@ -82,12 +67,28 @@ impl Command for BytesReverse {
}
}
fn reverse(input: &[u8], _args: &Arguments, span: Span) -> Value {
let mut reversed_input = input.to_vec();
reversed_input.reverse();
Value::Binary {
val: reversed_input,
span,
fn reverse(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
match val {
Value::Binary {
val,
span: val_span,
} => {
let mut reversed_input = val.to_vec();
reversed_input.reverse();
Value::Binary {
val: reversed_input,
span: *val_span,
}
}
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Input's type is {}. This command only works with bytes.",
other.get_type()
),
span,
),
},
}
}

View File

@ -1,4 +1,4 @@
use super::{operate, BytesArgument};
use crate::input_handler::{operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
@ -8,12 +8,12 @@ use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShap
struct Arguments {
pattern: Vec<u8>,
column_paths: Option<Vec<CellPath>>,
cell_paths: Option<Vec<CellPath>>,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
@ -53,15 +53,11 @@ impl Command for BytesStartsWith {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() {
None
} else {
Some(column_paths)
};
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let arg = Arguments {
pattern,
column_paths,
cell_paths,
};
operate(
starts_with,
@ -102,10 +98,24 @@ impl Command for BytesStartsWith {
}
}
fn starts_with(input: &[u8], Arguments { pattern, .. }: &Arguments, span: Span) -> Value {
Value::Bool {
val: input.starts_with(pattern),
span,
fn starts_with(val: &Value, args: &Arguments, span: Span) -> Value {
match val {
Value::Binary {
val,
span: val_span,
} => Value::Bool {
val: val.starts_with(&args.pattern),
span: *val_span,
},
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Input's type is {}. This command only works with bytes.",
other.get_type()
),
span,
),
},
}
}

View File

@ -1,3 +1,4 @@
use crate::input_handler::{operate, CellPathOnlyArgs};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
@ -96,31 +97,12 @@ fn fmt(
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map(
move |v| {
if column_paths.is_empty() {
action(&v, head)
} else {
let mut ret = v;
for path in &column_paths {
let r =
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
if let Err(error) = r {
return Value::Error { error };
}
}
ret
}
},
engine_state.ctrlc.clone(),
)
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let args = CellPathOnlyArgs::from(cell_paths);
operate(action, args, input, call.head, engine_state.ctrlc.clone())
}
pub fn action(input: &Value, span: Span) -> Value {
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
match input {
Value::Int { val, .. } => fmt_it(*val, span),
Value::Filesize { val, .. } => fmt_it(*val, span),

View File

@ -1,3 +1,4 @@
use crate::input_handler::{operate, CellPathOnlyArgs};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
@ -100,7 +101,7 @@ fn into_binary(
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
match input {
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::Binary {
@ -120,27 +121,10 @@ fn into_binary(
}
.into_pipeline_data())
}
_ => input.map(
move |v| {
if column_paths.is_empty() {
action(&v, head)
} else {
let mut ret = v;
for path in &column_paths {
let r = ret.update_cell_path(
&path.members,
Box::new(move |old| action(old, head)),
);
if let Err(error) = r {
return Value::Error { error };
}
}
ret
}
},
engine_state.ctrlc.clone(),
),
_ => {
let arg = CellPathOnlyArgs::from(cell_paths);
operate(action, arg, input, call.head, engine_state.ctrlc.clone())
}
}
}
@ -160,7 +144,7 @@ fn float_to_endian(n: f64) -> Vec<u8> {
}
}
pub fn action(input: &Value, span: Span) -> Value {
pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
match input {
Value::Binary { .. } => input.clone(),
Value::Int { val, .. } => Value::Binary {
@ -180,7 +164,7 @@ pub fn action(input: &Value, span: Span) -> Value {
span,
},
Value::Bool { val, .. } => Value::Binary {
val: int_to_endian(if *val { 1i64 } else { 0 }),
val: int_to_endian(i64::from(*val)),
span,
},
Value::Date { val, .. } => Value::Binary {

View File

@ -1,3 +1,4 @@
use crate::input_handler::{operate, CellPathOnlyArgs};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
@ -108,28 +109,9 @@ fn into_bool(
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map(
move |v| {
if column_paths.is_empty() {
action(&v, head)
} else {
let mut ret = v;
for path in &column_paths {
let r =
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
if let Err(error) = r {
return Value::Error { error };
}
}
ret
}
},
engine_state.ctrlc.clone(),
)
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let args = CellPathOnlyArgs::from(cell_paths);
operate(action, args, input, call.head, engine_state.ctrlc.clone())
}
fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
@ -154,7 +136,7 @@ fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
}
}
fn action(input: &Value, span: Span) -> Value {
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
match input {
Value::Bool { .. } => input.clone(),
Value::Int { val, .. } => Value::Bool {

View File

@ -1,3 +1,4 @@
use crate::input_handler::{operate, CmdArgument};
use crate::{generate_strftime_list, parse_date_from_string};
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
use nu_engine::CallExt;
@ -5,14 +6,20 @@ use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
SyntaxShape, Value,
};
struct Arguments {
timezone: Option<Spanned<String>>,
offset: Option<Spanned<i64>>,
format: Option<String>,
column_paths: Vec<CellPath>,
zone_options: Option<Spanned<Zone>>,
format_options: Option<DatetimeFormat>,
cell_paths: Option<Vec<CellPath>>,
}
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
// In case it may be confused with chrono::TimeZone
@ -95,7 +102,36 @@ impl Command for SubCommand {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
operate(engine_state, stack, call, input)
if call.has_flag("list") {
Ok(generate_strftime_list(call.head, true).into_pipeline_data())
} else {
let cell_paths = call.rest(engine_state, stack, 0)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
// if zone-offset is specified, then zone will be neglected
let timezone = call.get_flag::<Spanned<String>>(engine_state, stack, "timezone")?;
let zone_options =
match &call.get_flag::<Spanned<i64>>(engine_state, stack, "offset")? {
Some(zone_offset) => Some(Spanned {
item: Zone::new(zone_offset.item),
span: zone_offset.span,
}),
None => timezone.as_ref().map(|zone| Spanned {
item: Zone::from_string(zone.item.clone()),
span: zone.span,
}),
};
let format_options = call
.get_flag::<String>(engine_state, stack, "format")?
.as_ref()
.map(|fmt| DatetimeFormat(fmt.to_string()));
let args = Arguments {
format_options,
zone_options,
cell_paths,
};
operate(action, args, input, call.head, engine_state.ctrlc.clone())
}
}
fn usage(&self) -> &str {
@ -162,72 +198,9 @@ impl Command for SubCommand {
#[derive(Clone)]
struct DatetimeFormat(String);
fn operate(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let options = Arguments {
timezone: call.get_flag(engine_state, stack, "timezone")?,
offset: call.get_flag(engine_state, stack, "offset")?,
format: call.get_flag(engine_state, stack, "format")?,
column_paths: call.rest(engine_state, stack, 0)?,
};
// if zone-offset is specified, then zone will be neglected
let zone_options = match &options.offset {
Some(zone_offset) => Some(Spanned {
item: Zone::new(zone_offset.item),
span: zone_offset.span,
}),
None => options.timezone.as_ref().map(|zone| Spanned {
item: Zone::from_string(zone.item.clone()),
span: zone.span,
}),
};
let list_flag = call.has_flag("list");
let format_options = options
.format
.as_ref()
.map(|fmt| DatetimeFormat(fmt.to_string()));
input.map(
move |v| {
if options.column_paths.is_empty() && !list_flag {
action(&v, &zone_options, &format_options, head)
} else if list_flag {
generate_strftime_list(head, true)
} else {
let mut ret = v;
for path in &options.column_paths {
let zone_options = zone_options.clone();
let format_options = format_options.clone();
let r = ret.update_cell_path(
&path.members,
Box::new(move |old| action(old, &zone_options, &format_options, head)),
);
if let Err(error) = r {
return Value::Error { error };
}
}
ret
}
},
engine_state.ctrlc.clone(),
)
}
fn action(
input: &Value,
timezone: &Option<Spanned<Zone>>,
dateformat: &Option<DatetimeFormat>,
head: Span,
) -> Value {
fn action(input: &Value, args: &Arguments, head: Span) -> Value {
let timezone = &args.zone_options;
let dateformat = &args.format_options;
// Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?)
let timestamp = match input {
Value::Int { val, .. } => Ok(*val),
@ -359,7 +332,12 @@ mod tests {
fn takes_a_date_format() {
let date_str = Value::test_string("16.11.1984 8:00 am +0000");
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
let actual = action(&date_str, &None, &fmt_options, Span::test_data());
let args = Arguments {
zone_options: None,
format_options: fmt_options,
cell_paths: None,
};
let actual = action(&date_str, &args, Span::test_data());
let expected = Value::Date {
val: DateTime::parse_from_str("16.11.1984 8:00 am +0000", "%d.%m.%Y %H:%M %P %z")
.unwrap(),
@ -371,7 +349,12 @@ mod tests {
#[test]
fn takes_iso8601_date_format() {
let date_str = Value::test_string("2020-08-04T16:39:18+00:00");
let actual = action(&date_str, &None, &None, Span::test_data());
let args = Arguments {
zone_options: None,
format_options: None,
cell_paths: None,
};
let actual = action(&date_str, &args, Span::test_data());
let expected = Value::Date {
val: DateTime::parse_from_str("2020-08-04T16:39:18+00:00", "%Y-%m-%dT%H:%M:%S%z")
.unwrap(),
@ -387,7 +370,12 @@ mod tests {
item: Zone::East(8),
span: Span::test_data(),
});
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
let args = Arguments {
zone_options: timezone_option,
format_options: None,
cell_paths: None,
};
let actual = action(&date_str, &args, Span::test_data());
let expected = Value::Date {
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
.unwrap(),
@ -404,7 +392,12 @@ mod tests {
item: Zone::East(8),
span: Span::test_data(),
});
let actual = action(&date_int, &timezone_option, &None, Span::test_data());
let args = Arguments {
zone_options: timezone_option,
format_options: None,
cell_paths: None,
};
let actual = action(&date_int, &args, Span::test_data());
let expected = Value::Date {
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
.unwrap(),
@ -421,7 +414,12 @@ mod tests {
item: Zone::Local,
span: Span::test_data(),
});
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
let args = Arguments {
zone_options: timezone_option,
format_options: None,
cell_paths: None,
};
let actual = action(&date_str, &args, Span::test_data());
let expected = Value::Date {
val: Local.timestamp(1614434140, 0).into(),
span: Span::test_data(),
@ -433,8 +431,12 @@ mod tests {
#[test]
fn takes_timestamp_without_timezone() {
let date_str = Value::test_string("1614434140");
let timezone_option = None;
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
let args = Arguments {
zone_options: None,
format_options: None,
cell_paths: None,
};
let actual = action(&date_str, &args, Span::test_data());
let expected = Value::Date {
val: Utc.timestamp(1614434140, 0).into(),
@ -451,7 +453,12 @@ mod tests {
item: Zone::Utc,
span: Span::test_data(),
});
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
let args = Arguments {
zone_options: timezone_option,
format_options: None,
cell_paths: None,
};
let actual = action(&date_str, &args, Span::test_data());
assert_eq!(actual.get_type(), Error);
}
@ -460,7 +467,12 @@ mod tests {
fn communicates_parsing_error_given_an_invalid_datetimelike_string() {
let date_str = Value::test_string("16.11.1984 8:00 am Oops0000");
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
let actual = action(&date_str, &None, &fmt_options, Span::test_data());
let args = Arguments {
zone_options: None,
format_options: fmt_options,
cell_paths: None,
};
let actual = action(&date_str, &args, Span::test_data());
assert_eq!(actual.get_type(), Error);
}

View File

@ -1,3 +1,4 @@
use crate::input_handler::{operate, CellPathOnlyArgs};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
@ -36,7 +37,9 @@ impl Command for SubCommand {
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
operate(engine_state, stack, call, input)
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let args = CellPathOnlyArgs::from(cell_paths);
operate(action, args, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
@ -72,37 +75,7 @@ impl Command for SubCommand {
}
}
fn operate(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map(
move |v| {
if column_paths.is_empty() {
action(&v, head)
} else {
let mut ret = v;
for path in &column_paths {
let r =
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
if let Err(error) = r {
return Value::Error { error };
}
}
ret
}
},
engine_state.ctrlc.clone(),
)
}
fn action(input: &Value, head: Span) -> Value {
fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
match input {
Value::String { val: s, span } => {
let other = s.trim();
@ -163,7 +136,7 @@ mod tests {
let word = Value::test_string("3.1415");
let expected = Value::test_float(3.1415);
let actual = action(&word, Span::test_data());
let actual = action(&word, &CellPathOnlyArgs::from(vec![]), Span::test_data());
assert_eq!(actual, expected);
}
@ -171,7 +144,11 @@ mod tests {
fn communicates_parsing_error_given_an_invalid_decimallike_string() {
let decimal_str = Value::test_string("11.6anra");
let actual = action(&decimal_str, Span::test_data());
let actual = action(
&decimal_str,
&CellPathOnlyArgs::from(vec![]),
Span::test_data(),
);
assert_eq!(actual.get_type(), Error);
}
@ -180,7 +157,11 @@ mod tests {
fn int_to_decimal() {
let decimal_str = Value::test_int(10);
let expected = Value::test_float(10.0);
let actual = action(&decimal_str, Span::test_data());
let actual = action(
&decimal_str,
&CellPathOnlyArgs::from(vec![]),
Span::test_data(),
);
assert_eq!(actual, expected);
}

View File

@ -1,3 +1,4 @@
use crate::input_handler::{operate, CellPathOnlyArgs};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
@ -38,7 +39,9 @@ impl Command for SubCommand {
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
into_filesize(engine_state, stack, call, input)
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let args = CellPathOnlyArgs::from(cell_paths);
operate(action, args, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
@ -84,37 +87,7 @@ impl Command for SubCommand {
}
}
fn into_filesize(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map(
move |v| {
if column_paths.is_empty() {
action(&v, head)
} else {
let mut ret = v;
for path in &column_paths {
let r =
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
if let Err(error) = r {
return Value::Error { error };
}
}
ret
}
},
engine_state.ctrlc.clone(),
)
}
pub fn action(input: &Value, span: Span) -> Value {
pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
if let Ok(value_span) = input.span() {
match input {
Value::Filesize { .. } => input.clone(),

View File

@ -1,3 +1,4 @@
use crate::input_handler::{operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
@ -6,11 +7,17 @@ use nu_protocol::{
};
struct Arguments {
radix: Option<Value>,
column_paths: Vec<CellPath>,
radix: u32,
cell_paths: Option<Vec<CellPath>>,
little_endian: bool,
}
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
#[derive(Clone)]
pub struct SubCommand;
@ -46,7 +53,29 @@ impl Command for SubCommand {
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
into_int(engine_state, stack, call, input)
let cell_paths = call.rest(engine_state, stack, 0)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let radix = call.get_flag::<Value>(engine_state, stack, "radix")?;
let radix: u32 = match radix {
Some(Value::Int { val, span }) => {
if !(2..=36).contains(&val) {
return Err(ShellError::UnsupportedInput(
"Radix must lie in the range [2, 36]".to_string(),
span,
));
}
val as u32
}
Some(_) => 10,
None => 10,
};
let args = Arguments {
radix,
little_endian: call.has_flag("little-endian"),
cell_paths,
};
operate(action, args, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
@ -121,59 +150,9 @@ impl Command for SubCommand {
}
}
fn into_int(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let options = Arguments {
radix: call.get_flag(engine_state, stack, "radix")?,
little_endian: call.has_flag("little-endian"),
column_paths: call.rest(engine_state, stack, 0)?,
};
let radix: u32 = match options.radix {
Some(Value::Int { val, .. }) => val as u32,
Some(_) => 10,
None => 10,
};
if let Some(val) = &options.radix {
if !(2..=36).contains(&radix) {
return Err(ShellError::UnsupportedInput(
"Radix must lie in the range [2, 36]".to_string(),
val.span()?,
));
}
}
input.map(
move |v| {
if options.column_paths.is_empty() {
action(&v, head, radix, options.little_endian)
} else {
let mut ret = v;
for path in &options.column_paths {
let r = ret.update_cell_path(
&path.members,
Box::new(move |old| action(old, head, radix, options.little_endian)),
);
if let Err(error) = r {
return Value::Error { error };
}
}
ret
}
},
engine_state.ctrlc.clone(),
)
}
pub fn action(input: &Value, span: Span, radix: u32, little_endian: bool) -> Value {
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
let radix = args.radix;
let little_endian = args.little_endian;
match input {
Value::Int { val: _, .. } => {
if radix == 10 {
@ -401,21 +380,45 @@ mod test {
let word = Value::test_string("10");
let expected = Value::test_int(10);
let actual = action(&word, Span::test_data(), 10, false);
let actual = action(
&word,
&Arguments {
radix: 10,
cell_paths: None,
little_endian: false,
},
Span::test_data(),
);
assert_eq!(actual, expected);
}
#[test]
fn turns_binary_to_integer() {
let s = Value::test_string("0b101");
let actual = action(&s, Span::test_data(), 10, false);
let actual = action(
&s,
&Arguments {
radix: 10,
cell_paths: None,
little_endian: false,
},
Span::test_data(),
);
assert_eq!(actual, Value::test_int(5));
}
#[test]
fn turns_hex_to_integer() {
let s = Value::test_string("0xFF");
let actual = action(&s, Span::test_data(), 16, false);
let actual = action(
&s,
&Arguments {
radix: 16,
cell_paths: None,
little_endian: false,
},
Span::test_data(),
);
assert_eq!(actual, Value::test_int(255));
}
@ -423,7 +426,15 @@ mod test {
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
let integer_str = Value::test_string("36anra");
let actual = action(&integer_str, Span::test_data(), 10, false);
let actual = action(
&integer_str,
&Arguments {
radix: 10,
cell_paths: None,
little_endian: false,
},
Span::test_data(),
);
assert_eq!(actual.get_type(), Error)
}

View File

@ -1,3 +1,4 @@
use crate::input_handler::{operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
@ -8,6 +9,19 @@ use nu_protocol::{
use nu_utils::get_system_locale;
use num_format::ToFormattedString;
struct Arguments {
decimals_value: Option<i64>,
decimals: bool,
cell_paths: Option<Vec<CellPath>>,
config: Config,
}
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
#[derive(Clone)]
pub struct SubCommand;
@ -149,9 +163,6 @@ fn string_helper(
let decimals = call.has_flag("decimals");
let head = call.head;
let decimals_value: Option<i64> = call.get_flag(engine_state, stack, "decimals")?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let config = engine_state.get_config().clone();
if let Some(decimal_val) = decimals_value {
if decimals && decimal_val.is_negative() {
return Err(ShellError::UnsupportedInput(
@ -160,6 +171,15 @@ fn string_helper(
));
}
}
let cell_paths = call.rest(engine_state, stack, 0)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let config = engine_state.get_config().clone();
let args = Arguments {
decimals_value,
decimals,
cell_paths,
config,
};
match input {
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::String {
@ -179,45 +199,18 @@ fn string_helper(
}
.into_pipeline_data())
}
_ => input.map(
move |v| {
if column_paths.is_empty() {
action(&v, head, decimals, decimals_value, false, &config)
} else {
let mut ret = v;
for path in &column_paths {
let config = config.clone();
let r = ret.update_cell_path(
&path.members,
Box::new(move |old| {
action(old, head, decimals, decimals_value, false, &config)
}),
);
if let Err(error) = r {
return Value::Error { error };
}
}
ret
}
},
engine_state.ctrlc.clone(),
),
_ => operate(action, args, input, head, engine_state.ctrlc.clone()),
}
}
pub fn action(
input: &Value,
span: Span,
decimals: bool,
digits: Option<i64>,
group_digits: bool,
config: &Config,
) -> Value {
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
let decimals = args.decimals;
let digits = args.decimals_value;
let config = &args.config;
match input {
Value::Int { val, .. } => {
let decimal_value = digits.unwrap_or(0) as usize;
let res = format_int(*val, group_digits, decimal_value);
let res = format_int(*val, false, decimal_value);
Value::String { val: res, span }
}
Value::Float { val, .. } => {

View File

@ -49,10 +49,17 @@ impl Command for Alias {
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Alias ll to ls -l",
example: "alias ll = ls -l",
result: None,
}]
vec![
Example {
description: "Alias ll to ls -l",
example: "alias ll = ls -l",
result: None,
},
Example {
description: "Make an alias that makes a list of all custom commands",
example: "alias customs = ($nu.scope.commands | where is_custom | get command)",
result: None,
},
]
}
}

View File

@ -23,7 +23,7 @@ impl Command for Do {
.required("block", SyntaxShape::Any, "the block to run")
.switch(
"ignore-errors",
"ignore errors as the block runs",
"ignore shell errors as the block runs",
Some('i'),
)
.switch(
@ -186,10 +186,15 @@ impl Command for Do {
result: Some(Value::test_string("hello")),
},
Example {
description: "Run the block and ignore errors",
description: "Run the block and ignore shell errors",
example: r#"do -i { thisisnotarealcommand }"#,
result: None,
},
Example {
description: "Abort the pipeline if a program returns a non-zero exit code",
example: r#"do -c { nu -c 'exit 1' } | myscarycommand"#,
result: None,
},
Example {
description: "Run the block, with a positional parameter",
example: r#"do {|x| 100 + $x } 50"#,

View File

@ -2,7 +2,7 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Value,
Category, Example, ListStream, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
#[derive(Clone)]
@ -14,7 +14,7 @@ impl Command for Echo {
}
fn usage(&self) -> &str {
"Echo the arguments back to the user."
"Returns its arguments, ignoring the piped-in value."
}
fn signature(&self) -> Signature {
@ -24,7 +24,9 @@ impl Command for Echo {
}
fn extra_usage(&self) -> &str {
"Unlike `print`, this command returns an actual value that will be passed to the next command of the pipeline."
r#"When given no arguments, it returns an empty string. When given one argument,
it returns it. Otherwise, it returns a list of the arguments. There is usually
little reason to use this over just writing the values as-is."#
}
fn run(
@ -61,13 +63,17 @@ impl Command for Echo {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Put a hello message in the pipeline",
example: "echo 'hello'",
result: Some(Value::test_string("hello")),
description: "Put a list of numbers in the pipeline. This is the same as [1 2 3].",
example: "echo 1 2 3",
result: Some(Value::List {
vals: vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)],
span: Span::test_data(),
}),
},
Example {
description: "Print the value of the special '$nu' variable",
example: "echo $nu",
description:
"Returns the piped-in value, by using the special $in variable to obtain it.",
example: "echo $in",
result: None,
},
]

View File

@ -363,15 +363,12 @@ pub fn highlight_search_string(
}
};
// strip haystack to remove existing ansi style
let stripped_haystack: String = match strip_ansi_escapes::strip(haystack) {
Ok(i) => String::from_utf8(i).unwrap_or_else(|_| String::from(haystack)),
Err(_) => String::from(haystack),
};
let stripped_haystack = nu_utils::strip_ansi_likely(haystack);
let mut last_match_end = 0;
let style = Style::new().fg(White).on(Red);
let mut highlighted = String::new();
for cap in regex.captures_iter(stripped_haystack.as_str()) {
for cap in regex.captures_iter(stripped_haystack.as_ref()) {
match cap {
Ok(capture) => {
let start = match capture.get(0) {

View File

@ -105,7 +105,7 @@ impl Command for OverlayHide {
},
Example {
description: "Hide an overlay created from a file",
example: r#"echo 'export alias f = "foo"' | save spam.nu
example: r#"'export alias f = "foo"' | save spam.nu
overlay use spam.nu
overlay hide spam"#,
result: None,

View File

@ -183,14 +183,14 @@ impl Command for OverlayUse {
},
Example {
description: "Create an overlay with a prefix",
example: r#"echo 'export def foo { "foo" }'
example: r#"'export def foo { "foo" }'
overlay use --prefix spam
spam foo"#,
result: None,
},
Example {
description: "Create an overlay from a file",
example: r#"echo 'export-env { let-env FOO = "foo" }' | save spam.nu
example: r#"'export-env { let-env FOO = "foo" }' | save spam.nu
overlay use spam.nu
$env.FOO"#,
result: None,

View File

@ -19,7 +19,7 @@ impl Command for GroupByDb {
}
fn usage(&self) -> &str {
"Group by query"
"Group-by query"
}
fn signature(&self) -> Signature {

View File

@ -15,13 +15,12 @@ impl Command for ToDataBase {
}
fn usage(&self) -> &str {
"Converts into an open db connection"
"Converts the input into an open db connection"
}
fn extra_usage(&self) -> &str {
"This function is used as type hint for parser, specially if the query is not started with 'from table'"
"This function is used as a hint to Nushell to optimize the pipeline for database queries."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_type(Type::Any)
@ -35,7 +34,7 @@ impl Command for ToDataBase {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Converts an open file into a db object",
description: "Converts an open file into a db object.",
example: "open db.sqlite | into db",
result: None,
}]

View File

@ -248,14 +248,8 @@ fn nu_value_to_string(value: Value, separator: &str, config: &Config) -> String
}
Value::String { val, .. } => {
// don't store ansi escape sequences in the database
let stripped = {
match strip_ansi_escapes::strip(&val) {
Ok(item) => String::from_utf8(item).unwrap_or(val),
Err(_) => val,
}
};
// escape single quotes
stripped.replace('\'', "''")
nu_utils::strip_ansi_unlikely(&val).replace('\'', "''")
}
Value::List { vals: val, .. } => val
.iter()

View File

@ -124,7 +124,8 @@ impl CustomValue for ExprDb {
| Operator::ShiftLeft
| Operator::ShiftRight
| Operator::StartsWith
| Operator::EndsWith => Err(ShellError::UnsupportedOperator(operator, op)),
| Operator::EndsWith
| Operator::Append => Err(ShellError::UnsupportedOperator(operator, op)),
}?;
let expr = Expr::BinaryOp {

View File

@ -55,7 +55,6 @@ impl Command for ColumnsDF {
}
}
#[allow(clippy::needless_collect)]
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,

View File

@ -56,7 +56,6 @@ impl Command for DataTypes {
}
}
#[allow(clippy::needless_collect)]
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,

View File

@ -103,14 +103,14 @@ impl SQLContext {
let idx = match idx.parse::<usize>() {
Ok(0)| Err(_) => Err(
PolarsError::ComputeError(
format!("Group By Error: Only positive number or expression are supported, got {idx}").into()
format!("Group-By Error: Only positive number or expression are supported, got {idx}").into()
)),
Ok(idx) => Ok(idx)
}?;
Ok(projection[idx].clone())
}
SqlExpr::Value(_) => Err(
PolarsError::ComputeError("Group By Error: Only positive number or expression are supported".into())
PolarsError::ComputeError("Group-By Error: Only positive number or expression are supported".into())
),
_ => parse_sql_expr(e)
}
@ -124,7 +124,7 @@ impl SQLContext {
// Return error on wild card, shouldn't process this
if contain_wildcard {
return Err(PolarsError::ComputeError(
"Group By Error: Can't processed wildcard in groupby".into(),
"Group-By Error: Can't process wildcard in group-by".into(),
));
}
// Default polars group by will have group by columns at the front

View File

@ -15,7 +15,7 @@ impl Command for ToDataFrame {
}
fn usage(&self) -> &str {
"Converts a List, Table or Dictionary into a dataframe"
"Converts a list, table or record into a dataframe"
}
fn signature(&self) -> Signature {

View File

@ -243,7 +243,7 @@ expr_command!(
"max",
"Creates a max expression",
vec![Example {
description: "Max aggregation for a group by",
description: "Max aggregation for a group-by",
example: r#"[[a b]; [one 2] [one 4] [two 1]]
| into df
| group-by a
@ -274,7 +274,7 @@ expr_command!(
"min",
"Creates a min expression",
vec![Example {
description: "Min aggregation for a group by",
description: "Min aggregation for a group-by",
example: r#"[[a b]; [one 2] [one 4] [two 1]]
| into df
| group-by a
@ -305,7 +305,7 @@ expr_command!(
"sum",
"Creates a sum expression for an aggregation",
vec![Example {
description: "Sum aggregation for a group by",
description: "Sum aggregation for a group-by",
example: r#"[[a b]; [one 2] [one 4] [two 1]]
| into df
| group-by a
@ -336,7 +336,7 @@ expr_command!(
"mean",
"Creates a mean expression for an aggregation",
vec![Example {
description: "Mean aggregation for a group by",
description: "Mean aggregation for a group-by",
example: r#"[[a b]; [one 2] [one 4] [two 1]]
| into df
| group-by a
@ -367,7 +367,7 @@ expr_command!(
"median",
"Creates a median expression for an aggregation",
vec![Example {
description: "Median aggregation for a group by",
description: "Median aggregation for a group-by",
example: r#"[[a b]; [one 2] [one 4] [two 1]]
| into df
| group-by a
@ -398,7 +398,7 @@ expr_command!(
"std",
"Creates a std expression for an aggregation",
vec![Example {
description: "Std aggregation for a group by",
description: "Std aggregation for a group-by",
example: r#"[[a b]; [one 2] [one 2] [two 1] [two 1]]
| into df
| group-by a
@ -429,7 +429,7 @@ expr_command!(
"var",
"Create a var expression for an aggregation",
vec![Example {
description: "Var aggregation for a group by",
description: "Var aggregation for a group-by",
example: r#"[[a b]; [one 2] [one 2] [two 1] [two 1]]
| into df
| group-by a

View File

@ -33,7 +33,7 @@ impl Command for ExprQuantile {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Quantile aggregation for a group by",
description: "Quantile aggregation for a group-by",
example: r#"[[a b]; [one 2] [one 4] [two 1]]
| into df
| group-by a

View File

@ -17,13 +17,13 @@ impl Command for LazyAggregate {
}
fn usage(&self) -> &str {
"Performs a series of aggregations from a group by"
"Performs a series of aggregations from a group-by"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.rest(
"Group by expressions",
"Group-by expressions",
SyntaxShape::Any,
"Expression(s) that define the aggregations to be applied",
)

View File

@ -16,15 +16,15 @@ impl Command for ToLazyGroupBy {
}
fn usage(&self) -> &str {
"Creates a groupby object that can be used for other aggregations"
"Creates a group-by object that can be used for other aggregations"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.rest(
"Group by expressions",
"Group-by expressions",
SyntaxShape::Any,
"Expression(s) that define the lazy group by",
"Expression(s) that define the lazy group-by",
)
.input_type(Type::Custom("dataframe".into()))
.output_type(Type::Custom("dataframe".into()))

View File

@ -316,7 +316,7 @@ impl NuDataFrame {
let column = conversion::create_column(&series, row, row + 1, span)?;
if column.len() == 0 {
Err(ShellError::AccessBeyondEnd(series.len(), span))
Err(ShellError::AccessEmptyContent(span))
} else {
let value = column
.into_iter()

View File

@ -87,7 +87,10 @@ impl Command for SubCommand {
Example {
description: "Format a given date using a given format string.",
example: r#""2021-10-22 20:00:12 +01:00" | date format "%Y-%m-%d""#,
result: None,
result: Some(Value::String {
val: "2021-10-22".to_string(),
span: Span::test_data(),
}),
},
]
}

View File

@ -63,7 +63,7 @@ impl Command for WithEnv {
},
Example {
description: "Set by row(e.g. `open x.json` or `from json`)",
example: r#"echo '{"X":"Y","W":"Z"}'|from json|with-env $in { echo $env.X $env.W }"#,
example: r#"'{"X":"Y","W":"Z"}'|from json|with-env $in { echo $env.X $env.W }"#,
result: None,
},
]

View File

@ -6,6 +6,29 @@ use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value,
};
use std::path::Path;
// when the file under the fold executeable
#[cfg(unix)]
mod permission_mods {
pub type Mode = u32;
pub mod unix {
use super::Mode;
pub const USER_EXECUTE: Mode = libc::S_IXUSR as Mode;
pub const GROUP_EXECUTE: Mode = libc::S_IXGRP as Mode;
pub const OTHER_EXECUTE: Mode = libc::S_IXOTH as Mode;
}
}
// use to return the message of the result of change director
// TODO: windows, maybe should use file_attributes function in https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html
// TODO: the meaning of the result of the function can be found in https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
// TODO: if have realize the logic on windows, remove the cfg
#[derive(Debug)]
enum PermissionResult<'a> {
PermissionOk,
PermissionDenied(&'a str),
}
#[derive(Clone)]
pub struct Cd;
@ -44,10 +67,7 @@ impl Command for Cd {
let path_val = {
if let Some(path) = path_val {
Some(Spanned {
item: match strip_ansi_escapes::strip(&path.item) {
Ok(item) => String::from_utf8(item).unwrap_or(path.item),
Err(_) => path.item,
},
item: nu_utils::strip_ansi_string_unlikely(path.item),
span: path.span,
})
} else {
@ -141,6 +161,7 @@ impl Command for Cd {
}
};
let path_tointo = path.clone();
let path_value = Value::String { val: path, span };
let cwd = Value::String {
val: cwd.to_string_lossy().to_string(),
@ -172,9 +193,16 @@ impl Command for Cd {
//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(), path_value);
Ok(PipelineData::new(call.head))
match have_permission(&path_tointo) {
PermissionResult::PermissionOk => {
stack.add_env_var("PWD".into(), path_value);
Ok(PipelineData::new(call.head))
}
PermissionResult::PermissionDenied(reason) => Err(ShellError::IOError(format!(
"Cannot change directory to {}: {}",
path_tointo, reason
))),
}
}
fn examples(&self) -> Vec<Example> {
@ -197,3 +225,65 @@ impl Command for Cd {
]
}
}
#[cfg(windows)]
fn have_permission(dir: impl AsRef<Path>) -> PermissionResult<'static> {
match dir.as_ref().read_dir() {
Err(e) => {
if matches!(e.kind(), std::io::ErrorKind::PermissionDenied) {
PermissionResult::PermissionDenied("Folder is unable to be read")
} else {
PermissionResult::PermissionOk
}
}
Ok(_) => PermissionResult::PermissionOk,
}
}
#[cfg(unix)]
fn have_permission(dir: impl AsRef<Path>) -> PermissionResult<'static> {
match dir.as_ref().metadata() {
Ok(metadata) => {
use std::os::unix::fs::MetadataExt;
let bits = metadata.mode();
let has_bit = |bit| bits & bit == bit;
let current_user = users::get_current_uid();
if current_user == 0 {
return PermissionResult::PermissionOk;
}
let current_group = users::get_current_gid();
let owner_user = metadata.uid();
let owner_group = metadata.gid();
match (current_user == owner_user, current_group == owner_group) {
(true, _) => {
if has_bit(permission_mods::unix::USER_EXECUTE) {
PermissionResult::PermissionOk
} else {
PermissionResult::PermissionDenied(
"You are the owner but do not have the execute permission",
)
}
}
(false, true) => {
if has_bit(permission_mods::unix::GROUP_EXECUTE) {
PermissionResult::PermissionOk
} else {
PermissionResult::PermissionDenied(
"You are in the group but do not have the execute permission",
)
}
}
// other_user or root
(false, false) => {
if has_bit(permission_mods::unix::OTHER_EXECUTE) {
PermissionResult::PermissionOk
} else {
PermissionResult::PermissionDenied(
"You are neither the owner, in the group, nor the super user and do not have permission",
)
}
}
}
}
Err(_) => PermissionResult::PermissionDenied("Could not retrieve the metadata"),
}
}

View File

@ -25,7 +25,6 @@ const GLOB_PARAMS: nu_glob::MatchOptions = nu_glob::MatchOptions {
#[derive(Clone)]
pub struct Cp;
#[allow(unused_must_use)]
impl Command for Cp {
fn name(&self) -> &str {
"cp"
@ -74,10 +73,7 @@ impl Command for Cp {
let src: Spanned<String> = call.req(engine_state, stack, 0)?;
let src = {
Spanned {
item: match strip_ansi_escapes::strip(&src.item) {
Ok(item) => String::from_utf8(item).unwrap_or(src.item),
Err(_) => src.item,
},
item: nu_utils::strip_ansi_string_unlikely(src.item),
span: src.span,
}
};

View File

@ -42,12 +42,12 @@ impl Command for Ls {
.switch("all", "Show hidden files", Some('a'))
.switch(
"long",
"List all available columns for each entry",
"Get all available columns for each entry (slower; columns are platform-dependent)",
Some('l'),
)
.switch(
"short-names",
"Only print the file names and not the path",
"Only print the file names, and not the path",
Some('s'),
)
.switch("full-paths", "display paths as absolute paths", Some('f'))
@ -86,10 +86,7 @@ impl Command for Ls {
let pattern_arg = {
if let Some(path) = pattern_arg {
Some(Spanned {
item: match strip_ansi_escapes::strip(&path.item) {
Ok(item) => String::from_utf8(item).unwrap_or(path.item),
Err(_) => path.item,
},
item: nu_utils::strip_ansi_string_unlikely(path.item),
span: path.span,
})
} else {
@ -201,7 +198,7 @@ impl Command for Ls {
} else if full_paths || absolute_path {
Some(path.to_string_lossy().to_string())
} else if let Some(prefix) = &prefix {
if let Ok(remainder) = path.strip_prefix(&prefix) {
if let Ok(remainder) = path.strip_prefix(prefix) {
if directory {
// When the path is the same as the cwd, path_diff should be "."
let path_diff =
@ -218,7 +215,7 @@ impl Command for Ls {
Some(path_diff)
} else {
let new_prefix = if let Some(pfx) = diff_paths(&prefix, &cwd) {
let new_prefix = if let Some(pfx) = diff_paths(prefix, &cwd) {
pfx
} else {
prefix.to_path_buf()
@ -274,45 +271,44 @@ impl Command for Ls {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "List all files in the current directory",
description: "List visible files in the current directory",
example: "ls",
result: None,
},
Example {
description: "List all files in a subdirectory",
description: "List visible files in a subdirectory",
example: "ls subdir",
result: None,
},
Example {
description: "List all files with full path in the parent directory",
description: "List visible files with full path in the parent directory",
example: "ls -f ..",
result: None,
},
Example {
description: "List all rust files",
description: "List Rust files",
example: "ls *.rs",
result: None,
},
Example {
description: "List all files and directories whose name do not contain 'bar'",
description: "List files and directories whose name do not contain 'bar'",
example: "ls -s | where name !~ bar",
result: None,
},
Example {
description: "List all dirs in your home directory",
example: "ls ~ | where type == dir",
example: "ls -a ~ | where type == dir",
result: None,
},
Example {
description:
"List all dirs in your home directory which have not been modified in 7 days",
example: "ls -s ~ | where type == dir && modified < ((date now) - 7day)",
example: "ls -as ~ | where type == dir && modified < ((date now) - 7day)",
result: None,
},
Example {
description: "List given paths, show directories themselves",
example:
"['/path/to/directory' '/path/to/file'] | each { |it| ls -D $it } | flatten",
description: "List given paths and show directories themselves",
example: "['/path/to/directory' '/path/to/file'] | each { ls -D $in } | flatten",
result: None,
},
]

View File

@ -20,7 +20,6 @@ const GLOB_PARAMS: nu_glob::MatchOptions = nu_glob::MatchOptions {
#[derive(Clone)]
pub struct Mv;
#[allow(unused_must_use)]
impl Command for Mv {
fn name(&self) -> &str {
"mv"
@ -51,8 +50,8 @@ impl Command for Mv {
"make mv to be verbose, showing files been moved.",
Some('v'),
)
.switch("force", "overwrite the destination.", Some('f'))
.switch("interactive", "ask user to confirm action", Some('i'))
// .switch("force", "suppress error when no file", Some('f'))
.category(Category::FileSystem)
}
@ -67,17 +66,14 @@ impl Command for Mv {
let spanned_source: Spanned<String> = call.req(engine_state, stack, 0)?;
let spanned_source = {
Spanned {
item: match strip_ansi_escapes::strip(&spanned_source.item) {
Ok(item) => String::from_utf8(item).unwrap_or(spanned_source.item),
Err(_) => spanned_source.item,
},
item: nu_utils::strip_ansi_string_unlikely(spanned_source.item),
span: spanned_source.span,
}
};
let spanned_destination: Spanned<String> = call.req(engine_state, stack, 1)?;
let verbose = call.has_flag("verbose");
let interactive = call.has_flag("interactive");
// let force = call.has_flag("force");
let force = call.has_flag("force");
let ctrlc = engine_state.ctrlc.clone();
@ -102,12 +98,22 @@ impl Command for Mv {
//
// First, the destination exists.
// - If a directory, move everything into that directory, otherwise
// - if only a single source, overwrite the file, otherwise
// - error.
// - if only a single source, and --force (or -f) is provided overwrite the file,
// - otherwise error.
//
// Second, the destination doesn't exist, so we can only rename a single source. Otherwise
// it's an error.
if destination.exists() && !force && !destination.is_dir() && !source.is_dir() {
return Err(ShellError::GenericError(
"Destination file already exists".into(),
"you can use -f, --force to force overwriting the destination".into(),
Some(spanned_destination.span),
None,
Vec::new(),
));
}
if (destination.exists() && !destination.is_dir() && sources.len() > 1)
|| (!destination.exists() && sources.len() > 1)
{
@ -289,7 +295,7 @@ fn move_file(
fn move_item(from: &Path, from_span: Span, to: &Path) -> Result<(), ShellError> {
// We first try a rename, which is a quick operation. If that doesn't work, we'll try a copy
// and remove the old file/folder. This is necessary if we're moving across filesystems or devices.
std::fs::rename(&from, &to).or_else(|_| {
std::fs::rename(from, to).or_else(|_| {
match if from.is_file() {
let mut options = fs_extra::file::CopyOptions::new();
options.overwrite = true;

View File

@ -53,10 +53,7 @@ impl Command for Open {
let path = {
if let Some(path_val) = path {
Some(Spanned {
item: match strip_ansi_escapes::strip(&path_val.item) {
Ok(item) => String::from_utf8(item).unwrap_or(path_val.item),
Err(_) => path_val.item,
},
item: nu_utils::strip_ansi_string_unlikely(path_val.item),
span: path_val.span,
})
} else {
@ -100,7 +97,7 @@ impl Command for Open {
let path_no_whitespace = &path.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
let path = Path::new(path_no_whitespace);
if permission_denied(&path) {
if permission_denied(path) {
#[cfg(unix)]
let error_msg = match path.metadata() {
Ok(md) => format!(
@ -172,8 +169,17 @@ impl Command for Open {
let block = engine_state.get_block(block_id);
eval_block(engine_state, stack, block, output, false, false)
} else {
decl.run(engine_state, stack, &Call::new(arg_span), output)
decl.run(engine_state, stack, &Call::new(call_span), output)
}
.map_err(|inner| {
ShellError::GenericError(
format!("Error while parsing as {}", ext),
format!("Could not parse '{}' with `from {}`", path.display(), ext),
Some(arg_span),
Some(format!("Check out `help from {}` or `help from` for more options or open raw data with `open --raw '{}'`", ext, path.display())),
vec![inner],
)
})
}
None => Ok(output),
}

View File

@ -143,10 +143,7 @@ fn rm(
for (idx, path) in targets.clone().into_iter().enumerate() {
let corrected_path = Spanned {
item: match strip_ansi_escapes::strip(&path.item) {
Ok(item) => String::from_utf8(item).unwrap_or(path.item),
Err(_) => path.item,
},
item: nu_utils::strip_ansi_string_unlikely(path.item),
span: path.span,
};
let _ = std::mem::replace(&mut targets[idx], corrected_path);
@ -202,6 +199,7 @@ fn rm(
let path = current_dir(engine_state, stack)?;
let (mut target_exists, mut empty_span) = (false, call.head);
let mut all_targets: HashMap<PathBuf, Span> = HashMap::new();
for target in targets {
if path.to_string_lossy() == target.item
@ -232,6 +230,10 @@ fn rm(
for file in files {
match file {
Ok(ref f) => {
if !target_exists {
target_exists = true;
}
// It is not appropriate to try and remove the
// current directory or its parent when using
// glob patterns.
@ -253,12 +255,17 @@ fn rm(
}
}
}
// Target doesn't exists
if !target_exists && empty_span.eq(&call.head) {
empty_span = target.span;
}
}
Err(e) => {
return Err(ShellError::GenericError(
e.to_string(),
e.to_string(),
Some(call.head),
Some(target.span),
None,
Vec::new(),
))
@ -270,15 +277,15 @@ fn rm(
return Err(ShellError::GenericError(
"No valid paths".into(),
"no valid paths".into(),
Some(call.head),
Some(empty_span),
None,
Vec::new(),
));
}
Ok(all_targets
.into_iter()
.map(move |(f, _)| {
.into_keys()
.map(move |f| {
let is_empty = || match f.read_dir() {
Ok(mut p) => p.next().is_none(),
Err(_) => false,

View File

@ -2,8 +2,10 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value,
Category, Example, PipelineData, RawStream, ShellError, Signature, Span, Spanned, SyntaxShape,
Value,
};
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;
@ -35,6 +37,12 @@ impl Command for Save {
fn signature(&self) -> nu_protocol::Signature {
Signature::build("save")
.required("filename", SyntaxShape::Filepath, "the filename to use")
.named(
"stderr",
SyntaxShape::Filepath,
"the filename used to save stderr, only works with `-r` flag",
Some('e'),
)
.switch("raw", "save file as raw binary", Some('r'))
.switch("append", "append input to the end of the file", Some('a'))
.category(Category::FileSystem)
@ -81,6 +89,35 @@ impl Command for Save {
));
}
};
let stderr_path = call.get_flag::<Spanned<String>>(engine_state, stack, "stderr")?;
let stderr_file = match stderr_path {
None => None,
Some(stderr_path) => {
let stderr_span = stderr_path.span;
let stderr_path = Path::new(&stderr_path.item);
if stderr_path == path {
Some(file.try_clone()?)
} else {
match std::fs::File::create(stderr_path) {
Ok(file) => Some(file),
Err(err) => {
return Ok(PipelineData::Value(
Value::Error {
error: ShellError::GenericError(
"Permission denied".into(),
err.to_string(),
Some(stderr_span),
None,
Vec::new(),
),
},
None,
))
}
}
}
}
};
let ext = if raw {
None
@ -148,33 +185,37 @@ impl Command for Save {
match input {
PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::new(span)),
PipelineData::ExternalStream {
stdout: Some(mut stream),
stdout: Some(stream),
stderr,
..
} => {
let mut writer = BufWriter::new(file);
// delegate a thread to redirect stderr to result.
let handler = stderr.map(|stderr_stream| match stderr_file {
Some(stderr_file) => std::thread::spawn(move || {
stream_to_file(stderr_stream, stderr_file, span)
}),
None => std::thread::spawn(move || {
let _ = stderr_stream.into_bytes();
Ok(PipelineData::new(span))
}),
});
stream
.try_for_each(move |result| {
let buf = match result {
Ok(v) => match v {
Value::String { val, .. } => val.into_bytes(),
Value::Binary { val, .. } => val,
_ => {
return Err(ShellError::UnsupportedInput(
format!("{:?} not supported", v.get_type()),
v.span()?,
));
}
},
Err(err) => return Err(err),
};
if let Err(err) = writer.write(&buf) {
return Err(ShellError::IOError(err.to_string()));
let res = stream_to_file(stream, file, span);
if let Some(h) = handler {
match h.join() {
Err(err) => {
return Err(ShellError::ExternalCommand(
"Fail to receive external commands stderr message".to_string(),
format!("{err:?}"),
span,
))
}
Ok(())
})
.map(|_| PipelineData::new(span))
Ok(res) => res,
}?;
res
} else {
res
}
}
input => match input.into_value(span) {
Value::String { val, .. } => {
@ -224,19 +265,60 @@ impl Command for Save {
vec![
Example {
description: "Save a string to foo.txt in the current directory",
example: r#"echo 'save me' | save foo.txt"#,
example: r#"'save me' | save foo.txt"#,
result: None,
},
Example {
description: "Append a string to the end of foo.txt",
example: r#"echo 'append me' | save --append foo.txt"#,
example: r#"'append me' | save --append foo.txt"#,
result: None,
},
Example {
description: "Save a record to foo.json in the current directory",
example: r#"echo { a: 1, b: 2 } | save foo.json"#,
example: r#"{ a: 1, b: 2 } | save foo.json"#,
result: None,
},
Example {
description: "Save a running program's stderr to foo.txt",
example: r#"do -i {} | save foo.txt --stderr foo.txt"#,
result: None,
},
Example {
description: "Save a running program's stderr to separate file",
example: r#"do -i {} | save foo.txt --stderr bar.txt"#,
result: None,
},
]
}
}
fn stream_to_file(
mut stream: RawStream,
file: File,
span: Span,
) -> Result<PipelineData, ShellError> {
let mut writer = BufWriter::new(file);
stream
.try_for_each(move |result| {
let buf = match result {
Ok(v) => match v {
Value::String { val, .. } => val.into_bytes(),
Value::Binary { val, .. } => val,
_ => {
return Err(ShellError::UnsupportedInput(
format!("{:?} not supported", v.get_type()),
v.span()?,
));
}
},
Err(err) => return Err(err),
};
if let Err(err) = writer.write(&buf) {
return Err(ShellError::IOError(err.to_string()));
}
Ok(())
})
.map(|_| PipelineData::new(span))
}

View File

@ -14,20 +14,11 @@ pub struct FileStructure {
pub resources: Vec<Resource>,
}
#[allow(dead_code)]
impl FileStructure {
pub fn new() -> FileStructure {
FileStructure { resources: vec![] }
}
pub fn contains_more_than_one_file(&self) -> bool {
self.resources.len() > 1
}
pub fn contains_files(&self) -> bool {
!self.resources.is_empty()
}
pub fn paths_applying_with<F>(
&mut self,
to: F,

View File

@ -72,7 +72,7 @@ impl Command for Watch {
.item
.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
let path = match nu_path::canonicalize_with(path_no_whitespace, &cwd) {
let path = match nu_path::canonicalize_with(path_no_whitespace, cwd) {
Ok(p) => p,
Err(e) => {
return Err(ShellError::DirectoryNotFound(

View File

@ -18,7 +18,7 @@ impl Command for All {
.required(
"predicate",
SyntaxShape::RowCondition,
"the predicate expression that must evaluate to a boolean",
"the expression, or block, that must evaluate to a boolean",
)
.category(Category::Filters)
}
@ -34,18 +34,26 @@ impl Command for All {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Find if services are running",
example: "echo [[status]; [UP] [UP]] | all status == UP",
description: "Check if each row's status is the string 'UP'",
example: "[[status]; [UP] [UP]] | all status == UP",
result: Some(Value::test_bool(true)),
},
Example {
description: "Check that all values are even",
example: "echo [2 4 6 8] | all ($it mod 2) == 0",
description:
"Check that all of the values are even, using the built-in $it variable",
example: "[2 4 6 8] | all ($it mod 2) == 0",
result: Some(Value::test_bool(true)),
},
Example {
description: "Check that all of the values are even, using a block",
example: "[2 4 6 8] | all {|e| $e mod 2 == 0 }",
result: Some(Value::test_bool(true)),
},
]
}
// This is almost entirely a copy-paste of `any`'s run(), so make sure any changes to this are
// reflected in the other!! (Or, you could figure out a way for both of them to use
// the same function...)
fn run(
&self,
engine_state: &EngineState,
@ -53,7 +61,6 @@ impl Command for All {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
// let predicate = &call.positional[0];
let span = call.head;
let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?;
@ -63,19 +70,24 @@ impl Command for All {
let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id);
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 ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
for value in input.into_interruptible_iter(ctrlc) {
stack.with_env(&orig_env_vars, &orig_env_hidden);
if let Some(var_id) = var_id {
stack.add_var(var_id, value);
stack.add_var(var_id, value.clone());
}
let eval = eval_block(
&engine_state,
&mut stack,
block,
PipelineData::new(span),
value.into_pipeline_data(),
call.redirect_stdout,
call.redirect_stderr,
);

View File

@ -18,7 +18,7 @@ impl Command for Any {
.required(
"predicate",
SyntaxShape::RowCondition,
"the predicate expression that should return a boolean",
"the expression, or block, that should return a boolean",
)
.category(Category::Filters)
}
@ -34,18 +34,25 @@ impl Command for Any {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Find if a service is not running",
example: "echo [[status]; [UP] [DOWN] [UP]] | any status == DOWN",
description: "Check if any row's status is the string 'DOWN'",
example: "[[status]; [UP] [DOWN] [UP]] | any status == DOWN",
result: Some(Value::test_bool(true)),
},
Example {
description: "Check if any of the values is odd",
example: "echo [2 4 1 6 8] | any ($it mod 2) == 1",
description: "Check if any of the values is odd, using the built-in $it variable",
example: "[2 4 1 6 8] | any ($it mod 2) == 1",
result: Some(Value::test_bool(true)),
},
Example {
description: "Check if any of the values are odd, using a block",
example: "[2 4 1 6 8] | any {|e| $e mod 2 == 1 }",
result: Some(Value::test_bool(true)),
},
]
}
// This is almost entirely a copy-paste of `all`'s run(), so make sure any changes to this are
// reflected in the other!! Or, you could figure out a way for both of them to use
// the same function...
fn run(
&self,
engine_state: &EngineState,
@ -62,19 +69,24 @@ impl Command for Any {
let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id);
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 ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
for value in input.into_interruptible_iter(ctrlc) {
stack.with_env(&orig_env_vars, &orig_env_hidden);
if let Some(var_id) = var_id {
stack.add_var(var_id, value);
stack.add_var(var_id, value.clone());
}
let eval = eval_block(
&engine_state,
&mut stack,
block,
PipelineData::new(span),
value.into_pipeline_data(),
call.redirect_stdout,
call.redirect_stderr,
);

View File

@ -24,6 +24,13 @@ impl Command for Append {
"Append any number of rows to a table."
}
fn extra_usage(&self) -> &str {
r#"Be aware that this command 'unwraps' lists passed to it. So, if you pass a variable to it,
and you want the variable's contents to be appended without being unwrapped, it's wise to
pre-emptively wrap the variable in a list, like so: `append [$val]`. This way, `append` will
only unwrap the outer list, and leave the variable's contents untouched."#
}
fn search_terms(&self) -> Vec<&str> {
vec!["add", "concatenate"]
}

View File

@ -40,7 +40,7 @@ impl Command for Compact {
vec![
Example {
description: "Filter out all records where 'Hello' is null (returns nothing)",
example: r#"echo [["Hello" "World"]; [$nothing 3]]| compact Hello"#,
example: r#"[["Hello" "World"]; [null 3]]| compact Hello"#,
result: Some(Value::List {
vals: vec![],
span: Span::test_data(),
@ -48,7 +48,7 @@ impl Command for Compact {
},
Example {
description: "Filter out all records where 'World' is null (Returns the table)",
example: r#"echo [["Hello" "World"]; [$nothing 3]]| compact World"#,
example: r#"[["Hello" "World"]; [null 3]]| compact World"#,
result: Some(Value::List {
vals: vec![Value::Record {
cols: vec!["Hello".into(), "World".into()],
@ -60,7 +60,7 @@ impl Command for Compact {
},
Example {
description: "Filter out all instances of nothing from a list (Returns [1,2])",
example: r#"echo [1, $nothing, 2] | compact"#,
example: r#"[1, null, 2] | compact"#,
result: Some(Value::List {
vals: vec![Value::test_int(1), Value::test_int(2)],
span: Span::test_data(),

View File

@ -50,8 +50,8 @@ impl Command for Default {
result: None, // Some(Value::test_string("abc")),
},
Example {
description: "Default the `$nothing` value in a list",
example: "[1, 2, $nothing, 4] | default 3",
description: "Replace the `null` value in a list",
example: "[1, 2, null, 4] | default 3",
result: Some(Value::List {
vals: vec![
Value::test_int(1),

View File

@ -26,7 +26,7 @@ impl Command for Drop {
}
fn usage(&self) -> &str {
"Remove the last number of rows or columns."
"Remove the last several rows of the input. Counterpart of 'skip'. Opposite of 'last'."
}
fn search_terms(&self) -> Vec<&str> {

View File

@ -16,11 +16,21 @@ impl Command for Each {
}
fn usage(&self) -> &str {
"Run a block on each element of input"
"Run a block on each row of input"
}
fn extra_usage(&self) -> &str {
r#"Since tables are lists of records, passing a table into 'each' will
iterate over each record, not necessarily each cell within it.
Avoid passing single records to this command. Since a record is a
one-row structure, 'each' will only run once, behaving similar to 'do'.
To iterate over a record's values, try converting it to a table
with 'transpose' first."#
}
fn search_terms(&self) -> Vec<&str> {
vec!["for", "loop", "iterate"]
vec!["for", "loop", "iterate", "map"]
}
fn signature(&self) -> nu_protocol::Signature {

View File

@ -15,7 +15,7 @@ impl Command for EachWhile {
}
fn usage(&self) -> &str {
"Run a block on each element of input until a $nothing is found"
"Run a block on each element of input until a null is found"
}
fn search_terms(&self) -> Vec<&str> {
@ -36,26 +36,44 @@ impl Command for EachWhile {
fn examples(&self) -> Vec<Example> {
let stream_test_1 = vec![
Value::Int {
val: 1,
val: 2,
span: Span::test_data(),
},
Value::Int {
val: 2,
val: 4,
span: Span::test_data(),
},
];
let stream_test_2 = vec![
Value::String {
val: "Output: 1".into(),
span: Span::test_data(),
},
Value::String {
val: "Output: 2".into(),
span: Span::test_data(),
},
];
vec![
Example {
example: "[1 2 3] | each while { |it| if $it < 3 {$it} else {$nothing} }",
description: "Multiplies elements in list",
example: "[1 2 3] | each while { |it| if $it < 3 { $it * 2 } else { null } }",
description: "Multiplies elements below three by two",
result: Some(Value::List {
vals: stream_test_1,
span: Span::test_data(),
}),
},
Example {
example: r#"[1 2 3] | each while -n { |it| if $it.item < 2 { $"value ($it.item) at ($it.index)!"} else { $nothing } }"#,
example: r#"[1 2 stop 3 4] | each while { |it| if $it == 'stop' { null } else { $"Output: ($it)" } }"#,
description: "Output elements till reaching 'stop'",
result: Some(Value::List {
vals: stream_test_2,
span: Span::test_data(),
}),
},
Example {
example: r#"[1 2 3] | each while -n { |it| if $it.item < 2 { $"value ($it.item) at ($it.index)!"} else { null } }"#,
description: "Iterate over each element, print the matching value and its index",
result: Some(Value::List {
vals: vec![Value::String {
@ -140,6 +158,7 @@ impl Command for EachWhile {
Err(_) => None,
}
})
.fuse()
.into_pipeline_data(ctrlc)),
PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::new(call.head)),
PipelineData::ExternalStream {
@ -198,6 +217,7 @@ impl Command for EachWhile {
Err(_) => None,
}
})
.fuse()
.into_pipeline_data(ctrlc)),
PipelineData::Value(x, ..) => {
if let Some(var) = block.signature.get_positional(0) {

View File

@ -1,13 +1,14 @@
use crate::help::highlight_search_string;
use fancy_regex::Regex;
use lscolors::{Color as LsColors_Color, Style as LsColors_Style};
use lscolors::{Color as LsColors_Color, LsColors, Style as LsColors_Style};
use nu_ansi_term::{Color, Color::Default, Style};
use nu_color_config::get_color_config;
use nu_engine::{env_to_string, eval_block, CallExt};
use nu_protocol::{
ast::Call,
engine::{CaptureBlock, Command, EngineState, Stack},
Category, Example, IntoInterruptiblePipelineData, ListStream, PipelineData, ShellError,
Category, Config, Example, IntoInterruptiblePipelineData, ListStream, PipelineData, ShellError,
Signature, Span, SyntaxShape, Value,
};
use nu_utils::get_ls_colors;
@ -67,7 +68,7 @@ impl Command for Find {
},
Example {
description: "Search for a term in a string",
example: r#"echo Cargo.toml | find toml"#,
example: r#"'Cargo.toml' | find toml"#,
result: Some(Value::test_string("Cargo.toml".to_owned()))
},
Example {
@ -278,6 +279,72 @@ fn find_with_predicate(
)
}
fn highlight_terms_in_record(
cols: &mut [String],
vals: &mut Vec<Value>,
span: &mut Span,
config: &Config,
terms: &[Value],
string_style: Style,
ls_colors: &LsColors,
) -> Value {
let mut output = vec![];
for val in vals {
let val_str = val.into_string("", config);
let lower_val = val.into_string("", config).to_lowercase();
let mut term_added_to_output = false;
for term in terms {
let term_str = term.into_string("", config);
let lower_term = term.into_string("", config).to_lowercase();
if lower_val.contains(&lower_term) {
if config.use_ls_colors {
// Get the original LS_COLORS color
let style = ls_colors.style_for_path(val_str.clone());
let ansi_style = style
.map(LsColors_Style::to_crossterm_style)
.unwrap_or_default();
let ls_colored_val = ansi_style.apply(&val_str).to_string();
let ansi_term_style = style
.map(to_nu_ansi_term_style)
.unwrap_or_else(|| string_style);
let hi =
match highlight_search_string(&ls_colored_val, &term_str, &ansi_term_style)
{
Ok(hi) => hi,
Err(_) => string_style.paint(term_str.to_string()).to_string(),
};
output.push(Value::String {
val: hi,
span: *span,
});
term_added_to_output = true;
} else {
// No LS_COLORS support, so just use the original value
let hi = match highlight_search_string(&val_str, &term_str, &string_style) {
Ok(hi) => hi,
Err(_) => string_style.paint(term_str.to_string()).to_string(),
};
output.push(Value::String {
val: hi,
span: *span,
});
}
}
}
if !term_added_to_output {
output.push(val.clone());
}
}
Value::Record {
cols: cols.to_vec(),
vals: output,
span: *span,
}
}
fn find_with_rest_and_highlight(
engine_state: &EngineState,
stack: &mut Stack,
@ -315,177 +382,129 @@ fn find_with_rest_and_highlight(
let ls_colors = get_ls_colors(ls_colors_env_str);
match input {
PipelineData::Value(_, _) => input.filter(
move |value| {
let lower_value = if let Ok(span) = value.span() {
Value::string(value.into_string("", &config).to_lowercase(), span)
} else {
value.clone()
};
lower_terms.iter().any(|term| match value {
Value::Bool { .. }
| Value::Int { .. }
| Value::Filesize { .. }
| Value::Duration { .. }
| Value::Date { .. }
| Value::Range { .. }
| Value::Float { .. }
| Value::Block { .. }
| Value::Nothing { .. }
| Value::Error { .. } => lower_value
.eq(span, term, span)
.map_or(false, |val| val.is_true()),
Value::String { .. }
| Value::List { .. }
| Value::CellPath { .. }
| Value::CustomValue { .. } => term
.r#in(span, &lower_value, span)
.map_or(false, |val| val.is_true()),
Value::Record { vals, .. } => vals.iter().any(|val| {
if let Ok(span) = val.span() {
let lower_val = Value::string(
val.into_string("", &config).to_lowercase(),
Span::test_data(),
);
term.r#in(span, &lower_val, span)
.map_or(false, |aval| aval.is_true())
} else {
term.r#in(span, val, span)
.map_or(false, |aval| aval.is_true())
}
}),
Value::Binary { .. } => false,
}) != invert
},
ctrlc,
),
PipelineData::ListStream(stream, meta) => {
Ok(ListStream::from_stream(
stream
.map(move |mut x| match &mut x {
Value::Record { cols, vals, span } => {
let mut output = vec![];
for val in vals {
let val_str = val.into_string("", &config);
let lower_val = val.into_string("", &config).to_lowercase();
let mut term_added_to_output = false;
for term in terms.clone() {
let term_str = term.into_string("", &config);
let lower_term = term.into_string("", &config).to_lowercase();
if lower_val.contains(&lower_term) {
if config.use_ls_colors {
// Get the original LS_COLORS color
let style = ls_colors.style_for_path(val_str.clone());
let ansi_style = style
.map(LsColors_Style::to_crossterm_style)
.unwrap_or_default();
let ls_colored_val =
ansi_style.apply(&val_str).to_string();
let ansi_term_style = style
.map(to_nu_ansi_term_style)
.unwrap_or_else(|| string_style);
let hi = match highlight_search_string(
&ls_colored_val,
&term_str,
&ansi_term_style,
) {
Ok(hi) => hi,
Err(_) => string_style
.paint(term_str.to_string())
.to_string(),
};
output.push(Value::String {
val: hi,
span: *span,
});
term_added_to_output = true;
} else {
// No LS_COLORS support, so just use the original value
let hi = match highlight_search_string(
&val_str,
&term_str,
&string_style,
) {
Ok(hi) => hi,
Err(_) => string_style
.paint(term_str.to_string())
.to_string(),
};
output.push(Value::String {
val: hi,
span: *span,
});
}
}
}
if !term_added_to_output {
output.push(val.clone());
}
}
Value::Record {
cols: cols.to_vec(),
vals: output,
span: *span,
}
}
_ => x,
})
.filter(move |value| {
let lower_value = if let Ok(span) = value.span() {
Value::string(
value.into_string("", &filter_config).to_lowercase(),
span,
)
} else {
value.clone()
};
lower_terms.iter().any(|term| match value {
Value::Bool { .. }
| Value::Int { .. }
| Value::Filesize { .. }
| Value::Duration { .. }
| Value::Date { .. }
| Value::Range { .. }
| Value::Float { .. }
| Value::Block { .. }
| Value::Nothing { .. }
| Value::Error { .. } => lower_value
.eq(span, term, span)
.map_or(false, |value| value.is_true()),
Value::String { .. }
| Value::List { .. }
| Value::CellPath { .. }
| Value::CustomValue { .. } => term
.r#in(span, &lower_value, span)
.map_or(false, |value| value.is_true()),
Value::Record { vals, .. } => vals.iter().any(|val| {
if let Ok(span) = val.span() {
let lower_val = Value::string(
val.into_string("", &filter_config).to_lowercase(),
Span::test_data(),
);
term.r#in(span, &lower_val, span)
.map_or(false, |value| value.is_true())
} else {
term.r#in(span, val, span)
.map_or(false, |value| value.is_true())
}
}),
Value::Binary { .. } => false,
}) != invert
}),
PipelineData::Value(_, _) => input
.map(
move |mut x| match &mut x {
Value::Record { cols, vals, span } => highlight_terms_in_record(
cols,
vals,
span,
&config,
&terms,
string_style,
&ls_colors,
),
_ => x,
},
ctrlc.clone(),
)
.into_pipeline_data(ctrlc)
.set_metadata(meta))
}
)?
.filter(
move |value| {
let lower_value = if let Ok(span) = value.span() {
Value::string(value.into_string("", &filter_config).to_lowercase(), span)
} else {
value.clone()
};
lower_terms.iter().any(|term| match value {
Value::Bool { .. }
| Value::Int { .. }
| Value::Filesize { .. }
| Value::Duration { .. }
| Value::Date { .. }
| Value::Range { .. }
| Value::Float { .. }
| Value::Block { .. }
| Value::Nothing { .. }
| Value::Error { .. } => lower_value
.eq(span, term, span)
.map_or(false, |val| val.is_true()),
Value::String { .. }
| Value::List { .. }
| Value::CellPath { .. }
| Value::CustomValue { .. } => term
.r#in(span, &lower_value, span)
.map_or(false, |val| val.is_true()),
Value::Record { vals, .. } => vals.iter().any(|val| {
if let Ok(span) = val.span() {
let lower_val = Value::string(
val.into_string("", &filter_config).to_lowercase(),
Span::test_data(),
);
term.r#in(span, &lower_val, span)
.map_or(false, |aval| aval.is_true())
} else {
term.r#in(span, val, span)
.map_or(false, |aval| aval.is_true())
}
}),
Value::Binary { .. } => false,
}) != invert
},
ctrlc,
),
PipelineData::ListStream(stream, meta) => Ok(ListStream::from_stream(
stream
.map(move |mut x| match &mut x {
Value::Record { cols, vals, span } => highlight_terms_in_record(
cols,
vals,
span,
&config,
&terms,
string_style,
&ls_colors,
),
_ => x,
})
.filter(move |value| {
let lower_value = if let Ok(span) = value.span() {
Value::string(value.into_string("", &filter_config).to_lowercase(), span)
} else {
value.clone()
};
lower_terms.iter().any(|term| match value {
Value::Bool { .. }
| Value::Int { .. }
| Value::Filesize { .. }
| Value::Duration { .. }
| Value::Date { .. }
| Value::Range { .. }
| Value::Float { .. }
| Value::Block { .. }
| Value::Nothing { .. }
| Value::Error { .. } => lower_value
.eq(span, term, span)
.map_or(false, |value| value.is_true()),
Value::String { .. }
| Value::List { .. }
| Value::CellPath { .. }
| Value::CustomValue { .. } => term
.r#in(span, &lower_value, span)
.map_or(false, |value| value.is_true()),
Value::Record { vals, .. } => vals.iter().any(|val| {
if let Ok(span) = val.span() {
let lower_val = Value::string(
val.into_string("", &filter_config).to_lowercase(),
Span::test_data(),
);
term.r#in(span, &lower_val, span)
.map_or(false, |value| value.is_true())
} else {
term.r#in(span, val, span)
.map_or(false, |value| value.is_true())
}
}),
Value::Binary { .. } => false,
}) != invert
}),
ctrlc.clone(),
)
.into_pipeline_data(ctrlc)
.set_metadata(meta)),
PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::new(span)),
PipelineData::ExternalStream {
stdout: Some(stream),

View File

@ -25,7 +25,7 @@ impl Command for First {
}
fn usage(&self) -> &str {
"Show only the first number of rows."
"Return only the first several rows of the input. Counterpart of 'last'. Opposite of 'skip'."
}
fn run(

View File

@ -25,7 +25,7 @@ impl Command for GroupBy {
}
fn usage(&self) -> &str {
"Create a new table grouped."
"Split a table into groups based on one column's values, and return a record with those groups."
}
fn run(
@ -38,16 +38,15 @@ impl Command for GroupBy {
group_by(engine_state, stack, call, input)
}
#[allow(clippy::unwrap_used)]
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "group items by column named \"type\"",
description: "Group items by the \"type\" column's values",
example: r#"ls | group-by type"#,
result: None,
},
Example {
description: "you can also group by raw values by leaving out the argument",
description: "You can also group by raw values by leaving out the argument",
example: "echo ['1' '3' '1' '3' '2' '1' '1'] | group-by",
result: Some(Value::Record {
cols: vec!["1".to_string(), "3".to_string(), "2".to_string()],
@ -211,7 +210,7 @@ pub fn data_group(
value.as_string()
};
let group = groups.entry(group_key?).or_insert(vec![]);
let group = groups.entry(group_key?).or_default();
group.push(value);
}

View File

@ -26,7 +26,7 @@ impl Command for Last {
}
fn usage(&self) -> &str {
"Show only the last number of rows."
"Return only the last several rows of the input. Counterpart of 'first'. Opposite of 'drop'."
}
fn examples(&self) -> Vec<Example> {

View File

@ -46,14 +46,14 @@ impl Command for Length {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Count the number of entries in a list",
example: "echo [1 2 3 4 5] | length",
description: "Count the number of items in a list",
example: "[1 2 3 4 5] | length",
result: Some(Value::test_int(5)),
},
Example {
description: "Count the number of columns in the calendar table",
example: "cal | length -c",
result: Some(Value::test_int(7)),
description: "Count the number of columns in a table",
example: "[{columnA: A0 columnB: B0}] | length -c",
result: Some(Value::test_int(2)),
},
]
}
@ -122,3 +122,15 @@ fn getcol(
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Length {})
}
}

View File

@ -129,7 +129,7 @@ impl Command for Lines {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Split multi-line string into lines",
example: "echo $'two(char nl)lines' | lines",
example: r#"echo $"two\nlines" | lines"#,
result: Some(Value::List {
vals: vec![Value::test_string("two"), Value::test_string("lines")],
span: Span::test_data(),
@ -252,3 +252,15 @@ impl RawStreamLinesAdapter {
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Lines {})
}
}

View File

@ -2,8 +2,8 @@ use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, SyntaxShape, Value,
Category, Example, FromValue, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Span, SyntaxShape, Value,
};
#[derive(Clone)]
@ -15,15 +15,25 @@ impl Command for Merge {
}
fn usage(&self) -> &str {
"Merge a table into an input table"
"Merge the input with a record or table, overwriting values in matching columns."
}
fn extra_usage(&self) -> &str {
r#"You may provide a column structure to merge, or a block
that generates a column structure.
When merging tables, row 0 of the input table is overwritten
with values from row 0 of the provided table, then
repeating this process with row 1, and so on."#
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("merge")
.required(
"block",
SyntaxShape::Block(Some(vec![])),
"the block to run and merge into the table",
// Both this and `update` should have a shape more like <record> | <table> | <block> than just <any>. -Leon 2022-10-27
SyntaxShape::Any,
"the new value to merge with, or a block that produces it",
)
.category(Category::Filters)
}
@ -32,7 +42,7 @@ impl Command for Merge {
vec![
Example {
example: "[a b c] | wrap name | merge { [1 2 3] | wrap index }",
description: "Merge an index column into the input table",
description: "Add an 'index' column to the input table",
result: Some(Value::List {
vals: vec![
Value::test_record(
@ -52,7 +62,7 @@ impl Command for Merge {
}),
},
Example {
example: "{a: 1, b: 2} | merge { {c: 3} }",
example: "{a: 1, b: 2} | merge {c: 3}",
description: "Merge two records",
result: Some(Value::test_record(
vec!["a", "b", "c"],
@ -60,8 +70,8 @@ impl Command for Merge {
)),
},
Example {
example: "{a: 1, b: 3} | merge { {b: 2, c: 4} }",
description: "Merge two records with overlap key",
example: "{a: 1, b: 3} | merge { { b: 2 } | merge { c: 4 } }",
description: "Merge records, overwriting overlapping values",
result: Some(Value::test_record(
vec!["a", "b", "c"],
vec![Value::test_int(1), Value::test_int(2), Value::test_int(4)],
@ -77,35 +87,47 @@ impl Command for Merge {
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let block: CaptureBlock = call.req(engine_state, stack, 0)?;
let mut stack = stack.captures_to_stack(&block.captures);
let replacement: Value = call.req(engine_state, stack, 0)?;
let metadata = input.metadata();
let ctrlc = engine_state.ctrlc.clone();
let block = engine_state.get_block(block.block_id);
let call = call.clone();
let result = eval_block(
engine_state,
&mut stack,
block,
PipelineData::new(call.head),
call.redirect_stdout,
call.redirect_stderr,
);
let argument_was_block = replacement.as_block().is_ok();
let table = match result {
Ok(res) => res,
Err(e) => return Err(e),
let merge_value: Value = if argument_was_block {
// When given a block, run it to obtain the matching value.
let capture_block: CaptureBlock = FromValue::from_value(&replacement)?;
let mut stack = stack.captures_to_stack(&capture_block.captures);
stack.with_env(&stack.env_vars.clone(), &stack.env_hidden.clone());
let block = engine_state.get_block(capture_block.block_id);
let result = eval_block(
engine_state,
&mut stack,
block,
PipelineData::new(call.head),
call.redirect_stdout,
call.redirect_stderr,
);
match result {
Ok(res) => res.into_value(call.head),
Err(e) => return Err(e),
}
} else {
// Otherwise, just use the passed-in value as-is.
replacement
};
match (&input, &table) {
match (&input, merge_value) {
// table (list of records)
(
PipelineData::Value(Value::List { .. }, ..) | PipelineData::ListStream { .. },
PipelineData::Value(Value::List { .. }, ..) | PipelineData::ListStream { .. },
Value::List { vals, .. },
) => {
let mut table_iter = table.into_iter();
let mut table_iter = vals.into_iter();
let res =
input
@ -147,14 +169,11 @@ impl Command for Merge {
},
..,
),
PipelineData::Value(
Value::Record {
cols: to_merge_cols,
vals: to_merge_vals,
..
},
..,
),
Value::Record {
cols: to_merge_cols,
vals: to_merge_vals,
..
},
) => {
let (cols, vals) = do_merge(
(inp_cols.to_vec(), inp_vals.to_vec()),
@ -167,21 +186,29 @@ impl Command for Merge {
}
.into_pipeline_data())
}
(_, PipelineData::Value(val, ..)) | (PipelineData::Value(val, ..), _) => {
let span = if val.span()? == Span::test_data() {
(PipelineData::Value(val, ..), merge_value) => {
// Only point the "value originates here" arrow at the merge value
// if it was generated from a block. Otherwise, point at the pipeline value. -Leon 2022-10-27
let span = if argument_was_block {
if merge_value.span()? == Span::test_data() {
Span::new(call.head.start, call.head.start)
} else {
merge_value.span()?
}
} else if val.span()? == Span::test_data() {
Span::new(call.head.start, call.head.start)
} else {
val.span()?
};
Err(ShellError::PipelineMismatch(
"record or table in both the input and the argument block".to_string(),
"input, and argument, to be both record or both table".to_string(),
call.head,
span,
))
}
_ => Err(ShellError::PipelineMismatch(
"record or table in both the input and the argument block".to_string(),
"input, and argument, to be both record or both table".to_string(),
call.head,
Span::new(call.head.start, call.head.start),
)),

View File

@ -65,7 +65,6 @@ impl Command for ParEach {
let numbered = call.has_flag("numbered");
let metadata = input.metadata();
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
let block_id = capture_block.block_id;
let mut stack = stack.captures_to_stack(&capture_block.captures);
let span = call.head;
@ -107,7 +106,7 @@ impl Command for ParEach {
let val_span = x.span();
match eval_block(
&engine_state,
engine_state,
&mut stack,
block,
x.into_pipeline_data(),
@ -159,7 +158,7 @@ impl Command for ParEach {
let val_span = x.span();
match eval_block(
&engine_state,
engine_state,
&mut stack,
block,
x.into_pipeline_data(),
@ -210,7 +209,7 @@ impl Command for ParEach {
let val_span = x.span();
match eval_block(
&engine_state,
engine_state,
&mut stack,
block,
x.into_pipeline_data(),
@ -269,7 +268,7 @@ impl Command for ParEach {
}
match eval_block(
&engine_state,
engine_state,
&mut stack,
block,
x.into_pipeline_data(),
@ -294,7 +293,7 @@ impl Command for ParEach {
}
eval_block(
&engine_state,
engine_state,
&mut stack,
block,
x.into_pipeline_data(),

View File

@ -28,6 +28,13 @@ impl Command for Prepend {
"Prepend any number of rows to a table."
}
fn extra_usage(&self) -> &str {
r#"Be aware that this command 'unwraps' lists passed to it. So, if you pass a variable to it,
and you want the variable's contents to be prepended without being unwrapped, it's wise to
pre-emptively wrap the variable in a list, like so: `prepend [$val]`. This way, `prepend` will
only unwrap the outer list, and leave the variable's contents untouched."#
}
fn search_terms(&self) -> Vec<&str> {
vec!["add", "concatenate"]
}

View File

@ -41,7 +41,7 @@ impl Command for Shuffle {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Shuffle rows randomly (execute it several times and see the difference)",
example: r#"echo [[version patch]; [1.0.0 false] [3.0.1 true] [2.0.0 false]] | shuffle"#,
example: r#"[[version patch]; [1.0.0 false] [3.0.1 true] [2.0.0 false]] | shuffle"#,
result: None,
}]
}

View File

@ -23,7 +23,11 @@ impl Command for Skip {
}
fn usage(&self) -> &str {
"Skip the first n elements of the input."
"Skip the first several rows of the input. Counterpart of 'drop'. Opposite of 'first'."
}
fn extra_usage(&self) -> &str {
r#"To skip specific numbered rows, try 'drop nth'. To skip specific named columns, try 'reject'."#
}
fn search_terms(&self) -> Vec<&str> {

View File

@ -35,7 +35,6 @@ impl Command for SplitBy {
split_by(engine_state, stack, call, input)
}
#[allow(clippy::unwrap_used)]
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "split items by column named \"lang\"",

View File

@ -40,7 +40,7 @@ impl Command for Update {
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
upsert(engine_state, stack, call, input)
update(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
@ -70,7 +70,7 @@ impl Command for Update {
}
}
fn upsert(
fn update(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
@ -138,6 +138,8 @@ fn upsert(
for idx in 0..*val {
if let Some(v) = input.next() {
pre_elems.push(v);
} else if idx == 0 {
return Err(ShellError::AccessEmptyContent(*span));
} else {
return Err(ShellError::AccessBeyondEnd(idx - 1, *span));
}

View File

@ -1,9 +1,9 @@
use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::{Call, CellPath};
use nu_protocol::ast::{Call, CellPath, PathMember};
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, FromValue, IntoPipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Value,
Category, Example, FromValue, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Span, SyntaxShape, Value,
};
#[derive(Clone)]
@ -64,7 +64,29 @@ impl Command for Upsert {
description: "Use in block form for more involved updating logic",
example: "echo [[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | upsert authors {|a| $a.authors | str join ','}",
result: Some(Value::List { vals: vec![Value::Record { cols: vec!["project".into(), "authors".into()], vals: vec![Value::test_string("nu"), Value::test_string("Andrés,JT,Yehuda")], span: Span::test_data()}], span: Span::test_data()}),
}]
},
Example {
description: "Upsert an int into a list, updating an existing value based on the index",
example: "[1 2 3] | upsert 0 2",
result: Some(Value::List {
vals: vec![Value::test_int(2), Value::test_int(2), Value::test_int(3)],
span: Span::test_data(),
}),
},
Example {
description: "Upsert an int into a list, inserting a new value based on the index",
example: "[1 2 3] | upsert 3 4",
result: Some(Value::List {
vals: vec![
Value::test_int(1),
Value::test_int(2),
Value::test_int(3),
Value::test_int(4),
],
span: Span::test_data(),
}),
},
]
}
}
@ -129,6 +151,28 @@ fn upsert(
ctrlc,
)
} else {
if let Some(PathMember::Int { val, span }) = cell_path.members.get(0) {
let mut input = input.into_iter();
let mut pre_elems = vec![];
for idx in 0..*val {
if let Some(v) = input.next() {
pre_elems.push(v);
} else {
return Err(ShellError::AccessBeyondEnd(idx - 1, *span));
}
}
// Skip over the replaced value
let _ = input.next();
return Ok(pre_elems
.into_iter()
.chain(vec![replacement])
.chain(input)
.into_pipeline_data(ctrlc));
}
input.map(
move |mut input| {
let replacement = replacement.clone();

View File

@ -4,7 +4,7 @@ use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, SyntaxShape, Value,
Signature, SyntaxShape, Value,
};
#[derive(Clone)]
@ -238,23 +238,37 @@ impl Command for Where {
example: "ls | where modified >= (date now) - 2wk",
result: None,
},
Example {
description: "Get all numbers above 3 with an existing block condition",
example: "let a = {$in > 3}; [1, 2, 5, 6] | where -b $a",
result: Some(Value::List {
vals: vec![
Value::Int {
val: 5,
span: Span::test_data(),
},
Value::Int {
val: 6,
span: Span::test_data(),
},
],
span: Span::test_data(),
}),
},
// TODO: This should work but does not. (Note that `Let` must be present in the working_set in `example_test.rs`).
// See https://github.com/nushell/nushell/issues/7034
// Example {
// description: "Get all numbers above 3 with an existing block condition",
// example: "let a = {$in > 3}; [1, 2, 5, 6] | where -b $a",
// result: Some(Value::List {
// vals: vec![
// Value::Int {
// val: 5,
// span: Span::test_data(),
// },
// Value::Int {
// val: 6,
// span: Span::test_data(),
// },
// ],
// span: Span::test_data(),
// }),
// },
]
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Where {})
}
}

View File

@ -1,6 +1,7 @@
use nu_engine::get_full_help;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, PipelineData, ShellError, Signature};
use nu_protocol::{Category, IntoPipelineData, PipelineData, ShellError, Signature, Value};
#[derive(Clone)]
pub struct From;
@ -20,11 +21,15 @@ impl Command for From {
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, ShellError> {
Ok(PipelineData::new(call.head))
Ok(Value::String {
val: get_full_help(&From.signature(), &From.examples(), engine_state, stack),
span: call.head,
}
.into_pipeline_data())
}
}

View File

@ -84,21 +84,20 @@ impl Command for FromJson {
// TODO: turn this into a structured underline of the nu_json error
if call.has_flag("objects") {
#[allow(clippy::needless_collect)]
let lines: Vec<String> = string_input.lines().map(|x| x.to_string()).collect();
Ok(lines
.into_iter()
let converted_lines: Vec<Value> = string_input
.lines()
.filter_map(move |x| {
if x.trim() == "" {
None
} else {
match convert_string_to_value(x, span) {
match convert_string_to_value(x.to_string(), span) {
Ok(v) => Some(v),
Err(error) => Some(Value::Error { error }),
}
}
})
.into_pipeline_data(engine_state.ctrlc.clone()))
.collect();
Ok(converted_lines.into_pipeline_data(engine_state.ctrlc.clone()))
} else {
Ok(convert_string_to_value(string_input, span)?.into_pipeline_data())
}

View File

@ -141,7 +141,7 @@ fn parse_aligned_columns<'a>(
let parse_without_headers = |ls: Vec<&str>| {
let mut indices = ls
.iter()
.flat_map(|s| find_indices(*s))
.flat_map(|s| find_indices(s))
.collect::<Vec<usize>>();
indices.sort_unstable();

View File

@ -48,27 +48,27 @@ impl Command for FromTsv {
vec![
Example {
description: "Create a tsv file with header columns and open it",
example: r#"echo $'c1(char tab)c2(char tab)c3(char nl)1(char tab)2(char tab)3' | save tsv-data | open tsv-data | from tsv"#,
example: r#"$'c1(char tab)c2(char tab)c3(char nl)1(char tab)2(char tab)3' | save tsv-data | open tsv-data | from tsv"#,
result: None,
},
Example {
description: "Create a tsv file without header columns and open it",
example: r#"echo $'a1(char tab)b1(char tab)c1(char nl)a2(char tab)b2(char tab)c2' | save tsv-data | open tsv-data | from tsv -n"#,
example: r#"$'a1(char tab)b1(char tab)c1(char nl)a2(char tab)b2(char tab)c2' | save tsv-data | open tsv-data | from tsv -n"#,
result: None,
},
Example {
description: "Create a tsv file without header columns and open it, removing all unnecessary whitespaces",
example: r#"echo $'a1(char tab)b1(char tab)c1(char nl)a2(char tab)b2(char tab)c2' | save tsv-data | open tsv-data | from tsv --trim all"#,
example: r#"$'a1(char tab)b1(char tab)c1(char nl)a2(char tab)b2(char tab)c2' | save tsv-data | open tsv-data | from tsv --trim all"#,
result: None,
},
Example {
description: "Create a tsv file without header columns and open it, removing all unnecessary whitespaces in the header names",
example: r#"echo $'a1(char tab)b1(char tab)c1(char nl)a2(char tab)b2(char tab)c2' | save tsv-data | open tsv-data | from tsv --trim headers"#,
example: r#"$'a1(char tab)b1(char tab)c1(char nl)a2(char tab)b2(char tab)c2' | save tsv-data | open tsv-data | from tsv --trim headers"#,
result: None,
},
Example {
description: "Create a tsv file without header columns and open it, removing all unnecessary whitespaces in the field values",
example: r#"echo $'a1(char tab)b1(char tab)c1(char nl)a2(char tab)b2(char tab)c2' | save tsv-data | open tsv-data | from tsv --trim fields"#,
example: r#"$'a1(char tab)b1(char tab)c1(char nl)a2(char tab)b2(char tab)c2' | save tsv-data | open tsv-data | from tsv --trim fields"#,
result: None,
},
]

View File

@ -1,6 +1,7 @@
use nu_engine::get_full_help;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, PipelineData, ShellError, Signature};
use nu_protocol::{Category, IntoPipelineData, PipelineData, ShellError, Signature, Value};
#[derive(Clone)]
pub struct To;
@ -20,11 +21,15 @@ impl Command for To {
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, ShellError> {
Ok(PipelineData::new(call.head))
Ok(Value::String {
val: get_full_help(&To.signature(), &To.examples(), engine_state, stack),
span: call.head,
}
.into_pipeline_data())
}
}

View File

@ -270,15 +270,10 @@ fn get_output_string(
output_string.push_str("\n|");
#[allow(clippy::needless_range_loop)]
for i in 0..headers.len() {
for &col_width in column_widths.iter().take(headers.len()) {
if pretty {
output_string.push(' ');
output_string.push_str(&get_padded_string(
String::from("-"),
column_widths[i],
'-',
));
output_string.push_str(&get_padded_string(String::from("-"), col_width, '-'));
output_string.push(' ');
} else {
output_string.push('-');

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