Compare commits

...

476 Commits

Author SHA1 Message Date
22ae962b57 Bump to 0.28 (#3149) 2021-03-09 23:40:17 +13:00
864139d67f move bel and backspace to char since they're not ansi (#3144)
* move bel and backspace to char since they're not ansi

* Trigger Build
2021-03-09 22:34:51 +13:00
Tw
1dc7e00d20 Fix trash functionality (#3146)
For now the trash doesn't work because the trash-support flag isn't enabled in nu-engine
crate, so make it work by adding this flag.

Signed-off-by: Tw <wei.tan@intel.com>

Co-authored-by: Tw <wei.tan@intel.com>
2021-03-09 22:34:14 +13:00
49a9107e0f Allow composing help message from two parts (#3124)
* Split help message into brief and full help

Demonstrate on ansi command

Brief help is printed when running `help commands` so it doesn't clutter
the table. Full help is printed when normal help message is requested
(e.g., `help ansi`, `ansi --help`, etc.).

* Split long command descriptions

Some are not split, just edited to be shorter.

* Capitalize the usage of all commands

* Make sure every usage ends with dot

* Fix random typo
2021-03-08 12:57:58 +13:00
7b8c2c232f fix: deadlock when printing errors (#3140)
Co-authored-by: hk <alexhaka10@protonmail.com>
2021-03-08 12:08:37 +13:00
15e1e6376b remove warnings (#3137) 2021-03-06 14:31:22 -06:00
06d9d9ed08 use minus v3.3.0 (#3136) 2021-03-07 06:47:01 +13:00
74e10d6f72 print string returned by draw_table, in autoview when pivot mode is on (#3135) 2021-03-06 10:17:37 -05:00
d43489a6a0 Add exit code argument (#3132) 2021-03-06 18:46:27 +13:00
983de8974b hopefully fixes the coercion error when comparing $nothing to $var (#3133) 2021-03-05 14:07:54 -06:00
c91a1ec08d Table paging release (#3128)
* use the InputHandler functionality from minus

* respond to Q and ESC character to quit

* use arijit79/minus main branch until new release is pushed

* rename NushellMinusInputHandler to MinusInputHandler
2021-03-05 10:32:16 +13:00
507de45d40 Revert "add config: prompt_color_enabled = true (#3115)" (#3127)
This reverts commit fe0fc8d5e1.
2021-03-04 12:22:14 -05:00
fe0fc8d5e1 add config: prompt_color_enabled = true (#3115) 2021-03-04 20:08:26 +13:00
e4a8db56f9 use add_exit_callback, update to rezural/nushell which contains add_exit_callback, and contains updated keybindings (#3121) 2021-03-04 20:06:22 +13:00
1d1ec4727a Refactor arguments of path subcommands & Add path join subcommand (#3123)
* Refactor path subcommand argument handling

DefaultArguments are no longer passed to each subcommand. Instead, each
subcommand has its own Path<xxx>Arguments. This means that it is no
longer necessary to edit every single path subcommand source file when
changing the arguments struct.

* Add new path join subcommand

Makes it easier to create new paths. It's just a wrapper around Rust's
Path.join().
2021-03-04 20:04:56 +13:00
0b71e45072 Preserve order when serializing/deserialize json by default. (#3126) 2021-03-04 01:35:13 -05:00
9c375b33a6 updated fetch to surf2.2 and feature h1-client-rustls (#3120) 2021-03-04 07:18:11 +13:00
28a6a5ea57 Add option to invert match command selection (#3114)
* Add option to invert match command selection

* Fix rustfmt error

* Rename match --exclude to --invert

To be more descriptive and conform to e.g. grep or ripgrep -v flag.
Also simplified the --invert flag description.

* Fix formatting when description got shorter

Co-authored-by: Jakub Žádník <jakub.zadnik@tuni.fi>
2021-03-02 06:48:22 +13:00
f83ff0e47d Use writer from host instead of always std::err (#3112) 2021-03-01 15:00:40 +13:00
079e575cac Table paging (Draft PR) (#3058)
* This adds table paging, relying on minus to perform the paging functionality
This is gated behind the table-pager feature

* fix problem with long running InputStreams blocking table() returning

* some comments regarding Arc clones, and callback from minus
2021-03-01 14:59:33 +13:00
6b2327f231 help generate_docs | flatten crashes nushell (#3099)
* fix case where parent_name was {nu, term} and possibly others in the future by doing an extra test first to see if if the *parent_name key actually exists in cmap

* update with help generate_docs testing
2021-02-27 09:05:22 +13:00
596608aa0c nu_plugin_match: accept -i -m -s flags (#3111) 2021-02-27 07:41:22 +13:00
120e80d1b6 refactor parse_math_expression, reduce indentation (#3093) 2021-02-26 18:11:20 +13:00
aa6c6120f6 Bump to 0.27.2 (#3109)
* Bump to 0.27.2

* Fix clippy and test
2021-02-26 17:55:25 +13:00
19d5f782cc Allow dropping columns. (#3107)
`drop` is used for removing the last row. Passing a number allows dropping N rows.
Here we introduce the same logic for dropping columns instead.

You can certainly remove columns by using `reject`, however, there could be cases
where we are interested in removing columns from tables that contain, say, a big
number of columns. Using `reject` becomes impractical, especially when you don't
care about the column names that could either be known or not known when exploring
tables.

```
> echo [[lib, extension]; [nu-core, rs] [rake, rb]]
─────────┬───────────
   lib   │ extension
─────────┼───────────
 nu-core │ rs
 rake    │ rb
─────────┴───────────
```

```
> echo [[lib, extension]; [nu-core, rs] [rake, rb]] | drop column
─────────
   lib
─────────
 nu-core
 rake
─────────
```
2021-02-25 15:37:21 -05:00
84169a91ff update readme (#3106)
added commit activity
added contributors
removed gitpod
2021-02-25 07:50:42 -06:00
d1c48cdcf9 update azure ci badge from master to main (#3105) 2021-02-25 07:36:09 -06:00
dfe95d3ae6 enabled the easy access use of nu-ansi-term's "Light" colors (#3100) 2021-02-24 15:36:22 -06:00
57ebec385f add ansi strip subcommand (#3095)
* add ansi subcommand

* changed example test, added additional test
2021-02-23 14:16:13 -06:00
7a77910720 Table content rolling. (#3097)
There are many use cases. Here we introduce the following:

- The rows can be rolled `... | roll` (up) or `... | roll down`
- Columns can be rolled too (the default is on the `left`, you can pass `... | roll column --opposite` to roll in the other direction)
- You can `roll` the cells of a table and keeping the header names in the same order (`... | roll column --cells-only`)
- Above examples can also be passed (Ex. `... | roll down 3`) a number to tell how many places to roll.

Basic working example with rolling columns:

```
> echo '00000100'
| split chars
| each { str to-int }
| rotate counter-clockwise _
| reject _
| rename bit1 bit2 bit3 bit4 bit5 bit6 bit7 bit8

───┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────
 # │ bit1 │ bit2 │ bit3 │ bit4 │ bit5 │ bit6 │ bit7 │ bit8
───┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────
 0 │    0 │    0 │    0 │    0 │    0 │    1 │    0 │    0
───┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────
```

We want to "shift" three bits to the left of the bitstring (four in decimal), let's try it:

```
> echo '00000100'
| split chars
| each { str to-int }
| rotate counter-clockwise _
| reject _
| rename bit1 bit2 bit3 bit4 bit5 bit6 bit7 bit8
| roll column 3

───┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────
 # │ bit4 │ bit5 │ bit6 │ bit7 │ bit8 │ bit1 │ bit2 │ bit3
───┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────
 0 │    0 │    0 │    1 │    0 │    0 │    0 │    0 │    0
───┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────
```

The tables was rolled correctly (32 in decimal, for above bitstring). However, the *last three header names* look confusing.
We can roll the cell contents only to fix it.

```
> echo '00000100'
| split chars
| each { str to-int }
| rotate counter-clockwise _
| reject _
| rename bit1 bit2 bit3 bit4 bit5 bit6 bit7 bit8
| roll column 3 --cells-only

───┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────
 # │ bit1 │ bit2 │ bit3 │ bit4 │ bit5 │ bit6 │ bit7 │ bit8
───┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────
 0 │    0 │    0 │    1 │    0 │    0 │    0 │    0 │    0
───┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────
```

There we go. Let's compute it's decimal value now (should be 32)

```
> echo '00000100'
| split chars
| each { str to-int }
| rotate counter-clockwise _
| reject _
| roll column 3 --cells-only
| pivot bit --ignore-titles
| get bit
| reverse
| each --numbered { = $it.item * (2 ** $it.index) }
| math sum

32
```
2021-02-23 13:29:07 -05:00
23d8dc959c return string from draw_table instead of printing directly (#3088) 2021-02-23 22:25:49 +13:00
7f303a856e Make sure CurDir is filtered out in absolutize. (#3084)
* Make sure `CurDir` is filtered out in absolutize.

Closes #3083

* Add test

* Make sure test works on windows
2021-02-23 22:22:17 +13:00
e834e617f3 Remove parking_lot crate reference from nu-data (#3091)
* remove parking_lot crate from nu-data as it is no longer being used

* remove commented out code from parse.rs

* remove commented out code from scope.rs
2021-02-23 22:21:31 +13:00
2c89a228d5 add nu-ansi-term (#3089) 2021-02-22 12:33:34 -06:00
803826cdcd 90 degree table rotations (clockwise and counter-clockwise) (#3086)
Also for 180 degree is expected. Rotation is not exactly like pivoting (transposing)
for instance, given the following table:

```
> echo [[col1, col2, col3]; [cell1, cell2, cell3] [cell4, cell5, cell6]]
───┬───────┬───────┬───────
 # │ col1  │ col2  │ col3
───┼───────┼───────┼───────
 0 │ cell1 │ cell2 │ cell3
 1 │ cell4 │ cell5 │ cell6
───┴───────┴───────┴───────
```

To rotate it counter clockwise by 90 degrees, we can resort to first transposing (`pivot`)
them adding a new column (preferably integers), sort by that column from highest to lowest,
then remove the column and we have a counter clockwise rotation.

```
> echo [[col1, col2, col3]; [cell1, cell2, cell3] [cell4, cell5, cell6]] | pivot | each --numbered { = $it.item | insert idx $it.index } | sort-by idx | reverse | reject idx
───┬─────────┬─────────┬─────────
 # │ Column0 │ Column1 │ Column2
───┼─────────┼─────────┼─────────
 0 │ col3    │ cell3   │ cell6
 1 │ col2    │ cell2   │ cell5
 2 │ col1    │ cell1   │ cell4
───┴─────────┴─────────┴─────────
```

Which we can get easily, in this case, by doing:

```
> echo [[col1, col2, cel3]; [cell1, cell2, cell3] [cell4, cell5, cell6]] | rotate counter-clockwise
───┬─────────┬─────────┬─────────
 # │ Column0 │ Column1 │ Column2
───┼─────────┼─────────┼─────────
 0 │ col3    │ cell3   │ cell6
 1 │ col2    │ cell2   │ cell5
 2 │ col1    │ cell1   │ cell4
───┴─────────┴─────────┴─────────
```

There are also many powerful use cases with rotation, it makes a breeze creating tables with many columns, say:

```
echo 0..12 | rotate counter-clockwise | reject Column0
───┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬──────────┬──────────┬──────────┬──────────
 # │ Column1 │ Column2 │ Column3 │ Column4 │ Column5 │ Column6 │ Column7 │ Column8 │ Column9 │ Column10 │ Column11 │ Column12 │ Column13
───┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼──────────┼──────────┼──────────┼──────────
 0 │       0 │       1 │       2 │       3 │       4 │       5 │       6 │       7 │       8 │        9 │       10 │       11 │       12
───┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴──────────┴──────────┴──────────┴──────────
```
2021-02-22 06:56:34 -05:00
42d18d2294 add "-0" as short for --headerless in "from" commands (#3042)
* replace --headerless flags with --noheaders / -n

* Update from_csv.rs

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2021-02-22 20:25:17 +13:00
b5ae024cc8 add the ability to time commands (#3081) 2021-02-20 07:37:14 -06:00
5968811441 Set skip_welcome_message to false by default and add note (#3069) 2021-02-19 21:41:11 +13:00
fc59c87606 this example now runs out of the box instead of failing with no value or the wrong value (#3067) 2021-02-19 21:40:53 +13:00
7dc1d6a350 Extract .nu-env tests and more granularity (#3078)
The autoenv logic mutates environment variables in the running session as
it operates and decides what to do for trusted directories containing `.nu-env`
files. Few of the ways to interact with it were all in a single test function.

We separate out all the ways that were done in the single test function to document
 it better. This will greatly help once we start refactoring our way out from setting
 environment variables this way to just setting them to `Scope`.

This is part of an on-going effort to keep variables (`PATH` and `ENV`)
in our `Scope` and rely on it for everything related to variables.

We expect to move away from setting (`std::*`) envrironment variables in the current
running process. This is non-trivial since we need to handle cases from vars
coming in from the outside world, prioritize, and also compare to the ones
we have both stored in memory and in configuration files.

Also to send out our in-memory (in `Scope`) variables properly to external
programs once we no longer rely on `std::env` vars from the running process.
2021-02-18 20:24:27 -05:00
deff1aa63b Bump to 0.27.1 (#3073) 2021-02-18 18:54:48 +13:00
08e7d0dfb6 Keep the environment properly set. (#3072)
* Revert "fix prompts on startup (#3056)"

This reverts commit b202951c1d.

* Ensure environment variables synced with global internal Nu Scope.
2021-02-18 15:56:14 +13:00
892aae267d add height method to Host trait, and implementors (#3064) 2021-02-17 09:02:13 +13:00
11a9144e84 update mock table for easier table testing (#3065) 2021-02-17 04:47:47 +13:00
039f223b53 Bump to 0.27 (#3063) 2021-02-16 19:20:05 +13:00
e1cb026184 Add back in column truncation (#3061) 2021-02-16 07:15:16 +13:00
2a96152a43 fix sample_config: date --format no longer supported (#3060)
Co-authored-by: alexhk <alexhk@protonmail.com>
2021-02-15 10:24:55 -06:00
0795d56c1c Source path including tilda (#3059)
* Use expand_path to handle the path including tilda

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

* cargo fmt

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

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

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

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

* update w & h, add examples

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

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

* Update sort_by.rs

Fix reverse test

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

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

* Move data structs to own file

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

* Rename param_flag_list to signature

* Add tests

* Fix clippy lint

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

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

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

* Further clean up the lexer

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

* Update meta.rs

* WIP

* fix clippy

* remove unwraps

* remove unwraps

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

* forgot to remove comment

* make specified size lowercase

* fix the test due to precision

* added another test

* Update README.md

add contributors graphic

* clippy - test adjustment

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

add contributors graphic

* just a couple of helpers

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

* Add tests and fix filesize display

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

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

* Update value.rs

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

* fixed FileSize so it reports with units configured

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

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

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

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

Parameters:
  <mandatory_param>
  (opt_param)
  (opt_param2)

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

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

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

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

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

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

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

Example after this PR is applied:

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

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

Parameters:
  ...args: My rest arg

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

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

* update shadow-rs to 0.5

* fix version not used

* update

* update Cargo.lock

* update Cargo.lock

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

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

* upgrade shadow-rs 0.5.2

* upgrade shadow-rs 0.5.7

make nushell reduce dependence crates smaller and  build fast.

* upgrade shadow-rs 0.5.8

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

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

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

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

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

* WIP

* more cleanup

* fmt

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

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

* Parse long and shortflags without space correctly

* Update param_flag_list.rs

* Update param_flag_list.rs

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

* update shadow-rs to 0.5

* fix version not used

* update

* update Cargo.lock

* update Cargo.lock

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

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

* upgrade shadow-rs 0.5.2

* upgrade shadow-rs 0.5.7

make nushell reduce dependence crates smaller and  build fast.

* upgrade shadow-rs 0.5.8

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

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

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

* upgrade Cargo.lock

* upgarde shadow-rs depencdence

* fix build error in wasm

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

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

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

* Fix clippy issue

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

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

* move over maybe_print_errors

* add nu_command crate references to nu_cli

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

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

* cargo fmt

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

* code cleanup

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

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

* add in allow unused imports

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

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

* Update Cargo.toml

Extend the optional features to nu-command

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

* Add failing lexer test

* Implement Parsing of definition signature

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

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

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

* Fix clippy lints

* Remove wrong comment

* Add spacing

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

* move basic_evaluation_context to nu-engine

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

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

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

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

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

With this commit applied its fixed :)

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

* Fix clippy lint

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

* Rename glob pattern also

* more fun

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

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

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

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

echo 42
```

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

* Document the lexer and lightly improve its names

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

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

* Fix rustfmt

* Update lock

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

* Updating initial value creating in Math Summation Reducer

* Update reducers.rs

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

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

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

* update shadow-rs to 0.5

* fix version not used

* update

* update Cargo.lock

* update Cargo.lock

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

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

* upgrade shadow-rs 0.5.2

* upgrade shadow-rs 0.5.7

make nushell reduce dependence crates smaller and  build fast.

* upgrade shadow-rs 0.5.8

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

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

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

* update shadow-rs to 0.5

* fix version not used

* update

* update Cargo.lock

* update Cargo.lock

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

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

* upgrade shadow-rs 0.5.2

* upgrade shadow-rs 0.5.7

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

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

Example output of nu after the commit is applied:

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

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

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

* cargo fmt

* code cleanup imports

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

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

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

* replace the stream crate in nu-cli

* cc

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

* clean up the prelude and hand merge everything together

* clean up Cargo.tom

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

* update shadow-rs to 0.5

* fix version not used

* update

* update Cargo.lock

* update Cargo.lock

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

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

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

* update shadow-rs to 0.5

* fix version not used

* update

* update Cargo.lock

* update Cargo.lock

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

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

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

* Rename initializer/math for better readability

* Fix description

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

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

* added leading spaces highlighting

* added config point to change the color

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

* clippy

* Finish moving to groups. Test pass

* Keep going

* WIP

* WIP

* BROKEN WIP

* WIP

* WIP

* Fix more tests

* WIP: alias starts working

* Broken WIP

* Broken WIP

* Variables begin to work

* captures start working

* A little better but needs fixed scope

* Shorthand env setting

* Update main merge

* Broken WIP

* WIP

* custom command parsing

* Custom commands start working

* Fix coloring and parsing of block

* Almost there

* Add some tests

* Add more param types

* Bump version

* Fix benchmark

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

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

* added proper error handling when date format string is invalid

* fixed format issue

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

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

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

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

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

* Append history instead of always save

* Add associated type to Hinter

* Convert to using Rustyline KeyEvent

* Use AcceptOrInsertLine as struct

* Cargo fmt

* Make convert_keyevent pub

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

Ensure test worked

fmt

WIP get it working for other types of base64

Use optional named arg

WIP

* rebased and refactored a little with encoding and decoding

Fix some typos

Add some more charactersets

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

Add main hash command so it can be found via help

Added tests for running the whole pipeline

* add test case to cover invalid character sets

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

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

* Add examples of replacing for path extension

* Refactor path extension and its example

* Add replacement functionality to path basename

* Refactor path subcommands to support more args

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

* Add replace and num_levels options to path dirname

* Rename num_levels option to num-levels

* Remove commented code

* Clean up path basename

* Fix path dirname description

* Add path filestem opts; Rename extension -> suffix

* Add prefix option and examples to path filestem

* Fix broken num-levels of path dirname

* Fix failing example test of path filestem

* Fix failing test of path extension

* Formatting

* Add Windows-specific path subcommand examples

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

* Fix weird path expand on Windows

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

* Formatting

* Return path datatype when appropriate

* Do not append empty remainder to path dirname

* Add tests for path subcommands

* Formatting

* Revisit path subcommand description strings

* Apply clippy suggestions; Formatting

* Remove problematic test checking '~' expansion

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

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

* Fork serde-hjson and bring in

* Fork serde-hjson and bring in

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

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

* everything seems to be working, yay!

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

* clippy

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

* WIP

* WIP

* Tests are passing

* make parser more resilient

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

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

* wip

* wip tests working

* probably good enough for a first pass

* oops, missed something.

* and something else...

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

* changed to matches

* fixed a bug with osc

* changed back to if let because: clippy

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

* why, oh why

* works with parameters

* widths should've been optional

* dbg messages

* working. rest had to be first.

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

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

* clippy

* fixed tests

* changed terminator help desc

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

* Remove commented code

* Fix typo

* Change comment to be more informative

* Make match statement to lookup in table

* Remove resolved question

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

* Pick ...or_insert_dependency functions into pieces

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

2 new functions have been added: get_result_shape_of_math_expr and
get_result_shape_of_math_expr_or_insert_dependency

* Remove flattening of deep binary expressions

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

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

* Simplify get_result_shapes_in_math_expr

* Simplify infer_shapes_in_binary_expr

* Clarify comment

* Clarify comment

* Fix clippy lint

* Move check for real var into checked_insert

* Remove comment

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

* refactored code to use -u option

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

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

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

* Cleanup

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

* added math floor

* added math ceil

* added math.md examples

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

* math round now works on streams

* math floor now works on streams

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

Also adds a test to match pi's.

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

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

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

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

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

* Add padding around values in each row

* Add padding to test

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

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

* Linter changes

* removed redundant pattern matching

* Changed the error message

* Added a comma after every argument

* Changed the test to accomodate the new err messages

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

* Lints problems

* Changed unwrap to expect

* Added the -f flag to rm command

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

* Lint problems

* Fixed the wrong line

* Removed println

* Spelling mistake

* Fix problems when you mv a file into itself

* Lint mistakes

* Remove unecessary filtering in most cases

* Allow the removal of sockets

* Conditional compilations to systems without socket

* Add a size-format option to ls command

* Added kib and mib formating

* Make patterns lowercase

* New subcommand to format, filesize

* Forgot the linter once more

* Remove the ls changes since its no longer needed

* CI mistakes

* Lint stuff

* Fix lint

* Added formatting for bytes

* fix lint

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

* rustfmt

* Fix Clippy warnings

* rustfmt

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

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

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

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

* Apply same order for ValuExt impl

* Nothing helper for tests

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

* new xpath engine is finally working

* nearly there

* closer

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

* broken again

* working again - time for some cleanup

* cleaned up code, added error handling and test

* update example, fix clippy

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

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

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

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

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

* retrigger checks

* removed the max

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

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

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

* Update default terminal width to 80

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

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

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

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

* left and right pad strings

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

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

* Linter changes

* removed redundant pattern matching

* Changed the error message

* Added a comma after every argument

* Changed the test to accomodate the new err messages

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

* Lints problems

* Changed unwrap to expect

* Added the -f flag to rm command

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

* Lint problems

* Fixed the wrong line

* Removed println

* Spelling mistake

* Fix problems when you mv a file into itself

* Lint mistakes

* Remove unecessary filtering in most cases

* Allow the removal of sockets

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

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

* Linter changes

* removed redundant pattern matching

* Changed the error message

* Added a comma after every argument

* Changed the test to accomodate the new err messages

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

* Lints problems

* Changed unwrap to expect

* Added the -f flag to rm command

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

* Lint problems

* Fixed the wrong line

* Removed println

* Spelling mistake

* Fix problems when you mv a file into itself

* Lint mistakes

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

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

* Refactor scope to have parents

* Refactor scope to have parents

* Clippy

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

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

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

* Add execution block for benchmark -p

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

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

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

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

* Specialize 'Context' to EvaluationContext and CompletionContext

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

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

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

* Feature-gate dependency on heim in benchmark

* Reorder let bindings in benchmark

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

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

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

* Tweak the `Completer` trait for nushell.

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

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

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

This commit adds right-exclusive ranges.

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

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

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

* Fix collect tests by changing ranges to ..=

* Fix clippy lints in exclusive range matching

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

* formatting

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

* fix linting issue

* applied requested changes

* applied requested change for avg.rs

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

* remove unused import

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

* add examples for test

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

* moved umask to cfg(unix)

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

* roll back Inflector

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

* Implmented `config set` as a subcommand.

* Implemented `config set_into` as subcommand

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

* Added `config clear` subcommand

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

* Added `config path` subcommand

* fixed clippy

* initial commit for implementing groups

* each group works

* each group is slightly cleaner + added example

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

* each window stride implemented

* Added tests and minor documentation changes

* fixed clippy

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

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

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

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

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

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

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

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

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

* fixed formatting

* fixed clippy issues

* fixed clippy issues, v2

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

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

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

* Added additional examples.

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

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

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

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

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

* clippy & fmt again

* Header default alignment is left.

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

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

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

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

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

This fixes a bug where completing

    ls <TAB>

would result in replacing the entire line.

* Revert to the default update implementation.

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

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

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

    > ls .../<TAB>

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

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

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

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

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

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

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

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

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

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

* resolving merge conflicts

* impl std::ops::Mul for Filesize

* rebased with main; refactored product implementation

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

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

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

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

* fix clippy.

* minimal implement the into_into command.

* Revert "fix clippy."

This reverts commit 0d7cf72ed2.

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

This reverts commit 9c4e3787f5.

* set the argument to any type.

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

* add tests for into-int command.

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

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

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

* WIP

* pretty much working, need to figure out sink plugins

* added a home for scripting plugin examples, ran fmt

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

* add viable plugin extensions

* clippy

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

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

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

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

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

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

    du -

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

* Implement Sleep command

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

* Fix typo

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

* added text_color and line_color to hastableproperties

* Refactor Tractor.

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

* add recurion on Block

* clean up/abstract, Invocations

* use Result

* inspection of Binary, tests

* improve code structure

* move arg shape inspection to Alias command

* add spanned errors, tests, cleanup for PR

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

* Add wasm sample for CI

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

* Refactor nu-data out of nu-cli

* Remove unwraps

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

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

* Linter changes

* removed redundant pattern matching

* Changed the error message

* Added a comma after every argument

* Changed the test to accomodate the new err messages

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

* Lints problems

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

* Run exitscript in the folder it was specified

* Update documentation

* Add comment

* Borrow instead of clone

* Does this just... work on windows?

* fmt

* as_str

* Collapse if by order of clippy

* Support windows

* fmt

* refactor tests

* fmt

* This time it will work on windows FOR SURE

* Remove debug prints

* Comment

* Refactor tests

* fmt

* fix spelling

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

* lighten the restriction on the inflector dependency

* publishing the case commands

* fix typo

* fix kebab case test

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

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

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

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

* Forgot the tests!

* Updated primitive.rs to match changes.

* Updated where example to match changes.

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

* Oops, too many deletes

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

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

* Replace unwrap with expect.

* Only have commit hash if git doesn't error

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

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

* Fix error message

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

* remove submodule

* fix clippy

* update Cargo.lock

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

View File

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

6
.cargo/config.toml Normal file
View File

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

View File

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

View File

@ -12,9 +12,9 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1.
2.
3.
1.
2.
3.
**Expected behavior**
A clear and concise description of what you expected to happen.
@ -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
```

5223
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

@ -1,17 +1,18 @@
# README
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/nushell/nushell)
[![Crates.io](https://img.shields.io/crates/v/nu.svg)](https://crates.io/crates/nu)
[![Build Status](https://dev.azure.com/nushell/nushell/_apis/build/status/nushell.nushell?branchName=master)](https://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=master)
[![Build Status](https://dev.azure.com/nushell/nushell/_apis/build/status/nushell.nushell?branchName=main)](https://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=main)
[![Discord](https://img.shields.io/discord/601130461678272522.svg?logo=discord)](https://discord.gg/NtAbbGn)
[![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)
![GitHub commit activity](https://img.shields.io/github/commit-activity/m/nushell/nushell)
![GitHub contributors](https://img.shields.io/github/contributors/nushell/nushell)
## 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 +35,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 +45,19 @@ Try it in Gitpod.
### Local
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/en/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
To build Nu, you will need to use the **latest stable (1.41 or later)** version of the compiler.
To build Nu, you will need to use the **latest stable (1.47 or later)** version of the compiler.
Required dependencies:
* pkg-config and libssl (only needed on Linux)
* On Debian/Ubuntu: `apt install pkg-config libssl-dev`
- pkg-config and libssl (only needed on Linux)
- On Debian/Ubuntu: `apt install pkg-config libssl-dev`
Optional dependencies:
* To use Nu with all possible optional features enabled, you'll also need the following:
* On Linux (on Debian/Ubuntu): `apt install libxcb-composite0-dev libx11-dev`
- To use Nu with all possible optional features enabled, you'll also need the following:
- On Linux (on Debian/Ubuntu): `apt install libxcb-composite0-dev libx11-dev`
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`):
@ -64,10 +65,10 @@ To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs
cargo install nu
```
You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/en/installation.html#dependencies) for your platform), once you have checked out this repo with git:
You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/installation.html#dependencies) for your platform), once you have checked out this repo with git:
```bash
cargo build --workspace --features=stable
cargo build --workspace --features=extra
```
### Docker
@ -139,9 +140,9 @@ Just as the Unix philosophy, Nu allows commands to output from stdout and read f
Additionally, commands can output structured data (you can think of this as a third kind of stream).
Commands that work in the pipeline fit into one of three categories:
* Commands that produce a stream (eg, `ls`)
* Commands that filter a stream (eg, `where type == "Dir"`)
* Commands that consume the output of the pipeline (eg, `autoview`)
- Commands that produce a stream (eg, `ls`)
- Commands that filter a stream (eg, `where type == "Dir"`)
- Commands that consume the output of the pipeline (eg, `autoview`)
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
@ -219,27 +220,27 @@ 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:
```shell
> config set edit_mode "vi"
> config set line_editor.edit_mode "vi"
> config set path $nu.path
```
@ -270,49 +271,55 @@ If the plugin is a sink, it is given the full vector of final data and is given
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
* First and foremost, Nu is cross-platform. Commands and techniques should carry between platforms and offer first-class consistent support for Windows, macOS, and Linux.
- First and foremost, Nu is cross-platform. Commands and techniques should carry between platforms and offer first-class consistent support for Windows, macOS, and Linux.
* Nu ensures direct compatibility with existing platform-specific executables that make up people's workflows.
- Nu ensures direct compatibility with existing platform-specific executables that make up people's workflows.
* Nu's workflow and tools should have the usability in day-to-day experience of using a shell in 2019 (and beyond).
- Nu's workflow and tools should have the usability in day-to-day experience of using a shell in 2019 (and beyond).
* Nu views data as both structured and unstructured. It is a structured shell like PowerShell.
- Nu views data as both structured and unstructured. It is a structured shell like PowerShell.
* Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
- Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
## Commands
You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/documentation.html#quick-command-references).
You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/book/command_reference.html).
## Progress
Nu is in heavy development, and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
Nu is in heavy development, and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
| Features | Not started | Prototype | MVP | Preview | Mature | Notes
| -------- |:-----------:|:---------:|:---:|:-------:|:------:| -----
| Aliases | | X | | | | Initial implementation but lacks necessary features
| Notebook | | X | | | | Initial jupyter support, but it loses state and lacks features
| File ops | | | X | | | cp, mv, rm, mkdir have some support, but lacking others
| Environment | | X | | | | Temporary environment, but no session-wide env variables
| Shells | | X | | | | Basic value and file shells, but no opt-in/opt-out for commands
| Protocol | | | X | | | Streaming protocol is serviceable
| Plugins | | X | | | | Plugins work on one row at a time, lack batching and expression eval
| Errors | | | X | | | Error reporting works, but could use usability polish
| Documentation | | X | | | | Book and related are barebones and lack task-based lessons
| Paging | | X | | | | Textview has paging, but we'd like paging for tables
| Functions| X | | | | | No functions, yet, only aliases
| Variables| X | | | | | Nu doesn't yet support variables
| Completions | | X | | | | Completions are currently barebones, at best
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked
| Features | Not started | Prototype | MVP | Preview | Mature | Notes |
| ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- |
| Aliases | | X | | | | Initial implementation but lacks necessary features |
| Notebook | | X | | | | Initial jupyter support, but it loses state and lacks features |
| File ops | | | X | | | cp, mv, rm, mkdir have some support, but lacking others |
| Environment | | X | | | | Temporary environment, but no session-wide env variables |
| Shells | | X | | | | Basic value and file shells, but no opt-in/opt-out for commands |
| Protocol | | | X | | | Streaming protocol is serviceable |
| Plugins | | X | | | | Plugins work on one row at a time, lack batching and expression eval |
| Errors | | | X | | | Error reporting works, but could use usability polish |
| Documentation | | X | | | | Book and related are barebones and lack task-based lessons |
| Paging | | X | | | | Textview has paging, but we'd like paging for tables |
| Functions | | X | | | | No functions, yet, only aliases |
| Variables | | X | | | | Nu doesn't yet support variables |
| Completions | | X | | | | Completions are currently barebones, at best |
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked |
## Current Roadmap
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in NuShell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in Nushell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
## Contributing
See [Contributing](CONTRIBUTING.md) for details.
Thanks to all the people who already contributed!
<a href="https://github.com/nushell/nushell/graphs/contributors">
<img src="https://contributors-img.web.app/image?repo=nushell/nushell" />
</a>
## License
The project is made available under the MIT license. See the `LICENSE` file for more information.

2
crates/nu-ansi-term/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
target
Cargo.lock

View File

@ -0,0 +1,35 @@
[package]
authors = [
"ogham@bsago.me",
"Ryan Scheel (Havvy) <ryan.havvy@gmail.com>",
"Josh Triplett <josh@joshtriplett.org>",
"The Nu Project Contributors",
]
description = "Library for ANSI terminal colors and styles (bold, underline)"
edition = "2018"
license = "MIT"
name = "nu-ansi-term"
version = "0.28.0"
[lib]
doctest = false
# name = "nu-ansi-term"
[features]
derive_serde_style = ["serde"]
[dependencies.serde]
version = "1.0.90"
features = ["derive"]
optional = true
[target.'cfg(target_os="windows")'.dependencies.winapi]
version = "0.3.4"
features = ["consoleapi", "errhandlingapi", "fileapi", "handleapi", "processenv"]
[dev-dependencies]
doc-comment = "0.3"
regex = "1.1.9"
[dev-dependencies.serde_json]
version = "1.0.39"

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Benjamin Sago
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,182 @@
# nu-ansi-term
> This is a copy of rust-ansi-term but with Color change to Color and light foreground colors added (90-97) as well as light background colors added (100-107).
This is a library for controlling colors and formatting, such as red bold text or blue underlined text, on ANSI terminals.
### [View the Rustdoc](https://docs.rs/nu_ansi_term/)
# Installation
This crate works with [Cargo](http://crates.io). Add the following to your `Cargo.toml` dependencies section:
```toml
[dependencies]
nu_ansi_term = "0.13"
```
## Basic usage
There are three main types in this crate that you need to be concerned with: `ANSIString`, `Style`, and `Color`.
A `Style` holds stylistic information: foreground and background colors, whether the text should be bold, or blinking, or other properties.
The `Color` enum represents the available colors.
And an `ANSIString` is a string paired with a `Style`.
`Color` is also available as an alias to `Color`.
To format a string, call the `paint` method on a `Style` or a `Color`, passing in the string you want to format as the argument.
For example, heres how to get some red text:
```rust
use nu_ansi_term::Color::Red;
println!("This is in red: {}", Red.paint("a red string"));
```
Its important to note that the `paint` method does _not_ actually return a string with the ANSI control characters surrounding it.
Instead, it returns an `ANSIString` value that has a `Display` implementation that, when formatted, returns the characters.
This allows strings to be printed with a minimum of `String` allocations being performed behind the scenes.
If you _do_ want to get at the escape codes, then you can convert the `ANSIString` to a string as you would any other `Display` value:
```rust
use nu_ansi_term::Color::Red;
let red_string = Red.paint("a red string").to_string();
```
**Note for Windows 10 users:** On Windows 10, the application must enable ANSI support first:
```rust,ignore
let enabled = nu_ansi_term::enable_ansi_support();
```
## Bold, underline, background, and other styles
For anything more complex than plain foreground color changes, you need to construct `Style` values themselves, rather than beginning with a `Color`.
You can do this by chaining methods based on a new `Style`, created with `Style::new()`.
Each method creates a new style that has that specific property set.
For example:
```rust
use nu_ansi_term::Style;
println!("How about some {} and {}?",
Style::new().bold().paint("bold"),
Style::new().underline().paint("underline"));
```
For brevity, these methods have also been implemented for `Color` values, so you can give your styles a foreground color without having to begin with an empty `Style` value:
```rust
use nu_ansi_term::Color::{Blue, Yellow};
println!("Demonstrating {} and {}!",
Blue.bold().paint("blue bold"),
Yellow.underline().paint("yellow underline"));
println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!"));
```
The complete list of styles you can use are:
`bold`, `dimmed`, `italic`, `underline`, `blink`, `reverse`, `hidden`, and `on` for background colors.
In some cases, you may find it easier to change the foreground on an existing `Style` rather than starting from the appropriate `Color`.
You can do this using the `fg` method:
```rust
use nu_ansi_term::Style;
use nu_ansi_term::Color::{Blue, Cyan, Yellow};
println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!"));
println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!"));
```
You can turn a `Color` into a `Style` with the `normal` method.
This will produce the exact same `ANSIString` as if you just used the `paint` method on the `Color` directly, but its useful in certain cases: for example, you may have a method that returns `Styles`, and need to represent both the “red bold” and “red, but not bold” styles with values of the same type. The `Style` struct also has a `Default` implementation if you want to have a style with _nothing_ set.
```rust
use nu_ansi_term::Style;
use nu_ansi_term::Color::Red;
Red.normal().paint("yet another red string");
Style::default().paint("a completely regular string");
```
## Extended colors
You can access the extended range of 256 colors by using the `Color::Fixed` variant, which takes an argument of the color number to use.
This can be included wherever you would use a `Color`:
```rust
use nu_ansi_term::Color::Fixed;
Fixed(134).paint("A sort of light purple");
Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup");
```
The first sixteen of these values are the same as the normal and bold standard color variants.
Theres nothing stopping you from using these as `Fixed` colors instead, but theres nothing to be gained by doing so either.
You can also access full 24-bit color by using the `Color::RGB` variant, which takes separate `u8` arguments for red, green, and blue:
```rust
use nu_ansi_term::Color::RGB;
RGB(70, 130, 180).paint("Steel blue");
```
## Combining successive coloured strings
The benefit of writing ANSI escape codes to the terminal is that they _stack_: you do not need to end every coloured string with a reset code if the text that follows it is of a similar style.
For example, if you want to have some blue text followed by some blue bold text, its possible to send the ANSI code for blue, followed by the ANSI code for bold, and finishing with a reset code without having to have an extra one between the two strings.
This crate can optimise the ANSI codes that get printed in situations like this, making life easier for your terminal renderer.
The `ANSIStrings` struct takes a slice of several `ANSIString` values, and will iterate over each of them, printing only the codes for the styles that need to be updated as part of its formatting routine.
The following code snippet uses this to enclose a binary number displayed in red bold text inside some red, but not bold, brackets:
```rust
use nu_ansi_term::Color::Red;
use nu_ansi_term::{ANSIString, ANSIStrings};
let some_value = format!("{:b}", 42);
let strings: &[ANSIString<'static>] = &[
Red.paint("["),
Red.bold().paint(some_value),
Red.paint("]"),
];
println!("Value: {}", ANSIStrings(strings));
```
There are several things to note here.
Firstly, the `paint` method can take _either_ an owned `String` or a borrowed `&str`.
Internally, an `ANSIString` holds a copy-on-write (`Cow`) string value to deal with both owned and borrowed strings at the same time.
This is used here to display a `String`, the result of the `format!` call, using the same mechanism as some statically-available `&str` slices.
Secondly, that the `ANSIStrings` value works in the same way as its singular counterpart, with a `Display` implementation that only performs the formatting when required.
## Byte strings
This library also supports formatting `[u8]` byte strings; this supports applications working with text in an unknown encoding.
`Style` and `Color` support painting `[u8]` values, resulting in an `ANSIByteString`.
This type does not implement `Display`, as it may not contain UTF-8, but it does provide a method `write_to` to write the result to any value that implements `Write`:
```rust
use nu_ansi_term::Color::Green;
Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap();
```
Similarly, the type `ANSIByteStrings` supports writing a list of `ANSIByteString` values with minimal escape sequences:
```rust
use nu_ansi_term::Color::Green;
use nu_ansi_term::ANSIByteStrings;
ANSIByteStrings(&[
Green.paint("user data 1\n".as_bytes()),
Green.bold().paint("user data 2\n".as_bytes()),
]).write_to(&mut std::io::stdout()).unwrap();
```

View File

@ -0,0 +1,72 @@
extern crate nu_ansi_term;
use nu_ansi_term::Color;
// This example prints out the 256 colors.
// They're arranged like this:
//
// - 0 to 8 are the eight standard colors.
// - 9 to 15 are the eight bold colors.
// - 16 to 231 are six blocks of six-by-six color squares.
// - 232 to 255 are shades of grey.
fn main() {
// First two lines
for c in 0..8 {
glow(c, c != 0);
print!(" ");
}
println!();
for c in 8..16 {
glow(c, c != 8);
print!(" ");
}
println!("\n");
// Six lines of the first three squares
for row in 0..6 {
for square in 0..3 {
for column in 0..6 {
glow(16 + square * 36 + row * 6 + column, row >= 3);
print!(" ");
}
print!(" ");
}
println!();
}
println!();
// Six more lines of the other three squares
for row in 0..6 {
for square in 0..3 {
for column in 0..6 {
glow(124 + square * 36 + row * 6 + column, row >= 3);
print!(" ");
}
print!(" ");
}
println!();
}
println!();
// The last greyscale lines
for c in 232..=243 {
glow(c, false);
print!(" ");
}
println!();
for c in 244..=255 {
glow(c, true);
print!(" ");
}
println!();
}
fn glow(c: u8, light_bg: bool) {
let base = if light_bg { Color::Black } else { Color::White };
let style = base.on(Color::Fixed(c));
print!("{}", style.paint(&format!(" {:3} ", c)));
}

View File

@ -0,0 +1,18 @@
extern crate nu_ansi_term;
use nu_ansi_term::{Color::*, Style};
// This example prints out the 16 basic colors.
fn main() {
let normal = Style::default();
println!("{} {}", normal.paint("Normal"), normal.bold().paint("bold"));
println!("{} {}", Black.paint("Black"), Black.bold().paint("bold"));
println!("{} {}", Red.paint("Red"), Red.bold().paint("bold"));
println!("{} {}", Green.paint("Green"), Green.bold().paint("bold"));
println!("{} {}", Yellow.paint("Yellow"), Yellow.bold().paint("bold"));
println!("{} {}", Blue.paint("Blue"), Blue.bold().paint("bold"));
println!("{} {}", Purple.paint("Purple"), Purple.bold().paint("bold"));
println!("{} {}", Cyan.paint("Cyan"), Cyan.bold().paint("bold"));
println!("{} {}", White.paint("White"), White.bold().paint("bold"));
}

View File

@ -0,0 +1,23 @@
extern crate nu_ansi_term;
use nu_ansi_term::{Color, Style};
// This example prints out a color gradient in a grid by calculating each
// characters red, green, and blue components, and using 24-bit color codes
// to display them.
const WIDTH: i32 = 80;
const HEIGHT: i32 = 24;
fn main() {
for row in 0..HEIGHT {
for col in 0..WIDTH {
let r = (row * 255 / HEIGHT) as u8;
let g = (col * 255 / WIDTH) as u8;
let b = 128;
print!("{}", Style::default().on(Color::RGB(r, g, b)).paint(" "));
}
println!();
}
}

View File

@ -0,0 +1,404 @@
use crate::style::{Color, Style};
use crate::write::AnyWrite;
use std::fmt;
impl Style {
/// Write any bytes that go *before* a piece of text to the given writer.
fn write_prefix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
// If there are actually no styles here, then dont write *any* codes
// as the prefix. An empty ANSI code may not affect the terminal
// output at all, but a user may just want a code-free string.
if self.is_plain() {
return Ok(());
}
// Write the codes prefix, then write numbers, separated by
// semicolons, for each text style we want to apply.
write!(f, "\x1B[")?;
let mut written_anything = false;
{
let mut write_char = |c| {
if written_anything {
write!(f, ";")?;
}
written_anything = true;
write!(f, "{}", c)?;
Ok(())
};
if self.is_bold {
write_char('1')?
}
if self.is_dimmed {
write_char('2')?
}
if self.is_italic {
write_char('3')?
}
if self.is_underline {
write_char('4')?
}
if self.is_blink {
write_char('5')?
}
if self.is_reverse {
write_char('7')?
}
if self.is_hidden {
write_char('8')?
}
if self.is_strikethrough {
write_char('9')?
}
}
// The foreground and background colors, if specified, need to be
// handled specially because the number codes are more complicated.
// (see `write_background_code` and `write_foreground_code`)
if let Some(bg) = self.background {
if written_anything {
write!(f, ";")?;
}
written_anything = true;
bg.write_background_code(f)?;
}
if let Some(fg) = self.foreground {
if written_anything {
write!(f, ";")?;
}
fg.write_foreground_code(f)?;
}
// All the codes end with an `m`, because reasons.
write!(f, "m")?;
Ok(())
}
/// Write any bytes that go *after* a piece of text to the given writer.
fn write_suffix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
if self.is_plain() {
Ok(())
} else {
write!(f, "{}", RESET)
}
}
}
/// The code to send to reset all styles and return to `Style::default()`.
pub static RESET: &str = "\x1B[0m";
impl Color {
fn write_foreground_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
match *self {
Color::Black => write!(f, "30"),
Color::Red => write!(f, "31"),
Color::Green => write!(f, "32"),
Color::Yellow => write!(f, "33"),
Color::Blue => write!(f, "34"),
Color::Purple => write!(f, "35"),
Color::Magenta => write!(f, "35"),
Color::Cyan => write!(f, "36"),
Color::White => write!(f, "37"),
Color::Fixed(num) => write!(f, "38;5;{}", &num),
Color::RGB(r, g, b) => write!(f, "38;2;{};{};{}", &r, &g, &b),
Color::DarkGray => write!(f, "90"),
Color::LightRed => write!(f, "91"),
Color::LightGreen => write!(f, "92"),
Color::LightYellow => write!(f, "93"),
Color::LightBlue => write!(f, "94"),
Color::LightPurple => write!(f, "95"),
Color::LightMagenta => write!(f, "95"),
Color::LightCyan => write!(f, "96"),
Color::LightGray => write!(f, "97"),
}
}
fn write_background_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
match *self {
Color::Black => write!(f, "40"),
Color::Red => write!(f, "41"),
Color::Green => write!(f, "42"),
Color::Yellow => write!(f, "43"),
Color::Blue => write!(f, "44"),
Color::Purple => write!(f, "45"),
Color::Magenta => write!(f, "45"),
Color::Cyan => write!(f, "46"),
Color::White => write!(f, "47"),
Color::Fixed(num) => write!(f, "48;5;{}", &num),
Color::RGB(r, g, b) => write!(f, "48;2;{};{};{}", &r, &g, &b),
Color::DarkGray => write!(f, "100"),
Color::LightRed => write!(f, "101"),
Color::LightGreen => write!(f, "102"),
Color::LightYellow => write!(f, "103"),
Color::LightBlue => write!(f, "104"),
Color::LightPurple => write!(f, "105"),
Color::LightMagenta => write!(f, "105"),
Color::LightCyan => write!(f, "106"),
Color::LightGray => write!(f, "107"),
}
}
}
/// Like `ANSIString`, but only displays the style prefix.
///
/// This type implements the `Display` trait, meaning it can be written to a
/// `std::fmt` formatting without doing any extra allocation, and written to a
/// string with the `.to_string()` method. For examples, see
/// [`Style::prefix`](struct.Style.html#method.prefix).
#[derive(Clone, Copy, Debug)]
pub struct Prefix(Style);
/// Like `ANSIString`, but only displays the difference between two
/// styles.
///
/// This type implements the `Display` trait, meaning it can be written to a
/// `std::fmt` formatting without doing any extra allocation, and written to a
/// string with the `.to_string()` method. For examples, see
/// [`Style::infix`](struct.Style.html#method.infix).
#[derive(Clone, Copy, Debug)]
pub struct Infix(Style, Style);
/// Like `ANSIString`, but only displays the style suffix.
///
/// This type implements the `Display` trait, meaning it can be written to a
/// `std::fmt` formatting without doing any extra allocation, and written to a
/// string with the `.to_string()` method. For examples, see
/// [`Style::suffix`](struct.Style.html#method.suffix).
#[derive(Clone, Copy, Debug)]
pub struct Suffix(Style);
impl Style {
/// The prefix bytes for this style. These are the bytes that tell the
/// terminal to use a different color or font style.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::{Style, Color::Blue};
///
/// let style = Style::default().bold();
/// assert_eq!("\x1b[1m",
/// style.prefix().to_string());
///
/// let style = Blue.bold();
/// assert_eq!("\x1b[1;34m",
/// style.prefix().to_string());
///
/// let style = Style::default();
/// assert_eq!("",
/// style.prefix().to_string());
/// ```
pub fn prefix(self) -> Prefix {
Prefix(self)
}
/// The infix bytes between this style and `next` style. These are the bytes
/// that tell the terminal to change the style to `next`. These may include
/// a reset followed by the next color and style, depending on the two styles.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::{Style, Color::Green};
///
/// let style = Style::default().bold();
/// assert_eq!("\x1b[32m",
/// style.infix(Green.bold()).to_string());
///
/// let style = Green.normal();
/// assert_eq!("\x1b[1m",
/// style.infix(Green.bold()).to_string());
///
/// let style = Style::default();
/// assert_eq!("",
/// style.infix(style).to_string());
/// ```
pub fn infix(self, next: Style) -> Infix {
Infix(self, next)
}
/// The suffix for this style. These are the bytes that tell the terminal
/// to reset back to its normal color and font style.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::{Style, Color::Green};
///
/// let style = Style::default().bold();
/// assert_eq!("\x1b[0m",
/// style.suffix().to_string());
///
/// let style = Green.normal().bold();
/// assert_eq!("\x1b[0m",
/// style.suffix().to_string());
///
/// let style = Style::default();
/// assert_eq!("",
/// style.suffix().to_string());
/// ```
pub fn suffix(self) -> Suffix {
Suffix(self)
}
}
impl Color {
/// The prefix bytes for this color as a `Style`. These are the bytes
/// that tell the terminal to use a different color or font style.
///
/// See also [`Style::prefix`](struct.Style.html#method.prefix).
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color::Green;
///
/// assert_eq!("\x1b[0m",
/// Green.suffix().to_string());
/// ```
pub fn prefix(self) -> Prefix {
Prefix(self.normal())
}
/// The infix bytes between this color and `next` color. These are the bytes
/// that tell the terminal to use the `next` color, or to do nothing if
/// the two colors are equal.
///
/// See also [`Style::infix`](struct.Style.html#method.infix).
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color::{Red, Yellow};
///
/// assert_eq!("\x1b[33m",
/// Red.infix(Yellow).to_string());
/// ```
pub fn infix(self, next: Color) -> Infix {
Infix(self.normal(), next.normal())
}
/// The suffix for this color as a `Style`. These are the bytes that
/// tell the terminal to reset back to its normal color and font style.
///
/// See also [`Style::suffix`](struct.Style.html#method.suffix).
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color::Purple;
///
/// assert_eq!("\x1b[0m",
/// Purple.suffix().to_string());
/// ```
pub fn suffix(self) -> Suffix {
Suffix(self.normal())
}
}
impl fmt::Display for Prefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let f: &mut dyn fmt::Write = f;
self.0.write_prefix(f)
}
}
impl fmt::Display for Infix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use crate::difference::Difference;
match Difference::between(&self.0, &self.1) {
Difference::ExtraStyles(style) => {
let f: &mut dyn fmt::Write = f;
style.write_prefix(f)
}
Difference::Reset => {
let f: &mut dyn fmt::Write = f;
write!(f, "{}{}", RESET, self.1.prefix())
}
Difference::NoDifference => {
Ok(()) // nothing to write
}
}
}
}
impl fmt::Display for Suffix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let f: &mut dyn fmt::Write = f;
self.0.write_suffix(f)
}
}
#[cfg(test)]
mod test {
use crate::style::Color::*;
use crate::style::Style;
macro_rules! test {
($name: ident: $style: expr; $input: expr => $result: expr) => {
#[test]
fn $name() {
assert_eq!($style.paint($input).to_string(), $result.to_string());
let mut v = Vec::new();
$style.paint($input.as_bytes()).write_to(&mut v).unwrap();
assert_eq!(v.as_slice(), $result.as_bytes());
}
};
}
test!(plain: Style::default(); "text/plain" => "text/plain");
test!(red: Red; "hi" => "\x1B[31mhi\x1B[0m");
test!(black: Black.normal(); "hi" => "\x1B[30mhi\x1B[0m");
test!(yellow_bold: Yellow.bold(); "hi" => "\x1B[1;33mhi\x1B[0m");
test!(yellow_bold_2: Yellow.normal().bold(); "hi" => "\x1B[1;33mhi\x1B[0m");
test!(blue_underline: Blue.underline(); "hi" => "\x1B[4;34mhi\x1B[0m");
test!(green_bold_ul: Green.bold().underline(); "hi" => "\x1B[1;4;32mhi\x1B[0m");
test!(green_bold_ul_2: Green.underline().bold(); "hi" => "\x1B[1;4;32mhi\x1B[0m");
test!(purple_on_white: Purple.on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
test!(purple_on_white_2: Purple.normal().on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
test!(yellow_on_blue: Style::new().on(Blue).fg(Yellow); "hi" => "\x1B[44;33mhi\x1B[0m");
test!(magenta_on_white: Magenta.on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
test!(magenta_on_white_2: Magenta.normal().on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
test!(yellow_on_blue_2: Cyan.on(Blue).fg(Yellow); "hi" => "\x1B[44;33mhi\x1B[0m");
test!(cyan_bold_on_white: Cyan.bold().on(White); "hi" => "\x1B[1;47;36mhi\x1B[0m");
test!(cyan_ul_on_white: Cyan.underline().on(White); "hi" => "\x1B[4;47;36mhi\x1B[0m");
test!(cyan_bold_ul_on_white: Cyan.bold().underline().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
test!(cyan_ul_bold_on_white: Cyan.underline().bold().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
test!(fixed: Fixed(100); "hi" => "\x1B[38;5;100mhi\x1B[0m");
test!(fixed_on_purple: Fixed(100).on(Purple); "hi" => "\x1B[45;38;5;100mhi\x1B[0m");
test!(fixed_on_fixed: Fixed(100).on(Fixed(200)); "hi" => "\x1B[48;5;200;38;5;100mhi\x1B[0m");
test!(rgb: RGB(70,130,180); "hi" => "\x1B[38;2;70;130;180mhi\x1B[0m");
test!(rgb_on_blue: RGB(70,130,180).on(Blue); "hi" => "\x1B[44;38;2;70;130;180mhi\x1B[0m");
test!(blue_on_rgb: Blue.on(RGB(70,130,180)); "hi" => "\x1B[48;2;70;130;180;34mhi\x1B[0m");
test!(rgb_on_rgb: RGB(70,130,180).on(RGB(5,10,15)); "hi" => "\x1B[48;2;5;10;15;38;2;70;130;180mhi\x1B[0m");
test!(bold: Style::new().bold(); "hi" => "\x1B[1mhi\x1B[0m");
test!(underline: Style::new().underline(); "hi" => "\x1B[4mhi\x1B[0m");
test!(bunderline: Style::new().bold().underline(); "hi" => "\x1B[1;4mhi\x1B[0m");
test!(dimmed: Style::new().dimmed(); "hi" => "\x1B[2mhi\x1B[0m");
test!(italic: Style::new().italic(); "hi" => "\x1B[3mhi\x1B[0m");
test!(blink: Style::new().blink(); "hi" => "\x1B[5mhi\x1B[0m");
test!(reverse: Style::new().reverse(); "hi" => "\x1B[7mhi\x1B[0m");
test!(hidden: Style::new().hidden(); "hi" => "\x1B[8mhi\x1B[0m");
test!(stricken: Style::new().strikethrough(); "hi" => "\x1B[9mhi\x1B[0m");
test!(lr_on_lr: LightRed.on(LightRed); "hi" => "\x1B[101;91mhi\x1B[0m");
#[test]
fn test_infix() {
assert_eq!(
Style::new().dimmed().infix(Style::new()).to_string(),
"\x1B[0m"
);
assert_eq!(
White.dimmed().infix(White.normal()).to_string(),
"\x1B[0m\x1B[37m"
);
assert_eq!(White.normal().infix(White.bold()).to_string(), "\x1B[1m");
assert_eq!(White.normal().infix(Blue.normal()).to_string(), "\x1B[34m");
assert_eq!(Blue.bold().infix(Blue.bold()).to_string(), "");
}
}

View File

@ -0,0 +1,152 @@
use crate::style::Style;
use std::fmt;
/// Styles have a special `Debug` implementation that only shows the fields that
/// are set. Fields that havent been touched arent included in the output.
///
/// This behaviour gets bypassed when using the alternate formatting mode
/// `format!("{:#?}")`.
///
/// use nu_ansi_term::Color::{Red, Blue};
/// assert_eq!("Style { fg(Red), on(Blue), bold, italic }",
/// format!("{:?}", Red.on(Blue).bold().italic()));
impl fmt::Debug for Style {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if fmt.alternate() {
fmt.debug_struct("Style")
.field("foreground", &self.foreground)
.field("background", &self.background)
.field("blink", &self.is_blink)
.field("bold", &self.is_bold)
.field("dimmed", &self.is_dimmed)
.field("hidden", &self.is_hidden)
.field("italic", &self.is_italic)
.field("reverse", &self.is_reverse)
.field("strikethrough", &self.is_strikethrough)
.field("underline", &self.is_underline)
.finish()
} else if self.is_plain() {
fmt.write_str("Style {}")
} else {
fmt.write_str("Style { ")?;
let mut written_anything = false;
if let Some(fg) = self.foreground {
if written_anything {
fmt.write_str(", ")?
}
written_anything = true;
write!(fmt, "fg({:?})", fg)?
}
if let Some(bg) = self.background {
if written_anything {
fmt.write_str(", ")?
}
written_anything = true;
write!(fmt, "on({:?})", bg)?
}
{
let mut write_flag = |name| {
if written_anything {
fmt.write_str(", ")?
}
written_anything = true;
fmt.write_str(name)
};
if self.is_blink {
write_flag("blink")?
}
if self.is_bold {
write_flag("bold")?
}
if self.is_dimmed {
write_flag("dimmed")?
}
if self.is_hidden {
write_flag("hidden")?
}
if self.is_italic {
write_flag("italic")?
}
if self.is_reverse {
write_flag("reverse")?
}
if self.is_strikethrough {
write_flag("strikethrough")?
}
if self.is_underline {
write_flag("underline")?
}
}
write!(fmt, " }}")
}
}
}
#[cfg(test)]
mod test {
use crate::style::Color::*;
use crate::style::Style;
fn style() -> Style {
Style::new()
}
macro_rules! test {
($name: ident: $obj: expr => $result: expr) => {
#[test]
fn $name() {
assert_eq!($result, format!("{:?}", $obj));
}
};
}
test!(empty: style() => "Style {}");
test!(bold: style().bold() => "Style { bold }");
test!(italic: style().italic() => "Style { italic }");
test!(both: style().bold().italic() => "Style { bold, italic }");
test!(red: Red.normal() => "Style { fg(Red) }");
test!(redblue: Red.normal().on(RGB(3, 2, 4)) => "Style { fg(Red), on(RGB(3, 2, 4)) }");
test!(everything:
Red.on(Blue).blink().bold().dimmed().hidden().italic().reverse().strikethrough().underline() =>
"Style { fg(Red), on(Blue), blink, bold, dimmed, hidden, italic, reverse, strikethrough, underline }");
#[test]
fn long_and_detailed() {
extern crate regex;
let expected_debug = "Style { fg(Blue), bold }";
let expected_pretty_repat = r##"(?x)
Style\s+\{\s+
foreground:\s+Some\(\s+
Blue,?\s+
\),\s+
background:\s+None,\s+
blink:\s+false,\s+
bold:\s+true,\s+
dimmed:\s+false,\s+
hidden:\s+false,\s+
italic:\s+false,\s+
reverse:\s+false,\s+
strikethrough:\s+
false,\s+
underline:\s+false,?\s+
\}"##;
let re = regex::Regex::new(expected_pretty_repat).unwrap();
let style = Blue.bold();
let style_fmt_debug = format!("{:?}", style);
let style_fmt_pretty = format!("{:#?}", style);
println!("style_fmt_debug:\n{}", style_fmt_debug);
println!("style_fmt_pretty:\n{}", style_fmt_pretty);
assert_eq!(expected_debug, style_fmt_debug);
assert!(re.is_match(&style_fmt_pretty));
}
}

View File

@ -0,0 +1,174 @@
use super::Style;
/// When printing out one colored string followed by another, use one of
/// these rules to figure out which *extra* control codes need to be sent.
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Difference {
/// Print out the control codes specified by this style to end up looking
/// like the second string's styles.
ExtraStyles(Style),
/// Converting between these two is impossible, so just send a reset
/// command and then the second string's styles.
Reset,
/// The before style is exactly the same as the after style, so no further
/// control codes need to be printed.
NoDifference,
}
impl Difference {
/// Compute the 'style difference' required to turn an existing style into
/// the given, second style.
///
/// For example, to turn green text into green bold text, it's redundant
/// to write a reset command then a second green+bold command, instead of
/// just writing one bold command. This method should see that both styles
/// use the foreground color green, and reduce it to a single command.
///
/// This method returns an enum value because it's not actually always
/// possible to turn one style into another: for example, text could be
/// made bold and underlined, but you can't remove the bold property
/// without also removing the underline property. So when this has to
/// happen, this function returns None, meaning that the entire set of
/// styles should be reset and begun again.
pub fn between(first: &Style, next: &Style) -> Difference {
use self::Difference::*;
// XXX(Havvy): This algorithm is kind of hard to replicate without
// having the Plain/Foreground enum variants, so I'm just leaving
// it commented out for now, and defaulting to Reset.
if first == next {
return NoDifference;
}
// Cannot un-bold, so must Reset.
if first.is_bold && !next.is_bold {
return Reset;
}
if first.is_dimmed && !next.is_dimmed {
return Reset;
}
if first.is_italic && !next.is_italic {
return Reset;
}
// Cannot un-underline, so must Reset.
if first.is_underline && !next.is_underline {
return Reset;
}
if first.is_blink && !next.is_blink {
return Reset;
}
if first.is_reverse && !next.is_reverse {
return Reset;
}
if first.is_hidden && !next.is_hidden {
return Reset;
}
if first.is_strikethrough && !next.is_strikethrough {
return Reset;
}
// Cannot go from foreground to no foreground, so must Reset.
if first.foreground.is_some() && next.foreground.is_none() {
return Reset;
}
// Cannot go from background to no background, so must Reset.
if first.background.is_some() && next.background.is_none() {
return Reset;
}
let mut extra_styles = Style::default();
if first.is_bold != next.is_bold {
extra_styles.is_bold = true;
}
if first.is_dimmed != next.is_dimmed {
extra_styles.is_dimmed = true;
}
if first.is_italic != next.is_italic {
extra_styles.is_italic = true;
}
if first.is_underline != next.is_underline {
extra_styles.is_underline = true;
}
if first.is_blink != next.is_blink {
extra_styles.is_blink = true;
}
if first.is_reverse != next.is_reverse {
extra_styles.is_reverse = true;
}
if first.is_hidden != next.is_hidden {
extra_styles.is_hidden = true;
}
if first.is_strikethrough != next.is_strikethrough {
extra_styles.is_strikethrough = true;
}
if first.foreground != next.foreground {
extra_styles.foreground = next.foreground;
}
if first.background != next.background {
extra_styles.background = next.background;
}
ExtraStyles(extra_styles)
}
}
#[cfg(test)]
mod test {
use super::Difference::*;
use super::*;
use crate::style::Color::*;
use crate::style::Style;
fn style() -> Style {
Style::new()
}
macro_rules! test {
($name: ident: $first: expr; $next: expr => $result: expr) => {
#[test]
fn $name() {
assert_eq!($result, Difference::between(&$first, &$next));
}
};
}
test!(nothing: Green.normal(); Green.normal() => NoDifference);
test!(uppercase: Green.normal(); Green.bold() => ExtraStyles(style().bold()));
test!(lowercase: Green.bold(); Green.normal() => Reset);
test!(nothing2: Green.bold(); Green.bold() => NoDifference);
test!(color_change: Red.normal(); Blue.normal() => ExtraStyles(Blue.normal()));
test!(addition_of_blink: style(); style().blink() => ExtraStyles(style().blink()));
test!(addition_of_dimmed: style(); style().dimmed() => ExtraStyles(style().dimmed()));
test!(addition_of_hidden: style(); style().hidden() => ExtraStyles(style().hidden()));
test!(addition_of_reverse: style(); style().reverse() => ExtraStyles(style().reverse()));
test!(addition_of_strikethrough: style(); style().strikethrough() => ExtraStyles(style().strikethrough()));
test!(removal_of_strikethrough: style().strikethrough(); style() => Reset);
test!(removal_of_reverse: style().reverse(); style() => Reset);
test!(removal_of_hidden: style().hidden(); style() => Reset);
test!(removal_of_dimmed: style().dimmed(); style() => Reset);
test!(removal_of_blink: style().blink(); style() => Reset);
}

View File

@ -0,0 +1,303 @@
use crate::ansi::RESET;
use crate::difference::Difference;
use crate::style::{Color, Style};
use crate::write::AnyWrite;
use std::borrow::Cow;
use std::fmt;
use std::io;
use std::ops::Deref;
/// An `ANSIGenericString` includes a generic string type and a `Style` to
/// display that string. `ANSIString` and `ANSIByteString` are aliases for
/// this type on `str` and `\[u8]`, respectively.
#[derive(PartialEq, Debug)]
pub struct ANSIGenericString<'a, S: 'a + ToOwned + ?Sized>
where
<S as ToOwned>::Owned: fmt::Debug,
{
style: Style,
string: Cow<'a, S>,
}
/// Cloning an `ANSIGenericString` will clone its underlying string.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::ANSIString;
///
/// let plain_string = ANSIString::from("a plain string");
/// let clone_string = plain_string.clone();
/// assert_eq!(clone_string, plain_string);
/// ```
impl<'a, S: 'a + ToOwned + ?Sized> Clone for ANSIGenericString<'a, S>
where
<S as ToOwned>::Owned: fmt::Debug,
{
fn clone(&self) -> ANSIGenericString<'a, S> {
ANSIGenericString {
style: self.style,
string: self.string.clone(),
}
}
}
// You might think that the hand-written Clone impl above is the same as the
// one that gets generated with #[derive]. But its not *quite* the same!
//
// `str` is not Clone, and the derived Clone implementation puts a Clone
// constraint on the S type parameter (generated using --pretty=expanded):
//
// ↓_________________↓
// impl <'a, S: ::std::clone::Clone + 'a + ToOwned + ?Sized> ::std::clone::Clone
// for ANSIGenericString<'a, S> where
// <S as ToOwned>::Owned: fmt::Debug { ... }
//
// This resulted in compile errors when you tried to derive Clone on a type
// that used it:
//
// #[derive(PartialEq, Debug, Clone, Default)]
// pub struct TextCellContents(Vec<ANSIString<'static>>);
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// error[E0277]: the trait `std::clone::Clone` is not implemented for `str`
//
// The hand-written impl above can ignore that constraint and still compile.
/// An ANSI String is a string coupled with the `Style` to display it
/// in a terminal.
///
/// Although not technically a string itself, it can be turned into
/// one with the `to_string` method.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::ANSIString;
/// use nu_ansi_term::Color::Red;
///
/// let red_string = Red.paint("a red string");
/// println!("{}", red_string);
/// ```
///
/// ```
/// use nu_ansi_term::ANSIString;
///
/// let plain_string = ANSIString::from("a plain string");
/// assert_eq!(&*plain_string, "a plain string");
/// ```
pub type ANSIString<'a> = ANSIGenericString<'a, str>;
/// An `ANSIByteString` represents a formatted series of bytes. Use
/// `ANSIByteString` when styling text with an unknown encoding.
pub type ANSIByteString<'a> = ANSIGenericString<'a, [u8]>;
impl<'a, I, S: 'a + ToOwned + ?Sized> From<I> for ANSIGenericString<'a, S>
where
I: Into<Cow<'a, S>>,
<S as ToOwned>::Owned: fmt::Debug,
{
fn from(input: I) -> ANSIGenericString<'a, S> {
ANSIGenericString {
string: input.into(),
style: Style::default(),
}
}
}
impl<'a, S: 'a + ToOwned + ?Sized> ANSIGenericString<'a, S>
where
<S as ToOwned>::Owned: fmt::Debug,
{
/// Directly access the style
pub fn style_ref(&self) -> &Style {
&self.style
}
/// Directly access the style mutably
pub fn style_ref_mut(&mut self) -> &mut Style {
&mut self.style
}
}
impl<'a, S: 'a + ToOwned + ?Sized> Deref for ANSIGenericString<'a, S>
where
<S as ToOwned>::Owned: fmt::Debug,
{
type Target = S;
fn deref(&self) -> &S {
self.string.deref()
}
}
/// A set of `ANSIGenericString`s collected together, in order to be
/// written with a minimum of control characters.
#[derive(Debug, PartialEq)]
pub struct ANSIGenericStrings<'a, S: 'a + ToOwned + ?Sized>(pub &'a [ANSIGenericString<'a, S>])
where
<S as ToOwned>::Owned: fmt::Debug,
S: PartialEq;
/// A set of `ANSIString`s collected together, in order to be written with a
/// minimum of control characters.
pub type ANSIStrings<'a> = ANSIGenericStrings<'a, str>;
/// A function to construct an `ANSIStrings` instance.
#[allow(non_snake_case)]
pub fn ANSIStrings<'a>(arg: &'a [ANSIString<'a>]) -> ANSIStrings<'a> {
ANSIGenericStrings(arg)
}
/// A set of `ANSIByteString`s collected together, in order to be
/// written with a minimum of control characters.
pub type ANSIByteStrings<'a> = ANSIGenericStrings<'a, [u8]>;
/// A function to construct an `ANSIByteStrings` instance.
#[allow(non_snake_case)]
pub fn ANSIByteStrings<'a>(arg: &'a [ANSIByteString<'a>]) -> ANSIByteStrings<'a> {
ANSIGenericStrings(arg)
}
// ---- paint functions ----
impl Style {
/// Paints the given text with this color, returning an ANSI string.
#[must_use]
pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> ANSIGenericString<'a, S>
where
I: Into<Cow<'a, S>>,
<S as ToOwned>::Owned: fmt::Debug,
{
ANSIGenericString {
string: input.into(),
style: self,
}
}
}
impl Color {
/// Paints the given text with this color, returning an ANSI string.
/// This is a short-cut so you dont have to use `Blue.normal()` just
/// to get blue text.
///
/// ```
/// use nu_ansi_term::Color::Blue;
/// println!("{}", Blue.paint("da ba dee"));
/// ```
#[must_use]
pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> ANSIGenericString<'a, S>
where
I: Into<Cow<'a, S>>,
<S as ToOwned>::Owned: fmt::Debug,
{
ANSIGenericString {
string: input.into(),
style: self.normal(),
}
}
}
// ---- writers for individual ANSI strings ----
impl<'a> fmt::Display for ANSIString<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let w: &mut dyn fmt::Write = f;
self.write_to_any(w)
}
}
impl<'a> ANSIByteString<'a> {
/// Write an `ANSIByteString` to an `io::Write`. This writes the escape
/// sequences for the associated `Style` around the bytes.
pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
let w: &mut dyn io::Write = w;
self.write_to_any(w)
}
}
impl<'a, S: 'a + ToOwned + ?Sized> ANSIGenericString<'a, S>
where
<S as ToOwned>::Owned: fmt::Debug,
&'a S: AsRef<[u8]>,
{
fn write_to_any<W: AnyWrite<Wstr = S> + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
write!(w, "{}", self.style.prefix())?;
w.write_str(self.string.as_ref())?;
write!(w, "{}", self.style.suffix())
}
}
// ---- writers for combined ANSI strings ----
impl<'a> fmt::Display for ANSIStrings<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let f: &mut dyn fmt::Write = f;
self.write_to_any(f)
}
}
impl<'a> ANSIByteStrings<'a> {
/// Write `ANSIByteStrings` to an `io::Write`. This writes the minimal
/// escape sequences for the associated `Style`s around each set of
/// bytes.
pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
let w: &mut dyn io::Write = w;
self.write_to_any(w)
}
}
impl<'a, S: 'a + ToOwned + ?Sized + PartialEq> ANSIGenericStrings<'a, S>
where
<S as ToOwned>::Owned: fmt::Debug,
&'a S: AsRef<[u8]>,
{
fn write_to_any<W: AnyWrite<Wstr = S> + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
use self::Difference::*;
let first = match self.0.first() {
None => return Ok(()),
Some(f) => f,
};
write!(w, "{}", first.style.prefix())?;
w.write_str(first.string.as_ref())?;
for window in self.0.windows(2) {
match Difference::between(&window[0].style, &window[1].style) {
ExtraStyles(style) => write!(w, "{}", style.prefix())?,
Reset => write!(w, "{}{}", RESET, window[1].style.prefix())?,
NoDifference => { /* Do nothing! */ }
}
w.write_str(&window[1].string)?;
}
// Write the final reset string after all of the ANSIStrings have been
// written, *except* if the last one has no styles, because it would
// have already been written by this point.
if let Some(last) = self.0.last() {
if !last.style.is_plain() {
write!(w, "{}", RESET)?;
}
}
Ok(())
}
}
// ---- tests ----
#[cfg(test)]
mod tests {
pub use super::super::ANSIStrings;
pub use crate::style::Color::*;
pub use crate::style::Style;
#[test]
fn no_control_codes_for_plain() {
let one = Style::default().paint("one");
let two = Style::default().paint("two");
let output = format!("{}", ANSIStrings(&[one, two]));
assert_eq!(&*output, "onetwo");
}
}

View File

@ -0,0 +1,267 @@
//! This is a library for controlling colors and formatting, such as
//! red bold text or blue underlined text, on ANSI terminals.
//!
//!
//! ## Basic usage
//!
//! There are three main types in this crate that you need to be
//! concerned with: [`ANSIString`], [`Style`], and [`Color`].
//!
//! A `Style` holds stylistic information: foreground and background colors,
//! whether the text should be bold, or blinking, or other properties. The
//! [`Color`] enum represents the available colors. And an [`ANSIString`] is a
//! string paired with a [`Style`].
//!
//! [`Color`] is also available as an alias to `Color`.
//!
//! To format a string, call the `paint` method on a `Style` or a `Color`,
//! passing in the string you want to format as the argument. For example,
//! heres how to get some red text:
//!
//! ```
//! use nu_ansi_term::Color::Red;
//!
//! println!("This is in red: {}", Red.paint("a red string"));
//! ```
//!
//! Its important to note that the `paint` method does *not* actually return a
//! string with the ANSI control characters surrounding it. Instead, it returns
//! an [`ANSIString`] value that has a [`Display`] implementation that, when
//! formatted, returns the characters. This allows strings to be printed with a
//! minimum of [`String`] allocations being performed behind the scenes.
//!
//! If you *do* want to get at the escape codes, then you can convert the
//! [`ANSIString`] to a string as you would any other `Display` value:
//!
//! ```
//! use nu_ansi_term::Color::Red;
//!
//! let red_string = Red.paint("a red string").to_string();
//! ```
//!
//!
//! ## Bold, underline, background, and other styles
//!
//! For anything more complex than plain foreground color changes, you need to
//! construct `Style` values themselves, rather than beginning with a `Color`.
//! You can do this by chaining methods based on a new `Style`, created with
//! [`Style::new()`]. Each method creates a new style that has that specific
//! property set. For example:
//!
//! ```
//! use nu_ansi_term::Style;
//!
//! println!("How about some {} and {}?",
//! Style::new().bold().paint("bold"),
//! Style::new().underline().paint("underline"));
//! ```
//!
//! For brevity, these methods have also been implemented for `Color` values,
//! so you can give your styles a foreground color without having to begin with
//! an empty `Style` value:
//!
//! ```
//! use nu_ansi_term::Color::{Blue, Yellow};
//!
//! println!("Demonstrating {} and {}!",
//! Blue.bold().paint("blue bold"),
//! Yellow.underline().paint("yellow underline"));
//!
//! println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!"));
//! ```
//!
//! The complete list of styles you can use are: [`bold`], [`dimmed`], [`italic`],
//! [`underline`], [`blink`], [`reverse`], [`hidden`], [`strikethrough`], and [`on`] for
//! background colors.
//!
//! In some cases, you may find it easier to change the foreground on an
//! existing `Style` rather than starting from the appropriate `Color`.
//! You can do this using the [`fg`] method:
//!
//! ```
//! use nu_ansi_term::Style;
//! use nu_ansi_term::Color::{Blue, Cyan, Yellow};
//!
//! println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!"));
//! println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!"));
//! ```
//!
//! You can turn a `Color` into a `Style` with the [`normal`] method.
//! This will produce the exact same `ANSIString` as if you just used the
//! `paint` method on the `Color` directly, but its useful in certain cases:
//! for example, you may have a method that returns `Styles`, and need to
//! represent both the “red bold” and “red, but not bold” styles with values of
//! the same type. The `Style` struct also has a [`Default`] implementation if you
//! want to have a style with *nothing* set.
//!
//! ```
//! use nu_ansi_term::Style;
//! use nu_ansi_term::Color::Red;
//!
//! Red.normal().paint("yet another red string");
//! Style::default().paint("a completely regular string");
//! ```
//!
//!
//! ## Extended colors
//!
//! You can access the extended range of 256 colors by using the `Color::Fixed`
//! variant, which takes an argument of the color number to use. This can be
//! included wherever you would use a `Color`:
//!
//! ```
//! use nu_ansi_term::Color::Fixed;
//!
//! Fixed(134).paint("A sort of light purple");
//! Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup");
//! ```
//!
//! The first sixteen of these values are the same as the normal and bold
//! standard color variants. Theres nothing stopping you from using these as
//! `Fixed` colors instead, but theres nothing to be gained by doing so
//! either.
//!
//! You can also access full 24-bit color by using the `Color::RGB` variant,
//! which takes separate `u8` arguments for red, green, and blue:
//!
//! ```
//! use nu_ansi_term::Color::RGB;
//!
//! RGB(70, 130, 180).paint("Steel blue");
//! ```
//!
//! ## Combining successive colored strings
//!
//! The benefit of writing ANSI escape codes to the terminal is that they
//! *stack*: you do not need to end every colored string with a reset code if
//! the text that follows it is of a similar style. For example, if you want to
//! have some blue text followed by some blue bold text, its possible to send
//! the ANSI code for blue, followed by the ANSI code for bold, and finishing
//! with a reset code without having to have an extra one between the two
//! strings.
//!
//! This crate can optimise the ANSI codes that get printed in situations like
//! this, making life easier for your terminal renderer. The [`ANSIStrings`]
//! type takes a slice of several [`ANSIString`] values, and will iterate over
//! each of them, printing only the codes for the styles that need to be updated
//! as part of its formatting routine.
//!
//! The following code snippet uses this to enclose a binary number displayed in
//! red bold text inside some red, but not bold, brackets:
//!
//! ```
//! use nu_ansi_term::Color::Red;
//! use nu_ansi_term::{ANSIString, ANSIStrings};
//!
//! let some_value = format!("{:b}", 42);
//! let strings: &[ANSIString<'static>] = &[
//! Red.paint("["),
//! Red.bold().paint(some_value),
//! Red.paint("]"),
//! ];
//!
//! println!("Value: {}", ANSIStrings(strings));
//! ```
//!
//! There are several things to note here. Firstly, the [`paint`] method can take
//! *either* an owned [`String`] or a borrowed [`&str`]. Internally, an [`ANSIString`]
//! holds a copy-on-write ([`Cow`]) string value to deal with both owned and
//! borrowed strings at the same time. This is used here to display a `String`,
//! the result of the `format!` call, using the same mechanism as some
//! statically-available `&str` slices. Secondly, that the [`ANSIStrings`] value
//! works in the same way as its singular counterpart, with a [`Display`]
//! implementation that only performs the formatting when required.
//!
//! ## Byte strings
//!
//! This library also supports formatting `\[u8]` byte strings; this supports
//! applications working with text in an unknown encoding. [`Style`] and
//! [`Color`] support painting `\[u8]` values, resulting in an [`ANSIByteString`].
//! This type does not implement [`Display`], as it may not contain UTF-8, but
//! it does provide a method [`write_to`] to write the result to any value that
//! implements [`Write`]:
//!
//! ```
//! use nu_ansi_term::Color::Green;
//!
//! Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap();
//! ```
//!
//! Similarly, the type [`ANSIByteStrings`] supports writing a list of
//! [`ANSIByteString`] values with minimal escape sequences:
//!
//! ```
//! use nu_ansi_term::Color::Green;
//! use nu_ansi_term::ANSIByteStrings;
//!
//! ANSIByteStrings(&[
//! Green.paint("user data 1\n".as_bytes()),
//! Green.bold().paint("user data 2\n".as_bytes()),
//! ]).write_to(&mut std::io::stdout()).unwrap();
//! ```
//!
//! [`Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html
//! [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
//! [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html
//! [`String`]: https://doc.rust-lang.org/std/string/struct.String.html
//! [`&str`]: https://doc.rust-lang.org/std/primitive.str.html
//! [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
//! [`Style`]: struct.Style.html
//! [`Style::new()`]: struct.Style.html#method.new
//! [`Color`]: enum.Color.html
//! [`Color`]: enum.Color.html
//! [`ANSIString`]: type.ANSIString.html
//! [`ANSIStrings`]: type.ANSIStrings.html
//! [`ANSIByteString`]: type.ANSIByteString.html
//! [`ANSIByteStrings`]: type.ANSIByteStrings.html
//! [`write_to`]: type.ANSIByteString.html#method.write_to
//! [`paint`]: type.ANSIByteString.html#method.write_to
//! [`normal`]: enum.Color.html#method.normal
//!
//! [`bold`]: struct.Style.html#method.bold
//! [`dimmed`]: struct.Style.html#method.dimmed
//! [`italic`]: struct.Style.html#method.italic
//! [`underline`]: struct.Style.html#method.underline
//! [`blink`]: struct.Style.html#method.blink
//! [`reverse`]: struct.Style.html#method.reverse
//! [`hidden`]: struct.Style.html#method.hidden
//! [`strikethrough`]: struct.Style.html#method.strikethrough
//! [`fg`]: struct.Style.html#method.fg
//! [`on`]: struct.Style.html#method.on
#![crate_name = "nu_ansi_term"]
#![crate_type = "rlib"]
#![crate_type = "dylib"]
#![warn(missing_copy_implementations)]
#![warn(missing_docs)]
#![warn(trivial_casts, trivial_numeric_casts)]
// #![warn(unused_extern_crates, unused_qualifications)]
#[cfg(target_os = "windows")]
extern crate winapi;
#[cfg(test)]
#[macro_use]
extern crate doc_comment;
#[cfg(test)]
doctest!("../README.md");
mod ansi;
pub use ansi::{Infix, Prefix, Suffix};
mod style;
pub use style::{Color, Style};
mod difference;
mod display;
pub use display::*;
mod write;
mod windows;
pub use windows::*;
mod util;
pub use util::*;
mod debug;

View File

@ -0,0 +1,620 @@
/// A style is a collection of properties that can format a string
/// using ANSI escape codes.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::{Style, Color};
///
/// let style = Style::new().bold().on(Color::Black);
/// println!("{}", style.paint("Bold on black"));
/// ```
#[derive(PartialEq, Clone, Copy)]
#[cfg_attr(
feature = "derive_serde_style",
derive(serde::Deserialize, serde::Serialize)
)]
pub struct Style {
/// The style's foreground color, if it has one.
pub foreground: Option<Color>,
/// The style's background color, if it has one.
pub background: Option<Color>,
/// Whether this style is bold.
pub is_bold: bool,
/// Whether this style is dimmed.
pub is_dimmed: bool,
/// Whether this style is italic.
pub is_italic: bool,
/// Whether this style is underlined.
pub is_underline: bool,
/// Whether this style is blinking.
pub is_blink: bool,
/// Whether this style has reverse colors.
pub is_reverse: bool,
/// Whether this style is hidden.
pub is_hidden: bool,
/// Whether this style is struckthrough.
pub is_strikethrough: bool,
}
impl Style {
/// Creates a new Style with no properties set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new();
/// println!("{}", style.paint("hi"));
/// ```
pub fn new() -> Style {
Style::default()
}
/// Returns a `Style` with the bold property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new().bold();
/// println!("{}", style.paint("hey"));
/// ```
pub fn bold(&self) -> Style {
Style {
is_bold: true,
..*self
}
}
/// Returns a `Style` with the dimmed property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new().dimmed();
/// println!("{}", style.paint("sup"));
/// ```
pub fn dimmed(&self) -> Style {
Style {
is_dimmed: true,
..*self
}
}
/// Returns a `Style` with the italic property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new().italic();
/// println!("{}", style.paint("greetings"));
/// ```
pub fn italic(&self) -> Style {
Style {
is_italic: true,
..*self
}
}
/// Returns a `Style` with the underline property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new().underline();
/// println!("{}", style.paint("salutations"));
/// ```
pub fn underline(&self) -> Style {
Style {
is_underline: true,
..*self
}
}
/// Returns a `Style` with the blink property set.
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new().blink();
/// println!("{}", style.paint("wazzup"));
/// ```
pub fn blink(&self) -> Style {
Style {
is_blink: true,
..*self
}
}
/// Returns a `Style` with the reverse property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new().reverse();
/// println!("{}", style.paint("aloha"));
/// ```
pub fn reverse(&self) -> Style {
Style {
is_reverse: true,
..*self
}
}
/// Returns a `Style` with the hidden property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new().hidden();
/// println!("{}", style.paint("ahoy"));
/// ```
pub fn hidden(&self) -> Style {
Style {
is_hidden: true,
..*self
}
}
/// Returns a `Style` with the strikethrough property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new().strikethrough();
/// println!("{}", style.paint("yo"));
/// ```
pub fn strikethrough(&self) -> Style {
Style {
is_strikethrough: true,
..*self
}
}
/// Returns a `Style` with the foreground color property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::{Style, Color};
///
/// let style = Style::new().fg(Color::Yellow);
/// println!("{}", style.paint("hi"));
/// ```
pub fn fg(&self, foreground: Color) -> Style {
Style {
foreground: Some(foreground),
..*self
}
}
/// Returns a `Style` with the background color property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::{Style, Color};
///
/// let style = Style::new().on(Color::Blue);
/// println!("{}", style.paint("eyyyy"));
/// ```
pub fn on(&self, background: Color) -> Style {
Style {
background: Some(background),
..*self
}
}
/// Return true if this `Style` has no actual styles, and can be written
/// without any control characters.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// assert_eq!(true, Style::default().is_plain());
/// assert_eq!(false, Style::default().bold().is_plain());
/// ```
pub fn is_plain(self) -> bool {
self == Style::default()
}
}
impl Default for Style {
/// Returns a style with *no* properties set. Formatting text using this
/// style returns the exact same text.
///
/// ```
/// use nu_ansi_term::Style;
/// assert_eq!(None, Style::default().foreground);
/// assert_eq!(None, Style::default().background);
/// assert_eq!(false, Style::default().is_bold);
/// assert_eq!("txt", Style::default().paint("txt").to_string());
/// ```
fn default() -> Style {
Style {
foreground: None,
background: None,
is_bold: false,
is_dimmed: false,
is_italic: false,
is_underline: false,
is_blink: false,
is_reverse: false,
is_hidden: false,
is_strikethrough: false,
}
}
}
// ---- colors ----
/// A color is one specific type of ANSI escape code, and can refer
/// to either the foreground or background color.
///
/// These use the standard numeric sequences.
/// See <http://invisible-island.net/xterm/ctlseqs/ctlseqs.html>
#[derive(PartialEq, Clone, Copy, Debug)]
#[cfg_attr(
feature = "derive_serde_style",
derive(serde::Deserialize, serde::Serialize)
)]
pub enum Color {
/// Color #0 (foreground code `30`, background code `40`).
///
/// This is not necessarily the background color, and using it as one may
/// render the text hard to read on terminals with dark backgrounds.
Black,
/// Color #0 (foreground code `90`, background code `100`).
DarkGray,
/// Color #1 (foreground code `31`, background code `41`).
Red,
/// Color #1 (foreground code `91`, background code `101`).
LightRed,
/// Color #2 (foreground code `32`, background code `42`).
Green,
/// Color #2 (foreground code `92`, background code `102`).
LightGreen,
/// Color #3 (foreground code `33`, background code `43`).
Yellow,
/// Color #3 (foreground code `93`, background code `103`).
LightYellow,
/// Color #4 (foreground code `34`, background code `44`).
Blue,
/// Color #4 (foreground code `94`, background code `104`).
LightBlue,
/// Color #5 (foreground code `35`, background code `45`).
Purple,
/// Color #5 (foreground code `95`, background code `105`).
LightPurple,
/// Color #5 (foreground code `35`, background code `45`).
Magenta,
/// Color #5 (foreground code `95`, background code `105`).
LightMagenta,
/// Color #6 (foreground code `36`, background code `46`).
Cyan,
/// Color #6 (foreground code `96`, background code `106`).
LightCyan,
/// Color #7 (foreground code `37`, background code `47`).
///
/// As above, this is not necessarily the foreground color, and may be
/// hard to read on terminals with light backgrounds.
White,
/// Color #7 (foreground code `97`, background code `107`).
LightGray,
/// A color number from 0 to 255, for use in 256-color terminal
/// environments.
///
/// - colors 0 to 7 are the `Black` to `White` variants respectively.
/// These colors can usually be changed in the terminal emulator.
/// - colors 8 to 15 are brighter versions of the eight colors above.
/// These can also usually be changed in the terminal emulator, or it
/// could be configured to use the original colors and show the text in
/// bold instead. It varies depending on the program.
/// - colors 16 to 231 contain several palettes of bright colors,
/// arranged in six squares measuring six by six each.
/// - colors 232 to 255 are shades of grey from black to white.
///
/// It might make more sense to look at a [color chart][cc].
///
/// [cc]: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg
Fixed(u8),
/// A 24-bit RGB color, as specified by ISO-8613-3.
RGB(u8, u8, u8),
}
impl Color {
/// Returns a `Style` with the foreground color set to this color.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::Red.normal();
/// println!("{}", style.paint("hi"));
/// ```
pub fn normal(self) -> Style {
Style {
foreground: Some(self),
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// bold property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::Green.bold();
/// println!("{}", style.paint("hey"));
/// ```
pub fn bold(self) -> Style {
Style {
foreground: Some(self),
is_bold: true,
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// dimmed property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::Yellow.dimmed();
/// println!("{}", style.paint("sup"));
/// ```
pub fn dimmed(self) -> Style {
Style {
foreground: Some(self),
is_dimmed: true,
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// italic property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::Blue.italic();
/// println!("{}", style.paint("greetings"));
/// ```
pub fn italic(self) -> Style {
Style {
foreground: Some(self),
is_italic: true,
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// underline property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::Purple.underline();
/// println!("{}", style.paint("salutations"));
/// ```
pub fn underline(self) -> Style {
Style {
foreground: Some(self),
is_underline: true,
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// blink property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::Cyan.blink();
/// println!("{}", style.paint("wazzup"));
/// ```
pub fn blink(self) -> Style {
Style {
foreground: Some(self),
is_blink: true,
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// reverse property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::Black.reverse();
/// println!("{}", style.paint("aloha"));
/// ```
pub fn reverse(self) -> Style {
Style {
foreground: Some(self),
is_reverse: true,
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// hidden property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::White.hidden();
/// println!("{}", style.paint("ahoy"));
/// ```
pub fn hidden(self) -> Style {
Style {
foreground: Some(self),
is_hidden: true,
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// strikethrough property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::Fixed(244).strikethrough();
/// println!("{}", style.paint("yo"));
/// ```
pub fn strikethrough(self) -> Style {
Style {
foreground: Some(self),
is_strikethrough: true,
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// background color property set to the given color.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::RGB(31, 31, 31).on(Color::White);
/// println!("{}", style.paint("eyyyy"));
/// ```
pub fn on(self, background: Color) -> Style {
Style {
foreground: Some(self),
background: Some(background),
..Style::default()
}
}
}
impl From<Color> for Style {
/// You can turn a `Color` into a `Style` with the foreground color set
/// with the `From` trait.
///
/// ```
/// use nu_ansi_term::{Style, Color};
/// let green_foreground = Style::default().fg(Color::Green);
/// assert_eq!(green_foreground, Color::Green.normal());
/// assert_eq!(green_foreground, Color::Green.into());
/// assert_eq!(green_foreground, Style::from(Color::Green));
/// ```
fn from(color: Color) -> Style {
color.normal()
}
}
#[cfg(test)]
#[cfg(feature = "derive_serde_style")]
mod serde_json_tests {
use super::{Color, Style};
#[test]
fn color_serialization() {
let colors = &[
Color::Red,
Color::Blue,
Color::RGB(123, 123, 123),
Color::Fixed(255),
];
assert_eq!(
serde_json::to_string(&colors).unwrap(),
String::from("[\"Red\",\"Blue\",{\"RGB\":[123,123,123]},{\"Fixed\":255}]")
);
}
#[test]
fn color_deserialization() {
let colors = &[
Color::Red,
Color::Blue,
Color::RGB(123, 123, 123),
Color::Fixed(255),
];
for color in colors.into_iter() {
let serialized = serde_json::to_string(&color).unwrap();
let deserialized: Color = serde_json::from_str(&serialized).unwrap();
assert_eq!(color, &deserialized);
}
}
#[test]
fn style_serialization() {
let style = Style::default();
assert_eq!(serde_json::to_string(&style).unwrap(), "{\"foreground\":null,\"background\":null,\"is_bold\":false,\"is_dimmed\":false,\"is_italic\":false,\"is_underline\":false,\"is_blink\":false,\"is_reverse\":false,\"is_hidden\":false,\"is_strikethrough\":false}".to_string());
}
}

View File

@ -0,0 +1,80 @@
use crate::display::{ANSIString, ANSIStrings};
use std::ops::Deref;
/// Return a substring of the given ANSIStrings sequence, while keeping the formatting.
pub fn sub_string<'a>(
start: usize,
len: usize,
strs: &ANSIStrings<'a>,
) -> Vec<ANSIString<'static>> {
let mut vec = Vec::new();
let mut pos = start;
let mut len_rem = len;
for i in strs.0.iter() {
let fragment = i.deref();
let frag_len = fragment.len();
if pos >= frag_len {
pos -= frag_len;
continue;
}
if len_rem == 0 {
break;
}
let end = pos + len_rem;
let pos_end = if end >= frag_len { frag_len } else { end };
vec.push(i.style_ref().paint(String::from(&fragment[pos..pos_end])));
if end <= frag_len {
break;
}
len_rem -= pos_end - pos;
pos = 0;
}
vec
}
/// Return a concatenated copy of `strs` without the formatting, as an allocated `String`.
pub fn unstyle(strs: &ANSIStrings) -> String {
let mut s = String::new();
for i in strs.0.iter() {
s += &i.deref();
}
s
}
/// Return the unstyled length of ANSIStrings. This is equaivalent to `unstyle(strs).len()`.
pub fn unstyled_len(strs: &ANSIStrings) -> usize {
let mut l = 0;
for i in strs.0.iter() {
l += i.deref().len();
}
l
}
#[cfg(test)]
mod test {
use super::*;
use crate::Color::*;
#[test]
fn test() {
let l = [
Black.paint("first"),
Red.paint("-second"),
White.paint("-third"),
];
let a = ANSIStrings(&l);
assert_eq!(unstyle(&a), "first-second-third");
assert_eq!(unstyled_len(&a), 18);
let l2 = [Black.paint("st"), Red.paint("-second"), White.paint("-t")];
assert_eq!(sub_string(3, 11, &a).as_slice(), &l2);
}
}

View File

@ -0,0 +1,62 @@
/// Enables ANSI code support on Windows 10.
///
/// This uses Windows API calls to alter the properties of the console that
/// the program is running in.
///
/// https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx
///
/// Returns a `Result` with the Windows error code if unsuccessful.
#[cfg(windows)]
pub fn enable_ansi_support() -> Result<(), u32> {
// ref: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#EXAMPLE_OF_ENABLING_VIRTUAL_TERMINAL_PROCESSING @@ https://archive.is/L7wRJ#76%
use std::ffi::OsStr;
use std::iter::once;
use std::os::windows::ffi::OsStrExt;
use std::ptr::null_mut;
use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::fileapi::{CreateFileW, OPEN_EXISTING};
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
use winapi::um::winnt::{FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE};
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
unsafe {
// ref: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
// Using `CreateFileW("CONOUT$", ...)` to retrieve the console handle works correctly even if STDOUT and/or STDERR are redirected
let console_out_name: Vec<u16> =
OsStr::new("CONOUT$").encode_wide().chain(once(0)).collect();
let console_handle = CreateFileW(
console_out_name.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE,
null_mut(),
OPEN_EXISTING,
0,
null_mut(),
);
if console_handle == INVALID_HANDLE_VALUE {
return Err(GetLastError());
}
// ref: https://docs.microsoft.com/en-us/windows/console/getconsolemode
let mut console_mode: u32 = 0;
if 0 == GetConsoleMode(console_handle, &mut console_mode) {
return Err(GetLastError());
}
// VT processing not already enabled?
if console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
if 0 == SetConsoleMode(
console_handle,
console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING,
) {
return Err(GetLastError());
}
}
}
Ok(())
}

View File

@ -0,0 +1,37 @@
use std::fmt;
use std::io;
pub trait AnyWrite {
type Wstr: ?Sized;
type Error;
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error>;
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error>;
}
impl<'a> AnyWrite for dyn fmt::Write + 'a {
type Wstr = str;
type Error = fmt::Error;
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> {
fmt::Write::write_fmt(self, fmt)
}
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error> {
fmt::Write::write_str(self, s)
}
}
impl<'a> AnyWrite for dyn io::Write + 'a {
type Wstr = [u8];
type Error = io::Error;
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> {
io::Write::write_fmt(self, fmt)
}
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error> {
io::Write::write_all(self, s)
}
}

View File

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

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

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,301 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::data::value::{format_leaf, style_leaf};
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
use nu_table::{draw_table, Alignment, StyledString, TextStyle, Theme};
use std::time::Instant;
const STREAM_PAGE_SIZE: usize = 1000;
const STREAM_TIMEOUT_CHECK_INTERVAL: usize = 100;
pub struct Table;
#[async_trait]
impl WholeStreamCommand for Table {
fn name(&self) -> &str {
"table"
}
fn signature(&self) -> Signature {
Signature::build("table").named(
"start_number",
SyntaxShape::Number,
"row number to start viewing from",
Some('n'),
)
}
fn usage(&self) -> &str {
"View the contents of the pipeline as a table."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
table(args, registry).await
}
}
fn str_to_color(s: String) -> Option<ansi_term::Color> {
match s.as_str() {
"g" | "green" => Some(ansi_term::Color::Green),
"r" | "red" => Some(ansi_term::Color::Red),
"u" | "blue" => Some(ansi_term::Color::Blue),
"b" | "black" => Some(ansi_term::Color::Black),
"y" | "yellow" => Some(ansi_term::Color::Yellow),
"p" | "purple" => Some(ansi_term::Color::Purple),
"c" | "cyan" => Some(ansi_term::Color::Cyan),
"w" | "white" => Some(ansi_term::Color::White),
_ => None,
}
}
pub fn from_list(values: &[Value], starting_idx: usize) -> nu_table::Table {
let config = crate::data::config::config(Tag::unknown());
let header_style = if let Ok(config) = &config {
let header_align = config.get("header_align").map_or(Alignment::Left, |a| {
a.as_string()
.map_or(Alignment::Center, |a| match a.to_lowercase().as_str() {
"center" | "c" => Alignment::Center,
"right" | "r" => Alignment::Right,
_ => Alignment::Center,
})
});
let header_color = match config.get("header_color") {
Some(c) => match c.as_string() {
Ok(color) => str_to_color(color.to_lowercase()).unwrap_or(ansi_term::Color::Green),
_ => ansi_term::Color::Green,
},
_ => ansi_term::Color::Green,
};
let header_bold = config
.get("header_bold")
.map(|x| x.as_bool().unwrap_or(true))
.unwrap_or(true);
TextStyle {
alignment: header_align,
color: Some(header_color),
is_bold: header_bold,
}
} else {
TextStyle::default_header()
};
let mut headers: Vec<StyledString> = nu_protocol::merge_descriptors(values)
.into_iter()
.map(|x| StyledString::new(x, header_style.clone()))
.collect();
let entries = values_to_entries(values, &mut headers, starting_idx);
if let Ok(config) = config {
if let Some(style) = config.get("table_mode") {
if let Ok(table_mode) = style.as_string() {
if table_mode == "light" {
return nu_table::Table {
headers,
data: entries,
theme: Theme::light(),
};
}
}
}
}
nu_table::Table {
headers,
data: entries,
theme: Theme::compact(),
}
}
fn are_table_indexes_disabled() -> bool {
let config = crate::data::config::config(Tag::unknown());
match config {
Ok(config) => {
let disable_indexes = config.get("disable_table_indexes");
disable_indexes.map_or(false, |x| x.as_bool().unwrap_or(false))
}
_ => false,
}
}
fn values_to_entries(
values: &[Value],
headers: &mut Vec<StyledString>,
starting_idx: usize,
) -> Vec<Vec<StyledString>> {
let disable_indexes = are_table_indexes_disabled();
let mut entries = vec![];
if headers.is_empty() {
headers.push(StyledString::new("".to_string(), TextStyle::basic()));
}
for (idx, value) in values.iter().enumerate() {
let mut row: Vec<StyledString> = headers
.iter()
.map(|d: &StyledString| {
if d.contents == "" {
match value {
Value {
value: UntaggedValue::Row(..),
..
} => StyledString::new(
format_leaf(&UntaggedValue::nothing()).plain_string(100_000),
style_leaf(&UntaggedValue::nothing()),
),
_ => StyledString::new(
format_leaf(value).plain_string(100_000),
style_leaf(value),
),
}
} else {
match value {
Value {
value: UntaggedValue::Row(..),
..
} => {
let data = value.get_data(&d.contents);
StyledString::new(
format_leaf(data.borrow()).plain_string(100_000),
style_leaf(data.borrow()),
)
}
_ => StyledString::new(
format_leaf(&UntaggedValue::nothing()).plain_string(100_000),
style_leaf(&UntaggedValue::nothing()),
),
}
}
})
.collect();
// Indices are green, bold, right-aligned:
if !disable_indexes {
row.insert(
0,
StyledString::new(
(starting_idx + idx).to_string(),
TextStyle {
alignment: Alignment::Right,
color: Some(ansi_term::Color::Green),
is_bold: true,
},
),
);
}
entries.push(row);
}
if !disable_indexes {
headers.insert(
0,
StyledString::new(
"#".to_owned(),
TextStyle {
alignment: Alignment::Center,
color: Some(ansi_term::Color::Green),
is_bold: true,
},
),
);
}
entries
}
async fn table(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let mut args = args.evaluate_once(&registry).await?;
let mut finished = false;
// let host = args.host.clone();
let mut start_number = match args.get("start_number") {
Some(Value {
value: UntaggedValue::Primitive(Primitive::Int(i)),
..
}) => {
if let Some(num) = i.to_usize() {
num
} else {
return Err(ShellError::labeled_error(
"Expected a row number",
"expected a row number",
&args.args.call_info.name_tag,
));
}
}
_ => 0,
};
let mut delay_slot = None;
let term_width = args.host.lock().width();
while !finished {
let mut new_input: VecDeque<Value> = VecDeque::new();
let start_time = Instant::now();
for idx in 0..STREAM_PAGE_SIZE {
if let Some(val) = delay_slot {
new_input.push_back(val);
delay_slot = None;
} else {
match args.input.next().await {
Some(a) => {
if !new_input.is_empty() {
if let Some(descs) = new_input.get(0) {
let descs = descs.data_descriptors();
let compare = a.data_descriptors();
if descs != compare {
delay_slot = Some(a);
break;
} else {
new_input.push_back(a);
}
} else {
new_input.push_back(a);
}
} else {
new_input.push_back(a);
}
}
_ => {
finished = true;
break;
}
}
// Check if we've gone over our buffering threshold
if (idx + 1) % STREAM_TIMEOUT_CHECK_INTERVAL == 0 {
let end_time = Instant::now();
// If we've been buffering over a second, go ahead and send out what we have so far
if (end_time - start_time).as_secs() >= 1 {
break;
}
}
}
}
let input: Vec<Value> = new_input.into();
if !input.is_empty() {
let t = from_list(&input, start_number);
draw_table(&t, term_width);
}
start_number += input.len();
}
Ok(OutputStream::empty())
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
use crate::commands;
use commands::autoenv;
use indexmap::{IndexMap, IndexSet};
use nu_command::commands::autoenv;
use nu_errors::ShellError;
use serde::Deserialize;
use std::env::*;
@ -52,7 +51,7 @@ impl DirectorySpecificEnvironment {
}
}
fn toml_if_trusted(&mut self, nu_env_file: &PathBuf) -> Result<NuEnvDoc, ShellError> {
fn toml_if_trusted(&mut self, nu_env_file: &Path) -> Result<NuEnvDoc, ShellError> {
let content = std::fs::read(&nu_env_file)?;
if autoenv::file_is_trusted(&nu_env_file, &content)? {
@ -112,7 +111,7 @@ impl DirectorySpecificEnvironment {
if let Some(es) = nu_env_doc.entryscripts {
for s in es {
run(s.as_str())?;
run(s.as_str(), None)?;
}
}
@ -147,7 +146,7 @@ impl DirectorySpecificEnvironment {
new_exitscripts.insert(dir, scripts);
} else {
for s in scripts {
run(s.as_str())?;
run(s.as_str(), Some(&dir))?;
}
}
}
@ -162,7 +161,7 @@ impl DirectorySpecificEnvironment {
pub fn maybe_add_key(
&mut self,
seen_vars: &mut IndexSet<EnvKey>,
dir: &PathBuf,
dir: &Path,
key: &str,
val: &str,
) {
@ -170,7 +169,7 @@ impl DirectorySpecificEnvironment {
if !seen_vars.contains(key) {
seen_vars.insert(key.to_string());
self.added_vars
.entry(dir.clone())
.entry(PathBuf::from(dir))
.or_insert(IndexMap::new())
.insert(key.to_string(), var_os(key));
@ -215,9 +214,23 @@ impl DirectorySpecificEnvironment {
}
}
fn run(cmd: &str) -> Result<(), ShellError> {
fn run(cmd: &str, dir: Option<&PathBuf>) -> Result<(), ShellError> {
if cfg!(target_os = "windows") {
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" {
@ -69,16 +95,18 @@ impl EnvironmentSyncer {
ctx.with_host(|host| {
host.env_set(
std::ffi::OsString::from(var.0),
std::ffi::OsString::from(string),
std::ffi::OsString::from(&string),
)
});
ctx.scope.add_env_var_to_base(var.0, string);
}
}
}
}
}
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() {
@ -102,15 +130,21 @@ impl EnvironmentSyncer {
if let Ok(paths_ready) = prepared {
ctx.with_host(|host| {
host.env_set(std::ffi::OsString::from("PATH"), paths_ready);
host.env_set(
std::ffi::OsString::from("PATH"),
std::ffi::OsString::from(&paths_ready),
);
});
ctx.scope
.add_env_var_to_base("PATH", paths_ready.to_string_lossy().to_string());
}
}
}
}
#[cfg(test)]
pub fn clear_env_vars(&mut self, ctx: &mut Context) {
pub fn clear_env_vars(&mut self, ctx: &mut EvaluationContext) {
for (key, _value) in ctx.with_host(|host| host.vars()) {
if key != "path" && key != "PATH" {
ctx.with_host(|host| host.env_rm(std::ffi::OsString::from(key)));
@ -119,7 +153,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 +161,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 +172,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 +381,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 +457,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 +544,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, ExternalRedirection, SpannedExpression};
use nu_protocol::{
ColumnPath, Primitive, RangeInclusion, UnspannedPathMember, UntaggedValue, Value,
};
#[async_recursion]
pub(crate) async fn evaluate_baseline_expr(
expr: &SpannedExpression,
registry: &CommandRegistry,
it: &Value,
vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>,
) -> Result<Value, ShellError> {
let tag = Tag {
span: expr.span,
anchor: None,
};
let span = expr.span;
match &expr.expr {
Expression::Literal(literal) => Ok(evaluate_literal(&literal, span)),
Expression::ExternalWord => Err(ShellError::argument_error(
"Invalid external word".spanned(tag.span),
ArgumentError::InvalidExternalWord,
)),
Expression::FilePath(path) => Ok(UntaggedValue::path(path.clone()).into_value(tag)),
Expression::Synthetic(hir::Synthetic::String(s)) => {
Ok(UntaggedValue::string(s).into_untagged_value())
}
Expression::Variable(var) => evaluate_reference(&var, it, vars, env, tag),
Expression::Command(_) => unimplemented!(),
Expression::Invocation(block) => evaluate_invocation(block, registry, it, vars, env).await,
Expression::ExternalCommand(_) => unimplemented!(),
Expression::Binary(binary) => {
// TODO: If we want to add short-circuiting, we'll need to move these down
let left = evaluate_baseline_expr(&binary.left, registry, it, vars, env).await?;
let right = evaluate_baseline_expr(&binary.right, registry, it, vars, env).await?;
trace!("left={:?} right={:?}", left.value, right.value);
match binary.op.expr {
Expression::Literal(hir::Literal::Operator(op)) => {
match apply_operator(op, &left, &right) {
Ok(result) => match result {
UntaggedValue::Error(shell_err) => Err(shell_err),
_ => Ok(result.into_value(tag)),
},
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(binary.left.span),
right_type.spanned(binary.right.span),
)),
}
}
_ => unreachable!(),
}
}
Expression::Range(range) => {
let left = &range.left;
let right = &range.right;
let left = evaluate_baseline_expr(&left, registry, it, vars, env).await?;
let right = evaluate_baseline_expr(&right, registry, it, vars, env).await?;
let left_span = left.tag.span;
let right_span = right.tag.span;
let left = (
left.as_primitive()?.spanned(left_span),
RangeInclusion::Inclusive,
);
let right = (
right.as_primitive()?.spanned(right_span),
RangeInclusion::Inclusive,
);
Ok(UntaggedValue::range(left, right).into_value(tag))
}
Expression::List(list) => {
let mut exprs = vec![];
for expr in list {
let expr = evaluate_baseline_expr(&expr, registry, it, vars, env).await?;
exprs.push(expr);
}
Ok(UntaggedValue::Table(exprs).into_value(tag))
}
Expression::Block(block) => Ok(UntaggedValue::Block(block.clone()).into_value(&tag)),
Expression::Path(path) => {
let value = evaluate_baseline_expr(&path.head, registry, it, vars, env).await?;
let mut item = value;
for member in &path.tail {
let next = item.get_data_by_member(member);
match next {
Err(err) => {
let possibilities = item.data_descriptors();
if let UnspannedPathMember::String(name) = &member.unspanned {
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|x| (natural::distance::levenshtein_distance(x, &name), x))
.collect();
possible_matches.sort();
if !possible_matches.is_empty() {
return Err(ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", possible_matches[0].1),
&member.span,
));
} else {
return Err(err);
}
}
}
Ok(next) => {
item = next.clone().value.into_value(&tag);
}
};
}
Ok(item.value.into_value(tag))
}
Expression::Boolean(_boolean) => unimplemented!(),
Expression::Garbage => unimplemented!(),
}
}
fn evaluate_literal(literal: &hir::Literal, span: Span) -> Value {
match &literal {
hir::Literal::ColumnPath(path) => {
let members = path.iter().map(|member| member.to_path_member()).collect();
UntaggedValue::Primitive(Primitive::ColumnPath(ColumnPath::new(members)))
.into_value(span)
}
hir::Literal::Number(int) => match int {
nu_protocol::hir::Number::Int(i) => UntaggedValue::int(i.clone()).into_value(span),
nu_protocol::hir::Number::Decimal(d) => {
UntaggedValue::decimal(d.clone()).into_value(span)
}
},
hir::Literal::Size(int, unit) => unit.compute(&int).into_value(span),
hir::Literal::String(string) => UntaggedValue::string(string).into_value(span),
hir::Literal::GlobPattern(pattern) => UntaggedValue::pattern(pattern).into_value(span),
hir::Literal::Bare(bare) => UntaggedValue::string(bare.clone()).into_value(span),
hir::Literal::Operator(_) => unimplemented!("Not sure what to do with operator yet"),
}
}
fn evaluate_reference(
name: &hir::Variable,
it: &Value,
vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>,
tag: Tag,
) -> Result<Value, ShellError> {
match name {
hir::Variable::It(_) => Ok(it.clone()),
hir::Variable::Other(name, _) => match name {
x if x == "$nu" => crate::evaluate::variables::nu(env, tag),
x if x == "$true" => Ok(Value {
value: UntaggedValue::boolean(true),
tag,
}),
x if x == "$false" => Ok(Value {
value: UntaggedValue::boolean(false),
tag,
}),
x => Ok(vars
.get(x)
.cloned()
.unwrap_or_else(|| UntaggedValue::nothing().into_value(tag))),
},
}
}
async fn evaluate_invocation(
block: &hir::Block,
registry: &CommandRegistry,
it: &Value,
vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>,
) -> Result<Value, ShellError> {
// FIXME: we should use a real context here
let mut context = Context::basic()?;
context.registry = registry.clone();
let input = InputStream::one(it.clone());
let mut block = block.clone();
block.set_redirect(ExternalRedirection::Stdout);
let result = run_block(&block, &mut context, input, it, vars, env).await?;
let output = result.into_vec().await;
if let Some(e) = context.get_errors().get(0) {
return Err(e.clone());
}
match output.len() {
x if x > 1 => Ok(UntaggedValue::Table(output).into_value(Tag::unknown())),
1 => Ok(output[0].clone()),
_ => Ok(UntaggedValue::nothing().into_value(Tag::unknown())),
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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