Compare commits

...

84 Commits

Author SHA1 Message Date
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
541 changed files with 12440 additions and 6802 deletions

View File

@ -21,15 +21,6 @@ strategy:
windows-stable: windows-stable:
image: windows-2019 image: windows-2019
style: 'unflagged' 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: fmt:
image: ubuntu-18.04 image: ubuntu-18.04
style: 'fmt' style: 'fmt'
@ -62,15 +53,9 @@ steps:
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used - bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
condition: eq(variables['style'], 'unflagged') condition: eq(variables['style'], 'unflagged')
displayName: Check clippy lints 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 && wasm-pack build
condition: eq(variables['style'], 'wasm') condition: eq(variables['style'], 'wasm')
displayName: Wasm build 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 - bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features --features=rustyline-support
condition: eq(variables['style'], 'minimal') condition: eq(variables['style'], 'minimal')
displayName: Run tests displayName: Run tests

913
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ license = "MIT"
name = "nu" name = "nu"
readme = "README.md" readme = "README.md"
repository = "https://github.com/nushell/nushell" repository = "https://github.com/nushell/nushell"
version = "0.32.0" version = "0.33.0"
[workspace] [workspace]
members = ["crates/*/"] members = ["crates/*/"]
@ -18,47 +18,46 @@ members = ["crates/*/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-cli = { version = "0.32.0", path = "./crates/nu-cli", default-features = false } nu-cli = { version="0.33.0", path="./crates/nu-cli", default-features=false }
nu-command = { version = "0.32.0", path = "./crates/nu-command" } nu-command = { version="0.33.0", path="./crates/nu-command" }
nu-data = { version = "0.32.0", path = "./crates/nu-data" } nu-completion = { version="0.33.0", path="./crates/nu-completion" }
nu-engine = { version = "0.32.0", path = "./crates/nu-engine" } nu-data = { version="0.33.0", path="./crates/nu-data" }
nu-errors = { version = "0.32.0", path = "./crates/nu-errors" } nu-engine = { version="0.33.0", path="./crates/nu-engine" }
nu-parser = { version = "0.32.0", path = "./crates/nu-parser" } nu-errors = { version="0.33.0", path="./crates/nu-errors" }
nu-plugin = { version = "0.32.0", path = "./crates/nu-plugin" } nu-parser = { version="0.33.0", path="./crates/nu-parser" }
nu-protocol = { version = "0.32.0", path = "./crates/nu-protocol" } nu-path = { version="0.33.0", path="./crates/nu-path" }
nu-source = { version = "0.32.0", path = "./crates/nu-source" } nu-plugin = { version="0.33.0", path="./crates/nu-plugin" }
nu-value-ext = { version = "0.32.0", path = "./crates/nu-value-ext" } nu-protocol = { version="0.33.0", path="./crates/nu-protocol" }
nu-source = { version="0.33.0", path="./crates/nu-source" }
nu-value-ext = { version="0.33.0", path="./crates/nu-value-ext" }
nu_plugin_binaryview = { version = "0.32.0", path = "./crates/nu_plugin_binaryview", optional = true } nu_plugin_binaryview = { version="0.33.0", path="./crates/nu_plugin_binaryview", optional=true }
nu_plugin_chart = { version = "0.32.0", path = "./crates/nu_plugin_chart", optional = true } nu_plugin_chart = { version="0.33.0", path="./crates/nu_plugin_chart", optional=true }
nu_plugin_fetch = { version = "0.32.0", path = "./crates/nu_plugin_fetch", optional = true } nu_plugin_fetch = { version="0.33.0", path="./crates/nu_plugin_fetch", optional=true }
nu_plugin_from_bson = { version = "0.32.0", path = "./crates/nu_plugin_from_bson", optional = true } nu_plugin_from_bson = { version="0.33.0", path="./crates/nu_plugin_from_bson", optional=true }
nu_plugin_from_sqlite = { version = "0.32.0", path = "./crates/nu_plugin_from_sqlite", optional = true } nu_plugin_from_sqlite = { version="0.33.0", path="./crates/nu_plugin_from_sqlite", optional=true }
nu_plugin_inc = { version = "0.32.0", path = "./crates/nu_plugin_inc", optional = true } nu_plugin_inc = { version="0.33.0", path="./crates/nu_plugin_inc", optional=true }
nu_plugin_match = { version = "0.32.0", path = "./crates/nu_plugin_match", optional = true } nu_plugin_match = { version="0.33.0", path="./crates/nu_plugin_match", optional=true }
nu_plugin_post = { version = "0.32.0", path = "./crates/nu_plugin_post", optional = true } nu_plugin_post = { version="0.33.0", path="./crates/nu_plugin_post", optional=true }
nu_plugin_ps = { version = "0.32.0", path = "./crates/nu_plugin_ps", optional = true } nu_plugin_ps = { version="0.33.0", path="./crates/nu_plugin_ps", optional=true }
nu_plugin_query_json = { version = "0.32.0", path = "./crates/nu_plugin_query_json", optional = true } nu_plugin_query_json = { version="0.33.0", path="./crates/nu_plugin_query_json", optional=true }
nu_plugin_s3 = { version = "0.32.0", path = "./crates/nu_plugin_s3", optional = true } nu_plugin_s3 = { version="0.33.0", path="./crates/nu_plugin_s3", optional=true }
nu_plugin_selector = { version = "0.32.0", path = "./crates/nu_plugin_selector", optional = true } nu_plugin_selector = { version="0.33.0", path="./crates/nu_plugin_selector", optional=true }
nu_plugin_start = { version = "0.32.0", path = "./crates/nu_plugin_start", optional = true } nu_plugin_start = { version="0.33.0", path="./crates/nu_plugin_start", optional=true }
nu_plugin_sys = { version = "0.32.0", path = "./crates/nu_plugin_sys", optional = true } nu_plugin_sys = { version="0.33.0", path="./crates/nu_plugin_sys", optional=true }
nu_plugin_textview = { version = "0.32.0", path = "./crates/nu_plugin_textview", optional = true } nu_plugin_textview = { version="0.33.0", path="./crates/nu_plugin_textview", optional=true }
nu_plugin_to_bson = { version = "0.32.0", path = "./crates/nu_plugin_to_bson", optional = true } nu_plugin_to_bson = { version="0.33.0", path="./crates/nu_plugin_to_bson", optional=true }
nu_plugin_to_sqlite = { version = "0.32.0", path = "./crates/nu_plugin_to_sqlite", optional = true } nu_plugin_to_sqlite = { version="0.33.0", path="./crates/nu_plugin_to_sqlite", optional=true }
nu_plugin_tree = { version = "0.32.0", path = "./crates/nu_plugin_tree", optional = true } nu_plugin_tree = { version="0.33.0", path="./crates/nu_plugin_tree", optional=true }
nu_plugin_xpath = { version = "0.32.0", path = "./crates/nu_plugin_xpath", optional = true } nu_plugin_xpath = { version="0.33.0", path="./crates/nu_plugin_xpath", optional=true }
# Required to bootstrap the main binary # Required to bootstrap the main binary
clap = "2.33.3" ctrlc = { version="3.1.7", optional=true }
ctrlc = { version = "3.1.7", optional = true } futures = { version="0.3.12", features=["compat", "io-compat"] }
futures = { version = "0.3.12", features = ["compat", "io-compat"] }
itertools = "0.10.0" itertools = "0.10.0"
log = "0.4.14"
pretty_env_logger = "0.4.0"
[dev-dependencies] [dev-dependencies]
nu-test-support = { version = "0.32.0", path = "./crates/nu-test-support" } nu-test-support = { version="0.33.0", path="./crates/nu-test-support" }
dunce = "1.0.1" dunce = "1.0.1"
serial_test = "0.5.1" serial_test = "0.5.1"
hamcrest2 = "0.3.0" hamcrest2 = "0.3.0"
@ -68,26 +67,17 @@ hamcrest2 = "0.3.0"
[features] [features]
ctrlc-support = ["nu-cli/ctrlc", "nu-command/ctrlc"] 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"] ptree-support = ["nu-cli/ptree", "nu-command/ptree"]
rustyline-support = ["nu-cli/rustyline-support", "nu-command/rustyline-support"] rustyline-support = ["nu-cli/rustyline-support", "nu-command/rustyline-support"]
term-support = ["nu-cli/term", "nu-command/term"] term-support = ["nu-cli/term", "nu-command/term"]
uuid-support = ["nu-cli/uuid_crate", "nu-command/uuid_crate"] uuid-support = ["nu-cli/uuid_crate", "nu-command/uuid_crate"]
which-support = ["nu-cli/which", "nu-command/which", "nu-engine/which"] which-support = ["nu-cli/which", "nu-command/which", "nu-engine/which"]
executable-support = ["nu-completion/is_executable"]
default = [ default = [
"nu-cli/shadow-rs", "nu-cli/shadow-rs",
"sys", "sys",
"ps", "ps",
"directories-support",
"ctrlc-support", "ctrlc-support",
"which-support", "which-support",
"term-support", "term-support",
@ -96,6 +86,7 @@ default = [
"post", "post",
"fetch", "fetch",
"zip-support", "zip-support",
"executable-support",
] ]
stable = ["default"] stable = ["default"]
@ -156,6 +147,7 @@ table-pager = ["nu-command/table-pager"]
#dataframe feature for nushell #dataframe feature for nushell
dataframe = [ dataframe = [
"nu-engine/dataframe",
"nu-protocol/dataframe", "nu-protocol/dataframe",
"nu-command/dataframe", "nu-command/dataframe",
"nu-value-ext/dataframe", "nu-value-ext/dataframe",

View File

@ -61,13 +61,19 @@ Optional dependencies:
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`): 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 cargo install nu
``` ```
To install Nu via the [Windows Package Manager](https://aka.ms/winget-cli):
```shell
winget install nu
```
You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/installation.html#dependencies) for your platform), once you have checked out this repo with git: You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/installation.html#dependencies) for your platform), once you have checked out this repo with git:
```bash ```shell
cargo build --workspace --features=extra cargo build --workspace --features=extra
``` ```
@ -77,7 +83,7 @@ cargo build --workspace --features=extra
Want to try Nu right away? Execute the following to get started. Want to try Nu right away? Execute the following to get started.
```bash ```shell
docker run -it quay.io/nushell/nu:latest docker run -it quay.io/nushell/nu:latest
``` ```
@ -86,30 +92,30 @@ docker run -it quay.io/nushell/nu:latest
If you want to pull a pre-built container, you can browse tags for the [nushell organization](https://quay.io/organization/nushell) 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: on Quay.io. Pulling a container would come down to:
```bash ```shell
docker pull quay.io/nushell/nu docker pull quay.io/nushell/nu
docker pull quay.io/nushell/nu-base 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` 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. in the container and all dependencies.
Optionally, you can also build the containers locally using the [dockerfiles provided](docker): Optionally, you can also build the containers locally using the [dockerfiles provided](docker):
To build the base image: To build the base image:
```bash ```shell
docker build -f docker/Dockerfile.nu-base -t nushell/nu-base . docker build -f docker/Dockerfile.nu-base -t nushell/nu-base .
``` ```
And then to build the smaller container (using a Multistage build): And then to build the smaller container (using a Multistage build):
```bash ```shell
docker build -f docker/Dockerfile -t nushell/nu . docker build -f docker/Dockerfile -t nushell/nu .
``` ```
Either way, you can run either container as follows: Either way, you can run either container as follows:
```bash ```shell
docker run -it nushell/nu-base docker run -it nushell/nu-base
docker run -it nushell/nu docker run -it nushell/nu
/> exit /> exit
@ -136,13 +142,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. 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_. 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). 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 work in the pipeline fit into one of three categories:
- Commands that produce a stream (eg, `ls`) - Commands that produce a stream (e.g., `ls`)
- Commands that filter a stream (eg, `where type == "Dir"`) - Commands that filter a stream (eg, `where type == "Dir"`)
- Commands that consume the output of the pipeline (eg, `autoview`) - 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. Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
@ -171,7 +177,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. 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 ```shell
> ps | where cpu > 0 > ps | where cpu > 0
@ -188,7 +194,7 @@ For example, we could use the built-in `ps` command as well to get a list of the
### Opening files ### 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: For example, you can load a .toml file as structured data and explore it:
```shell ```shell
@ -247,9 +253,9 @@ To set one of these variables, you can use `config set`. For example:
### Shells ### Shells
Nu will work inside of a single directory and allow you to navigate around your filesystem by default. 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. 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. Once you're done with a shell, you can `exit` it and remove it from the ring buffer.
@ -263,7 +269,7 @@ This allows you to extend nu for your needs.
There are a few examples in the `plugins` directory. 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. 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 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. 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.
@ -271,7 +277,7 @@ 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. 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.
@ -287,28 +293,28 @@ You can find a list of Nu commands, complete with documentation, in [quick comma
## Progress ## 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 | | Features | Not started | Prototype | MVP | Preview | Mature | Notes |
| ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- | | ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- |
| Aliases | | X | | | | Initial implementation but lacks necessary features | | Aliases | | | X | | | Aliases allow for shortening large commands, while passing flags |
| Notebook | | X | | | | Initial jupyter support, but it loses state and lacks 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 | | File ops | | | X | | | cp, mv, rm, mkdir have some support, but lacking others |
| Environment | | X | | | | Temporary environment, but no session-wide env variables | | Environment | | | X | | | Temporary environment and scoped environment variables |
| Shells | | X | | | | Basic value and file shells, but no opt-in/opt-out for commands | | Shells | | X | | | | Basic value and file shells, but no opt-in/opt-out for commands |
| Protocol | | | X | | | Streaming protocol is serviceable | | Protocol | | | X | | | Streaming protocol is serviceable |
| Plugins | | X | | | | Plugins work on one row at a time, lack batching and expression eval | | 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 | | Errors | | | X | | | Error reporting works, but could use usability polish |
| Documentation | | X | | | | Book and related are barebones and lack task-based lessons | | Documentation | | | X | | | Book updated to latest release, including usage examples |
| Paging | | X | | | | Textview has paging, but we'd like paging for tables | | Paging | | X | | | | Textview has paging, but we'd like paging for tables |
| Functions | | X | | | | No functions, yet, only aliases | | Functions | | | X | | | Functions and aliases are supported |
| Variables | | X | | | | Nu doesn't yet support variables | | Variables | | | X | | | Nu supports variables and environment variables |
| Completions | | X | | | | Completions are currently barebones, at best | | Completions | | | X | | | Completions for filepaths |
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked | | Type-checking | | | X | | | Commands check basic types, but input/output isn't checked |
## Current Roadmap ## Current Roadmap
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in Nushell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2). We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release and capture some important issues we'd like to see in Nushell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
## Contributing ## Contributing

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 differnt capabilities like working with different file types, charting, viewing binary data, and more.

View File

@ -9,7 +9,7 @@ description = "Library for ANSI terminal colors and styles (bold, underline)"
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.32.0" version = "0.33.0"
[lib] [lib]
doctest = false doctest = false
@ -18,10 +18,15 @@ doctest = false
[features] [features]
derive_serde_style = ["serde"] derive_serde_style = ["serde"]
[dependencies.serde] [dependencies]
version = "1.0.90" overload = "0.1.1"
features = ["derive"] serde = { version="1.0.90", features=["derive"], optional=true }
optional = true itertools = "0.10.0"
# [dependencies.serde]
# version = "1.0.90"
# features = ["derive"]
# optional = true
[target.'cfg(target_os="windows")'.dependencies.winapi] [target.'cfg(target_os="windows")'.dependencies.winapi]
version = "0.3.4" version = "0.3.4"

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

@ -320,7 +320,7 @@ impl fmt::Display for Infix {
let f: &mut dyn fmt::Write = f; let f: &mut dyn fmt::Write = f;
write!(f, "{}{}", RESET, self.1.prefix()) write!(f, "{}{}", RESET, self.1.prefix())
} }
Difference::NoDifference => { Difference::Empty => {
Ok(()) // nothing to write Ok(()) // nothing to write
} }
} }

View File

@ -14,7 +14,7 @@ pub enum Difference {
/// The before style is exactly the same as the after style, so no further /// The before style is exactly the same as the after style, so no further
/// control codes need to be printed. /// control codes need to be printed.
NoDifference, Empty,
} }
impl Difference { impl Difference {
@ -40,7 +40,7 @@ impl Difference {
// it commented out for now, and defaulting to Reset. // it commented out for now, and defaulting to Reset.
if first == next { if first == next {
return NoDifference; return Empty;
} }
// Cannot un-bold, so must Reset. // Cannot un-bold, so must Reset.
@ -153,10 +153,10 @@ mod test {
}; };
} }
test!(nothing: Green.normal(); Green.normal() => NoDifference); test!(nothing: Green.normal(); Green.normal() => Empty);
test!(uppercase: Green.normal(); Green.bold() => ExtraStyles(style().bold())); test!(uppercase: Green.normal(); Green.bold() => ExtraStyles(style().bold()));
test!(lowercase: Green.bold(); Green.normal() => Reset); test!(lowercase: Green.bold(); Green.normal() => Reset);
test!(nothing2: Green.bold(); Green.bold() => NoDifference); test!(nothing2: Green.bold(); Green.bold() => Empty);
test!(color_change: Red.normal(); Blue.normal() => ExtraStyles(Blue.normal())); test!(color_change: Red.normal(); Blue.normal() => ExtraStyles(Blue.normal()));

View File

@ -266,7 +266,7 @@ where
match Difference::between(&window[0].style, &window[1].style) { match Difference::between(&window[0].style, &window[1].style) {
ExtraStyles(style) => write!(w, "{}", style.prefix())?, ExtraStyles(style) => write!(w, "{}", style.prefix())?,
Reset => write!(w, "{}{}", RESET, window[1].style.prefix())?, Reset => write!(w, "{}{}", RESET, window[1].style.prefix())?,
NoDifference => { /* Do nothing! */ } Empty => { /* Do nothing! */ }
} }
w.write_str(&window[1].string)?; w.write_str(&window[1].string)?;

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

@ -121,13 +121,13 @@
//! `Fixed` colors instead, but theres nothing to be gained by doing so //! `Fixed` colors instead, but theres nothing to be gained by doing so
//! either. //! either.
//! //!
//! You can also access full 24-bit color by using the `Color::RGB` variant, //! You can also access full 24-bit color by using the `Color::Rgb` variant,
//! which takes separate `u8` arguments for red, green, and blue: //! which takes separate `u8` arguments for red, green, and blue:
//! //!
//! ``` //! ```
//! use nu_ansi_term::Color::RGB; //! use nu_ansi_term::Color::Rgb;
//! //!
//! RGB(70, 130, 180).paint("Steel blue"); //! Rgb(70, 130, 180).paint("Steel blue");
//! ``` //! ```
//! //!
//! ## Combining successive colored strings //! ## Combining successive colored strings
@ -233,7 +233,7 @@
#![crate_type = "rlib"] #![crate_type = "rlib"]
#![crate_type = "dylib"] #![crate_type = "dylib"]
#![warn(missing_copy_implementations)] #![warn(missing_copy_implementations)]
#![warn(missing_docs)] // #![warn(missing_docs)]
#![warn(trivial_casts, trivial_numeric_casts)] #![warn(trivial_casts, trivial_numeric_casts)]
// #![warn(unused_extern_crates, unused_qualifications)] // #![warn(unused_extern_crates, unused_qualifications)]
@ -265,3 +265,9 @@ mod util;
pub use util::*; pub use util::*;
mod debug; 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

@ -364,10 +364,16 @@ pub enum Color {
/// [cc]: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg /// [cc]: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg
Fixed(u8), Fixed(u8),
/// A 24-bit RGB color, as specified by ISO-8613-3. /// A 24-bit Rgb color, as specified by ISO-8613-3.
Rgb(u8, u8, u8), Rgb(u8, u8, u8),
} }
impl Default for Color {
fn default() -> Self {
Color::White
}
}
impl Color { impl Color {
/// Returns a `Style` with the foreground color set to this color. /// Returns a `Style` with the foreground color set to this color.
/// ///
@ -546,7 +552,7 @@ impl Color {
/// ``` /// ```
/// use nu_ansi_term::Color; /// use nu_ansi_term::Color;
/// ///
/// let style = Color::RGB(31, 31, 31).on(Color::White); /// let style = Color::Rgb(31, 31, 31).on(Color::White);
/// println!("{}", style.paint("eyyyy")); /// println!("{}", style.paint("eyyyy"));
/// ``` /// ```
pub fn on(self, background: Color) -> Style { pub fn on(self, background: Color) -> Style {
@ -584,13 +590,13 @@ mod serde_json_tests {
let colors = &[ let colors = &[
Color::Red, Color::Red,
Color::Blue, Color::Blue,
Color::RGB(123, 123, 123), Color::Rgb(123, 123, 123),
Color::Fixed(255), Color::Fixed(255),
]; ];
assert_eq!( assert_eq!(
serde_json::to_string(&colors).unwrap(), serde_json::to_string(&colors).unwrap(),
String::from("[\"Red\",\"Blue\",{\"RGB\":[123,123,123]},{\"Fixed\":255}]") String::from("[\"Red\",\"Blue\",{\"Rgb\":[123,123,123]},{\"Fixed\":255}]")
); );
} }
@ -599,11 +605,11 @@ mod serde_json_tests {
let colors = &[ let colors = &[
Color::Red, Color::Red,
Color::Blue, Color::Blue,
Color::RGB(123, 123, 123), Color::Rgb(123, 123, 123),
Color::Fixed(255), Color::Fixed(255),
]; ];
for color in colors.into_iter() { for color in colors.iter() {
let serialized = serde_json::to_string(&color).unwrap(); let serialized = serde_json::to_string(&color).unwrap();
let deserialized: Color = serde_json::from_str(&serialized).unwrap(); let deserialized: Color = serde_json::from_str(&serialized).unwrap();

View File

@ -43,7 +43,7 @@ pub fn unstyle(strs: &AnsiStrings) -> String {
let mut s = String::new(); let mut s = String::new();
for i in strs.0.iter() { for i in strs.0.iter() {
s += &i.deref(); s += i.deref();
} }
s s

View File

@ -5,70 +5,69 @@ description = "CLI for nushell"
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
name = "nu-cli" name = "nu-cli"
version = "0.32.0" version = "0.33.0"
[lib] [lib]
doctest = false doctest = false
[dependencies] [dependencies]
nu-command = { version = "0.32.0", path = "../nu-command" } nu-completion = { version="0.33.0", path="../nu-completion" }
nu-data = { version = "0.32.0", path = "../nu-data" } nu-command = { version="0.33.0", path="../nu-command" }
nu-engine = { version = "0.32.0", path = "../nu-engine" } nu-data = { version="0.33.0", path="../nu-data" }
nu-errors = { version = "0.32.0", path = "../nu-errors" } nu-engine = { version="0.33.0", path="../nu-engine" }
nu-json = { version = "0.32.0", path = "../nu-json" } nu-errors = { version="0.33.0", path="../nu-errors" }
nu-parser = { version = "0.32.0", path = "../nu-parser" } nu-json = { version="0.33.0", path="../nu-json" }
nu-plugin = { version = "0.32.0", path = "../nu-plugin" } nu-parser = { version="0.33.0", path="../nu-parser" }
nu-protocol = { version = "0.32.0", path = "../nu-protocol" } nu-plugin = { version="0.33.0", path="../nu-plugin" }
nu-source = { version = "0.32.0", path = "../nu-source" } nu-protocol = { version="0.33.0", path="../nu-protocol" }
nu-stream = { version = "0.32.0", path = "../nu-stream" } nu-source = { version="0.33.0", path="../nu-source" }
nu-table = { version = "0.32.0", path = "../nu-table" } nu-stream = { version="0.33.0", path="../nu-stream" }
nu-test-support = { version = "0.32.0", path = "../nu-test-support" } nu-table = { version="0.33.0", path="../nu-table" }
nu-value-ext = { version = "0.32.0", path = "../nu-value-ext" } nu-test-support = { version="0.33.0", path="../nu-test-support" }
nu-ansi-term = { version = "0.32.0", path = "../nu-ansi-term" } nu-value-ext = { version="0.33.0", path="../nu-value-ext" }
nu-pretty-hex = { version = "0.32.0", path = "../nu-pretty-hex" } nu-ansi-term = { version="0.33.0", path="../nu-ansi-term" }
nu-pretty-hex = { version="0.33.0", path="../nu-pretty-hex" }
Inflector = "0.11" Inflector = "0.11"
arboard = { version = "1.1.0", optional = true } arboard = { version="1.1.0", optional=true }
async-recursion = "0.3.2" async-recursion = "0.3.2"
async-trait = "0.1.42" async-trait = "0.1.42"
base64 = "0.13.0" base64 = "0.13.0"
bigdecimal = { version = "0.2.0", features = ["serde"] } bigdecimal = { version="0.2.0", features=["serde"] }
byte-unit = "4.0.9" byte-unit = "4.0.9"
bytes = "1.0.1" bytes = "1.0.1"
calamine = "0.17.0" calamine = "0.17.0"
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version="0.4.19", features=["serde"] }
chrono-tz = "0.5.3" chrono-tz = "0.5.3"
clap = "2.33.3"
codespan-reporting = "0.11.0" codespan-reporting = "0.11.0"
csv = "1.1.5" csv = "1.1.5"
ctrlc = { version = "3.1.7", optional = true } ctrlc = { version="3.1.7", optional=true }
derive-new = "0.5.8" derive-new = "0.5.8"
directories-next = { version = "2.0.0", optional = true } directories-next = "2.0.0"
dirs-next = { version = "2.0.0", optional = true } dirs-next = "2.0.0"
dtparse = "1.2.0" dtparse = "1.2.0"
dunce = "1.0.1" dunce = "1.0.1"
eml-parser = "0.1.0" eml-parser = "0.1.0"
encoding_rs = "0.8.28" encoding_rs = "0.8.28"
filesize = "0.2.0" filesize = "0.2.0"
fs_extra = "1.2.0" fs_extra = "1.2.0"
futures = { version = "0.3.12", features = ["compat", "io-compat"] } futures = { version="0.3.12", features=["compat", "io-compat"] }
futures-util = "0.3.12" futures-util = "0.3.12"
futures_codec = "0.4.1" futures_codec = "0.4.1"
getset = "0.1.1" getset = "0.1.1"
glob = "0.3.0" glob = "0.3.0"
htmlescape = "0.3.1" htmlescape = "0.3.1"
ical = "0.7.0" ical = "0.7.0"
indexmap = { version = "1.6.1", features = ["serde-1"] } indexmap = { version="1.6.1", features=["serde-1"] }
itertools = "0.10.0"
lazy_static = "1.*"
log = "0.4.14" log = "0.4.14"
pretty_env_logger = "0.4.0"
meval = "0.2.0" meval = "0.2.0"
num-bigint = { version = "0.3.1", features = ["serde"] } num-bigint = { version="0.3.1", features=["serde"] }
num-format = { version = "0.4.0", features = ["with-num-bigint"] } num-format = { version="0.4.0", features=["with-num-bigint"] }
num-traits = "0.2.14" num-traits = "0.2.14"
parking_lot = "0.11.1" parking_lot = "0.11.1"
pin-utils = "0.1.0" pin-utils = "0.1.0"
ptree = { version = "0.3.1", optional = true } ptree = { version="0.3.1", optional=true }
query_interface = "0.3.5" query_interface = "0.3.5"
quick-xml = "0.21.0" quick-xml = "0.21.0"
rand = "0.8.3" rand = "0.8.3"
@ -76,31 +75,30 @@ rayon = "1.5.0"
regex = "1.4.3" regex = "1.4.3"
roxmltree = "0.14.0" roxmltree = "0.14.0"
rust-embed = "5.9.0" rust-embed = "5.9.0"
rustyline = { version = "8.1.0", optional = true } rustyline = { version="8.1.0", optional=true }
serde = { version = "1.0.123", features = ["derive"] } serde = { version="1.0.123", features=["derive"] }
serde_bytes = "0.11.5" serde_bytes = "0.11.5"
serde_ini = "0.2.0" serde_ini = "0.2.0"
serde_json = "1.0.61" serde_json = "1.0.61"
serde_urlencoded = "0.7.0" serde_urlencoded = "0.7.0"
serde_yaml = "0.8.16" serde_yaml = "0.8.16"
sha2 = "0.9.3" sha2 = "0.9.3"
shellexpand = "2.1.0"
strip-ansi-escapes = "0.1.0" strip-ansi-escapes = "0.1.0"
sxd-document = "0.3.2" sxd-document = "0.3.2"
sxd-xpath = "0.4.2" sxd-xpath = "0.4.2"
tempfile = "3.2.0" tempfile = "3.2.0"
term = { version = "0.7.0", optional = true } term = { version="0.7.0", optional=true }
term_size = "0.3.2" term_size = "0.3.2"
termcolor = "1.1.2" termcolor = "1.1.2"
titlecase = "1.1.0" titlecase = "1.1.0"
toml = "0.5.8" toml = "0.5.8"
trash = { version = "1.3.0", optional = true } trash = { version="1.3.0", optional=true }
unicode-segmentation = "1.7.1" unicode-segmentation = "1.7.1"
url = "2.1.1" url = "2.1.1"
uuid_crate = { package = "uuid", version = "0.8.2", features = ["v4"], optional = true } uuid_crate = { package="uuid", version="0.8.2", features=["v4"], optional=true }
which = { version = "4.0.2", optional = true } which = { version="4.0.2", optional=true }
zip = { version = "0.5.9", optional = true } zip = { version="0.5.9", optional=true }
shadow-rs = { version = "0.5", default-features = false, optional = true } shadow-rs = { version="0.5", default-features=false, optional=true }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
umask = "1.0.0" umask = "1.0.0"
@ -130,5 +128,3 @@ clipboard-cli = ["arboard"]
rustyline-support = ["rustyline", "nu-engine/rustyline-support"] rustyline-support = ["rustyline", "nu-engine/rustyline-support"]
stable = [] stable = []
trash-support = ["trash"] 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.

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

@ -0,0 +1,525 @@
mod logger;
mod options;
mod options_parser;
pub use options::{CliOptions, NuScript, Options};
use options_parser::{NuParser, OptionsParser};
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 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> {
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)?;
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)?;
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();
use logger::{configure, debug_filters, logger, trace_filters};
logger(|builder| {
configure(&self, builder)?;
trace_filters(&self, builder)?;
debug_filters(&self, builder)?;
Ok(())
})?;
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 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(());
}
let context = crate::create_default_context(true)?;
if !self.skip_plugins() {
let _ = crate::register_plugins(&context);
}
#[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 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();
let args = format!("nu -l {}", *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_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,100 @@
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,
}
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,
}
}
}
#[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).map(Clone::clone)
}
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,143 @@
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);
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) {
Some(v)
} else {
Some(
UntaggedValue::Error(
ShellError::untagged_runtime_error(
format!("{} is not supported.", name),
),
)
.into_value(v.tag),
)
}
} else {
Some(v)
}
}
"loglevel" => {
if let Ok(name) = v.as_string() {
if loglevels().iter().any(|n| name == *n) {
Some(v)
} else {
Some(
UntaggedValue::Error(
ShellError::untagged_runtime_error(
format!("{} is not supported.", name),
),
)
.into_value(v.tag),
)
}
} else {
Some(v)
}
}
_ => Some(v),
})
.flatten();
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

