Compare commits

...

44 Commits
0.7.0 ... 0.8.0

Author SHA1 Message Date
41ebc6b42d Bump to 0.8.0 (#1166) 2020-01-07 20:08:31 +13:00
b574dc6365 Add the from-ods command (#1161)
* Put a sample_data.ods file for testing

This is a copy of the sample_data.xlsx file but in ods format

* Add the from-ods command

Most of the work was doing `rg xlsx` and then copy/paste with light editing

* Add tests for the from-ods command

* Fix failing test

The problem was improper filename sorting in the test `prepares_and_decorates_filesystem_source_files`
2020-01-07 19:35:00 +13:00
4af9e1de41 Resolves #750 (#1164)
Pick now produces an error when none of the columns are found
2020-01-07 17:06:48 +13:00
77d856fd53 Last unwraps (#1160)
* Work through most of the last unwraps

* Finish removing unwraps
2020-01-04 19:44:17 +13:00
6dceabf389 Isolate data processing helpers. (#1159)
Isolate data processing helpers. Remove unwraps and down to zero unwraps.
2020-01-03 23:00:39 -05:00
5919c6c433 Remove unwraps (#1153)
* Remove a batch of unwraps

* finish another batch
2020-01-04 10:11:21 +13:00
339a2de0eb More ununwraps (#1152)
* More ununwraps

* More ununwraps

* Update completer.rs

* Update completer.rs
2020-01-03 06:51:20 +13:00
3e3cb15f3d Yet more ununwraps (#1150) 2020-01-02 20:07:17 +13:00
5e31851070 A couple more (#1149) 2020-01-02 18:24:41 +13:00
0f626dd076 Another batch of un-unwrapping (#1148)
Another batch of un-unwrappings
2020-01-02 17:02:46 +13:00
aa577bf9bf Clean up some unwraps (#1147) 2020-01-02 09:45:32 +13:00
25298d35e4 Bump rustyline (#1146)
* Slightly improve new which command

* Bump rustyline
2020-01-02 06:54:25 +13:00
78016446dc Slightly improve new which command (#1145) 2020-01-01 20:47:25 +13:00
b304de8199 Rewrite which (#1144)
* Detect built-in commands passed as args to `which`

This expands the built-in `which` command to detect nushell commands
that may have the same name as a binary in the path.

* Allow which to interpret multiple arguments

Previously, it would discard any argument besides the first. This allows
`which` to process multiple arguments. It also makes the output a stream
of rows.

* Use map to build the output

* Add boolean column for builtins

* Use macros for entry creation shortcuts

* Process command args and use async_stream

In order to use `ichwh`, I'll need to use async_stream. But in order to
avoid lifetime errors with that, I have to process the command args
before using them. I'll admit I don't fully understand what is going on
with the `args.process(...)` function, but it works.

* Use `ichwh` for path searching

This commit transitions from `which` to `ichwh`. The path search is now
done asynchronously.

* Enable the `--all` flag on `which`

* Make `which` respect external commands

Escaped commands passed to wich (e.g., `which "^ls"`), are now searched
before builtins.

* Fix clippy warnings

This commit resolves two warnings from clippy, in light of #1142.

* Update Cargo.lock to get new `ichwh` version

`ichwh@0.2.1` has support for local paths.

* Add documentation for command
2020-01-01 19:45:27 +13:00
72838cc083 Move to using clippy (#1142)
* Clippy fixes

* Finish converting to use clippy

* fix warnings in new master

* fix windows

* fix windows

Co-authored-by: Artem Vorotnikov <artem@vorotnikov.me>
2019-12-31 20:36:08 +13:00
8093612cac Allow moving in text with Ctrl+ArrowLeft, Ctrl+ArrowRight (#1141)
* Allow moving in text with Ctrl+ArrowLeft, Ctrl+ArrowRight

* Document changes

* Format
2019-12-31 17:06:36 +13:00
f37f29b441 Add uniq command (#1132)
* start playing with ways to use the uniq command

* WIP

* Got uniq working, but still need to figure out args issue and add tests

* Add some tests for uniq

* fmt

* remove commented out code

* Add documentation and some additional tests showing uniq values and rows. Also removed args TODO

* add changes that didn't get committed

* whoops, I didn't save the docs correctly...

* fmt

* Add a test for uniq with nested json

* Add another test

* Fix unique-ness when json keys are out of order and make the test json more complicated
2019-12-31 17:05:02 +13:00
dba82ac530 handle single quoted external command args (#1139)
fixes #1138
2019-12-31 06:47:14 +13:00
0615adac94 Inc refactoring, Value helper test method extractions, and more integration helpers. (#1135)
* Manifests check. Ignore doctests for now.

* We continue with refactorings towards the separation of concerns between
crates. `nu_plugin_inc` and `nu_plugin_str` common test helpers usage
has been refactored into `nu-plugin` value test helpers.

Inc also uses the new API for integration tests.
2019-12-29 00:17:24 -05:00
21e508009f Refactor struct names for old commands (ls, cd, pwd) (#1133) 2019-12-29 10:33:31 +13:00
a9317d939f Update README.md 2019-12-28 15:27:51 +13:00
65d843c2a1 Merge pull request #1128 from andrasio/nu-plugin-extract
Extract nu-plugin crate.
2019-12-27 09:16:18 -05:00
f6c62bf121 Nu plugins now depend on nu-plugin crate. 2019-12-27 08:52:15 -05:00
b4bc5fe9af Merge pull request #1126 from jonathandturner/utf8_fix
UTF8 fix for twitter-reported issue
2019-12-27 19:48:42 +13:00
10368d7060 UTF8 fix for twitter-reported issue 2019-12-27 19:25:44 +13:00
68a314b5cb UTF8 fix for twitter-reported issue 2019-12-27 19:03:00 +13:00
3c7633ae9f Merge pull request #1125 from notryanb/update-readme
update readme to reflect >= 0.7.2 $nu variables
2019-12-27 15:42:25 +13:00
dba347ad00 update readme to show >= 0.7 nu path 2019-12-26 20:08:30 -05:00
bfba2c57f8 Merge pull request #1124 from quebin31/master
Fix positional macro on crate nu-macros
2019-12-27 07:16:47 +13:00
c69bf9f46f Merge branch 'master' of https://github.com/nushell/nushell 2019-12-26 12:32:28 -05:00
7ce1ddc6fd Fixed optional and required argument in signature.
This fixes issues like #1117
2019-12-26 12:29:41 -05:00
e7ce6f2fcd Merge pull request #1113 from jonathandturner/bump_0_7_2
Bump to 0.7.2
2019-12-24 14:51:58 +13:00
0c786bb890 Bump to 0.7.2 2019-12-24 14:51:10 +13:00
8d31c32bda Merge pull request #1112 from jonathandturner/assorted_fixes
Fix an assortment of issues
2019-12-24 14:45:15 +13:00
e7fb15be59 Fix an assortment of issues 2019-12-24 14:26:47 +13:00
be7550822c Merge pull request #1109 from nushell/ctrl_l_clear
Move to git rustyline to fix Ctrl-L
2019-12-24 05:48:42 +13:00
0ce216eec4 Move to git rustyline to fix Ctrl+L 2019-12-24 05:26:30 +13:00
1fe85cb91e Merge pull request #1108 from thegedge/faster-pipelines
Wait for process instead of polling its status.
2019-12-23 07:06:16 +13:00
8cadc5a4ac Wait for process instead of polling its status.
This provides a huge performance boost for pipelines that end in an
external command. Rough testing shows an improvement from roughly 400ms
to 30ms when `cat`-ing a large file.
2019-12-22 14:14:03 -03:30
f9da7f7d58 Merge pull request #1102 from jonathandturner/bump_nu
Bump nu version
2019-12-20 10:54:30 +13:00
367f11a62e Bump nu version 2019-12-20 09:03:54 +13:00
8a45ca9cc3 Merge pull request #1100 from nushell/fix-stable
Fix the stable plugins to correct list
2019-12-20 06:37:55 +13:00
e336930fd8 Update Cargo.toml 2019-12-20 06:18:06 +13:00
172ccc910e Fix the stable plugins to correct list 2019-12-20 06:01:42 +13:00
183 changed files with 5328 additions and 5464 deletions

View File

@ -45,9 +45,15 @@ steps:
- bash: RUSTFLAGS="-D warnings" cargo test --all --features=stable - bash: RUSTFLAGS="-D warnings" cargo test --all --features=stable
condition: eq(variables['style'], 'unflagged') condition: eq(variables['style'], 'unflagged')
displayName: Run tests displayName: Run tests
- bash: RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
condition: eq(variables['style'], 'unflagged')
displayName: Check clippy lints
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo test --all --features=stable - bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo test --all --features=stable
condition: eq(variables['style'], 'canary') condition: eq(variables['style'], 'canary')
displayName: Run tests displayName: Run tests
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
condition: eq(variables['style'], 'canary')
displayName: Check clippy lints
- bash: cargo fmt --all -- --check - bash: cargo fmt --all -- --check
condition: eq(variables['style'], 'fmt') condition: eq(variables['style'], 'fmt')
displayName: Lint displayName: Lint

534
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package] [package]
name = "nu" name = "nu"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
description = "A shell for the GitHub era" description = "A shell for the GitHub era"
license = "MIT" license = "MIT"
@ -30,6 +30,7 @@ members = [
"crates/nu_plugin_textview", "crates/nu_plugin_textview",
"crates/nu_plugin_tree", "crates/nu_plugin_tree",
"crates/nu-protocol", "crates/nu-protocol",
"crates/nu-plugin",
"crates/nu-parser", "crates/nu-parser",
"crates/nu-value-ext", "crates/nu-value-ext",
"crates/nu-build" "crates/nu-build"
@ -38,29 +39,30 @@ members = [
# 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-source = { version = "0.7.0", path = "./crates/nu-source" } nu-source = { version = "0.8.0", path = "./crates/nu-source" }
nu-protocol = { version = "0.7.0", path = "./crates/nu-protocol" } nu-plugin = { version = "0.8.0", path = "./crates/nu-plugin" }
nu-errors = { version = "0.7.0", path = "./crates/nu-errors" } nu-protocol = { version = "0.8.0", path = "./crates/nu-protocol" }
nu-parser = { version = "0.7.0", path = "./crates/nu-parser" } nu-errors = { version = "0.8.0", path = "./crates/nu-errors" }
nu-value-ext = { version = "0.7.0", path = "./crates/nu-value-ext" } nu-parser = { version = "0.8.0", path = "./crates/nu-parser" }
nu_plugin_average = {version = "0.7.0", path = "./crates/nu_plugin_average", optional=true} nu-value-ext = { version = "0.8.0", path = "./crates/nu-value-ext" }
nu_plugin_binaryview = {version = "0.7.0", path = "./crates/nu_plugin_binaryview", optional=true} nu_plugin_average = {version = "0.8.0", path = "./crates/nu_plugin_average", optional=true}
nu_plugin_fetch = {version = "0.7.0", path = "./crates/nu_plugin_fetch", optional=true} nu_plugin_binaryview = {version = "0.8.0", path = "./crates/nu_plugin_binaryview", optional=true}
nu_plugin_inc = {version = "0.7.0", path = "./crates/nu_plugin_inc", optional=true} nu_plugin_fetch = {version = "0.8.0", path = "./crates/nu_plugin_fetch", optional=true}
nu_plugin_match = {version = "0.7.0", path = "./crates/nu_plugin_match", optional=true} nu_plugin_inc = {version = "0.8.0", path = "./crates/nu_plugin_inc", optional=true}
nu_plugin_post = {version = "0.7.0", path = "./crates/nu_plugin_post", optional=true} nu_plugin_match = {version = "0.8.0", path = "./crates/nu_plugin_match", optional=true}
nu_plugin_ps = {version = "0.7.0", path = "./crates/nu_plugin_ps", optional=true} nu_plugin_post = {version = "0.8.0", path = "./crates/nu_plugin_post", optional=true}
nu_plugin_str = {version = "0.7.0", path = "./crates/nu_plugin_str", optional=true} nu_plugin_ps = {version = "0.8.0", path = "./crates/nu_plugin_ps", optional=true}
nu_plugin_sum = {version = "0.7.0", path = "./crates/nu_plugin_sum", optional=true} nu_plugin_str = {version = "0.8.0", path = "./crates/nu_plugin_str", optional=true}
nu_plugin_sys = {version = "0.7.0", path = "./crates/nu_plugin_sys", optional=true} nu_plugin_sum = {version = "0.8.0", path = "./crates/nu_plugin_sum", optional=true}
nu_plugin_textview = {version = "0.7.0", path = "./crates/nu_plugin_textview", optional=true} nu_plugin_sys = {version = "0.8.0", path = "./crates/nu_plugin_sys", optional=true}
nu_plugin_tree = {version = "0.7.0", path = "./crates/nu_plugin_tree", optional=true} nu_plugin_textview = {version = "0.8.0", path = "./crates/nu_plugin_textview", optional=true}
nu-macros = { version = "0.7.0", path = "./crates/nu-macros" } nu_plugin_tree = {version = "0.8.0", path = "./crates/nu_plugin_tree", optional=true}
nu-macros = { version = "0.8.0", path = "./crates/nu-macros" }
query_interface = "0.3.5" query_interface = "0.3.5"
typetag = "0.1.4" typetag = "0.1.4"
rustyline = "5.0.4" rustyline = "5.0.6"
chrono = { version = "0.4.10", features = ["serde"] } chrono = { version = "0.4.10", features = ["serde"] }
derive-new = "0.5.8" derive-new = "0.5.8"
prettytable-rs = "0.8.0" prettytable-rs = "0.8.0"
@ -105,7 +107,7 @@ subprocess = "0.1.18"
pretty-hex = "0.1.1" pretty-hex = "0.1.1"
hex = "0.4" hex = "0.4"
tempfile = "3.1.0" tempfile = "3.1.0"
which = "3.1" ichwh = "0.2"
textwrap = {version = "0.11.0", features = ["term_size"]} textwrap = {version = "0.11.0", features = ["term_size"]}
shellexpand = "1.0.0" shellexpand = "1.0.0"
pin-utils = "0.1.0-alpha.4" pin-utils = "0.1.0-alpha.4"
@ -121,6 +123,7 @@ umask = "0.1"
futures-util = "0.3.1" futures-util = "0.3.1"
termcolor = "1.0.5" termcolor = "1.0.5"
natural = "0.3.0" natural = "0.3.0"
parking_lot = "0.10.0"
clipboard = {version = "0.5", optional = true } clipboard = {version = "0.5", optional = true }
ptree = {version = "0.2" } ptree = {version = "0.2" }
@ -132,25 +135,28 @@ onig_sys = {version = "=69.1.0", optional = true }
crossterm = {version = "0.10.2", optional = true} crossterm = {version = "0.10.2", optional = true}
futures-timer = {version = "1.0.2", optional = true} futures-timer = {version = "1.0.2", optional = true}
url = {version = "2.1.0", optional = true} url = {version = "2.1.0", optional = true}
semver = {version = "0.9.0", optional = true}
[features] [features]
default = ["sys", "ps", "textview", "inc", "str"] default = ["sys", "ps", "textview", "inc", "str"]
stable = ["sys", "ps", "starship-prompt", "textview", "binaryview", "match", "tree", "average", "sum"] stable = ["sys", "ps", "textview", "inc", "str", "starship-prompt", "binaryview", "match", "tree", "average", "sum", "post", "fetch", "clipboard"]
# Default
sys = ["heim", "battery"] sys = ["heim", "battery"]
ps = ["heim", "futures-timer"] ps = ["heim", "futures-timer"]
textview = ["crossterm", "syntect", "onig_sys", "url"] textview = ["crossterm", "syntect", "onig_sys", "url"]
str = [] inc = ["nu_plugin_inc"]
str = ["nu_plugin_str"]
inc = ["semver"] # Stable
starship-prompt = ["starship"]
binaryview = ["nu_plugin_binaryview"]
match = ["nu_plugin_match"]
tree = ["nu_plugin_tree"]
average = ["nu_plugin_average"] average = ["nu_plugin_average"]
binaryview = ["nu_plugin_binaryview"]
fetch = ["nu_plugin_fetch"]
match = ["nu_plugin_match"]
post = ["nu_plugin_post"]
starship-prompt = ["starship"]
sum = ["nu_plugin_sum"] sum = ["nu_plugin_sum"]
trace = ["nu-parser/trace"] trace = ["nu-parser/trace"]
tree = ["nu_plugin_tree"]
[dependencies.rusqlite] [dependencies.rusqlite]
version = "0.20.0" version = "0.20.0"
@ -158,15 +164,16 @@ features = ["bundled", "blob"]
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.6.1" pretty_assertions = "0.6.1"
nu-test-support = { version = "0.7.0", path = "./crates/nu-test-support" } nu-test-support = { version = "0.8.0", path = "./crates/nu-test-support" }
[build-dependencies] [build-dependencies]
toml = "0.5.5" toml = "0.5.5"
serde = { version = "1.0.103", features = ["derive"] } serde = { version = "1.0.103", features = ["derive"] }
nu-build = { version = "0.7.0", path = "./crates/nu-build" } nu-build = { version = "0.8.0", path = "./crates/nu-build" }
[lib] [lib]
name = "nu" name = "nu"
doctest = false
path = "src/lib.rs" path = "src/lib.rs"
# Core plugins that ship with `cargo install nu` by default # Core plugins that ship with `cargo install nu` by default

View File

@ -52,10 +52,10 @@ To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs
cargo install nu cargo install nu
``` ```
You can also install Nu with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/en/installation.html#dependencies) for your platform): You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/en/installation.html#dependencies) for your platform), once you have checked out this repo with git:
``` ```
cargo install nu --features=stable cargo build --all --features=stable
``` ```
## Docker ## Docker
@ -202,7 +202,7 @@ To set one of these variables, you can use `config --set`. For example:
``` ```
> config --set [edit_mode "vi"] > config --set [edit_mode "vi"]
> config --set [path $nu:path] > config --set [path $nu.path]
``` ```
## Shells ## Shells

View File

@ -1,12 +1,13 @@
[package] [package]
name = "nu-build" name = "nu-build"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "Core build system for nushell" description = "Core build system for nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib]
doctest = false
[dependencies] [dependencies]
serde = { version = "1.0.103", features = ["derive"] } serde = { version = "1.0.103", features = ["derive"] }

View File

@ -13,10 +13,10 @@ lazy_static! {
// got from https://github.com/mitsuhiko/insta/blob/b113499249584cb650150d2d01ed96ee66db6b30/src/runtime.rs#L67-L88 // got from https://github.com/mitsuhiko/insta/blob/b113499249584cb650150d2d01ed96ee66db6b30/src/runtime.rs#L67-L88
fn get_cargo_workspace(manifest_dir: &str) -> Option<&Path> { fn get_cargo_workspace(manifest_dir: &str) -> Result<Option<&Path>, Box<dyn std::error::Error>> {
let mut workspaces = WORKSPACES.lock().unwrap(); let mut workspaces = WORKSPACES.lock()?;
if let Some(rv) = workspaces.get(manifest_dir) { if let Some(rv) = workspaces.get(manifest_dir) {
Some(rv) Ok(Some(rv))
} else { } else {
#[derive(Deserialize)] #[derive(Deserialize)]
struct Manifest { struct Manifest {
@ -26,12 +26,11 @@ fn get_cargo_workspace(manifest_dir: &str) -> Option<&Path> {
.arg("metadata") .arg("metadata")
.arg("--format-version=1") .arg("--format-version=1")
.current_dir(manifest_dir) .current_dir(manifest_dir)
.output() .output()?;
.unwrap(); let manifest: Manifest = serde_json::from_slice(&output.stdout)?;
let manifest: Manifest = serde_json::from_slice(&output.stdout).unwrap();
let path = Box::leak(Box::new(PathBuf::from(manifest.workspace_root))); let path = Box::leak(Box::new(PathBuf::from(manifest.workspace_root)));
workspaces.insert(manifest_dir.to_string(), path.as_path()); workspaces.insert(manifest_dir.to_string(), path.as_path());
workspaces.get(manifest_dir).map(|w| *w) Ok(workspaces.get(manifest_dir).cloned())
} }
} }
@ -43,11 +42,11 @@ struct Feature {
} }
pub fn build() -> Result<(), Box<dyn std::error::Error>> { pub fn build() -> Result<(), Box<dyn std::error::Error>> {
let input = env::var("CARGO_MANIFEST_DIR").unwrap(); let input = env::var("CARGO_MANIFEST_DIR")?;
let all_on = env::var("NUSHELL_ENABLE_ALL_FLAGS").is_ok(); let all_on = env::var("NUSHELL_ENABLE_ALL_FLAGS").is_ok();
let flags: HashSet<String> = env::var("NUSHELL_ENABLE_FLAGS") let flags: HashSet<String> = env::var("NUSHELL_ENABLE_FLAGS")
.map(|s| s.split(",").map(|s| s.to_string()).collect()) .map(|s| s.split(',').map(|s| s.to_string()).collect())
.unwrap_or_else(|_| HashSet::new()); .unwrap_or_else(|_| HashSet::new());
if all_on && !flags.is_empty() { if all_on && !flags.is_empty() {
@ -56,7 +55,7 @@ pub fn build() -> Result<(), Box<dyn std::error::Error>> {
); );
} }
let workspace = match get_cargo_workspace(&input) { let workspace = match get_cargo_workspace(&input)? {
// If the crate is being downloaded from crates.io, it won't have a workspace root, and that's ok // If the crate is being downloaded from crates.io, it won't have a workspace root, and that's ok
None => return Ok(()), None => return Ok(()),
Some(workspace) => workspace, Some(workspace) => workspace,
@ -72,7 +71,7 @@ pub fn build() -> Result<(), Box<dyn std::error::Error>> {
let toml: HashMap<String, Feature> = toml::from_str(&std::fs::read_to_string(path)?)?; let toml: HashMap<String, Feature> = toml::from_str(&std::fs::read_to_string(path)?)?;
for (key, value) in toml.iter() { for (key, value) in toml.iter() {
if value.enabled == true || all_on || flags.contains(key) { if value.enabled || all_on || flags.contains(key) {
println!("cargo:rustc-cfg={}", key); println!("cargo:rustc-cfg={}", key);
} }
} }

View File

@ -1,15 +1,16 @@
[package] [package]
name = "nu-errors" name = "nu-errors"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "Core error subsystem for Nushell" description = "Core error subsystem for Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib]
doctest = false
[dependencies] [dependencies]
nu-source = { path = "../nu-source", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.8.0" }
ansi_term = "0.12.1" ansi_term = "0.12.1"
bigdecimal = { version = "0.1.0", features = ["serde"] } bigdecimal = { version = "0.1.0", features = ["serde"] }
@ -28,4 +29,4 @@ toml = "0.5.5"
serde_json = "1.0.44" serde_json = "1.0.44"
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }

View File

@ -24,6 +24,10 @@ pub enum ParseErrorReason {
expected: &'static str, expected: &'static str,
actual: Spanned<String>, actual: Spanned<String>,
}, },
/// An unexpected internal error has occurred
InternalError { message: Spanned<String> },
/// The parser tried to parse an argument for a command, but it failed for /// The parser tried to parse an argument for a command, but it failed for
/// some reason /// some reason
ArgumentError { ArgumentError {
@ -69,6 +73,15 @@ impl ParseError {
} }
} }
/// Construct a [ParseErrorReason::InternalError](ParseErrorReason::InternalError)
pub fn internal_error(message: Spanned<impl Into<String>>) -> ParseError {
ParseError {
reason: ParseErrorReason::InternalError {
message: message.item.into().spanned(message.span),
},
}
}
/// Construct a [ParseErrorReason::ArgumentError](ParseErrorReason::ArgumentError) /// Construct a [ParseErrorReason::ArgumentError](ParseErrorReason::ArgumentError)
pub fn argument_error(command: Spanned<impl Into<String>>, kind: ArgumentError) -> ParseError { pub fn argument_error(command: Spanned<impl Into<String>>, kind: ArgumentError) -> ParseError {
ParseError { ParseError {
@ -85,12 +98,15 @@ impl From<ParseError> for ShellError {
fn from(error: ParseError) -> ShellError { fn from(error: ParseError) -> ShellError {
match error.reason { match error.reason {
ParseErrorReason::Eof { expected, span } => ShellError::unexpected_eof(expected, span), ParseErrorReason::Eof { expected, span } => ShellError::unexpected_eof(expected, span),
ParseErrorReason::ExtraTokens { actual } => { ParseErrorReason::ExtraTokens { actual } => ShellError::type_error("nothing", actual),
ShellError::type_error("nothing", actual.clone())
}
ParseErrorReason::Mismatch { actual, expected } => { ParseErrorReason::Mismatch { actual, expected } => {
ShellError::type_error(expected, actual.clone()) ShellError::type_error(expected, actual)
} }
ParseErrorReason::InternalError { message } => ShellError::labeled_error(
format!("Internal error: {}", message.item),
&message.item,
&message.span,
),
ParseErrorReason::ArgumentError { command, error } => { ParseErrorReason::ArgumentError { command, error } => {
ShellError::argument_error(command, error) ShellError::argument_error(command, error)
} }
@ -146,7 +162,7 @@ pub struct ShellError {
cause: Option<Box<ShellError>>, cause: Option<Box<ShellError>>,
} }
/// `PrettyDebug` is for internal debugging. For user-facing debugging, [to_diagnostic](ShellError::to_diagnostic) /// `PrettyDebug` is for internal debugging. For user-facing debugging, [into_diagnostic](ShellError::into_diagnostic)
/// is used, which prints an error, highlighting spans. /// is used, which prints an error, highlighting spans.
impl PrettyDebug for ShellError { impl PrettyDebug for ShellError {
fn pretty(&self) -> DebugDocBuilder { fn pretty(&self) -> DebugDocBuilder {
@ -169,7 +185,7 @@ impl PrettyDebug for ShellError {
+ b::space() + b::space()
+ b::description("actual:") + b::description("actual:")
+ b::space() + b::space()
+ b::option(actual.item.as_ref().map(|actual| b::description(actual))), + b::option(actual.item.as_ref().map(b::description)),
")", ")",
) )
} }
@ -388,13 +404,13 @@ impl ShellError {
// TODO: Get span of EOF // TODO: Get span of EOF
let diagnostic = Diagnostic::new( let diagnostic = Diagnostic::new(
Severity::Error, Severity::Error,
format!("Parse Error: Unexpected end of line"), "Parse Error: Unexpected end of line".to_string(),
); );
ShellError::diagnostic(diagnostic) ShellError::diagnostic(diagnostic)
} }
nom::Err::Failure(span) | nom::Err::Error(span) => { nom::Err::Failure(span) | nom::Err::Error(span) => {
let diagnostic = Diagnostic::new(Severity::Error, format!("Parse Error")) let diagnostic = Diagnostic::new(Severity::Error, "Parse Error".to_string())
.with_label(Label::new_primary(Span::from(span.0))); .with_label(Label::new_primary(Span::from(span.0)));
ShellError::diagnostic(diagnostic) ShellError::diagnostic(diagnostic)
@ -406,7 +422,7 @@ impl ShellError {
ProximateShellError::Diagnostic(ShellDiagnostic { diagnostic }).start() ProximateShellError::Diagnostic(ShellDiagnostic { diagnostic }).start()
} }
pub fn to_diagnostic(self) -> Diagnostic<Span> { pub fn into_diagnostic(self) -> Diagnostic<Span> {
match self.error { match self.error {
ProximateShellError::MissingValue { span, reason } => { ProximateShellError::MissingValue { span, reason } => {
let mut d = Diagnostic::new( let mut d = Diagnostic::new(
@ -426,7 +442,7 @@ impl ShellError {
} => match error { } => match error {
ArgumentError::InvalidExternalWord => Diagnostic::new( ArgumentError::InvalidExternalWord => Diagnostic::new(
Severity::Error, Severity::Error,
format!("Invalid bare word for Nu command (did you intend to invoke an external command?)")) "Invalid bare word for Nu command (did you intend to invoke an external command?)".to_string())
.with_label(Label::new_primary(command.span)), .with_label(Label::new_primary(command.span)),
ArgumentError::MissingMandatoryFlag(name) => Diagnostic::new( ArgumentError::MissingMandatoryFlag(name) => Diagnostic::new(
Severity::Error, Severity::Error,
@ -483,7 +499,7 @@ impl ShellError {
ProximateShellError::UnexpectedEof { ProximateShellError::UnexpectedEof {
expected, span expected, span
} => Diagnostic::new(Severity::Error, format!("Unexpected end of input")) } => Diagnostic::new(Severity::Error, "Unexpected end of input".to_string())
.with_label(Label::new_primary(span).with_message(format!("Expected {}", expected))), .with_label(Label::new_primary(span).with_message(format!("Expected {}", expected))),
ProximateShellError::RangeError { ProximateShellError::RangeError {

View File

@ -1,12 +1,13 @@
[package] [package]
name = "nu-macros" name = "nu-macros"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "Core macros for building Nushell" description = "Core macros for building Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib]
doctest = false
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.8.0" }

View File

@ -17,9 +17,9 @@ macro_rules! signature {
#[macro_export] #[macro_export]
macro_rules! positional { macro_rules! positional {
($ident:tt, $name:tt (optional $shape:tt) - $desc:tt) => { ($ident:tt, $name:tt (optional $shape:tt) - $desc:tt) => {
let $ident = $ident.required(stringify!($name), SyntaxShape::$shape, $desc);
};
($ident:tt, $name:tt ($shape:tt)- $desc:tt) => {
let $ident = $ident.optional(stringify!($name), SyntaxShape::$shape, $desc); let $ident = $ident.optional(stringify!($name), SyntaxShape::$shape, $desc);
}; };
($ident:tt, $name:tt ($shape:tt)- $desc:tt) => {
let $ident = $ident.required(stringify!($name), SyntaxShape::$shape, $desc);
};
} }

View File

@ -1,17 +1,18 @@
[package] [package]
name = "nu-parser" name = "nu-parser"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "Core parser used in Nushell" description = "Core parser used in Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib]
doctest = false
[dependencies] [dependencies]
nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.8.0" }
nu-source = { path = "../nu-source", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.8.0" }
nu-protocol = { path = "../nu-protocol", version = "0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.8.0" }
pretty_env_logger = "0.3.1" pretty_env_logger = "0.3.1"
pretty = "0.5.2" pretty = "0.5.2"
@ -40,7 +41,7 @@ enumflags2 = "0.6.2"
pretty_assertions = "0.6.1" pretty_assertions = "0.6.1"
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }
[features] [features]
stable = [] stable = []

View File

@ -50,7 +50,7 @@ impl PrettyDebug for ExternalCommand {
+ b::preceded( + b::preceded(
b::space(), b::space(),
b::intersperse( b::intersperse(
self.args.iter().map(|a| b::primitive(format!("{}", a.arg))), self.args.iter().map(|a| b::primitive(a.arg.to_string())),
b::space(), b::space(),
), ),
), ),

View File

@ -103,11 +103,11 @@ impl TestRegistry {
} }
impl SignatureRegistry for TestRegistry { impl SignatureRegistry for TestRegistry {
fn has(&self, name: &str) -> bool { fn has(&self, name: &str) -> Result<bool, ShellError> {
self.signatures.contains_key(name) Ok(self.signatures.contains_key(name))
} }
fn get(&self, name: &str) -> Option<Signature> { fn get(&self, name: &str) -> Result<Option<Signature>, ShellError> {
self.signatures.get(name).map(|sig| sig.clone()) Ok(self.signatures.get(name).cloned())
} }
} }
@ -159,7 +159,7 @@ fn inner_string_span(span: Span) -> Span {
} }
pub fn print_err(err: ShellError, source: &Text) { pub fn print_err(err: ShellError, source: &Text) {
let diag = err.to_diagnostic(); let diag = err.into_diagnostic();
let writer = termcolor::StandardStream::stderr(termcolor::ColorChoice::Auto); let writer = termcolor::StandardStream::stderr(termcolor::ColorChoice::Auto);
let mut source = source.to_string(); let mut source = source.to_string();

View File

@ -295,7 +295,7 @@ impl ColorSyntax for ExternalExpressionShape {
}; };
token_nodes.mutate_shapes(|shapes| atom.color_tokens(shapes)); token_nodes.mutate_shapes(|shapes| atom.color_tokens(shapes));
return ExternalExpressionResult::Processed; ExternalExpressionResult::Processed
} }
} }

View File

@ -24,16 +24,14 @@ impl PrettyDebugWithSource for NamedValue {
} }
} }
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct NamedArguments { pub struct NamedArguments {
pub named: IndexMap<String, NamedValue>, pub named: IndexMap<String, NamedValue>,
} }
impl NamedArguments { impl NamedArguments {
pub fn new() -> NamedArguments { pub fn new() -> NamedArguments {
NamedArguments { Default::default()
named: IndexMap::new(),
}
} }
pub fn iter(&self) -> impl Iterator<Item = (&String, &NamedValue)> { pub fn iter(&self) -> impl Iterator<Item = (&String, &NamedValue)> {
@ -47,7 +45,7 @@ impl NamedArguments {
trace!("Inserting switch -- {} = {:?}", name, switch); trace!("Inserting switch -- {} = {:?}", name, switch);
match switch { match switch {
None => self.named.insert(name.into(), NamedValue::AbsentSwitch), None => self.named.insert(name, NamedValue::AbsentSwitch),
Some(flag) => self.named.insert( Some(flag) => self.named.insert(
name, name,
NamedValue::PresentSwitch(Tag { NamedValue::PresentSwitch(Tag {

View File

@ -111,16 +111,13 @@ impl ExpandSyntax for KeywordShape {
) -> Result<Self::Output, ParseError> { ) -> Result<Self::Output, ParseError> {
let atom = expand_atom(token_nodes, "keyword", context, ExpansionRule::new())?; let atom = expand_atom(token_nodes, "keyword", context, ExpansionRule::new())?;
match &atom.unspanned { if let UnspannedAtomicToken::Word { text } = &atom.unspanned {
UnspannedAtomicToken::Word { text } => {
let word = text.slice(context.source()); let word = text.slice(context.source());
if word == self.keyword { if word == self.keyword {
return Ok(atom.span); return Ok(atom.span);
} }
} }
_ => {}
}
Err(ParseError::mismatch(self.keyword, atom.spanned_type_name())) Err(ParseError::mismatch(self.keyword, atom.spanned_type_name()))
} }
@ -338,8 +335,7 @@ impl ExpandSyntax for IdentifierShape {
) -> Result<Self::Output, ParseError> { ) -> Result<Self::Output, ParseError> {
let atom = expand_atom(token_nodes, "identifier", context, ExpansionRule::new())?; let atom = expand_atom(token_nodes, "identifier", context, ExpansionRule::new())?;
match atom.unspanned { if let UnspannedAtomicToken::Word { text } = atom.unspanned {
UnspannedAtomicToken::Word { text } => {
let body = text.slice(context.source()); let body = text.slice(context.source());
if is_id(body) { if is_id(body) {
return Ok(Identifier { return Ok(Identifier {
@ -348,8 +344,6 @@ impl ExpandSyntax for IdentifierShape {
}); });
} }
} }
_ => {}
}
Err(ParseError::mismatch("identifier", atom.spanned_type_name())) Err(ParseError::mismatch("identifier", atom.spanned_type_name()))
} }
@ -359,7 +353,7 @@ fn is_id(input: &str) -> bool {
let source = nu_source::nom_input(input); let source = nu_source::nom_input(input);
match crate::parse::parser::ident(source) { match crate::parse::parser::ident(source) {
Err(_) => false, Err(_) => false,
Ok((input, _)) => input.fragment.len() == 0, Ok((input, _)) => input.fragment.is_empty(),
} }
} }

View File

@ -127,8 +127,8 @@ impl ExpandExpression for SyntaxShape {
} }
pub trait SignatureRegistry { pub trait SignatureRegistry {
fn has(&self, name: &str) -> bool; fn has(&self, name: &str) -> Result<bool, ShellError>;
fn get(&self, name: &str) -> Option<Signature>; fn get(&self, name: &str) -> Result<Option<Signature>, ShellError>;
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -673,10 +673,26 @@ impl FallibleColorSyntax for CommandHeadShape {
UnspannedAtomicToken::Word { text } => { UnspannedAtomicToken::Word { text } => {
let name = text.slice(context.source); let name = text.slice(context.source);
if context.registry.has(name) { if context.registry.has(name)? {
// If the registry has the command, color it as an internal command // If the registry has the command, color it as an internal command
token_nodes.color_shape(FlatShape::InternalCommand.spanned(text)); token_nodes.color_shape(FlatShape::InternalCommand.spanned(text));
let signature = context.registry.get(name).unwrap(); let signature = context
.registry
.get(name)
.map_err(|_| {
ShellError::labeled_error(
"Internal error: could not load signature from registry",
"could not load from registry",
text,
)
})?
.ok_or_else(|| {
ShellError::labeled_error(
"Internal error: could not load signature from registry",
"could not load from registry",
text,
)
})?;
Ok(CommandHeadKind::Internal(signature)) Ok(CommandHeadKind::Internal(signature))
} else { } else {
// Otherwise, color it as an external command // Otherwise, color it as an external command
@ -715,8 +731,14 @@ impl ExpandSyntax for CommandHeadShape {
}, },
UnspannedToken::Bare => { UnspannedToken::Bare => {
let name = token_span.slice(context.source); let name = token_span.slice(context.source);
if context.registry.has(name) { if context.registry.has(name)? {
let signature = context.registry.get(name).unwrap(); let signature = context
.registry
.get(name)
.map_err(|_| ParseError::internal_error(name.spanned(token_span)))?
.ok_or_else(|| {
ParseError::internal_error(name.spanned(token_span))
})?;
CommandSignature::Internal(signature.spanned(token_span)) CommandSignature::Internal(signature.spanned(token_span))
} else { } else {
CommandSignature::External(token_span) CommandSignature::External(token_span)
@ -732,9 +754,9 @@ impl ExpandSyntax for CommandHeadShape {
}); });
match node { match node {
Ok(expr) => return Ok(expr), Ok(expr) => Ok(expr),
Err(_) => match expand_expr(&AnyExpressionShape, token_nodes, context) { Err(_) => match expand_expr(&AnyExpressionShape, token_nodes, context) {
Ok(expr) => return Ok(CommandSignature::Expression(expr)), Ok(expr) => Ok(CommandSignature::Expression(expr)),
Err(_) => Err(token_nodes.peek_non_ws().type_error("command head3")), Err(_) => Err(token_nodes.peek_non_ws().type_error("command head3")),
}, },
} }
@ -834,7 +856,7 @@ impl FallibleColorSyntax for InternalCommandHeadShape {
let node = peeked_head.commit(); let node = peeked_head.commit();
let _expr = match node { match node {
TokenNode::Token(Token { TokenNode::Token(Token {
unspanned: UnspannedToken::Bare, unspanned: UnspannedToken::Bare,
span, span,
@ -904,8 +926,8 @@ impl<'token> SingleError<'token> {
} }
} }
fn parse_single_node<'a, 'b, T>( fn parse_single_node<T>(
token_nodes: &'b mut TokensIterator<'a>, token_nodes: &mut TokensIterator<'_>,
expected: &'static str, expected: &'static str,
callback: impl FnOnce(UnspannedToken, Span, SingleError) -> Result<T, ParseError>, callback: impl FnOnce(UnspannedToken, Span, SingleError) -> Result<T, ParseError>,
) -> Result<T, ParseError> { ) -> Result<T, ParseError> {
@ -926,8 +948,8 @@ fn parse_single_node<'a, 'b, T>(
}) })
} }
fn parse_single_node_skipping_ws<'a, 'b, T>( fn parse_single_node_skipping_ws<T>(
token_nodes: &'b mut TokensIterator<'a>, token_nodes: &mut TokensIterator<'_>,
expected: &'static str, expected: &'static str,
callback: impl FnOnce(UnspannedToken, Span, SingleError) -> Result<T, ShellError>, callback: impl FnOnce(UnspannedToken, Span, SingleError) -> Result<T, ShellError>,
) -> Result<T, ShellError> { ) -> Result<T, ShellError> {
@ -982,7 +1004,7 @@ impl FallibleColorSyntax for WhitespaceShape {
let node = peeked.commit(); let node = peeked.commit();
let _ = match node { match node {
TokenNode::Whitespace(span) => { TokenNode::Whitespace(span) => {
token_nodes.color_shape(FlatShape::Whitespace.spanned(*span)) token_nodes.color_shape(FlatShape::Whitespace.spanned(*span))
} }

View File

@ -39,9 +39,7 @@ impl FallibleColorSyntax for AnyBlockShape {
// is it just a block? // is it just a block?
let block = block.node.as_block(); let block = block.node.as_block();
match block { if let Some((children, spans)) = block {
// If so, color it as a block
Some((children, spans)) => {
token_nodes.child(children, context.source.clone(), |token_nodes| { token_nodes.child(children, context.source.clone(), |token_nodes| {
color_syntax_with( color_syntax_with(
&DelimitedShape, &DelimitedShape,
@ -53,8 +51,6 @@ impl FallibleColorSyntax for AnyBlockShape {
return Ok(()); return Ok(());
} }
_ => {}
}
// Otherwise, look for a shorthand block. If none found, fail // Otherwise, look for a shorthand block. If none found, fail
color_fallible_syntax(&ShorthandBlock, token_nodes, context) color_fallible_syntax(&ShorthandBlock, token_nodes, context)
@ -76,8 +72,7 @@ impl ExpandExpression for AnyBlockShape {
// is it just a block? // is it just a block?
let block = block.node.as_block(); let block = block.node.as_block();
match block { if let Some((block, _tags)) = block {
Some((block, _tags)) => {
let mut iterator = let mut iterator =
TokensIterator::new(&block.item, block.span, context.source.clone(), false); TokensIterator::new(&block.item, block.span, context.source.clone(), false);
@ -85,8 +80,6 @@ impl ExpandExpression for AnyBlockShape {
return Ok(hir::RawExpression::Block(exprs.item).into_expr(block.span)); return Ok(hir::RawExpression::Block(exprs.item).into_expr(block.span));
} }
_ => {}
}
expand_syntax(&ShorthandBlock, token_nodes, context) expand_syntax(&ShorthandBlock, token_nodes, context)
} }
@ -169,30 +162,20 @@ impl FallibleColorSyntax for ShorthandPath {
token_nodes.atomic(|token_nodes| { token_nodes.atomic(|token_nodes| {
let variable = color_fallible_syntax(&VariablePathShape, token_nodes, context); let variable = color_fallible_syntax(&VariablePathShape, token_nodes, context);
match variable { if variable.is_ok() {
Ok(_) => {
// if it's a variable path, that's the head part // if it's a variable path, that's the head part
return Ok(()); return Ok(());
} }
Err(_) => {
// otherwise, we'll try to find a member path // otherwise, we'll try to find a member path
}
}
// look for a member (`<member>` -> `$it.<member>`) // look for a member (`<member>` -> `$it.<member>`)
color_fallible_syntax(&MemberShape, token_nodes, context)?; color_fallible_syntax(&MemberShape, token_nodes, context)?;
// Now that we've synthesized the head, of the path, proceed to expand the tail of the path // Now that we've synthesized the head, of the path, proceed to expand the tail of the path
// like any other path. // like any other path.
let tail = color_fallible_syntax(&PathTailShape, token_nodes, context);
match tail {
Ok(_) => {}
Err(_) => {
// It's ok if there's no path tail; a single member is sufficient // It's ok if there's no path tail; a single member is sufficient
} let _ = color_fallible_syntax(&PathTailShape, token_nodes, context);
}
Ok(()) Ok(())
}) })
@ -212,9 +195,8 @@ impl ExpandExpression for ShorthandPath {
// if it's a variable path, that's the head part // if it's a variable path, that's the head part
let path = expand_expr(&VariablePathShape, token_nodes, context); let path = expand_expr(&VariablePathShape, token_nodes, context);
match path { if let Ok(path) = path {
Ok(path) => return Ok(path), return Ok(path);
Err(_) => {}
} }
// Synthesize the head of the shorthand path (`<member>` -> `$it.<member>`) // Synthesize the head of the shorthand path (`<member>` -> `$it.<member>`)
@ -225,7 +207,7 @@ impl ExpandExpression for ShorthandPath {
let tail = expand_syntax(&PathTailShape, token_nodes, context); let tail = expand_syntax(&PathTailShape, token_nodes, context);
match tail { match tail {
Err(_) => return Ok(head), Err(_) => Ok(head),
Ok(PathTailSyntax { tail, .. }) => { Ok(PathTailSyntax { tail, .. }) => {
// For each member that `PathTailShape` expanded, join it onto the existing expression // For each member that `PathTailShape` expanded, join it onto the existing expression
// to form a new path // to form a new path

View File

@ -111,14 +111,10 @@ pub(crate) fn continue_coloring_expression(
// Check to see whether there's any continuation after the head expression // Check to see whether there's any continuation after the head expression
let result = color_fallible_syntax(&ExpressionContinuationShape, token_nodes, context); let result = color_fallible_syntax(&ExpressionContinuationShape, token_nodes, context);
match result { if result.is_err() {
Err(_) => {
// We already saw one continuation, so just return // We already saw one continuation, so just return
return Ok(()); return Ok(());
} }
Ok(_) => {}
}
} }
} }
@ -138,19 +134,17 @@ impl ExpandExpression for AnyExpressionStartShape {
let atom = expand_atom(token_nodes, "expression", context, ExpansionRule::new())?; let atom = expand_atom(token_nodes, "expression", context, ExpansionRule::new())?;
match atom.unspanned { match atom.unspanned {
UnspannedAtomicToken::Size { number, unit } => { UnspannedAtomicToken::Size { number, unit } => Ok(hir::Expression::size(
return Ok(hir::Expression::size(
number.to_number(context.source), number.to_number(context.source),
unit.item, unit.item,
Tag { Tag {
span: atom.span, span: atom.span,
anchor: None, anchor: None,
}, },
)) )),
}
UnspannedAtomicToken::SquareDelimited { nodes, .. } => { UnspannedAtomicToken::SquareDelimited { nodes, .. } => {
expand_delimited_square(&nodes, atom.span.into(), context) expand_delimited_square(&nodes, atom.span, context)
} }
UnspannedAtomicToken::Word { .. } => { UnspannedAtomicToken::Word { .. } => {
@ -158,11 +152,9 @@ impl ExpandExpression for AnyExpressionStartShape {
Ok(hir::Expression::bare(atom.span.until_option(end))) Ok(hir::Expression::bare(atom.span.until_option(end)))
} }
other => { other => other
return other
.into_atomic_token(atom.span) .into_atomic_token(atom.span)
.into_hir(context, "expression") .to_hir(context, "expression"),
}
} }
} }
} }
@ -208,7 +200,7 @@ impl FallibleColorSyntax for AnyExpressionStartShape {
UnspannedAtomicToken::Size { number, unit } => token_nodes.color_shape( UnspannedAtomicToken::Size { number, unit } => token_nodes.color_shape(
FlatShape::Size { FlatShape::Size {
number: number.span(), number: number.span(),
unit: unit.span.into(), unit: unit.span,
} }
.spanned(atom.span), .spanned(atom.span),
), ),
@ -218,7 +210,7 @@ impl FallibleColorSyntax for AnyExpressionStartShape {
(&nodes[..]).spanned(atom.span), (&nodes[..]).spanned(atom.span),
context.source.clone(), context.source.clone(),
|tokens| { |tokens| {
color_delimited_square(spans, tokens, atom.span.into(), context); color_delimited_square(spans, tokens, atom.span, context);
}, },
); );
} }
@ -257,13 +249,13 @@ impl FallibleColorSyntax for BareTailShape {
let word = let word =
color_fallible_syntax_with(&BareShape, &FlatShape::Word, token_nodes, context); color_fallible_syntax_with(&BareShape, &FlatShape::Word, token_nodes, context);
match word { if word.is_ok() {
// if a word was found, continue // if a word was found, continue
Ok(_) => continue, continue;
// if a word wasn't found, try to find a dot
Err(_) => {}
} }
// if a word wasn't found, try to find a dot
// try to find a dot // try to find a dot
let dot = color_fallible_syntax_with( let dot = color_fallible_syntax_with(
&ColorableDotShape, &ColorableDotShape,

View File

@ -148,7 +148,7 @@ impl<'tokens> Deref for AtomicToken<'tokens> {
} }
impl<'tokens> AtomicToken<'tokens> { impl<'tokens> AtomicToken<'tokens> {
pub fn into_hir( pub fn to_hir(
&self, &self,
context: &ExpandContext, context: &ExpandContext,
expected: &'static str, expected: &'static str,
@ -198,59 +198,49 @@ impl<'tokens> AtomicToken<'tokens> {
pub(crate) fn color_tokens(&self, shapes: &mut Vec<Spanned<FlatShape>>) { pub(crate) fn color_tokens(&self, shapes: &mut Vec<Spanned<FlatShape>>) {
match &self.unspanned { match &self.unspanned {
UnspannedAtomicToken::Eof { .. } => {} UnspannedAtomicToken::Eof { .. } => {}
UnspannedAtomicToken::Error { .. } => { UnspannedAtomicToken::Error { .. } => shapes.push(FlatShape::Error.spanned(self.span)),
return shapes.push(FlatShape::Error.spanned(self.span))
}
UnspannedAtomicToken::CompareOperator { .. } => { UnspannedAtomicToken::CompareOperator { .. } => {
return shapes.push(FlatShape::CompareOperator.spanned(self.span)); shapes.push(FlatShape::CompareOperator.spanned(self.span))
} }
UnspannedAtomicToken::ShorthandFlag { .. } => { UnspannedAtomicToken::ShorthandFlag { .. } => {
return shapes.push(FlatShape::ShorthandFlag.spanned(self.span)); shapes.push(FlatShape::ShorthandFlag.spanned(self.span))
} }
UnspannedAtomicToken::Whitespace { .. } => { UnspannedAtomicToken::Whitespace { .. } => {
return shapes.push(FlatShape::Whitespace.spanned(self.span)); shapes.push(FlatShape::Whitespace.spanned(self.span))
} }
UnspannedAtomicToken::Number { UnspannedAtomicToken::Number {
number: RawNumber::Decimal(_), number: RawNumber::Decimal(_),
} => { } => shapes.push(FlatShape::Decimal.spanned(self.span)),
return shapes.push(FlatShape::Decimal.spanned(self.span));
}
UnspannedAtomicToken::Number { UnspannedAtomicToken::Number {
number: RawNumber::Int(_), number: RawNumber::Int(_),
} => { } => shapes.push(FlatShape::Int.spanned(self.span)),
return shapes.push(FlatShape::Int.spanned(self.span)); UnspannedAtomicToken::Size { number, unit } => shapes.push(
}
UnspannedAtomicToken::Size { number, unit } => {
return shapes.push(
FlatShape::Size { FlatShape::Size {
number: number.span(), number: number.span(),
unit: unit.span, unit: unit.span,
} }
.spanned(self.span), .spanned(self.span),
); ),
}
UnspannedAtomicToken::String { .. } => { UnspannedAtomicToken::String { .. } => {
return shapes.push(FlatShape::String.spanned(self.span)) shapes.push(FlatShape::String.spanned(self.span))
} }
UnspannedAtomicToken::ItVariable { .. } => { UnspannedAtomicToken::ItVariable { .. } => {
return shapes.push(FlatShape::ItVariable.spanned(self.span)) shapes.push(FlatShape::ItVariable.spanned(self.span))
} }
UnspannedAtomicToken::Variable { .. } => { UnspannedAtomicToken::Variable { .. } => {
return shapes.push(FlatShape::Variable.spanned(self.span)) shapes.push(FlatShape::Variable.spanned(self.span))
} }
UnspannedAtomicToken::ExternalCommand { .. } => { UnspannedAtomicToken::ExternalCommand { .. } => {
return shapes.push(FlatShape::ExternalCommand.spanned(self.span)); shapes.push(FlatShape::ExternalCommand.spanned(self.span))
} }
UnspannedAtomicToken::ExternalWord { .. } => { UnspannedAtomicToken::ExternalWord { .. } => {
return shapes.push(FlatShape::ExternalWord.spanned(self.span)) shapes.push(FlatShape::ExternalWord.spanned(self.span))
} }
UnspannedAtomicToken::GlobPattern { .. } => { UnspannedAtomicToken::GlobPattern { .. } => {
return shapes.push(FlatShape::GlobPattern.spanned(self.span)) shapes.push(FlatShape::GlobPattern.spanned(self.span))
} }
UnspannedAtomicToken::Word { .. } => { UnspannedAtomicToken::Word { .. } => shapes.push(FlatShape::Word.spanned(self.span)),
return shapes.push(FlatShape::Word.spanned(self.span)) _ => shapes.push(FlatShape::Error.spanned(self.span)),
}
_ => return shapes.push(FlatShape::Error.spanned(self.span)),
} }
} }
} }
@ -524,14 +514,13 @@ fn expand_atom_inner<'me, 'content>(
rule: ExpansionRule, rule: ExpansionRule,
) -> Result<AtomicToken<'content>, ParseError> { ) -> Result<AtomicToken<'content>, ParseError> {
if token_nodes.at_end() { if token_nodes.at_end() {
match rule.allow_eof { if rule.allow_eof {
true => {
return Ok(UnspannedAtomicToken::Eof { return Ok(UnspannedAtomicToken::Eof {
span: Span::unknown(), span: Span::unknown(),
} }
.into_atomic_token(Span::unknown())) .into_atomic_token(Span::unknown()));
} } else {
false => return Err(ParseError::unexpected_eof("anything", Span::unknown())), return Err(ParseError::unexpected_eof("anything", Span::unknown()));
} }
} }
@ -540,9 +529,8 @@ fn expand_atom_inner<'me, 'content>(
// If treat_size_as_word, don't try to parse the head of the token stream // If treat_size_as_word, don't try to parse the head of the token stream
// as a size. // as a size.
match rule.treat_size_as_word { if !rule.treat_size_as_word {
true => {} match expand_syntax(&UnitShape, token_nodes, context) {
false => match expand_syntax(&UnitShape, token_nodes, context) {
// If the head of the stream isn't a valid unit, we'll try to parse // If the head of the stream isn't a valid unit, we'll try to parse
// it again next as a word // it again next as a word
Err(_) => {} Err(_) => {}
@ -552,12 +540,10 @@ fn expand_atom_inner<'me, 'content>(
unit: (number, unit), unit: (number, unit),
span, span,
}) => return Ok(UnspannedAtomicToken::Size { number, unit }.into_atomic_token(span)), }) => return Ok(UnspannedAtomicToken::Size { number, unit }.into_atomic_token(span)),
}, }
} }
match rule.separate_members { if rule.separate_members {
false => {}
true => {
let mut next = token_nodes.peek_any(); let mut next = token_nodes.peek_any();
match next.node { match next.node {
@ -578,7 +564,6 @@ fn expand_atom_inner<'me, 'content>(
_ => {} _ => {}
} }
} }
}
// Try to parse the head of the stream as a bare path. A bare path includes // Try to parse the head of the stream as a bare path. A bare path includes
// words as well as `.`s, connected together without whitespace. // words as well as `.`s, connected together without whitespace.

View File

@ -6,7 +6,7 @@ use nu_errors::ParseError;
use nu_source::{Span, SpannedItem, Tag}; use nu_source::{Span, SpannedItem, Tag};
pub fn expand_delimited_square( pub fn expand_delimited_square(
children: &Vec<TokenNode>, children: &[TokenNode],
span: Span, span: Span,
context: &ExpandContext, context: &ExpandContext,
) -> Result<hir::Expression, ParseError> { ) -> Result<hir::Expression, ParseError> {

View File

@ -74,15 +74,15 @@ impl ExpandExpression for FilePathShape {
| UnspannedAtomicToken::ExternalWord { text: body } | UnspannedAtomicToken::ExternalWord { text: body }
| UnspannedAtomicToken::String { body } => { | UnspannedAtomicToken::String { body } => {
let path = expand_file_path(body.slice(context.source), context); let path = expand_file_path(body.slice(context.source), context);
return Ok(hir::Expression::file_path(path, atom.span)); Ok(hir::Expression::file_path(path, atom.span))
} }
UnspannedAtomicToken::Number { .. } | UnspannedAtomicToken::Size { .. } => { UnspannedAtomicToken::Number { .. } | UnspannedAtomicToken::Size { .. } => {
let path = atom.span.slice(context.source); let path = atom.span.slice(context.source);
return Ok(hir::Expression::file_path(path, atom.span)); Ok(hir::Expression::file_path(path, atom.span))
} }
_ => return atom.into_hir(context, "file path"), _ => atom.to_hir(context, "file path"),
} }
} }
} }

View File

@ -120,30 +120,24 @@ impl ColorSyntax for ExpressionListShape {
} }
} else { } else {
// Try to color the head of the stream as an expression // Try to color the head of the stream as an expression
match color_fallible_syntax(&AnyExpressionShape, token_nodes, context) { if color_fallible_syntax(&AnyExpressionShape, token_nodes, context).is_err() {
// If no expression was found, switch to backoff coloring mode // If no expression was found, switch to backoff coloring mode
Err(_) => {
backoff = true; backoff = true;
continue; continue;
} }
Ok(_) => {}
}
// If an expression was found, consume a space // If an expression was found, consume a space
match color_fallible_syntax(&SpaceShape, token_nodes, context) { if color_fallible_syntax(&SpaceShape, token_nodes, context).is_err() {
Err(_) => {
// If no space was found, we're either at the end or there's an error. // If no space was found, we're either at the end or there's an error.
// Either way, switch to backoff coloring mode. If we're at the end // Either way, switch to backoff coloring mode. If we're at the end
// it won't have any consequences. // it won't have any consequences.
backoff = true; backoff = true;
} }
Ok(_) => {
// Otherwise, move on to the next expression // Otherwise, move on to the next expression
} }
} }
} }
}
}
} }
/// BackoffColoringMode consumes all of the remaining tokens in an infallible way /// BackoffColoringMode consumes all of the remaining tokens in an infallible way

View File

@ -68,9 +68,9 @@ impl ExpandExpression for PatternShape {
| UnspannedAtomicToken::ExternalWord { text: body } | UnspannedAtomicToken::ExternalWord { text: body }
| UnspannedAtomicToken::GlobPattern { pattern: body } => { | UnspannedAtomicToken::GlobPattern { pattern: body } => {
let path = expand_file_path(body.slice(context.source), context); let path = expand_file_path(body.slice(context.source), context);
return Ok(hir::Expression::pattern(path.to_string_lossy(), atom.span)); Ok(hir::Expression::pattern(path.to_string_lossy(), atom.span))
} }
_ => return atom.into_hir(context, "pattern"), _ => atom.to_hir(context, "pattern"),
} }
} }
} }

View File

@ -41,9 +41,8 @@ impl ExpandExpression for VariablePathShape {
let mut tail: Vec<PathMember> = vec![]; let mut tail: Vec<PathMember> = vec![];
loop { loop {
match DotShape.skip(token_nodes, context) { if DotShape.skip(token_nodes, context).is_err() {
Err(_) => break, break;
Ok(_) => {}
} }
let member = expand_syntax(&MemberShape, token_nodes, context)?; let member = expand_syntax(&MemberShape, token_nodes, context)?;
@ -77,17 +76,16 @@ impl FallibleColorSyntax for VariablePathShape {
loop { loop {
// look for a dot at the head of a stream // look for a dot at the head of a stream
let dot = color_fallible_syntax_with( if color_fallible_syntax_with(
&ColorableDotShape, &ColorableDotShape,
&FlatShape::Dot, &FlatShape::Dot,
token_nodes, token_nodes,
context, context,
); )
.is_err()
{
// if there's no dot, we're done // if there's no dot, we're done
match dot { break;
Err(_) => break,
Ok(_) => {}
} }
// otherwise, look for a member, and if you don't find one, fail // otherwise, look for a member, and if you don't find one, fail
@ -125,9 +123,8 @@ impl FallibleColorSyntax for PathTailShape {
context, context,
); );
match result { if result.is_err() {
Err(_) => return Ok(()), return Ok(());
Ok(_) => {}
} }
// If we've seen a dot but not a member, fail // If we've seen a dot but not a member, fail
@ -170,9 +167,8 @@ impl ExpandSyntax for PathTailShape {
let mut tail: Vec<PathMember> = vec![]; let mut tail: Vec<PathMember> = vec![];
loop { loop {
match DotShape.skip(token_nodes, context) { if DotShape.skip(token_nodes, context).is_err() {
Err(_) => break, break;
Ok(_) => {}
} }
let member = expand_syntax(&MemberShape, token_nodes, context)?; let member = expand_syntax(&MemberShape, token_nodes, context)?;
@ -649,12 +645,11 @@ impl FallibleColorSyntax for MemberShape {
let bare = let bare =
color_fallible_syntax_with(&BareShape, &FlatShape::BareMember, token_nodes, context); color_fallible_syntax_with(&BareShape, &FlatShape::BareMember, token_nodes, context);
match bare { if bare.is_ok() {
Ok(_) => return Ok(()), return Ok(());
Err(_) => { }
// If we don't have a bare word, we'll look for a string // If we don't have a bare word, we'll look for a string
}
}
// Look for a string token. If we don't find one, fail // Look for a string token. If we don't find one, fail
color_fallible_syntax_with(&StringShape, &FlatShape::StringMember, token_nodes, context) color_fallible_syntax_with(&StringShape, &FlatShape::StringMember, token_nodes, context)
@ -688,7 +683,11 @@ impl ExpandSyntax for IntMemberShape {
UnspannedAtomicToken::Number { UnspannedAtomicToken::Number {
number: RawNumber::Int(int), number: RawNumber::Int(int),
} => Ok(Member::Int( } => Ok(Member::Int(
BigInt::from_str(int.slice(context.source)).unwrap(), BigInt::from_str(int.slice(context.source)).map_err(|_| {
ParseError::internal_error(
"can't convert from string to big int".spanned(int),
)
})?,
int, int,
)), )),
@ -696,7 +695,7 @@ impl ExpandSyntax for IntMemberShape {
let int = BigInt::from_str(text.slice(context.source)); let int = BigInt::from_str(text.slice(context.source));
match int { match int {
Ok(int) => return Ok(Member::Int(int, text)), Ok(int) => Ok(Member::Int(int, text)),
Err(_) => Err(ParseError::mismatch("integer member", "word".spanned(text))), Err(_) => Err(ParseError::mismatch("integer member", "word".spanned(text))),
} }
} }
@ -737,7 +736,9 @@ impl ExpandSyntax for MemberShape {
if let Some(peeked) = number { if let Some(peeked) = number {
let node = peeked.not_eof("column")?.commit(); let node = peeked.not_eof("column")?.commit();
let (n, span) = node.as_number().unwrap(); let (n, span) = node.as_number().ok_or_else(|| {
ParseError::internal_error("can't convert node to number".spanned(node.span()))
})?;
return Ok(Member::Number(n, span)) return Ok(Member::Number(n, span))
}*/ }*/
@ -746,7 +747,9 @@ impl ExpandSyntax for MemberShape {
if let Some(peeked) = string { if let Some(peeked) = string {
let node = peeked.not_eof("column")?.commit(); let node = peeked.not_eof("column")?.commit();
let (outer, inner) = node.as_string().unwrap(); let (outer, inner) = node.as_string().ok_or_else(|| {
ParseError::internal_error("can't convert node to string".spanned(node.span()))
})?;
return Ok(Member::String(outer, inner)); return Ok(Member::String(outer, inner));
} }

View File

@ -35,7 +35,7 @@ pub enum FlatShape {
} }
impl FlatShape { impl FlatShape {
pub fn from(token: &TokenNode, source: &Text, shapes: &mut Vec<Spanned<FlatShape>>) -> () { pub fn from(token: &TokenNode, source: &Text, shapes: &mut Vec<Spanned<FlatShape>>) {
match token { match token {
TokenNode::Token(token) => match token.unspanned { TokenNode::Token(token) => match token.unspanned {
UnspannedToken::Number(RawNumber::Int(_)) => { UnspannedToken::Number(RawNumber::Int(_)) => {
@ -84,7 +84,7 @@ impl FlatShape {
} }
TokenNode::Pipeline(pipeline) => { TokenNode::Pipeline(pipeline) => {
for part in &pipeline.parts { for part in &pipeline.parts {
if let Some(_) = part.pipe { if part.pipe.is_some() {
shapes.push(FlatShape::Pipe.spanned(part.span())); shapes.push(FlatShape::Pipe.spanned(part.span()));
} }
} }

View File

@ -5,7 +5,6 @@ use self::debug::{ColorTracer, ExpandTracer};
use crate::hir::syntax_shape::FlatShape; use crate::hir::syntax_shape::FlatShape;
use crate::hir::Expression; use crate::hir::Expression;
use crate::TokenNode; use crate::TokenNode;
#[allow(unused)]
use getset::{Getters, MutGetters}; use getset::{Getters, MutGetters};
use nu_errors::{ParseError, ShellError}; use nu_errors::{ParseError, ShellError};
use nu_protocol::SpannedTypeName; use nu_protocol::SpannedTypeName;
@ -68,8 +67,8 @@ impl<'content, 'me> std::ops::Drop for Checkpoint<'content, 'me> {
pub struct Peeked<'content, 'me> { pub struct Peeked<'content, 'me> {
pub(crate) node: Option<&'content TokenNode>, pub(crate) node: Option<&'content TokenNode>,
iterator: &'me mut TokensIterator<'content>, iterator: &'me mut TokensIterator<'content>,
from: usize, pub from: usize,
to: usize, pub to: usize,
} }
impl<'content, 'me> Peeked<'content, 'me> { impl<'content, 'me> Peeked<'content, 'me> {
@ -102,7 +101,7 @@ impl<'content, 'me> Peeked<'content, 'me> {
} }
pub fn type_error(&self, expected: &'static str) -> ParseError { pub fn type_error(&self, expected: &'static str) -> ParseError {
peek_error(&self.node, self.iterator.eof_span(), expected) peek_error(self.node, self.iterator.eof_span(), expected)
} }
} }
@ -130,11 +129,11 @@ impl<'content, 'me> PeekedNode<'content, 'me> {
pub fn rollback(self) {} pub fn rollback(self) {}
pub fn type_error(&self, expected: &'static str) -> ParseError { pub fn type_error(&self, expected: &'static str) -> ParseError {
peek_error(&Some(self.node), self.iterator.eof_span(), expected) peek_error(Some(self.node), self.iterator.eof_span(), expected)
} }
} }
pub fn peek_error(node: &Option<&TokenNode>, eof_span: Span, expected: &'static str) -> ParseError { pub fn peek_error(node: Option<&TokenNode>, eof_span: Span, expected: &'static str) -> ParseError {
match node { match node {
None => ParseError::unexpected_eof(expected, eof_span), None => ParseError::unexpected_eof(expected, eof_span),
Some(node) => ParseError::mismatch(expected, node.spanned_type_name()), Some(node) => ParseError::mismatch(expected, node.spanned_type_name()),
@ -158,7 +157,7 @@ impl<'content> TokensIterator<'content> {
shapes: vec![], shapes: vec![],
}, },
color_tracer: ColorTracer::new(source.clone()), color_tracer: ColorTracer::new(source.clone()),
expand_tracer: ExpandTracer::new(source.clone()), expand_tracer: ExpandTracer::new(source),
} }
} }
@ -174,6 +173,10 @@ impl<'content> TokensIterator<'content> {
self.state.tokens.len() self.state.tokens.len()
} }
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn spanned<T>( pub fn spanned<T>(
&mut self, &mut self,
block: impl FnOnce(&mut TokensIterator<'content>) -> T, block: impl FnOnce(&mut TokensIterator<'content>) -> T,
@ -233,7 +236,7 @@ impl<'content> TokensIterator<'content> {
let mut color_tracer = ColorTracer::new(source.clone()); let mut color_tracer = ColorTracer::new(source.clone());
std::mem::swap(&mut color_tracer, &mut self.color_tracer); std::mem::swap(&mut color_tracer, &mut self.color_tracer);
let mut expand_tracer = ExpandTracer::new(source.clone()); let mut expand_tracer = ExpandTracer::new(source);
std::mem::swap(&mut expand_tracer, &mut self.expand_tracer); std::mem::swap(&mut expand_tracer, &mut self.expand_tracer);
let mut iterator = TokensIterator { let mut iterator = TokensIterator {
@ -409,7 +412,7 @@ impl<'content> TokensIterator<'content> {
let value = block(checkpoint.iterator)?; let value = block(checkpoint.iterator)?;
checkpoint.commit(); checkpoint.commit();
return Ok(value); Ok(value)
} }
/// Use a checkpoint when you need to peek more than one token ahead, but can't be sure /// Use a checkpoint when you need to peek more than one token ahead, but can't be sure
@ -437,7 +440,7 @@ impl<'content> TokensIterator<'content> {
let value = block(checkpoint.iterator)?; let value = block(checkpoint.iterator)?;
checkpoint.commit(); checkpoint.commit();
return Ok(value); Ok(value)
} }
/// Use a checkpoint when you need to peek more than one token ahead, but can't be sure /// Use a checkpoint when you need to peek more than one token ahead, but can't be sure
@ -474,7 +477,7 @@ impl<'content> TokensIterator<'content> {
checkpoint.commit(); checkpoint.commit();
std::mem::swap(&mut self.state.shapes, &mut shapes); std::mem::swap(&mut self.state.shapes, &mut shapes);
return (Ok(value), shapes); (Ok(value), shapes)
} }
fn eof_span(&self) -> Span { fn eof_span(&self) -> Span {
@ -583,12 +586,12 @@ impl<'content> TokensIterator<'content> {
let peeked = peeked.not_eof(expected); let peeked = peeked.not_eof(expected);
match peeked { match peeked {
Err(err) => return Err(err), Err(err) => Err(err),
Ok(peeked) => match block(peeked.node) { Ok(peeked) => match block(peeked.node) {
Err(err) => return Err(err), Err(err) => Err(err),
Ok(val) => { Ok(val) => {
peeked.commit(); peeked.commit();
return Ok(val); Ok(val)
} }
}, },
} }
@ -658,10 +661,7 @@ fn peek<'content, 'me>(
} }
} }
fn peek_pos<'content, 'me>( fn peek_pos(iterator: &TokensIterator<'_>, skip_ws: bool) -> Option<usize> {
iterator: &'me TokensIterator<'content>,
skip_ws: bool,
) -> Option<usize> {
let state = iterator.state(); let state = iterator.state();
let mut to = state.index; let mut to = state.index;

View File

@ -24,13 +24,11 @@ pub(crate) fn debug_tokens(state: &TokensIteratorState, source: &str) -> Vec<Deb
out.push(DebugIteratorToken::Cursor); out.push(DebugIteratorToken::Cursor);
} }
let msg = token.debug(source).to_string();
if state.seen.contains(&i) { if state.seen.contains(&i) {
out.push(DebugIteratorToken::Seen(format!("{}", token.debug(source)))); out.push(DebugIteratorToken::Seen(msg));
} else { } else {
out.push(DebugIteratorToken::Unseen(format!( out.push(DebugIteratorToken::Unseen(msg));
"{}",
token.debug(source)
)));
} }
} }

View File

@ -49,7 +49,7 @@ pub struct ColorFrame {
impl ColorFrame { impl ColorFrame {
fn colored_leaf_description(&self, f: &mut impl io::Write) -> io::Result<()> { fn colored_leaf_description(&self, f: &mut impl io::Write) -> io::Result<()> {
if self.has_only_error_descendents() { if self.has_only_error_descendents() {
if self.children.len() == 0 { if self.children.is_empty() {
write!( write!(
f, f,
"{}", "{}",
@ -109,15 +109,11 @@ impl ColorFrame {
fn any_child_shape(&self, predicate: impl Fn(Spanned<FlatShape>) -> bool) -> bool { fn any_child_shape(&self, predicate: impl Fn(Spanned<FlatShape>) -> bool) -> bool {
for item in &self.children { for item in &self.children {
match item { if let FrameChild::Shape(shape) = item {
FrameChild::Shape(shape) => {
if predicate(*shape) { if predicate(*shape) {
return true; return true;
} }
} }
_ => {}
}
} }
false false
@ -125,15 +121,11 @@ impl ColorFrame {
fn any_child_frame(&self, predicate: impl Fn(&ColorFrame) -> bool) -> bool { fn any_child_frame(&self, predicate: impl Fn(&ColorFrame) -> bool) -> bool {
for item in &self.children { for item in &self.children {
match item { if let FrameChild::Frame(frame) = item {
FrameChild::Frame(frame) => {
if predicate(frame) { if predicate(frame) {
return true; return true;
} }
} }
_ => {}
}
} }
false false
@ -148,7 +140,7 @@ impl ColorFrame {
} }
fn has_only_error_descendents(&self) -> bool { fn has_only_error_descendents(&self) -> bool {
if self.children.len() == 0 { if self.children.is_empty() {
// if this frame has no children at all, it has only error descendents if this frame // if this frame has no children at all, it has only error descendents if this frame
// is an error // is an error
self.error.is_some() self.error.is_some()
@ -259,7 +251,7 @@ impl ColorTracer {
let result = self.frame_stack.pop().expect("Can't pop root tracer frame"); let result = self.frame_stack.pop().expect("Can't pop root tracer frame");
if self.frame_stack.len() == 0 { if self.frame_stack.is_empty() {
panic!("Can't pop root tracer frame {:#?}", self); panic!("Can't pop root tracer frame {:#?}", self);
} }

View File

@ -19,7 +19,7 @@ impl FrameChild {
fn get_error_leaf(&self) -> Option<&'static str> { fn get_error_leaf(&self) -> Option<&'static str> {
match self { match self {
FrameChild::Frame(frame) if frame.error.is_some() => { FrameChild::Frame(frame) if frame.error.is_some() => {
if frame.children.len() == 0 { if frame.children.is_empty() {
Some(frame.description) Some(frame.description)
} else { } else {
None None
@ -33,12 +33,12 @@ impl FrameChild {
match self { match self {
FrameChild::Expr(expr) => TreeChild::OkExpr(expr.clone(), text.clone()), FrameChild::Expr(expr) => TreeChild::OkExpr(expr.clone(), text.clone()),
FrameChild::Result(result) => { FrameChild::Result(result) => {
let result = format!("{}", result.display()); let result = result.display();
TreeChild::OkNonExpr(result) TreeChild::OkNonExpr(result)
} }
FrameChild::Frame(frame) => { FrameChild::Frame(frame) => {
if frame.error.is_some() { if frame.error.is_some() {
if frame.children.len() == 0 { if frame.children.is_empty() {
TreeChild::ErrorLeaf(vec![frame.description]) TreeChild::ErrorLeaf(vec![frame.description])
} else { } else {
TreeChild::ErrorFrame(frame.to_tree_frame(text), text.clone()) TreeChild::ErrorFrame(frame.to_tree_frame(text), text.clone())
@ -67,7 +67,7 @@ impl ExprFrame {
if let Some(error_leaf) = child.get_error_leaf() { if let Some(error_leaf) = child.get_error_leaf() {
errors.push(error_leaf); errors.push(error_leaf);
continue; continue;
} else if errors.len() > 0 { } else if !errors.is_empty() {
children.push(TreeChild::ErrorLeaf(errors)); children.push(TreeChild::ErrorLeaf(errors));
errors = vec![]; errors = vec![];
} }
@ -75,7 +75,7 @@ impl ExprFrame {
children.push(child.to_tree_child(text)); children.push(child.to_tree_child(text));
} }
if errors.len() > 0 { if !errors.is_empty() {
children.push(TreeChild::ErrorLeaf(errors)); children.push(TreeChild::ErrorLeaf(errors));
} }
@ -115,9 +115,8 @@ impl TreeFrame {
write!(f, " -> ")?; write!(f, " -> ")?;
self.children[0].leaf_description(f) self.children[0].leaf_description(f)
} else { } else if self.error.is_some() {
if self.error.is_some() { if self.children.is_empty() {
if self.children.len() == 0 {
write!( write!(
f, f,
"{}", "{}",
@ -132,7 +131,6 @@ impl TreeFrame {
write!(f, "{}", Color::Yellow.bold().paint(self.description)) write!(f, "{}", Color::Yellow.bold().paint(self.description))
} }
} }
}
fn has_child_green(&self) -> bool { fn has_child_green(&self) -> bool {
self.children.iter().any(|item| match item { self.children.iter().any(|item| match item {
@ -143,15 +141,11 @@ impl TreeFrame {
fn any_child_frame(&self, predicate: impl Fn(&TreeFrame) -> bool) -> bool { fn any_child_frame(&self, predicate: impl Fn(&TreeFrame) -> bool) -> bool {
for item in &self.children { for item in &self.children {
match item { if let TreeChild::OkFrame(frame, ..) = item {
TreeChild::OkFrame(frame, ..) => {
if predicate(frame) { if predicate(frame) {
return true; return true;
} }
} }
_ => {}
}
} }
false false
@ -209,7 +203,7 @@ impl TreeChild {
Color::White Color::White
.bold() .bold()
.on(Color::Green) .on(Color::Green)
.paint(format!("{}", result)) .paint(result.to_string())
), ),
TreeChild::ErrorLeaf(desc) => { TreeChild::ErrorLeaf(desc) => {
@ -260,12 +254,7 @@ pub struct ExpandTracer {
impl ExpandTracer { impl ExpandTracer {
pub fn print(&self, source: Text) -> PrintTracer { pub fn print(&self, source: Text) -> PrintTracer {
let root = self let root = self.frame_stack[0].to_tree_frame(&source);
.frame_stack
.iter()
.nth(0)
.unwrap()
.to_tree_frame(&source);
PrintTracer { root, source } PrintTracer { root, source }
} }
@ -292,7 +281,7 @@ impl ExpandTracer {
fn pop_frame(&mut self) -> ExprFrame { fn pop_frame(&mut self) -> ExprFrame {
let result = self.frame_stack.pop().expect("Can't pop root tracer frame"); let result = self.frame_stack.pop().expect("Can't pop root tracer frame");
if self.frame_stack.len() == 0 { if self.frame_stack.is_empty() {
panic!("Can't pop root tracer frame"); panic!("Can't pop root tracer frame");
} }

View File

@ -3,14 +3,14 @@ use crate::parse::token_tree_builder::TokenTreeBuilder as b;
use crate::Span; use crate::Span;
#[test] #[test]
fn supplies_tokens() { fn supplies_tokens() -> Result<(), Box<dyn std::error::Error>> {
let tokens = b::token_list(vec![b::var("it"), b::op("."), b::bare("cpu")]); let tokens = b::token_list(vec![b::var("it"), b::op("."), b::bare("cpu")]);
let (tokens, _) = b::build(tokens); let (tokens, _) = b::build(tokens);
let tokens = tokens.expect_list(); let tokens = tokens.expect_list();
let mut iterator = TokensIterator::all(tokens, Span::unknown()); let mut iterator = TokensIterator::all(tokens, Span::unknown());
iterator.next().unwrap().expect_var(); iterator.next()?.expect_var();
iterator.next().unwrap().expect_dot(); iterator.next()?.expect_dot();
iterator.next().unwrap().expect_bare(); iterator.next()?.expect_bare();
} }

View File

@ -1,3 +1,5 @@
#![allow(clippy::large_enum_variant, clippy::type_complexity)]
pub mod commands; pub mod commands;
pub mod hir; pub mod hir;
pub mod parse; pub mod parse;

View File

@ -30,7 +30,7 @@ impl PrettyDebugWithSource for CallNode {
impl CallNode { impl CallNode {
pub fn new(head: Box<TokenNode>, children: Vec<TokenNode>) -> CallNode { pub fn new(head: Box<TokenNode>, children: Vec<TokenNode>) -> CallNode {
if children.len() == 0 { if children.is_empty() {
CallNode { CallNode {
head, head,
children: None, children: None,

View File

@ -26,7 +26,7 @@ impl language_reporting::ReportingFiles for Files {
} }
fn file_name(&self, _file: Self::FileId) -> FileName { fn file_name(&self, _file: Self::FileId) -> FileName {
FileName::Verbatim(format!("shell")) FileName::Verbatim("shell".to_string())
} }
fn byte_index(&self, _file: Self::FileId, _line: usize, _column: usize) -> Option<usize> { fn byte_index(&self, _file: Self::FileId, _line: usize, _column: usize) -> Option<usize> {
@ -143,9 +143,7 @@ impl language_reporting::ReportingFiles for Files {
fn source(&self, span: Self::Span) -> Option<String> { fn source(&self, span: Self::Span) -> Option<String> {
trace!("source(tag={:?}) snippet={:?}", span, self.snippet); trace!("source(tag={:?}) snippet={:?}", span, self.snippet);
if span.start() > span.end() { if span.start() > span.end() || span.end() > self.snippet.len() {
return None;
} else if span.end() > self.snippet.len() {
return None; return None;
} }
Some(span.slice(&self.snippet).to_string()) Some(span.slice(&self.snippet).to_string())

View File

@ -22,12 +22,12 @@ impl PrettyDebug for CompareOperator {
} }
impl CompareOperator { impl CompareOperator {
pub fn print(&self) -> String { pub fn print(self) -> String {
self.as_str().to_string() self.as_str().to_string()
} }
pub fn as_str(&self) -> &str { pub fn as_str(self) -> &'static str {
match *self { match self {
CompareOperator::Equal => "==", CompareOperator::Equal => "==",
CompareOperator::NotEqual => "!=", CompareOperator::NotEqual => "!=",
CompareOperator::LessThan => "<", CompareOperator::LessThan => "<",
@ -42,7 +42,11 @@ impl CompareOperator {
impl From<&str> for CompareOperator { impl From<&str> for CompareOperator {
fn from(input: &str) -> CompareOperator { fn from(input: &str) -> CompareOperator {
CompareOperator::from_str(input).unwrap() if let Ok(output) = CompareOperator::from_str(input) {
output
} else {
unreachable!("Internal error: CompareOperator from failed")
}
} }
} }
@ -76,12 +80,12 @@ impl PrettyDebug for EvaluationOperator {
} }
impl EvaluationOperator { impl EvaluationOperator {
pub fn print(&self) -> String { pub fn print(self) -> String {
self.as_str().to_string() self.as_str().to_string()
} }
pub fn as_str(&self) -> &str { pub fn as_str(self) -> &'static str {
match *self { match self {
EvaluationOperator::Dot => ".", EvaluationOperator::Dot => ".",
EvaluationOperator::DotDot => "..", EvaluationOperator::DotDot => "..",
} }
@ -90,7 +94,11 @@ impl EvaluationOperator {
impl From<&str> for EvaluationOperator { impl From<&str> for EvaluationOperator {
fn from(input: &str) -> EvaluationOperator { fn from(input: &str) -> EvaluationOperator {
EvaluationOperator::from_str(input).unwrap() if let Ok(output) = EvaluationOperator::from_str(input) {
output
} else {
unreachable!("Internal error: EvaluationOperator 'from' failed")
}
} }
} }

View File

@ -148,13 +148,21 @@ macro_rules! primitive_decimal {
$( $(
impl From<$ty> for Number { impl From<$ty> for Number {
fn from(decimal: $ty) -> Number { fn from(decimal: $ty) -> Number {
Number::Decimal(BigDecimal::$from(decimal).unwrap()) if let Some(num) = BigDecimal::$from(decimal) {
Number::Decimal(num)
} else {
unreachable!("Internal error: BigDecimal 'from' failed")
}
} }
} }
impl From<&$ty> for Number { impl From<&$ty> for Number {
fn from(decimal: &$ty) -> Number { fn from(decimal: &$ty) -> Number {
Number::Decimal(BigDecimal::$from(*decimal).unwrap()) if let Some(num) = BigDecimal::$from(*decimal) {
Number::Decimal(num)
} else {
unreachable!("Internal error: BigDecimal 'from' failed")
}
} }
} }
)* )*
@ -233,13 +241,9 @@ pub fn raw_number(input: NomSpan) -> IResult<NomSpan, RawNumber> {
let dotdot_result = dotdot(input); let dotdot_result = dotdot(input);
match dotdot_result { if let Ok((dotdot_input, _)) = dotdot_result {
// If we see a `..` immediately after an integer, it's a range, not a decimal // If we see a `..` immediately after an integer, it's a range, not a decimal
Ok((dotdot_input, _)) => { return Ok((input, RawNumber::int(Span::new(start, input.offset))));
return Ok((input, RawNumber::int(Span::new(start, input.offset))))
}
Err(_) => {}
} }
let dot: IResult<NomSpan, NomSpan, (NomSpan, nom::error::ErrorKind)> = tag(".")(input); let dot: IResult<NomSpan, NomSpan, (NomSpan, nom::error::ErrorKind)> = tag(".")(input);
@ -357,7 +361,10 @@ fn word<'a, T, U, V>(
pub fn matches(cond: fn(char) -> bool) -> impl Fn(NomSpan) -> IResult<NomSpan, NomSpan> + Copy { pub fn matches(cond: fn(char) -> bool) -> impl Fn(NomSpan) -> IResult<NomSpan, NomSpan> + Copy {
move |input: NomSpan| match input.iter_elements().next() { move |input: NomSpan| match input.iter_elements().next() {
Option::Some(c) if cond(c) => Ok((input.slice(1..), input.slice(0..1))), Option::Some(c) if cond(c) => {
let len_utf8 = c.len_utf8();
Ok((input.slice(len_utf8..), input.slice(0..len_utf8)))
}
_ => Err(nom::Err::Error(nom::error::ParseError::from_error_kind( _ => Err(nom::Err::Error(nom::error::ParseError::from_error_kind(
input, input,
nom::error::ErrorKind::Many0, nom::error::ErrorKind::Many0,
@ -431,10 +438,8 @@ enum SawSpecial {
fn start_file_char(input: NomSpan) -> IResult<NomSpan, BitFlags<SawSpecial>> { fn start_file_char(input: NomSpan) -> IResult<NomSpan, BitFlags<SawSpecial>> {
let path_sep_result = special_file_char(input); let path_sep_result = special_file_char(input);
match path_sep_result { if let Ok((input, special)) = path_sep_result {
Ok((input, special)) => return Ok((input, special)), return Ok((input, special));
Err(_) => {}
} }
start_filename(input).map(|(input, output)| (input, BitFlags::empty())) start_filename(input).map(|(input, output)| (input, BitFlags::empty()))
@ -444,9 +449,8 @@ fn start_file_char(input: NomSpan) -> IResult<NomSpan, BitFlags<SawSpecial>> {
fn continue_file_char(input: NomSpan) -> IResult<NomSpan, BitFlags<SawSpecial>> { fn continue_file_char(input: NomSpan) -> IResult<NomSpan, BitFlags<SawSpecial>> {
let path_sep_result = special_file_char(input); let path_sep_result = special_file_char(input);
match path_sep_result { if let Ok((input, special)) = path_sep_result {
Ok((input, special)) => return Ok((input, special)), return Ok((input, special));
Err(_) => {}
} }
matches(is_file_char)(input).map(|(input, _)| (input, BitFlags::empty())) matches(is_file_char)(input).map(|(input, _)| (input, BitFlags::empty()))
@ -454,9 +458,8 @@ fn continue_file_char(input: NomSpan) -> IResult<NomSpan, BitFlags<SawSpecial>>
#[tracable_parser] #[tracable_parser]
fn special_file_char(input: NomSpan) -> IResult<NomSpan, BitFlags<SawSpecial>> { fn special_file_char(input: NomSpan) -> IResult<NomSpan, BitFlags<SawSpecial>> {
match matches(is_path_separator)(input) { if let Ok((input, _)) = matches(is_path_separator)(input) {
Ok((input, _)) => return Ok((input, BitFlags::empty() | SawSpecial::PathSeparator)), return Ok((input, BitFlags::empty() | SawSpecial::PathSeparator));
Err(_) => {}
} }
let (input, _) = matches(is_glob_specific_char)(input)?; let (input, _) = matches(is_glob_specific_char)(input)?;
@ -664,9 +667,13 @@ pub fn spaced_token_list(input: NomSpan) -> IResult<NomSpan, Spanned<Vec<TokenNo
let mut out = vec![]; let mut out = vec![];
pre_ws.map(|pre_ws| out.extend(pre_ws)); if let Some(pre_ws) = pre_ws {
out.extend(pre_ws)
}
out.extend(items.item); out.extend(items.item);
post_ws.map(|post_ws| out.extend(post_ws)); if let Some(post_ws) = post_ws {
out.extend(post_ws)
}
Ok((input, out.spanned(Span::new(start, end)))) Ok((input, out.spanned(Span::new(start, end))))
} }
@ -910,11 +917,13 @@ pub fn module(input: NomSpan) -> IResult<NomSpan, TokenNode> {
} }
fn parse_int<T>(frag: &str, neg: Option<T>) -> i64 { fn parse_int<T>(frag: &str, neg: Option<T>) -> i64 {
let int = FromStr::from_str(frag).unwrap(); if let Ok(int) = FromStr::from_str(frag) {
match neg { match neg {
None => int, None => int,
Some(_) => int * -1, Some(_) => -int,
}
} else {
unreachable!("Internal error: parse_int failed");
} }
} }
@ -1101,42 +1110,63 @@ mod tests {
} }
#[test] #[test]
fn test_operator() { fn test_gt_operator() {
equal_tokens! { equal_tokens! {
<nodes> <nodes>
">" -> b::token_list(vec![b::op(">")]) ">" -> b::token_list(vec![b::op(">")])
} }
}
#[test]
fn test_gte_operator() {
equal_tokens! { equal_tokens! {
<nodes> <nodes>
">=" -> b::token_list(vec![b::op(">=")]) ">=" -> b::token_list(vec![b::op(">=")])
} }
}
#[test]
fn test_lt_operator() {
equal_tokens! { equal_tokens! {
<nodes> <nodes>
"<" -> b::token_list(vec![b::op("<")]) "<" -> b::token_list(vec![b::op("<")])
} }
}
#[test]
fn test_lte_operator() {
equal_tokens! { equal_tokens! {
<nodes> <nodes>
"<=" -> b::token_list(vec![b::op("<=")]) "<=" -> b::token_list(vec![b::op("<=")])
} }
}
#[test]
fn test_eq_operator() {
equal_tokens! { equal_tokens! {
<nodes> <nodes>
"==" -> b::token_list(vec![b::op("==")]) "==" -> b::token_list(vec![b::op("==")])
} }
}
#[test]
fn test_ne_operator() {
equal_tokens! { equal_tokens! {
<nodes> <nodes>
"!=" -> b::token_list(vec![b::op("!=")]) "!=" -> b::token_list(vec![b::op("!=")])
} }
}
#[test]
fn test_sim_operator() {
equal_tokens! { equal_tokens! {
<nodes> <nodes>
"=~" -> b::token_list(vec![b::op("=~")]) "=~" -> b::token_list(vec![b::op("=~")])
} }
}
#[test]
fn test_nsim_operator() {
equal_tokens! { equal_tokens! {
<nodes> <nodes>
"!~" -> b::token_list(vec![b::op("!~")]) "!~" -> b::token_list(vec![b::op("!~")])
@ -1393,37 +1423,58 @@ mod tests {
<nodes> <nodes>
"git add ." -> b::token_list(vec![b::bare("git"), b::sp(), b::bare("add"), b::sp(), b::bare(".")]) "git add ." -> b::token_list(vec![b::bare("git"), b::sp(), b::bare("add"), b::sp(), b::bare(".")])
} }
}
#[test]
fn test_smoke_single_command_open() {
equal_tokens! { equal_tokens! {
<nodes> <nodes>
"open Cargo.toml" -> b::token_list(vec![b::bare("open"), b::sp(), b::bare("Cargo"), b::dot(), b::bare("toml")]) "open Cargo.toml" -> b::token_list(vec![b::bare("open"), b::sp(), b::bare("Cargo"), b::dot(), b::bare("toml")])
} }
}
#[test]
fn test_smoke_single_command_select() {
equal_tokens! { equal_tokens! {
<nodes> <nodes>
"select package.version" -> b::token_list(vec![b::bare("select"), b::sp(), b::bare("package"), b::dot(), b::bare("version")]) "select package.version" -> b::token_list(vec![b::bare("select"), b::sp(), b::bare("package"), b::dot(), b::bare("version")])
} }
}
#[test]
fn test_smoke_single_command_it() {
equal_tokens! { equal_tokens! {
<nodes> <nodes>
"echo $it" -> b::token_list(vec![b::bare("echo"), b::sp(), b::var("it")]) "echo $it" -> b::token_list(vec![b::bare("echo"), b::sp(), b::var("it")])
} }
}
#[test]
fn test_smoke_single_command_open_raw() {
equal_tokens! { equal_tokens! {
<nodes> <nodes>
"open Cargo.toml --raw" -> b::token_list(vec![b::bare("open"), b::sp(), b::bare("Cargo"), b::dot(), b::bare("toml"), b::sp(), b::flag("raw")]) "open Cargo.toml --raw" -> b::token_list(vec![b::bare("open"), b::sp(), b::bare("Cargo"), b::dot(), b::bare("toml"), b::sp(), b::flag("raw")])
} }
}
#[test]
fn test_smoke_single_command_open_r() {
equal_tokens! { equal_tokens! {
<nodes> <nodes>
"open Cargo.toml -r" -> b::token_list(vec![b::bare("open"), b::sp(), b::bare("Cargo"), b::dot(), b::bare("toml"), b::sp(), b::shorthand("r")]) "open Cargo.toml -r" -> b::token_list(vec![b::bare("open"), b::sp(), b::bare("Cargo"), b::dot(), b::bare("toml"), b::sp(), b::shorthand("r")])
} }
}
#[test]
fn test_smoke_single_command_config() {
equal_tokens! { equal_tokens! {
<nodes> <nodes>
"config --set tabs 2" -> b::token_list(vec![b::bare("config"), b::sp(), b::flag("set"), b::sp(), b::bare("tabs"), b::sp(), b::int(2)]) "config --set tabs 2" -> b::token_list(vec![b::bare("config"), b::sp(), b::flag("set"), b::sp(), b::bare("tabs"), b::sp(), b::int(2)])
} }
}
#[test]
fn test_smoke_single_command_inc() {
equal_tokens! { equal_tokens! {
<nodes> <nodes>
"inc --patch package.version" -> b::token_list( "inc --patch package.version" -> b::token_list(

View File

@ -349,7 +349,7 @@ pub enum Delimiter {
} }
impl Delimiter { impl Delimiter {
pub(crate) fn open(&self) -> &'static str { pub(crate) fn open(self) -> &'static str {
match self { match self {
Delimiter::Paren => "(", Delimiter::Paren => "(",
Delimiter::Brace => "{", Delimiter::Brace => "{",
@ -357,7 +357,7 @@ impl Delimiter {
} }
} }
pub(crate) fn close(&self) -> &'static str { pub(crate) fn close(self) -> &'static str {
match self { match self {
Delimiter::Paren => ")", Delimiter::Paren => ")",
Delimiter::Brace => "}", Delimiter::Brace => "}",

View File

@ -9,17 +9,15 @@ use bigdecimal::BigDecimal;
use nu_source::{Span, Spanned, SpannedItem}; use nu_source::{Span, Spanned, SpannedItem};
use num_bigint::BigInt; use num_bigint::BigInt;
#[derive(Default)]
pub struct TokenTreeBuilder { pub struct TokenTreeBuilder {
pos: usize, pos: usize,
output: String, output: String,
} }
impl TokenTreeBuilder { impl TokenTreeBuilder {
pub fn new() -> TokenTreeBuilder { pub fn new() -> Self {
TokenTreeBuilder { Default::default()
pos: 0,
output: String::new(),
}
} }
} }
@ -319,16 +317,19 @@ impl TokenTreeBuilder {
} }
pub fn spanned_call(input: Vec<TokenNode>, span: impl Into<Span>) -> Spanned<CallNode> { pub fn spanned_call(input: Vec<TokenNode>, span: impl Into<Span>) -> Spanned<CallNode> {
if input.len() == 0 { if input.is_empty() {
panic!("BUG: spanned call (TODO)") panic!("BUG: spanned call (TODO)")
} }
let mut input = input.into_iter(); let mut input = input.into_iter();
let head = input.next().unwrap(); if let Some(head) = input.next() {
let tail = input.collect(); let tail = input.collect();
CallNode::new(Box::new(head), tail).spanned(span.into()) CallNode::new(Box::new(head), tail).spanned(span.into())
} else {
unreachable!("Internal error: spanned_call failed")
}
} }
fn consume_delimiter( fn consume_delimiter(

View File

@ -88,9 +88,19 @@ impl RawNumber {
pub(crate) fn to_number(self, source: &Text) -> Number { pub(crate) fn to_number(self, source: &Text) -> Number {
match self { match self {
RawNumber::Int(tag) => Number::Int(BigInt::from_str(tag.slice(source)).unwrap()), RawNumber::Int(tag) => {
if let Ok(int) = BigInt::from_str(tag.slice(source)) {
Number::Int(int)
} else {
unreachable!("Internal error: to_number failed")
}
}
RawNumber::Decimal(tag) => { RawNumber::Decimal(tag) => {
Number::Decimal(BigDecimal::from_str(tag.slice(source)).unwrap()) if let Ok(decimal) = BigDecimal::from_str(tag.slice(source)) {
Number::Decimal(decimal)
} else {
unreachable!("Internal error: to_number failed")
}
} }
} }
} }

View File

@ -34,14 +34,26 @@ impl PrettyDebug for Unit {
fn convert_number_to_u64(number: &Number) -> u64 { fn convert_number_to_u64(number: &Number) -> u64 {
match number { match number {
Number::Int(big_int) => big_int.to_u64().unwrap(), Number::Int(big_int) => {
Number::Decimal(big_decimal) => big_decimal.to_u64().unwrap(), if let Some(x) = big_int.to_u64() {
x
} else {
unreachable!("Internal error: convert_number_to_u64 given incompatible number")
}
}
Number::Decimal(big_decimal) => {
if let Some(x) = big_decimal.to_u64() {
x
} else {
unreachable!("Internal error: convert_number_to_u64 given incompatible number")
}
}
} }
} }
impl Unit { impl Unit {
pub fn as_str(&self) -> &str { pub fn as_str(self) -> &'static str {
match *self { match self {
Unit::Byte => "B", Unit::Byte => "B",
Unit::Kilobyte => "KB", Unit::Kilobyte => "KB",
Unit::Megabyte => "MB", Unit::Megabyte => "MB",
@ -58,10 +70,10 @@ impl Unit {
} }
} }
pub fn compute(&self, size: &Number) -> UntaggedValue { pub fn compute(self, size: &Number) -> UntaggedValue {
let size = size.clone(); let size = size.clone();
match &self { match self {
Unit::Byte => number(size), Unit::Byte => number(size),
Unit::Kilobyte => number(size * 1024), Unit::Kilobyte => number(size * 1024),
Unit::Megabyte => number(size * 1024 * 1024), Unit::Megabyte => number(size * 1024 * 1024),

View File

@ -134,7 +134,7 @@ pub fn parse_command_tail(
trace!(target: "nu::parse", "Constructed positional={:?} named={:?}", positional, named); trace!(target: "nu::parse", "Constructed positional={:?} named={:?}", positional, named);
let positional = if positional.len() == 0 { let positional = if positional.is_empty() {
None None
} else { } else {
Some(positional) Some(positional)
@ -204,7 +204,7 @@ impl ColorSyntax for CommandTailShape {
fn insert_flag( fn insert_flag(
token_nodes: &mut TokensIterator, token_nodes: &mut TokensIterator,
syntax_type: &SyntaxShape, syntax_type: SyntaxShape,
args: &mut ColoringArgs, args: &mut ColoringArgs,
flag: Flag, flag: Flag,
pos: usize, pos: usize,
@ -226,7 +226,7 @@ impl ColorSyntax for CommandTailShape {
// If the part after a mandatory flag isn't present, that's ok, but we // If the part after a mandatory flag isn't present, that's ok, but we
// should roll back any whitespace we chomped // should roll back any whitespace we chomped
color_fallible_syntax(syntax_type, token_nodes, context)?; color_fallible_syntax(&syntax_type, token_nodes, context)?;
Ok(()) Ok(())
}); });
@ -243,9 +243,10 @@ impl ColorSyntax for CommandTailShape {
match &kind.0 { match &kind.0 {
NamedType::Switch => { NamedType::Switch => {
match token_nodes.extract(|t| t.as_flag(name, context.source())) { if let Some((pos, flag)) =
Some((pos, flag)) => args.insert(pos, vec![flag.color()]), token_nodes.extract(|t| t.as_flag(name, context.source()))
None => {} {
args.insert(pos, vec![flag.color()])
} }
} }
NamedType::Mandatory(syntax_type) => { NamedType::Mandatory(syntax_type) => {
@ -260,7 +261,7 @@ impl ColorSyntax for CommandTailShape {
// The mandatory flag didn't exist at all, so there's nothing to color // The mandatory flag didn't exist at all, so there's nothing to color
} }
Ok((pos, flag)) => { Ok((pos, flag)) => {
insert_flag(token_nodes, syntax_type, &mut args, flag, pos, context) insert_flag(token_nodes, *syntax_type, &mut args, flag, pos, context)
} }
} }
} }
@ -270,7 +271,7 @@ impl ColorSyntax for CommandTailShape {
// The optional flag didn't exist at all, so there's nothing to color // The optional flag didn't exist at all, so there's nothing to color
} }
Ok(Some((pos, flag))) => { Ok(Some((pos, flag))) => {
insert_flag(token_nodes, syntax_type, &mut args, flag, pos, context) insert_flag(token_nodes, *syntax_type, &mut args, flag, pos, context)
} }
Ok(None) => { Ok(None) => {

View File

@ -0,0 +1,24 @@
[package]
name = "nu-plugin"
version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018"
description = "Nushell Plugin"
license = "MIT"
[lib]
doctest = false
[dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.8.0" }
nu-source = { path = "../nu-source", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.8.0" }
nu-value-ext = { path = "../nu-value-ext", version = "0.8.0" }
indexmap = { version = "1.3.0", features = ["serde-1"] }
serde = { version = "1.0.103", features = ["derive"] }
num-bigint = { version = "0.2.3", features = ["serde"] }
serde_json = "1.0.44"
[build-dependencies]
nu-build = { version = "0.8.0", path = "../nu-build" }

View File

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

View File

@ -0,0 +1,4 @@
mod plugin;
pub mod test_helpers;
pub use crate::plugin::{serve_plugin, Plugin};

View File

@ -1,8 +1,5 @@
use crate::call_info::CallInfo;
use crate::return_value::ReturnValue;
use crate::signature::Signature;
use crate::value::Value;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{outln, CallInfo, ReturnValue, Signature, Value};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::io; use std::io;
@ -27,9 +24,9 @@ pub trait Plugin {
} }
pub fn serve_plugin(plugin: &mut dyn Plugin) { pub fn serve_plugin(plugin: &mut dyn Plugin) {
let args = std::env::args(); let mut args = std::env::args();
if args.len() > 1 { if args.len() > 1 {
let input = args.skip(1).next(); let input = args.nth(1);
let input = match input { let input = match input {
Some(arg) => std::fs::read_to_string(arg), Some(arg) => std::fs::read_to_string(arg),

View File

@ -0,0 +1,213 @@
use crate::Plugin;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{CallInfo, EvaluatedArgs, ReturnSuccess, ReturnValue, UntaggedValue, Value};
use nu_source::Tag;
pub struct PluginTest<'a, T: Plugin> {
plugin: &'a mut T,
call_info: CallInfo,
input: Value,
}
impl<'a, T: Plugin> PluginTest<'a, T> {
pub fn for_plugin(plugin: &'a mut T) -> Self {
PluginTest {
plugin,
call_info: CallStub::new().create(),
input: UntaggedValue::nothing().into_value(Tag::unknown()),
}
}
pub fn args(&mut self, call_info: CallInfo) -> &mut PluginTest<'a, T> {
self.call_info = call_info;
self
}
pub fn configure(&mut self, callback: impl FnOnce(Vec<String>)) -> &mut PluginTest<'a, T> {
let signature = self
.plugin
.config()
.expect("There was a problem configuring the plugin.");
callback(signature.named.keys().map(String::from).collect());
self
}
pub fn input(&mut self, value: Value) -> &mut PluginTest<'a, T> {
self.input = value;
self
}
pub fn test(&mut self) -> Result<Vec<ReturnValue>, ShellError> {
let return_values = self.plugin.filter(self.input.clone());
let mut return_values = match return_values {
Ok(filtered) => filtered,
Err(reason) => return Err(reason),
};
let end = self.plugin.end_filter();
match end {
Ok(filter_ended) => return_values.extend(filter_ended),
Err(reason) => return Err(reason),
}
self.plugin.quit();
Ok(return_values)
}
pub fn setup(
&mut self,
callback: impl FnOnce(&mut T, Result<Vec<ReturnValue>, ShellError>),
) -> &mut PluginTest<'a, T> {
let call_stub = self.call_info.clone();
self.configure(|flags_configured| {
let flags_registered = &call_stub.args.named;
let flag_passed = match flags_registered {
Some(names) => Some(names.keys().map(String::from).collect::<Vec<String>>()),
None => None,
};
if let Some(flags) = flag_passed {
for flag in flags {
assert!(
flags_configured.iter().any(|f| *f == flag),
format!(
"The flag you passed ({}) is not configured in the plugin.",
flag
)
);
}
}
});
let began = self.plugin.begin_filter(call_stub);
let return_values = match began {
Ok(values) => Ok(values),
Err(reason) => Err(reason),
};
callback(self.plugin, return_values);
self
}
}
pub fn plugin<T: Plugin>(plugin: &mut T) -> PluginTest<T> {
PluginTest::for_plugin(plugin)
}
#[derive(Default)]
pub struct CallStub {
positionals: Vec<Value>,
flags: IndexMap<String, Value>,
}
impl CallStub {
pub fn new() -> Self {
Default::default()
}
pub fn with_named_parameter(&mut self, name: &str, value: Value) -> &mut Self {
self.flags.insert(name.to_string(), value);
self
}
pub fn with_long_flag(&mut self, name: &str) -> &mut Self {
self.flags.insert(
name.to_string(),
UntaggedValue::boolean(true).into_value(Tag::unknown()),
);
self
}
pub fn with_parameter(&mut self, name: &str) -> Result<&mut Self, ShellError> {
let fields: Vec<Value> = name
.split('.')
.map(|s| UntaggedValue::string(s.to_string()).into_value(Tag::unknown()))
.collect();
self.positionals.push(value::column_path(&fields)?);
Ok(self)
}
pub fn create(&self) -> CallInfo {
CallInfo {
args: EvaluatedArgs::new(Some(self.positionals.clone()), Some(self.flags.clone())),
name_tag: Tag::unknown(),
}
}
}
pub fn expect_return_value_at(
for_results: Result<Vec<Result<ReturnSuccess, ShellError>>, ShellError>,
at: usize,
) -> Value {
let return_values = for_results
.expect("Failed! This seems to be an error getting back the results from the plugin.");
for (idx, item) in return_values.iter().enumerate() {
let item = match item {
Ok(return_value) => return_value,
Err(reason) => panic!(format!("{}", reason)),
};
if idx == at {
if let Some(value) = item.raw_value() {
return value;
} else {
panic!("Internal error: could not get raw value in expect_return_value_at")
}
}
}
panic!(format!(
"Couldn't get return value from stream at {}. (There are {} items)",
at,
return_values.len() - 1
))
}
pub mod value {
use nu_errors::ShellError;
use nu_protocol::{Primitive, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::Tag;
use nu_value_ext::ValueExt;
use num_bigint::BigInt;
pub fn get_data(for_value: Value, key: &str) -> Value {
for_value.get_data(&key.to_string()).borrow().clone()
}
pub fn int(i: impl Into<BigInt>) -> Value {
UntaggedValue::Primitive(Primitive::Int(i.into())).into_untagged_value()
}
pub fn string(input: impl Into<String>) -> Value {
UntaggedValue::string(input.into()).into_untagged_value()
}
pub fn structured_sample_record(key: &str, value: &str) -> Value {
let mut record = TaggedDictBuilder::new(Tag::unknown());
record.insert_untagged(key, UntaggedValue::string(value));
record.into_value()
}
pub fn unstructured_sample_record(value: &str) -> Value {
UntaggedValue::string(value).into_value(Tag::unknown())
}
pub fn table(list: &[Value]) -> Value {
UntaggedValue::table(list).into_untagged_value()
}
pub fn column_path(paths: &[Value]) -> Result<Value, ShellError> {
Ok(UntaggedValue::Primitive(Primitive::ColumnPath(
table(&paths.to_vec()).as_column_path()?.item,
))
.into_untagged_value())
}
}

View File

@ -1,16 +1,17 @@
[package] [package]
name = "nu-protocol" name = "nu-protocol"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "Core values and protocols for Nushell" description = "Core values and protocols for Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib]
doctest = false
[dependencies] [dependencies]
nu-source = { path = "../nu-source", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.8.0" }
serde = { version = "1.0.103", features = ["derive"] } serde = { version = "1.0.103", features = ["derive"] }
indexmap = { version = "1.3.0", features = ["serde-1"] } indexmap = { version = "1.3.0", features = ["serde-1"] }
@ -39,4 +40,4 @@ toml = "0.5.5"
serde_json = "1.0.44" serde_json = "1.0.44"
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }

View File

@ -30,14 +30,14 @@ impl EvaluatedArgs {
pub fn nth(&self, pos: usize) -> Option<&Value> { pub fn nth(&self, pos: usize) -> Option<&Value> {
match &self.positional { match &self.positional {
None => None, None => None,
Some(array) => array.iter().nth(pos), Some(array) => array.get(pos),
} }
} }
pub fn expect_nth(&self, pos: usize) -> Result<&Value, ShellError> { pub fn expect_nth(&self, pos: usize) -> Result<&Value, ShellError> {
match &self.positional { match &self.positional {
None => Err(ShellError::unimplemented("Better error: expect_nth")), None => Err(ShellError::unimplemented("Better error: expect_nth")),
Some(array) => match array.iter().nth(pos) { Some(array) => match array.get(pos) {
None => Err(ShellError::unimplemented("Better error: expect_nth")), None => Err(ShellError::unimplemented("Better error: expect_nth")),
Some(item) => Ok(item), Some(item) => Ok(item),
}, },
@ -51,6 +51,10 @@ impl EvaluatedArgs {
} }
} }
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn has(&self, name: &str) -> bool { pub fn has(&self, name: &str) -> bool {
match &self.named { match &self.named {
None => false, None => false,

View File

@ -3,7 +3,6 @@ mod macros;
mod call_info; mod call_info;
mod maybe_owned; mod maybe_owned;
mod plugin;
mod return_value; mod return_value;
mod signature; mod signature;
mod syntax_shape; mod syntax_shape;
@ -13,7 +12,6 @@ mod value;
pub use crate::call_info::{CallInfo, EvaluatedArgs}; pub use crate::call_info::{CallInfo, EvaluatedArgs};
pub use crate::maybe_owned::MaybeOwned; pub use crate::maybe_owned::MaybeOwned;
pub use crate::plugin::{serve_plugin, Plugin};
pub use crate::return_value::{CommandAction, ReturnSuccess, ReturnValue}; pub use crate::return_value::{CommandAction, ReturnSuccess, ReturnValue};
pub use crate::signature::{NamedType, PositionalType, Signature}; pub use crate::signature::{NamedType, PositionalType, Signature};
pub use crate::syntax_shape::SyntaxShape; pub use crate::syntax_shape::SyntaxShape;

View File

@ -1,3 +1,5 @@
#![allow(clippy::should_implement_trait)]
#[derive(Debug)] #[derive(Debug)]
pub enum MaybeOwned<'a, T> { pub enum MaybeOwned<'a, T> {
Owned(T), Owned(T),

View File

@ -62,6 +62,14 @@ impl Into<ReturnValue> for Value {
} }
impl ReturnSuccess { impl ReturnSuccess {
pub fn raw_value(&self) -> Option<Value> {
match self {
ReturnSuccess::Value(raw) => Some(raw.clone()),
ReturnSuccess::DebugValue(raw) => Some(raw.clone()),
ReturnSuccess::Action(_) => None,
}
}
pub fn change_cwd(path: String) -> ReturnValue { pub fn change_cwd(path: String) -> ReturnValue {
Ok(ReturnSuccess::Action(CommandAction::ChangePath(path))) Ok(ReturnSuccess::Action(CommandAction::ChangePath(path)))
} }

View File

@ -235,7 +235,7 @@ impl PrettyDebug for Type {
(b::kind("table") + b::space() + b::keyword("of")).group() (b::kind("table") + b::space() + b::keyword("of")).group()
+ b::space() + b::space()
+ (if group.len() == 1 { + (if group.len() == 1 {
let (doc, _) = group.into_iter().nth(0).unwrap(); let (doc, _) = group.into_iter().collect::<Vec<_>>()[0].clone();
DebugDocBuilder::from_doc(doc) DebugDocBuilder::from_doc(doc)
} else { } else {
b::intersperse( b::intersperse(
@ -278,7 +278,7 @@ impl<'a> PrettyDebug for DebugEntry<'a> {
fn pretty(&self) -> DebugDocBuilder { fn pretty(&self) -> DebugDocBuilder {
(b::key(match self.key { (b::key(match self.key {
Column::String(string) => string.clone(), Column::String(string) => string.clone(),
Column::Value => format!("<value>"), Column::Value => "<value>".to_string(),
}) + b::delimit("(", self.value.pretty(), ")").into_kind()) }) + b::delimit("(", self.value.pretty(), ")").into_kind())
} }
} }
@ -346,12 +346,12 @@ where
None => { None => {
self.values.insert(key, { self.values.insert(key, {
let mut group = G::new(); let mut group = G::new();
group.merge(value.into()); group.merge(value);
group group
}); });
} }
Some(group) => { Some(group) => {
group.merge(value.into()); group.merge(value);
} }
} }
} }

View File

@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize};
use std::path::PathBuf; use std::path::PathBuf;
use std::time::SystemTime; use std::time::SystemTime;
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub enum UntaggedValue { pub enum UntaggedValue {
Primitive(Primitive), Primitive(Primitive),
Row(Dictionary), Row(Dictionary),
@ -46,12 +46,7 @@ impl UntaggedValue {
pub fn data_descriptors(&self) -> Vec<String> { pub fn data_descriptors(&self) -> Vec<String> {
match self { match self {
UntaggedValue::Primitive(_) => vec![], UntaggedValue::Primitive(_) => vec![],
UntaggedValue::Row(columns) => columns UntaggedValue::Row(columns) => columns.entries.keys().map(|x| x.to_string()).collect(),
.entries
.keys()
.into_iter()
.map(|x| x.to_string())
.collect(),
UntaggedValue::Block(_) => vec![], UntaggedValue::Block(_) => vec![],
UntaggedValue::Table(_) => vec![], UntaggedValue::Table(_) => vec![],
UntaggedValue::Error(_) => vec![], UntaggedValue::Error(_) => vec![],
@ -116,7 +111,7 @@ impl UntaggedValue {
UntaggedValue::Row(entries.into()) UntaggedValue::Row(entries.into())
} }
pub fn table(list: &Vec<Value>) -> UntaggedValue { pub fn table(list: &[Value]) -> UntaggedValue {
UntaggedValue::Table(list.to_vec()) UntaggedValue::Table(list.to_vec())
} }
@ -182,7 +177,7 @@ impl UntaggedValue {
} }
} }
#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Hash, Serialize, Deserialize)]
pub struct Value { pub struct Value {
pub value: UntaggedValue, pub value: UntaggedValue,
pub tag: Tag, pub tag: Tag,
@ -227,9 +222,7 @@ impl Value {
pub fn as_path(&self) -> Result<PathBuf, ShellError> { pub fn as_path(&self) -> Result<PathBuf, ShellError> {
match &self.value { match &self.value {
UntaggedValue::Primitive(Primitive::Path(path)) => Ok(path.clone()), UntaggedValue::Primitive(Primitive::Path(path)) => Ok(path.clone()),
UntaggedValue::Primitive(Primitive::String(path_str)) => { UntaggedValue::Primitive(Primitive::String(path_str)) => Ok(PathBuf::from(&path_str)),
Ok(PathBuf::from(&path_str).clone())
}
_ => Err(ShellError::type_error("Path", self.spanned_type_name())), _ => Err(ShellError::type_error("Path", self.spanned_type_name())),
} }
} }

View File

@ -48,8 +48,8 @@ impl ColumnPath {
self.members.iter() self.members.iter()
} }
pub fn split_last(&self) -> (&PathMember, &[PathMember]) { pub fn split_last(&self) -> Option<(&PathMember, &[PathMember])> {
self.members.split_last().unwrap() self.members.split_last()
} }
} }
@ -69,7 +69,7 @@ impl PrettyDebug for ColumnPath {
impl HasFallibleSpan for ColumnPath { impl HasFallibleSpan for ColumnPath {
fn maybe_span(&self) -> Option<Span> { fn maybe_span(&self) -> Option<Span> {
if self.members.len() == 0 { if self.members.is_empty() {
None None
} else { } else {
Some(span_for_spanned_list(self.members.iter().map(|m| m.span))) Some(span_for_spanned_list(self.members.iter().map(|m| m.span)))
@ -98,7 +98,7 @@ pub fn did_you_mean(obj_source: &Value, field_tried: &PathMember) -> Option<Vec<
let mut possible_matches: Vec<_> = possibilities let mut possible_matches: Vec<_> = possibilities
.into_iter() .into_iter()
.map(|x| { .map(|x| {
let word = x.clone(); let word = x;
let distance = natural::distance::levenshtein_distance(&word, &field_tried); let distance = natural::distance::levenshtein_distance(&word, &field_tried);
(distance, word) (distance, word)

View File

@ -7,6 +7,7 @@ use indexmap::IndexMap;
use nu_source::{b, DebugDocBuilder, PrettyDebug, Spanned, Tag}; use nu_source::{b, DebugDocBuilder, PrettyDebug, Spanned, Tag};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::cmp::{Ord, Ordering, PartialOrd}; use std::cmp::{Ord, Ordering, PartialOrd};
use std::hash::{Hash, Hasher};
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone, Getters, new)] #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone, Getters, new)]
pub struct Dictionary { pub struct Dictionary {
@ -14,6 +15,16 @@ pub struct Dictionary {
pub entries: IndexMap<String, Value>, pub entries: IndexMap<String, Value>,
} }
#[allow(clippy::derive_hash_xor_eq)]
impl Hash for Dictionary {
fn hash<H: Hasher>(&self, state: &mut H) {
let mut entries = self.entries.clone();
entries.sort_keys();
entries.keys().collect::<Vec<&String>>().hash(state);
entries.values().collect::<Vec<&Value>>().hash(state);
}
}
impl PartialOrd for Dictionary { impl PartialOrd for Dictionary {
fn partial_cmp(&self, other: &Dictionary) -> Option<Ordering> { fn partial_cmp(&self, other: &Dictionary) -> Option<Ordering> {
let this: Vec<&String> = self.entries.keys().collect(); let this: Vec<&String> = self.entries.keys().collect();
@ -95,7 +106,7 @@ impl From<IndexMap<String, Value>> for Dictionary {
} }
impl Dictionary { impl Dictionary {
pub fn get_data(&self, desc: &String) -> MaybeOwned<'_, Value> { pub fn get_data(&self, desc: &str) -> MaybeOwned<'_, Value> {
match self.entries.get(desc) { match self.entries.get(desc) {
Some(v) => MaybeOwned::Borrowed(v), Some(v) => MaybeOwned::Borrowed(v),
None => MaybeOwned::Owned( None => MaybeOwned::Owned(

View File

@ -12,7 +12,7 @@ use num_traits::cast::{FromPrimitive, ToPrimitive};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]
pub enum Primitive { pub enum Primitive {
Nothing, Nothing,
#[serde(with = "serde_bigint")] #[serde(with = "serde_bigint")]
@ -64,7 +64,11 @@ impl From<BigDecimal> for Primitive {
impl From<f64> for Primitive { impl From<f64> for Primitive {
fn from(float: f64) -> Primitive { fn from(float: f64) -> Primitive {
Primitive::Decimal(BigDecimal::from_f64(float).unwrap()) if let Some(f) = BigDecimal::from_f64(float) {
Primitive::Decimal(f)
} else {
unreachable!("Internal error: protocol did not use f64-compatible decimal")
}
} }
} }
@ -108,12 +112,12 @@ pub fn format_primitive(primitive: &Primitive, field_name: Option<&String>) -> S
match byte.get_unit() { match byte.get_unit() {
byte_unit::ByteUnit::B => format!("{} B ", byte.get_value()), byte_unit::ByteUnit::B => format!("{} B ", byte.get_value()),
_ => byte.format(1).to_string(), _ => byte.format(1),
} }
} }
Primitive::Duration(sec) => format_duration(*sec), Primitive::Duration(sec) => format_duration(*sec),
Primitive::Int(i) => i.to_string(), Primitive::Int(i) => i.to_string(),
Primitive::Decimal(decimal) => decimal.to_string(), Primitive::Decimal(decimal) => format!("{:.4}", decimal),
Primitive::Range(range) => format!( Primitive::Range(range) => format!(
"{}..{}", "{}..{}",
format_primitive(&range.from.0.item, None), format_primitive(&range.from.0.item, None),
@ -150,7 +154,7 @@ pub fn format_primitive(primitive: &Primitive, field_name: Option<&String>) -> S
} }
.to_owned(), .to_owned(),
Primitive::Binary(_) => "<binary>".to_owned(), Primitive::Binary(_) => "<binary>".to_owned(),
Primitive::Date(d) => d.humanize().to_string(), Primitive::Date(d) => d.humanize(),
} }
} }

View File

@ -3,21 +3,21 @@ use derive_new::new;
use nu_source::{b, DebugDocBuilder, Spanned}; use nu_source::{b, DebugDocBuilder, Spanned};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize, Hash)] #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub enum RangeInclusion { pub enum RangeInclusion {
Inclusive, Inclusive,
Exclusive, Exclusive,
} }
impl RangeInclusion { impl RangeInclusion {
pub fn debug_left_bracket(&self) -> DebugDocBuilder { pub fn debug_left_bracket(self) -> DebugDocBuilder {
b::delimiter(match self { b::delimiter(match self {
RangeInclusion::Exclusive => "(", RangeInclusion::Exclusive => "(",
RangeInclusion::Inclusive => "[", RangeInclusion::Inclusive => "[",
}) })
} }
pub fn debug_right_bracket(&self) -> DebugDocBuilder { pub fn debug_right_bracket(self) -> DebugDocBuilder {
b::delimiter(match self { b::delimiter(match self {
RangeInclusion::Exclusive => ")", RangeInclusion::Exclusive => ")",
RangeInclusion::Inclusive => "]", RangeInclusion::Inclusive => "]",
@ -25,7 +25,7 @@ impl RangeInclusion {
} }
} }
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize, new)] #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize, new)]
pub struct Range { pub struct Range {
pub from: (Spanned<Primitive>, RangeInclusion), pub from: (Spanned<Primitive>, RangeInclusion),
pub to: (Spanned<Primitive>, RangeInclusion), pub to: (Spanned<Primitive>, RangeInclusion),

View File

@ -9,7 +9,7 @@ where
serde::Serialize::serialize( serde::Serialize::serialize(
&big_decimal &big_decimal
.to_f64() .to_f64()
.ok_or(serde::ser::Error::custom("expected a f64-sized bignum"))?, .ok_or_else(|| serde::ser::Error::custom("expected a f64-sized bignum"))?,
serializer, serializer,
) )
} }
@ -20,5 +20,5 @@ where
{ {
let x: f64 = serde::Deserialize::deserialize(deserializer)?; let x: f64 = serde::Deserialize::deserialize(deserializer)?;
Ok(BigDecimal::from_f64(x) Ok(BigDecimal::from_f64(x)
.ok_or(serde::de::Error::custom("expected a f64-sized bigdecimal"))?) .ok_or_else(|| serde::de::Error::custom("expected a f64-sized bigdecimal"))?)
} }

View File

@ -9,7 +9,7 @@ where
serde::Serialize::serialize( serde::Serialize::serialize(
&big_int &big_int
.to_i64() .to_i64()
.ok_or(serde::ser::Error::custom("expected a i64-sized bignum"))?, .ok_or_else(|| serde::ser::Error::custom("expected a i64-sized bignum"))?,
serializer, serializer,
) )
} }
@ -19,5 +19,6 @@ where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
let x: i64 = serde::Deserialize::deserialize(deserializer)?; let x: i64 = serde::Deserialize::deserialize(deserializer)?;
Ok(BigInt::from_i64(x).ok_or(serde::de::Error::custom("expected a i64-sized bignum"))?) Ok(BigInt::from_i64(x)
.ok_or_else(|| serde::de::Error::custom("expected a i64-sized bignum"))?)
} }

View File

@ -1,12 +1,13 @@
[package] [package]
name = "nu-source" name = "nu-source"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "A source string characterizer for Nushell" description = "A source string characterizer for Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib]
doctest = false
[dependencies] [dependencies]
serde = { version = "1.0.103", features = ["derive"] } serde = { version = "1.0.103", features = ["derive"] }
@ -19,4 +20,4 @@ termcolor = "1.0.5"
pretty = "0.5.2" pretty = "0.5.2"
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }

View File

@ -414,8 +414,7 @@ pub trait PrettyDebug {
let doc = self.pretty_doc(); let doc = self.pretty_doc();
let mut buffer = termcolor::Buffer::no_color(); let mut buffer = termcolor::Buffer::no_color();
doc.render_raw(width, &mut TermColored::new(&mut buffer)) let _ = doc.render_raw(width, &mut TermColored::new(&mut buffer));
.unwrap();
String::from_utf8_lossy(buffer.as_slice()).to_string() String::from_utf8_lossy(buffer.as_slice()).to_string()
} }
@ -424,8 +423,7 @@ pub trait PrettyDebug {
let doc = self.pretty_doc(); let doc = self.pretty_doc();
let mut buffer = termcolor::Buffer::ansi(); let mut buffer = termcolor::Buffer::ansi();
doc.render_raw(width, &mut TermColored::new(&mut buffer)) let _ = doc.render_raw(width, &mut TermColored::new(&mut buffer));
.unwrap();
String::from_utf8_lossy(buffer.as_slice()).to_string() String::from_utf8_lossy(buffer.as_slice()).to_string()
} }
@ -488,6 +486,7 @@ fn hash_doc<H: std::hash::Hasher>(doc: &PrettyDebugDoc, state: &mut H) {
} }
} }
#[allow(clippy::derive_hash_xor_eq)]
impl std::hash::Hash for DebugDoc { impl std::hash::Hash for DebugDoc {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
hash_doc(&self.inner, state); hash_doc(&self.inner, state);

View File

@ -1,12 +1,13 @@
[package] [package]
name = "nu-test-support" name = "nu-test-support"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "A source string characterizer for Nushell" description = "A source string characterizer for Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib]
doctest = false
[dependencies] [dependencies]
app_dirs = "1.2.1" app_dirs = "1.2.1"

View File

@ -30,7 +30,11 @@ impl AbsoluteFile {
} }
pub fn dir(&self) -> AbsolutePath { pub fn dir(&self) -> AbsolutePath {
AbsolutePath::new(self.inner.parent().unwrap()) AbsolutePath::new(if let Some(parent) = self.inner.parent() {
parent
} else {
unreachable!("Internal error: could not get parent in dir")
})
} }
} }
@ -133,7 +137,7 @@ impl DisplayPath for str {
impl DisplayPath for &str { impl DisplayPath for &str {
fn display_path(&self) -> String { fn display_path(&self) -> String {
self.to_string() (*self).to_string()
} }
} }
@ -145,7 +149,7 @@ impl DisplayPath for String {
impl DisplayPath for &String { impl DisplayPath for &String {
fn display_path(&self) -> String { fn display_path(&self) -> String {
self.to_string() (*self).to_string()
} }
} }
pub enum Stub<'a> { pub enum Stub<'a> {
@ -196,7 +200,7 @@ pub fn create_file_at(full_path: impl AsRef<Path>) -> Result<(), std::io::Error>
panic!(format!("{:?} exists", parent.display())); panic!(format!("{:?} exists", parent.display()));
} }
std::fs::write(full_path, "fake data".as_bytes()) std::fs::write(full_path, b"fake data")
} }
pub fn copy_file_to(source: &str, destination: &str) { pub fn copy_file_to(source: &str, destination: &str) {

View File

@ -34,7 +34,7 @@ macro_rules! nu {
.spawn() .spawn()
{ {
Ok(child) => child, Ok(child) => child,
Err(why) => panic!("Can't run test {}", why.description()), Err(why) => panic!("Can't run test {}", why.to_string()),
}; };
let stdin = process.stdin.as_mut().expect("couldn't open stdin"); let stdin = process.stdin.as_mut().expect("couldn't open stdin");

View File

@ -22,7 +22,7 @@ pub struct Dirs {
impl Dirs { impl Dirs {
pub fn formats(&self) -> PathBuf { pub fn formats(&self) -> PathBuf {
PathBuf::from(self.fixtures.join("formats")) self.fixtures.join("formats")
} }
} }
@ -47,7 +47,7 @@ impl Playground {
std::fs::create_dir(PathBuf::from(&nuplay_dir)).expect("can not create directory"); std::fs::create_dir(PathBuf::from(&nuplay_dir)).expect("can not create directory");
let mut playground = Playground { let mut playground = Playground {
root: root, root,
tests: topic.to_string(), tests: topic.to_string(),
cwd: nuplay_dir, cwd: nuplay_dir,
}; };
@ -63,21 +63,29 @@ impl Playground {
.expect("Couldn't find the fixtures directory") .expect("Couldn't find the fixtures directory")
.join("tests/fixtures"); .join("tests/fixtures");
let fixtures = dunce::canonicalize(fixtures.clone()).expect(&format!( let fixtures = dunce::canonicalize(fixtures.clone()).unwrap_or_else(|e| {
"Couldn't canonicalize fixtures path {}", panic!(
fixtures.display() "Couldn't canonicalize fixtures path {}: {:?}",
)); fixtures.display(),
e
)
});
let test = let test = dunce::canonicalize(playground_root.join(topic)).unwrap_or_else(|e| {
dunce::canonicalize(PathBuf::from(playground_root.join(topic))).expect(&format!( panic!(
"Couldn't canonicalize test path {}", "Couldn't canonicalize test path {}: {:?}",
playground_root.join(topic).display() playground_root.join(topic).display(),
)); e
)
});
let root = dunce::canonicalize(playground_root).expect(&format!( let root = dunce::canonicalize(playground_root).unwrap_or_else(|e| {
"Couldn't canonicalize tests root path {}", panic!(
playground_root.display() "Couldn't canonicalize tests root path {}: {:?}",
)); playground_root.display(),
e
)
});
let dirs = Dirs { let dirs = Dirs {
root, root,
@ -119,8 +127,7 @@ impl Playground {
path.push(file_name); path.push(file_name);
std::fs::write(PathBuf::from(path), contents.as_bytes()) std::fs::write(path, contents.as_bytes()).expect("can not create file");
.expect("can not create file");
}) })
.for_each(drop); .for_each(drop);
self.back_to_playground(); self.back_to_playground();
@ -136,8 +143,7 @@ impl Playground {
pub fn glob_vec(pattern: &str) -> Vec<PathBuf> { pub fn glob_vec(pattern: &str) -> Vec<PathBuf> {
let glob = glob(pattern); let glob = glob(pattern);
match glob { glob.expect("invalid pattern")
Ok(paths) => paths
.map(|path| { .map(|path| {
if let Ok(path) = path { if let Ok(path) = path {
path path
@ -145,8 +151,6 @@ impl Playground {
unreachable!() unreachable!()
} }
}) })
.collect(), .collect()
Err(_) => panic!("Invalid pattern."),
}
} }
} }

View File

@ -1,21 +1,22 @@
[package] [package]
name = "nu-value-ext" name = "nu-value-ext"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "Extension traits for values in Nushell" description = "Extension traits for values in Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib]
doctest = false
[dependencies] [dependencies]
nu-source = { path = "../nu-source", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.8.0" }
nu-parser = { path = "../nu-parser", version = "0.7.0" } nu-parser = { path = "../nu-parser", version = "0.8.0" }
nu-protocol = { path = "../nu-protocol", version = "0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.8.0" }
num-traits = "0.2.10" num-traits = "0.2.10"
itertools = "0.8.2" itertools = "0.8.2"
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }

View File

@ -9,7 +9,7 @@ use num_traits::cast::ToPrimitive;
pub trait ValueExt { pub trait ValueExt {
fn into_parts(self) -> (UntaggedValue, Tag); fn into_parts(self) -> (UntaggedValue, Tag);
fn get_data(&self, desc: &String) -> MaybeOwned<'_, Value>; fn get_data(&self, desc: &str) -> MaybeOwned<'_, Value>;
fn get_data_by_key(&self, name: Spanned<&str>) -> Option<Value>; fn get_data_by_key(&self, name: Spanned<&str>) -> Option<Value>;
fn get_data_by_member(&self, name: &PathMember) -> Result<Value, ShellError>; fn get_data_by_member(&self, name: &PathMember) -> Result<Value, ShellError>;
fn get_data_by_column_path( fn get_data_by_column_path(
@ -43,7 +43,7 @@ impl ValueExt for Value {
(self.value, self.tag) (self.value, self.tag)
} }
fn get_data(&self, desc: &String) -> MaybeOwned<'_, Value> { fn get_data(&self, desc: &str) -> MaybeOwned<'_, Value> {
get_data(self, desc) get_data(self, desc)
} }
@ -163,14 +163,9 @@ pub fn get_data_by_member(value: &Value, name: &PathMember) -> Result<Value, She
) )
})?; })?;
match get_data_by_index(value, index.spanned(value.tag.span)) { get_data_by_index(value, index.spanned(value.tag.span)).ok_or_else(|| {
Some(v) => Ok(v.clone()), ShellError::range_error(0..(l.len()), &int.spanned(name.span), "indexing")
None => Err(ShellError::range_error( })
0..(l.len()),
&int.spanned(name.span),
"indexing",
)),
}
} }
} }
} }
@ -193,7 +188,7 @@ pub fn get_data_by_column_path(
match value { match value {
Ok(v) => current = v.clone(), Ok(v) => current = v.clone(),
Err(e) => return Err(callback((&current.clone(), &p.clone(), e))), Err(e) => return Err(callback((&current, &p.clone(), e))),
} }
} }
@ -210,10 +205,9 @@ pub fn insert_data_at_path(value: &Value, path: &str, new_value: Value) -> Optio
if split_path.len() == 1 { if split_path.len() == 1 {
// Special case for inserting at the top level // Special case for inserting at the top level
current.entries.insert( current
path.to_string(), .entries
new_value.value.clone().into_value(&value.tag), .insert(path.to_string(), new_value.value.into_value(&value.tag));
);
return Some(new_obj); return Some(new_obj);
} }
@ -275,7 +269,7 @@ pub fn insert_data_at_member(
) )
})?; })?;
insert_data_at_index(array, int.tagged(member.span), new_value.clone())?; insert_data_at_index(array, int.tagged(member.span), new_value)?;
Ok(()) Ok(())
} }
}, },
@ -297,7 +291,7 @@ pub fn insert_data_at_column_path(
split_path: &ColumnPath, split_path: &ColumnPath,
new_value: Value, new_value: Value,
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
let (last, front) = split_path.split_last(); if let Some((last, front)) = split_path.split_last() {
let mut original = value.clone(); let mut original = value.clone();
let mut current: &mut Value = &mut original; let mut current: &mut Value = &mut original;
@ -316,6 +310,11 @@ pub fn insert_data_at_column_path(
insert_data_at_member(current, &last, new_value)?; insert_data_at_member(current, &last, new_value)?;
Ok(original) Ok(original)
} else {
Err(ShellError::untagged_runtime_error(
"Internal error: could not split column-path correctly",
))
}
} }
pub fn replace_data_at_column_path( pub fn replace_data_at_column_path(
@ -428,7 +427,7 @@ fn insert_data_at_index(
} }
} }
pub fn get_data<'value>(value: &'value Value, desc: &String) -> MaybeOwned<'value, Value> { pub fn get_data<'value>(value: &'value Value, desc: &str) -> MaybeOwned<'value, Value> {
match &value.value { match &value.value {
UntaggedValue::Primitive(_) => MaybeOwned::Borrowed(value), UntaggedValue::Primitive(_) => MaybeOwned::Borrowed(value),
UntaggedValue::Row(o) => o.get_data(desc), UntaggedValue::Row(o) => o.get_data(desc),

View File

@ -1,17 +1,16 @@
[package] [package]
name = "nu_plugin_average" name = "nu_plugin_average"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "An average value plugin for Nushell" description = "An average value plugin for Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.7.0" } nu-plugin = { path = "../nu-plugin", version="0.8.0" }
nu-source = { path = "../nu-source", version = "0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.8.0" }
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }

View File

@ -1,7 +1,7 @@
use nu_errors::{CoerceInto, ShellError}; use nu_errors::{CoerceInto, ShellError};
use nu_plugin::{serve_plugin, Plugin};
use nu_protocol::{ use nu_protocol::{
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, Signature, CallInfo, Primitive, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value,
UntaggedValue, Value,
}; };
use nu_source::TaggedItem; use nu_source::TaggedItem;

View File

@ -1,23 +1,22 @@
[package] [package]
name = "nu_plugin_binaryview" name = "nu_plugin_binaryview"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "A binary viewer plugin for Nushell" description = "A binary viewer plugin for Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
ansi_term = "0.12.1" ansi_term = "0.12.1"
crossterm = { version = "0.10.2" } crossterm = { version = "0.10.2" }
nu-protocol = { path = "../nu-protocol", version = "0.7.0" } nu-plugin = { path = "../nu-plugin", version="0.8.0" }
nu-source = { path = "../nu-source", version = "0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.8.0" }
pretty-hex = "0.1.1" pretty-hex = "0.1.1"
image = { version = "0.22.3", default_features = false, features = ["png_codec", "jpeg"] } image = { version = "0.22.3", default_features = false, features = ["png_codec", "jpeg"] }
rawkey = "0.1.2" rawkey = "0.1.2"
neso = "0.5.0" neso = "0.5.0"
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }

View File

@ -1,8 +1,7 @@
use crossterm::{cursor, terminal, Attribute, RawScreen}; use crossterm::{cursor, terminal, Attribute, RawScreen};
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_plugin::{serve_plugin, Plugin};
outln, serve_plugin, CallInfo, Plugin, Primitive, Signature, UntaggedValue, Value, use nu_protocol::{outln, CallInfo, Primitive, Signature, UntaggedValue, Value};
};
use nu_source::AnchorLocation; use nu_source::AnchorLocation;
use pretty_hex::*; use pretty_hex::*;
@ -24,12 +23,9 @@ impl Plugin for BinaryView {
fn sink(&mut self, call_info: CallInfo, input: Vec<Value>) { fn sink(&mut self, call_info: CallInfo, input: Vec<Value>) {
for v in input { for v in input {
let value_anchor = v.anchor(); let value_anchor = v.anchor();
match &v.value { if let UntaggedValue::Primitive(Primitive::Binary(b)) = &v.value {
UntaggedValue::Primitive(Primitive::Binary(b)) => {
let _ = view_binary(&b, value_anchor.as_ref(), call_info.args.has("lores")); let _ = view_binary(&b, value_anchor.as_ref(), call_info.args.has("lores"));
} }
_ => {}
}
} }
} }
} }
@ -40,13 +36,10 @@ fn view_binary(
lores_mode: bool, lores_mode: bool,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
if b.len() > 3 { if b.len() > 3 {
match (b[0], b[1], b[2]) { if let (0x4e, 0x45, 0x53) = (b[0], b[1], b[2]) {
(0x4e, 0x45, 0x53) => {
view_contents_interactive(b, source, lores_mode)?; view_contents_interactive(b, source, lores_mode)?;
return Ok(()); return Ok(());
} }
_ => {}
}
} }
view_contents(b, source, lores_mode)?; view_contents(b, source, lores_mode)?;
Ok(()) Ok(())
@ -156,8 +149,7 @@ impl RenderContext {
} }
} }
if prev_count > 0 { if prev_count > 0 {
match (prev_fg, prev_bg) { if let (Some(c), Some(d)) = (prev_fg, prev_bg) {
(Some(c), Some(d)) => {
print!( print!(
"{}", "{}",
ansi_term::Colour::RGB(c.0, c.1, c.2) ansi_term::Colour::RGB(c.0, c.1, c.2)
@ -165,8 +157,6 @@ impl RenderContext {
.paint((0..prev_count).map(|_| "").collect::<String>()) .paint((0..prev_count).map(|_| "").collect::<String>())
); );
} }
_ => {}
}
} }
outln!("{}", Attribute::Reset); outln!("{}", Attribute::Reset);
Ok(()) Ok(())
@ -205,40 +195,32 @@ struct RawImageBuffer {
buffer: Vec<u8>, buffer: Vec<u8>,
} }
fn load_from_png_buffer(buffer: &[u8]) -> Option<RawImageBuffer> { fn load_from_png_buffer(buffer: &[u8]) -> Result<RawImageBuffer, Box<dyn std::error::Error>> {
use image::ImageDecoder; use image::ImageDecoder;
let decoder = image::png::PNGDecoder::new(buffer); let decoder = image::png::PNGDecoder::new(buffer)?;
if decoder.is_err() {
return None;
}
let decoder = decoder.unwrap();
let dimensions = decoder.dimensions(); let dimensions = decoder.dimensions();
let colortype = decoder.colortype(); let colortype = decoder.colortype();
let buffer = decoder.read_image().unwrap(); let buffer = decoder.read_image()?;
Some(RawImageBuffer { Ok(RawImageBuffer {
dimensions, dimensions,
colortype, colortype,
buffer, buffer,
}) })
} }
fn load_from_jpg_buffer(buffer: &[u8]) -> Option<RawImageBuffer> { fn load_from_jpg_buffer(buffer: &[u8]) -> Result<RawImageBuffer, Box<dyn std::error::Error>> {
use image::ImageDecoder; use image::ImageDecoder;
let decoder = image::jpeg::JPEGDecoder::new(buffer); let decoder = image::jpeg::JPEGDecoder::new(buffer)?;
if decoder.is_err() {
return None;
}
let decoder = decoder.unwrap();
let dimensions = decoder.dimensions(); let dimensions = decoder.dimensions();
let colortype = decoder.colortype(); let colortype = decoder.colortype();
let buffer = decoder.read_image().unwrap(); let buffer = decoder.read_image()?;
Some(RawImageBuffer { Ok(RawImageBuffer {
dimensions, dimensions,
colortype, colortype,
buffer, buffer,
@ -252,16 +234,16 @@ pub fn view_contents(
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
let mut raw_image_buffer = load_from_png_buffer(buffer); let mut raw_image_buffer = load_from_png_buffer(buffer);
if raw_image_buffer.is_none() { if raw_image_buffer.is_err() {
raw_image_buffer = load_from_jpg_buffer(buffer); raw_image_buffer = load_from_jpg_buffer(buffer);
} }
if raw_image_buffer.is_none() { if raw_image_buffer.is_err() {
//Not yet supported //Not yet supported
outln!("{:?}", buffer.hex_dump()); outln!("{:?}", buffer.hex_dump());
return Ok(()); return Ok(());
} }
let raw_image_buffer = raw_image_buffer.unwrap(); let raw_image_buffer = raw_image_buffer?;
let mut render_context: RenderContext = RenderContext::blank(lores_mode); let mut render_context: RenderContext = RenderContext::blank(lores_mode);
let _ = render_context.update(); let _ = render_context.update();
@ -274,7 +256,7 @@ pub fn view_contents(
raw_image_buffer.dimensions.1 as u32, raw_image_buffer.dimensions.1 as u32,
raw_image_buffer.buffer, raw_image_buffer.buffer,
) )
.unwrap(); .ok_or("Cannot convert image data")?;
let resized_img = image::imageops::resize( let resized_img = image::imageops::resize(
&img, &img,
@ -297,7 +279,7 @@ pub fn view_contents(
raw_image_buffer.dimensions.1 as u32, raw_image_buffer.dimensions.1 as u32,
raw_image_buffer.buffer, raw_image_buffer.buffer,
) )
.unwrap(); .ok_or("Cannot convert image data")?;
let resized_img = image::imageops::resize( let resized_img = image::imageops::resize(
&img, &img,
@ -384,8 +366,8 @@ pub fn view_contents_interactive(
let image_buffer = nes.image_buffer(); let image_buffer = nes.image_buffer();
let slice = unsafe { std::slice::from_raw_parts(image_buffer, 256 * 240 * 4) }; let slice = unsafe { std::slice::from_raw_parts(image_buffer, 256 * 240 * 4) };
let img = let img = image::ImageBuffer::<image::Rgba<u8>, &[u8]>::from_raw(256, 240, slice)
image::ImageBuffer::<image::Rgba<u8>, &[u8]>::from_raw(256, 240, slice).unwrap(); .ok_or("Cannot convert image data")?;
let resized_img = image::imageops::resize( let resized_img = image::imageops::resize(
&img, &img,
render_context.width as u32, render_context.width as u32,
@ -408,11 +390,11 @@ pub fn view_contents_interactive(
if rawkey.is_pressed(rawkey::KeyCode::Escape) { if rawkey.is_pressed(rawkey::KeyCode::Escape) {
break 'gameloop; break 'gameloop;
} else { } else {
for i in 0..buttons.len() { for (idx, button) in buttons.iter().enumerate() {
if rawkey.is_pressed(buttons[i]) { if rawkey.is_pressed(*button) {
nes.press_button(0, i as u8); nes.press_button(0, idx as u8);
} else { } else {
nes.release_button(0, i as u8); nes.release_button(0, idx as u8);
} }
} }
} }

View File

@ -1,20 +1,19 @@
[package] [package]
name = "nu_plugin_fetch" name = "nu_plugin_fetch"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "A URL fetch plugin for Nushell" description = "A URL fetch plugin for Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.7.0" } nu-plugin = { path = "../nu-plugin", version="0.8.0" }
nu-source = { path = "../nu-source", version = "0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.8.0" }
futures-preview = { version = "=0.3.0-alpha.19", features = ["compat", "io-compat"] } futures-preview = { version = "=0.3.0-alpha.19", features = ["compat", "io-compat"] }
surf = "1.0.3" surf = "1.0.3"
url = "2.1.0" url = "2.1.0"
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }

View File

@ -1,9 +1,10 @@
use futures::executor::block_on; use futures::executor::block_on;
use mime::Mime; use mime::Mime;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_plugin::{serve_plugin, Plugin};
use nu_protocol::{ use nu_protocol::{
serve_plugin, CallInfo, CommandAction, Plugin, ReturnSuccess, ReturnValue, Signature, CallInfo, CommandAction, ReturnSuccess, ReturnValue, Signature, SyntaxShape, UntaggedValue,
SyntaxShape, UntaggedValue, Value, Value,
}; };
use nu_source::{AnchorLocation, Span, Tag}; use nu_source::{AnchorLocation, Span, Tag};
use std::path::PathBuf; use std::path::PathBuf;
@ -62,7 +63,13 @@ impl Plugin for Fetch {
fn filter(&mut self, value: Value) -> Result<Vec<ReturnValue>, ShellError> { fn filter(&mut self, value: Value) -> Result<Vec<ReturnValue>, ShellError> {
Ok(vec![block_on(fetch_helper( Ok(vec![block_on(fetch_helper(
&self.path.clone().unwrap(), &self.path.clone().ok_or_else(|| {
ShellError::labeled_error(
"internal error: path not set",
"path not set",
&value.tag,
)
})?,
self.has_raw, self.has_raw,
value, value,
))]) ))])
@ -92,25 +99,25 @@ async fn fetch_helper(path: &Value, has_raw: bool, row: Value) -> ReturnValue {
if let Err(e) = result { if let Err(e) = result {
return Err(e); return Err(e);
} }
let (file_extension, contents, contents_tag) = result.unwrap(); let (file_extension, contents, contents_tag) = result?;
let file_extension = if has_raw { let file_extension = if has_raw {
None None
} else { } else {
// If the extension could not be determined via mimetype, try to use the path // If the extension could not be determined via mimetype, try to use the path
// extension. Some file types do not declare their mimetypes (such as bson files). // extension. Some file types do not declare their mimetypes (such as bson files).
file_extension.or(path_str.split('.').last().map(String::from)) file_extension.or_else(|| path_str.split('.').last().map(String::from))
}; };
let tagged_contents = contents.retag(&contents_tag); let tagged_contents = contents.retag(&contents_tag);
if let Some(extension) = file_extension { if let Some(extension) = file_extension {
return Ok(ReturnSuccess::Action(CommandAction::AutoConvert( Ok(ReturnSuccess::Action(CommandAction::AutoConvert(
tagged_contents, tagged_contents,
extension, extension,
))); )))
} else { } else {
return ReturnSuccess::value(tagged_contents); ReturnSuccess::value(tagged_contents)
} }
} }
@ -130,7 +137,13 @@ pub async fn fetch(
match response { match response {
Ok(mut r) => match r.headers().get("content-type") { Ok(mut r) => match r.headers().get("content-type") {
Some(content_type) => { Some(content_type) => {
let content_type = Mime::from_str(content_type).unwrap(); let content_type = Mime::from_str(content_type).map_err(|_| {
ShellError::labeled_error(
format!("MIME type unknown: {}", content_type),
"given unknown MIME type",
span,
)
})?;
match (content_type.type_(), content_type.subtype()) { match (content_type.type_(), content_type.subtype()) {
(mime::APPLICATION, mime::XML) => Ok(( (mime::APPLICATION, mime::XML) => Ok((
Some("xml".to_string()), Some("xml".to_string()),
@ -224,7 +237,13 @@ pub async fn fetch(
)), )),
(mime::TEXT, mime::PLAIN) => { (mime::TEXT, mime::PLAIN) => {
let path_extension = url::Url::parse(location) let path_extension = url::Url::parse(location)
.unwrap() .map_err(|_| {
ShellError::labeled_error(
format!("Cannot parse URL: {}", location),
"cannot parse",
span,
)
})?
.path_segments() .path_segments()
.and_then(|segments| segments.last()) .and_then(|segments| segments.last())
.and_then(|name| if name.is_empty() { None } else { Some(name) }) .and_then(|name| if name.is_empty() { None } else { Some(name) })

View File

@ -1,20 +1,21 @@
[package] [package]
name = "nu_plugin_inc" name = "nu_plugin_inc"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "A version incrementer plugin for Nushell" description = "A version incrementer plugin for Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib]
doctest = false
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.7.0" } nu-plugin = { path = "../nu-plugin", version="0.8.0" }
nu-source = { path = "../nu-source", version = "0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.8.0" }
nu-value-ext = { path = "../nu-value-ext", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.8.0" }
nu-value-ext = { path = "../nu-value-ext", version = "0.8.0" }
semver = "0.9.0" semver = "0.9.0"
indexmap = "1.3.0"
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }

View File

@ -0,0 +1,178 @@
use nu_errors::ShellError;
use nu_protocol::{did_you_mean, ColumnPath, Primitive, ShellTypeName, UntaggedValue, Value};
use nu_source::{span_for_spanned_list, HasSpan, SpannedItem, Tagged};
use nu_value_ext::ValueExt;
#[derive(Debug, Eq, PartialEq)]
pub enum Action {
SemVerAction(SemVerAction),
Default,
}
#[derive(Debug, Eq, PartialEq)]
pub enum SemVerAction {
Major,
Minor,
Patch,
}
#[derive(Default)]
pub struct Inc {
pub field: Option<Tagged<ColumnPath>>,
pub error: Option<String>,
pub action: Option<Action>,
}
impl Inc {
pub fn new() -> Self {
Default::default()
}
fn apply(&self, input: &str) -> Result<UntaggedValue, ShellError> {
let applied = match &self.action {
Some(Action::SemVerAction(act_on)) => {
let mut ver = match semver::Version::parse(&input) {
Ok(parsed_ver) => parsed_ver,
Err(_) => return Ok(UntaggedValue::string(input.to_string())),
};
match act_on {
SemVerAction::Major => ver.increment_major(),
SemVerAction::Minor => ver.increment_minor(),
SemVerAction::Patch => ver.increment_patch(),
}
UntaggedValue::string(ver.to_string())
}
Some(Action::Default) | None => match input.parse::<u64>() {
Ok(v) => UntaggedValue::string(format!("{}", v + 1)),
Err(_) => UntaggedValue::string(input),
},
};
Ok(applied)
}
pub fn for_semver(&mut self, part: SemVerAction) {
if self.permit() {
self.action = Some(Action::SemVerAction(part));
} else {
self.log_error("can only apply one");
}
}
fn permit(&mut self) -> bool {
self.action.is_none()
}
fn log_error(&mut self, message: &str) {
self.error = Some(message.to_string());
}
pub fn usage() -> &'static str {
"Usage: inc field [--major|--minor|--patch]"
}
pub fn inc(&self, value: Value) -> Result<Value, ShellError> {
match &value.value {
UntaggedValue::Primitive(Primitive::Int(i)) => {
Ok(UntaggedValue::int(i + 1).into_value(value.tag()))
}
UntaggedValue::Primitive(Primitive::Bytes(b)) => {
Ok(UntaggedValue::bytes(b + 1 as u64).into_value(value.tag()))
}
UntaggedValue::Primitive(Primitive::String(ref s)) => {
Ok(self.apply(&s)?.into_value(value.tag()))
}
UntaggedValue::Table(values) => {
if values.len() == 1 {
Ok(UntaggedValue::Table(vec![self.inc(values[0].clone())?])
.into_value(value.tag()))
} else {
Err(ShellError::type_error(
"incrementable value",
value.type_name().spanned(value.span()),
))
}
}
UntaggedValue::Row(_) => match self.field {
Some(ref f) => {
let fields = f.clone();
let replace_for = value.get_data_by_column_path(
&f,
Box::new(move |(obj_source, column_path_tried, _)| {
match did_you_mean(&obj_source, &column_path_tried) {
Some(suggestions) => ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", suggestions[0].1),
span_for_spanned_list(fields.iter().map(|p| p.span)),
),
None => ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
span_for_spanned_list(fields.iter().map(|p| p.span)),
),
}
}),
);
let got = replace_for?;
let replacement = self.inc(got)?;
match value
.replace_data_at_column_path(&f, replacement.value.into_untagged_value())
{
Some(v) => Ok(v),
None => Err(ShellError::labeled_error(
"inc could not find field to replace",
"column name",
value.tag(),
)),
}
}
None => Err(ShellError::untagged_runtime_error(
"inc needs a field when incrementing a column in a table",
)),
},
_ => Err(ShellError::type_error(
"incrementable value",
value.type_name().spanned(value.span()),
)),
}
}
}
#[cfg(test)]
mod tests {
mod semver {
use crate::inc::SemVerAction;
use crate::Inc;
use nu_plugin::test_helpers::value::string;
#[test]
fn major() -> Result<(), Box<dyn std::error::Error>> {
let mut inc = Inc::new();
inc.for_semver(SemVerAction::Major);
assert_eq!(inc.apply("0.1.3")?, string("1.0.0").value);
Ok(())
}
#[test]
fn minor() -> Result<(), Box<dyn std::error::Error>> {
let mut inc = Inc::new();
inc.for_semver(SemVerAction::Minor);
assert_eq!(inc.apply("0.1.3")?, string("0.2.0").value);
Ok(())
}
#[test]
fn patch() -> Result<(), Box<dyn std::error::Error>> {
let mut inc = Inc::new();
inc.for_semver(SemVerAction::Patch);
assert_eq!(inc.apply("0.1.3")?, string("0.1.4").value);
Ok(())
}
}
}

View File

@ -0,0 +1,38 @@
mod inc;
mod nu_plugin_inc;
pub use inc::Inc;
#[cfg(test)]
mod tests {
use super::Inc;
use crate::inc::Action;
use nu_protocol::Value;
use nu_value_ext::ValueExt;
impl Inc {
pub fn expect_action(&self, action: Action) {
match &self.action {
Some(set) if set == &action => {}
Some(other) => panic!(format!("\nExpected {:#?}\n\ngot {:#?}", action, other)),
None => panic!(format!("\nAction {:#?} not found.", action)),
}
}
pub fn expect_field(&self, field: Value) {
let field = match field.as_column_path() {
Ok(column_path) => column_path,
Err(reason) => panic!(format!(
"\nExpected {:#?} to be a ColumnPath, \n\ngot {:#?}",
field, reason
)),
};
match &self.field {
Some(column_path) if column_path == &field => {}
Some(other) => panic!(format!("\nExpected {:#?} \n\ngot {:#?}", field, other)),
None => panic!(format!("\nField {:#?} not found.", field)),
}
}
}
}

View File

@ -1,456 +1,6 @@
use nu_errors::ShellError; use nu_plugin::serve_plugin;
use nu_protocol::{ use nu_plugin_inc::Inc;
did_you_mean, serve_plugin, CallInfo, ColumnPath, Plugin, Primitive, ReturnSuccess,
ReturnValue, ShellTypeName, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::{span_for_spanned_list, HasSpan, SpannedItem, Tagged};
use nu_value_ext::ValueExt;
enum Action {
SemVerAction(SemVerAction),
Default,
}
pub enum SemVerAction {
Major,
Minor,
Patch,
}
struct Inc {
field: Option<Tagged<ColumnPath>>,
error: Option<String>,
action: Option<Action>,
}
impl Inc {
fn new() -> Inc {
Inc {
field: None,
error: None,
action: None,
}
}
fn apply(&self, input: &str) -> Result<UntaggedValue, ShellError> {
let applied = match &self.action {
Some(Action::SemVerAction(act_on)) => {
let mut ver = match semver::Version::parse(&input) {
Ok(parsed_ver) => parsed_ver,
Err(_) => return Ok(UntaggedValue::string(input.to_string())),
};
match act_on {
SemVerAction::Major => ver.increment_major(),
SemVerAction::Minor => ver.increment_minor(),
SemVerAction::Patch => ver.increment_patch(),
}
UntaggedValue::string(ver.to_string())
}
Some(Action::Default) | None => match input.parse::<u64>() {
Ok(v) => UntaggedValue::string(format!("{}", v + 1)),
Err(_) => UntaggedValue::string(input),
},
};
Ok(applied)
}
fn for_semver(&mut self, part: SemVerAction) {
if self.permit() {
self.action = Some(Action::SemVerAction(part));
} else {
self.log_error("can only apply one");
}
}
fn permit(&mut self) -> bool {
self.action.is_none()
}
fn log_error(&mut self, message: &str) {
self.error = Some(message.to_string());
}
pub fn usage() -> &'static str {
"Usage: inc field [--major|--minor|--patch]"
}
fn inc(&self, value: Value) -> Result<Value, ShellError> {
match &value.value {
UntaggedValue::Primitive(Primitive::Int(i)) => {
Ok(UntaggedValue::int(i + 1).into_value(value.tag()))
}
UntaggedValue::Primitive(Primitive::Bytes(b)) => {
Ok(UntaggedValue::bytes(b + 1 as u64).into_value(value.tag()))
}
UntaggedValue::Primitive(Primitive::String(ref s)) => {
Ok(self.apply(&s)?.into_value(value.tag()))
}
UntaggedValue::Table(values) => {
if values.len() == 1 {
Ok(UntaggedValue::Table(vec![self.inc(values[0].clone())?])
.into_value(value.tag()))
} else {
Err(ShellError::type_error(
"incrementable value",
value.type_name().spanned(value.span()),
))
}
}
UntaggedValue::Row(_) => match self.field {
Some(ref f) => {
let fields = f.clone();
let replace_for = value.get_data_by_column_path(
&f,
Box::new(move |(obj_source, column_path_tried, _)| {
match did_you_mean(&obj_source, &column_path_tried) {
Some(suggestions) => ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", suggestions[0].1),
span_for_spanned_list(fields.iter().map(|p| p.span)),
),
None => ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
span_for_spanned_list(fields.iter().map(|p| p.span)),
),
}
}),
);
let got = replace_for?;
let replacement = self.inc(got.clone())?;
match value.replace_data_at_column_path(
&f,
replacement.value.clone().into_untagged_value(),
) {
Some(v) => Ok(v),
None => Err(ShellError::labeled_error(
"inc could not find field to replace",
"column name",
value.tag(),
)),
}
}
None => Err(ShellError::untagged_runtime_error(
"inc needs a field when incrementing a column in a table",
)),
},
_ => Err(ShellError::type_error(
"incrementable value",
value.type_name().spanned(value.span()),
)),
}
}
}
impl Plugin for Inc {
fn config(&mut self) -> Result<Signature, ShellError> {
Ok(Signature::build("inc")
.desc("Increment a value or version. Optionally use the column of a table.")
.switch("major", "increment the major version (eg 1.2.1 -> 2.0.0)")
.switch("minor", "increment the minor version (eg 1.2.1 -> 1.3.0)")
.switch("patch", "increment the patch version (eg 1.2.1 -> 1.2.2)")
.rest(SyntaxShape::ColumnPath, "the column(s) to update")
.filter())
}
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
if call_info.args.has("major") {
self.for_semver(SemVerAction::Major);
}
if call_info.args.has("minor") {
self.for_semver(SemVerAction::Minor);
}
if call_info.args.has("patch") {
self.for_semver(SemVerAction::Patch);
}
if let Some(args) = call_info.args.positional {
for arg in args {
match arg {
table @ Value {
value: UntaggedValue::Primitive(Primitive::ColumnPath(_)),
..
} => {
self.field = Some(table.as_column_path()?);
}
value => {
return Err(ShellError::type_error(
"table",
value.type_name().spanned(value.span()),
))
}
}
}
}
if self.action.is_none() {
self.action = Some(Action::Default);
}
match &self.error {
Some(reason) => Err(ShellError::untagged_runtime_error(format!(
"{}: {}",
reason,
Inc::usage()
))),
None => Ok(vec![]),
}
}
fn filter(&mut self, input: Value) -> Result<Vec<ReturnValue>, ShellError> {
Ok(vec![ReturnSuccess::value(self.inc(input)?)])
}
}
fn main() { fn main() {
serve_plugin(&mut Inc::new()); serve_plugin(&mut Inc::new())
}
#[cfg(test)]
mod tests {
use super::{Inc, SemVerAction};
use indexmap::IndexMap;
use nu_protocol::{
CallInfo, EvaluatedArgs, PathMember, ReturnSuccess, UnspannedPathMember, UntaggedValue,
Value,
};
use nu_protocol::{Plugin, TaggedDictBuilder};
use nu_source::{Span, Tag};
struct CallStub {
positionals: Vec<Value>,
flags: IndexMap<String, Value>,
}
impl CallStub {
fn new() -> CallStub {
CallStub {
positionals: vec![],
flags: indexmap::IndexMap::new(),
}
}
fn with_long_flag(&mut self, name: &str) -> &mut Self {
self.flags.insert(
name.to_string(),
UntaggedValue::boolean(true).into_value(Tag::unknown()),
);
self
}
fn with_parameter(&mut self, name: &str) -> &mut Self {
let fields: Vec<PathMember> = name
.split(".")
.map(|s| {
UnspannedPathMember::String(s.to_string()).into_path_member(Span::unknown())
})
.collect();
self.positionals
.push(UntaggedValue::column_path(fields).into_untagged_value());
self
}
fn create(&self) -> CallInfo {
CallInfo {
args: EvaluatedArgs::new(Some(self.positionals.clone()), Some(self.flags.clone())),
name_tag: Tag::unknown(),
}
}
}
fn cargo_sample_record(with_version: &str) -> Value {
let mut package = TaggedDictBuilder::new(Tag::unknown());
package.insert_untagged("version", UntaggedValue::string(with_version));
package.into_value()
}
#[test]
fn inc_plugin_configuration_flags_wired() {
let mut plugin = Inc::new();
let configured = plugin.config().expect("Can not configure plugin");
for action_flag in &["major", "minor", "patch"] {
assert!(configured.named.get(*action_flag).is_some());
}
}
#[test]
fn inc_plugin_accepts_major() {
let mut plugin = Inc::new();
assert!(plugin
.begin_filter(CallStub::new().with_long_flag("major").create())
.is_ok());
assert!(plugin.action.is_some());
}
#[test]
fn inc_plugin_accepts_minor() {
let mut plugin = Inc::new();
assert!(plugin
.begin_filter(CallStub::new().with_long_flag("minor").create())
.is_ok());
assert!(plugin.action.is_some());
}
#[test]
fn inc_plugin_accepts_patch() {
let mut plugin = Inc::new();
assert!(plugin
.begin_filter(CallStub::new().with_long_flag("patch").create())
.is_ok());
assert!(plugin.action.is_some());
}
#[test]
fn inc_plugin_accepts_only_one_action() {
let mut plugin = Inc::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_long_flag("major")
.with_long_flag("minor")
.create(),
)
.is_err());
assert_eq!(plugin.error, Some("can only apply one".to_string()));
}
#[test]
fn inc_plugin_accepts_field() {
let mut plugin = Inc::new();
assert!(plugin
.begin_filter(CallStub::new().with_parameter("package.version").create())
.is_ok());
assert_eq!(
plugin
.field
.map(|f| f.iter().map(|f| f.unspanned.clone()).collect()),
Some(vec![
UnspannedPathMember::String("package".to_string()),
UnspannedPathMember::String("version".to_string())
])
);
}
#[test]
fn incs_major() {
let mut inc = Inc::new();
inc.for_semver(SemVerAction::Major);
assert_eq!(inc.apply("0.1.3").unwrap(), UntaggedValue::string("1.0.0"));
}
#[test]
fn incs_minor() {
let mut inc = Inc::new();
inc.for_semver(SemVerAction::Minor);
assert_eq!(inc.apply("0.1.3").unwrap(), UntaggedValue::string("0.2.0"));
}
#[test]
fn incs_patch() {
let mut inc = Inc::new();
inc.for_semver(SemVerAction::Patch);
assert_eq!(inc.apply("0.1.3").unwrap(), UntaggedValue::string("0.1.4"));
}
#[test]
fn inc_plugin_applies_major() {
let mut plugin = Inc::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_long_flag("major")
.with_parameter("version")
.create()
)
.is_ok());
let subject = cargo_sample_record("0.1.3");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Row(o),
..
}) => assert_eq!(
*o.get_data(&String::from("version")).borrow(),
UntaggedValue::string(String::from("1.0.0")).into_untagged_value()
),
_ => {}
}
}
#[test]
fn inc_plugin_applies_minor() {
let mut plugin = Inc::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_long_flag("minor")
.with_parameter("version")
.create()
)
.is_ok());
let subject = cargo_sample_record("0.1.3");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Row(o),
..
}) => assert_eq!(
*o.get_data(&String::from("version")).borrow(),
UntaggedValue::string(String::from("0.2.0")).into_untagged_value()
),
_ => {}
}
}
#[test]
fn inc_plugin_applies_patch() {
let field = String::from("version");
let mut plugin = Inc::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_long_flag("patch")
.with_parameter(&field)
.create()
)
.is_ok());
let subject = cargo_sample_record("0.1.3");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Row(o),
..
}) => assert_eq!(
*o.get_data(&field).borrow(),
UntaggedValue::string(String::from("0.1.4")).into_untagged_value()
),
_ => {}
}
}
} }

View File

@ -0,0 +1,73 @@
#[cfg(test)]
mod tests;
use crate::inc::{Action, SemVerAction};
use crate::Inc;
use nu_errors::ShellError;
use nu_plugin::Plugin;
use nu_protocol::{
CallInfo, Primitive, ReturnSuccess, ReturnValue, ShellTypeName, Signature, SyntaxShape,
UntaggedValue, Value,
};
use nu_source::{HasSpan, SpannedItem};
use nu_value_ext::ValueExt;
impl Plugin for Inc {
fn config(&mut self) -> Result<Signature, ShellError> {
Ok(Signature::build("inc")
.desc("Increment a value or version. Optionally use the column of a table.")
.switch("major", "increment the major version (eg 1.2.1 -> 2.0.0)")
.switch("minor", "increment the minor version (eg 1.2.1 -> 1.3.0)")
.switch("patch", "increment the patch version (eg 1.2.1 -> 1.2.2)")
.rest(SyntaxShape::ColumnPath, "the column(s) to update")
.filter())
}
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
if call_info.args.has("major") {
self.for_semver(SemVerAction::Major);
}
if call_info.args.has("minor") {
self.for_semver(SemVerAction::Minor);
}
if call_info.args.has("patch") {
self.for_semver(SemVerAction::Patch);
}
if let Some(args) = call_info.args.positional {
for arg in args {
match arg {
table @ Value {
value: UntaggedValue::Primitive(Primitive::ColumnPath(_)),
..
} => {
self.field = Some(table.as_column_path()?);
}
value => {
return Err(ShellError::type_error(
"table",
value.type_name().spanned(value.span()),
))
}
}
}
}
if self.action.is_none() {
self.action = Some(Action::Default);
}
match &self.error {
Some(reason) => Err(ShellError::untagged_runtime_error(format!(
"{}: {}",
reason,
Inc::usage()
))),
None => Ok(vec![]),
}
}
fn filter(&mut self, input: Value) -> Result<Vec<ReturnValue>, ShellError> {
Ok(vec![ReturnSuccess::value(self.inc(input)?)])
}
}

View File

@ -0,0 +1,135 @@
mod integration {
use crate::inc::{Action, SemVerAction};
use crate::Inc;
use nu_errors::ShellError;
use nu_plugin::test_helpers::value::{column_path, string};
use nu_plugin::test_helpers::{plugin, CallStub};
#[test]
fn picks_up_one_action_flag_only() {
plugin(&mut Inc::new())
.args(
CallStub::new()
.with_long_flag("major")
.with_long_flag("minor")
.create(),
)
.setup(|plugin, returned_values| {
let actual = format!("{}", returned_values.unwrap_err());
assert!(actual.contains("can only apply one"));
assert_eq!(plugin.error, Some("can only apply one".to_string()));
});
}
#[test]
fn picks_up_major_flag() {
plugin(&mut Inc::new())
.args(CallStub::new().with_long_flag("major").create())
.setup(|plugin, _| {
let sem_version_part = SemVerAction::Major;
plugin.expect_action(Action::SemVerAction(sem_version_part))
});
}
#[test]
fn picks_up_minor_flag() {
plugin(&mut Inc::new())
.args(CallStub::new().with_long_flag("minor").create())
.setup(|plugin, _| {
let sem_version_part = SemVerAction::Minor;
plugin.expect_action(Action::SemVerAction(sem_version_part))
});
}
#[test]
fn picks_up_patch_flag() {
plugin(&mut Inc::new())
.args(CallStub::new().with_long_flag("patch").create())
.setup(|plugin, _| {
let sem_version_part = SemVerAction::Patch;
plugin.expect_action(Action::SemVerAction(sem_version_part))
});
}
#[test]
fn picks_up_argument_for_field() -> Result<(), ShellError> {
plugin(&mut Inc::new())
.args(CallStub::new().with_parameter("package.version")?.create())
.setup(|plugin, _| {
//FIXME: this will need to be updated
if let Ok(column_path) = column_path(&[string("package"), string("version")]) {
plugin.expect_field(column_path)
}
});
Ok(())
}
mod sem_ver {
use crate::Inc;
use nu_errors::ShellError;
use nu_plugin::test_helpers::value::{get_data, string, structured_sample_record};
use nu_plugin::test_helpers::{expect_return_value_at, plugin, CallStub};
fn cargo_sample_record(with_version: &str) -> nu_protocol::Value {
structured_sample_record("version", with_version)
}
#[test]
fn major_input_using_the_field_passed_as_parameter() -> Result<(), ShellError> {
let run = plugin(&mut Inc::new())
.args(
CallStub::new()
.with_long_flag("major")
.with_parameter("version")?
.create(),
)
.input(cargo_sample_record("0.1.3"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(get_data(actual, "version"), string("1.0.0"));
Ok(())
}
#[test]
fn minor_input_using_the_field_passed_as_parameter() -> Result<(), ShellError> {
let run = plugin(&mut Inc::new())
.args(
CallStub::new()
.with_long_flag("minor")
.with_parameter("version")?
.create(),
)
.input(cargo_sample_record("0.1.3"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(get_data(actual, "version"), string("0.2.0"));
Ok(())
}
#[test]
fn patch_input_using_the_field_passed_as_parameter() -> Result<(), ShellError> {
let run = plugin(&mut Inc::new())
.args(
CallStub::new()
.with_long_flag("patch")
.with_parameter("version")?
.create(),
)
.input(cargo_sample_record("0.1.3"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(get_data(actual, "version"), string("0.1.4"));
Ok(())
}
}
}

View File

@ -1,19 +1,18 @@
[package] [package]
name = "nu_plugin_match" name = "nu_plugin_match"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "A regex match plugin for Nushell" description = "A regex match plugin for Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.7.0" } nu-plugin = { path = "../nu-plugin", version="0.8.0" }
nu-source = { path = "../nu-source", version = "0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.8.0" }
futures-preview = { version = "=0.3.0-alpha.19", features = ["compat", "io-compat"] } futures-preview = { version = "=0.3.0-alpha.19", features = ["compat", "io-compat"] }
regex = "1" regex = "1"
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }

View File

@ -1,7 +1,7 @@
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_plugin::{serve_plugin, Plugin};
use nu_protocol::{ use nu_protocol::{
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, Signature, SyntaxShape, CallInfo, Primitive, ReturnSuccess, ReturnValue, Signature, SyntaxShape, UntaggedValue, Value,
UntaggedValue, Value,
}; };
use regex::Regex; use regex::Regex;
@ -12,11 +12,12 @@ struct Match {
} }
impl Match { impl Match {
fn new() -> Self { #[allow(clippy::trivial_regex)]
Match { fn new() -> Result<Self, Box<dyn std::error::Error>> {
Ok(Match {
column: String::new(), column: String::new(),
regex: Regex::new("").unwrap(), regex: Regex::new("")?,
} })
} }
} }
@ -48,14 +49,20 @@ impl Plugin for Match {
match &args[1] { match &args[1] {
Value { Value {
value: UntaggedValue::Primitive(Primitive::String(s)), value: UntaggedValue::Primitive(Primitive::String(s)),
.. tag,
} => { } => {
self.regex = Regex::new(s).unwrap(); self.regex = Regex::new(s).map_err(|_| {
ShellError::labeled_error(
"Internal error while creating regex",
"internal error created by pattern",
tag,
)
})?;
} }
Value { tag, .. } => { Value { tag, .. } => {
return Err(ShellError::labeled_error( return Err(ShellError::labeled_error(
"Unrecognized type in params", "Unrecognized type in params",
"value", "unexpected value",
tag, tag,
)); ));
} }
@ -101,6 +108,7 @@ impl Plugin for Match {
} }
} }
fn main() { fn main() -> Result<(), Box<dyn std::error::Error>> {
serve_plugin(&mut Match::new()); serve_plugin(&mut Match::new()?);
Ok(())
} }

View File

@ -1,17 +1,16 @@
[package] [package]
name = "nu_plugin_post" name = "nu_plugin_post"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "An HTTP post plugin for Nushell" description = "An HTTP post plugin for Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.7.0" } nu-plugin = { path = "../nu-plugin", version="0.8.0" }
nu-source = { path = "../nu-source", version = "0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.8.0" }
futures-preview = { version = "=0.3.0-alpha.19", features = ["compat", "io-compat"] } futures-preview = { version = "=0.3.0-alpha.19", features = ["compat", "io-compat"] }
surf = "1.0.3" surf = "1.0.3"
url = "2.1.0" url = "2.1.0"
@ -20,4 +19,4 @@ base64 = "0.11"
num-traits = "0.2.10" num-traits = "0.2.10"
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }

View File

@ -2,9 +2,10 @@ use base64::encode;
use futures::executor::block_on; use futures::executor::block_on;
use mime::Mime; use mime::Mime;
use nu_errors::{CoerceInto, ShellError}; use nu_errors::{CoerceInto, ShellError};
use nu_plugin::{serve_plugin, Plugin};
use nu_protocol::{ use nu_protocol::{
serve_plugin, CallInfo, CommandAction, Plugin, Primitive, ReturnSuccess, ReturnValue, CallInfo, CommandAction, Primitive, ReturnSuccess, ReturnValue, Signature, SyntaxShape,
Signature, SyntaxShape, UnspannedPathMember, UntaggedValue, Value, UnspannedPathMember, UntaggedValue, Value,
}; };
use nu_source::{AnchorLocation, Tag, TaggedItem}; use nu_source::{AnchorLocation, Tag, TaggedItem};
use num_traits::cast::ToPrimitive; use num_traits::cast::ToPrimitive;
@ -25,6 +26,7 @@ struct Post {
user: Option<String>, user: Option<String>,
password: Option<String>, password: Option<String>,
headers: Vec<HeaderKind>, headers: Vec<HeaderKind>,
tag: Tag,
} }
impl Post { impl Post {
@ -36,6 +38,7 @@ impl Post {
user: None, user: None,
password: None, password: None,
headers: vec![], headers: vec![],
tag: Tag::unknown(),
} }
} }
@ -60,18 +63,20 @@ impl Post {
file => Some(file.clone()), file => Some(file.clone()),
}; };
self.user = call_info self.user = match call_info.args.get("user") {
.args Some(user) => Some(user.as_string()?),
.get("user") None => None,
.map(|x| x.as_string().unwrap().to_string()); };
self.password = call_info self.password = match call_info.args.get("password") {
.args Some(password) => Some(password.as_string()?),
.get("password") None => None,
.map(|x| x.as_string().unwrap().to_string()); };
self.headers = get_headers(&call_info)?; self.headers = get_headers(&call_info)?;
self.tag = call_info.name_tag;
ReturnSuccess::value(UntaggedValue::nothing().into_untagged_value()) ReturnSuccess::value(UntaggedValue::nothing().into_untagged_value())
} }
} }
@ -109,9 +114,13 @@ impl Plugin for Post {
fn filter(&mut self, row: Value) -> Result<Vec<ReturnValue>, ShellError> { fn filter(&mut self, row: Value) -> Result<Vec<ReturnValue>, ShellError> {
Ok(vec![block_on(post_helper( Ok(vec![block_on(post_helper(
&self.path.clone().unwrap(), &self.path.clone().ok_or_else(|| {
ShellError::labeled_error("expected a 'path'", "expected a 'path'", &self.tag)
})?,
self.has_raw, self.has_raw,
&self.body.clone().unwrap(), &self.body.clone().ok_or_else(|| {
ShellError::labeled_error("expected a 'body'", "expected a 'body'", &self.tag)
})?,
self.user.clone(), self.user.clone(),
self.password.clone(), self.password.clone(),
&self.headers.clone(), &self.headers.clone(),
@ -156,27 +165,25 @@ async fn post_helper(
}; };
let (file_extension, contents, contents_tag) = let (file_extension, contents, contents_tag) =
post(&path_str, &body, user, password, &headers, path_tag.clone()) post(&path_str, &body, user, password, &headers, path_tag.clone()).await?;
.await
.unwrap();
let file_extension = if has_raw { let file_extension = if has_raw {
None None
} else { } else {
// If the extension could not be determined via mimetype, try to use the path // If the extension could not be determined via mimetype, try to use the path
// extension. Some file types do not declare their mimetypes (such as bson files). // extension. Some file types do not declare their mimetypes (such as bson files).
file_extension.or(path_str.split('.').last().map(String::from)) file_extension.or_else(|| path_str.split('.').last().map(String::from))
}; };
let tagged_contents = contents.into_value(&contents_tag); let tagged_contents = contents.into_value(&contents_tag);
if let Some(extension) = file_extension { if let Some(extension) = file_extension {
return Ok(ReturnSuccess::Action(CommandAction::AutoConvert( Ok(ReturnSuccess::Action(CommandAction::AutoConvert(
tagged_contents, tagged_contents,
extension, extension,
))); )))
} else { } else {
return ReturnSuccess::value(tagged_contents); ReturnSuccess::value(tagged_contents)
} }
} }
@ -254,7 +261,13 @@ pub async fn post(
match response { match response {
Ok(mut r) => match r.headers().get("content-type") { Ok(mut r) => match r.headers().get("content-type") {
Some(content_type) => { Some(content_type) => {
let content_type = Mime::from_str(content_type).unwrap(); let content_type = Mime::from_str(content_type).map_err(|_| {
ShellError::labeled_error(
format!("Unknown MIME type: {}", content_type),
"unknown MIME type",
&tag,
)
})?;
match (content_type.type_(), content_type.subtype()) { match (content_type.type_(), content_type.subtype()) {
(mime::APPLICATION, mime::XML) => Ok(( (mime::APPLICATION, mime::XML) => Ok((
Some("xml".to_string()), Some("xml".to_string()),
@ -334,7 +347,13 @@ pub async fn post(
)), )),
(mime::TEXT, mime::PLAIN) => { (mime::TEXT, mime::PLAIN) => {
let path_extension = url::Url::parse(location) let path_extension = url::Url::parse(location)
.unwrap() .map_err(|_| {
ShellError::labeled_error(
format!("could not parse URL: {}", location),
"could not parse URL",
&tag,
)
})?
.path_segments() .path_segments()
.and_then(|segments| segments.last()) .and_then(|segments| segments.last())
.and_then(|name| if name.is_empty() { None } else { Some(name) }) .and_then(|name| if name.is_empty() { None } else { Some(name) })
@ -414,7 +433,13 @@ pub fn value_to_json_value(v: &Value) -> Result<serde_json::Value, ShellError> {
serde_json::Number::from_f64( serde_json::Number::from_f64(
f.to_f64().expect("TODO: What about really big decimals?"), f.to_f64().expect("TODO: What about really big decimals?"),
) )
.unwrap(), .ok_or_else(|| {
ShellError::labeled_error(
"Can not convert big decimal to f64",
"cannot convert big decimal to f64",
&v.tag,
)
})?,
), ),
UntaggedValue::Primitive(Primitive::Int(i)) => { UntaggedValue::Primitive(Primitive::Int(i)) => {
serde_json::Value::Number(serde_json::Number::from(CoerceInto::<i64>::coerce_into( serde_json::Value::Number(serde_json::Number::from(CoerceInto::<i64>::coerce_into(
@ -450,13 +475,22 @@ pub fn value_to_json_value(v: &Value) -> Result<serde_json::Value, ShellError> {
UntaggedValue::Block(_) | UntaggedValue::Primitive(Primitive::Range(_)) => { UntaggedValue::Block(_) | UntaggedValue::Primitive(Primitive::Range(_)) => {
serde_json::Value::Null serde_json::Value::Null
} }
UntaggedValue::Primitive(Primitive::Binary(b)) => serde_json::Value::Array( UntaggedValue::Primitive(Primitive::Binary(b)) => {
b.iter() let mut output = vec![];
.map(|x| {
serde_json::Value::Number(serde_json::Number::from_f64(*x as f64).unwrap()) for item in b.iter() {
}) output.push(serde_json::Value::Number(
.collect(), serde_json::Number::from_f64(*item as f64).ok_or_else(|| {
), ShellError::labeled_error(
"Cannot create number from from f64",
"cannot created number from f64",
&v.tag,
)
})?,
));
}
serde_json::Value::Array(output)
}
UntaggedValue::Row(o) => { UntaggedValue::Row(o) => {
let mut m = serde_json::Map::new(); let mut m = serde_json::Map::new();
for (k, v) in o.entries.iter() { for (k, v) in o.entries.iter() {
@ -467,7 +501,7 @@ pub fn value_to_json_value(v: &Value) -> Result<serde_json::Value, ShellError> {
}) })
} }
fn json_list(input: &Vec<Value>) -> Result<Vec<serde_json::Value>, ShellError> { fn json_list(input: &[Value]) -> Result<Vec<serde_json::Value>, ShellError> {
let mut out = vec![]; let mut out = vec![];
for value in input { for value in input {
@ -481,20 +515,22 @@ fn get_headers(call_info: &CallInfo) -> Result<Vec<HeaderKind>, ShellError> {
let mut headers = vec![]; let mut headers = vec![];
match extract_header_value(&call_info, "content-type") { match extract_header_value(&call_info, "content-type") {
Ok(h) => match h { Ok(h) => {
Some(ct) => headers.push(HeaderKind::ContentType(ct)), if let Some(ct) = h {
None => {} headers.push(HeaderKind::ContentType(ct))
}, }
}
Err(e) => { Err(e) => {
return Err(e); return Err(e);
} }
}; };
match extract_header_value(&call_info, "content-length") { match extract_header_value(&call_info, "content-length") {
Ok(h) => match h { Ok(h) => {
Some(cl) => headers.push(HeaderKind::ContentLength(cl)), if let Some(cl) = h {
None => {} headers.push(HeaderKind::ContentLength(cl))
}, }
}
Err(e) => { Err(e) => {
return Err(e); return Err(e);
} }

View File

@ -1,17 +1,16 @@
[package] [package]
name = "nu_plugin_ps" name = "nu_plugin_ps"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "A process list plugin for Nushell" description = "A process list plugin for Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.7.0" } nu-plugin = { path = "../nu-plugin", version="0.8.0" }
nu-source = { path = "../nu-source", version = "0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.8.0" }
futures-preview = { version = "=0.3.0-alpha.19", features = ["compat", "io-compat"] } futures-preview = { version = "=0.3.0-alpha.19", features = ["compat", "io-compat"] }
heim = "0.0.9" heim = "0.0.9"
@ -20,4 +19,4 @@ pin-utils = "0.1.0-alpha.4"
futures-util = "0.3.1" futures-util = "0.3.1"
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }

View File

@ -3,13 +3,13 @@ use futures::executor::block_on;
use futures_util::{StreamExt, TryStreamExt}; use futures_util::{StreamExt, TryStreamExt};
use heim::process::{self as process, Process, ProcessResult}; use heim::process::{self as process, Process, ProcessResult};
use heim::units::{ratio, Ratio}; use heim::units::{information, ratio, Ratio};
use std::usize; use std::usize;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_plugin::{serve_plugin, Plugin};
use nu_protocol::{ use nu_protocol::{
serve_plugin, CallInfo, Plugin, ReturnSuccess, ReturnValue, Signature, TaggedDictBuilder, CallInfo, ReturnSuccess, ReturnValue, Signature, TaggedDictBuilder, UntaggedValue, Value,
UntaggedValue, Value,
}; };
use nu_source::Tag; use nu_source::Tag;
@ -22,12 +22,14 @@ impl Ps {
} }
} }
async fn usage(process: Process) -> ProcessResult<(process::Process, Ratio)> { async fn usage(process: Process) -> ProcessResult<(process::Process, Ratio, process::Memory)> {
let usage_1 = process.cpu_usage().await?; let usage_1 = process.cpu_usage().await?;
futures_timer::Delay::new(Duration::from_millis(100)).await; futures_timer::Delay::new(Duration::from_millis(100)).await;
let usage_2 = process.cpu_usage().await?; let usage_2 = process.cpu_usage().await?;
Ok((process, usage_2 - usage_1)) let memory = process.memory().await?;
Ok((process, usage_2 - usage_1, memory))
} }
async fn ps(tag: Tag) -> Vec<Value> { async fn ps(tag: Tag) -> Vec<Value> {
@ -43,7 +45,7 @@ async fn ps(tag: Tag) -> Vec<Value> {
let mut output = vec![]; let mut output = vec![];
while let Some(res) = processes.next().await { while let Some(res) = processes.next().await {
if let Ok((process, usage)) = res { if let Ok((process, usage, memory)) = res {
let mut dict = TaggedDictBuilder::new(&tag); let mut dict = TaggedDictBuilder::new(&tag);
dict.insert_untagged("pid", UntaggedValue::int(process.pid())); dict.insert_untagged("pid", UntaggedValue::int(process.pid()));
if let Ok(name) = process.name().await { if let Ok(name) = process.name().await {
@ -53,6 +55,14 @@ async fn ps(tag: Tag) -> Vec<Value> {
dict.insert_untagged("status", UntaggedValue::string(format!("{:?}", status))); dict.insert_untagged("status", UntaggedValue::string(format!("{:?}", status)));
} }
dict.insert_untagged("cpu", UntaggedValue::decimal(usage.get::<ratio::percent>())); dict.insert_untagged("cpu", UntaggedValue::decimal(usage.get::<ratio::percent>()));
dict.insert_untagged(
"mem",
UntaggedValue::bytes(memory.rss().get::<information::byte>()),
);
dict.insert_untagged(
"virtual",
UntaggedValue::bytes(memory.vms().get::<information::byte>()),
);
output.push(dict.into_value()); output.push(dict.into_value());
} }
} }

View File

@ -1,22 +1,22 @@
[package] [package]
name = "nu_plugin_str" name = "nu_plugin_str"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "A string manipulation plugin for Nushell" description = "A string manipulation plugin for Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib]
doctest = false
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.7.0" } nu-plugin = { path = "../nu-plugin", version="0.8.0" }
nu-source = { path = "../nu-source", version = "0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.8.0" }
nu-value-ext = { path = "../nu-value-ext", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.8.0" }
nu-value-ext = { path = "../nu-value-ext", version = "0.8.0" }
regex = "1" regex = "1"
indexmap = "1.3.0"
num-bigint = "0.2.3" num-bigint = "0.2.3"
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }

View File

@ -0,0 +1,38 @@
mod nu_plugin_str;
mod strutils;
pub use strutils::Str;
#[cfg(test)]
mod tests {
use super::Str;
use crate::strutils::Action;
use nu_protocol::Value;
use nu_value_ext::ValueExt;
impl Str {
pub fn expect_action(&self, action: Action) {
match &self.action {
Some(set) if set == &action => {}
Some(other) => panic!(format!("\nExpected {:#?}\n\ngot {:#?}", action, other)),
None => panic!(format!("\nAction {:#?} not found.", action)),
}
}
pub fn expect_field(&self, field: Value) {
let field = match field.as_column_path() {
Ok(column_path) => column_path,
Err(reason) => panic!(format!(
"\nExpected {:#?} to be a ColumnPath, \n\ngot {:#?}",
field, reason
)),
};
match &self.field {
Some(column_path) if column_path == &field => {}
Some(other) => panic!(format!("\nExpected {:#?} \n\ngot {:#?}", field, other)),
None => panic!(format!("\nField {:#?} not found.", field)),
}
}
}
}

View File

@ -1,980 +1,6 @@
use nu_errors::ShellError; use nu_plugin::serve_plugin;
use nu_protocol::{ use nu_plugin_str::Str;
did_you_mean, serve_plugin, CallInfo, ColumnPath, Plugin, Primitive, ReturnSuccess,
ReturnValue, ShellTypeName, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::{span_for_spanned_list, Tagged};
use nu_value_ext::ValueExt;
use regex::Regex;
use std::cmp;
#[derive(Debug, Eq, PartialEq)]
enum Action {
Downcase,
Upcase,
ToInteger,
Substring(usize, usize),
Replace(ReplaceAction),
}
#[derive(Debug, Eq, PartialEq)]
enum ReplaceAction {
Direct(String),
FindAndReplace(String, String),
}
struct Str {
field: Option<Tagged<ColumnPath>>,
error: Option<String>,
action: Option<Action>,
}
impl Str {
fn new() -> Str {
Str {
field: None,
error: None,
action: None,
}
}
fn apply(&self, input: &str) -> Result<UntaggedValue, ShellError> {
let applied = match self.action.as_ref() {
Some(Action::Downcase) => UntaggedValue::string(input.to_ascii_lowercase()),
Some(Action::Upcase) => UntaggedValue::string(input.to_ascii_uppercase()),
Some(Action::Substring(s, e)) => {
let end: usize = cmp::min(*e, input.len());
let start: usize = *s;
if start > input.len() - 1 {
UntaggedValue::string("")
} else {
UntaggedValue::string(
&input
.chars()
.skip(start)
.take(end - start)
.collect::<String>(),
)
}
}
Some(Action::Replace(mode)) => match mode {
ReplaceAction::Direct(replacement) => UntaggedValue::string(replacement.as_str()),
ReplaceAction::FindAndReplace(find, replacement) => {
let regex = Regex::new(find.as_str());
match regex {
Ok(re) => UntaggedValue::string(
re.replace(input, replacement.as_str()).to_owned(),
),
Err(_) => UntaggedValue::string(input),
}
}
},
Some(Action::ToInteger) => match input.trim() {
other => match other.parse::<i64>() {
Ok(v) => UntaggedValue::int(v),
Err(_) => UntaggedValue::string(input),
},
},
None => UntaggedValue::string(input),
};
Ok(applied)
}
fn for_field(&mut self, column_path: Tagged<ColumnPath>) {
self.field = Some(column_path);
}
fn permit(&mut self) -> bool {
self.action.is_none()
}
fn log_error(&mut self, message: &str) {
self.error = Some(message.to_string());
}
fn for_to_int(&mut self) {
if self.permit() {
self.action = Some(Action::ToInteger);
} else {
self.log_error("can only apply one");
}
}
fn for_downcase(&mut self) {
if self.permit() {
self.action = Some(Action::Downcase);
} else {
self.log_error("can only apply one");
}
}
fn for_upcase(&mut self) {
if self.permit() {
self.action = Some(Action::Upcase);
} else {
self.log_error("can only apply one");
}
}
fn for_substring(&mut self, s: String) {
let v: Vec<&str> = s.split(',').collect();
let start: usize = match v[0] {
"" => 0,
_ => v[0].trim().parse().unwrap(),
};
let end: usize = match v[1] {
"" => usize::max_value(),
_ => v[1].trim().parse().unwrap(),
};
if start > end {
self.log_error("End must be greater than or equal to Start");
} else if self.permit() {
self.action = Some(Action::Substring(start, end));
} else {
self.log_error("can only apply one");
}
}
fn for_replace(&mut self, mode: ReplaceAction) {
if self.permit() {
self.action = Some(Action::Replace(mode));
} else {
self.log_error("can only apply one");
}
}
pub fn usage() -> &'static str {
"Usage: str field [--downcase|--upcase|--to-int|--substring \"start,end\"|--replace|--find-replace [pattern replacement]]]"
}
}
impl Str {
fn strutils(&self, value: Value) -> Result<Value, ShellError> {
match &value.value {
UntaggedValue::Primitive(Primitive::String(ref s)) => {
Ok(self.apply(&s)?.into_value(value.tag()))
}
UntaggedValue::Primitive(Primitive::Line(ref s)) => {
Ok(self.apply(&s)?.into_value(value.tag()))
}
UntaggedValue::Row(_) => match self.field {
Some(ref f) => {
let fields = f.clone();
let replace_for =
value.get_data_by_column_path(
&f,
Box::new(move |(obj_source, column_path_tried, error)| {
match did_you_mean(&obj_source, &column_path_tried) {
Some(suggestions) => ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", suggestions[0].1),
span_for_spanned_list(fields.iter().map(|p| p.span)),
),
None => error,
}
}),
);
let got = replace_for?;
let replacement = self.strutils(got.clone())?;
match value.replace_data_at_column_path(
&f,
replacement.value.clone().into_untagged_value(),
) {
Some(v) => Ok(v),
None => Err(ShellError::labeled_error(
"str could not find field to replace",
"column name",
value.tag(),
)),
}
}
None => Err(ShellError::untagged_runtime_error(format!(
"{}: {}",
"str needs a column when applied to a value in a row",
Str::usage()
))),
},
_ => Err(ShellError::labeled_error(
"Unrecognized type in stream",
value.type_name(),
value.tag,
)),
}
}
}
impl Plugin for Str {
fn config(&mut self) -> Result<Signature, ShellError> {
Ok(Signature::build("str")
.desc("Apply string function. Optional use the column of a table")
.switch("downcase", "convert string to lowercase")
.switch("upcase", "convert string to uppercase")
.switch("to-int", "convert string to integer")
.named("replace", SyntaxShape::String, "replaces the string")
.named(
"find-replace",
SyntaxShape::Any,
"finds and replaces [pattern replacement]",
)
.named(
"substring",
SyntaxShape::String,
"convert string to portion of original, requires \"start,end\"",
)
.rest(SyntaxShape::ColumnPath, "the column(s) to convert")
.filter())
}
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
let args = call_info.args;
if args.has("downcase") {
self.for_downcase();
}
if args.has("upcase") {
self.for_upcase();
}
if args.has("to-int") {
self.for_to_int();
}
if args.has("substring") {
if let Some(start_end) = args.get("substring") {
match start_end {
Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
..
} => {
self.for_substring(s.to_string());
}
_ => {
return Err(ShellError::labeled_error(
"Unrecognized type in params",
start_end.type_name(),
&start_end.tag,
))
}
}
}
}
if args.has("replace") {
if let Some(Value {
value: UntaggedValue::Primitive(Primitive::String(replacement)),
..
}) = args.get("replace")
{
self.for_replace(ReplaceAction::Direct(replacement.clone()));
}
}
if args.has("find-replace") {
if let Some(Value {
value: UntaggedValue::Table(arguments),
..
}) = args.get("find-replace")
{
self.for_replace(ReplaceAction::FindAndReplace(
arguments.get(0).unwrap().as_string()?.to_string(),
arguments.get(1).unwrap().as_string()?.to_string(),
));
}
}
if let Some(possible_field) = args.nth(0) {
let possible_field = possible_field.as_column_path()?;
self.for_field(possible_field);
}
match &self.error {
Some(reason) => Err(ShellError::untagged_runtime_error(format!(
"{}: {}",
reason,
Str::usage()
))),
None => Ok(vec![]),
}
}
fn filter(&mut self, input: Value) -> Result<Vec<ReturnValue>, ShellError> {
Ok(vec![ReturnSuccess::value(self.strutils(input)?)])
}
}
fn main() { fn main() {
serve_plugin(&mut Str::new()); serve_plugin(&mut Str::new())
}
#[cfg(test)]
mod tests {
use super::{Action, ReplaceAction, Str};
use indexmap::IndexMap;
use nu_protocol::{
CallInfo, EvaluatedArgs, Plugin, Primitive, ReturnSuccess, TaggedDictBuilder,
UntaggedValue, Value,
};
use nu_source::Tag;
use nu_value_ext::ValueExt;
use num_bigint::BigInt;
fn string(input: impl Into<String>) -> Value {
UntaggedValue::string(input.into()).into_untagged_value()
}
fn table(list: &Vec<Value>) -> Value {
UntaggedValue::table(list).into_untagged_value()
}
fn column_path(paths: &Vec<Value>) -> Value {
UntaggedValue::Primitive(Primitive::ColumnPath(
table(&paths.iter().cloned().collect())
.as_column_path()
.unwrap()
.item,
))
.into_untagged_value()
}
struct CallStub {
positionals: Vec<Value>,
flags: IndexMap<String, Value>,
}
impl CallStub {
fn new() -> CallStub {
CallStub {
positionals: vec![],
flags: indexmap::IndexMap::new(),
}
}
fn with_named_parameter(&mut self, name: &str, value: Value) -> &mut Self {
self.flags.insert(name.to_string(), value);
self
}
fn with_long_flag(&mut self, name: &str) -> &mut Self {
self.flags.insert(
name.to_string(),
UntaggedValue::boolean(true).into_value(Tag::unknown()),
);
self
}
fn with_parameter(&mut self, name: &str) -> &mut Self {
let fields: Vec<Value> = name
.split(".")
.map(|s| UntaggedValue::string(s.to_string()).into_value(Tag::unknown()))
.collect();
self.positionals.push(column_path(&fields));
self
}
fn create(&self) -> CallInfo {
CallInfo {
args: EvaluatedArgs::new(Some(self.positionals.clone()), Some(self.flags.clone())),
name_tag: Tag::unknown(),
}
}
}
fn structured_sample_record(key: &str, value: &str) -> Value {
let mut record = TaggedDictBuilder::new(Tag::unknown());
record.insert_untagged(key.clone(), UntaggedValue::string(value));
record.into_value()
}
fn unstructured_sample_record(value: &str) -> Value {
UntaggedValue::string(value).into_value(Tag::unknown())
}
#[test]
fn str_plugin_configuration_flags_wired() {
let mut plugin = Str::new();
let configured = plugin.config().unwrap();
for action_flag in &[
"downcase",
"upcase",
"to-int",
"substring",
"replace",
"find-replace",
] {
assert!(configured.named.get(*action_flag).is_some());
}
}
#[test]
fn str_plugin_accepts_downcase() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(CallStub::new().with_long_flag("downcase").create())
.is_ok());
assert_eq!(plugin.action.unwrap(), Action::Downcase);
}
#[test]
fn str_plugin_accepts_upcase() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(CallStub::new().with_long_flag("upcase").create())
.is_ok());
assert_eq!(plugin.action.unwrap(), Action::Upcase);
}
#[test]
fn str_plugin_accepts_to_int() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(CallStub::new().with_long_flag("to-int").create())
.is_ok());
assert_eq!(plugin.action.unwrap(), Action::ToInteger);
}
#[test]
fn str_plugin_accepts_replace() {
let mut plugin = Str::new();
let argument = String::from("replace_text");
assert!(plugin
.begin_filter(
CallStub::new()
.with_named_parameter("replace", string(&argument))
.create()
)
.is_ok());
match plugin.action {
Some(Action::Replace(ReplaceAction::Direct(replace_with))) => {
assert_eq!(replace_with, argument)
}
Some(_) | None => panic!("Din't accept."),
}
}
#[test]
fn str_plugin_accepts_find_replace() {
let mut plugin = Str::new();
let search_argument = String::from("kittens");
let replace_argument = String::from("jotandrehuda");
assert!(plugin
.begin_filter(
CallStub::new()
.with_named_parameter(
"find-replace",
table(&vec![string(&search_argument), string(&replace_argument)])
)
.create()
)
.is_ok());
match plugin.action {
Some(Action::Replace(ReplaceAction::FindAndReplace(find_with, replace_with))) => {
assert_eq!(find_with, search_argument);
assert_eq!(replace_with, replace_argument);
}
Some(_) | None => panic!("Din't accept."),
}
}
#[test]
fn str_plugin_accepts_field() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_parameter("package.description")
.create()
)
.is_ok());
let actual = &*plugin.field.unwrap();
let actual = UntaggedValue::Primitive(Primitive::ColumnPath(actual.clone()));
let actual = actual.into_value(Tag::unknown());
assert_eq!(
actual,
column_path(&vec![string("package"), string("description")])
)
}
#[test]
fn str_plugin_accepts_only_one_action() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_long_flag("upcase")
.with_long_flag("downcase")
.with_long_flag("to-int")
.with_long_flag("substring")
.create(),
)
.is_err());
assert_eq!(plugin.error, Some("can only apply one".to_string()));
}
#[test]
fn str_downcases() {
let mut strutils = Str::new();
strutils.for_downcase();
assert_eq!(
strutils.apply("ANDRES").unwrap(),
UntaggedValue::string("andres")
);
}
#[test]
fn str_upcases() {
let mut strutils = Str::new();
strutils.for_upcase();
assert_eq!(
strutils.apply("andres").unwrap(),
UntaggedValue::string("ANDRES")
);
}
#[test]
fn str_to_int() {
let mut strutils = Str::new();
strutils.for_to_int();
assert_eq!(
strutils.apply("9999").unwrap(),
UntaggedValue::int(9999 as i64)
);
}
#[test]
fn str_replace() {
let mut strutils = Str::new();
strutils.for_replace(ReplaceAction::Direct("robalino".to_string()));
assert_eq!(
strutils.apply("andres").unwrap(),
UntaggedValue::string("robalino")
);
}
#[test]
fn str_find_replace() {
let mut strutils = Str::new();
strutils.for_replace(ReplaceAction::FindAndReplace(
"kittens".to_string(),
"jotandrehuda".to_string(),
));
assert_eq!(
strutils.apply("wykittens").unwrap(),
UntaggedValue::string("wyjotandrehuda")
);
}
#[test]
fn str_plugin_applies_upcase_with_field() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_long_flag("upcase")
.with_parameter("name")
.create()
)
.is_ok());
let subject = structured_sample_record("name", "jotandrehuda");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Row(o),
..
}) => assert_eq!(
*o.get_data(&String::from("name")).borrow(),
UntaggedValue::string(String::from("JOTANDREHUDA")).into_untagged_value()
),
_ => {}
}
}
#[test]
fn str_plugin_applies_upcase_without_field() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(CallStub::new().with_long_flag("upcase").create())
.is_ok());
let subject = unstructured_sample_record("jotandrehuda");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
..
}) => assert_eq!(*s, String::from("JOTANDREHUDA")),
_ => {}
}
}
#[test]
fn str_plugin_applies_downcase_with_field() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_long_flag("downcase")
.with_parameter("name")
.create()
)
.is_ok());
let subject = structured_sample_record("name", "JOTANDREHUDA");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Row(o),
..
}) => assert_eq!(
*o.get_data(&String::from("name")).borrow(),
UntaggedValue::string(String::from("jotandrehuda")).into_untagged_value()
),
_ => {}
}
}
#[test]
fn str_plugin_applies_downcase_without_field() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(CallStub::new().with_long_flag("downcase").create())
.is_ok());
let subject = unstructured_sample_record("JOTANDREHUDA");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
..
}) => assert_eq!(*s, String::from("jotandrehuda")),
_ => {}
}
}
#[test]
fn str_plugin_applies_to_int_with_field() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_long_flag("to-int")
.with_parameter("Nu_birthday")
.create()
)
.is_ok());
let subject = structured_sample_record("Nu_birthday", "10");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Row(o),
..
}) => assert_eq!(
*o.get_data(&String::from("Nu_birthday")).borrow(),
UntaggedValue::int(10).into_untagged_value()
),
_ => {}
}
}
#[test]
fn str_plugin_applies_to_int_without_field() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(CallStub::new().with_long_flag("to-int").create())
.is_ok());
let subject = unstructured_sample_record("10");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(Primitive::Int(i)),
..
}) => assert_eq!(*i, BigInt::from(10)),
_ => {}
}
}
#[test]
fn str_plugin_applies_substring_without_field() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_named_parameter("substring", string("0,1"))
.create()
)
.is_ok());
let subject = unstructured_sample_record("0123456789");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
..
}) => assert_eq!(*s, String::from("0")),
_ => {}
}
}
#[test]
fn str_plugin_applies_substring_exceeding_string_length() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_named_parameter("substring", string("0,11"))
.create()
)
.is_ok());
let subject = unstructured_sample_record("0123456789");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
..
}) => assert_eq!(*s, String::from("0123456789")),
_ => {}
}
}
#[test]
fn str_plugin_applies_substring_returns_blank_if_start_exceeds_length() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_named_parameter("substring", string("20,30"))
.create()
)
.is_ok());
let subject = unstructured_sample_record("0123456789");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
..
}) => assert_eq!(*s, String::from("")),
_ => {}
}
}
#[test]
fn str_plugin_applies_substring_treats_blank_start_as_zero() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_named_parameter("substring", string(",5"))
.create()
)
.is_ok());
let subject = unstructured_sample_record("0123456789");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
..
}) => assert_eq!(*s, String::from("01234")),
_ => {}
}
}
#[test]
fn str_plugin_applies_substring_treats_blank_end_as_length() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_named_parameter("substring", string("2,"))
.create()
)
.is_ok());
let subject = unstructured_sample_record("0123456789");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
..
}) => assert_eq!(*s, String::from("23456789")),
_ => {}
}
}
#[test]
fn str_plugin_applies_substring_returns_error_if_start_exceeds_end() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_named_parameter("substring", string("3,1"))
.create()
)
.is_err());
assert_eq!(
plugin.error,
Some("End must be greater than or equal to Start".to_string())
);
}
#[test]
fn str_plugin_applies_replace_with_field() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_parameter("rustconf")
.with_named_parameter("replace", string("22nd August 2019"))
.create()
)
.is_ok());
let subject = structured_sample_record("rustconf", "1st January 1970");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Row(o),
..
}) => assert_eq!(
*o.get_data(&String::from("rustconf")).borrow(),
Value {
value: UntaggedValue::string(String::from("22nd August 2019")),
tag: Tag::unknown()
}
),
_ => {}
}
}
#[test]
fn str_plugin_applies_replace_without_field() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_named_parameter("replace", string("22nd August 2019"))
.create()
)
.is_ok());
let subject = unstructured_sample_record("1st January 1970");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
..
}) => assert_eq!(*s, String::from("22nd August 2019")),
_ => {}
}
}
#[test]
fn str_plugin_applies_find_replace_with_field() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_parameter("staff")
.with_named_parameter(
"find-replace",
table(&vec![string("kittens"), string("jotandrehuda")])
)
.create()
)
.is_ok());
let subject = structured_sample_record("staff", "wykittens");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Row(o),
..
}) => assert_eq!(
*o.get_data(&String::from("staff")).borrow(),
Value {
value: UntaggedValue::string(String::from("wyjotandrehuda")),
tag: Tag::unknown()
}
),
_ => {}
}
}
#[test]
fn str_plugin_applies_find_replace_without_field() {
let mut plugin = Str::new();
assert!(plugin
.begin_filter(
CallStub::new()
.with_named_parameter(
"find-replace",
table(&vec![string("kittens"), string("jotandrehuda")])
)
.create()
)
.is_ok());
let subject = unstructured_sample_record("wykittens");
let output = plugin.filter(subject).unwrap();
match output[0].as_ref().unwrap() {
ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
..
}) => assert_eq!(*s, String::from("wyjotandrehuda")),
_ => {}
}
}
} }

View File

@ -0,0 +1,126 @@
#[cfg(test)]
mod tests;
use crate::strutils::ReplaceAction;
use crate::Str;
use nu_errors::ShellError;
use nu_plugin::Plugin;
use nu_protocol::{
CallInfo, Primitive, ReturnSuccess, ReturnValue, ShellTypeName, Signature, SyntaxShape,
UntaggedValue, Value,
};
use nu_value_ext::ValueExt;
impl Plugin for Str {
fn config(&mut self) -> Result<Signature, ShellError> {
Ok(Signature::build("str")
.desc("Apply string function. Optional use the column of a table")
.switch("downcase", "convert string to lowercase")
.switch("upcase", "convert string to uppercase")
.switch("to-int", "convert string to integer")
.named("replace", SyntaxShape::String, "replaces the string")
.named(
"find-replace",
SyntaxShape::Any,
"finds and replaces [pattern replacement]",
)
.named(
"substring",
SyntaxShape::String,
"convert string to portion of original, requires \"start,end\"",
)
.rest(SyntaxShape::ColumnPath, "the column(s) to convert")
.filter())
}
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
let args = call_info.args;
if args.has("downcase") {
self.for_downcase();
}
if args.has("upcase") {
self.for_upcase();
}
if args.has("to-int") {
self.for_to_int();
}
if args.has("substring") {
if let Some(start_end) = args.get("substring") {
match start_end {
Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
..
} => {
self.for_substring(s.to_string())?;
}
_ => {
return Err(ShellError::labeled_error(
"Unrecognized type in params",
start_end.type_name(),
&start_end.tag,
))
}
}
}
}
if args.has("replace") {
if let Some(Value {
value: UntaggedValue::Primitive(Primitive::String(replacement)),
..
}) = args.get("replace")
{
self.for_replace(ReplaceAction::Direct(replacement.clone()));
}
}
if args.has("find-replace") {
if let Some(Value {
value: UntaggedValue::Table(arguments),
tag,
}) = args.get("find-replace")
{
self.for_replace(ReplaceAction::FindAndReplace(
arguments
.get(0)
.ok_or_else(|| {
ShellError::labeled_error(
"expected file and replace strings eg) [find replace]",
"missing find-replace values",
tag,
)
})?
.as_string()?,
arguments
.get(1)
.ok_or_else(|| {
ShellError::labeled_error(
"expected file and replace strings eg) [find replace]",
"missing find-replace values",
tag,
)
})?
.as_string()?,
));
}
}
if let Some(possible_field) = args.nth(0) {
let possible_field = possible_field.as_column_path()?;
self.for_field(possible_field);
}
match &self.error {
Some(reason) => Err(ShellError::untagged_runtime_error(format!(
"{}: {}",
reason,
Str::usage()
))),
None => Ok(vec![]),
}
}
fn filter(&mut self, input: Value) -> Result<Vec<ReturnValue>, ShellError> {
Ok(vec![ReturnSuccess::value(self.strutils(input)?)])
}
}

View File

@ -0,0 +1,380 @@
mod integration {
use crate::strutils::{Action, ReplaceAction};
use crate::Str;
use nu_errors::ShellError;
use nu_plugin::test_helpers::value::{
column_path, get_data, int, string, structured_sample_record, table,
unstructured_sample_record,
};
use nu_plugin::test_helpers::{expect_return_value_at, plugin, CallStub};
use nu_protocol::UntaggedValue;
#[test]
fn picks_up_one_action_flag_only() {
plugin(&mut Str::new())
.args(
CallStub::new()
.with_long_flag("upcase")
.with_long_flag("downcase")
.create(),
)
.setup(|plugin, returned_values| {
let actual = format!("{}", returned_values.unwrap_err());
assert!(actual.contains("can only apply one"));
assert_eq!(plugin.error, Some("can only apply one".to_string()));
});
}
#[test]
fn picks_up_downcase_flag() {
plugin(&mut Str::new())
.args(CallStub::new().with_long_flag("downcase").create())
.setup(|plugin, _| plugin.expect_action(Action::Downcase));
}
#[test]
fn picks_up_upcase_flag() {
plugin(&mut Str::new())
.args(CallStub::new().with_long_flag("upcase").create())
.setup(|plugin, _| plugin.expect_action(Action::Upcase));
}
#[test]
fn picks_up_to_int_flag() {
plugin(&mut Str::new())
.args(CallStub::new().with_long_flag("to-int").create())
.setup(|plugin, _| plugin.expect_action(Action::ToInteger));
}
#[test]
fn picks_up_arguments_for_replace_flag() {
let argument = String::from("replace_text");
plugin(&mut Str::new())
.args(
CallStub::new()
.with_named_parameter("replace", string(&argument))
.create(),
)
.setup(|plugin, _| {
let strategy = ReplaceAction::Direct(argument);
plugin.expect_action(Action::Replace(strategy));
});
}
#[test]
fn picks_up_arguments_for_find_replace() {
let search_argument = String::from("kittens");
let replace_argument = String::from("jotandrehuda");
plugin(&mut Str::new())
.args(
CallStub::new()
.with_named_parameter(
"find-replace",
table(&[string(&search_argument), string(&replace_argument)]),
)
.create(),
)
.setup(|plugin, _| {
let strategy = ReplaceAction::FindAndReplace(search_argument, replace_argument);
plugin.expect_action(Action::Replace(strategy))
});
}
#[test]
fn picks_up_argument_for_field() -> Result<(), ShellError> {
plugin(&mut Str::new())
.args(
CallStub::new()
.with_parameter("package.description")?
.create(),
)
.setup(|plugin, _| {
//FIXME: this is possibly not correct
if let Ok(column_path) = column_path(&[string("package"), string("description")]) {
plugin.expect_field(column_path)
}
});
Ok(())
}
#[test]
fn substring_errors_if_start_index_is_greater_than_end_index() {
plugin(&mut Str::new())
.args(
CallStub::new()
.with_named_parameter("substring", string("3,1"))
.create(),
)
.setup(|plugin, returned_values| {
let actual = format!("{}", returned_values.unwrap_err());
assert!(actual.contains("End must be greater than or equal to Start"));
assert_eq!(
plugin.error,
Some("End must be greater than or equal to Start".to_string())
);
});
}
#[test]
fn upcases_the_input_using_the_field_passed_as_parameter() -> Result<(), ShellError> {
let run = plugin(&mut Str::new())
.args(
CallStub::new()
.with_long_flag("upcase")
.with_parameter("name")?
.create(),
)
.input(structured_sample_record("name", "jotandrehuda"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(get_data(actual, "name"), string("JOTANDREHUDA"));
Ok(())
}
#[test]
fn downcases_the_input_using_the_field_passed_as_parameter() -> Result<(), ShellError> {
let run = plugin(&mut Str::new())
.args(
CallStub::new()
.with_long_flag("downcase")
.with_parameter("name")?
.create(),
)
.input(structured_sample_record("name", "JOTANDREHUDA"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(get_data(actual, "name"), string("jotandrehuda"));
Ok(())
}
#[test]
fn converts_the_input_to_integer_using_the_field_passed_as_parameter() -> Result<(), ShellError>
{
let run = plugin(&mut Str::new())
.args(
CallStub::new()
.with_long_flag("to-int")
.with_parameter("Nu_birthday")?
.create(),
)
.input(structured_sample_record("Nu_birthday", "10"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(get_data(actual, "Nu_birthday"), int(10));
Ok(())
}
#[test]
fn replaces_the_input_using_the_field_passed_as_parameter() -> Result<(), ShellError> {
let run = plugin(&mut Str::new())
.args(
CallStub::new()
.with_parameter("rustconf")?
.with_named_parameter("replace", string("22nd August 2019"))
.create(),
)
.input(structured_sample_record("rustconf", "1st January 1970"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(get_data(actual, "rustconf"), string("22nd August 2019"));
Ok(())
}
#[test]
fn find_and_replaces_the_input_using_the_field_passed_as_parameter() -> Result<(), ShellError> {
let run = plugin(&mut Str::new())
.args(
CallStub::new()
.with_parameter("staff")?
.with_named_parameter(
"find-replace",
table(&[string("kittens"), string("jotandrehuda")]),
)
.create(),
)
.input(structured_sample_record("staff", "wykittens"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(get_data(actual, "staff"), string("wyjotandrehuda"));
Ok(())
}
#[test]
fn upcases_the_input() {
let run = plugin(&mut Str::new())
.args(CallStub::new().with_long_flag("upcase").create())
.input(unstructured_sample_record("joandrehuda"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(actual, string("JOANDREHUDA"));
}
#[test]
fn downcases_the_input() {
let run = plugin(&mut Str::new())
.args(CallStub::new().with_long_flag("downcase").create())
.input(unstructured_sample_record("JOANDREHUDA"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(actual, string("joandrehuda"));
}
#[test]
fn converts_the_input_to_integer() {
let run = plugin(&mut Str::new())
.args(CallStub::new().with_long_flag("to-int").create())
.input(unstructured_sample_record("10"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(actual, UntaggedValue::int(10).into_untagged_value());
}
#[test]
fn substrings_the_input() {
let run = plugin(&mut Str::new())
.args(
CallStub::new()
.with_named_parameter("substring", string("0,1"))
.create(),
)
.input(unstructured_sample_record("0123456789"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(actual, string("0"));
}
#[test]
fn substrings_the_input_and_returns_the_string_if_end_index_exceeds_length() {
let run = plugin(&mut Str::new())
.args(
CallStub::new()
.with_named_parameter("substring", string("0,11"))
.create(),
)
.input(unstructured_sample_record("0123456789"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(actual, string("0123456789"));
}
#[test]
fn substrings_the_input_and_returns_blank_if_start_index_exceeds_length() {
let run = plugin(&mut Str::new())
.args(
CallStub::new()
.with_named_parameter("substring", string("20,30"))
.create(),
)
.input(unstructured_sample_record("0123456789"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(actual, string(""));
}
#[test]
fn substrings_the_input_and_treats_start_index_as_zero_if_blank_start_index_given() {
let run = plugin(&mut Str::new())
.args(
CallStub::new()
.with_named_parameter("substring", string(",5"))
.create(),
)
.input(unstructured_sample_record("0123456789"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(actual, string("01234"));
}
#[test]
fn substrings_the_input_and_treats_end_index_as_length_if_blank_end_index_given() {
let run = plugin(&mut Str::new())
.args(
CallStub::new()
.with_named_parameter("substring", string("2,"))
.create(),
)
.input(unstructured_sample_record("0123456789"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(actual, string("23456789"));
}
#[test]
fn replaces_the_input() {
let run = plugin(&mut Str::new())
.args(
CallStub::new()
.with_named_parameter("replace", string("22nd August 2019"))
.create(),
)
.input(unstructured_sample_record("1st January 1970"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(actual, string("22nd August 2019"));
}
#[test]
fn find_and_replaces_the_input() {
let run = plugin(&mut Str::new())
.args(
CallStub::new()
.with_named_parameter(
"find-replace",
table(&[string("kittens"), string("jotandrehuda")]),
)
.create(),
)
.input(unstructured_sample_record("wykittens"))
.setup(|_, _| {})
.test();
let actual = expect_return_value_at(run, 0);
assert_eq!(actual, string("wyjotandrehuda"));
}
}

View File

@ -0,0 +1,260 @@
use nu_errors::ShellError;
use nu_protocol::{did_you_mean, ColumnPath, Primitive, ShellTypeName, UntaggedValue, Value};
use nu_source::{span_for_spanned_list, Tagged};
use nu_value_ext::ValueExt;
use regex::Regex;
use std::cmp;
#[derive(Debug, Eq, PartialEq)]
pub enum Action {
Downcase,
Upcase,
ToInteger,
Substring(usize, usize),
Replace(ReplaceAction),
}
#[derive(Debug, Eq, PartialEq)]
pub enum ReplaceAction {
Direct(String),
FindAndReplace(String, String),
}
#[derive(Default)]
pub struct Str {
pub field: Option<Tagged<ColumnPath>>,
pub error: Option<String>,
pub action: Option<Action>,
}
impl Str {
pub fn new() -> Self {
Default::default()
}
fn apply(&self, input: &str) -> Result<UntaggedValue, ShellError> {
let applied = match self.action.as_ref() {
Some(Action::Downcase) => UntaggedValue::string(input.to_ascii_lowercase()),
Some(Action::Upcase) => UntaggedValue::string(input.to_ascii_uppercase()),
Some(Action::Substring(s, e)) => {
let end: usize = cmp::min(*e, input.len());
let start: usize = *s;
if start > input.len() - 1 {
UntaggedValue::string("")
} else {
UntaggedValue::string(
&input
.chars()
.skip(start)
.take(end - start)
.collect::<String>(),
)
}
}
Some(Action::Replace(mode)) => match mode {
ReplaceAction::Direct(replacement) => UntaggedValue::string(replacement.as_str()),
ReplaceAction::FindAndReplace(find, replacement) => {
let regex = Regex::new(find.as_str());
match regex {
Ok(re) => UntaggedValue::string(
re.replace(input, replacement.as_str()).to_owned(),
),
Err(_) => UntaggedValue::string(input),
}
}
},
Some(Action::ToInteger) => match input.trim() {
other => match other.parse::<i64>() {
Ok(v) => UntaggedValue::int(v),
Err(_) => UntaggedValue::string(input),
},
},
None => UntaggedValue::string(input),
};
Ok(applied)
}
pub fn for_field(&mut self, column_path: Tagged<ColumnPath>) {
self.field = Some(column_path);
}
fn permit(&mut self) -> bool {
self.action.is_none()
}
fn log_error(&mut self, message: &str) {
self.error = Some(message.to_string());
}
pub fn for_to_int(&mut self) {
if self.permit() {
self.action = Some(Action::ToInteger);
} else {
self.log_error("can only apply one");
}
}
pub fn for_downcase(&mut self) {
if self.permit() {
self.action = Some(Action::Downcase);
} else {
self.log_error("can only apply one");
}
}
pub fn for_upcase(&mut self) {
if self.permit() {
self.action = Some(Action::Upcase);
} else {
self.log_error("can only apply one");
}
}
pub fn for_substring(&mut self, s: String) -> Result<(), ShellError> {
let v: Vec<&str> = s.split(',').collect();
let start: usize = match v[0] {
"" => 0,
_ => v[0]
.trim()
.parse()
.map_err(|_| ShellError::untagged_runtime_error("Could not perform substring"))?,
};
let end: usize = match v[1] {
"" => usize::max_value(),
_ => v[1]
.trim()
.parse()
.map_err(|_| ShellError::untagged_runtime_error("Could not perform substring"))?,
};
if start > end {
self.log_error("End must be greater than or equal to Start");
} else if self.permit() {
self.action = Some(Action::Substring(start, end));
} else {
self.log_error("can only apply one");
}
Ok(())
}
pub fn for_replace(&mut self, mode: ReplaceAction) {
if self.permit() {
self.action = Some(Action::Replace(mode));
} else {
self.log_error("can only apply one");
}
}
pub fn usage() -> &'static str {
"Usage: str field [--downcase|--upcase|--to-int|--substring \"start,end\"|--replace|--find-replace [pattern replacement]]]"
}
pub fn strutils(&self, value: Value) -> Result<Value, ShellError> {
match &value.value {
UntaggedValue::Primitive(Primitive::String(ref s)) => {
Ok(self.apply(&s)?.into_value(value.tag()))
}
UntaggedValue::Primitive(Primitive::Line(ref s)) => {
Ok(self.apply(&s)?.into_value(value.tag()))
}
UntaggedValue::Row(_) => match self.field {
Some(ref f) => {
let fields = f.clone();
let replace_for =
value.get_data_by_column_path(
&f,
Box::new(move |(obj_source, column_path_tried, error)| {
match did_you_mean(&obj_source, &column_path_tried) {
Some(suggestions) => ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", suggestions[0].1),
span_for_spanned_list(fields.iter().map(|p| p.span)),
),
None => error,
}
}),
);
let got = replace_for?;
let replacement = self.strutils(got)?;
match value
.replace_data_at_column_path(&f, replacement.value.into_untagged_value())
{
Some(v) => Ok(v),
None => Err(ShellError::labeled_error(
"str could not find field to replace",
"column name",
value.tag(),
)),
}
}
None => Err(ShellError::untagged_runtime_error(format!(
"{}: {}",
"str needs a column when applied to a value in a row",
Str::usage()
))),
},
_ => Err(ShellError::labeled_error(
"Unrecognized type in stream",
value.type_name(),
value.tag,
)),
}
}
}
#[cfg(test)]
pub mod tests {
use super::ReplaceAction;
use super::Str;
use nu_plugin::test_helpers::value::{int, string};
#[test]
fn downcases() -> Result<(), Box<dyn std::error::Error>> {
let mut strutils = Str::new();
strutils.for_downcase();
assert_eq!(strutils.apply("ANDRES")?, string("andres").value);
Ok(())
}
#[test]
fn upcases() -> Result<(), Box<dyn std::error::Error>> {
let mut strutils = Str::new();
strutils.for_upcase();
assert_eq!(strutils.apply("andres")?, string("ANDRES").value);
Ok(())
}
#[test]
fn converts_to_int() -> Result<(), Box<dyn std::error::Error>> {
let mut strutils = Str::new();
strutils.for_to_int();
assert_eq!(strutils.apply("9999")?, int(9999 as i64).value);
Ok(())
}
#[test]
fn replaces() -> Result<(), Box<dyn std::error::Error>> {
let mut strutils = Str::new();
strutils.for_replace(ReplaceAction::Direct("robalino".to_string()));
assert_eq!(strutils.apply("andres")?, string("robalino").value);
Ok(())
}
#[test]
fn find_and_replaces() -> Result<(), Box<dyn std::error::Error>> {
let mut strutils = Str::new();
strutils.for_replace(ReplaceAction::FindAndReplace(
"kittens".to_string(),
"jotandrehuda".to_string(),
));
assert_eq!(strutils.apply("wykittens")?, string("wyjotandrehuda").value);
Ok(())
}
}

View File

@ -1,17 +1,16 @@
[package] [package]
name = "nu_plugin_sum" name = "nu_plugin_sum"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "A simple summation plugin for Nushell" description = "A simple summation plugin for Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.7.0" } nu-plugin = { path = "../nu-plugin", version="0.8.0" }
nu-source = { path = "../nu-source", version = "0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.8.0" }
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }

View File

@ -1,7 +1,7 @@
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_plugin::{serve_plugin, Plugin};
use nu_protocol::{ use nu_protocol::{
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, Signature, CallInfo, Primitive, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value,
UntaggedValue, Value,
}; };
struct Sum { struct Sum {

View File

@ -1,21 +1,20 @@
[package] [package]
name = "nu_plugin_sys" name = "nu_plugin_sys"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "A system info plugin for Nushell" description = "A system info plugin for Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.7.0" } nu-plugin = { path = "../nu-plugin", version="0.8.0" }
nu-source = { path = "../nu-source", version = "0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.8.0" }
heim = "0.0.9" heim = "0.0.9"
futures-preview = { version = "=0.3.0-alpha.19", features = ["compat", "io-compat"] } futures-preview = { version = "=0.3.0-alpha.19", features = ["compat", "io-compat"] }
battery = "0.7.5" battery = "0.7.5"
futures-util = "0.3.1" futures-util = "0.3.1"
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }

View File

@ -6,9 +6,9 @@ use futures_util::StreamExt;
use heim::units::{frequency, information, thermodynamic_temperature, time}; use heim::units::{frequency, information, thermodynamic_temperature, time};
use heim::{disk, host, memory, net, sensors}; use heim::{disk, host, memory, net, sensors};
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_plugin::{serve_plugin, Plugin};
use nu_protocol::{ use nu_protocol::{
serve_plugin, CallInfo, Plugin, ReturnSuccess, ReturnValue, Signature, TaggedDictBuilder, CallInfo, ReturnSuccess, ReturnValue, Signature, TaggedDictBuilder, UntaggedValue, Value,
UntaggedValue, Value,
}; };
use nu_source::Tag; use nu_source::Tag;

View File

@ -1,17 +1,16 @@
[package] [package]
name = "nu_plugin_textview" name = "nu_plugin_textview"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "Text viewer plugin for Nushell" description = "Text viewer plugin for Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.7.0" } nu-plugin = { path = "../nu-plugin", version="0.8.0" }
nu-source = { path = "../nu-source", version = "0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.8.0" }
crossterm = "0.10.2" crossterm = "0.10.2"
syntect = "3.2.0" syntect = "3.2.0"
@ -20,4 +19,4 @@ ansi_term = "0.12.1"
url = "2.1.0" url = "2.1.0"
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }

View File

@ -1,9 +1,8 @@
use crossterm::{cursor, terminal, RawScreen}; use crossterm::{cursor, terminal, RawScreen};
use crossterm::{InputEvent, KeyEvent}; use crossterm::{InputEvent, KeyEvent};
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_plugin::{serve_plugin, Plugin};
outln, serve_plugin, CallInfo, Plugin, Primitive, Signature, UntaggedValue, Value, use nu_protocol::{outln, CallInfo, Primitive, Signature, UntaggedValue, Value};
};
use nu_source::AnchorLocation; use nu_source::AnchorLocation;
use syntect::easy::HighlightLines; use syntect::easy::HighlightLines;
@ -39,7 +38,7 @@ impl Plugin for TextView {
} }
fn paint_textview( fn paint_textview(
draw_commands: &Vec<DrawCommand>, draw_commands: &[DrawCommand],
starting_row: usize, starting_row: usize,
use_color_buffer: bool, use_color_buffer: bool,
) -> usize { ) -> usize {
@ -149,8 +148,8 @@ fn scroll_view_lines_if_needed(draw_commands: Vec<DrawCommand>, use_color_buffer
loop { loop {
if let Some(ev) = sync_stdin.next() { if let Some(ev) = sync_stdin.next() {
match ev { if let InputEvent::Keyboard(k) = ev {
InputEvent::Keyboard(k) => match k { match k {
KeyEvent::Esc => { KeyEvent::Esc => {
break; break;
} }
@ -188,8 +187,7 @@ fn scroll_view_lines_if_needed(draw_commands: Vec<DrawCommand>, use_color_buffer
paint_textview(&draw_commands, starting_row, use_color_buffer); paint_textview(&draw_commands, starting_row, use_color_buffer);
} }
_ => {} _ => {}
}, }
_ => {}
} }
} }
@ -221,8 +219,7 @@ fn scroll_view(s: &str) {
fn view_text_value(value: &Value) { fn view_text_value(value: &Value) {
let value_anchor = value.anchor(); let value_anchor = value.anchor();
match &value.value { if let UntaggedValue::Primitive(Primitive::String(ref s)) = &value.value {
UntaggedValue::Primitive(Primitive::String(ref s)) => {
if let Some(source) = value_anchor { if let Some(source) = value_anchor {
let extension: Option<String> = match source { let extension: Option<String> = match source {
AnchorLocation::File(file) => { AnchorLocation::File(file) => {
@ -232,7 +229,6 @@ fn view_text_value(value: &Value) {
AnchorLocation::Url(url) => { AnchorLocation::Url(url) => {
let url = url::Url::parse(&url); let url = url::Url::parse(&url);
if let Ok(url) = url { if let Ok(url) = url {
let url = url.clone();
if let Some(mut segments) = url.path_segments() { if let Some(mut segments) = url.path_segments() {
if let Some(file) = segments.next_back() { if let Some(file) = segments.next_back() {
let path = Path::new(file); let path = Path::new(file);
@ -285,8 +281,6 @@ fn view_text_value(value: &Value) {
scroll_view(s); scroll_view(s);
} }
} }
_ => {}
}
} }
fn main() { fn main() {

View File

@ -1,19 +1,18 @@
[package] [package]
name = "nu_plugin_tree" name = "nu_plugin_tree"
version = "0.7.0" version = "0.8.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
edition = "2018" edition = "2018"
description = "Tree viewer plugin for Nushell" description = "Tree viewer plugin for Nushell"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.7.0" } nu-plugin = { path = "../nu-plugin", version="0.8.0" }
nu-source = { path = "../nu-source", version = "0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.8.0" }
nu-errors = { path = "../nu-errors", version = "0.8.0" }
ptree = {version = "0.2" } ptree = {version = "0.2" }
derive-new = "0.5.8" derive-new = "0.5.8"
[build-dependencies] [build-dependencies]
nu-build = { version = "0.7.0", path = "../nu-build" } nu-build = { version = "0.8.0", path = "../nu-build" }

View File

@ -1,8 +1,7 @@
use derive_new::new; use derive_new::new;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_plugin::{serve_plugin, Plugin};
format_primitive, serve_plugin, CallInfo, Plugin, Signature, UntaggedValue, Value, use nu_protocol::{format_primitive, CallInfo, Signature, UntaggedValue, Value};
};
use ptree::item::StringItem; use ptree::item::StringItem;
use ptree::output::print_tree_with; use ptree::output::print_tree_with;
use ptree::print_config::PrintConfig; use ptree::print_config::PrintConfig;
@ -88,7 +87,7 @@ impl Plugin for TreeViewer {
} }
fn sink(&mut self, _call_info: CallInfo, input: Vec<Value>) { fn sink(&mut self, _call_info: CallInfo, input: Vec<Value>) {
if input.len() > 0 { if !input.is_empty() {
for i in input.iter() { for i in input.iter() {
let view = TreeView::from_value(&i); let view = TreeView::from_value(&i);
let _ = view.render_view(); let _ = view.render_view();

36
docs/commands/uniq.rs Normal file
View File

@ -0,0 +1,36 @@
# uniq
Returns unique rows or values from a dataset.
## Examples
Given a file `test.csv`
```
first_name,last_name,rusty_at,type
Andrés,Robalino,10/11/2013,A
Andrés,Robalino,10/11/2013,A
Jonathan,Turner,10/12/2013,B
Yehuda,Katz,10/11/2013,A
```
```
> `open test.csv | uniq`
# first_name last_name rusty_at type
0 Andrés Robalino 10/11/2013 A
1 Jonathan Turner 10/12/2013 B
2 Yehuda Katz 10/11/2013 A
```
```
> `open test.csv | get type | uniq`
# <value>
0 A
1 B
```

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