Compare commits

..

100 Commits

Author SHA1 Message Date
JT
9ef65dcd69 Bump to 0.70 (#6800) 2022-10-19 07:13:36 +13:00
JT
f99c002426 Fix let-env in banner (#6795) 2022-10-18 22:42:00 +13:00
f0420c5a6c Pin reedline to the 0.13.0 release (#6789)
See the release notes:

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

* Pull reedline from the most recent main branch

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

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

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

* Run `cargo fmt --all`

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

* Treat empty glob argument as error

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

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

* Add tests for glob command

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

* Rename glob sanity check star test

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

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

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

* fix highlighting

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

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

* Add search terms

* Add search terms

* Change to parse

* Add search terms for from

* Add search terms for to

* Remove duplicate function

* Remove duplication of search terms

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

* fix for vars, overlays, env_hidden

* fmt

* carry over variables properly

* add test

* modify name, remove redundant

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

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

* try replace from sh to bash

* change description

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

Fixes #6644 

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

* make failed_with_proper_exit_code work again

* add test case for table

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

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

* cross platform tweaks

* return nushell datatype

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

* Fix "to nuon" not escaping double quotations

* Fix "to nuon" double backslash

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

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

* nu-table/ Fix first column alignment

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

* nu-table: Fix cargo clippy

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

* nu-table: Fix color issue

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

* nu-table: Fix footer row

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

* nu-table: Bump tabled

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

* nu-table: Bump tabled

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

* nu-table: Bump tabled

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

* Update

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

* nu-table/ Update

* Use latest tabled

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

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

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

* Fix clippy

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

* Fix clippy

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

* Update

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

* Fix cargo clippy

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

* Fix tests

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

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

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

* Publish new expand mode

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

* Add width ctrl for Expand mode

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

* Refactorings

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

* Refactorings

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

* Add tests

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

* Add tests

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

* Merge with main

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

* Fix clippy

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

* Fix tests

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

* Fix tests

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

* Bump tabled

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

* Add record expand and fix empty list issue

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

* refactoring

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

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

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

* refactor, make consume external result logic out of eval_external

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

This reverts commit 2bb367f570.

* Make foreground job control hopefully work correctly

These changes are mostly inspired by the glibc manual.

* Fix typo in external command description

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

* Rework terminal acquirement code to be like fish

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

* add two unit tests

* fix pipe

* add cp test

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

* add test

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

* better behaviour

* fix tests

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

* more changes

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

* missed filesize in floordiv

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

* Renamed to `show_table_indexes`

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

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

* remove unused test import
2022-09-28 13:48:34 +13:00
JT
f2ad7fae1f bump to updated reedline (#6626) 2022-09-28 12:08:42 +13:00
JT
13a4474512 Update Cargo.toml 2022-09-28 12:02:39 +13:00
JT
b746d8427c Revert to released syntax for release-pkg 2022-09-28 07:42:25 +13:00
JT
3beaca0d06 bump to 0.69 (#6623) 2022-09-28 07:14:31 +13:00
f7647584a3 Clippy with the current stable toolchain (#6615)
Fix lints that are coming with rust 1.64

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

* remove several export env usage in test code

* adjust hiding relative test case

* fix clippy

* adjust tests

* update tests

* unignore these tests to expose ut failed

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

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

This reverts commit 2ae24b24c3.

* Revert "adjust hiding relative test case"

This reverts commit 4369af6d05.

* Bring back module example

* Revert "update tests"

This reverts commit 6ae94ef513.

* Fix tests

* "Fix" a test

* Remove remaining deprecated env functionality

* Re-enable environment hiding for `hide`

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

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

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

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

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

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

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

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

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

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

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

* Run rustfmt

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

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

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

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

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

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

* Minor changes to code from rust compiler

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

* Did you mean suggestions: delete test instrumentation

* Fix tests

* Fix test

`foo` has a genuine match: `for`

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

Implements #6586

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

* Update reedline to report the available `space`

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

* get nushell to compile

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

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

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

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

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

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

* Apply clippy fix to avoid deref on an immutable reference

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

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

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

* add search terms

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

* format

* Update tests/overlays/mod.rs

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

* cleanup

* new tests

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

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

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

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

Fixes #6498

* Refactor path name rendering related code

* Make clickable links smarter

* Remove unneeded clone

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

* fmt

* add test

* fix test

* fix clippy

* move behaviour behind `-i` flag

* prevent any possibility of an unspanned error

* ignore all errors

* seperate testes

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

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

* Add unit test for open-df on Arrow

* Add -t flag to open-df command

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

* Evolve StrCollect into StrJoin

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

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

* Deprecate 'str collect'

* Revert "Deprecate 'str collect'"

This reverts commit 959d14203e.

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

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

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

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

* Update to development reedline

Includes nushell/reedline#472

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

View File

@ -15,3 +15,7 @@ Make sure you've run and fixed any issues with these commands:
- [ ] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
- [ ] `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
- [ ] `cargo test --workspace --features=extra` to check that all the tests pass
# Documentation
- [ ] If your PR touches a user-facing nushell feature then make sure that there is an entry in the documentation (https://github.com/nushell/nushell.github.io) for the feature, and update it if necessary.

View File

@ -76,9 +76,9 @@ cp -v README.release.txt $'($dist)/README.txt'
$'(char nl)Check binary release version detail:'; hr-line
let ver = if $os == 'windows-latest' {
(do -i { ./output/nu.exe -c 'version' }) | str collect
(do -i { ./output/nu.exe -c 'version' }) | str join
} else {
(do -i { ./output/nu -c 'version' }) | str collect
(do -i { ./output/nu -c 'version' }) | str join
}
if ($ver | str trim | is-empty) {
$'(ansi r)Incompatible nu binary...(ansi reset)'

View File

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

806
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,10 +8,9 @@ exclude = ["images"]
homepage = "https://www.nushell.sh"
license = "MIT"
name = "nu"
readme = "README.md"
repository = "https://github.com/nushell/nushell"
rust-version = "1.60"
version = "0.68.1"
version = "0.70.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -39,21 +38,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.1" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.68.1" }
nu-command = { path="./crates/nu-command", version = "0.68.1" }
nu-engine = { path="./crates/nu-engine", version = "0.68.1" }
nu-json = { path="./crates/nu-json", version = "0.68.1" }
nu-parser = { path="./crates/nu-parser", version = "0.68.1" }
nu-path = { path="./crates/nu-path", version = "0.68.1" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.68.1" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.68.1" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.68.1" }
nu-system = { path = "./crates/nu-system", version = "0.68.1" }
nu-table = { path = "./crates/nu-table", version = "0.68.1" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.68.1" }
nu-utils = { path = "./crates/nu-utils", version = "0.68.1" }
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
nu-cli = { path="./crates/nu-cli", version = "0.70.0" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.70.0" }
nu-command = { path="./crates/nu-command", version = "0.70.0" }
nu-engine = { path="./crates/nu-engine", version = "0.70.0" }
nu-json = { path="./crates/nu-json", version = "0.70.0" }
nu-parser = { path="./crates/nu-parser", version = "0.70.0" }
nu-path = { path="./crates/nu-path", version = "0.70.0" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.70.0" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.70.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.70.0" }
nu-system = { path = "./crates/nu-system", version = "0.70.0" }
nu-table = { path = "./crates/nu-table", version = "0.70.0" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.70.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.70.0" }
reedline = { version = "0.13.0", features = ["bashisms", "sqlite"]}
rayon = "1.5.1"
is_executable = "1.0.1"
simplelog = "0.12.0"
@ -64,8 +64,16 @@ time = "0.3.12"
openssl = { version = "0.10.38", features = ["vendored"], optional = true }
signal-hook = { version = "0.3.14", default-features = false }
[target.'cfg(windows)'.build-dependencies]
winres = "0.1"
[target.'cfg(target_family = "unix")'.dependencies]
nix = "0.24"
atty = "0.2"
[dev-dependencies]
nu-test-support = { path="./crates/nu-test-support", version = "0.68.1" }
nu-test-support = { path="./crates/nu-test-support", version = "0.70.0" }
tempfile = "3.2.0"
assert_cmd = "2.0.2"
pretty_assertions = "1.0.0"
@ -74,14 +82,11 @@ hamcrest2 = "0.3.0"
rstest = {version = "0.15.0", default-features = false}
itertools = "0.10.3"
[target.'cfg(windows)'.build-dependencies]
winres = "0.1"
[features]
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
extra = ["default", "dataframe", "database"]
default = ["plugin", "which-support", "trash-support"]
stable = ["default"]
extra = ["default", "dataframe", "database"]
wasi = []
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
static-link-openssl = ["dep:openssl"]
@ -122,3 +127,7 @@ debug = false
name = "nu"
path = "src/main.rs"
# To use a development version of a dependency please use a global override here
# changing versions in each sub-crate of the workspace is tedious
[patch.crates-io]
# reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" }

View File

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

View File

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

View File

@ -5,22 +5,22 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
edition = "2021"
license = "MIT"
name = "nu-cli"
version = "0.68.1"
version = "0.70.0"
[dev-dependencies]
nu-test-support = { path="../nu-test-support", version = "0.68.1" }
nu-command = { path = "../nu-command", version = "0.68.1" }
nu-test-support = { path="../nu-test-support", version = "0.70.0" }
nu-command = { path = "../nu-command", version = "0.70.0" }
rstest = {version = "0.15.0", default-features = false}
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.68.1" }
nu-path = { path = "../nu-path", version = "0.68.1" }
nu-parser = { path = "../nu-parser", version = "0.68.1" }
nu-protocol = { path = "../nu-protocol", version = "0.68.1" }
nu-utils = { path = "../nu-utils", version = "0.68.1" }
nu-engine = { path = "../nu-engine", version = "0.70.0" }
nu-path = { path = "../nu-path", version = "0.70.0" }
nu-parser = { path = "../nu-parser", version = "0.70.0" }
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
nu-utils = { path = "../nu-utils", version = "0.70.0" }
nu-ansi-term = "0.46.0"
nu-color-config = { path = "../nu-color-config", version = "0.68.1" }
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
nu-color-config = { path = "../nu-color-config", version = "0.70.0" }
reedline = { version = "0.13.0", features = ["bashisms", "sqlite"]}
atty = "0.2.14"
chrono = "0.4.21"
@ -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

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

View File

@ -666,6 +666,7 @@ fn add_parsed_keybinding(
KeyCode::Char(char)
}
"space" => KeyCode::Char(' '),
"down" => KeyCode::Down,
"up" => KeyCode::Up,
"left" => KeyCode::Left,
@ -821,6 +822,8 @@ fn event_from_record(
"ctrld" => ReedlineEvent::CtrlD,
"ctrlc" => ReedlineEvent::CtrlC,
"enter" => ReedlineEvent::Enter,
"submit" => ReedlineEvent::Submit,
"submitornewline" => ReedlineEvent::SubmitOrNewline,
"esc" | "escape" => ReedlineEvent::Esc,
"up" => ReedlineEvent::Up,
"down" => ReedlineEvent::Down,

View File

@ -11,16 +11,19 @@ use log::{info, trace, warn};
use miette::{IntoDiagnostic, Result};
use nu_color_config::get_color_config;
use nu_engine::{convert_env_values, eval_block};
use nu_parser::{lex, parse};
use nu_parser::{lex, parse, trim_quotes_str};
use nu_protocol::{
ast::PathMember,
engine::{EngineState, Stack, StateWorkingSet},
engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
Spanned, Type, Value, VarId,
};
use reedline::{DefaultHinter, Emacs, SqliteBackedHistory, Vi};
use std::io::{self, Write};
use std::{sync::atomic::Ordering, time::Instant};
use 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() {
@ -363,9 +380,13 @@ pub fn evaluate_repl(
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
// Check if this is a single call to a directory, if so auto-cd
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
let path = nu_path::expand_path_with(&s, &cwd);
let orig = s.clone();
let mut orig = s.clone();
if orig.starts_with('`') {
orig = trim_quotes_str(&orig).to_string()
}
let path = nu_path::expand_path_with(&orig, &cwd);
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
// We have an auto-cd
@ -435,7 +456,7 @@ pub fn evaluate_repl(
span,
},
);
} else {
} else if !s.trim().is_empty() {
trace!("eval source: {}", s);
eval_source(
@ -473,6 +494,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 +525,24 @@ pub fn evaluate_repl(
}
run_ansi_sequence(RESET_APPLICATION_MODE)?;
}
let mut ops = engine_state
.repl_operation_queue
.lock()
.expect("repl op queue mutex");
while let Some(op) = ops.pop_front() {
match op {
ReplOperation::Append(s) => line_editor.run_edit_commands(&[
EditCommand::MoveToEnd,
EditCommand::InsertString(s),
]),
ReplOperation::Insert(s) => {
line_editor.run_edit_commands(&[EditCommand::InsertString(s)])
}
ReplOperation::Replace(s) => line_editor
.run_edit_commands(&[EditCommand::Clear, EditCommand::InsertString(s)]),
}
}
}
Ok(Signal::CtrlC) => {
// `Reedline` clears the line content. New prompt is shown
@ -528,7 +582,7 @@ fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String {
engine_state,
stack,
None,
"(date now) - ('05/10/2019' | into datetime)",
"(date now) - ('2019-05-10 09:59:12-0700' | into datetime)",
) {
Ok(Value::Duration { val, .. }) => format_duration(val),
_ => "".to_string(),
@ -545,13 +599,13 @@ Our {}GitHub{} repository is at {}https://github.com/nushell/nushell{}
Our {}Documentation{} is located at {}http://nushell.sh{}
{}Tweet{} us at {}@nu_shell{}
{}Nushell{} has been around for:
It's been this long since {}Nushell{}'s first commit:
{}
{}You can disable this banner using the {}config nu{}{} command
to modify the config.nu file and setting show_banner to false.
let-env config {{
let-env config = {{
show_banner: false
...
}}{}
@ -612,9 +666,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

@ -231,23 +231,18 @@ pub fn eval_source(
}
match eval_block(engine_state, stack, &block, input, false, false) {
Ok(mut pipeline_data) => {
if let PipelineData::ExternalStream { exit_code, .. } = &mut pipeline_data {
if let Some(exit_code) = exit_code.take().and_then(|it| it.last()) {
stack.add_env_var("LAST_EXIT_CODE".to_string(), exit_code);
} else {
set_last_exit_code(stack, 0);
Ok(pipeline_data) => {
match pipeline_data.print(engine_state, stack, false, false) {
Err(err) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
return false;
}
Ok(exit_code) => {
set_last_exit_code(stack, exit_code);
}
} else {
set_last_exit_code(stack, 0);
}
if let Err(err) = pipeline_data.print(engine_state, stack, false, false) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
return false;
}
// reset vt processing, aka ansi because illbehaved externals can break it

View File

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

View File

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

View File

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

View File

@ -5,27 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
edition = "2021"
license = "MIT"
name = "nu-command"
version = "0.68.1"
version = "0.70.0"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-color-config = { path = "../nu-color-config", version = "0.68.1" }
nu-engine = { path = "../nu-engine", version = "0.68.1" }
nu-glob = { path = "../nu-glob", version = "0.68.1" }
nu-json = { path = "../nu-json", version = "0.68.1" }
nu-parser = { path = "../nu-parser", version = "0.68.1" }
nu-path = { path = "../nu-path", version = "0.68.1" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.68.1" }
nu-protocol = { path = "../nu-protocol", version = "0.68.1" }
nu-system = { path = "../nu-system", version = "0.68.1" }
nu-table = { path = "../nu-table", version = "0.68.1" }
nu-term-grid = { path = "../nu-term-grid", version = "0.68.1" }
nu-test-support = { path = "../nu-test-support", version = "0.68.1" }
nu-utils = { path = "../nu-utils", version = "0.68.1" }
nu-color-config = { path = "../nu-color-config", version = "0.70.0" }
nu-engine = { path = "../nu-engine", version = "0.70.0" }
nu-glob = { path = "../nu-glob", version = "0.70.0" }
nu-json = { path = "../nu-json", version = "0.70.0" }
nu-parser = { path = "../nu-parser", version = "0.70.0" }
nu-path = { path = "../nu-path", version = "0.70.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.70.0" }
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
nu-system = { path = "../nu-system", version = "0.70.0" }
nu-table = { path = "../nu-table", version = "0.70.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.70.0" }
nu-test-support = { path = "../nu-test-support", version = "0.70.0" }
nu-utils = { path = "../nu-utils", version = "0.70.0" }
nu-ansi-term = "0.46.0"
num-format = { version = "0.4.0" }
num-format = { version = "0.4.3" }
# Potential dependencies for extras
alphanumeric-sort = "1.4.4"
@ -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"
@ -87,10 +88,13 @@ unicode-segmentation = "1.8.0"
url = "2.2.1"
uuid = { version = "1.1.2", features = ["v4"] }
which = { version = "4.3.0", optional = true }
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
reedline = { version = "0.13.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(windows)'.dependencies]
winreg = "0.10.1"
[target.'cfg(unix)'.dependencies]
umask = "2.0.0"
@ -115,6 +119,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

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

View File

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

View File

@ -2,7 +2,8 @@ use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value,
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, SyntaxShape,
Value,
};
#[derive(Clone)]
@ -102,9 +103,75 @@ impl Command for Do {
Err(_) => Ok(PipelineData::new(call.head)),
}
} else if capture_errors {
// collect stdout and stderr and check exit code.
// if exit code is not 0, return back ShellError.
match result {
Ok(x) => Ok(x),
Err(err) => Ok((Value::Error { error: err }).into_pipeline_data()),
Ok(PipelineData::ExternalStream {
stdout,
stderr,
exit_code,
span,
metadata,
}) => {
// collect all output first.
let mut stderr_ctrlc = None;
let stderr_msg = match stderr {
None => "".to_string(),
Some(stderr_stream) => {
stderr_ctrlc = stderr_stream.ctrlc.clone();
stderr_stream.into_string().map(|s| s.item)?
}
};
let mut stdout_ctrlc = None;
let stdout_msg = match stdout {
None => "".to_string(),
Some(stdout_stream) => {
stdout_ctrlc = stdout_stream.ctrlc.clone();
stdout_stream.into_string().map(|s| s.item)?
}
};
let mut exit_code_ctrlc = None;
let exit_code: Vec<Value> = match exit_code {
None => vec![],
Some(exit_code_stream) => {
exit_code_ctrlc = exit_code_stream.ctrlc.clone();
exit_code_stream.into_iter().collect()
}
};
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
// if exit_code is not 0, it indicates error occured, return back Err.
if *code != 0 {
return Err(ShellError::ExternalCommand(
"External command runs to failed".to_string(),
stderr_msg,
span,
));
}
}
// construct pipeline data to our caller
Ok(PipelineData::ExternalStream {
stdout: Some(RawStream::new(
Box::new(vec![Ok(stdout_msg.into_bytes())].into_iter()),
stdout_ctrlc,
span,
)),
stderr: Some(RawStream::new(
Box::new(vec![Ok(stderr_msg.into_bytes())].into_iter()),
stderr_ctrlc,
span,
)),
exit_code: Some(ListStream::from_stream(
exit_code.into_iter(),
exit_code_ctrlc,
)),
span,
metadata,
})
}
Ok(other) => Ok(other),
Err(e) => Err(e),
}
} else {
result

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -362,11 +362,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

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

View File

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

View File

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

View File

@ -0,0 +1,220 @@
use crate::dataframe::eager::sql_expr::parse_sql_expr;
use polars::error::PolarsError;
use polars::prelude::{col, DataFrame, DataType, IntoLazy, LazyFrame};
use sqlparser::ast::{
Expr as SqlExpr, Select, SelectItem, SetExpr, Statement, TableFactor, Value as SQLValue,
};
use sqlparser::dialect::GenericDialect;
use sqlparser::parser::Parser;
use std::collections::HashMap;
#[derive(Default)]
pub struct SQLContext {
table_map: HashMap<String, LazyFrame>,
dialect: GenericDialect,
}
impl SQLContext {
pub fn new() -> Self {
Self {
table_map: HashMap::new(),
dialect: GenericDialect::default(),
}
}
pub fn register(&mut self, name: &str, df: &DataFrame) {
self.table_map.insert(name.to_owned(), df.clone().lazy());
}
fn execute_select(&self, select_stmt: &Select) -> Result<LazyFrame, PolarsError> {
// Determine involved dataframe
// Implicit join require some more work in query parsers, Explicit join are preferred for now.
let tbl = select_stmt.from.get(0).ok_or_else(|| {
PolarsError::NotFound("No table found in select statement".to_string())
})?;
let mut alias_map = HashMap::new();
let tbl_name = match &tbl.relation {
TableFactor::Table { name, alias, .. } => {
let tbl_name = name
.0
.get(0)
.ok_or_else(|| {
PolarsError::NotFound("No table found in select statement".to_string())
})?
.value
.to_string();
if self.table_map.contains_key(&tbl_name) {
if let Some(alias) = alias {
alias_map.insert(alias.name.value.clone(), tbl_name.to_owned());
};
tbl_name
} else {
return Err(PolarsError::ComputeError(
format!("Table name {tbl_name} was not found").into(),
));
}
}
// Support bare table, optional with alias for now
_ => return Err(PolarsError::ComputeError("Not implemented".into())),
};
let df = &self.table_map[&tbl_name];
let mut raw_projection_before_alias: HashMap<String, usize> = HashMap::new();
let mut contain_wildcard = false;
// Filter Expression
let df = match select_stmt.selection.as_ref() {
Some(expr) => {
let filter_expression = parse_sql_expr(expr)?;
df.clone().filter(filter_expression)
}
None => df.clone(),
};
// Column Projections
let projection = select_stmt
.projection
.iter()
.enumerate()
.map(|(i, select_item)| {
Ok(match select_item {
SelectItem::UnnamedExpr(expr) => {
let expr = parse_sql_expr(expr)?;
raw_projection_before_alias.insert(format!("{:?}", expr), i);
expr
}
SelectItem::ExprWithAlias { expr, alias } => {
let expr = parse_sql_expr(expr)?;
raw_projection_before_alias.insert(format!("{:?}", expr), i);
expr.alias(&alias.value)
}
SelectItem::QualifiedWildcard(_) | SelectItem::Wildcard => {
contain_wildcard = true;
col("*")
}
})
})
.collect::<Result<Vec<_>, PolarsError>>()?;
// Check for group by
// After projection since there might be number.
let group_by = select_stmt
.group_by
.iter()
.map(
|e|match e {
SqlExpr::Value(SQLValue::Number(idx, _)) => {
let idx = match idx.parse::<usize>() {
Ok(0)| Err(_) => Err(
PolarsError::ComputeError(
format!("Group By Error: Only positive number or expression are supported, got {idx}").into()
)),
Ok(idx) => Ok(idx)
}?;
Ok(projection[idx].clone())
}
SqlExpr::Value(_) => Err(
PolarsError::ComputeError("Group By Error: Only positive number or expression are supported".into())
),
_ => parse_sql_expr(e)
}
)
.collect::<Result<Vec<_>, PolarsError>>()?;
let df = if group_by.is_empty() {
df.select(projection)
} else {
// check groupby and projection due to difference between SQL and polars
// Return error on wild card, shouldn't process this
if contain_wildcard {
return Err(PolarsError::ComputeError(
"Group By Error: Can't 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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
@ -157,12 +158,17 @@ pub fn create_default_context() -> EngineState {
bind_command! {
Benchmark,
Complete,
Exec,
External,
NuCheck,
Sys,
};
#[cfg(unix)]
bind_command! { Exec }
#[cfg(windows)]
bind_command! { RegistryQuery }
#[cfg(any(
target_os = "android",
target_os = "linux",
@ -200,6 +206,7 @@ pub fn create_default_context() -> EngineState {
StrDistance,
StrDowncase,
StrEndswith,
StrJoin,
StrReplace,
StrIndexOf,
StrKebabCase,

View File

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

View File

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

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

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

View File

@ -491,7 +491,10 @@ pub(crate) fn dir_entry_dict(
span,
});
} else {
vals.push(Value::nothing(span))
vals.push(Value::Int {
val: md.uid() as i64,
span,
})
}
cols.push("group".into());
@ -501,7 +504,10 @@ pub(crate) fn dir_entry_dict(
span,
});
} else {
vals.push(Value::nothing(span))
vals.push(Value::Int {
val: md.gid() as i64,
span,
})
}
}
}

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

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

View File

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

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

@ -146,7 +146,7 @@ fn first_helper(
}
}
_ => {
if rows_desired == 1 {
if rows_desired == 1 && rows.is_none() {
match input_peek.next() {
Some(val) => Ok(val.into_pipeline_data()),
None => Err(ShellError::AccessBeyondEndOfStream(head)),

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

@ -11,6 +11,10 @@ impl Command for Roll {
"roll"
}
fn search_terms(&self) -> Vec<&str> {
vec!["rotate", "shift", "move"]
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Filters)
}

View File

@ -16,6 +16,10 @@ impl Command for RollDown {
"roll down"
}
fn search_terms(&self) -> Vec<&str> {
vec!["rotate", "shift", "move", "row"]
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.named("by", SyntaxShape::Int, "Number of rows to roll", Some('b'))

View File

@ -16,6 +16,10 @@ impl Command for RollLeft {
"roll left"
}
fn search_terms(&self) -> Vec<&str> {
vec!["rotate", "shift", "move", "column"]
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.named(

View File

@ -16,6 +16,10 @@ impl Command for RollRight {
"roll right"
}
fn search_terms(&self) -> Vec<&str> {
vec!["rotate", "shift", "move", "column"]
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.named(

View File

@ -16,6 +16,10 @@ impl Command for RollUp {
"roll up"
}
fn search_terms(&self) -> Vec<&str> {
vec!["rotate", "shift", "move", "row"]
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.named("by", SyntaxShape::Int, "Number of rows to roll", Some('b'))

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

@ -23,6 +23,11 @@ impl Command for Window {
"the number of rows to slide over between windows",
Some('s'),
)
.switch(
"remainder",
"yield last chunks even if they have fewer elements than size",
Some('r'),
)
.category(Category::Filters)
}
@ -115,6 +120,39 @@ impl Command for Window {
},
];
let stream_test_3 = vec![
Value::List {
vals: vec![
Value::Int {
val: 1,
span: Span::test_data(),
},
Value::Int {
val: 2,
span: Span::test_data(),
},
Value::Int {
val: 3,
span: Span::test_data(),
},
],
span: Span::test_data(),
},
Value::List {
vals: vec![
Value::Int {
val: 4,
span: Span::test_data(),
},
Value::Int {
val: 5,
span: Span::test_data(),
},
],
span: Span::test_data(),
},
];
vec![
Example {
example: "echo [1 2 3 4] | window 2",
@ -132,6 +170,14 @@ impl Command for Window {
span: Span::test_data(),
}),
},
Example {
example: "[1, 2, 3, 4, 5] | window 3 --stride 3 --remainder",
description: "A sliding window of equal stride that includes remainder. Equivalent to chunking",
result: Some(Value::List {
vals: stream_test_3,
span: Span::test_data(),
}),
},
]
}
@ -146,6 +192,7 @@ impl Command for Window {
let ctrlc = engine_state.ctrlc.clone();
let metadata = input.metadata();
let stride: Option<usize> = call.get_flag(engine_state, stack, "stride")?;
let remainder = call.has_flag("remainder");
let stride = stride.unwrap_or(1);
@ -155,8 +202,9 @@ impl Command for Window {
group_size: group_size.item,
input: Box::new(input.into_iter()),
span: call.head,
previous: vec![],
previous: None,
stride,
remainder,
};
Ok(each_group_iterator
@ -169,15 +217,23 @@ struct EachWindowIterator {
group_size: usize,
input: Box<dyn Iterator<Item = Value> + Send>,
span: Span,
previous: Vec<Value>,
previous: Option<Vec<Value>>,
stride: usize,
remainder: bool,
}
impl Iterator for EachWindowIterator {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
let mut group = self.previous.clone();
let mut group = self.previous.take().unwrap_or_else(|| {
let mut vec = Vec::new();
// We default to a Vec of capacity size + stride as striding pushes n extra elements to the end
vec.try_reserve(self.group_size + self.stride).ok();
vec
});
let mut current_count = 0;
if group.is_empty() {
@ -193,7 +249,13 @@ impl Iterator for EachWindowIterator {
break;
}
}
None => return None,
None => {
if self.remainder {
break;
} else {
return None;
}
}
}
}
} else {
@ -211,23 +273,31 @@ impl Iterator for EachWindowIterator {
break;
}
}
None => return None,
None => {
if self.remainder {
break;
} else {
return None;
}
}
}
}
for _ in 0..current_count {
let _ = group.remove(0);
}
// We now have elements + stride in our group, and need to
// drop the skipped elements. Drain to preserve allocation and capacity
// Dropping this iterator consumes it.
group.drain(..self.stride.min(group.len()));
}
if group.is_empty() || current_count == 0 {
if group.is_empty() {
return None;
}
self.previous = group.clone();
let return_group = group.clone();
self.previous = Some(group);
Some(Value::List {
vals: group,
vals: return_group,
span: self.span,
})
}

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

@ -173,12 +173,11 @@ fn value_to_string(v: &Value, span: Span) -> Result<String, ShellError> {
fn value_to_string_without_quotes(v: &Value, span: Span) -> Result<String, ShellError> {
match v {
Value::String { val, .. } => Ok({
let mut quoted = escape_quote_string(val);
if !needs_quotes(val) {
quoted.remove(0);
quoted.pop();
if needs_quotes(val) {
escape_quote_string(val)
} else {
val.clone()
}
quoted
}),
_ => value_to_string(v, span),
}
@ -209,6 +208,7 @@ fn needs_quotes(string: &str) -> bool {
|| string.contains('\t')
|| string.contains('\n')
|| string.contains('\r')
|| string.contains('\"')
}
#[cfg(test)]

View File

@ -66,10 +66,6 @@ impl Command for ToText {
},
]
}
fn search_terms(&self) -> Vec<&str> {
vec!["convert"]
}
}
fn local_into_string(value: Value, separator: &str, config: &Config) -> 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

@ -149,7 +149,7 @@ impl Command for Input {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get input from the user, and assign to a variable",
example: "let user-input = (input)",
example: "let user_input = (input)",
result: None,
}]
}

View File

@ -12,35 +12,28 @@ impl Command for TermSize {
}
fn usage(&self) -> &str {
"Returns the terminal size"
"Returns a record containing the number of columns (width) and rows (height) of the terminal"
}
fn signature(&self) -> Signature {
Signature::build("term size")
.switch(
"columns",
"Report only the width of the terminal",
Some('c'),
)
.switch("rows", "Report only the height of the terminal", Some('r'))
.category(Category::Platform)
Signature::build("term size").category(Category::Platform)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Return the width height of the terminal",
description: "Return the columns (width) and rows (height) of the terminal",
example: "term size",
result: None,
},
Example {
description: "Return the width (columns) of the terminal",
example: "term size -c",
description: "Return the columns (width) of the terminal",
example: "(term size).columns",
result: None,
},
Example {
description: "Return the height (rows) of the terminal",
example: "term size -r",
description: "Return the rows (height) of the terminal",
example: "(term size).rows",
result: None,
},
]
@ -54,60 +47,26 @@ impl Command for TermSize {
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let wide = call.has_flag("columns");
let tall = call.has_flag("rows");
let (cols, rows) = match terminal_size() {
Some((w, h)) => (Width(w.0), Height(h.0)),
None => (Width(0), Height(0)),
};
Ok((match (wide, tall) {
(true, false) => Value::Record {
cols: vec!["columns".into()],
vals: vec![Value::Int {
Ok(Value::Record {
cols: vec!["columns".into(), "rows".into()],
vals: vec![
Value::Int {
val: cols.0 as i64,
span: Span::test_data(),
}],
span: head,
},
(true, true) => Value::Record {
cols: vec!["columns".into(), "rows".into()],
vals: vec![
Value::Int {
val: cols.0 as i64,
span: Span::test_data(),
},
Value::Int {
val: rows.0 as i64,
span: Span::test_data(),
},
],
span: head,
},
(false, true) => Value::Record {
cols: vec!["rows".into()],
vals: vec![Value::Int {
},
Value::Int {
val: rows.0 as i64,
span: Span::test_data(),
}],
span: head,
},
(false, false) => Value::Record {
cols: vec!["columns".into(), "rows".into()],
vals: vec![
Value::Int {
val: cols.0 as i64,
span: Span::test_data(),
},
Value::Int {
val: rows.0 as i64,
span: Span::test_data(),
},
],
span: head,
},
})
},
],
span: head,
}
.into_pipeline_data())
}
}

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 {

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