@ -15,7 +15,6 @@ use crate::line_editor::{
use nu_data::config; use nu_data::config;
use nu_source::{Tag, Text}; use nu_source::{Tag, Text};
use nu_stream::InputStream; use nu_stream::InputStream;
use std::ffi::{OsStr, OsString};
#[allow(unused_imports)] #[allow(unused_imports)]
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
@ -31,69 +30,6 @@ use std::error::Error;
use std::iter::Iterator; use std::iter::Iterator;
use std::path::PathBuf; use std::path::PathBuf;
pub struct Options {
pub config: Option<OsString>,
pub stdin: bool,
pub scripts: Vec<NuScript>,
pub save_history: bool,
}
impl Default for Options {
fn default() -> Self {
Self::new()
}
}
impl Options {
pub fn new() -> Self {
Self {
config: None,
stdin: false,
scripts: vec![],
save_history: true,
}
}
}
pub struct NuScript {
pub filepath: Option<OsString>,
pub contents: String,
}
impl NuScript {
pub fn code<'a>(content: impl Iterator<Item = &'a str>) -> Result<Self, ShellError> {
let text = content
.map(|x| x.to_string())
.collect::<Vec<String>>()
.join("\n");
Ok(Self {
filepath: None,
contents: text,
})
}
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,
})
}
}
pub fn search_paths() -> Vec<std::path::PathBuf> { pub fn search_paths() -> Vec<std::path::PathBuf> {
use std::env; use std::env;
@ -123,7 +59,10 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
search_paths search_paths
} }
pub fn run_script_file(context: EvaluationContext, options: Options) -> Result<(), Box<dyn Error>> { pub fn run_script_file(
context: EvaluationContext,
options: super::app::CliOptions,
) -> Result<(), ShellError> {
if let Some(cfg) = options.config { if let Some(cfg) = options.config {
load_cfg_as_global_cfg(&context, PathBuf::from(cfg)); load_cfg_as_global_cfg(&context, PathBuf::from(cfg));
} else { } else {
@ -144,7 +83,10 @@ pub fn run_script_file(context: EvaluationContext, options: Options) -> Result<(
} }
#[cfg(feature = "rustyline-support")] #[cfg(feature = "rustyline-support")]
pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn Error>> { pub fn cli(
context: EvaluationContext,
options: super::app::CliOptions,
) -> Result<(), Box<dyn Error>> {
let _ = configure_ctrl_c(&context); let _ = configure_ctrl_c(&context);
// start time for running startup scripts (this metric includes loading of the cfg, but w/e) // start time for running startup scripts (this metric includes loading of the cfg, but w/e)
@ -157,8 +99,8 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
} }
// Store cmd duration in an env var // Store cmd duration in an env var
context.scope.add_env_var( context.scope.add_env_var(
"CMD_DURATION", "CMD_DURATION_MS",
format!("{:?}", startup_commands_start_time.elapsed()), format!("{}", startup_commands_start_time.elapsed().as_millis()),
); );
trace!( trace!(
"startup commands took {:?}", "startup commands took {:?}",
@ -167,7 +109,7 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
//Configure rustyline //Configure rustyline
let mut rl = default_rustyline_editor_configuration(); let mut rl = default_rustyline_editor_configuration();
let history_path = if let Some(cfg) = &context.configs.lock().global_config { let history_path = if let Some(cfg) = &context.configs().lock().global_config {
let _ = configure_rustyline_editor(&mut rl, cfg); let _ = configure_rustyline_editor(&mut rl, cfg);
let helper = Some(nu_line_editor_helper(&context, cfg)); let helper = Some(nu_line_editor_helper(&context, cfg));
rl.set_helper(helper); rl.set_helper(helper);
@ -182,7 +124,8 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
} }
//set vars from cfg if present //set vars from cfg if present
let (skip_welcome_message, prompt) = if let Some(cfg) = &context.configs.lock().global_config { let (skip_welcome_message, prompt) = if let Some(cfg) = &context.configs().lock().global_config
{
( (
cfg.var("skip_welcome_message") cfg.var("skip_welcome_message")
.map(|x| x.is_true()) .map(|x| x.is_true())
@ -205,7 +148,7 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
if !skip_welcome_message { if !skip_welcome_message {
println!( println!(
"Welcome to Nushell {} (type 'help' for more info)", "Welcome to Nushell {} (type 'help' for more info)",
clap::crate_version!() nu_command::commands::core_version()
); );
} }
@ -217,12 +160,12 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
let mut ctrlcbreak = false; let mut ctrlcbreak = false;
loop { loop {
if context.ctrl_c.load(Ordering::SeqCst) { if context.ctrl_c().load(Ordering::SeqCst) {
context.ctrl_c.store(false, Ordering::SeqCst); context.ctrl_c().store(false, Ordering::SeqCst);
continue; continue;
} }
let cwd = context.shell_manager.path(); let cwd = context.shell_manager().path();
let colored_prompt = { let colored_prompt = {
if let Some(prompt) = &prompt { if let Some(prompt) = &prompt {
@ -266,14 +209,14 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
} }
} }
Err(e) => { Err(e) => {
context.host.lock().print_err(e, &Text::from(prompt_line)); context.host().lock().print_err(e, &Text::from(prompt_line));
context.clear_errors(); context.clear_errors();
"> ".to_string() "> ".to_string()
} }
}, },
Err(e) => { Err(e) => {
context.host.lock().print_err(e, &Text::from(prompt_line)); context.host().lock().print_err(e, &Text::from(prompt_line));
context.clear_errors(); context.clear_errors();
"> ".to_string() "> ".to_string()
@ -307,7 +250,7 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
let mut initial_command = Some(String::new()); let mut initial_command = Some(String::new());
let mut readline = Err(ReadlineError::Eof); let mut readline = Err(ReadlineError::Eof);
while let Some(ref cmd) = initial_command { while let Some(ref cmd) = initial_command {
readline = rl.readline_with_initial(&prompt, (&cmd, "")); readline = rl.readline_with_initial(&prompt, (cmd, ""));
initial_command = None; initial_command = None;
} }
@ -332,9 +275,10 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
}; };
// Store cmd duration in an env var // Store cmd duration in an env var
context context.scope.add_env_var(
.scope "CMD_DURATION_MS",
.add_env_var("CMD_DURATION", format!("{:?}", cmd_start_time.elapsed())); format!("{}", cmd_start_time.elapsed().as_millis()),
);
match line { match line {
LineResult::Success(line) => { LineResult::Success(line) => {
@ -359,7 +303,7 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
} }
context context
.host .host()
.lock() .lock()
.print_err(err, &Text::from(session_text.clone())); .print_err(err, &Text::from(session_text.clone()));
@ -373,7 +317,7 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
LineResult::CtrlC => { LineResult::CtrlC => {
let config_ctrlc_exit = context let config_ctrlc_exit = context
.configs .configs()
.lock() .lock()
.global_config .global_config
.as_ref() .as_ref()
@ -399,8 +343,8 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
} }
LineResult::CtrlD => { LineResult::CtrlD => {
context.shell_manager.remove_at_current(); context.shell_manager().remove_at_current();
if context.shell_manager.is_empty() { if context.shell_manager().is_empty() {
break; break;
} }
} }
@ -422,15 +366,15 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
pub fn load_local_cfg_if_present(context: &EvaluationContext) { pub fn load_local_cfg_if_present(context: &EvaluationContext) {
trace!("Loading local cfg if present"); trace!("Loading local cfg if present");
match config::loadable_cfg_exists_in_dir(PathBuf::from(context.shell_manager.path())) { match config::loadable_cfg_exists_in_dir(PathBuf::from(context.shell_manager().path())) {
Ok(Some(cfg_path)) => { Ok(Some(cfg_path)) => {
if let Err(err) = context.load_config(&ConfigPath::Local(cfg_path)) { if let Err(err) = context.load_config(&ConfigPath::Local(cfg_path)) {
context.host.lock().print_err(err, &Text::from("")) context.host().lock().print_err(err, &Text::from(""))
} }
} }
Err(e) => { Err(e) => {
//Report error while checking for local cfg file //Report error while checking for local cfg file
context.host.lock().print_err(e, &Text::from("")) context.host().lock().print_err(e, &Text::from(""))
} }
Ok(None) => { Ok(None) => {
//No local cfg file present in start dir //No local cfg file present in start dir
@ -440,7 +384,7 @@ pub fn load_local_cfg_if_present(context: &EvaluationContext) {
fn load_cfg_as_global_cfg(context: &EvaluationContext, path: PathBuf) { fn load_cfg_as_global_cfg(context: &EvaluationContext, path: PathBuf) {
if let Err(err) = context.load_config(&ConfigPath::Global(path)) { if let Err(err) = context.load_config(&ConfigPath::Global(path)) {
context.host.lock().print_err(err, &Text::from("")); context.host().lock().print_err(err, &Text::from(""));
} }
} }
@ -450,7 +394,7 @@ pub fn load_global_cfg(context: &EvaluationContext) {
load_cfg_as_global_cfg(context, path); load_cfg_as_global_cfg(context, path);
} }
Err(e) => { Err(e) => {
context.host.lock().print_err(e, &Text::from("")); context.host().lock().print_err(e, &Text::from(""));
} }
} }
} }
@ -478,7 +422,7 @@ pub fn parse_and_eval(line: &str, ctx: &EvaluationContext) -> Result<String, She
// TODO ensure the command whose examples we're testing is actually in the pipeline // TODO ensure the command whose examples we're testing is actually in the pipeline
ctx.scope.enter_scope(); 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 { if let Some(err) = err {
ctx.scope.exit_scope(); ctx.scope.exit_scope();
return Err(err.into()); return Err(err.into());

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,89 +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.replace(std::path::is_separator, &SEP.to_string());
let expanded: &str = 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,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

@ -155,14 +155,14 @@ fn convert_cmd(cmd: Cmd) -> rustyline::Cmd {
fn convert_keybinding(keybinding: Keybinding) -> (rustyline::KeyEvent, rustyline::Cmd) { fn convert_keybinding(keybinding: Keybinding) -> (rustyline::KeyEvent, rustyline::Cmd) {
let rusty_modifiers = match keybinding.modifiers { let rusty_modifiers = match keybinding.modifiers {
Some(mods) => match mods { Some(mods) => match mods {
NuModifiers::CTRL => Some(Modifiers::CTRL), NuModifiers::Ctrl => Some(Modifiers::CTRL),
NuModifiers::ALT => Some(Modifiers::ALT), NuModifiers::Alt => Some(Modifiers::ALT),
NuModifiers::SHIFT => Some(Modifiers::SHIFT), NuModifiers::Shift => Some(Modifiers::SHIFT),
NuModifiers::NONE => Some(Modifiers::NONE), NuModifiers::None => Some(Modifiers::NONE),
NuModifiers::CTRL_SHIFT => Some(Modifiers::CTRL_SHIFT), NuModifiers::CtrlShift => Some(Modifiers::CTRL_SHIFT),
NuModifiers::ALT_SHIFT => Some(Modifiers::ALT_SHIFT), NuModifiers::AltShift => Some(Modifiers::ALT_SHIFT),
NuModifiers::CTRL_ALT => Some(Modifiers::CTRL_ALT), NuModifiers::CtrlAlt => Some(Modifiers::CTRL_ALT),
NuModifiers::CTRL_ALT_SHIFT => Some(Modifiers::CTRL_ALT_SHIFT), NuModifiers::CtrlAltShift => Some(Modifiers::CTRL_ALT_SHIFT),
// _ => None, // _ => None,
}, },
None => None, None => None,
@ -412,24 +412,31 @@ pub enum CharSearch {
/// The set of modifier keys that were triggered along with a key press. /// The set of modifier keys that were triggered along with a key press.
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
#[allow(clippy::clippy::upper_case_acronyms)]
pub enum NuModifiers { pub enum NuModifiers {
/// Control modifier /// Control modifier
CTRL = 8, #[serde(alias = "CTRL")]
Ctrl = 8,
/// Escape or Alt modifier /// Escape or Alt modifier
ALT = 4, #[serde(alias = "ALT")]
Alt = 4,
/// Shift modifier /// Shift modifier
SHIFT = 2, #[serde(alias = "SHIFT")]
Shift = 2,
/// No modifier /// No modifier
NONE = 0, #[serde(alias = "NONE")]
None = 0,
/// Ctrl + Shift /// Ctrl + Shift
CTRL_SHIFT = 10, #[serde(alias = "CTRL_SHIFT")]
CtrlShift = 10,
/// Alt + Shift /// Alt + Shift
ALT_SHIFT = 6, #[serde(alias = "ALT_SHIFT")]
AltShift = 6,
/// Ctrl + Alt /// Ctrl + Alt
CTRL_ALT = 12, #[serde(alias = "CTRL_ALT")]
CtrlAlt = 12,
/// Ctrl + Alt + Shift /// Ctrl + Alt + Shift
CTRL_ALT_SHIFT = 14, #[serde(alias = "CTRL_ALT_SHIFT")]
CtrlAltShift = 14,
} }
/// The number of times one command should be repeated. /// The number of times one command should be repeated.

View File

@ -1,31 +1,26 @@
#![recursion_limit = "2048"]
#[macro_use]
mod prelude;
#[cfg(test)] #[cfg(test)]
extern crate quickcheck; extern crate quickcheck;
#[cfg(test)] #[cfg(test)]
#[macro_use(quickcheck)] #[macro_use(quickcheck)]
extern crate quickcheck_macros; extern crate quickcheck_macros;
mod app;
mod cli; mod cli;
#[cfg(feature = "rustyline-support")] #[cfg(feature = "rustyline-support")]
mod completion;
mod format;
#[cfg(feature = "rustyline-support")]
mod keybinding; mod keybinding;
mod line_editor; mod line_editor;
#[cfg(feature = "rustyline-support")]
mod shell; mod shell;
pub mod types;
#[cfg(feature = "rustyline-support")] #[cfg(feature = "rustyline-support")]
pub use crate::cli::cli; 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::cli::{parse_and_eval, register_plugins, run_script_file};
pub use crate::cli::{NuScript, Options};
pub use nu_command::commands::default_context::create_default_context; pub use nu_command::{
commands::NuSignature as Nu, commands::Version as NuVersion, create_default_context,
};
pub use nu_data::config; pub use nu_data::config;
pub use nu_data::dict::TaggedListBuilder; pub use nu_data::dict::TaggedListBuilder;
pub use nu_data::primitive; pub use nu_data::primitive;

View File

@ -1,8 +1,9 @@
use nu_engine::EvaluationContext; use nu_engine::EvaluationContext;
use nu_errors::ShellError;
use std::error::Error; use std::error::Error;
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::prelude::*; use std::sync::atomic::Ordering;
#[allow(unused_imports)] #[allow(unused_imports)]
use nu_engine::script::LineResult; use nu_engine::script::LineResult;
@ -31,7 +32,7 @@ pub fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>)
Err(ReadlineError::Interrupted) => LineResult::CtrlC, Err(ReadlineError::Interrupted) => LineResult::CtrlC,
Err(ReadlineError::Eof) => LineResult::CtrlD, Err(ReadlineError::Eof) => LineResult::CtrlD,
Err(err) => { Err(err) => {
outln!("Error: {:?}", err); eprintln!("Error: {:?}", err);
LineResult::Break LineResult::Break
} }
} }
@ -258,14 +259,14 @@ pub fn rustyline_hinter(
pub fn configure_ctrl_c(_context: &EvaluationContext) -> Result<(), Box<dyn Error>> { pub fn configure_ctrl_c(_context: &EvaluationContext) -> Result<(), Box<dyn Error>> {
#[cfg(feature = "ctrlc")] #[cfg(feature = "ctrlc")]
{ {
let cc = _context.ctrl_c.clone(); let cc = _context.ctrl_c().clone();
ctrlc::set_handler(move || { ctrlc::set_handler(move || {
cc.store(true, Ordering::SeqCst); cc.store(true, Ordering::SeqCst);
})?; })?;
if _context.ctrl_c.load(Ordering::SeqCst) { if _context.ctrl_c().load(Ordering::SeqCst) {
_context.ctrl_c.store(false, Ordering::SeqCst); _context.ctrl_c().store(false, Ordering::SeqCst);
} }
} }

View File

@ -1,59 +0,0 @@
#[macro_export]
macro_rules! return_err {
($expr:expr) => {
match $expr {
Err(_) => return,
Ok(expr) => expr,
};
};
}
#[macro_export]
macro_rules! trace_out_stream {
(target: $target:tt, $desc:tt = $expr:expr) => {{
if log::log_enabled!(target: $target, log::Level::Trace) {
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 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::ActionStream;
#[allow(unused_imports)]
pub(crate) use nu_value_ext::ValueExt;
#[allow(unused_imports)]
pub(crate) use std::sync::atomic::Ordering;
#[allow(clippy::clippy::wrong_self_convention)]
pub trait FromInputStream {
fn from_input_stream(self) -> ActionStream;
}
impl<T> FromInputStream for T
where
T: Iterator<Item = nu_protocol::Value> + Send + Sync + 'static,
{
fn from_input_stream(self) -> ActionStream {
ActionStream {
values: Box::new(self.map(nu_protocol::ReturnSuccess::value)),
}
}
}

View File

@ -1,9 +1,212 @@
#![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 struct Helper {
pub(crate) mod completer; completer: NuCompleter,
#[cfg(feature = "rustyline-support")] hinter: Option<rustyline::hint::HistoryHinter>,
pub(crate) mod helper; context: EvaluationContext,
pub colored_prompt: String,
validator: NuValidator,
}
#[cfg(feature = "rustyline-support")] impl Helper {
pub(crate) use helper::Helper; pub(crate) fn new(
context: EvaluationContext,
hinter: Option<rustyline::hint::HistoryHinter>,
) -> Helper {
Helper {
completer: NuCompleter {},
hinter,
context,
colored_prompt: String::new(),
validator: NuValidator {},
}
}
}
struct CompletionContext<'a>(&'a EvaluationContext);
impl<'a> nu_completion::CompletionContext for CompletionContext<'a> {
fn signature_registry(&self) -> &dyn nu_parser::ParserScope {
&self.0.scope
}
}
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> {
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(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);
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,196 +0,0 @@
use crate::completion;
use crate::shell::completer::NuCompleter;
use nu_ansi_term::Color;
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 {
type Hint = String;
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(Color::DarkGray.prefix().to_string() + hint + nu_ansi_term::ansi::RESET)
}
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::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

@ -5,68 +5,68 @@ description = "CLI for nushell"
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
name = "nu-command" name = "nu-command"
version = "0.32.0" version = "0.33.0"
[lib] [lib]
doctest = false doctest = false
[dependencies] [dependencies]
nu-data = { version = "0.32.0", path = "../nu-data" } nu-data = { version="0.33.0", path="../nu-data" }
nu-engine = { version = "0.32.0", path = "../nu-engine" } nu-engine = { version="0.33.0", path="../nu-engine" }
nu-errors = { version = "0.32.0", path = "../nu-errors" } nu-errors = { version="0.33.0", path="../nu-errors" }
nu-json = { version = "0.32.0", path = "../nu-json" } nu-json = { version="0.33.0", path="../nu-json" }
nu-parser = { version = "0.32.0", path = "../nu-parser" } nu-path = { version="0.33.0", path="../nu-path" }
nu-plugin = { version = "0.32.0", path = "../nu-plugin" } nu-parser = { version="0.33.0", path="../nu-parser" }
nu-protocol = { version = "0.32.0", path = "../nu-protocol" } nu-plugin = { version="0.33.0", path="../nu-plugin" }
nu-source = { version = "0.32.0", path = "../nu-source" } nu-protocol = { version="0.33.0", path="../nu-protocol" }
nu-stream = { version = "0.32.0", path = "../nu-stream" } nu-source = { version="0.33.0", path="../nu-source" }
nu-table = { version = "0.32.0", path = "../nu-table" } nu-stream = { version="0.33.0", path="../nu-stream" }
nu-test-support = { version = "0.32.0", path = "../nu-test-support" } nu-table = { version="0.33.0", path="../nu-table" }
nu-value-ext = { version = "0.32.0", path = "../nu-value-ext" } nu-test-support = { version="0.33.0", path="../nu-test-support" }
nu-ansi-term = { version = "0.32.0", path = "../nu-ansi-term" } nu-value-ext = { version="0.33.0", path="../nu-value-ext" }
nu-pretty-hex = { version = "0.32.0", path = "../nu-pretty-hex" } nu-ansi-term = { version="0.33.0", path="../nu-ansi-term" }
nu-pretty-hex = { version="0.33.0", path="../nu-pretty-hex" }
Inflector = "0.11" Inflector = "0.11"
arboard = { version = "1.1.0", optional = true } arboard = { version="1.1.0", optional=true }
base64 = "0.13.0" base64 = "0.13.0"
bigdecimal = { version = "0.2.0", features = ["serde"] } bigdecimal = { version="0.2.0", features=["serde"] }
byte-unit = "4.0.9" byte-unit = "4.0.9"
bytes = "1.0.1" bytes = "1.0.1"
calamine = "0.17.0" calamine = "0.17.0"
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version="0.4.19", features=["serde"] }
chrono-tz = "0.5.3" chrono-tz = "0.5.3"
clap = "2.33.3"
codespan-reporting = "0.11.0" codespan-reporting = "0.11.0"
crossterm = { version = "0.19.0", optional = true } crossterm = { version="0.19.0", optional=true }
csv = "1.1.3" csv = "1.1.3"
ctrlc = { version = "3.1.7", optional = true } ctrlc = { version="3.1.7", optional=true }
derive-new = "0.5.8" derive-new = "0.5.8"
directories-next = { version = "2.0.0", optional = true } directories-next = "2.0.0"
dirs-next = { version = "2.0.0", optional = true } dirs-next = "2.0.0"
dtparse = "1.2.0" dtparse = "1.2.0"
dunce = "1.0.1" dunce = "1.0.1"
eml-parser = "0.1.0" eml-parser = "0.1.0"
encoding_rs = "0.8.28" encoding_rs = "0.8.28"
filesize = "0.2.0" filesize = "0.2.0"
fs_extra = "1.2.0" fs_extra = "1.2.0"
futures = { version = "0.3.12", features = ["compat", "io-compat"] } futures = { version="0.3.12", features=["compat", "io-compat"] }
getset = "0.1.1" getset = "0.1.1"
glob = "0.3.0" glob = "0.3.0"
htmlescape = "0.3.1" htmlescape = "0.3.1"
ical = "0.7.0" ical = "0.7.0"
indexmap = { version = "1.6.1", features = ["serde-1"] } indexmap = { version="1.6.1", features=["serde-1"] }
itertools = "0.10.0" itertools = "0.10.0"
lazy_static = "1.*" lazy_static = "1.*"
log = "0.4.14" log = "0.4.14"
md5 = "0.7.0" md5 = "0.7.0"
meval = "0.2.0" meval = "0.2.0"
minus = { version = "3.3.0", optional = true, features = ["async_std_lib", "search"] } minus = { version="3.3.0", optional=true, features=["async_std_lib", "search"] }
num-bigint = { version = "0.3.1", features = ["serde"] } num-bigint = { version="0.3.1", features=["serde"] }
num-format = { version = "0.4.0", features = ["with-num-bigint"] } num-format = { version="0.4.0", features=["with-num-bigint"] }
num-traits = "0.2.14" num-traits = "0.2.14"
parking_lot = "0.11.1" parking_lot = "0.11.1"
pin-utils = "0.1.0" pin-utils = "0.1.0"
ptree = { version = "0.3.1", optional = true } ptree = { version="0.3.1", optional=true }
query_interface = "0.3.5" query_interface = "0.3.5"
quick-xml = "0.21.0" quick-xml = "0.21.0"
rand = "0.7.3" rand = "0.7.3"
@ -74,35 +74,36 @@ rayon = "1.5.0"
regex = "1.4.3" regex = "1.4.3"
roxmltree = "0.14.0" roxmltree = "0.14.0"
rust-embed = "5.9.0" rust-embed = "5.9.0"
rustyline = { version = "8.1.0", optional = true } rustyline = { version="8.1.0", optional=true }
serde = { version = "1.0.123", features = ["derive"] } serde = { version="1.0.123", features=["derive"] }
serde_bytes = "0.11.5" serde_bytes = "0.11.5"
serde_ini = "0.2.0" serde_ini = "0.2.0"
serde_json = "1.0.61" serde_json = "1.0.61"
serde_urlencoded = "0.7.0" serde_urlencoded = "0.7.0"
serde_yaml = "0.8.16" serde_yaml = "0.8.16"
sha2 = "0.9.3" sha2 = "0.9.3"
shellexpand = "2.1.0"
strip-ansi-escapes = "0.1.0" strip-ansi-escapes = "0.1.0"
sxd-document = "0.3.2" sxd-document = "0.3.2"
sxd-xpath = "0.4.2" sxd-xpath = "0.4.2"
tempfile = "3.2.0" tempfile = "3.2.0"
term = { version = "0.7.0", optional = true } term = { version="0.7.0", optional=true }
term_size = "0.3.2" term_size = "0.3.2"
termcolor = "1.1.2" termcolor = "1.1.2"
titlecase = "1.1.0" titlecase = "1.1.0"
toml = "0.5.8" toml = "0.5.8"
trash = { version = "1.3.0", optional = true } trash = { version="1.3.0", optional=true }
unicode-segmentation = "1.7.1" unicode-segmentation = "1.7.1"
url = "2.2.0" url = "2.2.0"
uuid_crate = { package = "uuid", version = "0.8.2", features = ["v4"], optional = true } uuid_crate = { package="uuid", version="0.8.2", features=["v4"], optional=true }
which = { version = "4.1.0", optional = true } which = { version="4.1.0", optional=true }
zip = { version = "0.5.9", optional = true } zip = { version="0.5.9", optional=true }
[dependencies.polars] [dependencies.polars]
version = "0.13.4" git = "https://github.com/pola-rs/polars"
rev = "f60d86bc0921bd42635e8a33e7aad28ebe62dc3e"
version = "0.14.2"
optional = true optional = true
features = ["parquet", "json", "random"] features = ["parquet", "json", "random", "pivot", "strings", "is_in"]
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
umask = "1.0.0" umask = "1.0.0"
@ -132,7 +133,5 @@ clipboard-cli = ["arboard"]
rustyline-support = ["rustyline"] rustyline-support = ["rustyline"]
stable = [] stable = []
trash-support = ["trash"] trash-support = ["trash"]
directories = ["directories-next"]
dirs = ["dirs-next"]
table-pager = ["minus", "crossterm"] table-pager = ["minus", "crossterm"]
dataframe = ["nu-protocol/dataframe", "polars"] dataframe = ["nu-protocol/dataframe", "polars"]

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

@ -5,7 +5,6 @@ use nu_test_support::NATIVE_PATH_ENV_VAR;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::io::Write; use std::io::Write;
use std::ops::Deref;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::sync::mpsc; use std::sync::mpsc;
use std::{borrow::Cow, io::BufReader}; use std::{borrow::Cow, io::BufReader};
@ -27,7 +26,7 @@ pub(crate) fn run_external_command(
trace!(target: "nu::run::external", "-> {}", command.name); trace!(target: "nu::run::external", "-> {}", command.name);
context.sync_path_to_env(); context.sync_path_to_env();
if !context.host.lock().is_external_cmd(&command.name) { if !context.host().lock().is_external_cmd(&command.name) {
return Err(ShellError::labeled_error( return Err(ShellError::labeled_error(
"Command not found", "Command not found",
format!("command {} not found", &command.name), format!("command {} not found", &command.name),
@ -38,13 +37,28 @@ pub(crate) fn run_external_command(
run_with_stdin(command, context, input, external_redirection) run_with_stdin(command, context, input, external_redirection)
} }
#[allow(unused)]
fn trim_double_quotes(input: &str) -> String {
let mut chars = input.chars();
match (chars.next(), chars.next_back()) {
(Some('"'), Some('"')) => chars.collect(),
_ => input.to_string(),
}
}
#[allow(unused)]
fn escape_where_needed(input: &str) -> String {
input.split(' ').join("\\ ").split('\'').join("\\'")
}
fn run_with_stdin( fn run_with_stdin(
command: ExternalCommand, command: ExternalCommand,
context: &mut EvaluationContext, context: &mut EvaluationContext,
input: InputStream, input: InputStream,
external_redirection: ExternalRedirection, external_redirection: ExternalRedirection,
) -> Result<InputStream, ShellError> { ) -> Result<InputStream, ShellError> {
let path = context.shell_manager.path(); let path = context.shell_manager().path();
let mut command_args = vec![]; let mut command_args = vec![];
for arg in command.args.iter() { for arg in command.args.iter() {
@ -81,6 +95,7 @@ fn run_with_stdin(
} }
_ => { _ => {
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string(); 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)); command_args.push((trimmed_value_string, is_literal));
} }
} }
@ -89,18 +104,7 @@ fn run_with_stdin(
let process_args = command_args let process_args = command_args
.iter() .iter()
.map(|(arg, _is_literal)| { .map(|(arg, _is_literal)| {
let home_dir; let arg = nu_path::expand_tilde_string(Cow::Borrowed(arg));
#[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))] #[cfg(not(windows))]
{ {
@ -108,7 +112,12 @@ fn run_with_stdin(
let escaped = escape_double_quotes(&arg); let escaped = escape_double_quotes(&arg);
add_double_quotes(&escaped) add_double_quotes(&escaped)
} else { } else {
arg.as_ref().to_string() let trimmed = trim_double_quotes(&arg);
if trimmed != arg {
escape_where_needed(&trimmed)
} else {
trimmed
}
} }
} }
#[cfg(windows)] #[cfg(windows)]
@ -116,7 +125,7 @@ fn run_with_stdin(
if let Some(unquoted) = remove_quotes(&arg) { if let Some(unquoted) = remove_quotes(&arg) {
unquoted.to_string() unquoted.to_string()
} else { } else {
arg.as_ref().to_string() arg.to_string()
} }
} }
}) })
@ -242,7 +251,10 @@ fn spawn(
"Received unexpected type from pipeline ({})", "Received unexpected type from pipeline ({})",
unsupported.type_name() unsupported.type_name()
), ),
"expected a string", format!(
"expected a string, got {} as input",
unsupported.type_name()
),
stdin_name_tag.clone(), stdin_name_tag.clone(),
)), )),
tag: stdin_name_tag, tag: stdin_name_tag,
@ -430,7 +442,7 @@ fn spawn(
} }
} }
let _ = stdout_read_tx.send(Ok(Value { let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::external_non_zero()), value: UntaggedValue::nothing(),
tag: stdout_name_tag, tag: stdout_name_tag,
})); }));
} }
@ -439,7 +451,7 @@ fn spawn(
}); });
let stream = ChannelReceiver::new(rx); let stream = ChannelReceiver::new(rx);
Ok(stream.to_input_stream()) Ok(stream.into_input_stream())
} }
Err(e) => Err(ShellError::labeled_error( Err(e) => Err(ShellError::labeled_error(
format!("{}", e), format!("{}", e),
@ -473,15 +485,6 @@ impl Iterator for ChannelReceiver {
} }
} }
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 { fn argument_is_quoted(argument: &str) -> bool {
if argument.len() < 2 { if argument.len() < 2 {
return false; return false;
@ -530,9 +533,7 @@ fn shell_os_paths() -> Vec<std::path::PathBuf> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{ use super::{add_double_quotes, argument_is_quoted, escape_double_quotes, remove_quotes};
add_double_quotes, argument_is_quoted, escape_double_quotes, expand_tilde, remove_quotes,
};
#[cfg(feature = "which")] #[cfg(feature = "which")]
use super::{run_external_command, InputStream}; use super::{run_external_command, InputStream};
@ -604,26 +605,26 @@ mod tests {
#[test] #[test]
fn checks_quotes_from_argument_to_be_passed_in() { fn checks_quotes_from_argument_to_be_passed_in() {
assert_eq!(argument_is_quoted(""), false); assert!(!argument_is_quoted(""));
assert_eq!(argument_is_quoted("'"), false); assert!(!argument_is_quoted("'"));
assert_eq!(argument_is_quoted("'a"), false); assert!(!argument_is_quoted("'a"));
assert_eq!(argument_is_quoted("a"), false); assert!(!argument_is_quoted("a"));
assert_eq!(argument_is_quoted("a'"), false); assert!(!argument_is_quoted("a'"));
assert_eq!(argument_is_quoted("''"), true); assert!(argument_is_quoted("''"));
assert_eq!(argument_is_quoted(r#"""#), false); assert!(!argument_is_quoted(r#"""#));
assert_eq!(argument_is_quoted(r#""a"#), false); assert!(!argument_is_quoted(r#""a"#));
assert_eq!(argument_is_quoted(r#"a"#), false); assert!(!argument_is_quoted(r#"a"#));
assert_eq!(argument_is_quoted(r#"a""#), false); assert!(!argument_is_quoted(r#"a""#));
assert_eq!(argument_is_quoted(r#""""#), true); assert!(argument_is_quoted(r#""""#));
assert_eq!(argument_is_quoted("'andrés"), false); assert!(!argument_is_quoted("'andrés"));
assert_eq!(argument_is_quoted("andrés'"), false); assert!(!argument_is_quoted("andrés'"));
assert_eq!(argument_is_quoted(r#""andrés"#), false); assert!(!argument_is_quoted(r#""andrés"#));
assert_eq!(argument_is_quoted(r#"andrés""#), false); assert!(!argument_is_quoted(r#"andrés""#));
assert_eq!(argument_is_quoted("'andrés'"), true); assert!(argument_is_quoted("'andrés'"));
assert_eq!(argument_is_quoted(r#""andrés""#), true); assert!(argument_is_quoted(r#""andrés""#));
} }
#[test] #[test]
@ -654,20 +655,4 @@ mod tests {
assert_eq!(remove_quotes("'andrés'"), Some("andrés")); assert_eq!(remove_quotes("'andrés'"), Some("andrés"));
assert_eq!(remove_quotes(r#""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,385 +0,0 @@
#[macro_use]
pub(crate) mod macros;
mod from_delimited_data;
mod to_delimited_data;
pub(crate) mod all;
pub(crate) mod ansi;
pub(crate) mod any;
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(crate) mod compact;
pub(crate) mod config;
pub(crate) mod constants;
pub(crate) mod cp;
#[cfg(feature = "dataframe")]
pub(crate) mod dataframe;
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 for_in;
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;
pub(crate) mod keep;
pub(crate) mod last;
pub(crate) mod length;
pub(crate) mod let_;
pub(crate) mod let_env;
pub(crate) mod lines;
pub(crate) mod load_env;
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 roll;
pub(crate) mod rotate;
pub(crate) mod run_external;
pub(crate) mod save;
pub(crate) mod select;
pub(crate) mod seq;
pub(crate) mod seq_dates;
pub(crate) mod shells;
pub(crate) mod shuffle;
pub(crate) mod size;
pub(crate) mod skip;
pub(crate) mod sleep;
pub(crate) mod sort_by;
pub(crate) mod source;
pub(crate) mod split;
pub(crate) mod split_by;
pub(crate) mod str_;
pub(crate) mod table;
pub(crate) mod tags;
pub(crate) mod termsize;
pub(crate) mod to;
pub(crate) mod to_csv;
pub(crate) mod to_html;
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 ansi::AnsiStrip;
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, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto,
};
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, DropColumn};
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 for_in::ForIn;
pub(crate) use if_::If;
pub(crate) use into::Into;
pub(crate) use into::IntoBinary;
pub(crate) use into::IntoInt;
pub(crate) use into::IntoString;
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 all::Command as All;
pub(crate) use any::Command as Any;
#[cfg(feature = "dataframe")]
pub(crate) use dataframe::{
DataFrame, DataFrameAggregate, DataFrameConvert, DataFrameDTypes, DataFrameDrop,
DataFrameGroupBy, DataFrameJoin, DataFrameList, DataFrameLoad, DataFrameSample,
DataFrameSelect, DataFrameShow,
};
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::Command as Get;
pub(crate) use group_by::Command as GroupBy;
pub(crate) use group_by_date::GroupByDate;
pub(crate) use hash_::{Hash, HashBase64, HashMd5};
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 keep::{Keep, KeepUntil, KeepWhile};
pub(crate) use last::Last;
pub(crate) use length::Length;
pub(crate) use let_::Let;
pub(crate) use let_env::LetEnv;
pub(crate) use lines::Lines;
pub(crate) use load_env::LoadEnv;
pub(crate) use ls::Ls;
pub(crate) use math::{
Math, MathAbs, MathAverage, MathCeil, MathEval, MathFloor, MathMaximum, MathMedian,
MathMinimum, MathMode, MathProduct, MathRound, MathSqrt, 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, PathJoin, PathParse,
PathRelativeTo, PathSplit, 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 roll::{Roll, RollColumn, RollUp};
pub(crate) use rotate::{Rotate, RotateCounterClockwise};
pub(crate) use run_external::RunExternalCommand;
pub(crate) use save::Save;
pub(crate) use select::Command as Select;
pub(crate) use seq::Seq;
pub(crate) use seq_dates::SeqDates;
pub(crate) use shells::Shells;
pub(crate) use shuffle::Shuffle;
pub(crate) use size::Size;
pub(crate) use skip::{Skip, SkipUntil, SkipWhile};
pub(crate) use sleep::Sleep;
pub(crate) use sort_by::SortBy;
pub(crate) use source::Source;
pub(crate) use split::{Split, SplitChars, SplitColumn, SplitRow};
pub(crate) use split_by::SplitBy;
pub(crate) use str_::{
Str, StrCamelCase, StrCapitalize, StrCollect, StrContains, StrDowncase, StrEndsWith,
StrFindReplace, StrIndexOf, StrKebabCase, StrLPad, StrLength, StrPascalCase, StrRPad,
StrReverse, StrScreamingSnakeCase, StrSnakeCase, StrStartsWith, StrSubstring, StrToDatetime,
StrToDecimal, StrToInteger, StrTrim, StrTrimLeft, StrTrimRight, StrUpcase,
};
pub(crate) use table::Table;
pub(crate) use tags::Tags;
pub(crate) use termsize::TermSize;
pub(crate) use to::To;
pub(crate) use to_csv::ToCsv;
pub(crate) use to_html::ToHtml;
pub(crate) use to_json::ToJson;
pub(crate) use to_md::Command as ToMarkdown;
pub(crate) use to_toml::ToToml;
pub(crate) use to_tsv::ToTsv;
pub(crate) use to_url::ToUrl;
pub(crate) use to_xml::ToXml;
pub(crate) use to_yaml::ToYaml;
pub(crate) use touch::Touch;
pub(crate) use uniq::Uniq;
pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme};
pub(crate) use version::Version;
pub(crate) use where_::Command as Where;
pub(crate) use which_::Which;
pub(crate) use with_env::WithEnv;
pub(crate) use wrap::Wrap;
#[cfg(test)]
mod tests {
use super::*;
use crate::examples::{test_anchors, test_examples};
use nu_engine::{whole_stream_command, Command};
use nu_errors::ShellError;
fn full_tests() -> Vec<Command> {
vec![
whole_stream_command(Append),
whole_stream_command(GroupBy),
whole_stream_command(Insert),
whole_stream_command(Move),
whole_stream_command(Update),
whole_stream_command(Empty),
// whole_stream_command(Select),
// whole_stream_command(Get),
// Str Command Suite
whole_stream_command(Str),
whole_stream_command(StrToDecimal),
whole_stream_command(StrToInteger),
whole_stream_command(StrDowncase),
whole_stream_command(StrUpcase),
whole_stream_command(StrCapitalize),
whole_stream_command(StrFindReplace),
whole_stream_command(StrSubstring),
whole_stream_command(StrToDatetime),
whole_stream_command(StrContains),
whole_stream_command(StrIndexOf),
whole_stream_command(StrTrim),
whole_stream_command(StrTrimLeft),
whole_stream_command(StrTrimRight),
whole_stream_command(StrStartsWith),
whole_stream_command(StrEndsWith),
//whole_stream_command(StrCollect),
whole_stream_command(StrLength),
whole_stream_command(StrLPad),
whole_stream_command(StrReverse),
whole_stream_command(StrRPad),
whole_stream_command(StrCamelCase),
whole_stream_command(StrPascalCase),
whole_stream_command(StrKebabCase),
whole_stream_command(StrSnakeCase),
whole_stream_command(StrScreamingSnakeCase),
whole_stream_command(ToMarkdown),
]
}
fn only_examples() -> Vec<Command> {
let mut commands = full_tests();
commands.extend(vec![whole_stream_command(Flatten)]);
commands
}
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
for cmd in only_examples() {
println!("cmd: {}", cmd.name());
test_examples(cmd)?;
}
Ok(())
}
#[test]
fn tracks_metadata() -> Result<(), ShellError> {
for cmd in full_tests() {
test_anchors(cmd)?;
}
Ok(())
}
}

View File

@ -59,21 +59,10 @@ impl WholeStreamCommand for Histogram {
pub fn histogram(args: CommandArgs) -> Result<ActionStream, ShellError> { pub fn histogram(args: CommandArgs) -> Result<ActionStream, ShellError> {
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let (input, args) = args.evaluate_once()?.parts();
let values: Vec<Value> = input.collect(); let mut columns = args.rest::<ColumnPath>(0)?;
let evaluate_with = args.get_flag::<ColumnPath>("use")?.map(evaluator);
let mut columns = args let values: Vec<Value> = args.input.collect();
.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 column_grouper = if !columns.is_empty() { let column_grouper = if !columns.is_empty() {
columns columns
@ -160,16 +149,19 @@ pub fn histogram(args: CommandArgs) -> Result<ActionStream, ShellError> {
"{}%", "{}%",
// Some(2) < the number of digits // Some(2) < the number of digits
// true < group the digits // true < group the digits
crate::commands::into::string::action(&percentage, &name, Some(2), true)? crate::commands::conversions::into::string::action(
.as_string()? &percentage,
&name,
Some(2),
true
)?
.as_string()?
); );
fact.insert_untagged("percentage", UntaggedValue::string(fmt_percentage)); fact.insert_untagged("percentage", UntaggedValue::string(fmt_percentage));
let string = std::iter::repeat("*") let string = "*".repeat(percentage.as_u64().map_err(|_| {
.take(percentage.as_u64().map_err(|_| { ShellError::labeled_error("expected a number", "expected a number", &name)
ShellError::labeled_error("expected a number", "expected a number", &name) })? as usize);
})? as usize)
.collect::<String>();
fact.insert_untagged(&frequency_column_name, UntaggedValue::string(string)); fact.insert_untagged(&frequency_column_name, UntaggedValue::string(string));
@ -177,7 +169,7 @@ pub fn histogram(args: CommandArgs) -> Result<ActionStream, ShellError> {
ReturnSuccess::value(fact.into_value()) ReturnSuccess::value(fact.into_value())
}) })
.to_action_stream()) .into_action_stream())
} }
fn evaluator(by: ColumnPath) -> Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send> { fn evaluator(by: ColumnPath) -> Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send> {
@ -209,7 +201,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)),
} }
} }

View File

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

View File

@ -33,7 +33,7 @@ impl WholeStreamCommand for SubCommand {
pub fn clear(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn clear(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let ctx = EvaluationContext::from_args(&args); let ctx = &args.context;
let result = if let Some(global_cfg) = &mut args.configs().lock().global_config { let result = if let Some(global_cfg) = &mut args.configs().lock().global_config {
global_cfg.vars.clear(); global_cfg.vars.clear();

View File

@ -37,14 +37,13 @@ impl WholeStreamCommand for SubCommand {
pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let ctx = EvaluationContext::from_args(&args); let ctx = &args.context;
let args = args.evaluate_once()?;
let column_path = args.req(0)?; let column_path = args.req(0)?;
let result = if let Some(global_cfg) = &ctx.configs.lock().global_config { let result = if let Some(global_cfg) = &ctx.configs().lock().global_config {
let result = UntaggedValue::row(global_cfg.vars.clone()).into_value(&name); let result = UntaggedValue::row(global_cfg.vars.clone()).into_value(&name);
let value = crate::commands::get::get_column_path(&column_path, &result)?; let value = crate::commands::filters::get::get_column_path(&column_path, &result)?;
Ok(match value { Ok(match value {
Value { Value {
value: UntaggedValue::Table(list), value: UntaggedValue::Table(list),

View File

@ -38,13 +38,12 @@ impl WholeStreamCommand for SubCommand {
pub fn remove(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn remove(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let ctx = EvaluationContext::from_args(&args); let ctx = &args.context;
let args = args.evaluate_once()?;
let remove: Tagged<String> = args.req(0)?; let remove: Tagged<String> = args.req(0)?;
let key = remove.to_string(); let key = remove.to_string();
let result = if let Some(global_cfg) = &mut ctx.configs.lock().global_config { let result = if let Some(global_cfg) = &mut ctx.configs().lock().global_config {
if global_cfg.vars.contains_key(&key) { if global_cfg.vars.contains_key(&key) {
global_cfg.vars.swap_remove(&key); global_cfg.vars.swap_remove(&key);
global_cfg.write()?; global_cfg.write()?;

View File

@ -52,13 +52,12 @@ impl WholeStreamCommand for SubCommand {
pub fn set(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let ctx = EvaluationContext::from_args(&args); let ctx = &args.context;
let args = args.evaluate_once()?;
let column_path = args.req(0)?; let column_path = args.req(0)?;
let mut value: Value = args.req(1)?; let mut value: Value = args.req(1)?;
let result = if let Some(global_cfg) = &mut ctx.configs.lock().global_config { let result = if let Some(global_cfg) = &mut ctx.configs().lock().global_config {
let configuration = UntaggedValue::row(global_cfg.vars.clone()).into_value(&name); let configuration = UntaggedValue::row(global_cfg.vars.clone()).into_value(&name);
if let UntaggedValue::Table(rows) = &value.value { if let UntaggedValue::Table(rows) = &value.value {

View File

@ -38,15 +38,14 @@ impl WholeStreamCommand for SubCommand {
pub fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let ctx = EvaluationContext::from_args(&args); let ctx = &args.context;
let args = args.evaluate_once()?;
let set_into: Tagged<String> = args.req(0)?; let set_into: Tagged<String> = args.req(0)?;
let rows: Vec<Value> = args.input.collect(); let rows: Vec<Value> = args.input.collect();
let key = set_into.to_string(); let key = set_into.to_string();
let result = if let Some(global_cfg) = &mut ctx.configs.lock().global_config { let result = if let Some(global_cfg) = &mut ctx.configs().lock().global_config {
if rows.is_empty() { if rows.is_empty() {
return Err(ShellError::labeled_error( return Err(ShellError::labeled_error(
"No values given for set_into", "No values given for set_into",

View File

@ -113,7 +113,6 @@ impl WholeStreamCommand for SubCommand {
} }
fn into_binary(args: CommandArgs) -> Result<OutputStream, ShellError> { fn into_binary(args: CommandArgs) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once()?;
let skip: Option<Value> = args.get_flag("skip")?; let skip: Option<Value> = args.get_flag("skip")?;
let bytes: Option<Value> = args.get_flag("bytes")?; let bytes: Option<Value> = args.get_flag("bytes")?;
let column_paths: Vec<ColumnPath> = args.rest(0)?; let column_paths: Vec<ColumnPath> = args.rest(0)?;
@ -137,7 +136,7 @@ fn into_binary(args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(ret) Ok(ret)
} }
}) })
.to_input_stream()) .into_input_stream())
} }
fn int_to_endian(n: i64) -> Vec<u8> { fn int_to_endian(n: i64) -> Vec<u8> {

View File

@ -79,7 +79,6 @@ impl WholeStreamCommand for SubCommand {
} }
fn into_int(args: CommandArgs) -> Result<OutputStream, ShellError> { fn into_int(args: CommandArgs) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once()?;
let column_paths: Vec<ColumnPath> = args.rest(0)?; let column_paths: Vec<ColumnPath> = args.rest(0)?;
Ok(args Ok(args
@ -99,7 +98,7 @@ fn into_int(args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(ret) Ok(ret)
} }
}) })
.to_input_stream()) .into_input_stream())
} }
pub fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> { pub fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {

View File

@ -81,8 +81,6 @@ impl WholeStreamCommand for SubCommand {
} }
fn into_string(args: CommandArgs) -> Result<OutputStream, ShellError> { fn into_string(args: CommandArgs) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once()?;
let decimals: Option<Tagged<u64>> = args.get_flag("decimals")?; let decimals: Option<Tagged<u64>> = args.get_flag("decimals")?;
let column_paths: Vec<ColumnPath> = args.rest(0)?; let column_paths: Vec<ColumnPath> = args.rest(0)?;
@ -106,7 +104,7 @@ fn into_string(args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(ret) Ok(ret)
} }
}) })
.to_input_stream()) .into_input_stream())
} }
pub fn action( pub fn action(

View File

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

View File

@ -0,0 +1,40 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape};
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(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(_: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(OutputStream::empty())
}

View File

@ -5,11 +5,6 @@ use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
pub struct Debug; pub struct Debug;
#[derive(Deserialize)]
pub struct DebugArgs {
raw: bool,
}
impl WholeStreamCommand for Debug { impl WholeStreamCommand for Debug {
fn name(&self) -> &str { fn name(&self) -> &str {
"debug" "debug"
@ -29,7 +24,9 @@ impl WholeStreamCommand for Debug {
} }
fn debug_value(args: CommandArgs) -> Result<ActionStream, ShellError> { fn debug_value(args: CommandArgs) -> Result<ActionStream, ShellError> {
let (DebugArgs { raw }, input) = args.process()?; let raw = args.has_flag("raw");
let input = args.input;
Ok(input Ok(input
.map(move |v| { .map(move |v| {
if raw { if raw {
@ -40,7 +37,7 @@ fn debug_value(args: CommandArgs) -> Result<ActionStream, ShellError> {
ReturnSuccess::debug_value(v) ReturnSuccess::debug_value(v)
} }
}) })
.to_action_stream()) .into_action_stream())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -36,7 +36,7 @@ pub fn describe(args: CommandArgs) -> Result<ActionStream, ShellError> {
UntaggedValue::string(name).into_value(Tag::unknown_anchor(row.tag.span)), UntaggedValue::string(name).into_value(Tag::unknown_anchor(row.tag.span)),
) )
}) })
.to_action_stream()) .into_action_stream())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -59,11 +59,10 @@ impl WholeStreamCommand for Do {
} }
} }
fn do_(raw_args: CommandArgs) -> Result<OutputStream, ShellError> { fn do_(args: CommandArgs) -> Result<OutputStream, ShellError> {
let external_redirection = raw_args.call_info.args.external_redirection; let external_redirection = args.call_info.args.external_redirection;
let context = EvaluationContext::from_args(&raw_args); let context = args.context().clone();
let args = raw_args.evaluate_once()?;
let do_args = DoArgs { let do_args = DoArgs {
block: args.req(0)?, block: args.req(0)?,
ignore_errors: args.has_flag("ignore-errors"), ignore_errors: args.has_flag("ignore-errors"),
@ -119,12 +118,12 @@ fn do_(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(mut stream) => { Ok(mut stream) => {
let output = stream.drain_vec(); let output = stream.drain_vec();
context.clear_errors(); context.clear_errors();
Ok(output.into_iter().to_output_stream()) Ok(output.into_iter().into_output_stream())
} }
Err(_) => Ok(OutputStream::empty()), Err(_) => Ok(OutputStream::empty()),
} }
} else { } else {
result.map(|x| x.to_output_stream()) result.map(|x| x.into_output_stream())
} }
} }

View File

@ -54,7 +54,6 @@ pub fn expand_value_to_stream(v: Value) -> InputStream {
} }
fn echo(args: CommandArgs) -> Result<InputStream, ShellError> { fn echo(args: CommandArgs) -> Result<InputStream, ShellError> {
let args = args.evaluate_once()?;
let rest: Vec<Value> = args.rest(0)?; let rest: Vec<Value> = args.rest(0)?;
let stream = rest.into_iter().map(|i| match i.as_string() { let stream = rest.into_iter().map(|i| match i.as_string() {

View File

@ -13,11 +13,6 @@ use nu_value_ext::ValueExt;
pub struct Help; pub struct Help;
#[derive(Deserialize)]
pub struct HelpArgs {
rest: Vec<Tagged<String>>,
}
impl WholeStreamCommand for Help { impl WholeStreamCommand for Help {
fn name(&self) -> &str { fn name(&self) -> &str {
"help" "help"
@ -39,7 +34,8 @@ impl WholeStreamCommand for Help {
fn help(args: CommandArgs) -> Result<ActionStream, ShellError> { fn help(args: CommandArgs) -> Result<ActionStream, ShellError> {
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let scope = args.scope().clone(); let scope = args.scope().clone();
let (HelpArgs { rest }, ..) = args.process()?;
let rest: Vec<Tagged<String>> = args.rest(0)?;
if !rest.is_empty() { if !rest.is_empty() {
if rest[0].item == "commands" { if rest[0].item == "commands" {
@ -51,7 +47,7 @@ fn help(args: CommandArgs) -> Result<ActionStream, ShellError> {
// Internal only commands shouldn't be displayed // Internal only commands shouldn't be displayed
.filter(|cmd_name| { .filter(|cmd_name| {
scope scope
.get_command(&cmd_name) .get_command(cmd_name)
.filter(|command| !command.is_internal()) .filter(|command| !command.is_internal())
.is_some() .is_some()
}) })
@ -66,7 +62,7 @@ fn help(args: CommandArgs) -> Result<ActionStream, ShellError> {
) -> Result<(), ShellError> { ) -> Result<(), ShellError> {
let document_tag = rest[0].tag.clone(); let document_tag = rest[0].tag.clone();
let value = command_dict( let value = command_dict(
scope.get_command(&cmd_name).ok_or_else(|| { scope.get_command(cmd_name).ok_or_else(|| {
ShellError::labeled_error( ShellError::labeled_error(
format!("Could not load {}", cmd_name), format!("Could not load {}", cmd_name),
"could not load command", "could not load command",
@ -154,7 +150,7 @@ fn help(args: CommandArgs) -> Result<ActionStream, ShellError> {
ReturnSuccess::value(short_desc.into_value()) ReturnSuccess::value(short_desc.into_value())
}); });
Ok(iterator.to_action_stream()) Ok(iterator.into_action_stream())
} else if rest[0].item == "generate_docs" { } else if rest[0].item == "generate_docs" {
Ok(ActionStream::one(ReturnSuccess::value(generate_docs( Ok(ActionStream::one(ReturnSuccess::value(generate_docs(
&scope, &scope,

View File

@ -0,0 +1,74 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
use std::fs::File;
use std::io::{BufRead, BufReader};
pub struct History;
impl WholeStreamCommand for History {
fn name(&self) -> &str {
"history"
}
fn signature(&self) -> Signature {
Signature::build("history").switch("clear", "Clears out the history entries", Some('c'))
}
fn usage(&self) -> &str {
"Display command history."
}
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
history(args)
}
}
fn history(args: CommandArgs) -> Result<ActionStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let ctx = &args.context;
let clear = args.has_flag("clear");
let path = if let Some(global_cfg) = &ctx.configs().lock().global_config {
nu_data::config::path::history_path_or_default(global_cfg)
} else {
nu_data::config::path::default_history_path()
};
if clear {
// This is a NOOP, the logic to clear is handled in cli.rs
Ok(ActionStream::empty())
} else if let Ok(file) = File::open(path) {
let reader = BufReader::new(file);
// Skips the first line, which is a Rustyline internal
let output = reader.lines().skip(1).filter_map(move |line| match line {
Ok(line) => Some(ReturnSuccess::value(
UntaggedValue::string(line).into_value(tag.clone()),
)),
Err(_) => None,
});
Ok(output.into_action_stream())
} else {
Err(ShellError::labeled_error(
"Could not open history",
"history file could not be opened",
tag,
))
}
}
#[cfg(test)]
mod tests {
use super::History;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
test_examples(History {})
}
}

View File

@ -57,12 +57,11 @@ impl WholeStreamCommand for If {
] ]
} }
} }
fn if_command(raw_args: CommandArgs) -> Result<OutputStream, ShellError> { fn if_command(args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = raw_args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let external_redirection = raw_args.call_info.args.external_redirection; let external_redirection = args.call_info.args.external_redirection;
let context = Arc::new(EvaluationContext::from_args(&raw_args)); let context = Arc::new(args.context.clone());
let args = raw_args.evaluate_once()?;
let condition: CapturedBlock = args.req(0)?; let condition: CapturedBlock = args.req(0)?;
let then_case: CapturedBlock = args.req(1)?; let then_case: CapturedBlock = args.req(1)?;
let else_case: CapturedBlock = args.req(2)?; let else_case: CapturedBlock = args.req(2)?;
@ -101,7 +100,7 @@ fn if_command(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
context.scope.add_vars(&condition.captured.entries); context.scope.add_vars(&condition.captured.entries);
//FIXME: should we use the scope that's brought in as well? //FIXME: should we use the scope that's brought in as well?
let condition = evaluate_baseline_expr(&cond, &*context); let condition = evaluate_baseline_expr(cond, &*context);
match condition { match condition {
Ok(condition) => match condition.as_bool() { Ok(condition) => match condition.as_bool() {
Ok(b) => { Ok(b) => {

View File

@ -0,0 +1,49 @@
extern crate unicode_segmentation;
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::Signature;
pub struct Ignore;
impl WholeStreamCommand for Ignore {
fn name(&self) -> &str {
"ignore"
}
fn signature(&self) -> Signature {
Signature::build("ignore")
}
fn usage(&self) -> &str {
"Ignore the output of the previous command in the pipeline"
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let _: Vec<_> = args.input.collect();
Ok(OutputStream::empty())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Ignore the output of an echo command",
example: r#"echo done | ignore"#,
result: None,
}]
}
}
#[cfg(test)]
mod tests {
use super::Ignore;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
test_examples(Ignore {})
}
}

View File

@ -55,7 +55,7 @@ impl WholeStreamCommand for Let {
} }
pub fn letcmd(args: CommandArgs) -> Result<ActionStream, ShellError> { pub fn letcmd(args: CommandArgs) -> Result<ActionStream, ShellError> {
let ctx = EvaluationContext::from_args(&args); let ctx = &args.context;
let positional = args let positional = args
.call_info .call_info
.args .args
@ -98,7 +98,7 @@ pub fn letcmd(args: CommandArgs) -> Result<ActionStream, ShellError> {
}; };
ctx.scope.enter_scope(); ctx.scope.enter_scope();
let value = evaluate_baseline_expr(&expr, &ctx); let value = evaluate_baseline_expr(expr, &ctx);
ctx.scope.exit_scope(); ctx.scope.exit_scope();
let value = value?; let value = value?;

View File

@ -0,0 +1,35 @@
mod alias;
mod debug;
mod def;
mod describe;
mod do_;
pub(crate) mod echo;
mod help;
mod history;
mod if_;
mod ignore;
mod let_;
mod nu_plugin;
mod nu_signature;
mod source;
mod tags;
mod version;
pub use self::nu_plugin::SubCommand as NuPlugin;
pub use self::nu_signature::{
loglevels, testbins, version as core_version, Command as NuSignature,
};
pub use alias::Alias;
pub use debug::Debug;
pub use def::Def;
pub use describe::Describe;
pub use do_::Do;
pub use echo::Echo;
pub use help::Help;
pub use history::History;
pub use if_::If;
pub use ignore::Ignore;
pub use let_::Let;
pub use source::Source;
pub use tags::Tags;
pub use version::{version, Version};

View File

@ -1,10 +1,10 @@
use std::path::PathBuf; use std::path::PathBuf;
use crate::prelude::*; use crate::prelude::*;
use nu_engine::filesystem::path::canonicalize;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_path::canonicalize;
use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue}; use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged; use nu_source::Tagged;
@ -48,7 +48,8 @@ impl WholeStreamCommand for SubCommand {
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> { fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
let scope = args.scope().clone(); let scope = args.scope().clone();
let shell_manager = args.shell_manager(); let shell_manager = args.shell_manager();
let (Arguments { load_path }, _) = args.process()?;
let load_path: Option<Tagged<PathBuf>> = args.get_flag("load")?;
if let Some(Tagged { if let Some(Tagged {
item: load_path, item: load_path,

View File

@ -0,0 +1,69 @@
use nu_engine::WholeStreamCommand;
use nu_protocol::{Signature, SyntaxShape};
pub struct Command;
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"nu"
}
fn signature(&self) -> Signature {
Signature::build("nu")
.switch("version", "Display Nu version", Some('v'))
.switch("stdin", "redirect stdin", None)
.switch("skip-plugins", "do not load plugins", None)
.switch("no-history", "don't save history", None)
.named(
"commands",
SyntaxShape::String,
"commands to run",
Some('c'),
)
.named(
"testbin",
SyntaxShape::String,
"test bin: echo_env, cococo, iecho, fail, nonu, chop, repeater, meow",
None,
)
.named("develop", SyntaxShape::String, "trace mode", None)
.named("debug", SyntaxShape::String, "debug mode", None)
.named(
"loglevel",
SyntaxShape::String,
"LEVEL: error, warn, info, debug, trace",
Some('l'),
)
.named(
"config-file",
SyntaxShape::FilePath,
"custom configuration source file",
None,
)
.rest(SyntaxShape::String, "source file(s) to run")
}
fn usage(&self) -> &str {
"Nu - A new type of shell."
}
}
pub fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
pub fn testbins() -> Vec<String> {
vec![
"echo_env", "cococo", "iecho", "fail", "nonu", "chop", "repeater", "meow",
]
.into_iter()
.map(String::from)
.collect()
}
pub fn loglevels() -> Vec<String> {
vec!["error", "warn", "info", "debug", "trace"]
.into_iter()
.map(String::from)
.collect()
}

View File

@ -2,10 +2,12 @@ use crate::prelude::*;
use nu_engine::{script, WholeStreamCommand}; use nu_engine::{script, WholeStreamCommand};
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_parser::expand_path; use nu_path::expand_path;
use nu_protocol::{Signature, SyntaxShape}; use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged; use nu_source::Tagged;
use std::{borrow::Cow, path::Path};
pub struct Source; pub struct Source;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -21,7 +23,7 @@ impl WholeStreamCommand for Source {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("source").required( Signature::build("source").required(
"filename", "filename",
SyntaxShape::String, SyntaxShape::FilePath,
"the filepath to the script file to source", "the filepath to the script file to source",
) )
} }
@ -40,19 +42,19 @@ impl WholeStreamCommand for Source {
} }
pub fn source(args: CommandArgs) -> Result<ActionStream, ShellError> { pub fn source(args: CommandArgs) -> Result<ActionStream, ShellError> {
let ctx = EvaluationContext::from_args(&args); let ctx = &args.context;
let (SourceArgs { filename }, _) = args.process()?; let filename: Tagged<String> = args.req(0)?;
// Note: this is a special case for setting the context from a command // Note: this is a special case for setting the context from a command
// In this case, if we don't set it now, we'll lose the scope that this // In this case, if we don't set it now, we'll lose the scope that this
// variable should be set into. // variable should be set into.
let contents = std::fs::read_to_string(expand_path(&filename.item).into_owned()); let contents = std::fs::read_to_string(&expand_path(Cow::Borrowed(Path::new(&filename.item))));
match contents { match contents {
Ok(contents) => { Ok(contents) => {
let result = script::run_script_standalone(contents, true, &ctx, false); let result = script::run_script_standalone(contents, true, &ctx, false);
if let Err(err) = result { if let Err(err) = result {
ctx.error(err.into()); ctx.error(err);
} }
Ok(ActionStream::empty()) Ok(ActionStream::empty())
} }

View File

@ -48,7 +48,7 @@ fn tags(args: CommandArgs) -> ActionStream {
tags.into_value() tags.into_value()
}) })
.to_action_stream() .into_action_stream()
} }
#[cfg(test)] #[cfg(test)]

View File

@ -23,7 +23,7 @@ impl WholeStreamCommand for Version {
"Display Nu version." "Display Nu version."
} }
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
version(args) version(args)
} }
@ -36,14 +36,14 @@ impl WholeStreamCommand for Version {
} }
} }
pub fn version(args: CommandArgs) -> Result<ActionStream, ShellError> { pub fn version(args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.args.span; let tag = args.call_info.args.span;
let mut indexmap = IndexMap::with_capacity(4); let mut indexmap = IndexMap::with_capacity(4);
indexmap.insert( indexmap.insert(
"version".to_string(), "version".to_string(),
UntaggedValue::string(clap::crate_version!()).into_value(&tag), UntaggedValue::string(super::nu_signature::version()).into_value(&tag),
); );
let branch: Option<&str> = Some(shadow::BRANCH).filter(|x| !x.is_empty()); let branch: Option<&str> = Some(shadow::BRANCH).filter(|x| !x.is_empty());
@ -140,8 +140,45 @@ pub fn version(args: CommandArgs) -> Result<ActionStream, ShellError> {
features_enabled().join(", ").to_string_value_create_tag(), features_enabled().join(", ").to_string_value_create_tag(),
); );
// Manually create a list of all possible plugin names
let all_plugins = vec![
"fetch",
"inc",
"match",
"post",
"ps",
"sys",
"textview",
"binaryview",
"chart bar",
"chart line",
"from bson",
"from sqlite",
"query json",
"s3",
"selector",
"start",
"to bson",
"to sqlite",
"tree",
"xpath",
];
// Get a list of command names and check for plugins
let installed_plugins = args
.scope()
.get_command_names()
.into_iter()
.filter(|cmd| all_plugins.contains(&cmd.as_str()))
.collect::<Vec<_>>();
indexmap.insert(
"installed_plugins".to_string(),
installed_plugins.join(", ").to_string_value_create_tag(),
);
let value = UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag); let value = UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag);
Ok(ActionStream::one(value)) Ok(OutputStream::one(value))
} }
fn features_enabled() -> Vec<String> { fn features_enabled() -> Vec<String> {
@ -152,16 +189,6 @@ fn features_enabled() -> Vec<String> {
names.push("ctrlc".to_string()); names.push("ctrlc".to_string());
} }
#[cfg(feature = "dirs")]
{
names.push("dirs".to_string());
}
#[cfg(feature = "directories")]
{
names.push("directories".to_string());
}
#[cfg(feature = "ptree")] #[cfg(feature = "ptree")]
{ {
names.push("ptree".to_string()); names.push("ptree".to_string());

View File

@ -1,12 +1,15 @@
use crate::prelude::*; use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{
dataframe::{NuDataFrame, PolarsData}, dataframe::{NuDataFrame, PolarsData},
Signature, SyntaxShape, UntaggedValue, Value, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
}; };
use nu_source::Tagged; use nu_source::Tagged;
use polars::frame::groupby::GroupBy; use polars::{
frame::groupby::GroupBy,
prelude::{DataType, PolarsError, Series},
};
use super::utils::convert_columns; use super::utils::convert_columns;
@ -66,7 +69,7 @@ impl Operation {
"Operation not fount", "Operation not fount",
"Operation does not exist", "Operation does not exist",
&name.tag, &name.tag,
"Perhaps you want: mean, sum, min, max, first, last, nunique, quantile, median, count", "Perhaps you want: mean, sum, min, max, first, last, nunique, quantile, median, var, std, or count",
&name.tag, &name.tag,
)), )),
} }
@ -77,15 +80,15 @@ pub struct DataFrame;
impl WholeStreamCommand for DataFrame { impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str { fn name(&self) -> &str {
"pls aggregate" "dataframe aggregate"
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Performs an aggregation operation on a groupby object" "Performs an aggregation operation on a dataframe or groupby object"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("pls aggregate") Signature::build("dataframe aggregate")
.required("operation", SyntaxShape::String, "aggregate operation") .required("operation", SyntaxShape::String, "aggregate operation")
.optional( .optional(
"selection", "selection",
@ -101,22 +104,33 @@ impl WholeStreamCommand for DataFrame {
} }
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
aggregate(args) command(args)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![
description: "Aggregate sum by grouping by column a and summing on col b", Example {
example: description: "Aggregate sum by grouping by column a and summing on col b",
"echo [[a b]; [one 1] [one 2]] | pls convert | pls groupby [a] | pls aggregate sum", example:
result: None, "[[a b]; [one 1] [one 2]] | dataframe to-df | dataframe group-by [a] | dataframe aggregate sum",
}] result: None,
},
Example {
description: "Aggregate sum in dataframe columns",
example: "[[a b]; [4 1] [5 2]] | dataframe to-df | dataframe aggregate sum",
result: None,
},
Example {
description: "Aggregate sum in series",
example: "[4 1 5 6] | dataframe to-series | dataframe aggregate sum",
result: None,
},
]
} }
} }
fn aggregate(args: CommandArgs) -> Result<OutputStream, ShellError> { fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let mut args = args.evaluate_once()?;
let quantile: Option<Tagged<f64>> = args.get_flag("quantile")?; let quantile: Option<Tagged<f64>> = args.get_flag("quantile")?;
let operation: Tagged<String> = args.req(0)?; let operation: Tagged<String> = args.req(0)?;
@ -132,45 +146,53 @@ fn aggregate(args: CommandArgs) -> Result<OutputStream, ShellError> {
None => (None, Span::unknown()), None => (None, Span::unknown()),
}; };
// The operation is only done in one dataframe. Only one input is let value = args.input.next().ok_or_else(|| {
// expected from the InputStream ShellError::labeled_error("Empty stream", "No value found in the stream", &tag)
match args.input.next() { })?;
None => Err(ShellError::labeled_error(
"No input received",
"missing dataframe input from stream",
&tag,
)),
Some(value) => {
if let UntaggedValue::DataFrame(PolarsData::GroupBy(nu_groupby)) = value.value {
let groupby = nu_groupby.to_groupby()?;
let groupby = match &selection { match value.value {
Some(cols) => groupby.select(cols), UntaggedValue::DataFrame(PolarsData::GroupBy(nu_groupby)) => {
None => groupby, let groupby = nu_groupby.to_groupby()?;
};
let res = perform_aggregation(groupby, op, &operation.tag, &agg_span)?; let groupby = match &selection {
Some(cols) => groupby.select(cols),
None => groupby,
};
let final_df = Value { let res = perform_groupby_aggregation(groupby, op, &operation.tag, &agg_span)?;
tag,
value: UntaggedValue::DataFrame(PolarsData::EagerDataFrame(NuDataFrame::new(
res,
))),
};
Ok(OutputStream::one(final_df)) Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
} else {
Err(ShellError::labeled_error(
"No groupby in stream",
"no groupby found in input stream",
&tag,
))
}
} }
UntaggedValue::DataFrame(PolarsData::EagerDataFrame(df)) => {
let df = df.as_ref();
let res = match &selection {
Some(cols) => {
let df = df
.select(cols)
.map_err(|e| parse_polars_error::<&str>(&e, &agg_span, None))?;
perform_dataframe_aggregation(&df, op, &operation.tag)
}
None => perform_dataframe_aggregation(&df, op, &operation.tag),
}?;
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
}
UntaggedValue::DataFrame(PolarsData::Series(series)) => {
let value = perform_series_aggregation(series.as_ref(), op, &operation.tag)?;
Ok(OutputStream::one(value))
}
_ => Err(ShellError::labeled_error(
"No groupby or dataframe",
"no groupby or found in input stream",
&value.tag.span,
)),
} }
} }
fn perform_aggregation( fn perform_groupby_aggregation(
groupby: GroupBy, groupby: GroupBy,
operation: Operation, operation: Operation,
operation_tag: &Tag, operation_tag: &Tag,
@ -191,12 +213,197 @@ fn perform_aggregation(
Operation::Count => groupby.count(), Operation::Count => groupby.count(),
} }
.map_err(|e| { .map_err(|e| {
let span = if e.to_string().contains("Not found") { let span = match &e {
agg_span PolarsError::NotFound(_) => agg_span,
} else { _ => &operation_tag.span,
&operation_tag.span
}; };
ShellError::labeled_error("Aggregation error", format!("{}", e), span) parse_polars_error::<&str>(&e, span, None)
}) })
} }
fn perform_dataframe_aggregation(
dataframe: &polars::prelude::DataFrame,
operation: Operation,
operation_tag: &Tag,
) -> Result<polars::prelude::DataFrame, ShellError> {
match operation {
Operation::Mean => Ok(dataframe.mean()),
Operation::Sum => Ok(dataframe.sum()),
Operation::Min => Ok(dataframe.min()),
Operation::Max => Ok(dataframe.max()),
Operation::Quantile(quantile) => dataframe
.quantile(quantile)
.map_err(|e| parse_polars_error::<&str>(&e, &operation_tag.span, None)),
Operation::Median => Ok(dataframe.median()),
Operation::Var => Ok(dataframe.var()),
Operation::Std => Ok(dataframe.std()),
_ => Err(ShellError::labeled_error_with_secondary(
"Not valid operation",
"operation not valid for dataframe",
&operation_tag.span,
"Perhaps you want: mean, sum, min, max, quantile, median, var, or std",
&operation_tag.span,
)),
}
}
fn perform_series_aggregation(
series: &Series,
operation: Operation,
operation_tag: &Tag,
) -> Result<Value, ShellError> {
match operation {
Operation::Mean => {
let res = match series.mean() {
Some(val) => UntaggedValue::Primitive(val.into()),
None => UntaggedValue::Primitive(0.into()),
};
let value = Value {
value: res,
tag: operation_tag.clone(),
};
let mut data = TaggedDictBuilder::new(operation_tag.clone());
data.insert_value("mean", value);
Ok(data.into_value())
}
Operation::Median => {
let res = match series.median() {
Some(val) => UntaggedValue::Primitive(val.into()),
None => UntaggedValue::Primitive(0.into()),
};
let value = Value {
value: res,
tag: operation_tag.clone(),
};
let mut data = TaggedDictBuilder::new(operation_tag.clone());
data.insert_value("median", value);
Ok(data.into_value())
}
Operation::Sum => {
let untagged = match series.dtype() {
DataType::Int8
| DataType::Int16
| DataType::Int32
| DataType::Int64
| DataType::UInt8
| DataType::UInt16
| DataType::UInt32
| DataType::UInt64 => {
let res: i64 = series.sum().unwrap_or(0);
Ok(UntaggedValue::Primitive(res.into()))
}
DataType::Float32 | DataType::Float64 => {
let res: f64 = series.sum().unwrap_or(0.0);
Ok(UntaggedValue::Primitive(res.into()))
}
_ => Err(ShellError::labeled_error(
"Not valid type",
format!(
"this operation can not be performed with series of type {}",
series.dtype()
),
&operation_tag.span,
)),
}?;
let value = Value {
value: untagged,
tag: operation_tag.clone(),
};
let mut data = TaggedDictBuilder::new(operation_tag.clone());
data.insert_value("sum", value);
Ok(data.into_value())
}
Operation::Max => {
let untagged = match series.dtype() {
DataType::Int8
| DataType::Int16
| DataType::Int32
| DataType::Int64
| DataType::UInt8
| DataType::UInt16
| DataType::UInt32
| DataType::UInt64 => {
let res: i64 = series.max().unwrap_or(0);
Ok(UntaggedValue::Primitive(res.into()))
}
DataType::Float32 | DataType::Float64 => {
let res: f64 = series.max().unwrap_or(0.0);
Ok(UntaggedValue::Primitive(res.into()))
}
_ => Err(ShellError::labeled_error(
"Not valid type",
format!(
"this operation can not be performed with series of type {}",
series.dtype()
),
&operation_tag.span,
)),
}?;
let value = Value {
value: untagged,
tag: operation_tag.clone(),
};
let mut data = TaggedDictBuilder::new(operation_tag.clone());
data.insert_value("max", value);
Ok(data.into_value())
}
Operation::Min => {
let untagged = match series.dtype() {
DataType::Int8
| DataType::Int16
| DataType::Int32
| DataType::Int64
| DataType::UInt8
| DataType::UInt16
| DataType::UInt32
| DataType::UInt64 => {
let res: i64 = series.min().unwrap_or(0);
Ok(UntaggedValue::Primitive(res.into()))
}
DataType::Float32 | DataType::Float64 => {
let res: f64 = series.min().unwrap_or(0.0);
Ok(UntaggedValue::Primitive(res.into()))
}
_ => Err(ShellError::labeled_error(
"Not valid type",
format!(
"this operation can not be performed with series of type {}",
series.dtype()
),
&operation_tag.span,
)),
}?;
let value = Value {
value: untagged,
tag: operation_tag.clone(),
};
let mut data = TaggedDictBuilder::new(operation_tag.clone());
data.insert_value("min", value);
Ok(data.into_value())
}
_ => Err(ShellError::labeled_error_with_secondary(
"Not valid operation",
"operation not valid for series",
&operation_tag.span,
"Perhaps you want: mean, median, sum, max, min",
&operation_tag.span,
)),
}
}

View File

@ -0,0 +1,55 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{
dataframe::{NuDataFrame, NuSeries},
Signature, SyntaxShape,
};
use nu_source::Tagged;
use super::utils::parse_polars_error;
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe column"
}
fn usage(&self) -> &str {
"Returns the selected column as Series"
}
fn signature(&self) -> Signature {
Signature::build("dataframe column").required("column", SyntaxShape::String, "column name")
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns the selected column as series",
example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe column a",
result: None,
}]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let column: Tagged<String> = args.req(0)?;
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
let res = df
.as_ref()
.column(column.item.as_ref())
.map_err(|e| parse_polars_error::<&str>(&e, &column.tag.span, None))?;
Ok(OutputStream::one(NuSeries::series_to_value(
res.clone(),
tag,
)))
}

View File

@ -7,7 +7,7 @@ pub struct Command;
impl WholeStreamCommand for Command { impl WholeStreamCommand for Command {
fn name(&self) -> &str { fn name(&self) -> &str {
"pls" "dataframe"
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -15,7 +15,7 @@ impl WholeStreamCommand for Command {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("pls") Signature::build("dataframe")
} }
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -1,18 +1,15 @@
use crate::prelude::*; use crate::prelude::*;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{dataframe::NuDataFrame, Signature, SyntaxShape, Value};
dataframe::{NuDataFrame, PolarsData},
Signature, SyntaxShape, UntaggedValue, Value,
};
use super::utils::convert_columns; use super::utils::{convert_columns, parse_polars_error};
pub struct DataFrame; pub struct DataFrame;
impl WholeStreamCommand for DataFrame { impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str { fn name(&self) -> &str {
"pls drop" "dataframe drop"
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -20,7 +17,7 @@ impl WholeStreamCommand for DataFrame {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("pls drop").required( Signature::build("dataframe drop").required(
"columns", "columns",
SyntaxShape::Table, SyntaxShape::Table,
"column names to be dropped", "column names to be dropped",
@ -28,70 +25,45 @@ impl WholeStreamCommand for DataFrame {
} }
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
drop(args) command(args)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "drop column a", description: "drop column a",
example: "echo [[a b]; [1 2] [3 4]] | pls convert | pls drop [a]", example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe drop [a]",
result: None, result: None,
}] }]
} }
} }
fn drop(args: CommandArgs) -> Result<OutputStream, ShellError> { fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let mut args = args.evaluate_once()?;
let columns: Vec<Value> = args.req(0)?; let columns: Vec<Value> = args.req(0)?;
let (col_string, col_span) = convert_columns(&columns, &tag)?; let (col_string, col_span) = convert_columns(&columns, &tag)?;
match args.input.next() { let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
let new_df = match col_string.get(0) {
Some(col) => df
.as_ref()
.drop(col)
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None)),
None => Err(ShellError::labeled_error( None => Err(ShellError::labeled_error(
"No input received", "Empty names list",
"missing dataframe input from stream", "No column names where found",
&tag, &col_span,
)), )),
Some(value) => { }?;
if let UntaggedValue::DataFrame(PolarsData::EagerDataFrame(NuDataFrame {
dataframe: Some(ref df),
..
})) = value.value
{
let new_df = match col_string.iter().next() {
Some(col) => df.drop(col).map_err(|e| {
ShellError::labeled_error("Join error", format!("{}", e), &col_span)
}),
None => Err(ShellError::labeled_error(
"Empty names list",
"No column names where found",
&col_span,
)),
}?;
let res = col_string.iter().skip(1).try_fold(new_df, |new_df, col| { // If there are more columns in the drop selection list, these
new_df.drop(col).map_err(|e| { // are added from the resulting dataframe
ShellError::labeled_error("Drop error", format!("{}", e), &col_span) let res = col_string.iter().skip(1).try_fold(new_df, |new_df, col| {
}) new_df
})?; .drop(col)
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None))
})?;
let value = Value { Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
value: UntaggedValue::DataFrame(PolarsData::EagerDataFrame(NuDataFrame::new(
res,
))),
tag: tag.clone(),
};
Ok(OutputStream::one(value))
} else {
Err(ShellError::labeled_error(
"No dataframe in stream",
"no dataframe found in input stream",
&tag,
))
}
}
}
} }

View File

@ -0,0 +1,65 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{dataframe::NuDataFrame, Signature, SyntaxShape, Value};
use super::utils::{convert_columns, parse_polars_error};
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe drop-duplicates"
}
fn usage(&self) -> &str {
"Drops duplicate values in dataframe"
}
fn signature(&self) -> Signature {
Signature::build("dataframe drop-duplicates")
.optional(
"subset",
SyntaxShape::Table,
"subset of columns to drop duplicates",
)
.switch("maintain", "maintain order", Some('m'))
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "drop duplicates",
example: "[[a b]; [1 2] [3 4] [1 2]] | dataframe to-df | dataframe drop-duplicates",
result: None,
}]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
// Extracting the selection columns of the columns to perform the aggregation
let columns: Option<Vec<Value>> = args.opt(0)?;
let (subset, col_span) = match columns {
Some(cols) => {
let (agg_string, col_span) = convert_columns(&cols, &tag)?;
(Some(agg_string), col_span)
}
None => (None, Span::unknown()),
};
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
let subset_slice = subset.as_ref().map(|cols| &cols[..]);
let res = df
.as_ref()
.drop_duplicates(args.has_flag("maintain"), subset_slice)
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None))?;
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
}

View File

@ -0,0 +1,93 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{
dataframe::{NuDataFrame, NuSeries, PolarsData},
Signature, SyntaxShape, UntaggedValue, Value,
};
use super::utils::{convert_columns, parse_polars_error};
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe drop-nulls"
}
fn usage(&self) -> &str {
"Drops null values in dataframe"
}
fn signature(&self) -> Signature {
Signature::build("dataframe drop-nulls").optional(
"subset",
SyntaxShape::Table,
"subset of columns to drop duplicates",
)
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "drop null values in dataframe",
example: r#"let df = ([[a b]; [1 2] [3 0] [1 2]] | dataframe to-df);
let res = ($df.b / $df.b);
let df = ($df | dataframe with-column $res as res);
$df | dataframe drop-nulls
"#,
result: None,
},
Example {
description: "drop null values in dataframe",
example: r#"let s = ([1 2 0 0 3 4] | dataframe to-series);
($s / $s) | dataframe drop-nulls"#,
result: None,
},
]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let value = args.input.next().ok_or_else(|| {
ShellError::labeled_error("Empty stream", "No value found in stream", &tag.span)
})?;
match value.value {
UntaggedValue::DataFrame(PolarsData::EagerDataFrame(df)) => {
// Extracting the selection columns of the columns to perform the aggregation
let columns: Option<Vec<Value>> = args.opt(0)?;
let (subset, col_span) = match columns {
Some(cols) => {
let (agg_string, col_span) = convert_columns(&cols, &tag)?;
(Some(agg_string), col_span)
}
None => (None, Span::unknown()),
};
let subset_slice = subset.as_ref().map(|cols| &cols[..]);
let res = df
.as_ref()
.drop_nulls(subset_slice)
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None))?;
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
}
UntaggedValue::DataFrame(PolarsData::Series(series)) => {
let res = series.as_ref().drop_nulls();
Ok(OutputStream::one(NuSeries::series_to_value(res, tag)))
}
_ => Err(ShellError::labeled_error(
"Incorrect type",
"drop nulls cannot be done with this value",
&value.tag.span,
)),
}
}

View File

@ -1,16 +1,13 @@
use crate::prelude::*; use crate::prelude::*;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{dataframe::NuDataFrame, Signature, TaggedDictBuilder};
dataframe::{NuDataFrame, PolarsData},
Signature, TaggedDictBuilder, UntaggedValue,
};
pub struct DataFrame; pub struct DataFrame;
impl WholeStreamCommand for DataFrame { impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str { fn name(&self) -> &str {
"pls dtypes" "dataframe dtypes"
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -18,64 +15,46 @@ impl WholeStreamCommand for DataFrame {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("pls dtypes") Signature::build("dataframe dtypes")
} }
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
dtypes(args) command(args)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "drop column a", description: "drop column a",
example: "echo [[a b]; [1 2] [3 4]] | pls convert | pls dtypes", example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe dtypes",
result: None, result: None,
}] }]
} }
} }
fn dtypes(args: CommandArgs) -> Result<OutputStream, ShellError> { #[allow(clippy::needless_collect)]
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let mut args = args.evaluate_once()?;
match args.input.next() { let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
None => Err(ShellError::labeled_error( let col_names = df
"No input received", .as_ref()
"missing dataframe input from stream", .get_column_names()
&tag, .iter()
)), .map(|v| v.to_string())
Some(value) => { .collect::<Vec<String>>();
if let UntaggedValue::DataFrame(PolarsData::EagerDataFrame(NuDataFrame {
dataframe: Some(df),
..
})) = value.value
{
let col_names = df
.get_column_names()
.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>();
let values = let values = df
df.dtypes() .as_ref()
.into_iter() .dtypes()
.zip(col_names.into_iter()) .into_iter()
.map(move |(dtype, name)| { .zip(col_names.into_iter())
let mut data = TaggedDictBuilder::new(tag.clone()); .map(move |(dtype, name)| {
data.insert_value("column", name.as_ref()); let mut data = TaggedDictBuilder::new(tag.clone());
data.insert_value("dtype", format!("{}", dtype)); data.insert_value("column", name.as_ref());
data.insert_value("dtype", format!("{}", dtype));
data.into_value() data.into_value()
}); });
Ok(OutputStream::from_stream(values)) Ok(OutputStream::from_stream(values))
} else {
Err(ShellError::labeled_error(
"No dataframe in stream",
"no dataframe found in input stream",
&tag,
))
}
}
}
} }

View File

@ -0,0 +1,82 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{
dataframe::{NuDataFrame, PolarsData},
Signature, UntaggedValue,
};
use super::utils::parse_polars_error;
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe to-dummies"
}
fn usage(&self) -> &str {
"Creates a new dataframe with dummy variables"
}
fn signature(&self) -> Signature {
Signature::build("dataframe to-dummies")
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Create new dataframe with dummy variables from a dataframe",
example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe to-dummies",
result: None,
},
Example {
description: "Create new dataframe with dummy variables from a series",
example: "[1 2 2 3 3] | dataframe to-series | dataframe to-dummies",
result: None,
},
]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let value = args.input.next().ok_or_else(|| {
ShellError::labeled_error("Empty stream", "No value found in stream", &tag.span)
})?;
match value.value {
UntaggedValue::DataFrame(PolarsData::EagerDataFrame(df)) => {
let res = df.as_ref().to_dummies().map_err(|e| {
parse_polars_error(
&e,
&tag.span,
Some("The only allowed column types for dummies are String or Int"),
)
})?;
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
}
UntaggedValue::DataFrame(PolarsData::Series(series)) => {
let res = series.as_ref().to_dummies().map_err(|e| {
parse_polars_error(
&e,
&tag.span,
Some("The only allowed column types for dummies are String or Int"),
)
})?;
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
}
_ => Err(ShellError::labeled_error(
"Incorrect type",
"dummies cannot be done with this value",
&value.tag.span,
)),
}
}

View File

@ -0,0 +1,79 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{
dataframe::{NuDataFrame, PolarsData},
Signature, SyntaxShape, UntaggedValue, Value,
};
use super::utils::parse_polars_error;
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe filter"
}
fn usage(&self) -> &str {
"Filters dataframe using a mask as reference"
}
fn signature(&self) -> Signature {
Signature::build("dataframe filter")
.required("with", SyntaxShape::String, "the word 'with'")
.required("mask", SyntaxShape::Any, "boolean mask used to filter data")
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Filter dataframe using a bool mask",
example: r#"let mask = ([$true $false] | dataframe to-series);
[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe filter with $mask"#,
result: None,
},
Example {
description: "Filter dataframe by creating a mask from operation",
example: r#"let mask = (([5 6] | dataframe to-series) > 5);
[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe filter with $mask"#,
result: None,
},
]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let value: Value = args.req(1)?;
let series_span = value.tag.span;
let series = match value.value {
UntaggedValue::DataFrame(PolarsData::Series(series)) => Ok(series),
_ => Err(ShellError::labeled_error(
"Incorrect type",
"can only add a series to a dataframe",
value.tag.span,
)),
}?;
let casted = series.as_ref().bool().map_err(|e| {
parse_polars_error(
&e,
&&series_span,
Some("Perhaps you want to use a series with booleans as mask"),
)
})?;
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
let res = df
.as_ref()
.filter(&casted)
.map_err(|e| parse_polars_error::<&str>(&e, &tag.span, None))?;
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
}

View File

@ -0,0 +1,53 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{dataframe::NuDataFrame, Signature, SyntaxShape, Value};
use super::utils::{convert_columns, parse_polars_error};
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe get"
}
fn usage(&self) -> &str {
"Creates dataframe with the selected columns"
}
fn signature(&self) -> Signature {
Signature::build("dataframe get").required(
"columns",
SyntaxShape::Table,
"column names to sort dataframe",
)
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Creates dataframe with selected columns",
example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe get [a]",
result: None,
}]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let columns: Vec<Value> = args.req(0)?;
let (col_string, col_span) = convert_columns(&columns, &tag)?;
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
let res = df
.as_ref()
.select(&col_string)
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None))?;
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
}

View File

@ -1,4 +1,4 @@
use crate::prelude::*; use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{
@ -12,7 +12,7 @@ pub struct DataFrame;
impl WholeStreamCommand for DataFrame { impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str { fn name(&self) -> &str {
"pls groupby" "dataframe group-by"
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -20,7 +20,7 @@ impl WholeStreamCommand for DataFrame {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("pls groupby").required( Signature::build("dataframe group-by").required(
"by columns", "by columns",
SyntaxShape::Table, SyntaxShape::Table,
"groupby columns", "groupby columns",
@ -28,67 +28,45 @@ impl WholeStreamCommand for DataFrame {
} }
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
groupby(args) command(args)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "Grouping by column a", description: "Grouping by column a",
example: "echo [[a b]; [one 1] [one 2]] | pls convert | pls groupby [a]", example: "[[a b]; [one 1] [one 2]] | dataframe to-df | dataframe group-by [a]",
result: None, result: None,
}] }]
} }
} }
fn groupby(args: CommandArgs) -> Result<OutputStream, ShellError> { fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let mut args = args.evaluate_once()?;
// Extracting the names of the columns to perform the groupby // Extracting the names of the columns to perform the groupby
let by_columns: Vec<Value> = args.req(0)?; let by_columns: Vec<Value> = args.req(0)?;
let (columns_string, col_span) = convert_columns(&by_columns, &tag)?; let (columns_string, col_span) = convert_columns(&by_columns, &tag)?;
// The operation is only done in one dataframe. Only one input is let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
// expected from the InputStream
match args.input.next() {
None => Err(ShellError::labeled_error(
"No input received",
"missing dataframe input from stream",
&tag,
)),
Some(value) => {
if let UntaggedValue::DataFrame(PolarsData::EagerDataFrame(nu_df)) = value.value {
let df = match nu_df.dataframe {
Some(df) => df,
None => unreachable!("No dataframe in nu_dataframe"),
};
// This is the expensive part of the groupby; to create the // This is the expensive part of the groupby; to create the
// groups that will be used for grouping the data in the // groups that will be used for grouping the data in the
// dataframe. Once it has been done these values can be stored // dataframe. Once it has been done these values can be stored
// in the NuGroupBy // in a NuGroupBy
let groupby = df.groupby(&columns_string).map_err(|e| { let groupby = df
ShellError::labeled_error("Groupby error", format!("{}", e), col_span) .as_ref()
})?; .groupby(&columns_string)
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None))?;
let groups = groupby.get_groups().to_vec(); let groups = groupby.get_groups().to_vec();
let groupby = Value { let groupby = Value {
tag: value.tag, tag,
value: UntaggedValue::DataFrame(PolarsData::GroupBy(NuGroupBy::new( value: UntaggedValue::DataFrame(PolarsData::GroupBy(NuGroupBy::new(
NuDataFrame::new_with_name(df, nu_df.name), NuDataFrame::new(df.as_ref().clone()),
columns_string, columns_string,
groups, groups,
))), ))),
}; };
Ok(OutputStream::one(groupby)) Ok(OutputStream::one(groupby))
} else {
Err(ShellError::labeled_error(
"No dataframe in stream",
"no dataframe found in input stream",
&tag,
))
}
}
}
} }

View File

@ -0,0 +1,53 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{dataframe::NuDataFrame, Signature, SyntaxShape};
use nu_source::Tagged;
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe head"
}
fn usage(&self) -> &str {
"Creates new dataframe with head rows"
}
fn signature(&self) -> Signature {
Signature::build("dataframe select").optional(
"rows",
SyntaxShape::Number,
"Number of rows for head",
)
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Create new dataframe with head rows",
example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe head",
result: None,
}]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let rows: Option<Tagged<usize>> = args.opt(0)?;
let rows = match rows {
Some(val) => val.item,
None => 5,
};
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
let res = df.as_ref().head(Some(rows));
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
}

