Compare commits

...

433 Commits

Author SHA1 Message Date
11a9144e84 update mock table for easier table testing (#3065) 2021-02-17 04:47:47 +13:00
039f223b53 Bump to 0.27 (#3063) 2021-02-16 19:20:05 +13:00
e1cb026184 Add back in column truncation (#3061) 2021-02-16 07:15:16 +13:00
2a96152a43 fix sample_config: date --format no longer supported (#3060)
Co-authored-by: alexhk <alexhk@protonmail.com>
2021-02-15 10:24:55 -06:00
0795d56c1c Source path including tilda (#3059)
* Use expand_path to handle the path including tilda

* Publish path::expand_path for using in nu-command

* cargo fmt

Co-authored-by: Wataru Yamaguchi <nagisamark2@gmail.com>
2021-02-15 21:41:49 +13:00
48a90fea70 Fix let-env (#3057) 2021-02-15 20:58:51 +13:00
b202951c1d fix prompts on startup (#3056)
* fix prompts on startup

* Try again
2021-02-15 20:14:16 +13:00
c3d2c61729 nu-parser: fix parsing comments with unclosed ' " [] {} in functions (#3053)
* nu-parser: fix parsing comments with unclosed ' " [] {} in functions

* add tests for last commit
2021-02-14 21:40:28 +13:00
d7b707939f Fix quick command reference link in README (#3052) 2021-02-14 18:38:01 +13:00
991ac6eb77 change help text (#3054) 2021-02-13 13:20:34 -06:00
011b7c4a07 refactor parser: rename method pub fn block to parse_block (#3047)
* refactor parser: rename method block to parse_block

* nu-cli/src/completion/engine.rs block to parse_block
2021-02-13 09:31:11 +13:00
617341f8d5 nu-engine: deserialize_struct: fix missing conversion from string to column path (#3048) 2021-02-13 09:29:38 +13:00
abd2632977 Remove accidental debug symbols in release (#3050) 2021-02-13 09:28:41 +13:00
5481db4079 Fix latest clippy warnings (#3049) 2021-02-12 23:13:14 +13:00
041086d22a add config "filesize_metric = true" for default formatting of filesize (#3045) 2021-02-11 21:52:34 +13:00
aa564f5072 display boolean config options as true/false instead of Yes/No (#3043) 2021-02-11 21:50:33 +13:00
8367f2001c update nuver (#3044) 2021-02-10 08:58:38 -06:00
1cfb228924 New termsize command (#3038)
* add term size command

* update w & h, add examples

* changed default to output table
2021-02-10 08:58:22 -06:00
b403fb1275 nu-parser + nu-protocol: switch to metric for KB, MB, GB, add KiB, MiB, GiB units (#3035)
fixes inconsistency with formatting/rendering which uses standard Rust byte_unit
https://en.wikipedia.org/wiki/Byte#Multiple-byte_units
2021-02-10 15:31:12 +13:00
3443ca40c5 updated a few items (#3040) 2021-02-09 17:11:36 -06:00
96f95653a6 added comment for table_mode (#3036) 2021-02-09 09:06:30 -06:00
7f7e8465da Fix fmt and small cleaning in nu-parser (#3033)
* parse_unit: reduce indentation in loop

* fix fmt: crates/nu-parser/src/lex/tests.rs
2021-02-09 17:46:10 +13:00
e3a273cf73 Update config (#3031) 2021-02-09 17:44:42 +13:00
233161d56e sort_by: support -r flag for reverse (#3025)
* sort_by: support -r flag for reverse

* Update sort_by.rs

Fix reverse test

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2021-02-08 11:10:06 +13:00
d883ab250a which: accept several applications/commands (#3024)
* which: accept several applications

* fix fmt: which_.rs
2021-02-08 08:17:06 +13:00
ef4e3f907c parser/refactor def (#2986)
* Move tests into own file

* Move data structs to own file

* Move functions parsing 1 Token (primitives) into own file

* Rename param_flag_list to signature

* Add tests

* Fix clippy lint

* Change imports to new lexer structure
2021-02-08 08:10:14 +13:00
debeadbf3f Soft rest arguments column path cohersions. (#3016) 2021-02-06 20:05:47 -05:00
d66baaceb9 Fix 'ps -l' output when a process doesn't have a parent process. (#3015)
Before, ps would not insert a value if the process didn't have a parent.
Now, ps will insert an empty cell. This caused broken tables as some
rows didn't have all the columns.
2021-02-06 22:41:08 +13:00
85329f9a81 Fix readme (#3013) 2021-02-06 14:18:38 +13:00
a5fefaf78b Ensure selection of columns are done once per column (#3012) 2021-02-05 19:34:26 -05:00
6f17662a4e Update some deps (#3011) 2021-02-06 09:54:54 +13:00
c83aea3c89 Bump to 0.26.1 (#3008) 2021-02-05 19:38:04 +13:00
67aaf4cb2d fix the ps command's virtual mem (#3007) 2021-02-05 18:57:49 +13:00
3083346884 update sysinfo to v16 (#3006) 2021-02-05 06:59:24 +13:00
d07789677f Clean up lexer (#2956)
* Document the lexer and lightly improve its names

The bulk of this pull request adds a substantial amount of new inline
documentation for the lexer. Along the way, I made a few minor changes
to the names in the lexer, most of which were internal.

The main change that affects other files is renaming `group` to `block`,
since the function is actually parsing a block (a list of groups).

* Further clean up the lexer

- Consolidate the logic of the various token builders into a single type
- Improve and clean up the event-driven BlockParser
- Clean up comment parsing. Comments now contain their original leading
  whitespace as well as trailing whitespace, and know how to move some
  leading whitespace back into the body based on how the lexer decides
  to dedent the comments. This preserves the original whitespace
  information while still making it straight-forward to eliminate leading
  whitespace in help comments.

* Update meta.rs

* WIP

* fix clippy

* remove unwraps

* remove unwraps

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
Co-authored-by: Jonathan Turner <jonathan.d.turner@gmail.com>
2021-02-04 20:20:21 +13:00
fb1846120d standardize on how to get file size (#2992)
* standardize on how to get file size

* forgot to remove comment

* make specified size lowercase

* fix the test due to precision

* added another test

* Update README.md

add contributors graphic

* clippy - test adjustment

* tweaked matching
2021-02-03 07:19:38 -06:00
da1e1295ea Update README.md 2021-02-03 15:08:18 +13:00
ecaea57263 Value helpers (#3000)
* Update README.md

add contributors graphic

* just a couple of helpers

* separated some helpers out to individual fns
2021-02-03 15:06:11 +13:00
fa928bd25d Minimal markdown syntax per element support. (#2997) 2021-02-02 12:09:19 -05:00
c1981dfc26 Fix README formatting (#2996) 2021-02-02 19:23:38 +13:00
fd41fa31d5 add $nothing and tests (#2995) 2021-02-02 19:23:12 +13:00
2c52144f41 Update README.md (#2993)
add contributors graphic
2021-02-02 07:05:37 +13:00
87c7898b65 update sysinfo due to breaking change with get_version (#2988) 2021-01-30 12:21:32 -06:00
44e088c6fe Move filesize to use bigint (#2984)
* Move filesize to be bigint-sized

* Add tests and fix filesize display

* clippy
2021-01-30 11:35:18 +13:00
7b4cbd7ce9 Few fixes for WASI build (#2983)
- Disable shadow-rs (libgit2-sys compilation on WASI fails for various strange reasons, so seems easier to disable altogether for now).
 - Disable directories-support (WASI doesn't have concept of user directory and such calls fail at runtime).
2021-01-30 09:11:07 +13:00
b052d524da added pow operator, and filesize math (#2976)
* added pow operator, and filesize math

* removed + and - arms, removed some pow, pow higher precedence

* Update value.rs

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2021-01-29 07:44:02 -06:00
47c4b8e88a allow str from to convert more things to string (#2977)
* allow str from to convert more things to string

* fixed FileSize so it reports with units configured

* added tests
2021-01-29 07:43:35 -06:00
d0a2a02eea Add possibility to declare optional parameters and switch flags (#2966)
* Add possibility to declare optional parameters and switch flags

With this commit applied it is now possible to specify optional parameters and flags
as switches. This PR **only** makes guarantees about **parsing** optional flags and
switches correctly. This PR **does not guarantee flawless functionality** of
optional parameters / switches within scripts.
functionality within scripts. Example:

test.nu
```shell
def my_command [
    opt_param?
    opt_param2?: int
    --switch
] {echo hi nushell}
```

```shell
> source test.nu
> my_command -h
───┬─────────
 0 │ hi
 1 │ nushell
───┴─────────
Usage:
  > my_command <mandatory_param> (opt_param) (opt_param2) {flags}

Parameters:
  <mandatory_param>
  (opt_param)
  (opt_param2)

Flags:
  -h, --help: Display this help message
  --switch
  --opt_flag <any>
```

* Update def docs
2021-01-28 06:31:29 +13:00
b1e1dab4cb add % operator for modulus, work with decimals (#2975)
* add % operator, work with decimals

* removed the % operator to reserve for something else
2021-01-26 12:42:34 -06:00
388973e9ab Bump to 0.26.0 (#2974) 2021-01-26 23:07:08 +13:00
2129ec7558 allow pad to use multi-byte chars (#2973) 2021-01-26 22:09:38 +13:00
82f122525c Delete nushell-demo.svg 2021-01-25 20:35:10 +13:00
7c4c00f1e6 Update README.md 2021-01-25 20:34:05 +13:00
fe6c7dc10a Add files via upload 2021-01-25 20:33:06 +13:00
9bc24e3b12 Remove unnecessary clone() (#2970) 2021-01-25 20:13:05 +13:00
833baca66e Add a new animated demo (#2971) 2021-01-25 20:12:44 +13:00
9fd92512a2 Use equality assert macros (#2969) 2021-01-25 18:16:10 +13:00
b692ca7896 Fix ps sys units (#2967)
* Fix the units for sys and ps

* Better conversion
2021-01-25 08:34:43 +13:00
52dc04a35a Error on bad row in column path (#2964)
* Error on bad row in column path

* Add more pathing tests
2021-01-22 18:14:13 -05:00
42b1287759 Parity and anchor carrying for str command suite. (#2965)
Bring the majority of str sub commands to parity supporting their actions
by column paths. Ensuring they carry over anchor meta data as well.
2021-01-22 18:13:30 -05:00
5a471aa1d0 fixed char signature (#2963) 2021-01-22 15:48:31 -06:00
a4b8d4a098 Add rest support to blocks (#2962) 2021-01-23 10:28:32 +13:00
a3be6affa4 fix some misalignment errors (#2959) 2021-01-23 07:39:09 +13:00
71b99edd48 parser/add rest args to def (#2961)
* Add rest arg to def

This commit applied adds the ability to define the rest parameter of a def
command. It does not implement the functionality to expand the rest argument in
a user defined def function.

The rest argument has to be exactly worded "...rest".

Example after this PR is applied:

file test.nu
```shell
def my_command [
    ...rest:int # My rest arg
] {
    echo 1 2 3
}
```

```shell
> source test.nu
> my_command -h
Usage:
  > my_command ...args {flags}

Parameters:
  ...args: My rest arg

Flags:
  -h, --help: Display this help message
```

* Fix space in help on wrong side
2021-01-23 07:13:29 +13:00
64553ddcb7 upgrade shadow-rs 0.5.23 (#2960)
* update to shadow-rs 0.4. use easy

* update shadow-rs to 0.5

* fix version not used

* update

* update Cargo.lock

* update Cargo.lock

* fix wasm build error when use dependence git2
fix error link:https://dev.azure.com/nushell/nushell/_build/results?buildId=4858&view=logs&j=1a745d4c-b027-5f34-06d8-d6f256bfe9f9&t=a0a335cb-fa1f-5bbf-be01-1a90d6899e54

* remove code not used; fix warning by RUSTFLAGS="-D warnings" build error

* upgrade shadow-rs 0.5.2

* upgrade shadow-rs 0.5.7

make nushell reduce dependence crates smaller and  build fast.

* upgrade shadow-rs 0.5.8

fix when use api 'strip_prefix()' method in less than rust1.45.0 build failed

* fix https://github.com/baoyachi/shadow-rs/issues?q=is%3Aissue+is%3Aclosed
2021-01-23 07:09:57 +13:00
2a42482ae9 Clean up and refactoring examples tests. (#2957) 2021-01-20 21:07:16 -05:00
11f345a8ae added more char escapes (#2955)
* added more char escapes

* move commands with \x1b over from char.rs to ansi.rs
2021-01-21 13:15:58 +13:00
fec50d8cfe Fix bug #2921 (#2945)
* Fix bug #2921

Moving whether a range should be parsed further back, giving e.G. parsing of
invocations precedence fixes the bug

* Add test
2021-01-21 07:58:37 +13:00
05e42381df Add --skip flag to nth command (#2953)
clippy & rustfmt included
2021-01-21 06:37:30 +13:00
b435075e09 Temporarily(?) switch from heim+uom to sysinfo (#2954)
* Switch from heim to sysinfo

* WIP

* more cleanup

* fmt

* lint
2021-01-20 20:18:38 +13:00
430da53f0b Replace dirs and directories with maintained (#2949) 2021-01-19 14:24:27 -06:00
2e6d836dd1 Flush out! lines, helps autoview (#2952) 2021-01-20 07:23:37 +13:00
899d324a9c fix: error Variable not in scope for a def parameter #2901 (#2951)
adding tests to notice regressions on this issue

Co-authored-by: hk <alexhaka10@protonmail.com>
2021-01-20 07:21:11 +13:00
576ed6a906 parser/split long short flags (#2944)
* Remove wrong test case

* Parse long and shortflags without space correctly

* Update param_flag_list.rs

* Update param_flag_list.rs

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2021-01-20 07:19:53 +13:00
d744cf8437 [Gitpod] Don't test removed feature 'test-bins' (#2948)
Fixes nushell/nushell#2947
2021-01-19 22:43:42 +13:00
088e662285 Replace git current_branch to shadow-rs branch (#2935)
* update to shadow-rs 0.4. use easy

* update shadow-rs to 0.5

* fix version not used

* update

* update Cargo.lock

* update Cargo.lock

* fix wasm build error when use dependence git2
fix error link:https://dev.azure.com/nushell/nushell/_build/results?buildId=4858&view=logs&j=1a745d4c-b027-5f34-06d8-d6f256bfe9f9&t=a0a335cb-fa1f-5bbf-be01-1a90d6899e54

* remove code not used; fix warning by RUSTFLAGS="-D warnings" build error

* upgrade shadow-rs 0.5.2

* upgrade shadow-rs 0.5.7

make nushell reduce dependence crates smaller and  build fast.

* upgrade shadow-rs 0.5.8

fix when use api 'strip_prefix()' method in less than rust1.45.0 build failed

* use shadow-rs branch replace with current_branch method;
remove and reduce git dependencies.

* upgrade shadow-rs 0.5.12-pre,test build error with wasm

* upgrade Cargo.lock

* upgarde shadow-rs depencdence

* fix build error in wasm

* add clippy warning
2021-01-16 07:06:29 +13:00
f9b0b81eb2 Add def documentation (#2939) 2021-01-15 20:21:18 +13:00
c5485c6501 a small regex optimization (#2937)
* a small regex optimization

* removed comments
2021-01-15 20:20:28 +13:00
d8ed01400f str set sub command removal. (#2940) 2021-01-14 18:55:37 -05:00
ebc4694e05 move keybinding_path to nu-data (#2927) 2021-01-14 06:31:47 +13:00
a9441d670e Revert tab completion changes (#2929)
* Undo tab completion changes

* Remove extra newline
2021-01-14 06:29:18 +13:00
495d2ebd70 Improve tab completion behaviour (#2916)
* Improve tab completion behaviour

* Fix clippy issue

* Add test cases
2021-01-13 17:04:29 +13:00
ad26adc3e3 remove set from windows cmd_builtins (#2924) 2021-01-13 14:46:58 +13:00
63a62e19f9 Update alias docs (#2925) 2021-01-13 14:46:15 +13:00
4f2ae34df9 Don't throw err on typename as parameter name (#2926)
Before this was an error:
`def e [path:path] {echo $path}`
Now its not.
2021-01-13 14:44:55 +13:00
a636f161a4 Add dirs dependency to nu-engine (#2922)
* Add dirs dependency to nu-engine

* Dir feature should be added to root features
2021-01-13 10:18:13 +13:00
dfb1e22559 Update alias docs to new syntax (#2917)
This confused me today after upgrading Nu. I believe this is now correct.
2021-01-13 08:30:27 +13:00
dff85a7f70 RangeIterator can also go down (#2913) 2021-01-13 08:27:54 +13:00
3be198d2f5 Don't print description in help if none exists (#2915) 2021-01-13 07:27:48 +13:00
d19314fe3a Fix the wasm build (#2919) 2021-01-13 07:14:35 +13:00
d06f457b2a nu-cli refactor moving commands into their own crate nu-command (#2910)
* move commands, futures.rs, script.rs, utils

* move over maybe_print_errors

* add nu_command crate references to nu_cli

* in commands.rs open up to pub mod from pub(crate)

* nu-cli, nu-command, and nu tests are now passing

* cargo fmt

* clean up nu-cli/src/prelude.rs

* code cleanup

* for some reason lex.rs was not formatted, may be causing my error

* remove mod completion from lib.rs which was not being used along with quickcheck macros

* add in allow unused imports

* comment out one failing external test; comment out one failing internal test

* revert commenting out failing tests; something else might be going on; someone with a windows machine should check and see what is going on with these failing windows tests

* Update Cargo.toml

Extend the optional features to nu-command

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2021-01-12 17:59:53 +13:00
7d07881d96 Bump to 0.25.2 (#2908) 2021-01-12 07:50:53 +13:00
3e6e3a207c Feature/def signature with comments (#2905)
* Put parse_definition related funcs into own module

* Add failing lexer test

* Implement Parsing of definition signature

This commit applied changes how the signature of a function is parsed. Before
there was a little bit of "quick-and-dirty" string-matching/parsing involved.
Now, a signature is a little bit more properly parsed.
The grammar of a definition signature understood by these parsing-functions is
as follows:
 `[ (parameter | flag | <eol>)* ]`
where
parameter is:
    `name (<:> type)? (<,> | <eol> | (#Comment <eol>))?`
flag is:
    `--name (-shortform)? (<:> type)? (<,> | <eol> | (#Comment <eol>))?`
(Note: After the last item no <,> has to come.)
Note: It is now possible to pass comments to flags and parameters
Example:
[
  d:int          # The required d parameter
  --x (-x):string # The all powerful x flag
  --y (-y):int    # The accompanying y flag
]

(Sadly there seems to be a bug (Or is this expected behaviour?) in the lexer, because of which `--x(-x)` would
be treated as one baseline token and is therefore not correctly recognized as 2. For
now a space has to be inserted)

During the implementation of the module, 2 question arose:
Should flag/parameter names be allowed to be type names?
Example case:
```shell
def f [ string ] { echo $string }
```
Currently an error is thrown

* Fix clippy lints

* Remove wrong comment

* Add spacing

* Add Cargo.lock
2021-01-12 06:53:58 +13:00
481c6d4511 nu_cli refactor in preparation for a crate called nu_command (#2907)
* move basic_shell_manager to nu-engine

* move basic_evaluation_context to nu-engine

* fix failing test in feature which commands/classified/external.rs
2021-01-11 17:58:15 +13:00
231a445809 working for comparing filepath to string (#2906)
* working for comparing filepath to string

* added tests
2021-01-11 16:41:19 +13:00
93e8f6c05e Split nu-cli into nu-cli/nu-engine (#2898)
We split off the evaluation engine part of nu-cli into its own crate. This helps improve build times for nu-cli by 17% in my tests. It also helps us see a bit better what's the core engine portion vs the part specific to the interactive CLI piece.

There's more than can be done here, but I think it's a good start in the right direction.
2021-01-10 15:50:49 +13:00
9de2144fc4 compare filepath and string (#2897) 2021-01-09 14:09:49 -06:00
363dc51ba0 Add aliased command to which output (#2894)
* Add aliased command to which output

* Fix alias arguments not being displayed
2021-01-10 06:19:46 +13:00
99117ff2ef Fix reading/writing bigint and bigdecimal (#2893) 2021-01-09 12:53:59 +13:00
5356cb9fbd Obey precedence rules in which; Fix #2875 (#2885)
* Obay precedence rules in which; Fix #2875

Before which did not obay the precedence of alias before def commands.
Furthermore, `which -a echo` would only report either an alias or a def command or an
internal command with the provided name. Not all.

With this commit applied its fixed :)

Example:
```shell
/home/leo/repos/nushell(fix/which_reports_wrong_usage)> def echo [] {^echo hi}
/home/leo/repos/nushell(fix/which_reports_wrong_usage)> echo
hi
/home/leo/repos/nushell(fix/which_reports_wrong_usage)> which -a echo
───┬──────┬──────────────────────────┬─────────
 # │ arg  │           path           │ builtin
───┼──────┼──────────────────────────┼─────────
 0 │ echo │ Nushell custom command   │ No
 1 │ echo │ Nushell built-in command │ Yes
 2 │ echo │ /usr/bin/echo            │ No
───┴──────┴──────────────────────────┴─────────
/home/leo/repos/nushell(fix/which_reports_wrong_usage)> alias echo = ^echo hi there
/home/leo/repos/nushell(fix/which_reports_wrong_usage)> echo
hi there
/home/leo/repos/nushell(fix/which_reports_wrong_usage)> which -a echo
───┬──────┬──────────────────────────┬─────────
 # │ arg  │           path           │ builtin
───┼──────┼──────────────────────────┼─────────
 0 │ echo │ Nushell alias            │ No
 1 │ echo │ Nushell custom command   │ No
 2 │ echo │ Nushell built-in command │ Yes
 3 │ echo │ /usr/bin/echo            │ No
───┴──────┴──────────────────────────┴─────────
```

* Fix clippy lint

* Fix vec always Some even if empty
2021-01-09 06:44:31 +13:00
0e13d9fbaa Rename the Path and Pattern primitives (#2889)
* Rename the Path primitive to FilePath

* Rename glob pattern also

* more fun

* Fix the Windows path methods
2021-01-08 20:30:41 +13:00
2dcb16870b Treat all the startup commands as a single script file (#2890) 2021-01-08 19:36:31 +13:00
ac9909112f Remove the line primitive (#2887) 2021-01-08 14:45:25 +13:00
eb3c2c9e76 Add comments to next LiteCommand (#2846)
This commit applied adds comments preceding a command to the LiteCommands new
field `comments`.

This can be usefull for example when defining a function with `def`. Nushell
could pick up the comments and display them when the user types `help my_def_func`.

Example
```shell
def my_echo [arg] { echo $arg }
```
The LiteCommand def will now contain the comments `My echo` and `It's much
better :)`.

The comment is not associated with the next command if there is a (or multiple) newline
between them.
Example
```shell

echo 42
```

This new functionality is similar to DocStrings. One might introduce a special
notation for such DocStrings, so that the parser can differentiate better
between discardable comments and usefull documentation.
2021-01-08 06:14:51 +13:00
3d29e3efbf Update README.md 2021-01-07 18:12:04 +13:00
f410fb6689 Document lexer (#2865)
* Update dependencies

* Document the lexer and lightly improve its names

The bulk of this pull request adds a substantial amount of new inline
documentation for the lexer. Along the way, I made a few minor changes
to the names in the lexer, most of which were internal.

The main change that affects other files is renaming `group` to `block`,
since the function is actually parsing a block (a list of groups).

* Fix rustfmt

* Update lock

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
Co-authored-by: Jonathan Turner <jonathan.d.turner@gmail.com>
2021-01-07 16:03:00 +13:00
eb62fd466e Adding coerce filesize functionality to math avg median (#2848)
* Adding coerce filesize functionality to math avg median

* Updating initial value creating in Math Summation Reducer

* Update reducers.rs

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2021-01-07 16:01:52 +13:00
b50cdd6de8 Update dependency rust-embed now that issue with its use of syn has been fixed. (#2880)
* update the rust-embed dependency of nu-cli to 5.8.0 and undo the version pin of syn now that rust-embed-impl has been fixed

* unpin syn version in chart plugin
2021-01-07 14:33:39 +13:00
f38e2b5c6d updated dependencies (#2857)
Same as #2786

Co-authored-by: sousajo <sousajo@pop-os.localdomain>
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2021-01-07 13:38:22 +13:00
455915ec9e upgrade shadow-rs 0.5.8 (#2861)
* update to shadow-rs 0.4. use easy

* update shadow-rs to 0.5

* fix version not used

* update

* update Cargo.lock

* update Cargo.lock

* fix wasm build error when use dependence git2
fix error link:https://dev.azure.com/nushell/nushell/_build/results?buildId=4858&view=logs&j=1a745d4c-b027-5f34-06d8-d6f256bfe9f9&t=a0a335cb-fa1f-5bbf-be01-1a90d6899e54

* remove code not used; fix warning by RUSTFLAGS="-D warnings" build error

* upgrade shadow-rs 0.5.2

* upgrade shadow-rs 0.5.7

make nushell reduce dependence crates smaller and  build fast.

* upgrade shadow-rs 0.5.8

fix when use api 'strip_prefix()' method in less than rust1.45.0 build failed
2021-01-07 06:51:28 +13:00
2333158256 nucli refactor: move rustyline and ctrlc features in cli.rs to line_editor.rs (#2854)
* move rustyline and ctrlc features in cli.rs to feature.rs

* rename feature.rs to line_editor.rs
2021-01-07 06:47:36 +13:00
3ffa804088 Add syn to the lock file (#2871) 2021-01-06 15:58:04 +13:00
98810d22b1 Update Cargo.toml 2021-01-06 15:37:39 +13:00
5e72b2a797 Bump to 0.25.1 for the hotfix release (#2870) 2021-01-06 15:16:08 +13:00
7e4e7fa4a6 Pin the syn version to avoid breaking change (#2868)
* Pin the syn version to avoid breaking change

* pin syn in wasm also
2021-01-06 14:32:08 +13:00
d297199d7c Bump to 0.25.0 (#2860) 2021-01-05 18:10:24 +13:00
17a433996e rename set/set-env to let/let-env (#2859) 2021-01-05 12:30:55 +13:00
b9bb4692a4 Allow source during parsing. Hacky but works (#2855) 2021-01-04 19:32:17 +13:00
d05dcdda02 make nushell reduce dependence crates smaller and build fast (#2853)
* update to shadow-rs 0.4. use easy

* update shadow-rs to 0.5

* fix version not used

* update

* update Cargo.lock

* update Cargo.lock

* fix wasm build error when use dependence git2
fix error link:https://dev.azure.com/nushell/nushell/_build/results?buildId=4858&view=logs&j=1a745d4c-b027-5f34-06d8-d6f256bfe9f9&t=a0a335cb-fa1f-5bbf-be01-1a90d6899e54

* remove code not used; fix warning by RUSTFLAGS="-D warnings" build error

* upgrade shadow-rs 0.5.2

* upgrade shadow-rs 0.5.7

make nushell reduce dependence crates smaller and  build fast.
2021-01-04 06:14:03 +13:00
27fe356214 Add proper shadowing (#2851) 2021-01-03 20:48:02 +13:00
fc44df1e45 Don't leak set/set-env/source scopes via actions (#2849) 2021-01-03 19:44:21 +13:00
77f915befe Tighten how input streams handle nothing, and related (#2847) 2021-01-03 14:22:44 +13:00
a5f7600f6f Fix typos (#2842) 2021-01-02 17:24:32 +13:00
7eb8634ad7 Fix typo in sort-by error message (#2841) 2021-01-01 18:34:50 -06:00
452d8c06e9 Improve some errors, streamline internal error handling (#2839)
* Improve some errors, streamline internal error handling

* Fix lints
2021-01-02 08:52:19 +13:00
48f535f02e Display aliases and custom commands in which; fix #2810 (#2834)
* Display aliases and custom commands in which; Fix #2810

Example output of nu after the commit is applied:

```shell
/home/leo/repos/nushell(feature/which_inspect_alias)> def docker-ps [] { docker ps --format '{{json .}}' | from json -o }
/home/leo/repos/nushell(feature/which_inspect_alias)> which docker-ps
───┬───────────┬────────────────────────┬─────────
 # │    arg    │          path          │ builtin
───┼───────────┼────────────────────────┼─────────
 0 │ docker-ps │ nushell custom command │ No
───┴───────────┴────────────────────────┴─────────
/home/leo/repos/nushell(feature/which_inspect_alias)> alias d = gid pd
/home/leo/repos/nushell(feature/which_inspect_alias)> which d
───┬─────┬───────────────┬─────────
 # │ arg │     path      │ builtin
───┼─────┼───────────────┼─────────
 0 │ d   │ nushell alias │ No
───┴─────┴───────────────┴─────────
```

* Update documentation
2021-01-02 06:40:44 +13:00
43c10b0625 Properly handle commands defined inside of other commands (#2837)
* Fix function inner scopes

* tweak error
2021-01-01 19:23:54 +13:00
328b09fe04 Properly error when 'source' argument can't be found (#2836) 2021-01-01 17:33:38 +13:00
15d49e4096 Rust 1.49 Clippy Fixes (#2835) 2021-01-01 15:13:59 +13:00
3ef53fe2cd move create_default_context out of cli.rs and into its own mod (#2833) 2021-01-01 15:12:16 +13:00
7d8e759e98 Nucli refactor script mod (#2831)
* move process_script and run_script_standalone out of cli.rs

* cargo fmt

* code cleanup imports

* unused imports issue in cli.rs
2020-12-31 12:38:31 +13:00
69b3be61a4 Simplify run_block slightly (#2830)
* Simplify run_block slightly

* Add early return on C-c
2020-12-31 12:37:07 +13:00
79476a5cb2 Replace clipboard with arboard (#2832) 2020-12-31 06:16:02 +13:00
f449baf8de Change ls to output path (#2829)
* make name a path vs string

* add support for comparing path to string
2020-12-28 14:52:28 -06:00
5ff4bcfb7a Nucli refactor crate stream (#2828)
* nu-stream is building on its own, now clean up Cargo.toml

* replace the stream crate in nu-cli

* cc

* since we moved stream out of the nu-cli crate and into its own crate we need to remove pub(crate) and just make it pub

* clean up the prelude and hand merge everything together

* clean up Cargo.tom

* cargo fmt along with Cargo.lock
2020-12-28 18:34:27 +13:00
98537ce8b7 remove code not used. Fix use shadow-rs build warning (#2827)
* update to shadow-rs 0.4. use easy

* update shadow-rs to 0.5

* fix version not used

* update

* update Cargo.lock

* update Cargo.lock

* fix wasm build error when use dependence git2
fix error link:https://dev.azure.com/nushell/nushell/_build/results?buildId=4858&view=logs&j=1a745d4c-b027-5f34-06d8-d6f256bfe9f9&t=a0a335cb-fa1f-5bbf-be01-1a90d6899e54

* remove code not used; fix warning by RUSTFLAGS="-D warnings" build error

* upgrade shadow-rs 0.5.2
2020-12-28 08:00:14 +13:00
d2a00a2daa update to shadow-rs 0.5. make use easy (#2793)
* update to shadow-rs 0.4. use easy

* update shadow-rs to 0.5

* fix version not used

* update

* update Cargo.lock

* update Cargo.lock

* fix wasm build error when use dependence git2
fix error link:https://dev.azure.com/nushell/nushell/_build/results?buildId=4858&view=logs&j=1a745d4c-b027-5f34-06d8-d6f256bfe9f9&t=a0a335cb-fa1f-5bbf-be01-1a90d6899e54
2020-12-24 05:56:05 +13:00
f22938fc4a Add support for custom subcommands (#2814)
* Add support for custom subcommands

* clippy
2020-12-23 20:43:56 +13:00
1e67ae8e94 Fix broken links in the README (#2813) 2020-12-22 17:35:06 +13:00
c012d648fb Add experimental support for flags in custom commands (#2808)
* Add experimental support for flags in custom commands

* clippy
2020-12-21 20:36:59 +13:00
67acaae53c Rename cond math (#2807)
* Simplifies 'if' to work on the available scope rather than a stream

* Rename initializer/math for better readability

* Fix description

* fmt
2020-12-21 17:32:06 +13:00
e3da546e23 Simplifies 'if' to work on the available scope rather than a stream (#2805) 2020-12-21 16:02:39 +13:00
e5b136f70d Add script sourcing (#2803)
* Add script sourcing

* clippy
2020-12-19 20:47:34 +13:00
058ef69da3 Add set-env for setting environment variables (#2802) 2020-12-19 19:25:03 +13:00
2a483531a4 Bug report uses version command (#2797) 2020-12-19 18:25:32 +13:00
05202671db Improve errors on success (#2801) 2020-12-19 18:24:56 +13:00
8509873043 Parse mid-line comments (#2800) 2020-12-19 11:23:02 +13:00
57a2d695e2 Removing the defs inside of blocks for now (#2798) 2020-12-19 07:53:00 +13:00
0b5ab1ef22 Don't print a nothing value (#2796) 2020-12-19 05:48:22 +13:00
2eac79569c highlight trailing spaces in tables in darkgray (#2794)
* highlight trailing spaces in tables in darkgray

* added leading spaces highlighting

* added config point to change the color

* Trigger Build
2020-12-18 07:47:05 -06:00
ac578b8491 Multiline scripts part 2 (#2795)
* Begin allowing comments and multiline scripts.

* clippy

* Finish moving to groups. Test pass

* Keep going

* WIP

* WIP

* BROKEN WIP

* WIP

* WIP

* Fix more tests

* WIP: alias starts working

* Broken WIP

* Broken WIP

* Variables begin to work

* captures start working

* A little better but needs fixed scope

* Shorthand env setting

* Update main merge

* Broken WIP

* WIP

* custom command parsing

* Custom commands start working

* Fix coloring and parsing of block

* Almost there

* Add some tests

* Add more param types

* Bump version

* Fix benchmark

* Fix stuff
2020-12-18 20:53:49 +13:00
5183fd25bb Bump version (#2792) 2020-12-16 09:13:18 +13:00
10f5a8ef78 Update uom and heim dependencies (#2767)
v0.29.0 and earlier versions of `uom` fail to compile on nightly because
of now-ambiguous trait bounds. The issue was corrected in v0.30.0 of
`uom`. `uom` and `heim` dependencies have been updated to the
latest version to include this fix and allow nushell to compile on
nightly.

Co-authored-by: Boutin, Michael <mjboutin@ecolab.com>
2020-12-15 13:27:21 -06:00
a30837298d Bump version (#2791) 2020-12-16 06:30:50 +13:00
f377a3a7b4 Added math abs command. (#2789) 2020-12-16 05:37:12 +13:00
83c874666a Date utility commands (#2780)
* updated & added date related commands based on the new design

* added proper error handling when date format string is invalid

* fixed format issue

* fixed an issue caused due to the change in primitive Date type

* added `date list-timezone` command to list all supported time zones and updated `date to-timezone` accordingly
2020-12-12 12:18:03 -06:00
e000ed47cd Fix Gitpod dev setup by forcing a dev image rebuild (#2783) 2020-12-09 06:44:23 +13:00
af2f064f42 Add random chars cmd (#2782) 2020-12-09 06:43:46 +13:00
9c7b25134b Parsing: Explain parsing errors and show sample lines. (#2774)
This makes the errors slightly better. It took me a while to realize I was missing the `--raw` flag.

```
open "data.csv" | from csv --separator ';'
```

    error: Could not parse as CSV split by ',' (Line 1: expected 1 fields, found 14)
      ┌─ shell:1:1
      │
    1 │ open "data.csv" | from csv --separator ';'
      │ ^^^^ ------------------------------------------------- value originates from here
      │ │
      │ input cannot be parsed as CSV split by ','. Sample input:
    Name;Data
    Ugly;row
    AnotherUgly;row

I think this still needs some refinement. Maybe we don't want to show
the separator all the time, omitting the defaults or the separator
on other formats.
2020-12-07 07:19:04 +13:00
2d15df9e6c Revert "Bump Rustyline to 7.0.0 (#2776)" (#2778)
This reverts commit e73278990c.
2020-12-05 17:12:42 +13:00
d2ab287756 Tell Nu to look for hash's rest columns paths first. (#2777) 2020-12-04 13:49:58 -05:00
e73278990c Bump Rustyline to 7.0.0 (#2776)
* Bump Rustyline to 7.0.0

* Append history instead of always save

* Add associated type to Hinter

* Convert to using Rustyline KeyEvent

* Use AcceptOrInsertLine as struct

* Cargo fmt

* Make convert_keyevent pub

* Better naming for RL conversion
2020-12-05 06:29:40 +13:00
12bc92df35 Make run_block public (#2772) 2020-12-02 23:00:30 +13:00
f19a801022 enhanced version command with more info (#2773) 2020-12-01 13:57:49 -06:00
b193303aa3 Add hash command with base64 subcommand (#2769)
* WIP try testing hash command

Ensure test worked

fmt

WIP get it working for other types of base64

Use optional named arg

WIP

* rebased and refactored a little with encoding and decoding

Fix some typos

Add some more charactersets

refactor several args into the encoding config struct and fix character_set arg. It needs to match the field

Add main hash command so it can be found via help

Added tests for running the whole pipeline

* add test case to cover invalid character sets

* clippy and fmt
2020-12-01 06:47:35 +13:00
e299e76fcf Bump to 0.23 (#2766) 2020-11-25 07:22:27 +13:00
c857e18c4a Avoid subtract overflow when no ending index given. (#2764) 2020-11-24 05:50:38 -05:00
5fb3df4054 Initial implementation of the random decimal subcommand. (#2762)
Co-authored-by: Stacy Maydew <stacy.maydew@starlab.io>
2020-11-24 22:19:48 +13:00
8b597187fc Path Command Enhancement Project (#2742)
* Add string argument support for path subcommands

* Add --replace option to 'path extension' command

* Add examples of replacing for path extension

* Refactor path extension and its example

* Add replacement functionality to path basename

* Refactor path subcommands to support more args

This adds a lot of redundancy to non-relevant subcommands such as type,
exists or expand.

* Add replace and num_levels options to path dirname

* Rename num_levels option to num-levels

* Remove commented code

* Clean up path basename

* Fix path dirname description

* Add path filestem opts; Rename extension -> suffix

* Add prefix option and examples to path filestem

* Fix broken num-levels of path dirname

* Fix failing example test of path filestem

* Fix failing test of path extension

* Formatting

* Add Windows-specific path subcommand examples

`path expand` is still broken but otherwise seems to fix all examples
on Windows

* Fix weird path expand on Windows

Also disable example tests for path expand. Failed caconicalization
(e.g., due to path not existing) returns the original path so the
examples always fail.

* Formatting

* Return path datatype when appropriate

* Do not append empty remainder to path dirname

* Add tests for path subcommands

* Formatting

* Revisit path subcommand description strings

* Apply clippy suggestions; Formatting

* Remove problematic test checking '~' expansion

Wouldn't run on minimal due to useing optional dependency.
The test success was also deending on the presence of home dir on the
testing machine which might not be completely robust.

* Add missing newline to file
2020-11-24 22:18:38 +13:00
930f9f0063 Fix new clippy warnings (#2760)
* Fix new clippy warnings

* Fork serde-hjson and bring in

* Fork serde-hjson and bring in

* Fix clippy lint again
2020-11-22 13:37:16 +13:00
63d4df9810 Fix broken links to the documentation (#2755)
- fix the "installation chapter of the book" link, the current link has no "en/" for English as it does for other languages
- correct the link "learning resources in our [documentation]", missing in the new site, where the documentation is well highlighted in the top bar. Rephrased to point to the cookbook
2020-11-19 17:21:05 +13:00
13ba533fc4 helps table columns align a little bit better (#2753)
* helps table columns align a little bit better

* no change to push CI to work again.
2020-11-18 07:18:12 -06:00
6d60bab2fd fix zipped themes by adding zip feature (#2752) 2020-11-16 13:00:48 -06:00
5be774b2e5 these changes reduce size by 24mb (#2747) 2020-11-12 09:39:42 -06:00
b412ff92c0 Seq with dates (#2746)
* seq with dates - wip

* everything seems to be working, yay!

* clippy
2020-11-11 14:35:02 -06:00
5a75e11b0e Revert "Getting closer to multiline scripts (#2738)" (#2745)
This reverts commit e66bf70589.
2020-11-10 18:22:13 +13:00
e66bf70589 Getting closer to multiline scripts (#2738)
* Begin allowing comments and multiline scripts.

* clippy

* Finish moving to groups. Test pass
2020-11-10 16:52:42 +13:00
3924e9d50a added as_html switch so a selector can be passed to a selector (#2739) 2020-11-09 13:37:32 -06:00
8df748463d Getting ready for multiline scripts (#2737)
* WIP

* WIP

* WIP

* Tests are passing

* make parser more resilient

* lint
2020-11-10 05:27:07 +13:00
0113661c81 Flag to clear history file (#2720) 2020-11-10 05:23:41 +13:00
0ee054b14d Fix to md errors (#2729)
* Fix to md errors

* Fix variable name and avoid typecasts
2020-11-07 06:40:53 +13:00
80b39454ff Change Nu Shell and NuShell to Nushell (#2728) 2020-11-07 06:39:49 +13:00
97f3671e2c web scraping with css selectors (#2725)
* first step of making selector

* wip

* wip tests working

* probably good enough for a first pass

* oops, missed something.

* and something else...

* grrrr version errors
2020-11-03 15:46:42 -06:00
b674cee9d2 Remove the recursely-dep'd tests (#2727) 2020-11-04 09:26:07 +13:00
cb8491cfee Bump to 0.22 (#2726) 2020-11-04 07:31:41 +13:00
8196b031f8 Delete comments showing output of older nu version (#2717) 2020-11-03 19:29:13 +13:00
50dd56d3c4 bugfix for when pathext ends in ';' (#2723) 2020-11-02 13:00:47 -06:00
0f7e1d4d01 Support broad range of escape sequences (#2719)
* WIP

* changed to matches

* fixed a bug with osc

* changed back to if let because: clippy

* fixed example test
2020-10-30 15:06:15 -05:00
ec77c572b9 handle precision a tiny bit better than just hard coding to 4 decimal places. (#2712) 2020-10-31 06:40:28 +13:00
f97561c416 Inode added to ls -l (#2711) 2020-10-31 06:39:01 +13:00
5faa82e323 Update required rust version (#2718)
Co-authored-by: Joshua Shanks <jjshanks@stripe.com>
2020-10-30 12:17:49 -05:00
4e17292a12 Seq for nushell (#2704)
* seq command - WIP

* why, oh why

* works with parameters

* widths should've been optional

* dbg messages

* working. rest had to be first.

* updated so that it outputs a table instead of just strings

* made to work with floats, allowed separator be more than 1 char

* clippy

* fixed tests

* changed terminator help desc

* commit to get ci moving again
2020-10-29 15:51:48 -05:00
666fbbb0d1 Precision added to round cmd (#2710) 2020-10-29 16:14:08 +13:00
c6fe58467b Change alias shape inference to proposal of RFC#4 (#2685)
* Change alias shape inference to proposal of RFC#4

* Remove commented code

* Fix typo

* Change comment to be more informative

* Make match statement to lookup in table

* Remove resolved question

https://github.com/nushell/nushell/pull/2685#discussion_r509832054

* Pick ...or_insert_dependency functions into pieces

Previously there was get_shape_of_expr_or_insert dependency, now there is
get_shape_of_expr and get_shape_of_expr_or_insert_dependency

2 new functions have been added: get_result_shape_of_math_expr and
get_result_shape_of_math_expr_or_insert_dependency

* Remove flattening of deep binary expressions

Previously deep binary expressions have been flattened through the insertion of
fake vars. This logic was quite complicated. Now if a variable depends on the
result shape of a binary expression and the result shape can't be computed,
the variable simply depends on the whole binary.

* Change Expression::Variable(Variable::It(...)) to Expression::Variable(...)

* Simplify get_result_shapes_in_math_expr

* Simplify infer_shapes_in_binary_expr

* Clarify comment

* Clarify comment

* Fix clippy lint

* Move check for real var into checked_insert

* Remove comment

* Rename var
2020-10-29 06:49:38 +13:00
46d1938f5c add unicode to char command to print any unicode character (#2709)
* parsing unicode literal strings into chars

* refactored code to use -u option

* nudge ci
2020-10-28 09:08:09 -05:00
8229af7591 Improve parameter inference for blocks (#2708) 2020-10-28 07:47:11 +13:00
ee76523507 Add in parameter inference for blocks (#2706) 2020-10-27 20:37:35 +13:00
c283db373b Always escape non-literal arguments when running external command (#2697) 2020-10-27 16:33:40 +13:00
1b0ed30516 Added a bunch of extensions as helpers (#2698)
* Added a bunch of extensions as helpers

* change to restart ci
2020-10-26 09:25:06 -05:00
a6fdee4a51 bump to 0.21.1 (#2702)
* bump to 0.21.1

* bump trash version
2020-10-26 21:10:06 +13:00
6951fb440c Remove it expansion (#2701)
* Remove it-expansion, take 2

* Cleanup

* silly update to test CI
2020-10-26 19:55:52 +13:00
502c9ea706 Radix added to str decimal conversion (#2696) 2020-10-26 16:35:18 +13:00
22f67be461 added some weather symbols back and changed to emoji (#2695) 2020-10-22 15:10:19 -05:00
77ffd06715 Allow appending table literals. (#2693) 2020-10-22 03:26:30 -05:00
1d833ef972 Set weather chars as emoji only (#2691) 2020-10-22 14:36:27 +13:00
0d8064ed2d Add rounding functionalties (#2672)
* added math round

* added math floor

* added math ceil

* added math.md examples

* moved the detection of nonnumerical values in ceil/floor/round

* math round now works on streams

* math floor now works on streams

* math ceil now works on streams
2020-10-22 13:18:27 +13:00
cc06ea4d87 Add Tau constant (#2673)
Adds Tau constant using meval::Context.

Also adds a test to match pi's.

Note: Tau ends up not being more precise than 2*pi.

Resolves: #2258
2020-10-22 13:16:51 +13:00
3cf7652e86 fixed a bug where 'B' wasn't showing up (#2690)
right when get_appropriate_unit was called
2020-10-21 14:19:35 -05:00
1eb28c6cb6 add heavy & none table border options (#2686) 2020-10-21 08:53:08 -05:00
db590369a8 Fix filesize "B" regression (#2688) 2020-10-21 20:26:10 +13:00
f4d654d2a2 fix: remove duplicated "to" (#2682) 2020-10-21 05:35:43 +13:00
5725e55abb Flatten rows containing same sub-table columns with distinct column names. (#2684) 2020-10-20 05:37:40 -05:00
b6d19cc9fa Move command changes. Refactorings. (#2683)
Continuing on anchoring and improvements on Nu's overall internal commands (#2635).
`move column` sub command has been turned into the command `move` since
we use it to move exclusively columns. Examples added as well.

Fixed it to carry along any anchor locations that might be in place if
table to be moved originates from other sources.
2020-10-20 04:07:13 -05:00
bc6c884a14 added num-format to allow bytes to be formatted with commas. (#2681) 2020-10-19 12:52:11 -05:00
cb78bf8fd6 add filesize_format to config (#2676) 2020-10-19 11:34:39 -05:00
400bc97e35 Add parser improvements (#2679)
* Add parser improvements

Previously everything starting with "$" was parsed as a column path.
With this commit applied, the lite_arg starting with $ is parsed as
the most appropriate thing
- $true/$false ==> Expression::Boolean
- $(...) ==> Invocation
- $it ==> ColumnPath
- Anything with at least one '.' ==> ColumnPath
- Anything else ==> Variable

* Ignore failing tests
2020-10-19 20:03:14 +13:00
2fd464bf7b Refactor to md and Add Padding for Pretty Flag (#2678)
* refactor and cleanup to md

* Add padding around values in each row

* Add padding to test

* Update code to satisfy Clippy and pass other failing tests
2020-10-19 19:58:24 +13:00
e626522b3a LS support for other number formatting (#2650)
* make sort-by fail gracefully if mismatched types are compared

* Added a test to check if sorted-by with invalid types exists gracefully

* Linter changes

* removed redundant pattern matching

* Changed the error message

* Added a comma after every argument

* Changed the test to accomodate the new err messages

* Err message for sort-by invalid types now shows the mismatched types

* Lints problems

* Changed unwrap to expect

* Added the -f flag to rm command

Now when you a use rm -f there will be no error message, even if the
file doesnt actually exist

* Lint problems

* Fixed the wrong line

* Removed println

* Spelling mistake

* Fix problems when you mv a file into itself

* Lint mistakes

* Remove unecessary filtering in most cases

* Allow the removal of sockets

* Conditional compilations to systems without socket

* Add a size-format option to ls command

* Added kib and mib formating

* Make patterns lowercase

* New subcommand to format, filesize

* Forgot the linter once more

* Remove the ls changes since its no longer needed

* CI mistakes

* Lint stuff

* Fix lint

* Added formatting for bytes

* fix lint

* Changed the usage comment
2020-10-17 06:15:40 +13:00
791e07650d ColumnPath creation flexibility. (#2674) 2020-10-15 17:25:17 -05:00
bf2363947b Add pretty flag to to md (#2640)
* First draft for adding a `pretty` flag to `to md`

* rustfmt

* Fix Clippy warnings

* rustfmt

* Using Clippy suggestion broken code, reverting and putting in a statement to ignore clippy warning

* Add test for `to md -p`
2020-10-15 16:20:55 +13:00
a2cc2259e7 add bson and sqlite to wix (#2668)
* add bson and sqlite to wix

* add sqlite and bson from and to
2020-10-14 04:46:06 -05:00
808fe496a6 Fix typo in test support crate description (#2669) 2020-10-14 04:45:32 -05:00
2fb48bd6ac Flatten command. (#2670) 2020-10-14 04:36:11 -05:00
2df8775b48 Include chart binaries. (#2667) 2020-10-13 17:04:27 -05:00
e02b4f1443 Update wix plugin check with base test. (#2666) 2020-10-13 16:22:49 -05:00
194782215f Update main.wxs
Rename the plugins to be the production names
2020-10-14 09:01:13 +13:00
df17d28c0f Create README.build.txt 2020-10-14 07:14:47 +13:00
5f43c8f024 Update lib.rs 2020-10-14 06:45:04 +13:00
1a18734f9a Update Cargo.toml 2020-10-14 06:44:06 +13:00
4a70c1ff4f Update Cargo.toml
Get around the mutual dependency?
2020-10-14 06:42:24 +13:00
770e5d89f2 Bump to 0.21 (#2663) 2020-10-14 06:19:09 +13:00
cfac8e84dd Disable rustyline bracketed paste mode by default (#2659)
Multiline pastes wait for the user to hit enter before running,
because they enter a special paste mode in rustyline called
'bracketed paste' by default. This commit disables that mode
by default for nushell, causing multiline pastes to be executed
immediately, treating each new line as a separate command.
2020-10-13 19:33:36 +13:00
43d90c1745 Root level cleanup (#2654)
* Remove unused files from rootdir

* Remove unused build deps
2020-10-12 22:47:46 -05:00
38bdb053d2 Add tests for get_data_by_key (#2658)
* Add test for get_data_by_key

* Apply same order for ValuExt impl

* Nothing helper for tests

* Use get_data_by_key from ValueExt
2020-10-12 22:46:58 -05:00
95e61773a5 Restore bigint/bigdecimal serialization. (#2662) 2020-10-12 21:44:28 -05:00
4e931fa73f Extract out xpath to a plugin. (#2661) 2020-10-12 18:18:39 -05:00
2573441e28 xpath command for nushell (#2656)
* xpath prototype

* new xpath engine is finally working

* nearly there

* closer

* working with list, started to add test, code cleanup

* broken again

* working again - time for some cleanup

* cleaned up code, added error handling and test

* update example, fix clippy

* removed commented char
2020-10-12 08:03:00 -05:00
5770b15270 Use iterator chain instead of string concat. (#2655)
* Use iterator chain instead of string concat

* Add regression test for multi-value lines
2020-10-10 18:30:48 +13:00
6817b472d0 Handle inf/nan in delimited data (#2652) 2020-10-09 19:27:01 +13:00
2b076369e0 handle duration overflow error (#2616)
* handle duration overflow error

* handle checked_add_signed result
2020-10-09 14:51:47 +13:00
973a8ee8f3 Allow config to work with column paths. (#2653)
Nu has many commands that allow the nuño to customize behavior such
as UI and behavior. Today, coloring can be customized, the line editor,
and other things. The more options there are, the higher the complexity
in managing them.

To mitigate this Nu can store configuration options as nested properties.

But to add and edit them can be taxing. With column path support we can
work with them easier.
2020-10-08 20:04:19 -05:00
1159d3365a Fix clippy lints (#2651) 2020-10-09 10:47:51 +13:00
152ba32eb7 Debugging tips for contributors (#2647) 2020-10-08 15:14:59 +13:00
153320ef33 Added -1 back when determining term_width (#2646)
* Added -1 back

* retrigger checks

* removed the max

* fixed a mistake
2020-10-07 12:45:57 -05:00
ff236da72c [wasi] Update time & instant crates (#2645)
* [wasi] Update time & instant crates

In https://github.com/nushell/nushell/pull/2643 instant was updated by adding it as a hard dependency in Cargo.toml, but it's better to avoid it and only update in Cargo.lock via `cargo update -p ...`.

Additionally, updated `time` crate so that now some basic commands like `ls` work too, although formatting is pretty bad.

* Update default terminal width to 80

If termsize can't return anything, use 80 chars (e.g. on WASI).
2020-10-07 15:26:16 +13:00
54326869e4 Parse decimals as BigDecimal (#2644)
Use implicit serde from BigDecimal crate
2020-10-07 14:01:40 +13:00
f14f4e39c5 Begin adding wasi support (#2643)
* Begin adding wasi support

* Now it builds and runs but needs more help
2020-10-07 11:21:24 +13:00
a18b2702ca Parse integers as BigInt (#2642)
* Parse integer shape as BigInt

* Use implicit serde from BigInt crate
2020-10-07 06:30:18 +13:00
93410c470e Bump dtparse to fix panic (#2632) 2020-10-07 06:27:06 +13:00
5d945ef869 empty? rewrite. (#2641) 2020-10-06 05:21:20 -05:00
df07be6a42 Fix to_md function name (#2636)
* Fix to_md function name

* rustfmt
2020-10-05 18:13:27 -05:00
3c32d4947c added blink and underline options to coloring (#2638) 2020-10-05 18:12:56 -05:00
2ea5235aea Ensure Wix lists Nu plugin binaries. (#2637) 2020-10-05 14:29:04 -05:00
c096f031ce update wix to include _core_ and _extra_ plugins + s3, chart, line (#2634) 2020-10-04 05:56:33 +13:00
ae1d4bdb4c Nushell internal commands. Anchor locations tracker surveying. (#2635) 2020-10-03 09:06:02 -05:00
0adf2accdd Fix defaulting alias var values (#2631) 2020-10-03 09:18:23 +13:00
4201f48be5 Left Pad and Right Pad String (#2630)
* WIP

* left and right pad strings

* fixed some tests
2020-10-02 14:45:59 -05:00
b076e375ca Fix broken removal of sockets (#2629)
* make sort-by fail gracefully if mismatched types are compared

* Added a test to check if sorted-by with invalid types exists gracefully

* Linter changes

* removed redundant pattern matching

* Changed the error message

* Added a comma after every argument

* Changed the test to accomodate the new err messages

* Err message for sort-by invalid types now shows the mismatched types

* Lints problems

* Changed unwrap to expect

* Added the -f flag to rm command

Now when you a use rm -f there will be no error message, even if the
file doesnt actually exist

* Lint problems

* Fixed the wrong line

* Removed println

* Spelling mistake

* Fix problems when you mv a file into itself

* Lint mistakes

* Remove unecessary filtering in most cases

* Allow the removal of sockets

* Conditional compilations to systems without socket
2020-10-02 17:50:55 +13:00
2f1016d44f Add examples to update cmd (#2628) 2020-10-01 20:13:42 -05:00
ddf9d61346 Line charts. Chart plugin sub command extraction. (#2627) 2020-10-01 19:23:10 -05:00
f0b7ab5ecc chart tweaks for windows (#2626) 2020-10-01 14:48:57 -05:00
e4c6336bd4 Convert to string before clip (#2624) 2020-10-01 18:22:19 +13:00
66061192f8 Fix "mv allows moving a directory into itself" (#2619)
* make sort-by fail gracefully if mismatched types are compared

* Added a test to check if sorted-by with invalid types exists gracefully

* Linter changes

* removed redundant pattern matching

* Changed the error message

* Added a comma after every argument

* Changed the test to accomodate the new err messages

* Err message for sort-by invalid types now shows the mismatched types

* Lints problems

* Changed unwrap to expect

* Added the -f flag to rm command

Now when you a use rm -f there will be no error message, even if the
file doesnt actually exist

* Lint problems

* Fixed the wrong line

* Removed println

* Spelling mistake

* Fix problems when you mv a file into itself

* Lint mistakes

* Remove unecessary filtering in most cases
2020-10-01 14:01:05 +13:00
b7bc4c1f80 Exit bar visualization if any key is pressed other than left and right arrow keys. (#2623) 2020-09-30 14:34:29 -05:00
a56abb6502 Bar Chart baseline. (#2621)
Bar Chart ready.
2020-09-30 13:27:52 -05:00
892a416211 Move BTreeMap to IndexMap to preserve order (#2617) 2020-09-30 19:49:40 +13:00
f45adecd01 fix select for column names with spaces (#2613) 2020-09-30 10:00:07 +13:00
bd015e82dc Tidy up crates in nu-protocol (#2611)
* Remove unneeded crates from nu-protocol

* Simplify join, use std fn
2020-09-29 16:33:43 +13:00
cf43b74f26 did_you_mean without dependency (#2610) 2020-09-29 16:32:29 +13:00
18909ec14a Describe object now matches cmd name (#2603) 2020-09-27 14:54:47 +13:00
ed243c88d2 Move non-essential deps into specific crates (#2601) 2020-09-26 18:32:52 +12:00
cb7723f423 Refactor scope (#2602)
* Refactor scope to have parents

* Refactor scope to have parents

* Refactor scope to have parents

* Clippy

Co-authored-by: Jonathan Turner <jonathan@pop-os.localdomain>
2020-09-26 11:40:02 +12:00
9dc88f8a95 Add env var during benchmark to randomize stack (#2600) 2020-09-26 10:57:48 +12:00
0a439fe52f Removed unused files in nu-data (#2598) 2020-09-25 15:44:59 +12:00
a8b65e35ec Consolidate suggestions code (#2597) 2020-09-25 15:44:24 +12:00
bd9e598bf0 did_you_mean returns just the word matches (#2595) 2020-09-24 15:56:19 +12:00
75f8247af1 added .cargo/config.toml to build with bigger stack on windows (#2594)
* added .cargo/config.toml to build with bigger stack on windows

* updated comments
2020-09-24 15:55:33 +12:00
e8ec5027ff remove without check path exist, rm -f (#2590) 2020-09-22 17:11:31 -04:00
ebba89ea31 Bump to 0.20 (#2588) 2020-09-22 19:54:46 +12:00
8388afc9d9 add support for the text/csv content-type (#2587) 2020-09-22 15:26:20 +12:00
b133724b38 Add space between column suggestions (#2586) 2020-09-22 14:14:11 +12:00
09429d08aa Implement passthrough for benchmark (#2580)
* Implement passthrough for benchmark

Add a new option -p,--passthrough to benchmark.
With this option, the benchmark command prints its results to stdout and passes the block's output to the next command in the pipeline.

* Add execution block for benchmark -p

`benchmark --passthrough` now takes a block where the benchmark output is sent.
Example:
`benchmark -p {save bench.toml} {ls}`
2020-09-22 05:30:16 +12:00
9b577b8679 Update bigint/bigdecimal (#2585)
* Update bigint/bigdecimal

* clippy
2020-09-22 05:28:31 +12:00
7a595827f1 Fix subcommands column on help commands (#2584) 2020-09-21 19:57:26 +12:00
332e12ded0 Added test for ls -a (#2582) 2020-09-21 19:56:37 +12:00
a508e15efe CtrlD exits current shell (#2583) 2020-09-21 19:56:10 +12:00
a5b6bb6209 Add global mode to str trim (#2576)
* Add global mode to str trim

The global mode allows skipping non-string values,
and processes rows and tables as well

* Add tests to action with ActionMode::Global
2020-09-20 21:04:26 +12:00
1882a32b83 Context cleanup (#2581)
* Specialize 'Context' to EvaluationContext and CompletionContext

* Specialize 'Context' to EvaluationContext and CompletionContext

* fmt
2020-09-20 09:29:51 +12:00
798766b4b5 Remove panics from random integer and make the constraint more idiomatic (#2578)
* Remove panics from random integer and make the constraint more idiomatic

* Add open intervals to NumericRange
2020-09-20 08:41:49 +12:00
193c4cc6d5 Include subcommands in help commands (#2575)
* Add minor fixes to comments

* Include subcommands in `help commands`
2020-09-20 08:37:47 +12:00
422b6ca871 Add system, user and idle times to benchmark command (#2571)
* Add system, user and idle times to benchmark command

* Feature-gate dependency on heim in benchmark

* Reorder let bindings in benchmark

* Fully feature-gate rich-benchmark and print 0sec on zero duration
2020-09-20 05:13:14 +12:00
2b13ac3856 more table themes rounded and reinforced (#2579) 2020-09-19 12:10:34 -05:00
4c10351579 Exclude internal commands from 'help command' (#2573) 2020-09-19 11:48:30 +12:00
dd27aaef1b Limit open streaming to non-files, and files > 32mb (#2570) 2020-09-19 07:51:28 +12:00
6eb4a0e87b add replace all option to str find-replace (#2569) 2020-09-18 11:28:50 -05:00
15f3a545f0 Cleanup code in get and nu-value-ext (#2563)
* Cleanup code in get and nu-value-ext

* Remove unnecessary return statements from get
2020-09-18 18:40:20 +12:00
365f76ad19 Tidy up help command text (#2566) 2020-09-18 18:13:53 +12:00
df2845a9b4 implementing case-sensitive & case-insensitive completion matching (#2556)
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-09-17 10:52:58 -04:00
8453261211 Update rustyline to latest (#2565)
* Update rustyline to latest

* Go ahead and use rustyline for testing
2020-09-17 18:02:30 +12:00
1dc8f3300e Make the sleep command pass data through. (#2558) 2020-09-16 19:34:37 -04:00
10d4edc7af Slim down configuration readings and nu_cli clean up. (#2559)
We continue refactoring nu_cli and slim down a bit configuration
readings with a naive metadata `modified` field check.
2020-09-16 18:22:58 -05:00
50cbf91bc5 Remove trim in favor of str trim (#2560) 2020-09-16 15:59:32 -04:00
d05f9b3b1e Make the sleep command respond to Ctrl+C (#2550) 2020-09-16 12:14:33 -04:00
f5fad393d0 Refactor completion trait (#2555)
* Remove completion code from help/value shells

* Tweak the `Completer` trait for nushell.

Previously, this trait was built around rustyline's completion traits, and for
`Shell` instances. Now it is built for individual completers inside of nushell
that will complete a specific location based on a partial string. For example,
for completing a partially typed command in the command position.
2020-09-16 16:37:43 +12:00
d19a5f4c2f add a few more ansi escape sequences (#2553) 2020-09-15 16:01:57 -05:00
04451af776 Ctrl c exit issue (#2548)
* fix ctrl_c problem on windows

* updated cargo.lock
2020-09-15 09:59:51 -05:00
232aca76a4 Update rawkey for ARM support (#2547)
v0.1.2 of rawkey currently doesn't compile on ARM; v0.1.3 remedies this.
2020-09-15 11:20:32 +12:00
0178b53289 Core nu plugin load capability. (#2544)
We introduce the `plugin` nu sub command (`nu plugin`) with basic plugin
loading support. We can choose to load plugins from a directory. Originally
introduced to make integration tests faster (by not loading any plugins on startup at all)
but `nu plugin --load some_path ; test_pipeline_that_uses_plugins_just_loaded` does not see it.

Therefore, a `nu_with_plugins!` macro for tests was introduced on top of nu`s `--skip-plugins`
switch executable which is set to true when running the integration tests that use the `nu!` macro now..
2020-09-14 09:07:02 -05:00
e05e6b42fe Simplify a few boolean creations (#2543) 2020-09-14 13:15:44 +12:00
dd79afb503 Fix yanked crossterm version (#2542) 2020-09-14 13:14:59 +12:00
599bb9797d Implement exclusive and inclusive ranges with ..< and .. (#2541)
* Implement exclusive and inclusive ranges with .. and ..=

This commit adds right-exclusive ranges.

The original a..b inclusive syntax was changed to reflect the Rust notation.
New a..=b syntax was introduced to have the old behavior.

Currently, both a.. and b..= is valid, and it is unclear whether it's valid
to impose restrictions.

The original issue suggests .. for inclusive and ..< for exclusive ranges,
this can be implemented by making simple changes to this commit.

* Fix collect tests by changing ranges to ..=

* Fix clippy lints in exclusive range matching

* Implement exclusive ranges using `..<`
2020-09-14 09:53:08 +12:00
c355585112 Set active shell column to boolean (#2540) 2020-09-13 16:25:38 +12:00
45f32c9541 Allow math avg to work on durations (#2529)
* Allow `math avg` to work on durations

* formatting

* fix linting issue and implemented `math sum` for duration

* fix linting issue

* applied requested changes

* applied requested change for avg.rs

* formatting
2020-09-12 18:56:05 -05:00
7528094e12 Allow folding with tables. (#2538) 2020-09-12 01:40:52 -05:00
dcfa135ab9 support key-value argument in with-env(#2490) (#2530)
* support key-value argument in with-env

* remove unused import

* fix format for lint
2020-09-11 18:17:35 +12:00
e9bb4f25eb accept multiple variables in with-env(#2490) (#2526)
* accept multiple variables in with-env(#2490)

* add examples for test

* fix format(#2490)
2020-09-10 19:23:28 +12:00
0f7a9bbd31 Further clarify duration conversion (#2522) 2020-09-10 15:33:37 +12:00
73e65df5f6 Fix path completions for cd command. (#2525)
Previously, we weren't expanding `~`, so `std::fs::metadata` was failing. We now
make use of `PathSuggestion` to get the actual path, as represented by a
`PathBuf`.
2020-09-09 18:32:20 -04:00
a63a5adafa remove unused dependencies (#2520)
* remove unused dependencies

* moved umask to cfg(unix)

* changed Inflector to inflector, hoping it fixes the issue.

* roll back Inflector

* removed commented out deps now that everything looks good.
2020-09-09 13:57:51 -05:00
2eb4f8d28a updated dependencies (#2517) 2020-09-09 10:35:45 +12:00
d9ae66791a allow decimals as a range boundary (#2509) 2020-09-08 05:30:11 +12:00
2c5939dc7d each group and each window subcommands. (#2508)
* First commit updating `config` to use subcommands (#2119)
    - Implemented `get` subcommand

* Implmented `config set` as a subcommand.

* Implemented `config set_into` as subcommand

* Fixed base `config` command
 - Instead of outputting help, it now outputs the list of all
 configuration parameters.

* Added `config clear` subcommand

* Added `config load` and `config remove` subcommands

* Added `config path` subcommand

* fixed clippy

* initial commit for implementing groups

* each group works

* each group is slightly cleaner + added example

* Added `each window` subcommand
    - No support for stride flag yet

* each window stride implemented

* Added tests and minor documentation changes

* fixed clippy

* fixed clippy again
2020-09-07 17:54:52 +12:00
3150e70fc7 Add cpu time to ps -l (#2507) 2020-09-07 17:02:45 +12:00
c9ffd6afc0 Improve range parsing and handling (#2506)
* Improve range parsing and handling

* linting
2020-09-07 14:43:58 +12:00
986b427038 Add modulo operator and simplify in/not-in (#2505) 2020-09-07 12:12:55 +12:00
c973850571 Show completions more than one level below ~ (#2503)
Previously, we'd check for a `~/` prefix and then return the home directory as
the base `PathBuf`. We failed to consider pushing any of the other possible path
components into this base dir, which prevent completions more than one level
deep (for example, `~/.config/<TAB>` would fail).
2020-09-06 20:06:13 -04:00
5a725f9651 Num links added to ls -l output (#2496) 2020-09-06 12:36:50 -04:00
79cc725aff Interpreting ranges for substring (#2499) 2020-09-06 12:35:11 -04:00
e2cbc4e853 Weather symbol cleanup (#2502)
* WIP - compiling but not working

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

* clean up some comments
2020-09-05 14:52:49 -05:00
b5a27f0ccb pr to get my main in sync (#2501)
* WIP - compiling but not working

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges
2020-09-05 13:13:34 -05:00
bdb12f4bff Weather chars (#2500)
* WIP - compiling but not working

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

* added weather symbols/chars
2020-09-05 13:13:07 -05:00
c9c29f9e4c Remove space from command name completion suggestion. (#2498)
Although convenient, since the user doesn't have to type the space, it could be
a little surprising to users since they may think that was the only completion
in certain completions modes (for example, `cycle`).
2020-09-05 15:15:20 +12:00
32951f1161 implement exec for unix platforms (#2495) 2020-09-04 22:27:01 -04:00
56f85b3108 Provide path as part of the suggestion from the path completer. (#2497) 2020-09-04 22:10:26 -04:00
16f85f32a2 ls **/* does not show hidden files without the -a flag (#2407)
* fixed: .*.(ext|*)

* ls **/* does not return hidden files without the -a flag

* fixed formatting

* fixed clippy issues

* fixed clippy issues, v2

* added `#[cfg(unix)]` to windows-failing test
2020-09-05 07:32:58 +12:00
2ae2f2ea9d Ensure ansi mode windows (#2494)
* WIP - compiling but not working

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

* ensure ansi mode is enabled on windows
ansi mode sometimes gets out of sync in Windows.
I'm not sure why but this appears to fix it.
2020-09-04 14:24:46 -05:00
4696c9069b use fs_extra to recursively move folders (#2487) 2020-09-04 11:44:53 +12:00
1ffbb66e64 Initial implementation of random integer subcommand. (#2489)
* Initial implementation of random integer subcommand.

* Added additional examples.

Co-authored-by: Stacy Maydew <stacy.maydew@starlab.io>
2020-09-04 07:23:02 +12:00
8dc7b8a7cd Update clipboard feature (#2491)
* WIP - compiling but not working

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

* shouldn't these both bet clipboard-cli?
2020-09-04 07:21:32 +12:00
666e6a7b57 Size: count unicode graphmemes as single char (#2482) 2020-09-03 04:54:00 +12:00
47c5346934 Update release.yml
Fix github workflow to use extra instead of stable
2020-09-02 16:17:06 +12:00
882cf74137 Bump to 0.19.0 (#2483) 2020-09-02 15:37:06 +12:00
57a26bbd42 Default alignment (#2481)
* WIP - compiling but not working

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

* fixed bug where no config.toml or not set settings made weird defaults.

* clippy & fmt again

* Header default alignment is left.

Co-authored-by: Andrés N. Robalino <andres@androbtech.com>
2020-09-01 19:08:41 -05:00
f9acb7a7a5 Support ~ (home directory) in completions.
This requires a bit of a hack in command completions, since we don't expand `~`
for the replacement, just long enough to get child entries.
2020-09-01 18:31:36 -05:00
569345e1d4 Color info for config.toml (#2478)
* WIP - compiling but not working

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

* some info on how to set colors
2020-09-01 08:48:27 -05:00
adbbcafd30 Add minor theme support (#2449)
* WIP - compiling but not working

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* removed extra comments
2020-09-01 17:09:55 +12:00
860c2a606d More bug fixes for completions (#2476)
* Use the cursor position for the span when between locations.

This fixes a bug where completing

    ls <TAB>

would result in replacing the entire line.

* Revert to the default update implementation.

Replacing the length of the elected value was intended to do replacement when
one moves inside a quote. The problem is that a long elected suggestion could
replace bits of a pipeline that are after the cursor. For example:

    ls <TAB> | get name | str collect
2020-09-01 16:10:46 +12:00
6b5d96337e Add examples to uniq (#2472) 2020-09-01 14:52:55 +12:00
f54cf8a096 Size command: rename max length to bytes (#2473)
Since max length is just getting the byte length, rename to bytes
in order to be more clear.
2020-09-01 14:51:35 +12:00
b5d591bb09 Improve support for completing within quotes. (#2474)
We ensure the partially cimpleted item doesn't include the end quote. We also
ensure that the appropriate span is replaced, not just the suggested position up
to the cursor position.
2020-09-01 14:13:00 +12:00
60ce497edc Only requote path arguments. (#2471) 2020-08-31 20:11:39 -04:00
dd4351e2b7 tolerate os error while executing ls command (#2466) 2020-09-01 05:14:37 +12:00
3f443f40d0 with_love table theme (#2468) 2020-08-31 11:50:32 -05:00
8192360b20 added compact_double table theme for funsies (#2467) 2020-08-31 09:49:27 -05:00
889b67ca92 Improve quoting for path completions. (#2464)
- Ensure quotes surround suggestion replacement when there are spaces.
- Ensure an appropriate quote character is chosen based on other quoting
  characters being in the suggestions.
2020-08-31 15:29:13 +12:00
8d1cecf643 Set auto-pivot to never by default (#2462) 2020-08-31 14:38:08 +12:00
188d33b306 Add a custom path completer to nushell. (#2463)
Previously, we used rustyline's filename completer. This allowed us to make
progress on the completion engine without building all the parts at once. We now
need our own filename completer to make progress.

The primary driver to having our own filename completer is that it can better
integrate with our path constructs. For example, if we have

    > ls .../<TAB>

we want to show a list of suggestions that includes all files two directories up
from the current working directory. The least jarring experience to a user would
be to maintain the three dots. The easiest way for us to do this is by building
our own completer and path constructs.
2020-08-30 22:28:09 -04:00
965e07d8cc Minor updates to variance (#2458) 2020-08-30 16:50:36 -04:00
d6b6b1df38 Changing the directory to '.' doesn't modify the prompt anymore (#2457)
Doing 'cd .' or an equal command used to modify the prompt: It appended an './' and was even
repeatable, leading to strange prompts (believe me, I tested it for too long).

This fixes #2432
2020-08-31 05:24:38 +12:00
c897ac6e1e Add support for multiline rustyline edits (#2456)
* Add support for multiline rustyline edits

* clippy
2020-08-30 20:00:28 +12:00
df691c6c91 Light fixes (#2455)
* Add optional commas for items in lists and tables

* A couple last fixes
2020-08-30 19:03:18 +12:00
abc05ece21 Add optional commas for items in lists and tables (#2454) 2020-08-30 18:19:54 +12:00
6f69ae8707 Add table literals (#2453)
* Add table literals

* clippy
2020-08-30 16:55:33 +12:00
84a6010f71 Minor stddev updates (#2452)
1. Add an example for getting the sample stddev
2. Opt for ? instead of generic Err match
2020-08-30 15:36:43 +12:00
634bb688c1 CONTRIBUTING: useful commands as list (#2448) 2020-08-30 15:33:28 +12:00
c14b209276 Add missing math commands to docs (#2447) 2020-08-30 15:32:38 +12:00
6535ae3d6e Show directories and executable for command completion. (#2446)
* Show directories and executable for command completion.

Previously we chose from two sets for completing the command position:

1. internal commands, and
2. executables relative to the PATH environment variable.

We now also show directories/executables that match the relative/absolute path
that has been partially typed.

* Fix for Windows
2020-08-30 15:31:42 +12:00
0390ec97f4 Create benign email test fixture (#2445)
1. The previous one was an Ebay email and flagged by AVs, create something simple.
2. Add test case for another header
2020-08-29 12:57:50 -04:00
e3c4d82798 Ensure command name available for Argument completion locations (#2443) 2020-08-28 19:50:46 -04:00
ee71a35786 make cd command complete only folders (#2431) 2020-08-28 14:38:30 -04:00
11ea5e61fc Fix out of bounds in header command when row size is different (#2437)
* Use header vec to loop over the row instead of the row itself to fix out of bounds

* Fixed formatting
2020-08-29 06:32:08 +12:00
02763b47f7 Add spans to pipelines. 2020-08-28 05:17:58 -05:00
4828a7cd29 Update config.yml 2020-08-28 06:02:06 +12:00
728852c750 Remove unnecessary reference to ichwh in command completer (#2435) 2020-08-27 10:14:04 -04:00
26cec83b63 Extract out history parts. 2020-08-27 06:28:18 -05:00
4724b3c570 Slim down cli plugin logic. 2020-08-27 06:28:18 -05:00
303a9defd3 Added math product support (#2249)
* added math product: working initial impl

* resolving merge conflicts

* impl std::ops::Mul for Filesize

* rebased with main; refactored product implementation

* fixed error msg, added docs for math product
2020-08-27 17:58:01 +12:00
a64270829e Move alias type inference (experimental) behind --infer/-i flag (#2418)
* put alias type inference behind --infer/-i flag

* revert cargo.lock
2020-08-27 17:48:13 +12:00
8f5df89a78 added -e --end to search from the end of the string for the pattern (#2430)
* added -e --end to search from the end of the string for the pattern

* tests
2020-08-27 17:46:45 +12:00
39f402c8cc Add configuration option to disable hinter (#2403) (#2405)
* Add configuration option to disable hinter

* Move hint configuration inside line_editor
2020-08-27 17:45:55 +12:00
7702d683c7 Allow the calculation of bytes and int. (#2160)
* Allow the calculation of bytes and int.

* fix clippy.

* minimal implement the into_into command.

* Revert "fix clippy."

This reverts commit 0d7cf72ed2.

* Revert "Allow the calculation of bytes and int."

This reverts commit 9c4e3787f5.

* set the argument to any type.

* if the argument is an int, return it with no change in value.

* add tests for into-int command.

* fix a faild test.
2020-08-27 17:44:18 +12:00
766533aafb Path dirname and filestem (#2428)
* add dirname and filestem to path commands

* hacked around with gedge's help to get it to compile with cargo b

* fmt
2020-08-26 14:47:23 -05:00
781e423a97 cleaned up sample ps1 script and added more comments. (#2429)
It's not complete but a good start for anyone interested in learning.
2020-08-26 14:46:01 -05:00
6e3a827e32 plugin changes to support script plugins (#2315)
* plugin changes to support script plugins
* all platforms can have plugins with file extensions
* added .cmd, .py, .ps1 for windows
* added more trace! statements to document request and response

* WIP

* pretty much working, need to figure out sink plugins

* added a home for scripting plugin examples, ran fmt

* Had a visit with my good friend Clippy. We're on speaking terms again.

* add viable plugin extensions

* clippy

* update to load plugins without extension in *nix/mac.

* fmt
2020-08-26 13:45:11 -05:00
6685f74e03 Command especific configuration extraction baseline. 2020-08-26 10:33:55 -05:00
034c33c2b5 Allow invocations and fix span error reporting. 2020-08-26 06:46:14 -05:00
08d1be79fc Add some cross-references to usage texts (#2417) 2020-08-26 09:43:41 +12:00
c563b7862e Update battery version (#2413)
Update battery. Should should move us onto the same uom version for both battery and heim.
2020-08-26 06:59:39 +12:00
a64cfb6285 Command expression need not carry span information. 2020-08-24 22:48:33 -05:00
f078aacc25 Improve the error message if alias type inference fails (#2399)
* Improve the error message if alias type inference fails

* Improve error further
2020-08-25 06:38:24 +12:00
d859bff877 Sort subcommands in the help text (#2396) 2020-08-24 08:35:16 +12:00
de5cd4ec23 Fix Dockerfile (cache clear of apt) (#2394) 2020-08-24 05:24:49 +12:00
48850becd8 Add some minor fixes (#2391) 2020-08-22 17:06:19 +12:00
2ea42f296c Date subcommands (#2383)
* refactored date command

* format files
2020-08-22 15:46:48 +12:00
eb2ba470c7 Have lite-parse complete return a complete bare form. (#2389)
Previously, lite parse would stack up opening delimiters in vec, and if we
didn't close everything off, it would simply return an error with a partial form
that didn't include the missing closing delimiters. This commits adds those
delimiters so that `classify_block` can parse correctly.
2020-08-22 15:43:40 +12:00
a951edd0d5 Fix the version command to include the commit hash (#2390)
* Fix the version command to include the commit hash when it is _not_ empty

* Format code
2020-08-22 15:21:59 +12:00
d65a38dd41 ls .file and ls **/.* show hidden files (#2379) 2020-08-21 21:49:34 -04:00
8f568f4fc5 Support completions for a single hyphen argument. (#2388)
The parser sees this as a positional argument, but when requesting completions
this could be either a filename that starts with a hyphen, or it could be a
flag. This expands the completion engine's interface to return a vec of possible
completion locations instead of an optional one, because we want to show all
possibilities instead of assuming one or the either.
2020-08-21 21:26:47 -04:00
ee26590011 touch: support multiple arguments (#2386)
Fixes #2384
2020-08-22 12:08:30 +12:00
11352f87f0 Remove rustyline context from nu's completion context (#2387) 2020-08-21 18:21:14 -04:00
9f85b10fcb Add method to convert ClassifiedBlock into completion locations. (#2316)
The completion engine maps completion locations to spans on a line, which
indicate whther to complete a command name, flag name, argument, and so on.

Initial implementation is simplistic, with some rough edges, since it relies
heavily on the parser's interpretation. For example

    du -

if asking for completions, `-` is considered a positional argument by the
parser, but the user is likely looking for a flag. These scenarios will be
addressed in a series of progressive enhancements to the engine.
2020-08-21 15:37:51 -04:00
0dd1403a69 Sleep command (#2381)
* Add deserialization of Primitive::Duration; Fixes #2373

* Implement Sleep command

* Add comment saying you should name your rest field "rest"

* Fix typo

* Add documentation for sleep command
2020-08-22 05:51:29 +12:00
cb4527fc0d Add text_color and line_color for table theming (#2378)
* created text_color and line_color functions with hopes of theming soon.

* added text_color and line_color to hastableproperties

* Refactor Tractor.

* more refactoring
2020-08-20 11:03:56 -05:00
ad395944ef SyntaxShape checking in Alias (#2377)
* initial, working for shallow internals

* add recurion on Block

* clean up/abstract, Invocations

* use Result

* inspection of Binary, tests

* improve code structure

* move arg shape inspection to Alias command

* add spanned errors, tests, cleanup for PR

* fix test, clippy
2020-08-20 15:18:55 +12:00
6126209f57 Fix column count to not break on empty tables (#2374) 2020-08-18 22:16:35 -04:00
43e061f8c6 Add wasm sample for CI (#2372)
* Add wasm sample for CI

* Add wasm sample for CI

* Add wasm sample for CI
2020-08-19 07:34:05 +12:00
738541f727 Move nu-data out of nu-cli (#2369)
* WIP for moving nu-data out

* Refactor nu-data out of nu-cli

* Remove unwraps

* Remove unwraps
2020-08-18 19:00:02 +12:00
1d5518a214 Err message for sort-by invalid types now shows the mismatched types (#2366)
* make sort-by fail gracefully if mismatched types are compared

* Added a test to check if sorted-by with invalid types exists gracefully

* Linter changes

* removed redundant pattern matching

* Changed the error message

* Added a comma after every argument

* Changed the test to accomodate the new err messages

* Err message for sort-by invalid types now shows the mismatched types

* Lints problems

* Changed unwrap to expect
2020-08-18 17:37:45 +12:00
57101d5022 Run exitscripts in original dir (#2352)
* Modify testcase

* Run exitscript in the folder it was specified

* Update documentation

* Add comment

* Borrow instead of clone

* Does this just... work on windows?

* fmt

* as_str

* Collapse if by order of clippy

* Support windows

* fmt

* refactor tests

* fmt

* This time it will work on windows FOR SURE

* Remove debug prints

* Comment

* Refactor tests

* fmt

* fix spelling

* update comment
2020-08-18 17:36:09 +12:00
f6ff6ab6e4 added various case conversion commands for str. Added the inflection … (#2363)
* added various case conversion commands for str. Added the inflection crate as a dependency

* lighten the restriction on the inflector dependency

* publishing the case commands

* fix typo

* fix kebab case test

* formatting
2020-08-18 08:18:23 +12:00
a224cd38ab Solving the issue "sort-by should fail gracefully if mismatched types are compared" (#2360) 2020-08-17 14:57:29 -04:00
e292bb46bb added the other table "themes" so that they could be used via config (#2365) 2020-08-17 11:20:00 -05:00
05aca1c157 Config refactoring baseline.
The initial configuration refactoring already gave significant benefits
with my use case. On another note, Commands should know and ask for
configuration variables. We could, as we refactor, add the ability
for commands to tell what configuration variables knows about and
their types.

This way, completers can be used too when using `config` command if we
also add a sub command that config could set variables to.

Commands stating the config variables they know about will allow us
to implement it in `help` and display them.
2020-08-17 04:49:33 -05:00
8d269f62dd Pass metadata in 'to json' (#2359) 2020-08-16 15:47:00 +12:00
b1a946f0dc Add test file for count command (#2358)
* Add test file for count command

* rustfmt + Clippy
2020-08-16 15:16:49 +12:00
c59f860b48 Renamed time units (#2356)
* Changed time units as outlined in issue #2353.
Also applied changes to to_str for Unit - not sure if that was what was wanted.

* Forgot the tests!

* Updated primitive.rs to match changes.

* Updated where example to match changes.

* And the html test!
2020-08-16 07:03:28 +12:00
c6588c661a Add column flag to count command 2020-08-15 07:52:59 -05:00
8fe269a3d8 Add random_numbers.csv to repo, so it is easier to update histogram examples 2020-08-15 07:51:12 -05:00
8f00713ad2 Update histogram examples 2020-08-15 07:51:12 -05:00
d1d98a897a Fix "occurrences" typo in histrogram test file 2020-08-15 07:51:12 -05:00
3dc95ef765 Fix typo in "occurrences" 2020-08-15 07:51:12 -05:00
84da815b22 Update README.md 2020-08-14 16:46:25 +12:00
371a951668 Split extra (#2348)
* Split default/extra plugins

* Oops, too many deletes

* Pipelines
2020-08-14 16:45:27 +12:00
baf84f05d9 removed syntect dependency, limited bat to regex-fancy and paging (#2347) 2020-08-13 15:33:35 -05:00
da4d24d082 Bump to 0.18.2. Move starship external. (#2345)
* Bump to 0.18.2. Move starship external.

* Fix failing test
2020-08-14 07:02:45 +12:00
22519c9083 Only have commit hash in version if git doesn't error (#2336)
* Add commit to version command

* Replace unwrap with expect.

* Only have commit hash if git doesn't error

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-08-13 06:51:12 +12:00
1601aa2f49 Remove with-symlink-targets flag from ls (#2334)
* Remove `with-symlink-targets` flag from `ls`

* Fix test to use `ls -l` to output all the columns of `ls`

* Fix error message

* Delete test that originally covered `ls -w`
2020-08-13 05:21:19 +12:00
88555860f3 Fetch content from S3 (#2328)
* fetch content from s3 resource

* remove submodule

* fix clippy

* update Cargo.lock

* fix s3 plugin dependency version
2020-08-13 05:20:22 +12:00
015d2ee050 Lint [result|option]_unwrap_used as been renamed to clippy::unwrap_used 2020-08-12 09:34:16 -05:00
dd7ee1808a histogram: rename 'count' column to 'ocurrences' 2020-08-12 09:34:16 -05:00
0db4180cea histogram: optionally use a valuator for histogram value. 2020-08-12 09:34:16 -05:00
8ff15c46c1 histogram gives back percentage column. (#2340) 2020-08-12 04:21:28 -05:00
48cfc9b598 apply all the block run's stream. (#2339) 2020-08-12 02:51:24 -05:00
722 changed files with 43067 additions and 17762 deletions

View File

@ -9,6 +9,12 @@ strategy:
linux-minimal:
image: ubuntu-18.04
style: 'minimal'
linux-extra:
image: ubuntu-18.04
style: 'extra'
linux-wasm:
image: ubuntu-18.04
style: 'wasm'
macos-stable:
image: macos-10.14
style: 'unflagged'
@ -37,6 +43,7 @@ steps:
if [ -e /etc/debian_version ]
then
sudo apt-get -y install libxcb-composite0-dev libx11-dev
sudo npm install -g wasm-pack
fi
if [ "$(uname)" == "Darwin" ]; then
curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain "stable"
@ -49,21 +56,27 @@ steps:
# echo "##vso[task.prependpath]$HOME/.cargo/bin"
# rustup component add rustfmt
displayName: Install Rust
- bash: RUSTFLAGS="-D warnings" cargo test --all --features stable
- bash: RUSTFLAGS="-D warnings" cargo test --all
condition: eq(variables['style'], 'unflagged')
displayName: Run tests
- bash: RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
condition: eq(variables['style'], 'unflagged')
displayName: Check clippy lints
- bash: RUSTFLAGS="-D warnings" cargo test --all --features stable
- bash: RUSTFLAGS="-D warnings" cargo test --all
condition: eq(variables['style'], 'canary')
displayName: Run tests
- bash: RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
- bash: cd samples/wasm && wasm-pack build
condition: eq(variables['style'], 'wasm')
displayName: Wasm build
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
condition: eq(variables['style'], 'canary')
displayName: Check clippy lints
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features --features=rustyline-support
condition: eq(variables['style'], 'minimal')
displayName: Run tests
- bash: RUSTFLAGS="-D warnings" cargo test --all --features=extra
condition: eq(variables['style'], 'extra')
displayName: Run tests
- bash: cargo fmt --all -- --check
condition: eq(variables['style'], 'fmt')
displayName: Lint

6
.cargo/config.toml Normal file
View File

@ -0,0 +1,6 @@
# use LLD as linker on Windows because it could be faster
# for full and incremental builds compared to default
[target.x86_64-pc-windows-msvc]
#linker = "lld-link.exe"
rustflags = ["-C", "link-args=-stack:10000000"]

View File

@ -73,7 +73,7 @@ workflows:
branches:
ignore: /.*/
tags:
only: /^v.*/
only: /^\d+\.\d+\.\d+$/
before_build:
- run: docker pull quay.io/nushell/nu:latest
- run: docker pull quay.io/nushell/nu-base:latest

View File

@ -23,8 +23,25 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Configuration (please complete the following information):**
- OS (e.g. Windows):
- Nu version (you can use the `version` command to find out):
- Optional features (if any):
Add any other context about the problem here.
Run `version | pivot` and paste the output to show OS, features, etc.
```
> version | pivot
╭───┬────────────────────┬───────────────────────────────────────────────────────────────────────╮
│ # │ Column0 │ Column1 │
├───┼────────────────────┼───────────────────────────────────────────────────────────────────────┤
│ 0 │ version │ 0.24.1 │
│ 1 │ build_os │ macos-x86_64 │
│ 2 │ rust_version │ rustc 1.48.0 │
│ 3 │ cargo_version │ cargo 1.48.0 │
│ 4 │ pkg_version │ 0.24.1 │
│ 5 │ build_time │ 2020-12-18 09:54:09 │
│ 6 │ build_rust_channel │ release │
│ 7 │ features │ ctrlc, default, directories, dirs, git, ichwh, ptree, rich-benchmark, │
│ │ │ rustyline, term, uuid, which, zip │
╰───┴────────────────────┴───────────────────────────────────────────────────────────────────────╯
```
**Add any other context about the problem here.**

View File

@ -26,7 +26,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --release --all --features=stable
args: --release --all --features=extra
- name: Create output directory
run: mkdir output
@ -38,7 +38,7 @@ jobs:
cp LICENSE output/LICENSE
rm output/*.d
rm output/nu_plugin_core_*
rm output/nu_plugin_stable_*
rm output/nu_plugin_extra_*
# Note: If OpenSSL changes, this path will need to be updated
- name: Copy OpenSSL to output
@ -68,7 +68,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --release --all --features=stable
args: --release --all --features=extra
- name: Create output directory
run: mkdir output
@ -80,7 +80,7 @@ jobs:
cp LICENSE output/LICENSE
rm output/*.d
rm output/nu_plugin_core_*
rm output/nu_plugin_stable_*
rm output/nu_plugin_extra_*
- name: Upload artifact
uses: actions/upload-artifact@v2
@ -112,7 +112,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --release --all --features=stable
args: --release --all --features=extra
- name: Create output directory
run: mkdir output
@ -129,7 +129,7 @@ jobs:
cp LICENSE output\
cp target\release\LICENSE-for-less.txt output\
rm target\release\nu_plugin_core_*.exe
rm target\release\nu_plugin_stable_*.exe
rm target\release\nu_plugin_extra_*.exe
cp target\release\nu_plugin_*.exe output\
cp README.build.txt output\README.txt
cp target\release\less.exe output\

6
.gitpod.Dockerfile vendored
View File

@ -1,5 +1,9 @@
FROM gitpod/workspace-full
# Gitpod will not rebuild Nushell's dev image unless *some* change is made to this Dockerfile.
# To force a rebuild, simply increase this counter:
ENV TRIGGER_REBUILD 1
USER gitpod
RUN sudo apt-get update && \
@ -11,4 +15,4 @@ RUN sudo apt-get update && \
rust-lldb \
&& sudo rm -rf /var/lib/apt/lists/*
ENV RUST_LLDB=/usr/bin/lldb-8
ENV RUST_LLDB=/usr/bin/lldb-11

View File

@ -4,7 +4,7 @@ tasks:
- name: Clippy
init: cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
- name: Testing
init: cargo test --all --features=stable,test-bins
init: cargo test --all --features=stable
- name: Build
init: cargo build --features=stable
- name: Nu

View File

@ -25,38 +25,51 @@ cargo build
### Useful Commands
Build and run Nushell:
- Build and run Nushell:
```shell
cargo build --release && cargo run --release
```
```shell
cargo build --release && cargo run --release
```
Run Clippy on Nushell:
- Build and run with extra features:
```shell
cargo build --release --features=extra && cargo run --release --features=extra
```
```shell
cargo clippy --all --features=stable
```
- Run Clippy on Nushell:
Run all tests:
```shell
cargo clippy --all --features=stable
```
```shell
cargo test --all --features=stable
```
- Run all tests:
Run all tests for a specific command
```shell
cargo test --all --features=stable
```
```shell
cargo test --package nu-cli --test main -- commands::<command_name_here>
```
- Run all tests for a specific command
Check to see if there are code formatting issues
```shell
cargo test --package nu-cli --test main -- commands::<command_name_here>
```
```shell
cargo fmt --all -- --check
```
- Check to see if there are code formatting issues
Format the code in the project
```shell
cargo fmt --all -- --check
```
```shell
cargo fmt --all
```
- Format the code in the project
```shell
cargo fmt --all
```
### Debugging Tips
- To view verbose logs when developing, enable the `trace` log level.
```shell
cargo build --release --features=extra && cargo run --release --features=extra -- --loglevel trace
```

4634
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ license = "MIT"
name = "nu"
readme = "README.md"
repository = "https://github.com/nushell/nushell"
version = "0.18.1"
version = "0.27.0"
[workspace]
members = ["crates/*/"]
@ -18,93 +18,139 @@ members = ["crates/*/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-cli = {version = "0.18.1", path = "./crates/nu-cli"}
nu-errors = {version = "0.18.1", path = "./crates/nu-errors"}
nu-parser = {version = "0.18.1", path = "./crates/nu-parser"}
nu-plugin = {version = "0.18.1", path = "./crates/nu-plugin"}
nu-protocol = {version = "0.18.1", path = "./crates/nu-protocol"}
nu-source = {version = "0.18.1", path = "./crates/nu-source"}
nu-value-ext = {version = "0.18.1", path = "./crates/nu-value-ext"}
nu-cli = { version = "0.27.0", path = "./crates/nu-cli", default-features = false }
nu-command = { version = "0.27.0", path = "./crates/nu-command" }
nu-data = { version = "0.27.0", path = "./crates/nu-data" }
nu-engine = { version = "0.27.0", path = "./crates/nu-engine" }
nu-errors = { version = "0.27.0", path = "./crates/nu-errors" }
nu-parser = { version = "0.27.0", path = "./crates/nu-parser" }
nu-plugin = { version = "0.27.0", path = "./crates/nu-plugin" }
nu-protocol = { version = "0.27.0", path = "./crates/nu-protocol" }
nu-source = { version = "0.27.0", path = "./crates/nu-source" }
nu-value-ext = { version = "0.27.0", path = "./crates/nu-value-ext" }
nu_plugin_binaryview = {version = "0.18.1", path = "./crates/nu_plugin_binaryview", optional = true}
nu_plugin_fetch = {version = "0.18.1", path = "./crates/nu_plugin_fetch", optional = true}
nu_plugin_from_bson = {version = "0.18.1", path = "./crates/nu_plugin_from_bson", optional = true}
nu_plugin_from_sqlite = {version = "0.18.1", path = "./crates/nu_plugin_from_sqlite", optional = true}
nu_plugin_inc = {version = "0.18.1", path = "./crates/nu_plugin_inc", optional = true}
nu_plugin_match = {version = "0.18.1", path = "./crates/nu_plugin_match", optional = true}
nu_plugin_post = {version = "0.18.1", path = "./crates/nu_plugin_post", optional = true}
nu_plugin_ps = {version = "0.18.1", path = "./crates/nu_plugin_ps", optional = true}
nu_plugin_start = {version = "0.18.1", path = "./crates/nu_plugin_start", optional = true}
nu_plugin_sys = {version = "0.18.1", path = "./crates/nu_plugin_sys", optional = true}
nu_plugin_textview = {version = "0.18.1", path = "./crates/nu_plugin_textview", optional = true}
nu_plugin_to_bson = {version = "0.18.1", path = "./crates/nu_plugin_to_bson", optional = true}
nu_plugin_to_sqlite = {version = "0.18.1", path = "./crates/nu_plugin_to_sqlite", optional = true}
nu_plugin_tree = {version = "0.18.1", path = "./crates/nu_plugin_tree", optional = true}
nu_plugin_binaryview = { version = "0.27.0", path = "./crates/nu_plugin_binaryview", optional = true }
nu_plugin_chart = { version = "0.27.0", path = "./crates/nu_plugin_chart", optional = true }
nu_plugin_fetch = { version = "0.27.0", path = "./crates/nu_plugin_fetch", optional = true }
nu_plugin_from_bson = { version = "0.27.0", path = "./crates/nu_plugin_from_bson", optional = true }
nu_plugin_from_sqlite = { version = "0.27.0", path = "./crates/nu_plugin_from_sqlite", optional = true }
nu_plugin_inc = { version = "0.27.0", path = "./crates/nu_plugin_inc", optional = true }
nu_plugin_match = { version = "0.27.0", path = "./crates/nu_plugin_match", optional = true }
nu_plugin_post = { version = "0.27.0", path = "./crates/nu_plugin_post", optional = true }
nu_plugin_ps = { version = "0.27.0", path = "./crates/nu_plugin_ps", optional = true }
nu_plugin_s3 = { version = "0.27.0", path = "./crates/nu_plugin_s3", optional = true }
nu_plugin_selector = { version = "0.27.0", path = "./crates/nu_plugin_selector", optional = true }
nu_plugin_start = { version = "0.27.0", path = "./crates/nu_plugin_start", optional = true }
nu_plugin_sys = { version = "0.27.0", path = "./crates/nu_plugin_sys", optional = true }
nu_plugin_textview = { version = "0.27.0", path = "./crates/nu_plugin_textview", optional = true }
nu_plugin_to_bson = { version = "0.27.0", path = "./crates/nu_plugin_to_bson", optional = true }
nu_plugin_to_sqlite = { version = "0.27.0", path = "./crates/nu_plugin_to_sqlite", optional = true }
nu_plugin_tree = { version = "0.27.0", path = "./crates/nu_plugin_tree", optional = true }
nu_plugin_xpath = { version = "0.27.0", path = "./crates/nu_plugin_xpath", optional = true }
crossterm = {version = "0.17.5", optional = true}
semver = {version = "0.10.0", optional = true}
syntect = {version = "4.2", default-features = false, features = ["default-fancy"], optional = true}
url = {version = "2.1.1", optional = true}
clap = "2.33.1"
ctrlc = "3.1.4"
dunce = "1.0.1"
futures = {version = "0.3", features = ["compat", "io-compat"]}
log = "0.4.8"
# Required to bootstrap the main binary
clap = "2.33.3"
ctrlc = { version = "3.1.7", optional = true }
futures = { version = "0.3.12", features = ["compat", "io-compat"] }
itertools = "0.10.0"
log = "0.4.14"
pretty_env_logger = "0.4.0"
quick-xml = "0.18.1"
starship = "0.43.0"
[dev-dependencies]
nu-test-support = {version = "0.18.1", path = "./crates/nu-test-support"}
dunce = "1.0.1"
nu-test-support = { version = "0.27.0", path = "./crates/nu-test-support" }
[build-dependencies]
serde = {version = "1.0.110", features = ["derive"]}
toml = "0.5.6"
[features]
ctrlc-support = ["nu-cli/ctrlc", "nu-command/ctrlc"]
directories-support = [
"nu-cli/directories",
"nu-cli/dirs",
"nu-command/directories",
"nu-command/dirs",
"nu-data/directories",
"nu-data/dirs",
"nu-engine/dirs",
]
ptree-support = ["nu-cli/ptree", "nu-command/ptree"]
rustyline-support = ["nu-cli/rustyline-support", "nu-command/rustyline-support"]
term-support = ["nu-cli/term", "nu-command/term"]
uuid-support = ["nu-cli/uuid_crate", "nu-command/uuid_crate"]
which-support = [
"nu-cli/ichwh",
"nu-cli/which",
"nu-command/ichwh",
"nu-command/which",
]
default = [
"nu-cli/shadow-rs",
"sys",
"ps",
"textview",
"inc",
"git-support",
"directories-support",
"ctrlc-support",
"which-support",
"ptree-support",
"term-support",
"uuid-support",
"rustyline-support",
"match",
"post",
"fetch",
"zip-support",
]
stable = ["default", "binaryview", "match", "tree", "post", "fetch", "clipboard-cli", "trash-support", "start", "starship-prompt", "bson", "sqlite"]
# Default
inc = ["semver", "nu_plugin_inc"]
ps = ["nu_plugin_ps"]
sys = ["nu_plugin_sys"]
textview = ["crossterm", "syntect", "url", "nu_plugin_textview"]
stable = ["default"]
extra = [
"default",
"binaryview",
"tree",
"clipboard-cli",
"trash-support",
"start",
"bson",
"sqlite",
"s3",
"chart",
"xpath",
"selector",
]
# Stable
binaryview = ["nu_plugin_binaryview"]
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
wasi = ["inc", "match", "ptree-support", "match", "tree", "rustyline-support"]
trace = ["nu-parser/trace"]
# Stable (Default)
fetch = ["nu_plugin_fetch"]
inc = ["nu_plugin_inc"]
match = ["nu_plugin_match"]
post = ["nu_plugin_post"]
ps = ["nu_plugin_ps"]
sys = ["nu_plugin_sys"]
textview = ["nu_plugin_textview"]
zip-support = ["nu-cli/zip", "nu-command/zip"]
# Extra
binaryview = ["nu_plugin_binaryview"]
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
chart = ["nu_plugin_chart"]
clipboard-cli = ["nu-cli/clipboard-cli", "nu-command/clipboard-cli"]
s3 = ["nu_plugin_s3"]
selector = ["nu_plugin_selector"]
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
start = ["nu_plugin_start"]
trace = ["nu-parser/trace"]
trash-support = ["nu-cli/trash-support", "nu-command/trash-support"]
tree = ["nu_plugin_tree"]
xpath = ["nu_plugin_xpath"]
clipboard-cli = ["nu-cli/clipboard-cli"]
ctrlc-support = ["nu-cli/ctrlc"]
directories-support = ["nu-cli/directories", "nu-cli/dirs"]
git-support = ["nu-cli/git2"]
ptree-support = ["nu-cli/ptree"]
starship-prompt = ["nu-cli/starship-prompt"]
term-support = ["nu-cli/term"]
trash-support = ["nu-cli/trash-support"]
uuid-support = ["nu-cli/uuid_crate"]
which-support = ["nu-cli/ichwh", "nu-cli/which"]
[profile.release]
#strip = "symbols" #Couldn't get working +nightly
codegen-units = 1 #Reduce parallel codegen units
lto = true #Link Time Optimization
opt-level = 'z' #Optimize for size
# Core plugins that ship with `cargo install nu` by default
# Currently, Cargo limits us to installing only one binary
@ -129,37 +175,83 @@ name = "nu_plugin_core_sys"
path = "src/plugins/nu_plugin_core_sys.rs"
required-features = ["sys"]
# Stable plugins
[[bin]]
name = "nu_plugin_stable_fetch"
path = "src/plugins/nu_plugin_stable_fetch.rs"
name = "nu_plugin_core_fetch"
path = "src/plugins/nu_plugin_core_fetch.rs"
required-features = ["fetch"]
[[bin]]
name = "nu_plugin_stable_binaryview"
path = "src/plugins/nu_plugin_stable_binaryview.rs"
required-features = ["binaryview"]
[[bin]]
name = "nu_plugin_stable_match"
path = "src/plugins/nu_plugin_stable_match.rs"
name = "nu_plugin_core_match"
path = "src/plugins/nu_plugin_core_match.rs"
required-features = ["match"]
[[bin]]
name = "nu_plugin_stable_post"
path = "src/plugins/nu_plugin_stable_post.rs"
name = "nu_plugin_core_post"
path = "src/plugins/nu_plugin_core_post.rs"
required-features = ["post"]
# Extra plugins
[[bin]]
name = "nu_plugin_stable_tree"
path = "src/plugins/nu_plugin_stable_tree.rs"
name = "nu_plugin_extra_binaryview"
path = "src/plugins/nu_plugin_extra_binaryview.rs"
required-features = ["binaryview"]
[[bin]]
name = "nu_plugin_extra_tree"
path = "src/plugins/nu_plugin_extra_tree.rs"
required-features = ["tree"]
[[bin]]
name = "nu_plugin_stable_start"
path = "src/plugins/nu_plugin_stable_start.rs"
name = "nu_plugin_extra_start"
path = "src/plugins/nu_plugin_extra_start.rs"
required-features = ["start"]
[[bin]]
name = "nu_plugin_extra_s3"
path = "src/plugins/nu_plugin_extra_s3.rs"
required-features = ["s3"]
[[bin]]
name = "nu_plugin_extra_chart_bar"
path = "src/plugins/nu_plugin_extra_chart_bar.rs"
required-features = ["chart"]
[[bin]]
name = "nu_plugin_extra_chart_line"
path = "src/plugins/nu_plugin_extra_chart_line.rs"
required-features = ["chart"]
[[bin]]
name = "nu_plugin_extra_xpath"
path = "src/plugins/nu_plugin_extra_xpath.rs"
required-features = ["xpath"]
[[bin]]
name = "nu_plugin_extra_selector"
path = "src/plugins/nu_plugin_extra_selector.rs"
required-features = ["selector"]
[[bin]]
name = "nu_plugin_extra_from_bson"
path = "src/plugins/nu_plugin_extra_from_bson.rs"
required-features = ["bson"]
[[bin]]
name = "nu_plugin_extra_to_bson"
path = "src/plugins/nu_plugin_extra_to_bson.rs"
required-features = ["bson"]
[[bin]]
name = "nu_plugin_extra_from_sqlite"
path = "src/plugins/nu_plugin_extra_from_sqlite.rs"
required-features = ["sqlite"]
[[bin]]
name = "nu_plugin_extra_to_sqlite"
path = "src/plugins/nu_plugin_extra_to_sqlite.rs"
required-features = ["sqlite"]
# Main nu binary
[[bin]]
name = "nu"

View File

@ -7,11 +7,11 @@
[![The Changelog #363](https://img.shields.io/badge/The%20Changelog-%23363-61c192.svg)](https://changelog.com/podcast/363)
[![@nu_shell](https://img.shields.io/badge/twitter-@nu_shell-1DA1F3?style=flat-square)](https://twitter.com/nu_shell)
## Nu Shell
## Nushell
A new type of shell.
![Example of nushell](images/nushell-autocomplete.gif "Example of nushell")
![Example of nushell](images/nushell-autocomplete5.gif "Example of nushell")
## Status
@ -34,7 +34,7 @@ There are also [good first issues](https://github.com/nushell/nushell/issues?q=i
We also have an active [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell) if you'd like to come and chat with us.
You can also find more learning resources in our [documentation](https://www.nushell.sh/documentation.html) site.
You can also find information on more specific topics in our [cookbook](https://www.nushell.sh/cookbook/).
Try it in Gitpod.
@ -44,19 +44,19 @@ Try it in Gitpod.
### Local
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/en/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
To build Nu, you will need to use the **latest stable (1.41 or later)** version of the compiler.
To build Nu, you will need to use the **latest stable (1.47 or later)** version of the compiler.
Required dependencies:
* pkg-config and libssl (only needed on Linux)
* On Debian/Ubuntu: `apt install pkg-config libssl-dev`
- pkg-config and libssl (only needed on Linux)
- On Debian/Ubuntu: `apt install pkg-config libssl-dev`
Optional dependencies:
* To use Nu with all possible optional features enabled, you'll also need the following:
* On Linux (on Debian/Ubuntu): `apt install libxcb-composite0-dev libx11-dev`
- To use Nu with all possible optional features enabled, you'll also need the following:
- On Linux (on Debian/Ubuntu): `apt install libxcb-composite0-dev libx11-dev`
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`):
@ -64,10 +64,10 @@ To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs
cargo install nu
```
You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/en/installation.html#dependencies) for your platform), once you have checked out this repo with git:
You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/installation.html#dependencies) for your platform), once you have checked out this repo with git:
```bash
cargo build --workspace --features=stable
cargo build --workspace --features=extra
```
### Docker
@ -139,9 +139,9 @@ Just as the Unix philosophy, Nu allows commands to output from stdout and read f
Additionally, commands can output structured data (you can think of this as a third kind of stream).
Commands that work in the pipeline fit into one of three categories:
* Commands that produce a stream (eg, `ls`)
* Commands that filter a stream (eg, `where type == "Dir"`)
* Commands that consume the output of the pipeline (eg, `autoview`)
- Commands that produce a stream (eg, `ls`)
- Commands that filter a stream (eg, `where type == "Dir"`)
- Commands that consume the output of the pipeline (eg, `autoview`)
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
@ -219,22 +219,22 @@ We can pipeline this into a command that gets the contents of one of the columns
name │ nu
readme │ README.md
repository │ https://github.com/nushell/nushell
version │ 0.15.1
version │ 0.21.0
───────────────┴────────────────────────────────────
```
Finally, we can use commands outside of Nu once we have the data we want:
```shell
> open Cargo.toml | get package.version | echo $it
0.15.1
> open Cargo.toml | get package.version
0.21.0
```
Here we use the variable `$it` to refer to the value being piped to the external command.
### Configuration
Nu has early support for configuring the shell. You can refer to the book for a list of [all supported variables](https://www.nushell.sh/book/en/configuration.html).
Nu has early support for configuring the shell. You can refer to the book for a list of [all supported variables](https://www.nushell.sh/book/configuration.html).
To set one of these variables, you can use `config set`. For example:
@ -270,49 +270,55 @@ If the plugin is a sink, it is given the full vector of final data and is given
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
* First and foremost, Nu is cross-platform. Commands and techniques should carry between platforms and offer first-class consistent support for Windows, macOS, and Linux.
- First and foremost, Nu is cross-platform. Commands and techniques should carry between platforms and offer first-class consistent support for Windows, macOS, and Linux.
* Nu ensures direct compatibility with existing platform-specific executables that make up people's workflows.
- Nu ensures direct compatibility with existing platform-specific executables that make up people's workflows.
* Nu's workflow and tools should have the usability in day-to-day experience of using a shell in 2019 (and beyond).
- Nu's workflow and tools should have the usability in day-to-day experience of using a shell in 2019 (and beyond).
* Nu views data as both structured and unstructured. It is a structured shell like PowerShell.
- Nu views data as both structured and unstructured. It is a structured shell like PowerShell.
* Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
- Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
## Commands
You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/documentation.html#quick-command-references).
You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/book/command_reference.html).
## Progress
Nu is in heavy development, and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
| Features | Not started | Prototype | MVP | Preview | Mature | Notes
| -------- |:-----------:|:---------:|:---:|:-------:|:------:| -----
| Aliases | | X | | | | Initial implementation but lacks necessary features
| Notebook | | X | | | | Initial jupyter support, but it loses state and lacks features
| File ops | | | X | | | cp, mv, rm, mkdir have some support, but lacking others
| Environment | | X | | | | Temporary environment, but no session-wide env variables
| Shells | | X | | | | Basic value and file shells, but no opt-in/opt-out for commands
| Protocol | | | X | | | Streaming protocol is serviceable
| Plugins | | X | | | | Plugins work on one row at a time, lack batching and expression eval
| Errors | | | X | | | Error reporting works, but could use usability polish
| Documentation | | X | | | | Book and related are barebones and lack task-based lessons
| Paging | | X | | | | Textview has paging, but we'd like paging for tables
| Functions| X | | | | | No functions, yet, only aliases
| Variables| X | | | | | Nu doesn't yet support variables
| Completions | | X | | | | Completions are currently barebones, at best
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked
| Features | Not started | Prototype | MVP | Preview | Mature | Notes |
| ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- |
| Aliases | | X | | | | Initial implementation but lacks necessary features |
| Notebook | | X | | | | Initial jupyter support, but it loses state and lacks features |
| File ops | | | X | | | cp, mv, rm, mkdir have some support, but lacking others |
| Environment | | X | | | | Temporary environment, but no session-wide env variables |
| Shells | | X | | | | Basic value and file shells, but no opt-in/opt-out for commands |
| Protocol | | | X | | | Streaming protocol is serviceable |
| Plugins | | X | | | | Plugins work on one row at a time, lack batching and expression eval |
| Errors | | | X | | | Error reporting works, but could use usability polish |
| Documentation | | X | | | | Book and related are barebones and lack task-based lessons |
| Paging | | X | | | | Textview has paging, but we'd like paging for tables |
| Functions | | X | | | | No functions, yet, only aliases |
| Variables | | X | | | | Nu doesn't yet support variables |
| Completions | | X | | | | Completions are currently barebones, at best |
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked |
## Current Roadmap
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in NuShell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in Nushell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
## Contributing
See [Contributing](CONTRIBUTING.md) for details.
Thanks to all the people who already contributed!
<a href="https://github.com/nushell/nushell/graphs/contributors">
<img src="https://contributors-img.web.app/image?repo=nushell/nushell" />
</a>
## License
The project is made available under the MIT license. See the `LICENSE` file for more information.

View File

@ -1,106 +1,111 @@
[package]
authors = ["The Nu Project Contributors"]
build = "build.rs"
description = "CLI for nushell"
edition = "2018"
license = "MIT"
name = "nu-cli"
version = "0.18.1"
version = "0.27.0"
[lib]
doctest = false
[dependencies]
nu-errors = {version = "0.18.1", path = "../nu-errors"}
nu-parser = {version = "0.18.1", path = "../nu-parser"}
nu-plugin = {version = "0.18.1", path = "../nu-plugin"}
nu-protocol = {version = "0.18.1", path = "../nu-protocol"}
nu-source = {version = "0.18.1", path = "../nu-source"}
nu-table = {version = "0.18.1", path = "../nu-table"}
nu-test-support = {version = "0.18.1", path = "../nu-test-support"}
nu-value-ext = {version = "0.18.1", path = "../nu-value-ext"}
nu-command = { version = "0.27.0", path = "../nu-command" }
nu-data = { version = "0.27.0", path = "../nu-data" }
nu-engine = { version = "0.27.0", path = "../nu-engine" }
nu-errors = { version = "0.27.0", path = "../nu-errors" }
nu-json = { version = "0.27.0", path = "../nu-json" }
nu-parser = { version = "0.27.0", path = "../nu-parser" }
nu-plugin = { version = "0.27.0", path = "../nu-plugin" }
nu-protocol = { version = "0.27.0", path = "../nu-protocol" }
nu-source = { version = "0.27.0", path = "../nu-source" }
nu-stream = { version = "0.27.0", path = "../nu-stream" }
nu-table = { version = "0.27.0", path = "../nu-table" }
nu-test-support = { version = "0.27.0", path = "../nu-test-support" }
nu-value-ext = { version = "0.27.0", path = "../nu-value-ext" }
Inflector = "0.11"
ansi_term = "0.12.1"
app_dirs = {version = "2", package = "app_dirs2"}
async-recursion = "0.3.1"
async-trait = "0.1.36"
base64 = "0.12.3"
bigdecimal = {version = "0.1.2", features = ["serde"]}
byte-unit = "3.1.3"
bytes = "0.5.5"
calamine = "0.16"
chrono = {version = "0.4.11", features = ["serde"]}
clap = "2.33.1"
codespan-reporting = "0.9.5"
csv = "1.1"
ctrlc = {version = "3.1.4", optional = true}
arboard = { version = "1.1.0", optional = true }
async-recursion = "0.3.2"
async-trait = "0.1.42"
base64 = "0.13.0"
bigdecimal = { version = "0.2.0", features = ["serde"] }
byte-unit = "4.0.9"
bytes = "1.0.1"
calamine = "0.17.0"
chrono = { version = "0.4.19", features = ["serde"] }
chrono-tz = "0.5.3"
clap = "2.33.3"
codespan-reporting = "0.11.0"
csv = "1.1.5"
ctrlc = { version = "3.1.7", optional = true }
derive-new = "0.5.8"
directories = {version = "2.0.2", optional = true}
dirs = {version = "2.0.2", optional = true}
dtparse = "1.1.0"
directories-next = { version = "2.0.0", optional = true }
dirs-next = { version = "2.0.0", optional = true }
dtparse = "1.2.0"
dunce = "1.0.1"
eml-parser = "0.1.0"
encoding_rs = "0.8.28"
filesize = "0.2.0"
futures = {version = "0.3", features = ["compat", "io-compat"]}
futures-util = "0.3.5"
futures_codec = "0.4"
fs_extra = "1.2.0"
futures = { version = "0.3.12", features = ["compat", "io-compat"] }
futures-util = "0.3.12"
futures_codec = "0.4.1"
getset = "0.1.1"
git2 = {version = "0.13.6", default_features = false, optional = true}
glob = "0.3.0"
hex = "0.4"
htmlescape = "0.3.1"
ical = "0.6.*"
ichwh = {version = "0.3.4", optional = true}
indexmap = {version = "1.4.0", features = ["serde-1"]}
itertools = "0.9.0"
log = "0.4.8"
meval = "0.2"
natural = "0.5.0"
num-bigint = {version = "0.2.6", features = ["serde"]}
num-format = {version = "0.4", features = ["with-num-bigint"]}
num-traits = "0.2.11"
parking_lot = "0.11.0"
ical = "0.7.0"
ichwh = { version = "0.3.4", optional = true }
indexmap = { version = "1.6.1", features = ["serde-1"] }
itertools = "0.10.0"
lazy_static = "1.*"
log = "0.4.14"
meval = "0.2.0"
num-bigint = { version = "0.3.1", features = ["serde"] }
num-format = { version = "0.4.0", features = ["with-num-bigint"] }
num-traits = "0.2.14"
parking_lot = "0.11.1"
pin-utils = "0.1.0"
pretty-hex = "0.1.1"
pretty_env_logger = "0.4.0"
ptree = {version = "0.2", optional = true}
pretty-hex = "0.2.1"
ptree = { version = "0.3.1", optional = true }
query_interface = "0.3.5"
rand = "0.7"
regex = "1"
roxmltree = "0.13.0"
rust-embed = "5.6.0"
rustyline = "6.2.0"
serde = {version = "1.0.114", features = ["derive"]}
serde-hjson = "0.9.1"
quick-xml = "0.21.0"
rand = "0.8.3"
rayon = "1.5.0"
regex = "1.4.3"
roxmltree = "0.14.0"
rust-embed = "5.9.0"
rustyline = { version = "6.3.0", optional = true }
serde = { version = "1.0.123", features = ["derive"] }
serde_bytes = "0.11.5"
serde_ini = "0.2.0"
serde_json = "1.0.55"
serde_urlencoded = "0.6.1"
serde_yaml = "0.8"
sha2 = "0.9.1"
shellexpand = "2.0.0"
serde_json = "1.0.61"
serde_urlencoded = "0.7.0"
serde_yaml = "0.8.16"
sha2 = "0.9.3"
shellexpand = "2.1.0"
strip-ansi-escapes = "0.1.0"
tempfile = "3.1.0"
term = {version = "0.5.2", optional = true}
sxd-document = "0.3.2"
sxd-xpath = "0.4.2"
tempfile = "3.2.0"
term = { version = "0.7.0", optional = true }
term_size = "0.3.2"
termcolor = "1.1.0"
toml = "0.5.6"
typetag = "0.1.5"
umask = "1.0.0"
unicode-xid = "0.2.1"
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
which = {version = "4.0.2", optional = true}
zip = "0.5.6"
clipboard = {version = "0.5", optional = true}
encoding_rs = "0.8.23"
quick-xml = "0.18.1"
rayon = "1.3.1"
starship = {version = "0.43.0", optional = true}
trash = {version = "1.0.1", optional = true}
url = {version = "2.1.1"}
termcolor = "1.1.2"
titlecase = "1.1.0"
toml = "0.5.8"
trash = { version = "1.3.0", optional = true }
unicode-segmentation = "1.7.1"
url = "2.1.1"
uuid_crate = { package = "uuid", version = "0.8.2", features = ["v4"], optional = true }
which = { version = "4.0.2", optional = true }
zip = { version = "0.5.9", optional = true }
shadow-rs = { version = "0.5", default-features = false, optional = true }
[target.'cfg(unix)'.dependencies]
users = "0.10.0"
umask = "1.0.0"
users = "0.11.0"
# TODO this will be possible with new dependency resolver
# (currently on nightly behind -Zfeatures=itarget):
@ -111,16 +116,20 @@ users = "0.10.0"
[dependencies.rusqlite]
features = ["bundled", "blob"]
optional = true
version = "0.23.1"
version = "0.24.2"
[build-dependencies]
shadow-rs = "0.5"
[dev-dependencies]
quickcheck = "0.9"
quickcheck_macros = "0.9"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
[features]
clipboard-cli = ["clipboard"]
default = ["shadow-rs"]
clipboard-cli = ["arboard"]
rustyline-support = ["rustyline", "nu-engine/rustyline-support"]
stable = []
starship-prompt = ["starship"]
trash-support = ["trash"]
dirs = ["dirs-next"]
directories = ["directories-next"]

3
crates/nu-cli/build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() -> shadow_rs::SdResult<()> {
shadow_rs::new()
}

File diff suppressed because it is too large Load Diff

View File

@ -1,151 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::data::config;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::Tagged;
pub struct Alias;
#[derive(Deserialize)]
pub struct AliasArgs {
pub name: Tagged<String>,
pub args: Vec<Value>,
pub block: Block,
pub save: Option<bool>,
}
#[async_trait]
impl WholeStreamCommand for Alias {
fn name(&self) -> &str {
"alias"
}
fn signature(&self) -> Signature {
Signature::build("alias")
.required("name", SyntaxShape::String, "the name of the alias")
.required("args", SyntaxShape::Table, "the arguments to the alias")
.required(
"block",
SyntaxShape::Block,
"the block to run as the body of the alias",
)
.switch("save", "save the alias to your config", Some('s'))
}
fn usage(&self) -> &str {
"Define a shortcut for another command."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
alias(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "An alias without parameters",
example: "alias say-hi [] { echo 'Hello!' }",
result: None,
},
Example {
description: "An alias with a single parameter",
example: "alias l [x] { ls $x }",
result: None,
},
]
}
}
pub async fn alias(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let mut raw_input = args.raw_input.clone();
let (
AliasArgs {
name,
args: list,
block,
save,
},
_ctx,
) = args.process(&registry).await?;
let mut processed_args: Vec<String> = vec![];
if let Some(true) = save {
let mut result = crate::data::config::read(name.clone().tag, &None)?;
// process the alias to remove the --save flag
let left_brace = raw_input.find('{').unwrap_or(0);
let right_brace = raw_input.rfind('}').unwrap_or_else(|| raw_input.len());
let left = raw_input[..left_brace]
.replace("--save", "")
.replace("-s", "");
let right = raw_input[right_brace..]
.replace("--save", "")
.replace("-s", "");
raw_input = format!("{}{}{}", left, &raw_input[left_brace..right_brace], right);
// create a value from raw_input alias
let alias: Value = raw_input.trim().to_string().into();
let alias_start = raw_input.find('[').unwrap_or(0); // used to check if the same alias already exists
// add to startup if alias doesn't exist and replce if it does
match result.get_mut("startup") {
Some(startup) => {
if let UntaggedValue::Table(ref mut commands) = startup.value {
if let Some(command) = commands.iter_mut().find(|command| {
let cmd_str = command.as_string().unwrap_or_default();
cmd_str.starts_with(&raw_input[..alias_start])
}) {
*command = alias;
} else {
commands.push(alias);
}
}
}
None => {
let table = UntaggedValue::table(&[alias]);
result.insert("startup".to_string(), table.into_value(Tag::default()));
}
}
config::write(&result, &None)?;
}
for item in list.iter() {
if let Ok(string) = item.as_string() {
processed_args.push(format!("${}", string));
} else {
return Err(ShellError::labeled_error(
"Expected a string",
"expected a string",
item.tag(),
));
}
}
Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::AddAlias(name.to_string(), processed_args, block),
)))
}
#[cfg(test)]
mod tests {
use super::Alias;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Alias {})
}
}

View File

@ -1,148 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
pub struct Ansi;
#[derive(Deserialize)]
struct AnsiArgs {
color: Value,
}
#[async_trait]
impl WholeStreamCommand for Ansi {
fn name(&self) -> &str {
"ansi"
}
fn signature(&self) -> Signature {
Signature::build("ansi").required(
"color",
SyntaxShape::Any,
"the name of the color to use or 'reset' to reset the color",
)
}
fn usage(&self) -> &str {
"Output ANSI codes to change color"
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Change color to green",
example: r#"ansi green"#,
result: Some(vec![Value::from("\u{1b}[32m")]),
},
Example {
description: "Reset the color",
example: r#"ansi reset"#,
result: Some(vec![Value::from("\u{1b}[0m")]),
},
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] | str collect"#,
result: Some(vec![Value::from(
"\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld",
)]),
},
]
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let (AnsiArgs { color }, _) = args.process(&registry).await?;
let color_string = color.as_string()?;
let ansi_code = str_to_ansi_color(color_string);
if let Some(output) = ansi_code {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(color.tag()),
)))
} else {
Err(ShellError::labeled_error(
"Unknown color",
"unknown color",
color.tag(),
))
}
}
}
fn str_to_ansi_color(s: String) -> Option<String> {
match s.as_str() {
"g" | "green" => Some(ansi_term::Color::Green.prefix().to_string()),
"gb" | "green_bold" => Some(ansi_term::Color::Green.bold().prefix().to_string()),
"gu" | "green_underline" => Some(ansi_term::Color::Green.underline().prefix().to_string()),
"gi" | "green_italic" => Some(ansi_term::Color::Green.italic().prefix().to_string()),
"gd" | "green_dimmed" => Some(ansi_term::Color::Green.dimmed().prefix().to_string()),
"gr" | "green_reverse" => Some(ansi_term::Color::Green.reverse().prefix().to_string()),
"r" | "red" => Some(ansi_term::Color::Red.prefix().to_string()),
"rb" | "red_bold" => Some(ansi_term::Color::Red.bold().prefix().to_string()),
"ru" | "red_underline" => Some(ansi_term::Color::Red.underline().prefix().to_string()),
"ri" | "red_italic" => Some(ansi_term::Color::Red.italic().prefix().to_string()),
"rd" | "red_dimmed" => Some(ansi_term::Color::Red.dimmed().prefix().to_string()),
"rr" | "red_reverse" => Some(ansi_term::Color::Red.reverse().prefix().to_string()),
"u" | "blue" => Some(ansi_term::Color::Blue.prefix().to_string()),
"ub" | "blue_bold" => Some(ansi_term::Color::Blue.bold().prefix().to_string()),
"uu" | "blue_underline" => Some(ansi_term::Color::Blue.underline().prefix().to_string()),
"ui" | "blue_italic" => Some(ansi_term::Color::Blue.italic().prefix().to_string()),
"ud" | "blue_dimmed" => Some(ansi_term::Color::Blue.dimmed().prefix().to_string()),
"ur" | "blue_reverse" => Some(ansi_term::Color::Blue.reverse().prefix().to_string()),
"b" | "black" => Some(ansi_term::Color::Black.prefix().to_string()),
"bb" | "black_bold" => Some(ansi_term::Color::Black.bold().prefix().to_string()),
"bu" | "black_underline" => Some(ansi_term::Color::Black.underline().prefix().to_string()),
"bi" | "black_italic" => Some(ansi_term::Color::Black.italic().prefix().to_string()),
"bd" | "black_dimmed" => Some(ansi_term::Color::Black.dimmed().prefix().to_string()),
"br" | "black_reverse" => Some(ansi_term::Color::Black.reverse().prefix().to_string()),
"y" | "yellow" => Some(ansi_term::Color::Yellow.prefix().to_string()),
"yb" | "yellow_bold" => Some(ansi_term::Color::Yellow.bold().prefix().to_string()),
"yu" | "yellow_underline" => {
Some(ansi_term::Color::Yellow.underline().prefix().to_string())
}
"yi" | "yellow_italic" => Some(ansi_term::Color::Yellow.italic().prefix().to_string()),
"yd" | "yellow_dimmed" => Some(ansi_term::Color::Yellow.dimmed().prefix().to_string()),
"yr" | "yellow_reverse" => Some(ansi_term::Color::Yellow.reverse().prefix().to_string()),
"p" | "purple" => Some(ansi_term::Color::Purple.prefix().to_string()),
"pb" | "purple_bold" => Some(ansi_term::Color::Purple.bold().prefix().to_string()),
"pu" | "purple_underline" => {
Some(ansi_term::Color::Purple.underline().prefix().to_string())
}
"pi" | "purple_italic" => Some(ansi_term::Color::Purple.italic().prefix().to_string()),
"pd" | "purple_dimmed" => Some(ansi_term::Color::Purple.dimmed().prefix().to_string()),
"pr" | "purple_reverse" => Some(ansi_term::Color::Purple.reverse().prefix().to_string()),
"c" | "cyan" => Some(ansi_term::Color::Cyan.prefix().to_string()),
"cb" | "cyan_bold" => Some(ansi_term::Color::Cyan.bold().prefix().to_string()),
"cu" | "cyan_underline" => Some(ansi_term::Color::Cyan.underline().prefix().to_string()),
"ci" | "cyan_italic" => Some(ansi_term::Color::Cyan.italic().prefix().to_string()),
"cd" | "cyan_dimmed" => Some(ansi_term::Color::Cyan.dimmed().prefix().to_string()),
"cr" | "cyan_reverse" => Some(ansi_term::Color::Cyan.reverse().prefix().to_string()),
"w" | "white" => Some(ansi_term::Color::White.prefix().to_string()),
"wb" | "white_bold" => Some(ansi_term::Color::White.bold().prefix().to_string()),
"wu" | "white_underline" => Some(ansi_term::Color::White.underline().prefix().to_string()),
"wi" | "white_italic" => Some(ansi_term::Color::White.italic().prefix().to_string()),
"wd" | "white_dimmed" => Some(ansi_term::Color::White.dimmed().prefix().to_string()),
"wr" | "white_reverse" => Some(ansi_term::Color::White.reverse().prefix().to_string()),
"reset" => Some("\x1b[0m".to_owned()),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::Ansi;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Ansi {})
}
}

View File

@ -1,68 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
#[derive(Deserialize)]
struct AppendArgs {
row: Value,
}
pub struct Append;
#[async_trait]
impl WholeStreamCommand for Append {
fn name(&self) -> &str {
"append"
}
fn signature(&self) -> Signature {
Signature::build("append").required(
"row value",
SyntaxShape::Any,
"the value of the row to append to the table",
)
}
fn usage(&self) -> &str {
"Append the given row to the table"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let (AppendArgs { row }, input) = args.process(registry).await?;
let eos = futures::stream::iter(vec![row]);
Ok(input.chain(eos).to_output_stream())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Add something to the end of a list or table",
example: "echo [1 2 3] | append 4",
result: Some(vec![
UntaggedValue::int(1).into(),
UntaggedValue::int(2).into(),
UntaggedValue::int(3).into(),
UntaggedValue::int(4).into(),
]),
}]
}
}
#[cfg(test)]
mod tests {
use super::Append;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Append {})
}
}

View File

@ -1,78 +0,0 @@
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
hir::Block, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use chrono::prelude::*;
pub struct Benchmark;
#[derive(Deserialize, Debug)]
struct BenchmarkArgs {
block: Block,
}
#[async_trait]
impl WholeStreamCommand for Benchmark {
fn name(&self) -> &str {
"benchmark"
}
fn signature(&self) -> Signature {
Signature::build("benchmark").required(
"block",
SyntaxShape::Block,
"the block to run and benchmark",
)
}
fn usage(&self) -> &str {
"Runs a block and return the time it took to do execute it. Eg) benchmark { echo $nu.env.NAME }"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
benchmark(args, registry).await
}
}
async fn benchmark(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let mut context = Context::from_raw(&raw_args, &registry);
let scope = raw_args.call_info.scope.clone();
let (BenchmarkArgs { block }, input) = raw_args.process(&registry).await?;
let start_time: chrono::DateTime<_> = Utc::now();
let result = run_block(
&block,
&mut context,
input,
&scope.it,
&scope.vars,
&scope.env,
)
.await;
let _ = result?.drain_vec().await;
let run_duration: chrono::Duration = Utc::now().signed_duration_since(start_time);
context.clear_errors();
let output = Ok(ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(Primitive::from(run_duration)),
tag: Tag::from(block.span),
}));
Ok(OutputStream::from(vec![output]))
}

View File

@ -1,92 +0,0 @@
use crate::commands::classified::expr::run_expression_block;
use crate::commands::classified::internal::run_internal_command;
use crate::context::Context;
use crate::prelude::*;
use crate::stream::InputStream;
use futures::stream::TryStreamExt;
use nu_errors::ShellError;
use nu_protocol::hir::{Block, ClassifiedCommand, Commands};
use nu_protocol::{ReturnSuccess, UntaggedValue, Value};
use std::sync::atomic::Ordering;
pub(crate) async fn run_block(
block: &Block,
ctx: &mut Context,
mut input: InputStream,
it: &Value,
vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>,
) -> Result<InputStream, ShellError> {
let mut output: Result<InputStream, ShellError> = Ok(InputStream::empty());
for pipeline in &block.block {
match output {
Ok(inp) if inp.is_empty() => {}
Ok(inp) => {
let mut output_stream = inp.to_output_stream();
loop {
match output_stream.try_next().await {
Ok(Some(ReturnSuccess::Value(Value {
value: UntaggedValue::Error(e),
..
}))) => return Err(e),
Ok(Some(_item)) => {
if let Some(err) = ctx.get_errors().get(0) {
ctx.clear_errors();
return Err(err.clone());
}
if ctx.ctrl_c.load(Ordering::SeqCst) {
break;
}
}
Ok(None) => {
if let Some(err) = ctx.get_errors().get(0) {
ctx.clear_errors();
return Err(err.clone());
}
break;
}
Err(e) => return Err(e),
}
}
}
Err(e) => {
return Err(e);
}
}
output = run_pipeline(pipeline, ctx, input, it, vars, env).await;
input = InputStream::empty();
}
output
}
async fn run_pipeline(
commands: &Commands,
ctx: &mut Context,
mut input: InputStream,
it: &Value,
vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>,
) -> Result<InputStream, ShellError> {
for item in commands.list.clone() {
input = match item {
ClassifiedCommand::Dynamic(_) => {
return Err(ShellError::unimplemented("Dynamic commands"))
}
ClassifiedCommand::Expr(expr) => {
run_expression_block(*expr, ctx, it, vars, env).await?
}
ClassifiedCommand::Error(err) => return Err(err.into()),
ClassifiedCommand::Internal(left) => {
run_internal_command(left, ctx, input, it, vars, env).await?
}
};
}
Ok(input)
}

View File

@ -1,440 +0,0 @@
use crate::commands::help::get_help;
use crate::context::CommandRegistry;
use crate::deserializer::ConfigDeserializer;
use crate::evaluate::evaluate_args::evaluate_args;
use crate::prelude::*;
use derive_new::new;
use getset::Getters;
use nu_errors::ShellError;
use nu_protocol::hir;
use nu_protocol::{CallInfo, EvaluatedArgs, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use std::sync::atomic::AtomicBool;
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct UnevaluatedCallInfo {
pub args: hir::Call,
pub name_tag: Tag,
pub scope: Scope,
}
impl UnevaluatedCallInfo {
pub async fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
let args = evaluate_args(&self.args, registry, &self.scope).await?;
Ok(CallInfo {
args,
name_tag: self.name_tag,
})
}
pub async fn evaluate_with_new_it(
self,
registry: &CommandRegistry,
it: &Value,
) -> Result<CallInfo, ShellError> {
let mut scope = self.scope.clone();
scope.it = it.clone();
let args = evaluate_args(&self.args, registry, &scope).await?;
Ok(CallInfo {
args,
name_tag: self.name_tag,
})
}
pub fn switch_present(&self, switch: &str) -> bool {
self.args.switch_preset(switch)
}
}
#[derive(Getters)]
#[get = "pub(crate)"]
pub struct CommandArgs {
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub shell_manager: ShellManager,
pub call_info: UnevaluatedCallInfo,
pub input: InputStream,
pub raw_input: String,
}
#[derive(Getters, Clone)]
#[get = "pub(crate)"]
pub struct RawCommandArgs {
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub shell_manager: ShellManager,
pub call_info: UnevaluatedCallInfo,
}
impl RawCommandArgs {
pub fn with_input(self, input: impl Into<InputStream>) -> CommandArgs {
CommandArgs {
host: self.host,
ctrl_c: self.ctrl_c,
current_errors: self.current_errors,
shell_manager: self.shell_manager,
call_info: self.call_info,
input: input.into(),
raw_input: String::default(),
}
}
}
impl std::fmt::Debug for CommandArgs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.call_info.fmt(f)
}
}
impl CommandArgs {
pub async fn evaluate_once(
self,
registry: &CommandRegistry,
) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
let host = self.host.clone();
let ctrl_c = self.ctrl_c.clone();
let shell_manager = self.shell_manager.clone();
let input = self.input;
let call_info = self.call_info.evaluate(registry).await?;
Ok(EvaluatedWholeStreamCommandArgs::new(
host,
ctrl_c,
shell_manager,
call_info,
input,
))
}
pub async fn evaluate_once_with_scope(
self,
registry: &CommandRegistry,
scope: &Scope,
) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
let host = self.host.clone();
let ctrl_c = self.ctrl_c.clone();
let shell_manager = self.shell_manager.clone();
let input = self.input;
let call_info = UnevaluatedCallInfo {
name_tag: self.call_info.name_tag,
args: self.call_info.args,
scope: scope.clone(),
};
let call_info = call_info.evaluate(registry).await?;
Ok(EvaluatedWholeStreamCommandArgs::new(
host,
ctrl_c,
shell_manager,
call_info,
input,
))
}
pub async fn process<'de, T: Deserialize<'de>>(
self,
registry: &CommandRegistry,
) -> Result<(T, InputStream), ShellError> {
let args = self.evaluate_once(registry).await?;
let call_info = args.call_info.clone();
let mut deserializer = ConfigDeserializer::from_call_info(call_info);
Ok((T::deserialize(&mut deserializer)?, args.input))
}
}
pub struct RunnableContext {
pub input: InputStream,
pub shell_manager: ShellManager,
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub registry: CommandRegistry,
pub name: Tag,
pub raw_input: String,
}
impl RunnableContext {
pub fn get_command(&self, name: &str) -> Option<Command> {
self.registry.get_command(name)
}
}
pub struct EvaluatedWholeStreamCommandArgs {
pub args: EvaluatedCommandArgs,
pub input: InputStream,
}
impl Deref for EvaluatedWholeStreamCommandArgs {
type Target = EvaluatedCommandArgs;
fn deref(&self) -> &Self::Target {
&self.args
}
}
impl EvaluatedWholeStreamCommandArgs {
pub fn new(
host: Arc<parking_lot::Mutex<dyn Host>>,
ctrl_c: Arc<AtomicBool>,
shell_manager: ShellManager,
call_info: CallInfo,
input: impl Into<InputStream>,
) -> EvaluatedWholeStreamCommandArgs {
EvaluatedWholeStreamCommandArgs {
args: EvaluatedCommandArgs {
host,
ctrl_c,
shell_manager,
call_info,
},
input: input.into(),
}
}
pub fn name_tag(&self) -> Tag {
self.args.call_info.name_tag.clone()
}
pub fn parts(self) -> (InputStream, EvaluatedArgs) {
let EvaluatedWholeStreamCommandArgs { args, input } = self;
(input, args.call_info.args)
}
pub fn split(self) -> (InputStream, EvaluatedCommandArgs) {
let EvaluatedWholeStreamCommandArgs { args, input } = self;
(input, args)
}
}
#[derive(Getters)]
#[get = "pub"]
pub struct EvaluatedFilterCommandArgs {
args: EvaluatedCommandArgs,
}
impl Deref for EvaluatedFilterCommandArgs {
type Target = EvaluatedCommandArgs;
fn deref(&self) -> &Self::Target {
&self.args
}
}
impl EvaluatedFilterCommandArgs {
pub fn new(
host: Arc<parking_lot::Mutex<dyn Host>>,
ctrl_c: Arc<AtomicBool>,
shell_manager: ShellManager,
call_info: CallInfo,
) -> EvaluatedFilterCommandArgs {
EvaluatedFilterCommandArgs {
args: EvaluatedCommandArgs {
host,
ctrl_c,
shell_manager,
call_info,
},
}
}
}
#[derive(Getters, new)]
#[get = "pub(crate)"]
pub struct EvaluatedCommandArgs {
pub host: Arc<parking_lot::Mutex<dyn Host>>,
pub ctrl_c: Arc<AtomicBool>,
pub shell_manager: ShellManager,
pub call_info: CallInfo,
}
impl EvaluatedCommandArgs {
pub fn nth(&self, pos: usize) -> Option<&Value> {
self.call_info.args.nth(pos)
}
/// Get the nth positional argument, error if not possible
pub fn expect_nth(&self, pos: usize) -> Result<&Value, ShellError> {
self.call_info
.args
.nth(pos)
.ok_or_else(|| ShellError::unimplemented("Better error: expect_nth"))
}
pub fn get(&self, name: &str) -> Option<&Value> {
self.call_info.args.get(name)
}
pub fn has(&self, name: &str) -> bool {
self.call_info.args.has(name)
}
}
pub struct Example {
pub example: &'static str,
pub description: &'static str,
pub result: Option<Vec<Value>>,
}
#[async_trait]
pub trait WholeStreamCommand: Send + Sync {
fn name(&self) -> &str;
fn signature(&self) -> Signature {
Signature::new(self.name()).desc(self.usage()).filter()
}
fn usage(&self) -> &str;
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError>;
fn is_binary(&self) -> bool {
false
}
fn examples(&self) -> Vec<Example> {
Vec::new()
}
}
#[derive(Clone)]
pub struct Command(Arc<dyn WholeStreamCommand>);
impl PrettyDebugWithSource for Command {
fn pretty_debug(&self, source: &str) -> DebugDocBuilder {
b::typed(
"whole stream command",
b::description(self.name())
+ b::space()
+ b::equals()
+ b::space()
+ self.signature().pretty_debug(source),
)
}
}
impl std::fmt::Debug for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Command({})", self.name())
}
}
impl Command {
pub fn name(&self) -> &str {
self.0.name()
}
pub fn signature(&self) -> Signature {
self.0.signature()
}
pub fn usage(&self) -> &str {
self.0.usage()
}
pub fn examples(&self) -> Vec<Example> {
self.0.examples()
}
pub async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
if args.call_info.switch_present("help") {
let cl = self.0.clone();
let registry = registry.clone();
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(get_help(&*cl, &registry)).into_value(Tag::unknown()),
))))
} else {
self.0.run(args, registry).await
}
}
pub fn is_binary(&self) -> bool {
self.0.is_binary()
}
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
&*self.0
}
}
pub struct FnFilterCommand {
name: String,
func: fn(EvaluatedFilterCommandArgs) -> Result<OutputStream, ShellError>,
}
#[async_trait]
impl WholeStreamCommand for FnFilterCommand {
fn name(&self) -> &str {
&self.name
}
fn usage(&self) -> &str {
"usage"
}
async fn run(
&self,
CommandArgs {
host,
ctrl_c,
shell_manager,
call_info,
input,
..
}: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = Arc::new(registry.clone());
let func = self.func;
Ok(input
.then(move |it| {
let host = host.clone();
let registry = registry.clone();
let ctrl_c = ctrl_c.clone();
let shell_manager = shell_manager.clone();
let call_info = call_info.clone();
async move {
let call_info = match call_info.evaluate_with_new_it(&*registry, &it).await {
Err(err) => {
return OutputStream::one(Err(err));
}
Ok(args) => args,
};
let args = EvaluatedFilterCommandArgs::new(
host.clone(),
ctrl_c.clone(),
shell_manager.clone(),
call_info,
);
match func(args) {
Err(err) => return OutputStream::one(Err(err)),
Ok(stream) => stream,
}
}
})
.flatten()
.to_output_stream())
}
}
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command {
Command(Arc::new(command))
}

View File

@ -1,67 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct SetArgs {
key: Tagged<String>,
value: Value,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"config set"
}
fn signature(&self) -> Signature {
Signature::build("config set")
.required("key", SyntaxShape::String, "variable name to set")
.required("value", SyntaxShape::Any, "value to use")
}
fn usage(&self) -> &str {
"Sets a value in the config"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
set(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Set completion_mode to circular",
example: "config set [completion_mode circular]",
result: None,
}]
}
}
pub async fn set(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone();
let (SetArgs { key, value }, _) = args.process(&registry).await?;
// NOTE: None because we are not loading a new config file, we just want to read from the
// existing config
let mut result = crate::data::config::read(name_span, &None)?;
result.insert(key.to_string(), value.clone());
config::write(&result, &None)?;
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(&value.tag),
)))
}

View File

@ -1,56 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use futures::stream::StreamExt;
use nu_errors::ShellError;
use nu_protocol::{Signature, UntaggedValue, Value};
pub struct Count;
#[async_trait]
impl WholeStreamCommand for Count {
fn name(&self) -> &str {
"count"
}
fn signature(&self) -> Signature {
Signature::build("count")
}
fn usage(&self) -> &str {
"Show the total number of rows or items."
}
async fn run(
&self,
args: CommandArgs,
_registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let rows: Vec<Value> = args.input.collect().await;
Ok(OutputStream::one(
UntaggedValue::int(rows.len()).into_value(name),
))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Count the number of entries in a list",
example: "echo [1 2 3 4 5] | count",
result: Some(vec![UntaggedValue::int(5).into()]),
}]
}
}
#[cfg(test)]
mod tests {
use super::Count;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Count {})
}
}

View File

@ -1,175 +0,0 @@
use crate::prelude::*;
use chrono::{DateTime, Local, Utc};
use nu_errors::ShellError;
use nu_protocol::{Dictionary, Value};
use crate::commands::WholeStreamCommand;
use chrono::{Datelike, TimeZone, Timelike};
use core::fmt::Display;
use indexmap::IndexMap;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
pub struct Date;
#[async_trait]
impl WholeStreamCommand for Date {
fn name(&self) -> &str {
"date"
}
fn signature(&self) -> Signature {
Signature::build("date")
.switch("utc", "use universal time (UTC)", Some('u'))
.switch("local", "use the local time", Some('l'))
.named(
"format",
SyntaxShape::String,
"report datetime in supplied strftime format",
Some('f'),
)
.switch("raw", "print date without tables", Some('r'))
}
fn usage(&self) -> &str {
"Get the current datetime."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
date(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get the current local time and date",
example: "date",
result: None,
},
Example {
description: "Get the current UTC time and date",
example: "date --utc",
result: None,
},
Example {
description: "Get the current time and date and report it based on format",
example: "date --format '%Y-%m-%d %H:%M:%S.%f %z'",
result: None,
},
Example {
description: "Get the current time and date and report it without a table",
example: "date --format '%Y-%m-%d %H:%M:%S.%f %z' --raw",
result: None,
},
]
}
}
pub fn date_to_value_raw<T: TimeZone>(dt: DateTime<T>, dt_format: String) -> String
where
T::Offset: Display,
{
let result = dt.format(&dt_format);
format!("{}", result)
}
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, tag: Tag, dt_format: String) -> Value
where
T::Offset: Display,
{
let mut indexmap = IndexMap::new();
if dt_format.is_empty() {
indexmap.insert(
"year".to_string(),
UntaggedValue::int(dt.year()).into_value(&tag),
);
indexmap.insert(
"month".to_string(),
UntaggedValue::int(dt.month()).into_value(&tag),
);
indexmap.insert(
"day".to_string(),
UntaggedValue::int(dt.day()).into_value(&tag),
);
indexmap.insert(
"hour".to_string(),
UntaggedValue::int(dt.hour()).into_value(&tag),
);
indexmap.insert(
"minute".to_string(),
UntaggedValue::int(dt.minute()).into_value(&tag),
);
indexmap.insert(
"second".to_string(),
UntaggedValue::int(dt.second()).into_value(&tag),
);
let tz = dt.offset();
indexmap.insert(
"timezone".to_string(),
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
);
} else {
let result = dt.format(&dt_format);
indexmap.insert(
"formatted".to_string(),
UntaggedValue::string(format!("{}", result)).into_value(&tag),
);
}
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
}
pub async fn date(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.call_info.name_tag.clone();
let raw = args.has("raw");
let dt_fmt = if args.has("format") {
if let Some(dt_fmt) = args.get("format") {
dt_fmt.convert_to_string()
} else {
"".to_string()
}
} else {
"".to_string()
};
let value = if args.has("utc") {
let utc: DateTime<Utc> = Utc::now();
if raw {
UntaggedValue::string(date_to_value_raw(utc, dt_fmt)).into_untagged_value()
} else {
date_to_value(utc, tag, dt_fmt)
}
} else {
let local: DateTime<Local> = Local::now();
if raw {
UntaggedValue::string(date_to_value_raw(local, dt_fmt)).into_untagged_value()
} else {
date_to_value(local, tag, dt_fmt)
}
};
Ok(OutputStream::one(value))
}
#[cfg(test)]
mod tests {
use super::Date;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Date {})
}
}

View File

@ -1,263 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use indexmap::set::IndexSet;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape,
UnspannedPathMember, UntaggedValue, Value,
};
use nu_source::span_for_spanned_list;
use nu_value_ext::get_data_by_column_path;
pub struct Get;
#[derive(Deserialize)]
pub struct GetArgs {
rest: Vec<ColumnPath>,
}
#[async_trait]
impl WholeStreamCommand for Get {
fn name(&self) -> &str {
"get"
}
fn signature(&self) -> Signature {
Signature::build("get").rest(
SyntaxShape::ColumnPath,
"optionally return additional data by path",
)
}
fn usage(&self) -> &str {
"Open given cells as text."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
get(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Extract the name of files as a list",
example: "ls | get name",
result: None,
},
Example {
description: "Extract the cpu list from the sys information",
example: "sys | get cpu",
result: None,
},
]
}
}
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> {
let fields = path.clone();
get_data_by_column_path(
obj,
path,
Box::new(move |(obj_source, column_path_tried, error)| {
let path_members_span = span_for_spanned_list(fields.members().iter().map(|p| p.span));
match &obj_source.value {
UntaggedValue::Table(rows) => match column_path_tried {
PathMember {
unspanned: UnspannedPathMember::String(column),
..
} => {
let primary_label = format!("There isn't a column named '{}'", &column);
let suggestions: IndexSet<_> = rows
.iter()
.filter_map(|r| did_you_mean(&r, &column_path_tried))
.map(|s| s[0].1.to_owned())
.collect();
let mut existing_columns: IndexSet<_> = IndexSet::default();
let mut names: Vec<String> = vec![];
for row in rows {
for field in row.data_descriptors() {
if !existing_columns.contains(&field[..]) {
existing_columns.insert(field.clone());
names.push(field);
}
}
}
if names.is_empty() {
return ShellError::labeled_error_with_secondary(
"Unknown column",
primary_label,
column_path_tried.span,
"Appears to contain rows. Try indexing instead.",
column_path_tried.span.since(path_members_span),
);
} else {
return ShellError::labeled_error_with_secondary(
"Unknown column",
primary_label,
column_path_tried.span,
format!(
"Perhaps you meant '{}'? Columns available: {}",
suggestions
.iter()
.map(|x| x.to_owned())
.collect::<Vec<String>>()
.join(","),
names.join(",")
),
column_path_tried.span.since(path_members_span),
);
};
}
PathMember {
unspanned: UnspannedPathMember::Int(idx),
..
} => {
let total = rows.len();
let secondary_label = if total == 1 {
"The table only has 1 row".to_owned()
} else {
format!("The table only has {} rows (0 to {})", total, total - 1)
};
return ShellError::labeled_error_with_secondary(
"Row not found",
format!("There isn't a row indexed at {}", idx),
column_path_tried.span,
secondary_label,
column_path_tried.span.since(path_members_span),
);
}
},
UntaggedValue::Row(columns) => match column_path_tried {
PathMember {
unspanned: UnspannedPathMember::String(column),
..
} => {
let primary_label = format!("There isn't a column named '{}'", &column);
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
return ShellError::labeled_error_with_secondary(
"Unknown column",
primary_label,
column_path_tried.span,
format!(
"Perhaps you meant '{}'? Columns available: {}",
suggestions[0].1,
&obj_source.data_descriptors().join(",")
),
column_path_tried.span.since(path_members_span),
);
}
}
PathMember {
unspanned: UnspannedPathMember::Int(idx),
..
} => {
return ShellError::labeled_error_with_secondary(
"No rows available",
format!("A row at '{}' can't be indexed.", &idx),
column_path_tried.span,
format!(
"Appears to contain columns. Columns available: {}",
columns.keys().join(",")
),
column_path_tried.span.since(path_members_span),
)
}
},
_ => {}
}
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
return ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", suggestions[0].1),
column_path_tried.span.since(path_members_span),
);
}
error
}),
)
}
pub async fn get(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (GetArgs { rest: mut fields }, mut input) = args.process(&registry).await?;
if fields.is_empty() {
let vec = input.drain_vec().await;
let descs = nu_protocol::merge_descriptors(&vec);
Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
} else {
let member = fields.remove(0);
trace!("get {:?} {:?}", member, fields);
Ok(input
.map(move |item| {
let member = vec![member.clone()];
let column_paths = vec![&member, &fields]
.into_iter()
.flatten()
.collect::<Vec<&ColumnPath>>();
let mut output = vec![];
for path in column_paths {
let res = get_column_path(&path, &item);
match res {
Ok(got) => match got {
Value {
value: UntaggedValue::Table(rows),
..
} => {
for item in rows {
output.push(ReturnSuccess::value(item.clone()));
}
}
Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
..
} => {}
other => output.push(ReturnSuccess::value(other.clone())),
},
Err(reason) => output.push(ReturnSuccess::value(
UntaggedValue::Error(reason).into_untagged_value(),
)),
}
}
futures::stream::iter(output)
})
.flatten()
.to_output_stream())
}
}
#[cfg(test)]
mod tests {
use super::Get;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Get {})
}
}

View File

@ -1,169 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::data::command_dict;
use crate::documentation::{generate_docs, get_documentation, DocumentationConfig};
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue};
use nu_source::{SpannedItem, Tagged};
use nu_value_ext::get_data_by_key;
pub struct Help;
#[derive(Deserialize)]
pub struct HelpArgs {
rest: Vec<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for Help {
fn name(&self) -> &str {
"help"
}
fn signature(&self) -> Signature {
Signature::build("help").rest(SyntaxShape::String, "the name of command to get help on")
}
fn usage(&self) -> &str {
"Display help information about commands."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
help(args, registry).await
}
}
async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let (HelpArgs { rest }, ..) = args.process(&registry).await?;
if !rest.is_empty() {
if rest[0].item == "commands" {
let mut sorted_names = registry.names();
sorted_names.sort();
Ok(
futures::stream::iter(sorted_names.into_iter().filter_map(move |cmd| {
// If it's a subcommand, don't list it during the commands list
if cmd.contains(' ') {
return None;
}
let mut short_desc = TaggedDictBuilder::new(name.clone());
let document_tag = rest[0].tag.clone();
let value = command_dict(
match registry.get_command(&cmd).ok_or_else(|| {
ShellError::labeled_error(
format!("Could not load {}", cmd),
"could not load command",
document_tag,
)
}) {
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
},
name.clone(),
);
short_desc.insert_untagged("name", cmd);
short_desc.insert_untagged(
"description",
match match get_data_by_key(&value, "usage".spanned_unknown()).ok_or_else(
|| {
ShellError::labeled_error(
"Expected a usage key",
"expected a 'usage' key",
&value.tag,
)
},
) {
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
}
.as_string()
{
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
},
);
Some(ReturnSuccess::value(short_desc.into_value()))
}))
.to_output_stream(),
)
} else if rest[0].item == "generate_docs" {
Ok(OutputStream::one(ReturnSuccess::value(generate_docs(
&registry,
))))
} else if rest.len() == 2 {
// Check for a subcommand
let command_name = format!("{} {}", rest[0].item, rest[1].item);
if let Some(command) = registry.get_command(&command_name) {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(command.stream_command(), &registry))
.into_value(Tag::unknown()),
)))
} else {
Ok(OutputStream::empty())
}
} else if let Some(command) = registry.get_command(&rest[0].item) {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(command.stream_command(), &registry))
.into_value(Tag::unknown()),
)))
} else {
Err(ShellError::labeled_error(
"Can't find command (use 'help commands' for full list)",
"can't find command",
rest[0].tag.span,
))
}
} else {
let msg = r#"Welcome to Nushell.
Here are some tips to help you get started.
* help commands - list all available commands
* help <command name> - display help about a particular command
Nushell works on the idea of a "pipeline". Pipelines are commands connected with the '|' character.
Each stage in the pipeline works together to load, parse, and display information to you.
[Examples]
List the files in the current directory, sorted by size:
ls | sort-by size
Get information about the current system:
sys | get host
Get the processes on your system actively using CPU:
ps | where cpu > 0
You can also learn more at https://www.nushell.sh/book/"#;
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(msg).into_value(Tag::unknown()),
)))
}
}
pub fn get_help(cmd: &dyn WholeStreamCommand, registry: &CommandRegistry) -> String {
get_documentation(cmd, registry, &DocumentationConfig::default())
}
#[cfg(test)]
mod tests {
use super::Help;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Help {})
}
}

View File

@ -1,67 +0,0 @@
use crate::cli::History as HistoryFile;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
use std::fs::File;
use std::io::{BufRead, BufReader};
pub struct History;
#[async_trait]
impl WholeStreamCommand for History {
fn name(&self) -> &str {
"history"
}
fn signature(&self) -> Signature {
Signature::build("history")
}
fn usage(&self) -> &str {
"Display command history."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
history(args, registry)
}
}
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag;
let history_path = HistoryFile::path();
let file = File::open(history_path);
if let Ok(file) = file {
let reader = BufReader::new(file);
let output = reader.lines().filter_map(move |line| match line {
Ok(line) => Some(ReturnSuccess::value(
UntaggedValue::string(line).into_value(tag.clone()),
)),
Err(_) => None,
});
Ok(futures::stream::iter(output).to_output_stream())
} else {
Err(ShellError::labeled_error(
"Could not open history",
"history file could not be opened",
tag,
))
}
}
#[cfg(test)]
mod tests {
use super::History;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(History {})
}
}

View File

@ -1,188 +0,0 @@
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{hir::Block, hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue};
pub struct If;
#[derive(Deserialize)]
pub struct IfArgs {
condition: Block,
then_case: Block,
else_case: Block,
}
#[async_trait]
impl WholeStreamCommand for If {
fn name(&self) -> &str {
"if"
}
fn signature(&self) -> Signature {
Signature::build("if")
.required(
"condition",
SyntaxShape::Math,
"the condition that must match",
)
.required(
"then_case",
SyntaxShape::Block,
"block to run if condition is true",
)
.required(
"else_case",
SyntaxShape::Block,
"block to run if condition is false",
)
}
fn usage(&self) -> &str {
"Run blocks if a condition is true or false."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
if_command(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Run a block if a condition is true",
example: "echo 10 | if $it > 5 { echo 'greater than 5' } { echo 'less than or equal to 5' }",
result: Some(vec![UntaggedValue::string("greater than 5").into()]),
},
Example {
description: "Run a block if a condition is false",
example: "echo 1 | if $it > 5 { echo 'greater than 5' } { echo 'less than or equal to 5' }",
result: Some(vec![UntaggedValue::string("less than or equal to 5").into()]),
},
]
}
}
async fn if_command(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = Arc::new(registry.clone());
let scope = Arc::new(raw_args.call_info.scope.clone());
let tag = raw_args.call_info.name_tag.clone();
let context = Arc::new(Context::from_raw(&raw_args, &registry));
let (
IfArgs {
condition,
then_case,
else_case,
},
input,
) = raw_args.process(&registry).await?;
let condition = {
if condition.block.len() != 1 {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
match condition.block[0].list.get(0) {
Some(item) => match item {
ClassifiedCommand::Expr(expr) => expr.clone(),
_ => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
},
None => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
}
};
Ok(input
.then(move |input| {
let condition = condition.clone();
let then_case = then_case.clone();
let else_case = else_case.clone();
let registry = registry.clone();
let scope = scope.clone();
let mut context = context.clone();
async move {
//FIXME: should we use the scope that's brought in as well?
let condition =
evaluate_baseline_expr(&condition, &*registry, &input, &scope.vars, &scope.env)
.await;
match condition {
Ok(condition) => match condition.as_bool() {
Ok(b) => {
if b {
match run_block(
&then_case,
Arc::make_mut(&mut context),
InputStream::empty(),
&input,
&scope.vars,
&scope.env,
)
.await
{
Ok(stream) => stream.to_output_stream(),
Err(e) => futures::stream::iter(vec![Err(e)].into_iter())
.to_output_stream(),
}
} else {
match run_block(
&else_case,
Arc::make_mut(&mut context),
InputStream::empty(),
&input,
&scope.vars,
&scope.env,
)
.await
{
Ok(stream) => stream.to_output_stream(),
Err(e) => futures::stream::iter(vec![Err(e)].into_iter())
.to_output_stream(),
}
}
}
Err(e) => {
futures::stream::iter(vec![Err(e)].into_iter()).to_output_stream()
}
},
Err(e) => futures::stream::iter(vec![Err(e)].into_iter()).to_output_stream(),
}
}
})
.flatten()
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::If;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(If {})
}
}

View File

@ -1,165 +0,0 @@
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use futures::stream::once;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
use nu_value_ext::ValueExt;
pub struct Insert;
#[derive(Deserialize)]
pub struct InsertArgs {
column: ColumnPath,
value: Value,
}
#[async_trait]
impl WholeStreamCommand for Insert {
fn name(&self) -> &str {
"insert"
}
fn signature(&self) -> Signature {
Signature::build("insert")
.required(
"column",
SyntaxShape::ColumnPath,
"the column name to insert",
)
.required("value", SyntaxShape::Any, "the value to give the cell(s)")
}
fn usage(&self) -> &str {
"Insert a new column with a given value."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
insert(args, registry).await
}
}
async fn process_row(
scope: Arc<Scope>,
mut context: Arc<Context>,
input: Value,
mut value: Arc<Value>,
column: Arc<ColumnPath>,
) -> Result<OutputStream, ShellError> {
let value = Arc::make_mut(&mut value);
Ok(match value {
Value {
value: UntaggedValue::Block(block),
..
} => {
let for_block = input.clone();
let input_stream = once(async { Ok(for_block) }).to_input_stream();
let result = run_block(
&block,
Arc::make_mut(&mut context),
input_stream,
&input,
&scope.vars,
&scope.env,
)
.await;
match result {
Ok(mut stream) => {
let errors = context.get_errors();
if let Some(error) = errors.first() {
return Err(error.clone());
}
match input {
obj
@
Value {
value: UntaggedValue::Row(_),
..
} => {
if let Some(result) = stream.next().await {
match obj.insert_data_at_column_path(&column, result) {
Ok(v) => OutputStream::one(ReturnSuccess::value(v)),
Err(e) => OutputStream::one(Err(e)),
}
} else {
OutputStream::empty()
}
}
Value { tag, .. } => OutputStream::one(Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
tag,
))),
}
}
Err(e) => OutputStream::one(Err(e)),
}
}
_ => match input {
obj
@
Value {
value: UntaggedValue::Row(_),
..
} => match obj.insert_data_at_column_path(&column, value.clone()) {
Ok(v) => OutputStream::one(ReturnSuccess::value(v)),
Err(e) => OutputStream::one(Err(e)),
},
Value { tag, .. } => OutputStream::one(Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
tag,
))),
},
})
}
async fn insert(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = Arc::new(raw_args.call_info.scope.clone());
let context = Arc::new(Context::from_raw(&raw_args, &registry));
let (InsertArgs { column, value }, input) = raw_args.process(&registry).await?;
let value = Arc::new(value);
let column = Arc::new(column);
Ok(input
.then(move |input| {
let scope = scope.clone();
let context = context.clone();
let value = value.clone();
let column = column.clone();
async {
match process_row(scope, context, input, value, column).await {
Ok(s) => s,
Err(e) => OutputStream::one(Err(e)),
}
}
})
.flatten()
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Insert;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Insert {})
}
}

View File

@ -1,211 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use nu_value_ext::ValueExt;
enum IsEmptyFor {
Value,
RowWithFieldsAndFallback(Vec<Tagged<ColumnPath>>, Value),
RowWithField(Tagged<ColumnPath>),
RowWithFieldAndFallback(Box<Tagged<ColumnPath>>, Value),
}
pub struct IsEmpty;
#[derive(Deserialize)]
pub struct IsEmptyArgs {
rest: Vec<Value>,
}
#[async_trait]
impl WholeStreamCommand for IsEmpty {
fn name(&self) -> &str {
"empty?"
}
fn signature(&self) -> Signature {
Signature::build("empty?").rest(
SyntaxShape::Any,
"the names of the columns to check emptiness followed by the replacement value.",
)
}
fn usage(&self) -> &str {
"Checks emptiness. The last value is the replacement value for any empty column(s) given to check against the table."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
is_empty(args, registry).await
}
}
async fn is_empty(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (IsEmptyArgs { rest }, input) = args.process(&registry).await?;
Ok(input
.map(move |value| {
let value_tag = value.tag();
let action = if rest.len() <= 2 {
let field = rest.get(0);
let replacement_if_true = rest.get(1);
match (field, replacement_if_true) {
(Some(field), Some(replacement_if_true)) => {
IsEmptyFor::RowWithFieldAndFallback(
Box::new(field.as_column_path()?),
replacement_if_true.clone(),
)
}
(Some(field), None) => IsEmptyFor::RowWithField(field.as_column_path()?),
(_, _) => IsEmptyFor::Value,
}
} else {
// let no_args = vec![];
let mut arguments = rest.iter().rev();
let replacement_if_true = match arguments.next() {
Some(arg) => arg.clone(),
None => UntaggedValue::boolean(value.is_empty()).into_value(&value_tag),
};
IsEmptyFor::RowWithFieldsAndFallback(
arguments
.map(|a| a.as_column_path())
.filter_map(Result::ok)
.collect(),
replacement_if_true,
)
};
match action {
IsEmptyFor::Value => Ok(ReturnSuccess::Value(
UntaggedValue::boolean(value.is_empty()).into_value(value_tag),
)),
IsEmptyFor::RowWithFieldsAndFallback(fields, default) => {
let mut out = value;
for field in fields.iter() {
let val = crate::commands::get::get_column_path(&field, &out)?;
let emptiness_value = match out {
obj
@
Value {
value: UntaggedValue::Row(_),
..
} => {
if val.is_empty() {
obj.replace_data_at_column_path(&field, default.clone())
.ok_or_else(|| {
ShellError::labeled_error(
"empty? could not find place to check emptiness",
"column name",
&field.tag,
)
})
} else {
Ok(obj)
}
}
_ => Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
&value_tag,
)),
};
out = emptiness_value?;
}
Ok(ReturnSuccess::Value(out))
}
IsEmptyFor::RowWithField(field) => {
let val = crate::commands::get::get_column_path(&field, &value)?;
match &value {
obj
@
Value {
value: UntaggedValue::Row(_),
..
} => {
if val.is_empty() {
match obj.replace_data_at_column_path(
&field,
UntaggedValue::boolean(true).into_value(&value_tag),
) {
Some(v) => Ok(ReturnSuccess::Value(v)),
None => Err(ShellError::labeled_error(
"empty? could not find place to check emptiness",
"column name",
&field.tag,
)),
}
} else {
Ok(ReturnSuccess::Value(value))
}
}
_ => Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
&value_tag,
)),
}
}
IsEmptyFor::RowWithFieldAndFallback(field, default) => {
let val = crate::commands::get::get_column_path(&field, &value)?;
match &value {
obj
@
Value {
value: UntaggedValue::Row(_),
..
} => {
if val.is_empty() {
match obj.replace_data_at_column_path(&field, default) {
Some(v) => Ok(ReturnSuccess::Value(v)),
None => Err(ShellError::labeled_error(
"empty? could not find place to check emptiness",
"column name",
&field.tag,
)),
}
} else {
Ok(ReturnSuccess::Value(value))
}
}
_ => Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
&value_tag,
)),
}
}
}
})
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::IsEmpty;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(IsEmpty {})
}
}

View File

@ -1,107 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct SubCommandArgs {
expression: Option<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math eval"
}
fn usage(&self) -> &str {
"Evaluate a math expression into a number"
}
fn signature(&self) -> Signature {
Signature::build("math eval").desc(self.usage()).optional(
"math expression",
SyntaxShape::String,
"the math expression to evaluate",
)
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
eval(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Evalulate math in the pipeline",
example: "echo '10 / 4' | math eval",
result: Some(vec![UntaggedValue::decimal(2.5).into()]),
}]
}
}
pub async fn eval(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.span;
let (SubCommandArgs { expression }, input) = args.process(registry).await?;
Ok(input
.map(move |x| {
if let Some(Tagged {
tag,
item: expression,
}) = &expression
{
UntaggedValue::string(expression).into_value(tag)
} else {
x
}
})
.map(move |input| {
if let Ok(string) = input.as_string() {
match parse(&string, &input.tag) {
Ok(value) => ReturnSuccess::value(value),
Err(err) => Err(ShellError::labeled_error(
"Math evaluation error",
err,
&input.tag.span,
)),
}
} else {
Err(ShellError::labeled_error(
"Expected a string from pipeline",
"requires string input",
name,
))
}
})
.to_output_stream())
}
pub fn parse<T: Into<Tag>>(math_expression: &str, tag: T) -> Result<Value, String> {
match meval::eval_str(math_expression) {
Ok(num) if num.is_infinite() || num.is_nan() => Err("cannot represent result".to_string()),
Ok(num) => Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag)),
Err(error) => Err(error.to_string().to_lowercase()),
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -1,46 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
#[derive(Clone)]
pub struct Command;
#[async_trait]
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"move"
}
fn signature(&self) -> Signature {
Signature::build("move")
}
fn usage(&self) -> &str {
"moves across desired subcommand."
}
async fn run(
&self,
_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(crate::commands::help::get_help(&Command, &registry))
.into_value(Tag::unknown()),
))))
}
}
#[cfg(test)]
mod tests {
use super::Command;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Command {})
}
}

View File

@ -1,61 +0,0 @@
use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use std::path::Path;
pub struct PathBasename;
#[async_trait]
impl WholeStreamCommand for PathBasename {
fn name(&self) -> &str {
"path basename"
}
fn signature(&self) -> Signature {
Signature::build("path basename")
.rest(SyntaxShape::ColumnPath, "optionally operate by path")
}
fn usage(&self) -> &str {
"gets the filename of a path"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get basename of a path",
example: "echo '/home/joe/test.txt' | path basename",
result: Some(vec![Value::from("test.txt")]),
}]
}
}
fn action(path: &Path) -> UntaggedValue {
UntaggedValue::string(match path.file_name() {
Some(filename) => filename.to_string_lossy().to_string(),
_ => "".to_string(),
})
}
#[cfg(test)]
mod tests {
use super::PathBasename;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(PathBasename {})
}
}

View File

@ -1,45 +0,0 @@
use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use std::path::Path;
pub struct PathExists;
#[async_trait]
impl WholeStreamCommand for PathExists {
fn name(&self) -> &str {
"path exists"
}
fn signature(&self) -> Signature {
Signature::build("path exists").rest(SyntaxShape::ColumnPath, "optionally operate by path")
}
fn usage(&self) -> &str {
"checks whether the path exists"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Check if file exists",
example: "echo '/home/joe/todo.txt' | path exists",
result: Some(vec![Value::from(UntaggedValue::boolean(false))]),
}]
}
}
fn action(path: &Path) -> UntaggedValue {
UntaggedValue::boolean(path.exists())
}

View File

@ -1,51 +0,0 @@
use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use std::path::Path;
pub struct PathExpand;
#[async_trait]
impl WholeStreamCommand for PathExpand {
fn name(&self) -> &str {
"path expand"
}
fn signature(&self) -> Signature {
Signature::build("path expand").rest(SyntaxShape::ColumnPath, "optionally operate by path")
}
fn usage(&self) -> &str {
"expands the path to its absolute form"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Expand relative directories",
example: "echo '/home/joe/foo/../bar' | path expand",
result: Some(vec![Value::from("/home/joe/bar")]),
}]
}
}
fn action(path: &Path) -> UntaggedValue {
let ps = path.to_string_lossy();
let expanded = shellexpand::tilde(&ps);
let path: &Path = expanded.as_ref().as_ref();
UntaggedValue::string(match path.canonicalize() {
Ok(p) => p.to_string_lossy().to_string(),
Err(_) => ps.to_string(),
})
}

View File

@ -1,68 +0,0 @@
use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use std::path::Path;
pub struct PathExtension;
#[async_trait]
impl WholeStreamCommand for PathExtension {
fn name(&self) -> &str {
"path extension"
}
fn signature(&self) -> Signature {
Signature::build("path extension")
.rest(SyntaxShape::ColumnPath, "optionally operate by path")
}
fn usage(&self) -> &str {
"gets the extension of a path"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get extension of a path",
example: "echo 'test.txt' | path extension",
result: Some(vec![Value::from("txt")]),
},
Example {
description: "You get an empty string if there is no extension",
example: "echo 'test' | path extension",
result: Some(vec![Value::from("")]),
},
]
}
}
fn action(path: &Path) -> UntaggedValue {
UntaggedValue::string(match path.extension() {
Some(ext) => ext.to_string_lossy().to_string(),
_ => "".to_string(),
})
}
#[cfg(test)]
mod tests {
use super::PathExtension;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(PathExtension {})
}
}

View File

@ -1,62 +0,0 @@
use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::data::files::get_file_type;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use std::path::Path;
pub struct PathType;
#[async_trait]
impl WholeStreamCommand for PathType {
fn name(&self) -> &str {
"path type"
}
fn signature(&self) -> Signature {
Signature::build("path type").rest(SyntaxShape::ColumnPath, "optionally operate by path")
}
fn usage(&self) -> &str {
"gives the type of the object the path refers to (eg file, dir, symlink)"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Show type of a filepath",
example: "echo '.' | path type",
result: Some(vec![Value::from("Dir")]),
}]
}
}
fn action(path: &Path) -> UntaggedValue {
let meta = std::fs::symlink_metadata(path);
UntaggedValue::string(match &meta {
Ok(md) => get_file_type(md),
Err(_) => "",
})
}
#[cfg(test)]
mod tests {
use super::PathType;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(PathType {})
}
}

View File

@ -1,70 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::deserializer::NumericRange;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
use nu_source::Tagged;
#[derive(Deserialize)]
struct RangeArgs {
area: Tagged<NumericRange>,
}
pub struct Range;
#[async_trait]
impl WholeStreamCommand for Range {
fn name(&self) -> &str {
"range"
}
fn signature(&self) -> Signature {
Signature::build("range").required(
"rows ",
SyntaxShape::Range,
"range of rows to return: Eg) 4..7 (=> from 4 to 7)",
)
}
fn usage(&self) -> &str {
"Return only the selected rows"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
range(args, registry).await
}
}
async fn range(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (RangeArgs { area }, input) = args.process(&registry).await?;
let range = area.item;
let (from, _) = range.from;
let (to, _) = range.to;
let from = *from as usize;
let to = *to as usize;
Ok(input
.skip(from)
.take(to - from + 1)
.map(ReturnSuccess::value)
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Range;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Range {})
}
}

View File

@ -1,72 +0,0 @@
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use derive_new::new;
use nu_errors::ShellError;
use nu_protocol::{hir::Block, Signature, SyntaxShape};
#[derive(new, Clone)]
pub struct AliasCommand {
name: String,
args: Vec<String>,
block: Block,
}
#[async_trait]
impl WholeStreamCommand for AliasCommand {
fn name(&self) -> &str {
&self.name
}
fn signature(&self) -> Signature {
let mut alias = Signature::build(&self.name);
for arg in &self.args {
alias = alias.optional(arg, SyntaxShape::Any, "");
}
alias
}
fn usage(&self) -> &str {
""
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let call_info = args.call_info.clone();
let registry = registry.clone();
let mut block = self.block.clone();
block.set_redirect(call_info.args.external_redirection);
let alias_command = self.clone();
let mut context = Context::from_args(&args, &registry);
let input = args.input;
let mut scope = call_info.scope.clone();
let evaluated = call_info.evaluate(&registry).await?;
if let Some(positional) = &evaluated.args.positional {
for (pos, arg) in positional.iter().enumerate() {
scope
.vars
.insert(alias_command.args[pos].to_string(), arg.clone());
}
}
// FIXME: we need to patch up the spans to point at the top-level error
Ok(run_block(
&block,
&mut context,
input,
&scope.it,
&scope.vars,
&scope.env,
)
.await?
.to_output_stream())
}
}

View File

@ -1,77 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct SubCommandArgs {
separator: Option<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str collect"
}
fn signature(&self) -> Signature {
Signature::build("str collect").desc(self.usage()).optional(
"separator",
SyntaxShape::String,
"the separator to put between the different values",
)
}
fn usage(&self) -> &str {
"collects a list of strings into a string"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
collect(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Collect a list of string",
example: "echo ['a' 'b' 'c'] | str collect",
result: Some(vec![Value::from("abc")]),
}]
}
}
pub async fn collect(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (SubCommandArgs { separator }, input) = args.process(registry).await?;
let separator = separator.map(|tagged| tagged.item).unwrap_or_default();
let strings: Vec<Result<String, ShellError>> =
input.map(|value| value.as_string()).collect().await;
let strings: Vec<String> = strings.into_iter().collect::<Result<_, _>>()?;
let output = strings.join(&separator);
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(tag),
)))
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -1,65 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str length"
}
fn signature(&self) -> Signature {
Signature::build("str length")
}
fn usage(&self) -> &str {
"outputs the lengths of the strings in the pipeline"
}
async fn run(
&self,
args: CommandArgs,
_registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
Ok(args
.input
.map(move |x| match x.as_string() {
Ok(s) => ReturnSuccess::value(UntaggedValue::int(s.len()).into_untagged_value()),
Err(err) => Err(err),
})
.to_output_stream())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Return the lengths of multiple strings",
example: "echo 'hello' | str length",
result: Some(vec![UntaggedValue::int(5).into_untagged_value()]),
},
Example {
description: "Return the lengths of multiple strings",
example: "echo 'hi' 'there' | str length",
result: Some(vec![
UntaggedValue::int(2).into_untagged_value(),
UntaggedValue::int(5).into_untagged_value(),
]),
},
]
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -1,58 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str reverse"
}
fn signature(&self) -> Signature {
Signature::build("str reverse")
}
fn usage(&self) -> &str {
"outputs the reversals of the strings in the pipeline"
}
async fn run(
&self,
args: CommandArgs,
_registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
Ok(args
.input
.map(move |x| match x.as_string() {
Ok(s) => ReturnSuccess::value(
UntaggedValue::string(s.chars().rev().collect::<String>())
.into_untagged_value(),
),
Err(err) => Err(err),
})
.to_output_stream())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Return the reversals of multiple strings",
example: "echo 'Nushell' | str reverse",
result: Some(vec![UntaggedValue::string("llehsuN").into_untagged_value()]),
}]
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -1,123 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::{Tag, Tagged};
use nu_value_ext::ValueExt;
#[derive(Deserialize)]
struct Arguments {
replace: Tagged<String>,
rest: Vec<ColumnPath>,
}
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str set"
}
fn signature(&self) -> Signature {
Signature::build("str set")
.required("set", SyntaxShape::String, "the new string to set")
.rest(
SyntaxShape::ColumnPath,
"optionally set text by column paths",
)
}
fn usage(&self) -> &str {
"sets text"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Set contents with preferred string",
example: "echo 'good day' | str set 'good bye'",
result: Some(vec![Value::from("good bye")]),
},
Example {
description: "Set the contents on preferred column paths",
example: "open Cargo.toml | str set '255' package.version",
result: None,
},
]
}
}
#[derive(Clone)]
struct Replace(String);
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (Arguments { replace, rest }, input) = args.process(&registry).await?;
let options = Replace(replace.item);
let column_paths: Vec<_> = rest;
Ok(input
.map(move |v| {
if column_paths.is_empty() {
ReturnSuccess::value(action(&v, &options, v.tag())?)
} else {
let mut ret = v;
for path in &column_paths {
let options = options.clone();
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, &options, old.tag())),
)?;
}
ReturnSuccess::value(ret)
}
})
.to_output_stream())
}
fn action(_input: &Value, options: &Replace, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let replacement = &options.0;
Ok(UntaggedValue::string(replacement.as_str()).into_value(tag))
}
#[cfg(test)]
mod tests {
use super::{action, Replace, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
#[test]
fn sets() {
let word = string("andres");
let expected = string("robalino");
let set_options = Replace(String::from("robalino"));
let actual = action(&word, &set_options, Tag::unknown()).unwrap();
assert_eq!(actual, expected);
}
}

View File

@ -1,143 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::ShellTypeName;
use nu_protocol::{
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::Tag;
use nu_value_ext::ValueExt;
use num_bigint::BigInt;
use std::str::FromStr;
#[derive(Deserialize)]
struct Arguments {
rest: Vec<ColumnPath>,
}
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str to-int"
}
fn signature(&self) -> Signature {
Signature::build("str to-int").rest(
SyntaxShape::ColumnPath,
"optionally convert text into integer by column paths",
)
}
fn usage(&self) -> &str {
"converts text into integer"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Convert to an integer",
example: "echo '255' | str to-int",
result: None,
}]
}
}
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (Arguments { rest }, input) = args.process(&registry).await?;
let column_paths: Vec<_> = rest;
Ok(input
.map(move |v| {
if column_paths.is_empty() {
ReturnSuccess::value(action(&v, v.tag())?)
} else {
let mut ret = v;
for path in &column_paths {
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag())),
)?;
}
ReturnSuccess::value(ret)
}
})
.to_output_stream())
}
fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
match &input.value {
UntaggedValue::Primitive(Primitive::Line(s))
| UntaggedValue::Primitive(Primitive::String(s)) => {
let other = s.trim();
let out = match BigInt::from_str(other) {
Ok(v) => UntaggedValue::int(v),
Err(reason) => {
return Err(ShellError::labeled_error(
"could not parse as an integer",
reason.to_string(),
tag.into().span,
))
}
};
Ok(out.into_value(tag))
}
other => {
let got = format!("got {}", other.type_name());
Err(ShellError::labeled_error(
"value is not string",
got,
tag.into().span,
))
}
}
}
#[cfg(test)]
mod tests {
use super::{action, SubCommand};
use nu_plugin::test_helpers::value::{int, string};
use nu_source::Tag;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
#[test]
fn turns_to_integer() {
let word = string("10");
let expected = int(10);
let actual = action(&word, Tag::unknown()).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
let integer_str = string("36anra");
let actual = action(&integer_str, Tag::unknown());
assert!(actual.is_err());
}
}

View File

@ -1,93 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::data::value::format_leaf;
use crate::prelude::*;
use futures::StreamExt;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
pub struct ToMarkdown;
#[async_trait]
impl WholeStreamCommand for ToMarkdown {
fn name(&self) -> &str {
"to md"
}
fn signature(&self) -> Signature {
Signature::build("to md")
}
fn usage(&self) -> &str {
"Convert table into simple Markdown"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
to_html(args, registry).await
}
}
async fn to_html(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let name_tag = args.name_tag();
let input: Vec<Value> = args.input.collect().await;
let headers = nu_protocol::merge_descriptors(&input);
let mut output_string = String::new();
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
output_string.push_str("|");
for header in &headers {
output_string.push_str(&htmlescape::encode_minimal(&header));
output_string.push_str("|");
}
output_string.push_str("\n|");
for _ in &headers {
output_string.push_str("-");
output_string.push_str("|");
}
output_string.push_str("\n");
}
for row in input {
match row.value {
UntaggedValue::Row(row) => {
output_string.push_str("|");
for header in &headers {
let data = row.get_data(header);
output_string.push_str(&format_leaf(data.borrow()).plain_string(100_000));
output_string.push_str("|");
}
output_string.push_str("\n");
}
p => {
output_string.push_str(
&(htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000))),
);
output_string.push_str("\n");
}
}
}
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output_string).into_value(name_tag),
)))
}
#[cfg(test)]
mod tests {
use super::ToMarkdown;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(ToMarkdown {})
}
}

View File

@ -1,72 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged;
use std::fs::OpenOptions;
use std::path::PathBuf;
pub struct Touch;
#[derive(Deserialize)]
pub struct TouchArgs {
pub target: Tagged<PathBuf>,
}
#[async_trait]
impl WholeStreamCommand for Touch {
fn name(&self) -> &str {
"touch"
}
fn signature(&self) -> Signature {
Signature::build("touch").required(
"filename",
SyntaxShape::Path,
"the path of the file you want to create",
)
}
fn usage(&self) -> &str {
"creates a file"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
touch(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Creates \"fixture.json\"",
example: "touch fixture.json",
result: None,
}]
}
}
async fn touch(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (TouchArgs { target }, _) = args.process(&registry).await?;
match OpenOptions::new().write(true).create(true).open(&target) {
Ok(_) => Ok(OutputStream::empty()),
Err(err) => Err(ShellError::labeled_error(
"File Error",
err.to_string(),
&target.tag,
)),
}
}
#[cfg(test)]
mod tests {
use super::Touch;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Touch {})
}
}

View File

@ -1,95 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Dictionary, Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
pub struct Trim;
#[async_trait]
impl WholeStreamCommand for Trim {
fn name(&self) -> &str {
"trim"
}
fn signature(&self) -> Signature {
Signature::build("trim")
}
fn usage(&self) -> &str {
"Trim leading and following whitespace from text data."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
trim(args, registry)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Trims surrounding whitespace and outputs \"Hello world\"",
example: "echo \" Hello world\" | trim",
result: Some(vec![Value::from("Hello world")]),
}]
}
}
fn trim_primitive(p: &mut Primitive) {
match p {
Primitive::String(s) | Primitive::Line(s) => *p = Primitive::String(s.trim().to_string()),
Primitive::Nothing
| Primitive::Int(_)
| Primitive::Decimal(_)
| Primitive::Filesize(_)
| Primitive::ColumnPath(_)
| Primitive::Pattern(_)
| Primitive::Boolean(_)
| Primitive::Date(_)
| Primitive::Duration(_)
| Primitive::Range(_)
| Primitive::Path(_)
| Primitive::Binary(_)
| Primitive::BeginningOfStream
| Primitive::EndOfStream => (),
}
}
fn trim_row(d: &mut Dictionary) {
for (_, mut value) in d.entries.iter_mut() {
trim_value(&mut value);
}
}
fn trim_value(v: &mut Value) {
match &mut v.value {
UntaggedValue::Primitive(p) => trim_primitive(p),
UntaggedValue::Row(row) => trim_row(row),
_ => (),
};
}
fn trim(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
Ok(args
.input
.map(|v| {
let mut trimmed = v;
trim_value(&mut trimmed);
ReturnSuccess::value(trimmed)
})
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Trim;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Trim {})
}
}

View File

@ -1,177 +0,0 @@
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
use nu_value_ext::ValueExt;
use futures::stream::once;
pub struct Update;
#[derive(Deserialize)]
pub struct UpdateArgs {
field: ColumnPath,
replacement: Value,
}
#[async_trait]
impl WholeStreamCommand for Update {
fn name(&self) -> &str {
"update"
}
fn signature(&self) -> Signature {
Signature::build("update")
.required(
"field",
SyntaxShape::ColumnPath,
"the name of the column to update",
)
.required(
"replacement value",
SyntaxShape::Any,
"the new value to give the cell(s)",
)
}
fn usage(&self) -> &str {
"Update an existing column to have a new value."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
update(args, registry).await
}
}
async fn process_row(
scope: Arc<Scope>,
mut context: Arc<Context>,
input: Value,
mut replacement: Arc<Value>,
field: Arc<ColumnPath>,
) -> Result<OutputStream, ShellError> {
let replacement = Arc::make_mut(&mut replacement);
Ok(match replacement {
Value {
value: UntaggedValue::Block(block),
..
} => {
let for_block = input.clone();
let input_stream = once(async { Ok(for_block) }).to_input_stream();
let result = run_block(
&block,
Arc::make_mut(&mut context),
input_stream,
&input,
&scope.vars,
&scope.env,
)
.await;
match result {
Ok(mut stream) => {
let errors = context.get_errors();
if let Some(error) = errors.first() {
return Err(error.clone());
}
match input {
obj
@
Value {
value: UntaggedValue::Row(_),
..
} => {
if let Some(result) = stream.next().await {
match obj.replace_data_at_column_path(&field, result) {
Some(v) => OutputStream::one(ReturnSuccess::value(v)),
None => OutputStream::one(Err(ShellError::labeled_error(
"update could not find place to insert column",
"column name",
obj.tag,
))),
}
} else {
OutputStream::empty()
}
}
Value { tag, .. } => OutputStream::one(Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
tag,
))),
}
}
Err(e) => OutputStream::one(Err(e)),
}
}
_ => match input {
obj
@
Value {
value: UntaggedValue::Row(_),
..
} => match obj.replace_data_at_column_path(&field, replacement.clone()) {
Some(v) => OutputStream::one(ReturnSuccess::value(v)),
None => OutputStream::one(Err(ShellError::labeled_error(
"update could not find place to insert column",
"column name",
obj.tag,
))),
},
Value { tag, .. } => OutputStream::one(Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
tag,
))),
},
})
}
async fn update(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = Arc::new(raw_args.call_info.scope.clone());
let context = Arc::new(Context::from_raw(&raw_args, &registry));
let (UpdateArgs { field, replacement }, input) = raw_args.process(&registry).await?;
let replacement = Arc::new(replacement);
let field = Arc::new(field);
Ok(input
.then(move |input| {
let scope = scope.clone();
let context = context.clone();
let replacement = replacement.clone();
let field = field.clone();
async {
match process_row(scope, context, input, replacement, field).await {
Ok(s) => s,
Err(e) => OutputStream::one(Err(e)),
}
}
})
.flatten()
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Update;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Update {})
}
}

View File

@ -1,90 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::TaggedListBuilder;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{Dictionary, Signature, UntaggedValue};
pub struct Version;
#[async_trait]
impl WholeStreamCommand for Version {
fn name(&self) -> &str {
"version"
}
fn signature(&self) -> Signature {
Signature::build("version")
}
fn usage(&self) -> &str {
"Display Nu version"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
version(args, registry)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Display Nu version",
example: "version",
result: None,
}]
}
}
pub fn version(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let tag = args.call_info.args.span;
let mut indexmap = IndexMap::with_capacity(4);
indexmap.insert(
"version".to_string(),
UntaggedValue::string(clap::crate_version!()).into_value(&tag),
);
indexmap.insert("features".to_string(), features_enabled(&tag).into_value());
let value = UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag);
Ok(OutputStream::one(value))
}
fn features_enabled(tag: impl Into<Tag>) -> TaggedListBuilder {
let mut names = TaggedListBuilder::new(tag);
names.push_untagged(UntaggedValue::string("default"));
#[cfg(feature = "clipboard-cli")]
{
names.push_untagged(UntaggedValue::string("clipboard"));
}
#[cfg(feature = "trash-support")]
{
names.push_untagged(UntaggedValue::string("trash"));
}
#[cfg(feature = "starship-prompt")]
{
names.push_untagged(UntaggedValue::string("starship"));
}
names
}
#[cfg(test)]
mod tests {
use super::Version;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Version {})
}
}

View File

@ -1,133 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use indexmap::map::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct Which;
#[async_trait]
impl WholeStreamCommand for Which {
fn name(&self) -> &str {
"which"
}
fn signature(&self) -> Signature {
Signature::build("which")
.required("application", SyntaxShape::String, "application")
.switch("all", "list all executables", Some('a'))
}
fn usage(&self) -> &str {
"Finds a program file."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
which(args, registry).await
}
}
/// Shortcuts for creating an entry to the output table
fn entry(arg: impl Into<String>, path: Value, builtin: bool, tag: Tag) -> Value {
let mut map = IndexMap::new();
map.insert(
"arg".to_string(),
UntaggedValue::Primitive(Primitive::String(arg.into())).into_value(tag.clone()),
);
map.insert("path".to_string(), path);
map.insert(
"builtin".to_string(),
UntaggedValue::Primitive(Primitive::Boolean(builtin)).into_value(tag.clone()),
);
UntaggedValue::row(map).into_value(tag)
}
macro_rules! entry_builtin {
($arg:expr, $tag:expr) => {
entry(
$arg.clone(),
UntaggedValue::Primitive(Primitive::String("nushell built-in command".to_string()))
.into_value($tag.clone()),
true,
$tag,
)
};
}
#[allow(unused)]
macro_rules! entry_path {
($arg:expr, $path:expr, $tag:expr) => {
entry(
$arg.clone(),
UntaggedValue::Primitive(Primitive::Path($path)).into_value($tag.clone()),
false,
$tag,
)
};
}
#[derive(Deserialize, Debug)]
struct WhichArgs {
application: Tagged<String>,
all: bool,
}
async fn which(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let mut output = vec![];
let (WhichArgs { application, all }, _) = args.process(&registry).await?;
let external = application.starts_with('^');
let item = if external {
application.item[1..].to_string()
} else {
application.item.clone()
};
if !external {
let builtin = registry.has(&item);
if builtin {
output.push(ReturnSuccess::value(entry_builtin!(
item,
application.tag.clone()
)));
}
}
#[cfg(feature = "ichwh")]
{
if let Ok(paths) = ichwh::which_all(&item).await {
for path in paths {
output.push(ReturnSuccess::value(entry_path!(
item,
path.into(),
application.tag.clone()
)));
}
}
}
if all {
Ok(futures::stream::iter(output.into_iter()).to_output_stream())
} else {
Ok(futures::stream::iter(output.into_iter().take(1)).to_output_stream())
}
}
#[cfg(test)]
mod tests {
use super::Which;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Which {})
}
}

View File

@ -1,92 +0,0 @@
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{hir::Block, Signature, SyntaxShape, Value};
use nu_source::Tagged;
pub struct WithEnv;
#[derive(Deserialize, Debug)]
struct WithEnvArgs {
variable: (Tagged<String>, Tagged<String>),
block: Block,
}
#[async_trait]
impl WholeStreamCommand for WithEnv {
fn name(&self) -> &str {
"with-env"
}
fn signature(&self) -> Signature {
Signature::build("with-env")
.required(
"variable",
SyntaxShape::Any,
"the environment variable to temporarily set",
)
.required(
"block",
SyntaxShape::Block,
"the block to run once the variable is set",
)
}
fn usage(&self) -> &str {
"Runs a block with an environment set. Eg) with-env [NAME 'foo'] { echo $nu.env.NAME }"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
with_env(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Set the MYENV environment variable",
example: r#"with-env [MYENV "my env value"] { echo $nu.env.MYENV }"#,
result: Some(vec![Value::from("my env value")]),
}]
}
}
async fn with_env(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let mut context = Context::from_raw(&raw_args, &registry);
let mut scope = raw_args.call_info.scope.clone();
let (WithEnvArgs { variable, block }, input) = raw_args.process(&registry).await?;
scope.env.insert(variable.0.item, variable.1.item);
let result = run_block(
&block,
&mut context,
input,
&scope.it,
&scope.vars,
&scope.env,
)
.await;
result.map(|x| x.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::WithEnv;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(WithEnv {})
}
}

View File

@ -1,40 +0,0 @@
use nu_errors::ShellError;
use crate::context;
#[derive(Debug, Eq, PartialEq)]
pub struct Suggestion {
pub display: String,
pub replacement: String,
}
pub struct Context<'a>(&'a context::Context, &'a rustyline::Context<'a>);
impl<'a> Context<'a> {
pub fn new(a: &'a context::Context, b: &'a rustyline::Context<'a>) -> Context<'a> {
Context(a, b)
}
}
impl<'a> AsRef<context::Context> for Context<'a> {
fn as_ref(&self) -> &context::Context {
self.0
}
}
impl<'a> AsRef<rustyline::Context<'a>> for Context<'a> {
fn as_ref(&self) -> &rustyline::Context<'a> {
self.1
}
}
pub trait Completer {
fn complete(
&self,
line: &str,
pos: usize,
ctx: &Context<'_>,
) -> Result<(usize, Vec<Suggestion>), ShellError>;
fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option<String>;
}

View File

@ -0,0 +1,141 @@
use std::iter::FromIterator;
use std::path::Path;
use indexmap::set::IndexSet;
use super::matchers::Matcher;
use crate::completion::{Completer, CompletionContext, Suggestion};
use nu_engine::EvaluationContext;
pub struct CommandCompleter;
impl Completer for CommandCompleter {
fn complete(
&self,
ctx: &CompletionContext<'_>,
partial: &str,
matcher: &dyn Matcher,
) -> Vec<Suggestion> {
let context: &EvaluationContext = ctx.as_ref();
let mut commands: IndexSet<String> = IndexSet::from_iter(context.scope.get_command_names());
// Command suggestions can come from three possible sets:
// 1. internal command names,
// 2. external command names relative to PATH env var, and
// 3. any other executable (that matches what's been typed so far).
let path_executables = find_path_executables().unwrap_or_default();
// TODO quote these, if necessary
commands.extend(path_executables.into_iter());
let mut suggestions: Vec<_> = commands
.into_iter()
.filter(|v| matcher.matches(partial, v))
.map(|v| Suggestion {
replacement: v.clone(),
display: v,
})
.collect();
if !partial.is_empty() {
let path_completer = crate::completion::path::PathCompleter;
let path_results = path_completer.path_suggestions(partial, matcher);
let iter = path_results.into_iter().filter_map(|path_suggestion| {
let path = path_suggestion.path;
if path.is_dir() || is_executable(&path) {
Some(path_suggestion.suggestion)
} else {
None
}
});
suggestions.extend(iter);
}
suggestions
}
}
// TODO create a struct for "is executable" and store this information in it so we don't recompute
// on every dir entry
#[cfg(windows)]
fn pathext() -> Option<Vec<String>> {
std::env::var_os("PATHEXT").map(|v| {
v.to_string_lossy()
.split(';')
// Filter out empty tokens and ';' at the end
.filter(|f| f.len() > 1)
// Cut off the leading '.' character
.map(|ext| ext[1..].to_string())
.collect::<Vec<_>>()
})
}
#[cfg(windows)]
fn is_executable(path: &Path) -> bool {
if let Ok(metadata) = path.metadata() {
let file_type = metadata.file_type();
// If the entry isn't a file, it cannot be executable
if !(file_type.is_file() || file_type.is_symlink()) {
return false;
}
if let Some(extension) = path.extension() {
if let Some(exts) = pathext() {
exts.iter()
.any(|ext| extension.to_string_lossy().eq_ignore_ascii_case(ext))
} else {
false
}
} else {
false
}
} else {
false
}
}
#[cfg(target_arch = "wasm32")]
fn is_executable(_path: &Path) -> bool {
false
}
#[cfg(unix)]
fn is_executable(path: &Path) -> bool {
use std::os::unix::fs::PermissionsExt;
if let Ok(metadata) = path.metadata() {
let filetype = metadata.file_type();
let permissions = metadata.permissions();
// The file is executable if it is a directory or a symlink and the permissions are set for
// owner, group, or other
(filetype.is_file() || filetype.is_symlink()) && (permissions.mode() & 0o111 != 0)
} else {
false
}
}
// TODO cache these, but watch for changes to PATH
fn find_path_executables() -> Option<IndexSet<String>> {
let path_var = std::env::var_os("PATH")?;
let paths: Vec<_> = std::env::split_paths(&path_var).collect();
let mut executables: IndexSet<String> = IndexSet::new();
for path in paths {
if let Ok(mut contents) = std::fs::read_dir(path) {
while let Some(Ok(item)) = contents.next() {
if is_executable(&item.path()) {
if let Ok(name) = item.file_name().into_string() {
executables.insert(name);
}
}
}
}
}
Some(executables)
}

View File

@ -0,0 +1,434 @@
use nu_protocol::hir::*;
use nu_source::{Span, Spanned, SpannedItem};
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum LocationType {
Command,
Flag(String), // command name
Argument(Option<String>, Option<String>), // command name, argument name
Variable,
}
pub type CompletionLocation = Spanned<LocationType>;
// TODO The below is very similar to shapes / expression_to_flat_shape. Check back October 2020
// to see if we're close enough to just make use of those.
struct Flatten<'s> {
line: &'s str,
command: Option<String>,
flag: Option<String>,
}
impl<'s> Flatten<'s> {
/// Converts a SpannedExpression into a completion location for use in NuCompleter
fn expression(&self, e: &SpannedExpression) -> Vec<CompletionLocation> {
match &e.expr {
Expression::Block(block) => self.completion_locations(block),
Expression::Invocation(block) => self.completion_locations(block),
Expression::List(exprs) => exprs.iter().flat_map(|v| self.expression(v)).collect(),
Expression::Table(headers, cells) => headers
.iter()
.flat_map(|v| self.expression(v))
.chain(
cells
.iter()
.flat_map(|v| v.iter().flat_map(|v| self.expression(v))),
)
.collect(),
Expression::Command => vec![LocationType::Command.spanned(e.span)],
Expression::Path(path) => self.expression(&path.head),
Expression::Variable(_, _) => vec![LocationType::Variable.spanned(e.span)],
Expression::Boolean(_)
| Expression::FilePath(_)
| Expression::Literal(Literal::ColumnPath(_))
| Expression::Literal(Literal::GlobPattern(_))
| Expression::Literal(Literal::Number(_))
| Expression::Literal(Literal::Size(_, _))
| Expression::Literal(Literal::String(_)) => {
vec![
LocationType::Argument(self.command.clone(), self.flag.clone()).spanned(e.span),
]
}
Expression::Binary(binary) => {
let mut result = Vec::new();
result.append(&mut self.expression(&binary.left));
result.append(&mut self.expression(&binary.right));
result
}
Expression::Range(range) => {
let mut result = Vec::new();
if let Some(left) = &range.left {
result.append(&mut self.expression(left));
}
if let Some(right) = &range.right {
result.append(&mut self.expression(right));
}
result
}
Expression::ExternalWord
| Expression::ExternalCommand(_)
| Expression::Synthetic(_)
| Expression::Literal(Literal::Operator(_))
| Expression::Literal(Literal::Bare(_))
| Expression::Garbage => Vec::new(),
}
}
fn internal_command(&self, internal: &InternalCommand) -> Vec<CompletionLocation> {
let mut result = Vec::new();
match internal.args.head.expr {
Expression::Command => {
result.push(LocationType::Command.spanned(internal.name_span));
}
Expression::Literal(Literal::String(_)) => {
result.push(LocationType::Command.spanned(internal.name_span));
}
_ => (),
}
if let Some(positionals) = &internal.args.positional {
let mut positionals = positionals.iter();
if internal.name == "run_external" {
if let Some(external_command) = positionals.next() {
result.push(LocationType::Command.spanned(external_command.span));
}
}
result.extend(positionals.flat_map(|positional| match positional.expr {
Expression::Garbage => {
let garbage = positional.span.slice(self.line);
let location = if garbage.starts_with('-') {
LocationType::Flag(internal.name.clone())
} else {
// TODO we may be able to map this to the name of a positional,
// but we'll need a signature
LocationType::Argument(Some(internal.name.clone()), None)
};
vec![location.spanned(positional.span)]
}
_ => self.expression(positional),
}));
}
if let Some(named) = &internal.args.named {
for (name, kind) in &named.named {
match kind {
NamedValue::PresentSwitch(span) => {
result.push(LocationType::Flag(internal.name.clone()).spanned(*span));
}
NamedValue::Value(span, expr) => {
result.push(LocationType::Flag(internal.name.clone()).spanned(*span));
result.append(&mut self.with_flag(name.clone()).expression(expr));
}
_ => (),
}
}
}
result
}
fn pipeline(&self, pipeline: &Pipeline) -> Vec<CompletionLocation> {
let mut result = Vec::new();
for command in &pipeline.list {
match command {
ClassifiedCommand::Internal(internal) => {
let engine = self.with_command(internal.name.clone());
result.append(&mut engine.internal_command(internal));
}
ClassifiedCommand::Expr(expr) => result.append(&mut self.expression(expr)),
_ => (),
}
}
result
}
/// Flattens the block into a Vec of completion locations
pub fn completion_locations(&self, block: &Block) -> Vec<CompletionLocation> {
block
.block
.iter()
.flat_map(|g| g.pipelines.iter().flat_map(|v| self.pipeline(v)))
.collect()
}
pub fn new(line: &'s str) -> Flatten<'s> {
Flatten {
line,
command: None,
flag: None,
}
}
pub fn with_command(&self, command: String) -> Flatten<'s> {
Flatten {
line: self.line,
command: Some(command),
flag: None,
}
}
pub fn with_flag(&self, flag: String) -> Flatten<'s> {
Flatten {
line: self.line,
command: self.command.clone(),
flag: Some(flag),
}
}
}
/// Characters that precede a command name
const BEFORE_COMMAND_CHARS: &[char] = &['|', '(', ';'];
/// Determines the completion location for a given block at the given cursor position
pub fn completion_location(line: &str, block: &Block, pos: usize) -> Vec<CompletionLocation> {
let completion_engine = Flatten::new(line);
let locations = completion_engine.completion_locations(block);
if locations.is_empty() {
vec![LocationType::Command.spanned(Span::unknown())]
} else {
let mut command = None;
let mut prev = None;
for loc in locations {
// We don't use span.contains because we want to include the end. This handles the case
// where the cursor is just after the text (i.e., no space between cursor and text)
if loc.span.start() <= pos && pos <= loc.span.end() {
// The parser sees the "-" in `cmd -` as an argument, but the user is likely
// expecting a flag.
return match loc.item {
LocationType::Argument(ref cmd, _) => {
if loc.span.slice(line) == "-" {
let cmd = cmd.clone();
let span = loc.span;
vec![
loc,
LocationType::Flag(cmd.unwrap_or_default()).spanned(span),
]
} else {
vec![loc]
}
}
_ => vec![loc],
};
} else if pos < loc.span.start() {
break;
}
if let LocationType::Command = loc.item {
command = Some(String::from(loc.span.slice(line)));
}
prev = Some(loc);
}
if let Some(prev) = prev {
// Cursor is between locations (or at the end). Look at the line to see if the cursor
// is after some character that would imply we're in the command position.
let start = prev.span.end();
if line[start..pos].contains(BEFORE_COMMAND_CHARS) {
vec![LocationType::Command.spanned(Span::new(pos, pos))]
} else {
// TODO this should be able to be mapped to a command
vec![LocationType::Argument(command, None).spanned(Span::new(pos, pos))]
}
} else {
// Cursor is before any possible completion location, so must be a command
vec![LocationType::Command.spanned(Span::unknown())]
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use nu_parser::{classify_block, lex, parse_block, ParserScope};
use nu_protocol::{Signature, SyntaxShape};
#[derive(Clone, Debug)]
struct VecRegistry(Vec<Signature>);
impl From<Vec<Signature>> for VecRegistry {
fn from(v: Vec<Signature>) -> Self {
VecRegistry(v)
}
}
impl ParserScope for VecRegistry {
fn has_signature(&self, name: &str) -> bool {
self.0.iter().any(|v| v.name == name)
}
fn get_signature(&self, name: &str) -> Option<nu_protocol::Signature> {
self.0.iter().find(|v| v.name == name).map(Clone::clone)
}
fn get_alias(&self, _name: &str) -> Option<Vec<Spanned<String>>> {
None
}
fn add_alias(&self, _name: &str, _replacement: Vec<Spanned<String>>) {
todo!()
}
fn add_definition(&self, _block: Block) {}
fn get_definitions(&self) -> Vec<Block> {
vec![]
}
fn enter_scope(&self) {}
fn exit_scope(&self) {}
}
mod completion_location {
use super::*;
use nu_parser::ParserScope;
fn completion_location(
line: &str,
scope: &dyn ParserScope,
pos: usize,
) -> Vec<LocationType> {
let (tokens, _) = lex(line, 0);
let (lite_block, _) = parse_block(tokens);
scope.enter_scope();
let (block, _) = classify_block(&lite_block, scope);
scope.exit_scope();
super::completion_location(line, &block, pos)
.into_iter()
.map(|v| v.item)
.collect()
}
#[test]
fn completes_internal_command_names() {
let registry: VecRegistry =
vec![Signature::build("echo").rest(SyntaxShape::Any, "the values to echo")].into();
let line = "echo 1 | echo 2";
assert_eq!(
completion_location(line, &registry, 10),
vec![LocationType::Command],
);
}
#[test]
fn completes_external_command_names() {
let registry: VecRegistry = Vec::new().into();
let line = "echo 1 | echo 2";
assert_eq!(
completion_location(line, &registry, 10),
vec![LocationType::Command],
);
}
#[test]
fn completes_command_names_when_cursor_immediately_after_command_name() {
let registry: VecRegistry = Vec::new().into();
let line = "echo 1 | echo 2";
assert_eq!(
completion_location(line, &registry, 4),
vec![LocationType::Command],
);
}
#[test]
fn completes_variables() {
let registry: VecRegistry = Vec::new().into();
let line = "echo $nu.env.";
assert_eq!(
completion_location(line, &registry, 13),
vec![LocationType::Variable],
);
}
#[test]
fn completes_flags() {
let registry: VecRegistry = vec![Signature::build("du")
.switch("recursive", "the values to echo", None)
.rest(SyntaxShape::Any, "blah")]
.into();
let line = "du --recurs";
assert_eq!(
completion_location(line, &registry, 7),
vec![LocationType::Flag("du".to_string())],
);
}
#[test]
fn completes_incomplete_nested_structure() {
let registry: VecRegistry = vec![Signature::build("sys")].into();
let line = "echo $(sy";
assert_eq!(
completion_location(line, &registry, 8),
vec![LocationType::Command],
);
}
#[test]
fn has_correct_command_name_for_argument() {
let registry: VecRegistry = vec![Signature::build("cd")].into();
let line = "cd ";
assert_eq!(
completion_location(line, &registry, 3),
vec![LocationType::Argument(Some("cd".to_string()), None)],
);
}
#[test]
fn completes_flags_with_just_a_single_hyphen() {
let registry: VecRegistry = vec![Signature::build("du")
.switch("recursive", "the values to echo", None)
.rest(SyntaxShape::Any, "blah")]
.into();
let line = "du -";
assert_eq!(
completion_location(line, &registry, 3),
vec![
LocationType::Argument(Some("du".to_string()), None),
LocationType::Flag("du".to_string()),
],
);
}
#[test]
fn completes_arguments() {
let registry: VecRegistry =
vec![Signature::build("echo").rest(SyntaxShape::Any, "the values to echo")].into();
let line = "echo 1 | echo 2";
assert_eq!(
completion_location(line, &registry, 6),
vec![LocationType::Argument(Some("echo".to_string()), None)],
);
}
}
}

View File

@ -0,0 +1,41 @@
use super::matchers::Matcher;
use crate::completion::{Completer, CompletionContext, Suggestion};
use nu_engine::EvaluationContext;
pub struct FlagCompleter {
pub(crate) cmd: String,
}
impl Completer for FlagCompleter {
fn complete(
&self,
ctx: &CompletionContext<'_>,
partial: &str,
matcher: &dyn Matcher,
) -> Vec<Suggestion> {
let context: &EvaluationContext = ctx.as_ref();
if let Some(cmd) = context.scope.get_command(&self.cmd) {
let sig = cmd.signature();
let mut suggestions = Vec::new();
for (name, (named_type, _desc)) in sig.named.iter() {
suggestions.push(format!("--{}", name));
if let Some(c) = named_type.get_short() {
suggestions.push(format!("-{}", c));
}
}
suggestions
.into_iter()
.filter(|v| matcher.matches(partial, v))
.map(|v| Suggestion {
replacement: format!("{} ", v),
display: v,
})
.collect()
} else {
Vec::new()
}
}
}

View File

@ -0,0 +1,45 @@
use crate::completion::matchers;
pub struct Matcher;
impl matchers::Matcher for Matcher {
fn matches(&self, partial: &str, from: &str) -> bool {
from.to_ascii_lowercase()
.starts_with(partial.to_ascii_lowercase().as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
// TODO: check some unicode matches if this becomes relevant
// FIXME: could work exhaustively through ['-', '--'. ''] in a loop for each test
#[test]
fn completes_exact_matches() {
let matcher: Box<dyn matchers::Matcher> = Box::new(Matcher);
assert!(matcher.matches("shouldmatch", "shouldmatch"));
assert!(matcher.matches("shouldm", "shouldmatch"));
assert!(matcher.matches("--also-should-m", "--also-should-match"));
assert!(matcher.matches("-also-should-m", "-also-should-match"));
}
#[test]
fn completes_case_insensitive_matches() {
let matcher: Box<dyn matchers::Matcher> = Box::new(Matcher);
assert!(matcher.matches("thisshould", "Thisshouldmatch"));
assert!(matcher.matches("--Shouldm", "--shouldmatch"));
assert!(matcher.matches("-Shouldm", "-shouldmatch"));
}
#[test]
fn should_not_match_when_unequal() {
let matcher: Box<dyn matchers::Matcher> = Box::new(Matcher);
assert!(!matcher.matches("ashouldmatch", "Shouldnotmatch"));
assert!(!matcher.matches("--ashouldnotmatch", "--Shouldnotmatch"));
assert!(!matcher.matches("-ashouldnotmatch", "-Shouldnotmatch"));
}
}

View File

@ -0,0 +1,28 @@
use crate::completion::matchers;
pub struct Matcher;
impl matchers::Matcher for Matcher {
fn matches(&self, partial: &str, from: &str) -> bool {
from.starts_with(partial)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn completes_case_sensitive() {
let matcher: Box<dyn matchers::Matcher> = Box::new(Matcher);
//Should match
assert!(matcher.matches("shouldmatch", "shouldmatch"));
assert!(matcher.matches("shouldm", "shouldmatch"));
assert!(matcher.matches("--also-should-m", "--also-should-match"));
assert!(matcher.matches("-also-should-m", "-also-should-match"));
// Should not match
assert!(!matcher.matches("--Shouldnot", "--shouldnotmatch"));
}
}

View File

@ -0,0 +1,6 @@
pub(crate) mod case_insensitive;
pub(crate) mod case_sensitive;
pub trait Matcher {
fn matches(&self, partial: &str, from: &str) -> bool;
}

View File

@ -0,0 +1,37 @@
pub(crate) mod command;
pub(crate) mod engine;
pub(crate) mod flag;
pub(crate) mod matchers;
pub(crate) mod path;
use matchers::Matcher;
use nu_engine::EvaluationContext;
#[derive(Debug, Eq, PartialEq)]
pub struct Suggestion {
pub display: String,
pub replacement: String,
}
pub struct CompletionContext<'a>(&'a EvaluationContext);
impl<'a> CompletionContext<'a> {
pub fn new(a: &'a EvaluationContext) -> CompletionContext<'a> {
CompletionContext(a)
}
}
impl<'a> AsRef<EvaluationContext> for CompletionContext<'a> {
fn as_ref(&self) -> &EvaluationContext {
self.0
}
}
pub trait Completer {
fn complete(
&self,
ctx: &CompletionContext<'_>,
partial: &str,
matcher: &dyn Matcher,
) -> Vec<Suggestion>;
}

View File

@ -0,0 +1,88 @@
use std::path::PathBuf;
use super::matchers::Matcher;
use crate::completion::{Completer, CompletionContext, Suggestion};
const SEP: char = std::path::MAIN_SEPARATOR;
pub struct PathCompleter;
pub struct PathSuggestion {
pub(crate) path: PathBuf,
pub(crate) suggestion: Suggestion,
}
impl PathCompleter {
pub fn path_suggestions(&self, partial: &str, matcher: &dyn Matcher) -> Vec<PathSuggestion> {
let expanded = nu_parser::expand_ndots(partial);
let expanded = expanded.as_ref();
let (base_dir_name, partial) = match expanded.rfind(SEP) {
Some(pos) => expanded.split_at(pos + SEP.len_utf8()),
None => ("", expanded),
};
let base_dir = if base_dir_name.is_empty() {
PathBuf::from(".")
} else {
#[cfg(feature = "directories")]
{
let home_prefix = format!("~{}", SEP);
if base_dir_name.starts_with(&home_prefix) {
let mut home_dir = dirs_next::home_dir().unwrap_or_else(|| PathBuf::from("~"));
home_dir.push(&base_dir_name[2..]);
home_dir
} else {
PathBuf::from(base_dir_name)
}
}
#[cfg(not(feature = "directories"))]
{
PathBuf::from(base_dir_name)
}
};
if let Ok(result) = base_dir.read_dir() {
result
.filter_map(|entry| {
entry.ok().and_then(|entry| {
let mut file_name = entry.file_name().to_string_lossy().into_owned();
if matcher.matches(partial, file_name.as_str()) {
let mut path = format!("{}{}", base_dir_name, file_name);
if entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) {
path.push(std::path::MAIN_SEPARATOR);
file_name.push(std::path::MAIN_SEPARATOR);
}
Some(PathSuggestion {
path: entry.path(),
suggestion: Suggestion {
replacement: path,
display: file_name,
},
})
} else {
None
}
})
})
.collect()
} else {
Vec::new()
}
}
}
impl Completer for PathCompleter {
fn complete(
&self,
_ctx: &CompletionContext<'_>,
partial: &str,
matcher: &dyn Matcher,
) -> Vec<Suggestion> {
self.path_suggestions(partial, matcher)
.into_iter()
.map(|ps| ps.suggestion)
.collect()
}
}

View File

@ -1,284 +0,0 @@
use crate::commands::{command::CommandArgs, Command, UnevaluatedCallInfo};
use crate::env::host::Host;
use crate::shell::shell_manager::ShellManager;
use crate::stream::{InputStream, OutputStream};
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_parser::SignatureRegistry;
use nu_protocol::{hir, Scope, Signature};
use nu_source::{Tag, Text};
use parking_lot::Mutex;
use std::error::Error;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
#[derive(Debug, Clone, Default)]
pub struct CommandRegistry {
registry: Arc<Mutex<IndexMap<String, Command>>>,
}
impl SignatureRegistry for CommandRegistry {
fn has(&self, name: &str) -> bool {
let registry = self.registry.lock();
registry.contains_key(name)
}
fn get(&self, name: &str) -> Option<Signature> {
let registry = self.registry.lock();
registry.get(name).map(|command| command.signature())
}
fn clone_box(&self) -> Box<dyn SignatureRegistry> {
Box::new(self.clone())
}
}
impl CommandRegistry {
pub fn new() -> CommandRegistry {
CommandRegistry {
registry: Arc::new(Mutex::new(IndexMap::default())),
}
}
}
impl CommandRegistry {
pub fn get_command(&self, name: &str) -> Option<Command> {
let registry = self.registry.lock();
registry.get(name).cloned()
}
pub fn expect_command(&self, name: &str) -> Result<Command, ShellError> {
self.get_command(name).ok_or_else(|| {
ShellError::untagged_runtime_error(format!("Could not load command: {}", name))
})
}
pub fn has(&self, name: &str) -> bool {
let registry = self.registry.lock();
registry.contains_key(name)
}
pub fn insert(&mut self, name: impl Into<String>, command: Command) {
let mut registry = self.registry.lock();
registry.insert(name.into(), command);
}
pub fn names(&self) -> Vec<String> {
let registry = self.registry.lock();
registry.keys().cloned().collect()
}
}
#[derive(Clone)]
pub struct Context {
pub registry: CommandRegistry,
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub ctrl_c: Arc<AtomicBool>,
pub raw_input: String,
pub user_recently_used_autoenv_untrust: bool,
pub(crate) shell_manager: ShellManager,
#[cfg(windows)]
pub windows_drives_previous_cwd: Arc<Mutex<std::collections::HashMap<String, String>>>,
}
impl Context {
pub(crate) fn registry(&self) -> &CommandRegistry {
&self.registry
}
pub(crate) fn from_raw(raw_args: &CommandArgs, registry: &CommandRegistry) -> Context {
#[cfg(windows)]
{
Context {
registry: registry.clone(),
host: raw_args.host.clone(),
current_errors: raw_args.current_errors.clone(),
ctrl_c: raw_args.ctrl_c.clone(),
shell_manager: raw_args.shell_manager.clone(),
user_recently_used_autoenv_untrust: false,
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
raw_input: String::default(),
}
}
#[cfg(not(windows))]
{
Context {
registry: registry.clone(),
host: raw_args.host.clone(),
current_errors: raw_args.current_errors.clone(),
ctrl_c: raw_args.ctrl_c.clone(),
shell_manager: raw_args.shell_manager.clone(),
user_recently_used_autoenv_untrust: false,
raw_input: String::default(),
}
}
}
pub(crate) fn from_args(args: &CommandArgs, registry: &CommandRegistry) -> Context {
#[cfg(windows)]
{
Context {
registry: registry.clone(),
host: args.host.clone(),
current_errors: args.current_errors.clone(),
ctrl_c: args.ctrl_c.clone(),
shell_manager: args.shell_manager.clone(),
user_recently_used_autoenv_untrust: false,
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
raw_input: String::default(),
}
}
#[cfg(not(windows))]
{
Context {
registry: registry.clone(),
host: args.host.clone(),
current_errors: args.current_errors.clone(),
ctrl_c: args.ctrl_c.clone(),
user_recently_used_autoenv_untrust: false,
shell_manager: args.shell_manager.clone(),
raw_input: String::default(),
}
}
}
pub fn basic() -> Result<Context, Box<dyn Error>> {
let registry = CommandRegistry::new();
#[cfg(windows)]
{
Ok(Context {
registry,
host: Arc::new(parking_lot::Mutex::new(Box::new(
crate::env::host::BasicHost,
))),
current_errors: Arc::new(Mutex::new(vec![])),
ctrl_c: Arc::new(AtomicBool::new(false)),
user_recently_used_autoenv_untrust: false,
shell_manager: ShellManager::basic()?,
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
raw_input: String::default(),
})
}
#[cfg(not(windows))]
{
Ok(Context {
registry,
host: Arc::new(parking_lot::Mutex::new(Box::new(
crate::env::host::BasicHost,
))),
current_errors: Arc::new(Mutex::new(vec![])),
ctrl_c: Arc::new(AtomicBool::new(false)),
user_recently_used_autoenv_untrust: false,
shell_manager: ShellManager::basic()?,
raw_input: String::default(),
})
}
}
pub(crate) fn error(&mut self, error: ShellError) {
self.with_errors(|errors| errors.push(error))
}
pub(crate) fn clear_errors(&mut self) {
self.current_errors.lock().clear()
}
pub(crate) fn get_errors(&self) -> Vec<ShellError> {
self.current_errors.lock().clone()
}
pub(crate) fn add_error(&self, err: ShellError) {
self.current_errors.lock().push(err);
}
pub(crate) fn maybe_print_errors(&mut self, source: Text) -> bool {
let errors = self.current_errors.clone();
let mut errors = errors.lock();
if errors.len() > 0 {
let error = errors[0].clone();
*errors = vec![];
crate::cli::print_err(error, &source);
true
} else {
false
}
}
pub(crate) fn with_host<T>(&mut self, block: impl FnOnce(&mut dyn Host) -> T) -> T {
let mut host = self.host.lock();
block(&mut *host)
}
pub(crate) fn with_errors<T>(&mut self, block: impl FnOnce(&mut Vec<ShellError>) -> T) -> T {
let mut errors = self.current_errors.lock();
block(&mut *errors)
}
pub fn add_commands(&mut self, commands: Vec<Command>) {
for command in commands {
self.registry.insert(command.name().to_string(), command);
}
}
pub(crate) fn get_command(&self, name: &str) -> Option<Command> {
self.registry.get_command(name)
}
pub(crate) fn expect_command(&self, name: &str) -> Result<Command, ShellError> {
self.registry.expect_command(name)
}
pub(crate) async fn run_command(
&mut self,
command: Command,
name_tag: Tag,
args: hir::Call,
scope: &Scope,
input: InputStream,
) -> Result<OutputStream, ShellError> {
let command_args = self.command_args(args, input, name_tag, scope);
command.run(command_args, self.registry()).await
}
fn call_info(&self, args: hir::Call, name_tag: Tag, scope: &Scope) -> UnevaluatedCallInfo {
UnevaluatedCallInfo {
args,
name_tag,
scope: scope.clone(),
}
}
fn command_args(
&self,
args: hir::Call,
input: InputStream,
name_tag: Tag,
scope: &Scope,
) -> CommandArgs {
CommandArgs {
host: self.host.clone(),
ctrl_c: self.ctrl_c.clone(),
current_errors: self.current_errors.clone(),
shell_manager: self.shell_manager.clone(),
call_info: self.call_info(args, name_tag, scope),
input,
raw_input: self.raw_input.clone(),
}
}
pub fn get_env(&self) -> IndexMap<String, String> {
let mut output = IndexMap::new();
for (var, value) in self.host.lock().vars() {
output.insert(var, value);
}
output
}
}

View File

@ -1,12 +0,0 @@
pub(crate) mod base;
pub(crate) mod command;
pub mod config;
pub(crate) mod dict;
pub(crate) mod files;
pub mod primitive;
pub(crate) mod types;
pub mod value;
pub(crate) use command::command_dict;
pub(crate) use dict::TaggedListBuilder;
pub(crate) use files::dir_entry_dict;

View File

@ -1,270 +0,0 @@
use crate::prelude::*;
use chrono::{DateTime, Utc};
use nu_protocol::RangeInclusion;
use nu_protocol::{format_primitive, ColumnPath, Dictionary, Primitive, UntaggedValue, Value};
use nu_source::{b, PrettyDebug};
use std::collections::BTreeMap;
use std::fmt::Debug;
use std::hash::Hash;
use std::path::PathBuf;
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct InlineRange {
from: (InlineShape, RangeInclusion),
to: (InlineShape, RangeInclusion),
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum InlineShape {
Nothing,
Int(BigInt),
Decimal(BigDecimal),
Range(Box<InlineRange>),
Bytesize(u64),
String(String),
Line(String),
ColumnPath(ColumnPath),
Pattern(String),
Boolean(bool),
Date(DateTime<Utc>),
Duration(BigInt),
Path(PathBuf),
Binary(usize),
Row(BTreeMap<Column, InlineShape>),
Table(Vec<InlineShape>),
// TODO: Block arguments
Block,
// TODO: Error type
Error,
// Stream markers (used as bookend markers rather than actual values)
BeginningOfStream,
EndOfStream,
}
pub struct FormatInlineShape {
shape: InlineShape,
column: Option<Column>,
}
impl InlineShape {
pub fn from_primitive(primitive: &Primitive) -> InlineShape {
match primitive {
Primitive::Nothing => InlineShape::Nothing,
Primitive::Int(int) => InlineShape::Int(int.clone()),
Primitive::Range(range) => {
let (left, left_inclusion) = &range.from;
let (right, right_inclusion) = &range.to;
InlineShape::Range(Box::new(InlineRange {
from: (InlineShape::from_primitive(left), *left_inclusion),
to: (InlineShape::from_primitive(right), *right_inclusion),
}))
}
Primitive::Decimal(decimal) => InlineShape::Decimal(decimal.clone()),
Primitive::Filesize(bytesize) => InlineShape::Bytesize(*bytesize),
Primitive::String(string) => InlineShape::String(string.clone()),
Primitive::Line(string) => InlineShape::Line(string.clone()),
Primitive::ColumnPath(path) => InlineShape::ColumnPath(path.clone()),
Primitive::Pattern(pattern) => InlineShape::Pattern(pattern.clone()),
Primitive::Boolean(boolean) => InlineShape::Boolean(*boolean),
Primitive::Date(date) => InlineShape::Date(*date),
Primitive::Duration(duration) => InlineShape::Duration(duration.clone()),
Primitive::Path(path) => InlineShape::Path(path.clone()),
Primitive::Binary(b) => InlineShape::Binary(b.len()),
Primitive::BeginningOfStream => InlineShape::BeginningOfStream,
Primitive::EndOfStream => InlineShape::EndOfStream,
}
}
pub fn from_dictionary(dictionary: &Dictionary) -> InlineShape {
let mut map = BTreeMap::new();
for (key, value) in dictionary.entries.iter() {
let column = Column::String(key.clone());
map.insert(column, InlineShape::from_value(value));
}
InlineShape::Row(map)
}
pub fn from_table<'a>(table: impl IntoIterator<Item = &'a Value>) -> InlineShape {
let mut vec = vec![];
for item in table.into_iter() {
vec.push(InlineShape::from_value(item))
}
InlineShape::Table(vec)
}
pub fn from_value<'a>(value: impl Into<&'a UntaggedValue>) -> InlineShape {
match value.into() {
UntaggedValue::Primitive(p) => InlineShape::from_primitive(p),
UntaggedValue::Row(row) => InlineShape::from_dictionary(row),
UntaggedValue::Table(table) => InlineShape::from_table(table.iter()),
UntaggedValue::Error(_) => InlineShape::Error,
UntaggedValue::Block(_) => InlineShape::Block,
}
}
#[allow(unused)]
pub fn format_for_column(self, column: impl Into<Column>) -> FormatInlineShape {
FormatInlineShape {
shape: self,
column: Some(column.into()),
}
}
pub fn format(self) -> FormatInlineShape {
FormatInlineShape {
shape: self,
column: None,
}
}
}
impl PrettyDebug for FormatInlineShape {
fn pretty(&self) -> DebugDocBuilder {
let column = &self.column;
match &self.shape {
InlineShape::Nothing => b::blank(),
InlineShape::Int(int) => b::primitive(format!("{}", int)),
InlineShape::Decimal(decimal) => {
b::description(format_primitive(&Primitive::Decimal(decimal.clone()), None))
}
InlineShape::Range(range) => {
let (left, left_inclusion) = &range.from;
let (right, right_inclusion) = &range.to;
let op = match (left_inclusion, right_inclusion) {
(RangeInclusion::Inclusive, RangeInclusion::Exclusive) => "..",
_ => unimplemented!("No syntax for ranges that aren't inclusive on the left and exclusive on the right")
};
left.clone().format().pretty() + b::operator(op) + right.clone().format().pretty()
}
InlineShape::Bytesize(bytesize) => {
let byte = byte_unit::Byte::from_bytes(*bytesize as u128);
let byte = byte.get_appropriate_unit(false);
match byte.get_unit() {
byte_unit::ByteUnit::B => {
(b::primitive(format!("{}", byte.get_value())) + b::space() + b::kind("B"))
.group()
}
_ => b::primitive(byte.format(1)),
}
}
InlineShape::String(string) => b::primitive(string),
InlineShape::Line(string) => b::primitive(string),
InlineShape::ColumnPath(path) => {
b::intersperse(path.iter().map(|member| member.pretty()), b::keyword("."))
}
InlineShape::Pattern(pattern) => b::primitive(pattern),
InlineShape::Boolean(boolean) => b::primitive(
match (boolean, column) {
(true, None) => "Yes",
(false, None) => "No",
(true, Some(Column::String(s))) if !s.is_empty() => s,
(false, Some(Column::String(s))) if !s.is_empty() => "",
(true, Some(_)) => "Yes",
(false, Some(_)) => "No",
}
.to_owned(),
),
InlineShape::Date(date) => b::primitive(nu_protocol::format_date(date)),
InlineShape::Duration(duration) => b::description(format_primitive(
&Primitive::Duration(duration.clone()),
None,
)),
InlineShape::Path(path) => b::primitive(path.display()),
InlineShape::Binary(length) => b::opaque(format!("<binary: {} bytes>", length)),
InlineShape::Row(row) => b::delimit(
"[",
b::kind("row")
+ b::space()
+ if row.keys().len() <= 6 {
b::intersperse(
row.keys().map(|key| match key {
Column::String(string) => b::description(string),
Column::Value => b::blank(),
}),
b::space(),
)
} else {
b::description(format!("{} columns", row.keys().len()))
},
"]",
)
.group(),
InlineShape::Table(rows) => b::delimit(
"[",
b::kind("table")
+ b::space()
+ b::primitive(rows.len())
+ b::space()
+ b::description("rows"),
"]",
)
.group(),
InlineShape::Block => b::opaque("block"),
InlineShape::Error => b::error("error"),
InlineShape::BeginningOfStream => b::blank(),
InlineShape::EndOfStream => b::blank(),
}
}
}
pub trait GroupedValue: Debug + Clone {
type Item;
fn new() -> Self;
fn merge(&mut self, value: Self::Item);
}
impl GroupedValue for Vec<(usize, usize)> {
type Item = usize;
fn new() -> Vec<(usize, usize)> {
vec![]
}
fn merge(&mut self, new_value: usize) {
match self.last_mut() {
Some(value) if value.1 == new_value - 1 => {
value.1 += 1;
}
_ => self.push((new_value, new_value)),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Column {
String(String),
Value,
}
impl Into<Column> for String {
fn into(self) -> Column {
Column::String(self)
}
}
impl Into<Column> for &String {
fn into(self) -> Column {
Column::String(self.clone())
}
}
impl Into<Column> for &str {
fn into(self) -> Column {
Column::String(self.to_string())
}
}

View File

@ -1,163 +0,0 @@
mod conf;
mod nuconfig;
#[cfg(test)]
pub mod tests;
pub(crate) use conf::Conf;
pub(crate) use nuconfig::NuConfig;
use crate::commands::from_toml::convert_toml_value_to_nu_value;
use crate::commands::to_toml::value_to_toml_value;
use crate::prelude::*;
use indexmap::IndexMap;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{Dictionary, ShellTypeName, UntaggedValue, Value};
use nu_source::Tag;
use std::fs::{self, OpenOptions};
use std::io;
use std::path::{Path, PathBuf};
#[cfg(feature = "directories")]
pub fn config_path() -> Result<PathBuf, ShellError> {
use directories::ProjectDirs;
let dir = ProjectDirs::from("org", "nushell", "nu")
.ok_or_else(|| ShellError::untagged_runtime_error("Couldn't find project directory"))?;
let path = ProjectDirs::config_dir(&dir).to_owned();
std::fs::create_dir_all(&path).map_err(|err| {
ShellError::untagged_runtime_error(&format!("Couldn't create {} path:\n{}", "config", err))
})?;
Ok(path)
}
#[cfg(not(feature = "directories"))]
pub fn config_path() -> Result<PathBuf, ShellError> {
// FIXME: unsure if this should be error or a simple default
Ok(std::path::PathBuf::from("/"))
}
pub fn default_path() -> Result<PathBuf, ShellError> {
default_path_for(&None)
}
pub fn default_path_for(file: &Option<PathBuf>) -> Result<PathBuf, ShellError> {
let mut filename = config_path()?;
let file: &Path = file
.as_ref()
.map(AsRef::as_ref)
.unwrap_or_else(|| "config.toml".as_ref());
filename.push(file);
Ok(filename)
}
#[cfg(feature = "directories")]
pub fn user_data() -> Result<PathBuf, ShellError> {
use directories::ProjectDirs;
let dir = ProjectDirs::from("org", "nushell", "nu")
.ok_or_else(|| ShellError::untagged_runtime_error("Couldn't find project directory"))?;
let path = ProjectDirs::data_local_dir(&dir).to_owned();
std::fs::create_dir_all(&path).map_err(|err| {
ShellError::untagged_runtime_error(&format!(
"Couldn't create {} path:\n{}",
"user data", err
))
})?;
Ok(path)
}
#[cfg(not(feature = "directories"))]
pub fn user_data() -> Result<PathBuf, ShellError> {
// FIXME: unsure if this should be error or a simple default
Ok(std::path::PathBuf::from("/"))
}
pub fn read(
tag: impl Into<Tag>,
at: &Option<PathBuf>,
) -> Result<IndexMap<String, Value>, ShellError> {
let filename = default_path()?;
let filename = match at {
None => filename,
Some(ref file) => file.clone(),
};
if !filename.exists() && touch(&filename).is_err() {
// If we can't create configs, let's just return an empty indexmap instead as we may be in
// a readonly environment
return Ok(IndexMap::new());
}
trace!("config file = {}", filename.display());
let tag = tag.into();
let contents = fs::read_to_string(filename)
.map(|v| v.tagged(&tag))
.map_err(|err| {
ShellError::labeled_error(
&format!("Couldn't read config file:\n{}", err),
"file name",
&tag,
)
})?;
let parsed: toml::Value = toml::from_str(&contents).map_err(|err| {
ShellError::labeled_error(
&format!("Couldn't parse config file:\n{}", err),
"file name",
&tag,
)
})?;
let value = convert_toml_value_to_nu_value(&parsed, tag);
let tag = value.tag();
match value.value {
UntaggedValue::Row(Dictionary { entries }) => Ok(entries),
other => Err(ShellError::type_error(
"Dictionary",
other.type_name().spanned(tag.span),
)),
}
}
pub fn config(tag: impl Into<Tag>) -> Result<IndexMap<String, Value>, ShellError> {
read(tag, &None)
}
pub fn write(config: &IndexMap<String, Value>, at: &Option<PathBuf>) -> Result<(), ShellError> {
let filename = &mut default_path()?;
let filename = match at {
None => filename,
Some(file) => {
filename.pop();
filename.push(file);
filename
}
};
let contents = value_to_toml_value(
&UntaggedValue::Row(Dictionary::new(config.clone())).into_untagged_value(),
)?;
let contents = toml::to_string(&contents)?;
fs::write(&filename, &contents)?;
Ok(())
}
// A simple implementation of `% touch path` (ignores existing files)
fn touch(path: &Path) -> io::Result<()> {
match OpenOptions::new().create(true).write(true).open(path) {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
}

View File

@ -1,22 +0,0 @@
use nu_protocol::Value;
use std::fmt::Debug;
pub trait Conf: Debug + Send {
fn env(&self) -> Option<Value>;
fn path(&self) -> Option<Value>;
fn reload(&self);
}
impl Conf for Box<dyn Conf> {
fn env(&self) -> Option<Value> {
(**self).env()
}
fn path(&self) -> Option<Value> {
(**self).path()
}
fn reload(&self) {
(**self).reload();
}
}

View File

@ -1,64 +0,0 @@
use crate::data::config::{read, Conf};
use indexmap::IndexMap;
use nu_protocol::Value;
use nu_source::Tag;
use parking_lot::Mutex;
use std::fmt::Debug;
use std::sync::Arc;
#[derive(Debug, Clone, Default)]
pub struct NuConfig {
pub vars: Arc<Mutex<IndexMap<String, Value>>>,
}
impl Conf for NuConfig {
fn env(&self) -> Option<Value> {
self.env()
}
fn path(&self) -> Option<Value> {
self.path()
}
fn reload(&self) {
let mut vars = self.vars.lock();
if let Ok(variables) = read(Tag::unknown(), &None) {
vars.extend(variables);
}
}
}
impl NuConfig {
pub fn new() -> NuConfig {
let vars = if let Ok(variables) = read(Tag::unknown(), &None) {
variables
} else {
IndexMap::default()
};
NuConfig {
vars: Arc::new(Mutex::new(vars)),
}
}
pub fn env(&self) -> Option<Value> {
let vars = self.vars.lock();
if let Some(env_vars) = vars.get("env") {
return Some(env_vars.clone());
}
None
}
pub fn path(&self) -> Option<Value> {
let vars = self.vars.lock();
if let Some(env_vars) = vars.get("path") {
return Some(env_vars.clone());
}
None
}
}

View File

@ -1,44 +0,0 @@
use crate::data::config::{read, Conf, NuConfig};
use indexmap::IndexMap;
use nu_protocol::Value;
use nu_source::Tag;
use parking_lot::Mutex;
use std::path::{Path, PathBuf};
use std::sync::Arc;
#[derive(Debug)]
pub struct FakeConfig {
pub config: NuConfig,
}
impl Conf for FakeConfig {
fn env(&self) -> Option<Value> {
self.config.env()
}
fn path(&self) -> Option<Value> {
self.config.path()
}
fn reload(&self) {
// no-op
}
}
impl FakeConfig {
pub fn new(config_file: &Path) -> FakeConfig {
let config_file = PathBuf::from(config_file);
let vars = if let Ok(variables) = read(Tag::unknown(), &Some(config_file)) {
variables
} else {
IndexMap::default()
};
FakeConfig {
config: NuConfig {
vars: Arc::new(Mutex::new(vars)),
},
}
}
}

View File

@ -1,200 +0,0 @@
use crate::commands::du::{DirBuilder, DirInfo};
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
#[cfg(unix)]
use std::os::unix::fs::FileTypeExt;
pub(crate) fn get_file_type(md: &std::fs::Metadata) -> &str {
let ft = md.file_type();
let mut file_type = "Unknown";
if ft.is_dir() {
file_type = "Dir";
} else if ft.is_file() {
file_type = "File";
} else if ft.is_symlink() {
file_type = "Symlink";
} else {
#[cfg(unix)]
{
if ft.is_block_device() {
file_type = "Block device";
} else if ft.is_char_device() {
file_type = "Char device";
} else if ft.is_fifo() {
file_type = "Pipe";
} else if ft.is_socket() {
file_type = "Socket";
}
}
}
file_type
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn dir_entry_dict(
filename: &std::path::Path,
metadata: Option<&std::fs::Metadata>,
tag: impl Into<Tag>,
long: bool,
short_name: bool,
with_symlink_targets: bool,
du: bool,
ctrl_c: Arc<AtomicBool>,
) -> Result<Value, ShellError> {
let tag = tag.into();
let mut dict = TaggedDictBuilder::new(&tag);
// Insert all columns first to maintain proper table alignment if we can't find (or are not allowed to view) any information
if long {
#[cfg(windows)]
{
for column in [
"name", "type", "target", "readonly", "size", "created", "accessed", "modified",
]
.iter()
{
dict.insert_untagged(*column, UntaggedValue::nothing());
}
}
#[cfg(unix)]
{
for column in [
"name", "type", "target", "readonly", "mode", "uid", "group", "size", "created",
"accessed", "modified",
]
.iter()
{
dict.insert_untagged(&(*column.to_owned()), UntaggedValue::nothing());
}
}
} else {
for column in ["name", "type", "target", "size", "modified"].iter() {
if *column == "target" && !with_symlink_targets {
continue;
}
dict.insert_untagged(*column, UntaggedValue::nothing());
}
}
let name = if short_name {
filename.file_name().and_then(|s| s.to_str())
} else {
filename.to_str()
}
.ok_or_else(|| {
ShellError::labeled_error(
format!("Invalid file name: {:}", filename.to_string_lossy()),
"invalid file name",
tag,
)
})?;
dict.insert_untagged("name", UntaggedValue::string(name));
if let Some(md) = metadata {
dict.insert_untagged("type", get_file_type(md));
}
if long || with_symlink_targets {
if let Some(md) = metadata {
if md.file_type().is_symlink() {
let symlink_target_untagged_value: UntaggedValue;
if let Ok(path_to_link) = filename.read_link() {
symlink_target_untagged_value =
UntaggedValue::string(path_to_link.to_string_lossy());
} else {
symlink_target_untagged_value =
UntaggedValue::string("Could not obtain target file's path");
}
dict.insert_untagged("target", symlink_target_untagged_value);
}
}
}
if long {
if let Some(md) = metadata {
dict.insert_untagged(
"readonly",
UntaggedValue::boolean(md.permissions().readonly()),
);
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
use std::os::unix::fs::PermissionsExt;
let mode = md.permissions().mode();
dict.insert_untagged(
"mode",
UntaggedValue::string(umask::Mode::from(mode).to_string()),
);
if let Some(user) = users::get_user_by_uid(md.uid()) {
dict.insert_untagged(
"uid",
UntaggedValue::string(user.name().to_string_lossy()),
);
}
if let Some(group) = users::get_group_by_gid(md.gid()) {
dict.insert_untagged(
"group",
UntaggedValue::string(group.name().to_string_lossy()),
);
}
}
}
}
if let Some(md) = metadata {
let mut size_untagged_value: UntaggedValue = UntaggedValue::nothing();
if md.is_dir() {
let dir_size: u64 = if du {
let params = DirBuilder::new(
Tag {
anchor: None,
span: Span::new(0, 2),
},
None,
false,
None,
false,
);
DirInfo::new(filename, &params, None, ctrl_c).get_size()
} else {
md.len()
};
size_untagged_value = UntaggedValue::filesize(dir_size);
} else if md.is_file() {
size_untagged_value = UntaggedValue::filesize(md.len());
} else if md.file_type().is_symlink() {
if let Ok(symlink_md) = filename.symlink_metadata() {
size_untagged_value = UntaggedValue::filesize(symlink_md.len() as u64);
}
}
dict.insert_untagged("size", size_untagged_value);
}
if let Some(md) = metadata {
if long {
if let Ok(c) = md.created() {
dict.insert_untagged("created", UntaggedValue::system_date(c));
}
if let Ok(a) = md.accessed() {
dict.insert_untagged("accessed", UntaggedValue::system_date(a));
}
}
if let Ok(m) = md.modified() {
dict.insert_untagged("modified", UntaggedValue::system_date(m));
}
}
Ok(dict.into_value())
}

View File

@ -1,20 +0,0 @@
use nu_protocol::{hir::Number, Primitive};
use nu_table::TextStyle;
pub fn number(number: impl Into<Number>) -> Primitive {
let number = number.into();
match number {
Number::Int(int) => Primitive::Int(int),
Number::Decimal(decimal) => Primitive::Decimal(decimal),
}
}
pub fn style_primitive(primitive: &Primitive) -> TextStyle {
match primitive {
Primitive::Int(_) | Primitive::Filesize(_) | Primitive::Decimal(_) => {
TextStyle::basic_right()
}
_ => TextStyle::basic(),
}
}

View File

@ -1,29 +0,0 @@
use crate::data::{TaggedDictBuilder, Value};
use crate::prelude::*;
use itertools::join;
use sysinfo::ProcessExt;
pub(crate) fn process_dict(proc: &sysinfo::Process, tag: impl Into<Tag>) -> Value {
let mut dict = TaggedDictBuilder::new(tag);
let cmd = proc.cmd();
let cmd_value = if cmd.len() == 0 {
value::nothing()
} else {
value::string(join(cmd, ""))
};
dict.insert("pid", value::int(proc.pid() as i64));
dict.insert("status", value::string(proc.status().to_string()));
dict.insert("cpu", value::number(proc.cpu_usage()));
match cmd_value {
UntaggedValue::Primitive(Primitive::Nothing) => {
dict.insert("name", value::string(proc.name()));
}
_ => dict.insert("name", cmd_value),
}
dict.into_value()
}

View File

@ -1,6 +1,3 @@
pub(crate) mod directory_specific_environment;
pub(crate) mod environment;
pub(crate) mod environment_syncer;
pub(crate) mod host;
pub(crate) use self::host::Host;

View File

@ -1,6 +1,5 @@
use crate::commands;
use commands::autoenv;
use indexmap::{IndexMap, IndexSet};
use nu_command::commands::autoenv;
use nu_errors::ShellError;
use serde::Deserialize;
use std::env::*;
@ -52,7 +51,7 @@ impl DirectorySpecificEnvironment {
}
}
fn toml_if_trusted(&mut self, nu_env_file: &PathBuf) -> Result<NuEnvDoc, ShellError> {
fn toml_if_trusted(&mut self, nu_env_file: &Path) -> Result<NuEnvDoc, ShellError> {
let content = std::fs::read(&nu_env_file)?;
if autoenv::file_is_trusted(&nu_env_file, &content)? {
@ -112,7 +111,7 @@ impl DirectorySpecificEnvironment {
if let Some(es) = nu_env_doc.entryscripts {
for s in es {
run(s.as_str())?;
run(s.as_str(), None)?;
}
}
@ -147,7 +146,7 @@ impl DirectorySpecificEnvironment {
new_exitscripts.insert(dir, scripts);
} else {
for s in scripts {
run(s.as_str())?;
run(s.as_str(), Some(&dir))?;
}
}
}
@ -162,7 +161,7 @@ impl DirectorySpecificEnvironment {
pub fn maybe_add_key(
&mut self,
seen_vars: &mut IndexSet<EnvKey>,
dir: &PathBuf,
dir: &Path,
key: &str,
val: &str,
) {
@ -170,7 +169,7 @@ impl DirectorySpecificEnvironment {
if !seen_vars.contains(key) {
seen_vars.insert(key.to_string());
self.added_vars
.entry(dir.clone())
.entry(PathBuf::from(dir))
.or_insert(IndexMap::new())
.insert(key.to_string(), var_os(key));
@ -215,9 +214,23 @@ impl DirectorySpecificEnvironment {
}
}
fn run(cmd: &str) -> Result<(), ShellError> {
fn run(cmd: &str, dir: Option<&PathBuf>) -> Result<(), ShellError> {
if cfg!(target_os = "windows") {
if let Some(dir) = dir {
let command = format!("cd {} & {}", dir.to_string_lossy(), cmd);
Command::new("cmd")
.args(&["/C", command.as_str()])
.output()?
} else {
Command::new("cmd").args(&["/C", cmd]).output()?
}
} else if let Some(dir) = dir {
// FIXME: When nu scripting is added, cding like might not be a good idea. If nu decides to execute entryscripts when entering the dir this way, it will cause troubles.
// For now only standard shell scripts are used, so this is an issue for the future.
Command::new("sh")
.arg("-c")
.arg(format!("cd {:?}; {}", dir, cmd))
.output()?
} else {
Command::new("sh").arg("-c").arg(&cmd).output()?
};

View File

@ -1,38 +1,9 @@
use crate::data::config::Conf;
use crate::env::directory_specific_environment::*;
use indexmap::{indexmap, IndexSet};
use nu_data::config::Conf;
use nu_engine::Env;
use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value};
use std::env::*;
use std::ffi::OsString;
use std::fmt::Debug;
pub trait Env: Debug + Send {
fn env(&self) -> Option<Value>;
fn path(&self) -> Option<Value>;
fn add_env(&mut self, key: &str, value: &str);
fn add_path(&mut self, new_path: OsString);
}
impl Env for Box<dyn Env> {
fn env(&self) -> Option<Value> {
(**self).env()
}
fn path(&self) -> Option<Value> {
(**self).path()
}
fn add_env(&mut self, key: &str, value: &str) {
(**self).add_env(key, value);
}
fn add_path(&mut self, new_path: OsString) {
(**self).add_path(new_path);
}
}
#[derive(Debug, Default)]
pub struct Environment {
@ -128,7 +99,7 @@ impl Env for Environment {
{
let mut new_paths = current_paths.clone();
let new_path_candidates = split_paths(&paths).map(|path| {
let new_path_candidates = std::env::split_paths(&paths).map(|path| {
UntaggedValue::string(path.to_string_lossy()).into_value(tag.clone())
});
@ -154,7 +125,7 @@ impl Env for Environment {
#[cfg(test)]
mod tests {
use super::{Env, Environment};
use crate::data::config::{tests::FakeConfig, Conf};
use nu_data::config::{tests::FakeConfig, Conf};
use nu_protocol::UntaggedValue;
use nu_test_support::fs::Stub::FileWithContent;
use nu_test_support::playground::Playground;

View File

@ -1,14 +1,14 @@
use crate::context::Context;
use crate::data::config::{Conf, NuConfig};
use crate::env::environment::{Env, Environment};
use nu_source::Text;
use crate::env::environment::Environment;
use nu_data::config::{Conf, NuConfig};
use nu_engine::Env;
use nu_engine::EvaluationContext;
use nu_errors::ShellError;
use parking_lot::Mutex;
use std::sync::Arc;
use std::sync::{atomic::Ordering, Arc};
pub struct EnvironmentSyncer {
pub env: Arc<Mutex<Box<Environment>>>,
pub config: Arc<Box<dyn Conf>>,
pub config: Arc<Mutex<Box<dyn Conf>>>,
}
impl Default for EnvironmentSyncer {
@ -18,78 +18,93 @@ impl Default for EnvironmentSyncer {
}
impl EnvironmentSyncer {
pub fn with_config(config: Box<dyn Conf>) -> Self {
EnvironmentSyncer {
env: Arc::new(Mutex::new(Box::new(Environment::new()))),
config: Arc::new(Mutex::new(config)),
}
}
pub fn new() -> EnvironmentSyncer {
EnvironmentSyncer {
env: Arc::new(Mutex::new(Box::new(Environment::new()))),
config: Arc::new(Box::new(NuConfig::new())),
config: Arc::new(Mutex::new(Box::new(NuConfig::new()))),
}
}
#[cfg(test)]
pub fn set_config(&mut self, config: Box<dyn Conf>) {
self.config = Arc::new(config);
self.config = Arc::new(Mutex::new(config));
}
pub fn get_config(&self) -> Box<dyn Conf> {
let config = self.config.lock();
config.clone_box()
}
pub fn load_environment(&mut self) {
let config = self.config.clone();
let config = self.config.lock();
self.env = Arc::new(Mutex::new(Box::new(Environment::from_config(&*config))));
}
pub fn did_config_change(&mut self) -> bool {
let config = self.config.lock();
config.is_modified().unwrap_or(false)
}
pub fn reload(&mut self) {
self.config.reload();
let mut config = self.config.lock();
config.reload();
let mut environment = self.env.lock();
environment.morph(&*self.config);
environment.morph(&*config);
}
pub fn sync_env_vars(&mut self, ctx: &mut Context) {
pub fn autoenv(&self, ctx: &mut EvaluationContext) -> Result<(), ShellError> {
let mut environment = self.env.lock();
if let Err(e) = environment.autoenv(ctx.user_recently_used_autoenv_untrust) {
crate::cli::print_err(e, &Text::from(""));
}
ctx.user_recently_used_autoenv_untrust = false;
if environment.env().is_some() {
for (name, value) in ctx.with_host(|host| host.vars()) {
if name != "path" && name != "PATH" {
// account for new env vars present in the current session
// that aren't loaded from config.
environment.add_env(&name, &value);
// clear the env var from the session
// we are about to replace them
ctx.with_host(|host| host.env_rm(std::ffi::OsString::from(name)));
}
let recently_used = ctx
.user_recently_used_autoenv_untrust
.load(Ordering::SeqCst);
let auto = environment.autoenv(recently_used);
ctx.user_recently_used_autoenv_untrust
.store(false, Ordering::SeqCst);
auto
}
pub fn sync_env_vars(&mut self, ctx: &mut EvaluationContext) {
let nu_env_vars = ctx.scope.get_env_vars();
let environment = self.env.lock();
if let Some(variables) = environment.env() {
for var in variables.row_entries() {
if let Ok(string) = var.1.as_string() {
ctx.with_host(|host| {
host.env_set(
std::ffi::OsString::from(var.0),
std::ffi::OsString::from(string),
)
});
}
if var.0 != "path" && var.0 != "PATH" && !nu_env_vars.contains_key(var.0) {
ctx.scope.add_env_var(var.0, string);
}
}
}
}
pub fn sync_path_vars(&mut self, ctx: &mut Context) {
let nu_env_vars = ctx.scope.get_env_vars();
for (name, value) in ctx.with_host(|host| host.vars()) {
if name != "path" && name != "PATH" && !nu_env_vars.contains_key(&name) {
ctx.scope.add_env_var(name, value);
}
}
}
pub fn sync_path_vars(&mut self, ctx: &mut EvaluationContext) {
let mut environment = self.env.lock();
if environment.path().is_some() {
let native_paths = ctx.with_host(|host| host.env_get(std::ffi::OsString::from("PATH")));
if let Some(native_paths) = native_paths {
environment.add_path(native_paths);
ctx.with_host(|host| {
host.env_rm(std::ffi::OsString::from("PATH"));
});
for path in std::env::split_paths(&native_paths) {
environment.add_path(path.as_os_str().to_os_string());
}
}
if let Some(new_paths) = environment.path() {
@ -101,16 +116,14 @@ impl EnvironmentSyncer {
);
if let Ok(paths_ready) = prepared {
ctx.with_host(|host| {
host.env_set(std::ffi::OsString::from("PATH"), paths_ready);
});
}
ctx.scope
.add_env_var("PATH", paths_ready.to_string_lossy().to_string());
}
}
}
#[cfg(test)]
pub fn clear_env_vars(&mut self, ctx: &mut Context) {
pub fn clear_env_vars(&mut self, ctx: &mut EvaluationContext) {
for (key, _value) in ctx.with_host(|host| host.vars()) {
if key != "path" && key != "PATH" {
ctx.with_host(|host| host.env_rm(std::ffi::OsString::from(key)));
@ -119,7 +132,7 @@ impl EnvironmentSyncer {
}
#[cfg(test)]
pub fn clear_path_var(&mut self, ctx: &mut Context) {
pub fn clear_path_var(&mut self, ctx: &mut EvaluationContext) {
ctx.with_host(|host| host.env_rm(std::ffi::OsString::from("PATH")));
}
}
@ -127,10 +140,9 @@ impl EnvironmentSyncer {
#[cfg(test)]
mod tests {
use super::EnvironmentSyncer;
use crate::context::Context;
use crate::data::config::tests::FakeConfig;
use crate::env::environment::Env;
use indexmap::IndexMap;
use nu_data::config::tests::FakeConfig;
use nu_engine::basic_evaluation_context;
use nu_errors::ShellError;
use nu_test_support::fs::Stub::FileWithContent;
use nu_test_support::playground::Playground;
@ -138,11 +150,85 @@ mod tests {
use std::path::PathBuf;
use std::sync::Arc;
// This test fails on Linux.
// It's possible it has something to do with the fake configuration
// TODO: More tests.
#[cfg(not(target_os = "linux"))]
#[test]
fn syncs_env_if_new_env_entry_is_added_to_an_existing_configuration() -> Result<(), ShellError>
{
let mut ctx = basic_evaluation_context()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let mut expected = IndexMap::new();
expected.insert(
"SHELL".to_string(),
"/usr/bin/you_already_made_the_nu_choice".to_string(),
);
Playground::setup("syncs_env_from_config_updated_test_1", |dirs, sandbox| {
sandbox.with_files(vec![
FileWithContent(
"configuration.toml",
r#"
[env]
SHELL = "/usr/bin/you_already_made_the_nu_choice"
"#,
),
FileWithContent(
"updated_configuration.toml",
r#"
[env]
SHELL = "/usr/bin/you_already_made_the_nu_choice"
USER = "NUNO"
"#,
),
]);
let file = dirs.test().join("configuration.toml");
let new_file = dirs.test().join("updated_configuration.toml");
let fake_config = FakeConfig::new(&file);
let mut actual = EnvironmentSyncer::with_config(Box::new(fake_config));
// Here, the environment variables from the current session
// are cleared since we will load and set them from the
// configuration file
actual.clear_env_vars(&mut ctx);
// Nu loads the environment variables from the configuration file
actual.load_environment();
actual.sync_env_vars(&mut ctx);
assert!(!actual.did_config_change());
// Replacing the newer configuration file to the existing one.
let new_config_contents = std::fs::read_to_string(new_file).expect("Failed");
std::fs::write(&file, &new_config_contents).expect("Failed");
// A change has happened
assert!(actual.did_config_change());
// Syncer should reload and add new envs
actual.reload();
actual.sync_env_vars(&mut ctx);
let env_vars = ctx.scope.get_env_vars();
let result = env_vars.get("SHELL").unwrap();
assert_eq!(result, "/usr/bin/you_already_made_the_nu_choice");
let result = env_vars.get("USER").unwrap();
assert_eq!(result, "NUNO");
});
Ok(())
}
#[test]
fn syncs_env_if_new_env_entry_in_session_is_not_in_configuration_file() -> Result<(), ShellError>
{
let mut ctx = Context::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::FakeHost::new())));
let mut ctx = basic_evaluation_context()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let mut expected = IndexMap::new();
expected.insert(
@ -192,62 +278,20 @@ mod tests {
// Nu sees the missing "USER" variable and accounts for it.
actual.sync_env_vars(&mut ctx);
// Confirms session environment variables are replaced from Nu configuration file
// including the newer one accounted for.
ctx.with_host(|test_host| {
let var_user = test_host
.env_get(std::ffi::OsString::from("USER"))
.expect("Couldn't get USER var from host.")
.into_string()
.expect("Couldn't convert to string.");
let env_vars = ctx.scope.get_env_vars();
let result = env_vars.get("SHELL").unwrap();
assert_eq!(result, "/usr/bin/you_already_made_the_nu_choice");
let var_shell = test_host
.env_get(std::ffi::OsString::from("SHELL"))
.expect("Couldn't get SHELL var from host.")
.into_string()
.expect("Couldn't convert to string.");
let mut found = IndexMap::new();
found.insert("SHELL".to_string(), var_shell);
found.insert("USER".to_string(), var_user);
for k in found.keys() {
assert!(expected.contains_key(k));
}
});
// Now confirm in-memory environment variables synced appropriately
// including the newer one accounted for.
let environment = actual.env.lock();
let mut vars = IndexMap::new();
environment
.env()
.expect("No variables in the environment.")
.row_entries()
.for_each(|(name, value)| {
vars.insert(
name.to_string(),
value.as_string().expect("Couldn't convert to string"),
);
});
for k in expected.keys() {
assert!(vars.contains_key(k));
}
let result = env_vars.get("USER").unwrap();
assert_eq!(result, "NUNO");
});
Ok(())
}
#[test]
fn nu_envs_have_higher_priority_and_does_not_get_overwritten() -> Result<(), ShellError> {
let mut ctx = Context::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::FakeHost::new())));
let mut expected = IndexMap::new();
expected.insert(
"SHELL".to_string(),
"/usr/bin/you_already_made_the_nu_choice".to_string(),
);
let mut ctx = basic_evaluation_context()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
Playground::setup("syncs_env_test_2", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
@ -277,37 +321,10 @@ mod tests {
actual.load_environment();
actual.sync_env_vars(&mut ctx);
ctx.with_host(|test_host| {
let var_shell = test_host
.env_get(std::ffi::OsString::from("SHELL"))
.expect("Couldn't get SHELL var from host.")
.into_string()
.expect("Couldn't convert to string.");
let env_vars = ctx.scope.get_env_vars();
let result = env_vars.get("SHELL").unwrap();
let mut found = IndexMap::new();
found.insert("SHELL".to_string(), var_shell);
for k in found.keys() {
assert!(expected.contains_key(k));
}
});
let environment = actual.env.lock();
let mut vars = IndexMap::new();
environment
.env()
.expect("No variables in the environment.")
.row_entries()
.for_each(|(name, value)| {
vars.insert(
name.to_string(),
value.as_string().expect("couldn't convert to string"),
);
});
for k in expected.keys() {
assert!(vars.contains_key(k));
}
assert_eq!(result, "/usr/bin/you_already_made_the_nu_choice");
});
Ok(())
@ -316,8 +333,8 @@ mod tests {
#[test]
fn syncs_path_if_new_path_entry_in_session_is_not_in_configuration_file(
) -> Result<(), ShellError> {
let mut ctx = Context::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::FakeHost::new())));
let mut ctx = basic_evaluation_context()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let expected = std::env::join_paths(vec![
PathBuf::from("/Users/andresrobalino/.volta/bin"),
@ -369,32 +386,10 @@ mod tests {
// Nu sees the missing "/path/to/be/added" and accounts for it.
actual.sync_path_vars(&mut ctx);
ctx.with_host(|test_host| {
let actual = test_host
.env_get(std::ffi::OsString::from("PATH"))
.expect("Couldn't get PATH var from host.")
.into_string()
.expect("Couldn't convert to string.");
let env_vars = ctx.scope.get_env_vars();
let paths = env_vars.get("PATH").unwrap();
assert_eq!(actual, expected);
});
let environment = actual.env.lock();
let paths = std::env::join_paths(
&environment
.path()
.expect("No path variable in the environment.")
.table_entries()
.map(|value| value.as_string().expect("Couldn't convert to string"))
.map(PathBuf::from)
.collect::<Vec<_>>(),
)
.expect("Couldn't join paths.")
.into_string()
.expect("Couldn't convert to string.");
assert_eq!(paths, expected);
assert_eq!(paths, &expected);
});
Ok(())
@ -403,8 +398,8 @@ mod tests {
#[test]
fn nu_paths_have_higher_priority_and_new_paths_get_appended_to_the_end(
) -> Result<(), ShellError> {
let mut ctx = Context::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::FakeHost::new())));
let mut ctx = basic_evaluation_context()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let expected = std::env::join_paths(vec![
PathBuf::from("/Users/andresrobalino/.volta/bin"),
@ -443,32 +438,10 @@ mod tests {
actual.load_environment();
actual.sync_path_vars(&mut ctx);
ctx.with_host(|test_host| {
let actual = test_host
.env_get(std::ffi::OsString::from("PATH"))
.expect("Couldn't get PATH var from host.")
.into_string()
.expect("Couldn't convert to string.");
let env_vars = ctx.scope.get_env_vars();
let paths = env_vars.get("PATH").unwrap();
assert_eq!(actual, expected);
});
let environment = actual.env.lock();
let paths = std::env::join_paths(
&environment
.path()
.expect("No path variable in the environment.")
.table_entries()
.map(|value| value.as_string().expect("Couldn't convert to string"))
.map(PathBuf::from)
.collect::<Vec<_>>(),
)
.expect("Couldn't join paths.")
.into_string()
.expect("Couldn't convert to string.");
assert_eq!(paths, expected);
assert_eq!(paths, &expected);
});
Ok(())

View File

@ -1,215 +0,0 @@
use crate::commands::classified::block::run_block;
use crate::context::CommandRegistry;
use crate::evaluate::operator::apply_operator;
use crate::prelude::*;
use async_recursion::async_recursion;
use log::trace;
use nu_errors::{ArgumentError, ShellError};
use nu_protocol::hir::{self, Expression, ExternalRedirection, SpannedExpression};
use nu_protocol::{
ColumnPath, Primitive, RangeInclusion, UnspannedPathMember, UntaggedValue, Value,
};
#[async_recursion]
pub(crate) async fn evaluate_baseline_expr(
expr: &SpannedExpression,
registry: &CommandRegistry,
it: &Value,
vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>,
) -> Result<Value, ShellError> {
let tag = Tag {
span: expr.span,
anchor: None,
};
let span = expr.span;
match &expr.expr {
Expression::Literal(literal) => Ok(evaluate_literal(&literal, span)),
Expression::ExternalWord => Err(ShellError::argument_error(
"Invalid external word".spanned(tag.span),
ArgumentError::InvalidExternalWord,
)),
Expression::FilePath(path) => Ok(UntaggedValue::path(path.clone()).into_value(tag)),
Expression::Synthetic(hir::Synthetic::String(s)) => {
Ok(UntaggedValue::string(s).into_untagged_value())
}
Expression::Variable(var) => evaluate_reference(&var, it, vars, env, tag),
Expression::Command(_) => unimplemented!(),
Expression::Invocation(block) => evaluate_invocation(block, registry, it, vars, env).await,
Expression::ExternalCommand(_) => unimplemented!(),
Expression::Binary(binary) => {
// TODO: If we want to add short-circuiting, we'll need to move these down
let left = evaluate_baseline_expr(&binary.left, registry, it, vars, env).await?;
let right = evaluate_baseline_expr(&binary.right, registry, it, vars, env).await?;
trace!("left={:?} right={:?}", left.value, right.value);
match binary.op.expr {
Expression::Literal(hir::Literal::Operator(op)) => {
match apply_operator(op, &left, &right) {
Ok(result) => match result {
UntaggedValue::Error(shell_err) => Err(shell_err),
_ => Ok(result.into_value(tag)),
},
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(binary.left.span),
right_type.spanned(binary.right.span),
)),
}
}
_ => unreachable!(),
}
}
Expression::Range(range) => {
let left = &range.left;
let right = &range.right;
let left = evaluate_baseline_expr(&left, registry, it, vars, env).await?;
let right = evaluate_baseline_expr(&right, registry, it, vars, env).await?;
let left_span = left.tag.span;
let right_span = right.tag.span;
let left = (
left.as_primitive()?.spanned(left_span),
RangeInclusion::Inclusive,
);
let right = (
right.as_primitive()?.spanned(right_span),
RangeInclusion::Inclusive,
);
Ok(UntaggedValue::range(left, right).into_value(tag))
}
Expression::List(list) => {
let mut exprs = vec![];
for expr in list {
let expr = evaluate_baseline_expr(&expr, registry, it, vars, env).await?;
exprs.push(expr);
}
Ok(UntaggedValue::Table(exprs).into_value(tag))
}
Expression::Block(block) => Ok(UntaggedValue::Block(block.clone()).into_value(&tag)),
Expression::Path(path) => {
let value = evaluate_baseline_expr(&path.head, registry, it, vars, env).await?;
let mut item = value;
for member in &path.tail {
let next = item.get_data_by_member(member);
match next {
Err(err) => {
let possibilities = item.data_descriptors();
if let UnspannedPathMember::String(name) = &member.unspanned {
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|x| (natural::distance::levenshtein_distance(x, &name), x))
.collect();
possible_matches.sort();
if !possible_matches.is_empty() {
return Err(ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", possible_matches[0].1),
&member.span,
));
} else {
return Err(err);
}
}
}
Ok(next) => {
item = next.clone().value.into_value(&tag);
}
};
}
Ok(item.value.into_value(tag))
}
Expression::Boolean(_boolean) => unimplemented!(),
Expression::Garbage => unimplemented!(),
}
}
fn evaluate_literal(literal: &hir::Literal, span: Span) -> Value {
match &literal {
hir::Literal::ColumnPath(path) => {
let members = path.iter().map(|member| member.to_path_member()).collect();
UntaggedValue::Primitive(Primitive::ColumnPath(ColumnPath::new(members)))
.into_value(span)
}
hir::Literal::Number(int) => match int {
nu_protocol::hir::Number::Int(i) => UntaggedValue::int(i.clone()).into_value(span),
nu_protocol::hir::Number::Decimal(d) => {
UntaggedValue::decimal(d.clone()).into_value(span)
}
},
hir::Literal::Size(int, unit) => unit.compute(&int).into_value(span),
hir::Literal::String(string) => UntaggedValue::string(string).into_value(span),
hir::Literal::GlobPattern(pattern) => UntaggedValue::pattern(pattern).into_value(span),
hir::Literal::Bare(bare) => UntaggedValue::string(bare.clone()).into_value(span),
hir::Literal::Operator(_) => unimplemented!("Not sure what to do with operator yet"),
}
}
fn evaluate_reference(
name: &hir::Variable,
it: &Value,
vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>,
tag: Tag,
) -> Result<Value, ShellError> {
match name {
hir::Variable::It(_) => Ok(it.clone()),
hir::Variable::Other(name, _) => match name {
x if x == "$nu" => crate::evaluate::variables::nu(env, tag),
x if x == "$true" => Ok(Value {
value: UntaggedValue::boolean(true),
tag,
}),
x if x == "$false" => Ok(Value {
value: UntaggedValue::boolean(false),
tag,
}),
x => Ok(vars
.get(x)
.cloned()
.unwrap_or_else(|| UntaggedValue::nothing().into_value(tag))),
},
}
}
async fn evaluate_invocation(
block: &hir::Block,
registry: &CommandRegistry,
it: &Value,
vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>,
) -> Result<Value, ShellError> {
// FIXME: we should use a real context here
let mut context = Context::basic()?;
context.registry = registry.clone();
let input = InputStream::one(it.clone());
let mut block = block.clone();
block.set_redirect(ExternalRedirection::Stdout);
let result = run_block(&block, &mut context, input, it, vars, env).await?;
let output = result.into_vec().await;
if let Some(e) = context.get_errors().get(0) {
return Err(e.clone());
}
match output.len() {
x if x > 1 => Ok(UntaggedValue::Table(output).into_value(Tag::unknown())),
1 => Ok(output[0].clone()),
_ => Ok(UntaggedValue::nothing().into_value(Tag::unknown())),
}
}

View File

@ -1,58 +0,0 @@
use crate::cli::History;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
use nu_source::Tag;
pub fn nu(env: &IndexMap<String, String>, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let tag = tag.into();
let mut nu_dict = TaggedDictBuilder::new(&tag);
let mut dict = TaggedDictBuilder::new(&tag);
for v in env.iter() {
if v.0 != "PATH" && v.0 != "Path" {
dict.insert_untagged(v.0, UntaggedValue::string(v.1));
}
}
nu_dict.insert_value("env", dict.into_value());
let config = crate::data::config::read(&tag, &None)?;
nu_dict.insert_value("config", UntaggedValue::row(config).into_value(&tag));
let mut table = vec![];
let path = std::env::var_os("PATH");
if let Some(paths) = path {
for path in std::env::split_paths(&paths) {
table.push(UntaggedValue::path(path).into_value(&tag));
}
}
nu_dict.insert_value("path", UntaggedValue::table(&table).into_value(&tag));
let path = std::env::current_dir()?;
nu_dict.insert_value("cwd", UntaggedValue::path(path).into_value(&tag));
if let Some(home) = crate::shell::filesystem_shell::homedir_if_possible() {
nu_dict.insert_value("home-dir", UntaggedValue::path(home).into_value(&tag));
}
let temp = std::env::temp_dir();
nu_dict.insert_value("temp-dir", UntaggedValue::path(temp).into_value(&tag));
let config = crate::data::config::default_path()?;
nu_dict.insert_value("config-path", UntaggedValue::path(config).into_value(&tag));
let keybinding_path = crate::keybinding::keybinding_path()?;
nu_dict.insert_value(
"keybinding-path",
UntaggedValue::path(keybinding_path).into_value(&tag),
);
let history = History::path();
nu_dict.insert_value(
"history-path",
UntaggedValue::path(history).into_value(&tag),
);
Ok(nu_dict.into_value())
}

View File

@ -1,117 +0,0 @@
use futures::executor::block_on;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::hir::ClassifiedBlock;
use nu_protocol::{ShellTypeName, Value};
use crate::commands::classified::block::run_block;
use crate::commands::{whole_stream_command, BuildString, Echo, StrCollect};
use crate::context::Context;
use crate::stream::InputStream;
use crate::WholeStreamCommand;
pub fn test(cmd: impl WholeStreamCommand + 'static) {
let examples = cmd.examples();
let mut base_context = Context::basic().expect("could not create basic context");
base_context.add_commands(vec![
whole_stream_command(Echo {}),
whole_stream_command(BuildString {}),
whole_stream_command(cmd),
whole_stream_command(StrCollect),
]);
for example in examples {
let mut ctx = base_context.clone();
let block = parse_line(example.example, &mut ctx).expect("failed to parse example");
if let Some(expected) = example.result {
let result = block_on(evaluate_block(block, &mut ctx)).expect("failed to run example");
let errors = ctx.get_errors();
assert!(
errors.is_empty(),
"errors while running command.\ncommand: {}\nerrors: {:?}",
example.example,
errors
);
assert!(expected.len() == result.len(), "example command produced unexpected number of results.\ncommand: {}\nexpected number: {}\nactual: {}",
example.example,
expected.len(),
result.len(),);
assert!(
expected
.iter()
.zip(result.iter())
.all(|(e, a)| values_equal(e, a)),
"example command produced unexpected result.\ncommand: {}\nexpected: {:?}\nactual: {:?}",
example.example,
expected,
result,
);
}
}
}
/// Parse and run a nushell pipeline
fn parse_line(line: &'static str, ctx: &mut Context) -> Result<ClassifiedBlock, ShellError> {
let line = if line.ends_with('\n') {
&line[..line.len() - 1]
} else {
line
};
let lite_result = nu_parser::lite_parse(&line, 0)?;
// TODO ensure the command whose examples we're testing is actually in the pipeline
let mut classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
classified_block.block.expand_it_usage();
Ok(classified_block)
}
async fn evaluate_block(
block: ClassifiedBlock,
ctx: &mut Context,
) -> Result<Vec<Value>, ShellError> {
let input_stream = InputStream::empty();
let env = ctx.get_env();
Ok(run_block(
&block.block,
ctx,
input_stream,
&Value::nothing(),
&IndexMap::new(),
&env,
)
.await?
.into_vec()
.await)
}
// TODO probably something already available to do this
// TODO perhaps better panic messages when things don't compare
// Deep value comparisons that ignore tags
fn values_equal(expected: &Value, actual: &Value) -> bool {
use nu_protocol::UntaggedValue::*;
match (&expected.value, &actual.value) {
(Primitive(e), Primitive(a)) => e == a,
(Row(e), Row(a)) => {
if e.entries.len() != a.entries.len() {
return false;
}
e.entries
.iter()
.zip(a.entries.iter())
.all(|((ek, ev), (ak, av))| ek == ak && values_equal(ev, av))
}
(Table(e), Table(a)) => e.iter().zip(a.iter()).all(|(e, a)| values_equal(e, a)),
(e, a) => unimplemented!("{} {}", e.type_name(), a.type_name()),
}
}

View File

@ -1,41 +0,0 @@
use crate::prelude::*;
pub fn current_branch() -> Option<String> {
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
let use_starship = config
.get("use_starship")
.map(|x| x.is_true())
.unwrap_or(false);
if !use_starship {
#[cfg(feature = "git2")]
{
use git2::{Repository, RepositoryOpenFlags};
use std::ffi::OsString;
let v: Vec<OsString> = vec![];
match Repository::open_ext(".", RepositoryOpenFlags::empty(), v) {
Ok(repo) => {
let r = repo.head();
match r {
Ok(r) => match r.shorthand() {
Some(s) => Some(s.to_string()),
None => None,
},
_ => None,
}
}
_ => None,
}
}
#[cfg(not(feature = "git2"))]
{
None
}
} else {
None
}
} else {
None
}
}

View File

@ -405,14 +405,10 @@ pub struct Keybinding {
type Keybindings = Vec<Keybinding>;
pub(crate) fn keybinding_path() -> Result<std::path::PathBuf, nu_errors::ShellError> {
crate::data::config::default_path_for(&Some(std::path::PathBuf::from("keybindings.yml")))
}
pub(crate) fn load_keybindings(
rl: &mut rustyline::Editor<crate::shell::Helper>,
) -> Result<(), nu_errors::ShellError> {
let filename = keybinding_path()?;
let filename = nu_data::keybinding::keybinding_path()?;
let contents = std::fs::read_to_string(filename);
// Silently fail if there is no file there

View File

@ -14,43 +14,28 @@ extern crate quickcheck;
extern crate quickcheck_macros;
mod cli;
mod commands;
#[cfg(feature = "rustyline-support")]
mod completion;
mod context;
pub mod data;
mod deserializer;
mod documentation;
mod env;
mod evaluate;
mod format;
mod futures;
mod git;
#[cfg(feature = "rustyline-support")]
mod keybinding;
mod path;
mod line_editor;
mod shell;
mod stream;
pub mod utils;
pub mod types;
#[cfg(test)]
mod examples;
#[cfg(feature = "rustyline-support")]
pub use crate::cli::cli;
pub use crate::cli::{parse_and_eval, register_plugins, run_script_file};
pub use crate::cli::{
cli, create_default_context, load_plugins, parse_and_eval, process_line,
run_pipeline_standalone, run_vec_of_pipelines, LineResult,
};
pub use crate::commands::command::{
whole_stream_command, CommandArgs, EvaluatedWholeStreamCommandArgs, Example, WholeStreamCommand,
};
pub use crate::commands::help::get_help;
pub use crate::context::{CommandRegistry, Context};
pub use crate::data::config;
pub use crate::data::dict::TaggedListBuilder;
pub use crate::data::primitive;
pub use crate::data::value;
pub use crate::env::environment_syncer::EnvironmentSyncer;
pub use crate::env::host::BasicHost;
pub use crate::prelude::ToOutputStream;
pub use crate::stream::{InputStream, InterruptibleStream, OutputStream};
pub use nu_command::commands::default_context::create_default_context;
pub use nu_data::config;
pub use nu_data::dict::TaggedListBuilder;
pub use nu_data::primitive;
pub use nu_data::value;
pub use nu_stream::{InputStream, InterruptibleStream, OutputStream};
pub use nu_value_ext::ValueExt;
pub use num_traits::cast::ToPrimitive;

View File

@ -0,0 +1,234 @@
use nu_engine::EvaluationContext;
use std::error::Error;
#[allow(unused_imports)]
use crate::prelude::*;
#[allow(unused_imports)]
use nu_command::script::LineResult;
#[cfg(feature = "rustyline-support")]
use crate::shell::Helper;
#[cfg(feature = "rustyline-support")]
use rustyline::{
self,
config::Configurer,
config::{ColorMode, CompletionType, Config},
error::ReadlineError,
At, Cmd, Editor, KeyPress, Movement, Word,
};
#[cfg(feature = "rustyline-support")]
pub fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>) -> LineResult {
match input {
Ok(s) if s == "history -c" || s == "history --clear" => LineResult::ClearHistory,
Ok(s) => LineResult::Success(s),
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
Err(ReadlineError::Eof) => LineResult::CtrlD,
Err(err) => {
outln!("Error: {:?}", err);
LineResult::Break
}
}
}
#[cfg(feature = "rustyline-support")]
pub fn default_rustyline_editor_configuration() -> Editor<Helper> {
#[cfg(windows)]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
#[cfg(not(windows))]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
let config = Config::builder().color_mode(ColorMode::Forced).build();
let mut rl: Editor<_> = Editor::with_config(config);
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
rl.bind_sequence(
KeyPress::ControlLeft,
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
);
rl.bind_sequence(
KeyPress::ControlRight,
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
);
// workaround for multiline-paste hang in rustyline (see https://github.com/kkawakam/rustyline/issues/202)
rl.bind_sequence(KeyPress::BracketedPasteStart, rustyline::Cmd::Noop);
// Let's set the defaults up front and then override them later if the user indicates
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
rl.set_max_history_size(100);
rl.set_history_ignore_dups(true);
rl.set_history_ignore_space(false);
rl.set_completion_type(DEFAULT_COMPLETION_MODE);
rl.set_completion_prompt_limit(100);
rl.set_keyseq_timeout(-1);
rl.set_edit_mode(rustyline::config::EditMode::Emacs);
rl.set_auto_add_history(false);
rl.set_bell_style(rustyline::config::BellStyle::default());
rl.set_color_mode(rustyline::ColorMode::Enabled);
rl.set_tab_stop(8);
if let Err(e) = crate::keybinding::load_keybindings(&mut rl) {
println!("Error loading keybindings: {:?}", e);
}
rl
}
#[cfg(feature = "rustyline-support")]
pub fn configure_rustyline_editor(
rl: &mut Editor<Helper>,
config: &dyn nu_data::config::Conf,
) -> Result<(), ShellError> {
#[cfg(windows)]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
#[cfg(not(windows))]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
if let Some(line_editor_vars) = config.var("line_editor") {
for (idx, value) in line_editor_vars.row_entries() {
match idx.as_ref() {
"max_history_size" => {
if let Ok(max_history_size) = value.as_u64() {
rl.set_max_history_size(max_history_size as usize);
}
}
"history_duplicates" => {
// history_duplicates = match value.as_string() {
// Ok(s) if s.to_lowercase() == "alwaysadd" => {
// rustyline::config::HistoryDuplicates::AlwaysAdd
// }
// Ok(s) if s.to_lowercase() == "ignoreconsecutive" => {
// rustyline::config::HistoryDuplicates::IgnoreConsecutive
// }
// _ => rustyline::config::HistoryDuplicates::AlwaysAdd,
// };
if let Ok(history_duplicates) = value.as_bool() {
rl.set_history_ignore_dups(history_duplicates);
}
}
"history_ignore_space" => {
if let Ok(history_ignore_space) = value.as_bool() {
rl.set_history_ignore_space(history_ignore_space);
}
}
"completion_type" => {
let completion_type = match value.as_string() {
Ok(s) if s.to_lowercase() == "circular" => {
rustyline::config::CompletionType::Circular
}
Ok(s) if s.to_lowercase() == "list" => {
rustyline::config::CompletionType::List
}
#[cfg(all(unix, feature = "with-fuzzy"))]
Ok(s) if s.to_lowercase() == "fuzzy" => {
rustyline::config::CompletionType::Fuzzy
}
_ => DEFAULT_COMPLETION_MODE,
};
rl.set_completion_type(completion_type);
}
"completion_prompt_limit" => {
if let Ok(completion_prompt_limit) = value.as_u64() {
rl.set_completion_prompt_limit(completion_prompt_limit as usize);
}
}
"keyseq_timeout_ms" => {
if let Ok(keyseq_timeout_ms) = value.as_u64() {
rl.set_keyseq_timeout(keyseq_timeout_ms as i32);
}
}
"edit_mode" => {
let edit_mode = match value.as_string() {
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
Ok(s) if s.to_lowercase() == "emacs" => rustyline::config::EditMode::Emacs,
_ => rustyline::config::EditMode::Emacs,
};
rl.set_edit_mode(edit_mode);
// Note: When edit_mode is Emacs, the keyseq_timeout_ms is set to -1
// no matter what you may have configured. This is so that key chords
// can be applied without having to do them in a given timeout. So,
// it essentially turns off the keyseq timeout.
}
"auto_add_history" => {
if let Ok(auto_add_history) = value.as_bool() {
rl.set_auto_add_history(auto_add_history);
}
}
"bell_style" => {
let bell_style = match value.as_string() {
Ok(s) if s.to_lowercase() == "audible" => {
rustyline::config::BellStyle::Audible
}
Ok(s) if s.to_lowercase() == "none" => rustyline::config::BellStyle::None,
Ok(s) if s.to_lowercase() == "visible" => {
rustyline::config::BellStyle::Visible
}
_ => rustyline::config::BellStyle::default(),
};
rl.set_bell_style(bell_style);
}
"color_mode" => {
let color_mode = match value.as_string() {
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
Ok(s) if s.to_lowercase() == "forced" => rustyline::ColorMode::Forced,
Ok(s) if s.to_lowercase() == "disabled" => rustyline::ColorMode::Disabled,
_ => rustyline::ColorMode::Enabled,
};
rl.set_color_mode(color_mode);
}
"tab_stop" => {
if let Ok(tab_stop) = value.as_u64() {
rl.set_tab_stop(tab_stop as usize);
}
}
_ => (),
}
}
}
Ok(())
}
#[cfg(feature = "rustyline-support")]
pub fn nu_line_editor_helper(
context: &mut EvaluationContext,
config: &dyn nu_data::config::Conf,
) -> crate::shell::Helper {
let hinter = rustyline_hinter(config);
crate::shell::Helper::new(context.clone(), hinter)
}
#[cfg(feature = "rustyline-support")]
pub fn rustyline_hinter(
config: &dyn nu_data::config::Conf,
) -> Option<rustyline::hint::HistoryHinter> {
if let Some(line_editor_vars) = config.var("line_editor") {
for (idx, value) in line_editor_vars.row_entries() {
if idx == "show_hints" && value.expect_string() == "false" {
return None;
}
}
}
Some(rustyline::hint::HistoryHinter {})
}
pub fn configure_ctrl_c(_context: &mut EvaluationContext) -> Result<(), Box<dyn Error>> {
#[cfg(feature = "ctrlc")]
{
let cc = _context.ctrl_c.clone();
ctrlc::set_handler(move || {
cc.store(true, Ordering::SeqCst);
})?;
if _context.ctrl_c.load(Ordering::SeqCst) {
_context.ctrl_c.store(false, Ordering::SeqCst);
}
}
Ok(())
}

View File

@ -21,28 +21,6 @@ macro_rules! stream {
}}
}
#[macro_export]
macro_rules! trace_stream {
(target: $target:tt, $desc:tt = $expr:expr) => {{
if log::log_enabled!(target: $target, log::Level::Trace) {
use futures::stream::StreamExt;
let objects = $expr.inspect(move |o| {
trace!(
target: $target,
"{} = {}",
$desc,
nu_source::PrettyDebug::plain_string(o, 70)
);
});
$crate::stream::InputStream::from_stream(objects.boxed())
} else {
$expr
}
}};
}
#[macro_export]
macro_rules! trace_out_stream {
(target: $target:tt, $desc:tt = $expr:expr) => {{
@ -61,50 +39,26 @@ macro_rules! trace_out_stream {
);
});
$crate::stream::OutputStream::new(objects)
nu_stream::OutputStream::new(objects)
} else {
$expr
}
}};
}
pub(crate) use nu_protocol::{errln, out, outln};
use nu_source::HasFallibleSpan;
pub(crate) use crate::commands::command::{CommandArgs, RawCommandArgs, RunnableContext};
pub(crate) use crate::commands::Example;
pub(crate) use crate::context::CommandRegistry;
pub(crate) use crate::context::Context;
pub(crate) use crate::data::config;
pub(crate) use crate::data::value;
// pub(crate) use crate::env::host::handle_unexpected;
pub(crate) use crate::env::Host;
pub(crate) use crate::shell::filesystem_shell::FilesystemShell;
pub(crate) use crate::shell::help_shell::HelpShell;
pub(crate) use crate::shell::shell_manager::ShellManager;
pub(crate) use crate::shell::value_shell::ValueShell;
pub(crate) use crate::stream::{InputStream, InterruptibleStream, OutputStream};
pub(crate) use bigdecimal::BigDecimal;
pub(crate) use futures::stream::BoxStream;
pub(crate) use futures::{Stream, StreamExt};
pub(crate) use nu_protocol::MaybeOwned;
pub(crate) use nu_source::{
b, AnchorLocation, DebugDocBuilder, PrettyDebug, PrettyDebugWithSource, Span, SpannedItem, Tag,
TaggedItem, Text,
};
pub(crate) use nu_engine::Host;
#[allow(unused_imports)]
pub(crate) use nu_errors::ShellError;
#[allow(unused_imports)]
pub(crate) use nu_protocol::outln;
pub(crate) use nu_stream::OutputStream;
#[allow(unused_imports)]
pub(crate) use nu_value_ext::ValueExt;
pub(crate) use num_bigint::BigInt;
pub(crate) use num_traits::cast::ToPrimitive;
pub(crate) use serde::Deserialize;
pub(crate) use std::collections::VecDeque;
pub(crate) use std::future::Future;
pub(crate) use std::sync::atomic::AtomicBool;
pub(crate) use std::sync::Arc;
pub(crate) use async_trait::async_trait;
pub(crate) use indexmap::IndexMap;
pub(crate) use itertools::Itertools;
#[allow(unused_imports)]
pub(crate) use std::sync::atomic::Ordering;
#[allow(clippy::clippy::wrong_self_convention)]
pub trait FromInputStream {
fn from_input_stream(self) -> OutputStream;
}
@ -120,26 +74,7 @@ where
}
}
pub trait ToInputStream {
fn to_input_stream(self) -> InputStream;
}
impl<T, U> ToInputStream for T
where
T: Stream<Item = U> + Send + 'static,
U: Into<Result<nu_protocol::Value, nu_errors::ShellError>>,
{
fn to_input_stream(self) -> InputStream {
InputStream::from_stream(self.map(|item| match item.into() {
Ok(result) => result,
Err(err) => match HasFallibleSpan::maybe_span(&err) {
Some(span) => nu_protocol::UntaggedValue::Error(err).into_value(span),
None => nu_protocol::UntaggedValue::Error(err).into_untagged_value(),
},
}))
}
}
#[allow(clippy::clippy::wrong_self_convention)]
pub trait ToOutputStream {
fn to_output_stream(self) -> OutputStream;
}
@ -155,16 +90,3 @@ where
}
}
}
pub trait Interruptible<V> {
fn interruptible(self, ctrl_c: Arc<AtomicBool>) -> InterruptibleStream<V>;
}
impl<S, V> Interruptible<V> for S
where
S: Stream<Item = V> + Send + 'static,
{
fn interruptible(self, ctrl_c: Arc<AtomicBool>) -> InterruptibleStream<V> {
InterruptibleStream::new(self, ctrl_c)
}
}

View File

@ -1,12 +1,9 @@
#![allow(clippy::module_inception)]
#[cfg(feature = "rustyline-support")]
pub(crate) mod completer;
pub(crate) mod filesystem_shell;
pub(crate) mod help_shell;
#[cfg(feature = "rustyline-support")]
pub(crate) mod helper;
pub(crate) mod palette;
pub(crate) mod shell;
pub(crate) mod shell_manager;
pub(crate) mod value_shell;
#[cfg(feature = "rustyline-support")]
pub(crate) use helper::Helper;

View File

@ -1,364 +1,157 @@
use std::fs::{read_dir, DirEntry};
use crate::completion::command::CommandCompleter;
use crate::completion::flag::FlagCompleter;
use crate::completion::matchers;
use crate::completion::matchers::Matcher;
use crate::completion::path::{PathCompleter, PathSuggestion};
use crate::completion::{self, Completer, Suggestion};
use nu_engine::EvaluationContext;
use nu_parser::ParserScope;
use nu_source::Tag;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::borrow::Cow;
use indexmap::set::IndexSet;
use nu_errors::ShellError;
use rustyline::completion::{Completer as _, FilenameCompleter};
use rustyline::hint::{Hinter as _, HistoryHinter};
pub(crate) struct NuCompleter {}
#[cfg(all(windows, feature = "ichwh"))]
use ichwh::{IchwhError, IchwhResult};
use crate::completion::{self, Completer};
use crate::context;
use crate::data::config;
use crate::prelude::*;
pub(crate) struct NuCompleter {
file_completer: FilenameCompleter,
hinter: HistoryHinter,
}
#[derive(PartialEq, Eq, Debug)]
enum ReplacementLocation {
Command,
Other,
}
impl NuCompleter {}
impl NuCompleter {
fn complete_internal(
pub fn complete(
&self,
line: &str,
pos: usize,
context: &completion::Context,
) -> rustyline::Result<(usize, Vec<rustyline::completion::Pair>)> {
let line_chars: Vec<_> = line[..pos].chars().collect();
context: &completion::CompletionContext,
) -> (usize, Vec<Suggestion>) {
use completion::engine::LocationType;
let (replace_pos, replace_loc) = get_replace_pos(line, pos);
let nu_context: &EvaluationContext = context.as_ref();
// See if we're a flag
let mut completions;
if pos > 0 && replace_pos < line_chars.len() && line_chars[replace_pos] == '-' {
if let Ok(lite_block) = nu_parser::lite_parse(line, 0) {
completions = get_matching_arguments(
context.as_ref(),
&lite_block,
&line_chars,
line,
replace_pos,
pos,
);
} else {
completions = self.file_completer.complete(line, pos, context.as_ref())?.1;
}
} else {
completions = self.file_completer.complete(line, pos, context.as_ref())?.1;
}
nu_context.scope.enter_scope();
let (block, _) = nu_parser::parse(line, 0, &nu_context.scope);
nu_context.scope.exit_scope();
// Only complete executables or commands if the thing we're completing
// is syntactically a command
if replace_loc == ReplacementLocation::Command {
let context: &context::Context = context.as_ref();
let commands: Vec<String> = context.registry.names();
let mut all_executables: IndexSet<_> = commands.iter().map(|x| x.to_string()).collect();
let locations = completion::engine::completion_location(line, &block, pos);
let complete_from_path = config::config(Tag::unknown())
.map(|conf| {
conf.get("complete_from_path")
.map(|v| v.is_true())
.unwrap_or(true)
let matcher = nu_data::config::config(Tag::unknown())
.ok()
.and_then(|cfg| cfg.get("line_editor").cloned())
.and_then(|le| {
le.row_entries()
.find(|(idx, _value)| idx.as_str() == "completion_match_method")
.and_then(|(_idx, value)| value.as_string().ok())
})
.unwrap_or(true);
.unwrap_or_else(String::new);
if complete_from_path {
let path_executables = find_path_executables().unwrap_or_default();
for path_exe in path_executables {
all_executables.insert(path_exe);
}
let matcher = matcher.as_str();
let matcher: &dyn Matcher = match matcher {
"case-insensitive" => &matchers::case_insensitive::Matcher,
_ => &matchers::case_sensitive::Matcher,
};
for exe in all_executables.iter() {
let mut pos = replace_pos;
let mut matched = false;
if pos < line_chars.len() {
for chr in exe.chars() {
if line_chars[pos] != chr {
break;
}
pos += 1;
if pos == line_chars.len() {
matched = true;
break;
}
}
}
if matched {
completions.push(rustyline::completion::Pair {
display: exe.to_string(),
replacement: exe.to_string(),
});
}
}
}
// Adjust replacement to deal with a quote already at the cursor. Specifically, if there's
// already a quote at the cursor, but the replacement doesn't have one, we need to ensure
// one exists (to be safe, even if the completion doesn't need it).
for completion in &mut completions {
let cursor_char = line.chars().nth(replace_pos);
if cursor_char.unwrap_or(' ') == '"' && !completion.replacement.starts_with('"') {
completion.replacement.insert(0, '"');
}
}
Ok((replace_pos, completions))
}
}
impl Completer for NuCompleter {
fn complete(
&self,
line: &str,
pos: usize,
context: &completion::Context,
) -> Result<(usize, Vec<completion::Suggestion>), ShellError> {
let expanded = nu_parser::expand_ndots(&line);
// Find the first not-matching char position, if there is one
let differ_pos = line
.chars()
.zip(expanded.chars())
.enumerate()
.find(|(_index, (a, b))| a != b)
.map(|(differ_pos, _)| differ_pos);
let pos = if let Some(differ_pos) = differ_pos {
if differ_pos < pos {
pos + (expanded.len() - line.len())
if locations.is_empty() {
(pos, Vec::new())
} else {
pos
}
} else {
pos
};
self.complete_internal(&expanded, pos, context)
.map_err(|e| ShellError::untagged_runtime_error(format!("{}", e)))
.map(requote)
.map(|(pos, completions)| {
(
pos,
completions
let pos = locations[0].span.start();
let suggestions = locations
.into_iter()
.map(|pair| completion::Suggestion {
display: pair.display,
replacement: pair.replacement,
})
.collect(),
)
})
.flat_map(|location| {
let partial = location.span.slice(line);
match location.item {
LocationType::Command => {
let command_completer = CommandCompleter;
command_completer.complete(context, partial, matcher.to_owned())
}
fn hint(&self, line: &str, pos: usize, ctx: &completion::Context<'_>) -> Option<String> {
self.hinter.hint(line, pos, &ctx.as_ref())
}
}
impl Default for NuCompleter {
fn default() -> NuCompleter {
NuCompleter {
file_completer: FilenameCompleter::new(),
hinter: HistoryHinter {},
}
}
}
fn get_matching_arguments(
context: &context::Context,
lite_block: &nu_parser::LiteBlock,
line_chars: &[char],
line: &str,
replace_pos: usize,
pos: usize,
) -> Vec<rustyline::completion::Pair> {
let mut matching_arguments = vec![];
let mut line_copy = line.to_string();
let substring = line_chars[replace_pos..pos].iter().collect::<String>();
let replace_string = (replace_pos..pos).map(|_| " ").collect::<String>();
line_copy.replace_range(replace_pos..pos, &replace_string);
let result = nu_parser::classify_block(&lite_block, &context.registry);
for pipeline in &result.block.block {
for command in &pipeline.list {
if let nu_protocol::hir::ClassifiedCommand::Internal(
nu_protocol::hir::InternalCommand { args, .. },
) = command
{
if replace_pos >= args.span.start() && replace_pos <= args.span.end() {
if let Some(named) = &args.named {
for (name, _) in named.iter() {
let full_flag = format!("--{}", name);
if full_flag.starts_with(&substring) {
matching_arguments.push(rustyline::completion::Pair {
display: full_flag.clone(),
replacement: full_flag,
});
}
}
}
}
}
}
LocationType::Flag(cmd) => {
let flag_completer = FlagCompleter { cmd };
flag_completer.complete(context, partial, matcher.to_owned())
}
matching_arguments
}
LocationType::Argument(cmd, _arg_name) => {
let path_completer = PathCompleter;
// These is_executable/pathext implementations are copied from ichwh and modified
// to not be async
const QUOTE_CHARS: &[char] = &['\'', '"', '`'];
#[cfg(windows)]
fn pathext() -> IchwhResult<Vec<String>> {
Ok(std::env::var_os("PATHEXT")
.ok_or(IchwhError::PathextNotDefined)?
.to_string_lossy()
.split(';')
// Cut off the leading '.' character
.map(|ext| ext[1..].to_string())
.collect::<Vec<_>>())
}
#[cfg(windows)]
fn is_executable(file: &DirEntry) -> bool {
if let Ok(metadata) = file.metadata() {
let file_type = metadata.file_type();
// If the entry isn't a file, it cannot be executable
if !(file_type.is_file() || file_type.is_symlink()) {
return false;
}
if let Some(extension) = file.path().extension() {
if let Ok(exts) = pathext() {
exts.iter()
.any(|ext| extension.to_string_lossy().eq_ignore_ascii_case(ext))
// TODO Find a better way to deal with quote chars. Can the completion
// engine relay this back to us? Maybe have two spans: inner and
// outer. The former is what we want to complete, the latter what
// we'd need to replace.
let (quote_char, partial) = if partial.starts_with(QUOTE_CHARS) {
let (head, tail) = partial.split_at(1);
(Some(head), tail)
} else {
false
}
} else {
false
}
} else {
false
}
}
#[cfg(target_arch = "wasm32")]
fn is_executable(file: &DirEntry) -> bool {
false
}
#[cfg(unix)]
fn is_executable(file: &DirEntry) -> bool {
let metadata = file.metadata();
if let Ok(metadata) = metadata {
let filetype = metadata.file_type();
let permissions = metadata.permissions();
// The file is executable if it is a directory or a symlink and the permissions are set for
// owner, group, or other
(filetype.is_file() || filetype.is_symlink()) && (permissions.mode() & 0o111 != 0)
} else {
false
}
}
fn find_path_executables() -> Option<IndexSet<String>> {
let path_var = std::env::var_os("PATH")?;
let paths: Vec<_> = std::env::split_paths(&path_var).collect();
let mut executables: IndexSet<String> = IndexSet::new();
for path in paths {
if let Ok(mut contents) = read_dir(path) {
while let Some(Ok(item)) = contents.next() {
if is_executable(&item) {
if let Ok(name) = item.file_name().into_string() {
executables.insert(name);
}
}
}
}
}
Some(executables)
}
fn get_replace_pos(line: &str, pos: usize) -> (usize, ReplacementLocation) {
let line_chars: Vec<_> = line[..pos].chars().collect();
let mut replace_pos = line_chars.len();
let mut parsed_pos = false;
let mut loc = ReplacementLocation::Other;
if let Ok(lite_block) = nu_parser::lite_parse(line, 0) {
'outer: for pipeline in lite_block.block.iter() {
for command in pipeline.commands.iter() {
let name_span = command.name.span;
if name_span.start() <= pos && name_span.end() >= pos {
replace_pos = name_span.start();
parsed_pos = true;
loc = ReplacementLocation::Command;
break 'outer;
}
for arg in command.args.iter() {
if arg.span.start() <= pos && arg.span.end() >= pos {
replace_pos = arg.span.start();
parsed_pos = true;
break 'outer;
}
}
}
}
}
if !parsed_pos {
// If the command won't parse, naively detect the completion start point
while replace_pos > 0 {
if line_chars[replace_pos - 1] == ' ' {
break;
}
replace_pos -= 1;
}
}
(replace_pos, loc)
}
fn requote(
items: (usize, Vec<rustyline::completion::Pair>),
) -> (usize, Vec<rustyline::completion::Pair>) {
let mut new_items = Vec::with_capacity(items.1.len());
for item in items.1 {
let unescaped = rustyline::completion::unescape(&item.replacement, Some('\\'));
let maybe_quote = if unescaped != item.replacement {
"\""
} else {
""
(None, partial)
};
new_items.push(rustyline::completion::Pair {
display: item.display,
replacement: format!("{}{}{}", maybe_quote, unescaped, maybe_quote),
});
let partial = if let Some(quote_char) = quote_char {
if partial.ends_with(quote_char) {
&partial[..partial.len() - 1]
} else {
partial
}
} else {
partial
};
let completed_paths = path_completer.path_suggestions(partial, matcher);
match cmd.as_deref().unwrap_or("") {
"cd" => select_directory_suggestions(completed_paths),
_ => completed_paths,
}
.into_iter()
.map(|s| Suggestion {
replacement: requote(s.suggestion.replacement),
display: s.suggestion.display,
})
.collect()
}
(items.0, new_items)
LocationType::Variable => Vec::new(),
}
})
.collect();
(pos, suggestions)
}
}
}
fn select_directory_suggestions(completed_paths: Vec<PathSuggestion>) -> Vec<PathSuggestion> {
completed_paths
.into_iter()
.filter(|suggestion| {
suggestion
.path
.metadata()
.map(|md| md.is_dir())
.unwrap_or(false)
})
.collect()
}
fn requote(orig_value: String) -> String {
let value: Cow<str> = rustyline::completion::unescape(&orig_value, Some('\\'));
let mut quotes = vec!['"', '\'', '`'];
let mut should_quote = false;
for c in value.chars() {
if c.is_whitespace() {
should_quote = true;
} else if let Some(index) = quotes.iter().position(|q| *q == c) {
should_quote = true;
quotes.swap_remove(index);
}
}
if should_quote {
if quotes.is_empty() {
// TODO we don't really have an escape character, so there isn't a great option right
// now. One possibility is `{{$(char backtick)}}`
value.to_string()
} else {
let quote = quotes[0];
format!("{}{}{}", quote, value, quote)
}
} else {
value.to_string()
}
}

View File

@ -1,26 +1,28 @@
use crate::completion::{self, Completer};
use crate::context::Context;
use crate::shell::palette::{DefaultPalette, Palette};
use ansi_term::{Color, Style};
use nu_parser::SignatureRegistry;
use nu_protocol::hir::FlatShape;
use nu_source::{Spanned, Tag, Tagged};
use rustyline::hint::Hinter;
use crate::completion;
use crate::shell::completer::NuCompleter;
use nu_engine::{DefaultPalette, EvaluationContext, Painter};
use nu_source::{Tag, Tagged};
use std::borrow::Cow::{self, Owned};
pub struct Helper {
completer: Box<dyn Completer>,
context: Context,
completer: NuCompleter,
hinter: Option<rustyline::hint::HistoryHinter>,
context: EvaluationContext,
pub colored_prompt: String,
validator: NuValidator,
}
impl Helper {
pub(crate) fn new(completer: Box<dyn Completer>, context: Context) -> Helper {
pub(crate) fn new(
context: EvaluationContext,
hinter: Option<rustyline::hint::HistoryHinter>,
) -> Helper {
Helper {
completer,
completer: NuCompleter {},
hinter,
context,
colored_prompt: String::new(),
validator: NuValidator {},
}
}
}
@ -42,19 +44,21 @@ impl rustyline::completion::Completer for Helper {
&self,
line: &str,
pos: usize,
ctx: &rustyline::Context<'_>,
_ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<Self::Candidate>), rustyline::error::ReadlineError> {
let ctx = completion::Context::new(&self.context, ctx);
self.completer
.complete(line, pos, &ctx)
.map_err(|_| rustyline::error::ReadlineError::Eof)
let ctx = completion::CompletionContext::new(&self.context);
Ok(self.completer.complete(line, pos, &ctx))
}
fn update(&self, line: &mut rustyline::line_buffer::LineBuffer, start: usize, elected: &str) {
let end = line.pos();
line.replace(start..end, elected)
}
}
impl Hinter for Helper {
impl rustyline::hint::Hinter for Helper {
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
let ctx = completion::Context::new(&self.context, ctx);
self.completer.hint(line, pos, &ctx)
self.hinter.as_ref().and_then(|h| h.hint(line, pos, &ctx))
}
}
@ -78,11 +82,7 @@ impl rustyline::highlight::Highlighter for Helper {
}
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
Painter::paint_string(
line,
&self.context.registry().clone_box(),
&DefaultPalette {},
)
Painter::paint_string(line, &self.context.scope, &DefaultPalette {})
}
fn highlight_char(&self, _line: &str, _pos: usize) -> bool {
@ -90,6 +90,47 @@ impl rustyline::highlight::Highlighter for Helper {
}
}
impl rustyline::validate::Validator for Helper {
fn validate(
&self,
ctx: &mut rustyline::validate::ValidationContext,
) -> rustyline::Result<rustyline::validate::ValidationResult> {
self.validator.validate(ctx)
}
fn validate_while_typing(&self) -> bool {
self.validator.validate_while_typing()
}
}
struct NuValidator {}
impl rustyline::validate::Validator for NuValidator {
fn validate(
&self,
ctx: &mut rustyline::validate::ValidationContext,
) -> rustyline::Result<rustyline::validate::ValidationResult> {
let src = ctx.input();
let (tokens, err) = nu_parser::lex(src, 0);
if let Some(err) = err {
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
return Ok(rustyline::validate::ValidationResult::Incomplete);
}
}
let (_, err) = nu_parser::parse_block(tokens);
if let Some(err) = err {
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
return Ok(rustyline::validate::ValidationResult::Incomplete);
}
}
Ok(rustyline::validate::ValidationResult::Valid(None))
}
}
#[allow(unused)]
fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
let mut iter = input.iter();
@ -102,94 +143,52 @@ fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
})
}
pub struct Painter {
original: Vec<u8>,
styles: Vec<Style>,
}
impl Painter {
fn new(original: &str) -> Painter {
let bytes: Vec<u8> = original.bytes().collect();
let bytes_count = bytes.len();
Painter {
original: bytes,
styles: vec![Color::White.normal(); bytes_count],
}
}
pub fn paint_string<'l, P: Palette>(
line: &'l str,
registry: &dyn SignatureRegistry,
palette: &P,
) -> Cow<'l, str> {
let lite_block = nu_parser::lite_parse(line, 0);
match lite_block {
Err(_) => Cow::Borrowed(line),
Ok(lb) => {
let classified = nu_parser::classify_block(&lb, registry);
let shapes = nu_parser::shapes(&classified.block);
let mut painter = Painter::new(line);
for shape in shapes {
painter.paint_shape(&shape, palette);
}
Cow::Owned(painter.into_string())
}
}
}
fn paint_shape<P: Palette>(&mut self, shape: &Spanned<FlatShape>, palette: &P) {
palette
.styles_for_shape(shape)
.iter()
.for_each(|x| self.paint(x));
}
fn paint(&mut self, styled_span: &Spanned<Style>) {
for pos in styled_span.span.start()..styled_span.span.end() {
self.styles[pos] = styled_span.item;
}
}
fn into_string(self) -> String {
let mut idx_start = 0;
let mut idx_end = 1;
if self.original.is_empty() {
String::new()
} else {
let mut builder = String::new();
let mut current_style = self.styles[0];
while idx_end < self.styles.len() {
if self.styles[idx_end] != current_style {
// Emit, as we changed styles
let intermediate = String::from_utf8_lossy(&self.original[idx_start..idx_end]);
builder.push_str(&format!("{}", current_style.paint(intermediate)));
current_style = self.styles[idx_end];
idx_start = idx_end;
idx_end += 1;
} else {
idx_end += 1;
}
}
let intermediate = String::from_utf8_lossy(&self.original[idx_start..idx_end]);
builder.push_str(&format!("{}", current_style.paint(intermediate)));
builder
}
}
}
impl rustyline::Helper for Helper {}
// Use default validator for normal single line behaviour
// In the future we can implement this for custom multi-line support
impl rustyline::validate::Validator for Helper {}
#[cfg(test)]
mod tests {
use super::*;
use nu_engine::basic_evaluation_context;
use rustyline::completion::Completer;
use rustyline::line_buffer::LineBuffer;
#[ignore]
#[test]
fn closing_quote_should_replaced() {
let text = "cd \"folder with spaces\\subdirectory\\\"";
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
let mut buffer = LineBuffer::with_capacity(256);
buffer.insert_str(0, text);
buffer.set_pos(text.len() - 1);
let helper = Helper::new(basic_evaluation_context().unwrap(), None);
helper.update(&mut buffer, "cd ".len(), &replacement);
assert_eq!(
buffer.as_str(),
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
);
}
#[ignore]
#[test]
fn replacement_with_cursor_in_text() {
let text = "cd \"folder with spaces\\subdirectory\\\"";
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
let mut buffer = LineBuffer::with_capacity(256);
buffer.insert_str(0, text);
buffer.set_pos(text.len() - 30);
let helper = Helper::new(basic_evaluation_context().unwrap(), None);
helper.update(&mut buffer, "cd ".len(), &replacement);
assert_eq!(
buffer.as_str(),
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
);
}
}

View File

@ -0,0 +1 @@
pub(crate) mod deduction;

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +0,0 @@
use nu_test_support::nu;
use nu_test_support::playground::Playground;
#[test]
fn alias_args_work() {
Playground::setup("append_test_1", |dirs, _| {
let actual = nu!(
cwd: dirs.root(),
r#"
alias double_echo [a b] {echo $a $b}
double_echo 1 2 | to json
"#
);
assert_eq!(actual.out, "[1,2]");
})
}

View File

@ -1,13 +0,0 @@
use nu_test_support::{nu, pipeline};
#[test]
fn each_works_separately() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
echo [1 2 3] | each { echo $it 10 | math sum } | to json | echo $it
"#
));
assert_eq!(actual.out, "[11,12,13]");
}

View File

@ -1,13 +0,0 @@
use nu_test_support::{nu, pipeline};
#[test]
fn echo_range_is_lazy() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
echo 1..10000000000 | first 3 | echo $it | to json
"#
));
assert_eq!(actual.out, "[1,2,3]");
}

View File

@ -1,44 +0,0 @@
use nu_test_support::{nu, pipeline};
#[test]
fn creates_the_resulting_string_from_the_given_fields() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
open cargo_sample.toml
| get package
| format "{name} has license {license}"
| echo $it
"#
));
assert_eq!(actual.out, "nu has license ISC");
}
#[test]
fn given_fields_can_be_column_paths() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
open cargo_sample.toml
| format "{package.name} is {package.description}"
| echo $it
"#
));
assert_eq!(actual.out, "nu is a new type of shell");
}
#[test]
fn can_use_variables() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
open cargo_sample.toml
| format "{$it.package.name} is {$it.package.description}"
| echo $it
"#
));
assert_eq!(actual.out, "nu is a new type of shell");
}

View File

@ -1,40 +0,0 @@
use nu_test_support::{nu, pipeline};
#[test]
fn insert_plugin() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
open cargo_sample.toml
| insert dev-dependencies.newdep "1"
| get dev-dependencies.newdep
| echo $it
"#
));
assert_eq!(actual.out, "1");
}
#[test]
fn downcase_upcase() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo abcd | wrap downcase | insert upcase { echo $it.downcase | str upcase } | format "{downcase}{upcase}"
"#
));
assert_eq!(actual.out, "abcdABCD");
}
#[test]
fn number_and_its_negative_equal_zero() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo 1..10 | wrap num | insert neg { = $it.num * -1 } | math sum | = $it.num + $it.neg
"#
));
assert_eq!(actual.out, "0");
}

View File

@ -1,96 +0,0 @@
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
use nu_test_support::playground::Playground;
use nu_test_support::{nu, pipeline};
#[test]
fn adds_value_provided_if_column_is_empty() {
Playground::setup("is_empty_test_1", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContentToBeTrimmed(
"likes.csv",
r#"
first_name,last_name,rusty_at,likes
Andrés,Robalino,10/11/2013,1
Jonathan,Turner,10/12/2013,1
Jason,Gedge,10/11/2013,1
Yehuda,Katz,10/11/2013,
"#,
)]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
open likes.csv
| empty? likes 1
| get likes
| math sum
| echo $it
"#
));
assert_eq!(actual.out, "4");
})
}
#[test]
fn adds_value_provided_for_columns_that_are_empty() {
Playground::setup("is_empty_test_2", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContentToBeTrimmed(
"checks.json",
r#"
[
{"boost": 1, "check": []},
{"boost": 1, "check": ""},
{"boost": 1, "check": {}},
{"boost": null, "check": ["" {} [] ""]}
]
"#,
)]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
open checks.json
| empty? boost check 1
| get boost check
| math sum
| echo $it
"#
));
assert_eq!(actual.out, "8");
})
}
#[test]
fn value_emptiness_check() {
Playground::setup("is_empty_test_3", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContentToBeTrimmed(
"checks.json",
r#"
{
"are_empty": [
{"check": []},
{"check": ""},
{"check": {}},
{"check": ["" {} [] ""]}
]
}
"#,
)]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
open checks.json
| get are_empty.check
| empty?
| where $it
| count
| echo $it
"#
));
assert_eq!(actual.out, "4");
})
}

View File

@ -1,101 +0,0 @@
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
use nu_test_support::playground::Playground;
use nu_test_support::{nu, pipeline};
#[test]
fn regular_columns() {
Playground::setup("select_test_1", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContentToBeTrimmed(
"los_tres_caballeros.csv",
r#"
first_name,last_name,rusty_at,type
Andrés,Robalino,10/11/2013,A
Jonathan,Turner,10/12/2013,B
Yehuda,Katz,10/11/2013,A
"#,
)]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
open los_tres_caballeros.csv
| select rusty_at last_name
| nth 0
| get last_name
| echo $it
"#
));
assert_eq!(actual.out, "Robalino");
})
}
#[test]
fn complex_nested_columns() {
Playground::setup("select_test_2", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContentToBeTrimmed(
"los_tres_caballeros.json",
r#"
{
"nu": {
"committers": [
{"name": "Andrés N. Robalino"},
{"name": "Jonathan Turner"},
{"name": "Yehuda Katz"}
],
"releases": [
{"version": "0.2"}
{"version": "0.8"},
{"version": "0.9999999"}
],
"0xATYKARNU": [
["Th", "e", " "],
["BIG", " ", "UnO"],
["punto", "cero"]
]
}
}
"#,
)]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
open los_tres_caballeros.json
| select nu."0xATYKARNU" nu.committers.name nu.releases.version
| where "nu.releases.version" > "0.8"
| get "nu.releases.version"
| echo $it
"#
));
assert_eq!(actual.out, "0.9999999");
})
}
#[test]
fn allows_if_given_unknown_column_name_is_missing() {
Playground::setup("select_test_3", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContentToBeTrimmed(
"los_tres_caballeros.csv",
r#"
first_name,last_name,rusty_at,type
Andrés,Robalino,10/11/2013,A
Jonathan,Turner,10/12/2013,B
Yehuda,Katz,10/11/2013,A
"#,
)]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
open los_tres_caballeros.csv
| select rrusty_at first_name
| count
| echo $it
"#
));
assert_eq!(actual.out, "3");
})
}

View File

@ -1,85 +0,0 @@
use nu_test_support::fs::Stub::FileWithContent;
use nu_test_support::playground::Playground;
use nu_test_support::{nu, pipeline};
#[test]
fn string() {
Playground::setup("trim_test_1", |dirs, _sandbox| {
let test_strings = ["\n", " \n ", "\thi\n\n", "\u{2003}a"];
assert!(test_strings[3].chars().count() == 2);
for test_string in &test_strings {
let commandline = format!(
r#"
echo {}
| trim
"#,
test_string
);
let actual = nu!(
cwd: dirs.test(), pipeline(&commandline
));
assert_eq!(actual.out, test_string.trim())
}
})
}
#[test]
fn row() {
Playground::setup("trim_test_2", |dirs, sandbox| {
sandbox.with_files(vec![
FileWithContent("lines.csv", "lines\n l0\n\tl1\n l2\t \n\n"),
FileWithContent("lines_trimmed.csv", "lines\nl0\nl1\nl2\n"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
open lines.csv
| trim
"#
));
let expected = nu!(
cwd: dirs.test(), pipeline(
r#"
open lines_trimmed.csv
"#
));
assert_eq!(actual.out, expected.out)
})
}
#[test]
fn nested() {
Playground::setup("trim_test_3", |dirs, sandbox| {
sandbox.with_files(vec![
FileWithContent(
"nested.json",
r#"{ "l0" : {"l1": {"l2" : {"a" : " s0", "b" : "\t\ts1\n"} } } }"#,
),
FileWithContent(
"nested_trimmed.json",
r#"{ "l0" : {"l1": {"l2" : {"a" : "s0", "b" : "s1"} } } }"#,
),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
open nested.json
| trim
"#
));
let expected = nu!(
cwd: dirs.test(), pipeline(
r#"
open nested_trimmed.json
"#
));
assert_eq!(actual.out, expected.out)
})
}

View File

@ -0,0 +1,132 @@
[package]
authors = ["The Nu Project Contributors"]
build = "build.rs"
description = "CLI for nushell"
edition = "2018"
license = "MIT"
name = "nu-command"
version = "0.27.0"
[lib]
doctest = false
[dependencies]
nu-data = { version = "0.27.0", path = "../nu-data" }
nu-engine = { version = "0.27.0", path = "../nu-engine" }
nu-errors = { version = "0.27.0", path = "../nu-errors" }
nu-json = { version = "0.27.0", path = "../nu-json" }
nu-parser = { version = "0.27.0", path = "../nu-parser" }
nu-plugin = { version = "0.27.0", path = "../nu-plugin" }
nu-protocol = { version = "0.27.0", path = "../nu-protocol" }
nu-source = { version = "0.27.0", path = "../nu-source" }
nu-stream = { version = "0.27.0", path = "../nu-stream" }
nu-table = { version = "0.27.0", path = "../nu-table" }
nu-test-support = { version = "0.27.0", path = "../nu-test-support" }
nu-value-ext = { version = "0.27.0", path = "../nu-value-ext" }
Inflector = "0.11"
ansi_term = "0.12.1"
arboard = { version = "1.1.0", optional = true }
async-recursion = "0.3.2"
async-trait = "0.1.42"
base64 = "0.13.0"
bigdecimal = { version = "0.2.0", features = ["serde"] }
byte-unit = "4.0.9"
bytes = "1.0.1"
calamine = "0.17.0"
chrono = { version = "0.4.19", features = ["serde"] }
chrono-tz = "0.5.3"
clap = "2.33.3"
codespan-reporting = "0.11.0"
csv = "1.1.3"
ctrlc = { version = "3.1.7", optional = true }
derive-new = "0.5.8"
directories-next = { version = "2.0.0", optional = true }
dirs-next = { version = "2.0.0", optional = true }
dtparse = "1.2.0"
dunce = "1.0.1"
eml-parser = "0.1.0"
encoding_rs = "0.8.28"
filesize = "0.2.0"
fs_extra = "1.2.0"
futures = { version = "0.3.12", features = ["compat", "io-compat"] }
futures-util = "0.3.12"
futures_codec = "0.4.1"
getset = "0.1.1"
glob = "0.3.0"
htmlescape = "0.3.1"
ical = "0.7.0"
ichwh = { version = "0.3.4", optional = true }
indexmap = { version = "1.6.1", features = ["serde-1"] }
itertools = "0.10.0"
lazy_static = "1.*"
log = "0.4.14"
meval = "0.2.0"
num-bigint = { version = "0.3.1", features = ["serde"] }
num-format = { version = "0.4.0", features = ["with-num-bigint"] }
num-traits = "0.2.14"
parking_lot = "0.11.1"
pin-utils = "0.1.0"
pretty-hex = "0.2.1"
ptree = { version = "0.3.1", optional = true }
query_interface = "0.3.5"
quick-xml = "0.21.0"
rand = "0.7.3"
rayon = "1.5.0"
regex = "1.4.3"
roxmltree = "0.14.0"
rust-embed = "5.9.0"
rustyline = { version = "7.1.0", optional = true }
serde = { version = "1.0.123", features = ["derive"] }
serde_bytes = "0.11.5"
serde_ini = "0.2.0"
serde_json = "1.0.61"
serde_urlencoded = "0.7.0"
serde_yaml = "0.8.16"
sha2 = "0.9.3"
shellexpand = "2.1.0"
strip-ansi-escapes = "0.1.0"
sxd-document = "0.3.2"
sxd-xpath = "0.4.2"
tempfile = "3.2.0"
term = { version = "0.7.0", optional = true }
term_size = "0.3.2"
termcolor = "1.1.2"
titlecase = "1.1.0"
toml = "0.5.8"
trash = { version = "1.3.0", optional = true }
unicode-segmentation = "1.7.1"
url = "2.2.0"
uuid_crate = { package = "uuid", version = "0.8.2", features = ["v4"], optional = true }
which = { version = "4.0.2", optional = true }
zip = { version = "0.5.9", optional = true }
[target.'cfg(unix)'.dependencies]
umask = "1.0.0"
users = "0.11.0"
# TODO this will be possible with new dependency resolver
# (currently on nightly behind -Zfeatures=itarget):
# https://github.com/rust-lang/cargo/issues/7914
#[target.'cfg(not(windows))'.dependencies]
#num-format = {version = "0.4", features = ["with-system-locale"]}
[dependencies.rusqlite]
features = ["bundled", "blob"]
optional = true
version = "0.24.2"
[build-dependencies]
shadow-rs = "0.5"
[dev-dependencies]
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
[features]
clipboard-cli = ["arboard"]
rustyline-support = ["rustyline"]
stable = []
trash-support = ["trash"]
directories = ["directories-next"]
dirs = ["dirs-next"]

Binary file not shown.

View File

@ -0,0 +1,3 @@
fn main() -> shadow_rs::SdResult<()> {
shadow_rs::new()
}

View File

@ -4,11 +4,10 @@ pub(crate) mod macros;
mod from_delimited_data;
mod to_delimited_data;
pub(crate) mod alias;
pub(crate) mod ansi;
pub(crate) mod append;
pub(crate) mod args;
pub(crate) mod autoenv;
pub mod autoenv;
pub(crate) mod autoenv_trust;
pub(crate) mod autoenv_untrust;
pub(crate) mod autoview;
@ -17,10 +16,11 @@ pub(crate) mod build_string;
pub(crate) mod cal;
pub(crate) mod cd;
pub(crate) mod char_;
pub(crate) mod chart;
pub(crate) mod classified;
#[cfg(feature = "clipboard")]
#[cfg(feature = "clipboard-cli")]
pub(crate) mod clip;
pub(crate) mod command;
pub mod command;
pub(crate) mod compact;
pub(crate) mod config;
pub(crate) mod constants;
@ -28,16 +28,22 @@ pub(crate) mod count;
pub(crate) mod cp;
pub(crate) mod date;
pub(crate) mod debug;
pub(crate) mod def;
pub(crate) mod default;
pub mod default_context;
pub(crate) mod describe;
pub(crate) mod do_;
pub(crate) mod drop;
pub(crate) mod du;
pub(crate) mod each;
pub(crate) mod echo;
pub(crate) mod empty;
pub(crate) mod enter;
pub(crate) mod every;
pub(crate) mod exec;
pub(crate) mod exit;
pub(crate) mod first;
pub(crate) mod flatten;
pub(crate) mod format;
pub(crate) mod from;
pub(crate) mod from_csv;
@ -57,15 +63,18 @@ pub(crate) mod from_yaml;
pub(crate) mod get;
pub(crate) mod group_by;
pub(crate) mod group_by_date;
pub(crate) mod hash_;
pub(crate) mod headers;
pub(crate) mod help;
pub(crate) mod histogram;
pub(crate) mod history;
pub(crate) mod if_;
pub(crate) mod insert;
pub(crate) mod is_empty;
pub(crate) mod into_int;
pub(crate) mod keep;
pub(crate) mod last;
pub(crate) mod let_;
pub(crate) mod let_env;
pub(crate) mod lines;
pub(crate) mod ls;
pub(crate) mod math;
@ -74,11 +83,11 @@ pub(crate) mod mkdir;
pub(crate) mod move_;
pub(crate) mod next;
pub(crate) mod nth;
pub(crate) mod nu;
pub(crate) mod open;
pub(crate) mod parse;
pub(crate) mod path;
pub(crate) mod pivot;
pub(crate) mod plugin;
pub(crate) mod prepend;
pub(crate) mod prev;
pub(crate) mod pwd;
@ -89,20 +98,24 @@ pub(crate) mod reject;
pub(crate) mod rename;
pub(crate) mod reverse;
pub(crate) mod rm;
pub(crate) mod run_alias;
pub(crate) mod run_external;
pub(crate) mod save;
pub(crate) mod select;
pub(crate) mod seq;
pub(crate) mod seq_dates;
pub(crate) mod shells;
pub(crate) mod shuffle;
pub(crate) mod size;
pub(crate) mod skip;
pub(crate) mod sleep;
pub(crate) mod sort_by;
pub(crate) mod source;
pub(crate) mod split;
pub(crate) mod split_by;
pub(crate) mod str_;
pub(crate) mod table;
pub(crate) mod tags;
pub(crate) mod termsize;
pub(crate) mod to;
pub(crate) mod to_csv;
pub(crate) mod to_html;
@ -113,12 +126,10 @@ pub(crate) mod to_tsv;
pub(crate) mod to_url;
pub(crate) mod to_xml;
pub(crate) mod to_yaml;
pub(crate) mod trim;
pub(crate) mod uniq;
pub(crate) mod update;
pub(crate) mod url_;
pub(crate) mod version;
pub(crate) mod what;
pub(crate) mod where_;
pub(crate) mod which_;
pub(crate) mod with_env;
@ -126,13 +137,9 @@ pub(crate) mod wrap;
pub(crate) use autoview::Autoview;
pub(crate) use cd::Cd;
pub(crate) use command::{
whole_stream_command, Command, Example, UnevaluatedCallInfo, WholeStreamCommand,
};
pub(crate) use alias::Alias;
pub(crate) use ansi::Ansi;
pub(crate) use append::Append;
pub(crate) use append::Command as Append;
pub(crate) use autoenv::Autoenv;
pub(crate) use autoenv_trust::AutoenvTrust;
pub(crate) use autoenv_untrust::AutoenvUnTrust;
@ -140,23 +147,29 @@ pub(crate) use benchmark::Benchmark;
pub(crate) use build_string::BuildString;
pub(crate) use cal::Cal;
pub(crate) use char_::Char;
pub(crate) use chart::Chart;
pub(crate) use compact::Compact;
pub(crate) use config::{
Config, ConfigClear, ConfigGet, ConfigLoad, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto,
};
pub(crate) use count::Count;
pub(crate) use cp::Cpy;
pub(crate) use date::Date;
pub(crate) use date::{Date, DateFormat, DateListTimeZone, DateNow, DateToTable, DateToTimeZone};
pub(crate) use debug::Debug;
pub(crate) use def::Def;
pub(crate) use default::Default;
pub(crate) use describe::Describe;
pub(crate) use do_::Do;
pub(crate) use drop::Drop;
pub(crate) use du::Du;
pub(crate) use each::Each;
pub(crate) use each::EachGroup;
pub(crate) use each::EachWindow;
pub(crate) use echo::Echo;
pub(crate) use empty::Command as Empty;
pub(crate) use if_::If;
pub(crate) use is_empty::IsEmpty;
pub(crate) use update::Update;
pub(crate) use nu::NuPlugin;
pub(crate) use update::Command as Update;
pub(crate) mod kill;
pub(crate) use kill::Kill;
pub(crate) mod clear;
@ -164,9 +177,11 @@ pub(crate) use clear::Clear;
pub(crate) mod touch;
pub(crate) use enter::Enter;
pub(crate) use every::Every;
pub(crate) use exec::Exec;
pub(crate) use exit::Exit;
pub(crate) use first::First;
pub(crate) use format::Format;
pub(crate) use flatten::Command as Flatten;
pub(crate) use format::{FileSize, Format};
pub(crate) use from::From;
pub(crate) use from_csv::FromCSV;
pub(crate) use from_eml::FromEML;
@ -183,37 +198,46 @@ pub(crate) use from_xlsx::FromXLSX;
pub(crate) use from_xml::FromXML;
pub(crate) use from_yaml::FromYAML;
pub(crate) use from_yaml::FromYML;
pub(crate) use get::Get;
pub(crate) use group_by::GroupBy;
pub(crate) use get::Command as Get;
pub(crate) use group_by::Command as GroupBy;
pub(crate) use group_by_date::GroupByDate;
pub(crate) use hash_::{Hash, HashBase64};
pub(crate) use headers::Headers;
pub(crate) use help::Help;
pub(crate) use histogram::Histogram;
pub(crate) use history::History;
pub(crate) use insert::Insert;
pub(crate) use insert::Command as Insert;
pub(crate) use into_int::IntoInt;
pub(crate) use keep::{Keep, KeepUntil, KeepWhile};
pub(crate) use last::Last;
pub(crate) use let_::Let;
pub(crate) use let_env::LetEnv;
pub(crate) use lines::Lines;
pub(crate) use ls::Ls;
pub(crate) use math::{
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathStddev,
MathSummation, MathVariance,
Math, MathAbs, MathAverage, MathCeil, MathEval, MathFloor, MathMaximum, MathMedian,
MathMinimum, MathMode, MathProduct, MathRound, MathStddev, MathSummation, MathVariance,
};
pub(crate) use merge::Merge;
pub(crate) use mkdir::Mkdir;
pub(crate) use move_::{Move, MoveColumn, Mv};
pub(crate) use move_::{Move, Mv};
pub(crate) use next::Next;
pub(crate) use nth::Nth;
pub(crate) use open::Open;
pub(crate) use parse::Parse;
pub(crate) use path::{PathBasename, PathCommand, PathExists, PathExpand, PathExtension, PathType};
pub(crate) use path::{
PathBasename, PathCommand, PathDirname, PathExists, PathExpand, PathExtension, PathFilestem,
PathType,
};
pub(crate) use pivot::Pivot;
pub(crate) use prepend::Prepend;
pub(crate) use prev::Previous;
pub(crate) use pwd::Pwd;
#[cfg(feature = "uuid_crate")]
pub(crate) use random::RandomUUID;
pub(crate) use random::{Random, RandomBool, RandomDice};
pub(crate) use random::{
Random, RandomBool, RandomChars, RandomDecimal, RandomDice, RandomInteger,
};
pub(crate) use range::Range;
pub(crate) use reduce::Reduce;
pub(crate) use reject::Reject;
@ -222,38 +246,116 @@ pub(crate) use reverse::Reverse;
pub(crate) use rm::Remove;
pub(crate) use run_external::RunExternalCommand;
pub(crate) use save::Save;
pub(crate) use select::Select;
pub(crate) use select::Command as Select;
pub(crate) use seq::Seq;
pub(crate) use seq_dates::SeqDates;
pub(crate) use shells::Shells;
pub(crate) use shuffle::Shuffle;
pub(crate) use size::Size;
pub(crate) use skip::{Skip, SkipUntil, SkipWhile};
pub(crate) use sleep::Sleep;
pub(crate) use sort_by::SortBy;
pub(crate) use source::Source;
pub(crate) use split::{Split, SplitChars, SplitColumn, SplitRow};
pub(crate) use split_by::SplitBy;
pub(crate) use str_::{
Str, StrCapitalize, StrCollect, StrContains, StrDowncase, StrEndsWith, StrFindReplace, StrFrom,
StrIndexOf, StrLength, StrReverse, StrSet, StrStartsWith, StrSubstring, StrToDatetime,
Str, StrCamelCase, StrCapitalize, StrCollect, StrContains, StrDowncase, StrEndsWith,
StrFindReplace, StrFrom, StrIndexOf, StrKebabCase, StrLPad, StrLength, StrPascalCase, StrRPad,
StrReverse, StrScreamingSnakeCase, StrSnakeCase, StrStartsWith, StrSubstring, StrToDatetime,
StrToDecimal, StrToInteger, StrTrim, StrTrimLeft, StrTrimRight, StrUpcase,
};
pub(crate) use table::Table;
pub(crate) use tags::Tags;
pub(crate) use termsize::TermSize;
pub(crate) use to::To;
pub(crate) use to_csv::ToCSV;
pub(crate) use to_html::ToHTML;
pub(crate) use to_json::ToJSON;
pub(crate) use to_md::ToMarkdown;
pub(crate) use to_md::Command as ToMarkdown;
pub(crate) use to_toml::ToTOML;
pub(crate) use to_tsv::ToTSV;
pub(crate) use to_url::ToURL;
pub(crate) use to_xml::ToXML;
pub(crate) use to_yaml::ToYAML;
pub(crate) use touch::Touch;
pub(crate) use trim::Trim;
pub(crate) use uniq::Uniq;
pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme};
pub(crate) use version::Version;
pub(crate) use what::What;
pub(crate) use where_::Where;
pub(crate) use which_::Which;
pub(crate) use with_env::WithEnv;
pub(crate) use wrap::Wrap;
#[cfg(test)]
mod tests {
use super::*;
use crate::examples::{test_anchors, test_examples};
use nu_engine::{whole_stream_command, Command};
use nu_errors::ShellError;
fn full_tests() -> Vec<Command> {
vec![
whole_stream_command(Append),
whole_stream_command(GroupBy),
whole_stream_command(Insert),
whole_stream_command(Move),
whole_stream_command(Update),
whole_stream_command(Empty),
// whole_stream_command(Select),
// whole_stream_command(Get),
// Str Command Suite
whole_stream_command(Str),
whole_stream_command(StrToDecimal),
whole_stream_command(StrToInteger),
whole_stream_command(StrDowncase),
whole_stream_command(StrUpcase),
whole_stream_command(StrCapitalize),
whole_stream_command(StrFindReplace),
whole_stream_command(StrFrom),
whole_stream_command(StrSubstring),
whole_stream_command(StrToDatetime),
whole_stream_command(StrContains),
whole_stream_command(StrIndexOf),
whole_stream_command(StrTrim),
whole_stream_command(StrTrimLeft),
whole_stream_command(StrTrimRight),
whole_stream_command(StrStartsWith),
whole_stream_command(StrEndsWith),
//whole_stream_command(StrCollect),
whole_stream_command(StrLength),
whole_stream_command(StrLPad),
whole_stream_command(StrReverse),
whole_stream_command(StrRPad),
whole_stream_command(StrCamelCase),
whole_stream_command(StrPascalCase),
whole_stream_command(StrKebabCase),
whole_stream_command(StrSnakeCase),
whole_stream_command(StrScreamingSnakeCase),
whole_stream_command(ToMarkdown),
]
}
fn only_examples() -> Vec<Command> {
let mut commands = full_tests();
commands.extend(vec![whole_stream_command(Flatten)]);
commands
}
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
for cmd in only_examples() {
test_examples(cmd)?;
}
Ok(())
}
#[test]
fn tracks_metadata() -> Result<(), ShellError> {
for cmd in full_tests() {
test_anchors(cmd)?;
}
Ok(())
}
}

View File

@ -0,0 +1,291 @@
use crate::prelude::*;
use ansi_term::Color;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct Ansi;
#[derive(Deserialize)]
struct AnsiArgs {
code: Value,
escape: Option<Tagged<String>>,
osc: Option<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for Ansi {
fn name(&self) -> &str {
"ansi"
}
fn signature(&self) -> Signature {
Signature::build("ansi")
.optional(
"code",
SyntaxShape::Any,
"the name of the code to use like 'green' or 'reset' to reset the color",
)
.named(
"escape", // \x1b[
SyntaxShape::Any,
"escape sequence without the escape character(s)",
Some('e'),
)
.named(
"osc", // \x1b]
SyntaxShape::Any,
"operating system command (ocs) escape sequence without the escape character(s)",
Some('o'),
)
}
fn usage(&self) -> &str {
r#"Output ANSI codes to change color
For escape sequences:
Escape: '\x1b[' is not required for --escape parameter
Format: #(;#)m
Example: 1;31m for bold red or 2;37;41m for dimmed white fg with red bg
There can be multiple text formatting sequence numbers
separated by a ; and ending with an m where the # is of the
following values:
attributes
0 reset / normal display
1 bold or increased intensity
2 faint or decreased intensity
3 italic on (non-mono font)
4 underline on
5 slow blink on
6 fast blink on
7 reverse video on
8 nondisplayed (invisible) on
9 strike-through on
foreground/bright colors background/bright colors
30/90 black 40/100 black
31/91 red 41/101 red
32/92 green 42/102 green
33/93 yellow 43/103 yellow
34/94 blue 44/104 blue
35/95 magenta 45/105 magenta
36/96 cyan 46/106 cyan
37/97 white 47/107 white
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
Format: #
0 Set window title and icon name
1 Set icon name
2 Set window title
4 Set/read color palette
9 iTerm2 Grown notifications
10 Set foreground color (x11 color spec)
11 Set background color (x11 color spec)
... others"#
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Change color to green",
example: r#"ansi green"#,
result: Some(vec![Value::from("\u{1b}[32m")]),
},
Example {
description: "Reset the color",
example: r#"ansi reset"#,
result: Some(vec![Value::from("\u{1b}[0m")]),
},
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] | str collect"#,
result: Some(vec![Value::from(
"\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld",
)]),
},
Example {
description:
"Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)",
example: r#"echo [$(ansi -e '3;93;41m') Hello $(ansi reset) " " $(ansi gb) Nu " " $(ansi pb) World] | str collect"#,
result: Some(vec![Value::from(
"\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld",
)]),
},
]
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let (AnsiArgs { code, escape, osc }, _) = args.process().await?;
if let Some(e) = escape {
let esc_vec: Vec<char> = e.item.chars().collect();
if esc_vec[0] == '\\' {
return Err(ShellError::labeled_error(
"no need for escape characters",
"no need for escape characters",
e.tag(),
));
}
let output = format!("\x1b[{}", e.item);
return Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(e.tag()),
)));
}
if let Some(o) = osc {
let osc_vec: Vec<char> = o.item.chars().collect();
if osc_vec[0] == '\\' {
return Err(ShellError::labeled_error(
"no need for escape characters",
"no need for escape characters",
o.tag(),
));
}
//Operating system command aka osc ESC ] <- note the right brace, not left brace for osc
// OCS's need to end with a bell '\x07' char
let output = format!("\x1b]{};", o.item);
return Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(o.tag()),
)));
}
let code_string = code.as_string()?;
let ansi_code = str_to_ansi(code_string);
if let Some(output) = ansi_code {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(code.tag()),
)))
} else {
Err(ShellError::labeled_error(
"Unknown ansi code",
"unknown ansi code",
code.tag(),
))
}
}
}
pub fn str_to_ansi(s: String) -> Option<String> {
match s.as_str() {
"g" | "green" => Some(Color::Green.prefix().to_string()),
"gb" | "green_bold" => Some(Color::Green.bold().prefix().to_string()),
"gu" | "green_underline" => Some(Color::Green.underline().prefix().to_string()),
"gi" | "green_italic" => Some(Color::Green.italic().prefix().to_string()),
"gd" | "green_dimmed" => Some(Color::Green.dimmed().prefix().to_string()),
"gr" | "green_reverse" => Some(Color::Green.reverse().prefix().to_string()),
"r" | "red" => Some(Color::Red.prefix().to_string()),
"rb" | "red_bold" => Some(Color::Red.bold().prefix().to_string()),
"ru" | "red_underline" => Some(Color::Red.underline().prefix().to_string()),
"ri" | "red_italic" => Some(Color::Red.italic().prefix().to_string()),
"rd" | "red_dimmed" => Some(Color::Red.dimmed().prefix().to_string()),
"rr" | "red_reverse" => Some(Color::Red.reverse().prefix().to_string()),
"u" | "blue" => Some(Color::Blue.prefix().to_string()),
"ub" | "blue_bold" => Some(Color::Blue.bold().prefix().to_string()),
"uu" | "blue_underline" => Some(Color::Blue.underline().prefix().to_string()),
"ui" | "blue_italic" => Some(Color::Blue.italic().prefix().to_string()),
"ud" | "blue_dimmed" => Some(Color::Blue.dimmed().prefix().to_string()),
"ur" | "blue_reverse" => Some(Color::Blue.reverse().prefix().to_string()),
"b" | "black" => Some(Color::Black.prefix().to_string()),
"bb" | "black_bold" => Some(Color::Black.bold().prefix().to_string()),
"bu" | "black_underline" => Some(Color::Black.underline().prefix().to_string()),
"bi" | "black_italic" => Some(Color::Black.italic().prefix().to_string()),
"bd" | "black_dimmed" => Some(Color::Black.dimmed().prefix().to_string()),
"br" | "black_reverse" => Some(Color::Black.reverse().prefix().to_string()),
"y" | "yellow" => Some(Color::Yellow.prefix().to_string()),
"yb" | "yellow_bold" => Some(Color::Yellow.bold().prefix().to_string()),
"yu" | "yellow_underline" => Some(Color::Yellow.underline().prefix().to_string()),
"yi" | "yellow_italic" => Some(Color::Yellow.italic().prefix().to_string()),
"yd" | "yellow_dimmed" => Some(Color::Yellow.dimmed().prefix().to_string()),
"yr" | "yellow_reverse" => Some(Color::Yellow.reverse().prefix().to_string()),
"p" | "purple" => Some(Color::Purple.prefix().to_string()),
"pb" | "purple_bold" => Some(Color::Purple.bold().prefix().to_string()),
"pu" | "purple_underline" => Some(Color::Purple.underline().prefix().to_string()),
"pi" | "purple_italic" => Some(Color::Purple.italic().prefix().to_string()),
"pd" | "purple_dimmed" => Some(Color::Purple.dimmed().prefix().to_string()),
"pr" | "purple_reverse" => Some(Color::Purple.reverse().prefix().to_string()),
"c" | "cyan" => Some(Color::Cyan.prefix().to_string()),
"cb" | "cyan_bold" => Some(Color::Cyan.bold().prefix().to_string()),
"cu" | "cyan_underline" => Some(Color::Cyan.underline().prefix().to_string()),
"ci" | "cyan_italic" => Some(Color::Cyan.italic().prefix().to_string()),
"cd" | "cyan_dimmed" => Some(Color::Cyan.dimmed().prefix().to_string()),
"cr" | "cyan_reverse" => Some(Color::Cyan.reverse().prefix().to_string()),
"w" | "white" => Some(Color::White.prefix().to_string()),
"wb" | "white_bold" => Some(Color::White.bold().prefix().to_string()),
"wu" | "white_underline" => Some(Color::White.underline().prefix().to_string()),
"wi" | "white_italic" => Some(Color::White.italic().prefix().to_string()),
"wd" | "white_dimmed" => Some(Color::White.dimmed().prefix().to_string()),
"wr" | "white_reverse" => Some(Color::White.reverse().prefix().to_string()),
"reset" => Some("\x1b[0m".to_owned()),
// 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`
"title" => Some("\x1b]2;".to_string()), // ESC]2; xterm sets window title using OSC syntax escapes
"bel" => Some('\x07'.to_string()), // Terminal Bell
"backspace" => Some('\x08'.to_string()), // Backspace
// Ansi Erase Sequences
"clear_screen" => Some("\x1b[J".to_string()), // clears the screen
"clear_screen_from_cursor_to_end" => Some("\x1b[0J".to_string()), // clears from cursor until end of screen
"clear_screen_from_cursor_to_beginning" => Some("\x1b[1J".to_string()), // clears from cursor to beginning of screen
"cls" | "clear_entire_screen" => Some("\x1b[2J".to_string()), // clears the entire screen
"erase_line" => Some("\x1b[K".to_string()), // clears the current line
"erase_line_from_cursor_to_end" => Some("\x1b[0K".to_string()), // clears from cursor to end of line
"erase_line_from_cursor_to_beginning" => Some("\x1b[1K".to_string()), // clears from cursor to start of line
"erase_entire_line" => Some("\x1b[2K".to_string()), // clears entire line
// Turn on/off cursor
"cursor_off" => Some("\x1b[?25l".to_string()),
"cursor_on" => Some("\x1b[?25h".to_string()),
// Turn on/off blinking
"cursor_blink_off" => Some("\x1b[?12l".to_string()),
"cursor_blink_on" => Some("\x1b[?12h".to_string()),
// Cursor position in ESC [ <r>;<c>R where r = row and c = column
"cursor_position" => Some("\x1b[6n".to_string()),
// Report Terminal Identity
"identity" => Some("\x1b[0c".to_string()),
// Ansi escape only - CSI command
"csi" | "escape" | "escape_left" => Some("\x1b[".to_string()),
// OSC escape (Operating system command)
"osc" | "escape_right" => Some("\x1b]".to_string()),
// Ansi RGB - Needs to be 32;2;r;g;b or 48;2;r;g;b
// assuming the rgb will be passed via command and no here
"rgb_fg" => Some("\x1b[32;2;".to_string()),
"rgb_bg" => Some("\x1b[48;2;".to_string()),
// Ansi color index - Needs 38;5;idx or 48;5;idx where idx = 0 to 255
"idx_fg" | "color_idx_fg" => Some("\x1b[38;5;".to_string()),
"idx_bg" | "color_idx_bg" => Some("\x1b[48;5;".to_string()),
// Returns terminal size like "[<r>;<c>R" where r is rows and c is columns
// This should work assuming your terminal is not greater than 999x999
"size" => Some("\x1b[s\x1b[999;999H\x1b[6n\x1b[u".to_string()),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::Ansi;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
test_examples(Ansi {})
}
}

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