Compare commits

...

181 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
JT
b746d8427c Revert to released syntax for release-pkg 2022-09-28 07:42:25 +13:00
JT
3beaca0d06 bump to 0.69 (#6623) 2022-09-28 07:14:31 +13:00
f7647584a3 Clippy with the current stable toolchain (#6615)
Fix lints that are coming with rust 1.64

Passes with the earlier toolchain from `rust-toolchain.toml` as well.
2022-09-26 19:29:25 +02:00
f44473d510 Update reedline to better vi behavior (#6614)
See nushell/reedline#484
2022-09-26 00:22:54 +02:00
JT
d66a5398d1 Remove month and year duration constants (#6613)
remove month/year/decade durations for parsing and units, but leave the humanized output for viewing
2022-09-26 09:55:13 +13:00
JT
43905caa46 touchup some clippy warnings in tests (#6612) 2022-09-26 09:06:13 +13:00
b47bd22b37 Removes export env command (#6468)
* remove export_env command

* remove several export env usage in test code

* adjust hiding relative test case

* fix clippy

* adjust tests

* update tests

* unignore these tests to expose ut failed

* using `use` instead of `overlay use` in some tests

* Revert "using `use` instead of `overlay use` in some tests"

This reverts commit 2ae24b24c3.

* Revert "adjust hiding relative test case"

This reverts commit 4369af6d05.

* Bring back module example

* Revert "update tests"

This reverts commit 6ae94ef513.

* Fix tests

* "Fix" a test

* Remove remaining deprecated env functionality

* Re-enable environment hiding for `hide`

To not break virtualenv since the overlay update is not merged yet

* Fix hiding env in `hide` and ignore some tests

Co-authored-by: kubouch <kubouch@gmail.com>
2022-09-25 19:52:43 +03:00
7f21b7fd7e 6582 - Incorrect documentation for some string operations (#6610)
* 6582 - Incorrect documentation for some string operations

* Update crates/nu-command/src/strings/str_/contains.rs

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>

* Update crates/nu-command/src/strings/str_/ends_with.rs

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>

* Update crates/nu-command/src/strings/str_/index_of.rs

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>

* Update crates/nu-command/src/strings/str_/starts_with.rs

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>

* Run rustfmt

Co-authored-by: MichelMunoz <>
Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2022-09-25 18:09:09 +02:00
0ab4e5af2a version: show built time git branch (#6609) 2022-09-24 07:10:36 -05:00
848550771a Fix mv data loss when changing folder case (step 1) (#6599)
* Fix mv data loss when changing folder case (step 1)

* Use same-file to detect when changing case on Windows
2022-09-23 11:09:31 -07:00
d323ac3edc fix sys info mem usage (#6607) 2022-09-23 11:47:52 -05:00
2e23d4d734 fix issue 6602 (broken highlighting in find) (#6604)
* fix issue 6602 (broken highlighting in find)

Find uses highlight_search_string to see where the string was found. The problem was that the style would just "append" to the existing haystack (including the ANSI escape codes of LS_COLORS).

This first strips the ANSI escape codes out of the haystack before formatting the
output string.

* update formatting
2022-09-23 07:11:33 -05:00
d9d14b38de [Cleanup]Nu completion unit tests (#6601)
* clean up completion unit tests

* update
2022-09-22 21:50:16 +03:00
03b7dd2725 bump pinned rust version (#6600)
since rust 1.64 was just released, let's bump the pinned version but only be 1 version of rust behind instead of 2. 1 version should hopefully be enough to allow pkg repositories to get updated... i hope
2022-09-22 12:37:52 -05:00
ad0c6bf7d5 Improve "Did you mean?" suggestions (#6579)
* Copy lev_distance.rs from the rust compiler

* Minor changes to code from rust compiler

* "Did you mean" suggestions: test instrumented to generate markdown report

* Did you mean suggestions: delete test instrumentation

* Fix tests

* Fix test

`foo` has a genuine match: `for`

* Improve tests
2022-09-20 19:46:01 -05:00
9aed95408d Add "space" key to bind in vi normal mode (#6590)
* Add "space" key to bind in vi normal mode

Implements #6586

No special logic to prevent you from binding it in other modes!
Needs a separate change to reedline to make it available in the default
listing of `keybindings list`.

* Update reedline to report the available `space`

Pulls in nushell/reedline#486
2022-09-20 13:04:35 +02:00
71844755e5 add history session command (#6587) 2022-09-19 14:30:04 -05:00
0b9dd87ca8 add history session id to $nu (#6585)
* add history session id to $nu

* get nushell to compile

* update test
2022-09-19 09:28:36 -05:00
d704b05b7a Improve uniq documentation (#6580) 2022-09-18 08:24:27 -07:00
15ebf45f46 Apply clippy fix for rust 1.63.0 (#6576)
* Apply clippy fix to avoid extra allocation

error: `format!(..)` appended to existing `String`
  --> crates/nu-engine/src/documentation.rs:82:9
   |
82 |         long_desc.push_str(&format!("\n{G}Subcommands{RESET}:\n"));
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `-D clippy::format-push-string` implied by `-D warnings`
   = help: consider using `write!` to avoid the extra allocation
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#format_push_string

error: `format!(..)` appended to existing `String`
  --> crates/nu-engine/src/documentation.rs:96:9
   |
96 |         long_desc.push_str(&format!("\n{G}Parameters{RESET}:\n"));
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: consider using `write!` to avoid the extra allocation
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#format_push_string

error: `format!(..)` appended to existing `String`
   --> crates/nu-engine/src/documentation.rs:128:9
    |
128 |         long_desc.push_str(&format!("\n{}Examples{}:", G, RESET));
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = help: consider using `write!` to avoid the extra allocation
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#format_push_string

error: `format!(..)` appended to existing `String`
   --> crates/nu-engine/src/documentation.rs:202:5
    |
202 |     long_desc.push_str(&format!("\n{}Flags{}:\n", G, RESET));
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = help: consider using `write!` to avoid the extra allocation
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#format_push_string

error: could not compile `nu-engine` due to 4 previous errors

* Apply clippy fix to avoid deref on an immutable reference

error: deref on an immutable reference
   --> crates/nu-command/src/dataframe/eager/sql_context.rs:188:77
    |
188 |                         SetExpr::Select(select_stmt) => self.execute_select(&*select_stmt)?,
    |                                                                             ^^^^^^^^^^^^^
    |
    = note: `-D clippy::borrow-deref-ref` implied by `-D warnings`
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#borrow_deref_ref
help: if you would like to reborrow, try removing `&*`
    |
188 |                         SetExpr::Select(select_stmt) => self.execute_select(select_stmt)?,
    |                                                                             ~~~~~~~~~~~
help: if you would like to deref, try using `&**`
    |
188 |                         SetExpr::Select(select_stmt) => self.execute_select(&**select_stmt)?,
    |                                                                             ~~~~~~~~~~~~~~

error: deref on an immutable reference
   --> crates/nu-command/src/database/values/dsl/expression.rs:252:15
    |
252 |         match &*expr {
    |               ^^^^^^ help: if you would like to reborrow, try removing `&*`: `expr`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#borrow_deref_ref

error: could not compile `nu-command` due to 2 previous errors
2022-09-17 12:10:32 -05:00
b086f34fa2 Reinstate -a short form of save --append (#6575)
Present before engine-q merge (e.g.
265ee1281d) but not included when
--append was re-introduced at
https://github.com/nushell/nushell/pull/4744.
2022-09-17 07:02:17 -05:00
5491634dda escape external args (#6560) 2022-09-17 06:07:45 -05:00
e7bf89b311 Add export-env eval to use command (#6572) 2022-09-17 02:36:17 +03:00
4fdfd3d15e rename with_sql to query dfr (#6568)
* rename with_sql to query dfr

* add search terms

* update example command
2022-09-16 08:34:58 -05:00
35a521d762 Fix 6529 - Trim overlay name (#6555)
* trim overlay name

* format

* Update tests/overlays/mod.rs

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>

* cleanup

* new tests

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2022-09-16 10:27:12 +03:00
f0ae6ffe12 update sql-parser crate and all the files it touches (#6566)
* update sql-parser crate and all the files it touches

* undo adding extra as a default feature
2022-09-15 18:03:43 -05:00
10b9c65cb7 synchronize the db commands with file names (#6565) 2022-09-15 14:39:36 -05:00
02e3f49bce provide a way to use sql to query dataframes (#6537) 2022-09-15 09:22:00 -05:00
f6c791f199 Use style from lscolors to render the rest of the filename (#6564)
* Use style from lscolors to render the rest of the filename

Related: https://github.com/nushell/nushell/pull/6556#issuecomment-1246681644

* Make cargo-clippy happy
2022-09-15 06:12:20 -05:00
cc62e4db26 update to the latest sysinfo crate (#6563) 2022-09-15 05:47:40 -05:00
56bb9e92cb Use stripped path for lscolors to get style (#6561) 2022-09-15 05:34:47 -05:00
2791251268 update text in readme file (#6557) 2022-09-14 15:25:55 +02:00
b159bf2c28 Make clickable links smarter (#6556)
* Disable clickable links when we can't get metadata of files

Fixes #6498

* Refactor path name rendering related code

* Make clickable links smarter

* Remove unneeded clone

* Return early if `use_ls_colors` is disabled
2022-09-14 05:55:41 -05:00
12a0fe39f7 default to $nothing if cellpath not found (#6535)
* default to  if cellpath not found

* fmt

* add test

* fix test

* fix clippy

* move behaviour behind `-i` flag

* prevent any possibility of an unspanned error

* ignore all errors

* seperate testes

* fmt
2022-09-13 16:17:16 +03:00
df6a7b6f5c shell_integration: Report current working directory as OSC 7 (#6481)
This is a de-facto standard supported by many terminals, originally
added to macOS Terminal.app, now also supported by VTE (GNOME),
Konsole (KDE), WezTerm, and more.
2022-09-13 07:36:53 -05:00
8564c5371f Add more overlay use usage (#6551) 2022-09-13 10:38:21 +03:00
d08212409f Support Arrow IPC file format with dataframes (#6548)
* Add support for Arrow IPC file format

Add support for Arrow IPC file format to dataframes commands. Support
opening of Arrow IPC-format files with extension '.arrow' or '.ipc' in
the open-df command. Add a 'to arrow' command to write a dataframe to
Arrow IPC format.

* Add unit test for open-df on Arrow

* Add -t flag to open-df command

Add a `--type`/`-t` flag to the `open-df` command, to explicitly specify
the type of file being used. Allowed values are the same at the set of
allowed file extensions.
2022-09-12 18:30:20 -05:00
4490e97a13 Bump dev version to v0.68.2 (#6538) 2022-09-12 08:29:39 +12:00
2bb367f570 Revert "Try again: in unix like system, set foreground process while running external command (#6273)" (#6542)
This reverts commit 1d18f6947e.
2022-09-11 15:14:58 -05:00
367f79cb4f Don't compute 'did you mean' suggestions unless showing them to user (#6540) 2022-09-11 09:58:19 -07:00
4926865c4e str collect => str join (#6531)
* Initialize join.rs as a copy of collect.rs

* Evolve StrCollect into StrJoin

* Replace 'str collect' with 'str join' everywhere

git ls-files | lines | par-each { |it| sed -i 's,str collect,str join,g' $it }

* Deprecate 'str collect'

* Revert "Deprecate 'str collect'"

This reverts commit 959d14203e.

* Change `str collect` help message to say that it is deprecated

We cannot remove `str collect` currently (i.e. via
`nu_protocol::ShellError::DeprecatedCommand` since a prominent project
uses the API:

b85542c31c/src/virtualenv/activation/nushell/activate.nu (L43)
2022-09-11 11:48:27 +03:00
9ee4086dfa Add a 'commandline' command for manipulating the current buffer (#6492)
* Add a 'commandline' command for manipulating the current buffer

from `executehostcommand` keybindings. Inspired by fish:
https://fishshell.com/docs/current/cmds/commandline.html

* Update to development reedline

Includes nushell/reedline#472

Co-authored-by: sholderbach <sholderbach@users.noreply.github.com>
2022-09-09 15:31:32 -05:00
3e0655cdba build: update cpufeatures crate (#6527)
Version registered in Cargo.lock was yanked, suggesting a significant
issue with the older version.
2022-09-09 13:10:04 +02:00
368 changed files with 10615 additions and 6995 deletions

View File

@ -1,17 +1,26 @@
# Description
(description of your pull request here)
(Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience.)
# Tests
# Major Changes
Make sure you've done the following:
If you're considering making any major change to nushell, before starting work on it, seek feedback from regular contributors and get approval for the idea from the core team either on [Discord](https://discordapp.com/invite/NtAbbGn) or [GitHub issue](https://github.com/nushell/nushell/issues/new/choose).
Making sure we're all on board with the change saves everybody's time.
Thanks!
- [ ] Add tests that cover your changes, either in the command examples, the crate/tests folder, or in the /tests folder.
- [ ] Try to think about corner cases and various ways how your changes could break. Cover them with tests.
- [ ] If adding tests is not possible, please document in the PR body a minimal example with steps on how to reproduce so one can verify your change works.
# Tests + Formatting
Make sure you've done the following, if applicable:
- Add tests that cover your changes (either in the command examples, the crate/tests folder, or in the /tests folder)
- Try to think about corner cases and various ways how your changes could break. Cover those in the tests
Make sure you've run and fixed any issues with these commands:
- [ ] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
- [ ] `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
- [ ] `cargo test --workspace --features=extra` to check that all the tests pass
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# 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
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
# makes ci use rust-toolchain.toml
# with:
# profile: minimal
# toolchain: ${{ matrix.rust }}
# override: true
# components: rustfmt, clippy
- uses: Swatinem/rust-cache@v1
with:
key: "v2" # increment this to bust the cache if needed
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
- name: Rustfmt
uses: actions-rs/cargo@v1
uses: actions-rs/cargo@v1.0.1
with:
command: fmt
args: --all -- --check
- name: Clippy
uses: actions-rs/cargo@v1
uses: actions-rs/cargo@v1.0.1
with:
command: clippy
args: --features=extra --workspace --exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
@ -72,22 +62,13 @@ jobs:
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
# makes ci use rust-toolchain.toml
# with:
# profile: minimal
# toolchain: ${{ matrix.rust }}
# override: true
- uses: Swatinem/rust-cache@v1
with:
key: ${{ matrix.style }}v3 # increment this to bust the cache if needed
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
- name: Tests
uses: actions-rs/cargo@v1
uses: actions-rs/cargo@v1.0.1
with:
command: test
args: --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
@ -108,28 +89,19 @@ jobs:
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
# makes ci use rust-toolchain.toml
# with:
# profile: minimal
# toolchain: ${{ matrix.rust }}
# override: true
- uses: Swatinem/rust-cache@v1
with:
key: "2" # increment this to bust the cache if needed
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
- name: Install Nushell
uses: actions-rs/cargo@v1
uses: actions-rs/cargo@v1.0.1
with:
command: install
args: --path=. --profile ci --no-default-features
- name: Setup Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: "3.10"
@ -159,24 +131,19 @@ jobs:
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
# makes ci use rust-toolchain.toml
# with:
# profile: minimal
# toolchain: ${{ matrix.rust }}
# override: true
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
- name: Clippy
uses: actions-rs/cargo@v1
uses: actions-rs/cargo@v1.0.1
with:
command: clippy
args: --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
- name: Tests
uses: actions-rs/cargo@v1
uses: actions-rs/cargo@v1.0.1
with:
command: test
args: --profile ci --package nu_plugin_*

View File

@ -76,9 +76,9 @@ cp -v README.release.txt $'($dist)/README.txt'
$'(char nl)Check binary release version detail:'; hr-line
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 {
(do -i { ./output/nu -c 'version' }) | str collect
(do -i { ./output/nu -c 'version' }) | str join
}
if ($ver | str trim | is-empty) {
$'(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
if $os in ['ubuntu-latest', 'macos-latest'] {
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
let files = (ls | get name)
let dest = $'($bin)-($version)-($target)'
let archive = $'($dist)/($dest).tar.gz'
let archive = $'($dist)/($bin)-($version)-($target).tar.gz'
tar czf $archive *
mkdir $dest
$files | each {|it| mv $it $dest } | ignore
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls $dest
tar -czf $archive $dest
print $'archive: ---> ($archive)'; ls $archive
echo $'::set-output name=archive::($archive)'

View File

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

4
.gitignore vendored
View File

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

View File

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

977
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"
license = "MIT"
name = "nu"
readme = "README.md"
repository = "https://github.com/nushell/nushell"
rust-version = "1.60"
version = "0.68.1"
version = "0.71.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -37,23 +36,24 @@ chrono = { version = "0.4.21", features = ["serde"] }
crossterm = "0.24.0"
ctrlc = "3.2.1"
log = "0.4"
miette = "5.1.0"
miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
nu-ansi-term = "0.46.0"
nu-cli = { path="./crates/nu-cli", version = "0.68.1" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.68.1" }
nu-command = { path="./crates/nu-command", version = "0.68.1" }
nu-engine = { path="./crates/nu-engine", version = "0.68.1" }
nu-json = { path="./crates/nu-json", version = "0.68.1" }
nu-parser = { path="./crates/nu-parser", version = "0.68.1" }
nu-path = { path="./crates/nu-path", version = "0.68.1" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.68.1" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.68.1" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.68.1" }
nu-system = { path = "./crates/nu-system", version = "0.68.1" }
nu-table = { path = "./crates/nu-table", version = "0.68.1" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.68.1" }
nu-utils = { path = "./crates/nu-utils", version = "0.68.1" }
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
nu-cli = { path="./crates/nu-cli", version = "0.71.0" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.71.0" }
nu-command = { path="./crates/nu-command", version = "0.71.0" }
nu-engine = { path="./crates/nu-engine", version = "0.71.0" }
nu-json = { path="./crates/nu-json", version = "0.71.0" }
nu-parser = { path="./crates/nu-parser", version = "0.71.0" }
nu-path = { path="./crates/nu-path", version = "0.71.0" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.71.0" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.71.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.71.0" }
nu-system = { path = "./crates/nu-system", version = "0.71.0" }
nu-table = { path = "./crates/nu-table", version = "0.71.0" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.71.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.71.0" }
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
rayon = "1.5.1"
is_executable = "1.0.1"
simplelog = "0.12.0"
@ -64,8 +64,16 @@ time = "0.3.12"
openssl = { version = "0.10.38", features = ["vendored"], optional = true }
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]
nu-test-support = { path="./crates/nu-test-support", version = "0.68.1" }
nu-test-support = { path="./crates/nu-test-support", version = "0.71.0" }
tempfile = "3.2.0"
assert_cmd = "2.0.2"
pretty_assertions = "1.0.0"
@ -74,14 +82,11 @@ hamcrest2 = "0.3.0"
rstest = {version = "0.15.0", default-features = false}
itertools = "0.10.3"
[target.'cfg(windows)'.build-dependencies]
winres = "0.1"
[features]
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
extra = ["default", "dataframe", "database"]
default = ["plugin", "which-support", "trash-support"]
stable = ["default"]
extra = ["default", "dataframe", "database"]
wasi = []
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
static-link-openssl = ["dep:openssl"]
@ -122,3 +127,7 @@ debug = false
name = "nu"
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]
# 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 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 are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.

View File

@ -1,3 +1,3 @@
To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
> register -e json ./nu_plugin_query
> register ./nu_plugin_query

View File

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

View File

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

View File

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

View File

@ -60,10 +60,10 @@ impl NuCompleter {
fn external_completion(
&self,
block_id: BlockId,
spans: Vec<String>,
spans: &[String],
offset: usize,
span: Span,
) -> Vec<Suggestion> {
) -> Option<Vec<Suggestion>> {
let stack = self.stack.clone();
let block = self.engine_state.get_block(block_id);
let mut callee_stack = stack.gather_captures(&block.captures);
@ -75,9 +75,9 @@ impl NuCompleter {
var_id,
Value::List {
vals: spans
.into_iter()
.iter()
.map(|it| Value::String {
val: it,
val: it.to_string(),
span: Span::unknown(),
})
.collect(),
@ -109,13 +109,13 @@ impl NuCompleter {
offset,
);
return result;
return Some(result);
}
}
Err(err) => println!("failed to eval completer block: {}", err),
}
vec![]
None
}
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 (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set);
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 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();
prefix.remove(pos - (flat.0.start - span_offset));
let index = pos - (flat.0.start - span_offset);
prefix.drain(index..);
// Variables completion
if prefix.starts_with(b"$") || most_left_var.is_some() {
@ -211,7 +213,11 @@ impl NuCompleter {
// We got no results for internal completion
// now we can check if external completer is set and use it
if let Some(block_id) = config.external_completer {
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;
}
// 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
let mut completer = FileCompletion::new(self.engine_state.clone());
out = self.process_completion(
@ -352,12 +367,6 @@ impl NuCompleter {
if !out.is_empty() {
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);
}
}
};
}
@ -422,7 +431,7 @@ fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option<MatchedAl
}
// Push the rest to names vector.
if pos < input.len() {
vec_names.push((&input[pos..]).to_owned());
vec_names.push(input[pos..].to_owned());
}
for name in &vec_names {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -114,7 +114,7 @@ pub(crate) fn add_menus(
let res = eval_block(&engine_state, &mut temp_stack, &block, input, false, false)?;
if let PipelineData::Value(value, None) = res {
for menu in create_menus(&value, config)? {
for menu in create_menus(&value)? {
line_editor =
add_menu(line_editor, &menu, engine_state.clone(), stack, config)?;
}
@ -491,7 +491,7 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) {
KeyCode::Tab,
ReedlineEvent::UntilFound(vec![
ReedlineEvent::Menu("completion_menu".to_string()),
ReedlineEvent::MenuNext,
ReedlineEvent::Edit(vec![EditCommand::Complete]),
]),
);
@ -666,6 +666,7 @@ fn add_parsed_keybinding(
KeyCode::Char(char)
}
"space" => KeyCode::Char(' '),
"down" => KeyCode::Down,
"up" => KeyCode::Up,
"left" => KeyCode::Left,
@ -821,6 +822,8 @@ fn event_from_record(
"ctrld" => ReedlineEvent::CtrlD,
"ctrlc" => ReedlineEvent::CtrlC,
"enter" => ReedlineEvent::Enter,
"submit" => ReedlineEvent::Submit,
"submitornewline" => ReedlineEvent::SubmitOrNewline,
"esc" | "escape" => ReedlineEvent::Esc,
"up" => ReedlineEvent::Up,
"down" => ReedlineEvent::Down,
@ -961,6 +964,7 @@ fn edit_from_record(
let char = extract_char(value, config)?;
EditCommand::MoveLeftBefore(char)
}
"complete" => EditCommand::Complete,
e => {
return Err(ShellError::UnsupportedConfigValue(
"reedline EditCommand".to_string(),

View File

@ -11,17 +11,19 @@ use log::{info, trace, warn};
use miette::{IntoDiagnostic, Result};
use nu_color_config::get_color_config;
use nu_engine::{convert_env_values, eval_block};
use nu_parser::{lex, parse};
use nu_parser::{lex, parse, trim_quotes_str};
use nu_protocol::{
ast::PathMember,
engine::{EngineState, Stack, StateWorkingSet},
engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
Spanned, Type, Value, VarId,
};
use reedline::{DefaultHinter, Emacs, SqliteBackedHistory, Vi};
use std::io::{self, Write};
use std::{sync::atomic::Ordering, time::Instant};
use strip_ansi_escapes::strip;
use reedline::{DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
use std::{
io::{self, Write},
sync::atomic::Ordering,
time::Instant,
};
use sysinfo::SystemExt;
// According to Daniel Imms @Tyriar, we need to do these this way:
@ -38,7 +40,6 @@ pub fn evaluate_repl(
engine_state: &mut EngineState,
stack: &mut Stack,
nushell_path: &str,
is_perf_true: bool,
prerun_command: Option<Spanned<String>>,
) -> Result<()> {
use reedline::{FileBackedHistory, Reedline, Signal};
@ -48,7 +49,7 @@ pub fn evaluate_repl(
if !atty::is(atty::Stream::Stdin) {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Nushell launched as interactive REPL but STDIN is not a TTY, either launch in a valid terminal or provide arguments to invoke a script!",
"Nushell launched as a REPL, but STDIN is not a TTY; either launch in a valid terminal or provide arguments to invoke a script!",
))
.into_diagnostic();
}
@ -57,14 +58,12 @@ pub fn evaluate_repl(
let mut nu_prompt = NushellPrompt::new();
if is_perf_true {
info!(
"translate environment vars {}:{}:{}",
file!(),
line!(),
column!()
);
}
info!(
"translate environment vars {}:{}:{}",
file!(),
line!(),
column!()
);
// Translate environment variables from Strings to Values
if let Some(e) = convert_env_values(engine_state, stack) {
@ -89,31 +88,32 @@ pub fn evaluate_repl(
},
);
if is_perf_true {
info!(
"load config initially {}:{}:{}",
file!(),
line!(),
column!()
);
}
info!(
"load config initially {}:{}:{}",
file!(),
line!(),
column!()
);
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
let mut line_editor = Reedline::create();
// Now that reedline is created, get the history session id and store it in engine_state
let hist_sesh = match line_editor.get_history_session_id() {
Some(id) => i64::from(id),
None => 0,
};
engine_state.history_session_id = hist_sesh;
// Get the config once for the history `max_history_size`
// Updating that will not be possible in one session
let config = engine_state.get_config();
if is_perf_true {
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
}
let mut line_editor = Reedline::create();
let history_path = crate::config_files::get_history_path(
nushell_path,
engine_state.config.history_file_format,
);
if let Some(history_path) = history_path.as_deref() {
if is_perf_true {
info!("setup history {}:{}:{}", file!(), line!(), column!());
}
info!("setup history {}:{}:{}", file!(), line!(), column!());
let history: Box<dyn reedline::History> = match engine_state.config.history_file_format {
HistoryFileFormat::PlainText => Box::new(
@ -139,15 +139,7 @@ pub fn evaluate_repl(
if use_ansi {
println!("{}", banner);
} else {
let stripped_string = {
if let Ok(bytes) = strip(&banner) {
String::from_utf8_lossy(&bytes).to_string()
} else {
banner
}
};
println!("{}", stripped_string);
println!("{}", nu_utils::strip_ansi_string_likely(banner));
}
}
@ -163,14 +155,12 @@ pub fn evaluate_repl(
}
loop {
if is_perf_true {
info!(
"load config each loop {}:{}:{}",
file!(),
line!(),
column!()
);
}
info!(
"load config each loop {}:{}:{}",
file!(),
line!(),
column!()
);
let cwd = get_guaranteed_cwd(engine_state, stack);
@ -191,15 +181,11 @@ pub fn evaluate_repl(
let config = engine_state.get_config();
if is_perf_true {
info!("setup colors {}:{}:{}", file!(), line!(), column!());
}
info!("setup colors {}:{}:{}", file!(), line!(), column!());
let color_hm = get_color_config(config);
if is_perf_true {
info!("update reedline {}:{}:{}", file!(), line!(), column!());
}
info!("update reedline {}:{}:{}", file!(), line!(), column!());
let engine_reference = std::sync::Arc::new(engine_state.clone());
line_editor = line_editor
.with_highlighter(Box::new(NuHighlighter {
@ -256,18 +242,14 @@ pub fn evaluate_repl(
};
if config.sync_history_on_enter {
if is_perf_true {
info!("sync history {}:{}:{}", file!(), line!(), column!());
}
info!("sync history {}:{}:{}", file!(), line!(), column!());
if let Err(e) = line_editor.sync_history() {
warn!("Failed to sync history: {}", e);
}
}
if is_perf_true {
info!("setup keybindings {}:{}:{}", file!(), line!(), column!());
}
info!("setup keybindings {}:{}:{}", file!(), line!(), column!());
// Changing the line editor based on the found keybindings
line_editor = match create_keybindings(config) {
@ -291,14 +273,12 @@ pub fn evaluate_repl(
}
};
if is_perf_true {
info!("prompt_update {}:{}:{}", file!(), line!(), column!());
}
info!("prompt_update {}:{}:{}", file!(), line!(), column!());
// Right before we start our prompt and take input from the user,
// fire the "pre_prompt" hook
if let Some(hook) = config.hooks.pre_prompt.clone() {
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook) {
report_error_new(engine_state, &err);
}
}
@ -313,25 +293,23 @@ pub fn evaluate_repl(
}
let config = engine_state.get_config();
let prompt =
prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt, is_perf_true);
let prompt = prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt);
entry_num += 1;
if is_perf_true {
info!(
"finished setup, starting repl {}:{}:{}",
file!(),
line!(),
column!()
);
}
info!(
"finished setup, starting repl {}:{}:{}",
file!(),
line!(),
column!()
);
let input = line_editor.read_line(prompt);
let shell_integration = config.shell_integration;
match input {
Ok(Signal::Success(s)) => {
let hostname = sys.host_name();
let history_supports_meta =
matches!(config.history_file_format, HistoryFileFormat::Sqlite);
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
@ -339,7 +317,7 @@ pub fn evaluate_repl(
line_editor
.update_last_command_context(&|mut c| {
c.start_timestamp = Some(chrono::Utc::now());
c.hostname = sys.host_name();
c.hostname = hostname.clone();
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
c
@ -347,10 +325,16 @@ pub fn evaluate_repl(
.into_diagnostic()?; // todo: don't stop repl if error here?
}
engine_state
.repl_buffer_state
.lock()
.expect("repl buffer state mutex")
.replace(line_editor.current_buffer_contents().to_string());
// Right before we start running the code the user gave us,
// fire the "pre_execution" hook
if let Some(hook) = config.hooks.pre_execution.clone() {
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook) {
report_error_new(engine_state, &err);
}
}
@ -363,9 +347,13 @@ pub fn evaluate_repl(
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
// Check if this is a single call to a directory, if so auto-cd
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
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 {
// We have an auto-cd
@ -435,7 +423,7 @@ pub fn evaluate_repl(
span,
},
);
} else {
} else if !s.trim().is_empty() {
trace!("eval source: {}", s);
eval_source(
@ -473,6 +461,21 @@ pub fn evaluate_repl(
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
let path = cwd.as_string()?;
// Communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
run_ansi_sequence(&format!(
"\x1b]7;file://{}{}{}\x1b\\",
percent_encoding::utf8_percent_encode(
&hostname.unwrap_or_else(|| "localhost".to_string()),
percent_encoding::CONTROLS
),
if path.starts_with('/') { "" } else { "/" },
percent_encoding::utf8_percent_encode(
&path,
percent_encoding::CONTROLS
)
))?;
// Try to abbreviate string for windows title
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
path.replace(&p.as_path().display().to_string(), "~")
@ -489,6 +492,24 @@ pub fn evaluate_repl(
}
run_ansi_sequence(RESET_APPLICATION_MODE)?;
}
let mut ops = engine_state
.repl_operation_queue
.lock()
.expect("repl op queue mutex");
while let Some(op) = ops.pop_front() {
match op {
ReplOperation::Append(s) => line_editor.run_edit_commands(&[
EditCommand::MoveToEnd,
EditCommand::InsertString(s),
]),
ReplOperation::Insert(s) => {
line_editor.run_edit_commands(&[EditCommand::InsertString(s)])
}
ReplOperation::Replace(s) => line_editor
.run_edit_commands(&[EditCommand::Clear, EditCommand::InsertString(s)]),
}
}
}
Ok(Signal::CtrlC) => {
// `Reedline` clears the line content. New prompt is shown
@ -528,7 +549,7 @@ fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String {
engine_state,
stack,
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),
_ => "".to_string(),
@ -545,13 +566,13 @@ Our {}GitHub{} repository is at {}https://github.com/nushell/nushell{}
Our {}Documentation{} is located at {}http://nushell.sh{}
{}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
to modify the config.nu file and setting show_banner to false.
let-env config {{
let-env config = {{
show_banner: false
...
}}{}
@ -612,9 +633,7 @@ pub fn eval_string_with_input(
(output, working_set.render())
};
if let Err(err) = engine_state.merge_delta(delta) {
return Err(err);
}
engine_state.merge_delta(delta)?;
let input_as_pipeline_data = match input {
Some(input) => PipelineData::Value(input, None),
@ -667,6 +686,7 @@ pub fn eval_env_change_hook(
eval_hook(
engine_state,
stack,
None,
vec![("$before".into(), before), ("$after".into(), after.clone())],
hook_value,
)?;
@ -692,15 +712,17 @@ pub fn eval_env_change_hook(
pub fn eval_hook(
engine_state: &mut EngineState,
stack: &mut Stack,
input: Option<PipelineData>,
arguments: Vec<(String, Value)>,
value: &Value,
) -> Result<(), ShellError> {
) -> Result<PipelineData, ShellError> {
let value_span = value.span()?;
let condition_path = PathMember::String {
val: "condition".to_string(),
span: value_span,
};
let mut output = PipelineData::new(Span::new(0, 0));
let code_path = PathMember::String {
val: "code".to_string(),
@ -710,7 +732,7 @@ pub fn eval_hook(
match value {
Value::List { vals, .. } => {
for val in vals {
eval_hook(engine_state, stack, arguments.clone(), val)?
eval_hook(engine_state, stack, None, arguments.clone(), val)?;
}
}
Value::Record { .. } => {
@ -726,6 +748,7 @@ pub fn eval_hook(
engine_state,
stack,
block_id,
None,
arguments.clone(),
block_span,
) {
@ -805,7 +828,9 @@ pub fn eval_hook(
.collect();
match eval_block(engine_state, stack, &block, input, false, false) {
Ok(_) => {}
Ok(pipeline_data) => {
output = pipeline_data;
}
Err(err) => {
report_error_new(engine_state, &err);
}
@ -820,7 +845,14 @@ pub fn eval_hook(
span: block_span,
..
} => {
run_hook_block(engine_state, stack, block_id, arguments, block_span)?;
run_hook_block(
engine_state,
stack,
block_id,
input,
arguments,
block_span,
)?;
}
other => {
return Err(ShellError::UnsupportedConfigValue(
@ -837,7 +869,17 @@ pub fn eval_hook(
span: block_span,
..
} => {
run_hook_block(engine_state, stack, *block_id, arguments, *block_span)?;
output = PipelineData::Value(
run_hook_block(
engine_state,
stack,
*block_id,
input,
arguments,
*block_span,
)?,
None,
);
}
other => {
return Err(ShellError::UnsupportedConfigValue(
@ -851,19 +893,20 @@ pub fn eval_hook(
let cwd = get_guaranteed_cwd(engine_state, stack);
engine_state.merge_env(stack, cwd)?;
Ok(())
Ok(output)
}
pub fn run_hook_block(
engine_state: &EngineState,
stack: &mut Stack,
block_id: BlockId,
optional_input: Option<PipelineData>,
arguments: Vec<(String, Value)>,
span: Span,
) -> Result<Value, ShellError> {
let block = engine_state.get_block(block_id);
let input = PipelineData::new(span);
let input = optional_input.unwrap_or_else(|| PipelineData::new(span));
let mut callee_stack = stack.gather_captures(&block.captures);

View File

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

View File

@ -1,11 +1,11 @@
use log::trace;
use crate::repl::eval_hook;
use nu_engine::eval_block;
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
use nu_protocol::engine::StateWorkingSet;
use nu_protocol::CliError;
use nu_protocol::{
engine::{EngineState, Stack},
PipelineData, ShellError, Span, Value,
print_if_stream, PipelineData, ShellError, Span, Value,
};
#[cfg(windows)]
use nu_utils::enable_vt_processing;
@ -204,8 +204,6 @@ pub fn eval_source(
fname: &str,
input: PipelineData,
) -> bool {
trace!("eval_source");
let (block, delta) = {
let mut working_set = StateWorkingSet::new(engine_state);
let (output, err) = parse(
@ -231,23 +229,41 @@ pub fn eval_source(
}
match eval_block(engine_state, stack, &block, input, false, false) {
Ok(mut pipeline_data) => {
if let PipelineData::ExternalStream { exit_code, .. } = &mut pipeline_data {
if let Some(exit_code) = exit_code.take().and_then(|it| it.last()) {
stack.add_env_var("LAST_EXIT_CODE".to_string(), exit_code);
} else {
set_last_exit_code(stack, 0);
Ok(pipeline_data) => {
let config = engine_state.get_config();
let result;
if let PipelineData::ExternalStream {
stdout: stream,
stderr: stderr_stream,
exit_code,
..
} = pipeline_data
{
result = print_if_stream(stream, stderr_stream, false, exit_code);
} else if let Some(hook) = config.hooks.display_output.clone() {
match eval_hook(engine_state, stack, Some(pipeline_data), vec![], &hook) {
Err(err) => {
result = Err(err);
}
Ok(val) => {
result = val.print(engine_state, stack, false, false);
}
}
} 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) {
let working_set = StateWorkingSet::new(engine_state);
match result {
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

View File

@ -34,8 +34,20 @@ fn completer_strings() -> NuCompleter {
NuCompleter::new(std::sync::Arc::new(engine), stack)
}
#[test]
fn variables_dollar_sign_with_varialblecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "$ ";
let suggestions = completer.complete(target_dir, target_dir.len());
assert_eq!(7, suggestions.len());
}
#[rstest]
fn variables_completions_double_dash_argument(mut completer: NuCompleter) {
fn variables_double_dash_argument_with_flagcompletion(mut completer: NuCompleter) {
let suggestions = completer.complete("tst --", 6);
let expected: Vec<String> = vec!["--help".into(), "--mod".into()];
// dbg!(&expected, &suggestions);
@ -43,28 +55,30 @@ fn variables_completions_double_dash_argument(mut completer: NuCompleter) {
}
#[rstest]
fn variables_completions_single_dash_argument(mut completer: NuCompleter) {
fn variables_single_dash_argument_with_flagcompletion(mut completer: NuCompleter) {
let suggestions = completer.complete("tst -", 5);
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn variables_completions_command(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-command ", 9);
fn variables_command_with_commandcompletion(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-c ", 4);
let expected: Vec<String> = vec!["my-command".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn variables_completions_subcommands(mut completer_strings: NuCompleter) {
fn variables_subcommands_with_customcompletion(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-command ", 11);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn variables_completions_subcommands_2(mut completer_strings: NuCompleter) {
fn variables_customcompletion_subcommands_with_customcompletion_2(
mut completer_strings: NuCompleter,
) {
let suggestions = completer_strings.complete("my-command ", 11);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions);
@ -100,7 +114,7 @@ fn external_completer_trailing_space() {
let block = "let external_completer = {|spans| $spans}";
let input = "gh alias ".to_string();
let suggestions = run_external_completion(&block, &input);
let suggestions = run_external_completion(block, &input);
assert_eq!(3, suggestions.len());
assert_eq!("gh", suggestions.get(0).unwrap().value);
assert_eq!("alias", suggestions.get(1).unwrap().value);
@ -112,7 +126,7 @@ fn external_completer_no_trailing_space() {
let block = "let external_completer = {|spans| $spans}";
let input = "gh alias".to_string();
let suggestions = run_external_completion(&block, &input);
let suggestions = run_external_completion(block, &input);
assert_eq!(2, suggestions.len());
assert_eq!("gh", suggestions.get(0).unwrap().value);
assert_eq!("alias", suggestions.get(1).unwrap().value);
@ -123,7 +137,7 @@ fn external_completer_pass_flags() {
let block = "let external_completer = {|spans| $spans}";
let input = "gh api --".to_string();
let suggestions = run_external_completion(&block, &input);
let suggestions = run_external_completion(block, &input);
assert_eq!(3, suggestions.len());
assert_eq!("gh", suggestions.get(0).unwrap().value);
assert_eq!("api", suggestions.get(1).unwrap().value);
@ -168,7 +182,7 @@ fn file_completions() {
}
#[test]
fn command_ls_completion() {
fn command_ls_with_filecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
@ -200,7 +214,7 @@ fn command_ls_completion() {
match_suggestions(expected_paths, suggestions)
}
#[test]
fn command_open_completion() {
fn command_open_with_filecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
@ -233,7 +247,7 @@ fn command_open_completion() {
}
#[test]
fn command_rm_completion() {
fn command_rm_with_globcompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
@ -266,7 +280,7 @@ fn command_rm_completion() {
}
#[test]
fn command_cp_completion() {
fn command_cp_with_globcompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
@ -299,7 +313,7 @@ fn command_cp_completion() {
}
#[test]
fn command_save_completion() {
fn command_save_with_filecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
@ -332,7 +346,7 @@ fn command_save_completion() {
}
#[test]
fn command_touch_completion() {
fn command_touch_with_filecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
@ -365,7 +379,7 @@ fn command_touch_completion() {
}
#[test]
fn command_watch_completion() {
fn command_watch_with_filecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
@ -431,7 +445,7 @@ fn flag_completions() {
}
#[test]
fn folder_completions() {
fn folder_with_directorycompletions() {
// Create a new engine
let (dir, dir_str, engine, stack) = new_engine();
@ -623,7 +637,7 @@ fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);
completer.complete(&input, input.len())
completer.complete(input, input.len())
}
#[test]
@ -658,3 +672,63 @@ fn unknown_command_completion() {
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

@ -104,9 +104,7 @@ pub fn merge_input(
(block, working_set.render())
};
if let Err(err) = engine_state.merge_delta(delta) {
return Err(err);
}
engine_state.merge_delta(delta)?;
assert!(eval_block(
engine_state,

View File

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

View File

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

View File

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

View File

@ -5,27 +5,26 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
edition = "2021"
license = "MIT"
name = "nu-command"
version = "0.68.1"
version = "0.71.0"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-color-config = { path = "../nu-color-config", version = "0.68.1" }
nu-engine = { path = "../nu-engine", version = "0.68.1" }
nu-glob = { path = "../nu-glob", version = "0.68.1" }
nu-json = { path = "../nu-json", version = "0.68.1" }
nu-parser = { path = "../nu-parser", version = "0.68.1" }
nu-path = { path = "../nu-path", version = "0.68.1" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.68.1" }
nu-protocol = { path = "../nu-protocol", version = "0.68.1" }
nu-system = { path = "../nu-system", version = "0.68.1" }
nu-table = { path = "../nu-table", version = "0.68.1" }
nu-term-grid = { path = "../nu-term-grid", version = "0.68.1" }
nu-test-support = { path = "../nu-test-support", version = "0.68.1" }
nu-utils = { path = "../nu-utils", version = "0.68.1" }
nu-color-config = { path = "../nu-color-config", version = "0.71.0" }
nu-engine = { path = "../nu-engine", version = "0.71.0" }
nu-glob = { path = "../nu-glob", version = "0.71.0" }
nu-json = { path = "../nu-json", version = "0.71.0" }
nu-parser = { path = "../nu-parser", version = "0.71.0" }
nu-path = { path = "../nu-path", version = "0.71.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.71.0" }
nu-protocol = { path = "../nu-protocol", version = "0.71.0" }
nu-system = { path = "../nu-system", version = "0.71.0" }
nu-table = { path = "../nu-table", version = "0.71.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.71.0" }
nu-utils = { path = "../nu-utils", version = "0.71.0" }
nu-ansi-term = "0.46.0"
num-format = { version = "0.4.0" }
num-format = { version = "0.4.3" }
# Potential dependencies for extras
alphanumeric-sort = "1.4.4"
@ -55,7 +54,7 @@ is-root = "0.1.2"
itertools = "0.10.0"
lazy_static = "1.4.0"
log = "0.4.14"
lscolors = { version = "0.12.0", features = ["crossterm"]}
lscolors = { version = "0.12.0", features = ["crossterm"], default-features = false }
md5 = { package = "md-5", version = "0.10.0" }
meval = "0.2.0"
mime = "0.3.16"
@ -70,6 +69,7 @@ rayon = "1.5.1"
reqwest = {version = "0.11", features = ["blocking", "json"] }
roxmltree = "0.14.0"
rust-embed = "6.3.0"
same-file = "1.0.6"
serde = { version="1.0.123", features=["derive"] }
serde_ini = "0.2.0"
serde_urlencoded = "0.7.0"
@ -77,8 +77,7 @@ serde_yaml = "0.9.4"
sha2 = "0.10.0"
# Disable default features b/c the default features build Git (very slow to compile)
shadow-rs = { version = "0.16.1", default-features = false }
strip-ansi-escapes = "0.1.1"
sysinfo = "0.25.2"
sysinfo = "0.26.2"
terminal_size = "0.2.1"
thiserror = "1.0.31"
titlecase = "2.0.0"
@ -87,14 +86,18 @@ unicode-segmentation = "1.8.0"
url = "2.2.1"
uuid = { version = "1.1.2", features = ["v4"] }
which = { version = "4.3.0", optional = true }
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
wax = { version = "0.5.0", features = ["diagnostics"] }
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
wax = { version = "0.5.0" }
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
sqlparser = { version = "0.16.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]
umask = "2.0.0"
users = "0.11.0"
libc = "0.2"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
version = "2.1.3"
@ -115,6 +118,7 @@ features = [
"dtype-struct",
"dtype-categorical",
"dynamic_groupby",
"ipc",
"is_in",
"json",
"lazy",
@ -131,9 +135,8 @@ features = [
]
[target.'cfg(windows)'.dependencies.windows]
version = "0.37.0"
version = "0.42.0"
features = [
"alloc",
"Win32_Foundation",
"Win32_Storage_FileSystem",
"Win32_System_SystemServices",
@ -150,6 +153,8 @@ database = ["sqlparser", "rusqlite"]
shadow-rs = { version = "0.16.1", default-features = false }
[dev-dependencies]
nu-test-support = { path = "../nu-test-support", version = "0.71.0" }
hamcrest2 = "0.3.0"
dirs-next = "2.0.0"
proptest = "1.0.0"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -215,8 +215,8 @@ fn histogram_impl(
const MAX_FREQ_COUNT: f64 = 100.0;
for (val, count) in counter.into_iter() {
let quantile = match calc_method {
PercentageCalcMethod::Normalize => (count as f64 / total_cnt as f64),
PercentageCalcMethod::Relative => (count as f64 / max_cnt as f64),
PercentageCalcMethod::Normalize => count as f64 / total_cnt as f64,
PercentageCalcMethod::Relative => count as f64 / max_cnt as f64,
};
let percentage = format!("{:.2}%", quantile * 100_f64);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -114,14 +114,21 @@ impl Command for SubCommand {
}),
},
Example {
description: "Convert string to a named duration",
description: "Convert string to the requested duration as a string",
example: "'7min' | into duration --convert sec",
result: Some(Value::String {
val: "420 sec".to_string(),
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 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 config = engine_state.get_config();
let float_precision = config.float_precision as usize;
input.map(
move |v| {
if column_paths.is_empty() {
action(&v, &convert_to_unit, head)
action(&v, &convert_to_unit, float_precision, head)
} else {
let mut ret = v;
for path in &column_paths {
let d = convert_to_unit.clone();
let r = ret.update_cell_path(
&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 {
return Value::Error { error };
@ -166,127 +175,129 @@ fn convert_str_from_unit_to_unit(
to_unit: &str,
span: Span,
value_span: Span,
) -> Result<i64, ShellError> {
) -> Result<f64, ShellError> {
match (from_unit, to_unit) {
("ns", "ns") => Ok(val),
("ns", "us") => Ok(val / 1000),
("ns", "ms") => Ok(val / 1000 / 1000),
("ns", "sec") => Ok(val / 1000 / 1000 / 1000),
("ns", "min") => Ok(val / 1000 / 1000 / 1000 / 60),
("ns", "hr") => Ok(val / 1000 / 1000 / 1000 / 60 / 60),
("ns", "day") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24),
("ns", "wk") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 7),
("ns", "month") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 30),
("ns", "yr") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 365),
("ns", "dec") => Ok(val / 10 / 1000 / 1000 / 1000 / 60 / 60 / 24 / 365),
("ns", "ns") => Ok(val as f64),
("ns", "us") => Ok(val as f64 / 1000.0),
("ns", "ms") => Ok(val as f64 / 1000.0 / 1000.0),
("ns", "sec") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0),
("ns", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0),
("ns", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0),
("ns", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
("ns", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
("ns", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
("ns", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("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", "us") => Ok(val),
("us", "ms") => Ok(val / 1000),
("us", "sec") => Ok(val / 1000 / 1000),
("us", "min") => Ok(val / 1000 / 1000 / 60),
("us", "hr") => Ok(val / 1000 / 1000 / 60 / 60),
("us", "day") => Ok(val / 1000 / 1000 / 60 / 60 / 24),
("us", "wk") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 7),
("us", "month") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 30),
("us", "yr") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 365),
("us", "dec") => Ok(val / 10 / 1000 / 1000 / 60 / 60 / 24 / 365),
("us", "ns") => Ok(val as f64 * 1000.0),
("us", "us") => Ok(val as f64),
("us", "ms") => Ok(val as f64 / 1000.0),
("us", "sec") => Ok(val as f64 / 1000.0 / 1000.0),
("us", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0),
("us", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0),
("us", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
("us", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
("us", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
("us", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("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", "us") => Ok(val * 1000),
("ms", "ms") => Ok(val),
("ms", "sec") => Ok(val / 1000),
("ms", "min") => Ok(val / 1000 / 60),
("ms", "hr") => Ok(val / 1000 / 60 / 60),
("ms", "day") => Ok(val / 1000 / 60 / 60 / 24),
("ms", "wk") => Ok(val / 1000 / 60 / 60 / 24 / 7),
("ms", "month") => Ok(val / 1000 / 60 / 60 / 24 / 30),
("ms", "yr") => Ok(val / 1000 / 60 / 60 / 24 / 365),
("ms", "dec") => Ok(val / 10 / 1000 / 60 / 60 / 24 / 365),
("ms", "ns") => Ok(val as f64 * 1000.0 * 1000.0),
("ms", "us") => Ok(val as f64 * 1000.0),
("ms", "ms") => Ok(val as f64),
("ms", "sec") => Ok(val as f64 / 1000.0),
("ms", "min") => Ok(val as f64 / 1000.0 / 60.0),
("ms", "hr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0),
("ms", "day") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0),
("ms", "wk") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
("ms", "month") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
("ms", "yr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("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", "us") => Ok(val * 1000 * 1000),
("sec", "ms") => Ok(val * 1000),
("sec", "sec") => Ok(val),
("sec", "min") => Ok(val / 60),
("sec", "hr") => Ok(val / 60 / 60),
("sec", "day") => Ok(val / 60 / 60 / 24),
("sec", "wk") => Ok(val / 60 / 60 / 24 / 7),
("sec", "month") => Ok(val / 60 / 60 / 24 / 30),
("sec", "yr") => Ok(val / 60 / 60 / 24 / 365),
("sec", "dec") => Ok(val / 10 / 60 / 60 / 24 / 365),
("sec", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0),
("sec", "us") => Ok(val as f64 * 1000.0 * 1000.0),
("sec", "ms") => Ok(val as f64 * 1000.0),
("sec", "sec") => Ok(val as f64),
("sec", "min") => Ok(val as f64 / 60.0),
("sec", "hr") => Ok(val as f64 / 60.0 / 60.0),
("sec", "day") => Ok(val as f64 / 60.0 / 60.0 / 24.0),
("sec", "wk") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 7.0),
("sec", "month") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 30.0),
("sec", "yr") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 365.0),
("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", "us") => Ok(val * 1000 * 1000 * 60),
("min", "ms") => Ok(val * 1000 * 60),
("min", "sec") => Ok(val * 60),
("min", "min") => Ok(val),
("min", "hr") => Ok(val / 60),
("min", "day") => Ok(val / 60 / 24),
("min", "wk") => Ok(val / 60 / 24 / 7),
("min", "month") => Ok(val / 60 / 24 / 30),
("min", "yr") => Ok(val / 60 / 24 / 365),
("min", "dec") => Ok(val / 10 / 60 / 24 / 365),
("min", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0),
("min", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0),
("min", "ms") => Ok(val as f64 * 1000.0 * 60.0),
("min", "sec") => Ok(val as f64 * 60.0),
("min", "min") => Ok(val as f64),
("min", "hr") => Ok(val as f64 / 60.0),
("min", "day") => Ok(val as f64 / 60.0 / 24.0),
("min", "wk") => Ok(val as f64 / 60.0 / 24.0 / 7.0),
("min", "month") => Ok(val as f64 / 60.0 / 24.0 / 30.0),
("min", "yr") => Ok(val as f64 / 60.0 / 24.0 / 365.0),
("min", "dec") => Ok(val as f64 / 10.0 / 60.0 / 24.0 / 365.0),
("hr", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60),
("hr", "us") => Ok(val * 1000 * 1000 * 60 * 60),
("hr", "ms") => Ok(val * 1000 * 60 * 60),
("hr", "sec") => Ok(val * 60 * 60),
("hr", "min") => Ok(val * 60),
("hr", "hr") => Ok(val),
("hr", "day") => Ok(val / 24),
("hr", "wk") => Ok(val / 24 / 7),
("hr", "month") => Ok(val / 24 / 30),
("hr", "yr") => Ok(val / 24 / 365),
("hr", "dec") => Ok(val / 10 / 24 / 365),
("hr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0),
("hr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0),
("hr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0),
("hr", "sec") => Ok(val as f64 * 60.0 * 60.0),
("hr", "min") => Ok(val as f64 * 60.0),
("hr", "hr") => Ok(val as f64),
("hr", "day") => Ok(val as f64 / 24.0),
("hr", "wk") => Ok(val as f64 / 24.0 / 7.0),
("hr", "month") => Ok(val as f64 / 24.0 / 30.0),
("hr", "yr") => Ok(val as f64 / 24.0 / 365.0),
("hr", "dec") => Ok(val as f64 / 10.0 / 24.0 / 365.0),
("day", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24),
("day", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24),
("day", "ms") => Ok(val * 1000 * 60 * 60 * 24),
("day", "sec") => Ok(val * 60 * 60 * 24),
("day", "min") => Ok(val * 60 * 24),
("day", "hr") => Ok(val * 24),
("day", "day") => Ok(val),
("day", "wk") => Ok(val / 7),
("day", "month") => Ok(val / 30),
("day", "yr") => Ok(val / 365),
("day", "dec") => Ok(val / 10 / 365),
("day", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
("day", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
("day", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0),
("day", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0),
("day", "min") => Ok(val as f64 * 60.0 * 24.0),
("day", "hr") => Ok(val as f64 * 24.0),
("day", "day") => Ok(val as f64),
("day", "wk") => Ok(val as f64 / 7.0),
("day", "month") => Ok(val as f64 / 30.0),
("day", "yr") => Ok(val as f64 / 365.0),
("day", "dec") => Ok(val as f64 / 10.0 / 365.0),
("wk", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 7),
("wk", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 7),
("wk", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 7),
("wk", "sec") => Ok(val * 60 * 60 * 24 * 7),
("wk", "min") => Ok(val * 60 * 24 * 7),
("wk", "hr") => Ok(val * 24 * 7),
("wk", "day") => Ok(val * 7),
("wk", "wk") => Ok(val),
("wk", "month") => Ok(val / 4),
("wk", "yr") => Ok(val / 52),
("wk", "dec") => Ok(val / 10 / 52),
("wk", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
("wk", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
("wk", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
("wk", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 7.0),
("wk", "min") => Ok(val as f64 * 60.0 * 24.0 * 7.0),
("wk", "hr") => Ok(val as f64 * 24.0 * 7.0),
("wk", "day") => Ok(val as f64 * 7.0),
("wk", "wk") => Ok(val as f64),
("wk", "month") => Ok(val as f64 / 4.0),
("wk", "yr") => Ok(val as f64 / 52.0),
("wk", "dec") => Ok(val as f64 / 10.0 / 52.0),
("month", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 30),
("month", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 30),
("month", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 30),
("month", "sec") => Ok(val * 60 * 60 * 24 * 30),
("month", "min") => Ok(val * 60 * 24 * 30),
("month", "hr") => Ok(val * 24 * 30),
("month", "day") => Ok(val * 30),
("month", "wk") => Ok(val * 4),
("month", "month") => Ok(val),
("month", "yr") => Ok(val / 12),
("month", "dec") => Ok(val / 10 / 12),
("month", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
("month", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
("month", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
("month", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 30.0),
("month", "min") => Ok(val as f64 * 60.0 * 24.0 * 30.0),
("month", "hr") => Ok(val as f64 * 24.0 * 30.0),
("month", "day") => Ok(val as f64 * 30.0),
("month", "wk") => Ok(val as f64 * 4.0),
("month", "month") => Ok(val as f64),
("month", "yr") => Ok(val as f64 / 12.0),
("month", "dec") => Ok(val as f64 / 10.0 / 12.0),
("yr", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 365),
("yr", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 365),
("yr", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 365),
("yr", "sec") => Ok(val * 60 * 60 * 24 * 365),
("yr", "min") => Ok(val * 60 * 24 * 365),
("yr", "hr") => Ok(val * 24 * 365),
("yr", "day") => Ok(val * 365),
("yr", "wk") => Ok(val * 52),
("yr", "month") => Ok(val * 12),
("yr", "yr") => Ok(val),
("yr", "dec") => Ok(val / 10),
("yr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
("yr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
("yr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
("yr", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 365.0),
("yr", "min") => Ok(val as f64 * 60.0 * 24.0 * 365.0),
("yr", "hr") => Ok(val as f64 * 24.0 * 365.0),
("yr", "day") => Ok(val as f64 * 365.0),
("yr", "wk") => Ok(val as f64 * 52.0),
("yr", "month") => Ok(val as f64 * 12.0),
("yr", "yr") => Ok(val as f64),
("yr", "dec") => Ok(val as f64 / 10.0),
_ => Err(ShellError::CantConvertWithValue(
"string duration".to_string(),
@ -315,9 +326,6 @@ fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, Shel
Unit::Hour => return Ok(x * 60 * 60 * 1000 * 1000 * 1000),
Unit::Day => return Ok(x * 24 * 60 * 60 * 1000 * 1000 * 1000),
Unit::Week => return Ok(x * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000),
Unit::Month => return Ok(x * 30 * 24 * 60 * 60 * 1000 * 1000 * 1000), //30 days to a month
Unit::Year => return Ok(x * 365 * 24 * 60 * 60 * 1000 * 1000 * 1000), //365 days to a year
Unit::Decade => return Ok(x * 10 * 365 * 24 * 60 * 60 * 1000 * 1000 * 1000), //365 days to a year
_ => {}
}
}
@ -353,9 +361,6 @@ fn string_to_unit_duration(
Unit::Hour => return Ok(("hr", x)),
Unit::Day => return Ok(("day", x)),
Unit::Week => return Ok(("wk", x)),
Unit::Month => return Ok(("month", x)), //30 days to a month
Unit::Year => return Ok(("yr", x)), //365 days to a year
Unit::Decade => return Ok(("dec", x)), //365 days to a year
_ => return Ok(("ns", 0)),
}
@ -375,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 {
Value::Duration {
val: _val_num,
span: _value_span,
val: val_num,
span: value_span,
} => {
if let Some(_to_unit) = convert_to_unit {
Value::Error {
error: ShellError::UnsupportedInput(
"Cannot convert from a Value::Duration right now. Try making it a string."
.into(),
span,
),
if let Some(to_unit) = convert_to_unit {
let from_unit = "ns";
let duration = *val_num;
match convert_str_from_unit_to_unit(
duration,
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 {
input.clone()
@ -408,10 +435,19 @@ fn action(input: &Value, convert_to_unit: &Option<Spanned<String>>, span: Span)
span,
*value_span,
) {
Ok(d) => Value::String {
val: format!("{} {}", d, &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 {
@ -456,7 +492,7 @@ mod test {
let expected = Value::Duration { val: 3, span };
let convert_duration = None;
let actual = action(&word, &convert_duration, span);
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
@ -470,7 +506,7 @@ mod test {
};
let convert_duration = None;
let actual = action(&word, &convert_duration, span);
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
@ -484,7 +520,7 @@ mod test {
};
let convert_duration = None;
let actual = action(&word, &convert_duration, span);
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
@ -498,7 +534,7 @@ mod test {
};
let convert_duration = None;
let actual = action(&word, &convert_duration, span);
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
@ -512,7 +548,7 @@ mod test {
};
let convert_duration = None;
let actual = action(&word, &convert_duration, span);
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
@ -526,7 +562,7 @@ mod test {
};
let convert_duration = None;
let actual = action(&word, &convert_duration, span);
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
@ -540,7 +576,7 @@ mod test {
};
let convert_duration = None;
let actual = action(&word, &convert_duration, span);
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
@ -554,7 +590,7 @@ mod test {
};
let convert_duration = None;
let actual = action(&word, &convert_duration, span);
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,84 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::ReplOperation;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category;
use nu_protocol::IntoPipelineData;
use nu_protocol::{PipelineData, ShellError, Signature, SyntaxShape, Value};
#[derive(Clone)]
pub struct Commandline;
impl Command for Commandline {
fn name(&self) -> &str {
"commandline"
}
fn signature(&self) -> Signature {
Signature::build("commandline")
.switch(
"append",
"appends the string to the end of the buffer",
Some('a'),
)
.switch(
"insert",
"inserts the string into the buffer at the cursor position",
Some('i'),
)
.switch(
"replace",
"replaces the current contents of the buffer (default)",
Some('r'),
)
.optional(
"cmd",
SyntaxShape::String,
"the string to perform the operation with",
)
.category(Category::Core)
}
fn usage(&self) -> &str {
"View or modify the current command line input buffer"
}
fn search_terms(&self) -> Vec<&str> {
vec!["repl", "interactive"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
let mut ops = engine_state
.repl_operation_queue
.lock()
.expect("repl op queue mutex");
ops.push_back(if call.has_flag("append") {
ReplOperation::Append(cmd.as_string()?)
} else if call.has_flag("insert") {
ReplOperation::Insert(cmd.as_string()?)
} else {
ReplOperation::Replace(cmd.as_string()?)
});
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
} else if let Some(ref cmd) = *engine_state
.repl_buffer_state
.lock()
.expect("repl buffer state mutex")
{
Ok(Value::String {
val: cmd.clone(),
span: call.head,
}
.into_pipeline_data())
} else {
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
}
}
}

View File

@ -2,7 +2,8 @@ use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value,
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, SyntaxShape,
Value,
};
#[derive(Clone)]
@ -22,7 +23,7 @@ impl Command for Do {
.required("block", SyntaxShape::Any, "the block to run")
.switch(
"ignore-errors",
"ignore errors as the block runs",
"ignore shell errors as the block runs",
Some('i'),
)
.switch(
@ -102,9 +103,75 @@ impl Command for Do {
Err(_) => Ok(PipelineData::new(call.head)),
}
} else if capture_errors {
// collect stdout and stderr and check exit code.
// if exit code is not 0, return back ShellError.
match result {
Ok(x) => Ok(x),
Err(err) => Ok((Value::Error { error: err }).into_pipeline_data()),
Ok(PipelineData::ExternalStream {
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 {
result
@ -119,10 +186,15 @@ impl Command for Do {
result: Some(Value::test_string("hello")),
},
Example {
description: "Run the block and ignore errors",
description: "Run the block and ignore shell errors",
example: r#"do -i { thisisnotarealcommand }"#,
result: None,
},
Example {
description: "Abort the pipeline if a program returns a non-zero exit code",
example: r#"do -c { nu -c 'exit 1' } | myscarycommand"#,
result: None,
},
Example {
description: "Run the block, with a positional parameter",
example: r#"do {|x| 100 + $x } 50"#,

View File

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

View File

@ -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,
}]
}
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

@ -1,62 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
#[derive(Clone)]
pub struct ExportEnvModule;
impl Command for ExportEnvModule {
fn name(&self) -> &str {
"export env"
}
fn usage(&self) -> &str {
"Export a block from a module that will be evaluated as an environment variable when imported."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("export env")
.required(
"name",
SyntaxShape::String,
"name of the environment variable",
)
.required(
"block",
SyntaxShape::Block(Some(vec![])),
"body of the environment variable definition",
)
.category(Category::Core)
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
//TODO: Add the env to stack
Ok(PipelineData::new(call.head))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Import and evaluate environment variable from a module",
example: r#"module foo { export env FOO_ENV { "BAZ" } }; use foo FOO_ENV; $env.FOO_ENV"#,
result: Some(Value::String {
val: "BAZ".to_string(),
span: Span::test_data(),
}),
}]
}
}

View File

@ -47,4 +47,8 @@ impl Command for ExportExtern {
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

@ -362,11 +362,13 @@ pub fn highlight_search_string(
));
}
};
// strip haystack to remove existing ansi style
let stripped_haystack = nu_utils::strip_ansi_likely(haystack);
let mut last_match_end = 0;
let style = Style::new().fg(White).on(Red);
let mut highlighted = String::new();
for cap in regex.captures_iter(haystack) {
for cap in regex.captures_iter(stripped_haystack.as_ref()) {
match cap {
Ok(capture) => {
let start = match capture.get(0) {
@ -379,10 +381,10 @@ pub fn highlight_search_string(
};
highlighted.push_str(
&string_style
.paint(&haystack[last_match_end..start])
.paint(&stripped_haystack[last_match_end..start])
.to_string(),
);
highlighted.push_str(&style.paint(&haystack[start..end]).to_string());
highlighted.push_str(&style.paint(&stripped_haystack[start..end]).to_string());
last_match_end = end;
}
Err(e) => {
@ -397,6 +399,10 @@ pub fn highlight_search_string(
}
}
highlighted.push_str(&string_style.paint(&haystack[last_match_end..]).to_string());
highlighted.push_str(
&string_style
.paint(&stripped_haystack[last_match_end..])
.to_string(),
);
Ok(highlighted)
}

View File

@ -1,7 +1,7 @@
use nu_protocol::ast::{Call, Expr, Expression, ImportPatternMember};
use nu_protocol::ast::{Call, Expr, Expression};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
#[derive(Clone)]
@ -40,12 +40,15 @@ This command is a parser keyword. For details, check:
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let import_pattern = if let Some(Expression {
let env_var_name = if let Some(Expression {
expr: Expr::ImportPattern(pat),
..
}) = call.positional_nth(0)
{
pat
Spanned {
item: String::from_utf8_lossy(&pat.head.name).to_string(),
span: pat.head.span,
}
} else {
return Err(ShellError::GenericError(
"Unexpected import".into(),
@ -56,78 +59,7 @@ This command is a parser keyword. For details, check:
));
};
let head_name_str = if let Ok(s) = String::from_utf8(import_pattern.head.name.clone()) {
s
} else {
return Err(ShellError::NonUtf8(import_pattern.head.span));
};
if let Some(module_id) = engine_state.find_module(&import_pattern.head.name, &[]) {
// The first word is a module
let module = engine_state.get_module(module_id);
let env_vars_to_hide = if import_pattern.members.is_empty() {
module.env_vars_with_head(&import_pattern.head.name)
} else {
match &import_pattern.members[0] {
ImportPatternMember::Glob { .. } => module.env_vars(),
ImportPatternMember::Name { name, span } => {
let mut output = vec![];
if let Some((name, id)) =
module.env_var_with_head(name, &import_pattern.head.name)
{
output.push((name, id));
} else if !(module.has_alias(name) || module.has_decl(name)) {
return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(),
*span,
));
}
output
}
ImportPatternMember::List { names } => {
let mut output = vec![];
for (name, span) in names {
if let Some((name, id)) =
module.env_var_with_head(name, &import_pattern.head.name)
{
output.push((name, id));
} else if !(module.has_alias(name) || module.has_decl(name)) {
return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(),
*span,
));
}
}
output
}
}
};
for (name, _) in env_vars_to_hide {
let name = if let Ok(s) = String::from_utf8(name.clone()) {
s
} else {
return Err(ShellError::NonUtf8(import_pattern.span()));
};
if stack.remove_env_var(engine_state, &name).is_none() {
return Err(ShellError::NotFound(
call.positional_nth(0)
.expect("already checked for present positional")
.span,
));
}
}
} else if !import_pattern.hidden.contains(&import_pattern.head.name)
&& stack.remove_env_var(engine_state, &head_name_str).is_none()
{
// TODO: we may want to error in the future
}
stack.remove_env_var(engine_state, &env_var_name.item);
Ok(PipelineData::new(call.head))
}

View File

@ -1,5 +1,6 @@
mod alias;
mod ast;
mod commandline;
mod debug;
mod def;
mod def_env;
@ -11,7 +12,6 @@ mod export;
mod export_alias;
mod export_def;
mod export_def_env;
mod export_env;
mod export_extern;
mod export_use;
mod extern_;
@ -30,6 +30,7 @@ mod version;
pub use alias::Alias;
pub use ast::Ast;
pub use commandline::Commandline;
pub use debug::Debug;
pub use def::Def;
pub use def_env::DefEnv;
@ -41,7 +42,6 @@ pub use export::ExportCommand;
pub use export_alias::ExportAlias;
pub use export_def::ExportDef;
pub use export_def_env::ExportDefEnv;
pub use export_env::ExportEnvModule;
pub use export_extern::ExportExtern;
pub use export_use::ExportUse;
pub use extern_::Extern;

View File

@ -55,8 +55,8 @@ impl Command for Module {
}),
},
Example {
description: "Define an environment variable in a module and evaluate it",
example: r#"module foo { export env FOO_ENV { "BAZ" } }; use foo FOO_ENV; $env.FOO_ENV"#,
description: "Define an environment variable in a module",
example: r#"module foo { export-env { let-env FOO = "BAZ" } }; use foo; $env.FOO"#,
result: Some(Value::String {
val: "BAZ".to_string(),
span: Span::test_data(),

View File

@ -20,13 +20,13 @@ impl Command for OverlayHide {
.optional("name", SyntaxShape::String, "Overlay to hide")
.switch(
"keep-custom",
"Keep all newly added symbols within the next activated overlay",
"Keep all newly added commands and aliases in the next activated overlay",
Some('k'),
)
.named(
"keep-env",
SyntaxShape::List(Box::new(SyntaxShape::String)),
"List of environment variables to keep from the hidden overlay",
"List of environment variables to keep in the next activated overlay",
Some('e'),
)
.category(Category::Core)
@ -67,23 +67,7 @@ impl Command for OverlayHide {
let keep_env: Option<Vec<Spanned<String>>> =
call.get_flag(engine_state, stack, "keep-env")?;
let env_vars_to_keep = if call.has_flag("keep-custom") {
if let Some(overlay_id) = engine_state.find_overlay(overlay_name.item.as_bytes()) {
let overlay_frame = engine_state.get_overlay(overlay_id);
let origin_module = engine_state.get_module(overlay_frame.origin);
stack
.get_overlay_env_vars(engine_state, &overlay_name.item)
.into_iter()
.filter(|(name, _)| !origin_module.has_env_var(name.as_bytes()))
.collect()
} else {
return Err(ShellError::OverlayNotFoundAtRuntime(
overlay_name.item,
overlay_name.span,
));
}
} else if let Some(env_var_names_to_keep) = keep_env {
let env_vars_to_keep = if let Some(env_var_names_to_keep) = keep_env {
let mut env_vars_to_keep = vec![];
for name in env_var_names_to_keep.into_iter() {
@ -110,22 +94,25 @@ impl Command for OverlayHide {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Hide an overlay created from a module",
description: "Keep a custom command after hiding the overlay",
example: r#"module spam { export def foo [] { "foo" } }
overlay use spam
overlay hide spam"#,
def bar [] { "bar" }
overlay hide spam --keep-custom
bar
"#,
result: None,
},
Example {
description: "Hide an overlay created from a file",
example: r#"echo 'export alias f = "foo"' | save spam.nu
example: r#"'export alias f = "foo"' | save spam.nu
overlay use spam.nu
overlay hide spam"#,
result: None,
},
Example {
description: "Hide the last activated overlay",
example: r#"module spam { export env FOO { "foo" } }
example: r#"module spam { export-env { let-env FOO = "foo" } }
overlay use spam
overlay hide"#,
result: None,

View File

@ -1,4 +1,5 @@
use nu_engine::{eval_block, find_in_dirs_env, redirect_env, CallExt};
use nu_parser::trim_quotes_str;
use nu_protocol::ast::{Call, Expr};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
@ -55,7 +56,8 @@ impl Command for OverlayUse {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
let mut name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
name_arg.item = trim_quotes_str(&name_arg.item).to_string();
let origin_module_id = if let Some(overlay_expr) = call.positional_nth(0) {
if let Expr::Overlay(module_id) = overlay_expr.expr {
@ -121,28 +123,6 @@ impl Command for OverlayUse {
let module = engine_state.get_module(module_id);
for (name, block_id) in module.env_vars() {
let name = if let Ok(s) = String::from_utf8(name.clone()) {
s
} else {
return Err(ShellError::NonUtf8(call.head));
};
let block = engine_state.get_block(block_id);
let val = eval_block(
engine_state,
caller_stack,
block,
PipelineData::new(call.head),
false,
true,
)?
.into_value(call.head);
caller_stack.add_env_var(name, val);
}
// Evaluate the export-env block (if any) and keep its environment
if let Some(block_id) = module.env_block {
let maybe_path = find_in_dirs_env(&name_arg.item, engine_state, caller_stack)?;
@ -191,19 +171,26 @@ impl Command for OverlayUse {
description: "Create an overlay from a module",
example: r#"module spam { export def foo [] { "foo" } }
overlay use spam
foo"#,
result: None,
},
Example {
description: "Create an overlay from a module and rename it",
example: r#"module spam { export def foo [] { "foo" } }
overlay use spam as spam_new
foo"#,
result: None,
},
Example {
description: "Create an overlay with a prefix",
example: r#"echo 'export def foo { "foo" }'
example: r#"'export def foo { "foo" }'
overlay use --prefix spam
spam foo"#,
result: None,
},
Example {
description: "Create an overlay from a file",
example: r#"echo 'export env FOO { "foo" }' | save spam.nu
example: r#"'export-env { let-env FOO = "foo" }' | save spam.nu
overlay use spam.nu
$env.FOO"#,
result: None,

View File

@ -1,5 +1,5 @@
use nu_engine::eval_block;
use nu_protocol::ast::{Call, Expr, Expression, ImportPatternMember};
use nu_engine::{eval_block, find_in_dirs_env, redirect_env};
use nu_protocol::ast::{Call, Expr, Expression};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
@ -35,9 +35,9 @@ impl Command for Use {
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
caller_stack: &mut Stack,
call: &Call,
_input: PipelineData,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let import_pattern = if let Some(Expression {
expr: Expr::ImportPattern(pat),
@ -58,68 +58,47 @@ impl Command for Use {
if let Some(module_id) = import_pattern.head.id {
let module = engine_state.get_module(module_id);
let env_vars_to_use = if import_pattern.members.is_empty() {
module.env_vars_with_head(&import_pattern.head.name)
} else {
match &import_pattern.members[0] {
ImportPatternMember::Glob { .. } => module.env_vars(),
ImportPatternMember::Name { name, span } => {
let mut output = vec![];
if let Some(id) = module.get_env_var_id(name) {
output.push((name.clone(), id));
} else if !module.has_decl(name) && !module.has_alias(name) {
return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(),
*span,
));
}
output
}
ImportPatternMember::List { names } => {
let mut output = vec![];
for (name, span) in names {
if let Some(id) = module.get_env_var_id(name) {
output.push((name.clone(), id));
} else if !module.has_decl(name) && !module.has_alias(name) {
return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(),
*span,
));
}
}
output
}
}
};
for (name, block_id) in env_vars_to_use {
let name = if let Ok(s) = String::from_utf8(name.clone()) {
s
} else {
return Err(ShellError::NonUtf8(import_pattern.head.span));
};
// Evaluate the export-env block if there is one
if let Some(block_id) = module.env_block {
let block = engine_state.get_block(block_id);
let val = eval_block(
engine_state,
stack,
block,
PipelineData::new(call.head),
false,
true,
)?
.into_value(call.head);
// See if the module is a file
let module_arg_str = String::from_utf8_lossy(
engine_state.get_span_contents(&import_pattern.head.span),
);
let maybe_parent = if let Some(path) =
find_in_dirs_env(&module_arg_str, engine_state, caller_stack)?
{
path.parent().map(|p| p.to_path_buf()).or(None)
} else {
None
};
stack.add_env_var(name, val);
let mut callee_stack = caller_stack.gather_captures(&block.captures);
// If so, set the currently evaluated directory (file-relative PWD)
if let Some(parent) = maybe_parent {
let file_pwd = Value::String {
val: parent.to_string_lossy().to_string(),
span: call.head,
};
callee_stack.add_env_var("FILE_PWD".to_string(), file_pwd);
}
// Run the block (discard the result)
let _ = eval_block(
engine_state,
&mut callee_stack,
block,
input,
call.redirect_stdout,
call.redirect_stderr,
)?;
// Merge the block's environment to the current stack
redirect_env(engine_state, caller_stack, &callee_stack);
}
} else {
// TODO: This is a workaround since call.positional[0].span points at 0 for some reason
// when this error is triggered
return Err(ShellError::GenericError(
format!(
"Could not import from '{}'",
@ -145,14 +124,6 @@ impl Command for Use {
span: Span::test_data(),
}),
},
Example {
description: "Define an environment variable in a module and evaluate it",
example: r#"module foo { export env FOO_ENV { "BAZ" } }; use foo FOO_ENV; $env.FOO_ENV"#,
result: Some(Value::String {
val: "BAZ".to_string(),
span: Span::test_data(),
}),
},
Example {
description: "Define a custom command that participates in the environment in a module and call it",
example: r#"module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,

View File

@ -59,7 +59,7 @@ pub fn version(
cols.push("branch".to_string());
vals.push(Value::String {
val: shadow_rs::branch(),
val: shadow::BRANCH.to_string(),
span: call.head,
});

View File

@ -125,7 +125,7 @@ impl Command for AndDb {
}
fn modify_query(query: &mut Box<Query>, expression: Expr, span: Span) -> Result<(), ShellError> {
match query.body {
match *query.body {
SetExpr::Select(ref mut select) => modify_select(select, expression, span)?,
_ => {
return Err(ShellError::GenericError(

View File

@ -113,7 +113,7 @@ fn alias_db(
Vec::new(),
)),
Some(statement) => match statement {
Statement::Query(query) => match &mut query.body {
Statement::Query(query) => match &mut *query.body {
SetExpr::Select(select) => {
select.as_mut().from.iter_mut().for_each(|table| {
let new_alias = Some(TableAlias {

View File

@ -17,7 +17,7 @@ pub fn value_into_table_factor(
Ok(TableFactor::Table {
name: ObjectName(vec![ident]),
alias,
args: Vec::new(),
args: None,
with_hints: Vec::new(),
})
}

View File

@ -96,12 +96,12 @@ fn create_statement(
) -> Result<Statement, ShellError> {
let query = Query {
with: None,
body: SetExpr::Select(Box::new(create_select(
body: Box::new(SetExpr::Select(Box::new(create_select(
connection,
engine_state,
stack,
call,
)?)),
)?))),
order_by: Vec::new(),
limit: None,
offset: None,
@ -121,18 +121,18 @@ fn modify_statement(
) -> Result<Statement, ShellError> {
match statement {
Statement::Query(ref mut query) => {
match query.body {
match *query.body {
SetExpr::Select(ref mut select) => {
let table = create_table(connection, engine_state, stack, call)?;
select.from.push(table);
}
_ => {
query.as_mut().body = SetExpr::Select(Box::new(create_select(
query.as_mut().body = Box::new(SetExpr::Select(Box::new(create_select(
connection,
engine_state,
stack,
call,
)?));
)?)));
}
};
@ -167,6 +167,7 @@ fn create_select(
distribute_by: Vec::new(),
sort_by: Vec::new(),
having: None,
qualify: None,
})
}

View File

@ -19,7 +19,7 @@ impl Command for GroupByDb {
}
fn usage(&self) -> &str {
"Group by query"
"Group-by query"
}
fn signature(&self) -> Signature {
@ -104,7 +104,7 @@ impl Command for GroupByDb {
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
match db.statement.as_mut() {
Some(statement) => match statement {
Statement::Query(ref mut query) => match &mut query.body {
Statement::Query(ref mut query) => match &mut *query.body {
SetExpr::Select(ref mut select) => select.group_by = expressions,
s => {
return Err(ShellError::GenericError(

View File

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

View File

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

View File

@ -146,7 +146,7 @@ fn modify_statement(
) -> Result<Statement, ShellError> {
match statement {
Statement::Query(ref mut query) => {
match &mut query.body {
match &mut *query.body {
SetExpr::Select(ref mut select) => {
modify_from(connection, select, engine_state, stack, call)?
}

View File

@ -1,45 +1,45 @@
// Conversions between value and sqlparser objects
pub mod conversions;
mod alias;
mod and;
mod as_;
mod collect;
mod describe;
mod from;
mod from_table;
mod group_by;
mod into_db;
mod into_sqlite;
mod join;
mod limit;
mod open;
mod open_db;
mod or;
mod order_by;
mod query;
mod query_db;
mod schema;
mod select;
mod to_db;
mod where_;
// Temporal module to create Query objects
mod testing;
use testing::TestingDb;
mod testing_db;
use testing_db::TestingDb;
use alias::AliasDb;
use and::AndDb;
use as_::AliasDb;
use collect::CollectDb;
pub(crate) use describe::DescribeDb;
pub(crate) use from::FromDb;
pub(crate) use from_table::FromDb;
use group_by::GroupByDb;
pub(crate) use into_db::ToDataBase;
use into_sqlite::IntoSqliteDb;
use join::JoinDb;
use limit::LimitDb;
use nu_protocol::engine::StateWorkingSet;
use open::OpenDb;
use open_db::OpenDb;
use or::OrDb;
use order_by::OrderByDb;
use query::QueryDb;
use query_db::QueryDb;
use schema::SchemaDb;
pub(crate) use select::ProjectionDb;
pub(crate) use to_db::ToDataBase;
use where_::WhereDb;
pub fn add_commands_decls(working_set: &mut StateWorkingSet) {

View File

@ -125,7 +125,7 @@ impl Command for OrDb {
}
fn modify_query(query: &mut Box<Query>, expression: Expr, span: Span) -> Result<(), ShellError> {
match query.body {
match *query.body {
SetExpr::Select(ref mut select) => modify_select(select, expression, span)?,
_ => {
return Err(ShellError::GenericError(

View File

@ -108,7 +108,7 @@ impl Command for ProjectionDb {
fn create_statement(expressions: Vec<SelectItem>) -> Statement {
let query = Query {
with: None,
body: SetExpr::Select(Box::new(create_select(expressions))),
body: Box::new(SetExpr::Select(Box::new(create_select(expressions)))),
order_by: Vec::new(),
limit: None,
offset: None,
@ -126,10 +126,11 @@ fn modify_statement(
) -> Result<Statement, ShellError> {
match statement {
Statement::Query(ref mut query) => {
match query.body {
match *query.body {
SetExpr::Select(ref mut select) => select.as_mut().projection = expressions,
_ => {
query.as_mut().body = SetExpr::Select(Box::new(create_select(expressions)));
query.as_mut().body =
Box::new(SetExpr::Select(Box::new(create_select(expressions))));
}
};
@ -159,6 +160,7 @@ fn create_select(projection: Vec<SelectItem>) -> Select {
distribute_by: Vec::new(),
sort_by: Vec::new(),
having: None,
qualify: None,
}
}

View File

@ -99,10 +99,10 @@ impl Command for WhereDb {
}
fn modify_query(query: &mut Box<Query>, expression: Expr) {
match query.body {
match *query.body {
SetExpr::Select(ref mut select) => modify_select(select, expression),
_ => {
query.as_mut().body = SetExpr::Select(Box::new(create_select(expression)));
query.as_mut().body = Box::new(SetExpr::Select(Box::new(create_select(expression))));
}
};
}
@ -125,6 +125,7 @@ fn create_select(expression: Expr) -> Select {
distribute_by: Vec::new(),
sort_by: Vec::new(),
having: None,
qualify: None,
}
}

View File

@ -132,6 +132,7 @@ impl Command for FunctionExpr {
args,
over: None,
distinct: call.has_flag("distinct"),
special: false,
})
.into();

View File

@ -124,7 +124,8 @@ impl CustomValue for ExprDb {
| Operator::ShiftLeft
| Operator::ShiftRight
| Operator::StartsWith
| Operator::EndsWith => Err(ShellError::UnsupportedOperator(operator, op)),
| Operator::EndsWith
| Operator::Append => Err(ShellError::UnsupportedOperator(operator, op)),
}?;
let expr = Expr::BinaryOp {
@ -339,7 +340,7 @@ impl ExprDb {
Expr::TypedString { .. } => todo!(),
Expr::MapAccess { .. } => todo!(),
Expr::Case { .. } => todo!(),
Expr::Exists(_) => todo!(),
Expr::Exists { .. } => todo!(),
Expr::Subquery(_) => todo!(),
Expr::ListAgg(_) => todo!(),
Expr::GroupingSets(_) => todo!(),
@ -348,6 +349,25 @@ impl ExprDb {
Expr::Tuple(_) => todo!(),
Expr::ArrayIndex { .. } => todo!(),
Expr::Array(_) => todo!(),
Expr::JsonAccess { .. } => todo!(),
Expr::CompositeAccess { .. } => todo!(),
Expr::IsFalse(_) => todo!(),
Expr::IsNotFalse(_) => todo!(),
Expr::IsTrue(_) => todo!(),
Expr::IsNotTrue(_) => todo!(),
Expr::IsUnknown(_) => todo!(),
Expr::IsNotUnknown(_) => todo!(),
Expr::Like { .. } => todo!(),
Expr::ILike { .. } => todo!(),
Expr::SimilarTo { .. } => todo!(),
Expr::AnyOp(_) => todo!(),
Expr::AllOp(_) => todo!(),
Expr::SafeCast { .. } => todo!(),
Expr::AtTimeZone { .. } => todo!(),
Expr::Position { .. } => todo!(),
Expr::Overlay { .. } => todo!(),
Expr::AggregateExpressionWithFilter { .. } => todo!(),
Expr::ArraySubquery(_) => todo!(),
}
}
}

View File

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

View File

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

View File

@ -13,11 +13,15 @@ mod last;
mod list;
mod melt;
mod open;
mod query_df;
mod rename;
mod sample;
mod shape;
mod slice;
mod sql_context;
mod sql_expr;
mod take;
mod to_arrow;
mod to_csv;
mod to_df;
mod to_nu;
@ -41,11 +45,15 @@ pub use last::LastDF;
pub use list::ListDF;
pub use melt::MeltDF;
pub use open::OpenDataFrame;
pub use query_df::QueryDf;
pub use rename::RenameDF;
pub use sample::SampleDF;
pub use shape::ShapeDF;
pub use slice::SliceDF;
pub use sql_context::SQLContext;
pub use sql_expr::parse_sql_expr;
pub use take::TakeDF;
pub use to_arrow::ToArrow;
pub use to_csv::ToCSV;
pub use to_df::ToDataFrame;
pub use to_nu::ToNu;
@ -79,11 +87,13 @@ pub fn add_eager_decls(working_set: &mut StateWorkingSet) {
ListDF,
MeltDF,
OpenDataFrame,
QueryDf,
RenameDF,
SampleDF,
ShapeDF,
SliceDF,
TakeDF,
ToArrow,
ToCSV,
ToDataFrame,
ToNu,

View File

@ -9,8 +9,8 @@ use nu_protocol::{
use std::{fs::File, io::BufReader, path::PathBuf};
use polars::prelude::{
CsvEncoding, CsvReader, JsonReader, LazyCsvReader, LazyFrame, ParallelStrategy, ParquetReader,
ScanArgsParquet, SerReader,
CsvEncoding, CsvReader, IpcReader, JsonReader, LazyCsvReader, LazyFrame, ParallelStrategy,
ParquetReader, ScanArgsIpc, ScanArgsParquet, SerReader,
};
#[derive(Clone)]
@ -22,7 +22,7 @@ impl Command for OpenDataFrame {
}
fn usage(&self) -> &str {
"Opens csv, json or parquet file to create dataframe"
"Opens csv, json, arrow, or parquet file to create dataframe"
}
fn signature(&self) -> Signature {
@ -33,6 +33,12 @@ impl Command for OpenDataFrame {
"file path to load values from",
)
.switch("lazy", "creates a lazy dataframe", Some('l'))
.named(
"type",
SyntaxShape::String,
"File type: csv, tsv, json, parquet, arrow. If omitted, derive from file extension",
Some('t'),
)
.named(
"delimiter",
SyntaxShape::String,
@ -93,15 +99,33 @@ fn command(
) -> Result<PipelineData, ShellError> {
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
match file.item.extension() {
Some(e) => match e.to_str() {
Some("csv") | Some("tsv") => from_csv(engine_state, stack, call),
Some("parquet") => from_parquet(engine_state, stack, call),
Some("json") => from_json(engine_state, stack, call),
_ => Err(ShellError::FileNotFoundCustom(
"Not a csv, tsv, parquet or json file".into(),
let type_option: Option<Spanned<String>> = call.get_flag(engine_state, stack, "type")?;
let type_id = match &type_option {
Some(ref t) => Some((t.item.to_owned(), "Invalid type", t.span)),
None => match file.item.extension() {
Some(e) => Some((
e.to_string_lossy().into_owned(),
"Invalid extension",
file.span,
)),
None => None,
},
};
match type_id {
Some((e, msg, blamed)) => match e.as_str() {
"csv" | "tsv" => from_csv(engine_state, stack, call),
"parquet" => from_parquet(engine_state, stack, call),
"ipc" | "arrow" => from_ipc(engine_state, stack, call),
"json" => from_json(engine_state, stack, call),
_ => Err(ShellError::FileNotFoundCustom(
format!(
"{}. Supported values: csv, tsv, parquet, ipc, arrow, json",
msg
),
blamed,
)),
},
None => Err(ShellError::FileNotFoundCustom(
"File without extension".into(),
@ -177,6 +201,70 @@ fn from_parquet(
}
}
fn from_ipc(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
) -> Result<Value, ShellError> {
if call.has_flag("lazy") {
let file: String = call.req(engine_state, stack, 0)?;
let args = ScanArgsIpc {
n_rows: None,
cache: true,
rechunk: false,
row_count: None,
};
let df: NuLazyFrame = LazyFrame::scan_ipc(file, args)
.map_err(|e| {
ShellError::GenericError(
"IPC reader error".into(),
format!("{:?}", e),
Some(call.head),
None,
Vec::new(),
)
})?
.into();
df.into_value(call.head)
} else {
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
let r = File::open(&file.item).map_err(|e| {
ShellError::GenericError(
"Error opening file".into(),
e.to_string(),
Some(file.span),
None,
Vec::new(),
)
})?;
let reader = IpcReader::new(r);
let reader = match columns {
None => reader,
Some(columns) => reader.with_columns(Some(columns)),
};
let df: NuDataFrame = reader
.finish()
.map_err(|e| {
ShellError::GenericError(
"IPC reader error".into(),
format!("{:?}", e),
Some(call.head),
None,
Vec::new(),
)
})?
.into();
Ok(df.into_value(call.head))
}
}
fn from_json(
engine_state: &EngineState,
stack: &mut Stack,

View File

@ -0,0 +1,106 @@
use super::super::values::NuDataFrame;
use crate::dataframe::values::Column;
use crate::dataframe::{eager::SQLContext, values::NuLazyFrame};
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
// attribution:
// sql_context.rs, and sql_expr.rs were copied from polars-sql. thank you.
// maybe we should just use the crate at some point but it's not published yet.
// https://github.com/pola-rs/polars/tree/master/polars-sql
#[derive(Clone)]
pub struct QueryDf;
impl Command for QueryDf {
fn name(&self) -> &str {
"query df"
}
fn usage(&self) -> &str {
"Query dataframe using SQL. Note: The dataframe is always named 'df' in your query's from clause."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("sql", SyntaxShape::String, "sql query")
.input_type(Type::Custom("dataframe".into()))
.output_type(Type::Custom("dataframe".into()))
.category(Category::Custom("dataframe".into()))
}
fn search_terms(&self) -> Vec<&str> {
vec!["dataframe", "sql", "search"]
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Query dataframe using SQL",
example: "[[a b]; [1 2] [3 4]] | into df | query df 'select a from df'",
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"a".to_string(),
vec![Value::test_int(1), Value::test_int(3)],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let sql_query: String = call.req(engine_state, stack, 0)?;
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let mut ctx = SQLContext::new();
ctx.register("df", &df.df);
let df_sql = ctx.execute(&sql_query).map_err(|e| {
ShellError::GenericError(
"Dataframe Error".into(),
e.to_string(),
Some(call.head),
None,
Vec::new(),
)
})?;
let lazy = NuLazyFrame::new(false, df_sql);
let eager = lazy.collect(call.head)?;
let value = Value::CustomValue {
val: Box::new(eager),
span: call.head,
};
Ok(PipelineData::Value(value, None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(QueryDf {})])
}
}

View File

@ -0,0 +1,220 @@
use crate::dataframe::eager::sql_expr::parse_sql_expr;
use polars::error::PolarsError;
use polars::prelude::{col, DataFrame, DataType, IntoLazy, LazyFrame};
use sqlparser::ast::{
Expr as SqlExpr, Select, SelectItem, SetExpr, Statement, TableFactor, Value as SQLValue,
};
use sqlparser::dialect::GenericDialect;
use sqlparser::parser::Parser;
use std::collections::HashMap;
#[derive(Default)]
pub struct SQLContext {
table_map: HashMap<String, LazyFrame>,
dialect: GenericDialect,
}
impl SQLContext {
pub fn new() -> Self {
Self {
table_map: HashMap::new(),
dialect: GenericDialect::default(),
}
}
pub fn register(&mut self, name: &str, df: &DataFrame) {
self.table_map.insert(name.to_owned(), df.clone().lazy());
}
fn execute_select(&self, select_stmt: &Select) -> Result<LazyFrame, PolarsError> {
// Determine involved dataframe
// Implicit join require some more work in query parsers, Explicit join are preferred for now.
let tbl = select_stmt.from.get(0).ok_or_else(|| {
PolarsError::NotFound("No table found in select statement".to_string())
})?;
let mut alias_map = HashMap::new();
let tbl_name = match &tbl.relation {
TableFactor::Table { name, alias, .. } => {
let tbl_name = name
.0
.get(0)
.ok_or_else(|| {
PolarsError::NotFound("No table found in select statement".to_string())
})?
.value
.to_string();
if self.table_map.contains_key(&tbl_name) {
if let Some(alias) = alias {
alias_map.insert(alias.name.value.clone(), tbl_name.to_owned());
};
tbl_name
} else {
return Err(PolarsError::ComputeError(
format!("Table name {tbl_name} was not found").into(),
));
}
}
// Support bare table, optional with alias for now
_ => return Err(PolarsError::ComputeError("Not implemented".into())),
};
let df = &self.table_map[&tbl_name];
let mut raw_projection_before_alias: HashMap<String, usize> = HashMap::new();
let mut contain_wildcard = false;
// Filter Expression
let df = match select_stmt.selection.as_ref() {
Some(expr) => {
let filter_expression = parse_sql_expr(expr)?;
df.clone().filter(filter_expression)
}
None => df.clone(),
};
// Column Projections
let projection = select_stmt
.projection
.iter()
.enumerate()
.map(|(i, select_item)| {
Ok(match select_item {
SelectItem::UnnamedExpr(expr) => {
let expr = parse_sql_expr(expr)?;
raw_projection_before_alias.insert(format!("{:?}", expr), i);
expr
}
SelectItem::ExprWithAlias { expr, alias } => {
let expr = parse_sql_expr(expr)?;
raw_projection_before_alias.insert(format!("{:?}", expr), i);
expr.alias(&alias.value)
}
SelectItem::QualifiedWildcard(_) | SelectItem::Wildcard => {
contain_wildcard = true;
col("*")
}
})
})
.collect::<Result<Vec<_>, PolarsError>>()?;
// Check for group by
// After projection since there might be number.
let group_by = select_stmt
.group_by
.iter()
.map(
|e|match e {
SqlExpr::Value(SQLValue::Number(idx, _)) => {
let idx = match idx.parse::<usize>() {
Ok(0)| Err(_) => Err(
PolarsError::ComputeError(
format!("Group-By Error: Only positive number or expression are supported, got {idx}").into()
)),
Ok(idx) => Ok(idx)
}?;
Ok(projection[idx].clone())
}
SqlExpr::Value(_) => Err(
PolarsError::ComputeError("Group-By Error: Only positive number or expression are supported".into())
),
_ => parse_sql_expr(e)
}
)
.collect::<Result<Vec<_>, PolarsError>>()?;
let df = if group_by.is_empty() {
df.select(projection)
} else {
// check groupby and projection due to difference between SQL and polars
// Return error on wild card, shouldn't process this
if contain_wildcard {
return Err(PolarsError::ComputeError(
"Group-By Error: Can't process wildcard in group-by".into(),
));
}
// Default polars group by will have group by columns at the front
// need some container to contain position of group by columns and its position
// at the final agg projection, check the schema for the existence of group by column
// and its projections columns, keeping the original index
let (exclude_expr, groupby_pos): (Vec<_>, Vec<_>) = group_by
.iter()
.map(|expr| raw_projection_before_alias.get(&format!("{:?}", expr)))
.enumerate()
.filter(|(_, proj_p)| proj_p.is_some())
.map(|(gb_p, proj_p)| (*proj_p.unwrap_or(&0), (*proj_p.unwrap_or(&0), gb_p)))
.unzip();
let (agg_projection, agg_proj_pos): (Vec<_>, Vec<_>) = projection
.iter()
.enumerate()
.filter(|(i, _)| !exclude_expr.contains(i))
.enumerate()
.map(|(agg_pj, (proj_p, expr))| (expr.clone(), (proj_p, agg_pj + group_by.len())))
.unzip();
let agg_df = df.groupby(group_by).agg(agg_projection);
let mut final_proj_pos = groupby_pos
.into_iter()
.chain(agg_proj_pos.into_iter())
.collect::<Vec<_>>();
final_proj_pos.sort_by(|(proj_pa, _), (proj_pb, _)| proj_pa.cmp(proj_pb));
let final_proj = final_proj_pos
.into_iter()
.map(|(_, shm_p)| {
col(agg_df
.clone()
// FIXME: had to do this mess to get get_index to work, not sure why. need help
.collect()
.unwrap_or_default()
.schema()
.get_index(shm_p)
.unwrap_or((&"".to_string(), &DataType::Null))
.0)
})
.collect::<Vec<_>>();
agg_df.select(final_proj)
};
Ok(df)
}
pub fn execute(&self, query: &str) -> Result<LazyFrame, PolarsError> {
let ast = Parser::parse_sql(&self.dialect, query)
.map_err(|e| PolarsError::ComputeError(format!("{:?}", e).into()))?;
if ast.len() != 1 {
Err(PolarsError::ComputeError(
"One and only one statement at a time please".into(),
))
} else {
let ast = ast
.get(0)
.ok_or_else(|| PolarsError::NotFound("No statement found".to_string()))?;
Ok(match ast {
Statement::Query(query) => {
let rs = match &*query.body {
SetExpr::Select(select_stmt) => self.execute_select(select_stmt)?,
_ => {
return Err(PolarsError::ComputeError(
"INSERT, UPDATE is not supported for polars".into(),
))
}
};
match &query.limit {
Some(SqlExpr::Value(SQLValue::Number(nrow, _))) => {
let nrow = nrow.parse().map_err(|err| {
PolarsError::ComputeError(
format!("Conversion Error: {:?}", err).into(),
)
})?;
rs.limit(nrow)
}
None => rs,
_ => {
return Err(PolarsError::ComputeError(
"Only support number argument to LIMIT clause".into(),
))
}
}
}
_ => {
return Err(PolarsError::ComputeError(
format!("Statement type {:?} is not supported", ast).into(),
))
}
})
}
}
}

View File

@ -0,0 +1,191 @@
use polars::error::PolarsError;
use polars::prelude::{col, lit, DataType, Expr, LiteralValue, Result, TimeUnit};
use sqlparser::ast::{
BinaryOperator as SQLBinaryOperator, DataType as SQLDataType, Expr as SqlExpr,
Function as SQLFunction, Value as SqlValue, WindowSpec,
};
fn map_sql_polars_datatype(data_type: &SQLDataType) -> Result<DataType> {
Ok(match data_type {
SQLDataType::Char(_)
| SQLDataType::Varchar(_)
| SQLDataType::Uuid
| SQLDataType::Clob(_)
| SQLDataType::Text
| SQLDataType::String => DataType::Utf8,
SQLDataType::Float(_) => DataType::Float32,
SQLDataType::Real => DataType::Float32,
SQLDataType::Double => DataType::Float64,
SQLDataType::TinyInt(_) => DataType::Int8,
SQLDataType::UnsignedTinyInt(_) => DataType::UInt8,
SQLDataType::SmallInt(_) => DataType::Int16,
SQLDataType::UnsignedSmallInt(_) => DataType::UInt16,
SQLDataType::Int(_) => DataType::Int32,
SQLDataType::UnsignedInt(_) => DataType::UInt32,
SQLDataType::BigInt(_) => DataType::Int64,
SQLDataType::UnsignedBigInt(_) => DataType::UInt64,
SQLDataType::Boolean => DataType::Boolean,
SQLDataType::Date => DataType::Date,
SQLDataType::Time => DataType::Time,
SQLDataType::Timestamp => DataType::Datetime(TimeUnit::Milliseconds, None),
SQLDataType::Interval => DataType::Duration(TimeUnit::Milliseconds),
SQLDataType::Array(inner_type) => {
DataType::List(Box::new(map_sql_polars_datatype(inner_type)?))
}
_ => {
return Err(PolarsError::ComputeError(
format!(
"SQL Datatype {:?} was not supported in polars-sql yet!",
data_type
)
.into(),
))
}
})
}
fn cast_(expr: Expr, data_type: &SQLDataType) -> Result<Expr> {
let polars_type = map_sql_polars_datatype(data_type)?;
Ok(expr.cast(polars_type))
}
fn binary_op_(left: Expr, right: Expr, op: &SQLBinaryOperator) -> Result<Expr> {
Ok(match op {
SQLBinaryOperator::Plus => left + right,
SQLBinaryOperator::Minus => left - right,
SQLBinaryOperator::Multiply => left * right,
SQLBinaryOperator::Divide => left / right,
SQLBinaryOperator::Modulo => left % right,
SQLBinaryOperator::StringConcat => left.cast(DataType::Utf8) + right.cast(DataType::Utf8),
SQLBinaryOperator::Gt => left.gt(right),
SQLBinaryOperator::Lt => left.lt(right),
SQLBinaryOperator::GtEq => left.gt_eq(right),
SQLBinaryOperator::LtEq => left.lt_eq(right),
SQLBinaryOperator::Eq => left.eq(right),
SQLBinaryOperator::NotEq => left.eq(right).not(),
SQLBinaryOperator::And => left.and(right),
SQLBinaryOperator::Or => left.or(right),
SQLBinaryOperator::Xor => left.xor(right),
_ => {
return Err(PolarsError::ComputeError(
format!("SQL Operator {:?} was not supported in polars-sql yet!", op).into(),
))
}
})
}
fn literal_expr(value: &SqlValue) -> Result<Expr> {
Ok(match value {
SqlValue::Number(s, _) => {
// Check for existence of decimal separator dot
if s.contains('.') {
s.parse::<f64>().map(lit).map_err(|_| {
PolarsError::ComputeError(format!("Can't parse literal {:?}", s).into())
})
} else {
s.parse::<i64>().map(lit).map_err(|_| {
PolarsError::ComputeError(format!("Can't parse literal {:?}", s).into())
})
}?
}
SqlValue::SingleQuotedString(s) => lit(s.clone()),
SqlValue::NationalStringLiteral(s) => lit(s.clone()),
SqlValue::HexStringLiteral(s) => lit(s.clone()),
SqlValue::DoubleQuotedString(s) => lit(s.clone()),
SqlValue::Boolean(b) => lit(*b),
SqlValue::Null => Expr::Literal(LiteralValue::Null),
_ => {
return Err(PolarsError::ComputeError(
format!(
"Parsing SQL Value {:?} was not supported in polars-sql yet!",
value
)
.into(),
))
}
})
}
pub fn parse_sql_expr(expr: &SqlExpr) -> Result<Expr> {
Ok(match expr {
SqlExpr::Identifier(e) => col(&e.value),
SqlExpr::BinaryOp { left, op, right } => {
let left = parse_sql_expr(left)?;
let right = parse_sql_expr(right)?;
binary_op_(left, right, op)?
}
SqlExpr::Function(sql_function) => parse_sql_function(sql_function)?,
SqlExpr::Cast { expr, data_type } => cast_(parse_sql_expr(expr)?, data_type)?,
SqlExpr::Nested(expr) => parse_sql_expr(expr)?,
SqlExpr::Value(value) => literal_expr(value)?,
_ => {
return Err(PolarsError::ComputeError(
format!(
"Expression: {:?} was not supported in polars-sql yet!",
expr
)
.into(),
))
}
})
}
fn apply_window_spec(expr: Expr, window_spec: &Option<WindowSpec>) -> Result<Expr> {
Ok(match &window_spec {
Some(window_spec) => {
// Process for simple window specification, partition by first
let partition_by = window_spec
.partition_by
.iter()
.map(parse_sql_expr)
.collect::<Result<Vec<_>>>()?;
expr.over(partition_by)
// Order by and Row range may not be supported at the moment
}
None => expr,
})
}
fn parse_sql_function(sql_function: &SQLFunction) -> Result<Expr> {
use sqlparser::ast::{FunctionArg, FunctionArgExpr};
// Function name mostly do not have name space, so it mostly take the first args
let function_name = sql_function.name.0[0].value.to_lowercase();
let args = sql_function
.args
.iter()
.map(|arg| match arg {
FunctionArg::Named { arg, .. } => arg,
FunctionArg::Unnamed(arg) => arg,
})
.collect::<Vec<_>>();
Ok(
match (
function_name.as_str(),
args.as_slice(),
sql_function.distinct,
) {
("sum", [FunctionArgExpr::Expr(expr)], false) => {
apply_window_spec(parse_sql_expr(expr)?, &sql_function.over)?.sum()
}
("count", [FunctionArgExpr::Expr(expr)], false) => {
apply_window_spec(parse_sql_expr(expr)?, &sql_function.over)?.count()
}
("count", [FunctionArgExpr::Expr(expr)], true) => {
apply_window_spec(parse_sql_expr(expr)?, &sql_function.over)?.n_unique()
}
// Special case for wildcard args to count function.
("count", [FunctionArgExpr::Wildcard], false) => lit(1i32).count(),
_ => {
return Err(PolarsError::ComputeError(
format!(
"Function {:?} with args {:?} was not supported in polars-sql yet!",
function_name, args
)
.into(),
))
}
},
)
}

View File

@ -0,0 +1,94 @@
use std::{fs::File, path::PathBuf};
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
};
use polars::prelude::{IpcWriter, SerWriter};
use super::super::values::NuDataFrame;
#[derive(Clone)]
pub struct ToArrow;
impl Command for ToArrow {
fn name(&self) -> &str {
"to arrow"
}
fn usage(&self) -> &str {
"Saves dataframe to arrow file"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("file", SyntaxShape::Filepath, "file path to save dataframe")
.input_type(Type::Custom("dataframe".into()))
.output_type(Type::Any)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Saves dataframe to arrow file",
example: "[[a b]; [1 2] [3 4]] | into df | to arrow test.arrow",
result: None,
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let file_name: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
let mut file = File::create(&file_name.item).map_err(|e| {
ShellError::GenericError(
"Error with file name".into(),
e.to_string(),
Some(file_name.span),
None,
Vec::new(),
)
})?;
IpcWriter::new(&mut file).finish(df.as_mut()).map_err(|e| {
ShellError::GenericError(
"Error saving file".into(),
e.to_string(),
Some(file_name.span),
None,
Vec::new(),
)
})?;
let file_value = Value::String {
val: format!("saved {:?}", &file_name.item),
span: file_name.span,
};
Ok(PipelineData::Value(
Value::List {
vals: vec![file_value],
span: call.head,
},
None,
))
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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