View File

@ -6,7 +6,7 @@ use nu_protocol::{
Signature, SyntaxShape, UntaggedValue, Value, Signature, SyntaxShape, UntaggedValue, Value,
}; };
use super::utils::convert_columns; use super::utils::{convert_columns, parse_polars_error};
use polars::prelude::JoinType; use polars::prelude::JoinType;
@ -16,7 +16,7 @@ pub struct DataFrame;
impl WholeStreamCommand for DataFrame { impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str { fn name(&self) -> &str {
"pls join" "dataframe join"
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -24,7 +24,7 @@ impl WholeStreamCommand for DataFrame {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("pls join") Signature::build("dataframe join")
.required("dataframe", SyntaxShape::Any, "right dataframe to join") .required("dataframe", SyntaxShape::Any, "right dataframe to join")
.required( .required(
"l_columns", "l_columns",
@ -45,29 +45,28 @@ impl WholeStreamCommand for DataFrame {
} }
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
join(args) command(args)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
description: "inner join dataframe", description: "inner join dataframe",
example: "echo [[a b]; [1 2] [3 4]] | pls convert | pls join $right [a] [a]", example: "echo [[a b]; [1 2] [3 4]] | dataframe to-df | dataframe join $right [a] [a]",
result: None, result: None,
}, },
Example { Example {
description: "right join dataframe", description: "right join dataframe",
example: example:
"echo [[a b]; [1 2] [3 4] [5 6]] | pls convert | pls join $right [b] [b] -t right", "[[a b]; [1 2] [3 4] [5 6]] | dataframe to-df | dataframe join $right [b] [b] -t right",
result: None, result: None,
}, },
] ]
} }
} }
fn join(args: CommandArgs) -> Result<OutputStream, ShellError> { fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let mut args = args.evaluate_once()?;
let r_df: Value = args.req(0)?; let r_df: Value = args.req(0)?;
let l_col: Vec<Value> = args.req(1)?; let l_col: Vec<Value> = args.req(1)?;
@ -95,65 +94,31 @@ fn join(args: CommandArgs) -> Result<OutputStream, ShellError> {
let (l_col_string, l_col_span) = convert_columns(&l_col, &tag)?; let (l_col_string, l_col_span) = convert_columns(&l_col, &tag)?;
let (r_col_string, r_col_span) = convert_columns(&r_col, &tag)?; let (r_col_string, r_col_span) = convert_columns(&r_col, &tag)?;
match args.input.next() { let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
None => Err(ShellError::labeled_error(
"No input received",
"missing dataframe input from stream",
&tag,
)),
Some(value) => {
if let UntaggedValue::DataFrame(PolarsData::EagerDataFrame(NuDataFrame {
dataframe: Some(ref df),
..
})) = value.value
{
let res = match r_df.value {
UntaggedValue::DataFrame(PolarsData::EagerDataFrame(NuDataFrame {
dataframe: Some(r_df),
..
})) => {
// Checking the column types before performing the join
check_column_datatypes(
df,
&l_col_string,
&l_col_span,
&r_col_string,
&r_col_span,
)?;
df.join(&r_df, &l_col_string, &r_col_string, join_type) let res = match r_df.value {
.map_err(|e| { UntaggedValue::DataFrame(PolarsData::EagerDataFrame(r_df)) => {
ShellError::labeled_error( // Checking the column types before performing the join
"Join error", check_column_datatypes(
format!("{}", e), df.as_ref(),
&l_col_span, &l_col_string,
) &l_col_span,
}) &r_col_string,
} &r_col_span,
_ => Err(ShellError::labeled_error( )?;
"Not a dataframe",
"not a dataframe type value",
&r_df.tag,
)),
}?;
let value = Value { df.as_ref()
value: UntaggedValue::DataFrame(PolarsData::EagerDataFrame(NuDataFrame::new( .join(r_df.as_ref(), &l_col_string, &r_col_string, join_type)
res, .map_err(|e| parse_polars_error::<&str>(&e, &l_col_span, None))
))),
tag: tag.clone(),
};
Ok(OutputStream::one(value))
} else {
Err(ShellError::labeled_error(
"No dataframe in stream",
"no dataframe found in input stream",
&tag,
))
}
} }
} _ => Err(ShellError::labeled_error(
"Not a dataframe",
"not a dataframe type value",
&r_df.tag,
)),
}?;
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
} }
fn check_column_datatypes<T: AsRef<str>>( fn check_column_datatypes<T: AsRef<str>>(
@ -180,11 +145,11 @@ fn check_column_datatypes<T: AsRef<str>>(
for (l, r) in l_cols.iter().zip(r_cols.iter()) { for (l, r) in l_cols.iter().zip(r_cols.iter()) {
let l_series = df let l_series = df
.column(l.as_ref()) .column(l.as_ref())
.map_err(|e| ShellError::labeled_error("Join error", format!("{}", e), l_col_span))?; .map_err(|e| parse_polars_error::<&str>(&e, &l_col_span, None))?;
let r_series = df let r_series = df
.column(r.as_ref()) .column(r.as_ref())
.map_err(|e| ShellError::labeled_error("Join error", format!("{}", e), r_col_span))?; .map_err(|e| parse_polars_error::<&str>(&e, &r_col_span, None))?;
if l_series.dtype() != r_series.dtype() { if l_series.dtype() != r_series.dtype() {
return Err(ShellError::labeled_error_with_secondary( return Err(ShellError::labeled_error_with_secondary(

View File

@ -1,16 +1,13 @@
use crate::prelude::*; use crate::prelude::*;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{dataframe::PolarsData, Signature, TaggedDictBuilder, UntaggedValue};
dataframe::{NuDataFrame, PolarsData},
Signature, TaggedDictBuilder, UntaggedValue,
};
pub struct DataFrame; pub struct DataFrame;
impl WholeStreamCommand for DataFrame { impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str { fn name(&self) -> &str {
"pls list" "dataframe list"
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -18,33 +15,36 @@ impl WholeStreamCommand for DataFrame {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("pls list") Signature::build("dataframe list")
} }
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once()?;
let values = args let values = args
.context .context
.scope .scope
.get_vars() .get_vars()
.into_iter() .into_iter()
.filter_map(|(name, value)| { .filter_map(|(name, value)| {
if let UntaggedValue::DataFrame(PolarsData::EagerDataFrame(NuDataFrame { if let UntaggedValue::DataFrame(PolarsData::EagerDataFrame(df)) = &value.value {
dataframe: Some(df),
name: file_name,
})) = &value.value
{
let mut data = TaggedDictBuilder::new(value.tag.clone()); let mut data = TaggedDictBuilder::new(value.tag.clone());
let rows = df.height(); let rows = df.as_ref().height();
let cols = df.width(); let cols = df.as_ref().width();
data.insert_value("name", name.as_ref()); data.insert_value("name", name.as_ref());
data.insert_value("file", file_name.as_ref());
data.insert_value("rows", format!("{}", rows)); data.insert_value("rows", format!("{}", rows));
data.insert_value("columns", format!("{}", cols)); data.insert_value("columns", format!("{}", cols));
match value.tag.anchor {
Some(AnchorLocation::File(name)) => data.insert_value("location", name),
Some(AnchorLocation::Url(name)) => data.insert_value("location", name),
Some(AnchorLocation::Source(text)) => {
let loc_name = text.slice(0..text.end);
data.insert_value("location", loc_name.text)
}
None => data.insert_value("location", "stream"),
}
Some(data.into_value()) Some(data.into_value())
} else { } else {
None None
@ -57,7 +57,7 @@ impl WholeStreamCommand for DataFrame {
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "Lists loaded dataframes in current scope", description: "Lists loaded dataframes in current scope",
example: "pls list", example: "dataframe list",
result: None, result: None,
}] }]
} }

View File

@ -1,11 +1,10 @@
use std::path::PathBuf; use std::path::PathBuf;
use crate::prelude::*; use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
use nu_engine::{EvaluatedCommandArgs, WholeStreamCommand}; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{
dataframe::{NuDataFrame, PolarsData}, dataframe::NuDataFrame, Primitive, Signature, SyntaxShape, UntaggedValue, Value,
Primitive, Signature, SyntaxShape, UntaggedValue, Value,
}; };
use nu_source::Tagged; use nu_source::Tagged;
@ -16,7 +15,7 @@ pub struct DataFrame;
impl WholeStreamCommand for DataFrame { impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str { fn name(&self) -> &str {
"pls load" "dataframe load"
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -24,11 +23,11 @@ impl WholeStreamCommand for DataFrame {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("pls load") Signature::build("dataframe load")
.required( .required(
"file", "file",
SyntaxShape::FilePath, SyntaxShape::FilePath,
"the file path to load values from", "file path to load values from",
) )
.named( .named(
"delimiter", "delimiter",
@ -62,21 +61,20 @@ impl WholeStreamCommand for DataFrame {
} }
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
create_from_file(args) command(args)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "Takes a file name and creates a dataframe", description: "Takes a file name and creates a dataframe",
example: "pls load test.csv", example: "dataframe load test.csv",
result: None, result: None,
}] }]
} }
} }
fn create_from_file(args: CommandArgs) -> Result<OutputStream, ShellError> { fn command(args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let args = args.evaluate_once()?;
let file: Tagged<PathBuf> = args.req(0)?; let file: Tagged<PathBuf> = args.req(0)?;
let df = match file.item().extension() { let df = match file.item().extension() {
@ -101,24 +99,24 @@ fn create_from_file(args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(name) => name, Ok(name) => name,
Err(e) => { Err(e) => {
return Err(ShellError::labeled_error( return Err(ShellError::labeled_error(
"Error with file name", "File Name Error",
format!("{:?}", e), format!("{:?}", e),
&file.tag, &file.tag,
)) ))
} }
}; };
let init = InputStream::one( let df_tag = Tag {
UntaggedValue::DataFrame(PolarsData::EagerDataFrame(NuDataFrame::new_with_name( anchor: Some(AnchorLocation::File(file_name)),
df, file_name, span: tag.span,
))) };
.into_value(&tag),
);
Ok(init.to_output_stream()) Ok(OutputStream::one(NuDataFrame::dataframe_to_value(
df, df_tag,
)))
} }
fn from_parquet(args: EvaluatedCommandArgs) -> Result<polars::prelude::DataFrame, ShellError> { fn from_parquet(args: CommandArgs) -> Result<polars::prelude::DataFrame, ShellError> {
let file: Tagged<PathBuf> = args.req(0)?; let file: Tagged<PathBuf> = args.req(0)?;
let r = File::open(&file.item) let r = File::open(&file.item)
@ -128,10 +126,10 @@ fn from_parquet(args: EvaluatedCommandArgs) -> Result<polars::prelude::DataFrame
reader reader
.finish() .finish()
.map_err(|e| ShellError::labeled_error("Error with file", format!("{:?}", e), &file.tag)) .map_err(|e| parse_polars_error::<&str>(&e, &file.tag.span, None))
} }
fn from_json(args: EvaluatedCommandArgs) -> Result<polars::prelude::DataFrame, ShellError> { fn from_json(args: CommandArgs) -> Result<polars::prelude::DataFrame, ShellError> {
let file: Tagged<PathBuf> = args.req(0)?; let file: Tagged<PathBuf> = args.req(0)?;
let r = File::open(&file.item) let r = File::open(&file.item)
@ -141,10 +139,10 @@ fn from_json(args: EvaluatedCommandArgs) -> Result<polars::prelude::DataFrame, S
reader reader
.finish() .finish()
.map_err(|e| ShellError::labeled_error("Error with file", format!("{:?}", e), &file.tag)) .map_err(|e| parse_polars_error::<&str>(&e, &file.tag.span, None))
} }
fn from_csv(args: EvaluatedCommandArgs) -> Result<polars::prelude::DataFrame, ShellError> { fn from_csv(args: CommandArgs) -> Result<polars::prelude::DataFrame, ShellError> {
let file: Tagged<PathBuf> = args.req(0)?; let file: Tagged<PathBuf> = args.req(0)?;
let delimiter: Option<Tagged<String>> = args.get_flag("delimiter")?; let delimiter: Option<Tagged<String>> = args.get_flag("delimiter")?;
let no_header: bool = args.has_flag("no_header"); let no_header: bool = args.has_flag("no_header");
@ -152,9 +150,8 @@ fn from_csv(args: EvaluatedCommandArgs) -> Result<polars::prelude::DataFrame, Sh
let skip_rows: Option<Tagged<usize>> = args.get_flag("skip_rows")?; let skip_rows: Option<Tagged<usize>> = args.get_flag("skip_rows")?;
let columns: Option<Vec<Value>> = args.get_flag("columns")?; let columns: Option<Vec<Value>> = args.get_flag("columns")?;
let csv_reader = CsvReader::from_path(&file.item).map_err(|e| { let csv_reader = CsvReader::from_path(&file.item)
ShellError::labeled_error("Unable to parse file", format!("{}", e), &file.tag) .map_err(|e| parse_polars_error::<&str>(&e, &file.tag.span, None))?;
})?;
let csv_reader = match delimiter { let csv_reader = match delimiter {
None => csv_reader, None => csv_reader,
@ -166,7 +163,7 @@ fn from_csv(args: EvaluatedCommandArgs) -> Result<polars::prelude::DataFrame, Sh
&d.tag, &d.tag,
)); ));
} else { } else {
let delimiter = match d.item.chars().nth(0) { let delimiter = match d.item.chars().next() {
Some(d) => d as u8, Some(d) => d as u8,
None => unreachable!(), None => unreachable!(),
}; };
@ -212,10 +209,6 @@ fn from_csv(args: EvaluatedCommandArgs) -> Result<polars::prelude::DataFrame, Sh
match csv_reader.finish() { match csv_reader.finish() {
Ok(csv_reader) => Ok(csv_reader), Ok(csv_reader) => Ok(csv_reader),
Err(e) => Err(ShellError::labeled_error( Err(e) => Err(parse_polars_error::<&str>(&e, &file.tag.span, None)),
"Error while parsing dataframe",
format!("{}", e),
&file.tag,
)),
} }
} }

View File

@ -0,0 +1,105 @@
use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{dataframe::NuDataFrame, Signature, SyntaxShape, Value};
use super::utils::convert_columns;
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe melt"
}
fn usage(&self) -> &str {
"Unpivot a DataFrame from wide to long format"
}
fn signature(&self) -> Signature {
Signature::build("dataframe melt")
.required("id_columns", SyntaxShape::Table, "Id columns for melting")
.required(
"value_columns",
SyntaxShape::Table,
"columns used as value columns",
)
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "melt dataframe",
example: "[[a b]; [a 2] [b 4] [a 6]] | dataframe to-df | dataframe melt [a] [b]",
result: None,
}]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let id_col: Vec<Value> = args.req(0)?;
let val_col: Vec<Value> = args.req(1)?;
let (id_col_string, id_col_span) = convert_columns(&id_col, &tag)?;
let (val_col_string, val_col_span) = convert_columns(&val_col, &tag)?;
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
check_column_datatypes(df.as_ref(), &id_col_string, &id_col_span)?;
check_column_datatypes(df.as_ref(), &val_col_string, &val_col_span)?;
let res = df
.as_ref()
.melt(&id_col_string, &val_col_string)
.map_err(|e| parse_polars_error::<&str>(&e, &tag.span, None))?;
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
}
fn check_column_datatypes<T: AsRef<str>>(
df: &polars::prelude::DataFrame,
cols: &[T],
col_span: &Span,
) -> Result<(), ShellError> {
if cols.is_empty() {
return Err(ShellError::labeled_error(
"Merge error",
"empty column list",
col_span,
));
}
// Checking if they are same type
if cols.len() > 1 {
for w in cols.windows(2) {
let l_series = df
.column(w[0].as_ref())
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None))?;
let r_series = df
.column(w[1].as_ref())
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None))?;
if l_series.dtype() != r_series.dtype() {
return Err(ShellError::labeled_error_with_secondary(
"Merge error",
"found different column types in list",
col_span,
format!(
"datatypes {} and {} are incompatible",
l_series.dtype(),
r_series.dtype()
),
col_span,
));
}
}
}
Ok(())
}

View File

@ -1,26 +1,81 @@
pub mod aggregate; pub mod aggregate;
pub mod column;
pub mod command; pub mod command;
pub mod convert;
pub mod drop; pub mod drop;
pub mod drop_duplicates;
pub mod drop_nulls;
pub mod dtypes; pub mod dtypes;
pub mod dummies;
pub mod filter;
pub mod get;
pub mod groupby; pub mod groupby;
pub mod head;
pub mod join; pub mod join;
pub mod list; pub mod list;
pub mod load; pub mod load;
pub mod melt;
pub mod pivot;
pub mod sample; pub mod sample;
pub mod select; pub mod select;
pub mod show; pub mod show;
pub mod slice;
pub mod sort;
pub mod tail;
pub mod to_csv;
pub mod to_df;
pub mod to_parquet;
pub mod to_series;
pub(crate) mod utils; pub(crate) mod utils;
pub mod where_;
pub mod with_column;
pub use aggregate::DataFrame as DataFrameAggregate; pub use aggregate::DataFrame as DataFrameAggregate;
pub use column::DataFrame as DataFrameColumn;
pub use command::Command as DataFrame; pub use command::Command as DataFrame;
pub use convert::DataFrame as DataFrameConvert;
pub use drop::DataFrame as DataFrameDrop; pub use drop::DataFrame as DataFrameDrop;
pub use drop_duplicates::DataFrame as DataFrameDropDuplicates;
pub use drop_nulls::DataFrame as DataFrameDropNulls;
pub use dtypes::DataFrame as DataFrameDTypes; pub use dtypes::DataFrame as DataFrameDTypes;
pub use dummies::DataFrame as DataFrameDummies;
pub use filter::DataFrame as DataFrameFilter;
pub use get::DataFrame as DataFrameGet;
pub use groupby::DataFrame as DataFrameGroupBy; pub use groupby::DataFrame as DataFrameGroupBy;
pub use head::DataFrame as DataFrameHead;
pub use join::DataFrame as DataFrameJoin; pub use join::DataFrame as DataFrameJoin;
pub use list::DataFrame as DataFrameList; pub use list::DataFrame as DataFrameList;
pub use load::DataFrame as DataFrameLoad; pub use load::DataFrame as DataFrameLoad;
pub use melt::DataFrame as DataFrameMelt;
pub use pivot::DataFrame as DataFramePivot;
pub use sample::DataFrame as DataFrameSample; pub use sample::DataFrame as DataFrameSample;
pub use select::DataFrame as DataFrameSelect; pub use select::DataFrame as DataFrameSelect;
pub use show::DataFrame as DataFrameShow; pub use show::DataFrame as DataFrameShow;
pub use slice::DataFrame as DataFrameSlice;
pub use sort::DataFrame as DataFrameSort;
pub use tail::DataFrame as DataFrameTail;
pub use to_csv::DataFrame as DataFrameToCsv;
pub use to_df::DataFrame as DataFrameToDF;
pub use to_parquet::DataFrame as DataFrameToParquet;
pub use to_series::DataFrame as DataFrameToSeries;
pub use where_::DataFrame as DataFrameWhere;
pub use with_column::DataFrame as DataFrameWithColumn;
pub mod series;
pub use series::DataFrameAllFalse;
pub use series::DataFrameAllTrue;
pub use series::DataFrameArgMax;
pub use series::DataFrameArgMin;
pub use series::DataFrameArgSort;
pub use series::DataFrameArgTrue;
pub use series::DataFrameArgUnique;
pub use series::DataFrameIsDuplicated;
pub use series::DataFrameIsIn;
pub use series::DataFrameIsNotNull;
pub use series::DataFrameIsNull;
pub use series::DataFrameIsUnique;
pub use series::DataFrameNNull;
pub use series::DataFrameNUnique;
pub use series::DataFrameSeriesRename;
pub use series::DataFrameSet;
pub use series::DataFrameShift;
pub use series::DataFrameUnique;
pub use series::DataFrameValueCounts;

View File

@ -0,0 +1,169 @@
use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{
dataframe::{NuDataFrame, NuGroupBy},
Signature, SyntaxShape,
};
use nu_source::Tagged;
use polars::prelude::DataType;
enum Operation {
First,
Sum,
Min,
Max,
Mean,
Median,
}
impl Operation {
fn from_tagged(name: &Tagged<String>) -> Result<Operation, ShellError> {
match name.item.as_ref() {
"first" => Ok(Operation::First),
"sum" => Ok(Operation::Sum),
"min" => Ok(Operation::Min),
"max" => Ok(Operation::Max),
"mean" => Ok(Operation::Mean),
"median" => Ok(Operation::Median),
_ => Err(ShellError::labeled_error_with_secondary(
"Operation not fount",
"Operation does not exist for pivot",
&name.tag,
"Perhaps you want: first, sum, min, max, mean, median",
&name.tag,
)),
}
}
}
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe pivot"
}
fn usage(&self) -> &str {
"Performs a pivot operation on a groupby object"
}
fn signature(&self) -> Signature {
Signature::build("dataframe pivot")
.required(
"pivot column",
SyntaxShape::String,
"pivot column to perform pivot",
)
.required(
"value column",
SyntaxShape::String,
"value column to perform pivot",
)
.required("operation", SyntaxShape::String, "aggregate operation")
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Pivot a dataframe on b and aggregation on col c",
example:
"[[a b c]; [one x 1] [two y 2]] | dataframe to-df | dataframe group-by [a] | dataframe pivot b c sum",
result: None,
}]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
// Extracting the pivot col from arguments
let pivot_col: Tagged<String> = args.req(0)?;
// Extracting the value col from arguments
let value_col: Tagged<String> = args.req(1)?;
let operation: Tagged<String> = args.req(2)?;
let op = Operation::from_tagged(&operation)?;
// The operation is only done in one groupby. Only one input is
// expected from the InputStream
let nu_groupby = NuGroupBy::try_from_stream(&mut args.input, &tag.span)?;
let df_ref = nu_groupby.as_ref();
check_pivot_column(df_ref, &pivot_col)?;
check_value_column(df_ref, &value_col)?;
let mut groupby = nu_groupby.to_groupby()?;
let pivot = groupby.pivot(pivot_col.item.as_ref(), value_col.item.as_ref());
let res = match op {
Operation::Mean => pivot.mean(),
Operation::Sum => pivot.sum(),
Operation::Min => pivot.min(),
Operation::Max => pivot.max(),
Operation::First => pivot.first(),
Operation::Median => pivot.median(),
}
.map_err(|e| parse_polars_error::<&str>(&e, &tag.span, None))?;
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
}
fn check_pivot_column(
df: &polars::prelude::DataFrame,
col: &Tagged<String>,
) -> Result<(), ShellError> {
let series = df
.column(col.item.as_ref())
.map_err(|e| parse_polars_error::<&str>(&e, &col.tag.span, None))?;
match series.dtype() {
DataType::UInt8
| DataType::UInt16
| DataType::UInt32
| DataType::UInt64
| DataType::Int8
| DataType::Int16
| DataType::Int32
| DataType::Int64
| DataType::Utf8 => Ok(()),
_ => Err(ShellError::labeled_error(
"Pivot error",
format!("Unsupported datatype {}", series.dtype()),
col.tag.span,
)),
}
}
fn check_value_column(
df: &polars::prelude::DataFrame,
col: &Tagged<String>,
) -> Result<(), ShellError> {
let series = df
.column(col.item.as_ref())
.map_err(|e| parse_polars_error::<&str>(&e, &col.tag.span, None))?;
match series.dtype() {
DataType::UInt8
| DataType::UInt16
| DataType::UInt32
| DataType::UInt64
| DataType::Int8
| DataType::Int16
| DataType::Int32
| DataType::Int64
| DataType::Float32
| DataType::Float64 => Ok(()),
_ => Err(ShellError::labeled_error(
"Pivot error",
format!("Unsupported datatype {}", series.dtype()),
col.tag.span,
)),
}
}

