Compare commits

...

419 Commits

Author SHA1 Message Date
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
e76b3d61de Require static path for source-env (#6526) 2022-09-08 23:41:49 +03:00
1adebefc3e Improve wording around all and any (#6524)
* Improve wording around `all` and `any`

The role of the `predicate` for `all` and `any` was not as clear.

See #6499

* type-o

* type-o

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-09-08 08:45:01 -05:00
d1e1d0ac3e remove panic from lpad and rpad, change truncation behaviour for lpad (#6495)
* condense `lpad` and `rpad` into `pad`

* change description

* back to original names, add change
2022-09-08 14:29:56 +02:00
b398448cd9 Stop panic when typing module spam { export def-env (#6523)
* Stop `panic` when typing `module spam { export def-env`

same goes for `export extern` and `export alias`

* fmt
2022-09-08 12:27:11 +03:00
02f92fa527 remove tests (#6515) 2022-09-07 20:19:29 -05:00
773d167449 update regsiter-plugins script to not use encoding (#6512) 2022-09-07 11:54:28 -05:00
aa92141ad7 Remove --encoding argument during register plugin (#6486)
* first implement new plugin protocol core logic

* fix debug body construct

* fix output message from plugin

* finish plugin commands calling

* fix tests and adjust plugin_custom_value call

* fmt code

* fmt code, fix clippy

* add FIXME comment

* change from FIXME to TODO
2022-09-07 09:07:42 -05:00
80624267fd Pass TERM environment var to clear (#6500)
* Pass `TERM` environment var to clear

* don't panic

* use IOErrorSpanned instead of IOError
2022-09-07 10:40:44 +02:00
2030e25ddc fix typo (#6508) 2022-09-07 16:16:55 +08:00
247fff424d update to nu v0.68 for release workflow (#6505) 2022-09-07 15:36:42 +12:00
c902d8bc0c bump dev version to v0.68.1 (#6504) 2022-09-07 14:27:33 +12:00
JT
b0e5723a68 move back to old names for upcoming release 2022-09-07 06:42:11 +12:00
JT
9273bb3f72 bump to 0.68 (#6501) 2022-09-07 06:29:01 +12:00
f7d3ccfc70 Pin reedline to 0.11.0 release (#6497)
Includes minor bugfixes around the history

Release notes:

https://github.com/nushell/reedline/releases/tag/v0.11.0
2022-09-06 11:29:51 +02:00
JT
d86350af80 Revert "Make $ on variable names optional (#6434)" (#6446)
This reverts commit 3cb9147f22.
2022-09-06 05:42:47 +12:00
14512988ba Rename all?, any? and empty? (#6464)
Rename `all?`, `any?` and `empty?` to `all`, `any` and `is-empty` for sake of simplicity and consistency.

- More understandable for newcomers, that these commands are no special to others.
- `?` syntax did not really aprove readability. For me it made it worse.
- We can reserve `?` syntax for any other nushell feature.
2022-09-05 16:41:06 +02:00
33e1120add Terminate REPL if not connected to tty input (#6480)
* Terminate REPL if not connected to tty input

If the standard input stream is not a TTY abort the REPL execution.

Solves a problem as the current REPL tries to be IO fault tolerant and
would indefinetely fail when crossterm tries to handle the STDIN.

Fixes nushell/nushell#6452

* Improve the error message
2022-09-05 13:33:54 +02:00
3278d290be Avoid update_last_command_context "No command run" error (#6483)
* Avoid update_last_command_context "No command run" error

When using `executehostcommand` bindings without having run actual user input commands yet,
update_last_command_context is guaranteed to fail. A function has been added to reedline
that allows checking for this case.

* Update to most recent reedline

Includes bugfixes around the (SQlite) history

Co-authored-by: sholderbach <sholderbach@users.noreply.github.com>
2022-09-05 13:31:26 +02:00
daec3fc3d3 let path split keeps 'C:\' together (#6485)
* `path split` keeps 'C:\' together

* fmt

* fix clippt

* fix match arm
2022-09-04 23:32:09 -07:00
a6ba58ec41 restrict plugin file name (#6479) 2022-09-04 18:00:20 -05:00
65327e0e7e Disable cyclical module imports (#6477) 2022-09-04 23:19:20 +03:00
3ed3712fdc Fix overlays not preserving hidden env vars (#6475)
* Fix overlays not preserving hidden env vars

* Add a few more test

* Add one more test of resetting hidden env vars

* Move removed source-env tests
2022-09-04 20:32:06 +03:00
f46962d236 Fix scoped overlay use not finding a module (#6474)
* Add source-env test for dynamic path

* Use correct module ID for env overlay imports

* Remove parser check from "overlay list"

It would cause unnecessary errors from some inner scope if some
overlay module was also defined in some inner scope.

* Restore Cargo.lock back

* Remove comments
2022-09-04 18:36:42 +03:00
aa4778ff07 remove useless file (#6472) 2022-09-04 06:39:29 -05:00
e81689f2c0 Allow for rejecting nested record cells (#6463)
* add new function to remove data at a cellpath; allow reject to use cellpath

* add tests

* fmt

* fix clippt

* get it working properly with lists of records

* fix clippy, hopefully

* fix clippy, hopefully 2
2022-09-03 07:35:36 -05:00
4656310a1c Bump lz4-sys from 1.9.3 to 1.9.4 (#6462)
Bumps [lz4-sys](https://github.com/10xGenomics/lz4-rs) from 1.9.3 to 1.9.4.
- [Release notes](https://github.com/10xGenomics/lz4-rs/releases)
- [Changelog](https://github.com/10XGenomics/lz4-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/10xGenomics/lz4-rs/commits)

---
updated-dependencies:
- dependency-name: lz4-sys
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-03 07:32:12 -05:00
34e58bc5d6 add tests, deal with pipes, newlines, tabs for to nuon (#6391)
* remove unnecessary FlatShape

* add proptest

* remove files that belonged in another PR

* more tests, more chars

* add exception for parser error unrelated ot PR
2022-09-01 14:08:19 +02:00
fbe9d6f529 Highlight source value as well as failure point. (#6442)
* show multiple errors at once for some commands

* change from invalid item to source value
2022-09-01 12:20:22 +02:00
e266590813 Fix ps command CPU usage on Apple Silicon M1 macs. #4142 (#6457)
* Fix ps command CPU usage on Apple Silicon M1 macs. #4142

The cpu user and system times returned my libproc are not in
nanoseconds; they are in mach ticks units.  This is not documented very
well.  The convert from mach ticks to ns, the kernel provides a timebase
info function and datatype:

https://developer.apple.com/documentation/driverkit/3433733-mach_timebase_info

The commit makes the PS command work for me.

* Cargo fmt of previous commit.

* Clippy format suggestion

Co-authored-by: Ondrej Baudys <ondrej.baudys@nextgen.net>
2022-09-01 18:09:52 +12:00
b27148d14b Fix build on *BSD, illumos, etc. (#6456)
* nu-path: use 'linux' code on all non-macOS unix

* nu-command: cfg() the Ps command to platforms it's actually implemented on

* nu-system: cfg() the Ps test to the platforms Ps is implemented on
2022-09-01 12:34:26 +12:00
4858a9a817 Revert "Add support for optional list stream output formatting (#6325)" (#6454)
This reverts commit ec4e3a6d5c.
2022-08-31 18:09:40 -05:00
3ec53e544c remove capnp protocol for plugin... (#6421)
* remove capnp protocol for plugin...

* remove relative doc
2022-08-31 17:33:30 -05:00
JT
c52d45cb97 Move from source to source-env (#6277)
* start working on source-env

* WIP

* Get most tests working, still one to go

* Fix file-relative paths; Report parser error

* Fix merge conflicts; Restore source as deprecated

* Tests: Use source-env; Remove redundant tests

* Fmt

* Respect hidden env vars

* Fix file-relative eval for source-env

* Add file-relative eval to "overlay use"

* Use FILE_PWD only in source-env and "overlay use"

* Ignore new tests for now

This will be another issue

* Throw an error if setting FILE_PWD manually

* Fix source-related test failures

* Fix nu-check to respect FILE_PWD

* Fix corrupted spans in source-env shell errors

* Fix up some references to old source

* Remove deprecation message

* Re-introduce deleted tests

Co-authored-by: kubouch <kubouch@gmail.com>
2022-09-01 08:32:56 +12:00
11531b7630 Upgrade which dependency to fix case on Windows (#6453) 2022-08-31 09:50:18 -07:00
a098a27837 Bump iana-time-zone from 0.1.44 to 0.1.47 (#6448)
Bumps [iana-time-zone](https://github.com/strawlab/iana-time-zone) from 0.1.44 to 0.1.47.
- [Release notes](https://github.com/strawlab/iana-time-zone/releases)
- [Changelog](https://github.com/strawlab/iana-time-zone/blob/main/CHANGELOG.md)
- [Commits](https://github.com/strawlab/iana-time-zone/compare/0.1.44...v0.1.47)

---
updated-dependencies:
- dependency-name: iana-time-zone
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-31 06:19:20 -05:00
2591bd8c63 add more color highlighting to help (#6449) 2022-08-31 20:15:03 +12:00
JT
a03fb946d9 Allow parens around signatures (#6444)
* DRAFT: make var dollar optional

* couple fixes

* fix some tests + cleanup

* allow parens around signature

* clippy
2022-08-30 16:17:10 +12:00
9c58f2a522 Disable clickable links in SSH sessions (#6439)
* Disable clickable links in WSL and SSH sessions

* Revert WSL change; disable links in SSH only
2022-08-29 07:52:55 -07:00
JT
3cb9147f22 Make $ on variable names optional (#6434)
* DRAFT: make var dollar optional

* couple fixes

* fix some tests + cleanup
2022-08-29 14:35:55 +12:00
f1d72e2670 better error handling for nu_command::env::conig::utils::get_editor (#6430) 2022-08-28 12:56:55 +03:00
f1e7a01b2e shows wrong item when each command runs to failed. (#6437)
* add --wrong-item for each command

* fix test

* show multiple errors at once
2022-08-28 11:40:14 +03:00
b88ace4cde keep raw for variable inputed argument (#6426)
* keep raw for variable inputed argument

* fix clippy for windows

* make test runs on windows
2022-08-27 08:22:02 -05:00
34d7c17e78 Bring module's environment when activating overlay (#6425) 2022-08-27 01:32:19 +03:00
3f1824111d add the ast command to peek at the internals of nushell (#6423)
* add the ast command to peak at the internals of nushell

* fixed a bug in an example
2022-08-26 14:48:48 -05:00
fbae137442 Try to make argument with quotes for external command better (#6420)
* fix arg quote for external

* adjust comment
2022-08-26 06:50:41 -05:00
9850424251 Make run_external parameter required (#6418) 2022-08-26 06:31:33 -05:00
918ec9daa8 nu-command/filters: drop column check positive value (#6412) 2022-08-25 19:03:18 +03:00
7b502a4c7f register-plugin.nu: refactor register plugin (#6409) 2022-08-25 06:57:48 -05:00
7b07e976b8 Fix the span of "invalid time zone" (#6411)
Signed-off-by: nibon7 <nibon7@163.com>

Signed-off-by: nibon7 <nibon7@163.com>
2022-08-25 13:21:54 +02:00
e45b169cba default to file completion after first command, add command option for completions (#6257)
* remove unnecessary FlatShape

* add test
2022-08-24 22:46:00 +03:00
5ebfa10495 convert string duration to named duration (#6406) 2022-08-24 14:45:51 -05:00
3f93dc2f1d Always report errors in cp (#6404) 2022-08-24 10:39:28 -07:00
a43514deb2 register-plugin.nu: remove .exe extension match to simplify code (#6400)
Signed-off-by: nibon7 <nibon7@163.com>

Signed-off-by: nibon7 <nibon7@163.com>
2022-08-24 06:43:21 -05:00
ab77bf3289 Fix search terms for str distance (#6398)
Redundancy with the command name is unnecessary and now tested since #6380 
Fixes CI failure
2022-08-24 11:49:03 +02:00
0afe1e4e67 Test command names and search terms for redundancy (#6380)
* Test commands for proper names and search terms

Assert that the `Command.name()` is equal to `Signature.name`

Check that search terms are not just substrings of the command name as
they would not help finding the command.

* Clean up search terms

Remove redundant terms that just replicate the command name.
Try to eliminate substring between search terms, clean up where
necessary.
2022-08-24 11:16:47 +02:00
ef26d539a7 Make cp errors more specific (#6396) 2022-08-23 21:32:41 -07:00
fce8581321 add a plugin registration script (#6395) 2022-08-23 19:38:02 -05:00
ba6abd77c9 add another split words example (#6394) 2022-08-23 13:27:06 -05:00
a7295c8f1b Plugin: Add benchmark for different encoding protocol (#6384)
* add MessagePack as a plugin protocol

* tmp merge from remote

* add benchmark

* use less benchmark group, and add README for analysing benchmark result

* update README

* update README

* rewrite

* remove comment

* rename

* fmt

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-08-23 11:49:51 -05:00
2a310ef187 [Experiment] Reenable CI build cache for tests (#6390)
Let's see, if we can use `cargo-cache` again for the tests after #6389
reduced the number of test binaries to build that are quite large due as
they statically link copies of the same engine.
This might be one of the reasons why the tests on windows exceeded the
allotted disk space.
2022-08-23 17:17:33 +02:00
530e250573 nu-cli: merge completions tests into one file (#6389)
This PR merges all the completions tests into one file.

The reason for them to be separated was organization, so we wouldn't need to scroll a huge file.
But that came with another issue, because rust generates a new binary for each completion test file and each completion test depends on Nu looks like all the dataframes were coming into each test file as well (as pointed by @rgwood
2022-08-23 16:24:24 +02:00
6fbc76bc0f add edit distance/levenshtein command (#6383)
* add edit distance/levenshtein command

* change output to a record

* update test
2022-08-23 08:53:14 -05:00
884382bac4 preserve space by letting to nuon only add quotes when necessary (#6379)
* preserve space by letting `to nuon` only add quotes when necessary

* fix CI, add quotes with colon

* fmt

* add more chars to blacklist
2022-08-23 06:51:07 -05:00
d97975e9fa Allow "export-env" parsing in modules (#6382)
* Allow "export-env" parsing in modules

* Fmt

* Add test for importing module's environment
2022-08-23 10:45:17 +03:00
839b264261 Add test cases for $nu.config-path change (#6385)
* Add test cases for $nu.config-path change

Part of #6366

Signed-off-by: nibon7 <nibon7@163.com>

* do not start a new process to test default config path

Signed-off-by: nibon7 <nibon7@163.com>

Signed-off-by: nibon7 <nibon7@163.com>
2022-08-23 10:18:14 +03:00
7ef4e5f940 Allow parsing modules as scripts (#6357)
* Allow parsing modules as scripts

* Remove 'export env' from the supported keywords

* Add test for export in blocks; Allow "export use"

* Allow evaluating "export alias"

* Fmt; Clippy

* Allow running "export extern" in scripts
2022-08-23 00:19:47 +03:00
646aace05b feat: external completions for commands/flags (#6295)
* wip

* wip

* cleanup

* error message

* cleanup

* cleanup

* fix clippy

* add test

* fix span

* cleanup

* cleanup

* cleanup

* fixed completion

* push char

* wip

* small fixes

* fix remove last span

* fmt

* cleanup

* fixes + more tests

* fix test

* only complete for commands

* also complete flags

* change decl_id to block_id

* use nu completion first

* fix test

* ignore test

* update config section
2022-08-22 21:38:51 +03:00
772ad896c8 Get $nu.config-path and $nu.env-path from EngineState (#6366)
* Get `$nu.config-path` and `$nu.env-path` from `EngineState`

Signed-off-by: nibon7 <nibon7@163.com>

* replace tuple with hashmap

Signed-off-by: nibon7 <nibon7@163.com>

* refactor set_config_path

Signed-off-by: nibon7 <nibon7@163.com>

Signed-off-by: nibon7 <nibon7@163.com>
2022-08-22 19:30:09 +03:00
9c4bbe3c63 Rename overlay commands (#6375)
* rename from overlay add to overlay use

* rename from overlay remove to overlay hide

* rename add to use_
2022-08-21 17:27:56 +03:00
c5ca839294 Add pause and cls to cmd.exe exceptions (#6371) 2022-08-21 07:21:27 -07:00
5337a6dffa add MessagePack as a plugin protocol (#6370) 2022-08-21 06:13:38 -05:00
56ce10347e let to nuon convert column names with spaces (#6376)
* let `to nuon` convert column names with spaces

* change test
2022-08-21 13:12:13 +03:00
37bc90c62a fix the way lists are rendered in markdown (#6369) 2022-08-20 21:04:30 -05:00
ad7522bba0 Use string interpolation to construct log file path (#6365)
Signed-off-by: nibon7 <nibon7@163.com>

Signed-off-by: nibon7 <nibon7@163.com>
2022-08-19 20:06:45 -05:00
bbcf374886 Update nu version for release workflow (#6361) 2022-08-20 08:05:58 +08:00
99c42582fe add a split words command (#6363)
* add a split words command

* changed regex
2022-08-20 05:55:54 +12:00
5a56d47f25 Add export-env command (#6355)
* WIP Start export-env

* Add missing file

* Do not modify the parser

Let's leave that for later

* Enable tests for export-env; Fmt
2022-08-18 23:24:39 +03:00
529c98085a Return error when kill didn't terminate successfully (#6354)
* Return error when `kill` didn't terminate successfully

Signed-off-by: nibon7 <nibon7@163.com>

* add test

Signed-off-by: nibon7 <nibon7@163.com>

Signed-off-by: nibon7 <nibon7@163.com>
2022-08-18 11:58:51 -05:00
2b955f82b7 Fix #6330 (#6332) 2022-08-18 10:53:46 -05:00
1843fdc060 create clickable links in ls output if configured (#6333)
* create clickable links in ls output if configured

* move some comments
2022-08-18 05:45:49 -05:00
ec4e3a6d5c Add support for optional list stream output formatting (#6325)
* add support for optional list stream output formatting

* cargo fmt

* table: add ValueFormatter test
2022-08-18 05:44:53 -05:00
4ab468e65f Fix slice indexing (#6322)
* Return empty suggestions if no span contents is present

* Fix slice indexing
2022-08-18 05:44:09 -05:00
1d18f6947e Try again: in unix like system, set foreground process while running external command (#6273)
* Revert "Fix intermittent test crash (#6268)"

This reverts commit 555d9ee763.

* make a working version again

* try second impl

* add

* fmt

* check stdin is atty before acquire stdin

* add libc

* clean comment

* fix typo
2022-08-18 05:41:01 -05:00
df3b6d9d26 Add --execute option (#6302) 2022-08-18 12:25:52 +03:00
4bbdb73668 Bump dev version (#6350) 2022-08-18 21:14:17 +12:00
62d3497bbb fix links to the "think in nu" page in --help (#6348)
This commit uses `sed` on all the files of the code base to
replace each and every instance of https://www.nushell.sh/book/thinking_in_nushell.html,
which is a broken link, to https://www.nushell.sh/book/thinking_in_nu.html,
which is the new URL to the book page.

This exact command was
```nushell
ls **/* -f |
    where type == file |
    each {
        |it|
        sed -i 's|https://www.nushell.sh/book/thinking_in_nushell.html|https://www.nushell.sh/book/thinking_in_nu.html|' $it.name
    }
```

Co-authored-by: amtoine <44101798+AntoineStevan@users.noreply.github.com>
2022-08-17 13:51:07 -04:00
e614970c08 Use an older version of wingetcreate to do the msi package submission (#6347) 2022-08-18 00:11:19 +08:00
d931331b57 Add a manual run workflow for winget submission (#6345) 2022-08-17 23:01:34 +08:00
f18da2609a this pr fixes the wix building (#6343) 2022-08-17 09:10:13 -05:00
JT
2ef9cc118e Update engine_state.rs 2022-08-17 09:18:17 +12:00
JT
33674d3a98 bump to 0.67 (#6336) 2022-08-17 05:47:47 +12:00
0167649e6f Bug issue template (#6329)
* auto label bug issue

* nu config is obligatory

* bug report defaults to question

* feature request defaults to question

* applied suggestions
2022-08-15 09:32:38 -05:00
cc263ee15d Update to reedline 0.10.0 (#6327)
Release notes:

https://github.com/nushell/reedline/releases/tag/v0.10.0
2022-08-15 13:00:00 +02:00
a4809f2e68 Update reedline to improved undo-system (#6326)
* Update after Reedline API update

* Remove references to deleted `ReedlineEvent::ActionHandler`
* Update `DescriptionMenu` implementation for the new `Menu` trait
  API changes that work on `Editor` rather than `LineBuffer` objects

* Update reedline

Includes nushell/reedline#460

Co-authored-by: Ben Parks <bnprks+git@gmail.com>
2022-08-15 00:35:37 +02:00
21770367e2 make date format supports locale (#6306)
* add --locale flag to make output support locale

* implement again based on nu-utils get_system_locale_string

* add comment
2022-08-14 08:07:04 -05:00
6145f734b7 Add repository info to all workspace crates (#6320)
This helps people who find these crates on crates.io
2022-08-14 07:21:20 -05:00
9d8d305e9d lazy dataframe reader (#6321)
* lazy dataframe reader

* correct space for polars dependencies
2022-08-14 13:06:31 +01:00
eb55fd2383 cmd(df/first): returns the first row by default. (#6312) 2022-08-13 14:08:00 -05:00
613d2fb8df Bump chrono dependency to fix panic (#6317) 2022-08-13 11:21:28 -07:00
8783742060 Add 'as' keyword to 'overlay add' (#6314) 2022-08-13 17:28:18 +03:00
20528e96c7 Add hide-env to hide environment variables (#6313)
* Add hide-env to hide env vars; Cleanup tests

Also, there were some old unalias tests that I converted to hide.

* Add missing file

* Re-enable hide for env vars

* Fix test

* Rename did you mean error back

It was causing random tests to break
2022-08-13 12:55:06 +03:00
3b6c4c1bb5 run_external: only suggest alternative commands when file not found (#6311) 2022-08-13 00:27:50 -04:00
cb18dd5200 Add decimals to int when using into string --decimals (#6085)
* Add decimals to int when using `into string --decimals`

* Add tests for `into string` when converting int with `--decimals`

* Apply formatting

* Merge `into_str` test files

* Comment out unused code and add TODOs

* Use decimal separator depending on system locale

* Add test helper to run closure in different locale

* Add tests for int-to-string conversion using different locales

* Add utils function to get system locale

* Add panic message when locking mutex fails

* Catch and resume panic later to prevent Mutex poisoning when test fails

* Move test to `nu-test-support` to keep `nu-utils` free of `nu-*` dependencies

See https://github.com/nushell/nushell/pull/6085#issuecomment-1193131694

* Rename test support fn `with_fake_locale` to `with_locale_override`

* Move `get_system_locale()` to `locale` module

* Allow overriding locale with special env variable (when not in release)

* Use special env var to override locale during testing

* Allow callback to return a value in `with_locale_override()`

* Allow multiple options in `nu!` macro

* Allow to set locale as `nu!` macro option

* Use new `locale` option of `nu!` macro instead of `with_locale_override`

Using the `locale` options does not lock the `LOCALE_OVERRIDE_MUTEX`
mutex in `nu-test-support::locale_override` but instead calls the `nu`
command directly with the `NU_LOCALE_OVERRIDE` environment variable.
This allows for parallel test excecution.

* Fix: Add option identifier for `cwd` in usage of `nu!` macro

* Rely on `Display` trait for formatting `nu!` macro command

- Removed the `DisplayPath` trait
- Implement `Display` for `AbsolutePath`, `RelativePath` and
  `AbsoluteFile`

* Default to locale `en_US.UTF-8` for tests when using `nu!` macro

* Add doc comment to `nu!` macro

* Format code using `cargo fmt --all`

* Pass function directly instead of wrapping the call in a closure

https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure

* Pass function to `or_else()` instead of calling it inside `or()`

https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call

* Fix: Add option identifier for `cwd` in usage of `nu!` macro
2022-08-12 21:13:50 -05:00
ccebdd7a7f Fix environment merging in hooks (#6309) 2022-08-13 01:13:28 +03:00
c3efb12733 Allow overlays to import prefixed definitions (#6301)
* WIP

* Fix overlay prefix not preserving correctly

* Work around failing REPL tests

* Remove wrong code when removing with --keep-custom
2022-08-12 21:06:51 +03:00
d885258dc7 Clarify external command error (#6308) 2022-08-13 05:34:10 +12:00
2da915d0c7 make ci use rust-toolchain.toml (#6305)
* make ci use rust-toolchain.toml

* update ci to use actions-rust-lang/setup-rust-toolchain@v1
2022-08-12 09:14:14 -05:00
ae64c58f59 Polars upgrade 0.23 (#6303)
* more lazy expressions

* upgrade polars and correct functions

* arg-where example

* cargo clippy

* restore modified filter files

* correct string addition with str

* correct string addition with str

* correct message in test
2022-08-12 13:10:36 +01:00
ff6868b329 not resolve symlink (#6304) 2022-08-12 06:17:31 -05:00
47ef193600 add rust toolchain file to pin rust version (#6298)
* add rust toolchain file to pin rust version

* rust 1.63 release, bump toolchain

* linux clippy

* pin to 1.63

* pin to 1.61
2022-08-11 15:45:01 -05:00
c2f4969d4f Clippy fix for Rust 1.63 (#6299)
Take more sensitive lints into account

Somewhat ugly in some cases is the replacement of `.get(0)` with
`.first()`
2022-08-11 11:54:54 -05:00
08c98967e0 update build scripts (#6296) 2022-08-11 09:40:35 -05:00
8d091f6f83 Add custom log target to debugging tips (#6293)
Signed-off-by: nibon7 <nibon7@163.com>

Signed-off-by: nibon7 <nibon7@163.com>
2022-08-10 23:10:27 -07:00
58094987ff update a few nushell dependencies (#6291)
* update a few nushell dependencies

* update a test
2022-08-10 14:56:15 -05:00
ce26ef97e4 Revert "Allow using the system's copy of zstd for Polars (#6232)" (#6292)
This reverts commit 9f131d998d.
2022-08-10 13:26:04 -05:00
8f9bd4a299 remove sharkdp's lscolors repo again (#6290)
somehow this got reverted accidentally by someone
2022-08-10 11:13:27 -05:00
45dd7d8770 Fix panic when building without git (#6289)
Signed-off-by: nibon7 <nibon7@163.com>

Signed-off-by: nibon7 <nibon7@163.com>
2022-08-10 10:31:12 -05:00
0f10d984c3 add -n for path expand, so it doesn't follow symlink (#6255)
* add -p for path expand, so it doesn't follow symlink

* fix arg name

* rename from no-dereferenct to no-follow-link

* rename from no-follow-link to no-symlink, and change short -p to -n

* follow strict first

* fix

* simplify test

* fix clippy

* fix test on windows
2022-08-10 08:43:56 -05:00
2e5d981a09 add search terms to ignore command (#6288) 2022-08-10 08:42:21 -05:00
c74254c2cb Fix color settings for logger (#6285)
Signed-off-by: nibon7 <nibon7@163.com>
2022-08-10 06:52:11 -05:00
271fda7c91 Return error when moving a source directory to a target directory which contains a subdirectory with the same name as the source (#6284)
Fixes #6275

Signed-off-by: nibon7 <nibon7@163.com>
2022-08-10 06:51:11 -05:00
0e5886ace1 Fix unused import warning on Linux+Mac (#6281) 2022-08-10 00:28:03 -04:00
4b89c5f900 ignore tests that fail on local machines (#6279) 2022-08-09 23:30:40 -04:00
dcab255d59 Support running batch files without typing their extension (#6278)
* Support running batch files without typing their extension

* suppress warning
2022-08-09 19:24:08 -04:00
fc8512be39 Replace pretty_env_logger with simplelog (#6274)
Fixes #5963

Signed-off-by: nibon7 <nibon7@163.com>
2022-08-09 11:44:37 -05:00
e10ef4aaae bump lscolors to v12.0 (#6272) 2022-08-09 09:32:30 -05:00
0b70ca8451 escape single quotes and remove ansi escape sequences (#6271)
* escape single quotes and remove ansi escape sequences prior to storing strings in db

* clippy
2022-08-09 07:58:36 -05:00
JT
555d9ee763 Fix intermittent test crash (#6268)
* Fix intermittent test crash

* fix windows build
2022-08-09 14:06:46 +12:00
JT
121b801baa bump dev version ahead of language changes (#6267) 2022-08-09 08:15:41 +12:00
9adcecbbf1 new command into sqlite allows you to take lists and create a sqlite db (#6266) 2022-08-08 14:12:42 -05:00
9f131d998d Allow using the system's copy of zstd for Polars (#6232) 2022-08-08 10:58:40 -05:00
cd0a04f02a Delete most deprecated commands (#6260) 2022-08-08 07:46:59 -07:00
aaf5684f9c when spawned process during register plugin, pass env to child process (#6261)
* when spawned process during register plugin, pass env to child process

* tweak comment

* tweak comment

* remove trailing whitespace

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-08-08 07:26:49 -05:00
2f0cb044a5 Refactor shell listing related code (#6262)
* Refactor shell listing related code

Signed-off-by: nibon7 <nibon7@163.com>

* add test

Signed-off-by: nibon7 <nibon7@163.com>
2022-08-08 06:31:24 -05:00
8b55757a0b add more verbose error messages to mv (#6259)
* add more verbose error messages to mv

* tweak output

* clippy

* yet another tweak
2022-08-07 15:25:05 -05:00
84fae6e07e Suggest alternative when command not found (#6256)
* Suggest alternative when command not found

* Add tests for command-not-found suggestions

* Put suggestion in label

* Fix tests
2022-08-07 14:40:41 -04:00
63e220a763 Refactor shell switching related code (#6258)
* Refactor shell switching related code

Signed-off-by: nibon7 <nibon7@163.com>

* add tests

Signed-off-by: nibon7 <nibon7@163.com>

* fix tests

Signed-off-by: nibon7 <nibon7@163.com>
2022-08-07 13:30:40 -05:00
a96fc21f88 Windows: only shell out to cmd for specific commands (#6253) 2022-08-06 13:03:06 -07:00
1ba5b25b29 Make g - switch to the last used shell (#6249)
* Make `g -` switch to the last used shell

Related #6223

Signed-off-by: nibon7 <nibon7@163.com>

* simplify error handling

Signed-off-by: nibon7 <nibon7@163.com>

* update NUSHELL_LAST_SHELL environment

Signed-off-by: nibon7 <nibon7@163.com>

* add test

Signed-off-by: nibon7 <nibon7@163.com>

* fix description

Signed-off-by: nibon7 <nibon7@163.com>
2022-08-06 10:11:03 -05:00
a871f2344a fix examples and let into decimal convert bools too (#6246) 2022-08-06 07:10:33 -05:00
a217bc0715 Fix issue 6223 (#6241)
* Fix6223

* clippy fix

Co-authored-by: Frank <v-frankz@microsoft.com>
2022-08-06 07:09:14 -05:00
34ab4d8360 fix python plugin example (#6242) 2022-08-05 23:57:31 -04:00
48f1c3a49e add bits ror and bits rol commands (#6224) 2022-08-05 15:40:01 +02:00
692376e830 export get_shells and get_current_shell (#6236)
Signed-off-by: nibon7 <nibon7@163.com>
2022-08-05 07:58:40 -05:00
cc99df5ef1 upgrade chrono to v0.4.20 (#6235) 2022-08-05 06:53:01 -05:00
d255a2a050 Fix color parsing (#6234)
Signed-off-by: nibon7 <nibon7@163.com>
2022-08-05 06:30:44 -05:00
c07835f3ad point to the latest main branch for lscolors (#6230) 2022-08-04 17:53:40 -05:00
78a5067434 remove the nana filename string, add some exclusions to gitignore (#6228) 2022-08-04 15:26:34 -05:00
cdeb8de75d replace the regex crate with the fancy-regex crate (#6227) 2022-08-04 14:51:02 -05:00
606547ecb4 Some code refactor for shells related commands (#6226) 2022-08-04 12:55:49 -05:00
3b809b38e8 make cd, cp, ls, mv, open and rm automatically strip ansi codes (#6220)
* make `cd`, `cp`, `ls`, `mv`, `open` and `rm` automatically strip ansi escape code

* fix nu-cli test

* fix nu-cli test 2

* fix nu-cli test 3

* remove `include-ansi` arg

* fix test
2022-08-04 06:59:20 -05:00
7c49a42b68 Fix path_contains_hidden_folder (#6173)
* Fix path_contains_hidden_folder

Signed-off-by: nibon7 <nibon7@163.com>

* add test

Signed-off-by: nibon7 <nibon7@163.com>
2022-08-03 20:59:57 -05:00
87823b0cb5 Reduce dev-deps by narrowing rstest features (#6215)
`rstest = 0.12` added support for asynchronous timeouts during testing
thus requiring a larger set of dependencies. Since `rstest = 0.14` this
can be disabled if not used.

Should keep build times for local or CI tests in check.
2022-08-03 11:55:58 +02:00
ebf845f431 Change how to identify custom comamnd (#6187)
Co-authored-by: Frank <v-frankz@microsoft.com>
2022-08-02 18:40:07 -05:00
ce6df93d05 Add bits shl and bits shr command (#6202)
* Add `bits shift-left` and `bits shift-right` command

* update bits shift error tips

* some code refactor

* update shift right

* some code refactor for bits shift commands

* rename bits shift commands align with bits operators

* update search term

* Update crates/nu-command/src/bits/not.rs

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

* Update crates/nu-command/src/bits/shift_left.rs

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

* Update crates/nu-command/src/bits/shift_right.rs

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

* ci skip

* change default number-bytes for bits shift

* fix bits not tests

* fix bits tests

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2022-08-02 15:52:04 -05:00
e7958bebac sqlite query without collect (#6217) 2022-08-02 21:29:02 +01:00
233afebdf0 allow -h flags for export subcommands (#6189)
* allow `-h` flags for `export` subcommands

* remove unnecessary check

* add tests

* fmt
2022-08-02 10:26:16 -05:00
56069af42d Make open test independent of locale (#6211)
The test was reading the operating system error message which is
dependent on the system locale.

Just test for the `(os error 2)` errorcode instead.
This should support both
unixoid systems and Windows in more locales.
2022-08-02 16:54:26 +02:00
376d22e331 In unix like system, set foreground process while running external command (#6206)
* while executing external command, make it as foreground

* remove useless file

* add comment, make var more readable

* add comment

* fmt code

* fix windows

* fix func name

* fix clippy

* fix windows clippy

* add comments, introduce `ForegroundProcess and ForegroundChild

* fix windows clippy

* fix on windows

* no need fg_process_setup module

* Revert "no need fg_process_setup module"

This reverts commit 21ee4ffbf6.

* restrict visibility for helper functions
2022-08-02 16:53:50 +02:00
7fc8ff60fd Patch lscolors to not blink (#6210)
Unreleased patch for the `lscolors` -> `crossterm` translation, where I
introduced a bug/incompatibility with several terminal emulators causing
blinking

https://github.com/nushell/nushell/pull/6172#issuecomment-1201856518

Refers to patch, can be replaced once a new `lscolors` version is
released:

https://github.com/sharkdp/lscolors/pull/51
2022-08-02 15:15:26 +02:00
1f4791a191 use from table to remove into-db command (#6205)
* use from table to remove into-db command

* correct tests for db expressions
2022-08-01 21:27:55 +01:00
2ac7a4d48d performance improvements for SQLite reads (#6204) 2022-07-31 23:09:03 -07:00
01386f4d58 adds a config reset command (#6149)
* moves config files to nu_utils

* fmt

* fix dockerfile

* fix docs
2022-07-31 20:44:33 -05:00
1086fbe9b5 Revert query command to query db (#6200) 2022-07-31 15:36:14 -04:00
a83bd4ab20 allow uppercase chars to be captured during suppressed input (#6199) 2022-07-31 08:12:13 -05:00
26caf7e1b2 Return error early if seconds part of timestamp is invalid (#6193)
Signed-off-by: nibon7 <nibon7@163.com>
2022-07-31 07:32:16 -05:00
dd2a0e35f4 Add $OLDPWD example for cd (#6194)
* add example for cd

* Fix format_conversions tests
2022-07-30 23:29:52 -04:00
6a4eabf5c7 Add bits or and bits xor command (#6190) 2022-07-30 13:26:37 -05:00
0e2c888f73 Add bits root command and bits and command (#6188) 2022-07-30 07:34:11 -05:00
c140da5740 Update crossterm to version 0.24 (#6172)
- Includes version bump for `lscolors = 0.11` and `reedline` as git
patch
2022-07-30 11:41:15 +02:00
586c0ea3d8 Add bits not command (#6143)
Add `bits not`

Options: `--number-bytes` and `--sized`
2022-07-30 11:25:44 +02:00
d6f4189c7b Fix file lookup in parser keywords; Refactor nu_repl (#6185)
* Fix file lookup in parser keywords

* Make nu_repl a testbin; Fix wrong cwd test error
2022-07-29 23:42:00 +03:00
7a820b1304 add a new welcome banner to nushell (#6163)
* add a new welcome banner to nushell

* remove top line

* tweaked colors and wording

* changed to dimmed white

* removed a comment

* make config nu stand out a little

* fix type-o
2022-07-30 05:50:12 +12:00
767201c40d bump to 0.66.3 dev version (#6183) 2022-07-30 05:48:10 +12:00
3c3614a120 move application reset mode ansi sequence after cmdline execute (#6153) 2022-07-29 08:47:31 -05:00
9e24e452a5 Fix touch panics when using invalid timestamp (#6181)
Signed-off-by: nibon7 <nibon7@163.com>
2022-07-29 06:41:28 -05:00
2cffff0c1b Allow modules to use other modules (#6162)
* Allow private imports inside modules

Can call `use ...` inside modules now.

* Add more tests

* Add a leak test

* Refactor exportables; Prepare for 'export use'

* Fix description

* Implement 'export use' command

This allows re-exporting module's commands and aliases from another
module.

* Add more tests; Fix import pattern list strings

The import pattern strings didn't trim the surrounding quotes.

* Add ignored test
2022-07-29 11:57:10 +03:00
cf2e9cf481 Prevent mv panic again (#6171)
Closes #6170

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-29 10:00:52 +03:00
6b2c7a4c86 update defaut_env (#6176) 2022-07-29 09:57:56 +03:00
JT
98e199f7b5 Move ls back to last-known-good state (#6175)
* revert the recent ls changes

* cargo fmt
2022-07-29 11:00:54 +12:00
JT
10e463180e Revert cp and mv back to last-known-good state (#6169) 2022-07-29 07:49:20 +12:00
c9d0003818 Quickly patch wrong 'export' command name (#6168)
It was looked up as `alias` which explains issue #6167. Proper fix is still needed to enable the -h flag for `export` subcommands.
2022-07-28 21:06:50 +03:00
e2a21afca8 maintain quotes for arguments (#6161) 2022-07-28 16:35:55 +01:00
2ea209bcc0 Prevent mv panic (#6158)
Signed-off-by: nibon7 <nibon7@163.com>
2022-07-28 01:15:02 -04:00
JT
e049ca8ebf bump to 0.66.2 dev version (#6157) 2022-07-28 11:38:52 +12:00
9037a9467b winget wants this to match (#6152)
See the link below for more information
https://github.com/microsoft/winget-pkgs/pull/67598#issuecomment-1196952191
2022-07-27 11:43:17 -05:00
4c6cf36aa5 Fix ls panics when a file or directory not exists (#6148)
* Fix ls panics when a file or directory not exists

Fixes #6146

Signed-off-by: nibon7 <nibon7@163.com>

* add test

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-27 18:53:00 +03:00
c92211c016 Use relative paths as file-relative when parsing a file (#6150)
* Make function local (not used anywhere else)

* Use path relative to the parsed file

* Do not use real cwd at all
2022-07-27 18:36:56 +03:00
8bd6b5b913 clean up some comments (#6147) 2022-07-27 07:44:05 -05:00
b67fe31544 Make path::canonicalize::canonicalize_dot test case more reliable (#6141)
Fixes #6139

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-27 11:30:03 +03:00
JT
c8adb06ca7 fix var names coming from long/short flags (#6142) 2022-07-27 19:27:28 +12:00
JT
9695331eed require variable names to follow additional restrictions (#6125) 2022-07-27 14:08:54 +12:00
JT
d42cfab6ef bump to 0.66.1 dev version (#6140) 2022-07-27 13:15:04 +12:00
JT
2b7c811402 fix 0.66 nu-command crate (#6138) 2022-07-27 11:20:12 +12:00
JT
c6cb491e77 bump to 0.66 (#6137) 2022-07-27 07:56:14 +12:00
JT
e2a4632159 move to latest stable reedline (#6136) 2022-07-27 07:19:38 +12:00
65f0edd14b Allow multiple patterns in ls command (#6098)
* Allow multiple patterns in ls command

* Run formatter

* Comply with style

* Fix format error
2022-07-26 13:08:19 -05:00
b2c466bca6 Make login.nu work when using nu as a login shell (#6134)
* Make login.nu work when using nu as a login shell

Fixes #6055

Signed-off-by: nibon7 <nibon7@163.com>

* fix clippy warning

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-26 09:41:05 -05:00
6b4e577032 plugin show signature (#6126)
* plugin show signature

* remove expect from macro

* use fold to create string
2022-07-26 14:47:54 +01:00
b12a3dd0e5 allow view-source to view aliases (#6135) 2022-07-26 08:06:16 -05:00
d856ac92f4 expand durations to include month, year, decade (#6123)
* expand durations to include month, year, decade

* remove commented out fn

* oops, found more debug comments

* tweaked tests for the new way, borrowed heavily from chrono-humanize-rs

* clippy

* grammar
2022-07-26 08:05:37 -05:00
f5856b0914 Use local time for logger (#6132)
Signed-off-by: nibon7 <nibon7@163.com>
2022-07-26 06:20:35 -05:00
8c675a0d31 update some dependencies (#6131) 2022-07-25 21:09:32 -05:00
86a0e77065 Fix print_table_or_error when table is overridden (#6130)
Related #6113

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-25 20:11:46 -05:00
72c27bd095 Fix PipelineData::print when table is overridden (#6129)
* Fix PipelineData::print when `table` is overridden

Fixes #6113

Signed-off-by: nibon7 <nibon7@163.com>

* don't use deprecated `is_custom_command`

Signed-off-by: nibon7 <nibon7@163.com>

* add test

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-25 18:41:30 -05:00
e4e27b6e11 Use official virtualenv repo for the CI tests (#6127) 2022-07-26 10:20:03 +12:00
JT
475d32045f Revert "Refactor external command (#6083)" (#6116)
This reverts commit 0646f1118c.
2022-07-26 05:37:15 +12:00
3643ee6dfd Simplify print_table_or_error (#6122)
Signed-off-by: nibon7 <nibon7@163.com>
2022-07-25 12:01:10 -05:00
32e4535f24 Simplify eval_block (#6121)
Signed-off-by: nibon7 <nibon7@163.com>
2022-07-25 12:00:31 -05:00
daa2148136 Add CustomValue support to plugins (#6070)
* Skeleton implementation

Lots and lots of TODOs

* Bootstrap simple CustomValue plugin support test

* Create nu_plugin_custom_value

* Skeleton for nu_plugin_custom_values

* Return a custom value from plugin

* Encode CustomValues from plugin calls as PluginResponse::PluginData

* Add new PluginCall variant CollapseCustomValue

* Handle CollapseCustomValue plugin calls

* Add CallInput::Data variant to CallInfo inputs

* Handle CallInfo with CallInput::Data plugin calls

* Send CallInput::Data if Value is PluginCustomValue from plugin calls

* Remove unnecessary boxing of plugins CallInfo

* Add fields needed to collapse PluginCustomValue to it

* Document PluginCustomValue and its purpose

* Impl collapsing using plugin calls in PluginCustomValue::to_base_value

* Implement proper typetag based deserialization for CoolCustomValue

* Test demonstrating that passing back a custom value to plugin works

* Added a failing test for describing plugin CustomValues

* Support describe for PluginCustomValues

- Add name to PluginResponse::PluginData
  - Also turn it into a struct for clarity
- Add name to PluginCustomValue
- Return name field from PluginCustomValue

* Demonstrate that plugins can create and handle multiple CustomValues

* Add bincode to nu-plugin dependencies

This is for demonstration purposes, any schemaless binary seralization
format will work. I picked bincode since it's the most popular for Rust
but there are defintely better options out there for this usecase

* serde_json::Value -> Vec<u8>

* Update capnp schema for new CallInfo.input field

* Move call_input capnp serialization and deserialization into new file

* Deserialize Value's span from Value itself instead of passing call.head

I am not sure if this was correct and I am breaking it or if it was a
bug, I don't fully understand how nu creates and uses Spans. What should
reuse spans and what should recreate new ones?
But yeah it felt weird that the Value's Span was being ignored since in
the json serializer just uses the Value's Span

* Add call_info value round trip test

* Add capnp CallInput::Data serialization and deserialization support

* Add CallInfo::CollapseCustomValue to capnp schema

* Add capnp PluginCall::CollapseCustomValue serialization and deserialization support

* Add PluginResponse::PluginData to capnp schema

* Add capnp PluginResponse::PluginData serialization and deserialization support

* Switch plugins::custom_values tests to capnp

Both json and capnp would work now! Sadly I can't choose both at the
same time :(

* Add missing JsonSerializer round trip tests

* Handle plugin returning PluginData as a response to CollapseCustomValue

* Refactor plugin calling into a reusable function

Many less levels of indentation now!

* Export PluginData from nu_plugin

So plugins can create their very own serve_plugin with whatever
CustomValue behavior they may desire

* Error if CustomValue cannot be handled by Plugin
2022-07-25 17:32:56 +01:00
9097e865ca fix typo of port command (#6120) 2022-07-25 07:07:26 -05:00
894d3e7452 try make port test more reliable (#6117) 2022-07-25 06:42:06 -05:00
5a5c65ee4b Simplify PipelineData::print (#6119)
* Simplify PipelineData::print

Signed-off-by: nibon7 <nibon7@163.com>

* make write_all_and_flush to be associated function

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-25 06:38:21 -05:00
8b35239bce remove misleading example from source (#6118) 2022-07-25 11:52:16 +03:00
87e2fa137a Allow cp multiple files at once (#6114)
* Allow cp multiple files at once

* Expand destination with expand_ndots
2022-07-25 10:42:25 +03:00
JT
46f64c6fdc exit with non-zero exit code when script ends with non-zero exit (#6115) 2022-07-25 10:57:10 +12:00
10536f70f3 move the shell integration title setting to the right place (#6112) 2022-07-24 09:01:59 -05:00
0812a08bfb Don't panic if nu failed to create config files (#6104)
* Don't panic if nu failed to create config files

Signed-off-by: nibon7 <nibon7@163.com>

* eval default config

Signed-off-by: nibon7 <nibon7@163.com>

* tweak words

Signed-off-by: nibon7 <nibon7@163.com>

* tweak words again

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-24 07:00:52 -05:00
5706eddee3 throw error if any? or all? expression invokes invalid command (#6110)
* throw error if any? or all? expression invokes invalid command

* fix tests for windows
2022-07-24 06:28:12 -05:00
0b429fde24 Log warning message if nu failed to sync history (#6106)
Fixes #6088

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-23 11:35:43 -05:00
388ff78a26 trim spaces when converting strings to ints (#6105) 2022-07-23 09:23:04 -05:00
7d46177cf3 Allow mv multiple files at once (#6103)
* Allow mv multiple files at once

* Expand dots in mv src + dst
2022-07-23 07:51:41 -05:00
8a0bd20e84 Prevents panic when parsing JSON containing large number (#6096)
* prevents panic when parsing JSON containing large number

* fmt

* check for '-' sign first

* fmt

* clippy
2022-07-23 13:31:06 +12:00
a1a5a3646b Bump powierza-coefficient to 1.0.1 (#6099) 2022-07-22 19:12:41 -05:00
453c11b4b5 nu-table/ Bump tabled version (#6097)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-22 10:33:29 -05:00
c66b97126f Restore nu_with_plugins test macro (#6065)
* Updated nu_with_plugins to handle new nushell

- Now it requires the plugin format and name to be passed in, because
  we can't really guess the format
- It calls `register` with format and plugin path
- It creates a temporary folder and in it an empty temporary plugin.nu
  so that the tests don't conflict with each other or with local copy of
  plugin.nu
- Instead of passing the commands via stdin it passes them via the new
  --commands command line argument

* Rename path to command for clarity

* Enable core_inc tests

Remove deprecated inc feature and replace with new plugin feature

* Update core_inc tests for new nu_with_plugins syntax

* Rework core_inc::can_only_apply_one

The new inc plugin doesn't error if passed more than one but instead
chooses the highest increment

* Gate all plugin tests behind feature = "plugin" instead of one by one

* Remove format!-like behavior from nu_with_plugins

nu_with_plugins had format!-like behavior where it would allow calls
such as this:
```rs
nu_with_plugins!(
  cwd: "dir/",
  "open {} | get {}",
  "Cargo.toml",
  "package.version"
)
```
And although nifty it seems to have never been used before and the same
can be achieved with a format! like so:
```rs
nu_with_plugins!(
  cwd: "dir/",
  format!("open {} | get {}", "Cargo.toml", "package.version")
)
```
So I am removing it to keep the complexity of the macro in check

* Add multi-plugin support to nu_with_plugins

Useful for testing interactions between plugins

* Alternative 1: run `cargo build` inside of tests

* Handle Windows by canonicalizing paths and add .exe

One VM install later and lots of learning about how command line
arguments work and here we are
2022-07-22 00:14:37 -04:00
0646f1118c Refactor external command (#6083)
Co-authored-by: Frank <v-frankz@microsoft.com>
2022-07-21 19:56:57 -04:00
0bcfa12e0d enable find to work on some external streams (#6094) 2022-07-21 13:19:16 -05:00
b2ec32fdf0 concat string with lazy expressions (#6093) 2022-07-21 18:05:56 +01:00
8f00848ff9 add a fair amount ofsearch terms (#6090) 2022-07-21 06:29:41 -05:00
604025fe34 append string to series (#6089) 2022-07-21 10:42:12 +01:00
98126e2981 add more shell integration ansi escapes in support of vscode (#6087)
* add more shell integration ansi escapes in support of vscode

* clippy
2022-07-20 15:03:29 -05:00
db9b88089e enable find to be able to highlight some hits (#6086)
* enable find to be able to highlight some hits

* oops, deps in the wrong place
2022-07-20 10:09:33 -05:00
a35a71fd82 Make Semicolon stop on error (#6079)
* introduce external command runs to failed error, and implement semicolon relative logic

* ignore test due to semicolon works

* not raise ShellError for external commands

* update comment

* add relative test in for windows

* fix type-o

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-07-20 07:44:42 -05:00
558cd58d09 make into string --decimals add decimals to integer numbers (#6084)
* make `into string --decimals` add decimals to integer numbers

* add exception for 0
2022-07-20 06:16:35 -05:00
410f3ef0f0 nu-table: Update tests after #6080 (#6082)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-19 15:16:12 -05:00
ae765c71fd add config option to limit external command completions (#6076)
* add config option to limit external command completions

* fmt

* small change

* change name in config

* change name in config again
2022-07-19 12:39:50 -05:00
e5684bc34c Consider space for single ... column not enough space (#6080)
* nu-table: Refactoring

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

* nu-table: consider space for single `...` column not enough space

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-19 12:35:25 -05:00
b4a7e7e6e9 nu-table: Add a few tests (#6074)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-19 12:35:13 -05:00
41669e60c8 nu-table: Fix header style (again 2x) (#6073)
* nu-table: Fix header style

It did appeared again after my small change...

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

* nu-table: Add a empty header style test

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-18 11:45:21 -05:00
eeaca50dee Conditionally disable expansion for external command (#6014)
* Fix 5978

* Add unit test for explicit glob

* Format

* Expansion vs none-expansion

* Add unit tests

* Fix format..

* Add debug message for MacOS

* Fix UT on Mac and add tests for windows

* cleanup

* clean up windows test

* single and double qoutes tests

* format...

* Save format.

* Add log to failed windows unit tests

* try `touch` a file

* PS or CMD

* roll back some change

* format

* Remove log and test case

* Add unit test comments

* Fix

Co-authored-by: Frank <v-frankz@microsoft.com>
2022-07-17 16:30:33 -05:00
d8d88cd395 nu-table: Add suffix coloring (#6071)
* nu-table: Bump tabled

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

* nu-table: Add suffix coloring while truncating

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

* Fix cargo fmt

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-17 13:56:31 -05:00
9aabafeb41 Add plugin CLI argument (#6064)
* Add plugin CLI argument

While working on supporting CustomValues in Plugins I stumbled upon the
test utilities defined in [nu-test-support][nu-test-support]
and thought these will come in handy, but they end up being outdated.
They haven't been used or since engine-q's was merged, so they are
currently using the old way engine-q handled plugins, where it would
just look into a specific folder for plugins and call them without
signatures or registration. While fixing that I realized that there is
currently no way to tell nushell to load and save signatures into a
specific path, and so those integration tests could end up potentially
conflicting with each other and with the local plugins the person
running them is using.

So this adds a new CLI argument to specify where to store and load
plugin signatures from

I am not super sure of the way I implemented this, mainly
I was a bit confused about the distinction between
[src/config_files.rs][src/config_files.rs] and
[crates/nu-cli/src/config_files.rs][crates/nu-cli/src/config_files.rs].
Should I be moving the plugin loading function from the `nu-cli` one to
the root one?

[nu-test-support]: 9d0be7d96f/crates/nu-test-support/src/macros.rs (L106)
[src/config_files.rs]: 9d0be7d96f/src/config_files.rs
[crates/nu-cli/src/config_files.rs]: 9d0be7d96f/crates/nu-cli/src/config_files.rs

* Gate new CLI option behind plugin feature

* Rename option to plugin-config
2022-07-17 13:29:19 -05:00
9ced5915ff Fix short-flag completion (#6067) 2022-07-17 07:46:40 -05:00
9d0be7d96f check column type during aggregation (#6058)
* check column type during aggregation

* check first if there is schema
2022-07-16 15:34:12 +01:00
57a6465ba0 add split list subcommand to split up lists (#6062)
* add `split list` subcommand to split up lists

* fmt

* fix shoddy signature
2022-07-16 06:24:37 -05:00
5cc6505512 Handle Windows drive paths in auto-cd (#6051)
* Handle Windows drive paths in auto-cd

* Limit `use regex` to Windows

* Use lazy_static for Windows drive path regex

* try fixing Clippy on *nix
2022-07-15 19:01:38 -07:00
3d45f77692 add wc search term for size and length (#6056) 2022-07-15 10:17:14 -05:00
e01974b7ab Ensure users colors are maintained when highlighting find matches (#6054) 2022-07-15 08:06:29 -05:00
1f01677b7b allow into int to convert octal numbers and 0 padded strings (#6053)
* allow `into int` to convert octal numbers and 0 padded strings

* added some tests in examples
2022-07-15 07:47:33 -05:00
58ee2bf06a fix documentation of plugin encodings (#6052)
Co-authored-by: Benjamin Lee <benjamin@computer.surgery>
2022-07-15 05:28:14 -05:00
7bf09559a6 Refactoring nu_table (#6049)
* nu-table: Remove unused dependencies

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

* nu-table: Small refactoring

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

* nu-table: Refactoring

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

* nu-table: Refactoring alignments

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

* nu-table: Add width check

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

* nu-table/ Use commit instead of branch of tabled

To be safe

* Update Cargo.lock

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

* nu-table: Bump tabled

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-14 15:24:32 -05:00
8dea08929a Cargo.lock was not checked in on typetag revert (#6050) 2022-07-14 13:30:25 -05:00
26f31da711 Split merging of parser delta and stack environment (#6005)
* Remove comment

* Split delta and environment merging

* Move table mode to a more logical place

* Cleanup

* Merge environment after reading default_env.nu

* Fmt
2022-07-14 17:09:27 +03:00
d95a065e3d Fix ps command on linux (#6047)
Fixes #6042

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-14 06:20:54 -05:00
ed50210832 load default env when user don't specified env path (#6040) 2022-07-14 08:53:13 +03:00
ceafe434b5 Downgrade crate typetag to 0.1.8 (#6044)
Co-authored-by: Frank <v-frankz@microsoft.com>
2022-07-13 14:38:29 -05:00
89b374cb16 allow for easy reset of config files with a single command (#6041)
* allow for easy config reset with a single command

* add slightly better help, rebase

* add option to make no backups, make all backups unique through including UNIX Epoch Time in the filename

* time is now formatted in rfc3339

* time is now formatted in a window-friendly format
2022-07-13 10:03:42 -05:00
47c1f475bf Fix panic when opening symlink which points to an inaccessible directory (#6034)
* Fix panic when opening symlink which points to an inaccessible directory

Fixes #6027

Signed-off-by: nibon7 <nibon7@163.com>

* tweak words

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-07-13 07:00:30 -05:00
61e027b227 nu-table: Bump tabled to master (#6038)
There was aparently some debug message on the target commit?

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-13 06:54:49 -05:00
58ab5aa887 nu-table: Remove width estimation logic (#6037)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-13 06:54:03 -05:00
2b2117173c nu-table: Restore atty check (#6036)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-13 06:49:43 -05:00
f2a79cf381 nu-table: Don't show empty header (#6035)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-13 06:43:39 -05:00
ad9449bf00 add ability to do into int on floats using a radix (#6033) 2022-07-12 20:37:57 -05:00
c2f8f4bd9b fix small bug converting string to int (#6031) 2022-07-12 19:34:26 -05:00
8b6232ac87 nu_table: Fix truncating logic (#6028)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-12 13:35:05 -05:00
93a965e3e2 nu_table: Fix style of tables with no header (#6025)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-12 20:56:36 +03:00
217c2bae99 Move wrap responsibility on tabled (#5999)
* nu_table/ Replace wrap.rs logic by tabled::Width::wrap

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

* nu_table: Rename wrap.rs to width_control.rs

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

* nu_table: Add configuration of trimming

```
let-env config = ($env.config | upsert table_trim { methodology: 'wrapping', wrapping_try_keep_words: false })
let-env config = ($env.config | upsert table_trim { methodology: 'truncating', truncatting_suffix: '...@@...' })
```

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

* nu_table: Fix right padding issue

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

* nu_table: Fix trancate issue

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

* nu_table: Fix spelling in config

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

* nu_table: Update tabled dependency

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

* Update default_config.nu with a table_trim options

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-12 11:23:50 -05:00
b9bbf0c10f make auto-cd change $env.OLDPWD (#6019)
* make auto-cd change `$env.OLDPWD`

* fmt

* use Config

* make auto-cd change `.OLDPWD`
2022-07-12 06:05:19 -05:00
a54f9719e5 add unspanned flag to error make, add tests (#6017)
* add `unspanned` flag to error make, add tests

* fmt
2022-07-12 06:03:50 -05:00
JT
a5470b2362 use simpler reedline (#6016) 2022-07-12 13:25:31 +12:00
c1bf9fd897 fixes ansi escape leakage from ill-behaved externals, again! (#6012)
* this fixes ansi escape leakage from ill-behaved externals

* cross-platform fix
2022-07-11 16:01:49 -05:00
f3036b8cfd Allow keeping selected environment variables from removed overlay (#6007)
* Allow keeping selected env from removed overlay

* Remove some duplicate code

* Change --keep-all back to --keep-custom

Because, apparently, you cannot have a named flag called --keep-all,
otherwise tests fail?

* Fix missing line and wrong test value
2022-07-11 23:58:28 +03:00
9b6b817276 update some dependencies (#6009)
* update some dependencies

* there may be some bugs here but it seems to compile and run

* clippy
2022-07-11 11:18:06 -05:00
9e3c64aa84 Add bytes collect, bytes remove, bytes build cmd (#6008)
* add bytes collect

* index_of support searching from end

* add bytes remove

* make bytes replace work better for empty pattern

* add bytes build

* remove comment

* tweak words

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-07-11 06:26:00 -05:00
920e0acb85 Fix load order of config files (#6006) 2022-07-10 18:12:24 +03:00
b7d3623e53 Revert "make module imports in scripts used for relative path. (#5913)" (#6002)
This reverts commit 6dde231dde.
2022-07-10 15:16:46 +03:00
3676a8a48d Expand Hooks Functionality (#5982)
* (WIP) Initial messy support for hooks as strings

* Cleanup after running condition & hook code

Also, remove prints

* Move env hooks eval into its own function

* Add env change hooks to simulator

* Fix hooks simulator not running env hooks properly

* Add missing hooks test file

* Expand hooks tests

* Add blocks as env hooks; Preserve hook environment

* Add full eval to pre prompt/exec hooks; Fix panic

* Rename env change hook back to orig. name

* Print err on test failure; Add list of hooks test

* Consolidate condition block; Fix panic; Misc

* CHange test to use real file

* Remove unused stuff

* Fix potential panics; Clean up errors

* Remove commented unused code

* Clippy: Fix extra references

* Add back support for old-style hooks

* Reorder functions; Fmt

* Fix test on Windows

* Add more test cases; Simplify some error reporting

* Add more tests for setting correct before/after

* Move pre_prompt hook to the beginning

Since we don't have a prompt or blocking on user input, all hooks just
follow after each other.
2022-07-10 13:45:46 +03:00
f85a1d003c throw parser error when multiple short flags are defined without whitespace (#6000)
* throw error when multiple short flags are defined without whitespace

* add tests
2022-07-10 20:32:52 +12:00
121e8678b6 nu-table: Fix a term_width value (#5997)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-09 14:55:47 -05:00
e4c512e33d nu-table: Fix wrap logic (#5998)
Adding space may overflow a cell_width.

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-09 14:55:39 -05:00
81df42d63b add more bytes cmd (#5989) 2022-07-08 21:42:31 -05:00
6802a4ee21 nu-table: Remove a error prone assertion (#5993) 2022-07-08 17:00:01 -05:00
c0ce78f892 add the ability to highlight with regular expressiosn (#5992) 2022-07-08 16:28:10 -05:00
221f36ca65 Add --directory (-D) flag to ls, list the directory itself instead of its contents (#5970)
* Avoid extending the directory without globs in `nu_engine::glob_from`

* avoid joining a `*` to the directory without globs

* remove checks on directory permission and whether it is empty

The previous implemention of `nu_engine::glob_from` will extend the
given directory even if it containes no glob pattern. This commit
overcomes lack of consistency with the function `nu_glob::glob`.

* Add flag -D to ls, to list the directory itself instead of its contents

* add --directory (-d) flag to ls

* correct the difference between the given path and the cwd

* set default path to `.` instead of `./*` when --directory (-d) flag is true

* add comments

* add an example

* add tests

* fmt
2022-07-08 14:15:34 -05:00
125e60d06a Add search terms to 'math' commands (#5990)
* Remove 'average' from search_terms

* Add search_terms to 'floor' and 'variance'
2022-07-08 09:14:51 -05:00
83458510a9 Revert "Return error when external command core dumped (#5908)" (#5987)
This reverts commit 5d00ecef56.
2022-07-07 20:00:04 -04:00
eac5f62959 tweak the find hit highlighting (#5981) 2022-07-07 11:32:58 -05:00
b19cc799aa make history.txt and history.sqlite3 tables have same command column (#5980) 2022-07-07 07:59:00 -05:00
efa56d0147 add the ability to highlight searched for terms (#5979) 2022-07-07 07:14:06 -05:00
47f6d20131 adds better error for failed string-to-duration conversions (#5977)
* adds better error for failed string-to-duration conversions

* makes error multi-spanned, conveys literally all the information available now
2022-07-07 05:54:38 -05:00
e0b4ab09eb compatible with old rust (#5974) 2022-07-06 18:22:45 -05:00
8abf28093a Bump openssl-src from 111.20.0+1.1.1o to 111.22.0+1.1.1q (#5971)
Bumps [openssl-src](https://github.com/alexcrichton/openssl-src-rs) from 111.20.0+1.1.1o to 111.22.0+1.1.1q.
- [Release notes](https://github.com/alexcrichton/openssl-src-rs/releases)
- [Commits](https://github.com/alexcrichton/openssl-src-rs/commits)

---
updated-dependencies:
- dependency-name: openssl-src
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-06 16:12:04 -05:00
d1687df067 Give tabled a try (#5969)
* Drop in replacement from nu-table to tabled.

Must act the same way as original nu-table.

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

Fix some issues

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

* Bump ansi-str version

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

* Update to latest

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

* Fix footer issue

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

* Fix header alignment

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

* Fix header style

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

* Use latest tabled/ansi-str

* Refactorings

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

* Fix clippy warnings

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-06 14:57:40 -05:00
e77219a59f allow where to work with variables (#5955)
* allow `where` to work with variables; breaking change

* change is no longer breaking, adds named to allow passage of blocks

* adds tests

* fmt
2022-07-06 08:49:07 -05:00
22edb37162 Add some bytes relative cmd (#5967)
* add reverse, ends_with command

* add bytes replace, make little refactor

* add bytes add
2022-07-06 08:25:37 -05:00
1ac87715ff add bytes root command (#5956)
* add bytes root command

* fixed type-o
2022-07-06 16:46:56 +12:00
de162c9aea Bump to 0.65.1 dev version (#5962) 2022-07-06 16:25:09 +12:00
390d06d4e7 add bytes starts-with command (#5950)
* refactor operate, make it generic

* refactor operate, add starts with command

* add comment

* remove useless file
2022-07-05 06:42:01 -05:00
89acbda877 Pin reedline to new 0.8.0 release (#5954)
For the nushell 0.65.0 release

https://github.com/nushell/reedline/releases/tag/v0.8.0
2022-07-05 21:25:35 +12:00
JT
0d40d0438f bump to 0.65 (#5952) 2022-07-05 17:54:16 +12:00
1e8212a938 add bytes len (#5945) 2022-07-04 05:51:07 -05:00
JT
2da8310b11 Fix 'skip' support for binary streams (#5943) 2022-07-04 19:53:54 +12:00
JT
c16d8f0d5f Make take work like first (#5942) 2022-07-04 08:03:35 +12:00
JT
2ac5b0480a Binary into int (#5941)
* Add support for binary to into int

* Add test
2022-07-04 06:31:50 +12:00
4e90b478b7 Add bit operator: bit-xor (#5940) 2022-07-03 06:45:20 -05:00
3a38fb94f0 add search terms for is-admin (#5939) 2022-07-03 06:44:26 -05:00
c6f6dcb57c Change C-u and C-k to be readline compatible, move old C-u to C-s (#5938) 2022-07-03 06:43:56 -05:00
b80299eba7 change default keybinding in default config (#5925)
* change default keybinding in default config

* change from alt-o to ctrl-o

* change back to alt-o

* really changed it back to alt-o this time

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-07-02 18:12:03 -05:00
JT
a48616697a Rename bitwise operators for readability (#5937) 2022-07-02 17:05:02 -05:00
b82dccf0bd Add band and bor operator for bit operations (#5936)
* Add `band` and `bor` Operator

* Add tests
2022-07-02 13:03:36 -05:00
84caf8859f add -e flag to print, to print the value to stderr (#5935)
* Refactor: make stdout write all and flush as generic function

* support print to stderr
2022-07-02 09:54:49 -05:00
be7f35246e Fix to md --pretty when rendering a list (#5932)
Fixes #5931

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-02 15:36:16 +03:00
3917fda7ed Update #4202: Add shift operator bshl and bshr for integers (#5928)
* Update #4202: Add shift operator bshl and bshr for integers

* Add more tests
2022-07-02 06:48:43 -05:00
3b357e5402 fix parse_failure_due_conflicted_flags test (#5926) 2022-07-01 21:59:51 -05:00
79da470239 simplify error make (#5883) 2022-07-01 21:06:36 -05:00
37949e70e0 Add all flag to nu-check command (#5911)
* Add all flag

* Make all and moduel flags as mutually exclusive

* Fix new test

* format code...

* tweak words

* another tweak

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-07-01 15:49:24 -05:00
5d00ecef56 Return error when external command core dumped (#5908)
* Return error when external command core dumped

Fixes #5903

Signed-off-by: nibon7 <nibon7@163.com>

* Use signal-hook to get signal name

Signed-off-by: nibon7 <nibon7@163.com>

* Fix comment

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-01 08:58:21 -05:00
6dde231dde make module imports in scripts used for relative path. (#5913)
* always load env

* add interactive argument for read_config_file
2022-07-01 06:35:09 -05:00
58fa2e51a2 update crate thiserror to version 1.0.31 in crates nu-cli, nu-command, nu-parser, nu-protocol (#5919) 2022-06-30 13:55:01 -07:00
cf0877bf72 ensure required positionals don't show up as optional when help (#5916)
* ensure `required` positionals show up as `required` when `help`

* moves it to the older format

* standardises across optional and required parameters
2022-07-01 05:51:41 +12:00
a0db4ce747 Better error handling using do (#5890)
* adds `capture-errors` flag for `do`

* adds `get-type` core command to get type

* fmt

* add tests in example

* fmt

* fix tests

* manually revert previous changes related to `get-type`

* adds method to check for error name using `into string`

* fix clippy
2022-06-29 20:01:34 -05:00
6ee13126f7 Update Dockerfile (#5910)
Container now uses unpriviledged user with UID 1000 by default
Container now uses Alpine as base
Final image size dropped to just 67MB
2022-06-29 18:36:24 -05:00
1c15a4ed3a docs: clarify print and echo commands (#5909)
I thought this comment was relevant:
https://github.com/nushell/nushell/issues/5724#issuecomment-1148164153
2022-06-29 18:43:46 -04:00
7aabc381a3 fix bug where thin theme wasn't getting applied correctly (#5905) 2022-06-28 14:14:20 -05:00
8c9dced71b fix excessive ansi escape sequences (#5901) 2022-06-27 18:51:14 -05:00
06d5a31301 Make sort logic available outside sort-by (#5893) 2022-06-27 13:36:59 -04:00
ffbc0b0180 Header filtering out of for loop (#5896)
* remove extra print

* dataframe with real index

* corrected dataframe tests

* clippy error

* clippy error

* moved header filter out of loop
2022-06-27 06:33:45 -05:00
c0901ef707 Dataframe with real index (#5892)
* remove extra print

* dataframe with real index

* corrected dataframe tests

* clippy error

* clippy error
2022-06-26 17:32:18 -05:00
d3e84daa49 remove extra print (#5891) 2022-06-26 11:48:30 -05:00
228ede18cf build: update miette dependency (#5889) 2022-06-26 07:03:38 -05:00
c5a69271a2 make path exists work on expanded path (#5886)
* make path exists works with home

* fix test name
2022-06-26 06:55:55 -05:00
dc9d939c83 Introduce new command - nu check (#5864)
* nu check command - 1

* Support stream

* Polish code and fix corner case
2022-06-26 06:53:06 -05:00
32f0f94b46 feat: add --binary(-b) option to hash commands (#5885)
For instance,

```
echo 'abcdefghijklmnopqrstuvwxyz' | hash sha256 --binary
```

Will returns the hash as a binary value instead of a hexadecimaly encoded string.
2022-06-26 06:50:56 -05:00
a142d1a192 update encode decode with new signature (#5881) 2022-06-25 19:06:39 -05:00
173d60d59d Deprecate hash base64, extend decode and add encode commands (#5863)
* feat: deprecate `hash base64` command

* feat: extend `decode` and `encode` command families

This commit
- Adds `encode` command family
- Backports `hash base64` features to `encode base64` and `decode base64` subcommands.
- Refactors code a bit and extends tests for encodings
- `decode base64` returns a binary `Value` (that may be decoded into a string using `decode` command)

* feat: add `--binary(-b)` flag to `decode base64`

Default output type is now string, but binary can be requested using this new flag.
2022-06-26 00:35:23 +03:00
JT
f2989bf704 Move input/output type from Command to Signature (#5880) 2022-06-26 09:23:56 +12:00
JT
575ddbd4ef Clippy and remove unused is_binary (#5879) 2022-06-26 08:20:28 +12:00
ef9b72d360 add ability to convert timestamp_millis() (#5876)
* add ability to convert timestamp_millis()

* add example test

* add nanos too
2022-06-25 09:51:41 -05:00
25349a1eac Add an example for default command to get an env var with fallback (#5874)
* Add an example for `default` command to get an env var with fallback

* update test

* update test
2022-06-25 17:27:54 +08:00
99e4c44862 Fix less.exe downloading for windows release pkgs, close #5868 (#5873)
* Fix less.exe downloading for windows release pkgs

* Fix less.exe downloading for windows release pkgs
2022-06-25 09:09:48 +08:00
1345f97202 Errors when let in, let env and similar commands are passed. (#5866)
* throw `let nu/env/nothing/in` error in parsing

* add tests and fmt

* fix clippy

* suggestions

* fmt

* `lvalue.span` instead of `spans[1]`

* clippy

* fmt
2022-06-25 00:55:25 +03:00
f02076daa8 fix plugin path with whitespace (#5871) 2022-06-24 12:44:22 -05:00
JT
533e04a60a Bump to 0.64.1 dev version (#5865) 2022-06-24 16:47:00 +12:00
13c152b00f finish git fetch custom completions (#5859) 2022-06-23 05:19:11 -05:00
f231a6df4a Remove quotes from external args (#5846)
* remove quotes from external args

* remove internal quotes

* correct escaped quotes in string
2022-06-22 22:01:44 -05:00
3c0bccb900 Exclude ./... from expansion (#5839)
* exclude ./... from expansion

* use all instead of any

* no path expansion for external arguments

* clippy error

* expand only tilde
2022-06-22 22:00:30 -05:00
f43a65d7a7 Prevents duplicate fields in transpose -r (#5840) 2022-06-22 19:19:06 -05:00
0827ed143d cleanup $config as a built-in (#5852) 2022-06-22 13:13:03 -05:00
4b84825dbf Remove externa nu from nu config (#5847) 2022-06-22 09:42:18 +03:00
82ae06865c Port command (#5849)
* implement port command

* better comment

* fmt code

* fix example description

* fix usage

* fix tests
2022-06-21 23:27:58 -04:00
128ce6f9b7 update reedline config based on recent reedline changes (#5845) 2022-06-21 12:22:11 -05:00
44cbd88b55 allow comparison for similar types (#5844) 2022-06-21 12:15:31 -05:00
7164929c61 Db commands without DB (#5838)
* database commands without db

* database command tests
2022-06-21 12:14:29 -05:00
848ff8453b feat: Update dockerfile for latest nu release (#5843) 2022-06-21 18:28:31 +08:00
f94ca6cfde root/admin prompt is red now (#5836)
I really miss bash's visual way of signalising root, i.e. blue: user, red: root

So I brought it to nushell (since you've added `is-admin` the code is fully portable and easily-readable) and hope you'll like it
2022-06-20 15:23:55 -05:00
fab3f8fd40 fix exit code (#5835)
* fix exit code

* fix usage

* add comment
2022-06-20 09:05:11 -05:00
dbcfcdae89 calculates history duration properly (#5827) 2022-06-19 00:44:46 -04:00
08aa248c42 Add more tests for completion (#5826)
* Add more tests for completion

* Fix windows

* Cleanup
2022-06-18 19:42:00 -05:00
9f07bcc66f first stab at minimizing ansi escapes (#5822) 2022-06-17 22:07:46 -05:00
2caa44cea8 Fix parser panic (#5820) 2022-06-17 11:11:48 -07:00
28c21121cf fixes to nuon for inf, -inf, and NaN (#5818) 2022-06-17 21:01:37 +03:00
a17d46f200 add more columns to the history command when using sqlite history (#5817) 2022-06-17 09:35:34 -05:00
6cc8402127 Standardise to commands (#5800)
* standarize to commands

* move from to to into
2022-06-17 07:51:50 -05:00
5f0ad1d6ad Fix alias completion crash (#5814)
* Solve crash - commit 1

* commit 2 with issue

* Fix corner case

* Unit tests

* Fix windows tests
2022-06-17 07:50:10 -05:00
8d7bb9147e Attempts to fix file completions for open, rm and ls (and other filesystem commands) (#5805)
* fixes the issue for 'open' and other commands that explicitly use `SyntaxShape::Filepath`

* fixes for `rm` and similar commands

* fixes for `ls`: potentially breaking?

* fmt

* a curious fix to the test

* a curious fix to the test, except for Windows this time

* fixes Windows tests failing

* resolves it by putting an explicit check for `ls`

* reverts unnecessary test changes; fmt

* changes the order of completion operations
2022-06-17 07:47:43 -05:00
bc48b4553c Move the history and tutor commands out of core_commands (#5813)
* move history and tutor commands from core to misc

* add in the Misc Category for the history and tutor commands
2022-06-16 09:58:38 -07:00
28c07a5072 Add Windows Terminal profile and icon in Windows control panel (#5812)
* Show icon in Windows 'Add/Remove Programs' control panel

* Add install option for Windows Terminal profile

* Re-create icon because the icon was not shwon in Windows Terminal

Procedure: opened the original file with GIMP and simply overwrited it
2022-06-16 09:46:33 -07:00
30c8dabeb4 Add test requirements to PR template (#5809)
* Add test requirements to PR template

* tweak words

* another tweak

* add /tests folder as a suggestion

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-06-16 08:14:59 -05:00
8b368b6a4e Fix drop nth with open end range on 32-bit platforms (#5808)
Fixes #5793

Signed-off-by: nibon7 <nibon7@163.com>
2022-06-16 06:39:48 -05:00
8c0d60d0fb add notes for def_env (#5807)
* add notes for def_env

* Update crates/nu-command/src/core_commands/export_def_env.rs

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

* Update crates/nu-command/src/core_commands/def_env.rs

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

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2022-06-16 06:37:44 -05:00
8b0a4ccf4c add light theme to default_config (#5804) 2022-06-16 06:19:49 -05:00
cfe4eff566 update default_context.rs to put the Du command in platform instead core (#5795) 2022-06-15 11:11:26 -07:00
38f3957edf update polars (#5791) 2022-06-15 11:45:03 -05:00
cb66d2bcad Try to fix winget package submit (#5790) 2022-06-15 07:35:28 -05:00
ff73623873 shows location of sqlite3 history file (#5784)
* shows location of sqlite3 file

* fmt
2022-06-15 10:06:49 +02:00
657 changed files with 29099 additions and 15363 deletions

View File

@ -53,7 +53,7 @@ body:
| features | clipboard-cli, ctrlc, dataframe, default, rustyline, term, trash, uuid, which, zip |
| installed_plugins | binaryview, chart bar, chart line, fetch, from bson, from sqlite, inc, match, post, ps, query json, s3, selector, start, sys, textview, to bson, to sqlite, tree, xpath |
validations:
required: false
required: true
- type: textarea
id: context
attributes:

View File

@ -1,5 +1,6 @@
name: Feature Request
description: "When you want a new feature for something that doesn't already exist"
labels: "enhancement"
body:
- type: textarea
id: problem

21
.github/ISSUE_TEMPLATE/question.yml vendored Normal file
View File

@ -0,0 +1,21 @@
name: Question
description: "When you have a question to ask"
labels: "question"
body:
- type: textarea
id: problem
attributes:
label: Question
description: Leave your question here
placeholder: |
A clear and concise question
Example: Is there any equivalent of bash's $CDPATH in Nu?
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional context and details
description: Add any other context, screenshots or other media that will help us understand your question here, if needed.
validations:
required: false

View File

@ -4,6 +4,12 @@
# Tests
Make sure you've done the following:
- [ ] 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.
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)

View File

@ -23,12 +23,13 @@ jobs:
- uses: actions/checkout@v2
- name: Setup Rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
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:
@ -74,17 +75,16 @@ jobs:
- uses: actions/checkout@v2
- name: Setup Rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
uses: actions-rust-lang/setup-rust-toolchain@v1
# makes ci use rust-toolchain.toml
# with:
# profile: minimal
# toolchain: ${{ matrix.rust }}
# override: true
# Temporarily disabled; the cache was getting huge (2.6GB compressed) on Windows and causing issues.
# TODO: investigate why the cache was so big
# - uses: Swatinem/rust-cache@v1
# with:
# key: ${{ matrix.style }}v3 # increment this to bust the cache if needed
- uses: Swatinem/rust-cache@v1
with:
key: ${{ matrix.style }}v3 # increment this to bust the cache if needed
- name: Tests
uses: actions-rs/cargo@v1
@ -111,11 +111,12 @@ jobs:
- uses: actions/checkout@v2
- name: Setup Rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
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:
@ -135,10 +136,7 @@ jobs:
- run: python -m pip install tox
- name: Install virtualenv
run: |
git clone https://github.com/kubouch/virtualenv.git && \
cd virtualenv && \
git checkout engine-q-update
run: git clone https://github.com/pypa/virtualenv.git
shell: bash
- name: Test Nushell in virtualenv
@ -164,11 +162,12 @@ jobs:
- uses: actions/checkout@v2
- name: Setup Rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
uses: actions-rust-lang/setup-rust-toolchain@v1
# makes ci use rust-toolchain.toml
# with:
# profile: minimal
# toolchain: ${{ matrix.rust }}
# override: true
- name: Clippy
uses: actions-rs/cargo@v1

41
.github/workflows/manual.yml vendored Normal file
View File

@ -0,0 +1,41 @@
# This is a basic workflow that is manually triggered
# Don't run it unless you know what you are doing
name: Manual Workflow for Winget Submission
# Controls when the action will run. Workflow runs when manually triggered using the UI
# or API.
on:
workflow_dispatch:
# Inputs the workflow accepts.
inputs:
ver:
# Friendly description to be shown in the UI instead of 'ver'
description: 'The nushell version to release'
# Default value if no value is explicitly provided
default: '0.66.0'
# Input has to be provided for the workflow to run
required: true
uri:
# Friendly description to be shown in the UI instead of 'uri'
description: 'The nushell windows .msi package URI to publish'
# Default value if no value is explicitly provided
default: 'https://github.com/nushell/nushell/releases/download/0.66.0/nu-0.66.0-x86_64-pc-windows-msvc.msi'
# Input has to be provided for the workflow to run
required: true
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job
rls-winget-pkg:
name: Publish winget package manually
# The type of runner that the job will run on
runs-on: windows-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Runs commands using the runners shell
- name: Submit package to Windows Package Manager Community Repository Manually
run: |
iwr https://github.com/microsoft/winget-create/releases/download/v1.0.4.0/wingetcreate.exe -OutFile wingetcreate.exe
.\wingetcreate.exe update Nushell.Nushell -s -v ${{ github.event.inputs.ver }} -u ${{ github.event.inputs.uri }} -t ${{ secrets.NUSHELL_PAT }}

View File

@ -41,7 +41,7 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
} else {
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
# Actually just for x86_64-unknown-linux-musl target
sudo apt install musl-tools -y
if $os == 'ubuntu-latest' { sudo apt install musl-tools -y }
cargo-build-nu $flags
}
}
@ -50,7 +50,7 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
# Build for Windows without static-link-openssl feature
# ----------------------------------------------------------------------------
if $os in ['windows-latest'] {
if ($flags | str trim | empty?) {
if ($flags | str trim | is-empty) {
cargo build --release --all --target $target --features=extra
} else {
cargo build --release --all --target $target --features=extra $flags
@ -80,7 +80,7 @@ let ver = if $os == 'windows-latest' {
} else {
(do -i { ./output/nu -c 'version' }) | str collect
}
if ($ver | str trim | empty?) {
if ($ver | str trim | is-empty) {
$'(ansi r)Incompatible nu binary...(ansi reset)'
} else { $ver }
@ -102,8 +102,8 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
let releaseStem = $'($bin)-($version)-($target)'
$'(char nl)Download less related stuffs...'; hr-line
curl https://github.com/jftuga/less-Windows/releases/download/less-v590/less.exe -o $'($dist)\less.exe'
curl https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o $'($dist)\LICENSE-for-less.txt'
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v590/less.exe -o less.exe
aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
# Create Windows msi release package
if (get-env _EXTRA_) == 'msi' {
@ -113,7 +113,7 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
cd $src; hr-line
# Wix need the binaries be stored in target/release/
cp -r $'($dist)/*' target/release/
cargo install cargo-wix --version 0.3.2
cargo install cargo-wix --version 0.3.3
cargo wix --no-build --nocapture --package nu --output $wixRelease
echo $'::set-output name=archive::($wixRelease)'
@ -124,14 +124,14 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
7z a $archive *
print $'archive: ---> ($archive)';
let pkg = (ls -f $archive | get name)
if not ($pkg | empty?) {
if not ($pkg | is-empty) {
echo $'::set-output name=archive::($pkg | get 0)'
}
}
}
def 'cargo-build-nu' [ options: string ] {
if ($options | str trim | empty?) {
if ($options | str trim | is-empty) {
cargo build --release --all --target $target --features=extra,static-link-openssl
} else {
cargo build --release --all --target $target --features=extra,static-link-openssl $options
@ -143,7 +143,7 @@ def 'hr-line' [
--blank-line(-b): bool
] {
print $'(ansi g)---------------------------------------------------------------------------->(ansi reset)'
if $blank-line { char nl }
if $blank_line { char nl }
}
# Get the specified env key's value or ''

View File

@ -70,9 +70,9 @@ jobs:
target: ${{ matrix.target }}
- name: Setup Nushell
uses: hustcer/setup-nu@v1
uses: hustcer/setup-nu@v2.1
with:
version: 0.63.0
version: 0.68.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,4 +1,4 @@
name: Submit Nushell package to Windows Package Manager Community Repository
name: Submit Nushell package to Windows Package Manager Community Repository
on:
workflow_dispatch:
@ -13,7 +13,7 @@ jobs:
steps:
- name: Submit package to Windows Package Manager Community Repository
run: |
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
iwr https://github.com/microsoft/winget-create/releases/download/v1.0.4.0/wingetcreate.exe -OutFile wingetcreate.exe
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows.msi' | Select -ExpandProperty browser_download_url -First 1
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows-msvc.msi' | Select -ExpandProperty browser_download_url -First 1
.\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}

6
.gitignore vendored
View File

@ -29,3 +29,9 @@ debian/nu/
# Coverage tools
lcov.info
tarpaulin-report.html
# Visual Studio
.vs/*
*.rsproj
*.rsproj.user
*.sln

View File

@ -66,3 +66,9 @@ cargo build
```shell
cargo run --release --features=extra -- --log-level trace
```
- To redirect trace logs to a file, enable the `--log-target file` switch.
```shell
cargo run --release --features=extra -- --log-level trace --log-target file
open $"($nu.temp-path)/nu-($nu.pid).log"
```

1670
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@ name = "nu"
readme = "README.md"
repository = "https://github.com/nushell/nushell"
rust-version = "1.60"
version = "0.64.0"
version = "0.69.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -28,34 +28,37 @@ members = [
"crates/nu_plugin_gstat",
"crates/nu_plugin_example",
"crates/nu_plugin_query",
"crates/nu_plugin_custom_values",
"crates/nu-utils",
]
[dependencies]
chrono = { version = "0.4.19", features = ["serde"] }
crossterm = "0.23.0"
chrono = { version = "0.4.21", features = ["serde"] }
crossterm = "0.24.0"
ctrlc = "3.2.1"
log = "0.4"
miette = "4.5.0"
miette = "5.1.0"
nu-ansi-term = "0.46.0"
nu-cli = { path="./crates/nu-cli", version = "0.64.0" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.64.0" }
nu-command = { path="./crates/nu-command", version = "0.64.0" }
nu-engine = { path="./crates/nu-engine", version = "0.64.0" }
nu-json = { path="./crates/nu-json", version = "0.64.0" }
nu-parser = { path="./crates/nu-parser", version = "0.64.0" }
nu-path = { path="./crates/nu-path", version = "0.64.0" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.64.0" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.64.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.64.0" }
nu-system = { path = "./crates/nu-system", version = "0.64.0" }
nu-table = { path = "./crates/nu-table", version = "0.64.0" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.64.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.64.0" }
reedline = { version = "0.7.0", features = ["bashisms", "sqlite"]}
pretty_env_logger = "0.4.0"
nu-cli = { path="./crates/nu-cli", version = "0.69.0" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.69.0" }
nu-command = { path="./crates/nu-command", version = "0.69.0" }
nu-engine = { path="./crates/nu-engine", version = "0.69.0" }
nu-json = { path="./crates/nu-json", version = "0.69.0" }
nu-parser = { path="./crates/nu-parser", version = "0.69.0" }
nu-path = { path="./crates/nu-path", version = "0.69.0" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.69.0" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.69.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.69.0" }
nu-system = { path = "./crates/nu-system", version = "0.69.0" }
nu-table = { path = "./crates/nu-table", version = "0.69.0" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.69.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.69.0" }
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
rayon = "1.5.1"
is_executable = "1.0.1"
simplelog = "0.12.0"
time = "0.3.12"
[target.'cfg(not(target_os = "windows"))'.dependencies]
# Our dependencies don't use OpenSSL on Windows
@ -63,13 +66,13 @@ openssl = { version = "0.10.38", features = ["vendored"], optional = true }
signal-hook = { version = "0.3.14", default-features = false }
[dev-dependencies]
nu-test-support = { path="./crates/nu-test-support", version = "0.64.0" }
nu-test-support = { path="./crates/nu-test-support", version = "0.69.0" }
tempfile = "3.2.0"
assert_cmd = "2.0.2"
pretty_assertions = "1.0.0"
serial_test = "0.5.1"
serial_test = "0.8.0"
hamcrest2 = "0.3.0"
rstest = "0.12.0"
rstest = {version = "0.15.0", default-features = false}
itertools = "0.10.3"
[target.'cfg(windows)'.build-dependencies]
@ -77,9 +80,9 @@ 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"]
@ -119,3 +122,6 @@ debug = false
[[bin]]
name = "nu"
path = "src/main.rs"
[patch.crates-io]
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -10,6 +10,7 @@ NU_PLUGINS=(
'nu_plugin_gstat'
'nu_plugin_inc'
'nu_plugin_query'
'nu_plugin_custom_values'
)
echo "Building nushell"

View File

@ -24,9 +24,13 @@ cargo build
@echo.
@cd ..\..\crates\nu_plugin_query
echo Building nu_plugin_query.exe
cargo build
@echo.
@cd ..\..\crates\nu_plugin_custom_values
echo Building nu_plugin_custom_values.exe
cargo build
@echo.
@cd ..\..

View File

@ -11,6 +11,7 @@ let plugins = [
nu_plugin_gstat,
nu_plugin_query,
nu_plugin_example,
nu_plugin_custom_values,
]
for plugin in $plugins {

View File

@ -1,33 +1,40 @@
[package]
authors = ["The Nushell Project Developers"]
description = "CLI-related functionality for Nushell"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
edition = "2021"
license = "MIT"
name = "nu-cli"
version = "0.64.0"
version = "0.69.0"
[dev-dependencies]
nu-test-support = { path="../nu-test-support", version = "0.64.0" }
nu-command = { path = "../nu-command", version = "0.64.0" }
nu-test-support = { path="../nu-test-support", version = "0.69.0" }
nu-command = { path = "../nu-command", version = "0.69.0" }
rstest = {version = "0.15.0", default-features = false}
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.64.0" }
nu-path = { path = "../nu-path", version = "0.64.0" }
nu-parser = { path = "../nu-parser", version = "0.64.0" }
nu-protocol = { path = "../nu-protocol", version = "0.64.0" }
nu-utils = { path = "../nu-utils", version = "0.64.0" }
nu-engine = { path = "../nu-engine", version = "0.69.0" }
nu-path = { path = "../nu-path", version = "0.69.0" }
nu-parser = { path = "../nu-parser", version = "0.69.0" }
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
nu-utils = { path = "../nu-utils", version = "0.69.0" }
nu-ansi-term = "0.46.0"
nu-color-config = { path = "../nu-color-config", version = "0.64.0" }
reedline = { version = "0.7.0", features = ["bashisms", "sqlite"]}
crossterm = "0.23.0"
miette = { version = "4.5.0", features = ["fancy"] }
thiserror = "1.0.29"
fuzzy-matcher = "0.3.7"
nu-color-config = { path = "../nu-color-config", version = "0.69.0" }
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
log = "0.4"
atty = "0.2.14"
chrono = "0.4.21"
crossterm = "0.24.0"
fancy-regex = "0.10.0"
fuzzy-matcher = "0.3.7"
is_executable = "1.0.1"
chrono = "0.4.19"
sysinfo = "0.24.1"
lazy_static = "1.4.0"
log = "0.4"
miette = { version = "5.1.0", features = ["fancy"] }
percent-encoding = "2"
strip-ansi-escapes = "0.1.1"
sysinfo = "0.26.2"
thiserror = "1.0.31"
[features]
plugin = []

View File

@ -5,21 +5,27 @@ use nu_engine::{convert_env_values, eval_block};
use nu_parser::parse;
use nu_protocol::engine::Stack;
use nu_protocol::{
engine::{EngineState, StateDelta, StateWorkingSet},
engine::{EngineState, StateWorkingSet},
PipelineData, Spanned, Value,
};
use std::path::Path;
/// Run a command (or commands) given to us by the user
pub fn evaluate_commands(
commands: &Spanned<String>,
init_cwd: &Path,
engine_state: &mut EngineState,
stack: &mut Stack,
input: PipelineData,
is_perf_true: bool,
table_mode: Option<Value>,
) -> Result<()> {
// Run a command (or commands) given to us by the user
) -> Result<Option<i64>> {
// Translate environment variables from Strings to Values
if let Some(e) = convert_env_values(engine_state, stack) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
std::process::exit(1);
}
// Parse the source code
let (block, delta) = {
if let Some(ref t_mode) = table_mode {
let mut config = engine_state.get_config().clone();
@ -39,43 +45,19 @@ pub fn evaluate_commands(
(output, working_set.render())
};
if let Err(err) = engine_state.merge_delta(delta, None, init_cwd) {
// Update permanent state
if let Err(err) = engine_state.merge_delta(delta) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
}
let mut config = engine_state.get_config().clone();
if let Some(t_mode) = table_mode {
config.table_mode = t_mode.as_string()?;
}
// Merge the delta in case env vars changed in the config
match nu_engine::env::current_dir(engine_state, stack) {
Ok(cwd) => {
if let Err(e) =
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
{
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
std::process::exit(1);
}
}
Err(e) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
std::process::exit(1);
}
}
// Translate environment variables from Strings to Values
if let Some(e) = convert_env_values(engine_state, stack) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
std::process::exit(1);
}
match eval_block(engine_state, stack, &block, input, false, false) {
// Run the block
let exit_code = match eval_block(engine_state, stack, &block, input, false, false) {
Ok(pipeline_data) => {
let mut config = engine_state.get_config().clone();
if let Some(t_mode) = table_mode {
config.table_mode = t_mode.as_string()?;
}
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
}
Err(err) => {
@ -84,11 +66,11 @@ pub fn evaluate_commands(
report_error(&working_set, &err);
std::process::exit(1);
}
}
};
if is_perf_true {
info!("evaluate {}:{}:{}", file!(), line!(), column!());
}
Ok(())
Ok(exit_code)
}

View File

@ -11,6 +11,7 @@ pub struct CommandCompletion {
engine_state: Arc<EngineState>,
flattened: Vec<(Span, FlatShape)>,
flat_shape: FlatShape,
force_completion_after_space: bool,
}
impl CommandCompletion {
@ -19,11 +20,13 @@ impl CommandCompletion {
_: &StateWorkingSet,
flattened: Vec<(Span, FlatShape)>,
flat_shape: FlatShape,
force_completion_after_space: bool,
) -> Self {
Self {
engine_state,
flattened,
flat_shape,
force_completion_after_space,
}
}
@ -43,19 +46,21 @@ impl CommandCompletion {
if let Ok(mut contents) = std::fs::read_dir(path) {
while let Some(Ok(item)) = contents.next() {
if !executables.contains(
&item
.path()
.file_name()
.map(|x| x.to_string_lossy().to_string())
.unwrap_or_default(),
) && matches!(
item.path()
.file_name()
.map(|x| match_algorithm
if self.engine_state.config.max_external_completion_results
> executables.len() as i64
&& !executables.contains(
&item
.path()
.file_name()
.map(|x| x.to_string_lossy().to_string())
.unwrap_or_default(),
)
&& matches!(
item.path().file_name().map(|x| match_algorithm
.matches_str(&x.to_string_lossy(), prefix)),
Some(true)
) && is_executable::is_executable(&item.path())
Some(true)
)
&& is_executable::is_executable(&item.path())
{
if let Ok(name) = item.file_name().into_string() {
executables.push(name);
@ -114,7 +119,8 @@ impl CommandCompletion {
let partial = working_set.get_span_contents(span);
let partial = String::from_utf8_lossy(partial).to_string();
let results = if find_externals {
if find_externals {
let results_external = self
.external_command_completion(&partial, match_algorithm)
.into_iter()
@ -146,9 +152,7 @@ impl CommandCompletion {
results
} else {
results
};
results
}
}
}
@ -205,6 +209,10 @@ impl Completer for CommandCompletion {
|| ((span.end - span.start) == 0)
{
// we're in a gap or at a command
if working_set.get_span_contents(span).is_empty() && !self.force_completion_after_space
{
return vec![];
}
self.complete_commands(
working_set,
span,

View File

@ -2,10 +2,11 @@ use crate::completions::{
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
DotNuCompletion, FileCompletion, FlagCompletion, MatchAlgorithm, VariableCompletion,
};
use nu_engine::eval_block;
use nu_parser::{flatten_expression, parse, FlatShape};
use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet},
Span,
BlockId, PipelineData, Span, Value,
};
use reedline::{Completer as ReedlineCompleter, Suggestion};
use std::str;
@ -56,6 +57,67 @@ impl NuCompleter {
suggestions
}
fn external_completion(
&self,
block_id: BlockId,
spans: Vec<String>,
offset: usize,
span: Span,
) -> 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);
// Line
if let Some(pos_arg) = block.signature.required_positional.get(0) {
if let Some(var_id) = pos_arg.var_id {
callee_stack.add_var(
var_id,
Value::List {
vals: spans
.into_iter()
.map(|it| Value::String {
val: it,
span: Span::unknown(),
})
.collect(),
span: Span::unknown(),
},
);
}
}
let result = eval_block(
&self.engine_state,
&mut callee_stack,
block,
PipelineData::new(span),
true,
true,
);
match result {
Ok(pd) => {
let value = pd.into_value(span);
if let Value::List { vals, span: _ } = value {
let result = map_value_completions(
vals.iter(),
Span {
start: span.start,
end: span.end,
},
offset,
);
return result;
}
}
Err(err) => println!("failed to eval completer block: {}", err),
}
vec![]
}
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
let mut working_set = StateWorkingSet::new(&self.engine_state);
let offset = working_set.next_span_start();
@ -63,19 +125,33 @@ impl NuCompleter {
let initial_line = line.to_string();
new_line.push(b'a');
let pos = offset + pos;
let config = self.engine_state.get_config();
let (output, _err) = parse(&mut working_set, Some("completer"), &new_line, false, &[]);
for pipeline in output.pipelines.into_iter() {
for expr in pipeline.expressions {
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
let span_offset: usize = alias_offset.iter().sum();
let mut spans: Vec<String> = vec![];
for (flat_idx, flat) in flattened.iter().enumerate() {
let alias = if alias_offset.is_empty() {
0
// Read the current spam to string
let current_span = working_set.get_span_contents(flat.0).to_vec();
let current_span_str = String::from_utf8_lossy(&current_span);
// Skip the last 'a' as span item
if flat_idx == flattened.len() - 1 {
let mut chars = current_span_str.chars();
chars.next_back();
let current_span_str = chars.as_str().to_owned();
spans.push(current_span_str.to_string());
} else {
alias_offset[flat_idx]
};
if pos >= flat.0.start - alias && pos < flat.0.end - alias {
spans.push(current_span_str.to_string());
}
// Complete based on the last span
if pos + span_offset >= flat.0.start && pos + span_offset < flat.0.end {
// Context variables
let most_left_var =
most_left_variable(flat_idx, &working_set, flattened.clone());
@ -84,42 +160,18 @@ impl NuCompleter {
let new_span = if flat_idx == 0 {
Span {
start: flat.0.start,
end: flat.0.end - 1 - alias,
end: flat.0.end - 1 - span_offset,
}
} else {
Span {
start: flat.0.start - alias,
end: flat.0.end - 1 - alias,
start: flat.0.start - span_offset,
end: flat.0.end - 1 - span_offset,
}
};
// Parses the prefix
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
prefix.remove(pos - (flat.0.start - alias));
// Completions that depends on the previous expression (e.g: use, source)
if flat_idx > 0 {
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
// Read the content for the previous expression
let prev_expr_str =
working_set.get_span_contents(previous_expr.0).to_vec();
// Completion for .nu files
if prev_expr_str == b"use" || prev_expr_str == b"source" {
let mut completer =
DotNuCompletion::new(self.engine_state.clone());
return self.process_completion(
&mut completer,
&working_set,
prefix,
new_span,
offset,
pos,
);
}
}
}
prefix.remove(pos - (flat.0.start - span_offset));
// Variables completion
if prefix.starts_with(b"$") || most_left_var.is_some() {
@ -141,8 +193,38 @@ impl NuCompleter {
// Flags completion
if prefix.starts_with(b"-") {
let mut completer = FlagCompletion::new(expr);
// Try to complete flag internally
let mut completer = FlagCompletion::new(expr.clone());
let result = self.process_completion(
&mut completer,
&working_set,
prefix.clone(),
new_span,
offset,
pos,
);
if !result.is_empty() {
return result;
}
// 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);
}
}
// specially check if it is currently empty - always complete commands
if flat_idx == 0 && working_set.get_span_contents(new_span).is_empty() {
let mut completer = CommandCompletion::new(
self.engine_state.clone(),
&working_set,
flattened.clone(),
// flat_idx,
FlatShape::String,
true,
);
return self.process_completion(
&mut completer,
&working_set,
@ -153,6 +235,42 @@ impl NuCompleter {
);
}
// Completions that depends on the previous expression (e.g: use, source-env)
if flat_idx > 0 {
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
// Read the content for the previous expression
let prev_expr_str =
working_set.get_span_contents(previous_expr.0).to_vec();
// Completion for .nu files
if prev_expr_str == b"use" || prev_expr_str == b"source-env" {
let mut completer =
DotNuCompletion::new(self.engine_state.clone());
return self.process_completion(
&mut completer,
&working_set,
prefix,
new_span,
offset,
pos,
);
} else if prev_expr_str == b"ls" {
let mut completer =
FileCompletion::new(self.engine_state.clone());
return self.process_completion(
&mut completer,
&working_set,
prefix,
new_span,
offset,
pos,
);
}
}
}
// Match other types
match &flat.1 {
FlatShape::Custom(decl_id) => {
@ -185,6 +303,18 @@ impl NuCompleter {
pos,
);
}
FlatShape::Filepath | FlatShape::GlobPattern => {
let mut completer = FileCompletion::new(self.engine_state.clone());
return self.process_completion(
&mut completer,
&working_set,
prefix,
new_span,
offset,
pos,
);
}
flat_shape => {
let mut completer = CommandCompletion::new(
self.engine_state.clone(),
@ -192,9 +322,10 @@ impl NuCompleter {
flattened.clone(),
// flat_idx,
flat_shape.clone(),
false,
);
let out: Vec<_> = self.process_completion(
let mut out: Vec<_> = self.process_completion(
&mut completer,
&working_set,
prefix.clone(),
@ -203,21 +334,30 @@ impl NuCompleter {
pos,
);
if out.is_empty() {
let mut completer =
FileCompletion::new(self.engine_state.clone());
return self.process_completion(
&mut completer,
&working_set,
prefix,
new_span,
offset,
pos,
);
if !out.is_empty() {
return out;
}
return out;
// Check for file completion
let mut completer = FileCompletion::new(self.engine_state.clone());
out = self.process_completion(
&mut completer,
&working_set,
prefix,
new_span,
offset,
pos,
);
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);
}
}
};
}
@ -225,7 +365,7 @@ impl NuCompleter {
}
}
return vec![];
vec![]
}
}
@ -235,7 +375,7 @@ impl ReedlineCompleter for NuCompleter {
}
}
type MatchedAlias<'a> = Vec<(&'a [u8], &'a [u8])>;
type MatchedAlias = Vec<(Vec<u8>, Vec<u8>)>;
// Handler the completion when giving lines contains at least one alias. (e.g: `g checkout`)
// that `g` is an alias of `git`
@ -254,6 +394,13 @@ fn try_find_alias(line: &[u8], working_set: &StateWorkingSet) -> (Vec<u8>, Vec<u
lens -= 1;
}
}
if !line.is_empty() {
let last = line.last().expect("input is empty");
if last == &b' ' {
output.push(b' ');
}
}
} else {
output = line.to_vec();
}
@ -261,7 +408,7 @@ fn try_find_alias(line: &[u8], working_set: &StateWorkingSet) -> (Vec<u8>, Vec<u
(output, alias_offset)
}
fn search_alias<'a>(input: &'a [u8], working_set: &'a StateWorkingSet) -> Option<MatchedAlias<'a>> {
fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option<MatchedAlias> {
let mut vec_names = vec![];
let mut vec_alias = vec![];
let mut pos = 0;
@ -269,27 +416,31 @@ fn search_alias<'a>(input: &'a [u8], working_set: &'a StateWorkingSet) -> Option
for (index, character) in input.iter().enumerate() {
if *character == b' ' {
let range = &input[pos..index];
vec_names.push(range);
vec_names.push(range.to_owned());
pos = index + 1;
}
}
// Push the rest to names vector.
if pos < input.len() {
vec_names.push(&input[pos..]);
vec_names.push(input[pos..].to_owned());
}
for name in &vec_names {
if let Some(alias_id) = working_set.find_alias(name) {
if let Some(alias_id) = working_set.find_alias(&name[..]) {
let alias_span = working_set.get_alias(alias_id);
let mut span_vec = vec![];
is_alias = true;
for alias in alias_span {
let name = working_set.get_span_contents(*alias);
if !name.is_empty() {
vec_alias.push(name);
span_vec.push(name);
}
}
// Join span of vector together for complex alias, e.g: `f` is an alias for `git remote -v`
let full_aliases = span_vec.join(&[b' '][..]);
vec_alias.push(full_aliases);
} else {
vec_alias.push(name);
vec_alias.push(name.to_owned());
}
}
@ -352,3 +503,65 @@ fn most_left_variable(
Some((var, sublevels))
}
pub fn map_value_completions<'a>(
list: impl Iterator<Item = &'a Value>,
span: Span,
offset: usize,
) -> Vec<Suggestion> {
list.filter_map(move |x| {
// Match for string values
if let Ok(s) = x.as_string() {
return Some(Suggestion {
value: s,
description: None,
extra: None,
span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
append_whitespace: false,
});
}
// Match for record values
if let Ok((cols, vals)) = x.as_record() {
let mut suggestion = Suggestion {
value: String::from(""), // Initialize with empty string
description: None,
extra: None,
span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
append_whitespace: false,
};
// Iterate the cols looking for `value` and `description`
cols.iter().zip(vals).for_each(|it| {
// Match `value` column
if it.0 == "value" {
// Convert the value to string
if let Ok(val_str) = it.1.as_string() {
// Update the suggestion value
suggestion.value = val_str;
}
}
// Match `description` column
if it.0 == "description" {
// Convert the value to string
if let Ok(desc_str) = it.1.as_string() {
// Update the suggestion value
suggestion.description = Some(desc_str);
}
}
});
return Some(suggestion);
}
None
})
.collect()
}

View File

@ -8,6 +8,8 @@ use nu_protocol::{
use reedline::Suggestion;
use std::sync::Arc;
use super::completer::map_value_completions;
pub struct CustomCompletion {
engine_state: Arc<EngineState>,
stack: Stack,
@ -26,69 +28,6 @@ impl CustomCompletion {
sort_by: SortBy::None,
}
}
fn map_completions<'a>(
&self,
list: impl Iterator<Item = &'a Value>,
span: Span,
offset: usize,
) -> Vec<Suggestion> {
list.filter_map(move |x| {
// Match for string values
if let Ok(s) = x.as_string() {
return Some(Suggestion {
value: s,
description: None,
extra: None,
span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
append_whitespace: false,
});
}
// Match for record values
if let Ok((cols, vals)) = x.as_record() {
let mut suggestion = Suggestion {
value: String::from(""), // Initialize with empty string
description: None,
extra: None,
span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
append_whitespace: false,
};
// Iterate the cols looking for `value` and `description`
cols.iter().zip(vals).for_each(|it| {
// Match `value` column
if it.0 == "value" {
// Convert the value to string
if let Ok(val_str) = it.1.as_string() {
// Update the suggestion value
suggestion.value = val_str;
}
}
// Match `description` column
if it.0 == "description" {
// Convert the value to string
if let Ok(desc_str) = it.1.as_string() {
// Update the suggestion value
suggestion.description = Some(desc_str);
}
}
});
return Some(suggestion);
}
None
})
.collect()
}
}
impl Completer for CustomCompletion {
@ -144,7 +83,7 @@ impl Completer for CustomCompletion {
.and_then(|val| {
val.as_list()
.ok()
.map(|it| self.map_completions(it.iter(), span, offset))
.map(|it| map_value_completions(it.iter(), span, offset))
})
.unwrap_or_default();
let options = value.get_data_by_key("options");
@ -189,7 +128,7 @@ impl Completer for CustomCompletion {
completions
}
Value::List { vals, .. } => self.map_completions(vals.iter(), span, offset),
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
_ => vec![],
}
}

View File

@ -41,7 +41,7 @@ impl Completer for VariableCompletion {
options: &CompletionOptions,
) -> Vec<Suggestion> {
let mut output = vec![];
let builtins = ["$nu", "$in", "$config", "$env", "$nothing"];
let builtins = ["$nu", "$in", "$env", "$nothing"];
let var_str = std::str::from_utf8(&self.var_context.0)
.unwrap_or("")
.to_lowercase();

View File

@ -1,7 +1,13 @@
use crate::util::{eval_source, report_error};
#[cfg(feature = "plugin")]
use log::info;
use nu_protocol::engine::{EngineState, Stack, StateDelta, StateWorkingSet};
#[cfg(feature = "plugin")]
use nu_parser::ParseError;
#[cfg(feature = "plugin")]
use nu_path::canonicalize_with;
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
#[cfg(feature = "plugin")]
use nu_protocol::Spanned;
use nu_protocol::{HistoryFileFormat, PipelineData, Span};
use std::path::PathBuf;
@ -15,12 +21,13 @@ const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
pub fn read_plugin_file(
engine_state: &mut EngineState,
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
add_plugin_file(engine_state, storage_path);
add_plugin_file(engine_state, plugin_file, storage_path);
let plugin_path = engine_state.plugin_signatures.clone();
if let Some(plugin_path) = plugin_path {
@ -43,8 +50,23 @@ pub fn read_plugin_file(
}
#[cfg(feature = "plugin")]
pub fn add_plugin_file(engine_state: &mut EngineState, storage_path: &str) {
if let Some(mut plugin_path) = nu_path::config_dir() {
pub fn add_plugin_file(
engine_state: &mut EngineState,
plugin_file: Option<Spanned<String>>,
storage_path: &str,
) {
if let Some(plugin_file) = plugin_file {
let working_set = StateWorkingSet::new(engine_state);
let cwd = working_set.get_cwd();
match canonicalize_with(&plugin_file.item, cwd) {
Ok(path) => engine_state.plugin_signatures = Some(path),
Err(_) => {
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
report_error(&working_set, &e);
}
}
} else if let Some(mut plugin_path) = nu_path::config_dir() {
// Path to store plugins signatures
plugin_path.push(storage_path);
plugin_path.push(PLUGIN_FILE);
@ -69,12 +91,10 @@ pub fn eval_config_contents(
PipelineData::new(Span::new(0, 0)),
);
// Merge the delta in case env vars changed in the config
// Merge the environment in case env vars changed in the config
match nu_engine::env::current_dir(engine_state, stack) {
Ok(cwd) => {
if let Err(e) =
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
{
if let Err(e) = engine_state.merge_env(stack, cwd) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
}

View File

@ -66,7 +66,7 @@ pub fn print_table_or_error(
stack: &mut Stack,
mut pipeline_data: PipelineData,
config: &mut Config,
) {
) -> Option<i64> {
let exit_code = match &mut pipeline_data {
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
_ => None,
@ -77,59 +77,63 @@ pub fn print_table_or_error(
match engine_state.find_decl("table".as_bytes(), &[]) {
Some(decl_id) => {
let table = engine_state.get_decl(decl_id).run(
engine_state,
stack,
&Call::new(Span::new(0, 0)),
pipeline_data,
);
let command = engine_state.get_decl(decl_id);
if command.get_block_id().is_some() {
print_or_exit(pipeline_data, engine_state, config);
} else {
let table = command.run(
engine_state,
stack,
&Call::new(Span::new(0, 0)),
pipeline_data,
);
match table {
Ok(table) => {
for item in table {
if let Value::Error { error } = item {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &error);
std::process::exit(1);
}
let mut out = item.into_string("\n", config);
out.push('\n');
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
match table {
Ok(table) => {
print_or_exit(table, engine_state, config);
}
}
Err(error) => {
let working_set = StateWorkingSet::new(engine_state);
Err(error) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &error);
report_error(&working_set, &error);
std::process::exit(1);
std::process::exit(1);
}
}
}
}
None => {
for item in pipeline_data {
if let Value::Error { error } = item {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &error);
std::process::exit(1);
}
let mut out = item.into_string("\n", config);
out.push('\n');
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
}
print_or_exit(pipeline_data, engine_state, config);
}
};
// Make sure everything has finished
if let Some(exit_code) = exit_code {
let _: Vec<_> = exit_code.into_iter().collect();
let mut exit_code: Vec<_> = exit_code.into_iter().collect();
exit_code
.pop()
.and_then(|last_exit_code| match last_exit_code {
Value::Int { val: code, .. } => Some(code),
_ => None,
})
} else {
None
}
}
fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, config: &Config) {
for item in pipeline_data {
if let Value::Error { error } = item {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &error);
std::process::exit(1);
}
let mut out = item.into_string("\n", config);
out.push('\n');
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
}
}

View File

@ -22,8 +22,9 @@ pub use nu_highlight::NuHighlight;
pub use print::Print;
pub use prompt::NushellPrompt;
pub use repl::evaluate_repl;
pub use repl::{eval_env_change_hook, eval_hook};
pub use syntax_highlight::NuHighlighter;
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error};
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error, report_error_new};
pub use validation::NuValidator;
#[cfg(feature = "plugin")]

View File

@ -1,8 +1,8 @@
use {
nu_ansi_term::{ansi::RESET, Style},
reedline::{
menu_functions::string_difference, Completer, LineBuffer, Menu, MenuEvent, MenuTextStyle,
Painter, Suggestion,
menu_functions::string_difference, Completer, Editor, Menu, MenuEvent, MenuTextStyle,
Painter, Suggestion, UndoBehavior,
},
};
@ -459,7 +459,7 @@ impl Menu for DescriptionMenu {
fn can_partially_complete(
&mut self,
_values_updated: bool,
_line_buffer: &mut LineBuffer,
_editor: &mut Editor,
_completer: &mut dyn Completer,
) -> bool {
false
@ -481,19 +481,21 @@ impl Menu for DescriptionMenu {
}
/// Updates menu values
fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &mut dyn Completer) {
fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) {
if self.only_buffer_difference {
if let Some(old_string) = &self.input {
let (start, input) = string_difference(line_buffer.get_buffer(), old_string);
let (start, input) = string_difference(editor.get_buffer(), old_string);
if !input.is_empty() {
self.reset_position();
self.values = completer.complete(input, start);
}
}
} else {
let trimmed_buffer = line_buffer.get_buffer().replace('\n', " ");
self.values =
completer.complete(trimmed_buffer.as_str(), line_buffer.insertion_point());
let trimmed_buffer = editor.get_buffer().replace('\n', " ");
self.values = completer.complete(
trimmed_buffer.as_str(),
editor.line_buffer().insertion_point(),
);
self.reset_position();
}
}
@ -502,7 +504,7 @@ impl Menu for DescriptionMenu {
/// collected from the completer
fn update_working_details(
&mut self,
line_buffer: &mut LineBuffer,
editor: &mut Editor,
completer: &mut dyn Completer,
painter: &Painter,
) {
@ -560,13 +562,13 @@ impl Menu for DescriptionMenu {
match event {
MenuEvent::Activate(_) => {
self.reset_position();
self.input = Some(line_buffer.get_buffer().to_string());
self.update_values(line_buffer, completer);
self.input = Some(editor.get_buffer().to_string());
self.update_values(editor, completer);
}
MenuEvent::Deactivate => self.active = false,
MenuEvent::Edit(_) => {
self.reset_position();
self.update_values(line_buffer, completer);
self.update_values(editor, completer);
self.update_examples()
}
MenuEvent::NextElement => {
@ -627,27 +629,28 @@ impl Menu for DescriptionMenu {
}
/// The buffer gets replaced in the Span location
fn replace_in_buffer(&self, line_buffer: &mut LineBuffer) {
fn replace_in_buffer(&self, editor: &mut Editor) {
if let Some(Suggestion { value, span, .. }) = self.get_value() {
let start = span.start.min(line_buffer.len());
let end = span.end.min(line_buffer.len());
let start = span.start.min(editor.line_buffer().len());
let end = span.end.min(editor.line_buffer().len());
let string_len = if let Some(example_index) = self.example_index {
let example = self
.examples
let replacement = if let Some(example_index) = self.example_index {
self.examples
.get(example_index)
.expect("the example index is always checked");
line_buffer.replace(start..end, example);
example.len()
.expect("the example index is always checked")
} else {
line_buffer.replace(start..end, &value);
value.len()
&value
};
let mut offset = line_buffer.insertion_point();
offset += string_len.saturating_sub(end.saturating_sub(start));
line_buffer.set_insertion_point(offset);
editor.edit_buffer(
|lb| {
lb.replace_range(start..end, replacement);
let mut offset = lb.insertion_point();
offset += lb.len().saturating_sub(end.saturating_sub(start));
lb.set_insertion_point(offset);
},
UndoBehavior::CreateUndoPoint,
);
}
}

View File

@ -21,11 +21,19 @@ impl Command for Print {
"print without inserting a newline for the line ending",
Some('n'),
)
.switch("stderr", "print to stderr instead of stdout", Some('e'))
.category(Category::Strings)
}
fn usage(&self) -> &str {
"Prints the values given"
"Print the given values to stdout"
}
fn extra_usage(&self) -> &str {
r#"Unlike `echo`, this command does not return any value (`print | describe` will return "nothing").
Since this command has no output, there is no point in piping it with other commands.
`print` may be used inside blocks of code (e.g.: hooks) to display text during execution without interfering with the pipeline."#
}
fn search_terms(&self) -> Vec<&str> {
@ -41,11 +49,12 @@ impl Command for Print {
) -> Result<PipelineData, ShellError> {
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
let no_newline = call.has_flag("no-newline");
let to_stderr = call.has_flag("stderr");
let head = call.head;
for arg in args {
arg.into_pipeline_data()
.print(engine_state, stack, no_newline)?;
.print(engine_state, stack, no_newline, to_stderr)?;
}
Ok(PipelineData::new(head))

View File

@ -1,5 +1,6 @@
#[cfg(windows)]
use nu_utils::enable_vt_processing;
use reedline::DefaultPrompt;
use {
reedline::{
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
@ -86,6 +87,11 @@ impl NushellPrompt {
impl Prompt for NushellPrompt {
fn render_prompt_left(&self) -> Cow<str> {
#[cfg(windows)]
{
let _ = enable_vt_processing();
}
if let Some(prompt_string) = &self.left_prompt_string {
prompt_string.replace('\n', "\r\n").into()
} else {

View File

@ -15,6 +15,10 @@ pub(crate) const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR";
pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT";
pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL";
pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR";
// According to Daniel Imms @Tyriar, we need to do these this way:
// <133 A><prompt><133 B><command><133 C><command output>
const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
fn get_prompt_string(
prompt: &str,
@ -98,6 +102,20 @@ pub(crate) fn update_prompt<'prompt>(
is_perf_true,
);
// Now that we have the prompt string lets ansify it.
// <133 A><prompt><133 B><command><133 C><command output>
let left_prompt_string = if config.shell_integration {
match left_prompt_string {
Some(prompt_string) => Some(format!(
"{}{}{}",
PRE_PROMPT_MARKER, prompt_string, POST_PROMPT_MARKER
)),
None => left_prompt_string,
}
} else {
left_prompt_string
};
let right_prompt_string = get_prompt_string(
PROMPT_COMMAND_RIGHT,
config,

View File

@ -666,6 +666,7 @@ fn add_parsed_keybinding(
KeyCode::Char(char)
}
"space" => KeyCode::Char(' '),
"down" => KeyCode::Down,
"up" => KeyCode::Up,
"left" => KeyCode::Left,
@ -814,7 +815,6 @@ fn event_from_record(
) -> Result<ReedlineEvent, ShellError> {
let event = match name {
"none" => ReedlineEvent::None,
"actionhandler" => ReedlineEvent::ActionHandler,
"clearscreen" => ReedlineEvent::ClearScreen,
"clearscrollback" => ReedlineEvent::ClearScrollback,
"historyhintcomplete" => ReedlineEvent::HistoryHintComplete,
@ -875,7 +875,16 @@ fn edit_from_record(
"moveleft" => EditCommand::MoveLeft,
"moveright" => EditCommand::MoveRight,
"movewordleft" => EditCommand::MoveWordLeft,
"movebigwordleft" => EditCommand::MoveBigWordLeft,
"movewordright" => EditCommand::MoveWordRight,
"movewordrightend" => EditCommand::MoveWordRightEnd,
"movebigwordrightend" => EditCommand::MoveBigWordRightEnd,
"movewordrightstart" => EditCommand::MoveWordRightStart,
"movebigwordrightstart" => EditCommand::MoveBigWordRightStart,
"movetoposition" => {
let value = extract_value("value", cols, vals, span)?;
EditCommand::MoveToPosition(value.as_integer()? as usize)
}
"insertchar" => {
let value = extract_value("value", cols, vals, span)?;
let char = extract_char(value, config)?;
@ -888,6 +897,7 @@ fn edit_from_record(
"insertnewline" => EditCommand::InsertNewline,
"backspace" => EditCommand::Backspace,
"delete" => EditCommand::Delete,
"cutchar" => EditCommand::CutChar,
"backspaceword" => EditCommand::BackspaceWord,
"deleteword" => EditCommand::DeleteWord,
"clear" => EditCommand::Clear,
@ -898,7 +908,11 @@ fn edit_from_record(
"cuttoend" => EditCommand::CutToEnd,
"cuttolineend" => EditCommand::CutToLineEnd,
"cutwordleft" => EditCommand::CutWordLeft,
"cutbigwordleft" => EditCommand::CutBigWordLeft,
"cutwordright" => EditCommand::CutWordRight,
"cutbigwordright" => EditCommand::CutBigWordRight,
"cutwordrighttonext" => EditCommand::CutWordRightToNext,
"cutbigwordrighttonext" => EditCommand::CutBigWordRightToNext,
"pastecutbufferbefore" => EditCommand::PasteCutBufferBefore,
"pastecutbufferafter" => EditCommand::PasteCutBufferAfter,
"uppercaseword" => EditCommand::UppercaseWord,

View File

@ -2,26 +2,39 @@ use crate::{
completions::NuCompleter,
prompt_update,
reedline_config::{add_menus, create_keybindings, KeybindingsMode},
util::{eval_source, report_error},
util::{eval_source, get_guaranteed_cwd, report_error, report_error_new},
NuHighlighter, NuValidator, NushellPrompt,
};
use log::{info, trace};
use fancy_regex::Regex;
use lazy_static::lazy_static;
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;
use nu_parser::{lex, parse};
use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet},
BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span, Value,
ast::PathMember,
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 reedline::{DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
use std::{
io::{self, Write},
sync::atomic::Ordering,
time::Instant,
};
use strip_ansi_escapes::strip;
use sysinfo::SystemExt;
const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
// According to Daniel Imms @Tyriar, we need to do these this way:
// <133 A><prompt><133 B><command><133 C><command output>
// These first two have been moved to prompt_update to get as close as possible to the prompt.
// const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
// const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
const PRE_EXECUTE_MARKER: &str = "\x1b]133;C\x1b\\";
const CMD_FINISHED_MARKER: &str = "\x1b]133;D\x1b\\";
// This one is in get_command_finished_marker() now so we can capture the exit codes properly.
// const CMD_FINISHED_MARKER: &str = "\x1b]133;D;{}\x1b\\";
const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
pub fn evaluate_repl(
@ -29,9 +42,20 @@ pub fn evaluate_repl(
stack: &mut Stack,
nushell_path: &str,
is_perf_true: bool,
prerun_command: Option<Spanned<String>>,
) -> Result<()> {
use reedline::{FileBackedHistory, Reedline, Signal};
// Guard against invocation without a connected terminal.
// reedline / crossterm event polling will fail without a connected tty
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!",
))
.into_diagnostic();
}
let mut entry_num = 0;
let mut nu_prompt = NushellPrompt::new();
@ -77,14 +101,21 @@ pub fn evaluate_repl(
);
}
// Get the config once for the history `max_history_size`
// Updating that will not be possible in one session
let mut config = engine_state.get_config();
if is_perf_true {
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;
let config = engine_state.get_config();
let history_path = crate::config_files::get_history_path(
nushell_path,
engine_state.config.history_file_format,
@ -111,6 +142,36 @@ pub fn evaluate_repl(
let sys = sysinfo::System::new();
let show_banner = config.show_banner;
let use_ansi = config.use_ansi_coloring;
if show_banner {
let banner = get_banner(engine_state, stack);
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);
}
}
if let Some(s) = prerun_command {
eval_source(
engine_state,
stack,
s.item.as_bytes(),
&format!("entry #{}", entry_num),
PipelineData::new(Span::new(0, 0)),
);
engine_state.merge_env(stack, get_guaranteed_cwd(engine_state, stack))?;
}
loop {
if is_perf_true {
info!(
@ -121,6 +182,14 @@ pub fn evaluate_repl(
);
}
let cwd = get_guaranteed_cwd(engine_state, stack);
// Before doing anything, merge the environment from the previous REPL iteration into the
// permanent state.
if let Err(err) = engine_state.merge_env(stack, cwd) {
report_error_new(engine_state, &err);
}
//Reset the ctrl-c handler
if let Some(ctrlc) = &mut engine_state.ctrlc {
ctrlc.store(false, Ordering::SeqCst);
@ -130,7 +199,7 @@ pub fn evaluate_repl(
sig_quit.store(false, Ordering::SeqCst);
}
config = engine_state.get_config();
let config = engine_state.get_config();
if is_perf_true {
info!("setup colors {}:{}:{}", file!(), line!(), column!());
@ -147,7 +216,6 @@ pub fn evaluate_repl(
engine_state: engine_state.clone(),
config: config.clone(),
}))
.with_animation(config.animate_prompt)
.with_validator(Box::new(NuValidator {
engine_state: engine_state.clone(),
}))
@ -201,7 +269,10 @@ pub fn evaluate_repl(
if is_perf_true {
info!("sync history {}:{}:{}", file!(), line!(), column!());
}
line_editor.sync_history().into_diagnostic()?;
if let Err(e) = line_editor.sync_history() {
warn!("Failed to sync history: {}", e);
}
}
if is_perf_true {
@ -236,64 +307,22 @@ pub fn evaluate_repl(
// 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 {
if let Err(err) = run_hook(engine_state, stack, vec![], hook) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
if let Some(hook) = config.hooks.pre_prompt.clone() {
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
report_error_new(engine_state, &err);
}
}
// Next, check all the environment variables they ask for
// fire the "env_change" hook
if let Some(hook) = config.hooks.env_change.clone() {
match hook {
Value::Record {
cols, vals: blocks, ..
} => {
for (idx, env_var) in cols.iter().enumerate() {
let before = engine_state
.previous_env_vars
.get(env_var)
.cloned()
.unwrap_or_default();
let after = stack.get_env_var(engine_state, env_var).unwrap_or_default();
if before != after {
if let Err(err) = run_hook(
engine_state,
stack,
vec![before, after.clone()],
&blocks[idx],
) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
}
engine_state
.previous_env_vars
.insert(env_var.to_string(), after);
}
}
}
x => {
let working_set = StateWorkingSet::new(engine_state);
report_error(
&working_set,
&ShellError::TypeMismatch(
"record for 'env_change' hook".to_string(),
x.span().unwrap_or_else(|_| Span::new(0, 0)),
),
)
}
}
}
config = engine_state.get_config();
let shell_integration = config.shell_integration;
if shell_integration {
run_ansi_sequence(PRE_PROMPT_MARKER)?;
let config = engine_state.get_config();
if let Err(error) =
eval_env_change_hook(config.hooks.env_change.clone(), engine_state, stack)
{
report_error_new(engine_state, &error)
}
let config = engine_state.get_config();
let prompt =
prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt, is_perf_true);
@ -309,16 +338,19 @@ pub fn evaluate_repl(
}
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() {
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
{
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
@ -326,34 +358,22 @@ 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 {
if let Err(err) = run_hook(engine_state, stack, vec![], hook) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
if let Some(hook) = config.hooks.pre_execution.clone() {
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
report_error_new(engine_state, &err);
}
}
if shell_integration {
run_ansi_sequence(RESET_APPLICATION_MODE)?;
run_ansi_sequence(PRE_EXECUTE_MARKER)?;
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
let path = cwd.as_string()?;
// Try to abbreviate string for windows title
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
path.replace(&p.as_path().display().to_string(), "~")
} else {
path
};
// Set window title too
// https://tldp.org/HOWTO/Xterm-Title-3.html
// ESC]0;stringBEL -- Set icon name and window title to string
// ESC]1;stringBEL -- Set icon name to string
// ESC]2;stringBEL -- Set window title to string
run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
}
}
let start_time = Instant::now();
@ -364,13 +384,7 @@ pub fn evaluate_repl(
let orig = s.clone();
if (orig.starts_with('.')
|| orig.starts_with('~')
|| orig.starts_with('/')
|| orig.starts_with('\\'))
&& path.is_dir()
&& tokens.0.len() == 1
{
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
// We have an auto-cd
let (path, span) = {
if !path.exists() {
@ -386,6 +400,14 @@ pub fn evaluate_repl(
(path.to_string_lossy().to_string(), tokens.0[0].span)
};
stack.add_env_var(
"OLDPWD".into(),
Value::String {
val: cwd.clone(),
span: Span { start: 0, end: 0 },
},
);
//FIXME: this only changes the current scope, but instead this environment variable
//should probably be a block that loads the information from the state in the overlay
stack.add_env_var(
@ -413,9 +435,23 @@ pub fn evaluate_repl(
0
};
let last_shell = stack.get_env_var(engine_state, "NUSHELL_LAST_SHELL");
let last_shell = if let Some(v) = last_shell {
v.as_integer().unwrap_or_default() as usize
} else {
0
};
shells[current_shell] = Value::String { val: path, span };
stack.add_env_var("NUSHELL_SHELLS".into(), Value::List { vals: shells, span });
stack.add_env_var(
"NUSHELL_LAST_SHELL".into(),
Value::Int {
val: last_shell as i64,
span,
},
);
} else {
trace!("eval source: {}", s);
@ -437,15 +473,8 @@ pub fn evaluate_repl(
},
);
// FIXME: permanent state changes like this hopefully in time can be removed
// and be replaced by just passing the cwd in where needed
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
let path = cwd.as_string()?;
let _ = std::env::set_current_dir(path);
engine_state.add_env_var("PWD".into(), cwd);
}
if history_supports_meta && !s.is_empty() {
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
{
line_editor
.update_last_command_context(&|mut c| {
c.duration = Some(cmd_duration);
@ -458,20 +487,69 @@ pub fn evaluate_repl(
}
if shell_integration {
// FIXME: use variant with exit code, if apropriate
run_ansi_sequence(CMD_FINISHED_MARKER)?;
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(), "~")
} else {
path
};
// Set window title too
// https://tldp.org/HOWTO/Xterm-Title-3.html
// ESC]0;stringBEL -- Set icon name and window title to string
// ESC]1;stringBEL -- Set icon name to string
// ESC]2;stringBEL -- Set window title to string
run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
}
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
if shell_integration {
run_ansi_sequence(CMD_FINISHED_MARKER)?;
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
}
}
Ok(Signal::CtrlD) => {
// When exiting clear to a new line
if shell_integration {
run_ansi_sequence(CMD_FINISHED_MARKER)?;
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
}
println!();
break;
@ -480,9 +558,13 @@ pub fn evaluate_repl(
let message = err.to_string();
if !message.contains("duration") {
println!("Error: {:?}", err);
// TODO: Identify possible error cases where a hard failure is preferable
// Ignoring and reporting could hide bigger problems
// e.g. https://github.com/nushell/nushell/issues/6452
// Alternatively only allow that expected failures let the REPL loop
}
if shell_integration {
run_ansi_sequence(CMD_FINISHED_MARKER)?;
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
}
}
}
@ -491,6 +573,390 @@ pub fn evaluate_repl(
Ok(())
}
fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String {
let age = match eval_string_with_input(
engine_state,
stack,
None,
"(date now) - ('05/10/2019' | into datetime)",
) {
Ok(Value::Duration { val, .. }) => format_duration(val),
_ => "".to_string(),
};
let banner = format!(
r#"{} __ ,
{} .--()°'.' {}Welcome to {}Nushell{},
{}'|, . ,' {}based on the {}nu{} language,
{} !_-(_\ {}where all data is structured!
Please join our {}Discord{} community at {}https://discord.gg/NtAbbGn{}
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:
{}
{}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 {{
show_banner: false
...
}}{}
"#,
"\x1b[32m", //start line 1 green
"\x1b[32m", //start line 2
"\x1b[0m", //before welcome
"\x1b[32m", //before nushell
"\x1b[0m", //after nushell
"\x1b[32m", //start line 3
"\x1b[0m", //before based
"\x1b[32m", //before nu
"\x1b[0m", //after nu
"\x1b[32m", //start line 4
"\x1b[0m", //before where
"\x1b[35m", //before Discord purple
"\x1b[0m", //after Discord
"\x1b[35m", //before Discord URL
"\x1b[0m", //after Discord URL
"\x1b[1;32m", //before GitHub green_bold
"\x1b[0m", //after GitHub
"\x1b[1;32m", //before GitHub URL
"\x1b[0m", //after GitHub URL
"\x1b[32m", //before Documentation
"\x1b[0m", //after Documentation
"\x1b[32m", //before Documentation URL
"\x1b[0m", //after Documentation URL
"\x1b[36m", //before Tweet blue
"\x1b[0m", //after Tweet
"\x1b[1;36m", //before @nu_shell cyan_bold
"\x1b[0m", //after @nu_shell
"\x1b[32m", //before Nushell
"\x1b[0m", //after Nushell
age,
"\x1b[2;37m", //before banner disable dim white
"\x1b[2;36m", //before config nu dim cyan
"\x1b[0m", //after config nu
"\x1b[2;37m", //after config nu dim white
"\x1b[0m", //after banner disable
);
banner
}
// Taken from Nana's simple_eval
/// Evaluate a block of Nu code, optionally with input.
/// For example, source="$in * 2" will multiply the value in input by 2.
pub fn eval_string_with_input(
engine_state: &mut EngineState,
stack: &mut Stack,
input: Option<Value>,
source: &str,
) -> Result<Value, ShellError> {
let (block, delta) = {
let mut working_set = StateWorkingSet::new(engine_state);
let (output, _) = parse(&mut working_set, None, source.as_bytes(), false, &[]);
(output, working_set.render())
};
engine_state.merge_delta(delta)?;
let input_as_pipeline_data = match input {
Some(input) => PipelineData::Value(input, None),
None => PipelineData::new(Span::test_data()),
};
eval_block(
engine_state,
stack,
&block,
input_as_pipeline_data,
false,
true,
)
.map(|x| x.into_value(Span::test_data()))
}
pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String {
let exit_code = stack
.get_env_var(engine_state, "LAST_EXIT_CODE")
.and_then(|e| e.as_i64().ok());
format!("\x1b]133;D;{}\x1b\\", exit_code.unwrap_or(0))
}
pub fn eval_env_change_hook(
env_change_hook: Option<Value>,
engine_state: &mut EngineState,
stack: &mut Stack,
) -> Result<(), ShellError> {
if let Some(hook) = env_change_hook {
match hook {
Value::Record {
cols: env_names,
vals: hook_values,
..
} => {
for (env_name, hook_value) in env_names.iter().zip(hook_values.iter()) {
let before = engine_state
.previous_env_vars
.get(env_name)
.cloned()
.unwrap_or_default();
let after = stack
.get_env_var(engine_state, env_name)
.unwrap_or_default();
if before != after {
eval_hook(
engine_state,
stack,
vec![("$before".into(), before), ("$after".into(), after.clone())],
hook_value,
)?;
engine_state
.previous_env_vars
.insert(env_name.to_string(), after);
}
}
}
x => {
return Err(ShellError::TypeMismatch(
"record for the 'env_change' hook".to_string(),
x.span()?,
));
}
}
}
Ok(())
}
pub fn eval_hook(
engine_state: &mut EngineState,
stack: &mut Stack,
arguments: Vec<(String, Value)>,
value: &Value,
) -> Result<(), ShellError> {
let value_span = value.span()?;
let condition_path = PathMember::String {
val: "condition".to_string(),
span: value_span,
};
let code_path = PathMember::String {
val: "code".to_string(),
span: value_span,
};
match value {
Value::List { vals, .. } => {
for val in vals {
eval_hook(engine_state, stack, arguments.clone(), val)?
}
}
Value::Record { .. } => {
let do_run_hook =
if let Ok(condition) = value.clone().follow_cell_path(&[condition_path], false) {
match condition {
Value::Block {
val: block_id,
span: block_span,
..
} => {
match run_hook_block(
engine_state,
stack,
block_id,
arguments.clone(),
block_span,
) {
Ok(value) => match value {
Value::Bool { val, .. } => val,
other => {
return Err(ShellError::UnsupportedConfigValue(
"boolean output".to_string(),
format!("{}", other.get_type()),
other.span()?,
));
}
},
Err(err) => {
return Err(err);
}
}
}
other => {
return Err(ShellError::UnsupportedConfigValue(
"block".to_string(),
format!("{}", other.get_type()),
other.span()?,
));
}
}
} else {
// always run the hook
true
};
if do_run_hook {
match value.clone().follow_cell_path(&[code_path], false)? {
Value::String {
val,
span: source_span,
} => {
let (block, delta, vars) = {
let mut working_set = StateWorkingSet::new(engine_state);
let mut vars: Vec<(VarId, Value)> = vec![];
for (name, val) in arguments {
let var_id = working_set.add_variable(
name.as_bytes().to_vec(),
val.span()?,
Type::Any,
);
vars.push((var_id, val));
}
let (output, err) =
parse(&mut working_set, Some("hook"), val.as_bytes(), false, &[]);
if let Some(err) = err {
report_error(&working_set, &err);
return Err(ShellError::UnsupportedConfigValue(
"valid source code".into(),
"source code with syntax errors".into(),
source_span,
));
}
(output, working_set.render(), vars)
};
engine_state.merge_delta(delta)?;
let input = PipelineData::new(value_span);
let var_ids: Vec<VarId> = vars
.into_iter()
.map(|(var_id, val)| {
stack.add_var(var_id, val);
var_id
})
.collect();
match eval_block(engine_state, stack, &block, input, false, false) {
Ok(_) => {}
Err(err) => {
report_error_new(engine_state, &err);
}
}
for var_id in var_ids.iter() {
stack.vars.remove(var_id);
}
}
Value::Block {
val: block_id,
span: block_span,
..
} => {
run_hook_block(engine_state, stack, block_id, arguments, block_span)?;
}
other => {
return Err(ShellError::UnsupportedConfigValue(
"block or string".to_string(),
format!("{}", other.get_type()),
other.span()?,
));
}
}
}
}
Value::Block {
val: block_id,
span: block_span,
..
} => {
run_hook_block(engine_state, stack, *block_id, arguments, *block_span)?;
}
other => {
return Err(ShellError::UnsupportedConfigValue(
"block, record, or list of records".into(),
format!("{}", other.get_type()),
other.span()?,
));
}
}
let cwd = get_guaranteed_cwd(engine_state, stack);
engine_state.merge_env(stack, cwd)?;
Ok(())
}
pub fn run_hook_block(
engine_state: &EngineState,
stack: &mut Stack,
block_id: BlockId,
arguments: Vec<(String, Value)>,
span: Span,
) -> Result<Value, ShellError> {
let block = engine_state.get_block(block_id);
let input = PipelineData::new(span);
let mut callee_stack = stack.gather_captures(&block.captures);
for (idx, PositionalArg { var_id, .. }) in
block.signature.required_positional.iter().enumerate()
{
if let Some(var_id) = var_id {
if let Some(arg) = arguments.get(idx) {
callee_stack.add_var(*var_id, arg.1.clone())
} else {
return Err(ShellError::IncompatibleParametersSingle(
"This hook block has too many parameters".into(),
span,
));
}
}
}
match eval_block(engine_state, &mut callee_stack, block, input, false, false) {
Ok(pipeline_data) => match pipeline_data.into_value(span) {
Value::Error { error } => Err(error),
val => {
// If all went fine, preserve the environment of the called block
let caller_env_vars = stack.get_env_var_names(engine_state);
// remove env vars that are present in the caller but not in the callee
// (the callee hid them)
for var in caller_env_vars.iter() {
if !callee_stack.has_env_var(engine_state, var) {
stack.remove_env_var(engine_state, var);
}
}
// add new env vars from callee to caller
for (var, value) in callee_stack.get_stack_env_vars() {
stack.add_env_var(var, value);
}
Ok(val)
}
},
Err(err) => Err(err),
}
}
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
match io::stdout().write_all(seq.as_bytes()) {
Ok(it) => it,
@ -515,62 +981,33 @@ fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
})
}
pub fn run_hook(
engine_state: &EngineState,
stack: &mut Stack,
arguments: Vec<Value>,
value: &Value,
) -> Result<(), ShellError> {
match value {
Value::List { vals, .. } => {
for val in vals {
run_hook(engine_state, stack, arguments.clone(), val)?
}
Ok(())
}
Value::Block {
val: block_id,
span,
..
} => run_hook_block(engine_state, stack, *block_id, arguments, *span),
x => match x.span() {
Ok(span) => Err(ShellError::MissingConfigValue(
"block for hook in config".into(),
span,
)),
_ => Err(ShellError::MissingConfigValue(
"block for hook in config".into(),
Span { start: 0, end: 0 },
)),
},
}
lazy_static! {
// Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo'
static ref DRIVE_PATH_REGEX: Regex =
Regex::new(r"^[a-zA-Z]:[/\\]?").expect("Internal error: regex creation");
}
pub fn run_hook_block(
engine_state: &EngineState,
stack: &mut Stack,
block_id: BlockId,
arguments: Vec<Value>,
span: Span,
) -> Result<(), ShellError> {
let block = engine_state.get_block(block_id);
let input = PipelineData::new(span);
let mut callee_stack = stack.gather_captures(&block.captures);
for (idx, PositionalArg { var_id, .. }) in
block.signature.required_positional.iter().enumerate()
// A best-effort "does this string look kinda like a path?" function to determine whether to auto-cd
fn looks_like_path(orig: &str) -> bool {
#[cfg(windows)]
{
if let Some(var_id) = var_id {
callee_stack.add_var(*var_id, arguments[idx].clone())
if DRIVE_PATH_REGEX.is_match(orig).unwrap_or(false) {
return true;
}
}
match eval_block(engine_state, &mut callee_stack, block, input, false, false) {
Ok(pipeline_data) => match pipeline_data.into_value(span) {
Value::Error { error } => Err(error),
_ => Ok(()),
},
Err(err) => Err(err),
}
orig.starts_with('.')
|| orig.starts_with('~')
|| orig.starts_with('/')
|| orig.starts_with('\\')
}
#[test]
fn looks_like_path_windows_drive_path_works() {
let on_windows = cfg!(windows);
assert_eq!(looks_like_path("C:"), on_windows);
assert_eq!(looks_like_path("D:\\"), on_windows);
assert_eq!(looks_like_path("E:/"), on_windows);
assert_eq!(looks_like_path("F:\\some_dir"), on_windows);
assert_eq!(looks_like_path("G:/some_dir"), on_windows);
}

View File

@ -224,16 +224,11 @@ pub fn eval_source(
(output, working_set.render())
};
let cwd = match nu_engine::env::current_dir(engine_state, stack) {
Ok(p) => p,
Err(e) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
get_init_cwd()
}
};
let _ = engine_state.merge_delta(delta, Some(stack), &cwd);
if let Err(err) = engine_state.merge_delta(delta) {
set_last_exit_code(stack, 1);
report_error_new(engine_state, &err);
return false;
}
match eval_block(engine_state, stack, &block, input, false, false) {
Ok(mut pipeline_data) => {
@ -247,7 +242,7 @@ pub fn eval_source(
set_last_exit_code(stack, 0);
}
if let Err(err) = pipeline_data.print(engine_state, stack, false) {
if let Err(err) = pipeline_data.print(engine_state, stack, false, false) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
@ -297,6 +292,15 @@ pub fn report_error(
}
}
pub fn report_error_new(
engine_state: &EngineState,
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, error);
}
pub fn get_init_cwd() -> PathBuf {
match std::env::current_dir() {
Ok(cwd) => cwd,
@ -310,6 +314,17 @@ pub fn get_init_cwd() -> PathBuf {
}
}
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf {
match nu_engine::env::current_dir(engine_state, stack) {
Ok(p) => p,
Err(e) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
get_init_cwd()
}
}
}
#[cfg(test)]
mod test {
use super::*;

View File

@ -0,0 +1,674 @@
pub mod support;
use nu_cli::NuCompleter;
use nu_parser::parse;
use nu_protocol::engine::StateWorkingSet;
use reedline::{Completer, Suggestion};
use rstest::{fixture, rstest};
use support::{file, folder, match_suggestions, new_engine};
#[fixture]
fn completer() -> NuCompleter {
// Create a new engine
let (dir, _, mut engine, mut stack) = new_engine();
// Add record value as example
let record = "def tst [--mod -s] {}";
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
// Instantiate a new completer
NuCompleter::new(std::sync::Arc::new(engine), stack)
}
#[fixture]
fn completer_strings() -> NuCompleter {
// Create a new engine
let (dir, _, mut engine, mut stack) = new_engine();
// Add record value as example
let record = r#"def animals [] { ["cat", "dog", "eel" ] }
def my-command [animal: string@animals] { print $animal }"#;
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
// Instantiate a new completer
NuCompleter::new(std::sync::Arc::new(engine), stack)
}
#[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_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);
match_suggestions(expected, suggestions);
}
#[rstest]
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_command_with_commandcompletion(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-command ", 9);
let expected: Vec<String> = vec!["my-command".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
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_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);
}
#[test]
fn dotnu_completions() {
// Create a new engine
let (_, _, engine, stack) = new_engine();
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test source completion
let completion_str = "source-env ".to_string();
let suggestions = completer.complete(&completion_str, completion_str.len());
assert_eq!(1, suggestions.len());
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
// Test use completion
let completion_str = "use ".to_string();
let suggestions = completer.complete(&completion_str, completion_str.len());
assert_eq!(1, suggestions.len());
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
}
#[test]
#[ignore]
fn external_completer_trailing_space() {
// https://github.com/nushell/nushell/issues/6378
let block = "let external_completer = {|spans| $spans}";
let input = "gh alias ".to_string();
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);
assert_eq!("", suggestions.get(2).unwrap().value);
}
#[test]
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);
assert_eq!(2, suggestions.len());
assert_eq!("gh", suggestions.get(0).unwrap().value);
assert_eq!("alias", suggestions.get(1).unwrap().value);
}
#[test]
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);
assert_eq!(3, suggestions.len());
assert_eq!("gh", suggestions.get(0).unwrap().value);
assert_eq!("api", suggestions.get(1).unwrap().value);
assert_eq!("--", suggestions.get(2).unwrap().value);
}
#[test]
fn file_completions() {
// Create a new engine
let (dir, dir_str, engine, stack) = new_engine();
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the current folder
let target_dir = format!("cp {}", dir_str);
let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values
let expected_paths: Vec<String> = vec![
file(dir.join("nushell")),
folder(dir.join("test_a")),
folder(dir.join("test_b")),
folder(dir.join("another")),
file(dir.join("custom_completion.nu")),
file(dir.join(".hidden_file")),
folder(dir.join(".hidden_folder")),
];
// Match the results
match_suggestions(expected_paths, suggestions);
// Test completions for a file
let target_dir = format!("cp {}", folder(dir.join("another")));
let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values
let expected_paths: Vec<String> = vec![file(dir.join("another").join("newfile"))];
// Match the results
match_suggestions(expected_paths, suggestions);
}
#[test]
fn command_ls_with_filecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "ls ";
let suggestions = completer.complete(target_dir, target_dir.len());
#[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)
}
#[test]
fn command_open_with_filecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "open ";
let suggestions = completer.complete(target_dir, target_dir.len());
#[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)
}
#[test]
fn command_rm_with_globcompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "rm ";
let suggestions = completer.complete(target_dir, target_dir.len());
#[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)
}
#[test]
fn command_cp_with_globcompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "cp ";
let suggestions = completer.complete(target_dir, target_dir.len());
#[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)
}
#[test]
fn command_save_with_filecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "save ";
let suggestions = completer.complete(target_dir, target_dir.len());
#[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)
}
#[test]
fn command_touch_with_filecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "touch ";
let suggestions = completer.complete(target_dir, target_dir.len());
#[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)
}
#[test]
fn command_watch_with_filecompletion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "watch ";
let suggestions = completer.complete(target_dir, target_dir.len());
#[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)
}
#[test]
fn flag_completions() {
// Create a new engine
let (_, _, engine, stack) = new_engine();
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the 'ls' flags
let suggestions = completer.complete("ls -", 4);
assert_eq!(14, suggestions.len());
let expected: Vec<String> = vec![
"--all".into(),
"--directory".into(),
"--du".into(),
"--full-paths".into(),
"--help".into(),
"--long".into(),
"--short-names".into(),
"-D".into(),
"-a".into(),
"-d".into(),
"-f".into(),
"-h".into(),
"-l".into(),
"-s".into(),
];
// Match results
match_suggestions(expected, suggestions);
}
#[test]
fn folder_with_directorycompletions() {
// Create a new engine
let (dir, dir_str, engine, stack) = new_engine();
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the current folder
let target_dir = format!("cd {}", dir_str);
let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values
let expected_paths: Vec<String> = vec![
folder(dir.join("test_a")),
folder(dir.join("test_b")),
folder(dir.join("another")),
folder(dir.join(".hidden_folder")),
];
// Match the results
match_suggestions(expected_paths, suggestions);
}
#[test]
fn variables_completions() {
// Create a new engine
let (dir, _, mut engine, mut stack) = new_engine();
// Add record value as example
let record = "let actor = { name: 'Tom Hardy', age: 44 }";
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for $nu
let suggestions = completer.complete("$nu.", 4);
assert_eq!(9, suggestions.len());
let expected: Vec<String> = vec![
"config-path".into(),
"env-path".into(),
"history-path".into(),
"home-path".into(),
"loginshell-path".into(),
"os-info".into(),
"pid".into(),
"scope".into(),
"temp-path".into(),
];
// Match results
match_suggestions(expected, suggestions);
// Test completions for $nu.h (filter)
let suggestions = completer.complete("$nu.h", 5);
assert_eq!(2, suggestions.len());
let expected: Vec<String> = vec!["history-path".into(), "home-path".into()];
// Match results
match_suggestions(expected, suggestions);
// Test completions for custom var
let suggestions = completer.complete("$actor.", 7);
assert_eq!(2, suggestions.len());
let expected: Vec<String> = vec!["age".into(), "name".into()];
// Match results
match_suggestions(expected, suggestions);
// Test completions for custom var (filtering)
let suggestions = completer.complete("$actor.n", 8);
assert_eq!(1, suggestions.len());
let expected: Vec<String> = vec!["name".into()];
// Match results
match_suggestions(expected, suggestions);
// Test completions for $env
let suggestions = completer.complete("$env.", 5);
assert_eq!(2, suggestions.len());
let expected: Vec<String> = vec!["PWD".into(), "TEST".into()];
// Match results
match_suggestions(expected, suggestions);
// Test completions for $env
let suggestions = completer.complete("$env.T", 6);
assert_eq!(1, suggestions.len());
let expected: Vec<String> = vec!["TEST".into()];
// Match results
match_suggestions(expected, suggestions);
}
#[test]
fn alias_of_command_and_flags() {
let (dir, _, mut engine, mut stack) = new_engine();
// Create an alias
let alias = r#"alias ll = ls -l"#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let suggestions = completer.complete("ll t", 4);
#[cfg(windows)]
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
match_suggestions(expected_paths, suggestions)
}
#[test]
fn alias_of_basic_command() {
let (dir, _, mut engine, mut stack) = new_engine();
// Create an alias
let alias = r#"alias ll = ls "#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let suggestions = completer.complete("ll t", 4);
#[cfg(windows)]
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
match_suggestions(expected_paths, suggestions)
}
#[test]
fn alias_of_another_alias() {
let (dir, _, mut engine, mut stack) = new_engine();
// Create an alias
let alias = r#"alias ll = ls -la"#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir.clone()).is_ok());
// Create the second alias
let alias = r#"alias lf = ll -f"#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let suggestions = completer.complete("lf t", 4);
#[cfg(windows)]
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
match_suggestions(expected_paths, suggestions)
}
fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
// Create a new engine
let (dir, _, mut engine_state, mut stack) = new_engine();
let (_, delta) = {
let mut working_set = StateWorkingSet::new(&engine_state);
let (block, err) = parse(&mut working_set, None, block.as_bytes(), false, &[]);
assert!(err.is_none());
(block, working_set.render())
};
assert!(engine_state.merge_delta(delta).is_ok());
// Merge environment into the permanent state
assert!(engine_state.merge_env(&mut stack, &dir).is_ok());
let latest_block_id = engine_state.num_blocks() - 1;
// Change config adding the external completer
let mut config = engine_state.get_config().clone();
config.external_completer = Some(latest_block_id);
engine_state.set_config(&config);
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);
completer.complete(input, input.len())
}
#[test]
fn unknown_command_completion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "thiscommanddoesnotexist ";
let suggestions = completer.complete(target_dir, target_dir.len());
#[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

@ -1,29 +0,0 @@
pub mod support;
use nu_cli::NuCompleter;
use reedline::Completer;
use support::{match_suggestions, new_engine};
#[test]
fn variables_completions() {
// Create a new engine
let (dir, _, mut engine, mut stack) = new_engine();
// Add record value as example
let record = r#"def animals [] { ["cat", "dog", "eel" ] }
def my-command [animal: string@animals] { print $animal }"#;
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for $nu
let suggestions = completer.complete("my-command ", 11);
assert_eq!(3, suggestions.len());
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
// Match results
match_suggestions(expected, suggestions);
}

View File

@ -1,28 +0,0 @@
pub mod support;
use nu_cli::NuCompleter;
use reedline::Completer;
use support::new_engine;
#[test]
fn dotnu_completions() {
// Create a new engine
let (_, _, engine, stack) = new_engine();
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test source completion
let completion_str = "source ".to_string();
let suggestions = completer.complete(&completion_str, completion_str.len());
assert_eq!(1, suggestions.len());
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
// Test use completion
let completion_str = "use ".to_string();
let suggestions = completer.complete(&completion_str, completion_str.len());
assert_eq!(1, suggestions.len());
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
}

View File

@ -1,42 +0,0 @@
pub mod support;
use nu_cli::NuCompleter;
use reedline::Completer;
use support::{file, folder, match_suggestions, new_engine};
#[test]
fn file_completions() {
// Create a new engine
let (dir, dir_str, engine, stack) = new_engine();
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the current folder
let target_dir = format!("cp {}", dir_str);
let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values
let expected_paths: Vec<String> = vec![
file(dir.join("nushell")),
folder(dir.join("test_a")),
folder(dir.join("test_b")),
folder(dir.join("another")),
file(dir.join("custom_completion.nu")),
file(dir.join(".hidden_file")),
folder(dir.join(".hidden_folder")),
];
// Match the results
match_suggestions(expected_paths, suggestions);
// Test completions for the completions/another folder
let target_dir = format!("cd {}", folder(dir.join("another")));
let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values
let expected_paths: Vec<String> = vec![file(dir.join("another").join("newfile"))];
// Match the results
match_suggestions(expected_paths, suggestions);
}

View File

@ -1,36 +0,0 @@
pub mod support;
use nu_cli::NuCompleter;
use reedline::Completer;
use support::{match_suggestions, new_engine};
#[test]
fn flag_completions() {
// Create a new engine
let (_, _, engine, stack) = new_engine();
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the 'ls' flags
let suggestions = completer.complete("ls -", 4);
assert_eq!(12, suggestions.len());
let expected: Vec<String> = vec![
"--all".into(),
"--du".into(),
"--full-paths".into(),
"--help".into(),
"--long".into(),
"--short-names".into(),
"-a".into(),
"-d".into(),
"-f".into(),
"-h".into(),
"-l".into(),
"-s".into(),
];
// Match results
match_suggestions(expected, suggestions);
}

View File

@ -1,29 +0,0 @@
pub mod support;
use nu_cli::NuCompleter;
use reedline::Completer;
use support::{folder, match_suggestions, new_engine};
#[test]
fn folder_completions() {
// Create a new engine
let (dir, dir_str, engine, stack) = new_engine();
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the current folder
let target_dir = format!("cd {}", dir_str);
let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values
let expected_paths: Vec<String> = vec![
folder(dir.join("test_a")),
folder(dir.join("test_b")),
folder(dir.join("another")),
folder(dir.join(".hidden_folder")),
];
// Match the results
match_suggestions(expected_paths, suggestions);
}

View File

@ -4,7 +4,7 @@ use nu_command::create_default_context;
use nu_engine::eval_block;
use nu_parser::parse;
use nu_protocol::{
engine::{EngineState, Stack, StateDelta, StateWorkingSet},
engine::{EngineState, Stack, StateWorkingSet},
PipelineData, ShellError, Span, Value,
};
use nu_test_support::fs;
@ -23,14 +23,11 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
dir_str.push(SEP);
// Create a new engine with default context
let mut engine_state = create_default_context(&dir);
let mut engine_state = create_default_context();
// New stack
let mut stack = Stack::new();
// New delta state
let delta = StateDelta::new(&engine_state);
// Add pwd as env var
stack.add_env_var(
"PWD".to_string(),
@ -53,8 +50,8 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
},
);
// Merge delta
let merge_result = engine_state.merge_delta(delta, Some(&mut stack), &dir);
// Merge environment into the permanent state
let merge_result = engine_state.merge_env(&mut stack, &dir);
assert!(merge_result.is_ok());
(dir, dir_str, engine_state, stack)
@ -62,6 +59,15 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
// match a list of suggestions with the expected values
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
let expected_len = expected.len();
let suggestions_len = suggestions.len();
if expected_len != suggestions_len {
panic!(
"\nexpected {expected_len} suggestions but got {suggestions_len}: \n\
Suggestions: {suggestions:#?} \n\
Expected: {expected:#?}\n"
)
}
expected.iter().zip(suggestions).for_each(|it| {
assert_eq!(it.0, &it.1.value);
});
@ -97,6 +103,9 @@ pub fn merge_input(
(block, working_set.render())
};
engine_state.merge_delta(delta)?;
assert!(eval_block(
engine_state,
stack,
@ -112,6 +121,6 @@ pub fn merge_input(
)
.is_ok());
// Merge delta
engine_state.merge_delta(delta, Some(stack), &dir)
// Merge environment into the permanent state
engine_state.merge_env(stack, &dir)
}

View File

@ -1,88 +0,0 @@
pub mod support;
use nu_cli::NuCompleter;
use reedline::Completer;
use support::{match_suggestions, new_engine};
#[test]
fn variables_completions() {
// Create a new engine
let (dir, _, mut engine, mut stack) = new_engine();
// Add record value as example
let record = "let actor = { name: 'Tom Hardy', age: 44 }";
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for $nu
let suggestions = completer.complete("$nu.", 4);
assert_eq!(9, suggestions.len());
let expected: Vec<String> = vec![
"config-path".into(),
"env-path".into(),
"history-path".into(),
"home-path".into(),
"loginshell-path".into(),
"os-info".into(),
"pid".into(),
"scope".into(),
"temp-path".into(),
];
// Match results
match_suggestions(expected, suggestions);
// Test completions for $nu.h (filter)
let suggestions = completer.complete("$nu.h", 5);
assert_eq!(2, suggestions.len());
let expected: Vec<String> = vec!["history-path".into(), "home-path".into()];
// Match results
match_suggestions(expected, suggestions);
// Test completions for custom var
let suggestions = completer.complete("$actor.", 7);
assert_eq!(2, suggestions.len());
let expected: Vec<String> = vec!["age".into(), "name".into()];
// Match results
match_suggestions(expected, suggestions);
// Test completions for custom var (filtering)
let suggestions = completer.complete("$actor.n", 8);
assert_eq!(1, suggestions.len());
let expected: Vec<String> = vec!["name".into()];
// Match results
match_suggestions(expected, suggestions);
// Test completions for $env
let suggestions = completer.complete("$env.", 5);
assert_eq!(2, suggestions.len());
let expected: Vec<String> = vec!["PWD".into(), "TEST".into()];
// Match results
match_suggestions(expected, suggestions);
// Test completions for $env
let suggestions = completer.complete("$env.T", 6);
assert_eq!(1, suggestions.len());
let expected: Vec<String> = vec!["TEST".into()];
// Match results
match_suggestions(expected, suggestions);
}

View File

@ -1,14 +1,15 @@
[package]
authors = ["The Nushell Project Developers"]
description = "Color configuration code used by Nushell"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-config"
edition = "2021"
license = "MIT"
name = "nu-color-config"
version = "0.64.0"
version = "0.69.0"
[dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.64.0" }
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
nu-ansi-term = "0.46.0"
nu-json = { path = "../nu-json", version = "0.64.0" }
nu-table = { path = "../nu-table", version = "0.64.0" }
nu-json = { path = "../nu-json", version = "0.69.0" }
nu-table = { path = "../nu-table", version = "0.69.0" }
serde = { version="1.0.123", features=["derive"] }

View File

@ -11,12 +11,12 @@ pub struct NuStyle {
pub fn parse_nustyle(nu_style: NuStyle) -> Style {
// get the nu_ansi_term::Color foreground color
let fg_color = match nu_style.fg {
Some(fg) => color_from_hex(&fg).expect("error with foreground color"),
Some(fg) => color_from_hex(&fg).unwrap_or_default(),
_ => None,
};
// get the nu_ansi_term::Color background color
let bg_color = match nu_style.bg {
Some(bg) => color_from_hex(&bg).expect("error with background color"),
Some(bg) => color_from_hex(&bg).unwrap_or_default(),
_ => None,
};
// get the attributes

View File

@ -1,45 +1,49 @@
[package]
authors = ["The Nushell Project Developers"]
description = "Nushell's built-in commands"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
edition = "2021"
license = "MIT"
name = "nu-command"
version = "0.64.0"
version = "0.69.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.64.0" }
nu-engine = { path = "../nu-engine", version = "0.64.0" }
nu-glob = { path = "../nu-glob", version = "0.64.0" }
nu-json = { path = "../nu-json", version = "0.64.0" }
nu-parser = { path = "../nu-parser", version = "0.64.0" }
nu-path = { path = "../nu-path", version = "0.64.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.64.0" }
nu-protocol = { path = "../nu-protocol", version = "0.64.0" }
nu-system = { path = "../nu-system", version = "0.64.0" }
nu-table = { path = "../nu-table", version = "0.64.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.64.0" }
nu-test-support = { path = "../nu-test-support", version = "0.64.0" }
nu-utils = { path = "../nu-utils", version = "0.64.0" }
nu-color-config = { path = "../nu-color-config", version = "0.69.0" }
nu-engine = { path = "../nu-engine", version = "0.69.0" }
nu-glob = { path = "../nu-glob", version = "0.69.0" }
nu-json = { path = "../nu-json", version = "0.69.0" }
nu-parser = { path = "../nu-parser", version = "0.69.0" }
nu-path = { path = "../nu-path", version = "0.69.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.69.0" }
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
nu-system = { path = "../nu-system", version = "0.69.0" }
nu-table = { path = "../nu-table", version = "0.69.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.69.0" }
nu-test-support = { path = "../nu-test-support", version = "0.69.0" }
nu-utils = { path = "../nu-utils", version = "0.69.0" }
nu-ansi-term = "0.46.0"
num-format = { version = "0.4.0" }
# Potential dependencies for extras
alphanumeric-sort = "1.4.4"
base64 = "0.13.0"
byteorder = "1.4.3"
bytesize = "1.1.0"
calamine = "0.18.0"
chrono = { version = "0.4.19", features = ["serde"] }
chrono = { version = "0.4.21", features = ["serde", "unstable-locales"] }
chrono-humanize = "0.2.1"
chrono-tz = "0.6.1"
crossterm = "0.23.0"
chrono-tz = "0.6.3"
crossterm = "0.24.0"
csv = "1.1.6"
dialoguer = "0.9.0"
digest = "0.10.0"
dtparse = "1.2.0"
eml-parser = "0.1.0"
encoding_rs = "0.8.30"
fancy-regex = "0.10.0"
filesize = "0.2.0"
filetime = "0.2.15"
fs_extra = "1.2.0"
@ -51,42 +55,43 @@ is-root = "0.1.2"
itertools = "0.10.0"
lazy_static = "1.4.0"
log = "0.4.14"
lscolors = { version = "0.9.0", features = ["crossterm"]}
lscolors = { version = "0.12.0", features = ["crossterm"]}
md5 = { package = "md-5", version = "0.10.0" }
meval = "0.2.0"
mime = "0.3.16"
notify = "4.0.17"
num = { version = "0.4.0", optional = true }
num-traits = "0.2.14"
pathdiff = "0.2.1"
powierza-coefficient = "1.0"
quick-xml = "0.22"
powierza-coefficient = "1.0.1"
quick-xml = "0.23.0"
rand = "0.8"
rayon = "1.5.1"
regex = "1.5.4"
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"
serde_yaml = "0.8.16"
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.11.0", default-features = false }
shadow-rs = { version = "0.16.1", default-features = false }
strip-ansi-escapes = "0.1.1"
sysinfo = "0.23.5"
terminal_size = "0.1.17"
thiserror = "1.0.29"
titlecase = "1.1.0"
sysinfo = "0.26.2"
terminal_size = "0.2.1"
thiserror = "1.0.31"
titlecase = "2.0.0"
toml = "0.5.8"
unicode-segmentation = "1.8.0"
url = "2.2.1"
uuid = { version = "0.8.2", features = ["v4"] }
which = { version = "4.2.2", optional = true }
reedline = { version = "0.7.0", features = ["bashisms", "sqlite"]}
wax = { version = "0.4.0", features = ["diagnostics"] }
rusqlite = { version = "0.27.0", features = ["bundled"], optional = true }
sqlparser = { version = "0.16.0", features = ["serde"], optional = true }
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"] }
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
sqlparser = { version = "0.23.0", features = ["serde"], optional = true }
[target.'cfg(unix)'.dependencies]
umask = "2.0.0"
@ -97,15 +102,34 @@ version = "2.1.3"
optional = true
[dependencies.polars]
version = "0.21.1"
# path = "../../../../polars/polars"
version = "0.23.2"
optional = true
features = [
"default", "to_dummies", "parquet", "json", "serde", "serde-lazy",
"object", "checked_arithmetic", "strings", "cum_agg", "is_in",
"rolling_window", "strings", "rows", "random",
"dtype-datetime", "dtype-struct", "lazy", "cross_join",
"dynamic_groupby"
"arg_where",
"checked_arithmetic",
"concat_str",
"cross_join",
"csv-file",
"cum_agg",
"default",
"dtype-datetime",
"dtype-struct",
"dtype-categorical",
"dynamic_groupby",
"ipc",
"is_in",
"json",
"lazy",
"object",
"parquet",
"random",
"rolling_window",
"rows",
"serde",
"serde-lazy",
"strings",
"strings",
"to_dummies",
]
[target.'cfg(windows)'.dependencies.windows]
@ -125,10 +149,12 @@ dataframe = ["polars", "num"]
database = ["sqlparser", "rusqlite"]
[build-dependencies]
shadow-rs = { version = "0.11.0", default-features = false }
shadow-rs = { version = "0.16.1", default-features = false }
[dev-dependencies]
hamcrest2 = "0.3.0"
dirs-next = "2.0.0"
proptest = "1.0.0"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
rstest = {version = "0.15.0", default-features = false}

View File

@ -3,16 +3,18 @@ use std::process::Command;
fn main() -> shadow_rs::SdResult<()> {
// Look up the current Git commit ourselves instead of relying on shadow_rs,
// because shadow_rs does it in a really slow-to-compile way (it builds libgit2)
let hash = get_git_hash().expect("failed to get latest git commit hash");
let hash = get_git_hash().unwrap_or_default();
println!("cargo:rustc-env=NU_COMMIT_HASH={}", hash);
shadow_rs::new()
}
fn get_git_hash() -> Result<String, std::io::Error> {
let out = Command::new("git").args(["rev-parse", "HEAD"]).output()?;
Ok(String::from_utf8(out.stdout)
.expect("could not convert stdout to string")
.trim()
.to_string())
fn get_git_hash() -> Option<String> {
Command::new("git")
.args(["rev-parse", "HEAD"])
.output()
.ok()
.filter(|output| output.status.success())
.and_then(|output| String::from_utf8(output.stdout).ok())
.map(|hash| hash.trim().to_string())
}

View File

@ -0,0 +1,23 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 96a80ecd19729fb43a7b7bb2766b37d6083ba73b16abb97075875e3cfcdc763f # shrinks to c = '"'
cc 4146602559ea717a02bcef3c6d73cdf613c30d0c3f92c48e26c79b9a1544e027 # shrinks to c = '\\'
cc 80532a0ee73df456a719b9e3cce1ae5f3d26009dde819cbaf16f8e0cb6709705 # shrinks to c = ':'
cc cdb88505686eea3c74c36f282fd29b2b68bc118ded4ebfc36f9838d174bd7653 # shrinks to c = '`'
cc 0f534d55f9771e8810b9c4252a4168abfaec1a35e1b0cac12dbaf726d295a08c # shrinks to c = '\0'
cc 5d31bcbab722acd1f4e23ca3a4f95ff309a636b45a73ca8ae9f820d93ff57acc # shrinks to c = '{'
cc 5afec063bc96160d681d77f90041b67ef5cfdea4dcbd12d984fd828fbeb4b421 # shrinks to c = '#'
cc f919beb3ee5c70e756a15635d65ded7d44f3ae58b5e86b6c09e814d5d8cdd506 # shrinks to c = ';'
cc ec00f39b8d45dfd8808947a56af5e50ba5a0ef7c951723b45377815a02e515b1 # shrinks to c = '('
cc 25b773cdf4c24179151fa86244c7de4136e05df9e94e6ee77a336ebfd8764444 # shrinks to c = '|'
cc 94dc0d54b97d59e1c0f4cb11bdccb3823a1bb908cbc3fd643ee8f067169fad72 # shrinks to c = '0'
cc c9d0051fb1e5a8bdc1d4f5a3dceac1b4b465827d1dff4fc3a3755baae6a7bb48 # shrinks to c = '$'
cc 14ec40d2eb5bd2663e9b11bb49fb2120852f9ea71678c69d732161412b55a3ec # shrinks to s = ""
cc d4afccc51ed9d421bdb7e1723e273dfb6e77c3a449489671a496db234e87c5ed # shrinks to c = '\r'
cc 515a56d73eb1b69290ef4c714b629421989879aebd57991bd2c2bf11294353b1 # shrinks to s = "\\\\𐊀{"
cc 111566990fffa432acd2dbc845141b0e7870f97125c7621e3ddf142204568246 # shrinks to s = "'\":`"
cc 0424c33000d9188be96b3049046eb052770b2158bf5ebb0c98656d7145e8aca9 # shrinks to s = "0"

View File

@ -0,0 +1,100 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"bits and"
}
fn signature(&self) -> Signature {
Signature::build("bits and")
.required(
"target",
SyntaxShape::Int,
"target integer to perform bit and",
)
.category(Category::Bits)
}
fn usage(&self) -> &str {
"Performs bitwise and for integers"
}
fn search_terms(&self) -> Vec<&str> {
vec!["logic and"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let target: i64 = call.req(engine_state, stack, 0)?;
input.map(
move |value| operate(value, target, head),
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Apply bits and to two numbers",
example: "2 | bits and 2",
result: Some(Value::Int {
val: 2,
span: Span::test_data(),
}),
},
Example {
description: "Apply logical and to a list of numbers",
example: "[4 3 2] | bits and 2",
result: Some(Value::List {
vals: vec![Value::test_int(0), Value::test_int(2), Value::test_int(2)],
span: Span::test_data(),
}),
},
]
}
}
fn operate(value: Value, target: i64, head: Span) -> Value {
match value {
Value::Int { val, span } => Value::Int {
val: val & target,
span,
},
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Only integer values are supported, input type: {:?}",
other.get_type()
),
other.span().unwrap_or(head),
),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,49 @@
use nu_engine::get_full_help;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, IntoPipelineData, PipelineData, Signature, Value,
};
#[derive(Clone)]
pub struct Bits;
impl Command for Bits {
fn name(&self) -> &str {
"bits"
}
fn signature(&self) -> Signature {
Signature::build("bits").category(Category::Bits)
}
fn usage(&self) -> &str {
"Various commands for working with bits"
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(Value::String {
val: get_full_help(&Bits.signature(), &Bits.examples(), engine_state, stack),
span: call.head,
}
.into_pipeline_data())
}
}
#[cfg(test)]
mod test {
use crate::Bits;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Bits {})
}
}

View File

@ -0,0 +1,99 @@
mod and;
mod bits_;
mod not;
mod or;
mod rotate_left;
mod rotate_right;
mod shift_left;
mod shift_right;
mod xor;
use nu_protocol::Spanned;
pub use and::SubCommand as BitsAnd;
pub use bits_::Bits;
pub use not::SubCommand as BitsNot;
pub use or::SubCommand as BitsOr;
pub use rotate_left::SubCommand as BitsRotateLeft;
pub use rotate_right::SubCommand as BitsRotateRight;
pub use shift_left::SubCommand as BitsShiftLeft;
pub use shift_right::SubCommand as BitsShiftRight;
pub use xor::SubCommand as BitsXor;
#[derive(Clone, Copy)]
enum NumberBytes {
One,
Two,
Four,
Eight,
Auto,
Invalid,
}
#[derive(Clone, Copy)]
enum InputNumType {
One,
Two,
Four,
Eight,
SignedOne,
SignedTwo,
SignedFour,
SignedEight,
}
fn get_number_bytes(number_bytes: &Option<Spanned<String>>) -> NumberBytes {
match number_bytes.as_ref() {
None => NumberBytes::Eight,
Some(size) => match size.item.as_str() {
"1" => NumberBytes::One,
"2" => NumberBytes::Two,
"4" => NumberBytes::Four,
"8" => NumberBytes::Eight,
"auto" => NumberBytes::Auto,
_ => NumberBytes::Invalid,
},
}
}
fn get_input_num_type(val: i64, signed: bool, number_size: NumberBytes) -> InputNumType {
if signed || val < 0 {
match number_size {
NumberBytes::One => InputNumType::SignedOne,
NumberBytes::Two => InputNumType::SignedTwo,
NumberBytes::Four => InputNumType::SignedFour,
NumberBytes::Eight => InputNumType::SignedEight,
NumberBytes::Auto => {
if val <= 0x7F && val >= -(2i64.pow(7)) {
InputNumType::SignedOne
} else if val <= 0x7FFF && val >= -(2i64.pow(15)) {
InputNumType::SignedTwo
} else if val <= 0x7FFFFFFF && val >= -(2i64.pow(31)) {
InputNumType::SignedFour
} else {
InputNumType::SignedEight
}
}
NumberBytes::Invalid => InputNumType::SignedFour,
}
} else {
match number_size {
NumberBytes::One => InputNumType::One,
NumberBytes::Two => InputNumType::Two,
NumberBytes::Four => InputNumType::Four,
NumberBytes::Eight => InputNumType::Eight,
NumberBytes::Auto => {
if val <= 0xFF {
InputNumType::One
} else if val <= 0xFFFF {
InputNumType::Two
} else if val <= 0xFFFFFFFF {
InputNumType::Four
} else {
InputNumType::Eight
}
}
NumberBytes::Invalid => InputNumType::Four,
}
}
}

View File

@ -0,0 +1,163 @@
use super::{get_number_bytes, NumberBytes};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"bits not"
}
fn signature(&self) -> Signature {
Signature::build("bits not")
.switch(
"signed",
"always treat input number as a signed number",
Some('s'),
)
.named(
"number-bytes",
SyntaxShape::String,
"the size of unsigned number in bytes, it can be 1, 2, 4, 8, auto",
Some('n'),
)
.category(Category::Bits)
}
fn usage(&self) -> &str {
"Performs logical negation on each bit"
}
fn search_terms(&self) -> Vec<&str> {
vec!["negation"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let signed = call.has_flag("signed");
let number_bytes: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "number-bytes")?;
let bytes_len = get_number_bytes(&number_bytes);
if let NumberBytes::Invalid = bytes_len {
if let Some(val) = number_bytes {
return Err(ShellError::UnsupportedInput(
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
val.span,
));
}
}
input.map(
move |value| operate(value, head, signed, bytes_len),
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Apply logical negation to a list of numbers",
example: "[4 3 2] | bits not",
result: Some(Value::List {
vals: vec![
Value::test_int(140737488355323),
Value::test_int(140737488355324),
Value::test_int(140737488355325),
],
span: Span::test_data(),
}),
},
Example {
description:
"Apply logical negation to a list of numbers, treat input as 2 bytes number",
example: "[4 3 2] | bits not -n 2",
result: Some(Value::List {
vals: vec![
Value::test_int(65531),
Value::test_int(65532),
Value::test_int(65533),
],
span: Span::test_data(),
}),
},
Example {
description:
"Apply logical negation to a list of numbers, treat input as signed number",
example: "[4 3 2] | bits not -s",
result: Some(Value::List {
vals: vec![
Value::test_int(-5),
Value::test_int(-4),
Value::test_int(-3),
],
span: Span::test_data(),
}),
},
]
}
}
fn operate(value: Value, head: Span, signed: bool, number_size: NumberBytes) -> Value {
match value {
Value::Int { val, span } => {
if signed || val < 0 {
Value::Int { val: !val, span }
} else {
use NumberBytes::*;
let out_val = match number_size {
One => !val & 0x00_00_00_00_00_FF,
Two => !val & 0x00_00_00_00_FF_FF,
Four => !val & 0x00_00_FF_FF_FF_FF,
Eight => !val & 0x7F_FF_FF_FF_FF_FF,
Auto => {
if val <= 0xFF {
!val & 0x00_00_00_00_00_FF
} else if val <= 0xFF_FF {
!val & 0x00_00_00_00_FF_FF
} else if val <= 0xFF_FF_FF_FF {
!val & 0x00_00_FF_FF_FF_FF
} else {
!val & 0x7F_FF_FF_FF_FF_FF
}
}
// This case shouldn't happen here, as it's handled before
Invalid => 0,
};
Value::Int { val: out_val, span }
}
}
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Only numerical values are supported, input type: {:?}",
other.get_type()
),
other.span().unwrap_or(head),
),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,100 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"bits or"
}
fn signature(&self) -> Signature {
Signature::build("bits or")
.required(
"target",
SyntaxShape::Int,
"target integer to perform bit or",
)
.category(Category::Bits)
}
fn usage(&self) -> &str {
"Performs bitwise or for integers"
}
fn search_terms(&self) -> Vec<&str> {
vec!["logic or"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let target: i64 = call.req(engine_state, stack, 0)?;
input.map(
move |value| operate(value, target, head),
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Apply bits or to two numbers",
example: "2 | bits or 6",
result: Some(Value::Int {
val: 6,
span: Span::test_data(),
}),
},
Example {
description: "Apply logical or to a list of numbers",
example: "[8 3 2] | bits or 2",
result: Some(Value::List {
vals: vec![Value::test_int(10), Value::test_int(3), Value::test_int(2)],
span: Span::test_data(),
}),
},
]
}
}
fn operate(value: Value, target: i64, head: Span) -> Value {
match value {
Value::Int { val, span } => Value::Int {
val: val | target,
span,
},
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Only integer values are supported, input type: {:?}",
other.get_type()
),
other.span().unwrap_or(head),
),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,156 @@
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
use num_traits::int::PrimInt;
use std::fmt::Display;
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"bits rol"
}
fn signature(&self) -> Signature {
Signature::build("bits rol")
.required("bits", SyntaxShape::Int, "number of bits to rotate left")
.switch(
"signed",
"always treat input number as a signed number",
Some('s'),
)
.named(
"number-bytes",
SyntaxShape::String,
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
Some('n'),
)
.category(Category::Bits)
}
fn usage(&self) -> &str {
"Bitwise rotate left for integers"
}
fn search_terms(&self) -> Vec<&str> {
vec!["rotate left"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let bits: usize = call.req(engine_state, stack, 0)?;
let signed = call.has_flag("signed");
let number_bytes: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "number-bytes")?;
let bytes_len = get_number_bytes(&number_bytes);
if let NumberBytes::Invalid = bytes_len {
if let Some(val) = number_bytes {
return Err(ShellError::UnsupportedInput(
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
val.span,
));
}
}
input.map(
move |value| operate(value, bits, head, signed, bytes_len),
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Rotate left a number with 2 bits",
example: "17 | bits rol 2",
result: Some(Value::Int {
val: 68,
span: Span::test_data(),
}),
},
Example {
description: "Rotate left a list of numbers with 2 bits",
example: "[5 3 2] | bits rol 2",
result: Some(Value::List {
vals: vec![Value::test_int(20), Value::test_int(12), Value::test_int(8)],
span: Span::test_data(),
}),
},
]
}
}
fn get_rotate_left<T: Display + PrimInt>(val: T, bits: u32, span: Span) -> Value
where
i64: std::convert::TryFrom<T>,
{
let rotate_result = i64::try_from(val.rotate_left(bits));
match rotate_result {
Ok(val) => Value::Int { val, span },
Err(_) => Value::Error {
error: ShellError::GenericError(
"Rotate left result beyond the range of 64 bit signed number".to_string(),
format!(
"{} of the specified number of bytes rotate left {} bits exceed limit",
val, bits
),
Some(span),
None,
Vec::new(),
),
},
}
}
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
match value {
Value::Int { val, span } => {
use InputNumType::*;
// let bits = (((bits % 64) + 64) % 64) as u32;
let bits = bits as u32;
let input_type = get_input_num_type(val, signed, number_size);
match input_type {
One => get_rotate_left(val as u8, bits, span),
Two => get_rotate_left(val as u16, bits, span),
Four => get_rotate_left(val as u32, bits, span),
Eight => get_rotate_left(val as u64, bits, span),
SignedOne => get_rotate_left(val as i8, bits, span),
SignedTwo => get_rotate_left(val as i16, bits, span),
SignedFour => get_rotate_left(val as i32, bits, span),
SignedEight => get_rotate_left(val as i64, bits, span),
}
}
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Only integer values are supported, input type: {:?}",
other.get_type()
),
other.span().unwrap_or(head),
),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,160 @@
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
use num_traits::int::PrimInt;
use std::fmt::Display;
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"bits ror"
}
fn signature(&self) -> Signature {
Signature::build("bits ror")
.required("bits", SyntaxShape::Int, "number of bits to rotate right")
.switch(
"signed",
"always treat input number as a signed number",
Some('s'),
)
.named(
"number-bytes",
SyntaxShape::String,
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
Some('n'),
)
.category(Category::Bits)
}
fn usage(&self) -> &str {
"Bitwise rotate right for integers"
}
fn search_terms(&self) -> Vec<&str> {
vec!["rotate right"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let bits: usize = call.req(engine_state, stack, 0)?;
let signed = call.has_flag("signed");
let number_bytes: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "number-bytes")?;
let bytes_len = get_number_bytes(&number_bytes);
if let NumberBytes::Invalid = bytes_len {
if let Some(val) = number_bytes {
return Err(ShellError::UnsupportedInput(
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
val.span,
));
}
}
input.map(
move |value| operate(value, bits, head, signed, bytes_len),
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Rotate right a number with 60 bits",
example: "17 | bits ror 60",
result: Some(Value::Int {
val: 272,
span: Span::test_data(),
}),
},
Example {
description: "Rotate right a list of numbers of one byte",
example: "[15 33 92] | bits ror 2 -n 1",
result: Some(Value::List {
vals: vec![
Value::test_int(195),
Value::test_int(72),
Value::test_int(23),
],
span: Span::test_data(),
}),
},
]
}
}
fn get_rotate_right<T: Display + PrimInt>(val: T, bits: u32, span: Span) -> Value
where
i64: std::convert::TryFrom<T>,
{
let rotate_result = i64::try_from(val.rotate_right(bits));
match rotate_result {
Ok(val) => Value::Int { val, span },
Err(_) => Value::Error {
error: ShellError::GenericError(
"Rotate right result beyond the range of 64 bit signed number".to_string(),
format!(
"{} of the specified number of bytes rotate right {} bits exceed limit",
val, bits
),
Some(span),
None,
Vec::new(),
),
},
}
}
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
match value {
Value::Int { val, span } => {
use InputNumType::*;
// let bits = (((bits % 64) + 64) % 64) as u32;
let bits = bits as u32;
let input_type = get_input_num_type(val, signed, number_size);
match input_type {
One => get_rotate_right(val as u8, bits, span),
Two => get_rotate_right(val as u16, bits, span),
Four => get_rotate_right(val as u32, bits, span),
Eight => get_rotate_right(val as u64, bits, span),
SignedOne => get_rotate_right(val as i8, bits, span),
SignedTwo => get_rotate_right(val as i16, bits, span),
SignedFour => get_rotate_right(val as i32, bits, span),
SignedEight => get_rotate_right(val as i64, bits, span),
}
}
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Only integer values are supported, input type: {:?}",
other.get_type()
),
other.span().unwrap_or(head),
),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,188 @@
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
use num_traits::CheckedShl;
use std::fmt::Display;
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"bits shl"
}
fn signature(&self) -> Signature {
Signature::build("bits shl")
.required("bits", SyntaxShape::Int, "number of bits to shift left")
.switch(
"signed",
"always treat input number as a signed number",
Some('s'),
)
.named(
"number-bytes",
SyntaxShape::String,
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
Some('n'),
)
.category(Category::Bits)
}
fn usage(&self) -> &str {
"Bitwise shift left for integers"
}
fn search_terms(&self) -> Vec<&str> {
vec!["shift left"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let bits: usize = call.req(engine_state, stack, 0)?;
let signed = call.has_flag("signed");
let number_bytes: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "number-bytes")?;
let bytes_len = get_number_bytes(&number_bytes);
if let NumberBytes::Invalid = bytes_len {
if let Some(val) = number_bytes {
return Err(ShellError::UnsupportedInput(
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
val.span,
));
}
}
input.map(
move |value| operate(value, bits, head, signed, bytes_len),
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Shift left a number by 7 bits",
example: "2 | bits shl 7",
result: Some(Value::Int {
val: 256,
span: Span::test_data(),
}),
},
Example {
description: "Shift left a number with 1 byte by 7 bits",
example: "2 | bits shl 7 -n 1",
result: Some(Value::Int {
val: 0,
span: Span::test_data(),
}),
},
Example {
description: "Shift left a signed number by 1 bit",
example: "0x7F | bits shl 1 -s",
result: Some(Value::Int {
val: 254,
span: Span::test_data(),
}),
},
Example {
description: "Shift left a list of numbers",
example: "[5 3 2] | bits shl 2",
result: Some(Value::List {
vals: vec![Value::test_int(20), Value::test_int(12), Value::test_int(8)],
span: Span::test_data(),
}),
},
]
}
}
fn get_shift_left<T: CheckedShl + Display + Copy>(val: T, bits: u32, span: Span) -> Value
where
i64: std::convert::TryFrom<T>,
{
match val.checked_shl(bits) {
Some(val) => {
let shift_result = i64::try_from(val);
match shift_result {
Ok(val) => Value::Int { val, span },
Err(_) => Value::Error {
error: ShellError::GenericError(
"Shift left result beyond the range of 64 bit signed number".to_string(),
format!(
"{} of the specified number of bytes shift left {} bits exceed limit",
val, bits
),
Some(span),
None,
Vec::new(),
),
},
}
}
None => Value::Error {
error: ShellError::GenericError(
"Shift left failed".to_string(),
format!(
"{} shift left {} bits failed, you may shift too many bits",
val, bits
),
Some(span),
None,
Vec::new(),
),
},
}
}
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
match value {
Value::Int { val, span } => {
use InputNumType::*;
// let bits = (((bits % 64) + 64) % 64) as u32;
let bits = bits as u32;
let input_type = get_input_num_type(val, signed, number_size);
match input_type {
One => get_shift_left(val as u8, bits, span),
Two => get_shift_left(val as u16, bits, span),
Four => get_shift_left(val as u32, bits, span),
Eight => get_shift_left(val as u64, bits, span),
SignedOne => get_shift_left(val as i8, bits, span),
SignedTwo => get_shift_left(val as i16, bits, span),
SignedFour => get_shift_left(val as i32, bits, span),
SignedEight => get_shift_left(val as i64, bits, span),
}
}
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Only integer values are supported, input type: {:?}",
other.get_type()
),
other.span().unwrap_or(head),
),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,172 @@
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
use num_traits::CheckedShr;
use std::fmt::Display;
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"bits shr"
}
fn signature(&self) -> Signature {
Signature::build("bits shr")
.required("bits", SyntaxShape::Int, "number of bits to shift right")
.switch(
"signed",
"always treat input number as a signed number",
Some('s'),
)
.named(
"number-bytes",
SyntaxShape::String,
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
Some('n'),
)
.category(Category::Bits)
}
fn usage(&self) -> &str {
"Bitwise shift right for integers"
}
fn search_terms(&self) -> Vec<&str> {
vec!["shift right"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let bits: usize = call.req(engine_state, stack, 0)?;
let signed = call.has_flag("signed");
let number_bytes: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "number-bytes")?;
let bytes_len = get_number_bytes(&number_bytes);
if let NumberBytes::Invalid = bytes_len {
if let Some(val) = number_bytes {
return Err(ShellError::UnsupportedInput(
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
val.span,
));
}
}
input.map(
move |value| operate(value, bits, head, signed, bytes_len),
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Shift right a number with 2 bits",
example: "8 | bits shr 2",
result: Some(Value::Int {
val: 2,
span: Span::test_data(),
}),
},
Example {
description: "Shift right a list of numbers",
example: "[15 35 2] | bits shr 2",
result: Some(Value::List {
vals: vec![Value::test_int(3), Value::test_int(8), Value::test_int(0)],
span: Span::test_data(),
}),
},
]
}
}
fn get_shift_right<T: CheckedShr + Display + Copy>(val: T, bits: u32, span: Span) -> Value
where
i64: std::convert::TryFrom<T>,
{
match val.checked_shr(bits) {
Some(val) => {
let shift_result = i64::try_from(val);
match shift_result {
Ok(val) => Value::Int { val, span },
Err(_) => Value::Error {
error: ShellError::GenericError(
"Shift right result beyond the range of 64 bit signed number".to_string(),
format!(
"{} of the specified number of bytes shift right {} bits exceed limit",
val, bits
),
Some(span),
None,
Vec::new(),
),
},
}
}
None => Value::Error {
error: ShellError::GenericError(
"Shift right failed".to_string(),
format!(
"{} shift right {} bits failed, you may shift too many bits",
val, bits
),
Some(span),
None,
Vec::new(),
),
},
}
}
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
match value {
Value::Int { val, span } => {
use InputNumType::*;
// let bits = (((bits % 64) + 64) % 64) as u32;
let bits = bits as u32;
let input_type = get_input_num_type(val, signed, number_size);
match input_type {
One => get_shift_right(val as u8, bits, span),
Two => get_shift_right(val as u16, bits, span),
Four => get_shift_right(val as u32, bits, span),
Eight => get_shift_right(val as u64, bits, span),
SignedOne => get_shift_right(val as i8, bits, span),
SignedTwo => get_shift_right(val as i16, bits, span),
SignedFour => get_shift_right(val as i32, bits, span),
SignedEight => get_shift_right(val as i64, bits, span),
}
}
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Only integer values are supported, input type: {:?}",
other.get_type()
),
other.span().unwrap_or(head),
),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,100 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"bits xor"
}
fn signature(&self) -> Signature {
Signature::build("bits xor")
.required(
"target",
SyntaxShape::Int,
"target integer to perform bit xor",
)
.category(Category::Bits)
}
fn usage(&self) -> &str {
"Performs bitwise xor for integers"
}
fn search_terms(&self) -> Vec<&str> {
vec!["logic xor"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let target: i64 = call.req(engine_state, stack, 0)?;
input.map(
move |value| operate(value, target, head),
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Apply bits xor to two numbers",
example: "2 | bits xor 2",
result: Some(Value::Int {
val: 0,
span: Span::test_data(),
}),
},
Example {
description: "Apply logical xor to a list of numbers",
example: "[8 3 2] | bits xor 2",
result: Some(Value::List {
vals: vec![Value::test_int(10), Value::test_int(1), Value::test_int(0)],
span: Span::test_data(),
}),
},
]
}
}
fn operate(value: Value, target: i64, head: Span) -> Value {
match value {
Value::Int { val, span } => Value::Int {
val: val ^ target,
span,
},
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Only integer values are supported, input type: {:?}",
other.get_type()
),
other.span().unwrap_or(head),
),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,167 @@
use super::{operate, BytesArgument};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category;
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
struct Arguments {
added_data: Vec<u8>,
index: Option<usize>,
end: bool,
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 BytesAdd;
impl Command for BytesAdd {
fn name(&self) -> &str {
"bytes add"
}
fn signature(&self) -> Signature {
Signature::build("bytes add")
.required("data", SyntaxShape::Binary, "the binary to add")
.named(
"index",
SyntaxShape::Int,
"index to insert binary data",
Some('i'),
)
.switch("end", "add to the end of binary", Some('e'))
.rest(
"rest",
SyntaxShape::CellPath,
"optionally matches prefix of text by column paths",
)
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Add specified bytes to the input"
}
fn search_terms(&self) -> Vec<&str> {
vec!["append", "truncate", "padding"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
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 index: Option<usize> = call.get_flag(engine_state, stack, "index")?;
let end = call.has_flag("end");
let arg = Arguments {
added_data,
index,
end,
column_paths,
};
operate(add, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Add bytes `0x[AA]` to `0x[1F FF AA AA]`",
example: "0x[1F FF AA AA] | bytes add 0x[AA]",
result: Some(Value::Binary {
val: vec![0xAA, 0x1F, 0xFF, 0xAA, 0xAA],
span: Span::test_data(),
}),
},
Example {
description: "Add bytes `0x[AA BB]` to `0x[1F FF AA AA]` at index 1",
example: "0x[1F FF AA AA] | bytes add 0x[AA BB] -i 1",
result: Some(Value::Binary {
val: vec![0x1F, 0xAA, 0xBB, 0xFF, 0xAA, 0xAA],
span: Span::test_data(),
}),
},
Example {
description: "Add bytes `0x[11]` to `0x[FF AA AA]` at the end",
example: "0x[FF AA AA] | bytes add 0x[11] -e",
result: Some(Value::Binary {
val: vec![0xFF, 0xAA, 0xAA, 0x11],
span: Span::test_data(),
}),
},
Example {
description: "Add bytes `0x[11 22 33]` to `0x[FF AA AA]` at the end, at index 1(the index is start from end)",
example: "0x[FF AA BB] | bytes add 0x[11 22 33] -e -i 1",
result: Some(Value::Binary {
val: vec![0xFF, 0xAA, 0x11, 0x22, 0x33, 0xBB],
span: Span::test_data(),
}),
},
]
}
}
fn add(input: &[u8], args: &Arguments, span: Span) -> Value {
match args.index {
None => {
if args.end {
let mut added_data = args.added_data.clone();
let mut result = input.to_vec();
result.append(&mut added_data);
Value::Binary { val: result, span }
} else {
let mut result = args.added_data.clone();
let mut input = input.to_vec();
result.append(&mut input);
Value::Binary { val: result, span }
}
}
Some(mut indx) => {
let inserted_index = if args.end {
input.len().saturating_sub(indx)
} else {
if indx > input.len() {
indx = input.len()
}
indx
};
let mut result = vec![];
let mut prev_data = input[..inserted_index].to_vec();
result.append(&mut prev_data);
let mut added_data = args.added_data.clone();
result.append(&mut added_data);
let mut after_data = input[inserted_index..].to_vec();
result.append(&mut after_data);
Value::Binary { val: result, span }
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesAdd {})
}
}

View File

@ -0,0 +1,281 @@
use super::{operate, BytesArgument};
use nu_engine::CallExt;
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, SyntaxShape, Value,
};
use std::cmp::Ordering;
#[derive(Clone)]
pub struct BytesAt;
struct Arguments {
start: isize,
end: isize,
arg_span: Span,
column_paths: Option<Vec<CellPath>>,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
}
}
/// ensure given `range` is valid, and returns [start, end, val_span] pair.
fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellError> {
let (start, end, span) = match range {
Value::List { mut vals, span } => {
if vals.len() != 2 {
return Err(ShellError::UnsupportedInput(
"More than two indices given".to_string(),
span,
));
} else {
let end = vals.pop().expect("Already check has size 2");
let end = match end {
Value::Int { val, .. } => val.to_string(),
Value::String { val, .. } => val,
other => {
return Err(ShellError::UnsupportedInput(
"could not perform subbytes. Expecting a string or int".to_string(),
other.span().unwrap_or(head),
))
}
};
let start = vals.pop().expect("Already check has size 1");
let start = match start {
Value::Int { val, .. } => val.to_string(),
Value::String { val, .. } => val,
other => {
return Err(ShellError::UnsupportedInput(
"could not perform subbytes. Expecting a string or int".to_string(),
other.span().unwrap_or(head),
))
}
};
(start, end, span)
}
}
Value::String { val, span } => {
let splitted_result = val.split_once(',');
match splitted_result {
Some((start, end)) => (start.to_string(), end.to_string(), span),
None => {
return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(),
span,
))
}
}
}
other => {
return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(),
other.span().unwrap_or(head),
))
}
};
let start: isize = if start.is_empty() || start == "_" {
0
} else {
match start.trim().parse() {
Ok(s) => s,
Err(_) => {
return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(),
span,
))
}
}
};
let end: isize = if end.is_empty() || end == "_" {
isize::max_value()
} else {
match end.trim().parse() {
Ok(s) => s,
Err(_) => {
return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(),
span,
))
}
}
};
Ok((start, end, span))
}
impl Command for BytesAt {
fn name(&self) -> &str {
"bytes at"
}
fn signature(&self) -> Signature {
Signature::build("bytes at")
.required("range", SyntaxShape::Any, "the indexes to get bytes")
.rest(
"rest",
SyntaxShape::CellPath,
"optionally get bytes by column paths",
)
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Get bytes defined by a range. Note that the start is included but the end is excluded, and that the first byte is index 0."
}
fn search_terms(&self) -> Vec<&str> {
vec!["slice"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> 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 arg = Arguments {
start,
end,
arg_span,
column_paths,
};
operate(at, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get a subbytes `0x[10 01]` from the bytes `0x[33 44 55 10 01 13]`",
example: " 0x[33 44 55 10 01 13] | bytes at [3 4]",
result: Some(Value::Binary {
val: vec![0x10],
span: Span::test_data(),
}),
},
Example {
description: "Alternatively, you can use the form",
example: " 0x[33 44 55 10 01 13] | bytes at '3,4'",
result: Some(Value::Binary {
val: vec![0x10],
span: Span::test_data(),
}),
},
Example {
description: "Drop the last `n` characters from the string",
example: " 0x[33 44 55 10 01 13] | bytes at ',-3'",
result: Some(Value::Binary {
val: vec![0x33, 0x44, 0x55],
span: Span::test_data(),
}),
},
Example {
description: "Get the remaining characters from a starting index",
example: " 0x[33 44 55 10 01 13] | bytes at '3,'",
result: Some(Value::Binary {
val: vec![0x10, 0x01, 0x13],
span: Span::test_data(),
}),
},
Example {
description: "Get the characters from the beginning until ending index",
example: " 0x[33 44 55 10 01 13] | bytes at ',4'",
result: Some(Value::Binary {
val: vec![0x33, 0x44, 0x55, 0x10],
span: Span::test_data(),
}),
},
Example {
description:
"Or the characters from the beginning until ending index inside a table",
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes at "1," ColB ColC"#,
result: Some(Value::List {
vals: vec![Value::Record {
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
vals: vec![
Value::Binary {
val: vec![0x11, 0x12, 0x13],
span: Span::test_data(),
},
Value::Binary {
val: vec![0x15, 0x16],
span: Span::test_data(),
},
Value::Binary {
val: vec![0x18, 0x19],
span: Span::test_data(),
},
],
span: Span::test_data(),
}],
span: Span::test_data(),
}),
},
]
}
}
fn at(input: &[u8], arg: &Arguments, span: Span) -> Value {
let len: isize = input.len() as isize;
let start: isize = if arg.start < 0 {
arg.start + len
} else {
arg.start
};
let end: isize = if arg.end < 0 {
std::cmp::max(len + arg.end, 0)
} else {
arg.end
};
if start < len && end >= 0 {
match start.cmp(&end) {
Ordering::Equal => Value::Binary { val: vec![], span },
Ordering::Greater => Value::Error {
error: ShellError::UnsupportedInput(
"End must be greater than or equal to Start".to_string(),
arg.arg_span,
),
},
Ordering::Less => Value::Binary {
val: {
let input_iter = input.iter().copied().skip(start as usize);
if end == isize::max_value() {
input_iter.collect()
} else {
input_iter.take((end - start) as usize).collect()
}
},
span,
},
}
} else {
Value::Binary { val: vec![], span }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesAt {})
}
}

View File

@ -0,0 +1,81 @@
use nu_engine::eval_expression;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Value,
};
#[derive(Clone)]
pub struct BytesBuild;
impl Command for BytesBuild {
fn name(&self) -> &str {
"bytes build"
}
fn usage(&self) -> &str {
"Create bytes from the arguments."
}
fn search_terms(&self) -> Vec<&str> {
vec!["concatenate", "join"]
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("bytes build")
.rest("rest", SyntaxShape::Any, "list of bytes")
.category(Category::Bytes)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "bytes build 0x[01 02] 0x[03] 0x[04]",
description: "Builds binary data from 0x[01 02], 0x[03], 0x[04]",
result: Some(Value::Binary {
val: vec![0x01, 0x02, 0x03, 0x04],
span: Span::test_data(),
}),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let mut output = vec![];
for expr in call.positional_iter() {
let val = eval_expression(engine_state, stack, expr)?;
match val {
Value::Binary { mut val, .. } => output.append(&mut val),
other => {
return Err(ShellError::UnsupportedInput(
"only support expression which yields to binary data".to_string(),
other.span().unwrap_or(call.head),
))
}
}
}
Ok(Value::Binary {
val: output,
span: call.head,
}
.into_pipeline_data())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesBuild {})
}
}

View File

@ -0,0 +1,49 @@
use nu_engine::get_full_help;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, IntoPipelineData, PipelineData, Signature, Value,
};
#[derive(Clone)]
pub struct Bytes;
impl Command for Bytes {
fn name(&self) -> &str {
"bytes"
}
fn signature(&self) -> Signature {
Signature::build("bytes").category(Category::Bytes)
}
fn usage(&self) -> &str {
"Various commands for working with byte data"
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(Value::String {
val: get_full_help(&Bytes.signature(), &Bytes.examples(), engine_state, stack),
span: call.head,
}
.into_pipeline_data())
}
}
#[cfg(test)]
mod test {
use crate::Bytes;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Bytes {})
}
}

View File

@ -0,0 +1,127 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Value,
};
#[derive(Clone, Copy)]
pub struct BytesCollect;
impl Command for BytesCollect {
fn name(&self) -> &str {
"bytes collect"
}
fn signature(&self) -> Signature {
Signature::build("bytes collect")
.optional(
"separator",
SyntaxShape::Binary,
"optional separator to use when creating binary",
)
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Concatenate multiple binary into a single binary, with an optional separator between each"
}
fn search_terms(&self) -> Vec<&str> {
vec!["join", "concatenate"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let separator: Option<Vec<u8>> = call.opt(engine_state, stack, 0)?;
// input should be a list of binary data.
let mut output_binary = vec![];
for value in input {
match value {
Value::Binary { mut val, .. } => {
output_binary.append(&mut val);
// manually concat
// TODO: make use of std::slice::Join when it's available in stable.
if let Some(sep) = &separator {
let mut work_sep = sep.clone();
output_binary.append(&mut work_sep)
}
}
other => {
return Err(ShellError::UnsupportedInput(
format!(
"The element type is {}, this command only works with bytes.",
other.get_type()
),
other.span().unwrap_or(call.head),
))
}
}
}
match separator {
None => Ok(Value::Binary {
val: output_binary,
span: call.head,
}
.into_pipeline_data()),
Some(sep) => {
if output_binary.is_empty() {
Ok(Value::Binary {
val: output_binary,
span: call.head,
}
.into_pipeline_data())
} else {
// have push one extra separator in previous step, pop them out.
for _ in sep {
let _ = output_binary.pop();
}
Ok(Value::Binary {
val: output_binary,
span: call.head,
}
.into_pipeline_data())
}
}
}
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Create a byte array from input",
example: "[0x[11] 0x[13 15]] | bytes collect",
result: Some(Value::Binary {
val: vec![0x11, 0x13, 0x15],
span: Span::test_data(),
}),
},
Example {
description: "Create a byte array from input with a separator",
example: "[0x[11] 0x[33] 0x[44]] | bytes collect 0x[01]",
result: Some(Value::Binary {
val: vec![0x11, 0x01, 0x33, 0x01, 0x44],
span: Span::test_data(),
}),
},
]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesCollect {})
}
}

View File

@ -0,0 +1,116 @@
use super::{operate, BytesArgument};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category;
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
struct Arguments {
pattern: Vec<u8>,
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 BytesEndsWith;
impl Command for BytesEndsWith {
fn name(&self) -> &str {
"bytes ends-with"
}
fn signature(&self) -> Signature {
Signature::build("bytes ends-with")
.required("pattern", SyntaxShape::Binary, "the pattern to match")
.rest(
"rest",
SyntaxShape::CellPath,
"optionally matches prefix of text by column paths",
)
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Check if bytes ends with a pattern"
}
fn search_terms(&self) -> Vec<&str> {
vec!["pattern", "match", "find", "search"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
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 arg = Arguments {
pattern,
column_paths,
};
operate(ends_with, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Checks if binary ends with `0x[AA]`",
example: "0x[1F FF AA AA] | bytes ends-with 0x[AA]",
result: Some(Value::Bool {
val: true,
span: Span::test_data(),
}),
},
Example {
description: "Checks if binary ends with `0x[FF AA AA]`",
example: "0x[1F FF AA AA] | bytes ends-with 0x[FF AA AA]",
result: Some(Value::Bool {
val: true,
span: Span::test_data(),
}),
},
Example {
description: "Checks if binary ends with `0x[11]`",
example: "0x[1F FF AA AA] | bytes ends-with 0x[11]",
result: Some(Value::Bool {
val: false,
span: Span::test_data(),
}),
},
]
}
}
fn ends_with(input: &[u8], Arguments { pattern, .. }: &Arguments, span: Span) -> Value {
Value::Bool {
val: input.ends_with(pattern),
span,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesEndsWith {})
}
}

View File

@ -0,0 +1,210 @@
use super::{operate, BytesArgument};
use nu_engine::CallExt;
use nu_protocol::ast::{Call, CellPath};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
struct Arguments {
pattern: Vec<u8>,
end: bool,
all: bool,
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 BytesIndexOf;
impl Command for BytesIndexOf {
fn name(&self) -> &str {
"bytes index-of"
}
fn signature(&self) -> Signature {
Signature::build("bytes index-of")
.required(
"pattern",
SyntaxShape::Binary,
"the pattern to find index of",
)
.rest(
"rest",
SyntaxShape::CellPath,
"optionally returns index of pattern in string by column paths",
)
.switch("all", "returns all matched index", Some('a'))
.switch("end", "search from the end of the binary", Some('e'))
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Returns start index of first occurrence of pattern in bytes, or -1 if no match"
}
fn search_terms(&self) -> Vec<&str> {
vec!["pattern", "match", "find", "search"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
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 arg = Arguments {
pattern,
end: call.has_flag("end"),
all: call.has_flag("all"),
column_paths,
};
operate(index_of, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Returns index of pattern in bytes",
example: " 0x[33 44 55 10 01 13 44 55] | bytes index-of 0x[44 55]",
result: Some(Value::test_int(1)),
},
Example {
description: "Returns index of pattern, search from end",
example: " 0x[33 44 55 10 01 13 44 55] | bytes index-of -e 0x[44 55]",
result: Some(Value::test_int(6)),
},
Example {
description: "Returns all matched index",
example: " 0x[33 44 55 10 01 33 44 33 44] | bytes index-of -a 0x[33 44]",
result: Some(Value::List {
vals: vec![Value::test_int(0), Value::test_int(5), Value::test_int(7)],
span: Span::test_data(),
}),
},
Example {
description: "Returns all matched index, searching from end",
example: " 0x[33 44 55 10 01 33 44 33 44] | bytes index-of -a -e 0x[33 44]",
result: Some(Value::List {
vals: vec![Value::test_int(7), Value::test_int(5), Value::test_int(0)],
span: Span::test_data(),
}),
},
Example {
description: "Returns index of pattern for specific column",
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes index-of 0x[11] ColA ColC"#,
result: Some(Value::List {
vals: vec![Value::Record {
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
vals: vec![
Value::test_int(0),
Value::Binary {
val: vec![0x14, 0x15, 0x16],
span: Span::test_data(),
},
Value::test_int(-1),
],
span: Span::test_data(),
}],
span: Span::test_data(),
}),
},
]
}
}
fn index_of(input: &[u8], arg: &Arguments, span: Span) -> Value {
if arg.all {
search_all_index(input, &arg.pattern, arg.end, span)
} else {
let mut iter = input.windows(arg.pattern.len());
if arg.end {
Value::Int {
val: iter
.rev()
.position(|sub_bytes| sub_bytes == arg.pattern)
.map(|x| (input.len() - arg.pattern.len() - x) as i64)
.unwrap_or(-1),
span,
}
} else {
Value::Int {
val: iter
.position(|sub_bytes| sub_bytes == arg.pattern)
.map(|x| x as i64)
.unwrap_or(-1),
span,
}
}
}
}
fn search_all_index(input: &[u8], pattern: &[u8], from_end: bool, span: Span) -> Value {
let mut result = vec![];
if from_end {
let (mut left, mut right) = (
input.len() as isize - pattern.len() as isize,
input.len() as isize,
);
while left >= 0 {
if &input[left as usize..right as usize] == pattern {
result.push(Value::Int {
val: left as i64,
span,
});
left -= pattern.len() as isize;
right -= pattern.len() as isize;
} else {
left -= 1;
right -= 1;
}
}
Value::List { vals: result, span }
} else {
// doing find stuff.
let (mut left, mut right) = (0, pattern.len());
let input_len = input.len();
let pattern_len = pattern.len();
while right <= input_len {
if &input[left..right] == pattern {
result.push(Value::Int {
val: left as i64,
span,
});
left += pattern_len;
right += pattern_len;
} else {
left += 1;
right += 1;
}
}
Value::List { vals: result, span }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesIndexOf {})
}
}

View File

@ -0,0 +1,98 @@
use super::{operate, BytesArgument};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category;
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
#[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"
}
fn signature(&self) -> Signature {
Signature::build("bytes length")
.rest(
"rest",
SyntaxShape::CellPath,
"optionally find length of binary by column paths",
)
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Output the length of any bytes in the pipeline"
}
fn search_terms(&self) -> Vec<&str> {
vec!["size", "count"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
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 };
operate(length, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Return the lengths of multiple strings",
example: "0x[1F FF AA AB] | bytes length",
result: Some(Value::test_int(4)),
},
Example {
description: "Return the lengths of multiple strings",
example: "[0x[1F FF AA AB] 0x[1F]] | bytes length",
result: Some(Value::List {
vals: vec![Value::test_int(4), Value::test_int(1)],
span: Span::test_data(),
}),
},
]
}
}
fn length(input: &[u8], _arg: &Arguments, span: Span) -> Value {
Value::Int {
val: input.len() as i64,
span,
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesLen {})
}
}

View File

@ -0,0 +1,98 @@
mod add;
mod at;
mod build_;
mod bytes_;
mod collect;
mod ends_with;
mod index_of;
mod length;
mod remove;
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;
pub use bytes_::Bytes;
pub use collect::BytesCollect;
pub use ends_with::BytesEndsWith;
pub use index_of::BytesIndexOf;
pub use length::BytesLen;
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

@ -0,0 +1,196 @@
use super::{operate, BytesArgument};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
struct Arguments {
pattern: Vec<u8>,
end: bool,
column_paths: Option<Vec<CellPath>>,
all: bool,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
}
}
#[derive(Clone)]
pub struct BytesRemove;
impl Command for BytesRemove {
fn name(&self) -> &str {
"bytes remove"
}
fn signature(&self) -> Signature {
Signature::build("bytes remove")
.required("pattern", SyntaxShape::Binary, "the pattern to find")
.rest(
"rest",
SyntaxShape::CellPath,
"optionally remove bytes by column paths",
)
.switch("end", "remove from end of binary", Some('e'))
.switch("all", "remove occurrences of finding binary", Some('a'))
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Remove bytes"
}
fn search_terms(&self) -> Vec<&str> {
vec!["search", "shift", "switch"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
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 pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
if pattern_to_remove.item.is_empty() {
return Err(ShellError::UnsupportedInput(
"the pattern to remove cannot be empty".to_string(),
pattern_to_remove.span,
));
}
let pattern_to_remove: Vec<u8> = pattern_to_remove.item;
let arg = Arguments {
pattern: pattern_to_remove,
end: call.has_flag("end"),
column_paths,
all: call.has_flag("all"),
};
operate(remove, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Remove contents",
example: "0x[10 AA FF AA FF] | bytes remove 0x[10 AA]",
result: Some(Value::Binary {
val: vec![0xFF, 0xAA, 0xFF],
span: Span::test_data(),
}),
},
Example {
description: "Remove all occurrences of find binary",
example: "0x[10 AA 10 BB 10] | bytes remove -a 0x[10]",
result: Some(Value::Binary {
val: vec![0xAA, 0xBB],
span: Span::test_data(),
}),
},
Example {
description: "Remove occurrences of find binary from end",
example: "0x[10 AA 10 BB CC AA 10] | bytes remove -e 0x[10]",
result: Some(Value::Binary {
val: vec![0x10, 0xAA, 0x10, 0xBB, 0xCC, 0xAA],
span: Span::test_data(),
}),
},
Example {
description: "Remove all occurrences of find binary in table",
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes remove 0x[11] ColA ColC",
result: Some(Value::List {
vals: vec![Value::Record {
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
vals: vec![
Value::Binary {
val: vec![0x12, 0x13],
span: Span::test_data(),
},
Value::Binary {
val: vec![0x14, 0x15, 0x16],
span: Span::test_data(),
},
Value::Binary {
val: vec![0x17, 0x18, 0x19],
span: Span::test_data(),
},
],
span: Span::test_data(),
}],
span: Span::test_data(),
}),
},
]
}
}
fn remove(input: &[u8], arg: &Arguments, span: Span) -> Value {
let mut result = vec![];
let remove_all = arg.all;
let input_len = input.len();
let pattern_len = arg.pattern.len();
// Note:
// remove_all from start and end will generate the same result.
// so we'll put `remove_all` relative logic into else clouse.
if arg.end && !remove_all {
let (mut left, mut right) = (
input.len() as isize - arg.pattern.len() as isize,
input.len() as isize,
);
while left >= 0 && input[left as usize..right as usize] != arg.pattern {
result.push(input[right as usize - 1]);
left -= 1;
right -= 1;
}
// append the remaining thing to result, this can be happeneed when
// we have something to remove and remove_all is False.
let mut remain = input[..left as usize].iter().copied().rev().collect();
result.append(&mut remain);
result = result.into_iter().rev().collect();
Value::Binary { val: result, span }
} else {
let (mut left, mut right) = (0, arg.pattern.len());
while right <= input_len {
if input[left..right] == arg.pattern {
left += pattern_len;
right += pattern_len;
if !remove_all {
break;
}
} else {
result.push(input[left]);
left += 1;
right += 1;
}
}
// append the remaing thing to result, this can happened when
// we have something to remove and remove_all is False.
let mut remain = input[left..].to_vec();
result.append(&mut remain);
Value::Binary { val: result, span }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesRemove {})
}
}

View File

@ -0,0 +1,171 @@
use super::{operate, BytesArgument};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
struct Arguments {
find: Vec<u8>,
replace: Vec<u8>,
column_paths: Option<Vec<CellPath>>,
all: bool,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
}
}
#[derive(Clone)]
pub struct BytesReplace;
impl Command for BytesReplace {
fn name(&self) -> &str {
"bytes replace"
}
fn signature(&self) -> Signature {
Signature::build("bytes replace")
.required("find", SyntaxShape::Binary, "the pattern to find")
.required("replace", SyntaxShape::Binary, "the replacement pattern")
.rest(
"rest",
SyntaxShape::CellPath,
"optionally find and replace text by column paths",
)
.switch("all", "replace all occurrences of find binary", Some('a'))
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Find and replace binary"
}
fn search_terms(&self) -> Vec<&str> {
vec!["search", "shift", "switch"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
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 find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
if find.item.is_empty() {
return Err(ShellError::UnsupportedInput(
"the pattern to find cannot be empty".to_string(),
find.span,
));
}
let arg = Arguments {
find: find.item,
replace: call.req::<Vec<u8>>(engine_state, stack, 1)?,
column_paths,
all: call.has_flag("all"),
};
operate(replace, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Find and replace contents",
example: "0x[10 AA FF AA FF] | bytes replace 0x[10 AA] 0x[FF]",
result: Some(Value::Binary {
val: vec![0xFF, 0xFF, 0xAA, 0xFF],
span: Span::test_data(),
}),
},
Example {
description: "Find and replace all occurrences of find binary",
example: "0x[10 AA 10 BB 10] | bytes replace -a 0x[10] 0x[A0]",
result: Some(Value::Binary {
val: vec![0xA0, 0xAA, 0xA0, 0xBB, 0xA0],
span: Span::test_data(),
}),
},
Example {
description: "Find and replace all occurrences of find binary in table",
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes replace -a 0x[11] 0x[13] ColA ColC",
result: Some(Value::List {
vals: vec![Value::Record {
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
vals: vec![
Value::Binary {
val: vec![0x13, 0x12, 0x13],
span: Span::test_data(),
},
Value::Binary {
val: vec![0x14, 0x15, 0x16],
span: Span::test_data(),
},
Value::Binary {
val: vec![0x17, 0x18, 0x19],
span: Span::test_data(),
},
],
span: Span::test_data(),
}],
span: Span::test_data(),
}),
},
]
}
}
fn replace(input: &[u8], arg: &Arguments, span: Span) -> Value {
let mut replaced = vec![];
let replace_all = arg.all;
// doing find-and-replace stuff.
let (mut left, mut right) = (0, arg.find.len());
let input_len = input.len();
let pattern_len = arg.find.len();
while right <= input_len {
if input[left..right] == arg.find {
let mut to_replace = arg.replace.clone();
replaced.append(&mut to_replace);
left += pattern_len;
right += pattern_len;
if !replace_all {
break;
}
} else {
replaced.push(input[left]);
left += 1;
right += 1;
}
}
let mut remain = input[left..].to_vec();
replaced.append(&mut remain);
Value::Binary {
val: replaced,
span,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesReplace {})
}
}

View File

@ -0,0 +1,104 @@
use super::{operate, BytesArgument};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
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;
impl Command for BytesReverse {
fn name(&self) -> &str {
"bytes reverse"
}
fn signature(&self) -> Signature {
Signature::build("bytes reverse")
.rest(
"rest",
SyntaxShape::CellPath,
"optionally matches prefix of text by column paths",
)
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Reverse every bytes in the pipeline"
}
fn search_terms(&self) -> Vec<&str> {
vec!["convert", "inverse", "flip"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
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 };
operate(reverse, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Reverse bytes `0x[1F FF AA AA]`",
example: "0x[1F FF AA AA] | bytes reverse",
result: Some(Value::Binary {
val: vec![0xAA, 0xAA, 0xFF, 0x1F],
span: Span::test_data(),
}),
},
Example {
description: "Reverse bytes `0x[FF AA AA]`",
example: "0x[FF AA AA] | bytes reverse",
result: Some(Value::Binary {
val: vec![0xAA, 0xAA, 0xFF],
span: Span::test_data(),
}),
},
]
}
}
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,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesReverse {})
}
}

View File

@ -0,0 +1,122 @@
use super::{operate, BytesArgument};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category;
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
struct Arguments {
pattern: Vec<u8>,
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 BytesStartsWith;
impl Command for BytesStartsWith {
fn name(&self) -> &str {
"bytes starts-with"
}
fn signature(&self) -> Signature {
Signature::build("bytes starts-with")
.required("pattern", SyntaxShape::Binary, "the pattern to match")
.rest(
"rest",
SyntaxShape::CellPath,
"optionally matches prefix of text by column paths",
)
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Check if bytes starts with a pattern"
}
fn search_terms(&self) -> Vec<&str> {
vec!["pattern", "match", "find", "search"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
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 arg = Arguments {
pattern,
column_paths,
};
operate(
starts_with,
arg,
input,
call.head,
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Checks if binary starts with `0x[1F FF AA]`",
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F FF AA]",
result: Some(Value::Bool {
val: true,
span: Span::test_data(),
}),
},
Example {
description: "Checks if binary starts with `0x[1F]`",
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F]",
result: Some(Value::Bool {
val: true,
span: Span::test_data(),
}),
},
Example {
description: "Checks if binary starts with `0x[1F]`",
example: "0x[1F FF AA AA] | bytes starts-with 0x[11]",
result: Some(Value::Bool {
val: false,
span: Span::test_data(),
}),
},
]
}
}
fn starts_with(input: &[u8], Arguments { pattern, .. }: &Arguments, span: Span) -> Value {
Value::Bool {
val: input.starts_with(pattern),
span,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesStartsWith {})
}
}

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

@ -29,7 +29,7 @@ impl Command for SubCommand {
}
fn search_terms(&self) -> Vec<&str> {
vec!["convert", "binary", "bytes", "bin"]
vec!["convert", "bytes"]
}
fn run(

View File

@ -1,3 +1,4 @@
use crate::{generate_strftime_list, parse_date_from_string};
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
@ -7,9 +8,6 @@ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
use crate::generate_strftime_list;
use crate::parse_date_from_string;
struct Arguments {
timezone: Option<Spanned<String>>,
offset: Option<Spanned<i64>>,
@ -105,7 +103,7 @@ impl Command for SubCommand {
}
fn search_terms(&self) -> Vec<&str> {
vec!["convert", "date", "time", "timezone", "UTC"]
vec!["convert", "timezone", "UTC"]
}
fn examples(&self) -> Vec<Example> {
@ -148,6 +146,15 @@ impl Command for SubCommand {
example: "1614434140 | into datetime -o +9",
result: None,
},
Example {
description:
"Convert timestamps like the sqlite history t",
example: "1656165681720 | into datetime",
result: Some(Value::Date {
val: Utc.timestamp_millis(1656165681720).into(),
span: Span::test_data(),
}),
},
]
}
}
@ -251,10 +258,19 @@ fn action(
return match timezone {
// default to UTC
None => Value::Date {
val: Utc.timestamp(ts, 0).into(),
span: head,
},
None => {
// be able to convert chrono::Utc::now()
let dt = match ts.to_string().len() {
x if x > 13 => Utc.timestamp_nanos(ts).into(),
x if x > 10 => Utc.timestamp_millis(ts).into(),
_ => Utc.timestamp(ts, 0).into(),
};
Value::Date {
val: dt,
span: head,
}
}
Some(Spanned { item, span }) => match item {
Zone::Utc => Value::Date {
val: Utc.timestamp(ts, 0).into(),
@ -295,7 +311,7 @@ fn action(
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
Ok(d) => Value::Date { val: d, span: head },
Err(reason) => {
return Value::Error {
Value::Error {
error: ShellError::CantConvert(
format!("could not parse as datetime using format '{}'", dt.0),
reason.to_string(),

View File

@ -42,7 +42,7 @@ impl Command for SubCommand {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Convert string to integer in table",
description: "Convert string to decimal in table",
example: "[[num]; ['5.01']] | into decimal num",
result: Some(Value::List {
vals: vec![Value::Record {
@ -54,15 +54,20 @@ impl Command for SubCommand {
}),
},
Example {
description: "Convert string to integer",
description: "Convert string to decimal",
example: "'1.345' | into decimal",
result: Some(Value::test_float(1.345)),
},
Example {
description: "Convert decimal to integer",
description: "Convert decimal to decimal",
example: "'-5.9' | into decimal",
result: Some(Value::test_float(-5.9)),
},
Example {
description: "Convert boolean to decimal",
example: "true | into decimal",
result: Some(Value::test_float(1.0)),
},
]
}
}
@ -118,6 +123,13 @@ fn action(input: &Value, head: Span) -> Value {
val: *v as f64,
span: *span,
},
Value::Bool { val: b, span } => Value::Float {
val: match b {
true => 1.0,
false => 0.0,
},
span: *span,
},
other => {
let span = other.span();
match span {

View File

@ -3,7 +3,8 @@ use nu_parser::parse_duration_bytes;
use nu_protocol::{
ast::{Call, CellPath, Expr},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Unit, Value,
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Unit,
Value,
};
#[derive(Clone)]
@ -16,6 +17,12 @@ impl Command for SubCommand {
fn signature(&self) -> Signature {
Signature::build("into duration")
.named(
"convert",
SyntaxShape::String,
"convert duration into another duration",
Some('c'),
)
.rest(
"rest",
SyntaxShape::CellPath,
@ -28,6 +35,10 @@ impl Command for SubCommand {
"Convert value to duration"
}
fn extra_usage(&self) -> &str {
"into duration does not take leap years into account and every month is calculated with 30 days"
}
fn search_terms(&self) -> Vec<&str> {
vec!["convert", "time", "period"]
}
@ -102,6 +113,15 @@ impl Command for SubCommand {
span,
}),
},
Example {
description: "Convert string to a named duration",
example: "'7min' | into duration --convert sec",
result: Some(Value::String {
val: "420 sec".to_string(),
span,
}),
},
]
}
}
@ -113,17 +133,21 @@ fn into_duration(
input: PipelineData,
) -> Result<PipelineData, ShellError> {
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)?;
input.map(
move |v| {
if column_paths.is_empty() {
action(&v, head)
action(&v, &convert_to_unit, 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)));
let d = convert_to_unit.clone();
let r = ret.update_cell_path(
&path.members,
Box::new(move |old| action(old, &d, head)),
);
if let Err(error) = r {
return Value::Error { error };
}
@ -136,7 +160,149 @@ fn into_duration(
)
}
fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
fn convert_str_from_unit_to_unit(
val: i64,
from_unit: &str,
to_unit: &str,
span: Span,
value_span: Span,
) -> Result<i64, 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),
("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),
("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),
("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),
("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),
("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),
("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),
("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),
("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),
("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),
_ => Err(ShellError::CantConvertWithValue(
"string duration".to_string(),
"string duration".to_string(),
to_unit.to_string(),
span,
value_span,
Some(
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec"
.to_string(),
),
)),
}
}
fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, ShellError> {
if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
if let Expr::ValueWithUnit(value, unit) = expression.expr {
if let Expr::Int(x) = value.expr {
@ -155,21 +321,108 @@ fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
}
}
Err(ShellError::CantConvert(
Err(ShellError::CantConvertWithValue(
"duration".to_string(),
"string".to_string(),
s.to_string(),
span,
Some("supported units are ns, us, ms, sec, min, hr, day, and wk".to_string()),
value_span,
Some(
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec".to_string(),
),
))
}
fn action(input: &Value, span: Span) -> Value {
fn string_to_unit_duration(
s: &str,
span: Span,
value_span: Span,
) -> Result<(&str, i64), ShellError> {
if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
if let Expr::ValueWithUnit(value, unit) = expression.expr {
if let Expr::Int(x) = value.expr {
match unit.item {
Unit::Nanosecond => return Ok(("ns", x)),
Unit::Microsecond => return Ok(("us", x)),
Unit::Millisecond => return Ok(("ms", x)),
Unit::Second => return Ok(("sec", x)),
Unit::Minute => return Ok(("min", x)),
Unit::Hour => return Ok(("hr", x)),
Unit::Day => return Ok(("day", x)),
Unit::Week => return Ok(("wk", x)),
_ => return Ok(("ns", 0)),
}
}
}
}
Err(ShellError::CantConvertWithValue(
"duration".to_string(),
"string".to_string(),
s.to_string(),
span,
value_span,
Some(
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec".to_string(),
),
))
}
fn action(input: &Value, convert_to_unit: &Option<Spanned<String>>, span: Span) -> Value {
match input {
Value::Duration { .. } => input.clone(),
Value::String { val, .. } => match string_to_duration(val, span) {
Ok(val) => Value::Duration { val, span },
Err(error) => Value::Error { error },
},
Value::Duration {
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,
),
}
} else {
input.clone()
}
}
Value::String {
val,
span: value_span,
} => {
if let Some(to_unit) = convert_to_unit {
if let Ok(dur) = string_to_unit_duration(val, span, *value_span) {
let from_unit = dur.0;
let duration = dur.1;
match convert_str_from_unit_to_unit(
duration,
from_unit,
&to_unit.item,
span,
*value_span,
) {
Ok(d) => Value::String {
val: format!("{} {}", d, &to_unit.item),
span: *value_span,
},
Err(e) => Value::Error { error: e },
}
} else {
Value::Error {
error: ShellError::UnsupportedInput(
"'into duration' does not support this string input".into(),
span,
),
}
}
} else {
match string_to_duration(val, span, *value_span) {
Ok(val) => Value::Duration { val, span },
Err(error) => Value::Error { error },
}
}
}
_ => Value::Error {
error: ShellError::UnsupportedInput(
"'into duration' does not support this input".into(),
@ -195,8 +448,9 @@ mod test {
let span = Span::test_data();
let word = Value::test_string("3ns");
let expected = Value::Duration { val: 3, span };
let convert_duration = None;
let actual = action(&word, span);
let actual = action(&word, &convert_duration, span);
assert_eq!(actual, expected);
}
@ -208,8 +462,9 @@ mod test {
val: 4 * 1000,
span,
};
let convert_duration = None;
let actual = action(&word, span);
let actual = action(&word, &convert_duration, span);
assert_eq!(actual, expected);
}
@ -221,8 +476,9 @@ mod test {
val: 5 * 1000 * 1000,
span,
};
let convert_duration = None;
let actual = action(&word, span);
let actual = action(&word, &convert_duration, span);
assert_eq!(actual, expected);
}
@ -234,8 +490,9 @@ mod test {
val: 1000 * 1000 * 1000,
span,
};
let convert_duration = None;
let actual = action(&word, span);
let actual = action(&word, &convert_duration, span);
assert_eq!(actual, expected);
}
@ -247,8 +504,9 @@ mod test {
val: 7 * 60 * 1000 * 1000 * 1000,
span,
};
let convert_duration = None;
let actual = action(&word, span);
let actual = action(&word, &convert_duration, span);
assert_eq!(actual, expected);
}
@ -260,8 +518,9 @@ mod test {
val: 42 * 60 * 60 * 1000 * 1000 * 1000,
span,
};
let convert_duration = None;
let actual = action(&word, span);
let actual = action(&word, &convert_duration, span);
assert_eq!(actual, expected);
}
@ -273,8 +532,9 @@ mod test {
val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000,
span,
};
let convert_duration = None;
let actual = action(&word, span);
let actual = action(&word, &convert_duration, span);
assert_eq!(actual, expected);
}
@ -286,8 +546,9 @@ mod test {
val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000,
span,
};
let convert_duration = None;
let actual = action(&word, span);
let actual = action(&word, &convert_duration, span);
assert_eq!(actual, expected);
}
}

View File

@ -28,7 +28,7 @@ impl Command for SubCommand {
}
fn search_terms(&self) -> Vec<&str> {
vec!["convert", "number", "size", "bytes"]
vec!["convert", "number", "bytes"]
}
fn run(

View File

@ -8,6 +8,7 @@ use nu_protocol::{
struct Arguments {
radix: Option<Value>,
column_paths: Vec<CellPath>,
little_endian: bool,
}
#[derive(Clone)]
@ -21,6 +22,7 @@ impl Command for SubCommand {
fn signature(&self) -> Signature {
Signature::build("into int")
.named("radix", SyntaxShape::Number, "radix of integer", Some('r'))
.switch("little-endian", "use little-endian byte decoding", None)
.rest(
"rest",
SyntaxShape::CellPath,
@ -100,6 +102,21 @@ impl Command for SubCommand {
example: "'FF' | into int -r 16",
result: Some(Value::test_int(255)),
},
Example {
description: "Convert octal string to integer",
example: "'0o10132' | into int",
result: Some(Value::test_int(4186)),
},
Example {
description: "Convert 0 padded string to integer",
example: "'0010132' | into int",
result: Some(Value::test_int(10132)),
},
Example {
description: "Convert 0 padded string to integer with radix",
example: "'0010132' | into int -r 8",
result: Some(Value::test_int(4186)),
},
]
}
}
@ -114,6 +131,7 @@ fn into_int(
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)?,
};
@ -135,13 +153,13 @@ fn into_int(
input.map(
move |v| {
if options.column_paths.is_empty() {
action(&v, head, radix)
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)),
Box::new(move |old| action(old, head, radix, options.little_endian)),
);
if let Err(error) = r {
return Value::Error { error };
@ -155,7 +173,7 @@ fn into_int(
)
}
pub fn action(input: &Value, span: Span, radix: u32) -> Value {
pub fn action(input: &Value, span: Span, radix: u32, little_endian: bool) -> Value {
match input {
Value::Int { val: _, .. } => {
if radix == 10 {
@ -166,7 +184,32 @@ pub fn action(input: &Value, span: Span, radix: u32) -> Value {
}
Value::Filesize { val, .. } => Value::Int { val: *val, span },
Value::Float { val, .. } => Value::Int {
val: *val as i64,
val: {
if radix == 10 {
*val as i64
} else {
match convert_int(
&Value::Int {
val: *val as i64,
span,
},
span,
radix,
)
.as_i64()
{
Ok(v) => v,
_ => {
return Value::Error {
error: ShellError::UnsupportedInput(
"Could not convert float to integer".to_string(),
span,
),
}
}
}
}
},
span,
},
Value::String { val, .. } => {
@ -190,6 +233,33 @@ pub fn action(input: &Value, span: Span, radix: u32) -> Value {
val: val.timestamp(),
span,
},
Value::Binary { val, span } => {
use byteorder::{BigEndian, ByteOrder, LittleEndian};
let mut val = val.to_vec();
if little_endian {
while val.len() < 8 {
val.push(0);
}
val.resize(8, 0);
Value::Int {
val: LittleEndian::read_i64(&val),
span: *span,
}
} else {
while val.len() < 8 {
val.insert(0, 0);
}
val.resize(8, 0);
Value::Int {
val: BigEndian::read_i64(&val),
span: *span,
}
}
}
_ => Value::Error {
error: ShellError::UnsupportedInput(
format!("'into int' for unsupported type '{}'", input.get_type()),
@ -203,11 +273,31 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
let i = match input {
Value::Int { val, .. } => val.to_string(),
Value::String { val, .. } => {
if val.starts_with("0x") || val.starts_with("0b") {
let val = val.trim();
if val.starts_with("0x") // hex
|| val.starts_with("0b") // binary
|| val.starts_with("0o")
// octal
{
match int_from_string(val, head) {
Ok(x) => return Value::Int { val: x, span: head },
Err(e) => return Value::Error { error: e },
}
} else if val.starts_with("00") {
// It's a padded string
match i64::from_str_radix(val, radix) {
Ok(n) => return Value::Int { val: n, span: head },
Err(e) => {
return Value::Error {
error: ShellError::CantConvert(
"string".to_string(),
"int".to_string(),
head,
Some(e.to_string()),
),
}
}
}
}
val.to_string()
}
@ -220,10 +310,10 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
}
}
};
match i64::from_str_radix(&i, radix) {
match i64::from_str_radix(i.trim(), radix) {
Ok(n) => Value::Int { val: n, span: head },
Err(_reason) => Value::Error {
error: ShellError::CantConvert("int".to_string(), "string".to_string(), head, None),
error: ShellError::CantConvert("string".to_string(), "int".to_string(), head, None),
},
}
}
@ -261,7 +351,21 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
};
Ok(num)
}
_ => match a_string.parse::<i64>() {
o if o.starts_with("0o") => {
let num = match i64::from_str_radix(o.trim_start_matches("0o"), 8) {
Ok(n) => n,
Err(_reason) => {
return Err(ShellError::CantConvert(
"int".to_string(),
"string".to_string(),
span,
Some(r#"octal digits following "0o" should be in 0-7"#.to_string()),
))
}
};
Ok(num)
}
_ => match trimmed.parse::<i64>() {
Ok(n) => Ok(n),
Err(_) => match a_string.parse::<f64>() {
Ok(f) => Ok(f as i64),
@ -269,7 +373,10 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
"int".to_string(),
"string".to_string(),
span,
None,
Some(format!(
r#"string "{}" does not represent a valid integer"#,
trimmed
)),
)),
},
},
@ -294,21 +401,21 @@ mod test {
let word = Value::test_string("10");
let expected = Value::test_int(10);
let actual = action(&word, Span::test_data(), 10);
let actual = action(&word, Span::test_data(), 10, false);
assert_eq!(actual, expected);
}
#[test]
fn turns_binary_to_integer() {
let s = Value::test_string("0b101");
let actual = action(&s, Span::test_data(), 10);
let actual = action(&s, Span::test_data(), 10, false);
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);
let actual = action(&s, Span::test_data(), 16, false);
assert_eq!(actual, Value::test_int(255));
}
@ -316,7 +423,7 @@ 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);
let actual = action(&integer_str, Span::test_data(), 10, false);
assert_eq!(actual.get_type(), Error)
}

View File

@ -2,11 +2,11 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Value,
into_code, Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature,
Span, SyntaxShape, Value,
};
// TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml)
use nu_utils::get_system_locale;
use num_format::ToFormattedString;
#[derive(Clone)]
pub struct SubCommand;
@ -38,7 +38,7 @@ impl Command for SubCommand {
}
fn search_terms(&self) -> Vec<&str> {
vec!["convert", "str", "text"]
vec!["convert", "text"]
}
fn run(
@ -53,6 +53,14 @@ impl Command for SubCommand {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "convert integer to string and append three decimal places",
example: "5 | into string -d 3",
result: Some(Value::String {
val: "5.000".to_string(),
span: Span::test_data(),
}),
},
Example {
description: "convert decimal to string and round to nearest integer",
example: "1.7 | into string -d 0",
@ -208,12 +216,8 @@ pub fn action(
) -> Value {
match input {
Value::Int { val, .. } => {
let res = if group_digits {
format_int(*val) // int.to_formatted_string(*locale)
} else {
val.to_string()
};
let decimal_value = digits.unwrap_or(0) as usize;
let res = format_int(*val, group_digits, decimal_value);
Value::String { val: res, span }
}
Value::Float { val, .. } => {
@ -247,6 +251,15 @@ pub fn action(
val: input.into_string(", ", config),
span,
},
Value::Error { error } => Value::String {
val: {
match into_code(error) {
Some(code) => code,
None => "".to_string(),
}
},
span,
},
Value::Nothing { .. } => Value::String {
val: "".to_string(),
span,
@ -279,21 +292,29 @@ pub fn action(
},
}
}
fn format_int(int: i64) -> String {
int.to_string()
// TODO once platform-specific dependencies are stable (see Cargo.toml)
// #[cfg(windows)]
// {
// int.to_formatted_string(&Locale::en)
// }
// #[cfg(not(windows))]
// {
// match SystemLocale::default() {
// Ok(locale) => int.to_formatted_string(&locale),
// Err(_) => int.to_formatted_string(&Locale::en),
// }
// }
fn format_int(int: i64, group_digits: bool, decimals: usize) -> String {
let locale = get_system_locale();
let str = if group_digits {
int.to_formatted_string(&locale)
} else {
int.to_string()
};
if decimals > 0 {
let decimal_point = locale.decimal();
format!(
"{}{decimal_point}{dummy:0<decimals$}",
str,
decimal_point = decimal_point,
dummy = "",
decimals = decimals
)
} else {
str
}
}
#[cfg(test)]

View File

@ -27,7 +27,7 @@ impl Command for Alias {
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -0,0 +1,77 @@
use nu_engine::CallExt;
use nu_parser::parse;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack, StateWorkingSet},
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
};
#[derive(Clone)]
pub struct Ast;
impl Command for Ast {
fn name(&self) -> &str {
"ast"
}
fn usage(&self) -> &str {
"Print the abstract syntax tree (ast) for a pipeline."
}
fn signature(&self) -> Signature {
Signature::build("ast")
.required(
"pipeline",
SyntaxShape::String,
"the pipeline to print the ast for",
)
.category(Category::Core)
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let pipeline: Spanned<String> = call.req(engine_state, stack, 0)?;
let mut working_set = StateWorkingSet::new(engine_state);
let (output, err) = parse(&mut working_set, None, pipeline.item.as_bytes(), false, &[]);
eprintln!("output: {:#?}\nerror: {:#?}", output, err);
Ok(PipelineData::new(head))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Print the ast of a string",
example: "ast 'hello'",
result: None,
},
Example {
description: "Print the ast of a pipeline",
example: "ast 'ls | where name =~ README'",
result: None,
},
Example {
description: "Print the ast of a pipeline with an error",
example: "ast 'for x in 1..10 { echo $x '",
result: None,
},
]
}
}
#[cfg(test)]
mod test {
#[test]
fn test_examples() {
use super::Ast;
use crate::test_examples;
test_examples(Ast {})
}
}

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

@ -28,7 +28,7 @@ impl Command for Def {
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -28,7 +28,33 @@ impl Command for DefEnv {
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
https://www.nushell.sh/book/thinking_in_nu.html
=== EXTRA NOTE ===
All blocks are scoped, including variable definition and environment variable changes.
Because of this, the following doesn't work:
def-env cd_with_fallback [arg = ""] {
let fall_back_path = "/tmp"
if $arg != "" {
cd $arg
} else {
cd $fall_back_path
}
}
Instead, you have to use cd in the top level scope:
def-env cd_with_fallback [arg = ""] {
let fall_back_path = "/tmp"
let path = if $arg != "" {
$arg
} else {
$fall_back_path
}
cd $path
}"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -1,7 +1,9 @@
use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Value};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value,
};
#[derive(Clone)]
pub struct Do;
@ -23,6 +25,11 @@ impl Command for Do {
"ignore errors as the block runs",
Some('i'),
)
.switch(
"capture-errors",
"capture errors as the block runs and return it",
Some('c'),
)
.rest("rest", SyntaxShape::Any, "the parameter(s) for the block")
.category(Category::Core)
}
@ -37,6 +44,7 @@ impl Command for Do {
let block: CaptureBlock = call.req(engine_state, stack, 0)?;
let rest: Vec<Value> = call.rest(engine_state, stack, 1)?;
let ignore_errors = call.has_flag("ignore-errors");
let capture_errors = call.has_flag("capture-errors");
let mut stack = stack.captures_to_stack(&block.captures);
let block = engine_state.get_block(block.block_id);
@ -85,7 +93,7 @@ impl Command for Do {
block,
input,
call.redirect_stdout,
ignore_errors,
ignore_errors || capture_errors,
);
if ignore_errors {
@ -93,6 +101,11 @@ impl Command for Do {
Ok(x) => Ok(x),
Err(_) => Ok(PipelineData::new(call.head)),
}
} else if capture_errors {
match result {
Ok(x) => Ok(x),
Err(err) => Ok((Value::Error { error: err }).into_pipeline_data()),
}
} else {
result
}

View File

@ -23,6 +23,10 @@ impl Command for Echo {
.category(Category::Core)
}
fn extra_usage(&self) -> &str {
"Unlike `print`, this command returns an actual value that will be passed to the next command of the pipeline."
}
fn run(
&self,
engine_state: &EngineState,

View File

@ -2,8 +2,7 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Value,
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
#[derive(Clone)]
@ -16,7 +15,12 @@ impl Command for ErrorMake {
fn signature(&self) -> Signature {
Signature::build("error make")
.optional("error_struct", SyntaxShape::Record, "the error to create")
.required("error_struct", SyntaxShape::Record, "the error to create")
.switch(
"unspanned",
"remove the origin label from the error",
Some('u'),
)
.category(Category::Core)
}
@ -25,7 +29,7 @@ impl Command for ErrorMake {
}
fn search_terms(&self) -> Vec<&str> {
vec!["err", "panic", "crash", "throw"]
vec!["panic", "crash", "throw"]
}
fn run(
@ -33,42 +37,32 @@ impl Command for ErrorMake {
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let span = call.head;
let ctrlc = engine_state.ctrlc.clone();
let arg: Option<Value> = call.opt(engine_state, stack, 0)?;
let arg: Value = call.req(engine_state, stack, 0)?;
let unspanned = call.has_flag("unspanned");
if let Some(arg) = arg {
Ok(make_error(&arg, span)
.map(|err| Value::Error { error: err })
.unwrap_or_else(|| Value::Error {
error: ShellError::GenericError(
"Creating error value not supported.".into(),
"unsupported error format".into(),
Some(span),
None,
Vec::new(),
),
})
.into_pipeline_data())
if unspanned {
Err(make_error(&arg, None).unwrap_or_else(|| {
ShellError::GenericError(
"Creating error value not supported.".into(),
"unsupported error format".into(),
Some(span),
None,
Vec::new(),
)
}))
} else {
input.map(
move |value| {
make_error(&value, span)
.map(|err| Value::Error { error: err })
.unwrap_or_else(|| Value::Error {
error: ShellError::GenericError(
"Creating error value not supported.".into(),
"unsupported error format".into(),
Some(span),
None,
Vec::new(),
),
})
},
ctrlc,
)
Err(make_error(&arg, Some(span)).unwrap_or_else(|| {
ShellError::GenericError(
"Creating error value not supported.".into(),
"unsupported error format".into(),
Some(span),
None,
Vec::new(),
)
}))
}
}
@ -93,7 +87,7 @@ impl Command for ErrorMake {
}
}
fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
fn make_error(value: &Value, throw_span: Option<Span>) -> Option<ShellError> {
if let Value::Record { .. } = &value {
let msg = value.get_data_by_key("msg");
let label = value.get_data_by_key("label");
@ -130,7 +124,7 @@ fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
) => Some(ShellError::GenericError(
message,
label_text,
Some(throw_span),
throw_span,
None,
Vec::new(),
)),
@ -140,7 +134,7 @@ fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
(Some(Value::String { val: message, .. }), None) => Some(ShellError::GenericError(
message,
"originates from here".to_string(),
Some(throw_span),
throw_span,
None,
Vec::new(),
)),

View File

@ -18,12 +18,12 @@ impl Command for ExportCommand {
}
fn usage(&self) -> &str {
"Export custom commands or environment variables from a module."
"Export definitions or environment variables from a module."
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -27,7 +27,7 @@ impl Command for ExportAlias {
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -28,7 +28,7 @@ impl Command for ExportDef {
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -28,7 +28,33 @@ impl Command for ExportDefEnv {
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
https://www.nushell.sh/book/thinking_in_nu.html
=== EXTRA NOTE ===
All blocks are scoped, including variable definition and environment variable changes.
Because of this, the following doesn't work:
export def-env cd_with_fallback [arg = ""] {
let fall_back_path = "/tmp"
if $arg != "" {
cd $arg
} else {
cd $fall_back_path
}
}
Instead, you have to use cd in the top level scope:
export def-env cd_with_fallback [arg = ""] {
let fall_back_path = "/tmp"
let path = if $arg != "" {
$arg
} else {
$fall_back_path
}
cd $path
}"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -23,7 +23,7 @@ impl Command for ExportExtern {
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -3,35 +3,26 @@ use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
#[derive(Clone)]
pub struct ExportEnv;
pub struct ExportUse;
impl Command for ExportEnv {
impl Command for ExportUse {
fn name(&self) -> &str {
"export env"
"export use"
}
fn usage(&self) -> &str {
"Export a block from a module that will be evaluated as an environment variable when imported."
"Use definitions from a module and export them from this module"
}
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",
)
Signature::build("export use")
.required("pattern", SyntaxShape::ImportPattern, "import pattern")
.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_nushell.html"#
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {
@ -45,16 +36,19 @@ impl Command for ExportEnv {
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"#,
description: "Re-export a command from another module",
example: r#"module spam { export def foo [] { "foo" } }
module eggs { export use spam foo }
use eggs foo
foo
"#,
result: Some(Value::String {
val: "BAZ".to_string(),
val: "foo".to_string(),
span: Span::test_data(),
}),
}]

View File

@ -23,7 +23,7 @@ impl Command for Extern {
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -46,7 +46,7 @@ impl Command for For {
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -1,14 +1,17 @@
use fancy_regex::Regex;
use nu_ansi_term::{
Color::{Default, Red, White},
Style,
};
use nu_color_config::get_color_config;
use nu_engine::{get_full_help, CallExt};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
span, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Spanned, SyntaxShape, Value,
ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
use nu_engine::{get_full_help, CallExt};
use std::borrow::Borrow;
#[derive(Clone)]
pub struct Help;
@ -81,23 +84,29 @@ fn help(
let head = call.head;
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
let commands = engine_state.get_decl_ids_sorted(false);
let config = engine_state.get_config();
let color_hm = get_color_config(config);
let default_style = Style::new().fg(Default).on(Default);
let string_style = match color_hm.get("string") {
Some(style) => style,
None => &default_style,
};
if let Some(f) = find {
let org_search_string = f.item.clone();
let search_string = f.item.to_lowercase();
let mut found_cmds_vec = Vec::new();
for decl_id in commands {
let mut cols = vec![];
let mut vals = vec![];
let decl = engine_state.get_decl(decl_id);
let sig = decl.signature().update_from_command(decl.borrow());
let key = sig.name;
let usage = sig.usage;
let search_terms = sig.search_terms;
let matches_term = if !search_terms.is_empty() {
search_terms
.iter()
@ -106,13 +115,16 @@ fn help(
false
};
if key.to_lowercase().contains(&search_string)
|| usage.to_lowercase().contains(&search_string)
|| matches_term
{
let key_match = key.to_lowercase().contains(&search_string);
let usage_match = usage.to_lowercase().contains(&search_string);
if key_match || usage_match || matches_term {
cols.push("name".into());
vals.push(Value::String {
val: key,
val: if key_match {
highlight_search_string(&key, &org_search_string, string_style)?
} else {
key
},
span: head,
});
@ -130,7 +142,7 @@ fn help(
cols.push("is_custom".into());
vals.push(Value::Bool {
val: decl.get_block_id().is_some(),
val: decl.is_custom_command(),
span: head,
});
@ -142,7 +154,11 @@ fn help(
cols.push("usage".into());
vals.push(Value::String {
val: usage,
val: if usage_match {
highlight_search_string(&usage, &org_search_string, string_style)?
} else {
usage
},
span: head,
});
@ -151,7 +167,30 @@ fn help(
Value::nothing(head)
} else {
Value::String {
val: search_terms.join(", "),
val: if matches_term {
search_terms
.iter()
.map(|term| {
if term.to_lowercase().contains(&search_string) {
match highlight_search_string(
term,
&org_search_string,
string_style,
) {
Ok(s) => s,
Err(_) => {
string_style.paint(term.to_string()).to_string()
}
}
} else {
string_style.paint(term.to_string()).to_string()
}
})
.collect::<Vec<_>>()
.join(", ")
} else {
search_terms.join(", ")
},
span: head,
}
});
@ -204,7 +243,7 @@ fn help(
cols.push("is_custom".into());
vals.push(Value::Bool {
val: decl.get_block_id().is_some(),
val: decl.is_custom_command(),
span: head,
});
@ -303,3 +342,70 @@ You can also learn more at https://www.nushell.sh/book/"#;
.into_pipeline_data())
}
}
// Highlight the search string using ANSI escape sequences and regular expressions.
pub fn highlight_search_string(
haystack: &str,
needle: &str,
string_style: &Style,
) -> Result<String, ShellError> {
let regex_string = format!("(?i){}", needle);
let regex = match Regex::new(&regex_string) {
Ok(regex) => regex,
Err(err) => {
return Err(ShellError::GenericError(
"Could not compile regex".into(),
err.to_string(),
Some(Span::test_data()),
None,
Vec::new(),
));
}
};
// strip haystack to remove existing ansi style
let stripped_haystack: String = match strip_ansi_escapes::strip(haystack) {
Ok(i) => String::from_utf8(i).unwrap_or_else(|_| String::from(haystack)),
Err(_) => String::from(haystack),
};
let mut last_match_end = 0;
let style = Style::new().fg(White).on(Red);
let mut highlighted = String::new();
for cap in regex.captures_iter(stripped_haystack.as_str()) {
match cap {
Ok(capture) => {
let start = match capture.get(0) {
Some(acap) => acap.start(),
None => 0,
};
let end = match capture.get(0) {
Some(acap) => acap.end(),
None => 0,
};
highlighted.push_str(
&string_style
.paint(&stripped_haystack[last_match_end..start])
.to_string(),
);
highlighted.push_str(&style.paint(&stripped_haystack[start..end]).to_string());
last_match_end = end;
}
Err(e) => {
return Err(ShellError::GenericError(
"Error with regular expression capture".into(),
e.to_string(),
None,
None,
Vec::new(),
));
}
}
}
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)]
@ -19,14 +19,14 @@ impl Command for Hide {
}
fn usage(&self) -> &str {
"Hide symbols in the current scope"
"Hide definitions in the current scope"
}
fn extra_usage(&self) -> &str {
r#"Symbols are hidden by priority: First aliases, then custom commands, then environment variables.
r#"Definitions are hidden by priority: First aliases, then custom commands.
This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {
@ -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

@ -0,0 +1,75 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
did_you_mean, Category, Example, PipelineData, ShellError, Signature, Span, Spanned,
SyntaxShape, Value,
};
#[derive(Clone)]
pub struct HideEnv;
impl Command for HideEnv {
fn name(&self) -> &str {
"hide-env"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("hide-env")
.rest(
"name",
SyntaxShape::String,
"environment variable names to hide",
)
.switch(
"ignore-errors",
"do not throw an error if an environment variable was not found",
Some('i'),
)
.category(Category::Core)
}
fn usage(&self) -> &str {
"Hide environment variables in the current scope"
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let env_var_names: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
let ignore_errors = call.has_flag("ignore-errors");
for name in env_var_names {
if stack.remove_env_var(engine_state, &name.item).is_none() && !ignore_errors {
let all_names: Vec<String> = stack
.get_env_var_names(engine_state)
.iter()
.cloned()
.collect();
if let Some(closest_match) = did_you_mean(&all_names, &name.item) {
return Err(ShellError::DidYouMeanCustom(
format!("Environment variable '{}' not found", name.item),
closest_match,
name.span,
));
} else {
return Err(ShellError::EnvVarNotFoundAtRuntime(name.item, name.span));
}
}
}
Ok(PipelineData::new(call.head))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Hide an environment variable",
example: r#"let-env HZ_ENV_ABC = 1; hide-env HZ_ENV_ABC; 'HZ_ENV_ABC' in (env).name"#,
result: Some(Value::boolean(false, Span::test_data())),
}]
}
}

View File

@ -1,131 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, HistoryFileFormat, IntoInterruptiblePipelineData, PipelineData, ShellError,
Signature, Value,
};
use reedline::{
FileBackedHistory, History as ReedlineHistory, SearchDirection, SearchQuery,
SqliteBackedHistory,
};
#[derive(Clone)]
pub struct History;
impl Command for History {
fn name(&self) -> &str {
"history"
}
fn usage(&self) -> &str {
"Get the command history"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("history")
.switch("clear", "Clears out the history entries", Some('c'))
.category(Category::Core)
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
// todo for sqlite history this command should be an alias to `open ~/.config/nushell/history.sqlite3 | get history`
if let Some(config_path) = nu_path::config_dir() {
let clear = call.has_flag("clear");
let ctrlc = engine_state.ctrlc.clone();
let mut history_path = config_path;
history_path.push("nushell");
match engine_state.config.history_file_format {
HistoryFileFormat::Sqlite => {
history_path.push("history.sqlite3");
}
HistoryFileFormat::PlainText => {
history_path.push("history.txt");
}
}
if clear {
let _ = std::fs::remove_file(history_path);
// TODO: FIXME also clear the auxiliary files when using sqlite
Ok(PipelineData::new(head))
} else {
let history_reader: Option<Box<dyn ReedlineHistory>> =
match engine_state.config.history_file_format {
HistoryFileFormat::Sqlite => SqliteBackedHistory::with_file(history_path)
.map(|inner| {
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
boxed
})
.ok(),
HistoryFileFormat::PlainText => FileBackedHistory::with_file(
engine_state.config.max_history_size as usize,
history_path,
)
.map(|inner| {
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
boxed
})
.ok(),
};
let data = history_reader
.and_then(|h| {
h.search(SearchQuery::everything(SearchDirection::Forward))
.ok()
})
.map(move |entries| {
entries
.into_iter()
.enumerate()
.map(move |(idx, entry)| Value::Record {
cols: vec!["command".to_string(), "index".to_string()],
vals: vec![
Value::String {
val: entry.command_line,
span: head,
},
Value::Int {
val: idx as i64,
span: head,
},
],
span: head,
})
})
.ok_or(ShellError::FileNotFound(head))?
.into_pipeline_data(ctrlc);
Ok(data)
}
} else {
Err(ShellError::FileNotFound(head))
}
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "history | length",
description: "Get current history length",
result: None,
},
Example {
example: "history | last 5",
description: "Show last 5 commands you have ran",
result: None,
},
Example {
example: "history | wrap cmd | where cmd =~ cargo",
description: "Search all the commands from history that contains 'cargo'",
result: None,
},
]
}
}

View File

@ -35,7 +35,7 @@ impl Command for If {
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {
@ -92,6 +92,7 @@ impl Command for If {
call.redirect_stdout,
call.redirect_stderr,
)
.map(|res| res.0)
}
} else {
eval_expression_with_input(
@ -102,6 +103,7 @@ impl Command for If {
call.redirect_stdout,
call.redirect_stderr,
)
.map(|res| res.0)
}
} else {
Ok(PipelineData::new(call.head))

View File

@ -18,6 +18,10 @@ impl Command for Ignore {
Signature::build("ignore").category(Category::Core)
}
fn search_terms(&self) -> Vec<&str> {
vec!["silent", "quiet", "out-null"]
}
fn run(
&self,
_engine_state: &EngineState,

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