Compare commits

...

138 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
JT
9ef65dcd69 Bump to 0.70 (#6800) 2022-10-19 07:13:36 +13:00
JT
f99c002426 Fix let-env in banner (#6795) 2022-10-18 22:42:00 +13:00
f0420c5a6c Pin reedline to the 0.13.0 release (#6789)
See the release notes:

https://github.com/nushell/reedline/releases/tag/v0.13.0
2022-10-17 23:45:28 +02:00
46eec5e3a2 Tolerate more tty acquisition failures in non-interactive mode, fixes #6719 (#6779) 2022-10-17 21:08:25 +02:00
378248341e Update README.md (#6782)
Fixed a very small inconsistency.
2022-10-17 06:23:11 -05:00
803f9d4daf Upgrade reedline to latest dev version (#6778)
* Reorder conditional deps for readability

* Pull reedline from the most recent main branch

* Map 'Submit' and 'SubmitOrNewline' events
  Introduced by nushell/reedline#490
2022-10-16 23:51:15 +02:00
ce809881eb Rename query dfr -> query df (#6777) 2022-10-16 21:04:48 +02:00
1a99893e2d Add documentation requirement to PR template (#6749) 2022-10-16 18:19:54 +03:00
ec8e57cde9 Add search terms to roll commands (#6761) 2022-10-16 13:04:22 +02:00
a498234f1d fix stdout hangged on (#6715) 2022-10-15 14:29:29 -05:00
de77cb0cc4 add filesize_metric comment (#6760) 2022-10-15 14:26:18 -05:00
7d5d53cf85 Add search terms to arg dataframe commands (#6724)
* added search terms for arg prefixed dataframe commands

* remove search terms that already produce results
2022-10-15 12:49:09 -05:00
1572808adb Filter out empty glob patterns to "glob" command (#6707)
* Filter out empty glob patterns

An empty argument to the "glob" command will now produce an empty result.
Working towards nushell/nushell#6653.

* Run `cargo fmt --all`

Just autoformatted the repo so that CI passes and we have a consistent code
format across modules.

* Treat empty glob argument as error

The glob command will now report an empty string argument as an error instead
of silently ignoring it.

See https://github.com/nushell/nushell/pull/6707#discussion_r993345013.

* Add tests for glob command

Two small tests for the glob command, one to check that the empty string errors
it, and another to sanity check the '*' glob, have been added.

* Rename glob sanity check star test

Co-authored-by: Kyle Anderson <kyle.anderson@uwaterloo.ca>
2022-10-15 18:00:38 +02:00
9d77e3fc7c Improve erroring of config nu and config env (#6730)
* improve errors for `config nu` and `config env`

* fix tests
2022-10-15 08:28:54 -05:00
e22f2e9f13 window --remainder (#6738)
* Implement window remainder, and save allocation

* Fallible memory reservation
2022-10-15 08:06:54 -05:00
4ffa4ac42a Delete out.log (#6731) 2022-10-15 07:37:57 -05:00
da6f548dfd Remove unnecessary clone (#6729) 2022-10-14 17:13:24 -05:00
JT
7532991544 Allow auto-cd to work with backticks (#6728) 2022-10-15 10:37:31 +13:00
b7f47317c2 Fix quadratic time complexity with large strides (#6727) 2022-10-14 15:17:47 -05:00
5849e4f6e3 let alias list aliases (#6717)
* let `alias` list aliases

* fix highlighting

* Update parse_keywords.rs
2022-10-14 21:51:44 +02:00
868d94f573 Add search terms for export commands (#6722)
Contributes to https://github.com/nushell/nushell/issues/5093
2022-10-14 12:02:22 -05:00
1344ae3a65 add the ability to convert durations (#6723)
* add the ability to convert durations

* add conversion to floats if necessary
2022-10-14 11:46:48 -05:00
804b155035 Add search terms for uppercase (#6720)
* Add search terms for uppercase

* Add search terms

* Add search terms

* Change to parse

* Add search terms for from

* Add search terms for to

* Remove duplicate function

* Remove duplication of search terms

* Remove search term
2022-10-14 05:36:31 -05:00
9446e3960b Fix invalid variable name in input command docs (#6716) 2022-10-13 12:42:24 -05:00
90ba39184a allow for $in to affect environment (#6649)
* allow for `$in` to affect environment

* fix for vars, overlays, env_hidden

* fmt

* carry over variables properly

* add test

* modify name, remove redundant

* fmt
2022-10-13 12:04:34 +03:00
d40a73aafe Backport fixes from nushell/nushell.github.io#633 (#6712)
Co-authored-by: Eric Hodel <drbrain@segment7.net>

Co-authored-by: Eric Hodel <drbrain@segment7.net>
2022-10-12 19:14:16 +02:00
5815f122ed avoid freeze when capturing external stderr (#6700)
* avoid freeze when capturing external stderr

* try replace from sh to bash

* change description

* fmt code
2022-10-12 08:41:20 -05:00
0bbb3a20df Fix ex. completion git push --force-with-lease (#6702)
The `--force-with-lease` flag was given as requiring an additional string which is not true.

Fixes #6644 

for this to take effect you need to update your `config.nu`
2022-10-11 12:41:50 +02:00
1998bce19f avoid freeze for table print (#6688)
* avoid freeze for table print

* make failed_with_proper_exit_code work again

* add test case for table

* fix un-used import on windows
2022-10-10 07:32:55 -05:00
2f1711f783 return gid and uid in numbers if name not found (#6684)
* return git and uid in numbers if name not found

* fmt
2022-10-10 14:29:16 +02:00
34c8b276ab Return Error on str replace RegEx parse fail (#6695) 2022-10-10 07:27:01 -05:00
fde56cfe99 upgrade num-format (#6694) 2022-10-10 06:25:57 -05:00
118033e4a5 don't attempt to eval and record down if the repl line is empty (#6674) 2022-10-08 16:38:35 -05:00
7910d20e50 add a new command to query the registry on windows (#6670)
* add a new command to query the registry on windows

* cross platform tweaks

* return nushell datatype

* change visibility of exec and registry commands
2022-10-07 13:54:36 -05:00
e1d5180e6d Update nushell version for release workflow (#6666) 2022-10-05 22:10:25 +08:00
79ce13abef To nuon escapes (#6660)
* Add tests for "to nuon" escaping handling

* Fix "to nuon" not escaping double quotations

* Fix "to nuon" double backslash

Fix value_to_string_without_quotes leaving escaped backslash in
non-quoted strings
2022-10-04 06:25:21 -05:00
5921c19bc0 WIP/ Checkout to new tabled (#6286)
* nu-table/ Use latest tabled

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

* nu-table/ Fix first column alignment

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

* nu-table: Fix cargo clippy

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

* nu-table: Fix color issue

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

* nu-table: Fix footer row

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

* nu-table: Bump tabled

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

* nu-table: Bump tabled

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

* nu-table: Bump tabled

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

* Update

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

* nu-table/ Update

* Use latest tabled

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

* Add optional -e, -c argument to `table` command for different view

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

* Fix clippy

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

* Fix clippy

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

* Update

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

* Fix cargo clippy

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

* Fix tests

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

* nu-table: Add footer into -e/c mode

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

* Publish new expand mode

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

* Add width ctrl for Expand mode

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

* Refactorings

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

* Refactorings

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

* Add tests

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

* Add tests

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

* Merge with main

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

* Fix clippy

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

* Fix tests

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

* Fix tests

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

* Bump tabled

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

* Add record expand and fix empty list issue

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

* refactoring

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-10-03 11:40:16 -05:00
e629ef203a nu-cli: external completer precedence before file (#6652) 2022-10-01 07:24:22 -05:00
5959d1366a Remove unnecessary flags from term size (#6651)
The columns and rows can be obtained individually using

(term size).columns
(term size).rows
2022-10-01 07:00:54 -05:00
530ff3893e Eval external command result immediately when using do command with -c (#6645)
* make capture error works better in do command

* remove into string test because we have no way to generate Value::Error for now
2022-09-30 07:14:02 -05:00
6f59167960 Make semicolon works better for internal commands (#6643)
* make semicolon works with some internal command like do

* refactor, make consume external result logic out of eval_external

* update comment
2022-09-30 07:13:46 -05:00
ca715bb929 tweak the banner message and make the time more accurate (#6641) 2022-09-29 14:07:32 -05:00
4af0a6a3fa Foreground process group management, again (#6584)
* Revert "Revert "Try again: in unix like system, set foreground process while running external command (#6273)" (#6542)"

This reverts commit 2bb367f570.

* Make foreground job control hopefully work correctly

These changes are mostly inspired by the glibc manual.

* Fix typo in external command description

* Only restore tty control to shell when no fg procs are left; reuse pgrp

* Rework terminal acquirement code to be like fish

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-09-29 13:37:48 -05:00
6486364610 changed the way durations and filesizes are parsed (#6640) 2022-09-29 13:24:17 -05:00
6aa8a0073b add better description to table_index_mode (#6637) 2022-09-29 06:26:01 -05:00
f5e1b08e6a ensure Operator::And errors out with incompatible types (#6638) 2022-09-29 06:17:21 -05:00
7b9ad9d2e5 Fix issue 6596 (#6603)
* Fix issue 6596

* add two unit tests

* fix pipe

* add cp test

* fix test on windows
2022-09-29 10:43:58 +02:00
1a3762b905 prevent alias name from being filesize or number (#6595)
* prevent alias name from being filesize or number

* add test

* fmt
2022-09-28 17:08:38 -05:00
32fbcf39cc make first behave same way as last: always return list when with number argument (#6616)
* make `first` behave same way as `last`

* better behaviour

* fix tests

* add tests
2022-09-28 17:08:17 -05:00
dd578926c3 add some float operations with filesize (#6618)
* add some float operations with filesize

* more changes

* update return values on filesize-filesize, duration-duration

* missed filesize in floordiv

* missed float * duration
2022-09-28 17:07:50 -05:00
5c99921e15 Table indexes (#6620)
* Table indexes

* Renamed to `show_table_indexes`

* Renamed to `table_index_mode`
2022-09-28 17:07:33 -05:00
d2e4f03d19 update and fix python plugin example (#6633)
* update and fix python plugin example

* update comment
2022-09-28 17:06:43 -05:00
23bba9935f bump to dev version 0.69.2 (#6635) 2022-09-28 17:06:21 -05:00
JT
8a5abc7afc bump to 0.69.1 (#6631) 2022-09-28 15:48:01 +13:00
JT
ec711cb79d remove -d and -t from touch (#6629)
* remove -d and -t from touch

* remove unused test import
2022-09-28 13:48:34 +13:00
JT
f2ad7fae1f bump to updated reedline (#6626) 2022-09-28 12:08:42 +13:00
JT
13a4474512 Update Cargo.toml 2022-09-28 12:02:39 +13:00
298 changed files with 8499 additions and 5855 deletions

View File

@ -1,17 +1,26 @@
# Description # 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. # Tests + Formatting
- [ ] 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. 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: 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 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 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 test --workspace --features=extra` to check that all tests pass
# After Submitting
* 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 NUSHELL_CARGO_TARGET: ci
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup Rust toolchain - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
# 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: Rustfmt - name: Rustfmt
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1.0.1
with: with:
command: fmt command: fmt
args: --all -- --check args: --all -- --check
- name: Clippy - name: Clippy
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1.0.1
with: with:
command: clippy command: clippy
args: --features=extra --workspace --exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect 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 }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup Rust toolchain - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
# 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: Tests - name: Tests
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1.0.1
with: with:
command: test command: test
args: --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }} args: --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
@ -108,28 +89,19 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup Rust toolchain - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
# 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: Install Nushell - name: Install Nushell
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1.0.1
with: with:
command: install command: install
args: --path=. --profile ci --no-default-features args: --path=. --profile ci --no-default-features
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v2 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.10"
@ -159,24 +131,19 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup Rust toolchain - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
# makes ci use rust-toolchain.toml
# with:
# profile: minimal
# toolchain: ${{ matrix.rust }}
# override: true
- name: Clippy - name: Clippy
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1.0.1
with: with:
command: clippy command: clippy
args: --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect args: --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
- name: Tests - name: Tests
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1.0.1
with: with:
command: test command: test
args: --profile ci --package nu_plugin_* args: --profile ci --package nu_plugin_*

View File

@ -76,9 +76,9 @@ cp -v README.release.txt $'($dist)/README.txt'
$'(char nl)Check binary release version detail:'; hr-line $'(char nl)Check binary release version detail:'; hr-line
let ver = if $os == 'windows-latest' { let ver = if $os == 'windows-latest' {
(do -i { ./output/nu.exe -c 'version' }) | str collect (do -i { ./output/nu.exe -c 'version' }) | str join
} else { } else {
(do -i { ./output/nu -c 'version' }) | str collect (do -i { ./output/nu -c 'version' }) | str join
} }
if ($ver | str trim | is-empty) { if ($ver | str trim | is-empty) {
$'(ansi r)Incompatible nu binary...(ansi reset)' $'(ansi r)Incompatible nu binary...(ansi reset)'
@ -90,10 +90,16 @@ if ($ver | str trim | is-empty) {
cd $dist; $'(char nl)Creating release archive...'; hr-line cd $dist; $'(char nl)Creating release archive...'; hr-line
if $os in ['ubuntu-latest', 'macos-latest'] { 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' mkdir $dest
tar czf $archive * $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 print $'archive: ---> ($archive)'; ls $archive
echo $'::set-output name=archive::($archive)' echo $'::set-output name=archive::($archive)'

View File

@ -72,7 +72,7 @@ jobs:
- name: Setup Nushell - name: Setup Nushell
uses: hustcer/setup-nu@v2.1 uses: hustcer/setup-nu@v2.1
with: with:
version: 0.68.0 version: 0.69.1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

4
.gitignore vendored
View File

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

View File

@ -1,8 +1,23 @@
# Contributing # 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 ## Developing

967
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,10 +8,9 @@ exclude = ["images"]
homepage = "https://www.nushell.sh" homepage = "https://www.nushell.sh"
license = "MIT" license = "MIT"
name = "nu" name = "nu"
readme = "README.md"
repository = "https://github.com/nushell/nushell" repository = "https://github.com/nushell/nushell"
rust-version = "1.60" rust-version = "1.60"
version = "0.69.0" version = "0.71.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -37,23 +36,23 @@ chrono = { version = "0.4.21", features = ["serde"] }
crossterm = "0.24.0" crossterm = "0.24.0"
ctrlc = "3.2.1" ctrlc = "3.2.1"
log = "0.4" log = "0.4"
miette = "5.1.0" miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
nu-ansi-term = "0.46.0" nu-ansi-term = "0.46.0"
nu-cli = { path="./crates/nu-cli", version = "0.69.0" } nu-cli = { path="./crates/nu-cli", version = "0.71.0" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.69.0" } nu-color-config = { path = "./crates/nu-color-config", version = "0.71.0" }
nu-command = { path="./crates/nu-command", version = "0.69.0" } nu-command = { path="./crates/nu-command", version = "0.71.0" }
nu-engine = { path="./crates/nu-engine", version = "0.69.0" } nu-engine = { path="./crates/nu-engine", version = "0.71.0" }
nu-json = { path="./crates/nu-json", version = "0.69.0" } nu-json = { path="./crates/nu-json", version = "0.71.0" }
nu-parser = { path="./crates/nu-parser", version = "0.69.0" } nu-parser = { path="./crates/nu-parser", version = "0.71.0" }
nu-path = { path="./crates/nu-path", version = "0.69.0" } nu-path = { path="./crates/nu-path", version = "0.71.0" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.69.0" } nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.71.0" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.69.0" } nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.71.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.69.0" } nu-protocol = { path = "./crates/nu-protocol", version = "0.71.0" }
nu-system = { path = "./crates/nu-system", version = "0.69.0" } nu-system = { path = "./crates/nu-system", version = "0.71.0" }
nu-table = { path = "./crates/nu-table", version = "0.69.0" } nu-table = { path = "./crates/nu-table", version = "0.71.0" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.69.0" } nu-term-grid = { path = "./crates/nu-term-grid", version = "0.71.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.69.0" } nu-utils = { path = "./crates/nu-utils", version = "0.71.0" }
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]} reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
rayon = "1.5.1" rayon = "1.5.1"
is_executable = "1.0.1" is_executable = "1.0.1"
@ -65,8 +64,16 @@ time = "0.3.12"
openssl = { version = "0.10.38", features = ["vendored"], optional = true } openssl = { version = "0.10.38", features = ["vendored"], optional = true }
signal-hook = { version = "0.3.14", default-features = false } signal-hook = { version = "0.3.14", default-features = false }
[target.'cfg(windows)'.build-dependencies]
winres = "0.1"
[target.'cfg(target_family = "unix")'.dependencies]
nix = { version = "0.25", default-features = false, features = ["signal", "process", "fs", "term"]}
atty = "0.2"
[dev-dependencies] [dev-dependencies]
nu-test-support = { path="./crates/nu-test-support", version = "0.69.0" } nu-test-support = { path="./crates/nu-test-support", version = "0.71.0" }
tempfile = "3.2.0" tempfile = "3.2.0"
assert_cmd = "2.0.2" assert_cmd = "2.0.2"
pretty_assertions = "1.0.0" pretty_assertions = "1.0.0"
@ -75,9 +82,6 @@ hamcrest2 = "0.3.0"
rstest = {version = "0.15.0", default-features = false} rstest = {version = "0.15.0", default-features = false}
itertools = "0.10.3" itertools = "0.10.3"
[target.'cfg(windows)'.build-dependencies]
winres = "0.1"
[features] [features]
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"] plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
extra = ["default", "dataframe", "database"] extra = ["default", "dataframe", "database"]
@ -123,5 +127,7 @@ debug = false
name = "nu" name = "nu"
path = "src/main.rs" path = "src/main.rs"
# To use a development version of a dependency please use a global override here
# changing versions in each sub-crate of the workspace is tedious
[patch.crates-io] [patch.crates-io]
reedline = { git = "https://github.com/nushell/reedline", branch = "main" } # reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" }

View File

@ -71,7 +71,7 @@ Additionally, commands can output structured data (you can think of this as a th
Commands that work in the pipeline fit into one of three categories: Commands that work in the pipeline fit into one of three categories:
- Commands that produce a stream (e.g., `ls`) - Commands that produce a stream (e.g., `ls`)
- Commands that filter a stream (eg, `where type == "dir"`) - Commands that filter a stream (e.g., `where type == "dir"`)
- Commands that consume the output of the pipeline (e.g., `table`) - Commands that consume the output of the pipeline (e.g., `table`)
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right. Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.

View File

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

View File

@ -15,7 +15,6 @@ pub fn evaluate_commands(
engine_state: &mut EngineState, engine_state: &mut EngineState,
stack: &mut Stack, stack: &mut Stack,
input: PipelineData, input: PipelineData,
is_perf_true: bool,
table_mode: Option<Value>, table_mode: Option<Value>,
) -> Result<Option<i64>> { ) -> Result<Option<i64>> {
// Translate environment variables from Strings to Values // 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) Ok(exit_code)
} }

View File

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

View File

@ -60,10 +60,10 @@ impl NuCompleter {
fn external_completion( fn external_completion(
&self, &self,
block_id: BlockId, block_id: BlockId,
spans: Vec<String>, spans: &[String],
offset: usize, offset: usize,
span: Span, span: Span,
) -> Vec<Suggestion> { ) -> Option<Vec<Suggestion>> {
let stack = self.stack.clone(); let stack = self.stack.clone();
let block = self.engine_state.get_block(block_id); let block = self.engine_state.get_block(block_id);
let mut callee_stack = stack.gather_captures(&block.captures); let mut callee_stack = stack.gather_captures(&block.captures);
@ -75,9 +75,9 @@ impl NuCompleter {
var_id, var_id,
Value::List { Value::List {
vals: spans vals: spans
.into_iter() .iter()
.map(|it| Value::String { .map(|it| Value::String {
val: it, val: it.to_string(),
span: Span::unknown(), span: Span::unknown(),
}) })
.collect(), .collect(),
@ -109,13 +109,13 @@ impl NuCompleter {
offset, offset,
); );
return result; return Some(result);
} }
} }
Err(err) => println!("failed to eval completer block: {}", err), Err(err) => println!("failed to eval completer block: {}", err),
} }
vec![] None
} }
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> { fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
@ -123,7 +123,8 @@ impl NuCompleter {
let offset = working_set.next_span_start(); let offset = working_set.next_span_start();
let (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set); let (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set);
let initial_line = line.to_string(); let initial_line = line.to_string();
new_line.push(b'a'); let alias_total_offset: usize = alias_offset.iter().sum();
new_line.insert(alias_total_offset + pos, b'a');
let pos = offset + pos; let pos = offset + pos;
let config = self.engine_state.get_config(); let config = self.engine_state.get_config();
@ -169,9 +170,10 @@ impl NuCompleter {
} }
}; };
// Parses the prefix // Parses the prefix. Completion should look up to the cursor position, not after.
let mut prefix = working_set.get_span_contents(flat.0).to_vec(); let mut prefix = working_set.get_span_contents(flat.0).to_vec();
prefix.remove(pos - (flat.0.start - span_offset)); let index = pos - (flat.0.start - span_offset);
prefix.drain(index..);
// Variables completion // Variables completion
if prefix.starts_with(b"$") || most_left_var.is_some() { if prefix.starts_with(b"$") || most_left_var.is_some() {
@ -211,7 +213,11 @@ impl NuCompleter {
// We got no results for internal completion // We got no results for internal completion
// now we can check if external completer is set and use it // now we can check if external completer is set and use it
if let Some(block_id) = config.external_completer { if let Some(block_id) = config.external_completer {
return self.external_completion(block_id, spans, offset, new_span); if let Some(external_result) =
self.external_completion(block_id, &spans, offset, new_span)
{
return external_result;
}
} }
} }
@ -338,6 +344,15 @@ impl NuCompleter {
return out; return out;
} }
// Try to complete using an external completer (if set)
if let Some(block_id) = config.external_completer {
if let Some(external_result) =
self.external_completion(block_id, &spans, offset, new_span)
{
return external_result;
}
}
// Check for file completion // Check for file completion
let mut completer = FileCompletion::new(self.engine_state.clone()); let mut completer = FileCompletion::new(self.engine_state.clone());
out = self.process_completion( out = self.process_completion(
@ -352,12 +367,6 @@ impl NuCompleter {
if !out.is_empty() { if !out.is_empty() {
return out; return out;
} }
// Try to complete using an exnternal compelter (if set)
if let Some(block_id) = config.external_completer {
return self
.external_completion(block_id, spans, offset, new_span);
}
} }
}; };
} }

View File

@ -23,7 +23,6 @@ pub fn read_plugin_file(
stack: &mut Stack, stack: &mut Stack,
plugin_file: Option<Spanned<String>>, plugin_file: Option<Spanned<String>>,
storage_path: &str, storage_path: &str,
is_perf_true: bool,
) { ) {
// Reading signatures from signature file // Reading signatures from signature file
// The plugin.nu file stores the parsed signature collected from each registered plugin // 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(); let plugin_path = engine_state.plugin_signatures.clone();
if let Some(plugin_path) = plugin_path { 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) { if let Ok(contents) = std::fs::read(&plugin_path) {
eval_source( 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")] #[cfg(feature = "plugin")]
@ -80,7 +77,7 @@ pub fn eval_config_contents(
stack: &mut Stack, stack: &mut Stack,
) { ) {
if config_path.exists() & config_path.is_file() { 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) { if let Ok(contents) = std::fs::read(&config_path) {
eval_source( eval_source(

View File

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

View File

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

View File

@ -17,6 +17,7 @@ pub struct NushellPrompt {
default_vi_insert_prompt_indicator: Option<String>, default_vi_insert_prompt_indicator: Option<String>,
default_vi_normal_prompt_indicator: Option<String>, default_vi_normal_prompt_indicator: Option<String>,
default_multiline_indicator: Option<String>, default_multiline_indicator: Option<String>,
render_right_prompt_on_last_line: bool,
} }
impl Default for NushellPrompt { impl Default for NushellPrompt {
@ -34,6 +35,7 @@ impl NushellPrompt {
default_vi_insert_prompt_indicator: None, default_vi_insert_prompt_indicator: None,
default_vi_normal_prompt_indicator: None, default_vi_normal_prompt_indicator: None,
default_multiline_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; 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.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>) { pub fn update_prompt_indicator(&mut self, prompt_indicator_string: Option<String>) {
@ -68,6 +75,7 @@ impl NushellPrompt {
prompt_indicator_string: Option<String>, prompt_indicator_string: Option<String>,
prompt_multiline_indicator_string: Option<String>, prompt_multiline_indicator_string: Option<String>,
prompt_vi: (Option<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; 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_insert_prompt_indicator = prompt_vi_insert_string;
self.default_vi_normal_prompt_indicator = prompt_vi_normal_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 { fn default_wrapped_custom_string(&self, str: String) -> String {
@ -162,4 +172,8 @@ impl Prompt for NushellPrompt {
prefix, history_search.term 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, config: &Config,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
is_perf_true: bool,
) -> Option<String> { ) -> Option<String> {
stack stack
.get_env_var(engine_state, prompt) .get_env_var(engine_state, prompt)
@ -44,14 +43,12 @@ fn get_prompt_string(
block, block,
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
); );
if is_perf_true { info!(
info!( "get_prompt_string (block) {}:{}:{}",
"get_prompt_string (block) {}:{}:{}", file!(),
file!(), line!(),
line!(), column!()
column!() );
);
}
match ret_val { match ret_val {
Ok(ret_val) => Some(ret_val), Ok(ret_val) => Some(ret_val),
@ -90,17 +87,10 @@ pub(crate) fn update_prompt<'prompt>(
engine_state: &EngineState, engine_state: &EngineState,
stack: &Stack, stack: &Stack,
nu_prompt: &'prompt mut NushellPrompt, nu_prompt: &'prompt mut NushellPrompt,
is_perf_true: bool,
) -> &'prompt dyn Prompt { ) -> &'prompt dyn Prompt {
let mut stack = stack.clone(); let mut stack = stack.clone();
let left_prompt_string = get_prompt_string( let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack);
PROMPT_COMMAND,
config,
engine_state,
&mut stack,
is_perf_true,
);
// Now that we have the prompt string lets ansify it. // Now that we have the prompt string lets ansify it.
// <133 A><prompt><133 B><command><133 C><command output> // <133 A><prompt><133 B><command><133 C><command output>
@ -116,45 +106,20 @@ pub(crate) fn update_prompt<'prompt>(
left_prompt_string left_prompt_string
}; };
let right_prompt_string = get_prompt_string( let right_prompt_string =
PROMPT_COMMAND_RIGHT, get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack);
config,
engine_state,
&mut stack,
is_perf_true,
);
let prompt_indicator_string = get_prompt_string( let prompt_indicator_string =
PROMPT_INDICATOR, get_prompt_string(PROMPT_INDICATOR, config, engine_state, &mut stack);
config,
engine_state,
&mut stack,
is_perf_true,
);
let prompt_multiline_string = get_prompt_string( let prompt_multiline_string =
PROMPT_MULTILINE_INDICATOR, get_prompt_string(PROMPT_MULTILINE_INDICATOR, config, engine_state, &mut stack);
config,
engine_state,
&mut stack,
is_perf_true,
);
let prompt_vi_insert_string = get_prompt_string( let prompt_vi_insert_string =
PROMPT_INDICATOR_VI_INSERT, get_prompt_string(PROMPT_INDICATOR_VI_INSERT, config, engine_state, &mut stack);
config,
engine_state,
&mut stack,
is_perf_true,
);
let prompt_vi_normal_string = get_prompt_string( let prompt_vi_normal_string =
PROMPT_INDICATOR_VI_NORMAL, get_prompt_string(PROMPT_INDICATOR_VI_NORMAL, config, engine_state, &mut stack);
config,
engine_state,
&mut stack,
is_perf_true,
);
// apply the other indicators // apply the other indicators
nu_prompt.update_all_prompt_strings( nu_prompt.update_all_prompt_strings(
@ -163,12 +128,11 @@ pub(crate) fn update_prompt<'prompt>(
prompt_indicator_string, prompt_indicator_string,
prompt_multiline_string, prompt_multiline_string,
(prompt_vi_insert_string, prompt_vi_normal_string), (prompt_vi_insert_string, prompt_vi_normal_string),
config.render_right_prompt_on_last_line,
); );
let ret_val = nu_prompt as &dyn Prompt; 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 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)?; let res = eval_block(&engine_state, &mut temp_stack, &block, input, false, false)?;
if let PipelineData::Value(value, None) = res { if let PipelineData::Value(value, None) = res {
for menu in create_menus(&value, config)? { for menu in create_menus(&value)? {
line_editor = line_editor =
add_menu(line_editor, &menu, engine_state.clone(), stack, config)?; add_menu(line_editor, &menu, engine_state.clone(), stack, config)?;
} }
@ -491,7 +491,7 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) {
KeyCode::Tab, KeyCode::Tab,
ReedlineEvent::UntilFound(vec![ ReedlineEvent::UntilFound(vec![
ReedlineEvent::Menu("completion_menu".to_string()), ReedlineEvent::Menu("completion_menu".to_string()),
ReedlineEvent::MenuNext, ReedlineEvent::Edit(vec![EditCommand::Complete]),
]), ]),
); );
@ -822,6 +822,8 @@ fn event_from_record(
"ctrld" => ReedlineEvent::CtrlD, "ctrld" => ReedlineEvent::CtrlD,
"ctrlc" => ReedlineEvent::CtrlC, "ctrlc" => ReedlineEvent::CtrlC,
"enter" => ReedlineEvent::Enter, "enter" => ReedlineEvent::Enter,
"submit" => ReedlineEvent::Submit,
"submitornewline" => ReedlineEvent::SubmitOrNewline,
"esc" | "escape" => ReedlineEvent::Esc, "esc" | "escape" => ReedlineEvent::Esc,
"up" => ReedlineEvent::Up, "up" => ReedlineEvent::Up,
"down" => ReedlineEvent::Down, "down" => ReedlineEvent::Down,
@ -962,6 +964,7 @@ fn edit_from_record(
let char = extract_char(value, config)?; let char = extract_char(value, config)?;
EditCommand::MoveLeftBefore(char) EditCommand::MoveLeftBefore(char)
} }
"complete" => EditCommand::Complete,
e => { e => {
return Err(ShellError::UnsupportedConfigValue( return Err(ShellError::UnsupportedConfigValue(
"reedline EditCommand".to_string(), "reedline EditCommand".to_string(),

View File

@ -11,7 +11,7 @@ use log::{info, trace, warn};
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use nu_color_config::get_color_config; use nu_color_config::get_color_config;
use nu_engine::{convert_env_values, eval_block}; use nu_engine::{convert_env_values, eval_block};
use nu_parser::{lex, parse}; use nu_parser::{lex, parse, trim_quotes_str};
use nu_protocol::{ use nu_protocol::{
ast::PathMember, ast::PathMember,
engine::{EngineState, ReplOperation, Stack, StateWorkingSet}, engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
@ -24,7 +24,6 @@ use std::{
sync::atomic::Ordering, sync::atomic::Ordering,
time::Instant, time::Instant,
}; };
use strip_ansi_escapes::strip;
use sysinfo::SystemExt; use sysinfo::SystemExt;
// According to Daniel Imms @Tyriar, we need to do these this way: // According to Daniel Imms @Tyriar, we need to do these this way:
@ -41,7 +40,6 @@ pub fn evaluate_repl(
engine_state: &mut EngineState, engine_state: &mut EngineState,
stack: &mut Stack, stack: &mut Stack,
nushell_path: &str, nushell_path: &str,
is_perf_true: bool,
prerun_command: Option<Spanned<String>>, prerun_command: Option<Spanned<String>>,
) -> Result<()> { ) -> Result<()> {
use reedline::{FileBackedHistory, Reedline, Signal}; use reedline::{FileBackedHistory, Reedline, Signal};
@ -51,7 +49,7 @@ pub fn evaluate_repl(
if !atty::is(atty::Stream::Stdin) { if !atty::is(atty::Stream::Stdin) {
return Err(std::io::Error::new( return Err(std::io::Error::new(
std::io::ErrorKind::NotFound, 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(); .into_diagnostic();
} }
@ -60,14 +58,12 @@ pub fn evaluate_repl(
let mut nu_prompt = NushellPrompt::new(); let mut nu_prompt = NushellPrompt::new();
if is_perf_true { info!(
info!( "translate environment vars {}:{}:{}",
"translate environment vars {}:{}:{}", file!(),
file!(), line!(),
line!(), column!()
column!() );
);
}
// Translate environment variables from Strings to Values // Translate environment variables from Strings to Values
if let Some(e) = convert_env_values(engine_state, stack) { if let Some(e) = convert_env_values(engine_state, stack) {
@ -92,18 +88,14 @@ pub fn evaluate_repl(
}, },
); );
if is_perf_true { info!(
info!( "load config initially {}:{}:{}",
"load config initially {}:{}:{}", file!(),
file!(), line!(),
line!(), column!()
column!() );
);
}
if is_perf_true { info!("setup reedline {}:{}:{}", file!(), line!(), column!());
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
}
let mut line_editor = Reedline::create(); let mut line_editor = Reedline::create();
@ -121,9 +113,7 @@ pub fn evaluate_repl(
engine_state.config.history_file_format, engine_state.config.history_file_format,
); );
if let Some(history_path) = history_path.as_deref() { 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 { let history: Box<dyn reedline::History> = match engine_state.config.history_file_format {
HistoryFileFormat::PlainText => Box::new( HistoryFileFormat::PlainText => Box::new(
@ -149,15 +139,7 @@ pub fn evaluate_repl(
if use_ansi { if use_ansi {
println!("{}", banner); println!("{}", banner);
} else { } else {
let stripped_string = { println!("{}", nu_utils::strip_ansi_string_likely(banner));
if let Ok(bytes) = strip(&banner) {
String::from_utf8_lossy(&bytes).to_string()
} else {
banner
}
};
println!("{}", stripped_string);
} }
} }
@ -173,14 +155,12 @@ pub fn evaluate_repl(
} }
loop { loop {
if is_perf_true { info!(
info!( "load config each loop {}:{}:{}",
"load config each loop {}:{}:{}", file!(),
file!(), line!(),
line!(), column!()
column!() );
);
}
let cwd = get_guaranteed_cwd(engine_state, stack); let cwd = get_guaranteed_cwd(engine_state, stack);
@ -201,15 +181,11 @@ pub fn evaluate_repl(
let config = engine_state.get_config(); 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); 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()); let engine_reference = std::sync::Arc::new(engine_state.clone());
line_editor = line_editor line_editor = line_editor
.with_highlighter(Box::new(NuHighlighter { .with_highlighter(Box::new(NuHighlighter {
@ -266,18 +242,14 @@ pub fn evaluate_repl(
}; };
if config.sync_history_on_enter { 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() { if let Err(e) = line_editor.sync_history() {
warn!("Failed to sync history: {}", e); 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 // Changing the line editor based on the found keybindings
line_editor = match create_keybindings(config) { 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, // Right before we start our prompt and take input from the user,
// fire the "pre_prompt" hook // fire the "pre_prompt" hook
if let Some(hook) = config.hooks.pre_prompt.clone() { 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); report_error_new(engine_state, &err);
} }
} }
@ -323,19 +293,16 @@ pub fn evaluate_repl(
} }
let config = engine_state.get_config(); let config = engine_state.get_config();
let prompt = let prompt = prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt);
prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt, is_perf_true);
entry_num += 1; entry_num += 1;
if is_perf_true { info!(
info!( "finished setup, starting repl {}:{}:{}",
"finished setup, starting repl {}:{}:{}", file!(),
file!(), line!(),
line!(), column!()
column!() );
);
}
let input = line_editor.read_line(prompt); let input = line_editor.read_line(prompt);
let shell_integration = config.shell_integration; 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, // Right before we start running the code the user gave us,
// fire the "pre_execution" hook // fire the "pre_execution" hook
if let Some(hook) = config.hooks.pre_execution.clone() { 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); report_error_new(engine_state, &err);
} }
} }
@ -380,9 +347,13 @@ pub fn evaluate_repl(
let tokens = lex(s.as_bytes(), 0, &[], &[], false); let tokens = lex(s.as_bytes(), 0, &[], &[], false);
// Check if this is a single call to a directory, if so auto-cd // Check if this is a single call to a directory, if so auto-cd
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?; let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
let path = nu_path::expand_path_with(&s, &cwd);
let orig = s.clone(); let mut orig = s.clone();
if orig.starts_with('`') {
orig = trim_quotes_str(&orig).to_string()
}
let path = nu_path::expand_path_with(&orig, &cwd);
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 { if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
// We have an auto-cd // We have an auto-cd
@ -452,7 +423,7 @@ pub fn evaluate_repl(
span, span,
}, },
); );
} else { } else if !s.trim().is_empty() {
trace!("eval source: {}", s); trace!("eval source: {}", s);
eval_source( eval_source(
@ -578,7 +549,7 @@ fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String {
engine_state, engine_state,
stack, stack,
None, None,
"(date now) - ('05/10/2019' | into datetime)", "(date now) - ('2019-05-10 09:59:12-0700' | into datetime)",
) { ) {
Ok(Value::Duration { val, .. }) => format_duration(val), Ok(Value::Duration { val, .. }) => format_duration(val),
_ => "".to_string(), _ => "".to_string(),
@ -595,13 +566,13 @@ Our {}GitHub{} repository is at {}https://github.com/nushell/nushell{}
Our {}Documentation{} is located at {}http://nushell.sh{} Our {}Documentation{} is located at {}http://nushell.sh{}
{}Tweet{} us at {}@nu_shell{} {}Tweet{} us at {}@nu_shell{}
{}Nushell{} has been around for: It's been this long since {}Nushell{}'s first commit:
{} {}
{}You can disable this banner using the {}config nu{}{} command {}You can disable this banner using the {}config nu{}{} command
to modify the config.nu file and setting show_banner to false. to modify the config.nu file and setting show_banner to false.
let-env config {{ let-env config = {{
show_banner: false show_banner: false
... ...
}}{} }}{}
@ -715,6 +686,7 @@ pub fn eval_env_change_hook(
eval_hook( eval_hook(
engine_state, engine_state,
stack, stack,
None,
vec![("$before".into(), before), ("$after".into(), after.clone())], vec![("$before".into(), before), ("$after".into(), after.clone())],
hook_value, hook_value,
)?; )?;
@ -740,15 +712,17 @@ pub fn eval_env_change_hook(
pub fn eval_hook( pub fn eval_hook(
engine_state: &mut EngineState, engine_state: &mut EngineState,
stack: &mut Stack, stack: &mut Stack,
input: Option<PipelineData>,
arguments: Vec<(String, Value)>, arguments: Vec<(String, Value)>,
value: &Value, value: &Value,
) -> Result<(), ShellError> { ) -> Result<PipelineData, ShellError> {
let value_span = value.span()?; let value_span = value.span()?;
let condition_path = PathMember::String { let condition_path = PathMember::String {
val: "condition".to_string(), val: "condition".to_string(),
span: value_span, span: value_span,
}; };
let mut output = PipelineData::new(Span::new(0, 0));
let code_path = PathMember::String { let code_path = PathMember::String {
val: "code".to_string(), val: "code".to_string(),
@ -758,7 +732,7 @@ pub fn eval_hook(
match value { match value {
Value::List { vals, .. } => { Value::List { vals, .. } => {
for val in vals { for val in vals {
eval_hook(engine_state, stack, arguments.clone(), val)? eval_hook(engine_state, stack, None, arguments.clone(), val)?;
} }
} }
Value::Record { .. } => { Value::Record { .. } => {
@ -774,6 +748,7 @@ pub fn eval_hook(
engine_state, engine_state,
stack, stack,
block_id, block_id,
None,
arguments.clone(), arguments.clone(),
block_span, block_span,
) { ) {
@ -853,7 +828,9 @@ pub fn eval_hook(
.collect(); .collect();
match eval_block(engine_state, stack, &block, input, false, false) { match eval_block(engine_state, stack, &block, input, false, false) {
Ok(_) => {} Ok(pipeline_data) => {
output = pipeline_data;
}
Err(err) => { Err(err) => {
report_error_new(engine_state, &err); report_error_new(engine_state, &err);
} }
@ -868,7 +845,14 @@ pub fn eval_hook(
span: block_span, 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 => { other => {
return Err(ShellError::UnsupportedConfigValue( return Err(ShellError::UnsupportedConfigValue(
@ -885,7 +869,17 @@ pub fn eval_hook(
span: block_span, 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 => { other => {
return Err(ShellError::UnsupportedConfigValue( return Err(ShellError::UnsupportedConfigValue(
@ -899,19 +893,20 @@ pub fn eval_hook(
let cwd = get_guaranteed_cwd(engine_state, stack); let cwd = get_guaranteed_cwd(engine_state, stack);
engine_state.merge_env(stack, cwd)?; engine_state.merge_env(stack, cwd)?;
Ok(()) Ok(output)
} }
pub fn run_hook_block( pub fn run_hook_block(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
block_id: BlockId, block_id: BlockId,
optional_input: Option<PipelineData>,
arguments: Vec<(String, Value)>, arguments: Vec<(String, Value)>,
span: Span, span: Span,
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
let block = engine_state.get_block(block_id); 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); let mut callee_stack = stack.gather_captures(&block.captures);

View File

@ -1,9 +1,10 @@
use log::trace; use log::trace;
use nu_ansi_term::Style; 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_parser::{flatten_block, parse, FlatShape};
use nu_protocol::ast::{Argument, Block, Expr, Expression};
use nu_protocol::engine::{EngineState, StateWorkingSet}; use nu_protocol::engine::{EngineState, StateWorkingSet};
use nu_protocol::Config; use nu_protocol::{Config, Span};
use reedline::{Highlighter, StyledText}; use reedline::{Highlighter, StyledText};
pub struct NuHighlighter { pub struct NuHighlighter {
@ -15,10 +16,12 @@ impl Highlighter for NuHighlighter {
fn highlight(&self, line: &str, _cursor: usize) -> StyledText { fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
trace!("highlighting: {}", line); 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, &[]); let (block, _) = parse(&mut working_set, None, line.as_bytes(), false, &[]);
block
};
let (shapes, global_span_offset) = {
let shapes = flatten_block(&working_set, &block); let shapes = flatten_block(&working_set, &block);
(shapes, self.engine_state.next_span_start()) (shapes, self.engine_state.next_span_start())
}; };
@ -26,6 +29,15 @@ impl Highlighter for NuHighlighter {
let mut output = StyledText::default(); let mut output = StyledText::default();
let mut last_seen_span = global_span_offset; 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 { for shape in &shapes {
if shape.0.end <= last_seen_span if shape.0.end <= last_seen_span
|| last_seen_span < global_span_offset || last_seen_span < global_span_offset
@ -44,166 +56,71 @@ impl Highlighter for NuHighlighter {
let next_token = line let next_token = line
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)] [(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
.to_string(); .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 { match shape.1 {
FlatShape::Garbage => output.push(( FlatShape::Garbage => add_colored_token!(shape.1, next_token),
// nushell Garbage FlatShape::Nothing => add_colored_token!(shape.1, next_token),
get_shape_color(shape.1.to_string(), &self.config), FlatShape::Binary => add_colored_token!(shape.1, next_token),
next_token, FlatShape::Bool => add_colored_token!(shape.1, next_token),
)), FlatShape::Int => add_colored_token!(shape.1, next_token),
FlatShape::Nothing => output.push(( FlatShape::Float => add_colored_token!(shape.1, next_token),
// nushell Nothing FlatShape::Range => add_colored_token!(shape.1, next_token),
get_shape_color(shape.1.to_string(), &self.config), FlatShape::InternalCall => add_colored_token!(shape.1, next_token),
next_token, FlatShape::External => add_colored_token!(shape.1, next_token),
)), FlatShape::ExternalArg => add_colored_token!(shape.1, next_token),
FlatShape::Binary => { FlatShape::Literal => add_colored_token!(shape.1, next_token),
// nushell ? FlatShape::Operator => add_colored_token!(shape.1, next_token),
output.push(( FlatShape::Signature => add_colored_token!(shape.1, next_token),
get_shape_color(shape.1.to_string(), &self.config), FlatShape::String => add_colored_token!(shape.1, next_token),
next_token, FlatShape::StringInterpolation => add_colored_token!(shape.1, next_token),
)) FlatShape::DateTime => add_colored_token!(shape.1, 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::List => { FlatShape::List => {
// nushell ??? add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
} }
FlatShape::Table => { FlatShape::Table => {
// nushell ??? add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
} }
FlatShape::Record => { FlatShape::Record => {
// nushell ??? add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
} }
FlatShape::Block => { FlatShape::Block => {
// nushell ??? add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
} }
FlatShape::Filepath => output.push((
// nushell Path FlatShape::Filepath => add_colored_token!(shape.1, next_token),
get_shape_color(shape.1.to_string(), &self.config), FlatShape::Directory => add_colored_token!(shape.1, next_token),
next_token, FlatShape::GlobPattern => add_colored_token!(shape.1, next_token),
)), FlatShape::Variable => add_colored_token!(shape.1, next_token),
FlatShape::Directory => output.push(( FlatShape::Flag => add_colored_token!(shape.1, next_token),
// nushell Directory FlatShape::Custom(..) => add_colored_token!(shape.1, next_token),
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,
)),
} }
last_seen_span = shape.0.end; last_seen_span = shape.0.end;
} }
@ -216,3 +133,297 @@ impl Highlighter for NuHighlighter {
output 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_engine::eval_block;
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents}; use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
use nu_protocol::engine::StateWorkingSet; use nu_protocol::engine::StateWorkingSet;
use nu_protocol::CliError; use nu_protocol::CliError;
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, Stack}, engine::{EngineState, Stack},
PipelineData, ShellError, Span, Value, print_if_stream, PipelineData, ShellError, Span, Value,
}; };
#[cfg(windows)] #[cfg(windows)]
use nu_utils::enable_vt_processing; use nu_utils::enable_vt_processing;
@ -204,8 +204,6 @@ pub fn eval_source(
fname: &str, fname: &str,
input: PipelineData, input: PipelineData,
) -> bool { ) -> bool {
trace!("eval_source");
let (block, delta) = { let (block, delta) = {
let mut working_set = StateWorkingSet::new(engine_state); let mut working_set = StateWorkingSet::new(engine_state);
let (output, err) = parse( let (output, err) = parse(
@ -231,23 +229,41 @@ pub fn eval_source(
} }
match eval_block(engine_state, stack, &block, input, false, false) { match eval_block(engine_state, stack, &block, input, false, false) {
Ok(mut pipeline_data) => { Ok(pipeline_data) => {
if let PipelineData::ExternalStream { exit_code, .. } = &mut pipeline_data { let config = engine_state.get_config();
if let Some(exit_code) = exit_code.take().and_then(|it| it.last()) { let result;
stack.add_env_var("LAST_EXIT_CODE".to_string(), exit_code); if let PipelineData::ExternalStream {
} else { stdout: stream,
set_last_exit_code(stack, 0); 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 { } else {
set_last_exit_code(stack, 0); result = pipeline_data.print(engine_state, stack, false, false);
} }
if let Err(err) = pipeline_data.print(engine_state, stack, false, false) { match result {
let working_set = StateWorkingSet::new(engine_state); Err(err) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err); report_error(&working_set, &err);
return false; return false;
}
Ok(exit_code) => {
set_last_exit_code(stack, exit_code);
}
} }
// reset vt processing, aka ansi because illbehaved externals can break it // reset vt processing, aka ansi because illbehaved externals can break it

View File

@ -63,7 +63,7 @@ fn variables_single_dash_argument_with_flagcompletion(mut completer: NuCompleter
#[rstest] #[rstest]
fn variables_command_with_commandcompletion(mut completer_strings: NuCompleter) { fn variables_command_with_commandcompletion(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-command ", 9); let suggestions = completer_strings.complete("my-c ", 4);
let expected: Vec<String> = vec!["my-command".into()]; let expected: Vec<String> = vec!["my-command".into()];
match_suggestions(expected, suggestions); match_suggestions(expected, suggestions);
} }
@ -672,3 +672,63 @@ fn unknown_command_completion() {
match_suggestions(expected_paths, suggestions) match_suggestions(expected_paths, suggestions)
} }
#[rstest]
fn flagcompletion_triggers_after_cursor(mut completer: NuCompleter) {
let suggestions = completer.complete("tst -h", 5);
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn customcompletion_triggers_after_cursor(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-command c", 11);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn customcompletion_triggers_after_cursor_piped(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-command c | ls", 11);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn flagcompletion_triggers_after_cursor_piped(mut completer: NuCompleter) {
let suggestions = completer.complete("tst -h | ls", 5);
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
match_suggestions(expected, suggestions);
}
#[test]
fn filecompletions_triggers_after_cursor() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let suggestions = completer.complete("cp test_c", 3);
#[cfg(windows)]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a\\".to_string(),
"test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder\\".to_string(),
];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a/".to_string(),
"test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder/".to_string(),
];
match_suggestions(expected_paths, suggestions);
}

View File

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

View File

@ -1,7 +1,9 @@
mod color_config; mod color_config;
mod matching_brackets_style;
mod nu_style; mod nu_style;
mod shape_color; mod shape_color;
pub use color_config::*; pub use color_config::*;
pub use matching_brackets_style::*;
pub use nu_style::*; pub use nu_style::*;
pub use shape_color::*; 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,27 +5,26 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-command" name = "nu-command"
version = "0.69.0" version = "0.71.0"
build = "build.rs" build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-color-config = { path = "../nu-color-config", version = "0.69.0" } nu-color-config = { path = "../nu-color-config", version = "0.71.0" }
nu-engine = { path = "../nu-engine", version = "0.69.0" } nu-engine = { path = "../nu-engine", version = "0.71.0" }
nu-glob = { path = "../nu-glob", version = "0.69.0" } nu-glob = { path = "../nu-glob", version = "0.71.0" }
nu-json = { path = "../nu-json", version = "0.69.0" } nu-json = { path = "../nu-json", version = "0.71.0" }
nu-parser = { path = "../nu-parser", version = "0.69.0" } nu-parser = { path = "../nu-parser", version = "0.71.0" }
nu-path = { path = "../nu-path", version = "0.69.0" } nu-path = { path = "../nu-path", version = "0.71.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.69.0" } nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.71.0" }
nu-protocol = { path = "../nu-protocol", version = "0.69.0" } nu-protocol = { path = "../nu-protocol", version = "0.71.0" }
nu-system = { path = "../nu-system", version = "0.69.0" } nu-system = { path = "../nu-system", version = "0.71.0" }
nu-table = { path = "../nu-table", version = "0.69.0" } nu-table = { path = "../nu-table", version = "0.71.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.69.0" } nu-term-grid = { path = "../nu-term-grid", version = "0.71.0" }
nu-test-support = { path = "../nu-test-support", version = "0.69.0" } nu-utils = { path = "../nu-utils", version = "0.71.0" }
nu-utils = { path = "../nu-utils", version = "0.69.0" }
nu-ansi-term = "0.46.0" nu-ansi-term = "0.46.0"
num-format = { version = "0.4.0" } num-format = { version = "0.4.3" }
# Potential dependencies for extras # Potential dependencies for extras
alphanumeric-sort = "1.4.4" alphanumeric-sort = "1.4.4"
@ -55,7 +54,7 @@ is-root = "0.1.2"
itertools = "0.10.0" itertools = "0.10.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
log = "0.4.14" 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" } md5 = { package = "md-5", version = "0.10.0" }
meval = "0.2.0" meval = "0.2.0"
mime = "0.3.16" mime = "0.3.16"
@ -78,7 +77,6 @@ serde_yaml = "0.9.4"
sha2 = "0.10.0" sha2 = "0.10.0"
# Disable default features b/c the default features build Git (very slow to compile) # Disable default features b/c the default features build Git (very slow to compile)
shadow-rs = { version = "0.16.1", default-features = false } shadow-rs = { version = "0.16.1", default-features = false }
strip-ansi-escapes = "0.1.1"
sysinfo = "0.26.2" sysinfo = "0.26.2"
terminal_size = "0.2.1" terminal_size = "0.2.1"
thiserror = "1.0.31" thiserror = "1.0.31"
@ -88,14 +86,18 @@ unicode-segmentation = "1.8.0"
url = "2.2.1" url = "2.2.1"
uuid = { version = "1.1.2", features = ["v4"] } uuid = { version = "1.1.2", features = ["v4"] }
which = { version = "4.3.0", optional = true } which = { version = "4.3.0", optional = true }
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]} reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
wax = { version = "0.5.0", features = ["diagnostics"] } wax = { version = "0.5.0" }
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true } rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
sqlparser = { version = "0.23.0", features = ["serde"], optional = true } sqlparser = { version = "0.23.0", features = ["serde"], optional = true }
[target.'cfg(windows)'.dependencies]
winreg = "0.10.1"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
umask = "2.0.0" umask = "2.0.0"
users = "0.11.0" users = "0.11.0"
libc = "0.2"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
version = "2.1.3" version = "2.1.3"
@ -133,9 +135,8 @@ features = [
] ]
[target.'cfg(windows)'.dependencies.windows] [target.'cfg(windows)'.dependencies.windows]
version = "0.37.0" version = "0.42.0"
features = [ features = [
"alloc",
"Win32_Foundation", "Win32_Foundation",
"Win32_Storage_FileSystem", "Win32_Storage_FileSystem",
"Win32_System_SystemServices", "Win32_System_SystemServices",
@ -152,6 +153,8 @@ database = ["sqlparser", "rusqlite"]
shadow-rs = { version = "0.16.1", default-features = false } shadow-rs = { version = "0.16.1", default-features = false }
[dev-dependencies] [dev-dependencies]
nu-test-support = { path = "../nu-test-support", version = "0.71.0" }
hamcrest2 = "0.3.0" hamcrest2 = "0.3.0"
dirs-next = "2.0.0" dirs-next = "2.0.0"
proptest = "1.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_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath; use nu_protocol::ast::CellPath;
@ -10,12 +10,12 @@ struct Arguments {
added_data: Vec<u8>, added_data: Vec<u8>,
index: Option<usize>, index: Option<usize>,
end: bool, end: bool,
column_paths: Option<Vec<CellPath>>, cell_paths: Option<Vec<CellPath>>,
} }
impl BytesArgument for Arguments { impl CmdArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> { fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take() self.cell_paths.take()
} }
} }
@ -62,12 +62,8 @@ impl Command for BytesAdd {
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let added_data: Vec<u8> = call.req(engine_state, stack, 0)?; let added_data: Vec<u8> = call.req(engine_state, stack, 0)?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() { let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
None
} else {
Some(column_paths)
};
let index: Option<usize> = call.get_flag(engine_state, stack, "index")?; let index: Option<usize> = call.get_flag(engine_state, stack, "index")?;
let end = call.has_flag("end"); let end = call.has_flag("end");
@ -75,7 +71,7 @@ impl Command for BytesAdd {
added_data, added_data,
index, index,
end, end,
column_paths, cell_paths,
}; };
operate(add, arg, input, call.head, engine_state.ctrlc.clone()) 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 { match args.index {
None => { None => {
if args.end { 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_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath; use nu_protocol::ast::CellPath;
@ -15,12 +15,12 @@ struct Arguments {
start: isize, start: isize,
end: isize, end: isize,
arg_span: Span, arg_span: Span,
column_paths: Option<Vec<CellPath>>, cell_paths: Option<Vec<CellPath>>,
} }
impl BytesArgument for Arguments { impl CmdArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> { fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take() self.cell_paths.take()
} }
} }
@ -141,17 +141,13 @@ impl Command for BytesAt {
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let range: Value = call.req(engine_state, stack, 0)?; let range: Value = call.req(engine_state, stack, 0)?;
let (start, end, arg_span) = parse_range(range, call.head)?; let (start, end, arg_span) = parse_range(range, call.head)?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() { let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
None
} else {
Some(column_paths)
};
let arg = Arguments { let arg = Arguments {
start, start,
end, end,
arg_span, arg_span,
column_paths, cell_paths,
}; };
operate(at, arg, input, call.head, engine_state.ctrlc.clone()) 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 len: isize = input.len() as isize;
let start: isize = if arg.start < 0 { 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_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath; use nu_protocol::ast::CellPath;
@ -8,12 +8,12 @@ use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShap
struct Arguments { struct Arguments {
pattern: Vec<u8>, pattern: Vec<u8>,
column_paths: Option<Vec<CellPath>>, cell_paths: Option<Vec<CellPath>>,
} }
impl BytesArgument for Arguments { impl CmdArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> { fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take() self.cell_paths.take()
} }
} }
@ -53,15 +53,11 @@ impl Command for BytesEndsWith {
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?; let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() { let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
None
} else {
Some(column_paths)
};
let arg = Arguments { let arg = Arguments {
pattern, pattern,
column_paths, cell_paths,
}; };
operate(ends_with, arg, input, call.head, engine_state.ctrlc.clone()) 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 { fn ends_with(val: &Value, args: &Arguments, span: Span) -> Value {
Value::Bool { match val {
val: input.ends_with(pattern), Value::Binary {
span, 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_engine::CallExt;
use nu_protocol::ast::{Call, CellPath}; use nu_protocol::ast::{Call, CellPath};
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
@ -10,12 +10,12 @@ struct Arguments {
pattern: Vec<u8>, pattern: Vec<u8>,
end: bool, end: bool,
all: bool, all: bool,
column_paths: Option<Vec<CellPath>>, cell_paths: Option<Vec<CellPath>>,
} }
impl BytesArgument for Arguments { impl CmdArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> { fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take() self.cell_paths.take()
} }
} }
@ -60,17 +60,13 @@ impl Command for BytesIndexOf {
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?; let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() { let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
None
} else {
Some(column_paths)
};
let arg = Arguments { let arg = Arguments {
pattern, pattern,
end: call.has_flag("end"), end: call.has_flag("end"),
all: call.has_flag("all"), all: call.has_flag("all"),
column_paths, cell_paths,
}; };
operate(index_of, arg, input, call.head, engine_state.ctrlc.clone()) 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 { if arg.all {
search_all_index(input, &arg.pattern, arg.end, span) search_all_index(input, &arg.pattern, arg.end, span)
} else { } else {

View File

@ -1,4 +1,4 @@
use super::{operate, BytesArgument}; use crate::input_handler::{operate, CellPathOnlyArgs};
use nu_engine::CallExt; use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath; use nu_protocol::ast::CellPath;
@ -9,16 +9,6 @@ use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShap
#[derive(Clone)] #[derive(Clone)]
pub struct BytesLen; 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 { impl Command for BytesLen {
fn name(&self) -> &str { fn name(&self) -> &str {
"bytes length" "bytes length"
@ -49,13 +39,8 @@ impl Command for BytesLen {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() { let arg = CellPathOnlyArgs::from(cell_paths);
None
} else {
Some(column_paths)
};
let arg = Arguments { column_paths };
operate(length, arg, input, call.head, engine_state.ctrlc.clone()) 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 { fn length(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
Value::Int { match val {
val: input.len() as i64, Value::Binary {
span, 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 reverse;
mod starts_with; 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 add::BytesAdd;
pub use at::BytesAt; pub use at::BytesAt;
pub use build_::BytesBuild; pub use build_::BytesBuild;
@ -28,71 +23,3 @@ pub use remove::BytesRemove;
pub use replace::BytesReplace; pub use replace::BytesReplace;
pub use reverse::BytesReverse; pub use reverse::BytesReverse;
pub use starts_with::BytesStartsWith; 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_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
@ -9,13 +9,13 @@ use nu_protocol::{
struct Arguments { struct Arguments {
pattern: Vec<u8>, pattern: Vec<u8>,
end: bool, end: bool,
column_paths: Option<Vec<CellPath>>, cell_paths: Option<Vec<CellPath>>,
all: bool, all: bool,
} }
impl BytesArgument for Arguments { impl CmdArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> { fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take() self.cell_paths.take()
} }
} }
@ -55,12 +55,8 @@ impl Command for BytesRemove {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() { let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
None
} else {
Some(column_paths)
};
let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?; let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
if pattern_to_remove.item.is_empty() { if pattern_to_remove.item.is_empty() {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
@ -73,7 +69,7 @@ impl Command for BytesRemove {
let arg = Arguments { let arg = Arguments {
pattern: pattern_to_remove, pattern: pattern_to_remove,
end: call.has_flag("end"), end: call.has_flag("end"),
column_paths, cell_paths,
all: call.has_flag("all"), 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 mut result = vec![];
let remove_all = arg.all; let remove_all = arg.all;
let input_len = input.len(); 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_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
@ -9,13 +9,13 @@ use nu_protocol::{
struct Arguments { struct Arguments {
find: Vec<u8>, find: Vec<u8>,
replace: Vec<u8>, replace: Vec<u8>,
column_paths: Option<Vec<CellPath>>, cell_paths: Option<Vec<CellPath>>,
all: bool, all: bool,
} }
impl BytesArgument for Arguments { impl CmdArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> { fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take() self.cell_paths.take()
} }
} }
@ -55,12 +55,8 @@ impl Command for BytesReplace {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 2)?; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 2)?;
let column_paths = if column_paths.is_empty() { let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
None
} else {
Some(column_paths)
};
let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?; let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
if find.item.is_empty() { if find.item.is_empty() {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
@ -72,7 +68,7 @@ impl Command for BytesReplace {
let arg = Arguments { let arg = Arguments {
find: find.item, find: find.item,
replace: call.req::<Vec<u8>>(engine_state, stack, 1)?, replace: call.req::<Vec<u8>>(engine_state, stack, 1)?,
column_paths, cell_paths,
all: call.has_flag("all"), 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 mut replaced = vec![];
let replace_all = arg.all; 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_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath; use nu_protocol::ast::CellPath;
@ -6,16 +6,6 @@ use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category; use nu_protocol::Category;
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value}; 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)] #[derive(Clone)]
pub struct BytesReverse; pub struct BytesReverse;
@ -50,13 +40,8 @@ impl Command for BytesReverse {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let column_paths = if column_paths.is_empty() { let arg = CellPathOnlyArgs::from(cell_paths);
None
} else {
Some(column_paths)
};
let arg = Arguments { column_paths };
operate(reverse, arg, input, call.head, engine_state.ctrlc.clone()) 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 { fn reverse(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
let mut reversed_input = input.to_vec(); match val {
reversed_input.reverse(); Value::Binary {
Value::Binary { val,
val: reversed_input, span: val_span,
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_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath; use nu_protocol::ast::CellPath;
@ -8,12 +8,12 @@ use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShap
struct Arguments { struct Arguments {
pattern: Vec<u8>, pattern: Vec<u8>,
column_paths: Option<Vec<CellPath>>, cell_paths: Option<Vec<CellPath>>,
} }
impl BytesArgument for Arguments { impl CmdArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> { fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take() self.cell_paths.take()
} }
} }
@ -53,15 +53,11 @@ impl Command for BytesStartsWith {
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?; let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() { let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
None
} else {
Some(column_paths)
};
let arg = Arguments { let arg = Arguments {
pattern, pattern,
column_paths, cell_paths,
}; };
operate( operate(
starts_with, starts_with,
@ -102,10 +98,24 @@ impl Command for BytesStartsWith {
} }
} }
fn starts_with(input: &[u8], Arguments { pattern, .. }: &Arguments, span: Span) -> Value { fn starts_with(val: &Value, args: &Arguments, span: Span) -> Value {
Value::Bool { match val {
val: input.starts_with(pattern), Value::Binary {
span, 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_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
@ -96,31 +97,12 @@ fn fmt(
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let column_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())
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 { fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
match input { match input {
Value::Int { val, .. } => fmt_it(*val, span), Value::Int { val, .. } => fmt_it(*val, span),
Value::Filesize { 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_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
@ -100,7 +101,7 @@ fn into_binary(
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; 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 { match input {
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::Binary { PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::Binary {
@ -120,27 +121,10 @@ fn into_binary(
} }
.into_pipeline_data()) .into_pipeline_data())
} }
_ => input.map( _ => {
move |v| { let arg = CellPathOnlyArgs::from(cell_paths);
if column_paths.is_empty() { operate(action, arg, input, call.head, engine_state.ctrlc.clone())
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(),
),
} }
} }
@ -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 { match input {
Value::Binary { .. } => input.clone(), Value::Binary { .. } => input.clone(),
Value::Int { val, .. } => Value::Binary { Value::Int { val, .. } => Value::Binary {
@ -180,7 +164,7 @@ pub fn action(input: &Value, span: Span) -> Value {
span, span,
}, },
Value::Bool { val, .. } => Value::Binary { Value::Bool { val, .. } => Value::Binary {
val: int_to_endian(if *val { 1i64 } else { 0 }), val: int_to_endian(i64::from(*val)),
span, span,
}, },
Value::Date { val, .. } => Value::Binary { Value::Date { val, .. } => Value::Binary {

View File

@ -1,3 +1,4 @@
use crate::input_handler::{operate, CellPathOnlyArgs};
use nu_engine::CallExt; use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
@ -108,28 +109,9 @@ fn into_bool(
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let column_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())
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 string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> { 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 { match input {
Value::Bool { .. } => input.clone(), Value::Bool { .. } => input.clone(),
Value::Int { val, .. } => Value::Bool { 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 crate::{generate_strftime_list, parse_date_from_string};
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc}; use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
use nu_engine::CallExt; use nu_engine::CallExt;
@ -5,14 +6,20 @@ use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath; use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
SyntaxShape, Value,
}; };
struct Arguments { struct Arguments {
timezone: Option<Spanned<String>>, zone_options: Option<Spanned<Zone>>,
offset: Option<Spanned<i64>>, format_options: Option<DatetimeFormat>,
format: Option<String>, cell_paths: Option<Vec<CellPath>>,
column_paths: 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 // In case it may be confused with chrono::TimeZone
@ -95,7 +102,36 @@ impl Command for SubCommand {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> 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 { fn usage(&self) -> &str {
@ -162,72 +198,9 @@ impl Command for SubCommand {
#[derive(Clone)] #[derive(Clone)]
struct DatetimeFormat(String); struct DatetimeFormat(String);
fn operate( fn action(input: &Value, args: &Arguments, head: Span) -> Value {
engine_state: &EngineState, let timezone = &args.zone_options;
stack: &mut Stack, let dateformat = &args.format_options;
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 {
// Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?) // Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?)
let timestamp = match input { let timestamp = match input {
Value::Int { val, .. } => Ok(*val), Value::Int { val, .. } => Ok(*val),
@ -359,7 +332,12 @@ mod tests {
fn takes_a_date_format() { fn takes_a_date_format() {
let date_str = Value::test_string("16.11.1984 8:00 am +0000"); 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 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 { let expected = Value::Date {
val: DateTime::parse_from_str("16.11.1984 8:00 am +0000", "%d.%m.%Y %H:%M %P %z") val: DateTime::parse_from_str("16.11.1984 8:00 am +0000", "%d.%m.%Y %H:%M %P %z")
.unwrap(), .unwrap(),
@ -371,7 +349,12 @@ mod tests {
#[test] #[test]
fn takes_iso8601_date_format() { fn takes_iso8601_date_format() {
let date_str = Value::test_string("2020-08-04T16:39:18+00:00"); 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 { let expected = Value::Date {
val: DateTime::parse_from_str("2020-08-04T16:39:18+00:00", "%Y-%m-%dT%H:%M:%S%z") val: DateTime::parse_from_str("2020-08-04T16:39:18+00:00", "%Y-%m-%dT%H:%M:%S%z")
.unwrap(), .unwrap(),
@ -387,7 +370,12 @@ mod tests {
item: Zone::East(8), item: Zone::East(8),
span: Span::test_data(), 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 { let expected = Value::Date {
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z") val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
.unwrap(), .unwrap(),
@ -404,7 +392,12 @@ mod tests {
item: Zone::East(8), item: Zone::East(8),
span: Span::test_data(), 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 { let expected = Value::Date {
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z") val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
.unwrap(), .unwrap(),
@ -421,7 +414,12 @@ mod tests {
item: Zone::Local, item: Zone::Local,
span: Span::test_data(), 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 { let expected = Value::Date {
val: Local.timestamp(1614434140, 0).into(), val: Local.timestamp(1614434140, 0).into(),
span: Span::test_data(), span: Span::test_data(),
@ -433,8 +431,12 @@ mod tests {
#[test] #[test]
fn takes_timestamp_without_timezone() { fn takes_timestamp_without_timezone() {
let date_str = Value::test_string("1614434140"); let date_str = Value::test_string("1614434140");
let timezone_option = None; let args = Arguments {
let actual = action(&date_str, &timezone_option, &None, Span::test_data()); zone_options: None,
format_options: None,
cell_paths: None,
};
let actual = action(&date_str, &args, Span::test_data());
let expected = Value::Date { let expected = Value::Date {
val: Utc.timestamp(1614434140, 0).into(), val: Utc.timestamp(1614434140, 0).into(),
@ -451,7 +453,12 @@ mod tests {
item: Zone::Utc, item: Zone::Utc,
span: Span::test_data(), 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); assert_eq!(actual.get_type(), Error);
} }
@ -460,7 +467,12 @@ mod tests {
fn communicates_parsing_error_given_an_invalid_datetimelike_string() { fn communicates_parsing_error_given_an_invalid_datetimelike_string() {
let date_str = Value::test_string("16.11.1984 8:00 am Oops0000"); 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 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); assert_eq!(actual.get_type(), Error);
} }

View File

@ -1,3 +1,4 @@
use crate::input_handler::{operate, CellPathOnlyArgs};
use nu_engine::CallExt; use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
@ -36,7 +37,9 @@ impl Command for SubCommand {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> 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> { fn examples(&self) -> Vec<Example> {
@ -72,37 +75,7 @@ impl Command for SubCommand {
} }
} }
fn operate( fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
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 {
match input { match input {
Value::String { val: s, span } => { Value::String { val: s, span } => {
let other = s.trim(); let other = s.trim();
@ -163,7 +136,7 @@ mod tests {
let word = Value::test_string("3.1415"); let word = Value::test_string("3.1415");
let expected = Value::test_float(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); assert_eq!(actual, expected);
} }
@ -171,7 +144,11 @@ mod tests {
fn communicates_parsing_error_given_an_invalid_decimallike_string() { fn communicates_parsing_error_given_an_invalid_decimallike_string() {
let decimal_str = Value::test_string("11.6anra"); 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); assert_eq!(actual.get_type(), Error);
} }
@ -180,7 +157,11 @@ mod tests {
fn int_to_decimal() { fn int_to_decimal() {
let decimal_str = Value::test_int(10); let decimal_str = Value::test_int(10);
let expected = Value::test_float(10.0); 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); assert_eq!(actual, expected);
} }

View File

@ -114,14 +114,21 @@ impl Command for SubCommand {
}), }),
}, },
Example { Example {
description: "Convert string to a named duration", description: "Convert string to the requested duration as a string",
example: "'7min' | into duration --convert sec", example: "'7min' | into duration --convert sec",
result: Some(Value::String { result: Some(Value::String {
val: "420 sec".to_string(), val: "420 sec".to_string(),
span, span,
}), }),
}, },
Example {
description: "Convert duration to the requested duration as a string",
example: "420sec | into duration --convert min",
result: Some(Value::String {
val: "7 min".to_string(),
span,
}),
},
] ]
} }
} }
@ -135,18 +142,20 @@ fn into_duration(
let head = call.head; let head = call.head;
let convert_to_unit: Option<Spanned<String>> = call.get_flag(engine_state, stack, "convert")?; let convert_to_unit: Option<Spanned<String>> = call.get_flag(engine_state, stack, "convert")?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?; let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let config = engine_state.get_config();
let float_precision = config.float_precision as usize;
input.map( input.map(
move |v| { move |v| {
if column_paths.is_empty() { if column_paths.is_empty() {
action(&v, &convert_to_unit, head) action(&v, &convert_to_unit, float_precision, head)
} else { } else {
let mut ret = v; let mut ret = v;
for path in &column_paths { for path in &column_paths {
let d = convert_to_unit.clone(); let d = convert_to_unit.clone();
let r = ret.update_cell_path( let r = ret.update_cell_path(
&path.members, &path.members,
Box::new(move |old| action(old, &d, head)), Box::new(move |old| action(old, &d, float_precision, head)),
); );
if let Err(error) = r { if let Err(error) = r {
return Value::Error { error }; return Value::Error { error };
@ -166,127 +175,129 @@ fn convert_str_from_unit_to_unit(
to_unit: &str, to_unit: &str,
span: Span, span: Span,
value_span: Span, value_span: Span,
) -> Result<i64, ShellError> { ) -> Result<f64, ShellError> {
match (from_unit, to_unit) { match (from_unit, to_unit) {
("ns", "ns") => Ok(val), ("ns", "ns") => Ok(val as f64),
("ns", "us") => Ok(val / 1000), ("ns", "us") => Ok(val as f64 / 1000.0),
("ns", "ms") => Ok(val / 1000 / 1000), ("ns", "ms") => Ok(val as f64 / 1000.0 / 1000.0),
("ns", "sec") => Ok(val / 1000 / 1000 / 1000), ("ns", "sec") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0),
("ns", "min") => Ok(val / 1000 / 1000 / 1000 / 60), ("ns", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0),
("ns", "hr") => Ok(val / 1000 / 1000 / 1000 / 60 / 60), ("ns", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0),
("ns", "day") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24), ("ns", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
("ns", "wk") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 7), ("ns", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
("ns", "month") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 30), ("ns", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
("ns", "yr") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 365), ("ns", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("ns", "dec") => Ok(val / 10 / 1000 / 1000 / 1000 / 60 / 60 / 24 / 365), ("ns", "dec") => {
Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0)
}
("us", "ns") => Ok(val * 1000), ("us", "ns") => Ok(val as f64 * 1000.0),
("us", "us") => Ok(val), ("us", "us") => Ok(val as f64),
("us", "ms") => Ok(val / 1000), ("us", "ms") => Ok(val as f64 / 1000.0),
("us", "sec") => Ok(val / 1000 / 1000), ("us", "sec") => Ok(val as f64 / 1000.0 / 1000.0),
("us", "min") => Ok(val / 1000 / 1000 / 60), ("us", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0),
("us", "hr") => Ok(val / 1000 / 1000 / 60 / 60), ("us", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0),
("us", "day") => Ok(val / 1000 / 1000 / 60 / 60 / 24), ("us", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
("us", "wk") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 7), ("us", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
("us", "month") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 30), ("us", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
("us", "yr") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 365), ("us", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("us", "dec") => Ok(val / 10 / 1000 / 1000 / 60 / 60 / 24 / 365), ("us", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("ms", "ns") => Ok(val * 1000 * 1000), ("ms", "ns") => Ok(val as f64 * 1000.0 * 1000.0),
("ms", "us") => Ok(val * 1000), ("ms", "us") => Ok(val as f64 * 1000.0),
("ms", "ms") => Ok(val), ("ms", "ms") => Ok(val as f64),
("ms", "sec") => Ok(val / 1000), ("ms", "sec") => Ok(val as f64 / 1000.0),
("ms", "min") => Ok(val / 1000 / 60), ("ms", "min") => Ok(val as f64 / 1000.0 / 60.0),
("ms", "hr") => Ok(val / 1000 / 60 / 60), ("ms", "hr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0),
("ms", "day") => Ok(val / 1000 / 60 / 60 / 24), ("ms", "day") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0),
("ms", "wk") => Ok(val / 1000 / 60 / 60 / 24 / 7), ("ms", "wk") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
("ms", "month") => Ok(val / 1000 / 60 / 60 / 24 / 30), ("ms", "month") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
("ms", "yr") => Ok(val / 1000 / 60 / 60 / 24 / 365), ("ms", "yr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("ms", "dec") => Ok(val / 10 / 1000 / 60 / 60 / 24 / 365), ("ms", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("sec", "ns") => Ok(val * 1000 * 1000 * 1000), ("sec", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0),
("sec", "us") => Ok(val * 1000 * 1000), ("sec", "us") => Ok(val as f64 * 1000.0 * 1000.0),
("sec", "ms") => Ok(val * 1000), ("sec", "ms") => Ok(val as f64 * 1000.0),
("sec", "sec") => Ok(val), ("sec", "sec") => Ok(val as f64),
("sec", "min") => Ok(val / 60), ("sec", "min") => Ok(val as f64 / 60.0),
("sec", "hr") => Ok(val / 60 / 60), ("sec", "hr") => Ok(val as f64 / 60.0 / 60.0),
("sec", "day") => Ok(val / 60 / 60 / 24), ("sec", "day") => Ok(val as f64 / 60.0 / 60.0 / 24.0),
("sec", "wk") => Ok(val / 60 / 60 / 24 / 7), ("sec", "wk") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 7.0),
("sec", "month") => Ok(val / 60 / 60 / 24 / 30), ("sec", "month") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 30.0),
("sec", "yr") => Ok(val / 60 / 60 / 24 / 365), ("sec", "yr") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 365.0),
("sec", "dec") => Ok(val / 10 / 60 / 60 / 24 / 365), ("sec", "dec") => Ok(val as f64 / 10.0 / 60.0 / 60.0 / 24.0 / 365.0),
("min", "ns") => Ok(val * 1000 * 1000 * 1000 * 60), ("min", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0),
("min", "us") => Ok(val * 1000 * 1000 * 60), ("min", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0),
("min", "ms") => Ok(val * 1000 * 60), ("min", "ms") => Ok(val as f64 * 1000.0 * 60.0),
("min", "sec") => Ok(val * 60), ("min", "sec") => Ok(val as f64 * 60.0),
("min", "min") => Ok(val), ("min", "min") => Ok(val as f64),
("min", "hr") => Ok(val / 60), ("min", "hr") => Ok(val as f64 / 60.0),
("min", "day") => Ok(val / 60 / 24), ("min", "day") => Ok(val as f64 / 60.0 / 24.0),
("min", "wk") => Ok(val / 60 / 24 / 7), ("min", "wk") => Ok(val as f64 / 60.0 / 24.0 / 7.0),
("min", "month") => Ok(val / 60 / 24 / 30), ("min", "month") => Ok(val as f64 / 60.0 / 24.0 / 30.0),
("min", "yr") => Ok(val / 60 / 24 / 365), ("min", "yr") => Ok(val as f64 / 60.0 / 24.0 / 365.0),
("min", "dec") => Ok(val / 10 / 60 / 24 / 365), ("min", "dec") => Ok(val as f64 / 10.0 / 60.0 / 24.0 / 365.0),
("hr", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60), ("hr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0),
("hr", "us") => Ok(val * 1000 * 1000 * 60 * 60), ("hr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0),
("hr", "ms") => Ok(val * 1000 * 60 * 60), ("hr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0),
("hr", "sec") => Ok(val * 60 * 60), ("hr", "sec") => Ok(val as f64 * 60.0 * 60.0),
("hr", "min") => Ok(val * 60), ("hr", "min") => Ok(val as f64 * 60.0),
("hr", "hr") => Ok(val), ("hr", "hr") => Ok(val as f64),
("hr", "day") => Ok(val / 24), ("hr", "day") => Ok(val as f64 / 24.0),
("hr", "wk") => Ok(val / 24 / 7), ("hr", "wk") => Ok(val as f64 / 24.0 / 7.0),
("hr", "month") => Ok(val / 24 / 30), ("hr", "month") => Ok(val as f64 / 24.0 / 30.0),
("hr", "yr") => Ok(val / 24 / 365), ("hr", "yr") => Ok(val as f64 / 24.0 / 365.0),
("hr", "dec") => Ok(val / 10 / 24 / 365), ("hr", "dec") => Ok(val as f64 / 10.0 / 24.0 / 365.0),
("day", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24), ("day", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
("day", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24), ("day", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
("day", "ms") => Ok(val * 1000 * 60 * 60 * 24), ("day", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0),
("day", "sec") => Ok(val * 60 * 60 * 24), ("day", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0),
("day", "min") => Ok(val * 60 * 24), ("day", "min") => Ok(val as f64 * 60.0 * 24.0),
("day", "hr") => Ok(val * 24), ("day", "hr") => Ok(val as f64 * 24.0),
("day", "day") => Ok(val), ("day", "day") => Ok(val as f64),
("day", "wk") => Ok(val / 7), ("day", "wk") => Ok(val as f64 / 7.0),
("day", "month") => Ok(val / 30), ("day", "month") => Ok(val as f64 / 30.0),
("day", "yr") => Ok(val / 365), ("day", "yr") => Ok(val as f64 / 365.0),
("day", "dec") => Ok(val / 10 / 365), ("day", "dec") => Ok(val as f64 / 10.0 / 365.0),
("wk", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 7), ("wk", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
("wk", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 7), ("wk", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
("wk", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 7), ("wk", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
("wk", "sec") => Ok(val * 60 * 60 * 24 * 7), ("wk", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 7.0),
("wk", "min") => Ok(val * 60 * 24 * 7), ("wk", "min") => Ok(val as f64 * 60.0 * 24.0 * 7.0),
("wk", "hr") => Ok(val * 24 * 7), ("wk", "hr") => Ok(val as f64 * 24.0 * 7.0),
("wk", "day") => Ok(val * 7), ("wk", "day") => Ok(val as f64 * 7.0),
("wk", "wk") => Ok(val), ("wk", "wk") => Ok(val as f64),
("wk", "month") => Ok(val / 4), ("wk", "month") => Ok(val as f64 / 4.0),
("wk", "yr") => Ok(val / 52), ("wk", "yr") => Ok(val as f64 / 52.0),
("wk", "dec") => Ok(val / 10 / 52), ("wk", "dec") => Ok(val as f64 / 10.0 / 52.0),
("month", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 30), ("month", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
("month", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 30), ("month", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
("month", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 30), ("month", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
("month", "sec") => Ok(val * 60 * 60 * 24 * 30), ("month", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 30.0),
("month", "min") => Ok(val * 60 * 24 * 30), ("month", "min") => Ok(val as f64 * 60.0 * 24.0 * 30.0),
("month", "hr") => Ok(val * 24 * 30), ("month", "hr") => Ok(val as f64 * 24.0 * 30.0),
("month", "day") => Ok(val * 30), ("month", "day") => Ok(val as f64 * 30.0),
("month", "wk") => Ok(val * 4), ("month", "wk") => Ok(val as f64 * 4.0),
("month", "month") => Ok(val), ("month", "month") => Ok(val as f64),
("month", "yr") => Ok(val / 12), ("month", "yr") => Ok(val as f64 / 12.0),
("month", "dec") => Ok(val / 10 / 12), ("month", "dec") => Ok(val as f64 / 10.0 / 12.0),
("yr", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 365), ("yr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
("yr", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 365), ("yr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
("yr", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 365), ("yr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
("yr", "sec") => Ok(val * 60 * 60 * 24 * 365), ("yr", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 365.0),
("yr", "min") => Ok(val * 60 * 24 * 365), ("yr", "min") => Ok(val as f64 * 60.0 * 24.0 * 365.0),
("yr", "hr") => Ok(val * 24 * 365), ("yr", "hr") => Ok(val as f64 * 24.0 * 365.0),
("yr", "day") => Ok(val * 365), ("yr", "day") => Ok(val as f64 * 365.0),
("yr", "wk") => Ok(val * 52), ("yr", "wk") => Ok(val as f64 * 52.0),
("yr", "month") => Ok(val * 12), ("yr", "month") => Ok(val as f64 * 12.0),
("yr", "yr") => Ok(val), ("yr", "yr") => Ok(val as f64),
("yr", "dec") => Ok(val / 10), ("yr", "dec") => Ok(val as f64 / 10.0),
_ => Err(ShellError::CantConvertWithValue( _ => Err(ShellError::CantConvertWithValue(
"string duration".to_string(), "string duration".to_string(),
@ -369,19 +380,41 @@ fn string_to_unit_duration(
)) ))
} }
fn action(input: &Value, convert_to_unit: &Option<Spanned<String>>, span: Span) -> Value { fn action(
input: &Value,
convert_to_unit: &Option<Spanned<String>>,
float_precision: usize,
span: Span,
) -> Value {
match input { match input {
Value::Duration { Value::Duration {
val: _val_num, val: val_num,
span: _value_span, span: value_span,
} => { } => {
if let Some(_to_unit) = convert_to_unit { if let Some(to_unit) = convert_to_unit {
Value::Error { let from_unit = "ns";
error: ShellError::UnsupportedInput( let duration = *val_num;
"Cannot convert from a Value::Duration right now. Try making it a string." match convert_str_from_unit_to_unit(
.into(), duration,
span, from_unit,
), &to_unit.item,
span,
*value_span,
) {
Ok(d) => {
if d.fract() == 0.0 {
Value::String {
val: format!("{} {}", d, &to_unit.item),
span: *value_span,
}
} else {
Value::String {
val: format!("{:.float_precision$} {}", d, &to_unit.item),
span: *value_span,
}
}
}
Err(e) => Value::Error { error: e },
} }
} else { } else {
input.clone() input.clone()
@ -402,10 +435,19 @@ fn action(input: &Value, convert_to_unit: &Option<Spanned<String>>, span: Span)
span, span,
*value_span, *value_span,
) { ) {
Ok(d) => Value::String { Ok(d) => {
val: format!("{} {}", d, &to_unit.item), if d.fract() == 0.0 {
span: *value_span, Value::String {
}, val: format!("{} {}", d, &to_unit.item),
span: *value_span,
}
} else {
Value::String {
val: format!("{:.float_precision$} {}", d, &to_unit.item),
span: *value_span,
}
}
}
Err(e) => Value::Error { error: e }, Err(e) => Value::Error { error: e },
} }
} else { } else {
@ -450,7 +492,7 @@ mod test {
let expected = Value::Duration { val: 3, span }; let expected = Value::Duration { val: 3, span };
let convert_duration = None; let convert_duration = None;
let actual = action(&word, &convert_duration, span); let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
@ -464,7 +506,7 @@ mod test {
}; };
let convert_duration = None; let convert_duration = None;
let actual = action(&word, &convert_duration, span); let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
@ -478,7 +520,7 @@ mod test {
}; };
let convert_duration = None; let convert_duration = None;
let actual = action(&word, &convert_duration, span); let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
@ -492,7 +534,7 @@ mod test {
}; };
let convert_duration = None; let convert_duration = None;
let actual = action(&word, &convert_duration, span); let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
@ -506,7 +548,7 @@ mod test {
}; };
let convert_duration = None; let convert_duration = None;
let actual = action(&word, &convert_duration, span); let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
@ -520,7 +562,7 @@ mod test {
}; };
let convert_duration = None; let convert_duration = None;
let actual = action(&word, &convert_duration, span); let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
@ -534,7 +576,7 @@ mod test {
}; };
let convert_duration = None; let convert_duration = None;
let actual = action(&word, &convert_duration, span); let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
@ -548,7 +590,7 @@ mod test {
}; };
let convert_duration = None; let convert_duration = None;
let actual = action(&word, &convert_duration, span); let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
} }

View File

@ -1,3 +1,4 @@
use crate::input_handler::{operate, CellPathOnlyArgs};
use nu_engine::CallExt; use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
@ -38,7 +39,9 @@ impl Command for SubCommand {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> 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> { fn examples(&self) -> Vec<Example> {
@ -84,37 +87,7 @@ impl Command for SubCommand {
} }
} }
fn into_filesize( pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
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 {
if let Ok(value_span) = input.span() { if let Ok(value_span) = input.span() {
match input { match input {
Value::Filesize { .. } => input.clone(), Value::Filesize { .. } => input.clone(),

View File

@ -1,3 +1,4 @@
use crate::input_handler::{operate, CmdArgument};
use nu_engine::CallExt; use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
@ -6,11 +7,17 @@ use nu_protocol::{
}; };
struct Arguments { struct Arguments {
radix: Option<Value>, radix: u32,
column_paths: Vec<CellPath>, cell_paths: Option<Vec<CellPath>>,
little_endian: bool, little_endian: bool,
} }
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct SubCommand; pub struct SubCommand;
@ -46,7 +53,29 @@ impl Command for SubCommand {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> 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> { fn examples(&self) -> Vec<Example> {
@ -121,59 +150,9 @@ impl Command for SubCommand {
} }
} }
fn into_int( fn action(input: &Value, args: &Arguments, span: Span) -> Value {
engine_state: &EngineState, let radix = args.radix;
stack: &mut Stack, let little_endian = args.little_endian;
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 {
match input { match input {
Value::Int { val: _, .. } => { Value::Int { val: _, .. } => {
if radix == 10 { if radix == 10 {
@ -401,21 +380,45 @@ mod test {
let word = Value::test_string("10"); let word = Value::test_string("10");
let expected = Value::test_int(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); assert_eq!(actual, expected);
} }
#[test] #[test]
fn turns_binary_to_integer() { fn turns_binary_to_integer() {
let s = Value::test_string("0b101"); 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)); assert_eq!(actual, Value::test_int(5));
} }
#[test] #[test]
fn turns_hex_to_integer() { fn turns_hex_to_integer() {
let s = Value::test_string("0xFF"); 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)); assert_eq!(actual, Value::test_int(255));
} }
@ -423,7 +426,15 @@ mod test {
fn communicates_parsing_error_given_an_invalid_integerlike_string() { fn communicates_parsing_error_given_an_invalid_integerlike_string() {
let integer_str = Value::test_string("36anra"); 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) assert_eq!(actual.get_type(), Error)
} }

View File

@ -1,3 +1,4 @@
use crate::input_handler::{operate, CmdArgument};
use nu_engine::CallExt; use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
@ -8,6 +9,19 @@ use nu_protocol::{
use nu_utils::get_system_locale; use nu_utils::get_system_locale;
use num_format::ToFormattedString; 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)] #[derive(Clone)]
pub struct SubCommand; pub struct SubCommand;
@ -149,9 +163,6 @@ fn string_helper(
let decimals = call.has_flag("decimals"); let decimals = call.has_flag("decimals");
let head = call.head; let head = call.head;
let decimals_value: Option<i64> = call.get_flag(engine_state, stack, "decimals")?; 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 let Some(decimal_val) = decimals_value {
if decimals && decimal_val.is_negative() { if decimals && decimal_val.is_negative() {
return Err(ShellError::UnsupportedInput( 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 { match input {
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::String { PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::String {
@ -179,45 +199,18 @@ fn string_helper(
} }
.into_pipeline_data()) .into_pipeline_data())
} }
_ => input.map( _ => operate(action, args, input, head, engine_state.ctrlc.clone()),
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(),
),
} }
} }
pub fn action( fn action(input: &Value, args: &Arguments, span: Span) -> Value {
input: &Value, let decimals = args.decimals;
span: Span, let digits = args.decimals_value;
decimals: bool, let config = &args.config;
digits: Option<i64>,
group_digits: bool,
config: &Config,
) -> Value {
match input { match input {
Value::Int { val, .. } => { Value::Int { val, .. } => {
let decimal_value = digits.unwrap_or(0) as usize; 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::String { val: res, span }
} }
Value::Float { val, .. } => { Value::Float { val, .. } => {

View File

@ -49,10 +49,17 @@ impl Command for Alias {
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![
description: "Alias ll to ls -l", Example {
example: "alias ll = ls -l", description: "Alias ll to ls -l",
result: None, 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

@ -2,7 +2,8 @@ use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value, Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, SyntaxShape,
Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -22,7 +23,7 @@ impl Command for Do {
.required("block", SyntaxShape::Any, "the block to run") .required("block", SyntaxShape::Any, "the block to run")
.switch( .switch(
"ignore-errors", "ignore-errors",
"ignore errors as the block runs", "ignore shell errors as the block runs",
Some('i'), Some('i'),
) )
.switch( .switch(
@ -102,9 +103,75 @@ impl Command for Do {
Err(_) => Ok(PipelineData::new(call.head)), Err(_) => Ok(PipelineData::new(call.head)),
} }
} else if capture_errors { } else if capture_errors {
// collect stdout and stderr and check exit code.
// if exit code is not 0, return back ShellError.
match result { match result {
Ok(x) => Ok(x), Ok(PipelineData::ExternalStream {
Err(err) => Ok((Value::Error { error: err }).into_pipeline_data()), stdout,
stderr,
exit_code,
span,
metadata,
}) => {
// collect all output first.
let mut stderr_ctrlc = None;
let stderr_msg = match stderr {
None => "".to_string(),
Some(stderr_stream) => {
stderr_ctrlc = stderr_stream.ctrlc.clone();
stderr_stream.into_string().map(|s| s.item)?
}
};
let mut stdout_ctrlc = None;
let stdout_msg = match stdout {
None => "".to_string(),
Some(stdout_stream) => {
stdout_ctrlc = stdout_stream.ctrlc.clone();
stdout_stream.into_string().map(|s| s.item)?
}
};
let mut exit_code_ctrlc = None;
let exit_code: Vec<Value> = match exit_code {
None => vec![],
Some(exit_code_stream) => {
exit_code_ctrlc = exit_code_stream.ctrlc.clone();
exit_code_stream.into_iter().collect()
}
};
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
// if exit_code is not 0, it indicates error occured, return back Err.
if *code != 0 {
return Err(ShellError::ExternalCommand(
"External command runs to failed".to_string(),
stderr_msg,
span,
));
}
}
// construct pipeline data to our caller
Ok(PipelineData::ExternalStream {
stdout: Some(RawStream::new(
Box::new(vec![Ok(stdout_msg.into_bytes())].into_iter()),
stdout_ctrlc,
span,
)),
stderr: Some(RawStream::new(
Box::new(vec![Ok(stderr_msg.into_bytes())].into_iter()),
stderr_ctrlc,
span,
)),
exit_code: Some(ListStream::from_stream(
exit_code.into_iter(),
exit_code_ctrlc,
)),
span,
metadata,
})
}
Ok(other) => Ok(other),
Err(e) => Err(e),
} }
} else { } else {
result result
@ -119,10 +186,15 @@ impl Command for Do {
result: Some(Value::test_string("hello")), result: Some(Value::test_string("hello")),
}, },
Example { Example {
description: "Run the block and ignore errors", description: "Run the block and ignore shell errors",
example: r#"do -i { thisisnotarealcommand }"#, example: r#"do -i { thisisnotarealcommand }"#,
result: None, 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 { Example {
description: "Run the block, with a positional parameter", description: "Run the block, with a positional parameter",
example: r#"do {|x| 100 + $x } 50"#, 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::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Value, Category, Example, ListStream, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -14,7 +14,7 @@ impl Command for Echo {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Echo the arguments back to the user." "Returns its arguments, ignoring the piped-in value."
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
@ -24,7 +24,9 @@ impl Command for Echo {
} }
fn extra_usage(&self) -> &str { 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( fn run(
@ -61,13 +63,17 @@ impl Command for Echo {
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
description: "Put a hello message in the pipeline", description: "Put a list of numbers in the pipeline. This is the same as [1 2 3].",
example: "echo 'hello'", example: "echo 1 2 3",
result: Some(Value::test_string("hello")), result: Some(Value::List {
vals: vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)],
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Print the value of the special '$nu' variable", description:
example: "echo $nu", "Returns the piped-in value, by using the special $in variable to obtain it.",
example: "echo $in",
result: None, result: None,
}, },
] ]

View File

@ -59,4 +59,8 @@ impl Command for ExportCommand {
}), }),
}] }]
} }
fn search_terms(&self) -> Vec<&str> {
vec!["module"]
}
} }

View File

@ -51,4 +51,8 @@ impl Command for ExportAlias {
result: None, result: None,
}] }]
} }
fn search_terms(&self) -> Vec<&str> {
vec!["aka", "abbr", "module"]
}
} }

View File

@ -55,4 +55,8 @@ impl Command for ExportDef {
}), }),
}] }]
} }
fn search_terms(&self) -> Vec<&str> {
vec!["module"]
}
} }

View File

@ -81,4 +81,8 @@ export def-env cd_with_fallback [arg = ""] {
}), }),
}] }]
} }
fn search_terms(&self) -> Vec<&str> {
vec!["module"]
}
} }

View File

@ -47,4 +47,8 @@ impl Command for ExportExtern {
result: None, result: None,
}] }]
} }
fn search_terms(&self) -> Vec<&str> {
vec!["signature", "module", "declare"]
}
} }

View File

@ -53,4 +53,8 @@ impl Command for ExportUse {
}), }),
}] }]
} }
fn search_terms(&self) -> Vec<&str> {
vec!["reexport", "import", "module"]
}
} }

View File

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

View File

@ -105,7 +105,7 @@ impl Command for OverlayHide {
}, },
Example { Example {
description: "Hide an overlay created from a file", 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 use spam.nu
overlay hide spam"#, overlay hide spam"#,
result: None, result: None,

View File

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

View File

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

View File

@ -15,13 +15,12 @@ impl Command for ToDataBase {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Converts into an open db connection" "Converts the input into an open db connection"
} }
fn extra_usage(&self) -> &str { 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 { fn signature(&self) -> Signature {
Signature::build(self.name()) Signature::build(self.name())
.input_type(Type::Any) .input_type(Type::Any)
@ -35,7 +34,7 @@ impl Command for ToDataBase {
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
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", example: "open db.sqlite | into db",
result: None, result: None,
}] }]

View File

@ -248,14 +248,8 @@ fn nu_value_to_string(value: Value, separator: &str, config: &Config) -> String
} }
Value::String { val, .. } => { Value::String { val, .. } => {
// don't store ansi escape sequences in the database // 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 // escape single quotes
stripped.replace('\'', "''") nu_utils::strip_ansi_unlikely(&val).replace('\'', "''")
} }
Value::List { vals: val, .. } => val Value::List { vals: val, .. } => val
.iter() .iter()

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@ mod last;
mod list; mod list;
mod melt; mod melt;
mod open; mod open;
mod query_dfr; mod query_df;
mod rename; mod rename;
mod sample; mod sample;
mod shape; mod shape;
@ -45,7 +45,7 @@ pub use last::LastDF;
pub use list::ListDF; pub use list::ListDF;
pub use melt::MeltDF; pub use melt::MeltDF;
pub use open::OpenDataFrame; pub use open::OpenDataFrame;
pub use query_dfr::QueryDfr; pub use query_df::QueryDf;
pub use rename::RenameDF; pub use rename::RenameDF;
pub use sample::SampleDF; pub use sample::SampleDF;
pub use shape::ShapeDF; pub use shape::ShapeDF;
@ -87,7 +87,7 @@ pub fn add_eager_decls(working_set: &mut StateWorkingSet) {
ListDF, ListDF,
MeltDF, MeltDF,
OpenDataFrame, OpenDataFrame,
QueryDfr, QueryDf,
RenameDF, RenameDF,
SampleDF, SampleDF,
ShapeDF, ShapeDF,

View File

@ -14,11 +14,11 @@ use nu_protocol::{
// https://github.com/pola-rs/polars/tree/master/polars-sql // https://github.com/pola-rs/polars/tree/master/polars-sql
#[derive(Clone)] #[derive(Clone)]
pub struct QueryDfr; pub struct QueryDf;
impl Command for QueryDfr { impl Command for QueryDf {
fn name(&self) -> &str { fn name(&self) -> &str {
"query dfr" "query df"
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -40,7 +40,7 @@ impl Command for QueryDfr {
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "Query dataframe using SQL", description: "Query dataframe using SQL",
example: "[[a b]; [1 2] [3 4]] | into df | query dfr 'select a from df'", example: "[[a b]; [1 2] [3 4]] | into df | query df 'select a from df'",
result: Some( result: Some(
NuDataFrame::try_from_columns(vec![Column::new( NuDataFrame::try_from_columns(vec![Column::new(
"a".to_string(), "a".to_string(),
@ -101,6 +101,6 @@ mod test {
#[test] #[test]
fn test_examples() { fn test_examples() {
test_dataframe(vec![Box::new(QueryDfr {})]) test_dataframe(vec![Box::new(QueryDf {})])
} }
} }

View File

@ -103,14 +103,14 @@ impl SQLContext {
let idx = match idx.parse::<usize>() { let idx = match idx.parse::<usize>() {
Ok(0)| Err(_) => Err( Ok(0)| Err(_) => Err(
PolarsError::ComputeError( 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(idx) => Ok(idx)
}?; }?;
Ok(projection[idx].clone()) Ok(projection[idx].clone())
} }
SqlExpr::Value(_) => Err( 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) _ => parse_sql_expr(e)
} }
@ -124,7 +124,7 @@ impl SQLContext {
// Return error on wild card, shouldn't process this // Return error on wild card, shouldn't process this
if contain_wildcard { if contain_wildcard {
return Err(PolarsError::ComputeError( 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 // 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 { 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 { fn signature(&self) -> Signature {

View File

@ -243,7 +243,7 @@ expr_command!(
"max", "max",
"Creates a max expression", "Creates a max expression",
vec![Example { 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]] example: r#"[[a b]; [one 2] [one 4] [two 1]]
| into df | into df
| group-by a | group-by a
@ -274,7 +274,7 @@ expr_command!(
"min", "min",
"Creates a min expression", "Creates a min expression",
vec![Example { 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]] example: r#"[[a b]; [one 2] [one 4] [two 1]]
| into df | into df
| group-by a | group-by a
@ -305,7 +305,7 @@ expr_command!(
"sum", "sum",
"Creates a sum expression for an aggregation", "Creates a sum expression for an aggregation",
vec![Example { 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]] example: r#"[[a b]; [one 2] [one 4] [two 1]]
| into df | into df
| group-by a | group-by a
@ -336,7 +336,7 @@ expr_command!(
"mean", "mean",
"Creates a mean expression for an aggregation", "Creates a mean expression for an aggregation",
vec![Example { 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]] example: r#"[[a b]; [one 2] [one 4] [two 1]]
| into df | into df
| group-by a | group-by a
@ -367,7 +367,7 @@ expr_command!(
"median", "median",
"Creates a median expression for an aggregation", "Creates a median expression for an aggregation",
vec![Example { 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]] example: r#"[[a b]; [one 2] [one 4] [two 1]]
| into df | into df
| group-by a | group-by a
@ -398,7 +398,7 @@ expr_command!(
"std", "std",
"Creates a std expression for an aggregation", "Creates a std expression for an aggregation",
vec![Example { 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]] example: r#"[[a b]; [one 2] [one 2] [two 1] [two 1]]
| into df | into df
| group-by a | group-by a
@ -429,7 +429,7 @@ expr_command!(
"var", "var",
"Create a var expression for an aggregation", "Create a var expression for an aggregation",
vec![Example { 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]] example: r#"[[a b]; [one 2] [one 2] [two 1] [two 1]]
| into df | into df
| group-by a | group-by a

View File

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

View File

@ -17,13 +17,13 @@ impl Command for LazyAggregate {
} }
fn usage(&self) -> &str { 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 { fn signature(&self) -> Signature {
Signature::build(self.name()) Signature::build(self.name())
.rest( .rest(
"Group by expressions", "Group-by expressions",
SyntaxShape::Any, SyntaxShape::Any,
"Expression(s) that define the aggregations to be applied", "Expression(s) that define the aggregations to be applied",
) )

View File

@ -16,15 +16,15 @@ impl Command for ToLazyGroupBy {
} }
fn usage(&self) -> &str { 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 { fn signature(&self) -> Signature {
Signature::build(self.name()) Signature::build(self.name())
.rest( .rest(
"Group by expressions", "Group-by expressions",
SyntaxShape::Any, SyntaxShape::Any,
"Expression(s) that define the lazy group by", "Expression(s) that define the lazy group-by",
) )
.input_type(Type::Custom("dataframe".into())) .input_type(Type::Custom("dataframe".into()))
.output_type(Type::Custom("dataframe".into())) .output_type(Type::Custom("dataframe".into()))

View File

@ -19,6 +19,10 @@ impl Command for ArgMax {
"Return index for max value in series" "Return index for max value in series"
} }
fn search_terms(&self) -> Vec<&str> {
vec!["argmax", "maximum", "most", "largest", "greatest"]
}
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build(self.name()) Signature::build(self.name())
.input_type(Type::Custom("dataframe".into())) .input_type(Type::Custom("dataframe".into()))

View File

@ -19,6 +19,10 @@ impl Command for ArgMin {
"Return index for min value in series" "Return index for min value in series"
} }
fn search_terms(&self) -> Vec<&str> {
vec!["argmin", "minimum", "least", "smallest", "lowest"]
}
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build(self.name()) Signature::build(self.name())
.input_type(Type::Custom("dataframe".into())) .input_type(Type::Custom("dataframe".into()))

View File

@ -19,6 +19,10 @@ impl Command for ArgSort {
"Returns indexes for a sorted series" "Returns indexes for a sorted series"
} }
fn search_terms(&self) -> Vec<&str> {
vec!["argsort", "order", "arrange"]
}
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build(self.name()) Signature::build(self.name())
.switch("reverse", "reverse order", Some('r')) .switch("reverse", "reverse order", Some('r'))

View File

@ -19,6 +19,10 @@ impl Command for ArgTrue {
"Returns indexes where values are true" "Returns indexes where values are true"
} }
fn search_terms(&self) -> Vec<&str> {
vec!["argtrue", "truth", "boolean-true"]
}
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build(self.name()) Signature::build(self.name())
.input_type(Type::Custom("dataframe".into())) .input_type(Type::Custom("dataframe".into()))

View File

@ -19,6 +19,10 @@ impl Command for ArgUnique {
"Returns indexes for unique values" "Returns indexes for unique values"
} }
fn search_terms(&self) -> Vec<&str> {
vec!["argunique", "distinct", "noduplicate", "unrepeated"]
}
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build(self.name()) Signature::build(self.name())
.input_type(Type::Custom("dataframe".into())) .input_type(Type::Custom("dataframe".into()))

View File

@ -19,6 +19,10 @@ impl Command for ToUpperCase {
"Uppercase the strings in the column" "Uppercase the strings in the column"
} }
fn search_terms(&self) -> Vec<&str> {
vec!["capitalize, caps, capital"]
}
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build(self.name()) Signature::build(self.name())
.input_type(Type::Custom("dataframe".into())) .input_type(Type::Custom("dataframe".into()))

View File

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

View File

@ -87,7 +87,10 @@ impl Command for SubCommand {
Example { Example {
description: "Format a given date using a given format string.", 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""#, 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

@ -158,12 +158,17 @@ pub fn create_default_context() -> EngineState {
bind_command! { bind_command! {
Benchmark, Benchmark,
Complete, Complete,
Exec,
External, External,
NuCheck, NuCheck,
Sys, Sys,
}; };
#[cfg(unix)]
bind_command! { Exec }
#[cfg(windows)]
bind_command! { RegistryQuery }
#[cfg(any( #[cfg(any(
target_os = "android", target_os = "android",
target_os = "linux", target_os = "linux",

View File

@ -37,7 +37,7 @@ impl Command for ConfigEnv {
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
_call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let env_vars_str = env_to_strings(engine_state, stack)?; let env_vars_str = env_to_strings(engine_state, stack)?;
@ -59,7 +59,7 @@ impl Command for ConfigEnv {
let name = Spanned { let name = Spanned {
item: get_editor(engine_state, stack)?, item: get_editor(engine_state, stack)?,
span: Span { start: 0, end: 0 }, span: call.head,
}; };
let args = vec![Spanned { let args = vec![Spanned {
@ -76,6 +76,6 @@ impl Command for ConfigEnv {
env_vars: env_vars_str, env_vars: env_vars_str,
}; };
command.run_with_input(engine_state, stack, input) command.run_with_input(engine_state, stack, input, true)
} }
} }

View File

@ -37,7 +37,7 @@ impl Command for ConfigNu {
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
_call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let env_vars_str = env_to_strings(engine_state, stack)?; let env_vars_str = env_to_strings(engine_state, stack)?;
@ -59,7 +59,7 @@ impl Command for ConfigNu {
let name = Spanned { let name = Spanned {
item: get_editor(engine_state, stack)?, item: get_editor(engine_state, stack)?,
span: Span { start: 0, end: 0 }, span: call.head,
}; };
let args = vec![Spanned { let args = vec![Spanned {
@ -76,6 +76,6 @@ impl Command for ConfigNu {
env_vars: env_vars_str, env_vars: env_vars_str,
}; };
command.run_with_input(engine_state, stack, input) command.run_with_input(engine_state, stack, input, true)
} }
} }

View File

@ -63,7 +63,7 @@ impl Command for WithEnv {
}, },
Example { Example {
description: "Set by row(e.g. `open x.json` or `from json`)", 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, result: None,
}, },
] ]

View File

@ -6,6 +6,29 @@ use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value, 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)] #[derive(Clone)]
pub struct Cd; pub struct Cd;
@ -44,10 +67,7 @@ impl Command for Cd {
let path_val = { let path_val = {
if let Some(path) = path_val { if let Some(path) = path_val {
Some(Spanned { Some(Spanned {
item: match strip_ansi_escapes::strip(&path.item) { item: nu_utils::strip_ansi_string_unlikely(path.item),
Ok(item) => String::from_utf8(item).unwrap_or(path.item),
Err(_) => path.item,
},
span: path.span, span: path.span,
}) })
} else { } else {
@ -141,6 +161,7 @@ impl Command for Cd {
} }
}; };
let path_tointo = path.clone();
let path_value = Value::String { val: path, span }; let path_value = Value::String { val: path, span };
let cwd = Value::String { let cwd = Value::String {
val: cwd.to_string_lossy().to_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 //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 //should probably be a block that loads the information from the state in the overlay
match have_permission(&path_tointo) {
stack.add_env_var("PWD".into(), path_value); PermissionResult::PermissionOk => {
Ok(PipelineData::new(call.head)) 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> { 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)] #[derive(Clone)]
pub struct Cp; pub struct Cp;
#[allow(unused_must_use)]
impl Command for Cp { impl Command for Cp {
fn name(&self) -> &str { fn name(&self) -> &str {
"cp" "cp"
@ -74,10 +73,7 @@ impl Command for Cp {
let src: Spanned<String> = call.req(engine_state, stack, 0)?; let src: Spanned<String> = call.req(engine_state, stack, 0)?;
let src = { let src = {
Spanned { Spanned {
item: match strip_ansi_escapes::strip(&src.item) { item: nu_utils::strip_ansi_string_unlikely(src.item),
Ok(item) => String::from_utf8(item).unwrap_or(src.item),
Err(_) => src.item,
},
span: src.span, span: src.span,
} }
}; };

View File

@ -99,6 +99,16 @@ impl Command for Glob {
let glob_pattern: Spanned<String> = call.req(engine_state, stack, 0)?; let glob_pattern: Spanned<String> = call.req(engine_state, stack, 0)?;
let depth = call.get_flag(engine_state, stack, "depth")?; let depth = call.get_flag(engine_state, stack, "depth")?;
if glob_pattern.item.is_empty() {
return Err(ShellError::GenericError(
"glob pattern must not be empty".to_string(),
"".to_string(),
Some(glob_pattern.span),
Some("add characters to the glob pattern".to_string()),
Vec::new(),
));
}
let folder_depth = if let Some(depth) = depth { let folder_depth = if let Some(depth) = depth {
depth depth
} else { } else {

View File

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

View File

@ -20,7 +20,6 @@ const GLOB_PARAMS: nu_glob::MatchOptions = nu_glob::MatchOptions {
#[derive(Clone)] #[derive(Clone)]
pub struct Mv; pub struct Mv;
#[allow(unused_must_use)]
impl Command for Mv { impl Command for Mv {
fn name(&self) -> &str { fn name(&self) -> &str {
"mv" "mv"
@ -51,8 +50,8 @@ impl Command for Mv {
"make mv to be verbose, showing files been moved.", "make mv to be verbose, showing files been moved.",
Some('v'), Some('v'),
) )
.switch("force", "overwrite the destination.", Some('f'))
.switch("interactive", "ask user to confirm action", Some('i')) .switch("interactive", "ask user to confirm action", Some('i'))
// .switch("force", "suppress error when no file", Some('f'))
.category(Category::FileSystem) .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<String> = call.req(engine_state, stack, 0)?;
let spanned_source = { let spanned_source = {
Spanned { Spanned {
item: match strip_ansi_escapes::strip(&spanned_source.item) { item: nu_utils::strip_ansi_string_unlikely(spanned_source.item),
Ok(item) => String::from_utf8(item).unwrap_or(spanned_source.item),
Err(_) => spanned_source.item,
},
span: spanned_source.span, span: spanned_source.span,
} }
}; };
let spanned_destination: Spanned<String> = call.req(engine_state, stack, 1)?; let spanned_destination: Spanned<String> = call.req(engine_state, stack, 1)?;
let verbose = call.has_flag("verbose"); let verbose = call.has_flag("verbose");
let interactive = call.has_flag("interactive"); let interactive = call.has_flag("interactive");
// let force = call.has_flag("force"); let force = call.has_flag("force");
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
@ -102,12 +98,22 @@ impl Command for Mv {
// //
// First, the destination exists. // First, the destination exists.
// - If a directory, move everything into that directory, otherwise // - If a directory, move everything into that directory, otherwise
// - if only a single source, overwrite the file, otherwise // - if only a single source, and --force (or -f) is provided overwrite the file,
// - error. // - otherwise error.
// //
// Second, the destination doesn't exist, so we can only rename a single source. Otherwise // Second, the destination doesn't exist, so we can only rename a single source. Otherwise
// it's an error. // 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) if (destination.exists() && !destination.is_dir() && sources.len() > 1)
|| (!destination.exists() && 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> { 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 // 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. // 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() { match if from.is_file() {
let mut options = fs_extra::file::CopyOptions::new(); let mut options = fs_extra::file::CopyOptions::new();
options.overwrite = true; options.overwrite = true;

View File

@ -53,10 +53,7 @@ impl Command for Open {
let path = { let path = {
if let Some(path_val) = path { if let Some(path_val) = path {
Some(Spanned { Some(Spanned {
item: match strip_ansi_escapes::strip(&path_val.item) { item: nu_utils::strip_ansi_string_unlikely(path_val.item),
Ok(item) => String::from_utf8(item).unwrap_or(path_val.item),
Err(_) => path_val.item,
},
span: path_val.span, span: path_val.span,
}) })
} else { } 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_no_whitespace = &path.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
let path = Path::new(path_no_whitespace); let path = Path::new(path_no_whitespace);
if permission_denied(&path) { if permission_denied(path) {
#[cfg(unix)] #[cfg(unix)]
let error_msg = match path.metadata() { let error_msg = match path.metadata() {
Ok(md) => format!( Ok(md) => format!(
@ -172,8 +169,17 @@ impl Command for Open {
let block = engine_state.get_block(block_id); let block = engine_state.get_block(block_id);
eval_block(engine_state, stack, block, output, false, false) eval_block(engine_state, stack, block, output, false, false)
} else { } 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), None => Ok(output),
} }

View File

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

View File

@ -2,8 +2,10 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ 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::io::{BufWriter, Write};
use std::path::Path; use std::path::Path;
@ -35,6 +37,12 @@ impl Command for Save {
fn signature(&self) -> nu_protocol::Signature { fn signature(&self) -> nu_protocol::Signature {
Signature::build("save") Signature::build("save")
.required("filename", SyntaxShape::Filepath, "the filename to use") .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("raw", "save file as raw binary", Some('r'))
.switch("append", "append input to the end of the file", Some('a')) .switch("append", "append input to the end of the file", Some('a'))
.category(Category::FileSystem) .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 { let ext = if raw {
None None
@ -148,33 +185,37 @@ impl Command for Save {
match input { match input {
PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::new(span)), PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::new(span)),
PipelineData::ExternalStream { 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 let res = stream_to_file(stream, file, span);
.try_for_each(move |result| { if let Some(h) = handler {
let buf = match result { match h.join() {
Ok(v) => match v { Err(err) => {
Value::String { val, .. } => val.into_bytes(), return Err(ShellError::ExternalCommand(
Value::Binary { val, .. } => val, "Fail to receive external commands stderr message".to_string(),
_ => { format!("{err:?}"),
return Err(ShellError::UnsupportedInput( span,
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(()) Ok(res) => res,
}) }?;
.map(|_| PipelineData::new(span)) res
} else {
res
}
} }
input => match input.into_value(span) { input => match input.into_value(span) {
Value::String { val, .. } => { Value::String { val, .. } => {
@ -224,19 +265,60 @@ impl Command for Save {
vec![ vec![
Example { Example {
description: "Save a string to foo.txt in the current directory", 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, result: None,
}, },
Example { Example {
description: "Append a string to the end of foo.txt", 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, result: None,
}, },
Example { Example {
description: "Save a record to foo.json in the current directory", 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, 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

@ -1,7 +1,7 @@
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::path::Path; use std::path::Path;
use chrono::{DateTime, Datelike, Local}; use chrono::{DateTime, Local};
use filetime::FileTime; use filetime::FileTime;
use nu_engine::CallExt; use nu_engine::CallExt;
@ -9,13 +9,6 @@ use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape}; use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape};
use crate::parse_date_from_string;
enum AddYear {
Full,
FirstDigits,
}
#[derive(Clone)] #[derive(Clone)]
pub struct Touch; pub struct Touch;
@ -35,18 +28,6 @@ impl Command for Touch {
SyntaxShape::Filepath, SyntaxShape::Filepath,
"the path of the file you want to create", "the path of the file you want to create",
) )
.named(
"timestamp",
SyntaxShape::String,
"change the file or directory time to a timestamp. Format: [[CC]YY]MMDDhhmm[.ss]\n\n If neither YY or CC is given, the current year will be assumed. If YY is specified, but CC is not, CC will be derived as follows:\n \tIf YY is between [69, 99], CC is 19\n \tIf YY is between [00, 68], CC is 20\n Note: It is expected that in a future version of this standard the default century inferred from a 2-digit year will change",
Some('t'),
)
.named(
"date",
SyntaxShape::String,
"change the file or directory time to a date",
Some('d'),
)
.named( .named(
"reference", "reference",
SyntaxShape::String, SyntaxShape::String,
@ -85,8 +66,6 @@ impl Command for Touch {
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let mut change_mtime: bool = call.has_flag("modified"); let mut change_mtime: bool = call.has_flag("modified");
let mut change_atime: bool = call.has_flag("access"); let mut change_atime: bool = call.has_flag("access");
let use_stamp: bool = call.has_flag("timestamp");
let use_date: bool = call.has_flag("date");
let use_reference: bool = call.has_flag("reference"); let use_reference: bool = call.has_flag("reference");
let no_create: bool = call.has_flag("no-create"); let no_create: bool = call.has_flag("no-create");
let target: String = call.req(engine_state, stack, 0)?; let target: String = call.req(engine_state, stack, 0)?;
@ -105,111 +84,6 @@ impl Command for Touch {
date = Some(Local::now()); date = Some(Local::now());
} }
if use_stamp || use_date {
let (val, span) = if use_stamp {
let stamp: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "timestamp")?;
let (stamp, span) = match stamp {
Some(stamp) => (stamp.item, stamp.span),
None => {
return Err(ShellError::MissingParameter(
"timestamp".to_string(),
call.head,
));
}
};
// Checks for the seconds stamp and removes the '.' delimiter if any
let (val, has_sec): (String, bool) = match stamp.split_once('.') {
Some((dtime, sec)) => match sec.parse::<u8>() {
Ok(sec) if sec < 60 => (format!("{}{}", dtime, sec), true),
_ => {
return Err(ShellError::UnsupportedInput(
"input has an invalid timestamp".to_string(),
span,
))
}
},
None => (stamp.to_string(), false),
};
let size = val.len();
// Each stamp is a 2 digit number and the whole stamp must not be less than 4 or greater than 7 pairs
if (size % 2 != 0 || !(8..=14).contains(&size)) || val.parse::<u64>().is_err() {
return Err(ShellError::UnsupportedInput(
"input has an invalid timestamp".to_string(),
span,
));
}
let add_year: Option<AddYear> = if has_sec {
match size {
10 => Some(AddYear::Full),
12 => Some(AddYear::FirstDigits),
14 => None,
_ => {
return Err(ShellError::UnsupportedInput(
"input has an invalid timestamp".to_string(),
span,
))
}
}
} else {
match size {
8 => Some(AddYear::Full),
10 => Some(AddYear::FirstDigits),
12 => None,
_ => {
return Err(ShellError::UnsupportedInput(
"input has an invalid timestamp".to_string(),
span,
))
}
}
};
if let Some(add_year) = add_year {
let year = Local::now().year();
match add_year {
AddYear::Full => (format!("{}{}", year, val), span),
AddYear::FirstDigits => {
// Compliance with the Unix version of touch
let yy = val[0..2]
.parse::<u8>()
.expect("should be a valid 2 digit number");
let mut year = 20;
if (69..=99).contains(&yy) {
year = 19;
}
(format!("{}{}", year, val), span)
}
}
} else {
(val, span)
}
} else {
let date_string: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "date")?;
match date_string {
Some(date_string) => (date_string.item, date_string.span),
None => {
return Err(ShellError::MissingParameter("date".to_string(), call.head));
}
}
};
date = if let Ok(parsed_date) = parse_date_from_string(&val, span) {
Some(parsed_date.into())
} else {
let flag = if use_stamp { "timestamp" } else { "date" };
return Err(ShellError::UnsupportedInput(
format!("input has an invalid {}", flag),
span,
));
};
}
if use_reference { if use_reference {
let reference: Option<Spanned<String>> = let reference: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "reference")?; call.get_flag(engine_state, stack, "reference")?;
@ -336,11 +210,6 @@ impl Command for Touch {
example: "touch -m fixture.json", example: "touch -m fixture.json",
result: None, result: None,
}, },
Example {
description: "Creates files d and e and set its last modified time to a timestamp",
example: "touch -m -t 201908241230.30 d e",
result: None,
},
Example { Example {
description: "Changes the last modified time of files a, b and c to a date", description: "Changes the last modified time of files a, b and c to a date",
example: r#"touch -m -d "yesterday" a b c"#, example: r#"touch -m -d "yesterday" a b c"#,
@ -356,11 +225,6 @@ impl Command for Touch {
example: r#"touch -a -d "August 24, 2019; 12:30:30" fixture.json"#, example: r#"touch -a -d "August 24, 2019; 12:30:30" fixture.json"#,
result: None, result: None,
}, },
Example {
description: "Changes both last modified and accessed time of a, b and c to a timestamp only if they exist",
example: r#"touch -c -t 201908241230.30 a b c"#,
result: None,
},
] ]
} }
} }

View File

@ -14,20 +14,11 @@ pub struct FileStructure {
pub resources: Vec<Resource>, pub resources: Vec<Resource>,
} }
#[allow(dead_code)]
impl FileStructure { impl FileStructure {
pub fn new() -> FileStructure { pub fn new() -> FileStructure {
FileStructure { resources: vec![] } 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>( pub fn paths_applying_with<F>(
&mut self, &mut self,
to: F, to: F,

View File

@ -72,7 +72,7 @@ impl Command for Watch {
.item .item
.trim_end_matches(|x| matches!(x, '\x09'..='\x0d')); .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, Ok(p) => p,
Err(e) => { Err(e) => {
return Err(ShellError::DirectoryNotFound( return Err(ShellError::DirectoryNotFound(

View File

@ -18,7 +18,7 @@ impl Command for All {
.required( .required(
"predicate", "predicate",
SyntaxShape::RowCondition, SyntaxShape::RowCondition,
"the predicate expression that must evaluate to a boolean", "the expression, or block, that must evaluate to a boolean",
) )
.category(Category::Filters) .category(Category::Filters)
} }
@ -34,18 +34,26 @@ impl Command for All {
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
description: "Find if services are running", description: "Check if each row's status is the string 'UP'",
example: "echo [[status]; [UP] [UP]] | all status == UP", example: "[[status]; [UP] [UP]] | all status == UP",
result: Some(Value::test_bool(true)), result: Some(Value::test_bool(true)),
}, },
Example { Example {
description: "Check that all values are even", description:
example: "echo [2 4 6 8] | all ($it mod 2) == 0", "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)), 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( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -53,7 +61,6 @@ impl Command for All {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
// let predicate = &call.positional[0];
let span = call.head; let span = call.head;
let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?; 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 var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id);
let mut stack = stack.captures_to_stack(&capture_block.captures); 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 ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone(); let engine_state = engine_state.clone();
for value in input.into_interruptible_iter(ctrlc) { for value in input.into_interruptible_iter(ctrlc) {
stack.with_env(&orig_env_vars, &orig_env_hidden);
if let Some(var_id) = var_id { if let Some(var_id) = var_id {
stack.add_var(var_id, value); stack.add_var(var_id, value.clone());
} }
let eval = eval_block( let eval = eval_block(
&engine_state, &engine_state,
&mut stack, &mut stack,
block, block,
PipelineData::new(span), value.into_pipeline_data(),
call.redirect_stdout, call.redirect_stdout,
call.redirect_stderr, call.redirect_stderr,
); );

View File

@ -18,7 +18,7 @@ impl Command for Any {
.required( .required(
"predicate", "predicate",
SyntaxShape::RowCondition, SyntaxShape::RowCondition,
"the predicate expression that should return a boolean", "the expression, or block, that should return a boolean",
) )
.category(Category::Filters) .category(Category::Filters)
} }
@ -34,18 +34,25 @@ impl Command for Any {
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
description: "Find if a service is not running", description: "Check if any row's status is the string 'DOWN'",
example: "echo [[status]; [UP] [DOWN] [UP]] | any status == DOWN", example: "[[status]; [UP] [DOWN] [UP]] | any status == DOWN",
result: Some(Value::test_bool(true)), result: Some(Value::test_bool(true)),
}, },
Example { Example {
description: "Check if any of the values is odd", description: "Check if any of the values is odd, using the built-in $it variable",
example: "echo [2 4 1 6 8] | any ($it mod 2) == 1", 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)), 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( fn run(
&self, &self,
engine_state: &EngineState, 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 var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id);
let mut stack = stack.captures_to_stack(&capture_block.captures); 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 ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone(); let engine_state = engine_state.clone();
for value in input.into_interruptible_iter(ctrlc) { for value in input.into_interruptible_iter(ctrlc) {
stack.with_env(&orig_env_vars, &orig_env_hidden);
if let Some(var_id) = var_id { if let Some(var_id) = var_id {
stack.add_var(var_id, value); stack.add_var(var_id, value.clone());
} }
let eval = eval_block( let eval = eval_block(
&engine_state, &engine_state,
&mut stack, &mut stack,
block, block,
PipelineData::new(span), value.into_pipeline_data(),
call.redirect_stdout, call.redirect_stdout,
call.redirect_stderr, call.redirect_stderr,
); );

View File

@ -24,6 +24,13 @@ impl Command for Append {
"Append any number of rows to a table." "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> { fn search_terms(&self) -> Vec<&str> {
vec!["add", "concatenate"] vec!["add", "concatenate"]
} }

View File

@ -1,4 +1,4 @@
use nu_engine::{eval_block, CallExt}; use nu_engine::{eval_block, redirect_env, CallExt};
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
@ -20,6 +20,11 @@ impl Command for Collect {
SyntaxShape::Block(Some(vec![SyntaxShape::Any])), SyntaxShape::Block(Some(vec![SyntaxShape::Any])),
"the block to run once the stream is collected", "the block to run once the stream is collected",
) )
.switch(
"keep-env",
"let the block affect environment variables",
None,
)
.category(Category::Filters) .category(Category::Filters)
} }
@ -37,26 +42,43 @@ impl Command for Collect {
let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?; let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?;
let block = engine_state.get_block(capture_block.block_id).clone(); let block = engine_state.get_block(capture_block.block_id).clone();
let mut stack = stack.captures_to_stack(&capture_block.captures); let mut stack_captures = stack.captures_to_stack(&capture_block.captures);
let metadata = input.metadata(); let metadata = input.metadata();
let input: Value = input.into_value(call.head); let input: Value = input.into_value(call.head);
let mut saved_positional = None;
if let Some(var) = block.signature.get_positional(0) { if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id { if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, input.clone()); stack_captures.add_var(*var_id, input.clone());
saved_positional = Some(*var_id);
} }
} }
eval_block( let result = eval_block(
engine_state, engine_state,
&mut stack, &mut stack_captures,
&block, &block,
input.into_pipeline_data(), input.into_pipeline_data(),
call.redirect_stdout, call.redirect_stdout,
call.redirect_stderr, call.redirect_stderr,
) )
.map(|x| x.set_metadata(metadata)) .map(|x| x.set_metadata(metadata));
if call.has_flag("keep-env") {
redirect_env(engine_state, stack, &stack_captures);
// for when we support `data | let x = $in;`
// remove the variables added earlier
for var_id in capture_block.captures.keys() {
stack_captures.vars.remove(var_id);
}
if let Some(u) = saved_positional {
stack_captures.vars.remove(&u);
}
// add any new variables to the stack
stack.vars.extend(stack_captures.vars);
}
result
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {

View File

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

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