View File

@ -1,10 +1,7 @@
use crate::prelude::*; use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{dataframe::NuDataFrame, Signature, SyntaxShape};
dataframe::{NuDataFrame, PolarsData},
Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::Tagged; use nu_source::Tagged;
@ -12,7 +9,7 @@ pub struct DataFrame;
impl WholeStreamCommand for DataFrame { impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str { fn name(&self) -> &str {
"pls sample" "dataframe sample"
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -20,7 +17,7 @@ impl WholeStreamCommand for DataFrame {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("pls load") Signature::build("dataframe load")
.named( .named(
"n_rows", "n_rows",
SyntaxShape::Number, SyntaxShape::Number,
@ -37,81 +34,57 @@ impl WholeStreamCommand for DataFrame {
} }
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
sample(args) command(args)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
description: "Sample rows from dataframe", description: "Sample rows from dataframe",
example: "echo [[a b]; [1 2] [3 4]] | pls load | pls sample -r 1", example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe sample -r 1",
result: None, result: None,
}, },
Example { Example {
description: "Shows sample row using fraction and replace", description: "Shows sample row using fraction and replace",
example: "echo [[a b]; [1 2] [3 4] [5 6]] | pls load | pls sample -f 0.5 -e", example:
"[[a b]; [1 2] [3 4] [5 6]] | dataframe to-df | dataframe sample -f 0.5 -e",
result: None, result: None,
}, },
] ]
} }
} }
fn sample(args: CommandArgs) -> Result<OutputStream, ShellError> { fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let mut args = args.evaluate_once()?;
let rows: Option<Tagged<usize>> = args.get_flag("n_rows")?; let rows: Option<Tagged<usize>> = args.get_flag("n_rows")?;
let fraction: Option<Tagged<f64>> = args.get_flag("fraction")?; let fraction: Option<Tagged<f64>> = args.get_flag("fraction")?;
let replace: bool = args.has_flag("replace"); let replace: bool = args.has_flag("replace");
match args.input.next() { let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
None => Err(ShellError::labeled_error(
"No input received", let res = match (rows, fraction) {
"missing dataframe input from stream", (Some(rows), None) => df
.as_ref()
.sample_n(rows.item, replace)
.map_err(|e| parse_polars_error::<&str>(&e, &rows.tag.span, None)),
(None, Some(frac)) => df
.as_ref()
.sample_frac(frac.item, replace)
.map_err(|e| parse_polars_error::<&str>(&e, &frac.tag.span, None)),
(Some(_), Some(_)) => Err(ShellError::labeled_error(
"Incompatible flags",
"Only one selection criterion allowed",
&tag, &tag,
)), )),
Some(value) => { (None, None) => Err(ShellError::labeled_error_with_secondary(
if let UntaggedValue::DataFrame(PolarsData::EagerDataFrame(NuDataFrame { "No selection",
dataframe: Some(ref df), "No selection criterion was found",
.. &tag,
})) = value.value "Perhaps you want to use the flag -n or -f",
{ &tag,
let res = match (rows, fraction) { )),
(Some(rows), None) => df.sample_n(rows.item, replace).map_err(|e| { }?;
ShellError::labeled_error("Polars error", format!("{}", e), &rows.tag)
}),
(None, Some(frac)) => df.sample_frac(frac.item, replace).map_err(|e| {
ShellError::labeled_error("Polars error", format!("{}", e), &frac.tag)
}),
(Some(_), Some(_)) => Err(ShellError::labeled_error(
"Incompatible flags",
"Only one selection criterion allowed",
&tag,
)),
(None, None) => Err(ShellError::labeled_error_with_secondary(
"No selection",
"No selection criterion was found",
&tag,
"Perhaps you want to use the flag -n or -f",
&tag,
)),
}?;
let value = Value { Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
value: UntaggedValue::DataFrame(PolarsData::EagerDataFrame(NuDataFrame::new(
res,
))),
tag: tag.clone(),
};
Ok(OutputStream::one(value))
} else {
Err(ShellError::labeled_error(
"No dataframe in stream",
"no dataframe found in input stream",
&tag,
))
}
}
}
} }

