Compare commits

...

54 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
180 changed files with 2709 additions and 1805 deletions

View File

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

View File

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

64
Cargo.lock generated
View File

@ -167,12 +167,14 @@ dependencies = [
"indexmap",
"json-deserializer",
"lexical-core",
"lz4",
"multiversion",
"num-traits",
"parquet2",
"simdutf8",
"streaming-iterator",
"strength_reduce",
"zstd",
]
[[package]]
@ -703,9 +705,9 @@ dependencies = [
[[package]]
name = "cpufeatures"
version = "0.2.2"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
dependencies = [
"libc",
]
@ -2509,7 +2511,6 @@ dependencies = [
"bitflags",
"cfg-if 1.0.0",
"libc",
"memoffset",
]
[[package]]
@ -2576,7 +2577,7 @@ dependencies = [
[[package]]
name = "nu"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"assert_cmd",
"chrono",
@ -2628,7 +2629,7 @@ dependencies = [
[[package]]
name = "nu-cli"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"atty",
"chrono",
@ -2648,6 +2649,7 @@ dependencies = [
"nu-protocol",
"nu-test-support",
"nu-utils",
"percent-encoding",
"reedline",
"rstest",
"strip-ansi-escapes",
@ -2657,7 +2659,7 @@ dependencies = [
[[package]]
name = "nu-color-config"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"nu-ansi-term",
"nu-json",
@ -2668,7 +2670,7 @@ dependencies = [
[[package]]
name = "nu-command"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"Inflector",
"alphanumeric-sort",
@ -2736,6 +2738,7 @@ dependencies = [
"rstest",
"rusqlite",
"rust-embed",
"same-file",
"serde",
"serde_ini",
"serde_urlencoded",
@ -2762,7 +2765,7 @@ dependencies = [
[[package]]
name = "nu-engine"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"chrono",
"nu-glob",
@ -2775,7 +2778,7 @@ dependencies = [
[[package]]
name = "nu-glob"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"doc-comment",
"tempdir",
@ -2783,7 +2786,7 @@ dependencies = [
[[package]]
name = "nu-json"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"fancy-regex",
"lazy_static",
@ -2796,7 +2799,7 @@ dependencies = [
[[package]]
name = "nu-parser"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"chrono",
"itertools",
@ -2812,7 +2815,7 @@ dependencies = [
[[package]]
name = "nu-path"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"dirs-next",
"dunce",
@ -2821,7 +2824,7 @@ dependencies = [
[[package]]
name = "nu-plugin"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"bincode",
"byte-order",
@ -2837,7 +2840,7 @@ dependencies = [
[[package]]
name = "nu-pretty-hex"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"heapless",
"nu-ansi-term",
@ -2846,7 +2849,7 @@ dependencies = [
[[package]]
name = "nu-protocol"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"byte-unit",
"chrono",
@ -2867,15 +2870,13 @@ dependencies = [
[[package]]
name = "nu-system"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"atty",
"chrono",
"errno",
"libc",
"libproc",
"mach2",
"nix",
"ntapi",
"once_cell",
"procfs",
@ -2884,7 +2885,7 @@ dependencies = [
[[package]]
name = "nu-table"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"atty",
"nu-ansi-term",
@ -2895,7 +2896,7 @@ dependencies = [
[[package]]
name = "nu-term-grid"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"strip-ansi-escapes",
"unicode-width",
@ -2903,7 +2904,7 @@ dependencies = [
[[package]]
name = "nu-test-support"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"getset",
"hamcrest2",
@ -2918,7 +2919,7 @@ dependencies = [
[[package]]
name = "nu-utils"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"crossterm_winapi",
"lscolors",
@ -2938,7 +2939,7 @@ dependencies = [
[[package]]
name = "nu_plugin_example"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"nu-plugin",
"nu-protocol",
@ -2946,7 +2947,7 @@ dependencies = [
[[package]]
name = "nu_plugin_gstat"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"git2",
"nu-engine",
@ -2956,7 +2957,7 @@ dependencies = [
[[package]]
name = "nu_plugin_inc"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"nu-plugin",
"nu-protocol",
@ -2965,7 +2966,7 @@ dependencies = [
[[package]]
name = "nu_plugin_query"
version = "0.68.0"
version = "0.69.0"
dependencies = [
"gjson",
"nu-engine",
@ -4079,8 +4080,7 @@ dependencies = [
[[package]]
name = "reedline"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5559b5ab4817b0da0c6fc6814edfae537209e01d955a2f3e7595606e3d039691"
source = "git+https://github.com/nushell/reedline?branch=main#3e92f97da21d62ded95ef9bd2c42386c8f701b99"
dependencies = [
"chrono",
"crossterm 0.24.0",
@ -4775,9 +4775,9 @@ dependencies = [
[[package]]
name = "sqlparser"
version = "0.16.0"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e9a527b68048eb95495a1508f6c8395c8defcff5ecdbe8ad4106d08a2ef2a3c"
checksum = "0beb13adabbdda01b63d595f38c8bfd19a361e697fd94ce0098a634077bc5b25"
dependencies = [
"log",
"serde",
@ -4966,9 +4966,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.25.2"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1594a36887d0f70096702bffadfb67dfbfe76ad4bf84605e86157dc9fce9961a"
checksum = "4ae2421f3e16b3afd4aa692d23b83d0ba42ee9b0081d5deeb7d21428d7195fb1"
dependencies = [
"cfg-if 1.0.0",
"core-foundation-sys",

View File

@ -11,7 +11,7 @@ name = "nu"
readme = "README.md"
repository = "https://github.com/nushell/nushell"
rust-version = "1.60"
version = "0.68.0"
version = "0.69.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -39,21 +39,22 @@ ctrlc = "3.2.1"
log = "0.4"
miette = "5.1.0"
nu-ansi-term = "0.46.0"
nu-cli = { path="./crates/nu-cli", version = "0.68.0" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.68.0" }
nu-command = { path="./crates/nu-command", version = "0.68.0" }
nu-engine = { path="./crates/nu-engine", version = "0.68.0" }
nu-json = { path="./crates/nu-json", version = "0.68.0" }
nu-parser = { path="./crates/nu-parser", version = "0.68.0" }
nu-path = { path="./crates/nu-path", version = "0.68.0" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.68.0" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.68.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.68.0" }
nu-system = { path = "./crates/nu-system", version = "0.68.0" }
nu-table = { path = "./crates/nu-table", version = "0.68.0" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.68.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.68.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"
@ -65,7 +66,7 @@ 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.68.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"
@ -79,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"]
@ -122,3 +123,5 @@ debug = false
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

View File

@ -5,21 +5,21 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
edition = "2021"
license = "MIT"
name = "nu-cli"
version = "0.68.0"
version = "0.69.0"
[dev-dependencies]
nu-test-support = { path="../nu-test-support", version = "0.68.0" }
nu-command = { path = "../nu-command", version = "0.68.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.68.0" }
nu-path = { path = "../nu-path", version = "0.68.0" }
nu-parser = { path = "../nu-parser", version = "0.68.0" }
nu-protocol = { path = "../nu-protocol", version = "0.68.0" }
nu-utils = { path = "../nu-utils", version = "0.68.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.68.0" }
nu-color-config = { path = "../nu-color-config", version = "0.69.0" }
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
atty = "0.2.14"
@ -31,8 +31,9 @@ is_executable = "1.0.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.25.2"
sysinfo = "0.26.2"
thiserror = "1.0.31"
[features]

View File

@ -422,7 +422,7 @@ fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option<MatchedAl
}
// Push the rest to names vector.
if pos < input.len() {
vec_names.push((&input[pos..]).to_owned());
vec_names.push(input[pos..].to_owned());
}
for name in &vec_names {

View File

@ -666,6 +666,7 @@ fn add_parsed_keybinding(
KeyCode::Char(char)
}
"space" => KeyCode::Char(' '),
"down" => KeyCode::Down,
"up" => KeyCode::Up,
"left" => KeyCode::Left,

View File

@ -14,13 +14,16 @@ use nu_engine::{convert_env_values, eval_block};
use nu_parser::{lex, parse};
use nu_protocol::{
ast::PathMember,
engine::{EngineState, Stack, StateWorkingSet},
engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
Spanned, Type, Value, VarId,
};
use reedline::{DefaultHinter, Emacs, SqliteBackedHistory, Vi};
use std::io::{self, Write};
use std::{sync::atomic::Ordering, time::Instant};
use reedline::{DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
use std::{
io::{self, Write},
sync::atomic::Ordering,
time::Instant,
};
use strip_ansi_escapes::strip;
use sysinfo::SystemExt;
@ -98,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 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,
@ -332,6 +342,7 @@ pub fn evaluate_repl(
match input {
Ok(Signal::Success(s)) => {
let hostname = sys.host_name();
let history_supports_meta =
matches!(config.history_file_format, HistoryFileFormat::Sqlite);
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
@ -339,7 +350,7 @@ pub fn evaluate_repl(
line_editor
.update_last_command_context(&|mut c| {
c.start_timestamp = Some(chrono::Utc::now());
c.hostname = sys.host_name();
c.hostname = hostname.clone();
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
c
@ -347,6 +358,12 @@ pub fn evaluate_repl(
.into_diagnostic()?; // todo: don't stop repl if error here?
}
engine_state
.repl_buffer_state
.lock()
.expect("repl buffer state mutex")
.replace(line_editor.current_buffer_contents().to_string());
// Right before we start running the code the user gave us,
// fire the "pre_execution" hook
if let Some(hook) = config.hooks.pre_execution.clone() {
@ -473,6 +490,21 @@ pub fn evaluate_repl(
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
let path = cwd.as_string()?;
// Communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
run_ansi_sequence(&format!(
"\x1b]7;file://{}{}{}\x1b\\",
percent_encoding::utf8_percent_encode(
&hostname.unwrap_or_else(|| "localhost".to_string()),
percent_encoding::CONTROLS
),
if path.starts_with('/') { "" } else { "/" },
percent_encoding::utf8_percent_encode(
&path,
percent_encoding::CONTROLS
)
))?;
// Try to abbreviate string for windows title
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
path.replace(&p.as_path().display().to_string(), "~")
@ -489,6 +521,24 @@ pub fn evaluate_repl(
}
run_ansi_sequence(RESET_APPLICATION_MODE)?;
}
let mut ops = engine_state
.repl_operation_queue
.lock()
.expect("repl op queue mutex");
while let Some(op) = ops.pop_front() {
match op {
ReplOperation::Append(s) => line_editor.run_edit_commands(&[
EditCommand::MoveToEnd,
EditCommand::InsertString(s),
]),
ReplOperation::Insert(s) => {
line_editor.run_edit_commands(&[EditCommand::InsertString(s)])
}
ReplOperation::Replace(s) => line_editor
.run_edit_commands(&[EditCommand::Clear, EditCommand::InsertString(s)]),
}
}
}
Ok(Signal::CtrlC) => {
// `Reedline` clears the line content. New prompt is shown
@ -612,9 +662,7 @@ pub fn eval_string_with_input(
(output, working_set.render())
};
if let Err(err) = engine_state.merge_delta(delta) {
return Err(err);
}
engine_state.merge_delta(delta)?;
let input_as_pipeline_data = match input {
Some(input) => PipelineData::Value(input, None),

View File

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

View File

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

View File

@ -5,11 +5,11 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
edition = "2021"
license = "MIT"
name = "nu-color-config"
version = "0.68.0"
version = "0.69.0"
[dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.68.0" }
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
nu-ansi-term = "0.46.0"
nu-json = { path = "../nu-json", version = "0.68.0" }
nu-table = { path = "../nu-table", version = "0.68.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

@ -5,25 +5,25 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
edition = "2021"
license = "MIT"
name = "nu-command"
version = "0.68.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.68.0" }
nu-engine = { path = "../nu-engine", version = "0.68.0" }
nu-glob = { path = "../nu-glob", version = "0.68.0" }
nu-json = { path = "../nu-json", version = "0.68.0" }
nu-parser = { path = "../nu-parser", version = "0.68.0" }
nu-path = { path = "../nu-path", version = "0.68.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.68.0" }
nu-protocol = { path = "../nu-protocol", version = "0.68.0" }
nu-system = { path = "../nu-system", version = "0.68.0" }
nu-table = { path = "../nu-table", version = "0.68.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.68.0" }
nu-test-support = { path = "../nu-test-support", version = "0.68.0" }
nu-utils = { path = "../nu-utils", version = "0.68.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" }
@ -70,6 +70,7 @@ rayon = "1.5.1"
reqwest = {version = "0.11", features = ["blocking", "json"] }
roxmltree = "0.14.0"
rust-embed = "6.3.0"
same-file = "1.0.6"
serde = { version="1.0.123", features=["derive"] }
serde_ini = "0.2.0"
serde_urlencoded = "0.7.0"
@ -78,7 +79,7 @@ sha2 = "0.10.0"
# Disable default features b/c the default features build Git (very slow to compile)
shadow-rs = { version = "0.16.1", default-features = false }
strip-ansi-escapes = "0.1.1"
sysinfo = "0.25.2"
sysinfo = "0.26.2"
terminal_size = "0.2.1"
thiserror = "1.0.31"
titlecase = "2.0.0"
@ -90,7 +91,7 @@ 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.16.0", features = ["serde"], optional = true }
sqlparser = { version = "0.23.0", features = ["serde"], optional = true }
[target.'cfg(unix)'.dependencies]
umask = "2.0.0"
@ -115,6 +116,7 @@ features = [
"dtype-struct",
"dtype-categorical",
"dynamic_groupby",
"ipc",
"is_in",
"json",
"lazy",

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

@ -315,9 +315,6 @@ fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, Shel
Unit::Hour => return Ok(x * 60 * 60 * 1000 * 1000 * 1000),
Unit::Day => return Ok(x * 24 * 60 * 60 * 1000 * 1000 * 1000),
Unit::Week => return Ok(x * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000),
Unit::Month => return Ok(x * 30 * 24 * 60 * 60 * 1000 * 1000 * 1000), //30 days to a month
Unit::Year => return Ok(x * 365 * 24 * 60 * 60 * 1000 * 1000 * 1000), //365 days to a year
Unit::Decade => return Ok(x * 10 * 365 * 24 * 60 * 60 * 1000 * 1000 * 1000), //365 days to a year
_ => {}
}
}
@ -353,9 +350,6 @@ fn string_to_unit_duration(
Unit::Hour => return Ok(("hr", x)),
Unit::Day => return Ok(("day", x)),
Unit::Week => return Ok(("wk", x)),
Unit::Month => return Ok(("month", x)), //30 days to a month
Unit::Year => return Ok(("yr", x)), //365 days to a year
Unit::Decade => return Ok(("dec", x)), //365 days to a year
_ => return Ok(("ns", 0)),
}

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

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

View File

@ -362,11 +362,16 @@ pub fn highlight_search_string(
));
}
};
// strip haystack to remove existing ansi style
let stripped_haystack: String = match strip_ansi_escapes::strip(haystack) {
Ok(i) => String::from_utf8(i).unwrap_or_else(|_| String::from(haystack)),
Err(_) => String::from(haystack),
};
let mut last_match_end = 0;
let style = Style::new().fg(White).on(Red);
let mut highlighted = String::new();
for cap in regex.captures_iter(haystack) {
for cap in regex.captures_iter(stripped_haystack.as_str()) {
match cap {
Ok(capture) => {
let start = match capture.get(0) {
@ -379,10 +384,10 @@ pub fn highlight_search_string(
};
highlighted.push_str(
&string_style
.paint(&haystack[last_match_end..start])
.paint(&stripped_haystack[last_match_end..start])
.to_string(),
);
highlighted.push_str(&style.paint(&haystack[start..end]).to_string());
highlighted.push_str(&style.paint(&stripped_haystack[start..end]).to_string());
last_match_end = end;
}
Err(e) => {
@ -397,6 +402,10 @@ pub fn highlight_search_string(
}
}
highlighted.push_str(&string_style.paint(&haystack[last_match_end..]).to_string());
highlighted.push_str(
&string_style
.paint(&stripped_haystack[last_match_end..])
.to_string(),
);
Ok(highlighted)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,12 +21,6 @@ impl Command for Register {
SyntaxShape::Filepath,
"path of executable for plugin",
)
.required_named(
"encoding",
SyntaxShape::String,
"Encoding used to communicate with plugin. Options: [json, msgpack]",
Some('e'),
)
.optional(
"signature",
SyntaxShape::Any,
@ -64,12 +58,12 @@ impl Command for Register {
vec![
Example {
description: "Register `nu_plugin_query` plugin from ~/.cargo/bin/ dir",
example: r#"register -e json ~/.cargo/bin/nu_plugin_query"#,
example: r#"register ~/.cargo/bin/nu_plugin_query"#,
result: None,
},
Example {
description: "Register `nu_plugin_query` plugin from `nu -c`(plugin will be available in that nu session only)",
example: r#"let plugin = ((which nu).path.0 | path dirname | path join 'nu_plugin_query'); nu -c $'register -e json ($plugin); version'"#,
example: r#"let plugin = ((which nu).path.0 | path dirname | path join 'nu_plugin_query'); nu -c $'register ($plugin); version'"#,
result: None,
},
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -339,7 +339,7 @@ impl ExprDb {
Expr::TypedString { .. } => todo!(),
Expr::MapAccess { .. } => todo!(),
Expr::Case { .. } => todo!(),
Expr::Exists(_) => todo!(),
Expr::Exists { .. } => todo!(),
Expr::Subquery(_) => todo!(),
Expr::ListAgg(_) => todo!(),
Expr::GroupingSets(_) => todo!(),
@ -348,6 +348,25 @@ impl ExprDb {
Expr::Tuple(_) => todo!(),
Expr::ArrayIndex { .. } => todo!(),
Expr::Array(_) => todo!(),
Expr::JsonAccess { .. } => todo!(),
Expr::CompositeAccess { .. } => todo!(),
Expr::IsFalse(_) => todo!(),
Expr::IsNotFalse(_) => todo!(),
Expr::IsTrue(_) => todo!(),
Expr::IsNotTrue(_) => todo!(),
Expr::IsUnknown(_) => todo!(),
Expr::IsNotUnknown(_) => todo!(),
Expr::Like { .. } => todo!(),
Expr::ILike { .. } => todo!(),
Expr::SimilarTo { .. } => todo!(),
Expr::AnyOp(_) => todo!(),
Expr::AllOp(_) => todo!(),
Expr::SafeCast { .. } => todo!(),
Expr::AtTimeZone { .. } => todo!(),
Expr::Position { .. } => todo!(),
Expr::Overlay { .. } => todo!(),
Expr::AggregateExpressionWithFilter { .. } => todo!(),
Expr::ArraySubquery(_) => todo!(),
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,6 +30,7 @@ pub fn create_default_context() -> EngineState {
bind_command! {
Alias,
Ast,
Commandline,
Debug,
Def,
DefEnv,
@ -41,7 +42,6 @@ pub fn create_default_context() -> EngineState {
ExportCommand,
ExportDef,
ExportDefEnv,
ExportEnvModule,
ExportExtern,
ExportUse,
Extern,
@ -137,6 +137,7 @@ pub fn create_default_context() -> EngineState {
bind_command! {
History,
Tutor,
HistorySession,
};
// Path
@ -200,6 +201,7 @@ pub fn create_default_context() -> EngineState {
StrDistance,
StrDowncase,
StrEndswith,
StrJoin,
StrReplace,
StrIndexOf,
StrKebabCase,

View File

@ -1,11 +1,10 @@
use std::path::PathBuf;
use nu_engine::{eval_block, find_in_dirs_env, redirect_env, CallExt};
use nu_parser::parse;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, CliError, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value,
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value,
};
/// Source a file for environment variables.
@ -40,96 +39,47 @@ impl Command for SourceEnv {
) -> Result<PipelineData, ShellError> {
let source_filename: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
if let Some(path) = find_in_dirs_env(&source_filename.item, engine_state, caller_stack)? {
if let Ok(content) = std::fs::read_to_string(&path) {
let mut parent = PathBuf::from(&path);
parent.pop();
// Note: this hidden positional is the block_id that corresponded to the 0th position
// it is put here by the parser
let block_id: i64 = call.req(engine_state, caller_stack, 1)?;
let mut new_engine_state = engine_state.clone();
let (block, delta) = {
let mut working_set = StateWorkingSet::new(&new_engine_state);
// Set the currently parsed directory
working_set.currently_parsed_cwd = Some(parent.clone());
let (block, err) = parse(&mut working_set, None, content.as_bytes(), true, &[]);
if let Some(err) = err {
// Because the error span points at new_engine_state, we must create the error message now
let msg = format!(
r#"Found this parser error: {:?}"#,
CliError(&err, &working_set)
);
return Err(ShellError::GenericError(
"Failed to parse content".to_string(),
"cannot parse this file".to_string(),
Some(source_filename.span),
Some(msg),
vec![],
));
} else {
(block, working_set.render())
}
};
// Merge parser changes to a temporary engine state
new_engine_state.merge_delta(delta)?;
// Set the currently evaluated directory
let file_pwd = Value::String {
val: parent.to_string_lossy().to_string(),
span: call.head,
};
caller_stack.add_env_var("FILE_PWD".to_string(), file_pwd);
// Evaluate the parsed file's block
let mut callee_stack = caller_stack.gather_captures(&block.captures);
let result = eval_block(
&new_engine_state,
&mut callee_stack,
&block,
input,
true,
true,
);
let result = if let Err(err) = result {
// Because the error span points at new_engine_state, we must create the error message now
let working_set = StateWorkingSet::new(&new_engine_state);
let msg = format!(
r#"Found this shell error: {:?}"#,
CliError(&err, &working_set)
);
Err(ShellError::GenericError(
"Failed to evaluate content".to_string(),
"cannot evaluate this file".to_string(),
Some(source_filename.span),
Some(msg),
vec![],
))
} else {
result
};
// Merge the block's environment to the current stack
redirect_env(engine_state, caller_stack, &callee_stack);
// Remove the file-relative PWD
caller_stack.remove_env_var(engine_state, "FILE_PWD");
result
} else {
Err(ShellError::FileNotFound(source_filename.span))
}
// Set the currently evaluated directory (file-relative PWD)
let mut parent = if let Some(path) =
find_in_dirs_env(&source_filename.item, engine_state, caller_stack)?
{
PathBuf::from(&path)
} else {
Err(ShellError::FileNotFound(source_filename.span))
}
return Err(ShellError::FileNotFound(source_filename.span));
};
parent.pop();
let file_pwd = Value::String {
val: parent.to_string_lossy().to_string(),
span: call.head,
};
caller_stack.add_env_var("FILE_PWD".to_string(), file_pwd);
// Evaluate the block
let block = engine_state.get_block(block_id as usize).clone();
let mut callee_stack = caller_stack.gather_captures(&block.captures);
let result = eval_block(
engine_state,
&mut callee_stack,
&block,
input,
call.redirect_stdout,
call.redirect_stderr,
);
// Merge the block's environment to the current stack
redirect_env(engine_state, caller_stack, &callee_stack);
// Remove the file-relative PWD
caller_stack.remove_env_var(engine_state, "FILE_PWD");
result
}
fn examples(&self) -> Vec<Example> {

View File

@ -110,7 +110,7 @@ fn with_env(
// primitive values([X Y W Z])
for row in table.chunks(2) {
if row.len() == 2 {
env.insert(row[0].as_string()?, (&row[1]).clone());
env.insert(row[0].as_string()?, row[1].clone());
}
// TODO: else error?
}

View File

@ -14,7 +14,7 @@ use crate::To;
#[cfg(test)]
use super::{
Ansi, Date, From, If, Into, LetEnv, Math, Path, Random, Split, SplitColumn, SplitRow, Str,
StrCollect, StrLength, StrReplace, Url, Wrap,
StrJoin, StrLength, StrReplace, Url, Wrap,
};
#[cfg(test)]
@ -29,7 +29,7 @@ pub fn test_examples(cmd: impl Command + 'static) {
// Try to keep this working set small to keep tests running as fast as possible
let mut working_set = StateWorkingSet::new(&*engine_state);
working_set.add_decl(Box::new(Str));
working_set.add_decl(Box::new(StrCollect));
working_set.add_decl(Box::new(StrJoin));
working_set.add_decl(Box::new(StrLength));
working_set.add_decl(Box::new(StrReplace));
working_set.add_decl(Box::new(BuildString));

View File

@ -189,9 +189,9 @@ impl Command for ViewSource {
},
Example {
description: "View the source of a module",
example: r#"module mod-foo { export env FOO_ENV { 'BAZ' } }; view-source mod-foo"#,
example: r#"module mod-foo { export-env { let-env FOO_ENV = 'BAZ' } }; view-source mod-foo"#,
result: Some(Value::String {
val: " export env FOO_ENV { 'BAZ' }".to_string(),
val: " export-env { let-env FOO_ENV = 'BAZ' }".to_string(),
span: Span::test_data(),
}),
},

View File

@ -154,10 +154,7 @@ impl Command for Mv {
}
if let Some(Ok(_filename)) = some_if_source_is_destination {
sources = sources
.into_iter()
.filter(|f| matches!(f, Ok(f) if !destination.starts_with(f)))
.collect();
sources.retain(|f| matches!(f, Ok(f) if !destination.starts_with(f)));
}
let span = call.head;
@ -253,8 +250,12 @@ fn move_file(
return Err(ShellError::DirectoryNotFound(to_span, None));
}
// This can happen when changing case on a case-insensitive filesystem (ex: changing foo to Foo on Windows)
// When it does, we want to do a plain rename instead of moving `from` into `to`
let from_to_are_same_file = same_file::is_same_file(&from, &to).unwrap_or(false);
let mut to = to;
if to.is_dir() {
if !from_to_are_same_file && to.is_dir() {
let from_file_name = match from.file_name() {
Some(name) => name,
None => return Err(ShellError::DirectoryNotFound(to_span, None)),

View File

@ -36,7 +36,7 @@ impl Command for Save {
Signature::build("save")
.required("filename", SyntaxShape::Filepath, "the filename to use")
.switch("raw", "save file as raw binary", Some('r'))
.switch("append", "append input to the end of the file", None)
.switch("append", "append input to the end of the file", Some('a'))
.category(Category::FileSystem)
}

View File

@ -18,17 +18,17 @@ impl Command for All {
.required(
"predicate",
SyntaxShape::RowCondition,
"the predicate that must match",
"the predicate expression that must evaluate to a boolean",
)
.category(Category::Filters)
}
fn usage(&self) -> &str {
"Test if every element of the input matches a predicate."
"Test if every element of the input fulfills a predicate expression."
}
fn search_terms(&self) -> Vec<&str> {
vec!["every"]
vec!["every", "and"]
}
fn examples(&self) -> Vec<Example> {

View File

@ -18,17 +18,17 @@ impl Command for Any {
.required(
"predicate",
SyntaxShape::RowCondition,
"the predicate that must match",
"the predicate expression that should return a boolean",
)
.category(Category::Filters)
}
fn usage(&self) -> &str {
"Tests if any element of the input matches a predicate."
"Tests if any element of the input fulfills a predicate expression."
}
fn search_terms(&self) -> Vec<&str> {
vec!["some"]
vec!["some", "or"]
}
fn examples(&self) -> Vec<Example> {

View File

@ -1,7 +1,7 @@
use crate::help::highlight_search_string;
use fancy_regex::Regex;
use lscolors::Style as LsColors_Style;
use nu_ansi_term::{Color::Default, Style};
use lscolors::{Color as LsColors_Color, Style as LsColors_Style};
use nu_ansi_term::{Color, Color::Default, Style};
use nu_color_config::get_color_config;
use nu_engine::{env_to_string, eval_block, CallExt};
use nu_protocol::{
@ -384,10 +384,15 @@ fn find_with_rest_and_highlight(
let ls_colored_val =
ansi_style.apply(&val_str).to_string();
let ansi_term_style = style
.map(to_nu_ansi_term_style)
.unwrap_or_else(|| string_style);
let hi = match highlight_search_string(
&ls_colored_val,
&term_str,
&string_style,
&ansi_term_style,
) {
Ok(hi) => hi,
Err(_) => string_style
@ -535,6 +540,47 @@ fn find_with_rest_and_highlight(
}
}
fn to_nu_ansi_term_style(style: &LsColors_Style) -> Style {
fn to_nu_ansi_term_color(color: &LsColors_Color) -> Color {
match *color {
LsColors_Color::Fixed(n) => Color::Fixed(n),
LsColors_Color::RGB(r, g, b) => Color::Rgb(r, g, b),
LsColors_Color::Black => Color::Black,
LsColors_Color::Red => Color::Red,
LsColors_Color::Green => Color::Green,
LsColors_Color::Yellow => Color::Yellow,
LsColors_Color::Blue => Color::Blue,
LsColors_Color::Magenta => Color::Magenta,
LsColors_Color::Cyan => Color::Cyan,
LsColors_Color::White => Color::White,
// Below items are a rough translations to 256 colors as
// nu-ansi-term do not have bright varients
LsColors_Color::BrightBlack => Color::Fixed(8),
LsColors_Color::BrightRed => Color::Fixed(9),
LsColors_Color::BrightGreen => Color::Fixed(10),
LsColors_Color::BrightYellow => Color::Fixed(11),
LsColors_Color::BrightBlue => Color::Fixed(12),
LsColors_Color::BrightMagenta => Color::Fixed(13),
LsColors_Color::BrightCyan => Color::Fixed(14),
LsColors_Color::BrightWhite => Color::Fixed(15),
}
}
Style {
foreground: style.foreground.as_ref().map(to_nu_ansi_term_color),
background: style.background.as_ref().map(to_nu_ansi_term_color),
is_bold: style.font_style.bold,
is_dimmed: style.font_style.dimmed,
is_italic: style.font_style.italic,
is_underline: style.font_style.underline,
is_blink: style.font_style.slow_blink || style.font_style.rapid_blink,
is_reverse: style.font_style.reverse,
is_hidden: style.font_style.hidden,
is_strikethrough: style.font_style.strikethrough,
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -28,7 +28,7 @@ impl Command for Get {
.rest("rest", SyntaxShape::CellPath, "additional cell paths")
.switch(
"ignore-errors",
"return nothing if path can't be found",
"when there are empty cells, instead of erroring out, replace them with nothing",
Some('i'),
)
.switch(

View File

@ -17,6 +17,11 @@ impl Command for Select {
// FIXME: also add support for --skip
fn signature(&self) -> Signature {
Signature::build("select")
.switch(
"ignore-errors",
"when a column has empty cells, instead of erroring out, replace them with nothing",
Some('i'),
)
.rest(
"rest",
SyntaxShape::CellPath,
@ -42,8 +47,9 @@ impl Command for Select {
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let columns: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let span = call.head;
let ignore_empty = call.has_flag("ignore-errors");
select(engine_state, span, columns, input)
select(engine_state, span, columns, input, ignore_empty)
}
fn examples(&self) -> Vec<Example> {
@ -67,6 +73,7 @@ fn select(
span: Span,
columns: Vec<CellPath>,
input: PipelineData,
ignore_empty: bool,
) -> Result<PipelineData, ShellError> {
let mut rows = vec![];
@ -121,6 +128,7 @@ fn select(
..,
) => {
let mut output = vec![];
let mut columns_with_value = Vec::new();
for input_val in input_vals {
if !columns.is_empty() {
@ -128,10 +136,25 @@ fn select(
let mut vals = vec![];
for path in &columns {
//FIXME: improve implementation to not clone
let fetcher = input_val.clone().follow_cell_path(&path.members, false)?;
if ignore_empty {
let fetcher = input_val.clone().follow_cell_path(&path.members, false);
cols.push(path.into_string().replace('.', "_"));
vals.push(fetcher);
cols.push(path.into_string().replace('.', "_"));
if let Ok(fetcher) = fetcher {
vals.push(fetcher);
if !columns_with_value.contains(&path) {
columns_with_value.push(path);
}
} else {
vals.push(Value::nothing(span));
}
} else {
let fetcher =
input_val.clone().follow_cell_path(&path.members, false)?;
cols.push(path.into_string().replace('.', "_"));
vals.push(fetcher);
}
}
output.push(Value::Record { cols, vals, span })

View File

@ -14,23 +14,31 @@ impl Command for Uniq {
fn signature(&self) -> Signature {
Signature::build("uniq")
.switch("count", "Count the unique rows", Some('c'))
.switch(
"count",
"Return a table containing the distinct input values together with their counts",
Some('c'),
)
.switch(
"repeated",
"Count the rows that has more than one value",
"Return the input values that occur more than once",
Some('d'),
)
.switch(
"ignore-case",
"Ignore differences in case when comparing",
"Ignore differences in case when comparing input values",
Some('i'),
)
.switch("unique", "Only return unique values", Some('u'))
.switch(
"unique",
"Return the input values that occur once only",
Some('u'),
)
.category(Category::Filters)
}
fn usage(&self) -> &str {
"Return the unique rows."
"Return the distinct values in the input."
}
fn search_terms(&self) -> Vec<&str> {
@ -50,7 +58,7 @@ impl Command for Uniq {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Remove duplicate rows of a list/table",
description: "Return the distinct values of a list/table (remove duplicates so that each value occurs once only)",
example: "[2 3 3 4] | uniq",
result: Some(Value::List {
vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(4)],
@ -58,7 +66,7 @@ impl Command for Uniq {
}),
},
Example {
description: "Only print duplicate lines, one for each group",
description: "Return the input values that occur more than once",
example: "[1 2 2] | uniq -d",
result: Some(Value::List {
vals: vec![Value::test_int(2)],
@ -66,7 +74,7 @@ impl Command for Uniq {
}),
},
Example {
description: "Only print unique lines lines",
description: "Return the input values that occur once only",
example: "[1 2 2] | uniq -u",
result: Some(Value::List {
vals: vec![Value::test_int(1)],
@ -74,7 +82,7 @@ impl Command for Uniq {
}),
},
Example {
description: "Ignore differences in case when comparing",
description: "Ignore differences in case when comparing input values",
example: "['hello' 'goodbye' 'Hello'] | uniq -i",
result: Some(Value::List {
vals: vec![Value::test_string("hello"), Value::test_string("goodbye")],
@ -82,7 +90,7 @@ impl Command for Uniq {
}),
},
Example {
description: "Remove duplicate rows and show counts of a list/table",
description: "Return a table containing the distinct input values together with their counts",
example: "[1 2 2] | uniq -c",
result: Some(Value::List {
vals: vec![

View File

@ -62,7 +62,7 @@ impl Command for Upsert {
result: Some(Value::List { vals: vec![Value::Record { cols: vec!["count".into(), "fruit".into()], vals: vec![Value::test_int(2), Value::test_string("apple")], span: Span::test_data()}], span: Span::test_data()}),
}, Example {
description: "Use in block form for more involved updating logic",
example: "echo [[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | upsert authors {|a| $a.authors | str collect ','}",
example: "echo [[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | upsert authors {|a| $a.authors | str join ','}",
result: Some(Value::List { vals: vec![Value::Record { cols: vec!["project".into(), "authors".into()], vals: vec![Value::test_string("nu"), Value::test_string("Andrés,JT,Yehuda")], span: Span::test_data()}], span: Span::test_data()}),
}]
}

View File

@ -526,35 +526,6 @@ fn convert_to_value(
expr.span,
)),
},
Unit::Month => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 30) {
Some(val) => Ok(Value::Duration { val, span }),
None => Err(ShellError::OutsideSpannedLabeledError(
original_text.to_string(),
"month duration too large".into(),
"month duration too large".into(),
expr.span,
)),
},
Unit::Year => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 365) {
Some(val) => Ok(Value::Duration { val, span }),
None => Err(ShellError::OutsideSpannedLabeledError(
original_text.to_string(),
"year duration too large".into(),
"year duration too large".into(),
expr.span,
)),
},
Unit::Decade => {
match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 365 * 10) {
Some(val) => Ok(Value::Duration { val, span }),
None => Err(ShellError::OutsideSpannedLabeledError(
original_text.to_string(),
"decade duration too large".into(),
"decade duration too large".into(),
expr.span,
)),
}
}
}
}
Expr::Var(..) => Err(ShellError::OutsideSpannedLabeledError(

View File

@ -110,7 +110,7 @@ fn fragment(input: Value, pretty: bool, config: &Config) -> String {
let mut out = String::new();
if headers.len() == 1 {
let markup = match (&headers[0]).to_ascii_lowercase().as_ref() {
let markup = match headers[0].to_ascii_lowercase().as_ref() {
"h1" => "# ".to_string(),
"h2" => "## ".to_string(),
"h3" => "### ".to_string(),

View File

@ -0,0 +1,43 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, IntoPipelineData, PipelineData, Signature, Value};
#[derive(Clone)]
pub struct HistorySession;
impl Command for HistorySession {
fn name(&self) -> &str {
"history session"
}
fn usage(&self) -> &str {
"Get the command history session"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("history session").category(Category::Misc)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "history session",
description: "Get current history session",
result: None,
}]
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(Value::Record {
cols: vec!["session-id".into()],
vals: vec![Value::int(engine_state.history_session_id, call.head)],
span: call.head,
}
.into_pipeline_data())
}
}

View File

@ -1,5 +1,7 @@
mod history;
mod history_session;
mod tutor;
pub use history::History;
pub use history_session::HistorySession;
pub use tutor::Tutor;

View File

@ -427,7 +427,7 @@ fn helper(
// primitive values ([key1 val1 key2 val2])
for row in table.chunks(2) {
if row.len() == 2 {
custom_headers.insert(row[0].as_string()?, (&row[1]).clone());
custom_headers.insert(row[0].as_string()?, row[1].clone());
}
}
}

View File

@ -281,7 +281,7 @@ fn helper(
// primitive values ([key1 val1 key2 val2])
for row in table.chunks(2) {
if row.len() == 2 {
custom_headers.insert(row[0].as_string()?, (&row[1]).clone());
custom_headers.insert(row[0].as_string()?, row[1].clone());
}
}
}

View File

@ -141,7 +141,7 @@ lazy_static! {
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
// Another good reference http://ascii-table.com/ansi-escape-sequences.php
// For setting title like `echo [(char title) (pwd) (char bel)] | str collect`
// For setting title like `echo [(char title) (pwd) (char bel)] | str join`
AnsiCode{short_name: None, long_name:"title", code: "\x1b]2;".to_string()}, // ESC]2; xterm sets window title using OSC syntax escapes
// Ansi Erase Sequences
@ -258,7 +258,7 @@ following values:
https://en.wikipedia.org/wiki/ANSI_escape_code
OSC: '\x1b]' is not required for --osc parameter
Example: echo [(ansi -o '0') 'some title' (char bel)] | str collect
Example: echo [(ansi -o '0') 'some title' (char bel)] | str join
Format: #
0 Set window title and icon name
1 Set icon name
@ -285,14 +285,14 @@ Format: #
Example {
description:
"Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)",
example: r#"echo [(ansi rb) Hello " " (ansi gb) Nu " " (ansi pb) World (ansi reset)] | str collect"#,
example: r#"echo [(ansi rb) Hello " " (ansi gb) Nu " " (ansi pb) World (ansi reset)] | str join"#,
result: Some(Value::test_string(
"\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld\u{1b}[0m",
)),
},
Example {
description: "Use ansi to color text (italic bright yellow on red 'Hello' with green bold 'Nu' and purple bold 'World')",
example: r#"echo [(ansi -e '3;93;41m') Hello (ansi reset) " " (ansi gb) Nu " " (ansi pb) World (ansi reset)] | str collect"#,
example: r#"echo [(ansi -e '3;93;41m') Hello (ansi reset) " " (ansi gb) Nu " " (ansi pb) World (ansi reset)] | str join"#,
result: Some(Value::test_string(
"\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld\u{1b}[0m",
)),

View File

@ -40,7 +40,7 @@ impl Command for SubCommand {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Strip ANSI escape sequences from a string",
example: r#"echo [ (ansi green) (ansi cursor_on) "hello" ] | str collect | ansi strip"#,
example: r#"echo [ (ansi green) (ansi cursor_on) "hello" ] | str join | ansi strip"#,
result: Some(Value::test_string("hello")),
}]
}

View File

@ -23,24 +23,31 @@ impl Command for Clear {
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let span = call.head;
if cfg!(windows) {
CommandSys::new("cmd")
.args(["/C", "cls"])
.status()
.expect("failed to execute process");
.map_err(|e| ShellError::IOErrorSpanned(e.to_string(), span))?;
} else if cfg!(unix) {
CommandSys::new("/bin/sh")
.args(["-c", "clear"])
let mut cmd = CommandSys::new("/bin/sh");
if let Some(Value::String { val, .. }) = stack.get_env_var(engine_state, "TERM") {
cmd.env("TERM", val);
}
cmd.args(["-c", "clear"])
.status()
.expect("failed to execute process");
.map_err(|e| ShellError::IOErrorSpanned(e.to_string(), span))?;
}
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
Ok(Value::Nothing { span }.into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {

View File

@ -185,7 +185,7 @@ impl Command for Char {
},
Example {
description: "Output prompt character, newline and a hamburger character",
example: r#"echo [(char prompt) (char newline) (char hamburger)] | str collect"#,
example: r#"echo [(char prompt) (char newline) (char hamburger)] | str join"#,
result: Some(Value::test_string("\u{25b6}\n\u{2261}")),
},
Example {

View File

@ -25,11 +25,7 @@ impl Command for StrCollect {
}
fn usage(&self) -> &str {
"Concatenate multiple strings into a single string, with an optional separator between each"
}
fn search_terms(&self) -> Vec<&str> {
vec!["join", "concatenate"]
"'str collect' is deprecated. Please use 'str join' instead."
}
fn run(

View File

@ -17,11 +17,11 @@ impl Command for SubCommand {
fn signature(&self) -> Signature {
Signature::build("str contains")
.required("pattern", SyntaxShape::String, "the pattern to find")
.required("string", SyntaxShape::String, "the string to find")
.rest(
"rest",
SyntaxShape::CellPath,
"optionally check if string contains pattern by column paths",
"optionally check if input contains string by column paths",
)
.switch("insensitive", "search is case insensitive", Some('i'))
.switch("not", "does not contain", Some('n'))
@ -29,11 +29,11 @@ impl Command for SubCommand {
}
fn usage(&self) -> &str {
"Checks if string contains pattern"
"Checks if input contains string"
}
fn search_terms(&self) -> Vec<&str> {
vec!["pattern", "match", "find", "search"]
vec!["substring", "match", "find", "search"]
}
fn run(
@ -49,7 +49,7 @@ impl Command for SubCommand {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Check if string contains pattern",
description: "Check if input contains string",
example: "'my_library.rb' | str contains '.rb'",
result: Some(Value::Bool {
val: true,
@ -57,7 +57,7 @@ impl Command for SubCommand {
}),
},
Example {
description: "Check if string contains pattern case insensitive",
description: "Check if input contains string case insensitive",
example: "'my_library.rb' | str contains -i '.RB'",
result: Some(Value::Bool {
val: true,
@ -65,7 +65,7 @@ impl Command for SubCommand {
}),
},
Example {
description: "Check if string contains pattern in a table",
description: "Check if input contains string in a table",
example: " [[ColA ColB]; [test 100]] | str contains 'e' ColA",
result: Some(Value::List {
vals: vec![Value::Record {
@ -83,7 +83,7 @@ impl Command for SubCommand {
}),
},
Example {
description: "Check if string contains pattern in a table",
description: "Check if input contains string in a table",
example: " [[ColA ColB]; [test 100]] | str contains -i 'E' ColA",
result: Some(Value::List {
vals: vec![Value::Record {
@ -101,7 +101,7 @@ impl Command for SubCommand {
}),
},
Example {
description: "Check if string contains pattern in a table",
description: "Check if input contains string in a table",
example: " [[ColA ColB]; [test hello]] | str contains 'e' ColA ColB",
result: Some(Value::List {
vals: vec![Value::Record {
@ -122,7 +122,7 @@ impl Command for SubCommand {
}),
},
Example {
description: "Check if string contains pattern",
description: "Check if input string contains 'banana'",
example: "'hello' | str contains 'banana'",
result: Some(Value::Bool {
val: false,
@ -130,7 +130,7 @@ impl Command for SubCommand {
}),
},
Example {
description: "Check if list contains pattern",
description: "Check if list contains string",
example: "[one two three] | str contains o",
result: Some(Value::List {
vals: vec![
@ -151,7 +151,7 @@ impl Command for SubCommand {
}),
},
Example {
description: "Check if list does not contain pattern",
description: "Check if list does not contain string",
example: "[one two three] | str contains -n o",
result: Some(Value::List {
vals: vec![
@ -182,7 +182,7 @@ fn operate(
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let pattern: Spanned<String> = call.req(engine_state, stack, 0)?;
let substring: Spanned<String> = call.req(engine_state, stack, 0)?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let case_insensitive = call.has_flag("insensitive");
let not_contain = call.has_flag("not");
@ -190,11 +190,11 @@ fn operate(
input.map(
move |v| {
if column_paths.is_empty() {
action(&v, case_insensitive, not_contain, &pattern.item, head)
action(&v, case_insensitive, not_contain, &substring.item, head)
} else {
let mut ret = v;
for path in &column_paths {
let p = pattern.item.clone();
let p = substring.item.clone();
let r = ret.update_cell_path(
&path.members,
Box::new(move |old| action(old, case_insensitive, not_contain, &p, head)),
@ -214,7 +214,7 @@ fn action(
input: &Value,
case_insensitive: bool,
not_contain: bool,
pattern: &str,
substring: &str,
head: Span,
) -> Value {
match input {
@ -222,16 +222,18 @@ fn action(
val: match case_insensitive {
true => {
if not_contain {
!val.to_lowercase().contains(pattern.to_lowercase().as_str())
!val.to_lowercase()
.contains(substring.to_lowercase().as_str())
} else {
val.to_lowercase().contains(pattern.to_lowercase().as_str())
val.to_lowercase()
.contains(substring.to_lowercase().as_str())
}
}
false => {
if not_contain {
!val.contains(pattern)
!val.contains(substring)
} else {
val.contains(pattern)
val.contains(substring)
}
}
},

View File

@ -30,7 +30,7 @@ impl Command for SubCommand {
}
fn usage(&self) -> &str {
"compare to strings and return the edit distance/levenshtein distance"
"compare two strings and return the edit distance/levenshtein distance"
}
fn search_terms(&self) -> Vec<&str> {

View File

@ -16,7 +16,7 @@ impl Command for SubCommand {
fn signature(&self) -> Signature {
Signature::build("str ends-with")
.required("pattern", SyntaxShape::String, "the pattern to match")
.required("string", SyntaxShape::String, "the string to match")
.rest(
"rest",
SyntaxShape::CellPath,
@ -26,11 +26,11 @@ impl Command for SubCommand {
}
fn usage(&self) -> &str {
"Check if a string ends with a pattern"
"Check if an input ends with a string"
}
fn search_terms(&self) -> Vec<&str> {
vec!["pattern", "match", "find", "search"]
vec!["suffix", "match", "find", "search"]
}
fn run(
@ -46,7 +46,7 @@ impl Command for SubCommand {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Checks if string ends with '.rb' pattern",
description: "Checks if string ends with '.rb'",
example: "'my_library.rb' | str ends-with '.rb'",
result: Some(Value::Bool {
val: true,
@ -54,7 +54,7 @@ impl Command for SubCommand {
}),
},
Example {
description: "Checks if string ends with '.txt' pattern",
description: "Checks if string ends with '.txt'",
example: "'my_library.rb' | str ends-with '.txt'",
result: Some(Value::Bool {
val: false,
@ -72,17 +72,17 @@ fn operate(
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let pattern: Spanned<String> = call.req(engine_state, stack, 0)?;
let substring: Spanned<String> = call.req(engine_state, stack, 0)?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
input.map(
move |v| {
if column_paths.is_empty() {
action(&v, &pattern.item, head)
action(&v, &substring.item, head)
} else {
let mut ret = v;
for path in &column_paths {
let p = pattern.item.clone();
let p = substring.item.clone();
let r = ret.update_cell_path(
&path.members,
Box::new(move |old| action(old, &p, head)),
@ -98,10 +98,10 @@ fn operate(
)
}
fn action(input: &Value, pattern: &str, head: Span) -> Value {
fn action(input: &Value, substring: &str, head: Span) -> Value {
match input {
Value::String { val, .. } => Value::Bool {
val: val.ends_with(pattern),
val: val.ends_with(substring),
span: head,
},
other => Value::Error {

View File

@ -9,7 +9,7 @@ use std::sync::Arc;
struct Arguments {
end: bool,
pattern: String,
substring: String,
range: Option<Value>,
column_paths: Vec<CellPath>,
}
@ -27,15 +27,11 @@ impl Command for SubCommand {
fn signature(&self) -> Signature {
Signature::build("str index-of")
.required(
"pattern",
SyntaxShape::String,
"the pattern to find index of",
)
.required("string", SyntaxShape::String, "the string to find index of")
.rest(
"rest",
SyntaxShape::CellPath,
"optionally returns index of pattern in string by column paths",
"optionally returns index of string in input by column paths",
)
.named(
"range",
@ -43,16 +39,16 @@ impl Command for SubCommand {
"optional start and/or end index",
Some('r'),
)
.switch("end", "search from the end of the string", Some('e'))
.switch("end", "search from the end of the input", Some('e'))
.category(Category::Strings)
}
fn usage(&self) -> &str {
"Returns start index of first occurrence of pattern in string, or -1 if no match"
"Returns start index of first occurrence of string in input, or -1 if no match"
}
fn search_terms(&self) -> Vec<&str> {
vec!["pattern", "match", "find", "search"]
vec!["match", "find", "search"]
}
fn run(
@ -68,22 +64,22 @@ impl Command for SubCommand {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Returns index of pattern in string",
description: "Returns index of string in input",
example: " 'my_library.rb' | str index-of '.rb'",
result: Some(Value::test_int(10)),
},
Example {
description: "Returns index of pattern in string with start index",
description: "Returns index of string in input with start index",
example: " '.rb.rb' | str index-of '.rb' -r '1,'",
result: Some(Value::test_int(3)),
},
Example {
description: "Returns index of pattern in string with end index",
description: "Returns index of string in input with end index",
example: " '123456' | str index-of '6' -r ',4'",
result: Some(Value::test_int(-1)),
},
Example {
description: "Returns index of pattern in string with start and end index",
description: "Returns index of string in input with start and end index",
example: " '123456' | str index-of '3' -r '1,4'",
result: Some(Value::test_int(2)),
},
@ -93,7 +89,7 @@ impl Command for SubCommand {
result: Some(Value::test_int(2)),
},
Example {
description: "Returns index of pattern in string",
description: "Returns index of string in input",
example: " '/this/is/some/path/file.txt' | str index-of '/' -e",
result: Some(Value::test_int(18)),
},
@ -107,10 +103,10 @@ fn operate(
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let pattern: Spanned<String> = call.req(engine_state, stack, 0)?;
let substring: Spanned<String> = call.req(engine_state, stack, 0)?;
let options = Arc::new(Arguments {
pattern: pattern.item,
substring: substring.item,
range: call.get_flag(engine_state, stack, "range")?,
end: call.has_flag("end"),
column_paths: call.rest(engine_state, stack, 1)?,
@ -142,7 +138,7 @@ fn operate(
fn action(
input: &Value,
Arguments {
ref pattern,
ref substring,
range,
end,
..
@ -167,7 +163,7 @@ fn action(
};
if *end {
if let Some(result) = s[start_index..end_index].rfind(&**pattern) {
if let Some(result) = s[start_index..end_index].rfind(&**substring) {
Value::Int {
val: result as i64 + start_index as i64,
span: head,
@ -178,7 +174,7 @@ fn action(
span: head,
}
}
} else if let Some(result) = s[start_index..end_index].find(&**pattern) {
} else if let Some(result) = s[start_index..end_index].find(&**substring) {
Value::Int {
val: result as i64 + start_index as i64,
span: head,
@ -292,7 +288,7 @@ mod tests {
};
let options = Arguments {
pattern: String::from(".tomL"),
substring: String::from(".tomL"),
range: Some(Value::String {
val: String::from(""),
@ -314,7 +310,7 @@ mod tests {
};
let options = Arguments {
pattern: String::from("Lm"),
substring: String::from("Lm"),
range: Some(Value::String {
val: String::from(""),
@ -337,7 +333,7 @@ mod tests {
};
let options = Arguments {
pattern: String::from("Cargo"),
substring: String::from("Cargo"),
range: Some(Value::String {
val: String::from("1"),
@ -359,7 +355,7 @@ mod tests {
};
let options = Arguments {
pattern: String::from("Banana"),
substring: String::from("Banana"),
range: Some(Value::String {
val: String::from(",5"),
@ -381,7 +377,7 @@ mod tests {
};
let options = Arguments {
pattern: String::from("123"),
substring: String::from("123"),
range: Some(Value::String {
val: String::from("2,6"),
@ -403,7 +399,7 @@ mod tests {
};
let options = Arguments {
pattern: String::from("1"),
substring: String::from("1"),
range: Some(Value::String {
val: String::from("2,4"),

View File

@ -0,0 +1,106 @@
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)]
pub struct StrJoin;
impl Command for StrJoin {
fn name(&self) -> &str {
"str join"
}
fn signature(&self) -> Signature {
Signature::build("str join")
.optional(
"separator",
SyntaxShape::String,
"optional separator to use when creating string",
)
.category(Category::Strings)
}
fn usage(&self) -> &str {
"Concatenate multiple strings into a single string, with an optional separator between each"
}
fn search_terms(&self) -> Vec<&str> {
vec!["collect", "concatenate"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let separator: Option<String> = call.opt(engine_state, stack, 0)?;
let config = engine_state.get_config();
// let output = input.collect_string(&separator.unwrap_or_default(), &config)?;
// Hmm, not sure what we actually want. If you don't use debug_string, Date comes out as human readable
// which feels funny
let mut strings: Vec<String> = vec![];
for value in input {
match value {
Value::Error { error } => {
return Err(error);
}
value => {
strings.push(value.debug_string("\n", config));
}
}
}
let output = if let Some(separator) = separator {
strings.join(&separator)
} else {
strings.join("")
};
Ok(Value::String {
val: output,
span: call.head,
}
.into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Create a string from input",
example: "['nu', 'shell'] | str join",
result: Some(Value::String {
val: "nushell".to_string(),
span: Span::test_data(),
}),
},
Example {
description: "Create a string from input with a separator",
example: "['nu', 'shell'] | str join '-'",
result: Some(Value::String {
val: "nu-shell".to_string(),
span: Span::test_data(),
}),
},
]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(StrJoin {})
}
}

View File

@ -74,10 +74,10 @@ impl Command for SubCommand {
}),
},
Example {
description: "Use lpad to truncate a string",
description: "Use lpad to truncate a string to its last three characters",
example: "'123456789' | str lpad -l 3 -c '0'",
result: Some(Value::String {
val: "123".to_string(),
val: "789".to_string(),
span: Span::test_data(),
}),
},
@ -105,6 +105,13 @@ fn operate(
column_paths: call.rest(engine_state, stack, 0)?,
});
if options.length.expect("this exists") < 0 {
return Err(ShellError::UnsupportedInput(
String::from("The length of the string cannot be negative"),
call.head,
));
}
let head = call.head;
input.map(
move |v| {
@ -142,7 +149,14 @@ fn action(
let s = *x as usize;
if s < val.len() {
Value::String {
val: val.chars().take(s).collect::<String>(),
val: val
.chars()
.rev()
.take(s)
.collect::<String>()
.chars()
.rev()
.collect::<String>(),
span: head,
}
} else {

View File

@ -4,6 +4,7 @@ mod contains;
mod distance;
mod ends_with;
mod index_of;
mod join;
mod length;
mod lpad;
mod replace;
@ -19,6 +20,7 @@ pub use contains::SubCommand as StrContains;
pub use distance::SubCommand as StrDistance;
pub use ends_with::SubCommand as StrEndswith;
pub use index_of::SubCommand as StrIndexOf;
pub use join::*;
pub use length::SubCommand as StrLength;
pub use lpad::SubCommand as StrLpad;
pub use replace::SubCommand as StrReplace;

View File

@ -74,7 +74,7 @@ impl Command for SubCommand {
}),
},
Example {
description: "Use rpad to truncate a string",
description: "Use rpad to truncate a string to its first three characters",
example: "'123456789' | str rpad -l 3 -c '0'",
result: Some(Value::String {
val: "123".to_string(),
@ -105,6 +105,13 @@ fn operate(
column_paths: call.rest(engine_state, stack, 0)?,
});
if options.length.expect("this exists") < 0 {
return Err(ShellError::UnsupportedInput(
String::from("The length of the string cannot be negative"),
call.head,
));
}
let head = call.head;
input.map(
move |v| {

View File

@ -8,7 +8,7 @@ use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShap
use std::sync::Arc;
struct Arguments {
pattern: String,
substring: String,
column_paths: Vec<CellPath>,
}
@ -23,7 +23,7 @@ impl Command for SubCommand {
fn signature(&self) -> Signature {
Signature::build("str starts-with")
.required("pattern", SyntaxShape::String, "the pattern to match")
.required("string", SyntaxShape::String, "the string to match")
.rest(
"rest",
SyntaxShape::CellPath,
@ -33,11 +33,11 @@ impl Command for SubCommand {
}
fn usage(&self) -> &str {
"Check if string starts with a pattern"
"Check if an input starts with a string"
}
fn search_terms(&self) -> Vec<&str> {
vec!["pattern", "match", "find", "search"]
vec!["prefix", "match", "find", "search"]
}
fn run(
@ -53,7 +53,7 @@ impl Command for SubCommand {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Checks if string starts with 'my' pattern",
description: "Checks if input string starts with 'my'",
example: "'my_library.rb' | str starts-with 'my'",
result: Some(Value::Bool {
val: true,
@ -61,7 +61,7 @@ impl Command for SubCommand {
}),
},
Example {
description: "Checks if string starts with 'my' pattern",
description: "Checks if input string starts with 'my'",
example: "'Cargo.toml' | str starts-with 'Car'",
result: Some(Value::Bool {
val: true,
@ -69,7 +69,7 @@ impl Command for SubCommand {
}),
},
Example {
description: "Checks if string starts with 'my' pattern",
description: "Checks if input string starts with 'my'",
example: "'Cargo.toml' | str starts-with '.toml'",
result: Some(Value::Bool {
val: false,
@ -86,10 +86,10 @@ fn operate(
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let pattern: Spanned<String> = call.req(engine_state, stack, 0)?;
let substring: Spanned<String> = call.req(engine_state, stack, 0)?;
let options = Arc::new(Arguments {
pattern: pattern.item,
substring: substring.item,
column_paths: call.rest(engine_state, stack, 1)?,
});
let head = call.head;
@ -116,10 +116,10 @@ fn operate(
)
}
fn action(input: &Value, Arguments { pattern, .. }: &Arguments, head: Span) -> Value {
fn action(input: &Value, Arguments { substring, .. }: &Arguments, head: Span) -> Value {
match input {
Value::String { val: s, .. } => {
let starts_with = s.starts_with(pattern);
let starts_with = s.starts_with(substring);
Value::Bool {
val: starts_with,
span: head,

View File

@ -7,7 +7,6 @@ use nu_protocol::did_you_mean;
use nu_protocol::engine::{EngineState, Stack};
use nu_protocol::{ast::Call, engine::Command, ShellError, Signature, SyntaxShape, Value};
use nu_protocol::{Category, Example, ListStream, PipelineData, RawStream, Span, Spanned};
use nu_system::ForegroundProcess;
use pathdiff::diff_paths;
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Write};
@ -142,7 +141,7 @@ impl ExternalCommand {
let ctrlc = engine_state.ctrlc.clone();
let mut fg_process = ForegroundProcess::new(self.create_process(&input, false, head)?);
let mut process = self.create_process(&input, false, head)?;
// mut is used in the windows branch only, suppress warning on other platforms
#[allow(unused_mut)]
let mut child;
@ -157,7 +156,8 @@ impl ExternalCommand {
// fails to be run as a normal executable:
// 1. "shell out" to cmd.exe if the command is a known cmd.exe internal command
// 2. Otherwise, use `which-rs` to look for batch files etc. then run those in cmd.exe
match fg_process.spawn() {
match process.spawn() {
Err(err) => {
// set the default value, maybe we'll override it later
child = Err(err);
@ -174,8 +174,7 @@ impl ExternalCommand {
.any(|&cmd| command_name_upper == cmd);
if looks_like_cmd_internal {
let mut cmd_process =
ForegroundProcess::new(self.create_process(&input, true, head)?);
let mut cmd_process = self.create_process(&input, true, head)?;
child = cmd_process.spawn();
} else {
#[cfg(feature = "which-support")]
@ -203,10 +202,8 @@ impl ExternalCommand {
item: file_name.to_string_lossy().to_string(),
span: self.name.span,
};
let mut cmd_process = ForegroundProcess::new(
new_command
.create_process(&input, true, head)?,
);
let mut cmd_process = new_command
.create_process(&input, true, head)?;
child = cmd_process.spawn();
}
}
@ -224,7 +221,7 @@ impl ExternalCommand {
#[cfg(not(windows))]
{
child = fg_process.spawn()
child = process.spawn()
}
match child {
@ -276,7 +273,7 @@ impl ExternalCommand {
engine_state.config.use_ansi_coloring = false;
// if there is a string or a stream, that is sent to the pipe std
if let Some(mut stdin_write) = child.as_mut().stdin.take() {
if let Some(mut stdin_write) = child.stdin.take() {
std::thread::spawn(move || {
let input = crate::Table::run(
&crate::Table,
@ -317,7 +314,7 @@ impl ExternalCommand {
// and we create a ListStream that can be consumed
if redirect_stderr {
let stderr = child.as_mut().stderr.take().ok_or_else(|| {
let stderr = child.stderr.take().ok_or_else(|| {
ShellError::ExternalCommand(
"Error taking stderr from external".to_string(),
"Redirects need access to stderr of an external command"
@ -356,7 +353,7 @@ impl ExternalCommand {
}
if redirect_stdout {
let stdout = child.as_mut().stdout.take().ok_or_else(|| {
let stdout = child.stdout.take().ok_or_else(|| {
ShellError::ExternalCommand(
"Error taking stdout from external".to_string(),
"Redirects need access to stdout of an external command"
@ -394,7 +391,7 @@ impl ExternalCommand {
}
}
match child.as_mut().wait() {
match child.wait() {
Err(err) => Err(ShellError::ExternalCommand(
"External command exited with error".into(),
err.to_string(),

View File

@ -276,43 +276,43 @@ pub fn mem(sys: &mut System, span: Span) -> Option<Value> {
cols.push("total".into());
vals.push(Value::Filesize {
val: total_mem as i64 * 1000,
val: total_mem as i64,
span,
});
cols.push("free".into());
vals.push(Value::Filesize {
val: free_mem as i64 * 1000,
val: free_mem as i64,
span,
});
cols.push("used".into());
vals.push(Value::Filesize {
val: used_mem as i64 * 1000,
val: used_mem as i64,
span,
});
cols.push("available".into());
vals.push(Value::Filesize {
val: avail_mem as i64 * 1000,
val: avail_mem as i64,
span,
});
cols.push("swap total".into());
vals.push(Value::Filesize {
val: total_swap as i64 * 1000,
val: total_swap as i64,
span,
});
cols.push("swap free".into());
vals.push(Value::Filesize {
val: free_swap as i64 * 1000,
val: free_swap as i64,
span,
});
cols.push("swap used".into());
vals.push(Value::Filesize {
val: used_swap as i64 * 1000,
val: used_swap as i64,
span,
});

View File

@ -1,4 +1,4 @@
use lscolors::Style;
use lscolors::{LsColors, Style};
use nu_color_config::{get_color_config, style_primitive};
use nu_engine::{column::get_columns, env_to_string, CallExt};
use nu_protocol::{
@ -261,10 +261,6 @@ fn handle_row_stream(
};
let ls_colors = get_ls_colors(ls_colors_env_str);
// clickable links don't work in remote SSH sessions
let in_ssh_session = std::env::var("SSH_CLIENT").is_ok();
let show_clickable_links = config.show_clickable_links_in_ls && !in_ssh_session;
ListStream::from_stream(
stream.map(move |mut x| match &mut x {
Value::Record { cols, vals, .. } => {
@ -273,62 +269,10 @@ fn handle_row_stream(
while idx < cols.len() {
if cols[idx] == "name" {
if let Some(Value::String { val: path, span }) = vals.get(idx) {
match std::fs::symlink_metadata(&path) {
Ok(metadata) => {
let style = ls_colors.style_for_path_with_metadata(
path.clone(),
Some(&metadata),
);
let ansi_style = style
.map(Style::to_crossterm_style)
// .map(ToNuAnsiStyle::to_nu_ansi_style)
.unwrap_or_default();
let use_ls_colors = config.use_ls_colors;
let full_path = PathBuf::from(path.clone())
.canonicalize()
.unwrap_or_else(|_| PathBuf::from(path));
let full_path_link = make_clickable_link(
full_path.display().to_string(),
Some(&path.clone()),
show_clickable_links,
);
if use_ls_colors {
vals[idx] = Value::String {
val: ansi_style
.apply(full_path_link)
.to_string(),
span: *span,
};
}
}
Err(_) => {
let style = ls_colors.style_for_path(path.clone());
let ansi_style = style
.map(Style::to_crossterm_style)
// .map(ToNuAnsiStyle::to_nu_ansi_style)
.unwrap_or_default();
let use_ls_colors = config.use_ls_colors;
let full_path = PathBuf::from(path.clone())
.canonicalize()
.unwrap_or_else(|_| PathBuf::from(path));
let full_path_link = make_clickable_link(
full_path.display().to_string(),
Some(&path.clone()),
show_clickable_links,
);
if use_ls_colors {
vals[idx] = Value::String {
val: ansi_style
.apply(full_path_link)
.to_string(),
span: *span,
};
}
}
if let Some(val) =
render_path_name(path, &config, &ls_colors, *span)
{
vals[idx] = val;
}
}
}
@ -413,10 +357,7 @@ fn convert_to_table(
// The header with the INDEX is removed from the table headers since
// it is added to the natural table index
headers = headers
.into_iter()
.filter(|header| header != INDEX_COLUMN_NAME)
.collect();
headers.retain(|header| header != INDEX_COLUMN_NAME);
// Vec of Vec of String1, String2 where String1 is datatype and String2 is value
let mut data: Vec<Vec<(String, String)>> = Vec::new();
@ -464,7 +405,7 @@ fn convert_to_table(
match result {
Ok(value) => row.push((
(&value.get_type()).to_string(),
value.get_type().to_string(),
value.into_abbreviated_string(config),
)),
Err(_) => row.push(("empty".to_string(), "".into())),
@ -629,3 +570,49 @@ fn load_theme_from_config(config: &Config) -> TableTheme {
_ => nu_table::TableTheme::rounded(),
}
}
fn render_path_name(
path: &String,
config: &Config,
ls_colors: &LsColors,
span: Span,
) -> Option<Value> {
if !config.use_ls_colors {
return None;
}
let stripped_path = match strip_ansi_escapes::strip(path) {
Ok(v) => String::from_utf8(v).unwrap_or_else(|_| path.to_owned()),
Err(_) => path.to_owned(),
};
let (style, has_metadata) = match std::fs::symlink_metadata(&stripped_path) {
Ok(metadata) => (
ls_colors.style_for_path_with_metadata(&stripped_path, Some(&metadata)),
true,
),
Err(_) => (ls_colors.style_for_path(&stripped_path), false),
};
// clickable links don't work in remote SSH sessions
let in_ssh_session = std::env::var("SSH_CLIENT").is_ok();
let show_clickable_links = config.show_clickable_links_in_ls && !in_ssh_session && has_metadata;
let ansi_style = style
.map(Style::to_crossterm_style)
// .map(ToNuAnsiStyle::to_nu_ansi_style)
.unwrap_or_default();
let full_path = PathBuf::from(&stripped_path)
.canonicalize()
.unwrap_or_else(|_| PathBuf::from(&stripped_path));
let full_path_link = make_clickable_link(
full_path.display().to_string(),
Some(path),
show_clickable_links,
);
let val = ansi_style.apply(full_path_link).to_string();
Some(Value::String { val, span })
}

View File

@ -65,7 +65,7 @@ fn each_implicit_it_in_block() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
echo [[foo bar]; [a b] [c d] [e f]] | each { |it| nu --testbin cococo $it.foo } | str collect
echo [[foo bar]; [a b] [c d] [e f]] | each { |it| nu --testbin cococo $it.foo } | str join
"#
));

View File

@ -11,7 +11,7 @@ fn flatten_nested_tables_with_columns() {
[[origin, people]; [Nu, ('nuno' | wrap name)]]
| flatten --all | flatten --all
| get name
| str collect ','
| str join ','
"#
));
@ -27,7 +27,7 @@ fn flatten_nested_tables_that_have_many_columns() {
[[origin, people]; [USA, (echo [[name, meal]; ['Katz', 'nurepa']])]]
| flatten --all | flatten --all
| get meal
| str collect ','
| str join ','
"#
));

View File

@ -137,8 +137,8 @@ fn errors_fetching_by_column_not_present() {
sandbox.with_files(vec![FileWithContent(
"sample.toml",
r#"
[taconushell]
sentence_words = ["Yo", "quiero", "taconushell"]
[tacos]
sentence_words = ["Yo", "quiero", "tacos"]
[pizzanushell]
sentence-words = ["I", "want", "pizza"]
"#,
@ -153,7 +153,7 @@ fn errors_fetching_by_column_not_present() {
));
assert!(actual.err.contains("Name not found"),);
assert!(actual.err.contains("did you mean 'taconushell'"),);
assert!(actual.err.contains("did you mean 'tacos'"),);
})
}

View File

@ -335,14 +335,11 @@ fn duration_decimal_math_with_all_units() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
5dec + 3yr + 2month + 1wk + 3day + 8hr + 10min + 16sec + 121ms + 11us + 12ns
1wk + 3day + 8hr + 10min + 16sec + 121ms + 11us + 12ns
"#
));
assert_eq!(
actual.out,
"53yr 2month 1wk 3day 8hr 10min 16sec 121ms 11µs 12ns"
);
assert_eq!(actual.out, "1wk 3day 8hr 10min 16sec 121ms 11µs 12ns");
}
#[test]

View File

@ -26,7 +26,7 @@ fn moves_a_column_before() {
| rename chars
| get chars
| str trim
| str collect
| str join
"#
));
@ -59,9 +59,9 @@ fn moves_columns_before() {
| move column99 column3 --before column2
| rename _ chars_1 chars_2
| select chars_2 chars_1
| upsert new_col {|f| $f | transpose | get column1 | str trim | str collect}
| upsert new_col {|f| $f | transpose | get column1 | str trim | str join}
| get new_col
| str collect
| str join
"#
));
@ -95,9 +95,9 @@ fn moves_a_column_after() {
| move letters and_more --before column2
| rename _ chars_1 chars_2
| select chars_1 chars_2
| upsert new_col {|f| $f | transpose | get column1 | str trim | str collect}
| upsert new_col {|f| $f | transpose | get column1 | str trim | str join}
| get new_col
| str collect
| str join
"#
));
@ -130,7 +130,7 @@ fn moves_columns_after() {
| move letters and_more --after column1
| columns
| select 1 2
| str collect
| str join
"#
));

View File

@ -393,3 +393,61 @@ fn mv_directory_with_same_name() {
assert!(actual.err.contains("Directory not empty"));
})
}
#[test]
// Test that changing the case of a file/directory name works;
// this is an important edge case on Windows (and any other case-insensitive file systems).
// We were bitten badly by this once: https://github.com/nushell/nushell/issues/6583
fn mv_change_case_of_directory() {
Playground::setup("mv_change_case_of_directory", |dirs, sandbox| {
sandbox
.mkdir("somedir")
.with_files(vec![EmptyFile("somedir/somefile.txt")]);
let original_dir = String::from("somedir");
let new_dir = String::from("SomeDir");
nu!(
cwd: dirs.test(),
format!("mv {original_dir} {new_dir}")
);
// Doing this instead of `Path::exists()` because we need to check file existence in
// a case-sensitive way. `Path::exists()` is understandably case-insensitive on NTFS
let files_in_test_directory: Vec<String> = std::fs::read_dir(dirs.test())
.unwrap()
.map(|de| de.unwrap().file_name().to_string_lossy().into_owned())
.collect();
assert!(!files_in_test_directory.contains(&original_dir));
assert!(files_in_test_directory.contains(&new_dir));
assert!(files_exist_at(
vec!["somefile.txt",],
dirs.test().join(new_dir)
));
})
}
#[test]
fn mv_change_case_of_file() {
Playground::setup("mv_change_case_of_file", |dirs, sandbox| {
sandbox.with_files(vec![EmptyFile("somefile.txt")]);
let original_file_name = String::from("somefile.txt");
let new_file_name = String::from("SomeFile.txt");
nu!(
cwd: dirs.test(),
format!("mv {original_file_name} {new_file_name}")
);
// Doing this instead of `Path::exists()` because we need to check file existence in
// a case-sensitive way. `Path::exists()` is understandably case-insensitive on NTFS
let files_in_test_directory: Vec<String> = std::fs::read_dir(dirs.test())
.unwrap()
.map(|de| de.unwrap().file_name().to_string_lossy().into_owned())
.collect();
assert!(!files_in_test_directory.contains(&original_file_name));
assert!(files_in_test_directory.contains(&new_file_name));
})
}

View File

@ -230,7 +230,7 @@ fn parse_module_success_2() {
r#"
# foo.nu
export env MYNAME { "Arthur, King of the Britons" }
export-env { let-env MYNAME = "Arthur, King of the Britons" }
"#,
)]);

View File

@ -208,6 +208,22 @@ fn parses_utf16_ini() {
assert_eq!(actual.out, "-236")
}
#[cfg(feature = "database")]
#[test]
fn parses_arrow_ipc() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
open-df caco3_plastics.arrow
| into nu
| first 1
| get origin
"#
));
assert_eq!(actual.out, "SPAIN")
}
#[test]
fn errors_if_file_not_found() {
let actual = nu!(

View File

@ -13,7 +13,7 @@ fn regular_columns() {
]
| reject type first_name
| columns
| str collect ", "
| str join ", "
"#
));
@ -56,7 +56,7 @@ fn complex_nested_columns() {
| reject nu."0xATYKARNU" nu.committers
| get nu
| columns
| str collect ", "
| str join ", "
"#,
));
@ -75,7 +75,7 @@ fn ignores_duplicate_columns_rejected() {
]
| reject "first name" "first name"
| columns
| str collect ", "
| str join ", "
"#
));

View File

@ -69,7 +69,7 @@ mod columns {
format!("{} | {}", table(), pipeline(r#"
roll left
| columns
| str collect "-"
| str join "-"
"#)));
assert_eq!(actual.out, "origin-stars-commit_author");
@ -82,7 +82,7 @@ mod columns {
format!("{} | {}", table(), pipeline(r#"
roll right --by 2
| columns
| str collect "-"
| str join "-"
"#)));
assert_eq!(actual.out, "origin-stars-commit_author");
@ -97,7 +97,7 @@ mod columns {
let actual = nu!(
cwd: ".",
format!("{} | roll right --by 3 --cells-only | columns | str collect '-' ", four_bitstring)
format!("{} | roll right --by 3 --cells-only | columns | str join '-' ", four_bitstring)
);
assert_eq!(actual.out, expected_value.1);

View File

@ -25,7 +25,7 @@ fn counter_clockwise() {
]
| where column0 == EXPECTED
| get column1 column2 column3
| str collect "-"
| str join "-"
"#,
));
@ -35,7 +35,7 @@ fn counter_clockwise() {
rotate --ccw
| where column0 == EXPECTED
| get column1 column2 column3
| str collect "-"
| str join "-"
"#)));
assert_eq!(actual.out, expected.out);
@ -66,7 +66,7 @@ fn clockwise() {
]
| where column3 == EXPECTED
| get column0 column1 column2
| str collect "-"
| str join "-"
"#,
));
@ -76,7 +76,7 @@ fn clockwise() {
rotate
| where column3 == EXPECTED
| get column0 column1 column2
| str collect "-"
| str join "-"
"#)));
assert_eq!(actual.out, expected.out);

View File

@ -186,6 +186,20 @@ fn external_arg_with_variable_name() {
})
}
#[test]
fn external_command_escape_args() {
Playground::setup("external failed command with semicolon", |dirs, _| {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
^echo "\"abcd"
"#
));
assert_eq!(actual.out, r#""abcd"#);
})
}
#[cfg(windows)]
#[test]
fn explicit_glob_windows() {
@ -244,72 +258,6 @@ fn failed_command_with_semicolon_will_not_execute_following_cmds_windows() {
})
}
#[cfg(windows)]
#[test]
#[ignore = "fails on local Windows machines"]
// This test case might fail based on the running shell on Windows - CMD vs PowerShell, the reason is
//
// Test command 1 - `dir * `
// Test command 2 - `dir '*'`
// Test command 3 - `dir "*"`
//
// In CMD, command 2 and 3 will give you an error of 'File Not Found'
// In Poweshell, all three commands will do the path expansion with any errors whatsoever
//
// With current Windows CI build(Microsoft Windows 2022 with version 10.0.20348),
// the unit test runs agaisnt PowerShell
fn double_quote_does_not_expand_path_glob_windows() {
Playground::setup("double quote do not run the expansion", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("D&D_volume_1.txt"),
EmptyFile("D&D_volume_2.txt"),
EmptyFile("foo.sh"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
dir "*.txt"
"#
));
assert!(actual.out.contains("D&D_volume_1.txt"));
assert!(actual.out.contains("D&D_volume_2.txt"));
})
}
#[cfg(windows)]
#[test]
#[ignore = "fails on local Windows machines"]
// This test case might fail based on the running shell on Windows - CMD vs PowerShell, the reason is
//
// Test command 1 - `dir * `
// Test command 2 - `dir '*'`
// Test command 3 - `dir "*"`
//
// In CMD, command 2 and 3 will give you an error of 'File Not Found'
// In Poweshell, all three commands will do the path expansion with any errors whatsoever
//
// With current Windows CI build(Microsoft Windows 2022 with version 10.0.20348),
// the unit test runs agaisnt PowerShell
fn single_quote_does_not_expand_path_glob_windows() {
Playground::setup("single quote do not run the expansion", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("D&D_volume_1.txt"),
EmptyFile("D&D_volume_2.txt"),
EmptyFile("foo.sh"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
dir '*.txt'
"#
));
assert!(actual.out.contains("D&D_volume_1.txt"));
assert!(actual.out.contains("D&D_volume_2.txt"));
});
}
#[cfg(windows)]
#[test]
fn can_run_batch_files() {

View File

@ -96,7 +96,7 @@ fn column_names_with_spaces() {
]
| select "last name"
| get "last name"
| str collect " "
| str join " "
"#
));
@ -115,7 +115,7 @@ fn ignores_duplicate_columns_selected() {
]
| select "first name" "last name" "first name"
| columns
| str collect " "
| str join " "
"#
));
@ -159,3 +159,27 @@ fn selects_many_rows() {
assert_eq!(actual.out, "2");
});
}
#[test]
fn select_ignores_errors_succesfully1() {
let actual = nu!(
cwd: ".", pipeline(
r#"
[{a: 1, b: 2} {a: 3, b: 5} {a: 3}] | select -i b
"#
));
assert!(actual.err.is_empty());
}
#[test]
fn select_ignores_errors_succesfully2() {
let actual = nu!(
cwd: ".", pipeline(
r#"
[{a: 1} {a: 2} {a: 3}] | select -i b
"#
));
assert!(actual.err.is_empty());
}

View File

@ -36,7 +36,7 @@ fn condition_is_met() {
| lines
| skip 2
| str trim
| str collect (char nl)
| str join (char nl)
| from csv
| skip until "Chicken Collection" == "Red Chickens"
| skip 1

View File

@ -36,7 +36,7 @@ fn condition_is_met() {
| lines
| skip 2
| str trim
| str collect (char nl)
| str join (char nl)
| from csv
| skip while "Chicken Collection" != "Red Chickens"
| skip 1

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