Compare commits

..

46 Commits

Author SHA1 Message Date
88f06c81b2 Update Cargo.toml 2020-04-22 06:34:32 +12:00
e6b315f05b Bump ichwh to version 0.3.4 (#1627)
Fixes #1626 and [this ichwh issue](https://gitlab.com/avandesa/ichwh-rs/-/issues/3)
2020-04-22 05:37:14 +12:00
01ef6b0732 Add config to disable table index column (#1623) 2020-04-21 18:09:22 +12:00
c7e11a5a28 bump to 0.13.0 (#1625) 2020-04-21 17:01:03 +12:00
ce0231049e Fix the panic running a bad alias (#1624) 2020-04-21 16:11:34 +12:00
0f7b270740 Add exit code verification (#1622) 2020-04-21 15:14:18 +12:00
72cf57dd99 Add booleans and fix semicolon shortcircuit (#1620) 2020-04-21 12:30:01 +12:00
e4fdb36511 External vars (#1615)
* fix empty table and missing spans

* wip

* WIP

* WIP

* working version with vars

* tidying

* WIP

* Fix external quoting issue
2020-04-21 09:45:11 +12:00
2ffb14c7d0 fix empty table and missing spans (#1614) 2020-04-20 19:44:19 +12:00
eec94e4016 Semicolon (#1613)
* WIP on blocks

* Getting further

* add some tests
2020-04-20 18:41:51 +12:00
6412bfd58d Move alias arguments to optional arguments (#1612)
This will allow people to use more variables to capture more, if necessary, without having to implement required/optional/rest
2020-04-20 16:04:40 +12:00
522a828687 Move external closer to internal (#1611)
* Refactor InputStream and affected commands.

First, making `values` private and leaning on the `Stream` implementation makes
consumes of `InputStream` less likely to have to change in the future, if we
change what an `InputStream` is internally.

Second, we're dropping `Option<InputStream>` as the input to pipelines,
internals, and externals. Instead, `InputStream.is_empty` can be used to check
for "emptiness". Empty streams are typically only ever used as the first input
to a pipeline.

* Add run_external internal command.

We want to push external commands closer to internal commands, eventually
eliminating the concept of "external" completely. This means we can consolidate
a couple of things:

- Variable evaluation (for example, `$it`, `$nu`, alias vars)
- Behaviour of whole stream vs per-item external execution

It should also make it easier for us to start introducing argument signatures
for external commands,

* Update run_external.rs

* Update run_external.rs

* Update run_external.rs

* Update run_external.rs

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-20 15:30:44 +12:00
6b8c6dec0e Bump ichwh minimum version to 0.3.3 (#1607)
The new version includes a fix for [this issue]

[this issue]: https://gitlab.com/avandesa/ichwh-rs/-/issues/2
2020-04-19 13:38:14 +12:00
2b0212880e Simplify cp command and allow multiple recursive copying (#1580)
* Better message error

* Use custom canonicalize in FileStructure build

* Better glob error in ls

* Use custom canonicalize, remove some duplicate code in cd.

* Enable recursive copying with patterns.

* Change test to fit new error message

* Test recursive with glob pattern

* Show that not matches were found in cp

* Fix typo in message error

* Change old canonicalize usage, follow newest changes
2020-04-19 11:05:24 +12:00
a16a91ede8 Fix precedence parsing of parens. Limit use (#1606) 2020-04-19 06:39:06 +12:00
c2a9bc3bf4 Add better docs to parser (#1604) 2020-04-19 05:30:40 +12:00
e5a79d09df Fix the versions up a bit to prevent breakage (#1602)
* Fix the versions up a bit to prevent breakage

* fix up the quickcheck test to not fail on parse error
2020-04-18 15:31:57 +12:00
7974e09eeb Math operators (#1601)
* Add some math operations

* WIP for adding compound expressions

* precedence parsing

* paren expressions

* better lhs handling

* add compound comparisons and shorthand lefthand parsing

* Add or comparison and shorthand paths
2020-04-18 13:50:58 +12:00
52d2d2b888 A random set of fixes (#1600) 2020-04-17 18:19:49 +12:00
ee778d2b03 WIP fix for the error bubbling (#1597) 2020-04-16 16:25:24 +12:00
928188b18e Redesign custom canonicalize, enable multiple dots expansion earlier. (#1588)
* Expand n dots early where tilde was also expanded.

* Remove normalize, not needed.
New function absolutize, doesn't follow links neither checks existence.
Renamed canonicalize_existing to canonicalize, works as expected.

* Remove normalize usages, change canonicalize.

* Treat strings as paths
2020-04-16 11:29:22 +12:00
59d516064c Add alias support to scripts and -c (#1593) 2020-04-16 05:50:35 +12:00
bd5836e25d Aliases (#1589)
* WIP getting scopes right

* finish adding initial support

* Finish with alias and add startup commands
2020-04-15 17:43:23 +12:00
e3da037b80 Canonical expr block (#1584)
* Add the canonical form for an expression block

* Remove commented out code
2020-04-14 06:59:33 +12:00
08a09e2273 Pipeline blocks (#1579)
* Making Commands match what UntaggedValue needs

* WIP

* WIP

* WIP

* Moved to expressions for conditions

* Add 'each' command to use command blocks

* More cleanup

* Add test for 'each'

* Instead use an expression block
2020-04-13 19:59:57 +12:00
85d6b24be3 Add more cmd builtins (#1583) 2020-04-13 13:46:59 +12:00
ed583bd79b Bump to latest ichwh (#1582) 2020-04-13 08:01:23 +12:00
e0fc09ac52 move rustyline to 6.1.1 to fix windows crash (#1581) 2020-04-13 07:01:44 +12:00
38b2846024 Split canonicalize function in two for missing and existing behavior (#1576)
* Split allow missing logic in two functions

* Replace use of old canonicalize
2020-04-12 20:33:38 +12:00
57c62de66f Remove erronous GPL license header (#1578) 2020-04-12 20:16:50 +12:00
dd4935fb23 Add quickcheck (#1574)
* Move uptime to being a duration value

* Adds our first quickcheck test
2020-04-12 07:05:59 +12:00
18dd009ca8 Unified path expansion under new module and better canonicalize (#1571)
* New 'path' module under nu-cli.
Added normalize and canonicalize method.
Added some unit tests.

* Replace old usages of normalize and canonicalize.

* Fix reading symlinks and existence logic.

* Better explained
2020-04-12 07:05:29 +12:00
c0dda36217 Update CONTRIBUTING.md 2020-04-12 06:54:16 +12:00
75b72f844e Create CONTRIBUTING.md (#998)
* Create CONTRIBUTING.md

* mention gitpod

* Update CONTRIBUTING.md

* mention community supports

* fix capitalization issues
2020-04-12 06:52:53 +12:00
fbddc12c02 Move uptime to being a duration value (#1573) 2020-04-11 19:40:56 +12:00
8e7e8c17e1 Make trash support optional (#1572) 2020-04-11 18:53:53 +12:00
8ac9d781fd Remove source text where not needed (#1567) 2020-04-10 19:56:48 +12:00
c86cf31aac some minor improvements and removing dead code (#1563) 2020-04-10 07:48:10 +12:00
2c513d1883 More dep bumps (#1562) 2020-04-09 10:28:20 +12:00
04702530a3 Bump a lot of deps (#1560) 2020-04-07 19:51:17 +12:00
c9f424977e actually bump version (#1559) 2020-04-07 07:18:47 +12:00
183c8407de fix nu variable. tweak shells (#1558) 2020-04-07 05:30:54 +12:00
d0618b0b32 Enable the use of multiple dots in FS Shell commands (#1547)
Every dot after `..` means another parent directory.
2020-04-06 07:28:56 -04:00
c4daa2e40f Add experimental new parser (#1554)
Move to an experimental new parser
2020-04-06 19:16:14 +12:00
0a198b9bd0 Have FilesystemShell#rm correctly delete symlinks (#1550) 2020-04-05 17:17:52 -04:00
6a604491f5 ci(docker-publish): force remove non-executable (#1540) 2020-04-02 04:20:18 +13:00
222 changed files with 5931 additions and 14060 deletions

View File

@ -24,7 +24,7 @@ jobs:
run: |
cross build --target ${{ matrix.arch }} --release
# leave only the executable file
rm -rd target/${{ matrix.arch }}/release/{*/*,*.d,*.rlib,.fingerprint}
rm -frd target/${{ matrix.arch }}/release/{*/*,*.d,*.rlib,.fingerprint}
find . -empty -delete
- uses: actions/upload-artifact@master
with:

11
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,11 @@
Welcome to nushell!
*Note: for a more complete guide see [The nu contributor book](https://github.com/nushell/contributor-book)*
For speedy contributions open it in Gitpod, nu will be pre-installed with the latest build in a VSCode like editor all from your browser.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/nushell/nushell)
To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)!
<!--WIP-->

944
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
[package]
name = "nu"
version = "0.12.0"
version = "0.13.0"
authors = ["The Nu Project Contributors"]
description = "A new kind of shell"
description = "A new type of shell"
license = "MIT"
edition = "2018"
readme = "README.md"
@ -18,30 +18,28 @@ members = ["crates/*/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-cli = { version = "0.12.0", path = "./crates/nu-cli" }
nu-source = { version = "0.12.0", path = "./crates/nu-source" }
nu-plugin = { version = "0.12.0", path = "./crates/nu-plugin" }
nu-protocol = { version = "0.12.0", path = "./crates/nu-protocol" }
nu-errors = { version = "0.12.0", path = "./crates/nu-errors" }
nu-parser = { version = "0.12.0", path = "./crates/nu-parser" }
nu-value-ext = { version = "0.12.0", path = "./crates/nu-value-ext" }
nu_plugin_average = { version = "0.12.0", path = "./crates/nu_plugin_average", optional=true }
nu_plugin_binaryview = { version = "0.12.0", path = "./crates/nu_plugin_binaryview", optional=true }
nu_plugin_fetch = { version = "0.12.0", path = "./crates/nu_plugin_fetch", optional=true }
nu_plugin_inc = { version = "0.12.0", path = "./crates/nu_plugin_inc", optional=true }
nu_plugin_match = { version = "0.12.0", path = "./crates/nu_plugin_match", optional=true }
nu_plugin_post = { version = "0.12.0", path = "./crates/nu_plugin_post", optional=true }
nu_plugin_ps = { version = "0.12.0", path = "./crates/nu_plugin_ps", optional=true }
nu_plugin_str = { version = "0.12.0", path = "./crates/nu_plugin_str", optional=true }
nu_plugin_sys = { version = "0.12.0", path = "./crates/nu_plugin_sys", optional=true }
nu_plugin_textview = { version = "0.12.0", path = "./crates/nu_plugin_textview", optional=true }
nu_plugin_tree = { version = "0.12.0", path = "./crates/nu_plugin_tree", optional=true }
nu-macros = { version = "0.12.0", path = "./crates/nu-macros" }
nu-cli = { version = "0.13.0", path = "./crates/nu-cli" }
nu-source = { version = "0.13.0", path = "./crates/nu-source" }
nu-plugin = { version = "0.13.0", path = "./crates/nu-plugin" }
nu-protocol = { version = "0.13.0", path = "./crates/nu-protocol" }
nu-errors = { version = "0.13.0", path = "./crates/nu-errors" }
nu-parser = { version = "0.13.0", path = "./crates/nu-parser" }
nu-value-ext = { version = "0.13.0", path = "./crates/nu-value-ext" }
nu_plugin_average = { version = "0.13.0", path = "./crates/nu_plugin_average", optional=true }
nu_plugin_binaryview = { version = "0.13.0", path = "./crates/nu_plugin_binaryview", optional=true }
nu_plugin_fetch = { version = "0.13.0", path = "./crates/nu_plugin_fetch", optional=true }
nu_plugin_inc = { version = "0.13.0", path = "./crates/nu_plugin_inc", optional=true }
nu_plugin_match = { version = "0.13.0", path = "./crates/nu_plugin_match", optional=true }
nu_plugin_post = { version = "0.13.0", path = "./crates/nu_plugin_post", optional=true }
nu_plugin_ps = { version = "0.13.0", path = "./crates/nu_plugin_ps", optional=true }
nu_plugin_str = { version = "0.13.0", path = "./crates/nu_plugin_str", optional=true }
nu_plugin_sys = { version = "0.13.0", path = "./crates/nu_plugin_sys", optional=true }
nu_plugin_textview = { version = "0.13.0", path = "./crates/nu_plugin_textview", optional=true }
nu_plugin_tree = { version = "0.13.0", path = "./crates/nu_plugin_tree", optional=true }
crossterm = { version = "0.16.0", optional = true }
onig_sys = { version = "=69.1.0", optional = true }
crossterm = { version = "0.17.2", optional = true }
semver = { version = "0.9.0", optional = true }
syntect = { version = "3.2.0", optional = true }
syntect = { version = "4.1", default-features = false, features = ["default-fancy"], optional = true}
url = { version = "2.1.1", optional = true }
clap = "2.33.0"
@ -52,23 +50,22 @@ log = "0.4.8"
pretty_env_logger = "0.4.0"
[dev-dependencies]
pretty_assertions = "0.6.1"
nu-test-support = { version = "0.12.0", path = "./crates/nu-test-support" }
nu-test-support = { version = "0.13.0", path = "./crates/nu-test-support" }
[build-dependencies]
toml = "0.5.6"
serde = { version = "1.0.105", features = ["derive"] }
nu-build = { version = "0.12.0", path = "./crates/nu-build" }
serde = { version = "1.0.106", features = ["derive"] }
nu-build = { version = "0.13.0", path = "./crates/nu-build" }
[features]
# Test executables
test-bins = []
default = ["sys", "ps", "textview", "inc", "str"]
stable = ["default", "starship-prompt", "binaryview", "match", "tree", "average", "post", "fetch", "clipboard-cli"]
stable = ["default", "starship-prompt", "binaryview", "match", "tree", "average", "post", "fetch", "clipboard-cli", "trash-support"]
# Default
textview = ["crossterm", "syntect", "onig_sys", "url", "nu_plugin_textview"]
textview = ["crossterm", "syntect", "url", "nu_plugin_textview"]
sys = ["nu_plugin_sys"]
ps = ["nu_plugin_ps"]
inc = ["semver", "nu_plugin_inc"]
@ -85,6 +82,7 @@ tree = ["nu_plugin_tree"]
clipboard-cli = ["nu-cli/clipboard-cli"]
starship-prompt = ["nu-cli/starship-prompt"]
trash-support = ["nu-cli/trash-support"]
[[bin]]
name = "fail"

View File

@ -1,7 +1,7 @@
[package]
name = "nu-build"
version = "0.12.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
version = "0.13.0"
authors = ["The Nu Project Contributors"]
edition = "2018"
description = "Core build system for nushell"
license = "MIT"
@ -10,7 +10,7 @@ license = "MIT"
doctest = false
[dependencies]
serde = { version = "1.0.103", features = ["derive"] }
serde = { version = "1.0.106", features = ["derive"] }
lazy_static = "1.4.0"
serde_json = "1.0.44"
toml = "0.5.5"
serde_json = "1.0.51"
toml = "0.5.6"

View File

@ -1,7 +1,7 @@
[package]
name = "nu-cli"
version = "0.12.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
version = "0.13.0"
authors = ["The Nu Project Contributors"]
description = "CLI for nushell"
edition = "2018"
license = "MIT"
@ -10,14 +10,13 @@ license = "MIT"
doctest = false
[dependencies]
nu-source = { version = "0.12.0", path = "../nu-source" }
nu-plugin = { version = "0.12.0", path = "../nu-plugin" }
nu-protocol = { version = "0.12.0", path = "../nu-protocol" }
nu-errors = { version = "0.12.0", path = "../nu-errors" }
nu-parser = { version = "0.12.0", path = "../nu-parser" }
nu-value-ext = { version = "0.12.0", path = "../nu-value-ext" }
nu-macros = { version = "0.12.0", path = "../nu-macros" }
nu-test-support = { version = "0.12.0", path = "../nu-test-support" }
nu-source = { version = "0.13.0", path = "../nu-source" }
nu-plugin = { version = "0.13.0", path = "../nu-plugin" }
nu-protocol = { version = "0.13.0", path = "../nu-protocol" }
nu-errors = { version = "0.13.0", path = "../nu-errors" }
nu-parser = { version = "0.13.0", path = "../nu-parser" }
nu-value-ext = { version = "0.13.0", path = "../nu-value-ext" }
nu-test-support = { version = "0.13.0", path = "../nu-test-support" }
ansi_term = "0.12.1"
app_dirs = "1.2.1"
@ -36,26 +35,23 @@ ctrlc = "3.1.4"
derive-new = "0.5.8"
dirs = "2.0.2"
dunce = "1.0.0"
filesize = "0.1.0"
filesize = "0.2.0"
futures = { version = "0.3", features = ["compat", "io-compat"] }
futures-util = "0.3.4"
futures_codec = "0.4"
getset = "0.1.0"
git2 = { version = "0.13.0", default_features = false }
git2 = { version = "0.13.1", default_features = false }
glob = "0.3.0"
hex = "0.4"
htmlescape = "0.3.1"
ical = "0.6.*"
ichwh = "0.3"
ichwh = "0.3.4"
indexmap = { version = "1.3.2", features = ["serde-1"] }
itertools = "0.9.0"
language-reporting = "0.4.0"
log = "0.4.8"
meval = "0.2"
natural = "0.5.0"
nom = "5.0.1"
nom-tracable = "0.4.1"
nom_locate = "1.0.0"
num-bigint = { version = "0.2.6", features = ["serde"] }
num-traits = "0.2.11"
parking_lot = "0.10.0"
@ -67,13 +63,13 @@ ptree = {version = "0.2" }
query_interface = "0.3.5"
rand = "0.7"
regex = "1"
roxmltree = "0.10.0"
rustyline = "6.0.0"
serde = { version = "1.0.105", features = ["derive"] }
roxmltree = "0.10.1"
rustyline = "6.1.1"
serde = { version = "1.0.106", features = ["derive"] }
serde-hjson = "0.9.1"
serde_bytes = "0.11.3"
serde_ini = "0.2.0"
serde_json = "1.0.48"
serde_json = "1.0.51"
serde_urlencoded = "0.6.1"
serde_yaml = "0.8"
shellexpand = "2.0.0"
@ -83,28 +79,30 @@ term = "0.5.2"
termcolor = "1.1.0"
textwrap = {version = "0.11.0", features = ["term_size"]}
toml = "0.5.6"
trash = "1.0.0"
typetag = "0.1.4"
umask = "0.1"
unicode-xid = "0.2.0"
trash = { version = "1.0.0", optional = true }
clipboard = { version = "0.5", optional = true }
starship = { version = "0.38.0", optional = true }
starship = { version = "0.39.0", optional = true }
[target.'cfg(unix)'.dependencies]
users = "0.9"
users = "0.10.0"
[dependencies.rusqlite]
version = "0.21.0"
version = "0.22.0"
features = ["bundled", "blob"]
[dev-dependencies]
pretty_assertions = "0.6.1"
[build-dependencies]
nu-build = { version = "0.12.0", path = "../nu-build" }
nu-build = { version = "0.13.0", path = "../nu-build" }
[dev-dependencies]
quickcheck = "0.9"
quickcheck_macros = "0.9"
[features]
stable = []
starship-prompt = ["starship"]
clipboard-cli = ["clipboard"]
trash-support = ["trash"]

View File

@ -1,5 +1,5 @@
use crate::commands::classified::block::run_block;
use crate::commands::classified::external::{MaybeTextCodec, StringOrBinary};
use crate::commands::classified::pipeline::run_pipeline;
use crate::commands::plugin::JsonRpc;
use crate::commands::plugin::{PluginCommand, PluginSink};
use crate::commands::whole_stream_command;
@ -10,13 +10,10 @@ use crate::prelude::*;
use futures_codec::FramedRead;
use nu_errors::ShellError;
use nu_parser::{
ClassifiedCommand, ClassifiedPipeline, ExternalCommand, PipelineShape, SpannedToken,
TokensIterator,
};
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
use log::{debug, log_enabled, trace};
use log::{debug, trace};
use rustyline::error::ReadlineError;
use rustyline::{
self, config::Configurer, config::EditMode, At, Cmd, ColorMode, CompletionType, Config, Editor,
@ -261,6 +258,7 @@ pub fn create_default_context(
whole_stream_command(What),
whole_stream_command(Which),
whole_stream_command(Debug),
per_item_command(Alias),
// Statistics
whole_stream_command(Size),
whole_stream_command(Count),
@ -307,6 +305,7 @@ pub fn create_default_context(
whole_stream_command(Range),
whole_stream_command(Rename),
whole_stream_command(Uniq),
per_item_command(Each),
// Table manipulation
whole_stream_command(Shuffle),
whole_stream_command(Wrap),
@ -345,6 +344,8 @@ pub fn create_default_context(
whole_stream_command(FromYML),
whole_stream_command(FromIcs),
whole_stream_command(FromVcf),
// "Private" commands (not intended to be accessed directly)
whole_stream_command(RunExternalCommand),
]);
cfg_if::cfg_if! {
@ -369,10 +370,64 @@ pub fn create_default_context(
Ok(context)
}
pub async fn run_vec_of_pipelines(
pipelines: Vec<String>,
redirect_stdin: bool,
) -> Result<(), Box<dyn Error>> {
let mut syncer = crate::EnvironmentSyncer::new();
let mut context = crate::create_default_context(&mut syncer)?;
let _ = crate::load_plugins(&mut context);
let cc = context.ctrl_c.clone();
ctrlc::set_handler(move || {
cc.store(true, Ordering::SeqCst);
})
.expect("Error setting Ctrl-C handler");
if context.ctrl_c.load(Ordering::SeqCst) {
context.ctrl_c.store(false, Ordering::SeqCst);
}
// before we start up, let's run our startup commands
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
if let Some(commands) = config.get("startup") {
match commands {
Value {
value: UntaggedValue::Table(pipelines),
..
} => {
for pipeline in pipelines {
if let Ok(pipeline_string) = pipeline.as_string() {
let _ = run_pipeline_standalone(
pipeline_string,
false,
&mut context,
false,
)
.await;
}
}
}
_ => {
println!("warning: expected a table of pipeline strings as startup commands");
}
}
}
}
for pipeline in pipelines {
run_pipeline_standalone(pipeline, redirect_stdin, &mut context, true).await?;
}
Ok(())
}
pub async fn run_pipeline_standalone(
pipeline: String,
redirect_stdin: bool,
context: &mut Context,
exit_on_error: bool,
) -> Result<(), Box<dyn Error>> {
let line = process_line(Ok(pipeline), context, redirect_stdin, false).await;
@ -401,7 +456,9 @@ pub async fn run_pipeline_standalone(
});
context.maybe_print_errors(Text::from(line));
std::process::exit(1);
if exit_on_error {
std::process::exit(1);
}
}
_ => {}
@ -444,6 +501,34 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
})
.expect("Error setting Ctrl-C handler");
let mut ctrlcbreak = false;
// before we start up, let's run our startup commands
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
if let Some(commands) = config.get("startup") {
match commands {
Value {
value: UntaggedValue::Table(pipelines),
..
} => {
for pipeline in pipelines {
if let Ok(pipeline_string) = pipeline.as_string() {
let _ = run_pipeline_standalone(
pipeline_string,
false,
&mut context,
false,
)
.await;
}
}
}
_ => {
println!("warning: expected a table of pipeline strings as startup commands");
}
}
}
}
loop {
if context.ctrl_c.load(Ordering::SeqCst) {
context.ctrl_c.store(false, Ordering::SeqCst);
@ -614,9 +699,9 @@ async fn process_line(
Ok(line) => {
let line = chomp_newline(line);
let result = match nu_parser::parse(&line) {
let result = match nu_parser::lite_parse(&line, 0) {
Err(err) => {
return LineResult::Error(line.to_string(), err);
return LineResult::Error(line.to_string(), err.into());
}
Ok(val) => val,
@ -625,9 +710,12 @@ async fn process_line(
debug!("=== Parsed ===");
debug!("{:#?}", result);
let pipeline = classify_pipeline(&result, &ctx, &Text::from(line));
let classified_block = nu_parser::classify_block(&result, ctx.registry());
if let Some(failure) = pipeline.failed {
debug!("{:#?}", classified_block);
//println!("{:#?}", pipeline);
if let Some(failure) = classified_block.failed {
return LineResult::Error(line.to_string(), failure.into());
}
@ -637,15 +725,43 @@ async fn process_line(
// ...and it doesn't have any arguments
// ...and we're in the CLI
// ...then change to this directory
if cli_mode && pipeline.commands.list.len() == 1 {
if let ClassifiedCommand::External(ExternalCommand {
if cli_mode
&& classified_block.block.block.len() == 1
&& classified_block.block.block[0].list.len() == 1
{
if let ClassifiedCommand::Internal(InternalCommand {
ref name, ref args, ..
}) = pipeline.commands.list[0]
}) = classified_block.block.block[0].list[0]
{
if dunce::canonicalize(name).is_ok()
&& PathBuf::from(name).is_dir()
&& ichwh::which(name).await.unwrap_or(None).is_none()
&& args.list.is_empty()
let internal_name = name;
let name = args
.positional
.as_ref()
.and_then(|potionals| {
potionals.get(0).map(|e| {
if let Expression::Literal(Literal::String(ref s)) = e.expr {
&s
} else {
""
}
})
})
.unwrap_or("");
if internal_name == "run_external"
&& args
.positional
.as_ref()
.map(|ref v| v.len() == 1)
.unwrap_or(true)
&& args
.named
.as_ref()
.map(NamedArguments::is_empty)
.unwrap_or(true)
&& dunce::canonicalize(&name).is_ok()
&& PathBuf::from(&name).is_dir()
&& ichwh::which(&name).await.unwrap_or(None).is_none()
{
// Here we work differently if we're in Windows because of the expected Windows behavior
#[cfg(windows)]
@ -687,6 +803,7 @@ async fn process_line(
}
}
}
let input_stream = if redirect_stdin {
let file = futures::io::AllowStdIo::new(std::io::stdin());
let stream = FramedRead::new(file, MaybeTextCodec).map(|line| {
@ -707,13 +824,13 @@ async fn process_line(
panic!("Internal error: could not read lines of text from stdin")
}
});
Some(stream.to_input_stream())
stream.to_input_stream()
} else {
None
InputStream::empty()
};
match run_pipeline(pipeline, ctx, input_stream, line).await {
Ok(Some(input)) => {
match run_block(&classified_block.block, ctx, input_stream, &Scope::empty()).await {
Ok(input) => {
// Running a pipeline gives us back a stream that we can then
// work through. At the top level, we just want to pull on the
// values to compute them.
@ -724,9 +841,8 @@ async fn process_line(
shell_manager: ctx.shell_manager.clone(),
host: ctx.host.clone(),
ctrl_c: ctx.ctrl_c.clone(),
commands: ctx.registry.clone(),
registry: ctx.registry.clone(),
name: Tag::unknown(),
source: Text::from(String::new()),
};
if let Ok(mut output_stream) = crate::commands::autoview::autoview(context) {
@ -749,7 +865,6 @@ async fn process_line(
LineResult::Success(line.to_string())
}
Ok(None) => LineResult::Success(line.to_string()),
Err(err) => LineResult::Error(line.to_string(), err),
}
}
@ -762,39 +877,32 @@ async fn process_line(
}
}
pub fn classify_pipeline(
pipeline: &SpannedToken,
context: &Context,
source: &Text,
) -> ClassifiedPipeline {
let pipeline_list = vec![pipeline.clone()];
let expand_context = context.expand_context(source);
let mut iterator = TokensIterator::new(&pipeline_list, expand_context, pipeline.span());
let result = iterator.expand_infallible(PipelineShape);
if log_enabled!(target: "nu::expand_syntax", log::Level::Debug) {
outln!("");
let _ = ptree::print_tree(&iterator.expand_tracer().print(source.clone()));
outln!("");
}
result
}
pub fn print_err(err: ShellError, host: &dyn Host, source: &Text) {
let diag = err.into_diagnostic();
let writer = host.err_termcolor();
let mut source = source.to_string();
source.push_str(" ");
let files = nu_parser::Files::new(source);
let _ = std::panic::catch_unwind(move || {
let _ = language_reporting::emit(
&mut writer.lock(),
&files,
&diag,
&language_reporting::DefaultConfig,
);
});
if let Some(diag) = err.into_diagnostic() {
let writer = host.err_termcolor();
let mut source = source.to_string();
source.push_str(" ");
let files = nu_parser::Files::new(source);
let _ = std::panic::catch_unwind(move || {
let _ = language_reporting::emit(
&mut writer.lock(),
&files,
&diag,
&language_reporting::DefaultConfig,
);
});
}
}
#[cfg(test)]
mod tests {
#[quickcheck]
fn quickcheck_parse(data: String) -> bool {
if let Ok(lite_block) = nu_parser::lite_parse(&data, 0) {
let context = crate::context::Context::basic().unwrap();
let _ = nu_parser::classify_block(&lite_block, context.registry());
}
true
}
}

View File

@ -4,6 +4,7 @@ pub(crate) mod macros;
mod from_delimited_data;
mod to_delimited_data;
pub(crate) mod alias;
pub(crate) mod append;
pub(crate) mod args;
pub(crate) mod autoview;
@ -20,6 +21,7 @@ pub(crate) mod date;
pub(crate) mod debug;
pub(crate) mod default;
pub(crate) mod du;
pub(crate) mod each;
pub(crate) mod echo;
pub(crate) mod edit;
pub(crate) mod enter;
@ -74,6 +76,8 @@ pub(crate) mod reject;
pub(crate) mod rename;
pub(crate) mod reverse;
pub(crate) mod rm;
pub(crate) mod run_alias;
pub(crate) mod run_external;
pub(crate) mod save;
pub(crate) mod shells;
pub(crate) mod shuffle;
@ -114,6 +118,7 @@ pub(crate) use command::{
WholeStreamCommand,
};
pub(crate) use alias::Alias;
pub(crate) use append::Append;
pub(crate) use calc::Calc;
pub(crate) use compact::Compact;
@ -124,6 +129,7 @@ pub(crate) use date::Date;
pub(crate) use debug::Debug;
pub(crate) use default::Default;
pub(crate) use du::Du;
pub(crate) use each::Each;
pub(crate) use echo::Echo;
pub(crate) use edit::Edit;
pub(crate) mod kill;
@ -184,6 +190,7 @@ pub(crate) use reject::Reject;
pub(crate) use rename::Rename;
pub(crate) use reverse::Reverse;
pub(crate) use rm::Remove;
pub(crate) use run_external::RunExternalCommand;
pub(crate) use save::Save;
pub(crate) use shells::Shells;
pub(crate) use shuffle::Shuffle;

View File

@ -0,0 +1,65 @@
use crate::commands::PerItemCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
CallInfo, CommandAction, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
pub struct Alias;
impl PerItemCommand for Alias {
fn name(&self) -> &str {
"alias"
}
fn signature(&self) -> Signature {
Signature::build("alias")
.required("name", SyntaxShape::String, "the name of the alias")
.required("args", SyntaxShape::Table, "the arguments to the alias")
.required("block", SyntaxShape::Block, "the block to run on each row")
}
fn usage(&self) -> &str {
"Run a block on each row of the table."
}
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> {
let call_info = call_info.clone();
let stream = async_stream! {
match (call_info.args.expect_nth(0)?, call_info.args.expect_nth(1)?, call_info.args.expect_nth(2)?) {
(Value {value: UntaggedValue::Primitive(Primitive::String(name)), .. },
Value { value: UntaggedValue::Table(list), .. },
Value {
value: UntaggedValue::Block(block),
tag
}) => {
let mut args: Vec<String> = vec![];
for item in list.iter() {
if let Ok(string) = item.as_string() {
args.push(format!("${}", string));
} else {
yield Err(ShellError::labeled_error("Expected a string", "expected a string", item.tag()));
}
}
yield ReturnSuccess::action(CommandAction::AddAlias(name.to_string(), args, block.clone()))
}
_ => {
yield Err(ShellError::labeled_error(
"Expected `name [args] {block}",
"needs a name, args, and a block",
call_info.name_tag,
))
}
};
};
Ok(stream.to_output_stream())
}
}

View File

@ -45,5 +45,5 @@ fn append(
after.push_back(row);
let after = futures::stream::iter(after);
Ok(OutputStream::from_input(input.values.chain(after)))
Ok(OutputStream::from_input(input.chain(after)))
}

View File

@ -2,8 +2,8 @@ use crate::commands::UnevaluatedCallInfo;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_parser::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use nu_protocol::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
@ -29,10 +29,9 @@ impl WholeStreamCommand for Autoview {
) -> Result<OutputStream, ShellError> {
autoview(RunnableContext {
input: args.input,
commands: registry.clone(),
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
source: args.call_info.source,
ctrl_c: args.ctrl_c,
name: args.call_info.name_tag,
})
@ -42,9 +41,8 @@ impl WholeStreamCommand for Autoview {
pub struct RunnableContextWithoutInput {
pub shell_manager: ShellManager,
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub source: Text,
pub ctrl_c: Arc<AtomicBool>,
pub commands: CommandRegistry,
pub registry: CommandRegistry,
pub name: Tag,
}
@ -53,9 +51,8 @@ impl RunnableContextWithoutInput {
let new_context = RunnableContextWithoutInput {
shell_manager: context.shell_manager,
host: context.host,
source: context.source,
ctrl_c: context.ctrl_c,
commands: context.commands,
registry: context.registry,
name: context.name,
};
(context.input, new_context)
@ -95,7 +92,7 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
if let Some(table) = table {
let command_args = create_default_command_args(&context).with_input(stream);
let result = table.run(command_args, &context.commands);
let result = table.run(command_args, &context.registry);
result.collect::<Vec<_>>().await;
}
}
@ -109,7 +106,7 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
let mut stream = VecDeque::new();
stream.push_back(UntaggedValue::string(s).into_value(Tag { anchor, span }));
let command_args = create_default_command_args(&context).with_input(stream);
let result = text.run(command_args, &context.commands);
let result = text.run(command_args, &context.registry);
result.collect::<Vec<_>>().await;
} else {
out!("{}", s);
@ -129,7 +126,7 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
let mut stream = VecDeque::new();
stream.push_back(UntaggedValue::string(s).into_value(Tag { anchor, span }));
let command_args = create_default_command_args(&context).with_input(stream);
let result = text.run(command_args, &context.commands);
let result = text.run(command_args, &context.registry);
result.collect::<Vec<_>>().await;
} else {
out!("{}\n", s);
@ -159,13 +156,19 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
} => {
out!("{}", n);
}
Value {
value: UntaggedValue::Primitive(Primitive::Boolean(b)),
..
} => {
out!("{}", b);
}
Value { value: UntaggedValue::Primitive(Primitive::Binary(ref b)), .. } => {
if let Some(binary) = binary {
let mut stream = VecDeque::new();
stream.push_back(x);
let command_args = create_default_command_args(&context).with_input(stream);
let result = binary.run(command_args, &context.commands);
let result = binary.run(command_args, &context.registry);
result.collect::<Vec<_>>().await;
} else {
use pretty_hex::*;
@ -251,7 +254,7 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
let mut stream = VecDeque::new();
stream.push_back(x);
let command_args = create_default_command_args(&context).with_input(stream);
let result = table.run(command_args, &context.commands);
let result = table.run(command_args, &context.registry);
result.collect::<Vec<_>>().await;
} else {
out!("{:?}", item);
@ -282,15 +285,16 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
call_info: UnevaluatedCallInfo {
args: hir::Call {
head: Box::new(SpannedExpression::new(
Expression::Literal(Literal::String(span)),
Expression::Literal(Literal::String(String::new())),
span,
)),
positional: None,
named: None,
span,
is_last: true,
},
source: context.source.clone(),
name_tag: context.name.clone(),
scope: Scope::empty(),
},
}
}

View File

@ -1,7 +1,6 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_macros::signature;
use nu_protocol::{Signature, SyntaxShape};
pub struct Cd;
@ -12,17 +11,11 @@ impl WholeStreamCommand for Cd {
}
fn signature(&self) -> Signature {
signature! {
def cd {
"the directory to change to"
directory(optional Path) - "the directory to change to"
}
}
// Signature::build("cd").optional(
// "directory",
// SyntaxShape::Path,
// "the directory to change to",
// )
Signature::build("cd").optional(
"directory",
SyntaxShape::Path,
"the directory to change to",
)
}
fn usage(&self) -> &str {

View File

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

View File

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

View File

@ -0,0 +1,29 @@
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use log::{log_enabled, trace};
use nu_errors::ShellError;
use nu_protocol::hir::SpannedExpression;
use nu_protocol::Scope;
pub(crate) fn run_expression_block(
expr: SpannedExpression,
context: &mut Context,
input: InputStream,
scope: &Scope,
) -> Result<InputStream, ShellError> {
if log_enabled!(log::Level::Trace) {
trace!(target: "nu::run::expr", "->");
trace!(target: "nu::run::expr", "{:?}", expr);
}
let scope = scope.clone();
let registry = context.registry().clone();
let stream = input.map(move |row| {
let scope = scope.clone().set_it(row);
evaluate_baseline_expr(&expr, &registry, &scope)
});
Ok(stream.to_input_stream())
}

View File

@ -1,20 +1,22 @@
use crate::evaluate::evaluate_baseline_expr;
use crate::futures::ThreadedReceiver;
use crate::prelude::*;
use std::io::Write;
use std::ops::Deref;
use std::process::{Command, Stdio};
use std::sync::mpsc;
use bytes::{BufMut, Bytes, BytesMut};
use futures::executor::block_on_stream;
use futures::stream::StreamExt;
use futures_codec::FramedRead;
use log::trace;
use nu_errors::ShellError;
use nu_parser::commands::classified::external::ExternalArg;
use nu_parser::ExternalCommand;
use nu_protocol::{ColumnPath, Primitive, ShellTypeName, UntaggedValue, Value};
use nu_source::{Tag, Tagged};
use nu_value_ext::as_column_path;
use std::io::Write;
use std::ops::Deref;
use std::process::{Command, Stdio};
use std::sync::mpsc;
use nu_protocol::hir::ExternalCommand;
use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
use nu_source::Tag;
pub enum StringOrBinary {
String(String),
@ -80,7 +82,7 @@ impl futures_codec::Decoder for MaybeTextCodec {
}
}
pub fn nu_value_to_string(command: &ExternalCommand, from: &Value) -> Result<String, ShellError> {
pub fn nu_value_to_string(name_tag: &Tag, from: &Value) -> Result<String, ShellError> {
match &from.value {
UntaggedValue::Primitive(Primitive::Int(i)) => Ok(i.to_string()),
UntaggedValue::Primitive(Primitive::String(s))
@ -89,7 +91,7 @@ pub fn nu_value_to_string(command: &ExternalCommand, from: &Value) -> Result<Str
unsupported => Err(ShellError::labeled_error(
format!("needs string data (given: {})", unsupported.type_name()),
"expected a string",
&command.name_tag,
name_tag,
)),
}
}
@ -97,9 +99,10 @@ pub fn nu_value_to_string(command: &ExternalCommand, from: &Value) -> Result<Str
pub(crate) async fn run_external_command(
command: ExternalCommand,
context: &mut Context,
input: Option<InputStream>,
input: InputStream,
scope: &Scope,
is_last: bool,
) -> Result<Option<InputStream>, ShellError> {
) -> Result<InputStream, ShellError> {
trace!(target: "nu::run::external", "-> {}", command.name);
if !did_find_command(&command.name).await {
@ -110,265 +113,71 @@ pub(crate) async fn run_external_command(
));
}
if command.has_it_argument() || command.has_nu_argument() {
run_with_iterator_arg(command, context, input, is_last)
if command.has_it_argument() {
run_with_iterator_arg(command, context, input, scope, is_last)
} else {
run_with_stdin(command, context, input, is_last)
run_with_stdin(command, context, input, scope, is_last)
}
}
fn prepare_column_path_for_fetching_it_variable(
argument: &ExternalArg,
) -> Result<Tagged<ColumnPath>, ShellError> {
// We have "$it.[contents of interest]"
// and start slicing from "$it.[member+]"
// ^ here.
let key = nu_source::Text::from(argument.deref()).slice(4..argument.len());
to_column_path(&key, &argument.tag)
}
fn prepare_column_path_for_fetching_nu_variable(
argument: &ExternalArg,
) -> Result<Tagged<ColumnPath>, ShellError> {
// We have "$nu.[contents of interest]"
// and start slicing from "$nu.[member+]"
// ^ here.
let key = nu_source::Text::from(argument.deref()).slice(4..argument.len());
to_column_path(&key, &argument.tag)
}
fn to_column_path(
path_members: &str,
tag: impl Into<Tag>,
) -> Result<Tagged<ColumnPath>, ShellError> {
let tag = tag.into();
as_column_path(
&UntaggedValue::Table(
path_members
.split('.')
.map(|x| {
let member = match x.parse::<u64>() {
Ok(v) => UntaggedValue::int(v),
Err(_) => UntaggedValue::string(x),
};
member.into_value(&tag)
})
.collect(),
)
.into_value(&tag),
)
}
fn run_with_iterator_arg(
command: ExternalCommand,
context: &mut Context,
input: Option<InputStream>,
input: InputStream,
scope: &Scope,
is_last: bool,
) -> Result<Option<InputStream>, ShellError> {
) -> Result<InputStream, ShellError> {
let path = context.shell_manager.path();
let mut inputs: InputStream = if let Some(input) = input {
trace_stream!(target: "nu::trace_stream::external::it", "input" = input)
} else {
InputStream::empty()
};
let mut inputs: InputStream =
trace_stream!(target: "nu::trace_stream::external::it", "input" = input);
let name_tag = command.name_tag.clone();
let scope = scope.clone();
let context = context.clone();
let stream = async_stream! {
while let Some(value) = inputs.next().await {
let name = command.name.clone();
let name_tag = command.name_tag.clone();
let home_dir = dirs::home_dir();
let path = &path;
let args = command.args.clone();
// Evaluate the expressions into values, and from values into strings for each iteration
let mut command_args = vec![];
let scope = scope.clone().set_it(value);
for arg in command.args.iter() {
let value = evaluate_baseline_expr(arg, &context.registry, &scope)?;
command_args.push(nu_value_to_string(&name_tag, &value)?);
}
let it_replacement = {
if command.has_it_argument() {
let empty_arg = ExternalArg {
arg: "".to_string(),
tag: name_tag.clone()
};
let process_args = command_args
.iter()
.map(|arg| {
let arg = expand_tilde(arg.deref(), dirs::home_dir);
let key = args.iter()
.find(|arg| arg.looks_like_it())
.unwrap_or_else(|| &empty_arg);
if args.iter().all(|arg| !arg.is_it()) {
let key = match prepare_column_path_for_fetching_it_variable(&key) {
Ok(keypath) => keypath,
Err(reason) => {
yield Ok(Value {
value: UntaggedValue::Error(reason),
tag: name_tag
});
return;
}
};
match crate::commands::get::get_column_path(&key, &value) {
Ok(field) => {
match nu_value_to_string(&command, &field) {
Ok(val) => Some(val),
Err(reason) => {
yield Ok(Value {
value: UntaggedValue::Error(reason),
tag: name_tag
});
return;
},
}
},
Err(reason) => {
yield Ok(Value {
value: UntaggedValue::Error(reason),
tag: name_tag
});
return;
}
}
} else {
match nu_value_to_string(&command, &value) {
Ok(val) => Some(val),
Err(reason) => {
yield Ok(Value {
value: UntaggedValue::Error(reason),
tag: name_tag
});
return;
},
}
}
} else {
None
}
};
let nu_replacement = {
if command.has_nu_argument() {
let empty_arg = ExternalArg {
arg: "".to_string(),
tag: name_tag.clone()
};
let key = args.iter()
.find(|arg| arg.looks_like_nu())
.unwrap_or_else(|| &empty_arg);
let nu_var = match crate::evaluate::variables::nu(&name_tag) {
Ok(variables) => variables,
Err(reason) => {
yield Ok(Value {
value: UntaggedValue::Error(reason),
tag: name_tag
});
return;
}
};
if args.iter().all(|arg| !arg.is_nu()) {
let key = match prepare_column_path_for_fetching_nu_variable(&key) {
Ok(keypath) => keypath,
Err(reason) => {
yield Ok(Value {
value: UntaggedValue::Error(reason),
tag: name_tag
});
return;
}
};
match crate::commands::get::get_column_path(&key, &nu_var) {
Ok(field) => {
match nu_value_to_string(&command, &field) {
Ok(val) => Some(val),
Err(reason) => {
yield Ok(Value {
value: UntaggedValue::Error(reason),
tag: name_tag
});
return;
},
}
},
Err(reason) => {
yield Ok(Value {
value: UntaggedValue::Error(reason),
tag: name_tag
});
return;
}
}
} else {
match nu_value_to_string(&command, &nu_var) {
Ok(val) => Some(val),
Err(reason) => {
yield Ok(Value {
value: UntaggedValue::Error(reason),
tag: name_tag
});
return;
},
}
}
} else {
None
}
};
let process_args = args.iter().filter_map(|arg| {
if arg.chars().all(|c| c.is_whitespace()) {
None
} else {
let arg = if arg.looks_like_it() {
if let Some(mut value) = it_replacement.to_owned() {
let mut value = expand_tilde(&value, || home_dir.as_ref()).as_ref().to_string();
#[cfg(not(windows))]
{
value = {
if argument_contains_whitespace(&value) && !argument_is_quoted(&value) {
add_quotes(&value)
} else {
value
}
};
}
Some(value)
#[cfg(not(windows))]
{
if argument_contains_whitespace(&arg) && argument_is_quoted(&arg) {
if let Some(unquoted) = remove_quotes(&arg) {
format!(r#""{}""#, unquoted)
} else {
None
}
} else if arg.looks_like_nu() {
if let Some(mut value) = nu_replacement.to_owned() {
#[cfg(not(windows))]
{
value = {
if argument_contains_whitespace(&value) && !argument_is_quoted(&value) {
add_quotes(&value)
} else {
value
}
};
}
Some(value)
} else {
None
arg.as_ref().to_string()
}
} else {
Some(arg.to_string())
};
arg
arg.as_ref().to_string()
}
}
}).collect::<Vec<String>>();
#[cfg(windows)]
{
if let Some(unquoted) = remove_quotes(&arg) {
unquoted.to_string()
} else {
arg.as_ref().to_string()
}
}
})
.collect::<Vec<String>>();
match spawn(&command, &path, &process_args[..], None, is_last) {
Ok(res) => {
if let Some(mut res) = res {
while let Some(item) = res.next().await {
yield Ok(item)
}
match spawn(&command, &path, &process_args[..], InputStream::empty(), is_last) {
Ok(mut res) => {
while let Some(item) = res.next().await {
yield Ok(item)
}
}
Err(reason) => {
@ -382,34 +191,35 @@ fn run_with_iterator_arg(
}
};
Ok(Some(stream.to_input_stream()))
Ok(stream.to_input_stream())
}
fn run_with_stdin(
command: ExternalCommand,
context: &mut Context,
input: Option<InputStream>,
input: InputStream,
scope: &Scope,
is_last: bool,
) -> Result<Option<InputStream>, ShellError> {
) -> Result<InputStream, ShellError> {
let path = context.shell_manager.path();
let input = input
.map(|input| trace_stream!(target: "nu::trace_stream::external::stdin", "input" = input));
let input = trace_stream!(target: "nu::trace_stream::external::stdin", "input" = input);
let process_args = command
.args
let mut command_args = vec![];
for arg in command.args.iter() {
let value = evaluate_baseline_expr(arg, &context.registry, scope)?;
command_args.push(value.as_string()?);
}
let process_args = command_args
.iter()
.map(|arg| {
let arg = expand_tilde(arg.deref(), dirs::home_dir);
#[cfg(not(windows))]
{
if argument_contains_whitespace(&arg) && argument_is_quoted(&arg) {
if let Some(unquoted) = remove_quotes(&arg) {
format!(r#""{}""#, unquoted)
} else {
arg.as_ref().to_string()
}
if argument_contains_whitespace(&arg) && !argument_is_quoted(&arg) {
add_quotes(&arg)
} else {
arg.as_ref().to_string()
}
@ -432,9 +242,9 @@ fn spawn(
command: &ExternalCommand,
path: &str,
args: &[String],
input: Option<InputStream>,
input: InputStream,
is_last: bool,
) -> Result<Option<InputStream>, ShellError> {
) -> Result<InputStream, ShellError> {
let command = command.clone();
let mut process = {
@ -444,6 +254,8 @@ fn spawn(
process.arg("/c");
process.arg(&command.name);
for arg in args {
// Clean the args before we use them:
let arg = arg.replace("|", "\\|");
process.arg(&arg);
}
process
@ -469,7 +281,7 @@ fn spawn(
}
// open since we have some contents for stdin
if input.is_some() {
if !input.is_empty() {
process.stdin(Stdio::piped());
trace!(target: "nu::run::external", "set up stdin pipe");
}
@ -488,7 +300,7 @@ fn spawn(
let stdout_name_tag = command.name_tag;
std::thread::spawn(move || {
if let Some(input) = input {
if !input.is_empty() {
let mut stdin_write = stdin
.take()
.expect("Internal error: could not get stdin pipe for external command");
@ -610,7 +422,15 @@ fn spawn(
// We can give an error when we see a non-zero exit code, but this is different
// than what other shells will do.
if child.wait().is_err() {
let external_failed = match child.wait() {
Err(_) => true,
Ok(exit_status) => match exit_status.code() {
Some(e) if e != 0 => true,
_ => false,
},
};
if external_failed {
let cfg = crate::data::config::config(Tag::unknown());
if let Ok(cfg) = cfg {
if cfg.contains_key("nonzero_exit_errors") {
@ -620,17 +440,21 @@ fn spawn(
"command failed",
&stdout_name_tag,
)),
tag: stdout_name_tag,
tag: stdout_name_tag.clone(),
}));
}
}
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::external_non_zero()),
tag: stdout_name_tag,
}));
}
Ok(())
});
let stream = ThreadedReceiver::new(rx);
Ok(Some(stream.to_input_stream()))
Ok(stream.to_input_stream())
} else {
Err(ShellError::labeled_error(
"Failed to spawn process",
@ -654,6 +478,7 @@ async fn did_find_command(name: &str) -> bool {
let cmd_builtins = [
"call", "cls", "color", "date", "dir", "echo", "find", "hostname", "pause",
"start", "time", "title", "ver", "copy", "mkdir", "rename", "rd", "rmdir", "type",
"mklink",
];
cmd_builtins.contains(&name)
@ -714,10 +539,11 @@ fn shell_os_paths() -> Vec<std::path::PathBuf> {
mod tests {
use super::{
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
run_external_command, Context,
run_external_command, Context, InputStream,
};
use futures::executor::block_on;
use nu_errors::ShellError;
use nu_protocol::Scope;
use nu_test_support::commands::ExternalBuilder;
// async fn read(mut stream: OutputStream) -> Option<Value> {
@ -736,11 +562,14 @@ mod tests {
async fn non_existent_run() -> Result<(), ShellError> {
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
let input = InputStream::empty();
let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
assert!(run_external_command(cmd, &mut ctx, None, false)
.await
.is_err());
assert!(
run_external_command(cmd, &mut ctx, input, &Scope::empty(), false)
.await
.is_err()
);
Ok(())
}

View File

@ -1,42 +1,37 @@
use crate::commands::command::per_item_command;
use crate::commands::run_alias::AliasCommand;
use crate::commands::UnevaluatedCallInfo;
use crate::prelude::*;
use log::{log_enabled, trace};
use nu_errors::ShellError;
use nu_parser::InternalCommand;
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, UntaggedValue, Value};
use nu_protocol::hir::InternalCommand;
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue, Value};
pub(crate) fn run_internal_command(
command: InternalCommand,
context: &mut Context,
input: Option<InputStream>,
source: Text,
) -> Result<Option<InputStream>, ShellError> {
input: InputStream,
scope: &Scope,
) -> Result<InputStream, ShellError> {
if log_enabled!(log::Level::Trace) {
trace!(target: "nu::run::internal", "->");
trace!(target: "nu::run::internal", "{}", command.name);
trace!(target: "nu::run::internal", "{}", command.args.debug(&source));
}
let objects: InputStream = if let Some(input) = input {
trace_stream!(target: "nu::trace_stream::internal", "input" = input)
} else {
InputStream::empty()
};
let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
let internal_command = context.expect_command(&command.name);
let result = {
context.run_command(
internal_command?,
command.name_tag.clone(),
Tag::unknown_anchor(command.name_span),
command.args.clone(),
&source,
scope,
objects,
)
};
let result = trace_out_stream!(target: "nu::trace_stream::internal", "output" = result);
let mut result = result.values;
let mut result = trace_out_stream!(target: "nu::trace_stream::internal", "output" = result);
let mut context = context.clone();
let stream = async_stream! {
@ -64,14 +59,15 @@ pub(crate) fn run_internal_command(
ctrl_c: context.ctrl_c.clone(),
shell_manager: context.shell_manager.clone(),
call_info: UnevaluatedCallInfo {
args: nu_parser::hir::Call {
args: nu_protocol::hir::Call {
head: command.args.head,
positional: None,
named: None,
span: Span::unknown()
span: Span::unknown(),
is_last: false,
},
source: source.clone(),
name_tag: command.name_tag,
name_tag: Tag::unknown_anchor(command.name_span),
scope: Scope::empty(),
}
};
let mut result = converter.run(new_args.with_input(vec![tagged_contents]), &context.registry);
@ -124,6 +120,15 @@ pub(crate) fn run_internal_command(
FilesystemShell::with_location(location, context.registry().clone()),
));
}
CommandAction::AddAlias(name, args, block) => {
context.add_commands(vec![
per_item_command(AliasCommand::new(
name,
args,
block,
))
]);
}
CommandAction::PreviousShell => {
context.shell_manager.prev();
}
@ -142,7 +147,8 @@ pub(crate) fn run_internal_command(
value: UntaggedValue::Error(err),
..
})) => {
context.error(err);
context.error(err.clone());
yield Err(err);
break;
}
@ -175,5 +181,5 @@ pub(crate) fn run_internal_command(
}
};
Ok(Some(stream.to_input_stream()))
Ok(stream.to_input_stream())
}

View File

@ -1,7 +1,8 @@
pub(crate) mod block;
mod dynamic;
pub(crate) mod expr;
pub(crate) mod external;
pub(crate) mod internal;
pub(crate) mod pipeline;
#[allow(unused_imports)]
pub(crate) use dynamic::Command as DynamicCommand;

View File

@ -1,50 +0,0 @@
use crate::commands::classified::external::run_external_command;
use crate::commands::classified::internal::run_internal_command;
use crate::context::Context;
use crate::stream::InputStream;
use nu_errors::ShellError;
use nu_parser::{ClassifiedCommand, ClassifiedPipeline};
use nu_source::Text;
pub(crate) async fn run_pipeline(
pipeline: ClassifiedPipeline,
ctx: &mut Context,
mut input: Option<InputStream>,
line: &str,
) -> Result<Option<InputStream>, ShellError> {
let mut iter = pipeline.commands.list.into_iter().peekable();
loop {
let item: Option<ClassifiedCommand> = iter.next();
let next: Option<&ClassifiedCommand> = iter.peek();
input = match (item, next) {
(Some(ClassifiedCommand::Dynamic(_)), _) | (_, Some(ClassifiedCommand::Dynamic(_))) => {
return Err(ShellError::unimplemented("Dynamic commands"))
}
(Some(ClassifiedCommand::Expr(_)), _) | (_, Some(ClassifiedCommand::Expr(_))) => {
return Err(ShellError::unimplemented("Expression-only commands"))
}
(Some(ClassifiedCommand::Error(err)), _) => return Err(err.into()),
(_, Some(ClassifiedCommand::Error(err))) => return Err(err.clone().into()),
(Some(ClassifiedCommand::Internal(left)), _) => {
run_internal_command(left, ctx, input, Text::from(line))?
}
(Some(ClassifiedCommand::External(left)), None) => {
run_external_command(left, ctx, input, true).await?
}
(Some(ClassifiedCommand::External(left)), _) => {
run_external_command(left, ctx, input, false).await?
}
(None, _) => break,
};
}
Ok(input)
}

View File

@ -41,7 +41,7 @@ pub mod clipboard {
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let values: Vec<Value> = input.values.collect().await;
let values: Vec<Value> = input.collect().await;
let mut clip_stream = inner_clip(values, name).await;
while let Some(value) = clip_stream.next().await {

View File

@ -6,7 +6,7 @@ use crate::prelude::*;
use derive_new::new;
use getset::Getters;
use nu_errors::ShellError;
use nu_parser::hir;
use nu_protocol::hir;
use nu_protocol::{CallInfo, EvaluatedArgs, ReturnValue, Scope, Signature, Value};
use serde::{Deserialize, Serialize};
use std::ops::Deref;
@ -15,17 +15,28 @@ use std::sync::atomic::AtomicBool;
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct UnevaluatedCallInfo {
pub args: hir::Call,
pub source: Text,
pub name_tag: Tag,
pub scope: Scope,
}
impl UnevaluatedCallInfo {
pub fn evaluate(
pub fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
let args = evaluate_args(&self.args, registry, &self.scope)?;
Ok(CallInfo {
args,
name_tag: self.name_tag,
})
}
pub fn evaluate_with_new_it(
self,
registry: &CommandRegistry,
scope: &Scope,
it: &Value,
) -> Result<CallInfo, ShellError> {
let args = evaluate_args(&self.args, registry, scope, &self.source)?;
let mut scope = self.scope.clone();
scope = scope.set_it(it.clone());
let args = evaluate_args(&self.args, registry, &scope)?;
Ok(CallInfo {
args,
@ -114,7 +125,7 @@ impl CommandArgs {
let ctrl_c = self.ctrl_c.clone();
let shell_manager = self.shell_manager.clone();
let input = self.input;
let call_info = self.call_info.evaluate(registry, &Scope::empty())?;
let call_info = self.call_info.evaluate(registry)?;
Ok(EvaluatedWholeStreamCommandArgs::new(
host,
@ -134,7 +145,12 @@ impl CommandArgs {
let ctrl_c = self.ctrl_c.clone();
let shell_manager = self.shell_manager.clone();
let input = self.input;
let call_info = self.call_info.evaluate(registry, scope)?;
let call_info = UnevaluatedCallInfo {
name_tag: self.call_info.name_tag,
args: self.call_info.args,
scope: scope.clone(),
};
let call_info = call_info.evaluate(registry)?;
Ok(EvaluatedWholeStreamCommandArgs::new(
host,
@ -145,10 +161,6 @@ impl CommandArgs {
))
}
pub fn source(&self) -> Text {
self.call_info.source.clone()
}
pub fn process<'de, T: Deserialize<'de>, O: ToOutputStream>(
self,
registry: &CommandRegistry,
@ -156,7 +168,6 @@ impl CommandArgs {
) -> Result<RunnableArgs<T, O>, ShellError> {
let shell_manager = self.shell_manager.clone();
let host = self.host.clone();
let source = self.source();
let ctrl_c = self.ctrl_c.clone();
let args = self.evaluate_once(registry)?;
let call_info = args.call_info.clone();
@ -168,8 +179,7 @@ impl CommandArgs {
args: T::deserialize(&mut deserializer)?,
context: RunnableContext {
input,
commands: registry.clone(),
source,
registry: registry.clone(),
shell_manager,
name: name_tag,
host,
@ -193,7 +203,6 @@ impl CommandArgs {
let shell_manager = self.shell_manager.clone();
let host = self.host.clone();
let source = self.source();
let ctrl_c = self.ctrl_c.clone();
let args = self.evaluate_once(registry)?;
let call_info = args.call_info.clone();
@ -206,8 +215,7 @@ impl CommandArgs {
args: T::deserialize(&mut deserializer)?,
context: RunnableContext {
input,
commands: registry.clone(),
source,
registry: registry.clone(),
shell_manager,
name: name_tag,
host,
@ -229,15 +237,14 @@ pub struct RunnableContext {
pub input: InputStream,
pub shell_manager: ShellManager,
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub source: Text,
pub ctrl_c: Arc<AtomicBool>,
pub commands: CommandRegistry,
pub registry: CommandRegistry,
pub name: Tag,
}
impl RunnableContext {
pub fn get_command(&self, name: &str) -> Option<Arc<Command>> {
self.commands.get_command(name)
self.registry.get_command(name)
}
}
@ -374,6 +381,14 @@ impl EvaluatedCommandArgs {
self.call_info.args.nth(pos)
}
/// Get the nth positional argument, error if not possible
pub fn expect_nth(&self, pos: usize) -> Result<&Value, ShellError> {
match self.call_info.args.nth(pos) {
None => Err(ShellError::unimplemented("Better error: expect_nth")),
Some(item) => Ok(item),
}
}
pub fn get(&self, name: &str) -> Option<&Value> {
self.call_info.args.get(name)
}
@ -515,12 +530,17 @@ impl Command {
let out = args
.input
.values
.map(move |x| {
let call_info = raw_args
.clone()
.call_info
.evaluate(&registry, &Scope::it_value(x.clone()));
let call_info = UnevaluatedCallInfo {
args: raw_args.call_info.args.clone(),
name_tag: raw_args.call_info.name_tag.clone(),
scope: raw_args.call_info.scope.clone().set_it(x.clone()),
}
.evaluate(&registry);
// let call_info = raw_args
// .clone()
// .call_info
// .evaluate(&registry, &Scope::it_value(x.clone()));
match call_info {
Ok(call_info) => match command.run(&call_info, &registry, &raw_args, x) {
@ -576,9 +596,9 @@ impl WholeStreamCommand for FnFilterCommand {
let registry: CommandRegistry = registry.clone();
let func = self.func;
let result = input.values.map(move |it| {
let result = input.map(move |it| {
let registry = registry.clone();
let call_info = match call_info.clone().evaluate(&registry, &Scope::it_value(it)) {
let call_info = match call_info.clone().evaluate_with_new_it(&registry, &it) {
Err(err) => return OutputStream::from(vec![Err(err)]).values,
Ok(args) => args,
};

View File

@ -39,7 +39,7 @@ pub fn compact(
CompactArgs { rest: columns }: CompactArgs,
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let objects = input.values.filter(move |item| {
let objects = input.filter(move |item| {
let keep = if columns.is_empty() {
item.is_some()
} else {

View File

@ -41,7 +41,7 @@ impl WholeStreamCommand for Config {
)
.named(
"set_into",
SyntaxShape::Member,
SyntaxShape::String,
"sets a variable from values in the pipeline",
Some('i'),
)
@ -124,7 +124,7 @@ pub fn config(
yield ReturnSuccess::value(UntaggedValue::Row(result.into()).into_value(&value.tag));
}
else if let Some(v) = set_into {
let rows: Vec<Value> = input.values.collect().await;
let rows: Vec<Value> = input.collect().await;
let key = v.to_string();
if rows.len() == 0 {

View File

@ -37,7 +37,7 @@ pub fn count(
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let rows: Vec<Value> = input.values.collect().await;
let rows: Vec<Value> = input.collect().await;
yield ReturnSuccess::value(UntaggedValue::int(rows.len()).into_value(name))
};

View File

@ -37,7 +37,6 @@ fn debug_value(
RunnableContext { input, .. }: RunnableContext,
) -> Result<impl ToOutputStream, ShellError> {
Ok(input
.values
.map(move |v| {
if raw {
ReturnSuccess::value(

View File

@ -47,7 +47,6 @@ fn default(
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = input
.values
.map(move |item| {
let mut result = VecDeque::new();

View File

@ -0,0 +1,86 @@
use crate::commands::classified::block::run_block;
use crate::commands::PerItemCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use futures::stream::once;
use nu_errors::ShellError;
use nu_protocol::{CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
pub struct Each;
impl PerItemCommand for Each {
fn name(&self) -> &str {
"each"
}
fn signature(&self) -> Signature {
Signature::build("each").required(
"block",
SyntaxShape::Block,
"the block to run on each row",
)
}
fn usage(&self) -> &str {
"Run a block on each row of the table."
}
fn run(
&self,
call_info: &CallInfo,
registry: &CommandRegistry,
raw_args: &RawCommandArgs,
input: Value,
) -> Result<OutputStream, ShellError> {
let call_info = call_info.clone();
let registry = registry.clone();
let raw_args = raw_args.clone();
let stream = async_stream! {
match call_info.args.expect_nth(0)? {
Value {
value: UntaggedValue::Block(block),
tag
} => {
let mut context = Context::from_raw(&raw_args, &registry);
let input_clone = input.clone();
let input_stream = once(async { Ok(input) }).to_input_stream();
let result = run_block(
block,
&mut context,
input_stream,
&Scope::new(input_clone),
).await;
match result {
Ok(mut stream) => {
let errors = context.get_errors();
if let Some(error) = errors.first() {
yield Err(error.clone());
return;
}
while let Some(result) = stream.next().await {
yield Ok(ReturnSuccess::Value(result));
}
}
Err(e) => {
yield Err(e);
}
}
}
Value { tag, .. } => {
yield Err(ShellError::labeled_error(
"Expected a block",
"each needs a block",
tag,
))
}
};
};
Ok(stream.to_output_stream())
}
}

View File

@ -97,14 +97,15 @@ impl PerItemCommand for Enter {
ctrl_c: raw_args.ctrl_c,
shell_manager: raw_args.shell_manager,
call_info: UnevaluatedCallInfo {
args: nu_parser::hir::Call {
args: nu_protocol::hir::Call {
head: raw_args.call_info.args.head,
positional: None,
named: None,
span: Span::unknown()
span: Span::unknown(),
is_last: false,
},
source: raw_args.call_info.source,
name_tag: raw_args.call_info.name_tag,
scope: raw_args.call_info.scope.clone()
},
};
let mut result = converter.run(

View File

@ -45,7 +45,7 @@ pub fn evaluate_by(
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let values: Vec<Value> = input.values.collect().await;
let values: Vec<Value> = input.collect().await;
if values.is_empty() {
yield Err(ShellError::labeled_error(

View File

@ -48,7 +48,5 @@ fn first(
1
};
Ok(OutputStream::from_input(
context.input.values.take(rows_desired),
))
Ok(OutputStream::from_input(context.input.take(rows_desired)))
}

View File

@ -9,11 +9,6 @@ use nu_source::Tagged;
use nu_value_ext::{as_column_path, get_data_by_column_path};
use std::borrow::Borrow;
use nom::{
bytes::complete::{tag, take_while},
IResult,
};
pub struct Format;
impl PerItemCommand for Format {
@ -45,14 +40,8 @@ impl PerItemCommand for Format {
let pattern_tag = pattern.tag.clone();
let pattern = pattern.as_string()?;
let format_pattern = format(&pattern).map_err(|_| {
ShellError::labeled_error(
"Could not create format pattern",
"could not create format pattern",
&pattern_tag,
)
})?;
let commands = format_pattern.1;
let format_pattern = format(&pattern);
let commands = format_pattern;
let output = match value {
value
@ -66,7 +55,7 @@ impl PerItemCommand for Format {
for command in &commands {
match command {
FormatCommand::Text(s) => {
output.push_str(s);
output.push_str(&s);
}
FormatCommand::Column(c) => {
let key = to_column_path(&c, &pattern_tag)?;
@ -104,32 +93,43 @@ enum FormatCommand {
Column(String),
}
fn format(input: &str) -> IResult<&str, Vec<FormatCommand>> {
fn format(input: &str) -> Vec<FormatCommand> {
let mut output = vec![];
let mut loop_input = input;
let mut loop_input = input.chars();
loop {
let (input, before) = take_while(|c| c != '{')(loop_input)?;
let mut before = String::new();
while let Some(c) = loop_input.next() {
if c == '{' {
break;
}
before.push(c);
}
if !before.is_empty() {
output.push(FormatCommand::Text(before.to_string()));
}
if input != "" {
// Look for column as we're now at one
let (input, _) = tag("{")(input)?;
let (input, column) = take_while(|c| c != '}')(input)?;
let (input, _) = tag("}")(input)?;
// Look for column as we're now at one
let mut column = String::new();
output.push(FormatCommand::Column(column.to_string()));
loop_input = input;
} else {
loop_input = input;
while let Some(c) = loop_input.next() {
if c == '}' {
break;
}
column.push(c);
}
if loop_input == "" {
if !column.is_empty() {
output.push(FormatCommand::Column(column.to_string()));
}
if before.is_empty() && column.is_empty() {
break;
}
}
Ok((loop_input, output))
output
}
fn to_column_path(

View File

@ -1,12 +1,45 @@
use crate::prelude::*;
use csv::{ErrorKind, ReaderBuilder};
use nu_errors::ShellError;
use nu_parser::hir::syntax_shape::{ExpandContext, SignatureRegistry};
use nu_parser::utils::{parse_line_with_separator as parse, LineSeparatedShape};
use nu_parser::TokensIterator;
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::nom_input;
use nu_protocol::{ReturnSuccess, TaggedDictBuilder, UntaggedValue, Value};
use derive_new::new;
fn from_delimited_string_to_value(
s: String,
headerless: bool,
separator: char,
tag: impl Into<Tag>,
) -> Result<Value, csv::Error> {
let mut reader = ReaderBuilder::new()
.has_headers(!headerless)
.delimiter(separator as u8)
.from_reader(s.as_bytes());
let tag = tag.into();
let headers = if headerless {
(1..=reader.headers()?.len())
.map(|i| format!("Column{}", i))
.collect::<Vec<String>>()
} else {
reader.headers()?.iter().map(String::from).collect()
};
let mut rows = vec![];
for row in reader.records() {
let mut tagged_row = TaggedDictBuilder::new(&tag);
for (value, header) in row?.iter().zip(headers.iter()) {
if let Ok(i) = value.parse::<i64>() {
tagged_row.insert_value(header, UntaggedValue::int(i).into_value(&tag))
} else if let Ok(f) = value.parse::<f64>() {
tagged_row.insert_value(header, UntaggedValue::decimal(f).into_value(&tag))
} else {
tagged_row.insert_value(header, UntaggedValue::string(value).into_value(&tag))
}
}
rows.push(tagged_row.into_value());
}
Ok(UntaggedValue::Table(rows).into_value(&tag))
}
pub fn from_delimited_data(
headerless: bool,
@ -20,20 +53,19 @@ pub fn from_delimited_data(
let concat_string = input.collect_string(name_tag.clone()).await?;
match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) {
Ok(rows) => {
for row in rows {
match row {
Value { value: UntaggedValue::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
}
x => yield ReturnSuccess::value(x),
Ok(x) => match x {
Value { value: UntaggedValue::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
}
x => yield ReturnSuccess::value(x),
},
Err(err) => {
let line_one = format!("Could not parse as {}", format_name);
let line_one = match pretty_csv_error(err) {
Some(pretty) => format!("Could not parse as {} ({})", format_name,pretty),
None => format!("Could not parse as {}", format_name),
};
let line_two = format!("input cannot be parsed as {}", format_name);
yield Err(ShellError::labeled_error_with_secondary(
line_one,
@ -49,121 +81,25 @@ pub fn from_delimited_data(
Ok(stream.to_output_stream())
}
#[derive(Debug, Clone, new)]
pub struct EmptyRegistry {
#[new(default)]
signatures: indexmap::IndexMap<String, Signature>,
}
impl EmptyRegistry {}
impl SignatureRegistry for EmptyRegistry {
fn has(&self, _name: &str) -> bool {
false
}
fn get(&self, _name: &str) -> Option<Signature> {
None
}
fn clone_box(&self) -> Box<dyn SignatureRegistry> {
Box::new(self.clone())
}
}
fn from_delimited_string_to_value(
s: String,
headerless: bool,
sep: char,
tag: impl Into<Tag>,
) -> Result<Vec<Value>, ShellError> {
let tag = tag.into();
let mut entries = s.lines();
let mut fields = vec![];
let mut out = vec![];
if let Some(first_entry) = entries.next() {
let tokens = match parse(&sep.to_string(), nom_input(first_entry)) {
Ok((_, tokens)) => tokens,
Err(err) => return Err(ShellError::parse_error(err)),
};
let tokens_span = tokens.span;
let source: nu_source::Text = tokens_span.slice(&first_entry).into();
if !headerless {
fields = tokens
.item
.iter()
.filter(|token| !token.is_separator())
.map(|field| field.source(&source).to_string())
.collect::<Vec<_>>();
}
let registry = Box::new(EmptyRegistry::new());
let ctx = ExpandContext::new(registry, &source, None);
let mut iterator = TokensIterator::new(&tokens.item, ctx, tokens_span);
let (results, tokens_identified) = iterator.expand(LineSeparatedShape);
let results = results?;
let mut row = TaggedDictBuilder::new(&tag);
if headerless {
let fallback_columns = (1..=tokens_identified)
.map(|i| format!("Column{}", i))
.collect::<Vec<String>>();
for (idx, field) in results.into_iter().enumerate() {
let key = if headerless {
&fallback_columns[idx]
} else {
&fields[idx]
};
row.insert_value(key, field.into_value(&tag));
}
out.push(row.into_value())
}
}
for entry in entries {
let tokens = match parse(&sep.to_string(), nom_input(entry)) {
Ok((_, tokens)) => tokens,
Err(err) => return Err(ShellError::parse_error(err)),
};
let tokens_span = tokens.span;
let source: nu_source::Text = tokens_span.slice(&entry).into();
let registry = Box::new(EmptyRegistry::new());
let ctx = ExpandContext::new(registry, &source, None);
let mut iterator = TokensIterator::new(&tokens.item, ctx, tokens_span);
let (results, tokens_identified) = iterator.expand(LineSeparatedShape);
let results = results?;
let mut row = TaggedDictBuilder::new(&tag);
let fallback_columns = (1..=tokens_identified)
.map(|i| format!("Column{}", i))
.collect::<Vec<String>>();
for (idx, field) in results.into_iter().enumerate() {
let key = if headerless {
&fallback_columns[idx]
fn pretty_csv_error(err: csv::Error) -> Option<String> {
match err.kind() {
ErrorKind::UnequalLengths {
pos,
expected_len,
len,
} => {
if let Some(pos) = pos {
Some(format!(
"Line {}: expected {} fields, found {}",
pos.line(),
expected_len,
len
))
} else {
match fields.get(idx) {
Some(key) => key,
None => &fallback_columns[idx],
}
};
row.insert_value(key, field.into_value(&tag));
Some(format!("Expected {} fields, found {}", expected_len, len))
}
}
out.push(row.into_value())
ErrorKind::Seek => Some("Internal error while parsing csv".to_string()),
_ => None,
}
Ok(out)
}

View File

@ -197,7 +197,6 @@ pub fn get(
let member = fields.remove(0);
trace!("get {:?} {:?}", member, fields);
let stream = input
.values
.map(move |item| {
let mut result = VecDeque::new();

View File

@ -43,7 +43,7 @@ pub fn group_by(
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let values: Vec<Value> = input.values.collect().await;
let values: Vec<Value> = input.collect().await;
if values.is_empty() {
yield Err(ShellError::labeled_error(

View File

@ -35,7 +35,7 @@ pub fn headers(
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let rows: Vec<Value> = input.values.collect().await;
let rows: Vec<Value> = input.collect().await;
if rows.len() < 1 {
yield Err(ShellError::untagged_runtime_error("Couldn't find headers, was the input a properly formatted, non-empty table?"));

View File

@ -76,6 +76,12 @@ impl PerItemCommand for Help {
return Ok(
get_help(&command.name(), &command.usage(), command.signature()).into(),
);
} else {
return Err(ShellError::labeled_error(
"Can't find command (use 'help commands' for full list)",
"can't find command",
tag,
));
}
let help = futures::stream::iter(help);
Ok(help.to_output_stream())

View File

@ -30,7 +30,7 @@ impl WholeStreamCommand for Histogram {
"the name of the column to graph by",
)
.rest(
SyntaxShape::Member,
SyntaxShape::String,
"column name to give the histogram's frequency column",
)
}
@ -53,7 +53,7 @@ pub fn histogram(
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let values: Vec<Value> = input.values.collect().await;
let values: Vec<Value> = input.collect().await;
let Tagged { item: group_by, .. } = column_name.clone();

View File

@ -47,7 +47,7 @@ fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream,
let mut leftover_string = String::new();
let stream = async_stream! {
loop {
match input.values.next().await {
match input.next().await {
Some(Value { value: UntaggedValue::Primitive(Primitive::String(st)), ..}) => {
let mut st = leftover_string.clone() + &st;
leftover.clear();

View File

@ -46,7 +46,7 @@ pub fn map_max_by(
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let values: Vec<Value> = input.values.collect().await;
let values: Vec<Value> = input.collect().await;
if values.is_empty() {

View File

@ -22,7 +22,7 @@ impl WholeStreamCommand for Nth {
Signature::build("nth")
.required(
"row number",
SyntaxShape::Any,
SyntaxShape::Int,
"the number of the row to return",
)
.rest(SyntaxShape::Any, "Optionally return more rows")
@ -49,7 +49,6 @@ fn nth(
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = input
.values
.enumerate()
.map(move |(idx, item)| {
let row_number = vec![row_number.clone()];

View File

@ -6,10 +6,6 @@ use nu_protocol::{
CallInfo, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
};
use nom::{
bytes::complete::{tag, take_while},
IResult,
};
use regex::Regex;
#[derive(Debug)]
@ -18,32 +14,44 @@ enum ParseCommand {
Column(String),
}
fn parse(input: &str) -> IResult<&str, Vec<ParseCommand>> {
fn parse(input: &str) -> Vec<ParseCommand> {
let mut output = vec![];
let mut loop_input = input;
//let mut loop_input = input;
let mut loop_input = input.chars();
loop {
let (input, before) = take_while(|c| c != '{')(loop_input)?;
let mut before = String::new();
while let Some(c) = loop_input.next() {
if c == '{' {
break;
}
before.push(c);
}
if !before.is_empty() {
output.push(ParseCommand::Text(before.to_string()));
}
if input != "" {
// Look for column as we're now at one
let (input, _) = tag("{")(input)?;
let (input, column) = take_while(|c| c != '}')(input)?;
let (input, _) = tag("}")(input)?;
// Look for column as we're now at one
let mut column = String::new();
output.push(ParseCommand::Column(column.to_string()));
loop_input = input;
} else {
loop_input = input;
while let Some(c) = loop_input.next() {
if c == '}' {
break;
}
column.push(c);
}
if loop_input == "" {
if !column.is_empty() {
output.push(ParseCommand::Column(column.to_string()));
}
if before.is_empty() && column.is_empty() {
break;
}
}
Ok((loop_input, output))
output
}
fn column_names(commands: &[ParseCommand]) -> Vec<String> {
@ -103,16 +111,10 @@ impl PerItemCommand for Parse {
//let value_tag = value.tag();
let pattern = call_info.args.expect_nth(0)?.as_string()?;
let parse_pattern = parse(&pattern).map_err(|_| {
ShellError::labeled_error(
"Could not create parse pattern",
"could not create parse pattern",
&value.tag,
)
})?;
let parse_regex = build_regex(&parse_pattern.1);
let parse_pattern = parse(&pattern);
let parse_regex = build_regex(&parse_pattern);
let column_names = column_names(&parse_pattern.1);
let column_names = column_names(&parse_pattern);
let regex = Regex::new(&parse_regex).map_err(|_| {
ShellError::labeled_error("Could not parse regex", "could not parse regex", &value.tag)
})?;

View File

@ -1,7 +1,6 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use futures_util::pin_mut;
use nu_errors::ShellError;
use nu_protocol::{
ColumnPath, PathMember, Primitive, ReturnSuccess, ReturnValue, Signature, SyntaxShape,
@ -44,7 +43,9 @@ impl WholeStreamCommand for Pick {
fn pick(
PickArgs { rest: mut fields }: PickArgs,
RunnableContext { input, name, .. }: RunnableContext,
RunnableContext {
mut input, name, ..
}: RunnableContext,
) -> Result<OutputStream, ShellError> {
if fields.is_empty() {
return Err(ShellError::labeled_error(
@ -64,13 +65,10 @@ fn pick(
.collect::<Vec<ColumnPath>>();
let stream = async_stream! {
let values = input.values;
pin_mut!(values);
let mut empty = true;
let mut bring_back: indexmap::IndexMap<String, Vec<Value>> = indexmap::IndexMap::new();
while let Some(value) = values.next().await {
while let Some(value) = input.next().await {
for path in &column_paths {
let path_members_span = span_for_spanned_list(path.members().iter().map(|p| p.span));

View File

@ -3,7 +3,7 @@ use crate::prelude::*;
use derive_new::new;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, ReturnValue, Scope, Signature, UntaggedValue, Value};
use nu_protocol::{Primitive, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value};
use serde::{self, Deserialize, Serialize};
use std::io::prelude::*;
use std::io::BufReader;
@ -71,10 +71,13 @@ pub fn filter_plugin(
) -> Result<OutputStream, ShellError> {
trace!("filter_plugin :: {}", path);
let args = args.evaluate_once_with_scope(
registry,
&Scope::it_value(UntaggedValue::string("$it").into_untagged_value()),
)?;
let scope = &args
.call_info
.scope
.clone()
.set_it(UntaggedValue::string("$it").into_untagged_value());
let args = args.evaluate_once_with_scope(registry, &scope)?;
let mut child = std::process::Command::new(path)
.stdin(std::process::Stdio::piped())
@ -95,7 +98,7 @@ pub fn filter_plugin(
trace!("filtering :: {:?}", call_info);
let stream = bos
.chain(args.input.values)
.chain(args.input)
.chain(eos)
.map(move |v| match v {
Value {
@ -340,7 +343,7 @@ pub fn sink_plugin(
let call_info = args.call_info.clone();
let stream = async_stream! {
let input: Vec<Value> = args.input.values.collect().await;
let input: Vec<Value> = args.input.collect().await;
let request = JsonRpc::new("sink", (call_info.clone(), input));
let request_raw = serde_json::to_string(&request);

View File

@ -43,5 +43,5 @@ fn prepend(
) -> Result<OutputStream, ShellError> {
let prepend = futures::stream::iter(vec![row]);
Ok(OutputStream::from_input(prepend.chain(input.values)))
Ok(prepend.chain(input).to_output_stream())
}

View File

@ -50,7 +50,5 @@ fn range(
let from = *from as usize;
let to = *to as usize;
Ok(OutputStream::from_input(
input.values.skip(from).take(to - from + 1),
))
Ok(input.skip(from).take(to - from + 1).to_output_stream())
}

View File

@ -45,7 +45,7 @@ pub fn reduce_by(
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let values: Vec<Value> = input.values.collect().await;
let values: Vec<Value> = input.collect().await;
if values.is_empty() {
yield Err(ShellError::labeled_error(

View File

@ -18,7 +18,7 @@ impl WholeStreamCommand for Reject {
}
fn signature(&self) -> Signature {
Signature::build("reject").rest(SyntaxShape::Member, "the names of columns to remove")
Signature::build("reject").rest(SyntaxShape::String, "the names of columns to remove")
}
fn usage(&self) -> &str {
@ -48,9 +48,7 @@ fn reject(
let fields: Vec<_> = fields.iter().map(|f| f.item.clone()).collect();
let stream = input
.values
.map(move |item| reject_fields(&item, &fields, &item.tag));
let stream = input.map(move |item| reject_fields(&item, &fields, &item.tag));
Ok(stream.from_input_stream())
}

View File

@ -26,7 +26,7 @@ impl WholeStreamCommand for Rename {
"the name of the column to rename for",
)
.rest(
SyntaxShape::Member,
SyntaxShape::String,
"Additional column name(s) to rename for",
)
}
@ -54,7 +54,6 @@ pub fn rename(
let new_column_names = new_column_names.into_iter().flatten().collect::<Vec<_>>();
let stream = input
.values
.map(move |item| {
let mut result = VecDeque::new();

View File

@ -32,7 +32,7 @@ fn reverse(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
let args = args.evaluate_once(registry)?;
let (input, _args) = args.parts();
let input = input.values.collect::<Vec<_>>();
let input = input.collect::<Vec<_>>();
let output = input.map(move |mut vec| {
vec.reverse();

View File

@ -12,6 +12,7 @@ pub struct Remove;
pub struct RemoveArgs {
pub rest: Vec<Tagged<PathBuf>>,
pub recursive: Tagged<bool>,
#[allow(unused)]
pub trash: Tagged<bool>,
}

View File

@ -0,0 +1,108 @@
use crate::commands::classified::block::run_block;
use crate::prelude::*;
use derive_new::new;
use nu_errors::ShellError;
use nu_protocol::{hir::Block, CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, Value};
#[derive(new)]
pub struct AliasCommand {
name: String,
args: Vec<String>,
block: Block,
}
impl PerItemCommand for AliasCommand {
fn name(&self) -> &str {
&self.name
}
fn signature(&self) -> Signature {
let mut alias = Signature::build(&self.name);
for arg in &self.args {
alias = alias.optional(arg, SyntaxShape::Any, "");
}
alias
}
fn usage(&self) -> &str {
""
}
fn run(
&self,
call_info: &CallInfo,
registry: &CommandRegistry,
raw_args: &RawCommandArgs,
input: Value,
) -> Result<OutputStream, ShellError> {
let tag = call_info.name_tag.clone();
let call_info = call_info.clone();
let registry = registry.clone();
let raw_args = raw_args.clone();
let block = self.block.clone();
let mut scope = Scope::it_value(input.clone());
if let Some(positional) = &call_info.args.positional {
for (pos, arg) in positional.iter().enumerate() {
scope = scope.set_var(self.args[pos].to_string(), arg.clone());
}
}
let stream = async_stream! {
let mut context = Context::from_raw(&raw_args, &registry);
let input_clone = Ok(input.clone());
let input_stream = futures::stream::once(async { input_clone }).boxed().to_input_stream();
let result = run_block(
&block,
&mut context,
input_stream,
&scope
).await;
match result {
Ok(stream) if stream.is_empty() => {
yield Err(ShellError::labeled_error(
"Expected a block",
"alias needs a block",
tag,
));
}
Ok(mut stream) => {
// We collect first to ensure errors are put into the context
while let Some(result) = stream.next().await {
yield Ok(ReturnSuccess::Value(result));
}
let errors = context.get_errors();
if let Some(x) = errors.first() {
//yield Err(error.clone());
yield Err(ShellError::labeled_error_with_secondary(
"Alias failed to run",
"alias failed to run",
tag.clone(),
x.to_string(),
tag
));
}
}
Err(e) => {
//yield Err(e);
yield Err(ShellError::labeled_error_with_secondary(
"Alias failed to run",
"alias failed to run",
tag.clone(),
e.to_string(),
tag
));
}
}
};
Ok(stream.to_output_stream())
}
}

View File

@ -0,0 +1,122 @@
use crate::commands::classified::external;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use derive_new::new;
use parking_lot::Mutex;
use nu_errors::ShellError;
use nu_protocol::hir::{Expression, ExternalArgs, ExternalCommand, Literal, SpannedExpression};
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
#[derive(Deserialize)]
pub struct RunExternalArgs {}
#[derive(new)]
pub struct RunExternalCommand;
fn spanned_expression_to_string(expr: SpannedExpression) -> String {
if let SpannedExpression {
expr: Expression::Literal(Literal::String(s)),
..
} = expr
{
s
} else {
"notacommand!!!".to_string()
}
}
impl WholeStreamCommand for RunExternalCommand {
fn name(&self) -> &str {
"run_external"
}
fn signature(&self) -> Signature {
Signature::build(self.name()).rest(SyntaxShape::Any, "external command arguments")
}
fn usage(&self) -> &str {
""
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let positionals = args.call_info.args.positional.ok_or_else(|| {
ShellError::untagged_runtime_error("positional arguments unexpectedly empty")
})?;
let mut positionals = positionals.into_iter();
let name = positionals
.next()
.map(spanned_expression_to_string)
.ok_or_else(|| {
ShellError::untagged_runtime_error(
"run_external unexpectedly missing external name positional arg",
)
})?;
let command = ExternalCommand {
name,
name_tag: args.call_info.name_tag.clone(),
args: ExternalArgs {
list: positionals.collect(),
span: args.call_info.args.span,
},
};
let mut external_context;
#[cfg(windows)]
{
external_context = Context {
registry: registry.clone(),
host: args.host.clone(),
shell_manager: args.shell_manager.clone(),
ctrl_c: args.ctrl_c.clone(),
current_errors: Arc::new(Mutex::new(vec![])),
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
};
}
#[cfg(not(windows))]
{
external_context = Context {
registry: registry.clone(),
host: args.host.clone(),
shell_manager: args.shell_manager.clone(),
ctrl_c: args.ctrl_c.clone(),
current_errors: Arc::new(Mutex::new(vec![])),
};
}
let scope = args.call_info.scope.clone();
let is_last = args.call_info.args.is_last;
let input = args.input;
let stream = async_stream! {
let result = external::run_external_command(
command,
&mut external_context,
input,
&scope,
is_last,
).await;
match result {
Ok(mut stream) => {
while let Some(value) = stream.next().await {
yield Ok(ReturnSuccess::Value(value));
}
},
Err(e) => {
yield Err(e);
},
_ => {}
}
};
Ok(stream.to_output_stream())
}
}

View File

@ -1,7 +1,7 @@
use crate::commands::{UnevaluatedCallInfo, WholeStreamCommand};
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use std::path::{Path, PathBuf};
@ -168,7 +168,7 @@ fn save(
shell_manager,
host,
ctrl_c,
commands: registry,
registry,
..
}: RunnableContext,
raw_args: RawCommandArgs,
@ -177,7 +177,7 @@ fn save(
let name_tag = name.clone();
let stream = async_stream! {
let input: Vec<Value> = input.values.collect().await;
let input: Vec<Value> = input.collect().await;
if path.is_none() {
// If there is no filename, check the metadata for the anchor filename
if input.len() > 0 {
@ -228,14 +228,15 @@ fn save(
ctrl_c,
shell_manager,
call_info: UnevaluatedCallInfo {
args: nu_parser::hir::Call {
args: nu_protocol::hir::Call {
head: raw_args.call_info.args.head,
positional: None,
named: None,
span: Span::unknown()
span: Span::unknown(),
is_last: false,
},
source: raw_args.call_info.source,
name_tag: raw_args.call_info.name_tag,
scope: Scope::empty(), // FIXME?
}
};
let mut result = converter.run(new_args.with_input(input), &registry);

View File

@ -36,9 +36,9 @@ fn shells(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream
let mut dict = TaggedDictBuilder::new(&tag);
if index == (*args.shell_manager.current_shell).load(Ordering::SeqCst) {
dict.insert_untagged(" ", "X".to_string());
dict.insert_untagged("active", "X".to_string());
} else {
dict.insert_untagged(" ", " ".to_string());
dict.insert_untagged("active", " ".to_string());
}
dict.insert_untagged("name", shell.name());
dict.insert_untagged("path", shell.path());

View File

@ -48,7 +48,7 @@ fn shuffle(
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let mut values: Vec<Value> = input.values.collect().await;
let mut values: Vec<Value> = input.collect().await;
let out = if let Some(n) = limit {
let (shuffled, _) = values.partial_shuffle(&mut thread_rng(), *n as usize);

View File

@ -33,7 +33,6 @@ fn size(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream,
let name_span = tag.span;
Ok(input
.values
.map(move |v| {
if let Ok(s) = v.as_string() {
ReturnSuccess::value(count(&s, &v.tag))

View File

@ -41,7 +41,5 @@ fn skip(SkipArgs { rows }: SkipArgs, context: RunnableContext) -> Result<OutputS
1
};
Ok(OutputStream::from_input(
context.input.values.skip(rows_desired),
))
Ok(OutputStream::from_input(context.input.skip(rows_desired)))
}

View File

@ -1,16 +1,12 @@
use crate::commands::WholeStreamCommand;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{Evaluate, Scope, Signature, SyntaxShape};
use nu_protocol::{hir::ClassifiedCommand, Scope, Signature, SyntaxShape, UntaggedValue, Value};
pub struct SkipWhile;
#[derive(Deserialize)]
pub struct SkipWhileArgs {
condition: Evaluate,
}
impl WholeStreamCommand for SkipWhile {
fn name(&self) -> &str {
"skip-while"
@ -20,7 +16,7 @@ impl WholeStreamCommand for SkipWhile {
Signature::build("skip-while")
.required(
"condition",
SyntaxShape::Block,
SyntaxShape::Math,
"the condition that must be met to continue skipping",
)
.filter()
@ -35,26 +31,66 @@ impl WholeStreamCommand for SkipWhile {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, skip_while)?.run()
}
}
let registry = registry.clone();
let call_info = args.evaluate_once(&registry)?;
pub fn skip_while(
SkipWhileArgs { condition }: SkipWhileArgs,
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let objects = input.values.skip_while(move |item| {
trace!("ITEM = {:?}", item);
let result = condition.invoke(&Scope::new(item.clone()));
trace!("RESULT = {:?}", result);
let block = call_info.args.expect_nth(0)?.clone();
let return_value = match result {
Ok(ref v) if v.is_true() => true,
_ => false,
let condition = match block {
Value {
value: UntaggedValue::Block(block),
tag,
} => {
if block.block.len() != 1 {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
match block.block[0].list.get(0) {
Some(item) => match item {
ClassifiedCommand::Expr(expr) => expr.clone(),
_ => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
))
}
},
None => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
}
}
Value { tag, .. } => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
};
futures::future::ready(return_value)
});
let objects = call_info.input.skip_while(move |item| {
let condition = condition.clone();
trace!("ITEM = {:?}", item);
let result = evaluate_baseline_expr(&*condition, &registry, &Scope::new(item.clone()));
trace!("RESULT = {:?}", result);
Ok(objects.from_input_stream())
let return_value = match result {
Ok(ref v) if v.is_true() => true,
_ => false,
};
futures::future::ready(return_value)
});
Ok(objects.from_input_stream())
}
}

View File

@ -44,7 +44,7 @@ pub fn split_by(
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let values: Vec<Value> = input.values.collect().await;
let values: Vec<Value> = input.collect().await;
if values.len() > 1 || values.is_empty() {
yield Err(ShellError::labeled_error(

View File

@ -30,7 +30,7 @@ impl WholeStreamCommand for SplitColumn {
"the character that denotes what separates columns",
)
.switch("collapse-empty", "remove empty columns", Some('c'))
.rest(SyntaxShape::Member, "column names to give the new columns")
.rest(SyntaxShape::String, "column names to give the new columns")
}
fn usage(&self) -> &str {
@ -57,7 +57,6 @@ fn split_column(
let name_span = name.span;
Ok(input
.values
.map(move |v| {
if let Ok(s) = v.as_string() {
let splitter = separator.replace("\\n", "\n");

View File

@ -43,7 +43,6 @@ fn split_row(
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = input
.values
.map(move |v| {
if let Ok(s) = v.as_string() {
let splitter = separator.item.replace("\\n", "\n");

View File

@ -27,10 +27,9 @@ impl WholeStreamCommand for Sum {
) -> Result<OutputStream, ShellError> {
sum(RunnableContext {
input: args.input,
commands: registry.clone(),
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
source: args.call_info.source,
ctrl_c: args.ctrl_c,
name: args.call_info.name_tag,
})

View File

@ -69,7 +69,7 @@ fn t_sort_by(
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
Ok(OutputStream::new(async_stream! {
let values: Vec<Value> = input.values.collect().await;
let values: Vec<Value> = input.collect().await;
let column_grouped_by_name = if let Some(grouped_by) = group_by {
Some(grouped_by.item().clone())

View File

@ -30,7 +30,6 @@ impl WholeStreamCommand for Tags {
fn tags(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
Ok(args
.input
.values
.map(move |v| {
let mut tags = TaggedDictBuilder::new(v.tag());
{

View File

@ -266,7 +266,7 @@ fn to_bson(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
let name_span = name_tag.span;
let stream = async_stream! {
let input: Vec<Value> = args.input.values.collect().await;
let input: Vec<Value> = args.input.collect().await;
let to_process_input = if input.len() > 1 {
let tag = input[0].tag.clone();

View File

@ -175,7 +175,7 @@ pub fn to_delimited_data(
let name_span = name_tag.span;
let stream = async_stream! {
let input: Vec<Value> = input.values.collect().await;
let input: Vec<Value> = input.collect().await;
let to_process_input = if input.len() > 1 {
let tag = input[0].tag.clone();

View File

@ -35,7 +35,7 @@ fn to_html(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
let name_tag = args.name_tag();
//let name_span = name_tag.span;
let stream = async_stream! {
let input: Vec<Value> = args.input.values.collect().await;
let input: Vec<Value> = args.input.collect().await;
let headers = nu_protocol::merge_descriptors(&input);
let mut output_string = "<html><body>".to_string();

View File

@ -136,7 +136,7 @@ fn to_json(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
let name_tag = args.name_tag();
let name_span = name_tag.span;
let stream = async_stream! {
let input: Vec<Value> = args.input.values.collect().await;
let input: Vec<Value> = args.input.collect().await;
let to_process_input = if input.len() > 1 {
let tag = input[0].tag.clone();

View File

@ -34,7 +34,7 @@ fn to_html(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
let name_tag = args.name_tag();
//let name_span = name_tag.span;
let stream = async_stream! {
let input: Vec<Value> = args.input.values.collect().await;
let input: Vec<Value> = args.input.collect().await;
let headers = nu_protocol::merge_descriptors(&input);
let mut output_string = String::new();

View File

@ -205,7 +205,7 @@ fn to_sqlite(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStre
let args = args.evaluate_once(registry)?;
let name_tag = args.name_tag();
let stream = async_stream! {
let input: Vec<Value> = args.input.values.collect().await;
let input: Vec<Value> = args.input.collect().await;
match sqlite_input_stream_to_bytes(input) {
Ok(out) => yield ReturnSuccess::value(out),

View File

@ -98,7 +98,7 @@ fn to_toml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
let name_tag = args.name_tag();
let name_span = name_tag.span;
let stream = async_stream! {
let input: Vec<Value> = args.input.values.collect().await;
let input: Vec<Value> = args.input.collect().await;
let to_process_input = if input.len() > 1 {
let tag = input[0].tag.clone();

View File

@ -33,7 +33,7 @@ fn to_url(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream,
let input = args.input;
let stream = async_stream! {
let input: Vec<Value> = input.values.collect().await;
let input: Vec<Value> = input.collect().await;
for value in input {
match value {

View File

@ -130,7 +130,7 @@ fn to_yaml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
let name_span = name_tag.span;
let stream = async_stream! {
let input: Vec<Value> = args.input.values.collect().await;
let input: Vec<Value> = args.input.collect().await;
let to_process_input = if input.len() > 1 {
let tag = input[0].tag.clone();

View File

@ -29,10 +29,8 @@ impl WholeStreamCommand for Trim {
}
fn trim(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let input = args.input;
Ok(input
.values
Ok(args
.input
.map(move |v| {
let string = String::extract(&v)?;
ReturnSuccess::value(UntaggedValue::string(string.trim()).into_value(v.tag()))

View File

@ -37,7 +37,7 @@ fn uniq(
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let uniq_values: IndexSet<_> = input.values.collect().await;
let uniq_values: IndexSet<_> = input.collect().await;
for item in uniq_values.iter().map(|row| ReturnSuccess::value(row.clone())) {
yield item;

View File

@ -1,8 +1,6 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use futures::StreamExt;
use futures_util::pin_mut;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, ReturnValue, Signature, UntaggedValue};
@ -34,14 +32,11 @@ impl WholeStreamCommand for What {
}
pub fn what(
WhatArgs {}: WhatArgs,
RunnableContext { input, .. }: RunnableContext,
_: WhatArgs,
RunnableContext { mut input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let values = input.values;
pin_mut!(values);
while let Some(row) = values.next().await {
while let Some(row) = input.next().await {
let name = value::format_type(&row, 100);
yield ReturnSuccess::value(UntaggedValue::string(name).into_value(Tag::unknown_anchor(row.tag.span)));
}

View File

@ -1,8 +1,12 @@
use crate::commands::PerItemCommand;
use crate::context::CommandRegistry;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{
hir::ClassifiedCommand, CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue,
Value,
};
pub struct Where;
@ -14,7 +18,7 @@ impl PerItemCommand for Where {
fn signature(&self) -> Signature {
Signature::build("where").required(
"condition",
SyntaxShape::Block,
SyntaxShape::Math,
"the condition that must match",
)
}
@ -26,37 +30,67 @@ impl PerItemCommand for Where {
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
input: Value,
) -> Result<OutputStream, ShellError> {
let condition = call_info.args.expect_nth(0)?;
let stream = match condition {
let block = call_info.args.expect_nth(0)?.clone();
let condition = match block {
Value {
value: UntaggedValue::Block(block),
..
tag,
} => {
let result = block.invoke(&Scope::new(input.clone()));
match result {
Ok(v) => {
if v.is_true() {
VecDeque::from(vec![Ok(ReturnSuccess::Value(input))])
} else {
VecDeque::new()
if block.block.len() != 1 {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
match block.block[0].list.get(0) {
Some(item) => match item {
ClassifiedCommand::Expr(expr) => expr.clone(),
_ => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
))
}
},
None => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
Err(e) => return Err(e),
}
}
Value { tag, .. } => {
return Err(ShellError::labeled_error(
"Expected a condition",
"where needs a condition",
"expected a condition",
tag,
))
));
}
};
//FIXME: should we use the scope that's brought in as well?
let condition = evaluate_baseline_expr(&condition, registry, &Scope::new(input.clone()))?;
let stream = match condition.as_bool() {
Ok(b) => {
if b {
VecDeque::from(vec![Ok(ReturnSuccess::Value(input))])
} else {
VecDeque::new()
}
}
Err(e) => return Err(e),
};
Ok(stream.into())
}
}

View File

@ -78,7 +78,7 @@ struct WhichArgs {
fn which(
WhichArgs { application, all }: WhichArgs,
RunnableContext { commands, .. }: RunnableContext,
RunnableContext { registry, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let external = application.starts_with('^');
let item = if external {
@ -89,7 +89,7 @@ fn which(
let stream = async_stream! {
if !external {
let builtin = commands.has(&item);
let builtin = registry.has(&item);
if builtin {
yield ReturnSuccess::value(entry_builtin!(item, application.tag.clone()));
}

View File

@ -1,11 +1,13 @@
use crate::commands::{command::CommandArgs, Command, UnevaluatedCallInfo};
use crate::commands::{
command::CommandArgs, command::RawCommandArgs, Command, UnevaluatedCallInfo,
};
use crate::env::host::Host;
use crate::shell::shell_manager::ShellManager;
use crate::stream::{InputStream, OutputStream};
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_parser::{hir, hir::syntax_shape::ExpandContext, hir::syntax_shape::SignatureRegistry};
use nu_protocol::Signature;
use nu_parser::SignatureRegistry;
use nu_protocol::{hir, Scope, Signature};
use nu_source::{Tag, Text};
use parking_lot::Mutex;
use std::error::Error;
@ -40,12 +42,6 @@ impl CommandRegistry {
}
impl CommandRegistry {
pub(crate) fn empty() -> CommandRegistry {
CommandRegistry {
registry: Arc::new(Mutex::new(IndexMap::default())),
}
}
pub(crate) fn get_command(&self, name: &str) -> Option<Arc<Command>> {
let registry = self.registry.lock();
@ -92,15 +88,28 @@ impl Context {
&self.registry
}
pub(crate) fn expand_context<'context>(
&'context self,
source: &'context Text,
) -> ExpandContext {
ExpandContext::new(
Box::new(self.registry.clone()),
source,
self.shell_manager.homedir(),
)
pub(crate) fn from_raw(raw_args: &RawCommandArgs, registry: &CommandRegistry) -> Context {
#[cfg(windows)]
{
Context {
registry: registry.clone(),
host: raw_args.host.clone(),
current_errors: Arc::new(Mutex::new(vec![])),
ctrl_c: raw_args.ctrl_c.clone(),
shell_manager: raw_args.shell_manager.clone(),
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
}
}
#[cfg(not(windows))]
{
Context {
registry: registry.clone(),
host: raw_args.host.clone(),
current_errors: Arc::new(Mutex::new(vec![])),
ctrl_c: raw_args.ctrl_c.clone(),
shell_manager: raw_args.shell_manager.clone(),
}
}
}
pub(crate) fn basic() -> Result<Context, Box<dyn Error>> {
@ -138,6 +147,14 @@ impl Context {
self.with_errors(|errors| errors.push(error))
}
pub(crate) fn clear_errors(&mut self) {
self.current_errors.lock().clear()
}
pub(crate) fn get_errors(&self) -> Vec<ShellError> {
self.current_errors.lock().clone()
}
pub(crate) fn maybe_print_errors(&mut self, source: Text) -> bool {
let errors = self.current_errors.clone();
let mut errors = errors.lock();
@ -187,18 +204,18 @@ impl Context {
command: Arc<Command>,
name_tag: Tag,
args: hir::Call,
source: &Text,
scope: &Scope,
input: InputStream,
) -> OutputStream {
let command_args = self.command_args(args, input, source, name_tag);
let command_args = self.command_args(args, input, name_tag, scope);
command.run(command_args, self.registry())
}
fn call_info(&self, args: hir::Call, source: &Text, name_tag: Tag) -> UnevaluatedCallInfo {
fn call_info(&self, args: hir::Call, name_tag: Tag, scope: &Scope) -> UnevaluatedCallInfo {
UnevaluatedCallInfo {
args,
source: source.clone(),
name_tag,
scope: scope.clone(),
}
}
@ -206,14 +223,14 @@ impl Context {
&self,
args: hir::Call,
input: InputStream,
source: &Text,
name_tag: Tag,
scope: &Scope,
) -> CommandArgs {
CommandArgs {
host: self.host.clone(),
ctrl_c: self.ctrl_c.clone(),
shell_manager: self.shell_manager.clone(),
call_info: self.call_info(args, source, name_tag),
call_info: self.call_info(args, name_tag, scope),
input,
}
}

View File

@ -1,18 +1,13 @@
pub(crate) mod shape;
use crate::context::CommandRegistry;
use crate::evaluate::evaluate_baseline_expr;
use bigdecimal::BigDecimal;
use chrono::{DateTime, Utc};
use derive_new::new;
use log::trace;
use nu_errors::ShellError;
use nu_parser::{hir, CompareOperator};
use nu_protocol::{
Evaluate, EvaluateTrait, Primitive, Scope, ShellTypeName, SpannedTypeName, TaggedDictBuilder,
UntaggedValue, Value,
hir, Primitive, ShellTypeName, SpannedTypeName, TaggedDictBuilder, UntaggedValue, Value,
};
use nu_source::{Tag, Text};
use nu_source::Tag;
use nu_value_ext::ValueExt;
use num_bigint::BigInt;
use num_traits::Zero;
@ -23,49 +18,18 @@ use std::time::SystemTime;
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, new, Serialize)]
pub struct Operation {
pub(crate) left: Value,
pub(crate) operator: CompareOperator,
pub(crate) operator: hir::Operator,
pub(crate) right: Value,
}
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Serialize, Deserialize, new)]
pub struct Block {
pub(crate) expressions: Vec<hir::SpannedExpression>,
pub(crate) source: Text,
pub(crate) commands: hir::Commands,
pub(crate) tag: Tag,
}
interfaces!(Block: dyn ObjectHash);
#[typetag::serde]
impl EvaluateTrait for Block {
fn invoke(&self, scope: &Scope) -> Result<Value, ShellError> {
if self.expressions.is_empty() {
return Ok(UntaggedValue::nothing().into_value(&self.tag));
}
let mut last = Ok(UntaggedValue::nothing().into_value(&self.tag));
trace!(
"EXPRS = {:?}",
self.expressions
.iter()
.map(|e| format!("{:?}", e))
.collect::<Vec<_>>()
);
for expr in self.expressions.iter() {
last = evaluate_baseline_expr(&expr, &CommandRegistry::empty(), &scope, &self.source)
}
last
}
fn clone_box(&self) -> Evaluate {
let block = self.clone();
Evaluate::new(block)
}
}
#[derive(Serialize, Deserialize)]
pub enum Switch {
Present,
@ -123,7 +87,8 @@ pub(crate) enum CompareValues {
Decimals(BigDecimal, BigDecimal),
String(String, String),
Date(DateTime<Utc>, DateTime<Utc>),
DateDuration(DateTime<Utc>, u64),
DateDuration(DateTime<Utc>, i64),
Booleans(bool, bool),
}
impl CompareValues {
@ -137,9 +102,14 @@ impl CompareValues {
use std::time::Duration;
// Create the datetime we're comparing against, as duration is an offset from now
let right: DateTime<Utc> = (SystemTime::now() - Duration::from_secs(*right)).into();
let right: DateTime<Utc> = if *right < 0 {
(SystemTime::now() + Duration::from_secs((*right * -1) as u64)).into()
} else {
(SystemTime::now() - Duration::from_secs(*right as u64)).into()
};
right.cmp(left)
}
CompareValues::Booleans(left, right) => left.cmp(right),
}
}
}
@ -176,11 +146,15 @@ fn coerce_compare_primitive(
(Decimal(left), Bytes(right)) => {
CompareValues::Decimals(left.clone(), BigDecimal::from(*right))
}
(Bytes(left), Bytes(right)) => {
CompareValues::Ints(BigInt::from(*left), BigInt::from(*right))
}
(Bytes(left), Int(right)) => CompareValues::Ints(BigInt::from(*left), right.clone()),
(Bytes(left), Decimal(right)) => {
CompareValues::Decimals(BigDecimal::from(*left), right.clone())
}
(Bytes(left), Nothing) => CompareValues::Ints(BigInt::from(*left), BigInt::from(0)),
(Nothing, Nothing) => CompareValues::Ints(BigInt::from(0), BigInt::from(0)),
(Nothing, Bytes(right)) => CompareValues::Ints(BigInt::from(0), BigInt::from(*right)),
(Int(left), Nothing) => CompareValues::Ints(left.clone(), BigInt::from(0)),
(Nothing, Int(right)) => CompareValues::Ints(BigInt::from(0), right.clone()),
@ -192,6 +166,7 @@ fn coerce_compare_primitive(
(Line(left), Line(right)) => CompareValues::String(left.clone(), right.clone()),
(Date(left), Date(right)) => CompareValues::Date(*left, *right),
(Date(left), Duration(right)) => CompareValues::DateDuration(*left, *right),
(Boolean(left), Boolean(right)) => CompareValues::Booleans(*left, *right),
_ => return Err((left.type_name(), right.type_name())),
})
}

View File

@ -27,7 +27,7 @@ pub enum InlineShape {
Pattern(String),
Boolean(bool),
Date(DateTime<Utc>),
Duration(u64),
Duration(i64),
Path(PathBuf),
Binary,

View File

@ -1,5 +1,4 @@
use nu_parser::Number;
use nu_protocol::Primitive;
use nu_protocol::{hir::Number, Primitive};
pub fn number(number: impl Into<Number>) -> Primitive {
let number = number.into();

View File

@ -3,7 +3,8 @@ use crate::data::base::shape::{Column, InlineShape};
use crate::data::primitive::style_primitive;
use chrono::DateTime;
use nu_errors::ShellError;
use nu_parser::CompareOperator;
use nu_protocol::hir::Operator;
use nu_protocol::ShellTypeName;
use nu_protocol::{Primitive, Type, UntaggedValue};
use nu_source::{DebugDocBuilder, PrettyDebug, Tagged};
@ -21,8 +22,91 @@ pub fn date_from_str(s: Tagged<&str>) -> Result<UntaggedValue, ShellError> {
Ok(UntaggedValue::Primitive(Primitive::Date(date)))
}
pub fn compute_values(
operator: Operator,
left: &UntaggedValue,
right: &UntaggedValue,
) -> Result<UntaggedValue, (&'static str, &'static str)> {
match (left, right) {
(UntaggedValue::Primitive(lhs), UntaggedValue::Primitive(rhs)) => match (lhs, rhs) {
(Primitive::Bytes(x), Primitive::Bytes(y)) => {
let result = match operator {
Operator::Plus => Ok(x + y),
Operator::Minus => Ok(x - y),
_ => Err((left.type_name(), right.type_name())),
}?;
Ok(UntaggedValue::Primitive(Primitive::Bytes(result)))
}
(Primitive::Int(x), Primitive::Int(y)) => match operator {
Operator::Plus => Ok(UntaggedValue::Primitive(Primitive::Int(x + y))),
Operator::Minus => Ok(UntaggedValue::Primitive(Primitive::Int(x - y))),
Operator::Multiply => Ok(UntaggedValue::Primitive(Primitive::Int(x * y))),
Operator::Divide => {
if x - (y * (x / y)) == num_bigint::BigInt::from(0) {
Ok(UntaggedValue::Primitive(Primitive::Int(x / y)))
} else {
Ok(UntaggedValue::Primitive(Primitive::Decimal(
bigdecimal::BigDecimal::from(x.clone())
/ bigdecimal::BigDecimal::from(y.clone()),
)))
}
}
_ => Err((left.type_name(), right.type_name())),
},
(Primitive::Decimal(x), Primitive::Int(y)) => {
let result = match operator {
Operator::Plus => Ok(x + bigdecimal::BigDecimal::from(y.clone())),
Operator::Minus => Ok(x - bigdecimal::BigDecimal::from(y.clone())),
Operator::Multiply => Ok(x * bigdecimal::BigDecimal::from(y.clone())),
Operator::Divide => Ok(x / bigdecimal::BigDecimal::from(y.clone())),
_ => Err((left.type_name(), right.type_name())),
}?;
Ok(UntaggedValue::Primitive(Primitive::Decimal(result)))
}
(Primitive::Int(x), Primitive::Decimal(y)) => {
let result = match operator {
Operator::Plus => Ok(bigdecimal::BigDecimal::from(x.clone()) + y),
Operator::Minus => Ok(bigdecimal::BigDecimal::from(x.clone()) - y),
Operator::Multiply => Ok(bigdecimal::BigDecimal::from(x.clone()) * y),
Operator::Divide => Ok(bigdecimal::BigDecimal::from(x.clone()) / y),
_ => Err((left.type_name(), right.type_name())),
}?;
Ok(UntaggedValue::Primitive(Primitive::Decimal(result)))
}
(Primitive::Decimal(x), Primitive::Decimal(y)) => {
let result = match operator {
Operator::Plus => Ok(x + y),
Operator::Minus => Ok(x - y),
Operator::Multiply => Ok(x * y),
Operator::Divide => Ok(x / y),
_ => Err((left.type_name(), right.type_name())),
}?;
Ok(UntaggedValue::Primitive(Primitive::Decimal(result)))
}
(Primitive::Date(x), Primitive::Date(y)) => {
let result = match operator {
Operator::Minus => Ok(x.signed_duration_since(*y).num_seconds()),
_ => Err((left.type_name(), right.type_name())),
}?;
Ok(UntaggedValue::Primitive(Primitive::Duration(result)))
}
(Primitive::Date(x), Primitive::Duration(y)) => {
let result = match operator {
Operator::Plus => Ok(x
.checked_add_signed(chrono::Duration::seconds(*y as i64))
.expect("Overflowing add of duration")),
_ => Err((left.type_name(), right.type_name())),
}?;
Ok(UntaggedValue::Primitive(Primitive::Date(result)))
}
_ => Err((left.type_name(), right.type_name())),
},
_ => Err((left.type_name(), right.type_name())),
}
}
pub fn compare_values(
operator: CompareOperator,
operator: Operator,
left: &UntaggedValue,
right: &UntaggedValue,
) -> Result<bool, (&'static str, &'static str)> {
@ -34,15 +118,16 @@ pub fn compare_values(
use std::cmp::Ordering;
let result = match (operator, ordering) {
(CompareOperator::Equal, Ordering::Equal) => true,
(CompareOperator::NotEqual, Ordering::Less)
| (CompareOperator::NotEqual, Ordering::Greater) => true,
(CompareOperator::LessThan, Ordering::Less) => true,
(CompareOperator::GreaterThan, Ordering::Greater) => true,
(CompareOperator::GreaterThanOrEqual, Ordering::Greater)
| (CompareOperator::GreaterThanOrEqual, Ordering::Equal) => true,
(CompareOperator::LessThanOrEqual, Ordering::Less)
| (CompareOperator::LessThanOrEqual, Ordering::Equal) => true,
(Operator::Equal, Ordering::Equal) => true,
(Operator::NotEqual, Ordering::Less) | (Operator::NotEqual, Ordering::Greater) => {
true
}
(Operator::LessThan, Ordering::Less) => true,
(Operator::GreaterThan, Ordering::Greater) => true,
(Operator::GreaterThanOrEqual, Ordering::Greater)
| (Operator::GreaterThanOrEqual, Ordering::Equal) => true,
(Operator::LessThanOrEqual, Ordering::Less)
| (Operator::LessThanOrEqual, Ordering::Equal) => true,
_ => false,
};

View File

@ -1,7 +1,8 @@
use log::trace;
use nu_errors::{CoerceInto, ShellError};
use nu_protocol::{
CallInfo, ColumnPath, Evaluate, Primitive, RangeInclusion, ShellTypeName, UntaggedValue, Value,
hir::Block, CallInfo, ColumnPath, Primitive, RangeInclusion, ShellTypeName, UntaggedValue,
Value,
};
use nu_source::{HasSpan, Spanned, SpannedItem, Tagged, TaggedItem};
use nu_value_ext::ValueExt;
@ -368,7 +369,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut ConfigDeserializer<'de> {
))
}
};
return visit::<Evaluate, _>(block, name, fields, visitor);
return visit::<Block, _>(block, name, fields, visitor);
}
if name == "ColumnPath" {

View File

@ -275,7 +275,7 @@ mod tests {
assert_eq!(
actual.path(),
Some(
UntaggedValue::table(&vec![
UntaggedValue::table(&[
UntaggedValue::string("/Users/andresrobalino/.volta/bin")
.into_untagged_value(),
UntaggedValue::string("/users/mosqueteros/bin").into_untagged_value(),

View File

@ -3,22 +3,19 @@ use crate::context::CommandRegistry;
use crate::evaluate::evaluate_baseline_expr;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_parser::hir;
use nu_protocol::{EvaluatedArgs, Scope, UntaggedValue, Value};
use nu_source::Text;
use nu_protocol::{hir, EvaluatedArgs, Scope, UntaggedValue, Value};
pub(crate) fn evaluate_args(
call: &hir::Call,
registry: &CommandRegistry,
scope: &Scope,
source: &Text,
) -> Result<EvaluatedArgs, ShellError> {
let positional: Result<Option<Vec<_>>, _> = call
.positional
.as_ref()
.map(|p| {
p.iter()
.map(|e| evaluate_baseline_expr(e, registry, scope, source))
.map(|e| evaluate_baseline_expr(e, registry, scope))
.collect()
})
.transpose();
@ -36,11 +33,9 @@ pub(crate) fn evaluate_args(
hir::NamedValue::PresentSwitch(tag) => {
results.insert(name.clone(), UntaggedValue::boolean(true).into_value(tag));
}
hir::NamedValue::Value(expr) => {
results.insert(
name.clone(),
evaluate_baseline_expr(expr, registry, scope, source)?,
);
hir::NamedValue::Value(_, expr) => {
results
.insert(name.clone(), evaluate_baseline_expr(expr, registry, scope)?);
}
_ => {}

View File

@ -1,28 +1,24 @@
use crate::context::CommandRegistry;
use crate::data::base::Block;
use crate::evaluate::operator::apply_operator;
use crate::prelude::*;
use log::trace;
use nu_errors::{ArgumentError, ShellError};
use nu_parser::hir::{self, Expression, SpannedExpression};
use nu_protocol::hir::{self, Expression, SpannedExpression};
use nu_protocol::{
ColumnPath, Evaluate, Primitive, RangeInclusion, Scope, UnspannedPathMember, UntaggedValue,
Value,
ColumnPath, Primitive, RangeInclusion, Scope, UnspannedPathMember, UntaggedValue, Value,
};
use nu_source::Text;
pub(crate) fn evaluate_baseline_expr(
expr: &SpannedExpression,
registry: &CommandRegistry,
scope: &Scope,
source: &Text,
) -> Result<Value, ShellError> {
let tag = Tag {
span: expr.span,
anchor: None,
};
match &expr.expr {
Expression::Literal(literal) => Ok(evaluate_literal(literal, expr.span, source)),
Expression::Literal(literal) => Ok(evaluate_literal(literal, expr.span)),
Expression::ExternalWord => Err(ShellError::argument_error(
"Invalid external word".spanned(tag.span),
ArgumentError::InvalidExternalWord,
@ -31,29 +27,35 @@ pub(crate) fn evaluate_baseline_expr(
Expression::Synthetic(hir::Synthetic::String(s)) => {
Ok(UntaggedValue::string(s).into_untagged_value())
}
Expression::Variable(var) => evaluate_reference(var, scope, source, tag),
Expression::Command(_) => evaluate_command(tag, scope, source),
Expression::ExternalCommand(external) => evaluate_external(external, scope, source),
Expression::Variable(var) => evaluate_reference(var, scope, tag),
Expression::Command(_) => evaluate_command(tag, scope),
Expression::ExternalCommand(external) => evaluate_external(external, scope),
Expression::Binary(binary) => {
let left = evaluate_baseline_expr(binary.left(), registry, scope, source)?;
let right = evaluate_baseline_expr(binary.right(), registry, scope, source)?;
// TODO: If we want to add short-circuiting, we'll need to move these down
let left = evaluate_baseline_expr(&binary.left, registry, scope)?;
let right = evaluate_baseline_expr(&binary.right, registry, scope)?;
trace!("left={:?} right={:?}", left.value, right.value);
match apply_operator(**binary.op(), &left, &right) {
Ok(result) => Ok(result.into_value(tag)),
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(binary.left().span),
right_type.spanned(binary.right().span),
)),
match binary.op.expr {
Expression::Literal(hir::Literal::Operator(op)) => {
match apply_operator(op, &left, &right) {
Ok(result) => Ok(result.into_value(tag)),
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(binary.left.span),
right_type.spanned(binary.right.span),
)),
}
}
_ => unreachable!(),
}
}
Expression::Range(range) => {
let left = range.left();
let right = range.right();
let left = &range.left;
let right = &range.right;
let left = evaluate_baseline_expr(left, registry, scope, source)?;
let right = evaluate_baseline_expr(right, registry, scope, source)?;
let left = evaluate_baseline_expr(left, registry, scope)?;
let right = evaluate_baseline_expr(right, registry, scope)?;
let left_span = left.tag.span;
let right_span = right.tag.span;
@ -72,23 +74,18 @@ pub(crate) fn evaluate_baseline_expr(
let mut exprs = vec![];
for expr in list {
let expr = evaluate_baseline_expr(expr, registry, scope, source)?;
let expr = evaluate_baseline_expr(expr, registry, scope)?;
exprs.push(expr);
}
Ok(UntaggedValue::Table(exprs).into_value(tag))
}
Expression::Block(block) => Ok(UntaggedValue::Block(Evaluate::new(Block::new(
block.clone(),
source.clone(),
tag.clone(),
)))
.into_value(&tag)),
Expression::Block(block) => Ok(UntaggedValue::Block(block.clone()).into_value(&tag)),
Expression::Path(path) => {
let value = evaluate_baseline_expr(path.head(), registry, scope, source)?;
let value = evaluate_baseline_expr(&path.head, registry, scope)?;
let mut item = value;
for member in path.tail() {
for member in &path.tail {
let next = item.get_data_by_member(member);
match next {
@ -107,7 +104,7 @@ pub(crate) fn evaluate_baseline_expr(
return Err(ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", possible_matches[0].1),
&tag,
&member.span,
));
} else {
return Err(err);
@ -123,42 +120,46 @@ pub(crate) fn evaluate_baseline_expr(
Ok(item.value.into_value(tag))
}
Expression::Boolean(_boolean) => unimplemented!(),
Expression::Garbage => unimplemented!(),
}
}
fn evaluate_literal(literal: &hir::Literal, span: Span, source: &Text) -> Value {
fn evaluate_literal(literal: &hir::Literal, span: Span) -> Value {
match &literal {
hir::Literal::ColumnPath(path) => {
let members = path
.iter()
.map(|member| member.to_path_member(source))
.collect();
let members = path.iter().map(|member| member.to_path_member()).collect();
UntaggedValue::Primitive(Primitive::ColumnPath(ColumnPath::new(members)))
.into_value(span)
}
hir::Literal::Number(int) => match int {
nu_parser::Number::Int(i) => UntaggedValue::int(i.clone()).into_value(span),
nu_parser::Number::Decimal(d) => UntaggedValue::decimal(d.clone()).into_value(span),
nu_protocol::hir::Number::Int(i) => UntaggedValue::int(i.clone()).into_value(span),
nu_protocol::hir::Number::Decimal(d) => {
UntaggedValue::decimal(d.clone()).into_value(span)
}
},
hir::Literal::Size(int, unit) => unit.compute(&int).into_value(span),
hir::Literal::String(tag) => UntaggedValue::string(tag.slice(source)).into_value(span),
hir::Literal::String(string) => UntaggedValue::string(string).into_value(span),
hir::Literal::GlobPattern(pattern) => UntaggedValue::pattern(pattern).into_value(span),
hir::Literal::Bare => UntaggedValue::string(span.slice(source)).into_value(span),
hir::Literal::Bare(bare) => UntaggedValue::string(bare.clone()).into_value(span),
hir::Literal::Operator(_) => unimplemented!("Not sure what to do with operator yet"),
}
}
fn evaluate_reference(
name: &hir::Variable,
scope: &Scope,
source: &Text,
tag: Tag,
) -> Result<Value, ShellError> {
fn evaluate_reference(name: &hir::Variable, scope: &Scope, tag: Tag) -> Result<Value, ShellError> {
trace!("Evaluating {:?} with Scope {:?}", name, scope);
match name {
hir::Variable::It(_) => Ok(scope.it.value.clone().into_value(tag)),
hir::Variable::Other(inner) => match inner.slice(source) {
x if x == "nu" => crate::evaluate::variables::nu(tag),
hir::Variable::Other(name, _) => match name {
x if x == "$nu" => crate::evaluate::variables::nu(tag),
x if x == "$true" => Ok(Value {
value: UntaggedValue::boolean(true),
tag,
}),
x if x == "$false" => Ok(Value {
value: UntaggedValue::boolean(false),
tag,
}),
x => Ok(scope
.vars
.get(x)
@ -169,16 +170,15 @@ fn evaluate_reference(
}
fn evaluate_external(
external: &hir::ExternalCommand,
external: &hir::ExternalStringCommand,
_scope: &Scope,
_source: &Text,
) -> Result<Value, ShellError> {
Err(ShellError::syntax_error(
"Unexpected external command".spanned(*external.name()),
"Unexpected external command".spanned(external.name.span),
))
}
fn evaluate_command(tag: Tag, _scope: &Scope, _source: &Text) -> Result<Value, ShellError> {
fn evaluate_command(tag: Tag, _scope: &Scope) -> Result<Value, ShellError> {
Err(ShellError::syntax_error(
"Unexpected command".spanned(tag.span),
))

View File

@ -1,30 +1,43 @@
use crate::data::value;
use nu_parser::CompareOperator;
use nu_protocol::hir::Operator;
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
use std::ops::Not;
pub fn apply_operator(
op: CompareOperator,
op: Operator,
left: &Value,
right: &Value,
) -> Result<UntaggedValue, (&'static str, &'static str)> {
match op {
CompareOperator::Equal
| CompareOperator::NotEqual
| CompareOperator::LessThan
| CompareOperator::GreaterThan
| CompareOperator::LessThanOrEqual
| CompareOperator::GreaterThanOrEqual => {
Operator::Equal
| Operator::NotEqual
| Operator::LessThan
| Operator::GreaterThan
| Operator::LessThanOrEqual
| Operator::GreaterThanOrEqual => {
value::compare_values(op, left, right).map(UntaggedValue::boolean)
}
CompareOperator::Contains => contains(left, right).map(UntaggedValue::boolean),
CompareOperator::NotContains => contains(left, right)
Operator::Contains => string_contains(left, right).map(UntaggedValue::boolean),
Operator::NotContains => string_contains(left, right)
.map(Not::not)
.map(UntaggedValue::boolean),
Operator::Plus => value::compute_values(op, left, right),
Operator::Minus => value::compute_values(op, left, right),
Operator::Multiply => value::compute_values(op, left, right),
Operator::Divide => value::compute_values(op, left, right),
Operator::In => table_contains(left, right).map(UntaggedValue::boolean),
Operator::And => match (left.as_bool(), right.as_bool()) {
(Ok(left), Ok(right)) => Ok(UntaggedValue::boolean(left && right)),
_ => Err((left.type_name(), right.type_name())),
},
Operator::Or => match (left.as_bool(), right.as_bool()) {
(Ok(left), Ok(right)) => Ok(UntaggedValue::boolean(left || right)),
_ => Err((left.type_name(), right.type_name())),
},
}
}
fn contains(
fn string_contains(
left: &UntaggedValue,
right: &UntaggedValue,
) -> Result<bool, (&'static str, &'static str)> {
@ -48,3 +61,14 @@ fn contains(
_ => Err((left.type_name(), right.type_name())),
}
}
fn table_contains(
left: &UntaggedValue,
right: &UntaggedValue,
) -> Result<bool, (&'static str, &'static str)> {
let left = left.clone();
match right {
UntaggedValue::Table(values) => Ok(values.iter().any(|x| x.value == left)),
_ => Err((left.type_name(), right.type_name())),
}
}

View File

@ -1,12 +1,8 @@
pub(crate) mod entries;
pub(crate) mod generic;
pub(crate) mod list;
pub(crate) mod table;
use crate::prelude::*;
use nu_errors::ShellError;
pub(crate) use entries::EntriesView;
pub(crate) use table::TableView;
pub(crate) trait RenderView {

View File

@ -1,50 +0,0 @@
use crate::data::value;
use crate::format::RenderView;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::Value;
use derive_new::new;
// An entries list is printed like this:
//
// name : ...
// name2 : ...
// another_name : ...
#[derive(new)]
pub struct EntriesView {
entries: Vec<(String, String)>,
}
impl EntriesView {
pub(crate) fn from_value(value: &Value) -> EntriesView {
let descs = value.data_descriptors();
let mut entries = vec![];
for desc in descs {
let value = value.get_data(&desc);
let formatted_value = value::format_leaf(value.borrow()).plain_string(75);
entries.push((desc.clone(), formatted_value))
}
EntriesView::new(entries)
}
}
impl RenderView for EntriesView {
fn render_view(&self, _host: &mut dyn Host) -> Result<(), ShellError> {
if self.entries.is_empty() {
return Ok(());
}
if let Some(max_name_size) = self.entries.iter().map(|(n, _)| n.len()).max() {
for (name, value) in &self.entries {
outln!("{:width$} : {}", name, value, width = max_name_size)
}
}
Ok(())
}
}

View File

@ -1,48 +0,0 @@
use crate::data::value::format_leaf;
use crate::format::{EntriesView, RenderView, TableView};
use crate::prelude::*;
use derive_new::new;
use nu_errors::ShellError;
use nu_protocol::{format_primitive, UntaggedValue, Value};
// A list is printed one line at a time with an optional separator between groups
#[derive(new)]
pub struct GenericView<'value> {
value: &'value Value,
}
impl RenderView for GenericView<'_> {
fn render_view(&self, host: &mut dyn Host) -> Result<(), ShellError> {
let tag = &self.value.tag;
match &self.value.value {
UntaggedValue::Primitive(p) => {
host.stdout(&format_primitive(p, None));
Ok(())
}
UntaggedValue::Table(l) => {
let view = TableView::from_list(l, 0);
if let Some(view) = view {
view.render_view(host)?;
}
Ok(())
}
o @ UntaggedValue::Row(_) => {
let view = EntriesView::from_value(&o.clone().into_value(tag));
view.render_view(host)?;
Ok(())
}
b @ UntaggedValue::Block(_) => {
let printed = format_leaf(b).plain_string(host.width());
let view = EntriesView::from_value(&UntaggedValue::string(printed).into_value(tag));
view.render_view(host)?;
Ok(())
}
UntaggedValue::Error(e) => Err(e.clone()),
}
}
}

View File

@ -1,23 +0,0 @@
use crate::format::RenderView;
use crate::prelude::*;
use derive_new::new;
use nu_errors::ShellError;
// A list is printed one line at a time with an optional separator between groups
#[derive(new)]
pub struct ListView {
list: Vec<Vec<String>>,
sep: String,
}
impl RenderView for ListView {
fn render_view(&self, host: &mut dyn Host) -> Result<(), ShellError> {
for output in &self.list {
let string: String = output.iter().map(|l| format!("{}\n", l)).collect();
host.stdout(&format!("{}{}", string, self.sep));
}
Ok(())
}
}

View File

@ -8,7 +8,6 @@ use textwrap::fill;
use prettytable::format::{Alignment, FormatBuilder, LinePosition, LineSeparator};
use prettytable::{color, Attr, Cell, Row, Table};
use term::color::Color;
type Entries = Vec<Vec<(String, &'static str)>>;
@ -74,7 +73,19 @@ impl TableView {
}
}
fn are_table_indexes_disabled() -> bool {
let config = crate::data::config::config(Tag::unknown());
match config {
Ok(config) => {
let disable_indexes = config.get("disable_table_indexes");
disable_indexes.map_or(false, |x| x.as_bool().unwrap_or(false))
}
_ => false,
}
}
fn values_to_entries(values: &[Value], headers: &mut Vec<String>, starting_idx: usize) -> Entries {
let disable_indexes = are_table_indexes_disabled();
let mut entries = vec![];
if headers.is_empty() {
@ -118,12 +129,16 @@ fn values_to_entries(values: &[Value], headers: &mut Vec<String>, starting_idx:
.collect();
// Indices are green, bold, right-aligned:
row.insert(0, ((starting_idx + idx).to_string(), "Fgbr"));
if !disable_indexes {
row.insert(0, ((starting_idx + idx).to_string(), "Fgbr"));
}
entries.push(row);
}
headers.insert(0, "#".to_owned());
if !disable_indexes {
headers.insert(0, "#".to_owned());
}
entries
}
@ -389,7 +404,7 @@ impl RenderView for TableView {
}
}
fn str_to_color(s: String) -> Option<Color> {
fn str_to_color(s: String) -> Option<color::Color> {
match s.as_str() {
"g" | "green" => Some(color::GREEN),
"r" | "red" => Some(color::RED),

View File

@ -7,6 +7,12 @@ extern crate indexmap;
#[macro_use]
mod prelude;
#[cfg(test)]
extern crate quickcheck;
#[cfg(test)]
#[macro_use(quickcheck)]
extern crate quickcheck_macros;
mod cli;
mod commands;
mod context;
@ -17,17 +23,19 @@ mod evaluate;
mod format;
mod futures;
mod git;
mod path;
mod shell;
mod stream;
mod utils;
pub use crate::cli::{cli, create_default_context, load_plugins, run_pipeline_standalone};
pub use crate::cli::{
cli, create_default_context, load_plugins, run_pipeline_standalone, run_vec_of_pipelines,
};
pub use crate::data::dict::TaggedListBuilder;
pub use crate::data::primitive;
pub use crate::data::value;
pub use crate::env::environment_syncer::EnvironmentSyncer;
pub use crate::env::host::BasicHost;
pub use nu_parser::TokenTreeBuilder;
pub use nu_value_ext::ValueExt;
pub use num_traits::cast::ToPrimitive;

103
crates/nu-cli/src/path.rs Normal file
View File

@ -0,0 +1,103 @@
use std::io;
use std::path::{Component, Path, PathBuf};
pub fn absolutize<P, Q>(relative_to: P, path: Q) -> PathBuf
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let path = relative_to.as_ref().join(path);
let (relative_to, path) = {
let components: Vec<_> = path.components().collect();
let separator = components
.iter()
.enumerate()
.find(|(_, c)| c == &&Component::CurDir || c == &&Component::ParentDir);
if let Some((index, _)) = separator {
let (absolute, relative) = components.split_at(index);
let absolute: PathBuf = absolute.iter().collect();
let relative: PathBuf = relative.iter().collect();
(absolute, relative)
} else {
(relative_to.as_ref().to_path_buf(), path)
}
};
let path = if path.is_relative() {
let mut result = relative_to;
path.components().for_each(|component| match component {
Component::ParentDir => {
result.pop();
}
Component::Normal(normal) => result.push(normal),
_ => {}
});
result
} else {
path
};
dunce::simplified(&path).to_path_buf()
}
pub fn canonicalize<P, Q>(relative_to: P, path: Q) -> io::Result<PathBuf>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let canonicalized = absolutize(relative_to, path);
let path = match std::fs::read_link(&canonicalized) {
Ok(resolved) => resolved,
Err(e) => {
if canonicalized.exists() {
canonicalized
} else {
return Err(e);
}
}
};
Ok(dunce::simplified(&path).to_path_buf())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io;
#[test]
fn absolutize_two_dots() {
let relative_to = Path::new("/foo/bar");
let path = Path::new("..");
assert_eq!(
PathBuf::from("/foo"), // missing path
absolutize(relative_to, path)
);
}
#[test]
fn canonicalize_should_succeed() -> io::Result<()> {
let relative_to = Path::new("/foo/bar");
let path = Path::new("../..");
assert_eq!(
PathBuf::from("/"), // existing path
canonicalize(relative_to, path)?,
);
Ok(())
}
#[test]
fn canonicalize_should_fail() {
let relative_to = Path::new("/foo/bar/baz"); // '/foo' is missing
let path = Path::new("../..");
assert!(canonicalize(relative_to, path).is_err());
}
}

View File

@ -27,7 +27,7 @@ macro_rules! trace_stream {
if log::log_enabled!(target: $target, log::Level::Trace) {
use futures::stream::StreamExt;
let objects = $expr.values.inspect(move |o| {
let objects = $expr.inspect(move |o| {
trace!(
target: $target,
"{} = {}",
@ -49,7 +49,7 @@ macro_rules! trace_out_stream {
if log::log_enabled!(target: $target, log::Level::Trace) {
use futures::stream::StreamExt;
let objects = $expr.values.inspect(move |o| {
let objects = $expr.inspect(move |o| {
trace!(
target: $target,
"{} = {}",
@ -91,10 +91,10 @@ pub(crate) use async_stream::stream as async_stream;
pub(crate) use bigdecimal::BigDecimal;
pub(crate) use futures::stream::BoxStream;
pub(crate) use futures::{FutureExt, Stream, StreamExt};
pub(crate) use nu_protocol::{EvaluateTrait, MaybeOwned};
pub(crate) use nu_protocol::MaybeOwned;
pub(crate) use nu_source::{
b, AnchorLocation, DebugDocBuilder, HasSpan, PrettyDebug, PrettyDebugWithSource, Span,
SpannedItem, Tag, TaggedItem, Text,
b, AnchorLocation, DebugDocBuilder, PrettyDebug, PrettyDebugWithSource, Span, SpannedItem, Tag,
TaggedItem, Text,
};
pub(crate) use nu_value_ext::ValueExt;
pub(crate) use num_bigint::BigInt;
@ -132,17 +132,13 @@ where
U: Into<Result<nu_protocol::Value, nu_errors::ShellError>>,
{
fn to_input_stream(self) -> InputStream {
InputStream {
values: self
.map(|item| match item.into() {
Ok(result) => result,
Err(err) => match HasFallibleSpan::maybe_span(&err) {
Some(span) => nu_protocol::UntaggedValue::Error(err).into_value(span),
None => nu_protocol::UntaggedValue::Error(err).into_untagged_value(),
},
})
.boxed(),
}
InputStream::from_stream(self.map(|item| match item.into() {
Ok(result) => result,
Err(err) => match HasFallibleSpan::maybe_span(&err) {
Some(span) => nu_protocol::UntaggedValue::Error(err).into_value(span),
None => nu_protocol::UntaggedValue::Error(err).into_untagged_value(),
},
}))
}
}

View File

@ -1,8 +1,6 @@
use crate::context::CommandRegistry;
use derive_new::new;
use nu_parser::ExpandContext;
use nu_source::{HasSpan, Text};
use rustyline::completion::{Completer, FilenameCompleter};
use std::path::PathBuf;
@ -20,14 +18,6 @@ impl NuCompleter {
pos: usize,
context: &rustyline::Context,
) -> rustyline::Result<(usize, Vec<rustyline::completion::Pair>)> {
let text = Text::from(line);
let expand_context =
ExpandContext::new(Box::new(self.commands.clone()), &text, self.homedir.clone());
#[allow(unused)]
// smarter completions
let shapes = nu_parser::pipeline_shapes(line, expand_context);
let commands: Vec<String> = self.commands.names();
let line_chars: Vec<_> = line[..pos].chars().collect();
@ -44,7 +34,12 @@ impl NuCompleter {
// See if we're a flag
if pos > 0 && replace_pos < line_chars.len() && line_chars[replace_pos] == '-' {
completions = self.get_matching_arguments(&line_chars, line, replace_pos, pos);
if let Ok(lite_block) = nu_parser::lite_parse(line, 0) {
completions =
self.get_matching_arguments(&lite_block, &line_chars, line, replace_pos, pos);
} else {
completions = self.file_completer.complete(line, pos, context)?.1;
}
} else {
completions = self.file_completer.complete(line, pos, context)?.1;
@ -69,15 +64,15 @@ impl NuCompleter {
for command in commands.iter() {
let mut pos = replace_pos;
let mut matched = true;
let mut matched = false;
if pos < line_chars.len() {
for chr in command.chars() {
if line_chars[pos] != chr {
matched = false;
break;
}
pos += 1;
if pos == line_chars.len() {
matched = true;
break;
}
}
@ -96,6 +91,7 @@ impl NuCompleter {
fn get_matching_arguments(
&self,
lite_block: &nu_parser::LiteBlock,
line_chars: &[char],
line: &str,
replace_pos: usize,
@ -108,39 +104,24 @@ impl NuCompleter {
let replace_string = (replace_pos..pos).map(|_| " ").collect::<String>();
line_copy.replace_range(replace_pos..pos, &replace_string);
if let Ok(val) = nu_parser::parse(&line_copy) {
let source = Text::from(line);
let pipeline_list = vec![val.clone()];
let result = nu_parser::classify_block(&lite_block, &self.commands);
let expand_context = nu_parser::ExpandContext {
homedir: None,
registry: Box::new(self.commands.clone()),
source: &source,
};
for pipeline in &result.block.block {
for command in &pipeline.list {
if let nu_protocol::hir::ClassifiedCommand::Internal(
nu_protocol::hir::InternalCommand { args, .. },
) = command
{
if replace_pos >= args.span.start() && replace_pos <= args.span.end() {
if let Some(named) = &args.named {
for (name, _) in named.iter() {
let full_flag = format!("--{}", name);
let mut iterator =
nu_parser::TokensIterator::new(&pipeline_list, expand_context, val.span());
let result = iterator.expand_infallible(nu_parser::PipelineShape);
if result.failed.is_none() {
for command in result.commands.list {
if let nu_parser::ClassifiedCommand::Internal(nu_parser::InternalCommand {
args,
..
}) = command
{
if replace_pos >= args.span.start() && replace_pos <= args.span.end() {
if let Some(named) = args.named {
for (name, _) in named.iter() {
let full_flag = format!("--{}", name);
if full_flag.starts_with(&substring) {
matching_arguments.push(rustyline::completion::Pair {
display: full_flag.clone(),
replacement: full_flag,
});
}
if full_flag.starts_with(&substring) {
matching_arguments.push(rustyline::completion::Pair {
display: full_flag.clone(),
replacement: full_flag,
});
}
}
}

View File

@ -5,18 +5,17 @@ use crate::commands::mkdir::MkdirArgs;
use crate::commands::mv::MoveArgs;
use crate::commands::rm::RemoveArgs;
use crate::data::dir_entry_dict;
use crate::path::canonicalize;
use crate::prelude::*;
use crate::shell::completer::NuCompleter;
use crate::shell::shell::Shell;
use crate::utils::FileStructure;
use nu_errors::ShellError;
use nu_parser::ExpandContext;
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue};
use rustyline::completion::FilenameCompleter;
use rustyline::hint::{Hinter, HistoryHinter};
use std::collections::HashMap;
use std::path::{Component, Path, PathBuf};
use trash as SendToTrash;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
@ -78,29 +77,6 @@ impl FilesystemShell {
hinter: HistoryHinter {},
}
}
fn canonicalize(&self, path: impl AsRef<Path>) -> std::io::Result<PathBuf> {
let path = if path.as_ref().is_relative() {
let components = path.as_ref().components();
let mut result = PathBuf::from(self.path());
for component in components {
match component {
Component::CurDir => { /* ignore current dir */ }
Component::ParentDir => {
result.pop();
}
Component::Normal(normal) => result.push(normal),
_ => {}
}
}
result
} else {
path.as_ref().into()
};
dunce::canonicalize(path)
}
}
impl Shell for FilesystemShell {
@ -148,13 +124,13 @@ impl Shell for FilesystemShell {
};
let mut paths = glob::glob(&path.to_string_lossy())
.map_err(|e| ShellError::labeled_error("Glob error", e.to_string(), &p_tag))?
.map_err(|e| ShellError::labeled_error(e.to_string(), "invalid pattern", &p_tag))?
.peekable();
if paths.peek().is_none() {
return Err(ShellError::labeled_error(
"Invalid File or Pattern",
"invalid file or pattern",
"No matches found",
"no matches found",
&p_tag,
));
}
@ -212,7 +188,7 @@ impl Shell for FilesystemShell {
if target == Path::new("-") {
PathBuf::from(&self.last_path)
} else {
let path = self.canonicalize(target).map_err(|_| {
let path = canonicalize(self.path(), target).map_err(|_| {
ShellError::labeled_error(
"Cannot change to directory",
"directory not found",
@ -278,134 +254,81 @@ impl Shell for FilesystemShell {
) -> Result<OutputStream, ShellError> {
let name_tag = name;
let mut source = PathBuf::from(path);
let mut destination = PathBuf::from(path);
source.push(&src.item);
destination.push(&dst.item);
let path = Path::new(path);
let source = path.join(&src.item);
let destination = path.join(&dst.item);
let sources: Vec<_> = match glob::glob(&source.to_string_lossy()) {
Ok(files) => files.collect(),
Err(_) => {
Err(e) => {
return Err(ShellError::labeled_error(
"Invalid pattern",
e.to_string(),
"invalid pattern",
src.tag,
))
}
};
if sources.len() == 1 {
if let Ok(entry) = &sources[0] {
if entry.is_dir() && !recursive.item {
return Err(ShellError::labeled_error(
"is a directory (not copied). Try using \"--recursive\".",
"is a directory (not copied). Try using \"--recursive\".",
src.tag,
));
}
if sources.is_empty() {
return Err(ShellError::labeled_error(
"No matches found",
"no matches found",
src.tag,
));
}
let mut sources: FileStructure = FileStructure::new();
if sources.len() > 1 && !destination.is_dir() {
return Err(ShellError::labeled_error(
"Destination must be a directory when copying multiple files",
"is not a directory",
dst.tag,
));
}
let any_source_is_dir = sources.iter().any(|f| match f {
Ok(f) => f.is_dir(),
Err(_) => false,
});
if any_source_is_dir && !recursive.item {
return Err(ShellError::labeled_error(
"Directories must be copied using \"--recursive\"",
"resolves to a directory (not copied)",
src.tag,
));
}
for entry in sources {
if let Ok(entry) = entry {
let mut sources = FileStructure::new();
sources.walk_decorate(&entry)?;
if entry.is_file() {
let strategy = |(source_file, _depth_level)| {
let sources = sources.paths_applying_with(|(source_file, _depth_level)| {
if destination.is_dir() {
let mut new_dst = dunce::canonicalize(destination.clone())?;
let mut dest = canonicalize(&path, &dst.item)?;
if let Some(name) = entry.file_name() {
new_dst.push(name);
dest.push(name);
}
Ok((source_file, new_dst))
Ok((source_file, dest))
} else {
Ok((source_file, destination.clone()))
}
};
})?;
let sources = sources.paths_applying_with(strategy)?;
for (ref src, ref dst) in sources {
for (src, dst) in sources {
if src.is_file() {
match std::fs::copy(src, dst) {
Err(e) => {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
name_tag,
));
}
Ok(o) => o,
};
std::fs::copy(src, dst).map_err(|e| {
ShellError::labeled_error(e.to_string(), e.to_string(), &name_tag)
})?;
}
}
}
if entry.is_dir() {
if !destination.exists() {
match std::fs::create_dir_all(&destination) {
Err(e) => {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
dst.tag,
));
}
Ok(o) => o,
};
let strategy = |(source_file, depth_level)| {
let mut new_dst = destination.clone();
let path = dunce::canonicalize(&source_file)?;
let mut comps: Vec<_> = path
.components()
.map(|fragment| fragment.as_os_str())
.rev()
.take(1 + depth_level)
.collect();
comps.reverse();
for fragment in comps.iter() {
new_dst.push(fragment);
}
Ok((PathBuf::from(&source_file), new_dst))
};
let sources = sources.paths_applying_with(strategy)?;
let dst_tag = dst.tag;
for (ref src, ref dst) in sources {
if src.is_dir() && !dst.exists() {
match std::fs::create_dir_all(dst) {
Err(e) => {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
dst_tag,
));
}
Ok(o) => o,
};
}
if src.is_file() {
match std::fs::copy(src, dst) {
Err(e) => {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
name_tag,
));
}
Ok(o) => o,
};
}
}
} else if entry.is_dir() {
let destination = if !destination.exists() {
destination.clone()
} else {
match entry.file_name() {
Some(name) => destination.push(name),
Some(name) => destination.join(name),
None => {
return Err(ShellError::labeled_error(
"Copy aborted. Not a valid path",
@ -414,131 +337,46 @@ impl Shell for FilesystemShell {
))
}
}
};
match std::fs::create_dir_all(&destination) {
Err(e) => {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
dst.tag,
));
}
Ok(o) => o,
};
std::fs::create_dir_all(&destination).map_err(|e| {
ShellError::labeled_error(e.to_string(), e.to_string(), &dst.tag)
})?;
let strategy = |(source_file, depth_level)| {
let mut new_dst = dunce::canonicalize(&destination)?;
let path = dunce::canonicalize(&source_file)?;
let sources = sources.paths_applying_with(|(source_file, depth_level)| {
let mut dest = destination.clone();
let path = canonicalize(&path, &source_file)?;
let mut comps: Vec<_> = path
.components()
.map(|fragment| fragment.as_os_str())
.rev()
.take(1 + depth_level)
.collect();
let comps: Vec<_> = path
.components()
.map(|fragment| fragment.as_os_str())
.rev()
.take(1 + depth_level)
.collect();
comps.reverse();
for fragment in comps.into_iter().rev() {
dest.push(fragment);
}
for fragment in comps.iter() {
new_dst.push(fragment);
}
Ok((PathBuf::from(&source_file), dest))
})?;
Ok((PathBuf::from(&source_file), new_dst))
};
let dst_tag = &dst.tag;
for (src, dst) in sources {
if src.is_dir() && !dst.exists() {
std::fs::create_dir_all(&dst).map_err(|e| {
ShellError::labeled_error(e.to_string(), e.to_string(), dst_tag)
})?;
}
let sources = sources.paths_applying_with(strategy)?;
let dst_tag = dst.tag;
for (ref src, ref dst) in sources {
if src.is_dir() && !dst.exists() {
match std::fs::create_dir_all(dst) {
Err(e) => {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
dst_tag,
));
}
Ok(o) => o,
};
}
if src.is_file() {
match std::fs::copy(src, dst) {
Err(e) => {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
name_tag,
));
}
Ok(o) => o,
};
}
if src.is_file() {
std::fs::copy(&src, &dst).map_err(|e| {
ShellError::labeled_error(e.to_string(), e.to_string(), &name_tag)
})?;
}
}
}
}
} else if destination.exists() {
if !sources.iter().all(|x| match x {
Ok(f) => f.is_file(),
Err(_) => false,
}) {
return Err(ShellError::labeled_error(
"Copy aborted (directories found). Recursive copying in patterns not supported yet (try copying the directory directly)",
"recursive copying in patterns not supported",
src.tag,
));
}
for entry in sources {
if let Ok(entry) = entry {
let mut to = PathBuf::from(&destination);
match entry.file_name() {
Some(name) => to.push(name),
None => {
return Err(ShellError::labeled_error(
"Copy aborted. Not a valid path",
"not a valid path",
dst.tag,
))
}
}
if entry.is_file() {
match std::fs::copy(&entry, &to) {
Err(e) => {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
src.tag,
));
}
Ok(o) => o,
};
}
}
}
} else {
let destination_file_name = {
match destination.file_name() {
Some(name) => PathBuf::from(name),
None => {
return Err(ShellError::labeled_error(
"Copy aborted. Not a valid destination",
"not a valid destination",
dst.tag,
))
}
}
};
return Err(ShellError::labeled_error(
format!("Copy aborted. (Does {:?} exist?)", destination_file_name),
format!("copy aborted (does {:?} exist?)", destination_file_name),
dst.tag,
));
}
Ok(OutputStream::empty())
@ -550,7 +388,7 @@ impl Shell for FilesystemShell {
name: Tag,
path: &str,
) -> Result<OutputStream, ShellError> {
let full_path = PathBuf::from(path);
let path = Path::new(path);
if directories.is_empty() {
return Err(ShellError::labeled_error(
@ -561,11 +399,7 @@ impl Shell for FilesystemShell {
}
for dir in directories.iter() {
let create_at = {
let mut loc = full_path.clone();
loc.push(&dir.item);
loc
};
let create_at = path.join(&dir.item);
let dir_res = std::fs::create_dir_all(create_at);
if let Err(reason) = dir_res {
@ -588,11 +422,9 @@ impl Shell for FilesystemShell {
) -> Result<OutputStream, ShellError> {
let name_tag = name;
let mut source = PathBuf::from(path);
let mut destination = PathBuf::from(path);
source.push(&src.item);
destination.push(&dst.item);
let path = Path::new(path);
let source = path.join(&src.item);
let mut destination = path.join(&dst.item);
let sources: Vec<_> = match glob::glob(&source.to_string_lossy()) {
Ok(files) => files.collect(),
@ -607,11 +439,12 @@ impl Shell for FilesystemShell {
if sources.is_empty() {
return Err(ShellError::labeled_error(
"Invalid File or Pattern.",
"Invalid File or Pattern",
"Invalid file or pattern.",
"invalid file or pattern",
src.tag,
));
}
let destination_file_name = {
match destination.file_name() {
Some(name) => PathBuf::from(name),
@ -978,7 +811,7 @@ impl Shell for FilesystemShell {
RemoveArgs {
rest: targets,
recursive,
trash,
trash: _trash,
}: RemoveArgs,
name: Tag,
path: &str,
@ -993,18 +826,23 @@ impl Shell for FilesystemShell {
));
}
let path = Path::new(path);
let mut all_targets: HashMap<PathBuf, Tag> = HashMap::new();
for target in targets {
if target.item.to_str() == Some(".") || target.item.to_str() == Some("..") {
let all_dots = target
.item
.to_str()
.map_or(false, |v| v.chars().all(|c| c == '.'));
if all_dots {
return Err(ShellError::labeled_error(
"Remove aborted. \".\" or \"..\" may not be removed.",
"\".\" or \"..\" may not be removed",
"Cannot remove any parent directory",
"cannot remove any parent directory",
target.tag,
));
}
let mut path = PathBuf::from(path);
path.push(&target.item);
let path = path.join(&target.item);
match glob::glob(&path.to_string_lossy()) {
Ok(files) => {
for file in files {
@ -1015,9 +853,8 @@ impl Shell for FilesystemShell {
.or_insert_with(|| target.tag.clone());
}
Err(e) => {
let msg = format!("Could not remove {:}", path.to_string_lossy());
return Err(ShellError::labeled_error(
msg,
format!("Could not remove {:}", path.to_string_lossy()),
e.to_string(),
&target.tag,
));
@ -1027,7 +864,7 @@ impl Shell for FilesystemShell {
}
Err(e) => {
return Err(ShellError::labeled_error(
format!("Remove aborted. {:}", e.to_string()),
e.to_string(),
e.to_string(),
&name_tag,
))
@ -1036,91 +873,77 @@ impl Shell for FilesystemShell {
}
if all_targets.is_empty() {
Err(ShellError::labeled_error(
"Remove aborted. No valid paths",
return Err(ShellError::labeled_error(
"No valid paths",
"no valid paths",
name_tag,
))
} else {
let stream = async_stream! {
for (f, tag) in all_targets.iter() {
let is_empty = match f.read_dir() {
Ok(mut p) => p.next().is_none(),
Err(_) => false
};
));
}
let valid_target =
f.exists() && (!f.is_dir() || (is_empty || recursive.item));
if valid_target {
if trash.item {
match SendToTrash::remove(f) {
Err(e) => {
let msg = format!(
"Could not delete {:}",
f.to_string_lossy()
);
let label = format!("{:?}", e);
yield Err(ShellError::labeled_error(
msg,
label,
tag,
))
},
Ok(()) => {
let val = format!("deleted {:}", f.to_string_lossy()).into();
yield Ok(ReturnSuccess::Value(val))
},
}
} else {
let success = if f.is_dir() {
std::fs::remove_dir_all(f)
} else {
let stream = async_stream! {
for (f, tag) in all_targets.iter() {
let is_empty = || match f.read_dir() {
Ok(mut p) => p.next().is_none(),
Err(_) => false
};
if let Ok(metadata) = f.symlink_metadata() {
if metadata.is_file() || metadata.file_type().is_symlink() || recursive.item || is_empty() {
let result;
#[cfg(feature = "trash-support")]
{
result = if _trash.item {
trash::remove(f)
.map_err(|e| f.to_string_lossy())
} else if metadata.is_file() {
std::fs::remove_file(f)
.map_err(|e| f.to_string_lossy())
} else {
std::fs::remove_dir_all(f)
.map_err(|e| f.to_string_lossy())
};
match success {
Err(e) => {
let msg = format!(
"Could not delete {:}",
f.to_string_lossy()
);
yield Err(ShellError::labeled_error(
msg,
e.to_string(),
tag,
))
},
Ok(()) => {
let val = format!("deleted {:}", f.to_string_lossy()).into();
yield Ok(ReturnSuccess::Value(
val,
))
},
}
}
#[cfg(not(feature = "trash-support"))]
{
result = if metadata.is_file() {
std::fs::remove_file(f)
.map_err(|e| f.to_string_lossy())
} else {
std::fs::remove_dir_all(f)
.map_err(|e| f.to_string_lossy())
};
}
if let Err(e) = result {
let msg = format!("Could not delete {:}", e);
yield Err(ShellError::labeled_error(msg, e, tag))
} else {
let val = format!("deleted {:}", f.to_string_lossy()).into();
yield Ok(ReturnSuccess::Value(val))
}
} else {
if f.is_dir() {
let msg = format!(
"Cannot remove {:}. try --recursive",
f.to_string_lossy()
);
yield Err(ShellError::labeled_error(
msg,
"cannot remove non-empty directory",
tag,
))
} else {
let msg = format!("Invalid file: {:}", f.to_string_lossy());
yield Err(ShellError::labeled_error(
msg,
"invalid file",
tag,
))
}
let msg = format!(
"Cannot remove {:}. try --recursive",
f.to_string_lossy()
);
yield Err(ShellError::labeled_error(
msg,
"cannot remove non-empty directory",
tag,
))
}
} else {
let msg = format!("no such file or directory: {:}", f.to_string_lossy());
yield Err(ShellError::labeled_error(
msg,
"no such file or directory",
tag,
))
}
};
Ok(stream.to_output_stream())
}
}
};
Ok(stream.to_output_stream())
}
fn path(&self) -> String {
@ -1174,13 +997,7 @@ impl Shell for FilesystemShell {
self.completer.complete(line, pos, ctx)
}
fn hint(
&self,
line: &str,
pos: usize,
ctx: &rustyline::Context<'_>,
_expand_context: ExpandContext,
) -> Option<String> {
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
self.hinter.hint(line, pos, ctx)
}
}

View File

@ -8,7 +8,6 @@ use crate::data::command_dict;
use crate::prelude::*;
use crate::shell::shell::Shell;
use nu_errors::ShellError;
use nu_parser::ExpandContext;
use nu_protocol::{
Primitive, ReturnSuccess, ShellTypeName, TaggedDictBuilder, UntaggedValue, Value,
};
@ -249,13 +248,7 @@ impl Shell for HelpShell {
Ok((replace_pos, completions))
}
fn hint(
&self,
_line: &str,
_pos: usize,
_ctx: &rustyline::Context<'_>,
_context: ExpandContext,
) -> Option<String> {
fn hint(&self, _line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option<String> {
None
}
}

View File

@ -1,9 +1,8 @@
use crate::context::Context;
use ansi_term::{Color, Style};
use log::log_enabled;
use nu_parser::{FlatShape, PipelineShape, ShapeResult, Token, TokensIterator};
use nu_protocol::{errln, outln};
use nu_source::{nom_input, HasSpan, Tag, Tagged, Text};
use nu_parser::SignatureRegistry;
use nu_protocol::hir::FlatShape;
use nu_source::{Span, Spanned, Tag, Tagged};
use rustyline::completion::Completer;
use rustyline::error::ReadlineError;
use rustyline::highlight::Highlighter;
@ -38,10 +37,7 @@ impl Completer for Helper {
impl Hinter for Helper {
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
let text = Text::from(line);
self.context
.shell_manager
.hint(line, pos, ctx, self.context.expand_context(&text))
self.context.shell_manager.hint(line, pos, ctx)
}
}
@ -65,49 +61,19 @@ impl Highlighter for Helper {
}
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
let tokens = nu_parser::pipeline(nom_input(line));
let lite_block = nu_parser::lite_parse(line, 0);
match tokens {
match lite_block {
Err(_) => Cow::Borrowed(line),
Ok((_rest, v)) => {
let pipeline = match v.as_pipeline() {
Err(_) => return Cow::Borrowed(line),
Ok(v) => v,
};
Ok(lb) => {
let classified =
nu_parser::classify_block(&lb, &self.context.registry().clone_box());
let text = Text::from(line);
let expand_context = self.context.expand_context(&text);
let tokens = vec![Token::Pipeline(pipeline).into_spanned(v.span())];
let mut tokens = TokensIterator::new(&tokens[..], expand_context, v.span());
let shapes = {
// We just constructed a token list that only contains a pipeline, so it can't fail
let result = tokens.expand_infallible(PipelineShape);
if let Some(failure) = result.failed {
errln!(
"BUG: PipelineShape didn't find a pipeline :: {:#?}",
failure
);
}
tokens.finish_tracer();
tokens.state().shapes()
};
if log_enabled!(target: "nu::expand_syntax", log::Level::Debug) {
outln!("");
let _ =
ptree::print_tree(&tokens.expand_tracer().clone().print(Text::from(line)));
outln!("");
}
let mut painter = Painter::new();
let shapes = nu_parser::shapes(&classified.block);
let mut painter = Painter::new(line);
for shape in shapes {
painter.paint_shape(&shape, line);
painter.paint_shape(&shape);
}
Cow::Owned(painter.into_string())
@ -133,73 +99,94 @@ fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
}
struct Painter {
current: Style,
buffer: String,
original: Vec<u8>,
styles: Vec<Style>,
}
impl Painter {
fn new() -> Painter {
fn new(original: &str) -> Painter {
let bytes: Vec<u8> = original.bytes().collect();
let bytes_count = bytes.len();
Painter {
current: Style::default(),
buffer: String::new(),
original: bytes,
styles: vec![Color::White.normal(); bytes_count],
}
}
fn paint_shape(&mut self, shape: &Spanned<FlatShape>) {
let style = match &shape.item {
FlatShape::OpenDelimiter(_) => Color::White.normal(),
FlatShape::CloseDelimiter(_) => Color::White.normal(),
FlatShape::ItVariable | FlatShape::Keyword => Color::Purple.bold(),
FlatShape::Variable | FlatShape::Identifier => Color::Purple.normal(),
FlatShape::Type => Color::Blue.bold(),
FlatShape::Operator => Color::Yellow.normal(),
FlatShape::DotDot => Color::Yellow.bold(),
FlatShape::Dot => Style::new().fg(Color::White),
FlatShape::InternalCommand => Color::Cyan.bold(),
FlatShape::ExternalCommand => Color::Cyan.normal(),
FlatShape::ExternalWord => Color::Green.bold(),
FlatShape::BareMember => Color::Yellow.bold(),
FlatShape::StringMember => Color::Yellow.bold(),
FlatShape::String => Color::Green.normal(),
FlatShape::Path => Color::Cyan.normal(),
FlatShape::GlobPattern => Color::Cyan.bold(),
FlatShape::Word => Color::Green.normal(),
FlatShape::Pipe => Color::Purple.bold(),
FlatShape::Flag => Color::Blue.bold(),
FlatShape::ShorthandFlag => Color::Blue.bold(),
FlatShape::Int => Color::Purple.bold(),
FlatShape::Decimal => Color::Purple.bold(),
FlatShape::Whitespace | FlatShape::Separator => Color::White.normal(),
FlatShape::Comment => Color::Green.bold(),
FlatShape::Garbage => Style::new().fg(Color::White).on(Color::Red),
FlatShape::Size { number, unit } => {
self.paint(Color::Purple.bold(), number);
self.paint(Color::Cyan.bold(), unit);
return;
}
};
self.paint(style, &shape.span);
}
fn paint(&mut self, style: Style, span: &Span) {
for pos in span.start()..span.end() {
self.styles[pos] = style;
}
}
fn into_string(self) -> String {
self.buffer
}
let mut idx_start = 0;
let mut idx_end = 1;
fn paint_shape(&mut self, shape: &ShapeResult, line: &str) {
let style = match &shape {
ShapeResult::Success(shape) => match shape.item {
FlatShape::OpenDelimiter(_) => Color::White.normal(),
FlatShape::CloseDelimiter(_) => Color::White.normal(),
FlatShape::ItVariable | FlatShape::Keyword => Color::Purple.bold(),
FlatShape::Variable | FlatShape::Identifier => Color::Purple.normal(),
FlatShape::Type => Color::Blue.bold(),
FlatShape::CompareOperator => Color::Yellow.normal(),
FlatShape::DotDot => Color::Yellow.bold(),
FlatShape::Dot => Style::new().fg(Color::White),
FlatShape::InternalCommand => Color::Cyan.bold(),
FlatShape::ExternalCommand => Color::Cyan.normal(),
FlatShape::ExternalWord => Color::Green.bold(),
FlatShape::BareMember => Color::Yellow.bold(),
FlatShape::StringMember => Color::Yellow.bold(),
FlatShape::String => Color::Green.normal(),
FlatShape::Path => Color::Cyan.normal(),
FlatShape::GlobPattern => Color::Cyan.bold(),
FlatShape::Word => Color::Green.normal(),
FlatShape::Pipe => Color::Purple.bold(),
FlatShape::Flag => Color::Blue.bold(),
FlatShape::ShorthandFlag => Color::Blue.bold(),
FlatShape::Int => Color::Purple.bold(),
FlatShape::Decimal => Color::Purple.bold(),
FlatShape::Whitespace | FlatShape::Separator => Color::White.normal(),
FlatShape::Comment => Color::Green.bold(),
FlatShape::Garbage => Style::new().fg(Color::White).on(Color::Red),
FlatShape::Size { number, unit } => {
let number = number.slice(line);
let unit = unit.slice(line);
if self.original.is_empty() {
String::new()
} else {
let mut builder = String::new();
self.paint(Color::Purple.bold(), number);
self.paint(Color::Cyan.bold(), unit);
return;
let mut current_style = self.styles[0];
while idx_end < self.styles.len() {
if self.styles[idx_end] != current_style {
// Emit, as we changed styles
let intermediate = String::from_utf8_lossy(&self.original[idx_start..idx_end]);
builder.push_str(&format!("{}", current_style.paint(intermediate)));
current_style = self.styles[idx_end];
idx_start = idx_end;
idx_end += 1;
} else {
idx_end += 1;
}
},
ShapeResult::Fallback { shape, .. } => match shape.item {
FlatShape::Whitespace | FlatShape::Separator => Color::White.normal(),
_ => Style::new().fg(Color::White).on(Color::Red),
},
};
}
self.paint(style, shape.span().slice(line));
}
let intermediate = String::from_utf8_lossy(&self.original[idx_start..idx_end]);
builder.push_str(&format!("{}", current_style.paint(intermediate)));
fn paint(&mut self, style: Style, body: &str) {
let infix = self.current.infix(style);
self.current = style;
self.buffer
.push_str(&format!("{}{}", infix, style.paint(body)));
builder
}
}
}

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