View File

@ -1,18 +1,15 @@
use crate::prelude::*; use crate::prelude::*;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{dataframe::NuDataFrame, Signature, SyntaxShape, Value};
dataframe::{NuDataFrame, PolarsData},
Signature, SyntaxShape, UntaggedValue, Value,
};
use super::utils::convert_columns; use super::utils::{convert_columns, parse_polars_error};
pub struct DataFrame; pub struct DataFrame;
impl WholeStreamCommand for DataFrame { impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str { fn name(&self) -> &str {
"pls select" "dataframe select"
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -20,7 +17,7 @@ impl WholeStreamCommand for DataFrame {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("pls select").required( Signature::build("dataframe select").required(
"columns", "columns",
SyntaxShape::Table, SyntaxShape::Table,
"selected column names", "selected column names",
@ -28,57 +25,31 @@ impl WholeStreamCommand for DataFrame {
} }
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
select(args) command(args)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "Create new dataframe with column a", description: "Create new dataframe with column a",
example: "echo [[a b]; [1 2] [3 4]] | pls convert | pls select [a]", example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe select [a]",
result: None, result: None,
}] }]
} }
} }
fn select(args: CommandArgs) -> Result<OutputStream, ShellError> { fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let mut args = args.evaluate_once()?;
let columns: Vec<Value> = args.req(0)?; let columns: Vec<Value> = args.req(0)?;
let (col_string, col_span) = convert_columns(&columns, &tag)?; let (col_string, col_span) = convert_columns(&columns, &tag)?;
match args.input.next() { let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
None => Err(ShellError::labeled_error(
"No input received",
"missing dataframe input from stream",
&tag,
)),
Some(value) => {
if let UntaggedValue::DataFrame(PolarsData::EagerDataFrame(NuDataFrame {
dataframe: Some(ref df),
..
})) = value.value
{
let res = df.select(&col_string).map_err(|e| {
ShellError::labeled_error("Drop error", format!("{}", e), &col_span)
})?;
let value = Value { let res = df
value: UntaggedValue::DataFrame(PolarsData::EagerDataFrame(NuDataFrame::new( .as_ref()
res, .select(&col_string)
))), .map_err(|e| parse_polars_error::<&str>(&e, &col_span, None))?;
tag: tag.clone(),
};
Ok(OutputStream::one(value)) Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
} else {
Err(ShellError::labeled_error(
"No dataframe in stream",
"no dataframe found in input stream",
&tag,
))
}
}
}
} }

