Compare commits

...

605 Commits

Author SHA1 Message Date
JT
610e3911f6 Bump to 0.41 (#4187) 2021-12-08 06:21:00 +13:00
ee9eddd851 avoid unnecessary allocation (#4178) 2021-12-06 07:38:58 +13:00
JT
c08e145501 Fix clippy warnings (#4176) 2021-12-03 07:05:38 +13:00
c00853a473 Seems like accessing $it outside each is not possible now (#4000) 2021-12-03 06:49:24 +13:00
79c7b20cfd add login shell flag (#4175) 2021-12-02 20:05:04 +13:00
JT
89cbfd758d Remove 'arboard' (#4174) 2021-12-02 08:48:03 +13:00
e6e6b730f3 Bye bye upx sorry (#4173)
* bye bye upx, let's try stripping alone

* remove all stripping - not sure it's even working
2021-11-30 13:34:16 -06:00
0fe6a7c1b5 bye bye upx, let's try stripping alone (#4172) 2021-11-30 12:11:01 -06:00
1794ad51bd Sanitize arguments to external commands a bit better (#4157)
* fix #4140

We are passing commands into a shell underneath but we were not
escaping arguments correctly. This new version of the code also takes
into consideration the ";" and "&" characters, which have special
meaning in shells.

We would probably benefit from a more robust way to join arguments to
shell programs. Python's stdlib has shlex.join, and perhaps we can
take that implementation as a reference.

* clean up escaping of posix shell args

I believe the right place to do escaping of arguments was in the
spawn_sh_command function. Note that this change prevents things like:

^echo "$(ls)"

from executing the ls command. Instead, this will just print

$(ls)

The regex has been taken from the python stdlib implementation of shlex.quote

* fix non-literal parameters and single quotes

* address clippy's comments

* fixup! address clippy's comments

* test that subshell commands are sanitized properly
2021-11-29 09:46:42 -06:00
fb197f562a save --append: create file if it doesn't exist (#4156)
* have save --append create file if not exists

Currently, doing:

echo a | save --raw --append file.txt

will fail if file.txt does not exist. This PR changes that

* test that `save --append` will create new file
2021-11-26 12:27:41 -06:00
91c270c14a fix markup (#4155) 2021-11-26 07:37:50 -06:00
3e93ae8af4 Correct spelling (#4152) 2021-11-25 11:11:20 -06:00
e06df124ca upgrading dependencies (#4135)
* upgrade dependencies
num-bigint 0.3.1 -> 0.4.3
bigdecimal-rs 0.2.1 -> bigdecimal 0.3.0
s3hander 0.7 -> 0.7.5
bat 0.18 -> 0.18, default-features = false

* upgrade arboard 1.1.0 -> 2.0.1

* in polars use comfy-table instead of prettytable-rs
the last release of prettytable-rs was `0.8.0 Sep 27, 2018`
and it uses `term 0.5` as a dependency

* upgrade dependencies

* upgrade trash -> 2.0.1

Co-authored-by: ahkrr <alexhk@protonmail.com>
2021-11-20 07:11:11 -06:00
JT
2590fcbe5c Bump to 0.40 (#4129) 2021-11-16 21:53:03 +13:00
JT
09691ff866 Delete docker-publish.yml 2021-11-16 14:19:35 +13:00
16db368232 upgrade polars to 0.17 (#4122) 2021-11-16 12:01:02 +13:00
JT
df87d90b8c Add 'detect columns' command (#4127)
* Add 'detect columns' command

* Fix warnings
2021-11-16 11:29:54 +13:00
f2f01b8a4d missed from_mp4, added back (#4128) 2021-11-15 16:19:44 -06:00
6c0190cd38 added upx and strip to mac and windows (#4126) 2021-11-15 15:32:48 -06:00
b26246bf12 trying upx and strip (#4125) 2021-11-15 15:01:25 -06:00
36a4effbb2 tweaked strip ci (#4124) 2021-11-15 14:30:32 -06:00
9fca417f8c update release to allow running manually (#4123) 2021-11-15 14:04:00 -06:00
d09e1148b2 add the ability to strip the debug symbols for smaller binaries on mac and linux 2021-11-15 13:47:46 -06:00
493bc2b1c9 Update README (#4118)
`winget install nu` fails because there's other options for "nu" now.
Using the full `nushell` word solved it for me.

[Imgur](https://imgur.com/aqz2qNp)
2021-11-14 19:34:57 +13:00
74b812228c upgrade dependencies (#4116)
* remove unused dependencies

* upgrade dependency bytes 0.5.6 -> 1.1.0

* upgrade dependency heapless 0.6.1 -> 0.7.8

* upgrade dependency image 0.22.4 -> 0.23.14

* upgrade dependency mp4 0.8.2 -> 0.9.0

* upgrade dependency bson 0.14.1 -> 2.0.1

Bson::Undefined, Bson::MaxKey, Bson::MinKey and Bson::DbPointer
weren't present in the previous version.

Co-authored-by: ahkrr <alexhk@protonmail.com>
2021-11-14 19:32:21 +13:00
649b3804c1 fix: panic! during parsing (#4107)
Typing `selector -qa` into nu would cause a `panic!`
This was the case because the inner loop incremented the `idx`
that was only checked in the outer loop and used it to index into
`lite_cmd.parts[idx]`
With the fix we now break loop.

Co-authored-by: ahkrr <alexhk@protonmail.com>
2021-11-05 21:46:46 +13:00
JT
df6a53f52e Update stale.yml (#4106) 2021-11-04 21:25:44 +13:00
JT
c4af5df828 Update stale.yml (#4102) 2021-10-31 16:48:58 +13:00
f94a3e15f5 Get rid of header bold option (#4076)
* refactor(options): get rid of 'header_bold' option

* docs(config): remove 'header_bold' from docs

* fix(options): replicate logic to apply true/false in bold

* style(options): apply lint fixes
2021-10-31 06:59:19 +13:00
75782f0f50 Fix #4070: Inconsistent file matching rule for ls and rm (#4099) 2021-10-28 15:05:07 +03:00
JT
2b06ce27d3 Bump to 0.39 (#4097) 2021-10-27 08:36:41 +13:00
72c241348b Remove dependencies (#4087)
* fix regression

* Removed the nipper dependency

* fix linting

* fix clippy
2021-10-22 06:58:40 +13:00
JT
ab2d2db987 Fix clippy warnings (#4088)
* Fix clippy warnings

* Fix clippy warnings
2021-10-22 06:57:51 +13:00
07e05ef183 fix regression (#4086) 2021-10-19 13:39:23 -05:00
a986de8ad0 Update stale.yml (#4073)
add labels that can exempt from stale bot
2021-10-09 14:50:27 -05:00
22cfe4391e remove history file after clearing it (#4069) 2021-10-07 10:09:31 -05:00
JT
97d17311f4 Update LICENSE (#4067) 2021-10-07 08:42:07 +13:00
0f6fd30619 stale.yml: mention time to close in stale message (#4066) 2021-10-06 09:05:29 -05:00
JT
e1ebd461d2 Bump to 0.28 (#4064) 2021-10-06 06:35:25 +13:00
JT
f000d5d0a1 Remove the broken scrolling support (#4063)
* Remove the broken scrolling support

* Remove the broken scrolling support
2021-10-06 05:57:14 +13:00
574c5961c8 Add -c flag to select command (#4062)
See cc3653cfd9 for more on the `-c` flag.

Co-authored-by: Andrés N. Robalino <andres@androbtech.com>

Co-authored-by: Andrés N. Robalino <andres@androbtech.com>
2021-10-05 13:23:37 +13:00
JT
69708f7244 update wasm deps (#4061) 2021-10-03 07:19:54 +13:00
62c5df5fc6 expand tilde when reading plugin_dirs (#4052) 2021-10-02 21:38:21 +13:00
92c855a412 Fixed two typos in the tutor. (#4051) 2021-10-02 21:37:59 +13:00
d395816929 remove ansi colors if this is not a tty (#4058) 2021-10-01 09:00:08 -05:00
5e34ef6dff new command: into column_path (#4048) 2021-09-29 07:23:34 -05:00
d567c58cc1 Add -c flag to update cells subcommand (#4039)
* Add `-c` flag to `update cells` subcommand

* Fix lints
2021-09-27 21:18:50 -05:00
4e0d7bc77c Less deps (#4038)
* compiles on nightly now. (breaking change)

* less deps

* Switch over to new resolver

(it's been stable for a while.)

* let's leave num-format for another PR
2021-09-28 07:17:00 +13:00
32581497ef Fix 90 degrees tables problem (#4043)
* fix 90 degrees tables problem

* linting

* clippy

* linting
2021-09-25 14:05:45 -05:00
d6df367c6b Corrected typo (#4040)
It is not BSON but SQLite
2021-09-25 04:25:00 -05:00
4e6327de1d Added BigInt handling to the delimited file format for the 'to' command (#4034)
Co-authored-by: patrick <patrick@spol42069.hitronhub.home>
2021-09-25 09:47:16 +12:00
b3d8666db0 compiles on nightly now. (breaking change) (#4037) 2021-09-25 09:46:48 +12:00
1de7c3d033 Scraping multiple tables (#4036)
* Output error when ls into a file without permission

* math sqrt

* added test to check fails when ls into prohibited dir

* fix lint

* math sqrt with tests and doc

* trigger wasm build

* Update filesystem_shell.rs

* Fix Running echo .. starts printing integers forever

* Allow for multiple table scraping

* linting

* Fix clippy

* linting

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2021-09-24 08:08:13 -05:00
962b258cc6 merge span (#4031) 2021-09-23 07:48:05 +12:00
59697cab63 force rebuild of dev container (#4033) 2021-09-23 07:47:28 +12:00
349af05da8 Do not throw error for files not found in lib_dirs (#4029) 2021-09-20 13:44:47 -05:00
JT
b3b3cf0689 Remove the docker instructions
Docker has been out of date for a long time, go ahead and remove.
2021-09-20 19:33:49 +12:00
5d59234f8d Flexibility updating table's cells. (#4027)
Very often we need to work with tables (say extracted from unstructured data or some
kind of final report, timeseries, and the like).

It's inevitable we will be having columns that we can't know beforehand what their names
will be, or how many.

Also, we may end up with certain cells having values we may want to remove as we explore.

Here, `update cells` fundamentally goes over every cell in the table coming in and updates
the cell's contents with the output of the block passed. Basic example here:

```
> [

    [   ty1,       t2,       ty];

    [     1,        a, $nothing]
    [(wrap), (0..<10),      1Mb]
    [    1s,     ({}),  1000000]
    [ $true,   $false,   ([[]])]

] | update cells { describe }

───┬───────────────────────┬───────────────────────────┬──────────
 # │          ty1          │            t2             │    ty
───┼───────────────────────┼───────────────────────────┼──────────
 0 │ integer               │ string                    │ nothing
 1 │ row Column(table of ) │ range[[integer, integer)] │ filesize
 2 │ string                │ nothing                   │ integer
 3 │ boolean               │ boolean                   │ table of
───┴───────────────────────┴───────────────────────────┴──────────
```

and another one (in the examples) for cases, say we have a timeseries table generated and
we want to remove the zeros and have empty strings and save it out to something like CSV.

```
> [
    [2021-04-16, 2021-06-10, 2021-09-18, 2021-10-15, 2021-11-16, 2021-11-17, 2021-11-18];
    [        37,          0,          0,          0,         37,          0,          0]
] | update cells {|value| i
  if ($value | into int) == 0 {
    ""
  } {
    $value
  }
}

───┬────────────┬────────────┬────────────┬────────────┬────────────┬────────────┬────────────
 # │ 2021-04-16 │ 2021-06-10 │ 2021-09-18 │ 2021-10-15 │ 2021-11-16 │ 2021-11-17 │ 2021-11-18
───┼────────────┼────────────┼────────────┼────────────┼────────────┼────────────┼────────────
 0 │         37 │            │            │            │         37 │            │
───┴────────────┴────────────┴────────────┴────────────┴────────────┴────────────┴────────────
```
2021-09-19 15:37:54 -05:00
Tw
4f7b423f36 Support completion when cursor inside an argument (#4023)
* Support completion when cursor inside an argument

Bash supports completion even when cursor is in an argument, this is very useful for some fixup after the initial completion.
Let add this feature as well.

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

* Add test for when cursor inside an argument

To support test this case, let's also take the position into account.

Signed-off-by: Tw <wei.tan@intel.com>
2021-09-19 17:23:05 +12:00
f7043bf690 Fix #3090: let binding in command leaks when error occurs (#4022) 2021-09-19 14:57:20 +12:00
Tw
1297499d7a add command g to switch shell quickly (#4014)
Signed-off-by: Tw <tw19881113@gmail.com>
2021-09-17 10:39:14 +01:00
bd0baa961c add table selector for downloading web tables (#4004)
* add table selector for downloading web tables

* type-o

* updated debug mode to inspect mode
2021-09-16 09:02:30 -05:00
4ee536f044 fix: enable SIMD (#4021) 2021-09-16 20:01:42 +12:00
JT
8581bec891 bump 0.37.1 (#4019) 2021-09-16 13:32:22 +12:00
8bcbc8eeb3 Move nu-path tests to integration tests (#4015)
* Move nu-path tests to integration tests

To prevent circular dependency between nu-path and nu-test-support crates.

* Fmt
2021-09-16 07:11:28 +12:00
c164ef5489 Update to polars 0.16 (#4013)
* update to polars 0.16

* enabled features for polars
2021-09-16 07:10:12 +12:00
cc3653cfd9 Path commands: Put column path args behid flag; Allow path join appending without flag (#4008)
* Change path join signature

* Appending now works without flag
* Column path operation is behind a -c flag

* Move column path arg retrieval to a function

Also improves errors

* Fix path join tests

* Propagate column path changes to all path commands

* Update path command examples with columns paths

* Modernize path command examples by removing "echo"

* Improve structured path error message

* Fix typo
2021-09-15 21:03:51 +03:00
JT
7fc65067cf Temporarily remove the circular dep (#4009) 2021-09-15 09:17:31 +12:00
JT
f9ae882012 Update main.wxs (#4007)
Remove references to the old binaries
2021-09-15 07:45:30 +12:00
JT
1d80a68f4c bump to 0.37 (#4006) 2021-09-15 06:44:24 +12:00
fda69354db change name to command_prompt (#4003) 2021-09-14 08:02:10 +12:00
cc5c4d38bb Small fixes and refactors to paths & source command (#3998)
* Expand path when converting value -> PathBuf

Also includes Tagged<PathBuf>.

Fixes #3605

* Expand path for PATH env. variable

Fixes #1834

* Remove leftover Cows after nu-path refactor

There were some unnecessary Cow conversions leftover from the old
nu-path implementation.

* Use canonicalize in source command; Improve errors

Previously, `source` used `expand_path()` which does not follow
symlinks.

As a follow up, I improved the source error messages so they now tell
why the source file could not be canonicalized or read into string.
2021-09-12 02:36:14 +03:00
JT
0fa0c25fb3 Fix clippy warnings (#3997) 2021-09-10 13:13:11 +12:00
55eafadf02 Improve error message when bash-style alias syntax is mistakenly used (#3995) 2021-09-10 10:44:55 +12:00
51c74eebd0 Add general refactorings (#3996) 2021-09-10 10:44:22 +12:00
Tw
ae9f4135c0 support appending when saving file (#3992)
This patch implements `>>` operation in bash.

Signed-off-by: Tw <tw19881113@gmail.com>
2021-09-05 06:12:08 +12:00
4e2d3ceaaf Allow knowing the command name tag given no input. (#3988)
```
tags
```
2021-09-03 01:46:15 -05:00
c9c6bd4836 Create errors from tables. (#3986)
```
> [
  [          msg,                 labels,                      span];
  ["The message", "Helpful message here", ([[start, end]; [0, 141]])]
] | error make

error: The message
  ┌─ shell:1:1
  │
1 │ ╭ [
2 │ │   [          msg,                 labels,                      span];
3 │ │   ["The message", "Helpful message here", ([[start, end]; [0, 141]])]
  │ ╰─────────────────────────────────────────────────────────────────────^ Helpful message here
```

Adding a more flexible approach for creating error values. One use case, for instance is the
idea of a test framework. A failed assertion instead of printing to the screen it could create
tables with more details of the failed assertion and pass it to this command for making a full
fledge error that Nu can show. This can (and should) be extended for capturing error values as well
in the pipeline. One could also use it for inspection.

For example: `.... | error inspect { # inspection here }`

or "error handling" as well, like so: `.... | error capture { fix here }`

However, we start here only with `error make` that creates an error value for you with limited support for the time being.
2021-09-02 21:07:26 -05:00
d90420ac4c Add subcommand into filesize (#3987)
* Add subcommand `into filesize`

It's currently not possible to convert a number or a string containing a number
into a filesize. The only way to create an instance of filesize type today is
with a literal in nushell syntax. This commit adds the `into filesize`
subcommand so that file sizes can be created from the outputs of programs
producing numbers or strings, like standard unix tools.

There is a limitation with this - it doesn't currently parse values like `10 MB`
or `10 MiB`, it can only look at the number itself. If the desire is there, more
flexible parsing can be added.

* fixup! Add subcommand `into filesize`

* fixup! Add subcommand `into filesize`
2021-09-02 18:19:54 -05:00
260ff99710 feat: spawn the executables directly if possible (#3974)
* feat: spawn the executables directly if possible

This pull request changes nu-command so that it spawns the process directly if:
- They are a `.exe` on Windows
- They are not a `.sh` or `.bash` on not windows.

Benefits:
- As I explained in [this comment](https://github.com/nushell/nushell/issues/3898#issuecomment-894000812), this is another step towards making Nushell a standalone shell, that doesn't need to shell out unless it is running a script for a particular shell (cmd, sh, ps1, etc.).
- Fixes the bug with multiline strings
- Better performance due to direct spawning.

For example, this script shows ~20 ms less latency.
After:
```nu
C:\> benchmark { node -e 'console.log("sssss")' }
───┬──────────────────
 # │    real time
───┼──────────────────
 0 │ 63ms 921us 600ns
───┴──────────────────
```
Before
```nu
C:\> benchmark { node -e 'console.log("sssss")' }
───┬──────────────────
 # │    real time
───┼──────────────────
 0 │ 79ms 136us 800ns
───┴──────────────────
```

Fixes #3898

* fix: make which dependency optional

Also fixes clippy warnings

* refactor: refactor spawn_exe, spawn_cmd, spawn_sh, and spawn_any

* fix: use which feature instead of which-support

* fix: use which_in to use the cwd of nu

* fix: use case insensitive comparison of the extensions

Sometimes the case of the extension is uppercased by the "which_in" function

Also use unix instead of not windows. Some os might not have sh support
2021-09-01 09:38:52 -05:00
JT
08014c6a98 Move sys, ps, fetch, post to internal commands (#3983)
* Move sys, ps, fetch, post to internal commands

* Remove old plugins

* clippy

Co-authored-by: JT <jonatha.d.turner@gmail.com>
2021-09-01 14:29:09 +12:00
66cedf0b3a Update char_.rs (#3975)
added a few more chars and abbreviations
2021-08-29 08:40:28 -05:00
707a4ebc15 added more escapes to support ansi art (#3973)
* added more escapes to support ansi art

* fixed some bugs
2021-08-28 14:58:59 -05:00
d95375d494 nu-path crate refactor (#3730)
* Resolve rebase artifacts

* Remove leftover dependencies on removed feature

* Remove unnecessary 'pub'

* Start taking notes and fooling around

* Split canonicalize to two versions; Add TODOs

One that takes `relative_to` and one that doesn't.
More TODO notes.

* Merge absolutize to and rename resolve_dots

* Add custom absolutize fn and use it in path expand

* Convert a couple of dunce::canonicalize to ours

* Update nu-path description

* Replace all canonicalize with nu-path version

* Remove leftover dunce dependencies

* Fix broken autocd with trailing slash

Trailing slash is preserved *only* in paths that do not contain "." or
"..". This should be fixed in the future to cover all paths but for now
it at least covers basic cases.

* Use dunce::canonicalize for canonicalizing

* Alow cd recovery from non-existent cwd

* Disable removed canonicalize functionality tests

Remove unused import

* Break down nu-path into separate modules

* Remove unused public imports

* Remove abundant cow mapping

* Fix clippy warning

* Reformulate old canonicalize tests to expand_path

They wouldn't work with the new canonicalize.

* Canonicalize also ~ and ndots; Unify path joining

Also, add doc comments in nu_path::expansions.

* Add comment

* Avoid expanding ndots if path is not valid UTF-8

With this change, no lossy path->string conversion should happen in the
nu-path crate.

* Fmt

* Slight expand_tilde refactor; Add doc comments

* Start nu-path integration tests

* Add tests TODO

* Fix docstring typo

* Fix some doc strings

* Add README for nu-path crate

* Add a couple of canonicalize tests

* Add nu-path integration tests

* Add trim trailing slashes tests

* Update nu-path dependency

* Remove unused import

* Regenerate lockfile
2021-08-28 15:59:09 +03:00
1c1c58e802 Remove duplicate dependencies (#3961)
* chore: Replace surf with reqwest

Removes a lot of older, duplication versions of some dependencies
(roughtly 90 dependencies removed in total)

* chore: Remove syn 0.11

* chore: Remove unnecessary features from ptree

Removes some more duplicate dependencies

* cargo update

* Ensure we run the fetch and post plugins on the tokio runtime

* Fix clippy warning

* fix: Github requires a user agent on requests

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2021-08-28 15:34:11 +12:00
JT
7fe05b8296 bump to 0.36.1 (#3972) 2021-08-27 20:48:58 +12:00
17ef531905 introducing the find command (#3971)
* introducing the `find` command

* added tests

* merged main to accomodate "rest" changes

* test fix
2021-08-27 20:48:41 +12:00
b8e2bdd6b1 Allow different names for ...rest (#3954)
* Allow different names for ...rest

* Resolves #3945

* This change requires an explicit name for the rest argument in `WholeStreamCommand`,
  which is why there are so many changed files.

* Remove redundant clone

* Add tests
2021-08-27 05:58:53 +12:00
88817a8f10 Allow environment variables to be hidden (#3950)
* Allow environment variables to be hidden

This change allows environment variables in Nushell to have a value of
`Nothing`, which can be set by the user by passing `$nothing` to
`let-env` and friends.

Environment variables with a value of Nothing behave as if they are not
set at all. This allows a user to shadow the value of an environment
variable in a parent scope, effectively removing it from their current
scope. This was not possible before, because a scope can not affect its
parent scopes.

This is a workaround for issues like #3920.

Additionally, this allows a user to simultaneously set, change and
remove multiple environment variables via `load-env`. Any environment
variables set to $nothing will be hidden and thus act as if they are
removed. This simplifies working with virtual environments, which rely
on setting multiple environment variables, including PATH, to specific
values, and remove/change them on deactivation.

One surprising behavior is that an environment variable set to $nothing
will act as if it is not set when querying it (via $nu.env.X), but it is
still possible to remove it entirely via `unlet-env`. If the same
environment variable is present in the parent scope, the value in the
parent scope will be visible to the user. This might be surprising
behavior to users who are not familiar with the implementation details.

An additional corner case is the the shorthand form of `with-env` does
not work with this feature. Using `X=$nothing` will set $nu.env.X to the
string "$nothing". The long-form works as expected: `with-env [X
$nothing] {...}`.

* Remove unused import

* Allow all primitives to be convert to strings
2021-08-26 08:15:58 -05:00
3e8ce43dcb rename command and rename for melt (#3968) 2021-08-26 08:13:54 -05:00
9d8845d7ad Allow custom lib dir path for sourcing nu script libraries. (#3940)
Given we can write nu scripts. As the codebase grows, splitting into many smaller nu scripts is necessary.

In general, when we work with paths and files we seem to face quite a few difficulties. Here we just tackle one of them and it involves sourcing
files that also source other nu files and so forth. The current working directory becomes important here and being on a different directory
when sourcing scripts will not work. Mostly because we expand the path on the current working directory and parse the files when a source command
call is done.

For the moment, we introduce a `lib_dirs` configuration value and, unfortunately, introduce a new dependency in `nu-parser` (`nu-data`) to get
a handle of the configuration file to retrieve it. This should give clues and ideas as the new parser engine continues (introduce a way to also know paths)

With this PR we can do the following:

Let's assume we want to write a nu library called `my_library`. We will have the code in a directory called `project`: The file structure will looks like this:

```
project/my_library.nu
project/my_library/hello.nu
project/my_library/name.nu
```

This "pattern" works well, that is, when creating a library have a directory named `my_library` and next to it a `my_library.nu` file. Filling them like this:

```

source my_library/hello.nu
source my_library/name.nu
```

```

def hello [] {
  "hello world"
}
```

```

def name [] {
  "Nu"
end
```

Assuming this `project` directory is stored at `/path/to/lib/project`, we can do:

```
config set lib_dirs ['path/to/lib/project']
```

Given we have this `lib_dirs` configuration value, we can be anywhere while using Nu and do the following:

```
source my_library.nu

echo (hello) (name)

```
2021-08-26 02:04:04 -05:00
52578ba483 tweak the version | pivot instructions (#3964) 2021-08-24 19:03:07 -05:00
JT
991a4801b1 Bump to 0.36 (#3963) 2021-08-25 06:01:17 +12:00
02b2c55146 Rolling and cumulative commands (#3960)
* rolling and cumulative operations

* update polars to 0.15.1

* change reference in function
2021-08-24 09:10:29 -05:00
0abe753003 update the config part (#3962) 2021-08-24 08:58:17 -05:00
JT
487fafbca3 Add a 'tutor' command (#3949)
* Add a 'tutor' command

* clippy
2021-08-21 19:41:54 +12:00
188a352c6f added help --find to search usage and extra_usage text for strings (#3948)
* added help --find to search usage and extra_usage text for strings

* changed my mind
2021-08-20 17:55:56 -05:00
e11b400a75 Allow source to accept paths with emojis (#3939)
* Allow sourcing paths with emojis

* Add source command tests for emoji paths

* Fmt

* Disable source tests on Windows with illegal paths

* Test sourcing also ASCII and single-quoted paths
2021-08-19 19:06:18 +12:00
6db5692be4 Only allow unaliasing in current scope, add tests (#3936)
* unalias only removes aliases in the current scope

* Add a test and fix previous ones which did not function as expected
2021-08-19 19:05:36 +12:00
JT
ead4029d49 Bump rustyline and add unalias test (#3935) 2021-08-18 05:55:34 +12:00
9bd408449e Add the ability to remove and list aliases (#3879)
* Add the ability to remove and list aliases

* Fix failing unit tests

* Add a test to check unalias shadowing blocks
2021-08-17 08:56:35 -05:00
JT
2b7390c2a1 Switch back to building for size (#3924) 2021-08-17 08:45:39 +12:00
ab961a78cb Add trailing slash in completion of symlinked dir (#3921) 2021-08-17 07:13:59 +12:00
65c639cf13 allow fetch to follow redirects (#3923) 2021-08-16 12:31:02 -05:00
0cf5dc11e3 Fix 'Inimplemented' typo. (#3922) 2021-08-16 09:17:31 -05:00
b873fa7a5f The zip command. (#3919)
We introduce it here and allow it to work with regular lists (tables with no columns) as well as symmetric tables. Say we have two lists and wish to zip them, like so:

```
[0 2 4 6 8] | zip {
  [1 3 5 7 9]
} | flatten

───┬───
 0 │ 0
 1 │ 1
 2 │ 2
 3 │ 3
 4 │ 4
 5 │ 5
 6 │ 6
 7 │ 7
 8 │ 8
 9 │ 9
───┴───
```

In the case for two tables instead:

```
[[symbol]; ['('] ['['] ['{']] | zip {
  [[symbol]; [')'] [']'] ['}']]
} | each {
  get symbol | $'($in.0)nushell($in.1)'
}

───┬───────────
 0 │ (nushell)
 1 │ [nushell]
 2 │ {nushell}
───┴───────────
```
2021-08-14 23:36:08 -05:00
ee563ecf4e PROMPT_STRING env variable (#3918)
* prompt string env variable

* cargo clippy
2021-08-15 06:14:14 +12:00
183b35d683 Rustyline bug fixes (#3916)
* Mitigate history file bug in Rustyline

Rustyline's duplicate ignoring code has a bug that can cause data loss and
history file corruption. Testing seems to indicate that disabling this behavior
and allowing duplicates will prevent the bug from showing up. Many people have
complained about this issue, I think it is worthwhile to fix the bug at the cost
of permitting duplicate history entries.

Upstream bug: https://github.com/kkawakam/rustyline/issues/559

* Increase Rustyline historyfile limit

Rustyline will only store 100 history items by default. This is quite a small
limit for a shell that people use as a daily driver. Especially when the
deduplication code is removed, we will hit that limit quickly and start to lose
history. This commit bumps the limit up to 10k. We can discuss if this is an
inappropriate limit or if we should allow users to specify this setting in their
nushell config file instead.
2021-08-14 06:56:28 +12:00
463dd48180 Flexible dropping of rows (by desired row number) (#3917)
We very well support `nth 0 2 3 --skip 1 4` to select particular rows and skip some using a flag. However, in practice we deal with tables (whether they come from parsing or loading files and whatnot) where we don't know the size of the table up front (and everytime we have these, they may have different sizes). There are also other use cases when we use intermediate tables during processing and wish to always drop certain rows and **keep the rest**.

Usage:

```
... | drop nth 0
... | drop nth 3 8
```
2021-08-13 12:48:05 -05:00
1bd3fdd912 upgrade shadow-rs 0.6.8 (#3914) 2021-08-13 06:03:50 +12:00
de71cbdd43 document engine-p porting (#3868)
* document engine-p porting

See #3390 for all the details.

* use static numbering
2021-08-08 05:57:32 +12:00
c9b87c4c03 Count the size of the directories when calculating the size in DirInfo (#3902)
* take dir entry size into consideration when calculting the size of a directory in DirInfo

* fmt check
2021-08-08 05:50:02 +12:00
38848082ae describe command (#3907) 2021-08-08 05:48:54 +12:00
JT
b6728efcd4 in/not-in for strings (#3906) 2021-08-07 09:49:37 +12:00
cd814851da Use bigdecimal-rs patch (#3905)
* Use bigdecimal-rs patch

* fix nu-serde's bigdecimal dependency
2021-08-07 09:27:19 +12:00
6646daab45 source config from $NU_CONFIG_DIR if it exists (#3883) 2021-08-06 11:47:11 -05:00
ba483155d7 Reimplement parsers with nu-serde (#3880)
The nu-serde crate allows us to become much more generic with respect to how we
convert output to `nu-protocol::Value`s. This allows us to remove a lot of the
special-case code that we wrote for deserializing JSON values.

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2021-08-06 11:46:19 -05:00
63abe1cb3e Datetime commands (#3894)
* date and duration from nu

* date commands

* Import to feature flag

* corrected to-csv example

* corrected sample example
2021-08-05 17:18:53 -05:00
28db8022fe Update README.md
added integrations and mentions in supported section
2021-08-05 07:35:22 -05:00
7dcc08985c Implement PartialEq for ReturnSuccess (#3888)
This makes ReturnValue and ReturnSuccess usable in assert_eq!.
2021-08-05 14:55:41 +12:00
55acdaaf8c add officially supported by section (#3895) 2021-08-04 15:57:57 -05:00
575c07c9c4 Normalize capitalization in issue templates (#3891)
* Normalize capitalization in feature_request.yml

* Normalize capitalization in bug_report.yml
2021-08-03 18:49:03 -05:00
325f45fa66 Fix typo: patter → pattern (#3890) 2021-08-03 18:48:23 -05:00
JT
bc682066d8 Bump to 0.35 (#3884) 2021-08-03 20:01:09 +12:00
0a1cdc5107 Add the nu-serde crate (#3878)
* Add the nu-serde crate

nu-serde is a crate that can be used to turn a value implementing
`serde::Serialize` into a `nu-protocol::Value`. This has the potential to
significantly simplify plugin authorship.

This crate was the previously independent
[serde-nu](https://github.com/lily-mara/serde-nu) but the nushell maintainers
expressed an interest in having it added to the mainline nushell repository.

* fixup! Add the nu-serde crate
2021-07-31 22:03:13 -05:00
6984185e61 Better representation in nested dataframes (#3875)
* better dataframe representation in nested df

* Error message correction
2021-07-31 09:02:32 -05:00
5826126284 simple contains arguments (#3874) 2021-07-31 09:01:05 -05:00
762e528ec5 Support equals sign in shorthand environment variable values (#3869)
Some environment variables, such as `RUST_LOG` include equals signs. Nushell
should support this in the shorthand environment variable syntax so that
developers using these variables can control them easily. We accomplish this by
swapping `std::str::split` for `std::str::splitn`, which ensures that we only
consider the first equals sign in the string instead of all of them, which we
did previously.

Closes #3867
2021-07-31 09:10:28 +12:00
JT
c3de9848b4 Revert "Unify use of the surf crate (#3855)" (#3871)
This reverts commit 7f7af2bbaa.
2021-07-31 09:04:01 +12:00
370ae8c20c Add support for mult-doc YAML files (#3870)
Single doc YAML files are returned as before. Multi-doc YAML files are
wrapped in an outer Table. Empty YAML files return Nothing.
2021-07-30 15:45:19 -05:00
69083bfca0 Adding parse fix for power operator error on negative integers and de… (#3821)
* Adding parse fix for power operator error on negative integers and decimal

* Adding correct formatting

* Changed is negative check to follow conventions

* Adding tests

* Added fix for the negatives and added tests

* Removed comments
2021-07-30 07:08:57 -05:00
1e15f26e98 fix interpolated strings when using unicode (#3866)
* fix interpolated strings when using unicode

* added test case
2021-07-29 19:07:34 -05:00
23ba01d89c add performance metrics for measuring startup time (#3854)
* add performance metrics for measureing startup time

* removed some comments

* update so tests pass

* update default.toml for tests, merged main

* fix clippy lints

* wording changes
2021-07-29 18:52:40 -05:00
653cbe651f Going deeper (#3864)
* nuframe in its own type in UntaggedValue

* Removed eager dataframe from enum

* Dataframe created from list of values

* Corrected order in dataframe columns

* Returned tag from stream collection

* Removed series from dataframe commands

* Arithmetic operators

* forced push

* forced push

* Replace all command

* String commands

* appending operations with dfs

* Testing suite for dataframes

* Unit test for dataframe commands

* improved equality for dataframes

* moving all dataframe operations to protocol

* objects in dataframes

* Removed cloning when converting to row
2021-07-30 09:16:30 +12:00
JT
f3e487e829 Add named positionals to all (#3863) 2021-07-30 09:12:24 +12:00
9c016ad479 document compiling without openssl (#3862) 2021-07-30 08:21:20 +12:00
JT
e602647d4d Fix clippy lint and disable broken lint (#3865) 2021-07-30 08:11:47 +12:00
9696e4d315 Improve md5 and sha256 code (#3841)
* Refactor Hash code to simplify md5 and sha256 implementations

Md5 and Sha256 (and other future digests) require less boilerplate code
now. Error reporting includues the name of the hash again.

* Add missing hash sha256 test
2021-07-29 10:22:16 -05:00
7f7af2bbaa Unify use of the surf crate (#3855)
This brings the features used by the `nu_plugin_fetch` and
`nu_plugin_post` in line and drops the default-features, reducing
the number of pulled-in dependencies and avoiding a second round of
compilations.

Retry of #3777 but with different features, post and fetch plugin tested

Signed-off-by: Daniel Egger <daniel@eggers-club.de>
2021-07-29 19:26:38 +12:00
b190051e15 Fix select to insert nulls in sparse tables instead of ignoring absent values (#3857)
Select used to ignore absent values resulting in "squashing" where
columns for multiple rows could be squashed into a single row.

This fixes the problem by inserting null/nothing into absent value, thus
preserving the structure of rows. This makes sure that all selected
columns are present in each row.
2021-07-28 16:39:42 -05:00
83b28cad8d Remove dependencies (#3853)
* nuframe in its own type in UntaggedValue

* Removed eager dataframe from enum

* Dataframe created from list of values

* Corrected order in dataframe columns

* Returned tag from stream collection

* Removed series from dataframe commands

* Arithmetic operators

* forced push

* forced push

* Replace all command

* String commands

* appending operations with dfs

* Testing suite for dataframes

* Unit test for dataframe commands

* improved equality for dataframes

* moving all dataframe operations to protocol
2021-07-27 14:20:06 -05:00
ea42a84a4a Update implementing_a_command.md (#3848)
+  nu-command/src/command.rs has been modified to nu-command/src/mod.rs in nowable version
2021-07-27 09:03:52 -05:00
e4c282f0a6 added the ability to compare time units like 1hr < 2hr (#3845) 2021-07-26 15:19:32 -05:00
d54d7cc431 append dataframes (#3839) 2021-07-26 08:36:09 +12:00
111477aa74 Add sha256 to the hash command (#3836)
Hashers now uses on Rust Crypto Digest trait which makes it trivial to
implement additional hash functions.

The original `md5` crate does not implement the Digest trait and was
replaced by `md-5` crate which does. Sha256 uses already included `sha2`
crate.
2021-07-25 14:08:08 -05:00
JT
226739d13f Bump to 0.34.1 (#3835) 2021-07-25 22:58:33 +12:00
f1ee9113ac All is a DataFrame (#3812)
* nuframe in its own type in UntaggedValue

* Removed eager dataframe from enum

* Dataframe created from list of values

* Corrected order in dataframe columns

* Returned tag from stream collection

* Removed series from dataframe commands

* Arithmetic operators

* forced push

* forced push

* Replace all command

* String commands

* appending operations with dfs

* Testing suite for dataframes

* Unit test for dataframe commands

* improved equality for dataframes
2021-07-25 22:01:54 +12:00
9120a64cfb use chrono_humanize for datetime formatting (#3834)
* use chrono_humanize for datetime formatting

* fix tests
2021-07-25 20:38:45 +12:00
fcd624a722 add date humanize command (#3833)
* add `date humanize` command

* add docs
2021-07-25 17:33:31 +12:00
e6af7f75a1 Read from standard input in rm (#3763)
* Read from standard input in `rm`

With this change, rm falls back to reading from the standard input if no
arguments are supplied. This leads to more intuitive pipes as seen in
https://github.com/nushell/nushell/issues/2824

 ls | get name | sort-by | first 20 | each {rm $it} becomes
 ls | get name | sort-by | first 20 | rm

* [Fix] Run cargo fmt, make files a rest parameter, and fix cargo build warnings

* [Fix] Fix clippy suggestions
2021-07-25 15:01:53 +12:00
27f1e7b60c change describe so it doesn't output colored strings (#3832) 2021-07-25 06:34:11 +12:00
2e24de7f47 Support other variables than PATH in pathvar (2nd attempt) (#3828)
* Fix swapped PATH env var separators

* Support pathvar to manipulate other vars than PATH

* Add tests for pathvar and its subcommands

* Adjust pathvar tests to comply with env quirks

* Make pathvar tests work on non-Windows as well

* Compact the comments in pathvar tests

* Fix last failing test

Co-authored-by: Jakub Žádník <jakub.zadnik@tuni.fi>
2021-07-24 11:44:36 -05:00
e514204db0 Fix wrong path separator (#3829) 2021-07-24 08:42:50 -05:00
0f9e55dac6 Revert "Support other variables than PATH in pathvar (#3791)" (#3827)
This reverts commit f9f39c0a1c.
2021-07-23 09:03:28 -05:00
5d7677dd07 Implement into path conversion (#3811)
This allows converting strings to filepaths without having to use
`path expand` roundtrip.

Filepaths are taken as-is without any validation/conversion.
2021-07-23 19:14:02 +12:00
57073cc6cf port capitalize to engine-p (#3794)
Part of #3390.
2021-07-23 19:13:11 +12:00
f9f39c0a1c Support other variables than PATH in pathvar (#3791)
* Fix swapped PATH env var separators

* Support pathvar to manipulate other vars than PATH

* Add tests for pathvar and its subcommands

* Fix PATH env name for Windows

Seems like Windows uses PATH as well.

Co-authored-by: Jakub Žádník <jakub.zadnik@tuni.fi>
2021-07-23 19:11:56 +12:00
d88d7f26e4 fix typo in release.yml (#3824) 2021-07-22 12:42:11 -05:00
aeaedd2e5e Convert templates to github forms (#3818)
* Create feature_request.yaml

* Rename feature_request.yaml to feature_request.yml

* Update feature_request.yml

* Update feature_request.yml

* Create bug_report.yml

* Update bug_report.yml

* Update bug_report.yml

* Update feature_request.yml

* Delete bug_report.md

* Delete feature_request.md
2021-07-23 05:39:20 +12:00
1f4ef3b606 Add worflow to publish package in winget (#3819)
Remove the job in release workflow to publish to winget.
Trigger winget workflow on published release.

Co-authored-by: Alexandre Nedelec <Alexandre.Nedelec@azeo.com>
2021-07-22 11:38:03 -05:00
9b5db297a6 Replace command (#3823)
* replace command

* cargo fmt

* Signature correction
2021-07-22 08:45:46 -05:00
b7215b5dde Fix expected age of mockup files in example tests (#3808) 2021-07-20 18:05:58 -05:00
411435d68f Dataframe Shape command (#3805)
* size command to get dataframe info

* change command name to shape

* apply lint to file
2021-07-20 07:07:42 -05:00
f656f906ff Update stale.yml
update days-before-issue-stale to 90 days
2021-07-19 15:03:24 -05:00
d0a7363e64 bat theme wasn't getting set properly (#3807) 2021-07-19 14:54:36 -05:00
7401fa2fa5 Fix docs for the config variable completion_type (#3804) 2021-07-19 07:20:56 -05:00
181ee1dade Revert "Unify use of the surf crate (#3777)" (#3783)
This reverts commit 37612345f2.
2021-07-14 20:21:18 -05:00
3645a0f0e4 Updated polars version for faster CSV reader (#3781) 2021-07-14 15:33:21 -05:00
2864eaebae fixed show_hints option to allow hints to be turned off (#3780) 2021-07-14 09:47:33 -05:00
37612345f2 Unify use of the surf crate (#3777)
This brings the features used by the `nu_plugin_fetch` and
`nu_plugin_post` in line and drops the default-features, reducing
the number of pulled-in dependencies and avoiding a second round of
compilations.

Signed-off-by: Daniel Egger <daniel@eggers-club.de>
2021-07-14 08:47:49 -05:00
bb218b824e corrected position of dataframes (#3776) 2021-07-14 08:46:32 -05:00
279329bfaa Add the -s parameter to submit package to winget in pipeline (#3767)
Co-authored-by: Alexandre Nedelec <Alexandre.Nedelec@azeo.com>
2021-07-13 18:35:56 -05:00
JT
71f4ea9d76 Bump to 0.34.0 (#3766) 2021-07-14 05:57:41 +12:00
1881a297c9 Use shadow-rs 0.6 in nu-cli. (#3759)
`nu-command` was already using `shadow-rs` 0.6, so there were two
copies being built and used. This makes them match up.
2021-07-10 16:11:08 +12:00
56c7a99eb4 Into binary changes (#3758)
* kind of works but not what we really want

* updated `into binary` and `first` to work better together

* attempt to fix wasm build problem

* attempt #2 to fix wasm stuff
2021-07-09 16:43:18 -05:00
3262ffc1a6 Update s3handler to 0.7 (really 0.7.3). (#3757)
This brings with it a reduction in the number of duplicated
dependencies that it has and the overhead it imposes on our
build.

The number of crates that need to be built for
`cargo build --features extra` drops by roughly 50.
2021-07-10 07:28:07 +12:00
5bc7a1f435 #3385: Add unique option for uniq command (#3754)
* Added -u arg for command uniq.

* Update uniq.rs

Co-authored-by: JT <jonathandturner@users.noreply.github.com>
2021-07-10 07:27:35 +12:00
11cb5ed10e port strings size engine-p (#3690) (#3753)
migrate `size` command to engine-p.

I also tweaked the signature of the primary logic (`size`) to mimic `keep`.

Part of #3390.
2021-07-10 06:45:19 +12:00
2b80f40164 Remove outdated note in README.md. (#3751) 2021-07-09 06:04:02 +12:00
70215fe480 a few things that make it easier to debug keybindings (#3752) 2021-07-08 08:56:54 -05:00
JT
69fa040361 Fix nothing string comparison (#3750) 2021-07-08 07:21:02 +12:00
720217a5e4 Update stale.yml
update to move it to a cron schedule
2021-07-07 14:09:31 -05:00
1911aad57f add a couple more features (#3749) 2021-07-07 12:03:59 -05:00
1943071d12 Simplify is_executable in nu-completion. (#3742)
On Windows, we used the `is-exeuctable` crate but on Unix, we
duplicated the check that it did, with one difference: We also
looked at whether or not it was a symlink.

The `is-executable` crate uses `std::fs::metadata` which follows
symlinks, so this scenario should never occur here, as it will
return the metadata for the target file.

Using the `is-executable` crate on both Unix and Windows lets us
make it non-optional. This lets us remove the `executable-support`
feature. (It is worth noting that this code didn't compile on
Windows when the `executable-support` feature was not specified.)

Right now, there is an alternate code path for `target_arch` being
`wasm32`. This isn't exactly correct as it should probably handle
something different for when the `target_os` is `wasi`.
2021-07-07 07:53:07 -05:00
08c624576c try to use regular trim commands as much as possible (#3743) 2021-07-07 07:51:52 -05:00
a99a2ce7e8 Update wasm sample gitignore for node_modules. (#3747)
When building the wasm sample in the way that it is built in CI,
a `node_modules` directory is populated.
2021-07-07 07:48:32 -05:00
56855f9791 Downgrade crossterm to fix pager compilation (#3740)
* Downgrade crossterm to fix pager compilation

With 0.20.0, the `table-pager` feature wouldn't compile.
Closes #3738

* Update Cargo.lock
2021-07-07 15:24:42 +12:00
b32979bc84 ^ls doesn't exist on windows (#3745) 2021-07-06 13:14:48 -05:00
651d425046 Remove ptree dep from nu-command, remove associated feature. (#3741)
Nothing used the `ptree` feature or optional dependency within
`nu-command` except to include it within the `version` output. This
may be related to when `nu-cli` also had a `ptree` feature, but
I'm not sure.

That leaves the code within `nu_plugin_tree` as the sole remaining
user of `ptree`, which is already covered by the feature `tree`
and included in the `version` output.
2021-07-06 10:33:24 -05:00
d1df9b9e38 Update minus, tui to remove crossterm 0.18 dep. (#3739)
We already depend on crossterm 0.19 (and 0.20), so we can at least
remove the usage of 0.18 by updating minus and tui.
2021-07-06 20:44:07 +12:00
f603b7ef8b Remove empty trace feature. (#3732)
In `nu-parser`, this was a relic of when nom was used by the parser
and it would enable using `nom-tracable`.
2021-07-06 07:21:28 +12:00
b8c3a10e5b Bump a few source compatible nu-command dependencies (#3724)
Signed-off-by: Daniel Egger <daniel@eggers-club.de>
2021-07-05 19:16:34 +12:00
cab181832f Bump rand version used by nu-command to 0.8 (#3723)
Signed-off-by: Daniel Egger <daniel@eggers-club.de>
2021-07-05 16:12:44 +12:00
af2b2c668d New take command (#3722)
* Type in command description

* filter name change

* Clean column name

* Clippy error and updated polars version

* Lint correction in file

* CSV Infer schema optional

* Correct float operations

* changes in series castings to allow other types

* Clippy error correction

* Removed lists from command signatures

* Added not command for series

* take command with args

* set with idx command
2021-07-05 11:46:53 +12:00
JT
c94c87eec0 Wasm fix (#3729)
* Try to fix azure pipelines

* Try to fix azure pipelines

* Try to fix azure pipelines
2021-07-04 20:11:22 +12:00
4e13c339ec Submit package to winget during release. (#3717)
Add a new job winget in release.yaml that uses wingetcreate to submit package.
2021-07-01 16:24:38 -05:00
9a1e1d5b1e move lang command to $nu (#3720) 2021-07-01 13:09:50 -05:00
17008bb648 Removed list from dataframe command signatures (#3713)
* Type in command description

* filter name change

* Clean column name

* Clippy error and updated polars version

* Lint correction in file

* CSV Infer schema optional

* Correct float operations

* changes in series castings to allow other types

* Clippy error correction

* Removed lists from command signatures

* Added not command for series
2021-07-01 16:33:52 +12:00
bb5ab5d16c nu-cli ctrl-c feature support. (#3718)
Seems we do `ctrl` feature checks in `nu-cli` and `nu-command`. We should find a better way to report the enabled features un the `version` command without using the conditionals (or somewhere else)
2021-06-30 19:45:27 -05:00
c36d356f4e nu-cli: Remove crates not needed. (#3716) 2021-06-30 17:47:56 -05:00
JT
e8c06b7152 Update stale.yml 2021-07-01 08:38:39 +12:00
3cc0ea5ff9 Update stale.yml (#3714)
update messages and days-before-issue-close
2021-06-30 15:16:37 -05:00
333335c366 add ansi osc string terminator (#3712) 2021-06-30 12:01:23 -05:00
08306f0db8 Remove unused dep on nu-cli from nu_plugin_chart. (#3709) 2021-06-30 17:47:28 +12:00
008bdfa43f a new command to query the nushell internals (#3704)
* a new command to query the nushell internals

* added signature

* a little cleanup
2021-06-29 09:27:16 -05:00
1d0483c946 Casting operations for Series with differents types (#3702)
* Type in command description

* filter name change

* Clean column name

* Clippy error and updated polars version

* Lint correction in file

* CSV Infer schema optional

* Correct float operations

* changes in series castings to allow other types

* Clippy error correction
2021-06-28 22:17:37 +12:00
7cb9fddc11 Add Test Case for Special Character Paths (#3596)
Added test cases that ensure that special characters in path names are passed
to external commands correctly. These cases have been implemented with rstest
to reuse existing test code.
2021-06-28 22:16:03 +12:00
989062d2f8 Removed bug occurring with f64 (#3697)
* Type in command description

* filter name change

* Clean column name

* Clippy error and updated polars version

* Lint correction in file

* CSV Infer schema optional

* Correct float operations
2021-06-28 05:42:37 +12:00
c2f78aaf88 Adding all-trim option (with format and all-flag) (#3696)
* adding changes for all-trim option

* adding changes for the all-flag and format flag

* renaming modules - clippy warning
2021-06-28 05:42:15 +12:00
8f39f4580a Add paste command (#3694)
* Add paste command

* fix build and format failures

* Add examples

* Make tests pass

* Format

* add cfg annotation for Clip

* format code

* remove additional import for clip

* Remove test
2021-06-27 08:42:17 +12:00
JT
6202c67fe8 Fix #3430 (#3693) 2021-06-26 17:38:36 +12:00
JT
0c82c1920e Support multiple shorthand env vars (#3692) 2021-06-26 14:15:57 +12:00
a2dc4199d0 port network url to engine-p (#3690)
migrate network URL related commands to engine-p.

Part of #3390.
2021-06-26 13:19:10 +12:00
JT
b1970f79ee Add support for arbitrarily nested subcommands (#3688) 2021-06-26 09:09:06 +12:00
6cdd8a2b07 Fix string interpolation is not working with external command (#3686) 2021-06-26 08:14:54 +12:00
4ed615cfcc documentation: consistent abbreviation for URL (#3684)
I noticed `fetch`'s documentation used "URL" so wanted to do so here as
well.
2021-06-25 09:14:20 -05:00
91da4e3168 No infer schema (#3683)
* Type in command description

* filter name change

* Clean column name

* Clippy error and updated polars version

* Lint correction in file

* CSV Infer schema optional
2021-06-25 20:35:07 +12:00
596062ccab Clean column names (#3678)
* Type in command description

* filter name change

* Clean column name

* Clippy error and updated polars version

* Lint correction in file
2021-06-25 19:09:41 +12:00
JT
93b5f3f421 Make lexing configurable wrt newlines (#3682) 2021-06-25 17:50:24 +12:00
JT
cac2875c96 Improve def parse errors (#3681) 2021-06-25 17:18:38 +12:00
a3f119e0bd Add pathvar command (#3670)
* Add pathvar command

Signed-off-by: nathom <nathanthomas707@gmail.com>

* Add pathvar command to context

Signed-off-by: nathom <nathanthomas707@gmail.com>

* Add pathvar reset command

Signed-off-by: nathom <nathanthomas707@gmail.com>

* Update help message

Signed-off-by: nathom <nathanthomas707@gmail.com>

* Remove insert command

Signed-off-by: nathom <nathanthomas707@gmail.com>

* Remove unused import

Signed-off-by: nathom <nathanthomas707@gmail.com>

* Remove insert mod

Signed-off-by: nathom <nathanthomas707@gmail.com>

* Support for windows path separator

Signed-off-by: nathom <nathanthomas707@gmail.com>

* Clear clippy errors

Signed-off-by: nathom <nathanthomas707@gmail.com>

* Remove empty file

Signed-off-by: nathom <nathanthomas707@gmail.com>

* Formatting

Signed-off-by: nathom <nathanthomas707@gmail.com>
2021-06-25 15:58:37 +12:00
JT
6ba40773bb Always read textview input from stream (#3680) 2021-06-25 14:17:58 +12:00
19c79f0a73 Update stale.yml
changed ascending to true in order to catch all the old ones next time
2021-06-24 13:17:37 -05:00
e58faeb66a Update stale.yml
upped the operations-per-run to 520
2021-06-24 12:59:25 -05:00
3a3d80826c Update stale.yml
turn off dry-run/debug-only mode
2021-06-24 12:45:28 -05:00
49c93e5b9e Update stale.yml 2021-06-23 18:45:15 -05:00
3f2a99a936 Update stale.yml
temporarily make it manually triggerable
2021-06-23 18:43:24 -05:00
62eae9b470 Create stale.yml (#3677) 2021-06-23 18:30:19 -05:00
a1aae8ca38 update filesize -> filesize math to fix coercion errors (#3675)
* update filesize -> filesize math to fix coercion errors

* maybe shouldn't do some operations with filesize and int?
2021-06-23 15:37:20 -05:00
104cf5b51b make nu-cli mod app public (#3673) 2021-06-24 06:05:59 +12:00
JT
4fe9d8a007 Enable dataframe command by default (#3672)
* Enable dataframe command by default

* Fix unwrap
2021-06-23 21:04:09 +12:00
JT
edbc828fc3 Bump to 0.33.1 (#3671) 2021-06-23 19:57:41 +12:00
03c9eaf005 Variable completions. (#3666)
In Nu we have variables (E.g. $var-name) and these contain `Value` types.
This means we can bind to variables any structured data and column path syntax
(E.g. `$variable.path.to`) allows flexibility for "querying" said structures.

Here we offer completions for these. For example, in a Nushell session the
variable `$nu` contains environment values among other things. If we wanted to
see in the screen some environment variable (say the var `SHELL`) we do:

```
> echo $nu.env.SHELL
```

with completions we can now do: `echo $nu.env.S[\TAB]` and we get suggestions
that start at the column path `$nu.env` with vars starting with the letter `S`
in this case `SHELL` appears in the suggestions.
2021-06-23 19:21:39 +12:00
2b021472d6 Fixed panic on math with large durations (#3669)
* Output error when ls into a file without permission

* math sqrt

* added test to check fails when ls into prohibited dir

* fix lint

* math sqrt with tests and doc

* trigger wasm build

* Update filesystem_shell.rs

* Fix Running echo .. starts printing integers forever

* Fixed panic on operations with very large durations

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2021-06-23 15:44:14 +12:00
JT
55cab9eb4f Bump to 0.33 (#3667) 2021-06-22 17:22:33 +12:00
b39dda0550 speed up windows completions (#3665)
* speed up windows completions

* fix CI failures

* make crate optional

* one more fix for CI

* allow unused
2021-06-21 16:39:21 -05:00
7c0a52a81e updated main in table so that it outputs again (#3662) 2021-06-21 14:14:20 -05:00
JT
318d13ed58 Add built-in var to refer to pipeline values (#3661) 2021-06-21 12:31:01 +12:00
21a3ceee92 update/add path separators and environment separators to char (#3660) 2021-06-21 05:55:34 +12:00
a8f6a13239 Move path handling to nu-path (#3653)
* fixes #3616
2021-06-20 11:07:26 +12:00
b9f1371994 Series commands (#3652)
* new series commands

* clippy corrections
2021-06-20 10:59:39 +12:00
51c685aa99 port string case commands to engine-p (#3649)
Port snake_case and friends to engine-p syntax.

Part of #3390.
2021-06-19 15:01:18 +12:00
9e39284de9 Add unlet_env command (#3629)
* Add ability to remove env variables

Signed-off-by: nathom <nathanthomas707@gmail.com>

* Implement unlet_env command

Signed-off-by: nathom <nathanthomas707@gmail.com>

* Update parameter description

Signed-off-by: nathom <nathanthomas707@gmail.com>

* Migrate to new filestructure

Signed-off-by: nathom <nathanthomas707@gmail.com>

* Added tests for unlet-env

Signed-off-by: nathom <nathanthomas707@gmail.com>

* Formatting

Signed-off-by: nathom <nathanthomas707@gmail.com>
2021-06-19 15:00:07 +12:00
JT
26899bc0f0 Update README.md 2021-06-19 12:08:44 +12:00
JT
a74d05061d Begin directory contrib docs and split commands (#3650)
* Begin directory contrib docs and split commands

* Fix unused import warning
2021-06-19 12:06:44 +12:00
4140834e4c Remove dir-s/ectories/ectories-support features (#3647) 2021-06-19 11:29:29 +12:00
bd44bcee32 Clean up nu-completion dependencies. (#3645) 2021-06-18 00:54:04 -05:00
JT
1e4678f929 Fix the ignore example (#3644) 2021-06-18 00:07:31 -05:00
JT
fe5055cf29 Relax groups and blocks to output at pipeline level (#3643)
* Relax groups and blocks to output at pipeline level

* Fix up tests and add ignore command
2021-06-18 13:04:51 +12:00
JT
d9d956e54f Fix issue in external subexpression paths (#3642)
* Fix issue in external subexpression paths

* new clippy dropped

* clippy
2021-06-18 07:59:58 +12:00
JT
6c2c16a971 Add back disks and net to sys (#3639) 2021-06-17 19:57:40 +12:00
955a5ed8fb Plugin: from_mp4 and UntaggedValue::duration fix (#3618)
* plugin: basic from_mp4 implementation

This patch introduces a very basic implementation of from_mp4, with only
a few bits of meta-data available. The rest of the available meta-data
(which is more than half left), will be included in a later patch

* Mp4: Almost all track metadata is implemented

Only meta-data that is not implemented is duration, facing some weird
issue I am going to check on later

* Mp4: All meta-data fields implemented

All meta-data fields that can be retrieved are now retrieved, with the
exception of duration for both tracks and the entire file itself because
there is still an issue. However, that will be fixed in the upcoming
patches

* fix: UntaggedValue::duration() serializes correctly now

Previous to this patch, there was an issue where when you would use
UntaggedValue::duration() it would result in an invalid JSONRPC
resulting string when using the protocol. This patch fixes this issue

* Mp4: Duration fixed for file and tracks

* plugins: Add plugin extra to src/plugins

* Mp4: Replace unwrap() with expect()

* Fix: Remove test mp4 file
2021-06-17 14:18:31 +12:00
a59414203f Only discard command comment if prev token was comment (#3628)
This fixes issues where a file with multiple def commands with their own
doc comments, some of the comments would be discarded.
2021-06-17 14:11:05 +12:00
631b067281 Shellcheck (#3635) 2021-06-17 14:09:08 +12:00
02bac0a326 fix FlatShape::Garbage's default foreground color (#3634) 2021-06-16 15:43:15 -05:00
7c8fb060f1 Extract completions into subcrate. (#3631) 2021-06-16 15:20:01 -05:00
04c0e94349 (docs) update README.md (#3630)
- suggest grammar changes
- correct punctuation
- make code fences consistently use `shell` vs `bash` && `shell`
2021-06-16 14:55:10 -05:00
2a946af81e Support version option in Nu bin. (#3632)
Additionally we remove the little pieces that we relied on `clap` (for version number in this case).
2021-06-16 14:53:28 -05:00
JT
18be6768c9 Update README.md 2021-06-16 18:24:21 +12:00
JT
fbe61a06f6 Update README.md 2021-06-16 18:23:27 +12:00
JT
7a4d6d64fd Add file not found error for nu cmd args (#3627) 2021-06-16 14:57:14 +12:00
0eae9c49b0 Nu's rest arguments are source(s) files scripts to run. (#3624) 2021-06-15 20:38:56 -05:00
d0bca1fb0f Remove 'help' Nu bin positional support. (#3621)
Mostly to keep parity with the rest of Nu internal commands (`-h` and `--help`) instead.
2021-06-15 18:26:04 -05:00
7c7e5112ea Make Nu bootstrap itself from main. (#3619)
We've relied on `clap` for building our cli app bootstrapping that figures out the positionals, flags, and other convenient facilities. Nu has been capable of solving this problem for quite some time. Given this and much more reasons (including the build time caused by `clap`) we start here working with our own.
2021-06-15 17:43:25 -05:00
ec96e85d04 Dataframe commands (#3608)
* Change name from pls to dataframe

* filter and rename commands

* filter example

* Filter example with bool mask
2021-06-15 14:34:08 +12:00
d60d71a697 Begin porting mkdir (#3607)
* Begin porting mkdir

* Addressed comments
2021-06-15 06:57:21 +12:00
6f0dd8e885 Update README.md (#3615) 2021-06-14 20:52:07 +12:00
JT
de99e35106 Refactor rarely changing engine state into its own struct (#3612)
* WIP

* Finish up EngineState refactor

* Fix Windows calls

* Fix Windows calls

* Fix Windows calls
2021-06-14 15:19:12 +12:00
774be79321 Fix syntax hightlight when using Circumflex-Operator (#3613) 2021-06-14 14:21:36 +12:00
721f704260 Add support to run external command with string evaluation (#3611) 2021-06-14 12:20:07 +12:00
2846e3f5d9 enable theming of the command line syntax (#3606)
* enable theming of the command line syntax

* added missing flatshape, sorted flatshapes for easier reading.

* sorted flat shapes again and saved it this time

* added sample rwb.json syntax them file to docs
2021-06-11 14:17:43 -05:00
JT
b9ca3b2039 Remove EvaluationContext::from_args (#3604) 2021-06-11 18:35:21 +12:00
JT
8ac572ed27 Make arg eval lazy, remove old arg evaluation code (#3603)
* Remove old argument eval

* Merge main

* fmt

* clippy

* clippy

* clippy
2021-06-11 13:57:01 +12:00
c4163c3621 Series arithmetic (#3602)
* operations with series

* contains operations with series

* Checked division and masked operations
2021-06-11 09:39:51 +12:00
1d7c909080 add single quote and double quote to char command (#3601) 2021-06-10 08:20:17 -05:00
500683831c from xlsx/ods: Add parameter --sheets (#3600)
* from xlsx: Add parameter --sheets

* from ods: Add parameter --sheets
2021-06-10 07:44:24 -05:00
9a2fe7ec0c from sqlite: Add test for table selection (#3599) 2021-06-10 07:41:43 -05:00
JT
3ae3e3d23d Further improve arg errors (#3598)
* Further improve arg errors

* Fix note

* Fix note
2021-06-10 20:33:06 +12:00
JT
fc07e849fe Improve the errors for arg types (#3597) 2021-06-10 18:47:06 +12:00
JT
fadcdde7f8 Add help flag support to alias (#3595) 2021-06-10 16:28:33 +12:00
JT
d056bf070f Improve alias highlighting/completions (#3594)
* Improve alias highlighting/completions

* Add example
2021-06-10 13:13:08 +12:00
2591050fbe Clarify exec help message; Update to engine-p (#3588)
* Fix and clarify description of 'exec'

Most importantly, I added the information that it replaces the current
process.

* Convert exec to OutputStream; Remove unused trait

* Remove dead code & unused imports on non-unix
2021-06-10 10:43:40 +12:00
JT
e8dfd4ba39 Enable syntax/completions for source (#3589) 2021-06-10 09:01:40 +12:00
JT
383e874166 Fix a bunch of future clippy warnings (#3586)
* Fix a bunch of future clippy warnings

* Fix a bunch of future clippy warnings
2021-06-10 07:08:12 +12:00
JT
e8a2250ef8 Improve expr parse (#3584)
* Require '-' to be a number for math

* Add test

* improve parse logic, add test
2021-06-10 05:17:45 +12:00
JT
440e12abc4 Fix #3582 (#3583)
* Fix #3582

* Fix empty?

* Fix parsing types
2021-06-09 18:07:54 +12:00
25ba6ea459 Def cleanup (#3580)
* Remove impossible condition

* Improve def error

* Fmt
2021-06-09 10:06:44 +12:00
5ec226a416 change command duration env var to emit duration in milliseconds and also change var name to CMD_DURATION_MS (#3581) 2021-06-08 16:14:38 -05:00
JT
a021b99614 Improve external quoting logic (#3579)
* Add tests and improve quoting logic

* fmt

* Fix clippy ling

* Fix clippy ling
2021-06-09 08:59:53 +12:00
JT
e9de4c08b0 Improve quoted completions (#3577) 2021-06-09 06:47:29 +12:00
JT
2e968d2557 Improve completions inside of a pipeline (#3575)
* Fix completion crash

* Improve inner completions
2021-06-08 19:48:02 +12:00
JT
bc6fa85a4b Fix completion crash (#3574) 2021-06-08 18:56:36 +12:00
4e6c2c0fa1 Column selector using FullColumnPath (#3572)
* Column selector using FullColumnPath

* column name with as

* standar group by name
2021-06-08 14:34:37 +12:00
JT
7eadbd938d Add support for subcommand completions (#3571)
* Add support for subcommand completions

* Update test

* WIP

* Fix prepend for completions

* Fix test
2021-06-08 14:31:39 +12:00
31a5de973d Fix duration literals in where docs (#3573)
Addresses #3569
2021-06-07 15:21:31 -05:00
94fc8a1334 added ansi gradient in the spirit of fun (#3570) 2021-06-07 13:20:05 -05:00
aa1cd7eba6 Series Operation (#3563)
* Sample command

* Join command with checks

* More dataframes commands

* Groupby and aggregate commands

* Missing feature dataframe flag

* Renamed file

* New commands for dataframes

* error parser and df reference

* filter command for dataframes

* removed name from nu_dataframe

* commands to save to parquet and csv

* polars new version

* new dataframe commands

* series type and print

* Series basic arithmetics

* Add new column to dataframe

* Command names changed to nushell standard
2021-06-08 05:27:46 +12:00
JT
16faafb7a8 Rename the use of invocation to subexpression (#3568)
* Rename the use of invocation to subexpression

* Fix test name
2021-06-07 20:08:35 +12:00
JT
e376c2d0d4 Remove the CI canaries (#3567) 2021-06-07 19:32:14 +12:00
JT
c4dc61425d Simpler parse improvement (#3566) 2021-06-07 18:39:23 +12:00
JT
128f5bce30 Improve partial completion/highlight (#3564) 2021-06-07 16:33:44 +12:00
82d69305b6 Path expand fixes (#3505)
* Throw an error if path failed to expand

Previously, it just repeated the non-expanded path.

* Allow expanding non-existent paths

This commit has a strange error in examples.

* Specify span manually in examples; Add an example

* Expand relative path without requiring cwd

* Remove redundant tilde expansion

This makes the tilde expansion in relative paths dependant on "dirs"
feature.

* Add missing example result

* Adjust path expand description

* Fix import error with missing feature
2021-06-07 05:28:55 +12:00
57a009b8e6 Respect territory in locale (e.g. de_CH) for byte formatting (#3560) 2021-06-07 05:25:03 +12:00
JT
a2e6f5ebdb Add hex, octal, binary (#3562) 2021-06-06 17:14:51 +12:00
995dbd25b3 plugin_sys: Bump sysinfo dep version (#3561)
Previous to this commit, the sysinfo crate would show blank `brand` for
Apple M1 architectures whenever you would run `sys | get cpu`.

I have fixed the issue in sysinfo, so bumping the dependency version to 0.18.2
means brand now is retrieved successfully on M1 architectures.
2021-06-05 18:15:30 -05:00
JT
1d0d0425d4 More fixes for bigint duration (#3557) 2021-06-05 04:54:18 +12:00
51890baace port group-by date to engine-p (#3556)
* migrate `group_by_date.rs` to engine-p

Part of #3390.
2021-06-05 03:58:22 +12:00
JT
4bca36f479 Switch duration back to bigint (#3554) 2021-06-04 19:39:12 +12:00
JT
7d78f40bf6 Bump to 0.32.1 (#3553) 2021-06-04 19:07:50 +12:00
JT
131b5b56d7 Finish removing arg deserialization (#3552)
* WIP remove process

* WIP

* WIP

* Finish removing arg deserialization
2021-06-04 18:23:57 +12:00
fcd94efbd6 README: output from -> output to (#3550) 2021-06-03 11:45:01 -05:00
13257004bc add list of installed plugins to version command (#3548) 2021-06-03 08:53:32 -05:00
8b193db0cb Fix VCS markers not showing up in textview (#3530)
Changes:
* Bug fix - bat adds markers only when a file path is passed and it can use git2
on it. It doesn't add markers when bytes are passed. Hence, the code is
adjusted accordingly. The sideeffect is files being opened multiple
times and its content being unnecessarily loaded in memory.
* Refactoring of the crate - Config is extracted to its struct file.
Repetitive blocks of code are dried and nested conditionals are
flattened.
2021-06-03 18:25:28 +12:00
5537dce3cc Dataframe commands (#3502)
* Sample command

* Join command with checks

* More dataframes commands

* Groupby and aggregate commands

* Missing feature dataframe flag

* Renamed file

* New commands for dataframes

* error parser and df reference

* filter command for dataframes

* removed name from nu_dataframe

* commands to save to parquet and csv
2021-06-03 18:23:14 +12:00
fd5da62c66 accomodate decimals when converting gjson numbers (#3544) 2021-06-02 16:12:21 -05:00
927578a26f fixed the prompt (#3539) 2021-06-02 08:39:28 -05:00
JT
290c712cde Retain tag when accessing a var (#3535) 2021-06-02 19:49:14 +12:00
2486492c4d from sqlite: Add parameter --tables (#3529)
* from sqlite: Add parameter '--tables'

* from sqlite: Enhance documentation
2021-06-01 17:06:32 -05:00
JT
7bf10b980c Update Cargo.lock (#3527) 2021-06-01 20:11:21 +12:00
JT
55c243a17b Update Cargo.toml (#3526) 2021-06-01 19:32:44 +12:00
JT
df526f73be Bump to 0.32 (#3521)
* Bump to 0.32

* Bump to 0.32
2021-06-01 08:14:50 +12:00
29a77fd6ae Bump rusqlite from 0.24.2 to 0.25.3 (#3523) 2021-06-01 07:34:51 +12:00
be9ebd9e18 fix: filename quoting # and update and unify surf dependency (#3524)
* fix: filenames with '#' don't get quoted #3496

* updating and unifying dependency surf

* adding hyper-client

Co-authored-by: ahkrr <alexhk@protonmail.com>
2021-05-31 10:22:46 -05:00
01f1208ad1 to sqlite: Fix panic caused by empty tables (#3522) 2021-05-31 21:46:07 +12:00
9dbb3e80fe feat: add attribute selection to nu_plugion_selector (#3519)
This allows the user to specify for example 
selector a -t href 
to downselect based on an attribute

Co-authored-by: ahkrr <alexhk@protonmail.com>
2021-05-30 11:49:43 -05:00
a5c14ba7d4 Ensure correct partial key=value flag. (#3518) 2021-05-29 23:19:58 -05:00
4b11b283ac Resolve issues with rm * globbing (#3516)
Using the `*` wildcard should not attempt to delete files with a leading dot
unless the more explicit `.*` is used. `rm *` should also not attempt to delete
the current directory or its parent directory (`.` and `..`). I have resolved
this bug as well in a less satisfactory way. I think it may be the case that we
can only disambiguate the `.` and `..` path segments by using `Path::display`.
Here is a short list of alternatives that I tried:

- `Path::ends_with()` can detect `/..` but not `/.`.
- `Path::iter()` and `Path::components()` leave out `/.`.
- `Path::file_name()` normalizes `/.` to the parent component's file name.

Fixes #3508
2021-05-30 15:36:36 +12:00
6fdfc84904 Parse key=value named flag support. (#3515) 2021-05-29 20:42:03 -05:00
JT
bff81f24aa Autogenerate missing docs (#3514)
* Autogenerate missing docs

* Update ansi.md

* Rename question mark command docs

* Delete empty?.md
2021-05-30 12:57:04 +12:00
ed515cbc0c update keybindings to support new rustyline functionality (#3511)
* update keybindings to support new rustyline functionality

* remove some keybindings comments

* fix wasm build

* fixed multiline editing binding
2021-05-28 15:10:04 -05:00
87a6d7166c remove expect so that config doesn't fail (#3510) 2021-05-29 05:23:15 +12:00
JT
55baee9a9a Cleanup let varname and rhs (#3507) 2021-05-28 19:48:54 +12:00
JT
0886afe650 Fix for in (#3506)
* Fix for..in examples

* Fix for..in examples
2021-05-28 11:20:33 +12:00
JT
872f6166e1 Add for..in command (#3504) 2021-05-28 10:32:45 +12:00
fe348e236f Convert do command to engine-p; Fix flag name (#3503)
Renamed "ignore_errors" to "ignore-errors" to be aligned with nushell's
naming conventions.
2021-05-28 10:12:52 +12:00
e0f083d117 fix polars compile warnings (#3501) 2021-05-27 12:30:46 -05:00
48171f8e24 remove str from (#3500) 2021-05-27 12:18:02 -05:00
bcdf74562b remove into int references (#3499) 2021-05-27 11:35:25 -05:00
3a5ee1aed0 Dataframe commands (#3498)
* Sample command

* Join command with checks

* More dataframes commands

* Groupby and aggregate commands

* Missing feature dataframe flag

* Renamed file
2021-05-27 17:09:48 +12:00
d8c4b9c4fb add locale for ls size (#3497)
* add locale for ls size

* updated to work when it's not an exact match like de_DE when it needs de
2021-05-27 11:02:24 +12:00
6ae7884786 Fix path dots expansion (#3491)
* Fix parser expanding dots where it shouldn't

Previously, the parser would expand "a...b" as "a../..b". Now, >2 dots
are only expanded when the whole path component consists of dots (i.e.,
"..." expands to "../.." while "a...b" stays as it is).

* Respect OS separator when expanding >2 dots

"..." now expands to either "../.." or "..\..", based on the host OS.
2021-05-26 20:17:18 +12:00
JT
41834d16d6 Allow aliases to expand and ignore painting outside of lines (#3492) 2021-05-26 17:58:32 +12:00
1ee51f2afa Add the load-env command (#3484)
* Add the load-env command

load-env can be used to add environment variables dynamically via an
InputStream. This allows developers to create tools that output environment
variables as key-value pairs, then have the user load those variables in using
load-env. This supplants most of the need for an `eval` command, which is
mostly used in POSIX envs for setting env vars.

Fixes #3481

* fixup! Add the load-env command
2021-05-26 06:18:20 +12:00
65ee7aa372 correctly escape pipe in windows/cmd.exe (#3489)
* correctly escape pipe in windows/cmd.exe

* add some comments, take out debug line
2021-05-25 09:19:45 -05:00
ac38ee82f4 error message cleanup for into string (#3488) 2021-05-25 07:49:12 -05:00
JT
5fcc7f2328 Fix bad operator (#3479) 2021-05-24 17:27:10 +12:00
3bcc2aad80 Pass command's span correctly when reporting unexpected flags. (#3478)
Not all lite command's first part denotes a real command (for cases like sub commands that it's second lite part accounts for the command name). This is important so that we can refer to it's span correctly. Here we fix to include the command's name span correctly whether it's a command or sub command when reporting missing flag errors.
2021-05-23 19:30:30 -05:00
6165b6ae77 Add params to do (#3477) 2021-05-24 09:21:41 +12:00
e335e4fddc Groupby operations on dataframes (#3473)
* Added PolarsStruct enum to implement groupby

* template groupby

* groupby operationi on dataframes
2021-05-23 19:37:04 +12:00
5ab4199d71 Add path separator to char; Update char to engine-p; List all names of all possible chars (#3470)
* Allow querying the current path separator

* Convert char command to engine-p

* Wrap char args into struct

* Add --list option to char command

This lists all the available character names, along with the character
and its unicode points.
2021-05-22 11:48:33 -05:00
f075e2459d Commands to engine (#3448)
* commands to engine

* Correction of error in parser

* Added detailed regex error to parse

* better regex error parsing

* clippy corrections

* parse example with test

* secondary error for regex

* removed clone in error parser

* Secondary error message
2021-05-22 10:52:04 -05:00
94a26abf21 Implement path relative-to subcommand (#3461)
* Register new path relative-to command

* Implement `path relative-to` subcommand
2021-05-22 09:29:40 -05:00
bcbdc33049 Use enginep style in enter command (#3469) 2021-05-22 09:27:42 -05:00
21ef3895b3 delete crates/nu-command/src/commands/date/utc.rs (#3464)
The `date utc` command was removed in this PR:

https://github.com/nushell/nushell/pull/2780

The file was left but is no longer referenced from the parent module
and was not used.

Co-authored-by: Henrik Sjööh <henrik.sjooh@configura.com>
2021-05-22 17:09:50 +12:00
JT
3e99dc01b0 let date commands pull default date (#3463) 2021-05-22 17:02:06 +12:00
3e325a1974 update min rust version (#3462)
Co-authored-by: Henrik Sjööh <henrik.sjooh@configura.com>
2021-05-22 16:06:33 +12:00
2b92e3e8a7 port group-by to engine-p (#3458)
* migrate group-by to engine p

Part of #3390.

* consume positional argument correctly
2021-05-21 21:19:43 -05:00
cb90b90cbf nothing converted to string should return nothing and not fail (#3459) 2021-05-21 11:06:53 -05:00
9776a252ee add addition characters that can be hard to work with in nushell (#3457) 2021-05-21 08:04:48 -05:00
JT
751de20f93 Do a bit more cleanup of block params (#3455)
* Do a bit more cleanup of block params

* Do a bit more cleanup of block params
2021-05-21 19:04:27 +12:00
JT
28388b4e3a Split unit into duration and filesize (#3453) 2021-05-21 13:21:46 +12:00
JT
4fdbf30308 Paren interpolation (#3452)
* Switch interp to use parens

* improve interp parsing
2021-05-21 10:55:38 +12:00
722f191e82 updated round to support i64 (#3451) 2021-05-20 13:59:19 -05:00
JT
20f6114617 Improve block params (#3450) 2021-05-20 16:26:54 +12:00
3075e2cfbf Remove rest_args() from evaluated CommandArgs (#3449)
It was too error prone when positional arguments were used with the rest
arguments. Now, you need to explicitly state from which position you
want to count the rest args (e.g., `rest(0)`).
2021-05-20 10:26:23 +12:00
JT
e2973d2176 Add explicit block params (#3444)
* Add explicit block params

* Add explicit block params
2021-05-19 20:23:45 +12:00
JT
0ff08bb63a Just treat u64 like i64 for now (#3442) 2021-05-19 09:32:37 +12:00
08c0bf52bc Fix path join argument type and a typo (#3441) 2021-05-19 09:30:37 +12:00
d0229cb96e Load parquet and json files (#3437)
* Load parquet and json files

* changed csv file error
2021-05-19 07:33:10 +12:00
0612e5ccfb updated to the latest rustyline (#3439) 2021-05-18 12:36:55 -05:00
1b4f7b34c8 don't let externals break ansi escapes (#3438) 2021-05-17 18:01:34 -05:00
86e6fcd309 Negative indexing for range (#3427)
* adds negative indexing to range

* fixes tests to reflect new parsing changes

* removes duplicate definitons

* fmt
2021-05-17 15:08:47 +12:00
dc9cd7d8b9 Add Support for Partial Completions (#3432)
This commit adds a conditional event handler that inserts the next word of the
hint text when the user presses control and right arrow.
2021-05-16 08:43:43 +12:00
c0cc9ce7cd Dataframe new commands (#3425)
* Folder for dataframe commands

* New commands for dataframe
2021-05-15 19:24:11 +12:00
be2f66397b re-enable ansi support when externals break it (#3429) 2021-05-15 16:11:21 +12:00
07760b4129 Commands to engine p (#3426)
* hash and into converted

* keep command to engine p

* Update int.rs

Co-authored-by: JT <jonathandturner@users.noreply.github.com>
2021-05-15 16:11:07 +12:00
JT
d79a3130b8 Make the default int an i64 (#3428)
* Make the default int an i64

* fmt

* Fix random integer

* Treat pids as i64 for now
2021-05-14 20:35:09 +12:00
440a589f9e changed comment slightly 2021-05-13 14:21:16 -05:00
f5fcf9d635 Dataframe feature for plugins (#3424) 2021-05-13 21:49:46 +12:00
JT
9b8b1bad57 Don't insert PATH variable on Windows (#3422)
* Don't insert PATH variable on Windows

* Simplify fix

* Just centralize the var

* Add a message about why we have to workaround the issue
2021-05-13 15:03:49 +12:00
874ecd6c88 Made completion matching case-insensitive by default for windows (#3420) 2021-05-13 11:38:43 +12:00
JT
57e2fec497 Update LICENSE 2021-05-13 07:44:36 +12:00
0905a2c3a2 commands to engine p (#3417)
* commands to engine p

* Clippy suggestion

* Clippy suggestion
2021-05-13 07:07:20 +12:00
JT
3aa00b78f9 Improve missing var in var-path error (#3415) 2021-05-12 20:53:34 +12:00
JT
6769d46dbb Do less work in sys (#3413) 2021-05-12 17:24:22 +12:00
JT
efac712f62 Fix string interp/shorthand overlap (#3412) 2021-05-12 16:20:29 +12:00
JT
2bb23c57df Bump to 0.31.1 (#3411) 2021-05-12 15:06:50 +12:00
bc699a2cc1 date commands ported (#3410) 2021-05-12 14:01:49 +12:00
758c128147 Config commands to engine p (#3408)
* config get command

* config remove command

* config set command

* config command

* config commands
2021-05-12 14:01:16 +12:00
3795c2a39d Migrate last to engine-p (#3406)
* migrates last to engine-p

* removes unused import

* linter fix

* switch to as_usize()
2021-05-12 13:59:08 +12:00
JT
311c0e3f50 Simplify string interpolation (#3401)
* [DRAFT] simplify string interpolation

* Fix test
2021-05-12 13:53:57 +12:00
JT
25a8caa9b0 Simplify expressions (#3389)
* WIP: experiment with simpler expressions

* fix simple invoke

* update tests

* fix a few tests

* Make paren parsing more robust

* fix external args

* Remove old invocation

* Update tests

* Update tests
2021-05-12 13:01:48 +12:00
c80a9585b0 Complete Dataframe MVP (#3373)
* Dataframe MVP

* Removed test csv file

* Dataframe MVP

* Removed test csv file

* New revision polars

* New revision polars

* csv file reader

* argument parser for file reader

* Parser from Row primitive

* Column conversion

* Added as f32 and f64

* Parsing row to dataframe

* Removed repeated push to vector

* Accept table values to create dataframe

* Removed default serde

* Dataframe to rows to show data

* Save name of file with dataframe

* Usage example

* Upgrade polars version

* Clippy changes

* Added print function with head and tail

* Move dataframe struct to folder

* Lock file after running tests and merge

* Optional feature for dataframe

* Removed dataframe from plugins

* Update primitive.rs

Co-authored-by: JT <jonathandturner@users.noreply.github.com>
2021-05-12 13:01:31 +12:00
e73491441a make seq more nu-like by returning numbers when possible (#3409) 2021-05-11 12:02:16 -05:00
b93b80ccaa Remove unnecessary work from ps (#3407)
The ps plugin has an unnecessary call to `std:🧵:sleep(500ms)` that
delays runtime by 500ms. Additionally, there is a call to
`sysinfo::SystemExt::refresh_process`, which is not required as the previous
call to sysinfo::SystemExt::refresh_all handles the "refresh" of all processes
without a need to do this individually. Combining both of these improvements
means that running ps runs nearly instentaneously.

Fixes #3402
2021-05-11 19:59:24 +12:00
JT
48128c9db6 Bump to 0.31.0 (#3405) 2021-05-11 16:44:52 +12:00
6dafaa197d Commands to engine p (#3404)
* Change ansi command

* Change ansi strip command

* Change benchmark to engine-p

* ansi strip removed arg.process()

* benchmark without process args
2021-05-11 16:03:55 +12:00
1634d8e087 add into string (#3403) 2021-05-10 12:58:51 -05:00
7a583083b8 Convert the rest of "random" subcommands to engine-p (#3399)
* Convert "random bool" to engine-p

Also implements FromValue for Tagged<BigDecimal> and Tagged<f64>.

* Convert "random dice" to engine-p

* Convert "random uuid" to engine-p

* Covert "random chars" to engine-p

* Convert "random" command to engine-p
2021-05-10 19:07:57 +12:00
75156ab0c9 engine-p: build-string remove ActionStream (#3394) 2021-05-08 16:59:12 +12:00
9fd6923821 Port random integer & decimal to engine-p + related refactoring (#3393)
* Implement minmax for Range; Simplify range command

* Port random integer to enginep; New FromValue impl

Now, FromValue is implemented for Tagged<Range> to allow extracting args
into this type.

* Make sure range value extraction fails properly

The range endpoint extraction methods now return error instead of
silently clipping the value. This now makes `random integer ..-4` fail
properly since -4 can't be cast as u64.

* Port random decimal to enginep & Refactor

This added a way to interpret Range limits as f64 and a Primitive helper
to get its value as f64.

A side effect of this commit is that it is now possible to specify the
command bounds as true decimals. E.g., `random decimal 0.0..3.14` does
not clip 3.14 to 3.
2021-05-08 07:58:12 +12:00
JT
91a929b2a9 Clippy fixes for new Rust version (#3392) 2021-05-07 07:58:21 +12:00
0f8e31af06 Juicy features cargo installer wrapper (#3388)
* Juicy features cargo installer wrapper

* Remove `--force` parameter
2021-05-06 18:55:47 +12:00
bd71c2f34d #3385: Add ignore-case and duplicated options to uniq command (#3387)
* #3385: Add ignore-case and duplicated options to uniq command

* rustfmt
2021-05-06 12:07:42 +12:00
001123dbd6 Add first prototype of functionality to parse numbers in parantheses (#3209)
* Add first prototype of functionality to parse numbers in parantheses (). Needs testing and more wide-testing+integration

* Fix styling issue

* Try something else by copying existing matching code

* Fix formatting

* Fix the parser to accept numbers in paranthesis. Not really happy with the code, but let's see

* Refactor to only use once the parsing of strings into numbers

* Remove errors that are not used

* Fix formatting

Co-authored-by: Stefan Stanciulescu <contact@stefanstanciulescu.com>
2021-05-06 09:00:55 +12:00
cfee151d4e Dont unwrap rustyline helper in cli (#3382)
If nu fails to load a user config on startup, no helper is set and the
later call to `rl.helper_mut()` will panic. There may be better ways to
handle this long-term, but printing an error about the failure to parse
the config and then starting with default values seems reasonable.
2021-05-04 15:50:38 +12:00
JT
fc59291191 Simplify down to one type of context (#3379)
* Simplify down to one type of context

* More simplification
2021-05-03 11:45:55 +12:00
4fc05cac56 Port range to engine-p (#3377)
* Removes arg serialization
* action stream -> output stream
* uses nu_protocol::Range instead of NumericRange
* random missing newline I found in the code
2021-05-03 07:47:59 +12:00
cc4616f25b added check for endian-ness, added a bytes and skip (#3375) 2021-05-01 15:48:17 -05:00
e82fbb7bcf added ability to change "#" color using header_color (#3374) 2021-05-01 12:30:50 -05:00
8cd639f6a2 add nu-pretty-hex, add into binary, update binaryview (#3370)
* add nu-pretty-hex, add into binary, update binaryview

* updated parameter name, updated examples

* fixed nu-pretty-hex test

* fixed tests again! and added a no color option to pretty-hex
2021-05-01 11:12:25 -05:00
a8f555856a tweaked the error handling to show specific errors (#3367) 2021-04-30 09:24:06 -05:00
3792562046 updated to a quicker levenshtein implementation (#3366) 2021-04-29 07:10:10 -05:00
d05c48a1d7 Fix #3231: Pick up nu-env if cd with shortcuts (#3344)
* Fix autoenv not set when using implied cd

* Fix wrong return value

* Fix windows no value returned err
2021-04-28 17:31:22 +12:00
36cc5eb933 Fix array index out of bounds error in nu_protocol::value::levenshtein_distance() (#3358)
* Fix array index out of bounds error.

Error occured when computing levenshtein_distance of two strings where
str.len() is not equal to str.chars().collect::<Vec<_>>().len()

* Add test for levenshtein_distance
2021-04-28 07:30:32 +12:00
f9f74a0f7d #3298: reduce --numbered bug (#3354) 2021-04-27 19:07:56 +12:00
77f42931ff Fix table-pager feature compilation (#3359)
It compiles and works without errors, but the pager is not asynchronous
anymore (i.e., you need to wait for `seq 1 1000000` to finish before the
pager is displayed).
2021-04-27 19:06:57 +12:00
73f62266c6 allow start to handle urls (#3351) 2021-04-24 09:33:17 -05:00
df2f3d25b0 ichwh removed (#3349)
* ichwh removed

* removed unnecessary into() on PathBuf
2021-04-23 05:14:20 +12:00
JT
599c43ce04 bump to 0.30.1 (#3348) 2021-04-22 21:07:54 +12:00
5c2199e7f4 Move any to enginep style (#3324) 2021-04-22 20:35:45 +12:00
JT
3ad4e0348f Fix external redirect (#3345)
* Fix external redirection

* Fix external redirection
2021-04-22 08:54:34 +12:00
JT
02d5729941 Properly evaluate dynamic blocks (#3339) 2021-04-21 14:31:54 +12:00
JT
ce35689d2e Make finding the path variable more robust (#3336)
* Make finding the path variable more robust

* Update reload_config also
2021-04-21 11:37:10 +12:00
JT
da81e21bf2 Add a sync from the known path to the env (#3335) 2021-04-21 08:22:53 +12:00
3b2ed7631f Path Enhancement Project #2: parse, join and split (#3256)
* Add new path parse subcommand

This includes a slight refactor to all the path subcommand `action()`
functions.

* Remove filestem and extension; Fix example

* Add additional description to path parse

* Put join arg behind flag; Fix missing import (Win)

* Fix error when column path is passed as arg

* Add structured path joining

Structured path is implicitly joined at every patch subcommand call.

* Fix existing path join tests; Fix rustfmt

* Remove redundant 'static lifetime (clippy)

* Add initial impl of path split subcommand

* Add ability to join path from parts

* Fix wrong results in path split examples

* Fix remaining asyncs after engine change

* Do not wrap split path parts into table

When the input is just a list of values, the `path split` command will
split each value directly into the output stream, similar to
`split-row`. Column path--specified values are still wrapped into a
table so they can still be used to replace table fields.

* Join list of values instead of going one-by-one

When `path join` encounters a list of values, it attempts to join them,
instead of going one-by-one like the rest of the path commands. You can
still `each { echo $it | path join }` to join them one-by-one, if the
values are, e.g., tables.

Now, the behavior of `path split` and `path join` should match the
`split-row` and `str collect` counterparts and should hopefully align
better with user's expectations.

* Make sure path join detects structured path

* Fix panic on empty input stream

Also, doesn't collect input into vector unnecessarily.

* Fix path join not appending value

* Remove argument serialization

* Make better errors; Misc refactor

* OsStr -> String encoding is now lossy, instead of throwing an error
* The consequence is action() now always returns Value instead of Result
* Removed redundant handle_value() call in `path join`
* Fix possible incorrect error detection in `path split`
* Applied rustfmt + clippy

* Add more usage, examples & test; Fix type error

The 'parent' column was required to be a path but didn't work with
string.

* Add more help & examples; Maybe fix Windows error

* Refactor operate function

Reducing code repetition

* Review usages and examples

* Add the option to manually specify the extension

* Add more tests; Fix failures on Windows

* Move path commands to engine-p

* Small refactor
2021-04-20 18:45:28 +12:00
1a46e70dfb Use new functions in which (#3310)
* Use new functions in which

* Impl rest_with_minimum and use it

* Use has_flag instead of get_switch
2021-04-20 18:38:36 +12:00
JT
0fc9b6cfa2 Bump to 0.30 (#3333)
* Bump to 0.30

* fix test
2021-04-20 18:34:10 +12:00
JT
61768aa2fd Fix parsing dot dot path (#3331) 2021-04-20 08:18:29 +12:00
ea5bf9db36 add query json plugin for experimentation (#3327)
* add query json plugin for experimentation

* add some error handling

* closer but Kind::Array is still horked

* unravel the table so the output looks right

* clippy

* added the ability to use gjson modifiers
2021-04-19 11:19:06 -05:00
JT
9d24afcfe3 Make nth more stream-able (#3330) 2021-04-19 19:45:12 +12:00
033df9457b Engine-p style in compact (#3325)
* Engine-p style in compact

* Remove unused import

* Use filter in compact, thanks to clippy for spotting it :]
2021-04-19 06:40:29 +12:00
d8e105fe34 Enginep/all (#3312)
* Add iter-extensions

* Move all to enginep style

* Remove iter extensions

* Fix clippy lints

* Add comment and make ? more visible

* Remove try_all

* Remove all because it cant return err
2021-04-19 06:39:33 +12:00
d34068da18 Implementing Nu command guide. (#3326) 2021-04-16 08:11:26 -05:00
611103d211 Fix Running echo .. starts printing integers forever (#3322) 2021-04-16 07:07:06 +12:00
528c1c5fd8 change $scope.variables output to a table (#3323) 2021-04-15 14:02:08 -05:00
JT
f73732bf1e Move to* and from* to engine-p (#3320)
* WIP

* Finish last batch
2021-04-15 19:43:33 +12:00
fd7875e572 sort scope.aliases, commands, variables (#3319) 2021-04-14 18:57:47 -05:00
e8bc319f08 Make sure that scripts can also have custom commands. (#3309)
With the current code it is possible to attach custom commands from
a custom binary, but only for interactive mode. This change makes
it possible to also customize the evaluation context for commands
and scripts.
2021-04-15 06:21:50 +12:00
a92ff57270 Use append_history instead of save_history to preserve existing history (#3314) 2021-04-15 06:20:25 +12:00
004230d02d Deserialization and outputstream math commands (#3315)
* Output error when ls into a file without permission

* math sqrt

* added test to check fails when ls into prohibited dir

* fix lint

* math sqrt with tests and doc

* trigger wasm build

* Update filesystem_shell.rs

* converted math commands to outputstream and new method for arg evaluation

* fmt

* clippy

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2021-04-15 06:18:37 +12:00
a148c640b2 add variables to $scope (#3316) 2021-04-14 09:48:14 -05:00
ea0205f2ff remote --help/-h from $scope.commands display (#3311)
* remote --help/-h from $scope.commands

* change a test
2021-04-14 07:55:58 -05:00
005649b6fc remove dupes in get_commands/get_command_names (#3308) 2021-04-13 09:21:44 -05:00
e09e3b01d6 Fix the auto-conversion regression (#3307) 2021-04-13 14:25:18 +12:00
fc15e0e27d A few optimisations (#3306)
* A few optimisations

* Fix test
2021-04-12 19:47:31 +12:00
b2fe5fabb1 Add commands to scope variable (#3272)
* Add commands to scope variable

* List commands with signature on scope variables

Usage:

```shell
echo $scope.commands | pivot
```

* Run rust formater

* Update variables.rs

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2021-04-12 14:38:47 +12:00
52d69bb021 Fix #3213 Rest arg is not optional (#3303) 2021-04-12 14:37:36 +12:00
5f550a355b Split OutputStream into ActionStream/OutputStream (#3304)
* Split OutputStream into ActionStream/OutputStream

* Fmt

* Missed update

* Cleanup helper names

* Fmt
2021-04-12 14:35:01 +12:00
dbecbdccd4 Fix type error when Value is viewed as file path (#3305)
I believe this should be "path", not "string".
2021-04-12 11:53:31 +12:00
734877338d Remove take_while from internal iterator (#3301) 2021-04-11 18:29:01 +12:00
2e439ca77f Improve range and internal iteration (#3300) 2021-04-11 13:31:08 +12:00
a853880e07 preparing for into subcommands (#3299) 2021-04-10 11:29:11 -05:00
93f3ed98e1 clean up error handling a bit (#3297)
* clean up error handling a bit

* clippy
2021-04-10 10:10:23 -05:00
a131eddf54 move out call info deserializing from str (#3294) 2021-04-09 22:58:18 -05:00
b19a7aa8a6 Disallow rm to trash if built without trash-support enabled. (#3278)
If built without `trash_support`, nu should explicitly reject attempts to use `rm` with the `--trash` option, or with a config file which includes `rm_always_trash = true`.

As of 42fac72, there doesn't seem to be any guard in the `#[cfg(not(feature = "trash-support"))]` block of `filesystem_shell::rm`, leading to the behavior described in #3116, where builds without the trash-support feature will delete things permanently regardless of flags/config options.

This should close #3116
2021-04-10 14:01:21 +12:00
f5aa53c530 Remove length collecting its input (#3292)
* Remove length collecting its input

* Update length.rs
2021-04-10 10:21:51 +12:00
80f5e14512 Fix ansi rgb fg (#3293)
* add term size command

* bug: fix ansi rgb_fg
2021-04-09 15:32:25 -05:00
41390cd963 Simplify the default feature list (#3288) 2021-04-09 13:39:44 -05:00
0b5e131410 Remove x1b, update prompt (#3291)
* add term size command

* remove \x1b and use nu_ansi_term, make prompt with no config prettier
2021-04-09 11:38:56 -05:00
556596bce8 add "into int" behavior (#3279)
* add 'int' command

* create `into int` behavior

- forcibly overwrote prior implementation

```sh
git mv -f  crates/nu-command/src/commands/int_.rs crates/nu-command/src/commands/into_int.rs
```
- picked up prior work, polished and renamed

Co-authored-by: Saeed Rasooli <saeed.gnu@gmail.com>
2021-04-09 09:17:39 -05:00
b791d1ab0d Move from using a Block to an Arc'd Block (#3289) 2021-04-09 20:12:25 +12:00
ac070ae942 Refactor/config commands (#3265)
* Use ctx.configs in all config commands

* Remove all setting/accessing of  vars.("config-path")

* Add tests

* Add comment

* Reload cfg on remove

* Hypocratic ws change

* Use history_path in hist_or_default

* Make clippy happy

* Fix rebase stuff

* Fix clippy lint
2021-04-09 18:03:12 +12:00
111ad868a7 Do not store whitespace entries into history (#3019) (#3286)
Before storing an entry into the history nushell will check if the entry
consists only of whitespaces and if so, it does not store it in the history and
this avoids  newline repetition when user is navigating in the history.
2021-04-09 12:01:31 +12:00
a7274115d0 make Table, Autoview read in memory config. (#3287) 2021-04-08 17:31:19 -05:00
81160bcefb Remove some clones and improve when autoview reads config (#3285) 2021-04-09 07:47:41 +12:00
2880109f31 Runnable contexts move. (#3283)
* Engine extract first steps.

* Don't depend on ShellManager.
2021-04-08 13:51:12 -05:00
09a1f5acb9 Begin migration away from arg serialization (#3281)
* initial implementation

* Move a few commands over to new arg system

* Fix char also
2021-04-08 20:15:36 +12:00
5fcf11fcb0 Fix externals busy waiting (#3280) 2021-04-08 07:25:15 +12:00
42fac722bb Bump to 0.29.2 (#3274)
* Bump to 0.29.2

* Fix test
2021-04-07 08:14:06 +12:00
073e5727c6 Switch to "engine-p" (#3270)
* WIP

* WIP

* first builds

* Tests pass
2021-04-06 11:19:43 -05:00
ad1c4f5e39 [fix] crashing issues when the given timestamp is out of range (#3271)
* add support for timestamp-based time conversion by specifing timezone or 'UTC/Local'

* [fix] fix the wrong test sample

* code formating

* code formating and import missing mod to test

* code formating again

* [fix] it won't crash when given timestamp is too big.

* [fix] code formatting =_=b
2021-04-06 07:22:07 -05:00
dc8a68c98f Bump sysinfo (#3267)
* add term size command

* update to the latest sysinfo
2021-04-05 14:36:19 -05:00
e5621dea58 Remove yr and mon (#3262)
* Remove `yr` and `mon`

* Remove usage of mon in test

* Fix test
2021-04-05 06:19:33 +12:00
00acf22f5f account for startup commands in the scope. (#3261)
* Revert "Impl one configurable function to run scripts (#3242)"
* pass config startup.
2021-04-04 00:14:58 -05:00
4c09716ad8 add TiB and PiB (#3257) 2021-04-04 12:08:17 +12:00
1c941557c3 Remove unused help shell. Slight cleanup and improvement. (#3258) 2021-04-03 18:56:46 -05:00
28e1a7915d Impl one configurable function to run scripts (#3242)
* Impl one func to run scripts

* Add exit_on_err

* Remove run_standalone

* Make the compiler happy :)
2021-04-04 07:31:53 +12:00
4bc9d9fd3b Fix typos and capitalization of "Unicode" (#3234)
* Capitalize "Unicode"

* Fix several typos

* Fix mixed whitespace in nu-parser's tests
2021-04-04 07:14:07 +12:00
e278ca61d1 commands: any? all? (#3252)
* commands: any? all?

We can check if `any` (or `all`) rows of tables match predicates.

Small `all?` example: Given the following table with `services` running:

```
> echo [[status]; [UP] [UP]]
───┬────────
 # │ status
───┼────────
 0 │ UP
 1 │ UP
───┴────────
```

We can ask if all services are UP, like so:

```
> echo [[status]; [UP] [UP]] | all? status == UP
true
```

* Fix any? signature.
2021-04-03 13:40:54 -05:00
2146ede15d Parse decimal units (#3243)
* parse decimal units

* linting

* stop clippy complaining

* Added tests to parsing decimals

* Fixed bug

* Fixed testing and add more
2021-04-03 21:06:13 +13:00
e737222a5d fix lack of auto-suggestion for aliases (#3249) 2021-04-03 10:39:30 +13:00
f03f1949bf Logs and tests (#3247)
* Add command name to err

* Add var name to error message

* Add test for def comment in test
2021-04-01 17:09:33 -05:00
0fe6c7c558 Mathsqrt (#3239)
* Output error when ls into a file without permission

* math sqrt

* added test to check fails when ls into prohibited dir

* fix lint

* math sqrt with tests and doc

* trigger wasm build

* Update filesystem_shell.rs

* always forgetting the linting

* fix clippy complaining

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2021-04-01 16:26:05 -05:00
b13202bbfc Fix #3244: Add tag reloaded frame (#3246) 2021-04-01 16:25:26 -05:00
90fae903ce Fixes error when trying to delete a FIFO (#3235)
* Output error when ls into a file without permission

* added test to check fails when ls into prohibited dir

* fix lint

* trigger wasm build

* be able to remove fifos

* Update filesystem_shell.rs

* I thought windows had fifos

* fixed unix and windows conditional compilation

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2021-04-01 06:10:40 +13:00
06b154f4b2 Bump to 0.29.1 (#3232)
* Bump to 0.29.1

* fix test
2021-03-31 20:13:40 +13:00
419a0665c8 Output error when ls into a file without permission (#3218)
* Output error when ls into a file without permission

* added test to check fails when ls into prohibited dir

* fix lint

* trigger wasm build

* Update filesystem_shell.rs

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2021-03-31 19:52:39 +13:00
387098fc87 Stop nu panicks in math.round on a large decimal value(Most of the time) (#3224)
* Stop crashing when dealing with large numbers in math round

* Fix formatting

* add tests

* just to trigger wasm build

* trigger wasm build
2021-03-31 19:01:39 +13:00
c42b588782 Refactor nu-cli/env* (#3041)
* Revert "History, more test coverage improvements, and refactorings. (#3217)"

This reverts commit 8fc8fc89aa.

* Add tests

* Refactor .nu-env

* Change logic of Config write to logic of read()

* Fix reload always appends to old vars

* Fix reload always takes last_modified of global config

* Add reload_config in evaluation context

* Reload config after writing to it in cfg set / cfg set_into

* Add --no-history to cli options

* Use --no-history in tests

* Add comment about maybe_print_errors

* Get ctrl_exit var from context.global_config

* Use context.global_config in command "config"

* Add Readme in engine how env vars are now handled

* Update docs from autoenv command

* Move history_path from engine to nu_data

* Move load history out of if

* No let before return

* Add import for indexmap
2021-03-31 18:52:34 +13:00
4faaa5310e Bump to 0.29 (#3230)
* Bump to 0.29

* fix test
2021-03-30 22:35:21 +13:00
c448abd44e echo $scope.aliases | pivot to see all of your aliases (#3203)
* enable ability to see all aliases, pull in code from scope branch

* add in the alias tests

* add back in my changes to variables.rs after merging in andrasios changes from PR #3217
2021-03-29 21:27:51 +13:00
2517588d7d update date to-timezone usage (#3223) 2021-03-28 18:41:42 -05:00
8fc8fc89aa History, more test coverage improvements, and refactorings. (#3217)
Improvements overall to Nu. Also among the changes here, we can also be more confident towards incorporating `3041`. End to end tests for checking envs properly exported to externals is not added here (since it's in the other PR)

A few things added in this PR (probably forgetting some too)

* no writes happen to history during test runs.
* environment syncing end to end coverage added.
* clean up / refactorings few areas.
* testing API for finer control (can write tests passing more than one pipeline)
* can pass environment variables in tests that nu will inherit when running.

* No longer needed.

* no longer under a module. No need to use super.
2021-03-27 00:08:03 -05:00
b243b3ee1d fixed typo in help text (#3216) 2021-03-26 08:11:41 -05:00
28e08afada add ability to cd to ~/blah (#3210)
* add ability to cd to ~/blah. tested on windows.

* added dirs_next

* put change behind feature for linux-minimal/wasm

* clippy

* holy crap minimal, i'm about done with you!
2021-03-26 22:29:02 +13:00
7e184b58b2 Fix warnings for Rust 1.51 (#3214)
* Fix warnings for Rust 1.51

* More fixes

* More fixes
2021-03-26 21:26:57 +13:00
589fc0b8ad add support for timestamp-based time conversion by specifying timezone (#3207)
* add support for timestamp-based time conversion by specifing timezone or 'UTC/Local'

* [fix] fix the wrong test sample

* code formating

* code formating and import missing mod to test

* code formating again
2021-03-24 08:08:23 -05:00
f0c7c1b500 fix: prompt does not find external commands #3134 (#3189)
the initial setup-commands of nushell were executed without loading environment variables
this resulted in the PATH not being available at this point until an external command was run once
which resulted in env_vars being added
let run_result = run_block(&prompt_block, &context, InputStream::empty()).await;

Co-authored-by: alexhk <alexhk@protonmail.com>
2021-03-23 22:38:07 +13:00
840bd98e01 support forward slash for directory completion in Windows (#3201)
While the "main" separator in Windows is the backslash, it supports the
forward slash as a separator too.
Add support for this so that the behavior is similar to the way Windows
PowerShell handles the forward slash: it is recognized as a separator,
and when using <tab> for path completion the slash is reversed.
2021-03-23 16:20:01 +13:00
a5cdd22bfe Add basic support for md5 hashing strings and binary data (#3197) 2021-03-21 07:48:53 +13:00
0c7bcae9b1 Update contributor-book url (#3198) 2021-03-20 23:09:17 +13:00
ab666c170c added the ability to create multi-byte unicode chars like emoji (#3195) 2021-03-18 13:42:39 -05:00
d2213d18fa Playground infraestructure (tests, etc) additions. (#3179)
* Playground infraestructure (tests, etc) additions.

A few things to note:

* Nu can be started with a custom configuration file (`nu --config-file /path/to/sample_config.toml`). Useful for mocking the configuration on test runs.
* When given a custom configuration file Nu will save any changes to the file supplied appropiately.
* The `$nu.config-path` variable either shows the default configuration file (or the custom one, if given)
* We can now run end to end tests with finer grained control (currently, since this is baseline work, standard out) This will allow to check things like exit status, assert the contents with a format, etc)

* Remove (for another PR)
2021-03-15 02:26:30 -05:00
82b6300dcb fix: cargo test failed with --release (#3183) (#3184)
Signed-off-by: nibon7 <nibon7@163.com>
2021-03-15 16:59:04 +13:00
b69cda9e07 Add --signal option to kill command (#3077) (#3079) 2021-03-15 13:10:52 +13:00
56adc7c3c6 imp: bump rustyline to 8.0.0 (#3167)
* imp: bump rustyline to 8.0.0

* fix: rustyline 8 keybindings

* fix: commands count/length test

Co-authored-by: alexhk <alexhk@protonmail.com>
2021-03-14 15:13:31 +13:00
2ace20fade Make opening a directory list its contents (#3118)
* Make opening a directory enter it.

Not sure if this change is wanted, but I'm not sure what else opening a directory could mean.
And I find myself accidentally using `open <dir>` to mean `enter <dir>`

* Add example to open directory

* Open dir should list it's contents

* Update example description and fix style
2021-03-14 10:47:31 +13:00
c13fe83784 Rename count to length (#3166)
* update docs to refer to length instead of count

* rename count to length

* change all occurrences of 'count' to 'length' in tests

* format length command
2021-03-14 10:46:40 +13:00
6cf8df8685 Move script to nu engine (#3092)
* Move run_script to engine

* Add which dep and feature to engine

* Change unwrap to expect

* Add wasm specification

* Remove which from default, add specification correctly

* Add nu-platform-specifics

* Move is_external_cmd to platform_specifics

* Add is_external_cmd to host and use it instead of nu_platform directly

* Clean up if else logic in is_external_cmd

* Bump nu-platform-specifics version

* Pass context to print_err

* Commit cargo.lock

* Move print functions to own module inside nu-engine

* Hypocratic change to run windows-nightly again

* Add import for Ordering

* Move printing of error to host

* Move platform specific which functionality to basic host

* Allow no use of cmd_name

* Fix windows compile issue
2021-03-12 18:20:54 +13:00
86a89404be fix: unicode byte counting error #3150 (#3159)
Co-authored-by: hk <alexhaka10@protonmail.com>
2021-03-12 07:11:07 +13:00
0d305d7c3e Lines no longer treats a text buffer as a line (#3153) 2021-03-11 11:35:15 +13:00
ee5bd2b4b3 move from h1-client-rustls to hyper-client (#3154) 2021-03-10 15:45:53 -06:00
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
1170 changed files with 64674 additions and 27216 deletions

View File

@ -21,15 +21,6 @@ strategy:
windows-stable:
image: windows-2019
style: 'unflagged'
linux-nightly-canary:
image: ubuntu-18.04
style: 'canary'
macos-nightly-canary:
image: macos-10.14
style: 'canary'
windows-nightly-canary:
image: windows-2019
style: 'canary'
fmt:
image: ubuntu-18.04
style: 'fmt'
@ -43,7 +34,6 @@ 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"
@ -59,18 +49,12 @@ steps:
- bash: RUSTFLAGS="-D warnings" cargo test --all
condition: eq(variables['style'], 'unflagged')
displayName: Run tests
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used -A clippy::needless_collect
condition: eq(variables['style'], 'unflagged')
displayName: Check clippy lints
- bash: RUSTFLAGS="-D warnings" cargo test --all
condition: eq(variables['style'], 'canary')
displayName: Run tests
- bash: cd samples/wasm && wasm-pack build
- bash: cd samples/wasm && npm install wasm-pack && node ./node_modules/wasm-pack/run.js 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 --features=rustyline-support
condition: eq(variables['style'], 'minimal')
displayName: Run tests

View File

@ -1,47 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1.
2.
3.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Configuration (please complete the following information):**
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.**

65
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,65 @@
name: Bug Report
description: Create a report to help us improve
body:
- type: textarea
id: description
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: repro
attributes:
label: How to reproduce
description: Steps to reproduce the behavior
placeholder: |
1.
2.
3.
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen.
placeholder: I expected nu to...
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: Please add any relevant screenshots here, if any
validations:
required: false
- type: textarea
id: config
attributes:
label: Configuration
description: "Please run `> version | pivot key value | to md` and paste the output to show OS, features, etc"
placeholder: |
> version | pivot key value | to md
╭───┬────────────────────┬───────────────────────────────────────────────────────────────────────╮
│ # │ key │ value │
├───┼────────────────────┼───────────────────────────────────────────────────────────────────────┤
│ 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, rich-benchmark, │
│ │ │ rustyline, term, uuid, which, zip │
╰───┴────────────────────┴───────────────────────────────────────────────────────────────────────╯
validations:
required: false
- type: textarea
id: context
attributes:
label: Additional context
description: Add any other context about the problem here.
validations:
required: false

View File

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -0,0 +1,34 @@
name: Feature Request
description: "When you want a new feature for something that doesn't already exist"
body:
- type: textarea
id: problem
attributes:
label: Related problem
description: Is your feature request related to a problem? Please describe.
placeholder: |
A clear and concise description of what the problem is.
Example: I am trying to do [...] but [...]
validations:
required: false
- type: textarea
id: desired
attributes:
label: "Describe the solution you'd like"
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: "Describe alternatives you've considered"
description: "A clear and concise description of any alternative solutions or features you've considered."
validations:
required: false
- type: textarea
id: context
attributes:
label: Additional context and details
description: Add any other context or screenshots about the feature request here.
validations:
required: false

View File

@ -1,118 +0,0 @@
name: Publish consumable Docker images
on:
push:
tags: ['v?[0-9]+.[0-9]+.[0-9]+*']
jobs:
compile:
runs-on: ubuntu-latest
strategy:
matrix:
arch:
- x86_64-unknown-linux-musl
- x86_64-unknown-linux-gnu
steps:
- uses: actions/checkout@v2
- name: Install rust-embedded/cross
env: { VERSION: v0.1.16 }
run: >-
wget -nv https://github.com/rust-embedded/cross/releases/download/${VERSION}/cross-${VERSION}-x86_64-unknown-linux-gnu.tar.gz
-O- | sudo tar xz -C /usr/local/bin/
- name: compile for specific target
env: { arch: '${{ matrix.arch }}' }
run: |
cross build --target ${{ matrix.arch }} --release
# leave only the executable file
rm -frd target/${{ matrix.arch }}/release/{*/*,*.d,*.rlib,.fingerprint}
find . -empty -delete
- uses: actions/upload-artifact@master
with:
name: ${{ matrix.arch }}
path: target/${{ matrix.arch }}/release
docker:
name: Build and publish docker images
needs: compile
runs-on: ubuntu-latest
env:
DOCKER_REGISTRY: quay.io/nushell
DOCKER_PASSWORD: ${{ secrets.DOCKER_REGISTRY }}
DOCKER_USER: ${{ secrets.DOCKER_USER }}
strategy:
matrix:
tag:
- alpine
- slim
- debian
- glibc-busybox
- musl-busybox
- musl-distroless
- glibc-distroless
- glibc
- musl
include:
- { tag: alpine, base-image: alpine, arch: x86_64-unknown-linux-musl, plugin: true, use-patch: false}
- { tag: slim, base-image: 'debian:stable-slim', arch: x86_64-unknown-linux-gnu, plugin: true, use-patch: false}
- { tag: debian, base-image: debian, arch: x86_64-unknown-linux-gnu, plugin: true, use-patch: false}
- { tag: glibc-busybox, base-image: 'busybox:glibc', arch: x86_64-unknown-linux-gnu, plugin: false, use-patch: true }
- { tag: musl-busybox, base-image: 'busybox:musl', arch: x86_64-unknown-linux-musl, plugin: false, use-patch: false}
- { tag: musl-distroless, base-image: 'gcr.io/distroless/static', arch: x86_64-unknown-linux-musl, plugin: false, use-patch: false}
- { tag: glibc-distroless, base-image: 'gcr.io/distroless/cc', arch: x86_64-unknown-linux-gnu, plugin: false, use-patch: true }
- { tag: glibc, base-image: scratch, arch: x86_64-unknown-linux-gnu, plugin: false, use-patch: false}
- { tag: musl, base-image: scratch, arch: x86_64-unknown-linux-musl, plugin: false, use-patch: false}
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@master
with: { name: '${{ matrix.arch }}', path: target/release }
- name: Build and publish exact version
run: |-
export DOCKER_TAG=${GITHUB_REF##*/}-${{ matrix.tag }}
export NU_BINS=target/release/$( [ ${{ matrix.plugin }} = true ] && echo nu* || echo nu )
export PATCH=$([ ${{ matrix.use-patch }} = true ] && echo .${{ matrix.tag }} || echo '')
chmod +x $NU_BINS
echo ${DOCKER_PASSWORD} | docker login ${DOCKER_REGISTRY} -u ${DOCKER_USER} --password-stdin
docker-compose --file docker/docker-compose.package.yml build
docker-compose --file docker/docker-compose.package.yml push # exact version
env:
BASE_IMAGE: ${{ matrix.base-image }}
#region semantics tagging
- name: Retag and push with suffixed version
run: |-
VERSION=${GITHUB_REF##*/}
latest_version=${VERSION%%%.*}-${{ matrix.tag }}
latest_feature=${VERSION%%.*}-${{ matrix.tag }}
latest_patch=${VERSION%.*}-${{ matrix.tag }}
exact_version=${VERSION}-${{ matrix.tag }}
tags=( ${latest_version} ${latest_feature} ${latest_patch} ${exact_version} )
for tag in ${tags[@]}; do
docker tag ${DOCKER_REGISTRY}/nu:${VERSION}-${{ matrix.tag }} ${DOCKER_REGISTRY}/nu:${tag}
docker push ${DOCKER_REGISTRY}/nu:${tag}
done
# latest version
docker tag ${DOCKER_REGISTRY}/nu:${VERSION}-${{ matrix.tag }} ${DOCKER_REGISTRY}/nu:${{ matrix.tag }}
docker push ${DOCKER_REGISTRY}/nu:${{ matrix.tag }}
- name: Retag and push debian as latest
if: matrix.tag == 'debian'
run: |-
VERSION=${GITHUB_REF##*/}
# ${latest features} ${latest patch} ${exact version}
tags=( ${VERSION%%.*} ${VERSION%.*} ${VERSION} )
for tag in ${tags[@]}; do
docker tag ${DOCKER_REGISTRY}/nu:${VERSION}-${{ matrix.tag }} ${DOCKER_REGISTRY}/nu:${tag}
docker push ${DOCKER_REGISTRY}/nu:${tag}
done
# latest version
docker tag ${DOCKER_REGISTRY}/nu:${{ matrix.tag }} ${DOCKER_REGISTRY}/nu:latest
docker push ${DOCKER_REGISTRY}/nu:latest
#endregion semantics tagging

View File

@ -1,8 +1,9 @@
name: Create Release Draft
on:
workflow_dispatch:
push:
tags: ['[0-9]+.[0-9]+.[0-9]+*']
tags: ["[0-9]+.[0-9]+.[0-9]+*"]
jobs:
linux:
@ -28,6 +29,60 @@ jobs:
command: build
args: --release --all --features=extra
# - name: Strip binaries (nu)
# run: strip target/release/nu
# - name: Strip binaries (nu_plugin_inc)
# run: strip target/release/nu_plugin_inc
# - name: Strip binaries (nu_plugin_match)
# run: strip target/release/nu_plugin_match
# - name: Strip binaries (nu_plugin_textview)
# run: strip target/release/nu_plugin_textview
# - name: Strip binaries (nu_plugin_binaryview)
# run: strip target/release/nu_plugin_binaryview
# - name: Strip binaries (nu_plugin_chart_bar)
# run: strip target/release/nu_plugin_chart_bar
# - name: Strip binaries (nu_plugin_chart_line)
# run: strip target/release/nu_plugin_chart_line
# - name: Strip binaries (nu_plugin_from_bson)
# run: strip target/release/nu_plugin_from_bson
# - name: Strip binaries (nu_plugin_from_sqlite)
# run: strip target/release/nu_plugin_from_sqlite
# - name: Strip binaries (nu_plugin_from_mp4)
# run: strip target/release/nu_plugin_from_mp4
# - name: Strip binaries (nu_plugin_query_json)
# run: strip target/release/nu_plugin_query_json
# - name: Strip binaries (nu_plugin_s3)
# run: strip target/release/nu_plugin_s3
# - name: Strip binaries (nu_plugin_selector)
# run: strip target/release/nu_plugin_selector
# - name: Strip binaries (nu_plugin_start)
# run: strip target/release/nu_plugin_start
# - name: Strip binaries (nu_plugin_to_bson)
# run: strip target/release/nu_plugin_to_bson
# - name: Strip binaries (nu_plugin_to_sqlite)
# run: strip target/release/nu_plugin_to_sqlite
# - name: Strip binaries (nu_plugin_tree)
# run: strip target/release/nu_plugin_tree
# - name: Strip binaries (nu_plugin_xpath)
# run: strip target/release/nu_plugin_xpath
- name: Create output directory
run: mkdir output
@ -70,6 +125,60 @@ jobs:
command: build
args: --release --all --features=extra
# - name: Strip binaries (nu)
# run: strip target/release/nu
# - name: Strip binaries (nu_plugin_inc)
# run: strip target/release/nu_plugin_inc
# - name: Strip binaries (nu_plugin_match)
# run: strip target/release/nu_plugin_match
# - name: Strip binaries (nu_plugin_textview)
# run: strip target/release/nu_plugin_textview
# - name: Strip binaries (nu_plugin_binaryview)
# run: strip target/release/nu_plugin_binaryview
# - name: Strip binaries (nu_plugin_chart_bar)
# run: strip target/release/nu_plugin_chart_bar
# - name: Strip binaries (nu_plugin_chart_line)
# run: strip target/release/nu_plugin_chart_line
# - name: Strip binaries (nu_plugin_from_bson)
# run: strip target/release/nu_plugin_from_bson
# - name: Strip binaries (nu_plugin_from_sqlite)
# run: strip target/release/nu_plugin_from_sqlite
# - name: Strip binaries (nu_plugin_from_mp4)
# run: strip target/release/nu_plugin_from_mp4
# - name: Strip binaries (nu_plugin_query_json)
# run: strip target/release/nu_plugin_query_json
# - name: Strip binaries (nu_plugin_s3)
# run: strip target/release/nu_plugin_s3
# - name: Strip binaries (nu_plugin_selector)
# run: strip target/release/nu_plugin_selector
# - name: Strip binaries (nu_plugin_start)
# run: strip target/release/nu_plugin_start
# - name: Strip binaries (nu_plugin_to_bson)
# run: strip target/release/nu_plugin_to_bson
# - name: Strip binaries (nu_plugin_to_sqlite)
# run: strip target/release/nu_plugin_to_sqlite
# - name: Strip binaries (nu_plugin_tree)
# run: strip target/release/nu_plugin_tree
# - name: Strip binaries (nu_plugin_xpath)
# run: strip target/release/nu_plugin_xpath
- name: Create output directory
run: mkdir output
@ -114,6 +223,60 @@ jobs:
command: build
args: --release --all --features=extra
# - name: Strip binaries (nu.exe)
# run: strip target/release/nu.exe
# - name: Strip binaries (nu_plugin_inc.exe)
# run: strip target/release/nu_plugin_inc.exe
# - name: Strip binaries (nu_plugin_match.exe)
# run: strip target/release/nu_plugin_match.exe
# - name: Strip binaries (nu_plugin_textview.exe)
# run: strip target/release/nu_plugin_textview.exe
# - name: Strip binaries (nu_plugin_binaryview.exe)
# run: strip target/release/nu_plugin_binaryview.exe
# - name: Strip binaries (nu_plugin_chart_bar.exe)
# run: strip target/release/nu_plugin_chart_bar.exe
# - name: Strip binaries (nu_plugin_chart_line.exe)
# run: strip target/release/nu_plugin_chart_line.exe
# - name: Strip binaries (nu_plugin_from_bson.exe)
# run: strip target/release/nu_plugin_from_bson.exe
# - name: Strip binaries (nu_plugin_from_sqlite.exe)
# run: strip target/release/nu_plugin_from_sqlite.exe
# - name: Strip binaries (nu_plugin_from_mp4.exe)
# run: strip target/release/nu_plugin_from_mp4.exe
# - name: Strip binaries (nu_plugin_query_json.exe)
# run: strip target/release/nu_plugin_query_json.exe
# - name: Strip binaries (nu_plugin_s3.exe)
# run: strip target/release/nu_plugin_s3.exe
# - name: Strip binaries (nu_plugin_selector.exe)
# run: strip target/release/nu_plugin_selector.exe
# - name: Strip binaries (nu_plugin_start.exe)
# run: strip target/release/nu_plugin_start.exe
# - name: Strip binaries (nu_plugin_to_bson.exe)
# run: strip target/release/nu_plugin_to_bson.exe
# - name: Strip binaries (nu_plugin_to_sqlite.exe)
# run: strip target/release/nu_plugin_to_sqlite.exe
# - name: Strip binaries (nu_plugin_tree.exe)
# run: strip target/release/nu_plugin_tree.exe
# - name: Strip binaries (nu_plugin_xpath.exe)
# run: strip target/release/nu_plugin_xpath.exe
- name: Create output directory
run: mkdir output
@ -274,7 +437,7 @@ jobs:
with:
name: windows-installer
path: ./
- name: Upload Windows installer
uses: actions/upload-release-asset@v1
env:
@ -283,4 +446,4 @@ jobs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./nushell-windows.msi
asset_name: ${{ steps.info.outputs.windowsdir }}.msi
asset_content_type: applictaion/x-msi
asset_content_type: application/x-msi

28
.github/workflows/stale.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: 'Close stale issues and PRs'
#on: [workflow_dispatch]
on:
schedule:
- cron: '30 1 * * *'
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
with:
#debug-only: true
ascending: true
operations-per-run: 520
enable-statistics: true
repo-token: ${{ secrets.GITHUB_TOKEN }}
close-issue-message: 'This issue has been marked stale for more than 100000 days without activity. Closing this issue, but if you find that the issue is still valid, please reopen.'
close-pr-message: 'This PR has been marked stale for more than 100 days without activity. Closing this PR, but if you are still working on it, please reopen.'
days-before-issue-stale: 90
days-before-pr-stale: 45
days-before-issue-close: 100000
days-before-pr-close: 100
exempt-issue-labels: 'exempt,keep'

18
.github/workflows/winget-submission.yml vendored Normal file
View File

@ -0,0 +1,18 @@
name: Submit Nushell package to Windows Package Manager Community Repository
on:
release:
types: [published]
jobs:
winget:
name: Publish winget package
runs-on: windows-latest
steps:
- name: Submit package to Windows Package Manager Community Repository
run: |
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows.msi' | Select -ExpandProperty browser_download_url -First 1
.\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}

2
.gitpod.Dockerfile vendored
View File

@ -2,7 +2,7 @@ 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
ENV TRIGGER_REBUILD 2
USER gitpod

View File

@ -2,7 +2,7 @@
Welcome to nushell!
*Note: for a more complete guide see [The nu contributor book](https://github.com/nushell/contributor-book)*
*Note: for a more complete guide see [The nu contributor book](https://www.nushell.sh/contributor-book/)*
For speedy contributions open it in Gitpod, nu will be pre-installed with the latest build in a VSCode like editor all from your browser.

4635
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.26.0"
version = "0.41.0"
[workspace]
members = ["crates/*/"]
@ -18,111 +18,125 @@ members = ["crates/*/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-cli = {version = "0.26.0", path = "./crates/nu-cli"}
nu-command = {version = "0.26.0", path = "./crates/nu-command"}
nu-data = {version = "0.26.0", path = "./crates/nu-data"}
nu-engine = {version = "0.26.0", path = "./crates/nu-engine"}
nu-errors = {version = "0.26.0", path = "./crates/nu-errors"}
nu-parser = {version = "0.26.0", path = "./crates/nu-parser"}
nu-plugin = {version = "0.26.0", path = "./crates/nu-plugin"}
nu-protocol = {version = "0.26.0", path = "./crates/nu-protocol"}
nu-source = {version = "0.26.0", path = "./crates/nu-source"}
nu-value-ext = {version = "0.26.0", path = "./crates/nu-value-ext"}
nu-cli = { version = "0.41.0", path="./crates/nu-cli", default-features=false }
nu-command = { version = "0.41.0", path="./crates/nu-command" }
nu-completion = { version = "0.41.0", path="./crates/nu-completion" }
nu-data = { version = "0.41.0", path="./crates/nu-data" }
nu-engine = { version = "0.41.0", path="./crates/nu-engine" }
nu-errors = { version = "0.41.0", path="./crates/nu-errors" }
nu-parser = { version = "0.41.0", path="./crates/nu-parser" }
nu-path = { version = "0.41.0", path="./crates/nu-path" }
nu-plugin = { version = "0.41.0", path="./crates/nu-plugin" }
nu-protocol = { version = "0.41.0", path="./crates/nu-protocol" }
nu-source = { version = "0.41.0", path="./crates/nu-source" }
nu-value-ext = { version = "0.41.0", path="./crates/nu-value-ext" }
nu_plugin_binaryview = {version = "0.26.0", path = "./crates/nu_plugin_binaryview", optional = true}
nu_plugin_chart = {version = "0.26.0", path = "./crates/nu_plugin_chart", optional = true}
nu_plugin_fetch = {version = "0.26.0", path = "./crates/nu_plugin_fetch", optional = true}
nu_plugin_from_bson = {version = "0.26.0", path = "./crates/nu_plugin_from_bson", optional = true}
nu_plugin_from_sqlite = {version = "0.26.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
nu_plugin_inc = {version = "0.26.0", path = "./crates/nu_plugin_inc", optional = true}
nu_plugin_match = {version = "0.26.0", path = "./crates/nu_plugin_match", optional = true}
nu_plugin_post = {version = "0.26.0", path = "./crates/nu_plugin_post", optional = true}
nu_plugin_ps = {version = "0.26.0", path = "./crates/nu_plugin_ps", optional = true}
nu_plugin_s3 = {version = "0.26.0", path = "./crates/nu_plugin_s3", optional = true}
nu_plugin_selector = {version = "0.26.0", path = "./crates/nu_plugin_selector", optional = true}
nu_plugin_start = {version = "0.26.0", path = "./crates/nu_plugin_start", optional = true}
nu_plugin_sys = {version = "0.26.0", path = "./crates/nu_plugin_sys", optional = true}
nu_plugin_textview = {version = "0.26.0", path = "./crates/nu_plugin_textview", optional = true}
nu_plugin_to_bson = {version = "0.26.0", path = "./crates/nu_plugin_to_bson", optional = true}
nu_plugin_to_sqlite = {version = "0.26.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
nu_plugin_tree = {version = "0.26.0", path = "./crates/nu_plugin_tree", optional = true}
nu_plugin_xpath = {version = "0.26.0", path = "./crates/nu_plugin_xpath", optional = true}
nu_plugin_binaryview = { version = "0.41.0", path="./crates/nu_plugin_binaryview", optional=true }
nu_plugin_chart = { version = "0.41.0", path="./crates/nu_plugin_chart", optional=true }
nu_plugin_from_bson = { version = "0.41.0", path="./crates/nu_plugin_from_bson", optional=true }
nu_plugin_from_sqlite = { version = "0.41.0", path="./crates/nu_plugin_from_sqlite", optional=true }
nu_plugin_inc = { version = "0.41.0", path="./crates/nu_plugin_inc", optional=true }
nu_plugin_match = { version = "0.41.0", path="./crates/nu_plugin_match", optional=true }
nu_plugin_query_json = { version = "0.41.0", path="./crates/nu_plugin_query_json", optional=true }
nu_plugin_s3 = { version = "0.41.0", path="./crates/nu_plugin_s3", optional=true }
nu_plugin_selector = { version = "0.41.0", path="./crates/nu_plugin_selector", optional=true }
nu_plugin_start = { version = "0.41.0", path="./crates/nu_plugin_start", optional=true }
nu_plugin_textview = { version = "0.41.0", path="./crates/nu_plugin_textview", optional=true }
nu_plugin_to_bson = { version = "0.41.0", path="./crates/nu_plugin_to_bson", optional=true }
nu_plugin_to_sqlite = { version = "0.41.0", path="./crates/nu_plugin_to_sqlite", optional=true }
nu_plugin_tree = { version = "0.41.0", path="./crates/nu_plugin_tree", optional=true }
nu_plugin_xpath = { version = "0.41.0", path="./crates/nu_plugin_xpath", optional=true }
# Required to bootstrap the main binary
clap = "2.33.3"
ctrlc = {version = "3.1.6", optional = true}
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
ctrlc = { version="3.1.7", optional=true }
futures = { version="0.3.12", features=["compat", "io-compat"] }
itertools = "0.10.0"
log = "0.4.11"
pretty_env_logger = "0.4.0"
[dev-dependencies]
dunce = "1.0.1"
nu-test-support = {version = "0.26.0", path = "./crates/nu-test-support"}
nu-test-support = { version = "0.41.0", path="./crates/nu-test-support" }
serial_test = "0.5.1"
hamcrest2 = "0.3.0"
rstest = "0.10.0"
[build-dependencies]
[features]
fetch-support = ["nu-command/fetch", "nu-command/post"]
sys-support = ["nu-command/sys", "nu-command/ps"]
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"]
term-support = ["nu-command/term"]
uuid-support = ["nu-command/uuid_crate"]
which-support = ["nu-command/which", "nu-engine/which"]
default = [
"sys",
"ps",
"textview",
"inc",
"directories-support",
"ctrlc-support",
"which-support",
"ptree-support",
"term-support",
"uuid-support",
"rustyline-support",
"match",
"post",
"fetch",
"zip-support",
"nu-cli/shadow-rs",
"sys-support",
"ctrlc-support",
"which-support",
"term-support",
"rustyline-support",
"match",
"fetch-support",
"zip-support",
"dataframe",
]
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3", "chart", "xpath", "selector"]
stable = ["default"]
extra = [
"default",
"binaryview",
"inc",
"tree",
"textview",
"trash-support",
"uuid-support",
"start",
"bson",
"sqlite",
"s3",
"chart",
"xpath",
"selector",
"query-json",
]
wasi = ["inc", "match", "directories-support", "ptree-support", "match", "tree", "rustyline-support"]
trace = ["nu-parser/trace"]
wasi = ["inc", "match", "match", "tree", "rustyline-support"]
# 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"]
query-json = ["nu_plugin_query_json"]
s3 = ["nu_plugin_s3"]
selector = ["nu_plugin_selector"]
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
start = ["nu_plugin_start"]
trash-support = ["nu-cli/trash-support", "nu-command/trash-support"]
trash-support = [
"nu-command/trash-support",
"nu-engine/trash-support",
]
tree = ["nu_plugin_tree"]
xpath = ["nu_plugin_xpath"]
zip-support = ["nu-command/zip"]
#dataframe feature for nushell
dataframe = [
"nu-engine/dataframe",
"nu-protocol/dataframe",
"nu-command/dataframe",
"nu-value-ext/dataframe",
"nu-data/dataframe",
"nu_plugin_to_bson/dataframe",
]
[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
opt-level = "s" # Optimize for size.
# Core plugins that ship with `cargo install nu` by default
# Currently, Cargo limits us to installing only one binary
@ -137,31 +151,11 @@ name = "nu_plugin_core_inc"
path = "src/plugins/nu_plugin_core_inc.rs"
required-features = ["inc"]
[[bin]]
name = "nu_plugin_core_ps"
path = "src/plugins/nu_plugin_core_ps.rs"
required-features = ["ps"]
[[bin]]
name = "nu_plugin_core_sys"
path = "src/plugins/nu_plugin_core_sys.rs"
required-features = ["sys"]
[[bin]]
name = "nu_plugin_core_fetch"
path = "src/plugins/nu_plugin_core_fetch.rs"
required-features = ["fetch"]
[[bin]]
name = "nu_plugin_core_match"
path = "src/plugins/nu_plugin_core_match.rs"
required-features = ["match"]
[[bin]]
name = "nu_plugin_core_post"
path = "src/plugins/nu_plugin_core_post.rs"
required-features = ["post"]
# Extra plugins
[[bin]]
@ -174,6 +168,11 @@ name = "nu_plugin_extra_tree"
path = "src/plugins/nu_plugin_extra_tree.rs"
required-features = ["tree"]
[[bin]]
name = "nu_plugin_extra_query_json"
path = "src/plugins/nu_plugin_extra_query_json.rs"
required-features = ["query-json"]
[[bin]]
name = "nu_plugin_extra_start"
path = "src/plugins/nu_plugin_extra_start.rs"

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 - 2020 Yehuda Katz, Jonathan Turner
Copyright (c) 2019 - 2021 Nushell Project
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

162
README.md
View File

@ -1,11 +1,12 @@
# 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)
## Nushell
@ -44,78 +45,37 @@ 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/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.47 or later)** version of the compiler.
To build Nu, you will need to use the **latest stable (1.51 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`):
```bash
```shell
cargo install nu
```
To install Nu via the [Windows Package Manager](https://aka.ms/winget-cli):
```shell
winget install nushell
```
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
```shell
cargo build --workspace --features=extra
```
### Docker
#### Quickstart
Want to try Nu right away? Execute the following to get started.
```bash
docker run -it quay.io/nushell/nu:latest
```
#### Guide
If you want to pull a pre-built container, you can browse tags for the [nushell organization](https://quay.io/organization/nushell)
on Quay.io. Pulling a container would come down to:
```bash
docker pull quay.io/nushell/nu
docker pull quay.io/nushell/nu-base
```
Both "nu-base" and "nu" provide the nu binary, however nu-base also includes the source code at `/code`
in the container and all dependencies.
Optionally, you can also build the containers locally using the [dockerfiles provided](docker):
To build the base image:
```bash
docker build -f docker/Dockerfile.nu-base -t nushell/nu-base .
```
And then to build the smaller container (using a Multistage build):
```bash
docker build -f docker/Dockerfile -t nushell/nu .
```
Either way, you can run either container as follows:
```bash
docker run -it nushell/nu-base
docker run -it nushell/nu
/> exit
```
The second container is a bit smaller if the size is important to you.
### Packaging status
[![Packaging status](https://repology.org/badge/vertical-allrepos/nushell.svg)](https://repology.org/project/nushell/versions)
@ -135,13 +95,13 @@ These values can be piped through a series of steps, in a series of commands cal
In Unix, it's common to pipe between commands to split up a sophisticated command over multiple steps.
Nu takes this a step further and builds heavily on the idea of _pipelines_.
Just as the Unix philosophy, Nu allows commands to output from stdout and read from stdin.
Just as the Unix philosophy, Nu allows commands to output to stdout and read from stdin.
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 (e.g., `ls`)
- Commands that filter a stream (eg, `where type == "Dir"`)
- Commands that consume the output of the pipeline (e.g., `autoview`)
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
@ -170,7 +130,7 @@ We could have also written the above:
```
Being able to use the same commands and compose them differently is an important philosophy in Nu.
For example, we could use the built-in `ps` command as well to get a list of the running processes, using the same `where` as above.
For example, we could use the built-in `ps` command to get a list of the running processes, using the same `where` as above.
```shell
> ps | where cpu > 0
@ -187,7 +147,7 @@ For example, we could use the built-in `ps` command as well to get a list of the
### Opening files
Nu can load file and URL contents as raw text or as structured data (if it recognizes the format).
Nu can load file and URL contents as raw text or structured data (if it recognizes the format).
For example, you can load a .toml file as structured data and explore it:
```shell
@ -219,7 +179,7 @@ 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.21.0
version │ 0.32.0
───────────────┴────────────────────────────────────
```
@ -227,11 +187,9 @@ Finally, we can use commands outside of Nu once we have the data we want:
```shell
> open Cargo.toml | get package.version
0.21.0
0.32.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/configuration.html).
@ -239,16 +197,16 @@ Nu has early support for configuring the shell. You can refer to the book for a
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
```
### Shells
Nu will work inside of a single directory and allow you to navigate around your filesystem by default.
Nu also offers a way of adding additional working directories that you can jump between, allowing you to work in multiple directories at the same time.
Nu also offers a way of adding additional working directories that you can jump between, allowing you to work in multiple directories simultaneously.
To do so, use the `enter` command, which will allow you create a new "shell" and enter it at the specified path.
To do so, use the `enter` command, which will allow you to create a new "shell" and enter it at the specified path.
You can toggle between this new shell and the original shell with the `p` (for previous) and `n` (for next), allowing you to navigate around a ring buffer of shells.
Once you're done with a shell, you can `exit` it and remove it from the ring buffer.
@ -262,7 +220,7 @@ This allows you to extend nu for your needs.
There are a few examples in the `plugins` directory.
Plugins are binaries that are available in your path and follow a `nu_plugin_*` naming convention.
These binaries interact with nu via a simple JSON-RPC protocol where the command identifies itself and passes along its configuration, which then makes it available for use.
These binaries interact with nu via a simple JSON-RPC protocol where the command identifies itself and passes along its configuration, making it available for use.
If the plugin is a filter, data streams to it one element at a time, and it can stream data back in return via stdin/stdout.
If the plugin is a sink, it is given the full vector of final data and is given free reign over stdin/stdout to use as it pleases.
@ -270,49 +228,61 @@ 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 consistent first-class 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 | | | Aliases allow for shortening large commands, while passing flags |
| 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 and scoped environment 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 updated to latest release, including usage examples |
| Paging | | X | | | | Textview has paging, but we'd like paging for tables |
| Functions | | | X | | | Functions and aliases are supported |
| Variables | | | X | | | Nu supports variables and environment variables |
| Completions | | | X | | | Completions for filepaths |
| Type-checking | | | X | | | Commands check basic types, but input/output isn't checked |
## Current Roadmap
## Officially Supported By
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).
Please submit an issue or PR to be added to this list.
### Integrations
- [zoxide](https://github.com/ajeetdsouza/zoxide)
- [starship](https://github.com/starship/starship)
### Mentions
- [The Python Launcher for Unix](https://github.com/brettcannon/python-launcher#how-do-i-get-a-table-of-python-executables-in-nushell)
## 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.

13
crates/README.md Normal file
View File

@ -0,0 +1,13 @@
# Nushell core libraries and plugins
These sub-crates form both the foundation for Nu and a set of plugins which extend Nu with additional functionality.
Foundational libraries are split into two kinds of crates:
* Core crates - those crates that work together to build the Nushell language engine
* Support crates - a set of crates that support the engine with additional features like JSON support, ANSI support, and more.
Plugins are likewise also split into two types:
* Core plugins - plugins that provide part of the default experience of Nu, including access to the system properties, processes, and web-connectivity features.
* Extra plugins - these plugins run a wide range of different capabilities like working with different file types, charting, viewing binary data, and more.

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

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

View File

@ -0,0 +1,39 @@
[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.41.0"
[lib]
doctest = false
# name = "nu-ansi-term"
[features]
derive_serde_style = ["serde"]
[dependencies]
overload = "0.1.1"
serde = { version="1.0.90", features=["derive"], optional=true }
# [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,37 @@
use nu_ansi_term::{build_all_gradient_text, Color, Gradient, Rgb, TargetGround};
fn main() {
let text = "lorem ipsum quia dolor sit amet, consectetur, adipisci velit";
// a gradient from hex colors
let start = Rgb::from_hex(0x40c9ff);
let end = Rgb::from_hex(0xe81cff);
let grad0 = Gradient::new(start, end);
// a gradient from color::rgb()
let start = Color::Rgb(64, 201, 255);
let end = Color::Rgb(232, 28, 255);
let gradient = Gradient::from_color_rgb(start, end);
// a slightly different gradient
let start2 = Color::Rgb(128, 64, 255);
let end2 = Color::Rgb(0, 28, 255);
let gradient2 = Gradient::from_color_rgb(start2, end2);
// reverse the gradient
let gradient3 = gradient.reverse();
let build_fg = gradient.build(text, TargetGround::Foreground);
println!("{}", build_fg);
let build_bg = gradient.build(text, TargetGround::Background);
println!("{}", build_bg);
let bgt = build_all_gradient_text(text, gradient, gradient2);
println!("{}", bgt);
let bgt2 = build_all_gradient_text(text, gradient, gradient3);
println!("{}", bgt2);
println!(
"{}",
grad0.build("nushell is awesome", TargetGround::Foreground)
);
}

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,405 @@
#![allow(missing_docs)]
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::Empty => {
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.
Empty,
}
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 Empty;
}
// 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() => Empty);
test!(uppercase: Green.normal(); Green.bold() => ExtraStyles(style().bold()));
test!(lowercase: Green.bold(); Green.normal() => Reset);
test!(nothing2: Green.bold(); Green.bold() => Empty);
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 `AnsiGenericStrings`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())?,
Empty => { /* 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 = AnsiStrings(&[one, two]).to_string();
assert_eq!(output, "onetwo");
}
}

View File

@ -0,0 +1,105 @@
use crate::{rgb::Rgb, Color};
/// Linear color gradient between two color stops
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Gradient {
/// Start Color of Gradient
pub start: Rgb,
/// End Color of Gradient
pub end: Rgb,
}
impl Gradient {
/// Creates a new [Gradient] with two [Rgb] colors, `start` and `end`
#[inline]
pub const fn new(start: Rgb, end: Rgb) -> Self {
Self { start, end }
}
pub const fn from_color_rgb(start: Color, end: Color) -> Self {
let start_grad = match start {
Color::Rgb(r, g, b) => Rgb { r, g, b },
_ => Rgb { r: 0, g: 0, b: 0 },
};
let end_grad = match end {
Color::Rgb(r, g, b) => Rgb { r, g, b },
_ => Rgb { r: 0, g: 0, b: 0 },
};
Self {
start: start_grad,
end: end_grad,
}
}
/// Computes the [Rgb] color between `start` and `end` for `t`
pub fn at(&self, t: f32) -> Rgb {
self.start.lerp(self.end, t)
}
/// Returns the reverse of `self`
#[inline]
pub const fn reverse(&self) -> Self {
Self::new(self.end, self.start)
}
#[allow(dead_code)]
pub fn build(&self, text: &str, target: TargetGround) -> String {
let delta = 1.0 / text.len() as f32;
let mut result = text.char_indices().fold(String::new(), |mut acc, (i, c)| {
let temp = format!(
"\x1B[{}m{}",
self.at(i as f32 * delta).ansi_color_code(target),
c
);
acc.push_str(&temp);
acc
});
result.push_str("\x1B[0m");
result
}
}
#[allow(dead_code)]
pub fn build_all_gradient_text(text: &str, foreground: Gradient, background: Gradient) -> String {
let delta = 1.0 / text.len() as f32;
let mut result = text.char_indices().fold(String::new(), |mut acc, (i, c)| {
let step = i as f32 * delta;
let temp = format!(
"\x1B[{};{}m{}",
foreground
.at(step)
.ansi_color_code(TargetGround::Foreground),
background
.at(step)
.ansi_color_code(TargetGround::Background),
c
);
acc.push_str(&temp);
acc
});
result.push_str("\x1B[0m");
result
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TargetGround {
Foreground,
Background,
}
impl TargetGround {
#[inline]
pub const fn code(&self) -> u8 {
match self {
Self::Foreground => 30,
Self::Background => 40,
}
}
}
pub trait ANSIColorCode {
fn ansi_color_code(&self, target: TargetGround) -> String;
}

View File

@ -0,0 +1,273 @@
//! 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");
pub 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;
pub mod gradient;
pub use gradient::*;
mod rgb;
pub use rgb::*;

View File

@ -0,0 +1,173 @@
// Code liberally borrowed from here
// https://github.com/navierr/coloriz
use std::ops;
use std::u32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rgb {
/// Red
pub r: u8,
/// Green
pub g: u8,
/// Blue
pub b: u8,
}
impl Rgb {
/// Creates a new [Rgb] color
#[inline]
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b }
}
/// Creates a new [Rgb] color with a hex code
#[inline]
pub const fn from_hex(hex: u32) -> Self {
Self::new((hex >> 16) as u8, (hex >> 8) as u8, hex as u8)
}
pub fn from_hex_string(hex: String) -> Self {
if hex.chars().count() == 8 && hex.starts_with("0x") {
// eprintln!("hex:{:?}", hex);
let (_, value_string) = hex.split_at(2);
// eprintln!("value_string:{:?}", value_string);
let int_val = u64::from_str_radix(value_string, 16);
match int_val {
Ok(num) => Self::new(
((num & 0xff0000) >> 16) as u8,
((num & 0xff00) >> 8) as u8,
(num & 0xff) as u8,
),
// Don't fail, just make the color black
// Should we fail?
_ => Self::new(0, 0, 0),
}
} else {
// Don't fail, just make the color black.
// Should we fail?
Self::new(0, 0, 0)
}
}
/// Creates a new [Rgb] color with three [f32] values
pub fn from_f32(r: f32, g: f32, b: f32) -> Self {
Self::new(
(r.clamp(0.0, 1.0) * 255.0) as u8,
(g.clamp(0.0, 1.0) * 255.0) as u8,
(b.clamp(0.0, 1.0) * 255.0) as u8,
)
}
/// Creates a grayscale [Rgb] color
#[inline]
pub const fn gray(x: u8) -> Self {
Self::new(x, x, x)
}
/// Creates a grayscale [Rgb] color with a [f32] value
pub fn gray_f32(x: f32) -> Self {
Self::from_f32(x, x, x)
}
/// Creates a new [Rgb] color from a [HSL] color
// pub fn from_hsl(hsl: HSL) -> Self {
// if hsl.s == 0.0 {
// return Self::gray_f32(hsl.l);
// }
// let q = if hsl.l < 0.5 {
// hsl.l * (1.0 + hsl.s)
// } else {
// hsl.l + hsl.s - hsl.l * hsl.s
// };
// let p = 2.0 * hsl.l - q;
// let h2c = |t: f32| {
// let t = t.clamp(0.0, 1.0);
// if 6.0 * t < 1.0 {
// p + 6.0 * (q - p) * t
// } else if t < 0.5 {
// q
// } else if 1.0 < 1.5 * t {
// p + 6.0 * (q - p) * (1.0 / 1.5 - t)
// } else {
// p
// }
// };
// Self::from_f32(h2c(hsl.h + 1.0 / 3.0), h2c(hsl.h), h2c(hsl.h - 1.0 / 3.0))
// }
/// Computes the linear interpolation between `self` and `other` for `t`
pub fn lerp(&self, other: Self, t: f32) -> Self {
let t = t.clamp(0.0, 1.0);
self * (1.0 - t) + other * t
}
}
impl From<(u8, u8, u8)> for Rgb {
fn from((r, g, b): (u8, u8, u8)) -> Self {
Self::new(r, g, b)
}
}
impl From<(f32, f32, f32)> for Rgb {
fn from((r, g, b): (f32, f32, f32)) -> Self {
Self::from_f32(r, g, b)
}
}
use crate::ANSIColorCode;
use crate::TargetGround;
impl ANSIColorCode for Rgb {
fn ansi_color_code(&self, target: TargetGround) -> String {
format!("{};2;{};{};{}", target.code() + 8, self.r, self.g, self.b)
}
}
overload::overload!(
(lhs: ?Rgb) + (rhs: ?Rgb) -> Rgb {
Rgb::new(
lhs.r.saturating_add(rhs.r),
lhs.g.saturating_add(rhs.g),
lhs.b.saturating_add(rhs.b)
)
}
);
overload::overload!(
(lhs: ?Rgb) - (rhs: ?Rgb) -> Rgb {
Rgb::new(
lhs.r.saturating_sub(rhs.r),
lhs.g.saturating_sub(rhs.g),
lhs.b.saturating_sub(rhs.b)
)
}
);
overload::overload!(
(lhs: ?Rgb) * (rhs: ?f32) -> Rgb {
Rgb::new(
(lhs.r as f32 * rhs.clamp(0.0, 1.0)) as u8,
(lhs.g as f32 * rhs.clamp(0.0, 1.0)) as u8,
(lhs.b as f32 * rhs.clamp(0.0, 1.0)) as u8
)
}
);
overload::overload!(
(lhs: ?f32) * (rhs: ?Rgb) -> Rgb {
Rgb::new(
(rhs.r as f32 * lhs.clamp(0.0, 1.0)) as u8,
(rhs.g as f32 * lhs.clamp(0.0, 1.0)) as u8,
(rhs.b as f32 * lhs.clamp(0.0, 1.0)) as u8
)
}
);
overload::overload!(
-(rgb: ?Rgb) -> Rgb {
Rgb::new(
255 - rgb.r,
255 - rgb.g,
255 - rgb.b)
}
);

View File

@ -0,0 +1,626 @@
/// 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 Default for Color {
fn default() -> Self {
Color::White
}
}
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 {
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), 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,135 +1,43 @@
[package]
authors = ["The Nu Project Contributors"]
build = "build.rs"
description = "CLI for nushell"
edition = "2018"
license = "MIT"
name = "nu-cli"
version = "0.26.0"
version = "0.41.0"
build = "build.rs"
[lib]
doctest = false
[dependencies]
nu-command = { version = "0.26.0", path = "../nu-command" }
nu-data = { version = "0.26.0", path = "../nu-data" }
nu-engine = { version = "0.26.0", path = "../nu-engine" }
nu-errors = { version = "0.26.0", path = "../nu-errors" }
nu-json = { version = "0.26.0", path = "../nu-json" }
nu-parser = { version = "0.26.0", path = "../nu-parser" }
nu-plugin = { version = "0.26.0", path = "../nu-plugin" }
nu-protocol = { version = "0.26.0", path = "../nu-protocol" }
nu-source = { version = "0.26.0", path = "../nu-source" }
nu-stream = { version = "0.26.0", path = "../nu-stream" }
nu-table = { version = "0.26.0", path = "../nu-table" }
nu-test-support = { version = "0.26.0", path = "../nu-test-support" }
nu-value-ext = { version = "0.26.0", path = "../nu-value-ext" }
nu-completion = { version = "0.41.0", path="../nu-completion" }
nu-command = { version = "0.41.0", path="../nu-command" }
nu-data = { version = "0.41.0", path="../nu-data" }
nu-engine = { version = "0.41.0", path="../nu-engine" }
nu-errors = { version = "0.41.0", path="../nu-errors" }
nu-parser = { version = "0.41.0", path="../nu-parser" }
nu-protocol = { version = "0.41.0", path="../nu-protocol" }
nu-source = { version = "0.41.0", path="../nu-source" }
nu-stream = { version = "0.41.0", path="../nu-stream" }
nu-ansi-term = { version = "0.41.0", path="../nu-ansi-term" }
nu-path = { version = "0.41.0", path="../nu-path" }
Inflector = "0.11"
ansi_term = "0.12.1"
arboard = { version = "1.1.0", optional = true }
async-recursion = "0.3.1"
async-trait = "0.1.40"
base64 = "0.13.0"
bigdecimal = { version = "0.2.0", features = ["serde"] }
byte-unit = "4.0.9"
bytes = "0.5.6"
calamine = "0.16.1"
chrono = { version = "0.4.15", features = ["serde"] }
chrono-tz = "0.5.3"
clap = "2.33.3"
codespan-reporting = "0.11.0"
csv = "1.1.3"
ctrlc = { version = "3.1.6", optional = true }
derive-new = "0.5.8"
directories-next = { version = "2.0.0", optional = true }
dirs-next = { version = "2.0.0", optional = true }
dtparse = "1.2.0"
dunce = "1.0.1"
eml-parser = "0.1.0"
encoding_rs = "0.8.24"
filesize = "0.2.0"
fs_extra = "1.2.0"
futures = { version = "0.3.5", features = ["compat", "io-compat"] }
futures-util = "0.3.8"
futures_codec = "0.4.1"
getset = "0.1.1"
glob = "0.3.0"
htmlescape = "0.3.1"
ical = "0.7.0"
ichwh = { version = "0.3.4", optional = true }
indexmap = { version = "1.6.0", features = ["serde-1"] }
itertools = "0.10.0"
lazy_static = "1.*"
log = "0.4.11"
meval = "0.2.0"
num-bigint = { version = "0.3.0", features = ["serde"] }
num-format = { version = "0.4.0", features = ["with-num-bigint"] }
num-traits = "0.2.12"
parking_lot = "0.11.0"
pin-utils = "0.1.0"
pretty-hex = "0.2.0"
ptree = { version = "0.3.0", optional = true }
query_interface = "0.3.5"
quick-xml = "0.20.0"
rand = "0.7.3"
rayon = "1.4.0"
regex = "1.3.9"
roxmltree = "0.14.0"
rust-embed = "5.8.0"
rustyline = { version = "6.3.0", optional = true }
serde = { version = "1.0.115", features = ["derive"] }
serde_bytes = "0.11.5"
serde_ini = "0.2.0"
serde_json = "1.0.57"
serde_urlencoded = "0.7.0"
serde_yaml = "0.8.13"
sha2 = "0.9.1"
shellexpand = "2.0.0"
indexmap ="1.6.1"
log = "0.4.14"
pretty_env_logger = "0.4.0"
strip-ansi-escapes = "0.1.0"
sxd-document = "0.3.2"
sxd-xpath = "0.4.2"
tempfile = "3.1.0"
term = { version = "0.6.1", optional = true }
term_size = "0.3.2"
termcolor = "1.1.0"
titlecase = "1.0"
toml = "0.5.6"
trash = { version = "1.2.0", optional = true }
unicode-segmentation = "1.6.0"
url = "2.1.1"
uuid_crate = { package = "uuid", version = "0.8.1", features = ["v4"], optional = true }
which = { version = "4.0.2", optional = true }
zip = { version = "0.5.7", optional = true }
shadow-rs = { version = "0.5", default-features = false, optional = true }
[target.'cfg(unix)'.dependencies]
umask = "1.0.0"
users = "0.10.0"
# TODO this will be possible with new dependency resolver
# (currently on nightly behind -Zfeatures=itarget):
# https://github.com/rust-lang/cargo/issues/7914
#[target.'cfg(not(windows))'.dependencies]
#num-format = {version = "0.4", features = ["with-system-locale"]}
[dependencies.rusqlite]
features = ["bundled", "blob"]
optional = true
version = "0.24.2"
rustyline = { version="9.0.0", optional=true }
ctrlc = { version="3.1.7", optional=true }
shadow-rs = { version = "0.8.1", default-features = false, optional = true }
serde = { version="1.0.123", features=["derive"] }
serde_yaml = "0.8.16"
lazy_static = "1.4.0"
[build-dependencies]
shadow-rs = "0.5"
[dev-dependencies]
quickcheck = "0.9.2"
quickcheck_macros = "0.9.1"
shadow-rs = "0.8.1"
[features]
default = ["shadow-rs"]
clipboard-cli = ["arboard"]
rustyline-support = ["rustyline", "nu-engine/rustyline-support"]
stable = []
trash-support = ["trash"]
dirs = ["dirs-next"]
directories = ["directories-next"]

4
crates/nu-cli/README.md Normal file
View File

@ -0,0 +1,4 @@
# nu-cli
This crate provides the fundamental needs when creating the Nushell interactive REPL. In it, you'll find features for interacting with the line editor (the piece which writes the prompt and takes input from the user), keybindings, handlers for the commandline arguments passed to the REPL as it starts up, and more.

Binary file not shown.

638
crates/nu-cli/src/app.rs Normal file
View File

@ -0,0 +1,638 @@
mod logger;
mod options;
mod options_parser;
pub mod stopwatch;
use self::stopwatch::Stopwatch;
use lazy_static::lazy_static;
use nu_command::{commands::NuSignature as Nu, utils::test_bins as binaries};
use nu_engine::{get_full_help, EvaluationContext};
use nu_errors::ShellError;
use nu_protocol::hir::{Call, Expression, SpannedExpression, Synthetic};
use nu_protocol::{Primitive, UntaggedValue};
use nu_source::{Span, Tag};
use nu_stream::InputStream;
pub use options::{CliOptions, NuScript, Options};
use options_parser::{NuParser, OptionsParser};
use std::sync::Mutex;
lazy_static! {
pub static ref STOPWATCH: Mutex<Stopwatch> = {
let mut sw = Stopwatch::default();
sw.start();
sw.stop();
Mutex::new(sw)
};
}
pub struct App {
parser: Box<dyn OptionsParser>,
pub options: Options,
}
impl App {
pub fn new(parser: Box<dyn OptionsParser>, options: Options) -> Self {
Self { parser, options }
}
pub fn run(args: &[String]) -> Result<(), ShellError> {
let nu = Box::new(NuParser::new());
let options = Options::default();
let ui = App::new(nu, options);
ui.main(args)
}
pub fn main(&self, argv: &[String]) -> Result<(), ShellError> {
if self.perf() {
// start the stopwatch running
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.start();
}
let argv = quote_positionals(argv).join(" ");
if let Err(cause) = self.parse(&argv) {
self.parser
.context()
.host()
.lock()
.print_err(cause, &nu_source::Text::from(argv));
std::process::exit(1);
}
if self.help() {
let context = self.parser.context();
let stream = nu_stream::OutputStream::one(
UntaggedValue::string(get_full_help(&Nu, &context.scope))
.into_value(nu_source::Tag::unknown()),
);
consume(context, stream)?;
if self.perf() {
// stop the stopwatch since we're exiting
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.stop();
eprintln!(
"help {:?}",
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.elapsed()
);
}
std::process::exit(0);
}
if self.version() {
let context = self.parser.context();
let stream = nu_command::commands::version(nu_engine::CommandArgs {
context: context.clone(),
call_info: nu_engine::UnevaluatedCallInfo {
args: Call::new(
Box::new(SpannedExpression::new(
Expression::Synthetic(Synthetic::String("version".to_string())),
Span::unknown(),
)),
Span::unknown(),
),
name_tag: Tag::unknown(),
},
input: InputStream::empty(),
})?;
let stream = {
let command = context
.get_command("pivot")
.expect("could not find version command");
context.run_command(
command,
Tag::unknown(),
Call::new(
Box::new(SpannedExpression::new(
Expression::Synthetic(Synthetic::String("pivot".to_string())),
Span::unknown(),
)),
Span::unknown(),
),
stream,
)?
};
consume(context, stream)?;
if self.perf() {
// stop the stopwatch since we're exiting
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.stop();
eprintln!(
"version {:?}",
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.elapsed()
);
}
std::process::exit(0);
}
if let Some(bin) = self.testbin() {
match bin.as_deref() {
Ok("echo_env") => binaries::echo_env(),
Ok("cococo") => binaries::cococo(),
Ok("meow") => binaries::meow(),
Ok("iecho") => binaries::iecho(),
Ok("fail") => binaries::fail(),
Ok("nonu") => binaries::nonu(),
Ok("chop") => binaries::chop(),
Ok("repeater") => binaries::repeater(),
_ => unreachable!(),
}
return Ok(());
}
let mut opts = CliOptions::new();
opts.config = self.config().map(std::ffi::OsString::from);
opts.stdin = self.takes_stdin();
opts.save_history = self.save_history();
opts.perf = self.perf();
use logger::{configure, debug_filters, logger, trace_filters};
logger(|builder| {
configure(self, builder)?;
trace_filters(self, builder)?;
debug_filters(self, builder)?;
Ok(())
})?;
if self.perf() {
// start a new split
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.start()
}
if let Some(commands) = self.commands() {
let commands = commands?;
let script = NuScript::code(&commands)?;
opts.scripts = vec![script];
let context = crate::create_default_context(false)?;
return crate::run_script_file(context, opts);
}
if self.perf() {
// start a new spit
eprintln!(
"commands using -c at launch: {:?}",
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.elapsed_split()
);
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.start();
}
if let Some(scripts) = self.scripts() {
let mut source_files = vec![];
for script in scripts {
let script_name = script?;
let path = std::ffi::OsString::from(&script_name);
match NuScript::source_file(path.as_os_str()) {
Ok(file) => source_files.push(file),
Err(_) => {
eprintln!("File not found: {}", script_name);
return Ok(());
}
}
}
for file in source_files {
let mut opts = opts.clone();
opts.scripts = vec![file];
let context = crate::create_default_context(false)?;
crate::run_script_file(context, opts)?;
}
return Ok(());
}
if self.perf() {
// start a new split
eprintln!(
"script file(s) passed in: {:?}",
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.elapsed_split()
);
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.start();
}
let context = crate::create_default_context(true)?;
if !self.skip_plugins() {
let _ = crate::register_plugins(&context);
}
if self.perf() {
// start a new split
eprintln!(
"plugins registered: {:?}",
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.elapsed_split()
);
}
#[cfg(feature = "rustyline-support")]
{
crate::cli(context, opts)?;
}
#[cfg(not(feature = "rustyline-support"))]
{
println!("Nushell needs the 'rustyline-support' feature for CLI support");
}
Ok(())
}
pub fn commands(&self) -> Option<Result<String, ShellError>> {
self.options.get("commands").map(|v| match v.value {
UntaggedValue::Error(err) => Err(err),
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
})
}
pub fn perf(&self) -> bool {
self.options
.get("perf")
.map(|v| matches!(v.as_bool(), Ok(true)))
.unwrap_or(false)
}
pub fn help(&self) -> bool {
self.options
.get("help")
.map(|v| matches!(v.as_bool(), Ok(true)))
.unwrap_or(false)
}
pub fn version(&self) -> bool {
self.options
.get("version")
.map(|v| matches!(v.as_bool(), Ok(true)))
.unwrap_or(false)
}
pub fn scripts(&self) -> Option<Vec<Result<String, ShellError>>> {
self.options.get("args").map(|v| {
v.table_entries()
.map(|v| match &v.value {
UntaggedValue::Error(err) => Err(err.clone()),
UntaggedValue::Primitive(Primitive::FilePath(path)) => {
Ok(path.display().to_string())
}
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name.clone()),
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
})
.collect()
})
}
pub fn takes_stdin(&self) -> bool {
self.options
.get("stdin")
.map(|v| matches!(v.as_bool(), Ok(true)))
.unwrap_or(false)
}
pub fn config(&self) -> Option<String> {
self.options
.get("config-file")
.map(|v| v.as_string().expect("not a string"))
}
pub fn develop(&self) -> Option<Vec<Result<String, ShellError>>> {
self.options.get("develop").map(|v| {
let mut values = vec![];
match v.value {
UntaggedValue::Error(err) => values.push(Err(err)),
UntaggedValue::Primitive(Primitive::String(filters)) => {
values.extend(filters.split(',').map(|filter| Ok(filter.to_string())));
}
_ => values.push(Err(ShellError::untagged_runtime_error(
"Unsupported option",
))),
};
values
})
}
pub fn debug(&self) -> Option<Vec<Result<String, ShellError>>> {
self.options.get("debug").map(|v| {
let mut values = vec![];
match v.value {
UntaggedValue::Error(err) => values.push(Err(err)),
UntaggedValue::Primitive(Primitive::String(filters)) => {
values.extend(filters.split(',').map(|filter| Ok(filter.to_string())));
}
_ => values.push(Err(ShellError::untagged_runtime_error(
"Unsupported option",
))),
};
values
})
}
pub fn loglevel(&self) -> Option<Result<String, ShellError>> {
self.options.get("loglevel").map(|v| match v.value {
UntaggedValue::Error(err) => Err(err),
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
})
}
pub fn testbin(&self) -> Option<Result<String, ShellError>> {
self.options.get("testbin").map(|v| match v.value {
UntaggedValue::Error(err) => Err(err),
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
})
}
pub fn skip_plugins(&self) -> bool {
self.options
.get("skip-plugins")
.map(|v| matches!(v.as_bool(), Ok(true)))
.unwrap_or(false)
}
pub fn save_history(&self) -> bool {
self.options
.get("no-history")
.map(|v| !matches!(v.as_bool(), Ok(true)))
.unwrap_or(true)
}
pub fn parse(&self, args: &str) -> Result<(), ShellError> {
self.parser.parse(args).map(|options| {
self.options.swap(&options);
})
}
}
fn quote_positionals(parameters: &[String]) -> Vec<String> {
parameters
.iter()
.cloned()
.map(|arg| {
if arg.contains(' ') {
format!("\"{}\"", arg)
} else {
arg
}
})
.collect::<Vec<_>>()
}
fn consume(context: &EvaluationContext, stream: InputStream) -> Result<(), ShellError> {
let autoview_cmd = context
.get_command("autoview")
.expect("could not find autoview command");
let stream = context.run_command(
autoview_cmd,
Tag::unknown(),
Call::new(
Box::new(SpannedExpression::new(
Expression::Synthetic(Synthetic::String("autoview".to_string())),
Span::unknown(),
)),
Span::unknown(),
),
stream,
)?;
for _ in stream {}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn cli_app() -> App {
let parser = Box::new(NuParser::new());
let options = Options::default();
App::new(parser, options)
}
#[test]
fn default_options() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu")?;
assert!(!ui.version());
assert!(!ui.help());
assert!(!ui.takes_stdin());
assert!(ui.save_history());
assert!(!ui.skip_plugins());
assert_eq!(ui.config(), None);
assert_eq!(ui.loglevel(), None);
assert_eq!(ui.debug(), None);
assert_eq!(ui.develop(), None);
assert_eq!(ui.testbin(), None);
assert_eq!(ui.commands(), None);
assert_eq!(ui.scripts(), None);
Ok(())
}
#[test]
fn reports_errors_on_unsupported_flags() -> Result<(), ShellError> {
let ui = cli_app();
assert!(ui.parse("nu --coonfig-file /path/to/config.toml").is_err());
assert!(ui.config().is_none());
Ok(())
}
#[test]
fn configures_debug_trace_level_with_filters() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu --develop=cli,parser")?;
assert_eq!(ui.develop().unwrap()[0], Ok("cli".to_string()));
assert_eq!(ui.develop().unwrap()[1], Ok("parser".to_string()));
Ok(())
}
#[test]
fn configures_debug_level_with_filters() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu --debug=cli,run")?;
assert_eq!(ui.debug().unwrap()[0], Ok("cli".to_string()));
assert_eq!(ui.debug().unwrap()[1], Ok("run".to_string()));
Ok(())
}
#[test]
fn can_use_loglevels() -> Result<(), ShellError> {
for level in ["error", "warn", "info", "debug", "trace"] {
let ui = cli_app();
let args = format!("nu --loglevel={}", level);
ui.parse(&args)?;
assert_eq!(ui.loglevel().unwrap(), Ok(level.to_string()));
}
let ui = cli_app();
ui.parse("nu --loglevel=nada")?;
assert_eq!(
ui.loglevel().unwrap(),
Err(ShellError::untagged_runtime_error("nada is not supported."))
);
Ok(())
}
#[test]
fn can_be_login() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu -l")?;
let ui = cli_app();
ui.parse("nu --login")?;
Ok(())
}
#[test]
fn can_be_passed_nu_scripts() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu code.nu bootstrap.nu")?;
assert_eq!(ui.scripts().unwrap()[0], Ok("code.nu".into()));
assert_eq!(ui.scripts().unwrap()[1], Ok("bootstrap.nu".into()));
Ok(())
}
#[test]
fn can_use_test_binaries() -> Result<(), ShellError> {
for binarie_name in [
"echo_env", "cococo", "iecho", "fail", "nonu", "chop", "repeater", "meow",
] {
let ui = cli_app();
let args = format!("nu --testbin={}", binarie_name);
ui.parse(&args)?;
assert_eq!(ui.testbin().unwrap(), Ok(binarie_name.to_string()));
}
let ui = cli_app();
ui.parse("nu --testbin=andres")?;
assert_eq!(
ui.testbin().unwrap(),
Err(ShellError::untagged_runtime_error(
"andres is not supported."
))
);
Ok(())
}
#[test]
fn has_version() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu --version")?;
assert!(ui.version());
Ok(())
}
#[test]
fn has_help() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu --help")?;
assert!(ui.help());
Ok(())
}
#[test]
fn can_take_stdin() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu --stdin")?;
assert!(ui.takes_stdin());
Ok(())
}
#[test]
fn can_opt_to_avoid_saving_history() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu --no-history")?;
assert!(!ui.save_history());
Ok(())
}
#[test]
fn can_opt_to_skip_plugins() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu --skip-plugins")?;
assert!(ui.skip_plugins());
Ok(())
}
#[test]
fn understands_commands_need_to_be_run() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu -c \"ls | get name\"")?;
assert_eq!(ui.commands().unwrap(), Ok(String::from("ls | get name")));
let ui = cli_app();
ui.parse("nu -c \"echo 'hola'\"")?;
assert_eq!(ui.commands().unwrap(), Ok(String::from("echo 'hola'")));
Ok(())
}
#[test]
fn knows_custom_configurations() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu --config-file /path/to/config.toml")?;
assert_eq!(ui.config().unwrap(), String::from("/path/to/config.toml"));
Ok(())
}
}

View File

@ -0,0 +1,52 @@
use super::App;
use log::LevelFilter;
use nu_errors::ShellError;
use pretty_env_logger::env_logger::Builder;
pub fn logger(f: impl FnOnce(&mut Builder) -> Result<(), ShellError>) -> Result<(), ShellError> {
let mut builder = pretty_env_logger::formatted_builder();
f(&mut builder)?;
let _ = builder.try_init();
Ok(())
}
pub fn configure(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
if let Some(level) = app.loglevel() {
let level = match level.as_deref() {
Ok("error") => LevelFilter::Error,
Ok("warn") => LevelFilter::Warn,
Ok("info") => LevelFilter::Info,
Ok("debug") => LevelFilter::Debug,
Ok("trace") => LevelFilter::Trace,
Ok(_) | Err(_) => LevelFilter::Warn,
};
logger.filter_module("nu", level);
};
if let Ok(s) = std::env::var("RUST_LOG") {
logger.parse_filters(&s);
}
Ok(())
}
pub fn trace_filters(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
if let Some(filters) = app.develop() {
filters.into_iter().filter_map(Result::ok).for_each(|name| {
logger.filter_module(&name, LevelFilter::Trace);
})
}
Ok(())
}
pub fn debug_filters(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
if let Some(filters) = app.debug() {
filters.into_iter().filter_map(Result::ok).for_each(|name| {
logger.filter_module(&name, LevelFilter::Debug);
})
}
Ok(())
}

View File

@ -0,0 +1,102 @@
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value};
use std::cell::RefCell;
use std::ffi::{OsStr, OsString};
#[derive(Debug, Clone)]
pub struct CliOptions {
pub config: Option<OsString>,
pub stdin: bool,
pub scripts: Vec<NuScript>,
pub save_history: bool,
pub perf: bool,
}
impl Default for CliOptions {
fn default() -> Self {
Self::new()
}
}
impl CliOptions {
pub fn new() -> Self {
Self {
config: None,
stdin: false,
scripts: vec![],
save_history: true,
perf: false,
}
}
}
#[derive(Debug)]
pub struct Options {
inner: RefCell<IndexMap<String, Value>>,
}
impl Options {
pub fn default() -> Self {
Self {
inner: RefCell::new(IndexMap::default()),
}
}
pub fn get(&self, key: &str) -> Option<Value> {
self.inner.borrow().get(key).cloned()
}
pub fn put(&self, key: &str, value: Value) {
self.inner.borrow_mut().insert(key.into(), value);
}
pub fn shift(&self) {
if let Some(Value {
value: UntaggedValue::Table(ref mut args),
..
}) = self.inner.borrow_mut().get_mut("args")
{
args.remove(0);
}
}
pub fn swap(&self, other: &Options) {
self.inner.swap(&other.inner);
}
}
#[derive(Debug, Clone)]
pub struct NuScript {
pub filepath: Option<OsString>,
pub contents: String,
}
impl NuScript {
pub fn code(content: &str) -> Result<Self, ShellError> {
Ok(Self {
filepath: None,
contents: content.to_string(),
})
}
pub fn get_code(&self) -> &str {
&self.contents
}
pub fn source_file(path: &OsStr) -> Result<Self, ShellError> {
use std::fs::File;
use std::io::Read;
let path = path.to_os_string();
let mut file = File::open(&path)?;
let mut buffer = String::new();
file.read_to_string(&mut buffer)?;
Ok(Self {
filepath: Some(path),
contents: buffer,
})
}
}

View File

@ -0,0 +1,132 @@
use super::Options;
use nu_command::commands::{loglevels, testbins, NuSignature as Nu};
use nu_command::commands::{Autoview, Pivot, Table, Version as NuVersion};
use nu_engine::{whole_stream_command, EvaluationContext};
use nu_errors::ShellError;
use nu_protocol::hir::{ClassifiedCommand, InternalCommand, NamedValue};
use nu_protocol::UntaggedValue;
use nu_source::Tag;
pub struct NuParser {
context: EvaluationContext,
}
pub trait OptionsParser {
fn parse(&self, input: &str) -> Result<Options, ShellError>;
fn context(&self) -> &EvaluationContext;
}
impl NuParser {
pub fn new() -> Self {
let context = EvaluationContext::basic();
context.add_commands(vec![
whole_stream_command(Nu {}),
whole_stream_command(NuVersion {}),
whole_stream_command(Autoview {}),
whole_stream_command(Pivot {}),
whole_stream_command(Table {}),
]);
Self { context }
}
}
impl OptionsParser for NuParser {
fn context(&self) -> &EvaluationContext {
&self.context
}
fn parse(&self, input: &str) -> Result<Options, ShellError> {
let options = Options::default();
let (lite_result, _err) = nu_parser::lex(input, 0, nu_parser::NewlineMode::Normal);
let (lite_result, _err) = nu_parser::parse_block(lite_result);
let (parsed, err) = nu_parser::classify_block(&lite_result, &self.context.scope);
if let Some(reason) = err {
return Err(reason.into());
}
match parsed.block[0].pipelines[0].list[0] {
ClassifiedCommand::Internal(InternalCommand { ref args, .. }) => {
if let Some(ref params) = args.named {
params.iter().for_each(|(k, v)| {
let value = match v {
NamedValue::AbsentSwitch => {
Some(UntaggedValue::from(false).into_untagged_value())
}
NamedValue::PresentSwitch(span) => {
Some(UntaggedValue::from(true).into_value(Tag::from(span)))
}
NamedValue::AbsentValue => None,
NamedValue::Value(span, exprs) => {
let value = nu_engine::evaluate_baseline_expr(exprs, &self.context)
.expect("value");
Some(value.value.into_value(Tag::from(span)))
}
};
let value = value.map(|v| match k.as_ref() {
"testbin" => {
if let Ok(name) = v.as_string() {
if testbins().iter().any(|n| name == *n) {
v
} else {
UntaggedValue::Error(ShellError::untagged_runtime_error(
format!("{} is not supported.", name),
))
.into_value(v.tag)
}
} else {
v
}
}
"loglevel" => {
if let Ok(name) = v.as_string() {
if loglevels().iter().any(|n| name == *n) {
v
} else {
UntaggedValue::Error(ShellError::untagged_runtime_error(
format!("{} is not supported.", name),
))
.into_value(v.tag)
}
} else {
v
}
}
_ => v,
});
if let Some(value) = value {
options.put(k, value);
}
});
}
let mut positional_args = vec![];
if let Some(positional) = &args.positional {
for pos in positional {
let result = nu_engine::evaluate_baseline_expr(pos, &self.context)?;
positional_args.push(result);
}
}
if !positional_args.is_empty() {
options.put(
"args",
UntaggedValue::Table(positional_args).into_untagged_value(),
);
}
}
ClassifiedCommand::Error(ref reason) => {
return Err(reason.clone().into());
}
_ => return Err(ShellError::untagged_runtime_error("unrecognized command")),
}
Ok(options)
}
}

View File

@ -0,0 +1,118 @@
#![allow(dead_code)]
use std::default::Default;
use std::fmt;
use std::time::{Duration, Instant};
#[derive(Clone, Copy)]
pub struct Stopwatch {
/// The time the stopwatch was started last, if ever.
start_time: Option<Instant>,
/// The time the stopwatch was split last, if ever.
split_time: Option<Instant>,
/// The time elapsed while the stopwatch was running (between start() and stop()).
elapsed: Duration,
}
impl Default for Stopwatch {
fn default() -> Stopwatch {
Stopwatch {
start_time: None,
split_time: None,
elapsed: Duration::from_secs(0),
}
}
}
impl fmt::Display for Stopwatch {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
return write!(f, "{}ms", self.elapsed_ms());
}
}
impl Stopwatch {
/// Returns a new stopwatch.
pub fn new() -> Stopwatch {
let sw: Stopwatch = Default::default();
sw
}
/// Returns a new stopwatch which will immediately be started.
pub fn start_new() -> Stopwatch {
let mut sw = Stopwatch::new();
sw.start();
sw
}
/// Starts the stopwatch.
pub fn start(&mut self) {
self.start_time = Some(Instant::now());
}
/// Stops the stopwatch.
pub fn stop(&mut self) {
self.elapsed = self.elapsed();
self.start_time = None;
self.split_time = None;
}
/// Resets all counters and stops the stopwatch.
pub fn reset(&mut self) {
self.elapsed = Duration::from_secs(0);
self.start_time = None;
self.split_time = None;
}
/// Resets and starts the stopwatch again.
pub fn restart(&mut self) {
self.reset();
self.start();
}
/// Returns whether the stopwatch is running.
pub fn is_running(&self) -> bool {
self.start_time.is_some()
}
/// Returns the elapsed time since the start of the stopwatch.
pub fn elapsed(&self) -> Duration {
match self.start_time {
// stopwatch is running
Some(t1) => t1.elapsed() + self.elapsed,
// stopwatch is not running
None => self.elapsed,
}
}
/// Returns the elapsed time since the start of the stopwatch in milliseconds.
pub fn elapsed_ms(&self) -> i64 {
let dur = self.elapsed();
(dur.as_secs() * 1000 + dur.subsec_millis() as u64) as i64
}
/// Returns the elapsed time since last split or start/restart.
///
/// If the stopwatch is in stopped state this will always return a zero Duration.
pub fn elapsed_split(&mut self) -> Duration {
match self.start_time {
// stopwatch is running
Some(start) => {
let res = match self.split_time {
Some(split) => split.elapsed(),
None => start.elapsed(),
};
self.split_time = Some(Instant::now());
res
}
// stopwatch is not running
None => Duration::from_secs(0),
}
}
/// Returns the elapsed time since last split or start/restart in milliseconds.
///
/// If the stopwatch is in stopped state this will always return zero.
pub fn elapsed_split_ms(&mut self) -> i64 {
let dur = self.elapsed_split();
(dur.as_secs() * 1000 + dur.subsec_millis() as u64) as i64
}
}

View File

@ -1,12 +1,10 @@
use crate::app::STOPWATCH;
use crate::line_editor::configure_ctrl_c;
use nu_command::commands::default_context::create_default_context;
#[allow(unused_imports)]
use nu_command::maybe_print_errors;
use nu_engine::run_block;
use nu_engine::EvaluationContext;
use nu_ansi_term::Color;
use nu_engine::{maybe_print_errors, run_block, script::run_script_standalone, EvaluationContext};
#[allow(unused_imports)]
pub(crate) use nu_command::script::{process_script, LineResult};
pub(crate) use nu_engine::script::{process_script, LineResult};
#[cfg(feature = "rustyline-support")]
use crate::line_editor::{
@ -21,23 +19,25 @@ use nu_stream::InputStream;
#[allow(unused_imports)]
use std::sync::atomic::Ordering;
use nu_command::script::{print_err, run_script_standalone};
#[cfg(feature = "rustyline-support")]
use rustyline::{self, error::ReadlineError};
use crate::EnvironmentSyncer;
use nu_errors::ShellError;
use nu_parser::ParserScope;
use nu_protocol::{UntaggedValue, Value};
use nu_path::expand_tilde;
use nu_protocol::{hir::ExternalRedirection, ConfigPath, UntaggedValue, Value};
use log::trace;
use std::error::Error;
use std::iter::Iterator;
use std::path::PathBuf;
// Name of environment variable where the prompt could be stored
#[cfg(feature = "rustyline-support")]
const PROMPT_COMMAND: &str = "PROMPT_COMMAND";
pub fn search_paths() -> Vec<std::path::PathBuf> {
use std::env;
let mut search_paths = Vec::new();
// Automatically add path `nu` is in as a search path
@ -48,16 +48,14 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
}
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
if let Some(plugin_dirs) = config.get("plugin_dirs") {
if let Value {
value: UntaggedValue::Table(pipelines),
..
} = plugin_dirs
{
for pipeline in pipelines {
if let Ok(plugin_dir) = pipeline.as_string() {
search_paths.push(PathBuf::from(plugin_dir));
}
if let Some(Value {
value: UntaggedValue::Table(pipelines),
..
}) = config.get("plugin_dirs")
{
for pipeline in pipelines {
if let Ok(plugin_dir) = pipeline.as_string() {
search_paths.push(expand_tilde(plugin_dir));
}
}
}
@ -66,142 +64,255 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
search_paths
}
pub async fn run_script_file(
file_contents: String,
redirect_stdin: bool,
) -> Result<(), Box<dyn Error>> {
let mut syncer = EnvironmentSyncer::new();
let mut context = create_default_context(false)?;
let config = syncer.get_config();
pub fn run_script_file(
context: EvaluationContext,
options: super::app::CliOptions,
) -> Result<(), ShellError> {
if let Some(cfg) = options.config {
load_cfg_as_global_cfg(&context, PathBuf::from(cfg));
} else {
load_global_cfg(&context);
}
context.configure(&config, |_, ctx| {
syncer.load_environment();
syncer.sync_env_vars(ctx);
syncer.sync_path_vars(ctx);
let _ = register_plugins(&context);
let _ = configure_ctrl_c(&context);
if let Err(reason) = syncer.autoenv(ctx) {
print_err(reason, &Text::from(""));
}
let script = options
.scripts
.get(0)
.ok_or_else(|| ShellError::unexpected("Nu source code not available"))?;
let _ = register_plugins(ctx);
let _ = configure_ctrl_c(ctx);
});
let _ = run_startup_commands(&mut context, &config).await;
run_script_standalone(file_contents, redirect_stdin, &context, true).await?;
run_script_standalone(script.get_code().to_string(), options.stdin, &context, true)?;
Ok(())
}
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input.
#[cfg(feature = "rustyline-support")]
pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
let mut syncer = EnvironmentSyncer::new();
let configuration = syncer.get_config();
fn default_prompt_string(cwd: &str) -> String {
format!(
"{}{}{}{}{}{}> ",
Color::Green.bold().prefix().to_string(),
cwd,
nu_ansi_term::ansi::RESET,
Color::Cyan.bold().prefix().to_string(),
current_branch(),
nu_ansi_term::ansi::RESET
)
}
let mut rl = default_rustyline_editor_configuration();
#[cfg(feature = "rustyline-support")]
fn evaluate_prompt_string(prompt_line: &str, context: &EvaluationContext, cwd: &str) -> String {
context.scope.enter_scope();
let (prompt_block, err) = nu_parser::parse(prompt_line, 0, &context.scope);
context.configure(&configuration, |config, ctx| {
syncer.load_environment();
syncer.sync_env_vars(ctx);
syncer.sync_path_vars(ctx);
if err.is_some() {
context.scope.exit_scope();
default_prompt_string(cwd)
} else {
let run_result = run_block(
&prompt_block,
context,
InputStream::empty(),
ExternalRedirection::Stdout,
);
context.scope.exit_scope();
if let Err(reason) = syncer.autoenv(ctx) {
print_err(reason, &Text::from(""));
match run_result {
Ok(result) => match result.collect_string(Tag::unknown()) {
Ok(string_result) => {
let errors = context.get_errors();
maybe_print_errors(context, Text::from(prompt_line));
context.clear_errors();
if !errors.is_empty() {
"> ".into()
} else {
string_result.item
}
}
Err(e) => {
context.host().lock().print_err(e, &Text::from(prompt_line));
context.clear_errors();
"> ".into()
}
},
Err(e) => {
context.host().lock().print_err(e, &Text::from(prompt_line));
context.clear_errors();
"> ".into()
}
}
}
}
let _ = configure_ctrl_c(ctx);
let _ = configure_rustyline_editor(&mut rl, config);
#[cfg(feature = "rustyline-support")]
pub fn cli(
context: EvaluationContext,
options: super::app::CliOptions,
) -> Result<(), Box<dyn Error>> {
let _ = configure_ctrl_c(&context);
let helper = Some(nu_line_editor_helper(ctx, config));
// start time for running startup scripts (this metric includes loading of the cfg, but w/e)
let startup_commands_start_time = std::time::Instant::now();
if let Some(cfg) = options.config {
load_cfg_as_global_cfg(&context, PathBuf::from(cfg));
} else {
load_global_cfg(&context);
}
// Store cmd duration in an env var
context.scope.add_env_var(
"CMD_DURATION_MS",
startup_commands_start_time
.elapsed()
.as_millis()
.to_string(),
);
if options.perf {
eprintln!(
"config loaded: {:?}",
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.elapsed_split()
);
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.start();
}
//Configure rustyline
let mut rl = default_rustyline_editor_configuration();
let history_path = if let Some(cfg) = &context.configs().lock().global_config {
let _ = configure_rustyline_editor(&mut rl, cfg);
let helper = Some(nu_line_editor_helper(&context, cfg));
rl.set_helper(helper);
});
nu_data::config::path::history_path_or_default(cfg)
} else {
nu_data::config::path::default_history_path()
};
let _ = run_startup_commands(&mut context, &configuration).await;
if options.perf {
eprintln!(
"rustyline configuration: {:?}",
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.elapsed_split()
);
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.start();
}
// Don't load history if it's not necessary
if options.save_history {
let _ = rl.load_history(&history_path);
}
if options.perf {
eprintln!(
"history load: {:?}",
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.elapsed_split()
);
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.start();
}
//set vars from cfg if present
let (skip_welcome_message, prompt) = if let Some(cfg) = &context.configs().lock().global_config
{
(
cfg.var("skip_welcome_message")
.map(|x| x.is_true())
.unwrap_or(false),
cfg.var("prompt"),
)
} else {
(false, None)
};
if options.perf {
eprintln!(
"load custom prompt: {:?}",
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.elapsed_split()
);
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.start();
}
//Check whether dir we start in contains local cfg file and if so load it.
load_local_cfg_if_present(&context);
// Give ourselves a scope to work in
context.scope.enter_scope();
let history_path = nu_engine::history_path(&configuration);
let _ = rl.load_history(&history_path);
let mut session_text = String::new();
let mut line_start: usize = 0;
let skip_welcome_message = configuration
.var("skip_welcome_message")
.map(|x| x.is_true())
.unwrap_or(false);
if !skip_welcome_message {
println!(
"Welcome to Nushell {} (type 'help' for more info)",
clap::crate_version!()
nu_command::commands::core_version()
);
}
#[cfg(windows)]
{
let _ = ansi_term::enable_ansi_support();
let _ = nu_ansi_term::enable_ansi_support();
}
let mut ctrlcbreak = false;
if options.perf {
eprintln!(
"timing stopped. starting run loop: {:?}",
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.elapsed_split()
);
STOPWATCH
.lock()
.expect("unable to lock the stopwatch")
.stop();
}
loop {
if context.ctrl_c.load(Ordering::SeqCst) {
context.ctrl_c.store(false, Ordering::SeqCst);
if context.ctrl_c().load(Ordering::SeqCst) {
context.ctrl_c().store(false, Ordering::SeqCst);
continue;
}
let cwd = context.shell_manager.path();
let cwd = context.shell_manager().path();
let colored_prompt = {
if let Some(prompt) = configuration.var("prompt") {
let prompt_line = prompt.as_string()?;
context.scope.enter_scope();
let (prompt_block, err) = nu_parser::parse(&prompt_line, 0, &context.scope);
if err.is_some() {
context.scope.exit_scope();
format!("\x1b[32m{}{}\x1b[m> ", cwd, current_branch())
// Check if the PROMPT_COMMAND env variable is set. This env variable
// contains nu code that is used to overwrite the prompt
let colored_prompt = match context.scope.get_env(PROMPT_COMMAND) {
Some(env_prompt) => evaluate_prompt_string(&env_prompt, &context, &cwd),
None => {
if let Some(prompt) = &prompt {
let prompt_line = prompt.as_string()?;
evaluate_prompt_string(&prompt_line, &context, &cwd)
} else {
// let env = context.get_env();
let run_result = run_block(&prompt_block, &context, InputStream::empty()).await;
context.scope.exit_scope();
match run_result {
Ok(result) => match result.collect_string(Tag::unknown()).await {
Ok(string_result) => {
let errors = context.get_errors();
maybe_print_errors(&context, Text::from(prompt_line));
context.clear_errors();
if !errors.is_empty() {
"> ".to_string()
} else {
string_result.item
}
}
Err(e) => {
crate::cli::print_err(e, &Text::from(prompt_line));
context.clear_errors();
"> ".to_string()
}
},
Err(e) => {
crate::cli::print_err(e, &Text::from(prompt_line));
context.clear_errors();
"> ".to_string()
}
}
default_prompt_string(&cwd)
}
} else {
format!("\x1b[32m{}{}\x1b[m> ", cwd, current_branch())
}
};
@ -213,11 +324,13 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
}
};
rl.helper_mut().expect("No helper").colored_prompt = colored_prompt;
if let Some(helper) = rl.helper_mut() {
helper.colored_prompt = colored_prompt;
}
let mut initial_command = Some(String::new());
let mut readline = Err(ReadlineError::Eof);
while let Some(ref cmd) = initial_command {
readline = rl.readline_with_initial(&prompt, (&cmd, ""));
readline = rl.readline_with_initial(&prompt, (cmd, ""));
initial_command = None;
}
@ -227,65 +340,69 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
session_text.push('\n');
}
// start time for command duration
let cmd_start_time = std::time::Instant::now();
let line = match convert_rustyline_result_to_string(readline) {
LineResult::Success(_) => {
process_script(
&session_text[line_start..],
&context,
false,
line_start,
true,
)
.await
}
LineResult::Success(_) => process_script(
&session_text[line_start..],
&context,
false,
line_start,
true,
),
x => x,
};
// Check the config to see if we need to update the path
// TODO: make sure config is cached so we don't path this load every call
// FIXME: we probably want to be a bit more graceful if we can't set the environment
context.configure(&configuration, |config, ctx| {
if syncer.did_config_change() {
syncer.reload();
syncer.sync_env_vars(ctx);
syncer.sync_path_vars(ctx);
}
if let Err(reason) = syncer.autoenv(ctx) {
print_err(reason, &Text::from(""));
}
let _ = configure_rustyline_editor(&mut rl, config);
});
// Store cmd duration in an env var
context.scope.add_env_var(
"CMD_DURATION_MS",
cmd_start_time.elapsed().as_millis().to_string(),
);
match line {
LineResult::Success(line) => {
rl.add_history_entry(&line);
let _ = rl.save_history(&history_path);
if options.save_history && !line.trim().is_empty() {
rl.add_history_entry(&line);
let _ = rl.append_history(&history_path);
}
maybe_print_errors(&context, Text::from(session_text.clone()));
}
LineResult::ClearHistory => {
rl.clear_history();
let _ = rl.save_history(&history_path);
if options.save_history {
rl.clear_history();
std::fs::remove_file(&history_path)?;
}
}
LineResult::Error(line, err) => {
rl.add_history_entry(&line);
let _ = rl.save_history(&history_path);
if options.save_history && !line.trim().is_empty() {
rl.add_history_entry(&line);
let _ = rl.append_history(&history_path);
}
context.with_host(|_host| {
print_err(err, &Text::from(session_text.clone()));
});
context
.host()
.lock()
.print_err(err, &Text::from(session_text.clone()));
// I am not so sure, we don't need maybe_print_errors here (as we printed an err
// above), because maybe_print_errors also clears the errors.
// TODO Analyze where above err comes from, and whether we need to clear
// context.errors here
// Or just be consistent and return errors always in context.errors...
maybe_print_errors(&context, Text::from(session_text.clone()));
}
LineResult::CtrlC => {
let config_ctrlc_exit = config::config(Tag::unknown())?
.get("ctrlc_exit")
.map(|s| s.value.is_true())
let config_ctrlc_exit = context
.configs()
.lock()
.global_config
.as_ref()
.and_then(|cfg| cfg.var("ctrlc_exit"))
.map(|ctrl_c| ctrl_c.is_true())
.unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells
if !config_ctrlc_exit {
@ -293,7 +410,9 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
}
if ctrlcbreak {
let _ = rl.save_history(&history_path);
if options.save_history {
let _ = rl.append_history(&history_path);
}
std::process::exit(0);
} else {
context.with_host(|host| host.stdout("CTRL-C pressed (again to quit)"));
@ -303,8 +422,8 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
}
LineResult::CtrlD => {
context.shell_manager.remove_at_current();
if context.shell_manager.is_empty() {
context.shell_manager().remove_at_current();
if context.shell_manager().is_empty() {
break;
}
}
@ -317,12 +436,50 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
}
// we are ok if we can not save history
let _ = rl.save_history(&history_path);
if options.save_history {
let _ = rl.append_history(&history_path);
}
Ok(())
}
pub fn register_plugins(context: &mut EvaluationContext) -> Result<(), ShellError> {
#[cfg(feature = "rustyline-support")]
pub fn load_local_cfg_if_present(context: &EvaluationContext) {
trace!("Loading local cfg if present");
match config::loadable_cfg_exists_in_dir(PathBuf::from(context.shell_manager().path())) {
Ok(Some(cfg_path)) => {
if let Err(err) = context.load_config(&ConfigPath::Local(cfg_path)) {
context.host().lock().print_err(err, &Text::from(""))
}
}
Err(e) => {
//Report error while checking for local cfg file
context.host().lock().print_err(e, &Text::from(""))
}
Ok(None) => {
//No local cfg file present in start dir
}
}
}
fn load_cfg_as_global_cfg(context: &EvaluationContext, path: PathBuf) {
if let Err(err) = context.load_config(&ConfigPath::Global(path)) {
context.host().lock().print_err(err, &Text::from(""));
}
}
pub fn load_global_cfg(context: &EvaluationContext) {
match config::default_path() {
Ok(path) => {
load_cfg_as_global_cfg(context, path);
}
Err(e) => {
context.host().lock().print_err(e, &Text::from(""));
}
}
}
pub fn register_plugins(context: &EvaluationContext) -> Result<(), ShellError> {
if let Ok(plugins) = nu_engine::plugin::build_plugin::scan(search_paths()) {
context.add_commands(
plugins
@ -335,35 +492,7 @@ pub fn register_plugins(context: &mut EvaluationContext) -> Result<(), ShellErro
Ok(())
}
async fn run_startup_commands(
context: &mut EvaluationContext,
config: &dyn nu_data::config::Conf,
) -> Result<(), ShellError> {
if let Some(commands) = config.var("startup") {
match commands {
Value {
value: UntaggedValue::Table(pipelines),
..
} => {
let mut script_file = String::new();
for pipeline in pipelines {
script_file.push_str(&pipeline.as_string()?);
script_file.push('\n');
}
let _ = run_script_standalone(script_file, false, context, false).await;
}
_ => {
return Err(ShellError::untagged_runtime_error(
"expected a table of pipeline strings as startup commands",
));
}
}
}
Ok(())
}
pub async fn parse_and_eval(line: &str, ctx: &EvaluationContext) -> Result<String, ShellError> {
pub fn parse_and_eval(line: &str, ctx: &EvaluationContext) -> Result<String, ShellError> {
// FIXME: do we still need this?
let line = if let Some(s) = line.strip_suffix('\n') {
s
@ -373,20 +502,23 @@ pub async fn parse_and_eval(line: &str, ctx: &EvaluationContext) -> Result<Strin
// TODO ensure the command whose examples we're testing is actually in the pipeline
ctx.scope.enter_scope();
let (classified_block, err) = nu_parser::parse(&line, 0, &ctx.scope);
let (classified_block, err) = nu_parser::parse(line, 0, &ctx.scope);
if let Some(err) = err {
ctx.scope.exit_scope();
return Err(err.into());
}
let input_stream = InputStream::empty();
let env = ctx.get_env();
ctx.scope.add_env(env);
let result = run_block(&classified_block, ctx, input_stream).await;
let result = run_block(
&classified_block,
ctx,
input_stream,
ExternalRedirection::Stdout,
);
ctx.scope.exit_scope();
result?.collect_string(Tag::unknown()).await.map(|x| x.item)
result?.collect_string(Tag::unknown()).map(|x| x.item)
}
#[allow(dead_code)]
@ -404,19 +536,3 @@ fn current_branch() -> String {
"".to_string()
}
}
#[cfg(test)]
mod tests {
use nu_engine::basic_evaluation_context;
#[quickcheck]
fn quickcheck_parse(data: String) -> bool {
let (tokens, err) = nu_parser::lex(&data, 0);
let (lite_block, err2) = nu_parser::block(tokens);
if err.is_none() && err2.is_none() {
let context = basic_evaluation_context().unwrap();
let _ = nu_parser::classify_block(&lite_block, &context.scope);
}
true
}
}

View File

@ -1,141 +0,0 @@
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

@ -1,37 +0,0 @@
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

@ -1,88 +0,0 @@
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,3 +0,0 @@
pub(crate) mod directory_specific_environment;
pub(crate) mod environment;
pub(crate) mod environment_syncer;

View File

@ -1,259 +0,0 @@
use indexmap::{IndexMap, IndexSet};
use nu_command::commands::autoenv;
use nu_errors::ShellError;
use serde::Deserialize;
use std::env::*;
use std::process::Command;
use std::{
ffi::OsString,
fmt::Debug,
path::{Path, PathBuf},
};
//Tests reside in /nushell/tests/shell/pipeline/commands/internal.rs
type EnvKey = String;
type EnvVal = OsString;
#[derive(Debug, Default)]
pub struct DirectorySpecificEnvironment {
pub last_seen_directory: PathBuf,
//If an environment var has been added from a .nu in a directory, we track it here so we can remove it when the user leaves the directory.
//If setting the var overwrote some value, we save the old value in an option so we can restore it later.
added_vars: IndexMap<PathBuf, IndexMap<EnvKey, Option<EnvVal>>>,
//We track directories that we have read .nu-env from. This is different from the keys in added_vars since sometimes a file only wants to run scripts.
visited_dirs: IndexSet<PathBuf>,
exitscripts: IndexMap<PathBuf, Vec<String>>,
}
#[derive(Deserialize, Debug, Default)]
pub struct NuEnvDoc {
pub env: Option<IndexMap<String, String>>,
pub scriptvars: Option<IndexMap<String, String>>,
pub scripts: Option<IndexMap<String, Vec<String>>>,
pub entryscripts: Option<Vec<String>>,
pub exitscripts: Option<Vec<String>>,
}
impl DirectorySpecificEnvironment {
pub fn new() -> DirectorySpecificEnvironment {
let root_dir = if cfg!(target_os = "windows") {
PathBuf::from("c:\\")
} else {
PathBuf::from("/")
};
DirectorySpecificEnvironment {
last_seen_directory: root_dir,
added_vars: IndexMap::new(),
visited_dirs: IndexSet::new(),
exitscripts: IndexMap::new(),
}
}
fn toml_if_trusted(&mut self, nu_env_file: &PathBuf) -> Result<NuEnvDoc, ShellError> {
let content = std::fs::read(&nu_env_file)?;
if autoenv::file_is_trusted(&nu_env_file, &content)? {
let mut doc: NuEnvDoc = toml::de::from_slice(&content)
.map_err(|e| ShellError::untagged_runtime_error(format!("{:?}", e)))?;
if let Some(scripts) = doc.scripts.as_ref() {
for (k, v) in scripts {
if k == "entryscripts" {
doc.entryscripts = Some(v.clone());
} else if k == "exitscripts" {
doc.exitscripts = Some(v.clone());
}
}
}
return Ok(doc);
}
Err(ShellError::untagged_runtime_error(
format!("{:?} is untrusted. Run 'autoenv trust {:?}' to trust it.\nThis needs to be done after each change to the file.", nu_env_file, nu_env_file.parent().unwrap_or_else(|| &Path::new("")))))
}
pub fn maintain_autoenv(&mut self) -> Result<(), ShellError> {
let mut dir = current_dir()?;
if self.last_seen_directory == dir {
return Ok(());
}
//We track which keys we set as we go up the directory hierarchy, so that we don't overwrite a value we set in a subdir.
let mut added_keys = IndexSet::new();
let mut new_visited_dirs = IndexSet::new();
let mut popped = true;
while popped {
let nu_env_file = dir.join(".nu-env");
if nu_env_file.exists() && !self.visited_dirs.contains(&dir) {
let nu_env_doc = self.toml_if_trusted(&nu_env_file)?;
//add regular variables from the [env section]
if let Some(env) = nu_env_doc.env {
for (env_key, env_val) in env {
self.maybe_add_key(&mut added_keys, &dir, &env_key, &env_val);
}
}
//Add variables that need to evaluate scripts to run, from [scriptvars] section
if let Some(sv) = nu_env_doc.scriptvars {
for (key, script) in sv {
self.maybe_add_key(
&mut added_keys,
&dir,
&key,
value_from_script(&script)?.as_str(),
);
}
}
if let Some(es) = nu_env_doc.entryscripts {
for s in es {
run(s.as_str(), None)?;
}
}
if let Some(es) = nu_env_doc.exitscripts {
self.exitscripts.insert(dir.clone(), es);
}
}
new_visited_dirs.insert(dir.clone());
popped = dir.pop();
}
//Time to clear out vars set by directories that we have left.
let mut new_vars = IndexMap::new();
for (dir, dirmap) in self.added_vars.drain(..) {
if new_visited_dirs.contains(&dir) {
new_vars.insert(dir, dirmap);
} else {
for (k, v) in dirmap {
if let Some(v) = v {
std::env::set_var(k, v);
} else {
std::env::remove_var(k);
}
}
}
}
//Run exitscripts, can not be done in same loop as new vars as some files can contain only exitscripts
let mut new_exitscripts = IndexMap::new();
for (dir, scripts) in self.exitscripts.drain(..) {
if new_visited_dirs.contains(&dir) {
new_exitscripts.insert(dir, scripts);
} else {
for s in scripts {
run(s.as_str(), Some(&dir))?;
}
}
}
self.visited_dirs = new_visited_dirs;
self.exitscripts = new_exitscripts;
self.added_vars = new_vars;
self.last_seen_directory = current_dir()?;
Ok(())
}
pub fn maybe_add_key(
&mut self,
seen_vars: &mut IndexSet<EnvKey>,
dir: &PathBuf,
key: &str,
val: &str,
) {
//This condition is to make sure variables in parent directories don't overwrite variables set by subdirectories.
if !seen_vars.contains(key) {
seen_vars.insert(key.to_string());
self.added_vars
.entry(dir.clone())
.or_insert(IndexMap::new())
.insert(key.to_string(), var_os(key));
std::env::set_var(key, val);
}
}
// If the user recently ran autoenv untrust on a file, we clear the environment variables it set and make sure to not run any possible exitscripts.
pub fn clear_recently_untrusted_file(&mut self) -> Result<(), ShellError> {
// Figure out which file was untrusted
// Remove all vars set by it
let current_trusted_files: IndexSet<PathBuf> = autoenv::read_trusted()?
.files
.iter()
.map(|(k, _)| PathBuf::from(k))
.collect();
// We figure out which file(s) the user untrusted by taking the set difference of current trusted files in .config/nu/nu-env.toml and the files tracked by self.added_env_vars
// If a file is in self.added_env_vars but not in nu-env.toml, it was just untrusted.
let untrusted_files: IndexSet<PathBuf> = self
.added_vars
.iter()
.filter_map(|(path, _)| {
if !current_trusted_files.contains(path) {
return Some(path.clone());
}
None
})
.collect();
for path in untrusted_files {
if let Some(added_keys) = self.added_vars.get(&path) {
for (key, _) in added_keys {
remove_var(key);
}
}
self.exitscripts.remove(&path);
self.added_vars.remove(&path);
}
Ok(())
}
}
fn run(cmd: &str, dir: Option<&PathBuf>) -> Result<(), ShellError> {
if cfg!(target_os = "windows") {
if let Some(dir) = dir {
let command = format!("cd {} & {}", dir.to_string_lossy(), cmd);
Command::new("cmd")
.args(&["/C", command.as_str()])
.output()?
} else {
Command::new("cmd").args(&["/C", cmd]).output()?
}
} else if let Some(dir) = dir {
// FIXME: When nu scripting is added, cding like might not be a good idea. If nu decides to execute entryscripts when entering the dir this way, it will cause troubles.
// For now only standard shell scripts are used, so this is an issue for the future.
Command::new("sh")
.arg("-c")
.arg(format!("cd {:?}; {}", dir, cmd))
.output()?
} else {
Command::new("sh").arg("-c").arg(&cmd).output()?
};
Ok(())
}
fn value_from_script(cmd: &str) -> Result<String, ShellError> {
let command = if cfg!(target_os = "windows") {
Command::new("cmd").args(&["/C", cmd]).output()?
} else {
Command::new("sh").arg("-c").arg(&cmd).output()?
};
if command.stdout.is_empty() {
return Err(ShellError::untagged_runtime_error(format!(
"{:?} did not return any output",
cmd
)));
}
let response = std::str::from_utf8(&command.stdout[..command.stdout.len()]).map_err(|e| {
ShellError::untagged_runtime_error(format!(
"Couldn't parse stdout from command {:?}: {:?}",
command, e
))
})?;
Ok(response.trim().to_string())
}

View File

@ -1,274 +0,0 @@
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};
#[derive(Debug, Default)]
pub struct Environment {
environment_vars: Option<Value>,
path_vars: Option<Value>,
pub autoenv: DirectorySpecificEnvironment,
}
impl Environment {
pub fn new() -> Environment {
Environment {
environment_vars: None,
path_vars: None,
autoenv: DirectorySpecificEnvironment::new(),
}
}
pub fn from_config<T: Conf>(configuration: &T) -> Environment {
let env = configuration.env();
let path = configuration.path();
Environment {
environment_vars: env,
path_vars: path,
autoenv: DirectorySpecificEnvironment::new(),
}
}
pub fn autoenv(&mut self, reload_trusted: bool) -> Result<(), ShellError> {
self.autoenv.maintain_autoenv()?;
if reload_trusted {
self.autoenv.clear_recently_untrusted_file()?;
}
Ok(())
}
pub fn morph<T: Conf>(&mut self, configuration: &T) {
self.environment_vars = configuration.env();
self.path_vars = configuration.path();
}
}
impl Env for Environment {
fn env(&self) -> Option<Value> {
if let Some(vars) = &self.environment_vars {
return Some(vars.clone());
}
None
}
fn path(&self) -> Option<Value> {
if let Some(vars) = &self.path_vars {
return Some(vars.clone());
}
None
}
fn add_env(&mut self, key: &str, value: &str) {
let value = UntaggedValue::string(value);
let new_envs = {
if let Some(Value {
value: UntaggedValue::Row(ref envs),
ref tag,
}) = self.environment_vars
{
let mut new_envs = envs.clone();
if !new_envs.contains_key(key) {
new_envs.insert_data_at_key(key, value.into_value(tag.clone()));
}
Value {
value: UntaggedValue::Row(new_envs),
tag: tag.clone(),
}
} else {
UntaggedValue::Row(indexmap! { key.into() => value.into_untagged_value() }.into())
.into_untagged_value()
}
};
self.environment_vars = Some(new_envs);
}
fn add_path(&mut self, paths: std::ffi::OsString) {
let new_paths = {
if let Some(Value {
value: UntaggedValue::Table(ref current_paths),
ref tag,
}) = self.path_vars
{
let mut new_paths = current_paths.clone();
let new_path_candidates = std::env::split_paths(&paths).map(|path| {
UntaggedValue::string(path.to_string_lossy()).into_value(tag.clone())
});
new_paths.extend(new_path_candidates);
let paths: IndexSet<Value> = new_paths.into_iter().collect();
Value {
value: UntaggedValue::Table(paths.into_iter().collect()),
tag: tag.clone(),
}
} else {
let p = paths.into_string().unwrap_or_else(|_| String::from(""));
let p = UntaggedValue::string(p).into_untagged_value();
UntaggedValue::Table(vec![p]).into_untagged_value()
}
};
self.path_vars = Some(new_paths);
}
}
#[cfg(test)]
mod tests {
use super::{Env, Environment};
use nu_data::config::{tests::FakeConfig, Conf};
use nu_protocol::UntaggedValue;
use nu_test_support::fs::Stub::FileWithContent;
use nu_test_support::playground::Playground;
#[test]
fn picks_up_environment_variables_from_configuration() {
Playground::setup("environment_test_1", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
[env]
mosquetero_1 = "Andrés N. Robalino"
mosquetero_2 = "Jonathan Turner"
mosquetero_3 = "Yehuda katz"
mosquetero_4 = "Jason Gedge"
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let actual = Environment::from_config(&fake_config);
assert_eq!(actual.env(), fake_config.env());
});
}
#[test]
fn picks_up_path_variables_from_configuration() {
Playground::setup("environment_test_2", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
path = ["/Users/andresrobalino/.volta/bin", "/users/mosqueteros/bin"]
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let actual = Environment::from_config(&fake_config);
assert_eq!(actual.path(), fake_config.path());
});
}
#[test]
fn updates_env_variable() {
Playground::setup("environment_test_3", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
[env]
SHELL = "/usr/bin/you_already_made_the_nu_choice"
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let mut actual = Environment::from_config(&fake_config);
actual.add_env("USER", "NUNO");
assert_eq!(
actual.env(),
Some(
UntaggedValue::row(
indexmap! {
"USER".into() => UntaggedValue::string("NUNO").into_untagged_value(),
"SHELL".into() => UntaggedValue::string("/usr/bin/you_already_made_the_nu_choice").into_untagged_value(),
}
).into_untagged_value()
)
);
});
}
#[test]
fn does_not_update_env_variable_if_it_exists() {
Playground::setup("environment_test_4", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
[env]
SHELL = "/usr/bin/you_already_made_the_nu_choice"
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let mut actual = Environment::from_config(&fake_config);
actual.add_env("SHELL", "/usr/bin/sh");
assert_eq!(
actual.env(),
Some(
UntaggedValue::row(
indexmap! {
"SHELL".into() => UntaggedValue::string("/usr/bin/you_already_made_the_nu_choice").into_untagged_value(),
}
).into_untagged_value()
)
);
});
}
#[test]
fn updates_path_variable() {
Playground::setup("environment_test_5", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
path = ["/Users/andresrobalino/.volta/bin", "/users/mosqueteros/bin"]
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let mut actual = Environment::from_config(&fake_config);
actual.add_path(std::ffi::OsString::from("/path/to/be/added"));
assert_eq!(
actual.path(),
Some(
UntaggedValue::table(&[
UntaggedValue::string("/Users/andresrobalino/.volta/bin")
.into_untagged_value(),
UntaggedValue::string("/users/mosqueteros/bin").into_untagged_value(),
UntaggedValue::string("/path/to/be/added").into_untagged_value(),
])
.into_untagged_value()
)
);
});
}
}

View File

@ -1,609 +0,0 @@
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::{atomic::Ordering, Arc};
pub struct EnvironmentSyncer {
pub env: Arc<Mutex<Box<Environment>>>,
pub config: Arc<Mutex<Box<dyn Conf>>>,
}
impl Default for EnvironmentSyncer {
fn default() -> Self {
Self::new()
}
}
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(Mutex::new(Box::new(NuConfig::new()))),
}
}
#[cfg(test)]
pub fn set_config(&mut self, config: Box<dyn Conf>) {
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.lock();
self.env = Arc::new(Mutex::new(Box::new(Environment::from_config(&*config))));
}
pub fn did_config_change(&mut self) -> bool {
let config = self.config.lock();
config.is_modified().unwrap_or(false)
}
pub fn reload(&mut self) {
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 environment.env().is_some() {
for (name, value) in ctx.with_host(|host| host.vars()) {
if name != "path" && name != "PATH" {
// account for new env vars present in the current session
// that aren't loaded from config.
environment.add_env(&name, &value);
// clear the env var from the session
// we are about to replace them
ctx.with_host(|host| host.env_rm(std::ffi::OsString::from(name)));
}
}
if let Some(variables) = environment.env() {
for var in variables.row_entries() {
if let Ok(string) = var.1.as_string() {
ctx.with_host(|host| {
host.env_set(
std::ffi::OsString::from(var.0),
std::ffi::OsString::from(string),
)
});
}
}
}
}
}
pub fn sync_path_vars(&mut self, ctx: &mut EvaluationContext) {
let mut environment = self.env.lock();
if environment.path().is_some() {
let native_paths = ctx.with_host(|host| host.env_get(std::ffi::OsString::from("PATH")));
if let Some(native_paths) = native_paths {
environment.add_path(native_paths);
ctx.with_host(|host| {
host.env_rm(std::ffi::OsString::from("PATH"));
});
}
if let Some(new_paths) = environment.path() {
let prepared = std::env::join_paths(
new_paths
.table_entries()
.map(|p| p.as_string())
.filter_map(Result::ok),
);
if let Ok(paths_ready) = prepared {
ctx.with_host(|host| {
host.env_set(std::ffi::OsString::from("PATH"), paths_ready);
});
}
}
}
}
#[cfg(test)]
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)));
}
}
}
#[cfg(test)]
pub fn clear_path_var(&mut self, ctx: &mut EvaluationContext) {
ctx.with_host(|host| host.env_rm(std::ffi::OsString::from("PATH")));
}
}
#[cfg(test)]
mod tests {
use super::EnvironmentSyncer;
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;
use parking_lot::Mutex;
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 = 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(),
);
expected.insert("USER".to_string(), "NUNO".to_string());
Playground::setup("syncs_env_test_1", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
[env]
SHELL = "/usr/bin/you_already_made_the_nu_choice"
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let mut actual = EnvironmentSyncer::new();
actual.set_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 (if any)
actual.clear_env_vars(&mut ctx);
// We explicitly simulate and add the USER variable to the current
// session's environment variables with the value "NUNO".
ctx.with_host(|test_host| {
test_host.env_set(
std::ffi::OsString::from("USER"),
std::ffi::OsString::from("NUNO"),
)
});
// Nu loads the environment variables from the configuration file (if any)
actual.load_environment();
// By this point, Nu has already loaded the environment variables
// stored in the configuration file. Before continuing we check
// if any new environment variables have been added from the ones loaded
// in the configuration file.
//
// Nu sees the missing "USER" variable and accounts for it.
actual.sync_env_vars(&mut ctx);
// Confirms session environment variables are replaced from Nu configuration file
// including the newer one accounted for.
ctx.with_host(|test_host| {
let var_user = test_host
.env_get(std::ffi::OsString::from("USER"))
.expect("Couldn't get USER var from host.")
.into_string()
.expect("Couldn't convert to string.");
let var_shell = test_host
.env_get(std::ffi::OsString::from("SHELL"))
.expect("Couldn't get SHELL var from host.")
.into_string()
.expect("Couldn't convert to string.");
let mut found = IndexMap::new();
found.insert("SHELL".to_string(), var_shell);
found.insert("USER".to_string(), var_user);
for k in found.keys() {
assert!(expected.contains_key(k));
}
});
// Now confirm in-memory environment variables synced appropriately
// including the newer one accounted for.
let environment = actual.env.lock();
let mut vars = IndexMap::new();
environment
.env()
.expect("No variables in the environment.")
.row_entries()
.for_each(|(name, value)| {
vars.insert(
name.to_string(),
value.as_string().expect("Couldn't convert to string"),
);
});
for k in expected.keys() {
assert!(vars.contains_key(k));
}
});
Ok(())
}
#[test]
fn nu_envs_have_higher_priority_and_does_not_get_overwritten() -> 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_test_2", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
[env]
SHELL = "/usr/bin/you_already_made_the_nu_choice"
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let mut actual = EnvironmentSyncer::new();
actual.set_config(Box::new(fake_config));
actual.clear_env_vars(&mut ctx);
ctx.with_host(|test_host| {
test_host.env_set(
std::ffi::OsString::from("SHELL"),
std::ffi::OsString::from("/usr/bin/sh"),
)
});
actual.load_environment();
actual.sync_env_vars(&mut ctx);
ctx.with_host(|test_host| {
let var_shell = test_host
.env_get(std::ffi::OsString::from("SHELL"))
.expect("Couldn't get SHELL var from host.")
.into_string()
.expect("Couldn't convert to string.");
let mut found = IndexMap::new();
found.insert("SHELL".to_string(), var_shell);
for k in found.keys() {
assert!(expected.contains_key(k));
}
});
let environment = actual.env.lock();
let mut vars = IndexMap::new();
environment
.env()
.expect("No variables in the environment.")
.row_entries()
.for_each(|(name, value)| {
vars.insert(
name.to_string(),
value.as_string().expect("couldn't convert to string"),
);
});
for k in expected.keys() {
assert!(vars.contains_key(k));
}
});
Ok(())
}
#[test]
fn syncs_path_if_new_path_entry_in_session_is_not_in_configuration_file(
) -> Result<(), ShellError> {
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"),
PathBuf::from("/Users/mosqueteros/bin"),
PathBuf::from("/path/to/be/added"),
])
.expect("Couldn't join paths.")
.into_string()
.expect("Couldn't convert to string.");
Playground::setup("syncs_path_test_1", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
path = ["/Users/andresrobalino/.volta/bin", "/Users/mosqueteros/bin"]
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let mut actual = EnvironmentSyncer::new();
actual.set_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 (if any)
actual.clear_path_var(&mut ctx);
// We explicitly simulate and add the PATH variable to the current
// session with the path "/path/to/be/added".
ctx.with_host(|test_host| {
test_host.env_set(
std::ffi::OsString::from("PATH"),
std::env::join_paths(vec![PathBuf::from("/path/to/be/added")])
.expect("Couldn't join paths."),
)
});
// Nu loads the path variables from the configuration file (if any)
actual.load_environment();
// By this point, Nu has already loaded environment path variable
// stored in the configuration file. Before continuing we check
// if any new paths have been added from the ones loaded in the
// configuration file.
//
// Nu sees the missing "/path/to/be/added" and accounts for it.
actual.sync_path_vars(&mut ctx);
ctx.with_host(|test_host| {
let actual = test_host
.env_get(std::ffi::OsString::from("PATH"))
.expect("Couldn't get PATH var from host.")
.into_string()
.expect("Couldn't convert to string.");
assert_eq!(actual, expected);
});
let environment = actual.env.lock();
let paths = std::env::join_paths(
&environment
.path()
.expect("No path variable in the environment.")
.table_entries()
.map(|value| value.as_string().expect("Couldn't convert to string"))
.map(PathBuf::from)
.collect::<Vec<_>>(),
)
.expect("Couldn't join paths.")
.into_string()
.expect("Couldn't convert to string.");
assert_eq!(paths, expected);
});
Ok(())
}
#[test]
fn nu_paths_have_higher_priority_and_new_paths_get_appended_to_the_end(
) -> Result<(), ShellError> {
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"),
PathBuf::from("/Users/mosqueteros/bin"),
PathBuf::from("/path/to/be/added"),
])
.expect("Couldn't join paths.")
.into_string()
.expect("Couldn't convert to string.");
Playground::setup("syncs_path_test_2", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
path = ["/Users/andresrobalino/.volta/bin", "/Users/mosqueteros/bin"]
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let mut actual = EnvironmentSyncer::new();
actual.set_config(Box::new(fake_config));
actual.clear_path_var(&mut ctx);
ctx.with_host(|test_host| {
test_host.env_set(
std::ffi::OsString::from("PATH"),
std::env::join_paths(vec![PathBuf::from("/path/to/be/added")])
.expect("Couldn't join paths."),
)
});
actual.load_environment();
actual.sync_path_vars(&mut ctx);
ctx.with_host(|test_host| {
let actual = test_host
.env_get(std::ffi::OsString::from("PATH"))
.expect("Couldn't get PATH var from host.")
.into_string()
.expect("Couldn't convert to string.");
assert_eq!(actual, expected);
});
let environment = actual.env.lock();
let paths = std::env::join_paths(
&environment
.path()
.expect("No path variable in the environment.")
.table_entries()
.map(|value| value.as_string().expect("Couldn't convert to string"))
.map(PathBuf::from)
.collect::<Vec<_>>(),
)
.expect("Couldn't join paths.")
.into_string()
.expect("Couldn't convert to string.");
assert_eq!(paths, expected);
});
Ok(())
}
}

View File

@ -1,6 +0,0 @@
use crate::prelude::*;
use nu_errors::ShellError;
pub(crate) trait RenderView {
fn render_view(&self, host: &mut dyn Host) -> Result<(), ShellError>;
}

View File

@ -1,38 +1,43 @@
use rustyline::{KeyCode as RustyKeyCode, Modifiers};
use serde::{Deserialize, Serialize};
fn convert_keypress(keypress: KeyPress) -> rustyline::KeyPress {
match keypress {
KeyPress::UnknownEscSeq => rustyline::KeyPress::UnknownEscSeq,
KeyPress::Backspace => rustyline::KeyPress::Backspace,
KeyPress::BackTab => rustyline::KeyPress::BackTab,
KeyPress::BracketedPasteStart => rustyline::KeyPress::BracketedPasteStart,
KeyPress::BracketedPasteEnd => rustyline::KeyPress::BracketedPasteEnd,
KeyPress::Char(c) => rustyline::KeyPress::Char(c),
KeyPress::ControlDown => rustyline::KeyPress::ControlDown,
KeyPress::ControlLeft => rustyline::KeyPress::ControlLeft,
KeyPress::ControlRight => rustyline::KeyPress::ControlRight,
KeyPress::ControlUp => rustyline::KeyPress::ControlUp,
KeyPress::Ctrl(c) => rustyline::KeyPress::Ctrl(c),
KeyPress::Delete => rustyline::KeyPress::Delete,
KeyPress::Down => rustyline::KeyPress::Down,
KeyPress::End => rustyline::KeyPress::End,
KeyPress::Enter => rustyline::KeyPress::Enter,
KeyPress::Esc => rustyline::KeyPress::Esc,
KeyPress::F(u) => rustyline::KeyPress::F(u),
KeyPress::Home => rustyline::KeyPress::Home,
KeyPress::Insert => rustyline::KeyPress::Insert,
KeyPress::Left => rustyline::KeyPress::Left,
KeyPress::Meta(c) => rustyline::KeyPress::Meta(c),
KeyPress::Null => rustyline::KeyPress::Null,
KeyPress::PageDown => rustyline::KeyPress::PageDown,
KeyPress::PageUp => rustyline::KeyPress::PageUp,
KeyPress::Right => rustyline::KeyPress::Right,
KeyPress::ShiftDown => rustyline::KeyPress::ShiftDown,
KeyPress::ShiftLeft => rustyline::KeyPress::ShiftLeft,
KeyPress::ShiftRight => rustyline::KeyPress::ShiftRight,
KeyPress::ShiftUp => rustyline::KeyPress::ShiftUp,
KeyPress::Tab => rustyline::KeyPress::Tab,
KeyPress::Up => rustyline::KeyPress::Up,
pub fn convert_keyevent(key_event: KeyCode, modifiers: Option<Modifiers>) -> rustyline::KeyEvent {
match key_event {
KeyCode::UnknownEscSeq => convert_to_rl_keyevent(RustyKeyCode::UnknownEscSeq, modifiers),
KeyCode::Backspace => convert_to_rl_keyevent(RustyKeyCode::Backspace, modifiers),
KeyCode::BackTab => convert_to_rl_keyevent(RustyKeyCode::BackTab, modifiers),
KeyCode::BracketedPasteStart => {
convert_to_rl_keyevent(RustyKeyCode::BracketedPasteStart, modifiers)
}
KeyCode::BracketedPasteEnd => {
convert_to_rl_keyevent(RustyKeyCode::BracketedPasteEnd, modifiers)
}
KeyCode::Char(c) => convert_to_rl_keyevent(RustyKeyCode::Char(c), modifiers),
KeyCode::Delete => convert_to_rl_keyevent(RustyKeyCode::Delete, modifiers),
KeyCode::Down => convert_to_rl_keyevent(RustyKeyCode::Down, modifiers),
KeyCode::End => convert_to_rl_keyevent(RustyKeyCode::End, modifiers),
KeyCode::Enter => convert_to_rl_keyevent(RustyKeyCode::Enter, modifiers),
KeyCode::Esc => convert_to_rl_keyevent(RustyKeyCode::Esc, modifiers),
KeyCode::F(u) => convert_to_rl_keyevent(RustyKeyCode::F(u), modifiers),
KeyCode::Home => convert_to_rl_keyevent(RustyKeyCode::Home, modifiers),
KeyCode::Insert => convert_to_rl_keyevent(RustyKeyCode::Insert, modifiers),
KeyCode::Left => convert_to_rl_keyevent(RustyKeyCode::Left, modifiers),
KeyCode::Null => convert_to_rl_keyevent(RustyKeyCode::Null, modifiers),
KeyCode::PageDown => convert_to_rl_keyevent(RustyKeyCode::PageDown, modifiers),
KeyCode::PageUp => convert_to_rl_keyevent(RustyKeyCode::PageUp, modifiers),
KeyCode::Right => convert_to_rl_keyevent(RustyKeyCode::Right, modifiers),
KeyCode::Tab => convert_to_rl_keyevent(RustyKeyCode::Tab, modifiers),
KeyCode::Up => convert_to_rl_keyevent(RustyKeyCode::Up, modifiers),
}
}
fn convert_to_rl_keyevent(
key_code: RustyKeyCode,
modifier: Option<Modifiers>,
) -> rustyline::KeyEvent {
rustyline::KeyEvent {
0: key_code,
1: modifier.unwrap_or(Modifiers::NONE),
}
}
@ -97,19 +102,23 @@ fn convert_cmd(cmd: Cmd) -> rustyline::Cmd {
match cmd {
Cmd::Abort => rustyline::Cmd::Abort,
Cmd::AcceptLine => rustyline::Cmd::AcceptLine,
Cmd::AcceptOrInsertLine => rustyline::Cmd::AcceptOrInsertLine,
Cmd::AcceptOrInsertLine => rustyline::Cmd::AcceptOrInsertLine {
accept_in_the_middle: false,
},
Cmd::BeginningOfHistory => rustyline::Cmd::BeginningOfHistory,
Cmd::CapitalizeWord => rustyline::Cmd::CapitalizeWord,
Cmd::ClearScreen => rustyline::Cmd::ClearScreen,
Cmd::Complete => rustyline::Cmd::Complete,
Cmd::CompleteBackward => rustyline::Cmd::CompleteBackward,
Cmd::CompleteHint => rustyline::Cmd::CompleteHint,
Cmd::Dedent(movement) => rustyline::Cmd::Dedent(convert_movement(movement)),
Cmd::DowncaseWord => rustyline::Cmd::DowncaseWord,
Cmd::EndOfFile => rustyline::Cmd::EndOfFile,
Cmd::EndOfHistory => rustyline::Cmd::EndOfHistory,
Cmd::ForwardSearchHistory => rustyline::Cmd::ForwardSearchHistory,
Cmd::HistorySearchBackward => rustyline::Cmd::HistorySearchBackward,
Cmd::HistorySearchForward => rustyline::Cmd::HistorySearchForward,
Cmd::Indent(movement) => rustyline::Cmd::Indent(convert_movement(movement)),
Cmd::Insert { repeat, string } => rustyline::Cmd::Insert(repeat, string),
Cmd::Interrupt => rustyline::Cmd::Interrupt,
Cmd::Kill(movement) => rustyline::Cmd::Kill(convert_movement(movement)),
@ -117,8 +126,11 @@ fn convert_cmd(cmd: Cmd) -> rustyline::Cmd {
Cmd::LineUpOrPreviousHistory(u) => rustyline::Cmd::LineUpOrPreviousHistory(u),
Cmd::Move(movement) => rustyline::Cmd::Move(convert_movement(movement)),
Cmd::NextHistory => rustyline::Cmd::NextHistory,
Cmd::Newline => rustyline::Cmd::Newline,
Cmd::Noop => rustyline::Cmd::Noop,
Cmd::Overwrite(c) => rustyline::Cmd::Overwrite(c),
#[cfg(windows)]
Cmd::PasteFromClipboard => rustyline::Cmd::PasteFromClipboard,
Cmd::PreviousHistory => rustyline::Cmd::PreviousHistory,
Cmd::QuotedInsert => rustyline::Cmd::QuotedInsert,
Cmd::Replace {
@ -140,18 +152,32 @@ fn convert_cmd(cmd: Cmd) -> rustyline::Cmd {
}
}
fn convert_keybinding(keybinding: Keybinding) -> (rustyline::KeyPress, rustyline::Cmd) {
fn convert_keybinding(keybinding: Keybinding) -> (rustyline::KeyEvent, rustyline::Cmd) {
let rusty_modifiers = match keybinding.modifiers {
Some(mods) => match mods {
NuModifiers::Ctrl => Some(Modifiers::CTRL),
NuModifiers::Alt => Some(Modifiers::ALT),
NuModifiers::Shift => Some(Modifiers::SHIFT),
NuModifiers::None => Some(Modifiers::NONE),
NuModifiers::CtrlShift => Some(Modifiers::CTRL_SHIFT),
NuModifiers::AltShift => Some(Modifiers::ALT_SHIFT),
NuModifiers::CtrlAlt => Some(Modifiers::CTRL_ALT),
NuModifiers::CtrlAltShift => Some(Modifiers::CTRL_ALT_SHIFT),
// _ => None,
},
None => None,
};
(
convert_keypress(keybinding.key),
convert_keyevent(keybinding.key, rusty_modifiers),
convert_cmd(keybinding.binding),
)
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum KeyPress {
pub enum KeyCode {
/// Unsupported escape sequence (on unix platform)
UnknownEscSeq,
/// ⌫ or `KeyPress::Ctrl('H')`
/// ⌫ or `KeyEvent::Ctrl('H')`
Backspace,
/// ⇤ (usually Shift-Tab)
BackTab,
@ -161,25 +187,15 @@ pub enum KeyPress {
BracketedPasteEnd,
/// Single char
Char(char),
/// Ctrl-↓
ControlDown,
/// Ctrl-←
ControlLeft,
/// Ctrl-→
ControlRight,
/// Ctrl-↑
ControlUp,
/// Ctrl-char
Ctrl(char),
/// ⌦
Delete,
/// ↓ arrow key
Down,
/// ⇲
End,
/// ↵ or `KeyPress::Ctrl('M')`
/// ↵ or `KeyEvent::Ctrl('M')`
Enter,
/// Escape or `KeyPress::Ctrl('[')`
/// Escape or `KeyEvent::Ctrl('[')`
Esc,
/// Function key
F(u8),
@ -189,9 +205,7 @@ pub enum KeyPress {
Insert,
/// ← arrow key
Left,
/// Escape-char or Alt-char
Meta(char),
/// `KeyPress::Char('\0')`
// /// `KeyEvent::Char('\0')`
Null,
/// ⇟
PageDown,
@ -199,15 +213,7 @@ pub enum KeyPress {
PageUp,
/// → arrow key
Right,
/// Shift-↓
ShiftDown,
/// Shift-←
ShiftLeft,
/// Shift-→
ShiftRight,
/// Shift-↑
ShiftUp,
/// ⇥ or `KeyPress::Ctrl('I')`
/// ⇥ or `KeyEvent::Ctrl('I')`
Tab,
/// ↑ arrow key
Up,
@ -231,6 +237,8 @@ pub enum Cmd {
CompleteBackward,
/// complete-hint
CompleteHint,
/// Dedent current line
Dedent(Movement),
/// downcase-word
DowncaseWord,
/// vi-eof-maybe
@ -243,6 +251,8 @@ pub enum Cmd {
HistorySearchBackward,
/// history-search-forward
HistorySearchForward,
/// Indent current line
Indent(Movement),
/// Insert text
Insert { repeat: RepeatCount, string: String },
/// Interrupt signal (Ctrl-C)
@ -255,12 +265,17 @@ pub enum Cmd {
/// forward-char, forward-word, vi-char-search, vi-end-word, vi-next-word,
/// vi-prev-word
Move(Movement),
/// Inserts a newline
Newline,
/// next-history
NextHistory,
/// No action
Noop,
/// vi-replace
Overwrite(char),
/// Paste from the clipboard
#[cfg(windows)]
PasteFromClipboard,
/// previous-history
PreviousHistory,
/// quoted-insert
@ -394,12 +409,43 @@ pub enum CharSearch {
BackwardAfter(char),
}
/// The set of modifier keys that were triggered along with a key press.
#[derive(Debug, Serialize, Deserialize)]
#[allow(non_camel_case_types)]
pub enum NuModifiers {
/// Control modifier
#[serde(alias = "CTRL")]
Ctrl = 8,
/// Escape or Alt modifier
#[serde(alias = "ALT")]
Alt = 4,
/// Shift modifier
#[serde(alias = "SHIFT")]
Shift = 2,
/// No modifier
#[serde(alias = "NONE")]
None = 0,
/// Ctrl + Shift
#[serde(alias = "CTRL_SHIFT")]
CtrlShift = 10,
/// Alt + Shift
#[serde(alias = "ALT_SHIFT")]
AltShift = 6,
/// Ctrl + Alt
#[serde(alias = "CTRL_ALT")]
CtrlAlt = 12,
/// Ctrl + Alt + Shift
#[serde(alias = "CTRL_ALT_SHIFT")]
CtrlAltShift = 14,
}
/// The number of times one command should be repeated.
pub type RepeatCount = usize;
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Debug, Serialize, Deserialize)]
pub struct Keybinding {
key: KeyPress,
key: KeyCode,
modifiers: Option<NuModifiers>,
binding: Cmd,
}
@ -414,9 +460,10 @@ pub(crate) fn load_keybindings(
// Silently fail if there is no file there
if let Ok(contents) = contents {
let keybindings: Keybindings = serde_yaml::from_str(&contents)?;
for keybinding in keybindings.into_iter() {
// eprintln!("{:#?}", keybindings);
for keybinding in keybindings {
let (k, b) = convert_keybinding(keybinding);
// eprintln!("{:?} {:?}", k, b);
rl.bind_sequence(k, b);
}

View File

@ -1,43 +1,15 @@
#![recursion_limit = "2048"]
#[cfg(test)]
#[macro_use]
extern crate indexmap;
#[macro_use]
mod prelude;
#[cfg(test)]
extern crate quickcheck;
#[cfg(test)]
#[macro_use(quickcheck)]
extern crate quickcheck_macros;
pub mod app;
mod cli;
#[cfg(feature = "rustyline-support")]
mod completion;
mod env;
mod format;
#[cfg(feature = "rustyline-support")]
mod keybinding;
mod line_editor;
#[cfg(feature = "rustyline-support")]
mod shell;
pub mod types;
#[cfg(feature = "rustyline-support")]
pub use crate::cli::cli;
pub use crate::app::App;
pub use crate::cli::{parse_and_eval, register_plugins, run_script_file};
pub use crate::env::environment_syncer::EnvironmentSyncer;
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;
// TODO: Temporary redirect
pub use nu_protocol::{did_you_mean, TaggedDictBuilder};
pub use nu_command::create_default_context;

View File

@ -1,11 +1,15 @@
use nu_engine::EvaluationContext;
use nu_errors::ShellError;
use std::error::Error;
#[allow(unused_imports)]
use crate::prelude::*;
use std::sync::atomic::Ordering;
#[allow(unused_imports)]
use nu_command::script::LineResult;
use nu_engine::script::LineResult;
#[cfg(feature = "rustyline-support")]
use crate::keybinding::{convert_keyevent, KeyCode};
#[cfg(feature = "rustyline-support")]
use crate::shell::Helper;
@ -16,7 +20,8 @@ use rustyline::{
config::Configurer,
config::{ColorMode, CompletionType, Config},
error::ReadlineError,
At, Cmd, Editor, KeyPress, Movement, Word,
line_buffer::LineBuffer,
At, Cmd, ConditionalEventHandler, Editor, EventHandler, Modifiers, Movement, Word,
};
#[cfg(feature = "rustyline-support")]
@ -27,12 +32,40 @@ pub fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>)
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
Err(ReadlineError::Eof) => LineResult::CtrlD,
Err(err) => {
outln!("Error: {:?}", err);
eprintln!("Error: {:?}", err);
LineResult::Break
}
}
}
#[derive(Clone)]
#[cfg(feature = "rustyline-support")]
struct PartialCompleteHintHandler;
#[cfg(feature = "rustyline-support")]
impl ConditionalEventHandler for PartialCompleteHintHandler {
fn handle(
&self,
_evt: &rustyline::Event,
_n: rustyline::RepeatCount,
_positive: bool,
ctx: &rustyline::EventContext,
) -> Option<Cmd> {
Some(match ctx.hint_text() {
Some(hint_text) if ctx.pos() == ctx.line().len() => {
let mut line_buffer = LineBuffer::with_capacity(hint_text.len());
line_buffer.update(hint_text, 0);
line_buffer.move_to_next_word(At::AfterEnd, Word::Vi, 1);
let text = hint_text[0..line_buffer.pos()].to_string();
Cmd::Insert(1, text)
}
_ => Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
})
}
}
#[cfg(feature = "rustyline-support")]
pub fn default_rustyline_editor_configuration() -> Editor<Helper> {
#[cfg(windows)]
@ -40,22 +73,31 @@ pub fn default_rustyline_editor_configuration() -> Editor<Helper> {
#[cfg(not(windows))]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
let config = Config::builder().color_mode(ColorMode::Forced).build();
let config = Config::builder()
.check_cursor_position(true)
.color_mode(ColorMode::Forced)
.history_ignore_dups(false)
.max_history_size(10_000)
.build();
let mut rl: Editor<_> = Editor::with_config(config);
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
//M modifier, E KeyEvent, K KeyCode
rl.bind_sequence(
KeyPress::ControlLeft,
convert_keyevent(KeyCode::Left, Some(Modifiers::CTRL)),
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
);
rl.bind_sequence(
KeyPress::ControlRight,
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
convert_keyevent(KeyCode::Right, Some(Modifiers::CTRL)),
EventHandler::Conditional(Box::new(PartialCompleteHintHandler)),
);
// workaround for multiline-paste hang in rustyline (see https://github.com/kkawakam/rustyline/issues/202)
rl.bind_sequence(KeyPress::BracketedPasteStart, rustyline::Cmd::Noop);
rl.bind_sequence(
convert_keyevent(KeyCode::BracketedPasteStart, None),
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);
@ -194,7 +236,7 @@ pub fn configure_rustyline_editor(
#[cfg(feature = "rustyline-support")]
pub fn nu_line_editor_helper(
context: &mut EvaluationContext,
context: &EvaluationContext,
config: &dyn nu_data::config::Conf,
) -> crate::shell::Helper {
let hinter = rustyline_hinter(config);
@ -207,7 +249,7 @@ pub fn rustyline_hinter(
) -> 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" {
if idx == "show_hints" && value.as_bool() == Ok(false) {
return None;
}
}
@ -216,17 +258,17 @@ pub fn rustyline_hinter(
Some(rustyline::hint::HistoryHinter {})
}
pub fn configure_ctrl_c(_context: &mut EvaluationContext) -> Result<(), Box<dyn Error>> {
pub fn configure_ctrl_c(_context: &EvaluationContext) -> Result<(), Box<dyn Error>> {
#[cfg(feature = "ctrlc")]
{
let cc = _context.ctrl_c.clone();
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);
if _context.ctrl_c().load(Ordering::SeqCst) {
_context.ctrl_c().store(false, Ordering::SeqCst);
}
}

View File

@ -1,90 +0,0 @@
#[macro_export]
macro_rules! return_err {
($expr:expr) => {
match $expr {
Err(_) => return,
Ok(expr) => expr,
};
};
}
#[macro_export]
macro_rules! stream {
($($expr:expr),*) => {{
let mut v = VecDeque::new();
$(
v.push_back($expr);
)*
v
}}
}
#[macro_export]
macro_rules! trace_out_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,
match o {
Err(err) => format!("{:?}", err),
Ok(value) => value.display(),
}
);
});
nu_stream::OutputStream::new(objects)
} else {
$expr
}
}};
}
pub(crate) use futures::{Stream, StreamExt};
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;
#[allow(unused_imports)]
pub(crate) use std::sync::atomic::Ordering;
pub trait FromInputStream {
fn from_input_stream(self) -> OutputStream;
}
impl<T> FromInputStream for T
where
T: Stream<Item = nu_protocol::Value> + Send + 'static,
{
fn from_input_stream(self) -> OutputStream {
OutputStream {
values: self.map(nu_protocol::ReturnSuccess::value).boxed(),
}
}
}
pub trait ToOutputStream {
fn to_output_stream(self) -> OutputStream;
}
impl<T, U> ToOutputStream for T
where
T: Stream<Item = U> + Send + 'static,
U: Into<nu_protocol::ReturnValue>,
{
fn to_output_stream(self) -> OutputStream {
OutputStream {
values: self.map(|item| item.into()).boxed(),
}
}
}

View File

@ -1,9 +1,234 @@
#![allow(clippy::module_inception)]
use nu_ansi_term::Color;
use nu_completion::NuCompleter;
use nu_engine::{DefaultPalette, EvaluationContext, Painter};
use nu_source::{Tag, Tagged};
use std::borrow::Cow::{self, Owned};
#[cfg(feature = "rustyline-support")]
pub(crate) mod completer;
#[cfg(feature = "rustyline-support")]
pub(crate) mod helper;
pub struct Helper {
completer: NuCompleter,
hinter: Option<rustyline::hint::HistoryHinter>,
context: EvaluationContext,
pub colored_prompt: String,
validator: NuValidator,
}
#[cfg(feature = "rustyline-support")]
pub(crate) use helper::Helper;
impl Helper {
pub(crate) fn new(
context: EvaluationContext,
hinter: Option<rustyline::hint::HistoryHinter>,
) -> Helper {
Helper {
completer: NuCompleter {},
hinter,
context,
colored_prompt: String::new(),
validator: NuValidator {},
}
}
}
use nu_protocol::{SignatureRegistry, VariableRegistry};
struct CompletionContext<'a>(&'a EvaluationContext);
impl<'a> nu_completion::CompletionContext for CompletionContext<'a> {
fn signature_registry(&self) -> &dyn SignatureRegistry {
&self.0.scope
}
fn scope(&self) -> &dyn nu_parser::ParserScope {
&self.0.scope
}
fn source(&self) -> &EvaluationContext {
self.as_ref()
}
fn variable_registry(&self) -> &dyn VariableRegistry {
self.0
}
}
impl<'a> AsRef<EvaluationContext> for CompletionContext<'a> {
fn as_ref(&self) -> &EvaluationContext {
self.0
}
}
pub struct CompletionSuggestion(nu_completion::Suggestion);
impl rustyline::completion::Candidate for CompletionSuggestion {
fn display(&self) -> &str {
&self.0.display
}
fn replacement(&self) -> &str {
&self.0.replacement
}
}
impl rustyline::completion::Completer for Helper {
type Candidate = CompletionSuggestion;
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<Self::Candidate>), rustyline::error::ReadlineError> {
let ctx = CompletionContext(&self.context);
let (position, suggestions) = self.completer.complete(line, pos, &ctx);
let suggestions = suggestions.into_iter().map(CompletionSuggestion).collect();
Ok((position, suggestions))
}
fn update(&self, line: &mut rustyline::line_buffer::LineBuffer, start: usize, elected: &str) {
let end = line.pos();
line.replace(start..end, elected)
}
}
impl rustyline::hint::Hinter for Helper {
type Hint = String;
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
match &self.hinter {
Some(the_hinter) => the_hinter.hint(line, pos, ctx),
None => Some("".to_string()),
}
}
}
impl rustyline::highlight::Highlighter for Helper {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
default: bool,
) -> Cow<'b, str> {
use std::borrow::Cow::Borrowed;
if default {
Borrowed(&self.colored_prompt)
} else {
Borrowed(prompt)
}
}
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
Owned(Color::DarkGray.prefix().to_string() + hint + nu_ansi_term::ansi::RESET)
}
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
let cfg = &self.context.configs().lock();
if let Some(palette) = &cfg.syntax_config {
Painter::paint_string(line, &self.context.scope, palette)
} else {
Painter::paint_string(line, &self.context.scope, &DefaultPalette {})
}
}
fn highlight_char(&self, _line: &str, _pos: usize) -> bool {
true
}
}
impl rustyline::validate::Validator for Helper {
fn validate(
&self,
ctx: &mut rustyline::validate::ValidationContext,
) -> rustyline::Result<rustyline::validate::ValidationResult> {
self.validator.validate(ctx)
}
fn validate_while_typing(&self) -> bool {
self.validator.validate_while_typing()
}
}
struct NuValidator {}
impl rustyline::validate::Validator for NuValidator {
fn validate(
&self,
ctx: &mut rustyline::validate::ValidationContext,
) -> rustyline::Result<rustyline::validate::ValidationResult> {
let src = ctx.input();
let (tokens, err) = nu_parser::lex(src, 0, nu_parser::NewlineMode::Normal);
if let Some(err) = err {
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
return Ok(rustyline::validate::ValidationResult::Incomplete);
}
}
let (_, err) = nu_parser::parse_block(tokens);
if let Some(err) = err {
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
return Ok(rustyline::validate::ValidationResult::Incomplete);
}
}
Ok(rustyline::validate::ValidationResult::Valid(None))
}
}
#[allow(unused)]
fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
let mut iter = input.iter();
let first = iter.next()?.tag.clone();
let last = iter.last();
Some(match last {
None => first,
Some(last) => first.until(&last.tag),
})
}
impl rustyline::Helper for Helper {}
#[cfg(test)]
mod tests {
use super::*;
use nu_engine::EvaluationContext;
use rustyline::completion::Completer;
use rustyline::line_buffer::LineBuffer;
#[ignore]
#[test]
fn closing_quote_should_replaced() {
let text = "cd \"folder with spaces\\subdirectory\\\"";
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
let mut buffer = LineBuffer::with_capacity(256);
buffer.insert_str(0, text);
buffer.set_pos(text.len() - 1);
let helper = Helper::new(EvaluationContext::basic(), None);
helper.update(&mut buffer, "cd ".len(), replacement);
assert_eq!(
buffer.as_str(),
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
);
}
#[ignore]
#[test]
fn replacement_with_cursor_in_text() {
let text = "cd \"folder with spaces\\subdirectory\\\"";
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
let mut buffer = LineBuffer::with_capacity(256);
buffer.insert_str(0, text);
buffer.set_pos(text.len() - 30);
let helper = Helper::new(EvaluationContext::basic(), None);
helper.update(&mut buffer, "cd ".len(), replacement);
assert_eq!(
buffer.as_str(),
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
);
}
}

View File

@ -1,194 +0,0 @@
use crate::completion;
use crate::shell::completer::NuCompleter;
use nu_engine::{DefaultPalette, EvaluationContext, Painter};
use nu_source::{Tag, Tagged};
use std::borrow::Cow::{self, Owned};
pub struct Helper {
completer: NuCompleter,
hinter: Option<rustyline::hint::HistoryHinter>,
context: EvaluationContext,
pub colored_prompt: String,
validator: NuValidator,
}
impl Helper {
pub(crate) fn new(
context: EvaluationContext,
hinter: Option<rustyline::hint::HistoryHinter>,
) -> Helper {
Helper {
completer: NuCompleter {},
hinter,
context,
colored_prompt: String::new(),
validator: NuValidator {},
}
}
}
impl rustyline::completion::Candidate for completion::Suggestion {
fn display(&self) -> &str {
&self.display
}
fn replacement(&self) -> &str {
&self.replacement
}
}
impl rustyline::completion::Completer for Helper {
type Candidate = completion::Suggestion;
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<Self::Candidate>), rustyline::error::ReadlineError> {
let ctx = completion::CompletionContext::new(&self.context);
Ok(self.completer.complete(line, pos, &ctx))
}
fn update(&self, line: &mut rustyline::line_buffer::LineBuffer, start: usize, elected: &str) {
let end = line.pos();
line.replace(start..end, elected)
}
}
impl rustyline::hint::Hinter for Helper {
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
self.hinter.as_ref().and_then(|h| h.hint(line, pos, &ctx))
}
}
impl rustyline::highlight::Highlighter for Helper {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
default: bool,
) -> Cow<'b, str> {
use std::borrow::Cow::Borrowed;
if default {
Borrowed(&self.colored_prompt)
} else {
Borrowed(prompt)
}
}
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
Owned("\x1b[1m".to_owned() + hint + "\x1b[m")
}
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
Painter::paint_string(line, &self.context.scope, &DefaultPalette {})
}
fn highlight_char(&self, _line: &str, _pos: usize) -> bool {
true
}
}
impl rustyline::validate::Validator for Helper {
fn validate(
&self,
ctx: &mut rustyline::validate::ValidationContext,
) -> rustyline::Result<rustyline::validate::ValidationResult> {
self.validator.validate(ctx)
}
fn validate_while_typing(&self) -> bool {
self.validator.validate_while_typing()
}
}
struct NuValidator {}
impl rustyline::validate::Validator for NuValidator {
fn validate(
&self,
ctx: &mut rustyline::validate::ValidationContext,
) -> rustyline::Result<rustyline::validate::ValidationResult> {
let src = ctx.input();
let (tokens, err) = nu_parser::lex(src, 0);
if let Some(err) = err {
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
return Ok(rustyline::validate::ValidationResult::Incomplete);
}
}
let (_, err) = nu_parser::block(tokens);
if let Some(err) = err {
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
return Ok(rustyline::validate::ValidationResult::Incomplete);
}
}
Ok(rustyline::validate::ValidationResult::Valid(None))
}
}
#[allow(unused)]
fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
let mut iter = input.iter();
let first = iter.next()?.tag.clone();
let last = iter.last();
Some(match last {
None => first,
Some(last) => first.until(&last.tag),
})
}
impl rustyline::Helper for Helper {}
#[cfg(test)]
mod tests {
use super::*;
use nu_engine::basic_evaluation_context;
use rustyline::completion::Completer;
use rustyline::line_buffer::LineBuffer;
#[ignore]
#[test]
fn closing_quote_should_replaced() {
let text = "cd \"folder with spaces\\subdirectory\\\"";
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
let mut buffer = LineBuffer::with_capacity(256);
buffer.insert_str(0, text);
buffer.set_pos(text.len() - 1);
let helper = Helper::new(basic_evaluation_context().unwrap(), None);
helper.update(&mut buffer, "cd ".len(), &replacement);
assert_eq!(
buffer.as_str(),
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
);
}
#[ignore]
#[test]
fn replacement_with_cursor_in_text() {
let text = "cd \"folder with spaces\\subdirectory\\\"";
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
let mut buffer = LineBuffer::with_capacity(256);
buffer.insert_str(0, text);
buffer.set_pos(text.len() - 30);
let helper = Helper::new(basic_evaluation_context().unwrap(), None);
helper.update(&mut buffer, "cd ".len(), &replacement);
assert_eq!(
buffer.as_str(),
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
);
}
}

View File

@ -5,128 +5,118 @@ description = "CLI for nushell"
edition = "2018"
license = "MIT"
name = "nu-command"
version = "0.26.0"
version = "0.41.0"
[lib]
doctest = false
[dependencies]
nu-data = { version = "0.26.0", path = "../nu-data" }
nu-engine = { version = "0.26.0", path = "../nu-engine" }
nu-errors = { version = "0.26.0", path = "../nu-errors" }
nu-json = { version = "0.26.0", path = "../nu-json" }
nu-parser = { version = "0.26.0", path = "../nu-parser" }
nu-plugin = { version = "0.26.0", path = "../nu-plugin" }
nu-protocol = { version = "0.26.0", path = "../nu-protocol" }
nu-source = { version = "0.26.0", path = "../nu-source" }
nu-stream = { version = "0.26.0", path = "../nu-stream" }
nu-table = { version = "0.26.0", path = "../nu-table" }
nu-test-support = { version = "0.26.0", path = "../nu-test-support" }
nu-value-ext = { version = "0.26.0", path = "../nu-value-ext" }
nu-data = { version = "0.41.0", path="../nu-data" }
nu-engine = { version = "0.41.0", path="../nu-engine" }
nu-errors = { version = "0.41.0", path="../nu-errors" }
nu-json = { version = "0.41.0", path="../nu-json" }
nu-path = { version = "0.41.0", path="../nu-path" }
nu-parser = { version = "0.41.0", path="../nu-parser" }
nu-plugin = { version = "0.41.0", path="../nu-plugin" }
nu-protocol = { version = "0.41.0", path="../nu-protocol" }
nu-serde = { version = "0.41.0", path="../nu-serde" }
nu-source = { version = "0.41.0", path="../nu-source" }
nu-stream = { version = "0.41.0", path="../nu-stream" }
nu-table = { version = "0.41.0", path="../nu-table" }
nu-test-support = { version = "0.41.0", path="../nu-test-support" }
nu-value-ext = { version = "0.41.0", path="../nu-value-ext" }
nu-ansi-term = { version = "0.41.0", path="../nu-ansi-term" }
nu-pretty-hex = { version = "0.41.0", path="../nu-pretty-hex" }
url = "2.2.1"
mime = "0.3.16"
Inflector = "0.11"
ansi_term = "0.12.1"
arboard = { version = "1.1.0", optional = true }
async-recursion = "0.3.1"
async-trait = "0.1.40"
base64 = "0.13.0"
bigdecimal = { version = "0.2.0", features = ["serde"] }
byte-unit = "4.0.9"
bytes = "0.5.6"
calamine = "0.16.1"
chrono = { version = "0.4.15", features = ["serde"] }
bigdecimal = { version = "0.3.0", features = ["serde"] }
calamine = "0.18.0"
chrono = { version="0.4.19", features=["serde"] }
chrono-tz = "0.5.3"
clap = "2.33.3"
codespan-reporting = "0.11.0"
crossterm = { version="0.19.0", optional=true }
csv = "1.1.3"
ctrlc = { version = "3.1.6", optional = true }
ctrlc = { version="3.1.7", optional=true }
derive-new = "0.5.8"
directories-next = { version = "2.0.0", optional = true }
dirs-next = { version = "2.0.0", optional = true }
dirs-next = "2.0.0"
dtparse = "1.2.0"
dunce = "1.0.1"
eml-parser = "0.1.0"
encoding_rs = "0.8.24"
encoding_rs = "0.8.28"
filesize = "0.2.0"
fs_extra = "1.2.0"
futures = { version = "0.3.5", features = ["compat", "io-compat"] }
futures-util = "0.3.8"
futures_codec = "0.4.1"
getset = "0.1.1"
futures = { version="0.3.12", features=["compat", "io-compat"] }
glob = "0.3.0"
htmlescape = "0.3.1"
ical = "0.7.0"
ichwh = { version = "0.3.4", optional = true }
indexmap = { version = "1.6.0", features = ["serde-1"] }
indexmap = { version="1.7", features=["serde-1"] }
itertools = "0.10.0"
lazy_static = "1.*"
log = "0.4.11"
log = "0.4.14"
md-5 = "0.9.1"
meval = "0.2.0"
num-bigint = { version = "0.3.0", features = ["serde"] }
num-format = { version = "0.4.0", features = ["with-num-bigint"] }
num-traits = "0.2.12"
parking_lot = "0.11.0"
pin-utils = "0.1.0"
pretty-hex = "0.2.0"
ptree = { version = "0.3.0", optional = true }
query_interface = "0.3.5"
quick-xml = "0.20.0"
rand = "0.7.3"
rayon = "1.4.0"
regex = "1.3.9"
num-bigint = { version="0.4.3", features=["serde"] }
num-format = { version="0.4.0", features=["with-num-bigint"] }
num-traits = "0.2.14"
parking_lot = "0.11.1"
quick-xml = "0.22"
rand = "0.8"
regex = "1.4.3"
reqwest = {version = "0.11", optional = true }
roxmltree = "0.14.0"
rust-embed = "5.8.0"
rustyline = { version = "6.3.0", optional = true }
serde = { version = "1.0.115", features = ["derive"] }
serde_bytes = "0.11.5"
rust-embed = "5.9.0"
rustyline = { version="9.0.0", optional=true }
serde = { version="1.0.123", features=["derive"] }
serde_ini = "0.2.0"
serde_json = "1.0.57"
serde_json = "1.0.61"
serde_urlencoded = "0.7.0"
serde_yaml = "0.8.13"
sha2 = "0.9.1"
shellexpand = "2.0.0"
serde_yaml = "0.8.16"
sha2 = "0.9.3"
strip-ansi-escapes = "0.1.0"
sxd-document = "0.3.2"
sxd-xpath = "0.4.2"
tempfile = "3.1.0"
term = { version = "0.6.1", optional = true }
sysinfo = { version = "0.21.1", optional = true }
thiserror = "1.0.26"
term = { version="0.7.0", optional=true }
term_size = "0.3.2"
termcolor = "1.1.0"
titlecase = "1.0"
toml = "0.5.6"
trash = { version = "1.2.0", optional = true }
unicode-segmentation = "1.6.0"
url = "2.1.1"
uuid_crate = { package = "uuid", version = "0.8.1", features = ["v4"], optional = true }
which = { version = "4.0.2", optional = true }
zip = { version = "0.5.7", optional = true }
titlecase = "1.1.0"
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
toml = "0.5.8"
trash = { version = "2.0.2", optional = true }
unicode-segmentation = "1.8"
uuid_crate = { package="uuid", version="0.8.2", features=["v4"], optional=true }
which = { version="4.1.0", optional=true }
zip = { version="0.5.9", optional=true }
digest = "0.9.0"
[dependencies.polars]
version = "0.17.0"
optional = true
default-features = false
features = ["docs", "zip_with", "csv-file", "temporal", "performant", "pretty_fmt", "dtype-slim", "parquet", "json", "random", "pivot", "strings", "is_in", "cum_agg", "rolling_window"]
[target.'cfg(unix)'.dependencies]
umask = "1.0.0"
users = "0.10.0"
users = "0.11.0"
# TODO this will be possible with new dependency resolver
# (currently on nightly behind -Zfeatures=itarget):
# https://github.com/rust-lang/cargo/issues/7914
#[target.'cfg(not(windows))'.dependencies]
#num-format = {version = "0.4", features = ["with-system-locale"]}
[dependencies.rusqlite]
features = ["bundled", "blob"]
optional = true
version = "0.24.2"
# [target.'cfg(not(windows))'.dependencies]
# num-format = { version = "0.4", features = ["with-system-locale"] }
[build-dependencies]
shadow-rs = "0.5"
shadow-rs = "0.8.1"
[dev-dependencies]
quickcheck = "0.9.2"
quickcheck_macros = "0.9.1"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
hamcrest2 = "0.3.0"
[features]
clipboard-cli = ["arboard"]
rustyline-support = ["rustyline"]
stable = []
trash-support = ["trash"]
directories = ["directories-next"]
dirs = ["dirs-next"]
dataframe = ["nu-protocol/dataframe", "polars"]
fetch = ["reqwest", "tokio"]
post = ["reqwest", "tokio"]
sys = ["sysinfo"]
ps = ["sysinfo"]

View File

@ -0,0 +1,7 @@
# nu-command
The Nu command crate contains the full set of internal commands, that is, the commands that can be form the set of built-in commands in a Nushell engine.
The default set of commands that Nushell ships with can be found in the [default context](src/default_context.rs).
The commands themselves live in the [commands module](src/commands/).

View File

@ -0,0 +1,725 @@
use crate::prelude::*;
use lazy_static::lazy_static;
use nu_engine::{evaluate_baseline_expr, BufCodecReader};
use nu_engine::{MaybeTextCodec, StringOrBinary};
use nu_test_support::NATIVE_PATH_ENV_VAR;
use parking_lot::Mutex;
use regex::Regex;
#[allow(unused)]
use std::env;
use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::sync::mpsc;
use std::{borrow::Cow, io::BufReader};
use log::trace;
use nu_errors::ShellError;
use nu_protocol::hir::Expression;
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
use nu_source::Tag;
#[cfg(feature = "which")]
use which::which_in;
pub(crate) fn run_external_command(
command: ExternalCommand,
context: &mut EvaluationContext,
input: InputStream,
external_redirection: ExternalRedirection,
) -> Result<InputStream, ShellError> {
trace!(target: "nu::run::external", "-> {}", command.name);
context.sync_path_to_env();
if !context.host().lock().is_external_cmd(&command.name) {
return Err(ShellError::labeled_error(
"Command not found",
format!("command {} not found", &command.name),
&command.name_tag,
));
}
run_with_stdin(command, context, input, external_redirection)
}
#[allow(unused)]
fn trim_enclosing_quotes(input: &str) -> String {
let mut chars = input.chars();
match (chars.next(), chars.next_back()) {
(Some('"'), Some('"')) => chars.collect(),
(Some('\''), Some('\'')) => chars.collect(),
_ => input.to_string(),
}
}
fn run_with_stdin(
command: ExternalCommand,
context: &mut EvaluationContext,
input: InputStream,
external_redirection: ExternalRedirection,
) -> Result<InputStream, ShellError> {
let path = context.shell_manager().path();
let mut command_args = vec![];
for arg in command.args.iter() {
let is_literal = matches!(arg.expr, Expression::Literal(_));
let value = evaluate_baseline_expr(arg, context)?;
// Skip any arguments that don't really exist, treating them as optional
// FIXME: we may want to preserve the gap in the future, though it's hard to say
// what value we would put in its place.
if value.value.is_none() {
continue;
}
// Do the cleanup that we need to do on any argument going out:
match &value.value {
UntaggedValue::Table(table) => {
for t in table {
match &t.value {
UntaggedValue::Primitive(_) => {
command_args.push((
t.convert_to_string().trim_end_matches('\n').to_string(),
is_literal,
));
}
_ => {
return Err(ShellError::labeled_error(
"Could not convert to positional arguments",
"could not convert to positional arguments",
value.tag(),
));
}
}
}
}
_ => {
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
//let trimmed_value_string = trim_quotes(&trimmed_value_string);
command_args.push((trimmed_value_string, is_literal));
}
}
}
let process_args = command_args
.iter()
.map(|(arg, _is_literal)| {
let arg = nu_path::expand_tilde(arg).to_string_lossy().to_string();
#[cfg(not(windows))]
{
if !_is_literal {
arg
} else {
trim_enclosing_quotes(&arg)
}
}
#[cfg(windows)]
{
if let Some(unquoted) = remove_quotes(&arg) {
unquoted.to_string()
} else {
arg
}
}
})
.collect::<Vec<String>>();
spawn(
&command,
&path,
&process_args[..],
input,
external_redirection,
&context.scope,
)
}
/// Spawn a direct exe
#[allow(unused)]
fn spawn_exe(full_path: PathBuf, args: &[String]) -> Command {
let mut process = Command::new(full_path);
for arg in args {
process.arg(&arg);
}
process
}
/// Spawn a cmd command with `cmd /c args...`
fn spawn_cmd_command(command: &ExternalCommand, args: &[String]) -> Command {
let mut process = Command::new("cmd");
process.arg("/c");
process.arg(&command.name);
for arg in args {
// Clean the args before we use them:
// https://stackoverflow.com/questions/1200235/how-to-pass-a-quoted-pipe-character-to-cmd-exe
// cmd.exe needs to have a caret to escape a pipe
let arg = arg.replace("|", "^|");
process.arg(&arg);
}
process
}
fn has_unsafe_shell_characters(arg: &str) -> bool {
lazy_static! {
static ref RE: Regex = Regex::new(r"[^\w@%+=:,./-]").expect("regex to be valid");
}
RE.is_match(arg)
}
fn shell_arg_escape(arg: &str) -> String {
match arg {
"" => String::from("''"),
s if !has_unsafe_shell_characters(s) => String::from(s),
_ => {
let single_quotes_escaped = arg.split('\'').join("'\"'\"'");
format!("'{}'", single_quotes_escaped)
}
}
}
/// Spawn a sh command with `sh -c args...`
fn spawn_sh_command(command: &ExternalCommand, args: &[String]) -> Command {
let joined_and_escaped_arguments = args.iter().map(|arg| shell_arg_escape(arg)).join(" ");
let cmd_with_args = vec![command.name.clone(), joined_and_escaped_arguments].join(" ");
let mut process = Command::new("sh");
process.arg("-c").arg(cmd_with_args);
process
}
/// a function to spawn any external command
#[allow(unused)] // for minimal builds cwd is unused
fn spawn_any(command: &ExternalCommand, args: &[String], cwd: &str) -> Command {
// resolve the executable name if it is spawnable directly
#[cfg(feature = "which")]
// TODO add more available paths to `env::var_os("PATH")`?
if let Result::Ok(full_path) = which_in(&command.name, env::var_os("PATH"), cwd) {
if let Some(extension) = full_path.extension() {
#[cfg(windows)]
if extension.eq_ignore_ascii_case("exe") {
// if exe spawn it directly
return spawn_exe(full_path, args);
} else {
// TODO implement special care for various executable types such as .bat, .ps1, .cmd, etc
// https://github.com/mklement0/Native/blob/e0e0b8785cad39a73053e35084d1f60d87fbac58/Native.psm1#L749
// otherwise shell out to cmd
return spawn_cmd_command(command, args);
}
#[cfg(not(windows))]
if !["sh", "bash"]
.iter()
.any(|ext| extension.eq_ignore_ascii_case(ext))
{
// if exe spawn it directly
return spawn_exe(full_path, args);
} else {
// otherwise shell out to sh
return spawn_sh_command(command, args);
}
}
}
// in all the other cases shell out
if cfg!(windows) {
spawn_cmd_command(command, args)
} else {
// TODO what happens if that os doesn't support spawning sh?
spawn_sh_command(command, args)
}
}
fn spawn(
command: &ExternalCommand,
path: &str,
args: &[String],
input: InputStream,
external_redirection: ExternalRedirection,
scope: &Scope,
) -> Result<InputStream, ShellError> {
let command = command.clone();
let mut process = spawn_any(&command, args, path);
process.current_dir(path);
trace!(target: "nu::run::external", "cwd = {:?}", &path);
process.env_clear();
process.envs(scope.get_env_vars());
// We want stdout regardless of what
// we are doing ($it case or pipe stdin)
match external_redirection {
ExternalRedirection::Stdout => {
process.stdout(Stdio::piped());
trace!(target: "nu::run::external", "set up stdout pipe");
}
ExternalRedirection::Stderr => {
process.stderr(Stdio::piped());
trace!(target: "nu::run::external", "set up stderr pipe");
}
ExternalRedirection::StdoutAndStderr => {
process.stdout(Stdio::piped());
trace!(target: "nu::run::external", "set up stdout pipe");
process.stderr(Stdio::piped());
trace!(target: "nu::run::external", "set up stderr pipe");
}
_ => {}
}
// open since we have some contents for stdin
if !input.is_empty() {
process.stdin(Stdio::piped());
trace!(target: "nu::run::external", "set up stdin pipe");
}
trace!(target: "nu::run::external", "built command {:?}", process);
// TODO Switch to async_std::process once it's stabilized
match process.spawn() {
Ok(mut child) => {
let (tx, rx) = mpsc::sync_channel(0);
let mut stdin = child.stdin.take();
let stdin_write_tx = tx.clone();
let stdout_read_tx = tx;
let stdin_name_tag = command.name_tag.clone();
let stdout_name_tag = command.name_tag;
std::thread::spawn(move || {
if !input.is_empty() {
let mut stdin_write = stdin
.take()
.expect("Internal error: could not get stdin pipe for external command");
for value in input {
match &value.value {
UntaggedValue::Primitive(Primitive::Nothing) => continue,
UntaggedValue::Primitive(Primitive::String(s)) => {
if stdin_write.write(s.as_bytes()).is_err() {
// Other side has closed, so exit
return Ok(());
}
}
UntaggedValue::Primitive(Primitive::Binary(b)) => {
if stdin_write.write(b).is_err() {
// Other side has closed, so exit
return Ok(());
}
}
unsupported => {
println!("Unsupported: {:?}", unsupported);
let _ = stdin_write_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
format!(
"Received unexpected type from pipeline ({})",
unsupported.type_name()
),
format!(
"expected a string, got {} as input",
unsupported.type_name()
),
stdin_name_tag.clone(),
)),
tag: stdin_name_tag,
}));
return Err(());
}
};
}
}
Ok(())
});
std::thread::spawn(move || {
if external_redirection == ExternalRedirection::Stdout
|| external_redirection == ExternalRedirection::StdoutAndStderr
{
let stdout = if let Some(stdout) = child.stdout.take() {
stdout
} else {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
"Can't redirect the stdout for external command",
"can't redirect stdout",
&stdout_name_tag,
)),
tag: stdout_name_tag,
}));
return Err(());
};
// let file = futures::io::AllowStdIo::new(stdout);
// let stream = FramedRead::new(file, MaybeTextCodec::default());
let buf_read = BufReader::new(stdout);
let buf_codec = BufCodecReader::new(buf_read, MaybeTextCodec::default());
for line in buf_codec {
match line {
Ok(line) => match line {
StringOrBinary::String(s) => {
let result = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Primitive(Primitive::String(
s.clone(),
)),
tag: stdout_name_tag.clone(),
}));
if result.is_err() {
break;
}
}
StringOrBinary::Binary(b) => {
let result = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Primitive(Primitive::Binary(
b.into_iter().collect(),
)),
tag: stdout_name_tag.clone(),
}));
if result.is_err() {
break;
}
}
},
Err(e) => {
// If there's an exit status, it makes sense that we may error when
// trying to read from its stdout pipe (likely been closed). In that
// case, don't emit an error.
let should_error = match child.wait() {
Ok(exit_status) => !exit_status.success(),
Err(_) => true,
};
if should_error {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
format!("Unable to read from stdout ({})", e),
"unable to read from stdout",
&stdout_name_tag,
)),
tag: stdout_name_tag.clone(),
}));
}
return Ok(());
}
}
}
}
if external_redirection == ExternalRedirection::Stderr
|| external_redirection == ExternalRedirection::StdoutAndStderr
{
let stderr = if let Some(stderr) = child.stderr.take() {
stderr
} else {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
"Can't redirect the stderr for external command",
"can't redirect stderr",
&stdout_name_tag,
)),
tag: stdout_name_tag,
}));
return Err(());
};
// let file = futures::io::AllowStdIo::new(stderr);
// let stream = FramedRead::new(file, MaybeTextCodec::default());
let buf_reader = BufReader::new(stderr);
let buf_codec = BufCodecReader::new(buf_reader, MaybeTextCodec::default());
for line in buf_codec {
match line {
Ok(line) => match line {
StringOrBinary::String(s) => {
let result = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(
ShellError::untagged_runtime_error(s),
),
tag: stdout_name_tag.clone(),
}));
if result.is_err() {
break;
}
}
StringOrBinary::Binary(_) => {
let result = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(
ShellError::untagged_runtime_error("<binary stderr>"),
),
tag: stdout_name_tag.clone(),
}));
if result.is_err() {
break;
}
}
},
Err(e) => {
// If there's an exit status, it makes sense that we may error when
// trying to read from its stdout pipe (likely been closed). In that
// case, don't emit an error.
let should_error = match child.wait() {
Ok(exit_status) => !exit_status.success(),
Err(_) => true,
};
if should_error {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
format!("Unable to read from stdout ({})", e),
"unable to read from stdout",
&stdout_name_tag,
)),
tag: stdout_name_tag.clone(),
}));
}
return Ok(());
}
}
}
}
// We can give an error when we see a non-zero exit code, but this is different
// than what other shells will do.
let external_failed = match child.wait() {
Err(_) => true,
Ok(exit_status) => !exit_status.success(),
};
if external_failed {
let cfg = nu_data::config::config(Tag::unknown());
if let Ok(cfg) = cfg {
if cfg.contains_key("nonzero_exit_errors") {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
"External command failed",
"command failed",
&stdout_name_tag,
)),
tag: stdout_name_tag.clone(),
}));
}
}
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::nothing(),
tag: stdout_name_tag,
}));
}
Ok(())
});
let stream = ChannelReceiver::new(rx);
Ok(stream.into_input_stream())
}
Err(e) => Err(ShellError::labeled_error(
e.to_string(),
"failed to spawn",
&command.name_tag,
)),
}
}
struct ChannelReceiver {
rx: Arc<Mutex<mpsc::Receiver<Result<Value, ShellError>>>>,
}
impl ChannelReceiver {
pub fn new(rx: mpsc::Receiver<Result<Value, ShellError>>) -> Self {
Self {
rx: Arc::new(Mutex::new(rx)),
}
}
}
impl Iterator for ChannelReceiver {
type Item = Result<Value, ShellError>;
fn next(&mut self) -> Option<Self::Item> {
let rx = self.rx.lock();
match rx.recv() {
Ok(v) => Some(v),
Err(_) => None,
}
}
}
fn argument_is_quoted(argument: &str) -> bool {
if argument.len() < 2 {
return false;
}
(argument.starts_with('"') && argument.ends_with('"'))
|| (argument.starts_with('\'') && argument.ends_with('\''))
}
#[allow(unused)]
fn add_double_quotes(argument: &str) -> String {
format!("\"{}\"", argument)
}
#[allow(unused)]
fn escape_double_quotes(argument: &str) -> Cow<'_, str> {
// allocate new string only if required
if argument.contains('"') {
Cow::Owned(argument.replace('"', r#"\""#))
} else {
Cow::Borrowed(argument)
}
}
#[allow(unused)]
fn remove_quotes(argument: &str) -> Option<&str> {
if !argument_is_quoted(argument) {
return None;
}
let size = argument.len();
Some(&argument[1..size - 1])
}
#[allow(unused)]
fn shell_os_paths() -> Vec<std::path::PathBuf> {
let mut original_paths = vec![];
if let Some(paths) = std::env::var_os(NATIVE_PATH_ENV_VAR) {
original_paths = std::env::split_paths(&paths).collect::<Vec<_>>();
}
original_paths
}
#[cfg(test)]
mod tests {
use super::{add_double_quotes, argument_is_quoted, escape_double_quotes, remove_quotes};
#[cfg(feature = "which")]
use super::{run_external_command, InputStream};
#[cfg(feature = "which")]
use nu_engine::EvaluationContext;
#[cfg(feature = "which")]
use nu_test_support::commands::ExternalBuilder;
// fn read(mut stream: OutputStream) -> Option<Value> {
// match stream.try_next() {
// Ok(val) => {
// if let Some(val) = val {
// val.raw_value()
// } else {
// None
// }
// }
// Err(_) => None,
// }
// }
#[cfg(feature = "which")]
fn non_existent_run() {
use nu_protocol::hir::ExternalRedirection;
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
let input = InputStream::empty();
let mut ctx = EvaluationContext::basic();
assert!(run_external_command(cmd, &mut ctx, input, ExternalRedirection::Stdout).is_err());
}
// fn failure_run() -> Result<(), ShellError> {
// let cmd = ExternalBuilder::for_name("fail").build();
// let mut ctx = crate::cli::EvaluationContext::basic().expect("There was a problem creating a basic context.");
// let stream = run_external_command(cmd, &mut ctx, None, false)
// ?
// .expect("There was a problem running the external command.");
// match read(stream.into()) {
// Some(Value {
// value: UntaggedValue::Error(_),
// ..
// }) => {}
// None | _ => panic!("Command didn't fail."),
// }
// Ok(())
// }
// #[test]
// fn identifies_command_failed() -> Result<(), ShellError> {
// block_on(failure_run())
// }
#[cfg(feature = "which")]
#[test]
fn identifies_command_not_found() {
non_existent_run()
}
#[test]
fn checks_escape_double_quotes() {
assert_eq!(escape_double_quotes("andrés"), "andrés");
assert_eq!(escape_double_quotes(r#"an"drés"#), r#"an\"drés"#);
assert_eq!(escape_double_quotes(r#""an"drés""#), r#"\"an\"drés\""#);
}
#[test]
fn checks_quotes_from_argument_to_be_passed_in() {
assert!(!argument_is_quoted(""));
assert!(!argument_is_quoted("'"));
assert!(!argument_is_quoted("'a"));
assert!(!argument_is_quoted("a"));
assert!(!argument_is_quoted("a'"));
assert!(argument_is_quoted("''"));
assert!(!argument_is_quoted(r#"""#));
assert!(!argument_is_quoted(r#""a"#));
assert!(!argument_is_quoted(r#"a"#));
assert!(!argument_is_quoted(r#"a""#));
assert!(argument_is_quoted(r#""""#));
assert!(!argument_is_quoted("'andrés"));
assert!(!argument_is_quoted("andrés'"));
assert!(!argument_is_quoted(r#""andrés"#));
assert!(!argument_is_quoted(r#"andrés""#));
assert!(argument_is_quoted("'andrés'"));
assert!(argument_is_quoted(r#""andrés""#));
}
#[test]
fn adds_double_quotes_to_argument_to_be_passed_in() {
assert_eq!(add_double_quotes("andrés"), "\"andrés\"");
}
#[test]
fn strips_quotes_from_argument_to_be_passed_in() {
assert_eq!(remove_quotes(""), None);
assert_eq!(remove_quotes("'"), None);
assert_eq!(remove_quotes("'a"), None);
assert_eq!(remove_quotes("a"), None);
assert_eq!(remove_quotes("a'"), None);
assert_eq!(remove_quotes("''"), Some(""));
assert_eq!(remove_quotes(r#"""#), None);
assert_eq!(remove_quotes(r#""a"#), None);
assert_eq!(remove_quotes(r#"a"#), None);
assert_eq!(remove_quotes(r#"a""#), None);
assert_eq!(remove_quotes(r#""""#), Some(""));
assert_eq!(remove_quotes("'andrés"), None);
assert_eq!(remove_quotes("andrés'"), None);
assert_eq!(remove_quotes(r#""andrés"#), None);
assert_eq!(remove_quotes(r#"andrés""#), None);
assert_eq!(remove_quotes("'andrés'"), Some("andrés"));
assert_eq!(remove_quotes(r#""andrés""#), Some("andrés"));
}
}

View File

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

View File

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

View File

@ -1,86 +0,0 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
use serde::Deserialize;
use serde::Serialize;
use sha2::{Digest, Sha256};
use std::io::Read;
use std::path::PathBuf;
pub struct Autoenv;
#[derive(Deserialize, Serialize, Debug, Default)]
pub struct Trusted {
pub files: IndexMap<String, Vec<u8>>,
}
impl Trusted {
pub fn new() -> Self {
Trusted {
files: IndexMap::new(),
}
}
}
pub fn file_is_trusted(nu_env_file: &PathBuf, content: &[u8]) -> Result<bool, ShellError> {
let contentdigest = Sha256::digest(&content).as_slice().to_vec();
let nufile = std::fs::canonicalize(nu_env_file)?;
let trusted = read_trusted()?;
Ok(trusted.files.get(&nufile.to_string_lossy().to_string()) == Some(&contentdigest))
}
pub fn read_trusted() -> Result<Trusted, ShellError> {
let config_path = config::default_path_for(&Some(PathBuf::from("nu-env.toml")))?;
let mut file = std::fs::OpenOptions::new()
.read(true)
.create(true)
.write(true)
.open(config_path)
.map_err(|_| ShellError::untagged_runtime_error("Couldn't open nu-env.toml"))?;
let mut doc = String::new();
file.read_to_string(&mut doc)?;
let allowed = toml::de::from_str(doc.as_str()).unwrap_or_else(|_| Trusted::new());
Ok(allowed)
}
#[async_trait]
impl WholeStreamCommand for Autoenv {
fn name(&self) -> &str {
"autoenv"
}
fn usage(&self) -> &str {
// "Mark a .nu-env file in a directory as trusted. Needs to be re-run after each change to the file or its filepath."
r#"Manage directory specific environment variables and scripts. Create a file called .nu-env in any directory and run 'autoenv trust' to let nushell read it when entering the directory.
The file can contain several optional sections:
env: environment variables to set when visiting the directory. The variables are unset after leaving the directory and any overwritten values are restored.
scriptvars: environment variables that should be set to the return value of a script. After they have been set, they behave in the same way as variables set in the env section.
scripts: scripts to run when entering the directory or leaving it."#
}
fn signature(&self) -> Signature {
Signature::build("autoenv")
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(&Autoenv, &args.scope)).into_value(Tag::unknown()),
)))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Example .nu-env file",
example: r#"cat .nu-env
[env]
mykey = "myvalue"
[scriptvars]
myscript = "echo myval"
[scripts]
entryscripts = ["touch hello.txt", "touch hello2.txt"]
exitscripts = ["touch bye.txt"]"#,
result: None,
}]
}
}

View File

@ -1,148 +0,0 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct Char;
#[derive(Deserialize)]
struct CharArgs {
name: Tagged<String>,
unicode: bool,
}
#[async_trait]
impl WholeStreamCommand for Char {
fn name(&self) -> &str {
"char"
}
fn signature(&self) -> Signature {
Signature::build("char")
.required(
"character",
SyntaxShape::Any,
"the name of the character to output",
)
.switch("unicode", "unicode string i.e. 1f378", Some('u'))
}
fn usage(&self) -> &str {
"Output special characters (eg. 'newline')"
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Output newline",
example: r#"char newline"#,
result: Some(vec![Value::from("\n")]),
},
Example {
description: "Output prompt character, newline and a hamburger character",
example: r#"echo $(char prompt) $(char newline) $(char hamburger)"#,
result: Some(vec![
UntaggedValue::string("\u{25b6}").into(),
UntaggedValue::string("\n").into(),
UntaggedValue::string("\u{2261}").into(),
]),
},
Example {
description: "Output unicode character",
example: r#"char -u 1f378"#,
result: Some(vec![Value::from("\u{1f378}")]),
},
]
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let (CharArgs { name, unicode }, _) = args.process().await?;
if unicode {
let decoded_char = string_to_unicode_char(&name.item);
if let Some(output) = decoded_char {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(name.tag()),
)))
} else {
Err(ShellError::labeled_error(
"error decoding unicode character",
"error decoding unicode character",
name.tag(),
))
}
} else {
let special_character = str_to_character(&name.item);
if let Some(output) = special_character {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(name.tag()),
)))
} else {
Err(ShellError::labeled_error(
"error finding named character",
"error finding named character",
name.tag(),
))
}
}
}
}
fn string_to_unicode_char(s: &str) -> Option<char> {
u32::from_str_radix(s, 16)
.ok()
.and_then(std::char::from_u32)
}
fn str_to_character(s: &str) -> Option<String> {
match s {
"newline" | "enter" | "nl" => Some("\n".into()),
"tab" => Some("\t".into()),
"sp" | "space" => Some(" ".into()),
// Unicode names came from https://www.compart.com/en/unicode
// Private Use Area (U+E000-U+F8FF)
// Unicode can't be mixed with Ansi or it will break width calculation
"branch" => Some('\u{e0a0}'.to_string()), // 
"segment" => Some('\u{e0b0}'.to_string()), // 
"identical_to" | "hamburger" => Some('\u{2261}'.to_string()), // ≡
"not_identical_to" | "branch_untracked" => Some('\u{2262}'.to_string()), // ≢
"strictly_equivalent_to" | "branch_identical" => Some('\u{2263}'.to_string()), // ≣
"upwards_arrow" | "branch_ahead" => Some('\u{2191}'.to_string()), // ↑
"downwards_arrow" | "branch_behind" => Some('\u{2193}'.to_string()), // ↓
"up_down_arrow" | "branch_ahead_behind" => Some('\u{2195}'.to_string()), // ↕
"black_right_pointing_triangle" | "prompt" => Some('\u{25b6}'.to_string()), // ▶
"vector_or_cross_product" | "failed" => Some('\u{2a2f}'.to_string()), //
"high_voltage_sign" | "elevated" => Some('\u{26a1}'.to_string()), // ⚡
"tilde" | "twiddle" | "squiggly" | "home" => Some("~".into()), // ~
"hash" | "hashtag" | "pound_sign" | "sharp" | "root" => Some("#".into()), // #
// Weather symbols
"sun" | "sunny" | "sunrise" => Some("☀️".to_string()),
"moon" => Some("🌛".to_string()),
"cloudy" | "cloud" | "clouds" => Some("☁️".to_string()),
"rainy" | "rain" => Some("🌦️".to_string()),
"foggy" | "fog" => Some("🌫️".to_string()),
"mist" | "haze" => Some("\u{2591}".to_string()),
"snowy" | "snow" => Some("❄️".to_string()),
"thunderstorm" | "thunder" => Some("🌩️".to_string()),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::Char;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(Char {})?)
}
}

View File

@ -6,7 +6,6 @@ use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
#[derive(Clone)]
pub struct Chart;
#[async_trait]
impl WholeStreamCommand for Chart {
fn name(&self) -> &str {
"chart"
@ -20,15 +19,15 @@ impl WholeStreamCommand for Chart {
"Displays charts."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.scope.get_command("chart bar").is_none() {
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
if args.scope().get_command("chart bar").is_none() {
return Err(ShellError::untagged_runtime_error(
"nu_plugin_chart not installed.",
));
}
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(get_help(&Chart, &args.scope)).into_value(Tag::unknown()),
Ok(ActionStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(get_full_help(&Chart, args.scope())).into_value(Tag::unknown()),
))))
}
}
@ -42,6 +41,6 @@ mod tests {
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(Chart {})?)
test_examples(Chart {})
}
}

View File

@ -8,7 +8,6 @@ use nu_source::Tagged;
pub struct Histogram;
#[async_trait]
impl WholeStreamCommand for Histogram {
fn name(&self) -> &str {
"histogram"
@ -23,6 +22,7 @@ impl WholeStreamCommand for Histogram {
None,
)
.rest(
"rest",
SyntaxShape::ColumnPath,
"column name to give the histogram's frequency column",
)
@ -32,8 +32,8 @@ impl WholeStreamCommand for Histogram {
"Creates a new table with a histogram based on the column name passed in."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
histogram(args).await
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
histogram(args)
}
fn examples(&self) -> Vec<Example> {
@ -58,29 +58,18 @@ impl WholeStreamCommand for Histogram {
}
}
pub async fn histogram(args: CommandArgs) -> Result<OutputStream, ShellError> {
pub fn histogram(args: CommandArgs) -> Result<ActionStream, ShellError> {
let name = args.call_info.name_tag.clone();
let (input, args) = args.evaluate_once().await?.parts();
let values: Vec<Value> = input.collect().await;
let mut columns = args
.positional_iter()
.map(|c| c.as_column_path())
.filter_map(Result::ok)
.collect::<Vec<_>>();
let evaluate_with = if let Some(path) = args.get("use") {
Some(evaluator(path.as_column_path()?.item))
} else {
None
};
let mut columns = args.rest::<ColumnPath>(0)?;
let evaluate_with = args.get_flag::<ColumnPath>("use")?.map(evaluator);
let values: Vec<Value> = args.input.collect();
let column_grouper = if !columns.is_empty() {
match columns.remove(0).split_last() {
Some((key, _)) => Some(key.as_string().tagged(&name)),
None => None,
}
columns
.remove(0)
.split_last()
.map(|(key, _)| key.as_string().tagged(&name))
} else {
None
};
@ -114,73 +103,74 @@ pub async fn histogram(args: CommandArgs) -> Result<OutputStream, ShellError> {
let labels = results.labels.y.clone();
let mut idx = 0;
Ok(futures::stream::iter(
results
.data
.table_entries()
.cloned()
.collect::<Vec<_>>()
.into_iter()
.zip(
results
.percentages
.table_entries()
.cloned()
.collect::<Vec<_>>()
.into_iter(),
)
.map(move |(counts, percentages)| {
let percentage = percentages
.table_entries()
.cloned()
.last()
.unwrap_or_else(|| {
UntaggedValue::decimal_from_float(0.0, name.span).into_value(&name)
});
let value = counts
.table_entries()
.cloned()
.last()
.unwrap_or_else(|| UntaggedValue::int(0).into_value(&name));
Ok(results
.data
.table_entries()
.cloned()
.collect::<Vec<_>>()
.into_iter()
.zip(
results
.percentages
.table_entries()
.cloned()
.collect::<Vec<_>>()
.into_iter(),
)
.map(move |(counts, percentages)| {
let percentage = percentages
.table_entries()
.cloned()
.last()
.unwrap_or_else(|| {
UntaggedValue::decimal_from_float(0.0, name.span).into_value(&name)
});
let value = counts
.table_entries()
.cloned()
.last()
.unwrap_or_else(|| UntaggedValue::int(0).into_value(&name));
let mut fact = TaggedDictBuilder::new(&name);
let column_value = labels
.get(idx)
.ok_or_else(|| {
ShellError::labeled_error(
"Unable to load group labels",
"unable to load group labels",
&name,
)
})?
.clone();
let mut fact = TaggedDictBuilder::new(&name);
let column_value = labels
.get(idx)
.ok_or_else(|| {
ShellError::labeled_error(
"Unable to load group labels",
"unable to load group labels",
&name,
)
})?
.clone();
fact.insert_value(&column.item, column_value);
fact.insert_untagged("count", value);
fact.insert_value(&column.item, column_value);
fact.insert_untagged("count", value);
let fmt_percentage = format!(
"{}%",
// Some(2) < the number of digits
// true < group the digits
crate::commands::str_::from::action(&percentage, &name, Some(2), true)?
.as_string()?
);
fact.insert_untagged("percentage", UntaggedValue::string(fmt_percentage));
let fmt_percentage = format!(
"{}%",
// Some(2) < the number of digits
// true < group the digits
crate::commands::conversions::into::string::action(
&percentage,
&name,
Some(2),
true
)?
.as_string()?
);
fact.insert_untagged("percentage", UntaggedValue::string(fmt_percentage));
let string = std::iter::repeat("*")
.take(percentage.as_u64().map_err(|_| {
ShellError::labeled_error("expected a number", "expected a number", &name)
})? as usize)
.collect::<String>();
let string = "*".repeat(percentage.as_u64().map_err(|_| {
ShellError::labeled_error("expected a number", "expected a number", &name)
})? as usize);
fact.insert_untagged(&frequency_column_name, UntaggedValue::string(string));
fact.insert_untagged(&frequency_column_name, UntaggedValue::string(string));
idx += 1;
idx += 1;
ReturnSuccess::value(fact.into_value())
}),
)
.to_output_stream())
ReturnSuccess::value(fact.into_value())
})
.into_action_stream())
}
fn evaluator(by: ColumnPath) -> Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send> {
@ -212,7 +202,7 @@ fn splitter(
)),
}
}),
None => Box::new(move |_, row: &Value| nu_value_ext::as_string(&row)),
None => Box::new(move |_, row: &Value| nu_value_ext::as_string(row)),
}
}
@ -225,6 +215,6 @@ mod tests {
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(Histogram {})?)
test_examples(Histogram {})
}
}

View File

@ -0,0 +1,5 @@
mod chart;
mod histogram;
pub use chart::Chart;
pub use histogram::Histogram;

View File

@ -1,7 +0,0 @@
use derive_new::new;
use nu_protocol::hir;
#[derive(new, Debug)]
pub(crate) struct Command {
pub(crate) args: hir::Call,
}

View File

@ -1,682 +0,0 @@
use crate::futures::ThreadedReceiver;
use crate::prelude::*;
use nu_engine::evaluate_baseline_expr;
use nu_engine::{MaybeTextCodec, StringOrBinary};
use std::borrow::Cow;
use std::io::Write;
use std::ops::Deref;
use std::process::{Command, Stdio};
use std::sync::mpsc;
use futures::executor::block_on_stream;
use futures_codec::FramedRead;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::hir::Expression;
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
use nu_source::Tag;
use nu_stream::trace_stream;
pub(crate) async fn run_external_command(
command: ExternalCommand,
context: &mut EvaluationContext,
input: InputStream,
external_redirection: ExternalRedirection,
) -> Result<InputStream, ShellError> {
trace!(target: "nu::run::external", "-> {}", command.name);
if !did_find_command(&command.name) {
return Err(ShellError::labeled_error(
"Command not found",
"command not found",
&command.name_tag,
));
}
run_with_stdin(command, context, input, external_redirection).await
}
async fn run_with_stdin(
command: ExternalCommand,
context: &mut EvaluationContext,
input: InputStream,
external_redirection: ExternalRedirection,
) -> Result<InputStream, ShellError> {
let path = context.shell_manager.path();
let input = trace_stream!(target: "nu::trace_stream::external::stdin", "input" = input);
let mut command_args = vec![];
for arg in command.args.iter() {
let is_literal = matches!(arg.expr, Expression::Literal(_));
let value = evaluate_baseline_expr(arg, context).await?;
// Skip any arguments that don't really exist, treating them as optional
// FIXME: we may want to preserve the gap in the future, though it's hard to say
// what value we would put in its place.
if value.value.is_none() {
continue;
}
// Do the cleanup that we need to do on any argument going out:
match &value.value {
UntaggedValue::Table(table) => {
for t in table {
match &t.value {
UntaggedValue::Primitive(_) => {
command_args.push((
t.convert_to_string().trim_end_matches('\n').to_string(),
is_literal,
));
}
_ => {
return Err(ShellError::labeled_error(
"Could not convert to positional arguments",
"could not convert to positional arguments",
value.tag(),
));
}
}
}
}
_ => {
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
command_args.push((trimmed_value_string, is_literal));
}
}
}
let process_args = command_args
.iter()
.map(|(arg, _is_literal)| {
let home_dir;
#[cfg(feature = "dirs")]
{
home_dir = dirs_next::home_dir;
}
#[cfg(not(feature = "dirs"))]
{
home_dir = || Some(std::path::PathBuf::from("/"));
}
let arg = expand_tilde(arg.deref(), home_dir);
#[cfg(not(windows))]
{
if !_is_literal {
let escaped = escape_double_quotes(&arg);
add_double_quotes(&escaped)
} else {
arg.as_ref().to_string()
}
}
#[cfg(windows)]
{
if let Some(unquoted) = remove_quotes(&arg) {
unquoted.to_string()
} else {
arg.as_ref().to_string()
}
}
})
.collect::<Vec<String>>();
spawn(
&command,
&path,
&process_args[..],
input,
external_redirection,
&context.scope,
)
}
fn spawn(
command: &ExternalCommand,
path: &str,
args: &[String],
input: InputStream,
external_redirection: ExternalRedirection,
scope: &Scope,
) -> Result<InputStream, ShellError> {
let command = command.clone();
let mut process = {
#[cfg(windows)]
{
let mut process = Command::new("cmd");
process.arg("/c");
process.arg(&command.name);
for arg in args {
// Clean the args before we use them:
let arg = arg.replace("|", "\\|");
process.arg(&arg);
}
process
}
#[cfg(not(windows))]
{
let cmd_with_args = vec![command.name.clone(), args.join(" ")].join(" ");
let mut process = Command::new("sh");
process.arg("-c").arg(cmd_with_args);
process
}
};
process.current_dir(path);
trace!(target: "nu::run::external", "cwd = {:?}", &path);
process.env_clear();
process.envs(scope.get_env_vars());
// We want stdout regardless of what
// we are doing ($it case or pipe stdin)
match external_redirection {
ExternalRedirection::Stdout => {
process.stdout(Stdio::piped());
trace!(target: "nu::run::external", "set up stdout pipe");
}
ExternalRedirection::Stderr => {
process.stderr(Stdio::piped());
trace!(target: "nu::run::external", "set up stderr pipe");
}
ExternalRedirection::StdoutAndStderr => {
process.stdout(Stdio::piped());
trace!(target: "nu::run::external", "set up stdout pipe");
process.stderr(Stdio::piped());
trace!(target: "nu::run::external", "set up stderr pipe");
}
_ => {}
}
// open since we have some contents for stdin
if !input.is_empty() {
process.stdin(Stdio::piped());
trace!(target: "nu::run::external", "set up stdin pipe");
}
trace!(target: "nu::run::external", "built command {:?}", process);
// TODO Switch to async_std::process once it's stabilized
if let Ok(mut child) = process.spawn() {
let (tx, rx) = mpsc::sync_channel(0);
let mut stdin = child.stdin.take();
let stdin_write_tx = tx.clone();
let stdout_read_tx = tx;
let stdin_name_tag = command.name_tag.clone();
let stdout_name_tag = command.name_tag;
std::thread::spawn(move || {
if !input.is_empty() {
let mut stdin_write = stdin
.take()
.expect("Internal error: could not get stdin pipe for external command");
for value in block_on_stream(input) {
match &value.value {
UntaggedValue::Primitive(Primitive::Nothing) => continue,
UntaggedValue::Primitive(Primitive::String(s)) => {
if stdin_write.write(s.as_bytes()).is_err() {
// Other side has closed, so exit
return Ok(());
}
}
UntaggedValue::Primitive(Primitive::Binary(b)) => {
if stdin_write.write(b).is_err() {
// Other side has closed, so exit
return Ok(());
}
}
unsupported => {
println!("Unsupported: {:?}", unsupported);
let _ = stdin_write_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
format!(
"Received unexpected type from pipeline ({})",
unsupported.type_name()
),
"expected a string",
stdin_name_tag.clone(),
)),
tag: stdin_name_tag,
}));
return Err(());
}
};
}
}
Ok(())
});
std::thread::spawn(move || {
if external_redirection == ExternalRedirection::Stdout
|| external_redirection == ExternalRedirection::StdoutAndStderr
{
let stdout = if let Some(stdout) = child.stdout.take() {
stdout
} else {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
"Can't redirect the stdout for external command",
"can't redirect stdout",
&stdout_name_tag,
)),
tag: stdout_name_tag,
}));
return Err(());
};
let file = futures::io::AllowStdIo::new(stdout);
let stream = FramedRead::new(file, MaybeTextCodec::default());
for line in block_on_stream(stream) {
match line {
Ok(line) => match line {
StringOrBinary::String(s) => {
let result = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Primitive(Primitive::String(s.clone())),
tag: stdout_name_tag.clone(),
}));
if result.is_err() {
break;
}
}
StringOrBinary::Binary(b) => {
let result = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Primitive(Primitive::Binary(
b.into_iter().collect(),
)),
tag: stdout_name_tag.clone(),
}));
if result.is_err() {
break;
}
}
},
Err(e) => {
// If there's an exit status, it makes sense that we may error when
// trying to read from its stdout pipe (likely been closed). In that
// case, don't emit an error.
let should_error = match child.wait() {
Ok(exit_status) => !exit_status.success(),
Err(_) => true,
};
if should_error {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
format!("Unable to read from stdout ({})", e),
"unable to read from stdout",
&stdout_name_tag,
)),
tag: stdout_name_tag.clone(),
}));
}
return Ok(());
}
}
}
}
if external_redirection == ExternalRedirection::Stderr
|| external_redirection == ExternalRedirection::StdoutAndStderr
{
let stderr = if let Some(stderr) = child.stderr.take() {
stderr
} else {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
"Can't redirect the stderr for external command",
"can't redirect stderr",
&stdout_name_tag,
)),
tag: stdout_name_tag,
}));
return Err(());
};
let file = futures::io::AllowStdIo::new(stderr);
let stream = FramedRead::new(file, MaybeTextCodec::default());
for line in block_on_stream(stream) {
match line {
Ok(line) => match line {
StringOrBinary::String(s) => {
let result = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(
ShellError::untagged_runtime_error(s),
),
tag: stdout_name_tag.clone(),
}));
if result.is_err() {
break;
}
}
StringOrBinary::Binary(_) => {
let result = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(
ShellError::untagged_runtime_error("<binary stderr>"),
),
tag: stdout_name_tag.clone(),
}));
if result.is_err() {
break;
}
}
},
Err(e) => {
// If there's an exit status, it makes sense that we may error when
// trying to read from its stdout pipe (likely been closed). In that
// case, don't emit an error.
let should_error = match child.wait() {
Ok(exit_status) => !exit_status.success(),
Err(_) => true,
};
if should_error {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
format!("Unable to read from stdout ({})", e),
"unable to read from stdout",
&stdout_name_tag,
)),
tag: stdout_name_tag.clone(),
}));
}
return Ok(());
}
}
}
}
// We can give an error when we see a non-zero exit code, but this is different
// than what other shells will do.
let external_failed = match child.wait() {
Err(_) => true,
Ok(exit_status) => !exit_status.success(),
};
if external_failed {
let cfg = nu_data::config::config(Tag::unknown());
if let Ok(cfg) = cfg {
if cfg.contains_key("nonzero_exit_errors") {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
"External command failed",
"command failed",
&stdout_name_tag,
)),
tag: stdout_name_tag.clone(),
}));
}
}
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::external_non_zero()),
tag: stdout_name_tag,
}));
}
Ok(())
});
let stream = ThreadedReceiver::new(rx);
Ok(stream.to_input_stream())
} else {
Err(ShellError::labeled_error(
"Failed to spawn process",
"failed to spawn",
&command.name_tag,
))
}
}
pub fn did_find_command(#[allow(unused)] name: &str) -> bool {
#[cfg(not(feature = "which"))]
{
// we can't perform this check, so just assume it can be found
true
}
#[cfg(all(feature = "which", unix))]
{
which::which(name).is_ok()
}
#[cfg(all(feature = "which", windows))]
{
if which::which(name).is_ok() {
true
} else {
// Reference: https://ss64.com/nt/syntax-internal.html
let cmd_builtins = [
"assoc", "break", "color", "copy", "date", "del", "dir", "dpath", "echo", "erase",
"for", "ftype", "md", "mkdir", "mklink", "move", "path", "ren", "rename", "rd",
"rmdir", "start", "time", "title", "type", "ver", "verify", "vol",
];
cmd_builtins.contains(&name)
}
}
}
fn expand_tilde<SI: ?Sized, P, HD>(input: &SI, home_dir: HD) -> std::borrow::Cow<str>
where
SI: AsRef<str>,
P: AsRef<std::path::Path>,
HD: FnOnce() -> Option<P>,
{
shellexpand::tilde_with_context(input, home_dir)
}
fn argument_is_quoted(argument: &str) -> bool {
if argument.len() < 2 {
return false;
}
(argument.starts_with('"') && argument.ends_with('"'))
|| (argument.starts_with('\'') && argument.ends_with('\''))
}
#[allow(unused)]
fn add_double_quotes(argument: &str) -> String {
format!("\"{}\"", argument)
}
#[allow(unused)]
fn escape_double_quotes(argument: &str) -> Cow<'_, str> {
// allocate new string only if required
if argument.contains('"') {
Cow::Owned(argument.replace('"', r#"\""#))
} else {
Cow::Borrowed(argument)
}
}
#[allow(unused)]
fn remove_quotes(argument: &str) -> Option<&str> {
if !argument_is_quoted(argument) {
return None;
}
let size = argument.len();
Some(&argument[1..size - 1])
}
#[allow(unused)]
fn shell_os_paths() -> Vec<std::path::PathBuf> {
let mut original_paths = vec![];
if let Some(paths) = std::env::var_os("PATH") {
original_paths = std::env::split_paths(&paths).collect::<Vec<_>>();
}
original_paths
}
#[cfg(test)]
mod tests {
use super::{
add_double_quotes, argument_is_quoted, escape_double_quotes, expand_tilde, remove_quotes,
};
#[cfg(feature = "which")]
use super::{run_external_command, InputStream};
#[cfg(feature = "which")]
use futures::executor::block_on;
#[cfg(feature = "which")]
use nu_engine::basic_evaluation_context;
#[cfg(feature = "which")]
use nu_errors::ShellError;
#[cfg(feature = "which")]
use nu_test_support::commands::ExternalBuilder;
// async fn read(mut stream: OutputStream) -> Option<Value> {
// match stream.try_next().await {
// Ok(val) => {
// if let Some(val) = val {
// val.raw_value()
// } else {
// None
// }
// }
// Err(_) => None,
// }
// }
#[cfg(feature = "which")]
async fn non_existent_run() -> Result<(), ShellError> {
use nu_protocol::hir::ExternalRedirection;
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
let input = InputStream::empty();
let mut ctx =
basic_evaluation_context().expect("There was a problem creating a basic context.");
assert!(
run_external_command(cmd, &mut ctx, input, ExternalRedirection::Stdout)
.await
.is_err()
);
Ok(())
}
// async fn failure_run() -> Result<(), ShellError> {
// let cmd = ExternalBuilder::for_name("fail").build();
// let mut ctx = crate::cli::basic_evaluation_context().expect("There was a problem creating a basic context.");
// let stream = run_external_command(cmd, &mut ctx, None, false)
// .await?
// .expect("There was a problem running the external command.");
// match read(stream.into()).await {
// Some(Value {
// value: UntaggedValue::Error(_),
// ..
// }) => {}
// None | _ => panic!("Command didn't fail."),
// }
// Ok(())
// }
// #[test]
// fn identifies_command_failed() -> Result<(), ShellError> {
// block_on(failure_run())
// }
#[cfg(feature = "which")]
#[test]
fn identifies_command_not_found() -> Result<(), ShellError> {
block_on(non_existent_run())
}
#[test]
fn checks_escape_double_quotes() {
assert_eq!(escape_double_quotes("andrés"), "andrés");
assert_eq!(escape_double_quotes(r#"an"drés"#), r#"an\"drés"#);
assert_eq!(escape_double_quotes(r#""an"drés""#), r#"\"an\"drés\""#);
}
#[test]
fn checks_quotes_from_argument_to_be_passed_in() {
assert_eq!(argument_is_quoted(""), false);
assert_eq!(argument_is_quoted("'"), false);
assert_eq!(argument_is_quoted("'a"), false);
assert_eq!(argument_is_quoted("a"), false);
assert_eq!(argument_is_quoted("a'"), false);
assert_eq!(argument_is_quoted("''"), true);
assert_eq!(argument_is_quoted(r#"""#), false);
assert_eq!(argument_is_quoted(r#""a"#), false);
assert_eq!(argument_is_quoted(r#"a"#), false);
assert_eq!(argument_is_quoted(r#"a""#), false);
assert_eq!(argument_is_quoted(r#""""#), true);
assert_eq!(argument_is_quoted("'andrés"), false);
assert_eq!(argument_is_quoted("andrés'"), false);
assert_eq!(argument_is_quoted(r#""andrés"#), false);
assert_eq!(argument_is_quoted(r#"andrés""#), false);
assert_eq!(argument_is_quoted("'andrés'"), true);
assert_eq!(argument_is_quoted(r#""andrés""#), true);
}
#[test]
fn adds_double_quotes_to_argument_to_be_passed_in() {
assert_eq!(add_double_quotes("andrés"), "\"andrés\"");
}
#[test]
fn strips_quotes_from_argument_to_be_passed_in() {
assert_eq!(remove_quotes(""), None);
assert_eq!(remove_quotes("'"), None);
assert_eq!(remove_quotes("'a"), None);
assert_eq!(remove_quotes("a"), None);
assert_eq!(remove_quotes("a'"), None);
assert_eq!(remove_quotes("''"), Some(""));
assert_eq!(remove_quotes(r#"""#), None);
assert_eq!(remove_quotes(r#""a"#), None);
assert_eq!(remove_quotes(r#"a"#), None);
assert_eq!(remove_quotes(r#"a""#), None);
assert_eq!(remove_quotes(r#""""#), Some(""));
assert_eq!(remove_quotes("'andrés"), None);
assert_eq!(remove_quotes("andrés'"), None);
assert_eq!(remove_quotes(r#""andrés"#), None);
assert_eq!(remove_quotes(r#"andrés""#), None);
assert_eq!(remove_quotes("'andrés'"), Some("andrés"));
assert_eq!(remove_quotes(r#""andrés""#), Some("andrés"));
}
#[test]
fn expands_tilde_if_starts_with_tilde_character() {
assert_eq!(
expand_tilde("~", || Some(std::path::Path::new("the_path_to_nu_light"))),
"the_path_to_nu_light"
);
}
#[test]
fn does_not_expand_tilde_if_tilde_is_not_first_character() {
assert_eq!(
expand_tilde("1~1", || Some(std::path::Path::new("the_path_to_nu_light"))),
"1~1"
);
}
}

View File

@ -1,5 +0,0 @@
mod dynamic;
pub(crate) mod external;
#[allow(unused_imports)]
pub(crate) use dynamic::Command as DynamicCommand;

View File

@ -1,106 +0,0 @@
use crate::prelude::*;
use futures::stream::StreamExt;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{Signature, Value};
use arboard::Clipboard;
pub struct Clip;
#[async_trait]
impl WholeStreamCommand for Clip {
fn name(&self) -> &str {
"clip"
}
fn signature(&self) -> Signature {
Signature::build("clip")
}
fn usage(&self) -> &str {
"Copy the contents of the pipeline to the copy/paste buffer"
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
clip(args).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Save text to the clipboard",
example: "echo 'secret value' | clip",
result: None,
},
Example {
description: "Save numbers to the clipboard",
example: "random integer 10000000..99999999 | clip",
result: None,
},
]
}
}
pub async fn clip(args: CommandArgs) -> Result<OutputStream, ShellError> {
let input = args.input;
let name = args.call_info.name_tag.clone();
let values: Vec<Value> = input.collect().await;
if let Ok(mut clip_context) = Clipboard::new() {
let mut new_copy_data = String::new();
if !values.is_empty() {
let mut first = true;
for i in values.iter() {
if !first {
new_copy_data.push('\n');
} else {
first = false;
}
let string: String = i.convert_to_string();
if string.is_empty() {
return Err(ShellError::labeled_error(
"Unable to convert to string",
"Unable to convert to string",
name,
));
}
new_copy_data.push_str(&string);
}
}
match clip_context.set_text(new_copy_data) {
Ok(_) => {}
Err(_) => {
return Err(ShellError::labeled_error(
"Could not set contents of clipboard",
"could not set contents of clipboard",
name,
));
}
}
} else {
return Err(ShellError::labeled_error(
"Could not open clipboard",
"could not open clipboard",
name,
));
}
Ok(OutputStream::empty())
}
#[cfg(test)]
mod tests {
use super::Clip;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(Clip {})?)
}
}

View File

@ -1,21 +0,0 @@
use crate::prelude::*;
use nu_engine::Command;
use nu_errors::ShellError;
use parking_lot::Mutex;
use std::sync::atomic::AtomicBool;
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 scope: Scope,
pub name: Tag,
}
impl RunnableContext {
pub fn get_command(&self, name: &str) -> Option<Command> {
self.scope.get_command(name)
}
}

View File

@ -1,86 +0,0 @@
use crate::prelude::*;
use futures::future;
use futures::stream::StreamExt;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct Compact;
#[derive(Deserialize)]
pub struct CompactArgs {
rest: Vec<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for Compact {
fn name(&self) -> &str {
"compact"
}
fn signature(&self) -> Signature {
Signature::build("compact").rest(SyntaxShape::Any, "the columns to compact from the table")
}
fn usage(&self) -> &str {
"Creates a table with non-empty rows"
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
compact(args).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Filter out all directory entries having no 'target'",
example: "ls -la | compact target",
result: None,
}]
}
}
pub async fn compact(args: CommandArgs) -> Result<OutputStream, ShellError> {
let (CompactArgs { rest: columns }, input) = args.process().await?;
Ok(input
.filter_map(move |item| {
future::ready(if columns.is_empty() {
if !item.is_empty() {
Some(ReturnSuccess::value(item))
} else {
None
}
} else {
match item {
Value {
value: UntaggedValue::Row(ref r),
..
} => {
if columns
.iter()
.all(|field| r.get_data(field).borrow().is_some())
{
Some(ReturnSuccess::value(item))
} else {
None
}
}
_ => None,
}
})
})
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Compact;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(Compact {})?)
}
}

View File

@ -1,11 +1,10 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
use nu_protocol::{Signature, UntaggedValue};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"config clear"
@ -19,8 +18,8 @@ impl WholeStreamCommand for SubCommand {
"clear the config"
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
clear(args).await
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
clear(args)
}
fn examples(&self) -> Vec<Example> {
@ -32,18 +31,23 @@ impl WholeStreamCommand for SubCommand {
}
}
pub async fn clear(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone();
pub fn clear(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let ctx = &args.context;
// NOTE: None because we are not loading a new config file, we just want to read from the
// existing config
let mut result = nu_data::config::read(name_span, &None)?;
let result = if let Some(global_cfg) = &mut args.configs().lock().global_config {
global_cfg.vars.clear();
global_cfg.write()?;
ctx.reload_config(global_cfg)?;
result.clear();
let value = UntaggedValue::Row(global_cfg.vars.clone().into()).into_value(name);
Ok(OutputStream::one(value))
} else {
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
.into_value(name);
config::write(&result, &None)?;
Ok(OutputStream::one(value))
};
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(args.call_info.name_tag),
)))
result
}

View File

@ -2,12 +2,10 @@ use crate::prelude::*;
use nu_engine::CommandArgs;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
use nu_stream::OutputStream;
use nu_protocol::{Signature, UntaggedValue};
pub struct Command;
#[async_trait]
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"config"
@ -21,14 +19,19 @@ impl WholeStreamCommand for Command {
"Configuration management."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone();
let name = args.call_info.name_tag;
let result = nu_data::config::read(name_span, &None)?;
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
Ok(futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),
)])
.to_output_stream())
if let Some(global_cfg) = &args.configs().lock().global_config {
let result = global_cfg.vars.clone();
let value = UntaggedValue::Row(result.into()).into_value(name);
Ok(OutputStream::one(value))
} else {
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
.into_value(name);
Ok(OutputStream::one(value))
}
}
}

View File

@ -1,16 +1,10 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
pub struct SubCommand;
#[derive(Deserialize)]
pub struct GetArgs {
path: ColumnPath,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"config get"
@ -28,8 +22,8 @@ impl WholeStreamCommand for SubCommand {
"Gets a value from the config"
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
get(args).await
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
get(args)
}
fn examples(&self) -> Vec<Example> {
@ -41,28 +35,28 @@ impl WholeStreamCommand for SubCommand {
}
}
pub async fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_tag = args.call_info.name_tag.clone();
let (GetArgs { path }, _) = args.process().await?;
pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let ctx = &args.context;
// NOTE: None because we are not loading a new config file, we just want to read from the
// existing config
let result = UntaggedValue::row(nu_data::config::read(&name_tag, &None)?).into_value(&name_tag);
let column_path = args.req(0)?;
let value = crate::commands::get::get_column_path(&path, &result)?;
let result = if let Some(global_cfg) = &ctx.configs().lock().global_config {
let result = UntaggedValue::row(global_cfg.vars.clone()).into_value(&name);
let value = crate::commands::filters::get::get_column_path(&column_path, &result)?;
Ok(match value {
Value {
value: UntaggedValue::Table(list),
..
} => OutputStream::from_stream(list.into_iter()),
x => OutputStream::one(x),
})
} else {
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
.into_value(name);
Ok(match value {
Value {
value: UntaggedValue::Table(list),
..
} => {
let list: Vec<_> = list
.iter()
.map(|x| ReturnSuccess::value(x.clone()))
.collect();
Ok(OutputStream::one(value))
};
futures::stream::iter(list).to_output_stream()
}
x => OutputStream::one(ReturnSuccess::value(x)),
})
result
}

View File

@ -1,51 +0,0 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
use std::path::PathBuf;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct LoadArgs {
load: Tagged<PathBuf>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"config load"
}
fn signature(&self) -> Signature {
Signature::build("config load").required(
"load",
SyntaxShape::FilePath,
"Path to load the config from",
)
}
fn usage(&self) -> &str {
"Loads the config from the path given"
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
set(args).await
}
}
pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let name_span = args.call_info.name_tag.clone();
let (LoadArgs { load }, _) = args.process().await?;
let configuration = load.item().clone();
let result = nu_data::config::read(name_span, &Some(configuration))?;
Ok(futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),
)])
.to_output_stream())
}

View File

@ -1,7 +1,6 @@
pub mod clear;
pub mod command;
pub mod get;
pub mod load;
pub mod path;
pub mod remove;
pub mod set;
@ -10,8 +9,13 @@ pub mod set_into;
pub use clear::SubCommand as ConfigClear;
pub use command::Command as Config;
pub use get::SubCommand as ConfigGet;
pub use load::SubCommand as ConfigLoad;
pub use path::SubCommand as ConfigPath;
pub use remove::SubCommand as ConfigRemove;
pub use set::SubCommand as ConfigSet;
pub use set_into::SubCommand as ConfigSetInto;
use nu_errors::ShellError;
pub fn err_no_global_cfg_present() -> ShellError {
ShellError::untagged_runtime_error("No global config found!")
}

View File

@ -1,11 +1,10 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue};
use nu_protocol::{Primitive, Signature, UntaggedValue};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"config path"
@ -19,8 +18,8 @@ impl WholeStreamCommand for SubCommand {
"return the path to the config file"
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
path(args).await
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
path(args)
}
fn examples(&self) -> Vec<Example> {
@ -32,10 +31,18 @@ impl WholeStreamCommand for SubCommand {
}
}
pub async fn path(args: CommandArgs) -> Result<OutputStream, ShellError> {
let path = config::default_path()?;
pub fn path(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::Primitive(Primitive::FilePath(path)).into_value(args.call_info.name_tag),
)))
if let Some(global_cfg) = &mut args.configs().lock().global_config {
let value = UntaggedValue::Primitive(Primitive::FilePath(global_cfg.file_path.clone()))
.into_value(name);
Ok(OutputStream::one(value))
} else {
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
.into_value(name);
Ok(OutputStream::one(value))
}
}

View File

@ -1,17 +1,11 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct RemoveArgs {
remove: Tagged<String>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"config remove"
@ -29,8 +23,8 @@ impl WholeStreamCommand for SubCommand {
"Removes a value from the config"
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
remove(args).await
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
remove(args)
}
fn examples(&self) -> Vec<Example> {
@ -42,26 +36,35 @@ impl WholeStreamCommand for SubCommand {
}
}
pub async fn remove(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone();
let (RemoveArgs { remove }, _) = args.process().await?;
let mut result = nu_data::config::read(name_span, &None)?;
pub fn remove(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let ctx = &args.context;
let remove: Tagged<String> = args.req(0)?;
let key = remove.to_string();
if result.contains_key(&key) {
result.swap_remove(&key);
config::write(&result, &None)?;
Ok(futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(remove.tag()),
)])
.to_output_stream())
let result = if let Some(global_cfg) = &mut ctx.configs().lock().global_config {
if global_cfg.vars.contains_key(&key) {
global_cfg.vars.swap_remove(&key);
global_cfg.write()?;
ctx.reload_config(global_cfg)?;
let value: Value = UntaggedValue::row(global_cfg.vars.clone()).into_value(remove.tag);
Ok(OutputStream::one(value))
} else {
Err(ShellError::labeled_error(
"Key does not exist in config",
"key",
remove.tag(),
))
}
} else {
Err(ShellError::labeled_error(
"Key does not exist in config",
"key",
remove.tag(),
))
}
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
.into_value(name);
Ok(OutputStream::one(value))
};
result
}

View File

@ -1,17 +1,10 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
pub struct SubCommand;
#[derive(Deserialize)]
pub struct SetArgs {
path: ColumnPath,
value: Value,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"config set"
@ -27,8 +20,8 @@ impl WholeStreamCommand for SubCommand {
"Sets a value in the config"
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
set(args).await
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
set(args)
}
fn examples(&self) -> Vec<Example> {
@ -45,7 +38,7 @@ impl WholeStreamCommand for SubCommand {
},
Example {
description: "Set coloring options",
example: "config set color_config [[header_align header_bold]; [left $true]]",
example: "config set color_config [[header_align header_color]; [left white_bold]]",
result: None,
},
Example {
@ -57,33 +50,43 @@ impl WholeStreamCommand for SubCommand {
}
}
pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_tag = args.call_info.name_tag.clone();
let (SetArgs { path, mut value }, _) = args.process().await?;
pub fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let ctx = &args.context;
// NOTE: None because we are not loading a new config file, we just want to read from the
// existing config
let raw_entries = nu_data::config::read(&name_tag, &None)?;
let configuration = UntaggedValue::row(raw_entries).into_value(&name_tag);
let column_path = args.req(0)?;
let mut value: Value = args.req(1)?;
if let UntaggedValue::Table(rows) = &value.value {
if rows.len() == 1 && rows[0].is_row() {
value = rows[0].clone();
let result = if let Some(global_cfg) = &mut ctx.configs().lock().global_config {
let configuration = UntaggedValue::row(global_cfg.vars.clone()).into_value(&name);
if let UntaggedValue::Table(rows) = &value.value {
if rows.len() == 1 && rows[0].is_row() {
value = rows[0].clone();
}
}
}
match configuration.forgiving_insert_data_at_column_path(&path, value) {
Ok(Value {
value: UntaggedValue::Row(changes),
..
}) => {
config::write(&changes.entries, &None)?;
match configuration.forgiving_insert_data_at_column_path(&column_path, value) {
Ok(Value {
value: UntaggedValue::Row(changes),
..
}) => {
global_cfg.vars = changes.entries;
global_cfg.write()?;
ctx.reload_config(global_cfg)?;
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(changes).into_value(name_tag),
)))
let value = UntaggedValue::row(global_cfg.vars.clone()).into_value(name);
Ok(OutputStream::one(value))
}
Ok(_) => Ok(OutputStream::empty()),
Err(reason) => Err(reason),
}
Ok(_) => Ok(OutputStream::empty()),
Err(reason) => Err(reason),
}
} else {
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
.into_value(name);
Ok(OutputStream::one(value))
};
result
}

View File

@ -1,17 +1,11 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct SetIntoArgs {
set_into: Tagged<String>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"config set_into"
@ -29,8 +23,8 @@ impl WholeStreamCommand for SubCommand {
"Sets a value in the config"
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
set_into(args).await
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
set_into(args)
}
fn examples(&self) -> Vec<Example> {
@ -42,49 +36,46 @@ impl WholeStreamCommand for SubCommand {
}
}
pub async fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone();
pub fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let ctx = &args.context;
let (SetIntoArgs { set_into: v }, input) = args.process().await?;
let set_into: Tagged<String> = args.req(0)?;
// NOTE: None because we are not loading a new config file, we just want to read from the
// existing config
let mut result = nu_data::config::read(name_span, &None)?;
let rows: Vec<Value> = args.input.collect();
let key = set_into.to_string();
// In the original code, this is set to `Some` if the `load flag is set`
let configuration = None;
let result = if let Some(global_cfg) = &mut ctx.configs().lock().global_config {
if rows.is_empty() {
return Err(ShellError::labeled_error(
"No values given for set_into",
"needs value(s) from pipeline",
set_into.tag(),
));
} else if rows.len() == 1 {
// A single value
let value = &rows[0];
let rows: Vec<Value> = input.collect().await;
let key = v.to_string();
global_cfg.vars.insert(key, value.clone());
} else {
// Take in the pipeline as a table
let value = UntaggedValue::Table(rows).into_value(name.clone());
Ok(if rows.is_empty() {
return Err(ShellError::labeled_error(
"No values given for set_into",
"needs value(s) from pipeline",
v.tag(),
));
} else if rows.len() == 1 {
// A single value
let value = &rows[0];
global_cfg.vars.insert(key, value);
}
result.insert(key, value.clone());
global_cfg.write()?;
ctx.reload_config(global_cfg)?;
config::write(&result, &configuration)?;
let value = UntaggedValue::row(global_cfg.vars.clone()).into_value(name);
OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),
))
Ok(OutputStream::one(value))
} else {
// Take in the pipeline as a table
let value = UntaggedValue::Table(rows).into_value(name.clone());
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
.into_value(name);
result.insert(key, value);
Ok(OutputStream::one(value))
};
config::write(&result, &configuration)?;
OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),
))
})
result
}

View File

@ -0,0 +1,185 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, Primitive, Signature, SyntaxShape, UntaggedValue, Value};
use num_bigint::{BigInt, ToBigInt};
pub struct SubCommand;
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"into binary"
}
fn signature(&self) -> Signature {
Signature::build("into binary").rest(
"rest",
SyntaxShape::ColumnPath,
"column paths to convert to binary (for table input)",
)
}
fn usage(&self) -> &str {
"Convert value to a binary primitive"
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
into_binary(args)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "convert string to a nushell binary primitive",
example:
"echo 'This is a string that is exactly 52 characters long.' | into binary",
result: Some(vec![UntaggedValue::binary(
"This is a string that is exactly 52 characters long."
.to_string()
.as_bytes()
.to_vec(),
)
.into()]),
},
Example {
description: "convert a number to a nushell binary primitive",
example: "echo 1 | into binary",
result: Some(vec![UntaggedValue::binary(
i64::from(1).to_le_bytes().to_vec(),
)
.into()]),
},
Example {
description: "convert a boolean to a nushell binary primitive",
example: "echo $true | into binary",
result: Some(vec![UntaggedValue::binary(
i64::from(1).to_le_bytes().to_vec(),
)
.into()]),
},
Example {
description: "convert a filesize to a nushell binary primitive",
example: "ls | where name == LICENSE | get size | into binary",
result: None,
},
Example {
description: "convert a filepath to a nushell binary primitive",
example: "ls | where name == LICENSE | get name | path expand | into binary",
result: None,
},
Example {
description: "convert a decimal to a nushell binary primitive",
example: "echo 1.234 | into binary",
result: Some(vec![
UntaggedValue::binary(BigInt::from(1).to_bytes_le().1).into()
]),
},
]
}
}
fn into_binary(args: CommandArgs) -> Result<OutputStream, ShellError> {
let column_paths: Vec<ColumnPath> = args.rest(0)?;
Ok(args
.input
.map(move |v| {
if column_paths.is_empty() {
action(&v, v.tag())
} else {
let mut ret = v;
for path in &column_paths {
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag())),
)?;
}
Ok(ret)
}
})
.into_input_stream())
}
fn int_to_endian(n: i64) -> Vec<u8> {
if cfg!(target_endian = "little") {
n.to_le_bytes().to_vec()
} else {
n.to_be_bytes().to_vec()
}
}
fn bigint_to_endian(n: &BigInt) -> Vec<u8> {
if cfg!(target_endian = "little") {
n.to_bytes_le().1
} else {
n.to_bytes_be().1
}
}
pub fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let tag = tag.into();
match &input.value {
UntaggedValue::Primitive(prim) => Ok(UntaggedValue::binary(match prim {
Primitive::Binary(b) => b.to_vec(),
Primitive::Int(n_ref) => int_to_endian(*n_ref),
Primitive::BigInt(n_ref) => bigint_to_endian(n_ref),
Primitive::Decimal(dec) => match dec.to_bigint() {
Some(n) => bigint_to_endian(&n),
None => {
return Err(ShellError::unimplemented(
"failed to convert decimal to int",
));
}
},
Primitive::Filesize(a_filesize) => match a_filesize.to_bigint() {
Some(n) => bigint_to_endian(&n),
None => {
return Err(ShellError::unimplemented(
"failed to convert filesize to bigint",
));
}
},
Primitive::String(a_string) => a_string.as_bytes().to_vec(),
Primitive::Boolean(a_bool) => match a_bool {
false => int_to_endian(0),
true => int_to_endian(1),
},
Primitive::Date(a_date) => a_date.format("%c").to_string().as_bytes().to_vec(),
Primitive::FilePath(a_filepath) => a_filepath
.as_path()
.display()
.to_string()
.as_bytes()
.to_vec(),
_ => {
return Err(ShellError::unimplemented(
"'into binary' for non-numeric primitives",
))
}
})
.into_value(&tag)),
UntaggedValue::Row(_) => Err(ShellError::labeled_error(
"specify column name to use, with 'into binary COLUMN'",
"found table",
tag,
)),
_ => Err(ShellError::unimplemented(
"'into binary' for unsupported type",
)),
}
}
#[cfg(test)]
mod tests {
use super::ShellError;
use super::SubCommand;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,118 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, Primitive, Signature, SyntaxShape, UntaggedValue, Value};
pub struct SubCommand;
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"into column_path"
}
fn signature(&self) -> Signature {
Signature::build("into column_path").rest(
"rest",
SyntaxShape::ColumnPath,
"values to convert to column_path",
)
}
fn usage(&self) -> &str {
"Convert value to column path"
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
into_filepath(args)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Convert string to column_path in table",
example: "echo [[name]; ['/dev/null'] ['C:\\Program Files'] ['../../Cargo.toml']] | into column_path name",
result: Some(vec![
UntaggedValue::row(indexmap! {
"name".to_string() => UntaggedValue::column_path("/dev/null", Span::unknown()).into(),
})
.into(),
UntaggedValue::row(indexmap! {
"name".to_string() => UntaggedValue::column_path("C:\\Program Files", Span::unknown()).into(),
})
.into(),
UntaggedValue::row(indexmap! {
"name".to_string() => UntaggedValue::column_path("../../Cargo.toml", Span::unknown()).into(),
})
.into(),
]),
},
Example {
description: "Convert string to column_path",
example: "echo 'Cargo.toml' | into column_path",
result: Some(vec![UntaggedValue::column_path("Cargo.toml", Span::unknown()).into()]),
},
]
}
}
fn into_filepath(args: CommandArgs) -> Result<OutputStream, ShellError> {
let column_paths: Vec<ColumnPath> = args.rest(0)?;
Ok(args
.input
.map(move |v| {
if column_paths.is_empty() {
action(&v, v.tag())
} else {
let mut ret = v;
for path in &column_paths {
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag())),
)?;
}
Ok(ret)
}
})
.into_input_stream())
}
pub fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let tag = tag.into();
match &input.value {
UntaggedValue::Primitive(prim) => Ok(UntaggedValue::column_path(
match prim {
Primitive::String(a_string) => a_string,
_ => {
return Err(ShellError::unimplemented(
"'into column_path' for non-string primitives",
))
}
},
Span::unknown(),
)
.into_value(&tag)),
UntaggedValue::Row(_) => Err(ShellError::labeled_error(
"specify column name to use, with 'into column_path COLUMN'",
"found table",
tag,
)),
_ => Err(ShellError::unimplemented(
"'into column_path' for unsupported type",
)),
}
}
#[cfg(test)]
mod tests {
use super::ShellError;
use super::SubCommand;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,39 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{Signature, UntaggedValue};
pub struct Command;
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"into"
}
fn signature(&self) -> Signature {
Signature::build("into")
}
fn usage(&self) -> &str {
"Apply into function."
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(OutputStream::one(
UntaggedValue::string(get_full_help(&Command, args.scope())).into_value(Tag::unknown()),
))
}
}
#[cfg(test)]
mod tests {
use super::Command;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
test_examples(Command {})
}
}

View File

@ -0,0 +1,127 @@
use std::path::PathBuf;
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, Primitive, Signature, SyntaxShape, UntaggedValue, Value};
pub struct SubCommand;
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"into path"
}
fn signature(&self) -> Signature {
Signature::build("into path").rest(
"rest",
SyntaxShape::ColumnPath,
"column paths to convert to filepath (for table input)",
)
}
fn usage(&self) -> &str {
"Convert value to filepath"
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
into_filepath(args)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Convert string to filepath in table",
example: "echo [[name]; ['/dev/null'] ['C:\\Program Files'] ['../../Cargo.toml']] | into path name",
result: Some(vec![
UntaggedValue::row(indexmap! {
"name".to_string() => UntaggedValue::filepath("/dev/null").into(),
})
.into(),
UntaggedValue::row(indexmap! {
"name".to_string() => UntaggedValue::filepath("C:\\Program Files").into(),
})
.into(),
UntaggedValue::row(indexmap! {
"name".to_string() => UntaggedValue::filepath("../../Cargo.toml").into(),
})
.into(),
]),
},
Example {
description: "Convert string to filepath",
example: "echo 'Cargo.toml' | into path",
result: Some(vec![UntaggedValue::filepath("Cargo.toml").into()]),
},
]
}
}
fn into_filepath(args: CommandArgs) -> Result<OutputStream, ShellError> {
let column_paths: Vec<ColumnPath> = args.rest(0)?;
Ok(args
.input
.map(move |v| {
if column_paths.is_empty() {
action(&v, v.tag())
} else {
let mut ret = v;
for path in &column_paths {
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag())),
)?;
}
Ok(ret)
}
})
.into_input_stream())
}
pub fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let tag = tag.into();
match &input.value {
UntaggedValue::Primitive(prim) => Ok(UntaggedValue::filepath(match prim {
Primitive::String(a_string) => match filepath_from_string(a_string, &tag) {
Ok(n) => n,
Err(e) => {
return Err(e);
}
},
Primitive::FilePath(a_filepath) => a_filepath.clone(),
_ => {
return Err(ShellError::unimplemented(
"'into path' for non-string primitives",
))
}
})
.into_value(&tag)),
UntaggedValue::Row(_) => Err(ShellError::labeled_error(
"specify column name to use, with 'into path COLUMN'",
"found table",
tag,
)),
_ => Err(ShellError::unimplemented(
"'into path' for unsupported type",
)),
}
}
fn filepath_from_string(a_string: &str, _tag: &Tag) -> Result<PathBuf, ShellError> {
Ok(PathBuf::from(a_string))
}
#[cfg(test)]
mod tests {
use super::ShellError;
use super::SubCommand;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,182 @@
use std::convert::TryInto;
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, Primitive, Signature, SyntaxShape, UntaggedValue, Value};
use num_bigint::ToBigInt;
pub struct SubCommand;
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"into filesize"
}
fn signature(&self) -> Signature {
Signature::build("into filesize").rest(
"rest",
SyntaxShape::ColumnPath,
"column paths to convert to filesize (for table input)",
)
}
fn usage(&self) -> &str {
"Convert value to filesize"
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
into_filesize(args)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Convert string to filesize in table",
example: "echo [[bytes]; ['5'] [3.2] [4] [2kb]] | into filesize bytes",
result: Some(vec![
UntaggedValue::row(indexmap! {
"bytes".to_string() => UntaggedValue::filesize(5).into(),
})
.into(),
UntaggedValue::row(indexmap! {
"bytes".to_string() => UntaggedValue::filesize(3).into(),
})
.into(),
UntaggedValue::row(indexmap! {
"bytes".to_string() => UntaggedValue::filesize(4).into(),
})
.into(),
UntaggedValue::row(indexmap! {
"bytes".to_string() => UntaggedValue::filesize(2000).into(),
})
.into(),
]),
},
Example {
description: "Convert string to filesize",
example: "echo '2' | into filesize",
result: Some(vec![UntaggedValue::filesize(2).into()]),
},
Example {
description: "Convert decimal to filesize",
example: "echo 8.3 | into filesize",
result: Some(vec![UntaggedValue::filesize(8).into()]),
},
Example {
description: "Convert int to filesize",
example: "echo 5 | into filesize",
result: Some(vec![UntaggedValue::filesize(5).into()]),
},
Example {
description: "Convert file size to filesize",
example: "echo 4KB | into filesize",
result: Some(vec![UntaggedValue::filesize(4000).into()]),
},
]
}
}
fn into_filesize(args: CommandArgs) -> Result<OutputStream, ShellError> {
let column_paths: Vec<ColumnPath> = args.rest(0)?;
Ok(args
.input
.map(move |v| {
if column_paths.is_empty() {
action(&v, v.tag())
} else {
let mut ret = v;
for path in &column_paths {
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag())),
)?;
}
Ok(ret)
}
})
.into_input_stream())
}
pub fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let tag = tag.into();
match &input.value {
UntaggedValue::Primitive(prim) => Ok(UntaggedValue::filesize(match prim {
Primitive::String(a_string) => match int_from_string(a_string.trim(), &tag) {
Ok(n) => n,
Err(e) => {
return Err(e);
}
},
Primitive::Decimal(dec) => match dec.to_bigint() {
Some(n) => match n.to_u64() {
Some(i) => i,
None => {
return Err(ShellError::unimplemented(
"failed to convert decimal to filesize",
));
}
},
None => {
return Err(ShellError::unimplemented(
"failed to convert decimal to filesize",
));
}
},
Primitive::Int(n_ref) => (*n_ref).try_into().map_err(|_| {
ShellError::unimplemented("cannot convert negative integer to filesize")
})?,
Primitive::Filesize(a_filesize) => *a_filesize,
_ => {
return Err(ShellError::unimplemented(
"'into filesize' for non-numeric primitives",
))
}
})
.into_value(&tag)),
UntaggedValue::Row(_) => Err(ShellError::labeled_error(
"specify column name to use, with 'into filesize COLUMN'",
"found table",
tag,
)),
_ => Err(ShellError::unimplemented(
"'into filesize' for unsupported type",
)),
}
}
fn int_from_string(a_string: &str, tag: &Tag) -> Result<u64, ShellError> {
match a_string.parse::<u64>() {
Ok(n) => Ok(n),
Err(_) => match a_string.parse::<f64>() {
Ok(f) => match f.to_u64() {
Some(i) => Ok(i),
None => Err(ShellError::labeled_error(
"Could not convert string value to filesize",
"original value",
tag.clone(),
)),
},
Err(_) => Err(ShellError::labeled_error(
"Could not convert string value to filesize",
"original value",
tag.clone(),
)),
},
}
}
#[cfg(test)]
mod tests {
use super::ShellError;
use super::SubCommand;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,198 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, Primitive, Signature, SyntaxShape, UntaggedValue, Value};
use num_bigint::ToBigInt;
pub struct SubCommand;
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"into int"
}
fn signature(&self) -> Signature {
Signature::build("into int").rest(
"rest",
SyntaxShape::ColumnPath,
"column paths to convert to int (for table input)",
)
}
fn usage(&self) -> &str {
"Convert value to integer"
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
into_int(args)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Convert string to integer in table",
example: "echo [[num]; ['-5'] [4] [1.5]] | into int num",
result: Some(vec![
UntaggedValue::row(indexmap! {
"num".to_string() => UntaggedValue::int(-5).into(),
})
.into(),
UntaggedValue::row(indexmap! {
"num".to_string() => UntaggedValue::int(4).into(),
})
.into(),
UntaggedValue::row(indexmap! {
"num".to_string() => UntaggedValue::int(1).into(),
})
.into(),
]),
},
Example {
description: "Convert string to integer",
example: "echo '2' | into int",
result: Some(vec![UntaggedValue::int(2).into()]),
},
Example {
description: "Convert decimal to integer",
example: "echo 5.9 | into int",
result: Some(vec![UntaggedValue::int(5).into()]),
},
Example {
description: "Convert decimal string to integer",
example: "echo '5.9' | into int",
result: Some(vec![UntaggedValue::int(5).into()]),
},
Example {
description: "Convert file size to integer",
example: "echo 4KB | into int",
result: Some(vec![UntaggedValue::int(4000).into()]),
},
Example {
description: "Convert bool to integer",
example: "echo $false $true | into int",
result: Some(vec![
UntaggedValue::int(0).into(),
UntaggedValue::int(1).into(),
]),
},
]
}
}
fn into_int(args: CommandArgs) -> Result<OutputStream, ShellError> {
let column_paths: Vec<ColumnPath> = args.rest(0)?;
Ok(args
.input
.map(move |v| {
if column_paths.is_empty() {
action(&v, v.tag())
} else {
let mut ret = v;
for path in &column_paths {
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag())),
)?;
}
Ok(ret)
}
})
.into_input_stream())
}
pub fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let tag = tag.into();
match &input.value {
UntaggedValue::Primitive(prim) => Ok(UntaggedValue::int(match prim {
Primitive::String(a_string) => match int_from_string(a_string, &tag) {
Ok(n) => n,
Err(e) => {
return Err(e);
}
},
Primitive::Decimal(dec) => match dec.to_bigint() {
Some(n) => match n.to_i64() {
Some(i) => i,
None => {
return Err(ShellError::unimplemented(
"failed to convert decimal to int",
));
}
},
None => {
return Err(ShellError::unimplemented(
"failed to convert decimal to int",
));
}
},
Primitive::Int(n_ref) => *n_ref,
Primitive::Boolean(a_bool) => match a_bool {
false => 0,
true => 1,
},
Primitive::Filesize(a_filesize) => match a_filesize.to_bigint() {
Some(n) => match n.to_i64() {
Some(i) => i,
None => {
return Err(ShellError::unimplemented(
"failed to convert filesize to bigint",
));
}
},
None => {
return Err(ShellError::unimplemented(
"failed to convert filesize to bigint",
));
}
},
_ => {
return Err(ShellError::unimplemented(
"'into int' for non-numeric primitives",
))
}
})
.into_value(&tag)),
UntaggedValue::Row(_) => Err(ShellError::labeled_error(
"specify column name to use, with 'into int COLUMN'",
"found table",
tag,
)),
_ => Err(ShellError::unimplemented("'into int' for unsupported type")),
}
}
fn int_from_string(a_string: &str, tag: &Tag) -> Result<i64, ShellError> {
match a_string.parse::<i64>() {
Ok(n) => Ok(n),
Err(_) => match a_string.parse::<f64>() {
Ok(f) => match f.to_i64() {
Some(i) => Ok(i),
None => Err(ShellError::labeled_error(
"Could not convert string value to int",
"original value",
tag.clone(),
)),
},
Err(_) => Err(ShellError::labeled_error(
"Could not convert string value to int",
"original value",
tag.clone(),
)),
},
}
}
#[cfg(test)]
mod tests {
use super::ShellError;
use super::SubCommand;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,15 @@
mod binary;
mod column_path;
mod command;
mod filepath;
mod filesize;
mod int;
pub mod string;
pub use self::filesize::SubCommand as IntoFilesize;
pub use binary::SubCommand as IntoBinary;
pub use column_path::SubCommand as IntoColumnPath;
pub use command::Command as Into;
pub use filepath::SubCommand as IntoFilepath;
pub use int::SubCommand as IntoInt;
pub use string::SubCommand as IntoString;

View File

@ -1,38 +1,28 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_protocol::{ColumnPath, Primitive, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use num_bigint::{BigInt, BigUint, ToBigInt};
// TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml)
use nu_data::base::shape::InlineShape;
use num_format::Locale;
use num_traits::{Pow, Signed};
use std::iter;
pub struct SubCommand;
#[derive(Deserialize)]
struct Arguments {
rest: Vec<ColumnPath>,
decimals: Option<Tagged<u64>>,
#[serde(rename(deserialize = "group-digits"))]
group_digits: bool,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str from"
"into string"
}
fn signature(&self) -> Signature {
Signature::build("str from")
Signature::build("into string")
.rest(
"rest",
SyntaxShape::ColumnPath,
"optionally convert to string by column paths",
"column paths to convert to string (for table input)",
)
.named(
"decimals",
@ -40,61 +30,69 @@ impl WholeStreamCommand for SubCommand {
"decimal digits to which to round",
Some('d'),
)
/*
FIXME: this isn't currently supported because of num_format being out of date. Once it's updated, re-enable this
.switch(
"group-digits",
// TODO according to system localization
"group digits, currently by thousand with commas",
Some('g'),
)
*/
}
fn usage(&self) -> &str {
"Converts numeric types to strings. Trims trailing zeros unless decimals parameter is specified."
"Convert value to string"
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
operate(args).await
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
into_string(args)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "round to nearest integer",
example: "echo 1.7 | str from -d 0",
description: "convert decimal to string and round to nearest integer",
example: "echo 1.7 | into string -d 0",
result: Some(vec![UntaggedValue::string("2").into_untagged_value()]),
},
/*
FIXME: this isn't currently supported because of num_format being out of date. Once it's updated, re-enable this
Example {
description: "format large number with localized digit grouping",
example: "= 1000000.2 | str from -g",
result: Some(vec![
UntaggedValue::string("1,000,000.2").into_untagged_value()
]),
description: "convert decimal to string",
example: "echo 4.3 | into string",
result: Some(vec![UntaggedValue::string("4.3").into_untagged_value()]),
},
Example {
description: "convert string to string",
example: "echo '1234' | into string",
result: Some(vec![UntaggedValue::string("1234").into_untagged_value()]),
},
Example {
description: "convert boolean to string",
example: "echo $true | into string",
result: Some(vec![UntaggedValue::string("true").into_untagged_value()]),
},
Example {
description: "convert date to string",
example: "date now | into string",
result: None,
},
Example {
description: "convert filepath to string",
example: "ls Cargo.toml | get name | into string",
result: None,
},
Example {
description: "convert filesize to string",
example: "ls Cargo.toml | get size | into string",
result: None,
},
*/
]
}
}
async fn operate(args: CommandArgs) -> Result<OutputStream, ShellError> {
let (
Arguments {
decimals,
group_digits,
rest: column_paths,
},
input,
) = args.process().await?;
let digits = decimals.map(|tagged| tagged.item);
fn into_string(args: CommandArgs) -> Result<OutputStream, ShellError> {
let decimals: Option<Tagged<u64>> = args.get_flag("decimals")?;
let column_paths: Vec<ColumnPath> = args.rest(0)?;
Ok(input
let digits = decimals.as_ref().map(|tagged| tagged.item);
let group_digits = false;
Ok(args
.input
.map(move |v| {
if column_paths.is_empty() {
ReturnSuccess::value(action(&v, v.tag(), digits, group_digits)?)
action(&v, v.tag(), digits, group_digits)
} else {
let mut ret = v;
for path in &column_paths {
@ -104,13 +102,12 @@ async fn operate(args: CommandArgs) -> Result<OutputStream, ShellError> {
)?;
}
ReturnSuccess::value(ret)
Ok(ret)
}
})
.to_output_stream())
.into_input_stream())
}
// TODO If you're using the with-system-locale feature and you're on Windows, Clang 3.9 or higher is also required.
pub fn action(
input: &Value,
tag: impl Into<Tag>,
@ -120,6 +117,13 @@ pub fn action(
match &input.value {
UntaggedValue::Primitive(prim) => Ok(UntaggedValue::string(match prim {
Primitive::Int(int) => {
if group_digits {
format_int(*int) // int.to_formatted_string(*locale)
} else {
int.to_string()
}
}
Primitive::BigInt(int) => {
if group_digits {
format_bigint(int) // int.to_formatted_string(*locale)
} else {
@ -127,26 +131,52 @@ pub fn action(
}
}
Primitive::Decimal(dec) => format_decimal(dec.clone(), digits, group_digits),
Primitive::String(a_string) => a_string.to_string(),
Primitive::Boolean(a_bool) => a_bool.to_string(),
Primitive::Date(a_date) => a_date.format("%c").to_string(),
Primitive::FilePath(a_filepath) => a_filepath.as_path().display().to_string(),
Primitive::Filesize(a_filesize) => {
let byte_string = InlineShape::format_bytes(*a_filesize, None);
byte_string.1
}
Primitive::Nothing => "nothing".to_string(),
_ => {
return Err(ShellError::unimplemented(
"str from for non-numeric primitives",
))
return Err(ShellError::unimplemented(&format!(
"into string for primitive: {:?}",
prim
)))
}
})
.into_value(tag)),
UntaggedValue::Row(_) => Err(ShellError::labeled_error(
"specify column to use 'str from'",
"specify column to use 'into string'",
"found table",
input.tag.clone(),
)),
_ => Err(ShellError::unimplemented(
"str from for non-primitive, non-table types",
)),
UntaggedValue::Table(_) => Err(ShellError::unimplemented("into string for table")),
_ => Err(ShellError::unimplemented("into string for non-primitive")),
}
}
fn format_int(int: i64) -> String {
int.to_string()
// TODO once platform-specific dependencies are stable (see Cargo.toml)
// #[cfg(windows)]
// {
// int.to_formatted_string(&Locale::en)
// }
// #[cfg(not(windows))]
// {
// match SystemLocale::default() {
// Ok(locale) => int.to_formatted_string(&locale),
// Err(_) => int.to_formatted_string(&Locale::en),
// }
// }
}
fn format_bigint(int: &BigInt) -> String {
format!("{}", int)
int.to_string()
// TODO once platform-specific dependencies are stable (see Cargo.toml)
// #[cfg(windows)]
@ -200,7 +230,7 @@ fn format_decimal(mut decimal: BigDecimal, digits: Option<u64>, group_digits: bo
let format_default_loc = |int_part: BigInt| {
let loc = Locale::en;
//TODO: when num_format is available for recent bigint, replace this with the locale-based format
let (int_str, sep) = (format!("{}", int_part), String::from(loc.decimal()));
let (int_str, sep) = (int_part.to_string(), String::from(loc.decimal()));
format!("{}{}{}", int_str, sep, dec_str)
};
@ -244,6 +274,6 @@ mod tests {
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(SubCommand {})?)
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,3 @@
pub(crate) mod into;
pub use into::*;

View File

@ -0,0 +1,52 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
pub struct Alias;
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("equals", SyntaxShape::String, "the equals sign")
.rest("rest", SyntaxShape::Any, "the expansion for the alias")
}
fn usage(&self) -> &str {
"Alias a command to an expansion."
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
alias(args)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Alias ll to ls -l",
example: "alias ll = ls -l",
result: None,
}]
}
}
pub fn alias(args: CommandArgs) -> Result<OutputStream, ShellError> {
// TODO: is there a better way of checking whether no arguments were passed?
if args.nth(0).is_none() {
let aliases = UntaggedValue::string(
&args
.scope()
.get_aliases()
.iter()
.map(|val| format!("{} = '{}'", val.0, val.1.iter().map(|x| &x.item).join(" ")))
.join("\n"),
);
return Ok(OutputStream::one(aliases));
}
Ok(OutputStream::empty())
}

View File

@ -5,12 +5,6 @@ use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
pub struct Debug;
#[derive(Deserialize)]
pub struct DebugArgs {
raw: bool,
}
#[async_trait]
impl WholeStreamCommand for Debug {
fn name(&self) -> &str {
"debug"
@ -21,16 +15,18 @@ impl WholeStreamCommand for Debug {
}
fn usage(&self) -> &str {
"Print the Rust debug representation of the values"
"Print the Rust debug representation of the values."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
debug_value(args).await
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
debug_value(args)
}
}
async fn debug_value(args: CommandArgs) -> Result<OutputStream, ShellError> {
let (DebugArgs { raw }, input) = args.process().await?;
fn debug_value(args: CommandArgs) -> Result<ActionStream, ShellError> {
let raw = args.has_flag("raw");
let input = args.input;
Ok(input
.map(move |v| {
if raw {
@ -41,7 +37,7 @@ async fn debug_value(args: CommandArgs) -> Result<OutputStream, ShellError> {
ReturnSuccess::debug_value(v)
}
})
.to_output_stream())
.into_action_stream())
}
#[cfg(test)]
@ -53,6 +49,6 @@ mod tests {
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(Debug {})?)
test_examples(Debug {})
}
}

View File

@ -14,7 +14,6 @@ pub struct DefArgs {
pub block: CapturedBlock,
}
#[async_trait]
impl WholeStreamCommand for Def {
fn name(&self) -> &str {
"def"
@ -35,11 +34,11 @@ impl WholeStreamCommand for Def {
"Create a command and set it to a definition."
}
async fn run(&self, _args: CommandArgs) -> Result<OutputStream, ShellError> {
fn run_with_actions(&self, _args: CommandArgs) -> Result<ActionStream, ShellError> {
// Currently, we don't do anything here because we should have already
// installed the definition as we entered the scope
// We just create a command so that we can get proper coloring
Ok(OutputStream::empty())
Ok(ActionStream::empty())
}
fn examples(&self) -> Vec<Example> {

View File

@ -9,7 +9,6 @@ pub struct Describe;
#[derive(Deserialize)]
pub struct DescribeArgs {}
#[async_trait]
impl WholeStreamCommand for Describe {
fn name(&self) -> &str {
"describe"
@ -23,21 +22,21 @@ impl WholeStreamCommand for Describe {
"Describes the objects in the stream."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
describe(args).await
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
describe(args)
}
}
pub async fn describe(args: CommandArgs) -> Result<OutputStream, ShellError> {
pub fn describe(args: CommandArgs) -> Result<ActionStream, ShellError> {
Ok(args
.input
.map(|row| {
let name = value::format_type(&row, 100);
let name = value::plain_type(&row, 100);
ReturnSuccess::value(
UntaggedValue::string(name).into_value(Tag::unknown_anchor(row.tag.span)),
)
})
.to_output_stream())
.into_action_stream())
}
#[cfg(test)]
@ -49,6 +48,6 @@ mod tests {
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(Describe {})?)
test_examples(Describe {})
}
}

View File

@ -2,17 +2,18 @@ use crate::prelude::*;
use nu_engine::run_block;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{hir::CapturedBlock, hir::ExternalRedirection, Signature, SyntaxShape, Value};
use nu_protocol::{
hir::CapturedBlock, hir::ExternalRedirection, Signature, SyntaxShape, UntaggedValue, Value,
};
pub struct Do;
#[derive(Deserialize, Debug)]
struct DoArgs {
block: CapturedBlock,
ignore_errors: bool,
rest: Vec<Value>,
}
#[async_trait]
impl WholeStreamCommand for Do {
fn name(&self) -> &str {
"do"
@ -22,18 +23,19 @@ impl WholeStreamCommand for Do {
Signature::build("do")
.required("block", SyntaxShape::Block, "the block to run ")
.switch(
"ignore_errors",
"ignore-errors",
"ignore errors as the block runs",
Some('i'),
)
.rest("rest", SyntaxShape::Any, "the parameter(s) for the block")
}
fn usage(&self) -> &str {
"Runs a block, optionally ignoring errors"
"Runs a block, optionally ignoring errors."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
do_(args).await
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
do_(args)
}
fn examples(&self) -> Vec<Example> {
@ -48,32 +50,35 @@ impl WholeStreamCommand for Do {
example: r#"do -i { thisisnotarealcommand }"#,
result: Some(vec![]),
},
Example {
description: "Run the block with a parameter",
example: r#"do { |x| $x + 100 } 55"#,
result: Some(vec![UntaggedValue::int(155).into()]),
},
]
}
}
async fn do_(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
let external_redirection = raw_args.call_info.args.external_redirection;
fn do_(args: CommandArgs) -> Result<OutputStream, ShellError> {
let external_redirection = args.call_info.args.external_redirection;
let context = EvaluationContext::from_raw(&raw_args);
let (
DoArgs {
ignore_errors,
mut block,
},
input,
) = raw_args.process().await?;
let context = args.context().clone();
let do_args = DoArgs {
block: args.req(0)?,
ignore_errors: args.has_flag("ignore-errors"),
rest: args.rest(1)?,
};
let block_redirection = match external_redirection {
ExternalRedirection::None => {
if ignore_errors {
if do_args.ignore_errors {
ExternalRedirection::Stderr
} else {
ExternalRedirection::None
}
}
ExternalRedirection::Stdout => {
if ignore_errors {
if do_args.ignore_errors {
ExternalRedirection::StdoutAndStderr
} else {
ExternalRedirection::Stdout
@ -82,25 +87,43 @@ async fn do_(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
x => x,
};
block.block.set_redirect(block_redirection);
context.scope.enter_scope();
let result = run_block(&block.block, &context, input).await;
context.scope.add_vars(&do_args.block.captured.entries);
for (param, value) in do_args
.block
.block
.params
.positional
.iter()
.zip(do_args.rest)
{
context.scope.add_var(param.0.name(), value.clone());
}
let result = run_block(
&do_args.block.block,
&context,
args.input,
block_redirection,
);
context.scope.exit_scope();
if ignore_errors {
if do_args.ignore_errors {
// To properly ignore errors we need to redirect stderr, consume it, and remove
// any errors we see in the process.
match result {
Ok(mut stream) => {
let output = stream.drain_vec().await;
let output = stream.drain_vec();
context.clear_errors();
Ok(futures::stream::iter(output).to_output_stream())
Ok(output.into_iter().into_output_stream())
}
Err(_) => Ok(OutputStream::empty()),
}
} else {
result.map(|x| x.to_output_stream())
result.map(|x| x.into_output_stream())
}
}
@ -113,6 +136,6 @@ mod tests {
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(Do {})?)
test_examples(Do {})
}
}

View File

@ -0,0 +1,207 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::hir::Operator;
use nu_protocol::{Primitive, Range, RangeInclusion, Signature, SyntaxShape, UntaggedValue, Value};
pub struct Echo;
impl WholeStreamCommand for Echo {
fn name(&self) -> &str {
"echo"
}
fn signature(&self) -> Signature {
Signature::build("echo").rest("rest", SyntaxShape::Any, "the values to echo")
}
fn usage(&self) -> &str {
"Echo the arguments back to the user."
}
fn run(&self, args: CommandArgs) -> Result<InputStream, ShellError> {
echo(args)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Put a hello message in the pipeline",
example: "echo 'hello'",
result: Some(vec![Value::from("hello")]),
},
Example {
description: "Print the value of the special '$nu' variable",
example: "echo $nu",
result: None,
},
]
}
}
pub fn expand_value_to_stream(v: Value) -> InputStream {
match v {
Value {
value: UntaggedValue::Table(table),
..
} => InputStream::from_stream(table.into_iter()),
Value {
value: UntaggedValue::Primitive(Primitive::Range(range)),
tag,
} => InputStream::from_stream(RangeIterator::new(*range, tag)),
x => InputStream::one(x),
}
}
fn echo(args: CommandArgs) -> Result<InputStream, ShellError> {
let rest: Vec<Value> = args.rest(0)?;
let stream = rest.into_iter().map(|i| match i.as_string() {
Ok(s) => InputStream::one(UntaggedValue::string(s).into_value(i.tag)),
_ => expand_value_to_stream(i),
});
Ok(InputStream::from_stream(stream.flatten()))
}
struct RangeIterator {
curr: UntaggedValue,
end: UntaggedValue,
tag: Tag,
is_end_inclusive: bool,
moves_up: bool,
one: UntaggedValue,
negative_one: UntaggedValue,
done: bool,
}
impl RangeIterator {
pub fn new(range: Range, tag: Tag) -> RangeIterator {
let start = match range.from.0.item {
Primitive::Nothing => Primitive::Int(0.into()),
x => x,
};
let end = match range.to.0.item {
Primitive::Nothing => Primitive::Int(i64::MAX),
x => x,
};
RangeIterator {
moves_up: start <= end,
curr: UntaggedValue::Primitive(start),
end: UntaggedValue::Primitive(end),
tag,
is_end_inclusive: matches!(range.to.1, RangeInclusion::Inclusive),
one: UntaggedValue::int(1),
negative_one: UntaggedValue::int(-1),
done: false,
}
}
}
impl Iterator for RangeIterator {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
use std::cmp::Ordering;
if self.done {
return None;
}
let ordering = if self.end == UntaggedValue::Primitive(Primitive::Nothing) {
Ordering::Less
} else {
match (&self.curr, &self.end) {
(
UntaggedValue::Primitive(Primitive::Int(x)),
UntaggedValue::Primitive(Primitive::Int(y)),
) => x.cmp(y),
(
UntaggedValue::Primitive(Primitive::Decimal(x)),
UntaggedValue::Primitive(Primitive::Decimal(y)),
) => x.cmp(y),
(
UntaggedValue::Primitive(Primitive::Decimal(x)),
UntaggedValue::Primitive(Primitive::Int(y)),
) => x.cmp(&(BigDecimal::from(*y))),
(
UntaggedValue::Primitive(Primitive::Int(x)),
UntaggedValue::Primitive(Primitive::Decimal(y)),
) => (BigDecimal::from(*x)).cmp(y),
_ => {
self.done = true;
return Some(
UntaggedValue::Error(ShellError::labeled_error(
"Cannot create range",
"unsupported range",
self.tag.span,
))
.into_untagged_value(),
);
}
}
};
if self.moves_up
&& (ordering == Ordering::Less || self.is_end_inclusive && ordering == Ordering::Equal)
{
let next_value = nu_data::value::compute_values(Operator::Plus, &self.curr, &self.one);
let mut next = match next_value {
Ok(result) => result,
Err((left_type, right_type)) => {
self.done = true;
return Some(
UntaggedValue::Error(ShellError::coerce_error(
left_type.spanned(self.tag.span),
right_type.spanned(self.tag.span),
))
.into_untagged_value(),
);
}
};
std::mem::swap(&mut self.curr, &mut next);
Some(next.into_value(self.tag.clone()))
} else if !self.moves_up
&& (ordering == Ordering::Greater
|| self.is_end_inclusive && ordering == Ordering::Equal)
{
let next_value =
nu_data::value::compute_values(Operator::Plus, &self.curr, &self.negative_one);
let mut next = match next_value {
Ok(result) => result,
Err((left_type, right_type)) => {
self.done = true;
return Some(
UntaggedValue::Error(ShellError::coerce_error(
left_type.spanned(self.tag.span),
right_type.spanned(self.tag.span),
))
.into_untagged_value(),
);
}
};
std::mem::swap(&mut self.curr, &mut next);
Some(next.into_value(self.tag.clone()))
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::Echo;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
test_examples(Echo {})
}
}

View File

@ -0,0 +1,109 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, UntaggedValue, Value};
pub struct SubCommand;
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"error make"
}
fn signature(&self) -> Signature {
Signature::build("error make")
}
fn usage(&self) -> &str {
"Create an error."
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let input = args.input;
Ok(input
.map(|value| {
make_error(&value)
.map(|err| UntaggedValue::Error(err).into_value(value.tag()))
.unwrap_or_else(|| {
UntaggedValue::Error(ShellError::untagged_runtime_error(
"Creating error value not supported.",
))
.into_value(value.tag())
})
})
.into_output_stream())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Creates a labeled error",
example: r#"[
[ msg, labels, span];
["The message", "Helpful message here", ([[start, end]; [0, 141]])]
] | error make"#,
result: None,
}]
}
}
fn make_error(value: &Value) -> Option<ShellError> {
if let Value {
value: UntaggedValue::Row(dict),
..
} = value
{
let msg = dict.get_data_by_key("msg".spanned_unknown());
let labels =
dict.get_data_by_key("labels".spanned_unknown())
.and_then(|table| match &table.value {
UntaggedValue::Table(_) => table
.table_entries()
.map(|value| value.as_string().ok())
.collect(),
UntaggedValue::Primitive(Primitive::String(label)) => {
Some(vec![label.to_string()])
}
_ => None,
});
let _anchor = dict.get_data_by_key("tag".spanned_unknown());
let span = dict.get_data_by_key("span".spanned_unknown());
if msg.is_none() || labels.is_none() || span.is_none() {
return None;
}
let msg = msg.and_then(|msg| msg.as_string().ok());
if let Some(labels) = labels {
if labels.is_empty() {
return None;
}
return Some(ShellError::labeled_error(
msg.expect("Message will always be present."),
&labels[0],
span.map(|data| match data {
Value {
value: UntaggedValue::Row(vals),
..
} => match (vals.entries.get("start"), vals.entries.get("end")) {
(Some(start), Some(end)) => {
let start = start.as_usize().ok().unwrap_or(0);
let end = end.as_usize().ok().unwrap_or(0);
Span::new(start, end)
}
(_, _) => Span::unknown(),
},
_ => Span::unknown(),
})
.unwrap_or_else(Span::unknown),
));
}
}
None
}

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