Compare commits

...

444 Commits

Author SHA1 Message Date
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
87d71604ad Bump to 0.18.1 (#2335) 2020-08-12 15:59:28 +12:00
e372e7c448 Display built features. Long/Short commit hashes display removed due to cargo publishing. (#2333)
Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-08-12 15:13:33 +12:00
0194dee3a6 Updated version hashing and bumped nu-cli to 0.18.1 (#2331)
* Updated version hashing and bumped nu-cli to 0.18.1

* made code pertyer

* Update version.rs

* Update version.rs

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-08-12 09:43:23 +12:00
cc3c10867c Histogram no longer requires a wrap command before it on unnamed columns (#2332) 2020-08-12 09:42:59 +12:00
3c18169f63 Autoenv fix: Exitscripts incorrectly running when visiting a subdirectory (#2326)
* Add test case for issue

* Preliminary fix

* fmt

* Reorder asserts

* move insertion

* Touch nu-env.toml

* Cleanup

* touch nu-env toml

* Remove touch

* Change feature flags
2020-08-12 07:54:49 +12:00
43e9c89125 moved theme assets local to crate (#2329)
* moved theme assets local to crate

* remove the TODO comment
2020-08-11 13:57:03 -05:00
2ad07912d9 Bump to 0.18 (#2325) 2020-08-11 18:44:53 +12:00
51ad019495 Update Cargo.lock (#2322) 2020-08-11 15:00:07 +12:00
9264325e57 Make history file location configurable (#2320)
* Make history location configurable

Add history-path to your config if you want an alternate history file
location

* use IndexMap.get() instead of index

Co-authored-by: Amanita Muscaria <nope>
2020-08-11 13:58:53 +12:00
901157341b Bumped which-rs to 4.0.2 (#2321)
This should fix #1541
2020-08-11 13:55:43 +12:00
eb766b80c1 added pkg_mgr/winget + yaml (#2297) 2020-08-11 05:45:53 +12:00
f0dbffd761 Add 228 json html themes for to html (#2308)
* add 228 json html themes
removed old assets, added new zipped asset
added --list to get a list of the theme names
reworked some older theme code
added rust-embed and zip crate
removed the dark tests

* fmt

* Updated, removed excess comments
Changed usage a bit
Updated the error handling
Added some helper items in --list
2020-08-11 05:43:16 +12:00
f14c0df582 Allow disabling welcome message on launch (#2314)
* Implements #2313
2020-08-09 11:38:21 +12:00
362bb1bea3 Add commit hash to version command (#2312)
* Add commit to version command

* Replace unwrap with expect.
2020-08-08 17:39:34 +12:00
724b177c97 Sample variance and Sample standard deviation. (#2310) 2020-08-06 23:56:19 -05:00
50343f2d6a Add stderr back when using do -i (#2309)
* Add stderr back when using do -i

* Add stderr back when using do -i
2020-08-07 16:53:37 +12:00
3122525b96 removed rustyline config duplication (#2306)
* removed rustyline config duplication
set other rustyline defaults if line_editor section doesn't exist
updated keyseq_timeout to -1 if emacs mode is chosen

* change checking rustyline config to if lets

* removed some unneccessary code
2020-08-05 16:34:28 -05:00
8232c6f185 Update rustyline defaults (#2305)
Use rustyline defaults if no config exists for line_editor (for most options)
2020-08-05 13:05:13 -05:00
6202705eb6 parse most common date formats using dtparse crate (#2303)
* use dtparse crate to parse most common date formats

* use dtparse crate to parse most common date formats - cargo fmt
2020-08-05 12:44:52 +12:00
e1c5940b04 Add command "reduce" (#2292)
* initial

* fold working

* tests and cleanup

* change command to reduce, with fold flag

* move complex example to tests

* add --numbered flag
2020-08-05 05:16:19 +12:00
7f35bfc005 histogram: support regular values. (#2300) 2020-08-04 04:57:25 -05:00
c48c092125 String funcs - Contains and IndexOf (#2298)
* Contains and index of string functions

* Clippy and fmt
2020-08-04 18:36:51 +12:00
028fc9b9cd Data summarize reporting overhaul. (#2299)
Refactored out most of internal work for summarizing data opening
the door for generating charts from it. A model is introduced
to hold information needed for a summary, Histogram command is
an example of a partial usage. This is the beginning.

Removed implicit arithmetic traits on Value and Primitive to avoid
mixed types panics. The std operations traits can't fail and we
can't guarantee that. We can handle gracefully now since compute_values
was introduced after the parser changes four months ago. The handling
logic should be taken care of either explicitly or in compute_values.

The zero identity trait was also removed (and implementing this forced
us to also implement Add, Mult, etc)

Also: the `math` operations now remove in the output if a given column is not computable:

```
> ls | math sum
──────┬──────────
 size │ 150.9 KB
──────┴──────────
```
2020-08-03 17:47:19 -05:00
eeb9b4edcb Match cleanup (#2294)
* Delete unnecessary match

* Use `unwrap_or_else()`

* Whitespace was trim on file save

* Use `map_or_else()`

* Use a default to group all match arms with same output

* Clippy made me do it
2020-08-04 05:43:27 +12:00
3a7869b422 Switch to maintained app_dirs (#2293)
* Switch to maintained app_dirs

* Update app_dirs under old name
2020-08-04 05:41:57 +12:00
c48ea46c4f Match cleanup (#2290) 2020-08-02 18:34:33 -04:00
f33da33626 Add --partial to 'to html' (#2291) 2020-08-03 08:47:54 +12:00
a88f5c7ae7 Make str collect take an optional separator value (#2289)
* Make `str collect` take an optional separator value

* Make `str collect` take an optional separator value

* Add some tests

* Fix my tests
2020-08-02 19:29:29 +12:00
cda53b6cda Return incomplete parse from lite_parse (#2284)
* Move lite_parse tests into a submodule

* Have lite_parse return partial parses when error encountered.

Although a parse fails, we can generally still return what was successfully
parsed. This is useful, for example, when figuring out completions at some
cursor position, because we can map the cursor to something more structured
(e.g., cursor is at a flag name).
2020-08-02 06:39:55 +12:00
ee734873ba Fix no longer working histogram example (#2271)
* Fix no longer working histogram example

* Oops
2020-08-02 06:38:45 +12:00
9fb6f5cd09 Change f/full flag to l/long for ls and ps commands (#2283)
* Change `f`/`full` flag to `l`/`long` for `ls` and `ps` commands

* Fix a few more `--full` instances
2020-08-02 06:30:45 +12:00
4ef15b5f80 docs/alias: simplify the 'persistent' section, using --save (#2285)
All the workarounds using `config` aren't necessary anymore. Only `config path` is still of interest.
2020-08-01 08:11:26 -04:00
ba81278ffd Remove build.rs and nu-build (#2282) 2020-08-01 09:21:10 +12:00
10fbed3808 updated cmd builtin commands (#2266)
* updated cmd builtin commands

* removed cd, chdir, exit, prompt, rem

* remove more commands, what remains is useful

* cargo fmt is so finicky
2020-07-31 09:51:42 +12:00
16cfc36aec set default edit_mode to emacs instead of vi (#2278) 2020-07-30 14:59:20 -05:00
aca7f71737 🐛 Fix path command error messages (#2261). (#2276) 2020-07-31 06:51:14 +12:00
3282a509a9 Make insert take in a block (#2265)
* Make insert take in a block

* Add some tests
2020-07-30 16:58:54 +12:00
878b748a41 Add list output for to html (#2273) 2020-07-30 16:54:55 +12:00
18a4505b9b starts_with ends_with match functions for string (#2269) 2020-07-30 16:51:20 +12:00
26e77a4b05 Add url commands (#2274)
* scheme
* path
* query
* host
2020-07-30 08:56:56 +12:00
37f10cf273 Add two further path cmds - type and exists (#2264)
* Add two further path cmds - type and exists

* Update type.rs

Try a more universal directory

* Update type.rs

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-07-27 14:12:07 +12:00
5e0a9aecaa ltrim and rtrim for string (#2262)
* Trim string from left and right

* Move trim to folder

* fmt

* Clippy
2020-07-27 06:09:35 +12:00
7e2c627044 WIP: Path utility commands (#2255)
* Add new path commands

basename, expand and extension. Currently there is no real error
handling. expand returns the initial path if it didn't work, the others
return empty string

* Optionally apply to path
2020-07-26 07:29:15 +12:00
4347339e9a Make all bullet point items uppercase (#2257) 2020-07-26 06:15:12 +12:00
e66a8258ec add example to parse command, with row output (#2256) 2020-07-26 06:14:29 +12:00
e4b42b54ad Simplify NuCompleter. (#2254)
- Removing old code for dealing with escaping, since that has moved elsewhere.
- Eliminating some match statements in favour of result/option methods.
- Fix an issue where completing inside quotes could remove the quote at the
  beginning, if one already existed on the line but the replacement didn't have
  a quote at the beginning.
2020-07-25 10:41:14 -04:00
de18b9ca2c Match cleanup (#2248) 2020-07-25 08:40:35 -04:00
a77f0f7b41 to-xml.md documentation update (#2253)
* Update to-xml.md documentation to be consistent

* Capitalize bullet point items

* Add link to this document wthin `to.md`
2020-07-25 20:19:15 +12:00
6b31a006b8 Refactor all completion logic into NuCompleter (#2252)
* Refactor all completion logic into `NuCompleter`

This is the next step to improving completions. Previously, completion logic was
scattered about (`FilesystemShell`, `NuCompleter`, `Helper`, and `ShellManager`).
By unifying the core logic into a central location, it will be easier to take the
next steps in improving completion.

* Update context.rs

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-07-25 11:39:12 +12:00
2db4fe83d8 Remove unnecessary peekable iterator (#2251) 2020-07-24 12:06:12 -04:00
55a2f284d9 Add to xml command (#2141) (#2155) 2020-07-24 19:41:22 +12:00
2d3b1e090a Remove piping of stderr. (#2247)
In any other shell, stderr is inherited like normal, and only piped if you
request it explicitly (e.g., `2>/dev/null`). In the case of a command like
`fzf`, stderr is used for the interactive selection of files. By piping it,
something like

    fzf | xargs echo

does not work. By removing all stderr piping we eliminate this issue. We can
return later with a way to deal with stderr piping when an actual use case
arises.
2020-07-24 17:56:50 +12:00
ed0c1038e3 Step 1 for to html theme-ing (#2245)
* reworked theming step 1.
added theme parameter.
hard coded 4 themes

* forgot about blulocolight

* aarrrrg! test are in another place. fixed i think.
2020-07-23 13:21:58 -05:00
0c20282200 added documentation of available binding options (#2246)
straight from the rustyline source code
2020-07-23 13:13:06 -05:00
e71f44d26f if config file doesn't exist, set defaults. (#2244)
if line_editor in config doesn't exist, set defaults.
2020-07-23 08:27:45 -05:00
e3d7e46855 added defaults. fixed but of not loading history. (#2243) 2020-07-23 07:19:05 -05:00
9b35aae5e8 update sample configs (#2242)
* update sample configs

* change rustyline to line_editor
2020-07-23 06:49:25 -05:00
7e9f87c57f Expose all rustyline configuration points (#2238)
* Added all rustyline config points

* comments cleanup

* my good friend fmt keeps changing his mind
2020-07-23 09:43:52 +12:00
5d17b72852 update config documentation (#2178)
* update config documentation

* update config syntax

* update config syntax

* Update alias.md

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-07-23 09:42:04 +12:00
6b4634b293 Convert table of primitives to positional arguments for external cmd (#2232)
* Convert table of primitives to positional arguments for external cmd

* Multiple file test, fix for cococo
2020-07-23 09:41:34 +12:00
748 changed files with 45771 additions and 18861 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: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo test --all --features stable
- bash: RUSTFLAGS="-D warnings" cargo test --all
condition: eq(variables['style'], 'canary')
displayName: Run tests
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 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

View File

@ -1 +0,0 @@
[build]

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
```

4572
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.17.0"
version = "0.26.0"
[workspace]
members = ["crates/*/"]
@ -18,92 +18,111 @@ members = ["crates/*/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-cli = {version = "0.17.0", path = "./crates/nu-cli"}
nu-errors = {version = "0.17.0", path = "./crates/nu-errors"}
nu-parser = {version = "0.17.0", path = "./crates/nu-parser"}
nu-plugin = {version = "0.17.0", path = "./crates/nu-plugin"}
nu-protocol = {version = "0.17.0", path = "./crates/nu-protocol"}
nu-source = {version = "0.17.0", path = "./crates/nu-source"}
nu-value-ext = {version = "0.17.0", path = "./crates/nu-value-ext"}
nu_plugin_binaryview = {version = "0.17.0", path = "./crates/nu_plugin_binaryview", optional = true}
nu_plugin_fetch = {version = "0.17.0", path = "./crates/nu_plugin_fetch", optional = true}
nu_plugin_from_bson = {version = "0.17.0", path = "./crates/nu_plugin_from_bson", optional = true}
nu_plugin_from_sqlite = {version = "0.17.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
nu_plugin_inc = {version = "0.17.0", path = "./crates/nu_plugin_inc", optional = true}
nu_plugin_match = {version = "0.17.0", path = "./crates/nu_plugin_match", optional = true}
nu_plugin_post = {version = "0.17.0", path = "./crates/nu_plugin_post", optional = true}
nu_plugin_ps = {version = "0.17.0", path = "./crates/nu_plugin_ps", optional = true}
nu_plugin_start = {version = "0.17.0", path = "./crates/nu_plugin_start", optional = true}
nu_plugin_sys = {version = "0.17.0", path = "./crates/nu_plugin_sys", optional = true}
nu_plugin_textview = {version = "0.17.0", path = "./crates/nu_plugin_textview", optional = true}
nu_plugin_to_bson = {version = "0.17.0", path = "./crates/nu_plugin_to_bson", optional = true}
nu_plugin_to_sqlite = {version = "0.17.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
nu_plugin_tree = {version = "0.17.0", path = "./crates/nu_plugin_tree", optional = true}
nu-cli = {version = "0.26.0", path = "./crates/nu-cli"}
nu-command = {version = "0.26.0", path = "./crates/nu-command"}
nu-data = {version = "0.26.0", path = "./crates/nu-data"}
nu-engine = {version = "0.26.0", path = "./crates/nu-engine"}
nu-errors = {version = "0.26.0", path = "./crates/nu-errors"}
nu-parser = {version = "0.26.0", path = "./crates/nu-parser"}
nu-plugin = {version = "0.26.0", path = "./crates/nu-plugin"}
nu-protocol = {version = "0.26.0", path = "./crates/nu-protocol"}
nu-source = {version = "0.26.0", path = "./crates/nu-source"}
nu-value-ext = {version = "0.26.0", path = "./crates/nu-value-ext"}
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}
nu_plugin_binaryview = {version = "0.26.0", path = "./crates/nu_plugin_binaryview", optional = true}
nu_plugin_chart = {version = "0.26.0", path = "./crates/nu_plugin_chart", optional = true}
nu_plugin_fetch = {version = "0.26.0", path = "./crates/nu_plugin_fetch", optional = true}
nu_plugin_from_bson = {version = "0.26.0", path = "./crates/nu_plugin_from_bson", optional = true}
nu_plugin_from_sqlite = {version = "0.26.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
nu_plugin_inc = {version = "0.26.0", path = "./crates/nu_plugin_inc", optional = true}
nu_plugin_match = {version = "0.26.0", path = "./crates/nu_plugin_match", optional = true}
nu_plugin_post = {version = "0.26.0", path = "./crates/nu_plugin_post", optional = true}
nu_plugin_ps = {version = "0.26.0", path = "./crates/nu_plugin_ps", optional = true}
nu_plugin_s3 = {version = "0.26.0", path = "./crates/nu_plugin_s3", optional = true}
nu_plugin_selector = {version = "0.26.0", path = "./crates/nu_plugin_selector", optional = true}
nu_plugin_start = {version = "0.26.0", path = "./crates/nu_plugin_start", optional = true}
nu_plugin_sys = {version = "0.26.0", path = "./crates/nu_plugin_sys", optional = true}
nu_plugin_textview = {version = "0.26.0", path = "./crates/nu_plugin_textview", optional = true}
nu_plugin_to_bson = {version = "0.26.0", path = "./crates/nu_plugin_to_bson", optional = true}
nu_plugin_to_sqlite = {version = "0.26.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
nu_plugin_tree = {version = "0.26.0", path = "./crates/nu_plugin_tree", optional = true}
nu_plugin_xpath = {version = "0.26.0", path = "./crates/nu_plugin_xpath", 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.6", optional = true}
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
itertools = "0.10.0"
log = "0.4.11"
pretty_env_logger = "0.4.0"
starship = "0.43.0"
[dev-dependencies]
nu-test-support = {version = "0.17.0", path = "./crates/nu-test-support"}
dunce = "1.0.1"
nu-test-support = {version = "0.26.0", path = "./crates/nu-test-support"}
[build-dependencies]
nu-build = {version = "0.17.0", path = "./crates/nu-build"}
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 = [
"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"]
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3", "chart", "xpath", "selector"]
stable = ["default"]
# Default
inc = ["semver", "nu_plugin_inc"]
ps = ["nu_plugin_ps"]
sys = ["nu_plugin_sys"]
textview = ["crossterm", "syntect", "url", "nu_plugin_textview"]
wasi = ["inc", "match", "directories-support", "ptree-support", "match", "tree", "rustyline-support"]
# Stable
binaryview = ["nu_plugin_binaryview"]
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
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
@ -128,37 +147,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`
* 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`
* 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
@ -194,7 +194,7 @@ For example, you can load a .toml file as structured data and explore it:
> open Cargo.toml
────────────────────┬───────────────────────────
bin │ [table 18 rows]
build-dependencies │ [row nu-build serde toml]
build-dependencies │ [row serde toml]
dependencies │ [row 29 columns]
dev-dependencies │ [row nu-test-support]
features │ [row 19 columns]
@ -219,28 +219,28 @@ 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:
To set one of these variables, you can use `config set`. For example:
```shell
> config --set [edit_mode "vi"]
> config --set [path $nu.path]
> config set edit_mode "vi"
> config set path $nu.path
```
### Shells
@ -300,14 +300,14 @@ Nu is in heavy development, and will naturally change as it matures and people u
| 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
| 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

Binary file not shown.

Binary file not shown.

View File

@ -1,3 +0,0 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
nu_build::build()
}

View File

@ -1,16 +0,0 @@
[package]
authors = ["The Nu Project Contributors"]
description = "Core build system for nushell"
edition = "2018"
license = "MIT"
name = "nu-build"
version = "0.17.0"
[lib]
doctest = false
[dependencies]
lazy_static = "1.4.0"
serde = {version = "1.0.114", features = ["derive"]}
serde_json = "1.0.55"
toml = "0.5.6"

View File

@ -1,80 +0,0 @@
use lazy_static::lazy_static;
use serde::Deserialize;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::env;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
lazy_static! {
static ref WORKSPACES: Mutex<BTreeMap<String, &'static Path>> = Mutex::new(BTreeMap::new());
}
// got from https://github.com/mitsuhiko/insta/blob/b113499249584cb650150d2d01ed96ee66db6b30/src/runtime.rs#L67-L88
fn get_cargo_workspace(manifest_dir: &str) -> Result<Option<&Path>, Box<dyn std::error::Error>> {
let mut workspaces = WORKSPACES.lock()?;
if let Some(rv) = workspaces.get(manifest_dir) {
Ok(Some(rv))
} else {
#[derive(Deserialize)]
struct Manifest {
workspace_root: String,
}
let output = std::process::Command::new(env!("CARGO"))
.arg("metadata")
.arg("--format-version=1")
.current_dir(manifest_dir)
.output()?;
let manifest: Manifest = serde_json::from_slice(&output.stdout)?;
let path = Box::leak(Box::new(PathBuf::from(manifest.workspace_root)));
workspaces.insert(manifest_dir.to_string(), path.as_path());
Ok(workspaces.get(manifest_dir).cloned())
}
}
#[derive(Deserialize)]
struct Feature {
#[allow(unused)]
description: String,
enabled: bool,
}
pub fn build() -> Result<(), Box<dyn std::error::Error>> {
let input = env::var("CARGO_MANIFEST_DIR")?;
let all_on = env::var("NUSHELL_ENABLE_ALL_FLAGS").is_ok();
let flags: HashSet<String> = env::var("NUSHELL_ENABLE_FLAGS")
.map(|s| s.split(',').map(|s| s.to_string()).collect())
.unwrap_or_else(|_| HashSet::new());
if all_on && !flags.is_empty() {
println!(
"cargo:warning=Both NUSHELL_ENABLE_ALL_FLAGS and NUSHELL_ENABLE_FLAGS were set. You don't need both."
);
}
let workspace = match get_cargo_workspace(&input)? {
// If the crate is being downloaded from crates.io, it won't have a workspace root, and that's ok
None => return Ok(()),
Some(workspace) => workspace,
};
let path = Path::new(&workspace).join("features.toml");
// If the crate is being downloaded from crates.io, it won't have a features.toml, and that's ok
if !path.exists() {
return Ok(());
}
let toml: HashMap<String, Feature> = toml::from_str(&std::fs::read_to_string(path)?)?;
for (key, value) in toml.iter() {
if value.enabled || all_on || flags.contains(key) {
println!("cargo:rustc-cfg={}", key);
}
}
Ok(())
}

View File

@ -1,100 +1,110 @@
[package]
authors = ["The Nu Project Contributors"]
build = "build.rs"
description = "CLI for nushell"
edition = "2018"
license = "MIT"
name = "nu-cli"
version = "0.17.0"
version = "0.26.0"
[lib]
doctest = false
[dependencies]
nu-errors = {version = "0.17.0", path = "../nu-errors"}
nu-parser = {version = "0.17.0", path = "../nu-parser"}
nu-plugin = {version = "0.17.0", path = "../nu-plugin"}
nu-protocol = {version = "0.17.0", path = "../nu-protocol"}
nu-source = {version = "0.17.0", path = "../nu-source"}
nu-table = {version = "0.17.0", path = "../nu-table"}
nu-test-support = {version = "0.17.0", path = "../nu-test-support"}
nu-value-ext = {version = "0.17.0", path = "../nu-value-ext"}
nu-command = { version = "0.26.0", path = "../nu-command" }
nu-data = { version = "0.26.0", path = "../nu-data" }
nu-engine = { version = "0.26.0", path = "../nu-engine" }
nu-errors = { version = "0.26.0", path = "../nu-errors" }
nu-json = { version = "0.26.0", path = "../nu-json" }
nu-parser = { version = "0.26.0", path = "../nu-parser" }
nu-plugin = { version = "0.26.0", path = "../nu-plugin" }
nu-protocol = { version = "0.26.0", path = "../nu-protocol" }
nu-source = { version = "0.26.0", path = "../nu-source" }
nu-stream = { version = "0.26.0", path = "../nu-stream" }
nu-table = { version = "0.26.0", path = "../nu-table" }
nu-test-support = { version = "0.26.0", path = "../nu-test-support" }
nu-value-ext = { version = "0.26.0", path = "../nu-value-ext" }
Inflector = "0.11"
ansi_term = "0.12.1"
app_dirs = "1.2.1"
arboard = { version = "1.1.0", optional = true }
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}
async-trait = "0.1.40"
base64 = "0.13.0"
bigdecimal = { version = "0.2.0", features = ["serde"] }
byte-unit = "4.0.9"
bytes = "0.5.6"
calamine = "0.16.1"
chrono = { version = "0.4.15", features = ["serde"] }
chrono-tz = "0.5.3"
clap = "2.33.3"
codespan-reporting = "0.11.0"
csv = "1.1.3"
ctrlc = { version = "3.1.6", optional = true }
derive-new = "0.5.8"
directories = {version = "2.0.2", optional = true}
dirs = {version = "2.0.2", optional = true}
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.24"
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.5", features = ["compat", "io-compat"] }
futures-util = "0.3.8"
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"
ical = "0.7.0"
ichwh = { version = "0.3.4", optional = true }
indexmap = { version = "1.6.0", features = ["serde-1"] }
itertools = "0.10.0"
lazy_static = "1.*"
log = "0.4.11"
meval = "0.2.0"
num-bigint = { version = "0.3.0", features = ["serde"] }
num-format = { version = "0.4.0", features = ["with-num-bigint"] }
num-traits = "0.2.12"
parking_lot = "0.11.0"
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.0"
ptree = { version = "0.3.0", optional = true }
query_interface = "0.3.5"
rand = "0.7"
regex = "1"
roxmltree = "0.13.0"
rustyline = "6.2.0"
serde = {version = "1.0.114", features = ["derive"]}
serde-hjson = "0.9.1"
quick-xml = "0.20.0"
rand = "0.7.3"
rayon = "1.4.0"
regex = "1.3.9"
roxmltree = "0.14.0"
rust-embed = "5.8.0"
rustyline = { version = "6.3.0", optional = true }
serde = { version = "1.0.115", 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"
serde_json = "1.0.57"
serde_urlencoded = "0.7.0"
serde_yaml = "0.8.13"
sha2 = "0.9.1"
shellexpand = "2.0.0"
strip-ansi-escapes = "0.1.0"
sxd-document = "0.3.2"
sxd-xpath = "0.4.2"
tempfile = "3.1.0"
term = {version = "0.5.2", optional = true}
term = { version = "0.6.1", optional = true }
term_size = "0.3.2"
termcolor = "1.1.0"
titlecase = "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.1", optional = true}
clipboard = {version = "0.5", optional = true}
encoding_rs = "0.8.23"
rayon = "1.3.1"
starship = {version = "0.43.0", optional = true}
trash = {version = "1.0.1", optional = true}
trash = { version = "1.2.0", optional = true }
unicode-segmentation = "1.6.0"
url = "2.1.1"
uuid_crate = { package = "uuid", version = "0.8.1", features = ["v4"], optional = true }
which = { version = "4.0.2", optional = true }
zip = { version = "0.5.7", optional = true }
shadow-rs = { version = "0.5", default-features = false, optional = true }
[target.'cfg(unix)'.dependencies]
umask = "1.0.0"
users = "0.10.0"
# TODO this will be possible with new dependency resolver
@ -106,17 +116,20 @@ users = "0.10.0"
[dependencies.rusqlite]
features = ["bundled", "blob"]
optional = true
version = "0.23.1"
version = "0.24.2"
[build-dependencies]
nu-build = {version = "0.17.0", path = "../nu-build"}
shadow-rs = "0.5"
[dev-dependencies]
quickcheck = "0.9"
quickcheck_macros = "0.9"
quickcheck = "0.9.2"
quickcheck_macros = "0.9.1"
[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"]

Binary file not shown.

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,80 +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 output = match result {
Ok(mut stream) => {
let _ = stream.drain_vec().await;
let run_duration: chrono::Duration = Utc::now().signed_duration_since(start_time);
context.clear_errors();
Ok(ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(Primitive::from(run_duration)),
tag: Tag::from(block.span),
}))
}
Err(e) => Err(e),
};
Ok(OutputStream::from(vec![output]))
}

View File

@ -1,98 +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> {
let mut iter = commands.list.clone().into_iter().peekable();
loop {
let item: Option<ClassifiedCommand> = iter.next();
let next: Option<&ClassifiedCommand> = iter.peek();
input = match (item, next) {
(Some(ClassifiedCommand::Dynamic(_)), _) | (_, Some(ClassifiedCommand::Dynamic(_))) => {
return Err(ShellError::unimplemented("Dynamic commands"))
}
(Some(ClassifiedCommand::Expr(expr)), _) => {
run_expression_block(*expr, ctx, it, vars, env).await?
}
(Some(ClassifiedCommand::Error(err)), _) => return Err(err.into()),
(_, Some(ClassifiedCommand::Error(err))) => return Err(err.clone().into()),
(Some(ClassifiedCommand::Internal(left)), _) => {
run_internal_command(left, ctx, input, it, vars, env).await?
}
(None, _) => break,
};
}
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> {
match self.call_info.args.nth(pos) {
None => Err(ShellError::unimplemented("Better error: expect_nth")),
Some(item) => Ok(item),
}
}
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,266 +0,0 @@
use crate::commands::group_by::group;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{columns_sorted, evaluate, map_max, reduce, t_sort};
use nu_errors::ShellError;
use nu_protocol::{
Primitive, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
};
use nu_source::Tagged;
use num_traits::{ToPrimitive, Zero};
pub struct Histogram;
#[derive(Deserialize)]
pub struct HistogramArgs {
column_name: Tagged<String>,
rest: Vec<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for Histogram {
fn name(&self) -> &str {
"histogram"
}
fn signature(&self) -> Signature {
Signature::build("histogram")
.required(
"column_name",
SyntaxShape::String,
"the name of the column to graph by",
)
.rest(
SyntaxShape::String,
"column name to give the histogram's frequency column",
)
}
fn usage(&self) -> &str {
"Creates a new table with a histogram based on the column name passed in."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
histogram(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get a histogram for the types of files",
example: "ls | histogram type",
result: None,
},
Example {
description:
"Get a histogram for the types of files, with frequency column named count",
example: "ls | histogram type count",
result: None,
},
Example {
description: "Get a histogram for a list of numbers",
example: "echo [1 2 3 1 1 1 2 2 1 1] | histogram",
result: None,
},
]
}
}
pub async fn histogram(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let (HistogramArgs { column_name, rest }, input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
let values = UntaggedValue::table(&values).into_value(&name);
let groups = group(&Some(column_name.clone()), &values, &name)?;
let group_labels = columns_sorted(Some(column_name.clone()), &groups, &name);
let sorted = t_sort(Some(column_name.clone()), None, &groups, &name)?;
let evaled = evaluate(&sorted, None, &name)?;
let reduced = reduce(&evaled, None, &name)?;
let maxima = map_max(&reduced, None, &name)?;
let percents = percentages(&reduced, maxima, &name)?;
match percents {
Value {
value: UntaggedValue::Table(datasets),
..
} => {
let mut idx = 0;
let column_names_supplied: Vec<_> = rest.iter().map(|f| f.item.clone()).collect();
let frequency_column_name = if column_names_supplied.is_empty() {
"frequency".to_string()
} else {
column_names_supplied[0].clone()
};
let column = (*column_name).clone();
let count_column_name = "count".to_string();
let count_shell_error = ShellError::labeled_error(
"Unable to load group count",
"unabled to load group count",
&name,
);
let mut count_values: Vec<u64> = Vec::new();
for table_entry in reduced.table_entries() {
match table_entry {
Value {
value: UntaggedValue::Table(list),
..
} => {
for i in list {
if let Ok(count) = i.value.clone().into_value(&name).as_u64() {
count_values.push(count);
} else {
return Err(count_shell_error);
}
}
}
_ => {
return Err(count_shell_error);
}
}
}
if let Value {
value: UntaggedValue::Table(start),
..
} = datasets.get(0).ok_or_else(|| {
ShellError::labeled_error(
"Unable to load dataset",
"unabled to load dataset",
&name,
)
})? {
let start = start.clone();
Ok(
futures::stream::iter(start.into_iter().map(move |percentage| {
let mut fact = TaggedDictBuilder::new(&name);
let value: Tagged<String> = group_labels
.get(idx)
.ok_or_else(|| {
ShellError::labeled_error(
"Unable to load group labels",
"unabled to load group labels",
&name,
)
})?
.clone();
fact.insert_value(
&column,
UntaggedValue::string(value.item).into_value(value.tag),
);
fact.insert_untagged(
&count_column_name,
UntaggedValue::int(count_values[idx]),
);
if let Value {
value: UntaggedValue::Primitive(Primitive::Int(ref num)),
ref tag,
} = percentage
{
let string = std::iter::repeat("*")
.take(num.to_i32().ok_or_else(|| {
ShellError::labeled_error(
"Expected a number",
"expected a number",
tag,
)
})? as usize)
.collect::<String>();
fact.insert_untagged(
&frequency_column_name,
UntaggedValue::string(string),
);
}
idx += 1;
ReturnSuccess::value(fact.into_value())
}))
.to_output_stream(),
)
} else {
Ok(OutputStream::empty())
}
}
_ => Ok(OutputStream::empty()),
}
}
fn percentages(values: &Value, max: Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let tag = tag.into();
let results: Value = match values {
Value {
value: UntaggedValue::Table(datasets),
..
} => {
let datasets: Vec<_> = datasets
.iter()
.map(|subsets| match subsets {
Value {
value: UntaggedValue::Table(data),
..
} => {
let data = data
.iter()
.map(|d| match d {
Value {
value: UntaggedValue::Primitive(Primitive::Int(n)),
..
} => {
let max = match &max {
Value {
value: UntaggedValue::Primitive(Primitive::Int(maxima)),
..
} => maxima.clone(),
_ => Zero::zero(),
};
let n = (n * 100) / max;
UntaggedValue::int(n).into_value(&tag)
}
_ => UntaggedValue::int(0).into_value(&tag),
})
.collect::<Vec<_>>();
UntaggedValue::Table(data).into_value(&tag)
}
_ => UntaggedValue::Table(vec![]).into_value(&tag),
})
.collect();
UntaggedValue::Table(datasets).into_value(&tag)
}
other => other.clone(),
};
Ok(results)
}
#[cfg(test)]
mod tests {
use super::Histogram;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Histogram {})
}
}

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,82 +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_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::String,
"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 insert(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (InsertArgs { column, value }, input) = args.process(&registry).await?;
Ok(input
.map(move |row| match row {
Value {
value: UntaggedValue::Row(_),
..
} => Ok(ReturnSuccess::Value(
row.insert_data_at_column_path(&column, value.clone())?,
)),
Value { tag, .. } => Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
tag,
)),
})
.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() {
match obj.replace_data_at_column_path(&field, default.clone()) {
Some(v) => Ok(v),
None => Err(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,130 +0,0 @@
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use bigdecimal::{FromPrimitive, Zero};
use nu_errors::ShellError;
use nu_protocol::{
hir::{convert_number_to_u64, Number, Operator},
Primitive, Signature, UntaggedValue, Value,
};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math avg"
}
fn signature(&self) -> Signature {
Signature::build("math avg")
}
fn usage(&self) -> &str {
"Finds the average of a list of numbers or tables"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
},
average,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the average of a list of numbers",
example: "echo [-50 100.0 25] | math avg",
result: Some(vec![UntaggedValue::decimal(25).into()]),
}]
}
}
pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let sum = reducer_for(Reduce::Summation);
let number = BigDecimal::from_usize(values.len()).ok_or_else(|| {
ShellError::labeled_error(
"could not convert to big decimal",
"could not convert to big decimal",
&name.span,
)
})?;
let total_rows = UntaggedValue::decimal(number);
let total = sum(Value::zero(), values.to_vec())?;
match total {
Value {
value: UntaggedValue::Primitive(Primitive::Filesize(num)),
..
} => {
let left = UntaggedValue::from(Primitive::Int(num.into()));
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => {
let number = Number::Decimal(result);
let number = convert_number_to_u64(&number);
Ok(UntaggedValue::filesize(number).into_value(name))
}
Ok(_) => Err(ShellError::labeled_error(
"could not calculate average of non-integer or unrelated types",
"source",
name,
)),
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
)),
}
}
Value {
value: UntaggedValue::Primitive(other),
..
} => {
let left = UntaggedValue::from(other);
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(value) => Ok(value.into_value(name)),
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
)),
}
}
_ => Err(ShellError::labeled_error(
"could not calculate average of non-integer or unrelated types",
"source",
name,
)),
}
}
#[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,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,87 +0,0 @@
use super::variance::variance;
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, UntaggedValue, Value};
use std::str::FromStr;
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math stddev"
}
fn signature(&self) -> Signature {
Signature::build("math stddev")
}
fn usage(&self) -> &str {
"Finds the stddev of a list of numbers or tables"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
},
stddev,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the stddev of a list of numbers",
example: "echo [1 2 3 4 5] | math stddev",
result: Some(vec![UntaggedValue::decimal(BigDecimal::from_str("1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641573").expect("Could not convert to decimal from string")).into()]),
}]
}
}
pub fn stddev(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let variance = variance(values, name)?.as_primitive()?;
let sqrt_var = match variance {
Primitive::Decimal(var) => var.sqrt(),
_ => {
return Err(ShellError::labeled_error(
"Could not take square root of variance",
"error occured here",
name.span,
))
}
};
match sqrt_var {
Some(stddev) => Ok(UntaggedValue::from(Primitive::Decimal(stddev)).into_value(name)),
None => Err(ShellError::labeled_error(
"Could not calculate stddev",
"error occured here",
name.span,
)),
}
}
#[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,106 +0,0 @@
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use nu_errors::ShellError;
use nu_protocol::{Dictionary, Signature, UntaggedValue, Value};
use num_traits::identities::Zero;
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math sum"
}
fn signature(&self) -> Signature {
Signature::build("math sum")
}
fn usage(&self) -> &str {
"Finds the sum of a list of numbers or tables"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
},
summation,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Sum a list of numbers",
example: "echo [1 2 3] | math sum",
result: Some(vec![UntaggedValue::int(6).into()]),
},
Example {
description: "Get the disk usage for the current directory",
example: "ls --all --du | get size | math sum",
result: None,
},
]
}
}
pub fn summation(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let sum = reducer_for(Reduce::Summation);
if values.iter().all(|v| v.is_primitive()) {
Ok(sum(Value::zero(), values.to_vec())?)
} else {
let mut column_values = IndexMap::new();
for value in values {
if let UntaggedValue::Row(row_dict) = value.value.clone() {
for (key, value) in row_dict.entries.iter() {
column_values
.entry(key.clone())
.and_modify(|v: &mut Vec<Value>| v.push(value.clone()))
.or_insert(vec![value.clone()]);
}
};
}
let mut column_totals = IndexMap::new();
for (col_name, col_vals) in column_values {
let sum = sum(Value::zero(), col_vals)?;
column_totals.insert(col_name, sum);
}
Ok(UntaggedValue::Row(Dictionary {
entries: column_totals,
})
.into_value(name))
}
}
#[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,162 +0,0 @@
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::data::value::compute_values;
use crate::prelude::*;
use bigdecimal::{FromPrimitive, Zero};
use nu_errors::ShellError;
use nu_protocol::{hir::Operator, Primitive, Signature, UntaggedValue, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math variance"
}
fn signature(&self) -> Signature {
Signature::build("math variance")
}
fn usage(&self) -> &str {
"Finds the variance of a list of numbers or tables"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
},
variance,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the variance of a list of numbers",
example: "echo [1 2 3 4 5] | math variance",
result: Some(vec![UntaggedValue::decimal(2).into()]),
}]
}
}
fn sum_of_squares(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let n = BigDecimal::from_usize(values.len()).ok_or_else(|| {
ShellError::labeled_error(
"could not convert to big decimal",
"could not convert to big decimal",
&name.span,
)
})?;
let mut sum_x = Value::zero();
let mut sum_x2 = Value::zero();
for value in values {
let v = match value {
Value {
value: UntaggedValue::Primitive(Primitive::Filesize(num)),
..
} => {
UntaggedValue::from(Primitive::Int(num.clone().into()))
},
Value {
value: UntaggedValue::Primitive(num),
..
} => {
UntaggedValue::from(num.clone())
},
_ => {
return Err(ShellError::labeled_error(
"Attempted to compute the sum of squared values of a value that cannot be summed or squared.",
"value appears here",
value.tag.span,
))
}
};
let v_squared = compute_values(Operator::Multiply, &v, &v);
match v_squared {
// X^2
Ok(x2) => sum_x2 = sum_x2 + x2.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned(value.tag.span),
right_type.spanned(value.tag.span),
))
}
};
sum_x = sum_x + v.into_untagged_value();
}
let sum_x_squared = match compute_values(Operator::Multiply, &sum_x, &sum_x) {
Ok(v) => v.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
))
}
};
let sum_x_squared_div_n = match compute_values(Operator::Divide, &sum_x_squared, &n.into()) {
Ok(v) => v.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
))
}
};
let ss = match compute_values(Operator::Minus, &sum_x2, &sum_x_squared_div_n) {
Ok(v) => v.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
))
}
};
Ok(ss)
}
pub fn variance(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let ss = sum_of_squares(values, name)?;
let n = BigDecimal::from_usize(values.len()).ok_or_else(|| {
ShellError::labeled_error(
"could not convert to big decimal",
"could not convert to big decimal",
&name.span,
)
})?;
let variance = compute_values(Operator::Divide, &ss, &n.into());
match variance {
Ok(value) => Ok(value.into_value(name)),
Err((_, _)) => Err(ShellError::labeled_error(
"could not calculate variance of non-integer or unrelated types",
"source",
name,
)),
}
}
#[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,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_is_last(call_info.args.is_last);
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,260 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
use nu_source::Tagged;
use nu_value_ext::as_string;
pub struct SplitBy;
#[derive(Deserialize)]
pub struct SplitByArgs {
column_name: Option<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for SplitBy {
fn name(&self) -> &str {
"split-by"
}
fn signature(&self) -> Signature {
Signature::build("split-by").optional(
"column_name",
SyntaxShape::String,
"the name of the column within the nested table to split by",
)
}
fn usage(&self) -> &str {
"Creates a new table with the data from the inner tables split by the column given."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
split_by(args, registry).await
}
}
pub async fn split_by(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let (SplitByArgs { column_name }, input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
if values.len() > 1 || values.is_empty() {
return Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
name,
));
}
match split(&column_name, &values[0], &name) {
Ok(splits) => Ok(OutputStream::one(ReturnSuccess::value(splits))),
Err(err) => Err(err),
}
}
enum Grouper {
ByColumn(Option<Tagged<String>>),
}
pub fn split(
column_name: &Option<Tagged<String>>,
values: &Value,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let name = tag.into();
let grouper = if let Some(column_name) = column_name {
Grouper::ByColumn(Some(column_name.clone()))
} else {
Grouper::ByColumn(None)
};
match grouper {
Grouper::ByColumn(Some(column_name)) => {
let block = Box::new(move |_, row: &Value| {
match row.get_data_by_key(column_name.borrow_spanned()) {
Some(group_key) => Ok(as_string(&group_key)?),
None => Err(suggestions(column_name.borrow_tagged(), &row)),
}
});
crate::utils::data::split(&values, &Some(block), &name)
}
Grouper::ByColumn(None) => {
let block = Box::new(move |_, row: &Value| match as_string(row) {
Ok(group_key) => Ok(group_key),
Err(reason) => Err(reason),
});
crate::utils::data::split(&values, &Some(block), &name)
}
}
}
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
let possibilities = for_value.data_descriptors();
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|x| (natural::distance::levenshtein_distance(x, &tried), x))
.collect();
possible_matches.sort();
if !possible_matches.is_empty() {
ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", possible_matches[0].1),
tried.tag(),
)
} else {
ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
tried.tag(),
)
}
}
#[cfg(test)]
mod tests {
use super::split;
use crate::commands::group_by::group;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value};
use nu_source::*;
fn string(input: impl Into<String>) -> Value {
UntaggedValue::string(input.into()).into_untagged_value()
}
fn row(entries: IndexMap<String, Value>) -> Value {
UntaggedValue::row(entries).into_untagged_value()
}
fn table(list: &[Value]) -> Value {
UntaggedValue::table(list).into_untagged_value()
}
fn nu_releases_grouped_by_date() -> Result<Value, ShellError> {
let key = Some(String::from("date").tagged_unknown());
let sample = table(&nu_releases_committers());
group(&key, &sample, Tag::unknown())
}
fn nu_releases_committers() -> Vec<Value> {
vec![
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")},
),
row(
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")},
),
row(
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")},
),
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("Sept 24-2019")},
),
row(
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")},
),
row(
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("Sept 24-2019")},
),
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")},
),
row(
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("Sept 24-2019")},
),
row(
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")},
),
]
}
#[test]
fn splits_inner_tables_by_key() -> Result<(), ShellError> {
let for_key = Some(String::from("country").tagged_unknown());
assert_eq!(
split(&for_key, &nu_releases_grouped_by_date()?, Tag::unknown())?,
UntaggedValue::row(indexmap! {
"EC".into() => row(indexmap! {
"August 23-2019".into() => table(&[
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")})
]),
"Sept 24-2019".into() => table(&[
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("Sept 24-2019")})
]),
"October 10-2019".into() => table(&[
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")})
])
}),
"NZ".into() => row(indexmap! {
"August 23-2019".into() => table(&[
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")})
]),
"Sept 24-2019".into() => table(&[
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("Sept 24-2019")})
]),
"October 10-2019".into() => table(&[
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")})
])
}),
"US".into() => row(indexmap! {
"August 23-2019".into() => table(&[
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")})
]),
"Sept 24-2019".into() => table(&[
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("Sept 24-2019")})
]),
"October 10-2019".into() => table(&[
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")})
])
})
}).into_untagged_value()
);
Ok(())
}
#[test]
fn errors_if_key_within_some_inner_table_is_missing() {
let for_key = Some(String::from("country").tagged_unknown());
let nu_releases = row(indexmap! {
"August 23-2019".into() => table(&[
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")})
]),
"Sept 24-2019".into() => table(&[
row(indexmap!{"name".into() => UntaggedValue::string("JT").into_value(Tag::from(Span::new(5,10))), "date".into() => string("Sept 24-2019")})
]),
"October 10-2019".into() => table(&[
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")})
])
});
assert!(split(&for_key, &nu_releases, Tag::from(Span::new(5, 10))).is_err());
}
#[test]
fn examples_work_as_expected() {
use super::SplitBy;
use crate::examples::test as test_examples;
test_examples(SplitBy {})
}
}

View File

@ -1,56 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str collect"
}
fn signature(&self) -> Signature {
Signature::build("str collect")
}
fn usage(&self) -> &str {
"collects a list of strings into a string"
}
async fn run(
&self,
args: CommandArgs,
_registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let output = args
.input
.collect_string(args.call_info.name_tag.clone())
.await?;
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output.item).into_value(output.tag),
)))
}
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")]),
}]
}
}
#[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,31 +0,0 @@
mod capitalize;
mod collect;
mod command;
mod downcase;
mod find_replace;
mod from;
mod length;
mod reverse;
mod set;
mod substring;
mod to_datetime;
mod to_decimal;
mod to_integer;
mod trim;
mod upcase;
pub use capitalize::SubCommand as StrCapitalize;
pub use collect::SubCommand as StrCollect;
pub use command::Command as Str;
pub use downcase::SubCommand as StrDowncase;
pub use find_replace::SubCommand as StrFindReplace;
pub use from::SubCommand as StrFrom;
pub use length::SubCommand as StrLength;
pub use reverse::SubCommand as StrReverse;
pub use set::SubCommand as StrSet;
pub use substring::SubCommand as StrSubstring;
pub use to_datetime::SubCommand as StrToDatetime;
pub use to_decimal::SubCommand as StrToDecimal;
pub use to_integer::SubCommand as StrToInteger;
pub use trim::SubCommand as StrTrim;
pub use upcase::SubCommand as StrUpcase;

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,175 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
ColumnPath, Primitive, ReturnSuccess, ShellTypeName, Signature, SyntaxShape, UntaggedValue,
Value,
};
use nu_source::{Tag, Tagged};
use nu_value_ext::ValueExt;
use chrono::DateTime;
#[derive(Deserialize)]
struct Arguments {
format: Option<Tagged<String>>,
rest: Vec<ColumnPath>,
}
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str to-datetime"
}
fn signature(&self) -> Signature {
Signature::build("str to-datetime")
.named(
"format",
SyntaxShape::String,
"Specify date and time formatting",
Some('f'),
)
.rest(
SyntaxShape::ColumnPath,
"optionally convert text into datetime by column paths",
)
}
fn usage(&self) -> &str {
"converts text into datetime"
}
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 datetime",
example: "echo '16.11.1984 8:00 am +0000' | str to-datetime",
result: None,
}]
}
}
#[derive(Clone)]
struct DatetimeFormat(String);
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (Arguments { format, rest }, input) = args.process(&registry).await?;
let column_paths: Vec<_> = rest;
let options = if let Some(Tagged { item: fmt, .. }) = format {
DatetimeFormat(fmt)
} else {
DatetimeFormat(String::from("%d.%m.%Y %H:%M %P %z"))
};
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: &DatetimeFormat,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
match &input.value {
UntaggedValue::Primitive(Primitive::Line(s))
| UntaggedValue::Primitive(Primitive::String(s)) => {
let dt = &options.0;
let out = match DateTime::parse_from_str(s, dt) {
Ok(d) => UntaggedValue::date(d),
Err(reason) => {
return Err(ShellError::labeled_error(
"could not parse as datetime",
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, DatetimeFormat, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_protocol::{Primitive, UntaggedValue};
use nu_source::Tag;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
#[test]
fn takes_a_date_format() {
let date_str = string("16.11.1984 8:00 am +0000");
let fmt_options = DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string());
let actual = action(&date_str, &fmt_options, Tag::unknown()).unwrap();
match actual.value {
UntaggedValue::Primitive(Primitive::Date(_)) => {}
_ => panic!("Didn't convert to date"),
}
}
#[test]
fn communicates_parsing_error_given_an_invalid_datetimelike_string() {
let date_str = string("16.11.1984 8:00 am Oops0000");
let fmt_options = DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string());
let actual = action(&date_str, &fmt_options, Tag::unknown());
assert!(actual.is_err());
}
}

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,168 +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, Tagged};
use nu_value_ext::ValueExt;
#[derive(Deserialize)]
struct Arguments {
rest: Vec<ColumnPath>,
#[serde(rename(deserialize = "char"))]
char_: Option<Tagged<char>>,
}
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str trim"
}
fn signature(&self) -> Signature {
Signature::build("str trim")
.rest(
SyntaxShape::ColumnPath,
"optionally trim text by column paths",
)
.named(
"char",
SyntaxShape::String,
"character to trim (default: whitespace)",
Some('c'),
)
}
fn usage(&self) -> &str {
"trims text"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Trim whitespace",
example: "echo 'Nu shell ' | str trim",
result: Some(vec![Value::from("Nu shell")]),
},
Example {
description: "Trim a specific character",
example: "echo '=== Nu shell ===' | str trim -c '=' | str trim",
result: Some(vec![Value::from("Nu shell")]),
},
]
}
}
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (Arguments { rest, char_ }, input) = args.process(&registry).await?;
let column_paths: Vec<_> = rest;
let to_trim = char_.map(|tagged| tagged.item);
Ok(input
.map(move |v| {
if column_paths.is_empty() {
ReturnSuccess::value(action(&v, v.tag(), to_trim)?)
} 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(), to_trim)),
)?;
}
ReturnSuccess::value(ret)
}
})
.to_output_stream())
}
fn action(input: &Value, tag: impl Into<Tag>, char_: Option<char>) -> Result<Value, ShellError> {
match &input.value {
UntaggedValue::Primitive(Primitive::Line(s))
| UntaggedValue::Primitive(Primitive::String(s)) => {
Ok(UntaggedValue::string(match char_ {
None => String::from(s.trim()),
Some(ch) => trim_char(s, ch, true, true),
})
.into_value(tag))
}
other => {
let got = format!("got {}", other.type_name());
Err(ShellError::labeled_error(
"value is not string",
got,
tag.into().span,
))
}
}
}
pub fn trim_char(from: &str, to_trim: char, leading: bool, trailing: bool) -> String {
let mut trimmed = String::from("");
let mut backlog = String::from("");
let mut at_left = true;
from.chars().for_each(|ch| match ch {
c if c == to_trim => {
if !(leading && at_left) {
if trailing {
backlog.push(c)
} else {
trimmed.push(c)
}
}
}
other => {
at_left = false;
if trailing {
trimmed.push_str(backlog.as_str());
backlog = String::from("");
}
trimmed.push(other);
}
});
trimmed
}
#[cfg(test)]
mod tests {
use super::{action, 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 trims() {
let word = string("andres ");
let expected = string("andres");
let actual = action(&word, Tag::unknown(), None).unwrap();
assert_eq!(actual, expected);
}
}

View File

@ -1,596 +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::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use nu_source::AnchorLocation;
use regex::Regex;
use std::collections::HashMap;
pub struct ToHTML;
#[derive(Deserialize)]
pub struct ToHTMLArgs {
html_color: bool,
no_color: bool,
dark_bg: bool,
use_campbell: bool,
}
#[async_trait]
impl WholeStreamCommand for ToHTML {
fn name(&self) -> &str {
"to html"
}
fn signature(&self) -> Signature {
Signature::build("to html")
.switch("html_color", "change ansi colors to html colors", Some('t'))
.switch("no_color", "remove all ansi colors in output", Some('n'))
.switch(
"dark_bg",
"indicate your background color is a darker color",
Some('d'),
)
.switch(
"use_campbell",
"use microsoft's windows terminal color scheme named campell",
Some('c'),
)
}
fn usage(&self) -> &str {
"Convert table into simple HTML"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
to_html(args, registry).await
}
}
fn get_campbell_theme(is_dark: bool) -> HashMap<&'static str, String> {
// for reference here is Microsoft's Campbell Theme
// taken from here
// https://docs.microsoft.com/en-us/windows/terminal/customize-settings/color-schemes
let mut hm: HashMap<&str, String> = HashMap::new();
if is_dark {
hm.insert("bold_black", "#767676".to_string());
hm.insert("bold_red", "#E74856".to_string());
hm.insert("bold_green", "#16C60C".to_string());
hm.insert("bold_yellow", "#F9F1A5".to_string());
hm.insert("bold_blue", "#3B78FF".to_string());
hm.insert("bold_magenta", "#B4009E".to_string());
hm.insert("bold_cyan", "#61D6D6".to_string());
hm.insert("bold_white", "#F2F2F2".to_string());
hm.insert("black", "#0C0C0C".to_string());
hm.insert("red", "#C50F1F".to_string());
hm.insert("green", "#13A10E".to_string());
hm.insert("yellow", "#C19C00".to_string());
hm.insert("blue", "#0037DA".to_string());
hm.insert("magenta", "#881798".to_string());
hm.insert("cyan", "#3A96DD".to_string());
hm.insert("white", "#CCCCCC".to_string());
hm.insert("background", "#0C0C0C".to_string());
hm.insert("foreground", "#CCCCCC".to_string());
} else {
hm.insert("bold_black", "#767676".to_string());
hm.insert("bold_red", "#E74856".to_string());
hm.insert("bold_green", "#16C60C".to_string());
hm.insert("bold_yellow", "#F9F1A5".to_string());
hm.insert("bold_blue", "#3B78FF".to_string());
hm.insert("bold_magenta", "#B4009E".to_string());
hm.insert("bold_cyan", "#61D6D6".to_string());
hm.insert("bold_white", "#F2F2F2".to_string());
hm.insert("black", "#0C0C0C".to_string());
hm.insert("red", "#C50F1F".to_string());
hm.insert("green", "#13A10E".to_string());
hm.insert("yellow", "#C19C00".to_string());
hm.insert("blue", "#0037DA".to_string());
hm.insert("magenta", "#881798".to_string());
hm.insert("cyan", "#3A96DD".to_string());
hm.insert("white", "#CCCCCC".to_string());
hm.insert("background", "#CCCCCC".to_string());
hm.insert("foreground", "#0C0C0C".to_string());
}
hm
}
fn get_default_theme(is_dark: bool) -> HashMap<&'static str, String> {
let mut hm: HashMap<&str, String> = HashMap::new();
if is_dark {
hm.insert("bold_black", "black".to_string());
hm.insert("bold_red", "red".to_string());
hm.insert("bold_green", "green".to_string());
hm.insert("bold_yellow", "yellow".to_string());
hm.insert("bold_blue", "blue".to_string());
hm.insert("bold_magenta", "magenta".to_string());
hm.insert("bold_cyan", "cyan".to_string());
hm.insert("bold_white", "white".to_string());
hm.insert("black", "black".to_string());
hm.insert("red", "red".to_string());
hm.insert("green", "green".to_string());
hm.insert("yellow", "yellow".to_string());
hm.insert("blue", "blue".to_string());
hm.insert("magenta", "magenta".to_string());
hm.insert("cyan", "cyan".to_string());
hm.insert("white", "white".to_string());
hm.insert("background", "black".to_string());
hm.insert("foreground", "white".to_string());
} else {
hm.insert("bold_black", "black".to_string());
hm.insert("bold_red", "red".to_string());
hm.insert("bold_green", "green".to_string());
hm.insert("bold_yellow", "#717100".to_string());
hm.insert("bold_blue", "blue".to_string());
hm.insert("bold_magenta", "#c800c8".to_string());
hm.insert("bold_cyan", "#037979".to_string());
hm.insert("bold_white", "white".to_string());
hm.insert("black", "black".to_string());
hm.insert("red", "red".to_string());
hm.insert("green", "green".to_string());
hm.insert("yellow", "#717100".to_string());
hm.insert("blue", "blue".to_string());
hm.insert("magenta", "#c800c8".to_string());
hm.insert("cyan", "#037979".to_string());
hm.insert("white", "white".to_string());
hm.insert("background", "white".to_string());
hm.insert("foreground", "black".to_string());
}
hm
}
fn get_colors(is_dark: bool, use_campbell: bool) -> HashMap<&'static str, String> {
// Currently now using bold_white and bold_black.
// This is not theming but it is kind of a start. The intent here is to use the
// regular terminal colors which appear on black for most people and are very
// high contrast. But when there's a light background, use something that works
// better for it.
if use_campbell {
get_campbell_theme(is_dark)
} else {
get_default_theme(is_dark)
}
}
async fn to_html(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name_tag = args.call_info.name_tag.clone();
let (
ToHTMLArgs {
html_color,
no_color,
dark_bg,
use_campbell,
},
input,
) = args.process(&registry).await?;
let input: Vec<Value> = input.collect().await;
let headers = nu_protocol::merge_descriptors(&input);
let mut output_string = "<html>".to_string();
let mut regex_hm: HashMap<u32, (&str, String)> = HashMap::new();
let color_hm = get_colors(dark_bg, use_campbell);
// change the color of the page
output_string.push_str(&format!(
r"<style>body {{ background-color:{};color:{}; }}</style><body>",
color_hm
.get("background")
.expect("Error getting background color"),
color_hm
.get("foreground")
.expect("Error getting foreground color")
));
// Add grid lines to html
// let mut output_string = "<html><head><style>".to_string();
// output_string.push_str("table, th, td { border: 2px solid black; border-collapse: collapse; padding: 10px; }");
// output_string.push_str("</style></head><body>");
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
// output_string.push_str("<table>");
// change the color of tables
output_string.push_str(&format!(
r"<table style='background-color:{};color:{};'>",
color_hm
.get("background")
.expect("Error getting background color"),
color_hm
.get("foreground")
.expect("Error getting foreground color")
));
output_string.push_str("<tr>");
for header in &headers {
output_string.push_str("<th>");
output_string.push_str(&htmlescape::encode_minimal(&header));
output_string.push_str("</th>");
}
output_string.push_str("</tr>");
}
for row in input {
match row.value {
UntaggedValue::Primitive(Primitive::Binary(b)) => {
// This might be a bit much, but it's fun :)
match row.tag.anchor {
Some(AnchorLocation::Url(f)) | Some(AnchorLocation::File(f)) => {
let extension = f.split('.').last().map(String::from);
match extension {
Some(s)
if ["png", "jpg", "bmp", "gif", "tiff", "jpeg"]
.contains(&s.to_lowercase().as_str()) =>
{
output_string.push_str("<img src=\"data:image/");
output_string.push_str(&s);
output_string.push_str(";base64,");
output_string.push_str(&base64::encode(&b));
output_string.push_str("\">");
}
_ => {
let output = pretty_hex::pretty_hex(&b);
output_string.push_str("<pre>");
output_string.push_str(&output);
output_string.push_str("</pre>");
}
}
}
_ => {
let output = pretty_hex::pretty_hex(&b);
output_string.push_str("<pre>");
output_string.push_str(&output);
output_string.push_str("</pre>");
}
}
}
UntaggedValue::Primitive(Primitive::String(ref b)) => {
// This might be a bit much, but it's fun :)
match row.tag.anchor {
Some(AnchorLocation::Url(f)) | Some(AnchorLocation::File(f)) => {
let extension = f.split('.').last().map(String::from);
match extension {
Some(s) if s.to_lowercase() == "svg" => {
output_string.push_str("<img src=\"data:image/svg+xml;base64,");
output_string.push_str(&base64::encode(&b.as_bytes()));
output_string.push_str("\">");
continue;
}
_ => {}
}
}
_ => {}
}
output_string.push_str(
&(htmlescape::encode_minimal(&format_leaf(&row.value).plain_string(100_000))
.replace("\n", "<br>")),
);
}
UntaggedValue::Row(row) => {
output_string.push_str("<tr>");
for header in &headers {
let data = row.get_data(header);
output_string.push_str("<td>");
output_string.push_str(&format_leaf(data.borrow()).plain_string(100_000));
output_string.push_str("</td>");
}
output_string.push_str("</tr>");
}
p => {
output_string.push_str(
&(htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000))
.replace("\n", "<br>")),
);
}
}
}
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
output_string.push_str("</table>");
}
output_string.push_str("</body></html>");
// Check to see if we want to remove all color or change ansi to html colors
if html_color {
setup_html_color_regexes(&mut regex_hm, dark_bg, use_campbell);
output_string = run_regexes(&regex_hm, &output_string);
} else if no_color {
setup_no_color_regexes(&mut regex_hm);
output_string = run_regexes(&regex_hm, &output_string);
}
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output_string).into_value(name_tag),
)))
}
fn setup_html_color_regexes(
hash: &mut HashMap<u32, (&'static str, String)>,
is_dark: bool,
use_campbell: bool,
) {
let color_hm = get_colors(is_dark, use_campbell);
// All the bold colors
hash.insert(
0,
(
r"(?P<reset>\[0m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
// Reset the text color, normal weight font
format!(
r"<span style='color:{};font-weight:normal;'>$word</span>",
color_hm
.get("foreground")
.expect("Error getting reset text color")
),
),
);
hash.insert(
1,
(
// Bold Black
r"(?P<bb>\[1;30m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
format!(
r"<span style='color:{};font-weight:bold;'>$word</span>",
color_hm
.get("foreground")
.expect("Error getting bold black text color")
),
),
);
hash.insert(
2,
(
// Bold Red
r"(?P<br>\[1;31m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
format!(
r"<span style='color:{};font-weight:bold;'>$word</span>",
color_hm
.get("bold_red")
.expect("Error getting bold red text color"),
),
),
);
hash.insert(
3,
(
// Bold Green
r"(?P<bg>\[1;32m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
format!(
r"<span style='color:{};font-weight:bold;'>$word</span>",
color_hm
.get("bold_green")
.expect("Error getting bold green text color"),
),
),
);
hash.insert(
4,
(
// Bold Yellow
r"(?P<by>\[1;33m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
format!(
r"<span style='color:{};font-weight:bold;'>$word</span>",
color_hm
.get("bold_yellow")
.expect("Error getting bold yellow text color"),
),
),
);
hash.insert(
5,
(
// Bold Blue
r"(?P<bu>\[1;34m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
format!(
r"<span style='color:{};font-weight:bold;'>$word</span>",
color_hm
.get("bold_blue")
.expect("Error getting bold blue text color"),
),
),
);
hash.insert(
6,
(
// Bold Magenta
r"(?P<bm>\[1;35m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
format!(
r"<span style='color:{};font-weight:bold;'>$word</span>",
color_hm
.get("bold_magenta")
.expect("Error getting bold magenta text color"),
),
),
);
hash.insert(
7,
(
// Bold Cyan
r"(?P<bc>\[1;36m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
format!(
r"<span style='color:{};font-weight:bold;'>$word</span>",
color_hm
.get("bold_cyan")
.expect("Error getting bold cyan text color"),
),
),
);
hash.insert(
8,
(
// Bold White
// Let's change this to black since the html background
// is white. White on white = no bueno.
r"(?P<bw>\[1;37m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
format!(
r"<span style='color:{};font-weight:bold;'>$word</span>",
color_hm
.get("foreground")
.expect("Error getting bold bold white text color"),
),
),
);
// All the normal colors
hash.insert(
9,
(
// Black
r"(?P<b>\[30m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
format!(
r"<span style='color:{};'>$word</span>",
color_hm
.get("foreground")
.expect("Error getting black text color"),
),
),
);
hash.insert(
10,
(
// Red
r"(?P<r>\[31m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
format!(
r"<span style='color:{};'>$word</span>",
color_hm.get("red").expect("Error getting red text color"),
),
),
);
hash.insert(
11,
(
// Green
r"(?P<g>\[32m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
format!(
r"<span style='color:{};'>$word</span>",
color_hm
.get("green")
.expect("Error getting green text color"),
),
),
);
hash.insert(
12,
(
// Yellow
r"(?P<y>\[33m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
format!(
r"<span style='color:{};'>$word</span>",
color_hm
.get("yellow")
.expect("Error getting yellow text color"),
),
),
);
hash.insert(
13,
(
// Blue
r"(?P<u>\[34m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
format!(
r"<span style='color:{};'>$word</span>",
color_hm.get("blue").expect("Error getting blue text color"),
),
),
);
hash.insert(
14,
(
// Magenta
r"(?P<m>\[35m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
format!(
r"<span style='color:{};'>$word</span>",
color_hm
.get("magenta")
.expect("Error getting magenta text color"),
),
),
);
hash.insert(
15,
(
// Cyan
r"(?P<c>\[36m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
format!(
r"<span style='color:{};'>$word</span>",
color_hm.get("cyan").expect("Error getting cyan text color"),
),
),
);
hash.insert(
16,
(
// White
// Let's change this to black since the html background
// is white. White on white = no bueno.
r"(?P<w>\[37m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
format!(
r"<span style='color:{};'>$word</span>",
color_hm
.get("foreground")
.expect("Error getting white text color"),
),
),
);
}
fn setup_no_color_regexes(hash: &mut HashMap<u32, (&'static str, String)>) {
// We can just use one regex here because we're just removing ansi sequences
// and not replacing them with html colors.
// attribution: https://stackoverflow.com/questions/14693701/how-can-i-remove-the-ansi-escape-sequences-from-a-string-in-python
hash.insert(
0,
(
r"(?:\x1B[@-Z\\-_]|[\x80-\x9A\x9C-\x9F]|(?:\x1B\[|\x9B)[0-?]*[ -/]*[@-~])",
r"$name_group_doesnt_exist".to_string(),
),
);
}
fn run_regexes(hash: &HashMap<u32, (&'static str, String)>, contents: &str) -> String {
let mut working_string = contents.to_owned();
let hash_count: u32 = hash.len() as u32;
for n in 0..hash_count {
let value = hash.get(&n).expect("error getting hash at index");
//println!("{},{}", value.0, value.1);
let re = Regex::new(value.0).expect("problem with color regex");
let after = re.replace_all(&working_string, &value.1[..]).to_string();
working_string = after.clone();
}
working_string
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(ToHTML {})
}
}

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 replacement = replacement.clone();
let scope = scope.clone();
let context = context.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,63 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
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::new();
indexmap.insert(
"version".to_string(),
UntaggedValue::string(clap::crate_version!()).into_value(&tag),
);
let value = UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag);
Ok(OutputStream::one(value))
}
#[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,26 +0,0 @@
use nu_errors::ShellError;
#[derive(Debug, Eq, PartialEq)]
pub struct Suggestion {
pub display: String,
pub replacement: String,
}
pub struct Context<'a>(pub &'a rustyline::Context<'a>);
impl<'a> AsRef<rustyline::Context<'a>> for Context<'a> {
fn as_ref(&self) -> &rustyline::Context<'a> {
self.0
}
}
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::{block, classify_block, lex, 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, _) = 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: registry.clone(),
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(registry)?,
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
raw_input: String::default(),
})
}
#[cfg(not(windows))]
{
Ok(Context {
registry: registry.clone(),
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(registry)?,
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,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;
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>,
full: 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 full {
#[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 full || 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 full {
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 full {
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::*;
@ -12,6 +11,8 @@ use std::{
path::{Path, PathBuf},
};
//Tests reside in /nushell/tests/shell/pipeline/commands/internal.rs
type EnvKey = String;
type EnvVal = OsString;
#[derive(Debug, Default)]
@ -82,14 +83,11 @@ impl DirectorySpecificEnvironment {
//We track which keys we set as we go up the directory hierarchy, so that we don't overwrite a value we set in a subdir.
let mut added_keys = IndexSet::new();
//We note which directories we pass so we can clear unvisited dirs later.
let mut seen_directories = IndexSet::new();
//Add all .nu-envs until we reach a dir which we have already added, or we reached the root.
let mut new_visited_dirs = IndexSet::new();
let mut popped = true;
while popped && !self.visited_dirs.contains(&dir) {
while popped {
let nu_env_file = dir.join(".nu-env");
if nu_env_file.exists() {
if nu_env_file.exists() && !self.visited_dirs.contains(&dir) {
let nu_env_doc = self.toml_if_trusted(&nu_env_file)?;
//add regular variables from the [env section]
@ -98,7 +96,6 @@ impl DirectorySpecificEnvironment {
self.maybe_add_key(&mut added_keys, &dir, &env_key, &env_val);
}
}
self.visited_dirs.insert(dir.clone());
//Add variables that need to evaluate scripts to run, from [scriptvars] section
if let Some(sv) = nu_env_doc.scriptvars {
@ -114,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)?;
}
}
@ -122,14 +119,14 @@ impl DirectorySpecificEnvironment {
self.exitscripts.insert(dir.clone(), es);
}
}
seen_directories.insert(dir.clone());
new_visited_dirs.insert(dir.clone());
popped = dir.pop();
}
//Time to clear out vars set by directories that we have left.
let mut new_vars = IndexMap::new();
for (dir, dirmap) in self.added_vars.drain(..) {
if seen_directories.contains(&dir) {
if new_visited_dirs.contains(&dir) {
new_vars.insert(dir, dirmap);
} else {
for (k, v) in dirmap {
@ -142,26 +139,19 @@ impl DirectorySpecificEnvironment {
}
}
let mut new_visited = IndexSet::new();
for dir in self.visited_dirs.drain(..) {
if seen_directories.contains(&dir) {
new_visited.insert(dir);
}
}
//Run exitscripts, can not be done in same loop as new vars as some files can contain only exitscripts
let mut new_exitscripts = IndexMap::new();
for (dir, scripts) in self.exitscripts.drain(..) {
if seen_directories.contains(&dir) {
if new_visited_dirs.contains(&dir) {
new_exitscripts.insert(dir, scripts);
} else {
for s in scripts {
run(s.as_str())?;
run(s.as_str(), Some(&dir))?;
}
}
}
self.visited_dirs = new_visited;
self.visited_dirs = new_visited_dirs;
self.exitscripts = new_exitscripts;
self.added_vars = new_vars;
self.last_seen_directory = current_dir()?;
@ -224,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") {
Command::new("cmd").args(&["/C", cmd]).output()?
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,38 +18,64 @@ 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 reload(&mut self) {
self.config.reload();
let mut environment = self.env.lock();
environment.morph(&*self.config);
pub fn did_config_change(&mut self) -> bool {
let config = self.config.lock();
config.is_modified().unwrap_or(false)
}
pub fn sync_env_vars(&mut self, ctx: &mut Context) {
pub fn reload(&mut self) {
let mut config = self.config.lock();
config.reload();
let mut environment = self.env.lock();
environment.morph(&*config);
}
pub fn autoenv(&self, ctx: &mut EvaluationContext) -> Result<(), ShellError> {
let mut environment = self.env.lock();
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 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" {
@ -78,7 +104,7 @@ impl EnvironmentSyncer {
}
}
pub fn sync_path_vars(&mut self, ctx: &mut Context) {
pub fn sync_path_vars(&mut self, ctx: &mut EvaluationContext) {
let mut environment = self.env.lock();
if environment.path().is_some() {
@ -110,7 +136,7 @@ impl EnvironmentSyncer {
}
#[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 +145,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 +153,10 @@ 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_engine::Env;
use nu_errors::ShellError;
use nu_test_support::fs::Stub::FileWithContent;
use nu_test_support::playground::Playground;
@ -138,11 +164,118 @@ 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);
{
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!(!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);
expected.insert("USER".to_string(), "NUNO".to_string());
{
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));
}
}
});
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(
@ -240,8 +373,8 @@ mod tests {
#[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 ctx = basic_evaluation_context()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let mut expected = IndexMap::new();
expected.insert(
@ -316,8 +449,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"),
@ -403,8 +536,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"),

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, 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::empty();
let mut block = block.clone();
block.set_is_last(false);
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,44 +0,0 @@
use crate::prelude::*;
pub fn current_branch() -> Option<String> {
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
let use_starship = match config.get("use_starship") {
Some(b) => match b.as_bool() {
Ok(b) => b,
_ => false,
},
_ => 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,49 +39,24 @@ 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;
pub trait FromInputStream {
fn from_input_stream(self) -> OutputStream;
@ -120,26 +73,6 @@ 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(),
},
}))
}
}
pub trait ToOutputStream {
fn to_output_stream(self) -> OutputStream;
}
@ -155,16 +88,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,293 +1,157 @@
use crate::context::CommandRegistry;
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;
use crate::data::config;
use crate::prelude::*;
use derive_new::new;
#[cfg(all(windows, feature = "ichwh"))]
use ichwh::IchwhError;
#[cfg(all(windows, feature = "ichwh"))]
use ichwh::IchwhResult;
use indexmap::set::IndexSet;
use rustyline::completion::{Completer, FilenameCompleter};
use std::fs::{read_dir, DirEntry};
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::borrow::Cow;
#[derive(new)]
pub(crate) struct NuCompleter {
pub file_completer: FilenameCompleter,
pub commands: CommandRegistry,
pub homedir: Option<PathBuf>,
}
pub(crate) struct NuCompleter {}
#[derive(PartialEq, Eq, Debug)]
enum ReplacementLocation {
Command,
Other,
}
impl NuCompleter {}
impl NuCompleter {
pub fn complete(
&self,
line: &str,
pos: usize,
context: &rustyline::Context,
) -> rustyline::Result<(usize, Vec<rustyline::completion::Pair>)> {
let commands: Vec<String> = self.commands.names();
context: &completion::CompletionContext,
) -> (usize, Vec<Suggestion>) {
use completion::engine::LocationType;
let line_chars: Vec<_> = line[..pos].chars().collect();
let nu_context: &EvaluationContext = context.as_ref();
let (replace_pos, replace_loc) = self.get_replace_pos(line, pos);
nu_context.scope.enter_scope();
let (block, _) = nu_parser::parse(line, 0, &nu_context.scope);
nu_context.scope.exit_scope();
let mut completions;
let locations = completion::engine::completion_location(line, &block, pos);
// See if we're a flag
if pos > 0 && replace_pos < line_chars.len() && line_chars[replace_pos] == '-' {
if let Ok(lite_block) = nu_parser::lite_parse(line, 0) {
completions =
self.get_matching_arguments(&lite_block, &line_chars, line, replace_pos, pos);
} else {
completions = self.file_completer.complete(line, pos, context)?.1;
}
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_else(String::new);
let matcher = matcher.as_str();
let matcher: &dyn Matcher = match matcher {
"case-insensitive" => &matchers::case_insensitive::Matcher,
_ => &matchers::case_sensitive::Matcher,
};
if locations.is_empty() {
(pos, Vec::new())
} else {
completions = self.file_completer.complete(line, pos, context)?.1;
for completion in &mut completions {
if completion.replacement.contains("\\ ") {
completion.replacement = completion.replacement.replace("\\ ", " ");
}
if completion.replacement.contains("\\(") {
completion.replacement = completion.replacement.replace("\\(", "(");
}
if completion.replacement.contains(' ') || completion.replacement.contains('(') {
if !completion.replacement.starts_with('\"') {
completion.replacement = format!("\"{}", completion.replacement);
}
if !completion.replacement.ends_with('\"') {
completion.replacement = format!("{}\"", completion.replacement);
}
}
}
};
let complete_from_path = match config::config(Tag::unknown()) {
Ok(conf) => match conf.get("complete_from_path") {
Some(val) => val.is_true(),
_ => true,
},
_ => true,
};
// Only complete executables or commands if the thing we're completing
// is syntactically a command
if replace_loc == ReplacementLocation::Command {
let mut all_executables: IndexSet<_> = commands.iter().map(|x| x.to_string()).collect();
if complete_from_path {
let path_executables = self.find_path_executables().unwrap_or_default();
for path_exe in path_executables {
all_executables.insert(path_exe);
}
};
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;
let pos = locations[0].span.start();
let suggestions = locations
.into_iter()
.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())
}
pos += 1;
if pos == line_chars.len() {
matched = true;
break;
LocationType::Flag(cmd) => {
let flag_completer = FlagCompleter { cmd };
flag_completer.complete(context, partial, matcher.to_owned())
}
}
}
if matched {
completions.push(rustyline::completion::Pair {
display: exe.to_string(),
replacement: exe.to_string(),
});
}
}
}
LocationType::Argument(cmd, _arg_name) => {
let path_completer = PathCompleter;
for completion in &mut completions {
// If the cursor is at a double-quote, remove the double-quote in the replacement
// This prevents duplicate quotes
let cursor_char = line.chars().nth(pos);
if cursor_char.unwrap_or(' ') == '"' && completion.replacement.ends_with('"') {
completion.replacement.pop();
}
}
const QUOTE_CHARS: &[char] = &['\'', '"', '`'];
Ok((replace_pos, completions))
}
// 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 {
(None, partial)
};
fn get_replace_pos(&self, 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 get_matching_arguments(
&self,
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, &self.commands);
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,
});
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()
}
LocationType::Variable => Vec::new(),
}
}
}
})
.collect();
(pos, suggestions)
}
matching_arguments
}
// These is_executable/pathext implementations are copied from ichwh and modified
// to not be async
#[cfg(windows)]
fn pathext(&self) -> 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(&self, 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) = self.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(&self, file: &DirEntry) -> bool {
false
}
#[cfg(unix)]
fn is_executable(&self, 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(&self) -> 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 self.is_executable(&item) {
if let Ok(name) = item.file_name().into_string() {
executables.insert(name);
}
}
}
}
}
Some(executables)
}
}
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 as _};
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::completion::Completer;
use rustyline::highlight::Highlighter;
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(crate) struct Helper {
context: Context,
pub struct Helper {
completer: NuCompleter,
hinter: Option<rustyline::hint::HistoryHinter>,
context: EvaluationContext,
pub colored_prompt: String,
validator: NuValidator,
}
impl Helper {
pub(crate) fn new(context: Context) -> Helper {
pub(crate) fn new(
context: EvaluationContext,
hinter: Option<rustyline::hint::HistoryHinter>,
) -> Helper {
Helper {
completer: NuCompleter {},
hinter,
context,
colored_prompt: String::new(),
validator: NuValidator {},
}
}
}
@ -35,31 +37,32 @@ impl rustyline::completion::Candidate for completion::Suggestion {
}
}
impl Completer for Helper {
impl rustyline::completion::Completer for Helper {
type Candidate = completion::Suggestion;
fn complete(
&self,
line: &str,
pos: usize,
ctx: &rustyline::Context<'_>,
_ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<Self::Candidate>), rustyline::error::ReadlineError> {
let ctx = completion::Context(ctx);
self.context
.shell_manager
.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(ctx);
self.context.shell_manager.hint(line, pos, &ctx)
self.hinter.as_ref().and_then(|h| h.hint(line, pos, &ctx))
}
}
impl Highlighter for Helper {
impl rustyline::highlight::Highlighter for Helper {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
@ -79,11 +82,7 @@ impl 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 {
@ -91,6 +90,47 @@ impl 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::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();
@ -103,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,5 +0,0 @@
pub mod group;
pub mod split;
pub use crate::utils::data::group::group;
pub use crate::utils::data::split::split;

View File

@ -1,675 +0,0 @@
use crate::data::value::compare_values;
use crate::data::TaggedListBuilder;
use chrono::{DateTime, NaiveDate, Utc};
use nu_errors::ShellError;
use nu_protocol::hir::Operator;
use nu_protocol::{Primitive, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::{SpannedItem, Tag, Tagged, TaggedItem};
use nu_value_ext::{get_data_by_key, ValueExt};
use num_traits::Zero;
// Re-usable error messages
const ERR_EMPTY_DATA: &str = "Cannot perform aggregate math operation on empty data";
pub fn columns_sorted(
_group_by_name: Option<Tagged<String>>,
value: &Value,
tag: impl Into<Tag>,
) -> Vec<Tagged<String>> {
let origin_tag = tag.into();
match value {
Value {
value: UntaggedValue::Row(rows),
..
} => {
let mut keys: Vec<Value> = rows
.entries
.keys()
.map(|s| s.as_ref())
.map(|k: &str| {
let date = NaiveDate::parse_from_str(k, "%B %d-%Y");
let date = match date {
Ok(parsed) => UntaggedValue::Primitive(Primitive::Date(
DateTime::<Utc>::from_utc(parsed.and_hms(12, 34, 56), Utc),
)),
Err(_) => UntaggedValue::string(k),
};
date.into_untagged_value()
})
.collect();
keys.sort();
let keys: Vec<String> = keys
.into_iter()
.map(|k| match k {
Value {
value: UntaggedValue::Primitive(Primitive::Date(d)),
..
} => format!("{}", d.format("%B %d-%Y")),
_ => k.as_string().unwrap_or_else(|_| String::from("<string>")),
})
.collect();
keys.into_iter().map(|k| k.tagged(&origin_tag)).collect()
}
_ => vec!["default".to_owned().tagged(&origin_tag)],
}
}
pub fn t_sort(
group_by_name: Option<Tagged<String>>,
split_by_name: Option<String>,
value: &Value,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let origin_tag = tag.into();
match group_by_name {
Some(column_name) => {
let sorted_labels: Vec<Tagged<String>> =
columns_sorted(Some(column_name), value, &origin_tag);
match split_by_name {
None => {
let mut dataset = TaggedDictBuilder::new(&origin_tag);
dataset.insert_value("default", value.clone());
let dataset = dataset.into_value();
let split_labels: Vec<Tagged<String>> = match &dataset {
Value {
value: UntaggedValue::Row(rows),
..
} => {
let mut keys: Vec<Tagged<String>> = rows
.entries
.keys()
.map(|k| k.clone().tagged_unknown())
.collect();
keys.sort();
keys
}
_ => vec![],
};
let results: Vec<Vec<Value>> = split_labels
.iter()
.map(|split| {
let groups = get_data_by_key(&dataset, split.borrow_spanned());
sorted_labels
.clone()
.into_iter()
.map(|label| match &groups {
Some(Value {
value: UntaggedValue::Row(dict),
..
}) => {
dict.get_data_by_key(label.borrow_spanned()).unwrap_or_else(
|| UntaggedValue::Table(vec![]).into_value(&origin_tag),
)
}
_ => UntaggedValue::Table(vec![]).into_value(&origin_tag),
})
.collect()
})
.collect();
let mut outer = TaggedListBuilder::new(&origin_tag);
for i in results {
outer.push_value(UntaggedValue::Table(i).into_value(&origin_tag));
}
Ok(UntaggedValue::Table(outer.list).into_value(&origin_tag))
}
Some(_) => Ok(UntaggedValue::nothing().into_value(&origin_tag)),
}
}
None => Ok(UntaggedValue::nothing().into_value(&origin_tag)),
}
}
pub fn fetch(key: Option<String>) -> Box<dyn Fn(Value, Tag) -> Option<Value> + 'static> {
Box::new(move |value: Value, tag| match &key {
Some(key_given) => value.get_data_by_key(key_given[..].spanned(tag.span)),
None => Some(UntaggedValue::int(1).into_value(tag)),
})
}
pub fn evaluate(
values: &Value,
evaluator: Option<String>,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let tag = tag.into();
let evaluate_with = match evaluator {
Some(keyfn) => fetch(Some(keyfn)),
None => fetch(None),
};
let results: Value = match values {
Value {
value: UntaggedValue::Table(datasets),
..
} => {
let datasets: Vec<_> = datasets
.iter()
.map(|subsets| match subsets {
Value {
value: UntaggedValue::Table(subsets),
..
} => {
let subsets: Vec<_> = subsets
.clone()
.into_iter()
.map(|data| match data {
Value {
value: UntaggedValue::Table(data),
..
} => {
let data: Vec<_> = data
.into_iter()
.map(|x| match evaluate_with(x, tag.clone()) {
Some(val) => val,
None => UntaggedValue::int(1).into_value(tag.clone()),
})
.collect();
UntaggedValue::Table(data).into_value(&tag)
}
_ => UntaggedValue::Table(vec![]).into_value(&tag),
})
.collect();
UntaggedValue::Table(subsets).into_value(&tag)
}
_ => UntaggedValue::Table(vec![]).into_value(&tag),
})
.collect();
UntaggedValue::Table(datasets).into_value(&tag)
}
_ => UntaggedValue::Table(vec![]).into_value(&tag),
};
Ok(results)
}
pub fn sum(data: Vec<Value>) -> Result<Value, ShellError> {
if data.is_empty() {
return Err(ShellError::unexpected(ERR_EMPTY_DATA));
}
let mut acc = Value::zero();
for value in data {
match value.value {
UntaggedValue::Primitive(_) => acc = acc + value,
_ => {
return Err(ShellError::labeled_error(
"Attempted to compute the sum of a value that cannot be summed.",
"value appears here",
value.tag.span,
))
}
}
}
Ok(acc)
}
pub fn max(data: Vec<Value>) -> Result<Value, ShellError> {
let mut biggest = data
.first()
.ok_or_else(|| ShellError::unexpected(ERR_EMPTY_DATA))?
.value
.clone();
for value in data.iter() {
if let Ok(greater_than) = compare_values(Operator::GreaterThan, &value.value, &biggest) {
if greater_than {
biggest = value.value.clone();
}
} else {
return Err(ShellError::unexpected(format!(
"Could not compare\nleft: {:?}\nright: {:?}",
biggest, value.value
)));
}
}
Ok(Value {
value: biggest,
tag: Tag::unknown(),
})
}
pub fn min(data: Vec<Value>) -> Result<Value, ShellError> {
let mut smallest = data
.first()
.ok_or_else(|| ShellError::unexpected(ERR_EMPTY_DATA))?
.value
.clone();
for value in data.iter() {
if let Ok(greater_than) = compare_values(Operator::LessThan, &value.value, &smallest) {
if greater_than {
smallest = value.value.clone();
}
} else {
return Err(ShellError::unexpected(format!(
"Could not compare\nleft: {:?}\nright: {:?}",
smallest, value.value
)));
}
}
Ok(Value {
value: smallest,
tag: Tag::unknown(),
})
}
fn formula(
acc_begin: Value,
calculator: Box<dyn Fn(Vec<Value>) -> Result<Value, ShellError> + Send + Sync + 'static>,
) -> Box<dyn Fn(Value, Vec<Value>) -> Result<Value, ShellError> + Send + Sync + 'static> {
Box::new(move |acc, datax| -> Result<Value, ShellError> {
let result = acc * acc_begin.clone();
match calculator(datax) {
Ok(total) => Ok(result + total),
Err(reason) => Err(reason),
}
})
}
pub fn reducer_for(
command: Reduce,
) -> Box<dyn Fn(Value, Vec<Value>) -> Result<Value, ShellError> + Send + Sync + 'static> {
match command {
Reduce::Summation | Reduce::Default => Box::new(formula(Value::zero(), Box::new(sum))),
Reduce::Minimum => Box::new(|_, values| min(values)),
Reduce::Maximum => Box::new(|_, values| max(values)),
}
}
pub enum Reduce {
Summation,
Minimum,
Maximum,
Default,
}
pub fn reduce(
values: &Value,
reducer: Option<String>,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let tag = tag.into();
let reduce_with = match reducer {
Some(cmd) if cmd == "sum" => reducer_for(Reduce::Summation),
Some(cmd) if cmd == "min" => reducer_for(Reduce::Minimum),
Some(cmd) if cmd == "max" => reducer_for(Reduce::Maximum),
Some(_) | None => reducer_for(Reduce::Default),
};
let results: Value = match values {
Value {
value: UntaggedValue::Table(datasets),
..
} => {
let datasets: Vec<_> = datasets
.iter()
.map(|subsets| {
let acc = Value::zero();
match subsets {
Value {
value: UntaggedValue::Table(data),
..
} => {
let data = data
.iter()
.map(|d| {
if let Value {
value: UntaggedValue::Table(x),
..
} = d
{
if let Ok(Value {
value:
UntaggedValue::Primitive(Primitive::Int(computed)),
..
}) = reduce_with(acc.clone(), x.clone())
{
UntaggedValue::int(computed).into_value(&tag)
} else {
UntaggedValue::int(0).into_value(&tag)
}
} else {
UntaggedValue::int(0).into_value(&tag)
}
})
.collect::<Vec<_>>();
UntaggedValue::Table(data).into_value(&tag)
}
_ => UntaggedValue::Table(vec![]).into_value(&tag),
}
})
.collect();
UntaggedValue::Table(datasets).into_value(&tag)
}
_ => UntaggedValue::Table(vec![]).into_value(&tag),
};
Ok(results)
}
pub fn map_max(
values: &Value,
_map_by_column_name: Option<String>,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let tag = tag.into();
let results: Value = match values {
Value {
value: UntaggedValue::Table(datasets),
..
} => {
let datasets: Vec<Value> = datasets
.iter()
.map(|subsets| match subsets {
Value {
value: UntaggedValue::Table(data),
..
} => data.iter().fold(Value::zero(), |acc, value| {
let left = &value.value;
let right = &acc.value;
if let Ok(is_greater_than) =
compare_values(Operator::GreaterThan, left, right)
{
if is_greater_than {
value.clone()
} else {
acc
}
} else {
acc
}
}),
_ => UntaggedValue::int(0).into_value(&tag),
})
.collect();
datasets.into_iter().fold(Value::zero(), |max, value| {
let left = &value.value;
let right = &max.value;
if let Ok(is_greater_than) = compare_values(Operator::GreaterThan, left, right) {
if is_greater_than {
value
} else {
max
}
} else {
max
}
})
}
_ => UntaggedValue::int(-1).into_value(&tag),
};
Ok(results)
}
#[cfg(test)]
mod tests {
use super::{columns_sorted, evaluate, fetch, map_max, reduce, reducer_for, t_sort, Reduce};
use crate::commands::group_by::group;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value};
use nu_source::*;
use num_bigint::BigInt;
use num_traits::Zero;
fn int(s: impl Into<BigInt>) -> Value {
UntaggedValue::int(s).into_untagged_value()
}
fn string(input: impl Into<String>) -> Value {
UntaggedValue::string(input.into()).into_untagged_value()
}
fn row(entries: IndexMap<String, Value>) -> Value {
UntaggedValue::row(entries).into_untagged_value()
}
fn table(list: &[Value]) -> Value {
UntaggedValue::table(list).into_untagged_value()
}
fn nu_releases_grouped_by_date() -> Result<Value, ShellError> {
let key = Some(String::from("date").tagged_unknown());
let sample = table(&nu_releases_committers());
group(&key, &sample, Tag::unknown())
}
fn nu_releases_sorted_by_date() -> Result<Value, ShellError> {
let key = String::from("date").tagged(Tag::unknown());
t_sort(
Some(key),
None,
&nu_releases_grouped_by_date()?,
Tag::unknown(),
)
}
fn nu_releases_evaluated_by_default_one() -> Result<Value, ShellError> {
evaluate(&nu_releases_sorted_by_date()?, None, Tag::unknown())
}
fn nu_releases_reduced_by_sum() -> Result<Value, ShellError> {
reduce(
&nu_releases_evaluated_by_default_one()?,
Some(String::from("sum")),
Tag::unknown(),
)
}
fn nu_releases_committers() -> Vec<Value> {
vec![
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")},
),
row(
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")},
),
row(
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")},
),
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("September 24-2019")},
),
row(
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")},
),
row(
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("September 24-2019")},
),
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")},
),
row(
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("September 24-2019")},
),
row(
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")},
),
]
}
#[test]
fn show_columns_sorted_given_a_column_to_sort_by() -> Result<(), ShellError> {
let by_column = String::from("date").tagged(Tag::unknown());
assert_eq!(
columns_sorted(
Some(by_column),
&nu_releases_grouped_by_date()?,
Tag::unknown()
),
vec![
"August 23-2019".to_string().tagged_unknown(),
"September 24-2019".to_string().tagged_unknown(),
"October 10-2019".to_string().tagged_unknown()
]
);
Ok(())
}
#[test]
fn sorts_the_tables() -> Result<(), ShellError> {
let group_by = String::from("date").tagged(Tag::unknown());
assert_eq!(
t_sort(
Some(group_by),
None,
&nu_releases_grouped_by_date()?,
Tag::unknown()
)?,
table(&[table(&[
table(&[
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}
),
row(
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")}
),
row(
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")}
)
]),
table(&[
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("September 24-2019")}
),
row(
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("September 24-2019")}
),
row(
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("September 24-2019")}
)
]),
table(&[
row(
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")}
),
row(
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")}
),
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")}
)
]),
]),])
);
Ok(())
}
#[test]
fn evaluator_fetches_by_column_if_supplied_a_column_name() -> Result<(), ShellError> {
let subject = row(indexmap! { "name".into() => string("andres") });
let evaluator = fetch(Some(String::from("name")));
assert_eq!(evaluator(subject, Tag::unknown()), Some(string("andres")));
Ok(())
}
#[test]
fn evaluator_returns_1_if_no_column_name_given() -> Result<(), ShellError> {
let subject = row(indexmap! { "name".into() => string("andres") });
let evaluator = fetch(None);
assert_eq!(
evaluator(subject, Tag::unknown()),
Some(UntaggedValue::int(1).into_untagged_value())
);
Ok(())
}
#[test]
fn evaluates_the_tables() -> Result<(), ShellError> {
assert_eq!(
evaluate(&nu_releases_sorted_by_date()?, None, Tag::unknown())?,
table(&[table(&[
table(&[int(1), int(1), int(1)]),
table(&[int(1), int(1), int(1)]),
table(&[int(1), int(1), int(1)]),
]),])
);
Ok(())
}
#[test]
fn evaluates_the_tables_with_custom_evaluator() -> Result<(), ShellError> {
let eval = String::from("name");
assert_eq!(
evaluate(&nu_releases_sorted_by_date()?, Some(eval), Tag::unknown())?,
table(&[table(&[
table(&[string("AR"), string("JT"), string("YK")]),
table(&[string("AR"), string("YK"), string("JT")]),
table(&[string("YK"), string("JT"), string("AR")]),
]),])
);
Ok(())
}
#[test]
fn reducer_computes_given_a_sum_command() -> Result<(), ShellError> {
let subject = vec![int(1), int(1), int(1)];
let action = reducer_for(Reduce::Summation);
assert_eq!(action(Value::zero(), subject)?, int(3));
Ok(())
}
#[test]
fn reducer_computes() -> Result<(), ShellError> {
assert_eq!(
reduce(
&nu_releases_evaluated_by_default_one()?,
Some(String::from("sum")),
Tag::unknown()
)?,
table(&[table(&[int(3), int(3), int(3)])])
);
Ok(())
}
#[test]
fn maps_and_gets_max_value() -> Result<(), ShellError> {
assert_eq!(
map_max(&nu_releases_reduced_by_sum()?, None, Tag::unknown())?,
int(3)
);
Ok(())
}
}

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