View File

@ -0,0 +1,67 @@
use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{dataframe::NuSeries, Signature, TaggedDictBuilder, UntaggedValue, Value};
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe all-false"
}
fn usage(&self) -> &str {
"Returns true if all values are false"
}
fn signature(&self) -> Signature {
Signature::build("dataframe all-false")
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Returns true if all values are false",
example: "[$false $false $false] | dataframe to-series | dataframe all-false",
result: None,
},
Example {
description: "Checks the result from a comparison",
example: r#"let s = ([5 6 2 8] | dataframe to-series);
let res = ($s > 9);
$res | dataframe all-false"#,
result: None,
},
]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let series = NuSeries::try_from_stream(&mut args.input, &tag.span)?;
let bool = series.as_ref().bool().map_err(|e| {
parse_polars_error::<&str>(
&e,
&tag.span,
Some("all-false only works with series of type bool"),
)
})?;
let res = bool.all_false();
let value = Value {
value: UntaggedValue::Primitive(res.into()),
tag: tag.clone(),
};
let mut data = TaggedDictBuilder::new(tag);
data.insert_value("all_false", value);
Ok(OutputStream::one(data.into_value()))
}

View File

@ -0,0 +1,67 @@
use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{dataframe::NuSeries, Signature, TaggedDictBuilder, UntaggedValue, Value};
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe all-true"
}
fn usage(&self) -> &str {
"Returns true if all values are true"
}
fn signature(&self) -> Signature {
Signature::build("dataframe all-true")
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Returns true if all values are true",
example: "[$true $true $true] | dataframe to-series | dataframe all-true",
result: None,
},
Example {
description: "Checks the result from a comparison",
example: r#"let s = ([5 6 2 8] | dataframe to-series);
let res = ($s > 9);
$res | dataframe all-true"#,
result: None,
},
]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let series = NuSeries::try_from_stream(&mut args.input, &tag.span)?;
let bool = series.as_ref().bool().map_err(|e| {
parse_polars_error::<&str>(
&e,
&tag.span,
Some("all-true only works with series of type bool"),
)
})?;
let res = bool.all_true();
let value = Value {
value: UntaggedValue::Primitive(res.into()),
tag: tag.clone(),
};
let mut data = TaggedDictBuilder::new(tag);
data.insert_value("all_true", value);
Ok(OutputStream::one(data.into_value()))
}

View File

@ -0,0 +1,57 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{
dataframe::NuSeries, Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value,
};
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe arg-max"
}
fn usage(&self) -> &str {
"Return index for max value in series"
}
fn signature(&self) -> Signature {
Signature::build("dataframe arg-max")
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns index for max value",
example: "[1 3 2] | dataframe to-series | dataframe arg-max",
result: None,
}]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let series = NuSeries::try_from_stream(&mut args.input, &tag.span)?;
let res = series.as_ref().arg_max();
let value = match res {
Some(index) => UntaggedValue::Primitive(Primitive::Int(index as i64)),
None => UntaggedValue::Primitive(Primitive::Nothing),
};
let value = Value {
value,
tag: tag.clone(),
};
let mut data = TaggedDictBuilder::new(tag);
data.insert_value("arg-max", value);
Ok(OutputStream::one(data.into_value()))
}

View File

@ -0,0 +1,57 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{
dataframe::NuSeries, Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value,
};
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe arg-min"
}
fn usage(&self) -> &str {
"Return index for min value in series"
}
fn signature(&self) -> Signature {
Signature::build("dataframe arg-min")
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns index for min value",
example: "[1 3 2] | dataframe to-series | dataframe arg-min",
result: None,
}]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let series = NuSeries::try_from_stream(&mut args.input, &tag.span)?;
let res = series.as_ref().arg_min();
let value = match res {
Some(index) => UntaggedValue::Primitive(Primitive::Int(index as i64)),
None => UntaggedValue::Primitive(Primitive::Nothing),
};
let value = Value {
value,
tag: tag.clone(),
};
let mut data = TaggedDictBuilder::new(tag);
data.insert_value("arg-min", value);
Ok(OutputStream::one(data.into_value()))
}

View File

@ -0,0 +1,47 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{dataframe::NuSeries, Signature};
use polars::prelude::IntoSeries;
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe arg-sort"
}
fn usage(&self) -> &str {
"Returns indexes for a sorted series"
}
fn signature(&self) -> Signature {
Signature::build("dataframe arg-sort").switch("reverse", "reverse order", Some('r'))
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns indexes for a sorted series",
example: "[1 2 2 3 3] | dataframe to-series | dataframe arg-sort",
result: None,
}]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let reverse = args.has_flag("reverse");
let series = NuSeries::try_from_stream(&mut args.input, &tag.span)?;
let res = series.as_ref().argsort(reverse);
Ok(OutputStream::one(NuSeries::series_to_value(
res.into_series(),
tag,
)))
}

View File

@ -0,0 +1,52 @@
use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{dataframe::NuSeries, Signature};
use polars::prelude::IntoSeries;
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe arg-true"
}
fn usage(&self) -> &str {
"Returns indexes where values are true"
}
fn signature(&self) -> Signature {
Signature::build("dataframe arg-true")
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns indexes where values are true",
example: "[$false $true $false] | dataframe to-series | dataframe arg-true",
result: None,
}]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let series = NuSeries::try_from_stream(&mut args.input, &tag.span)?;
let bool = series.as_ref().bool().map_err(|e| {
parse_polars_error::<&str>(
&e,
&tag.span,
Some("arg-true only works with series of type bool"),
)
})?;
let mut res = bool.arg_true().into_series();
res.rename("int");
Ok(OutputStream::one(NuSeries::series_to_value(res, tag)))
}

View File

@ -0,0 +1,49 @@
use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{dataframe::NuSeries, Signature};
use polars::prelude::IntoSeries;
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe arg-unique"
}
fn usage(&self) -> &str {
"Returns indexes for unique values"
}
fn signature(&self) -> Signature {
Signature::build("dataframe arg-unique")
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns indexes for unique values",
example: "[1 2 2 3 3] | dataframe to-series | dataframe arg-unique",
result: None,
}]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let series = NuSeries::try_from_stream(&mut args.input, &tag.span)?;
let res = series
.as_ref()
.arg_unique()
.map_err(|e| parse_polars_error::<&str>(&e, &tag.span, None))?;
Ok(OutputStream::one(NuSeries::series_to_value(
res.into_series(),
tag,
)))
}

View File

@ -0,0 +1,49 @@
use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{dataframe::NuSeries, Signature};
use polars::prelude::IntoSeries;
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe is-duplicated"
}
fn usage(&self) -> &str {
"Creates mask indicating duplicated values"
}
fn signature(&self) -> Signature {
Signature::build("dataframe is-duplicated")
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Create mask indicating duplicated values",
example: "[5 6 6 6 8 8 8] | dataframe to-series | dataframe is-duplicated",
result: None,
}]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let series = NuSeries::try_from_stream(&mut args.input, &tag.span)?;
let res = series
.as_ref()
.is_duplicated()
.map_err(|e| parse_polars_error::<&str>(&e, &tag.span, None))?;
Ok(OutputStream::one(NuSeries::series_to_value(
res.into_series(),
tag,
)))
}

View File

@ -0,0 +1,63 @@
use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{
dataframe::{NuSeries, PolarsData},
Signature, SyntaxShape, UntaggedValue, Value,
};
use polars::prelude::IntoSeries;
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe is-in"
}
fn usage(&self) -> &str {
"Checks if elements from a series are contained in right series"
}
fn signature(&self) -> Signature {
Signature::build("dataframe is-in").required("other", SyntaxShape::Any, "right series")
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Checks if elements from a series are contained in right series",
example: r#"let other = ([1 3 6] | dataframe to-series);
[5 6 6 6 8 8 8] | dataframe to-series | dataframe is-in $other"#,
result: None,
}]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let value: Value = args.req(0)?;
let other = match value.value {
UntaggedValue::DataFrame(PolarsData::Series(series)) => Ok(series),
_ => Err(ShellError::labeled_error(
"Incorrect type",
"can only search in a series",
value.tag.span,
)),
}?;
let series = NuSeries::try_from_stream(&mut args.input, &tag.span)?;
let res = series
.as_ref()
.is_in(other.as_ref())
.map_err(|e| parse_polars_error::<&str>(&e, &tag.span, None))?;
Ok(OutputStream::one(NuSeries::series_to_value(
res.into_series(),
tag,
)))
}

View File

@ -0,0 +1,48 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{dataframe::NuSeries, Signature};
use polars::prelude::IntoSeries;
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe is-not-null"
}
fn usage(&self) -> &str {
"Creates mask where value is not null"
}
fn signature(&self) -> Signature {
Signature::build("dataframe is-not-null")
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Create mask where values are not null",
example: r#"let s = ([5 6 0 8] | dataframe to-series);
let res = ($s / $s);
$res | dataframe is-not-null"#,
result: None,
}]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let series = NuSeries::try_from_stream(&mut args.input, &tag.span)?;
let res = series.as_ref().is_not_null();
Ok(OutputStream::one(NuSeries::series_to_value(
res.into_series(),
tag,
)))
}

View File

@ -0,0 +1,48 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{dataframe::NuSeries, Signature};
use polars::prelude::IntoSeries;
pub struct DataFrame;
impl WholeStreamCommand for DataFrame {
fn name(&self) -> &str {
"dataframe is-null"
}
fn usage(&self) -> &str {
"Creates mask where value is null"
}
fn signature(&self) -> Signature {
Signature::build("dataframe is-null")
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
command(args)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Create mask where values are null",
example: r#"let s = ([5 6 0 8] | dataframe to-series);
let res = ($s / $s);
$res | dataframe is-null"#,
result: None,
}]
}
}
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let series = NuSeries::try_from_stream(&mut args.input, &tag.span)?;
let res = series.as_ref().is_null();
Ok(OutputStream::one(NuSeries::series_to_value(
res.into_series(),
tag,
)))
}

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