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: | run: |
cross build --target ${{ matrix.arch }} --release cross build --target ${{ matrix.arch }} --release
# leave only the executable file # 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 find . -empty -delete
- uses: actions/upload-artifact@master - uses: actions/upload-artifact@master
with: 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] [package]
name = "nu" name = "nu"
version = "0.12.0" version = "0.13.0"
authors = ["The Nu Project Contributors"] authors = ["The Nu Project Contributors"]
description = "A new kind of shell" description = "A new type of shell"
license = "MIT" license = "MIT"
edition = "2018" edition = "2018"
readme = "README.md" 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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-cli = { version = "0.12.0", path = "./crates/nu-cli" } nu-cli = { version = "0.13.0", path = "./crates/nu-cli" }
nu-source = { version = "0.12.0", path = "./crates/nu-source" } nu-source = { version = "0.13.0", path = "./crates/nu-source" }
nu-plugin = { version = "0.12.0", path = "./crates/nu-plugin" } nu-plugin = { version = "0.13.0", path = "./crates/nu-plugin" }
nu-protocol = { version = "0.12.0", path = "./crates/nu-protocol" } nu-protocol = { version = "0.13.0", path = "./crates/nu-protocol" }
nu-errors = { version = "0.12.0", path = "./crates/nu-errors" } nu-errors = { version = "0.13.0", path = "./crates/nu-errors" }
nu-parser = { version = "0.12.0", path = "./crates/nu-parser" } nu-parser = { version = "0.13.0", path = "./crates/nu-parser" }
nu-value-ext = { version = "0.12.0", path = "./crates/nu-value-ext" } nu-value-ext = { version = "0.13.0", path = "./crates/nu-value-ext" }
nu_plugin_average = { version = "0.12.0", path = "./crates/nu_plugin_average", optional=true } nu_plugin_average = { version = "0.13.0", path = "./crates/nu_plugin_average", optional=true }
nu_plugin_binaryview = { version = "0.12.0", path = "./crates/nu_plugin_binaryview", optional=true } nu_plugin_binaryview = { version = "0.13.0", path = "./crates/nu_plugin_binaryview", optional=true }
nu_plugin_fetch = { version = "0.12.0", path = "./crates/nu_plugin_fetch", optional=true } nu_plugin_fetch = { version = "0.13.0", path = "./crates/nu_plugin_fetch", optional=true }
nu_plugin_inc = { version = "0.12.0", path = "./crates/nu_plugin_inc", optional=true } nu_plugin_inc = { version = "0.13.0", path = "./crates/nu_plugin_inc", optional=true }
nu_plugin_match = { version = "0.12.0", path = "./crates/nu_plugin_match", optional=true } nu_plugin_match = { version = "0.13.0", path = "./crates/nu_plugin_match", optional=true }
nu_plugin_post = { version = "0.12.0", path = "./crates/nu_plugin_post", optional=true } nu_plugin_post = { version = "0.13.0", path = "./crates/nu_plugin_post", optional=true }
nu_plugin_ps = { version = "0.12.0", path = "./crates/nu_plugin_ps", optional=true } nu_plugin_ps = { version = "0.13.0", path = "./crates/nu_plugin_ps", optional=true }
nu_plugin_str = { version = "0.12.0", path = "./crates/nu_plugin_str", optional=true } nu_plugin_str = { version = "0.13.0", path = "./crates/nu_plugin_str", optional=true }
nu_plugin_sys = { version = "0.12.0", path = "./crates/nu_plugin_sys", optional=true } nu_plugin_sys = { version = "0.13.0", path = "./crates/nu_plugin_sys", optional=true }
nu_plugin_textview = { version = "0.12.0", path = "./crates/nu_plugin_textview", optional=true } nu_plugin_textview = { version = "0.13.0", path = "./crates/nu_plugin_textview", optional=true }
nu_plugin_tree = { version = "0.12.0", path = "./crates/nu_plugin_tree", optional=true } nu_plugin_tree = { version = "0.13.0", path = "./crates/nu_plugin_tree", optional=true }
nu-macros = { version = "0.12.0", path = "./crates/nu-macros" }
crossterm = { version = "0.16.0", optional = true } crossterm = { version = "0.17.2", optional = true }
onig_sys = { version = "=69.1.0", optional = true }
semver = { version = "0.9.0", 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 } url = { version = "2.1.1", optional = true }
clap = "2.33.0" clap = "2.33.0"
@ -52,23 +50,22 @@ log = "0.4.8"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.6.1" nu-test-support = { version = "0.13.0", path = "./crates/nu-test-support" }
nu-test-support = { version = "0.12.0", path = "./crates/nu-test-support" }
[build-dependencies] [build-dependencies]
toml = "0.5.6" toml = "0.5.6"
serde = { version = "1.0.105", features = ["derive"] } serde = { version = "1.0.106", features = ["derive"] }
nu-build = { version = "0.12.0", path = "./crates/nu-build" } nu-build = { version = "0.13.0", path = "./crates/nu-build" }
[features] [features]
# Test executables # Test executables
test-bins = [] test-bins = []
default = ["sys", "ps", "textview", "inc", "str"] 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 # Default
textview = ["crossterm", "syntect", "onig_sys", "url", "nu_plugin_textview"] textview = ["crossterm", "syntect", "url", "nu_plugin_textview"]
sys = ["nu_plugin_sys"] sys = ["nu_plugin_sys"]
ps = ["nu_plugin_ps"] ps = ["nu_plugin_ps"]
inc = ["semver", "nu_plugin_inc"] inc = ["semver", "nu_plugin_inc"]
@ -85,6 +82,7 @@ tree = ["nu_plugin_tree"]
clipboard-cli = ["nu-cli/clipboard-cli"] clipboard-cli = ["nu-cli/clipboard-cli"]
starship-prompt = ["nu-cli/starship-prompt"] starship-prompt = ["nu-cli/starship-prompt"]
trash-support = ["nu-cli/trash-support"]
[[bin]] [[bin]]
name = "fail" name = "fail"

View File

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

View File

@ -1,7 +1,7 @@
[package] [package]
name = "nu-cli" name = "nu-cli"
version = "0.12.0" version = "0.13.0"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"] authors = ["The Nu Project Contributors"]
description = "CLI for nushell" description = "CLI for nushell"
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
@ -10,14 +10,13 @@ license = "MIT"
doctest = false doctest = false
[dependencies] [dependencies]
nu-source = { version = "0.12.0", path = "../nu-source" } nu-source = { version = "0.13.0", path = "../nu-source" }
nu-plugin = { version = "0.12.0", path = "../nu-plugin" } nu-plugin = { version = "0.13.0", path = "../nu-plugin" }
nu-protocol = { version = "0.12.0", path = "../nu-protocol" } nu-protocol = { version = "0.13.0", path = "../nu-protocol" }
nu-errors = { version = "0.12.0", path = "../nu-errors" } nu-errors = { version = "0.13.0", path = "../nu-errors" }
nu-parser = { version = "0.12.0", path = "../nu-parser" } nu-parser = { version = "0.13.0", path = "../nu-parser" }
nu-value-ext = { version = "0.12.0", path = "../nu-value-ext" } nu-value-ext = { version = "0.13.0", path = "../nu-value-ext" }
nu-macros = { version = "0.12.0", path = "../nu-macros" } nu-test-support = { version = "0.13.0", path = "../nu-test-support" }
nu-test-support = { version = "0.12.0", path = "../nu-test-support" }
ansi_term = "0.12.1" ansi_term = "0.12.1"
app_dirs = "1.2.1" app_dirs = "1.2.1"
@ -36,26 +35,23 @@ ctrlc = "3.1.4"
derive-new = "0.5.8" derive-new = "0.5.8"
dirs = "2.0.2" dirs = "2.0.2"
dunce = "1.0.0" dunce = "1.0.0"
filesize = "0.1.0" filesize = "0.2.0"
futures = { version = "0.3", features = ["compat", "io-compat"] } futures = { version = "0.3", features = ["compat", "io-compat"] }
futures-util = "0.3.4" futures-util = "0.3.4"
futures_codec = "0.4" futures_codec = "0.4"
getset = "0.1.0" getset = "0.1.0"
git2 = { version = "0.13.0", default_features = false } git2 = { version = "0.13.1", default_features = false }
glob = "0.3.0" glob = "0.3.0"
hex = "0.4" hex = "0.4"
htmlescape = "0.3.1" htmlescape = "0.3.1"
ical = "0.6.*" ical = "0.6.*"
ichwh = "0.3" ichwh = "0.3.4"
indexmap = { version = "1.3.2", features = ["serde-1"] } indexmap = { version = "1.3.2", features = ["serde-1"] }
itertools = "0.9.0" itertools = "0.9.0"
language-reporting = "0.4.0" language-reporting = "0.4.0"
log = "0.4.8" log = "0.4.8"
meval = "0.2" meval = "0.2"
natural = "0.5.0" 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-bigint = { version = "0.2.6", features = ["serde"] }
num-traits = "0.2.11" num-traits = "0.2.11"
parking_lot = "0.10.0" parking_lot = "0.10.0"
@ -67,13 +63,13 @@ ptree = {version = "0.2" }
query_interface = "0.3.5" query_interface = "0.3.5"
rand = "0.7" rand = "0.7"
regex = "1" regex = "1"
roxmltree = "0.10.0" roxmltree = "0.10.1"
rustyline = "6.0.0" rustyline = "6.1.1"
serde = { version = "1.0.105", features = ["derive"] } serde = { version = "1.0.106", features = ["derive"] }
serde-hjson = "0.9.1" serde-hjson = "0.9.1"
serde_bytes = "0.11.3" serde_bytes = "0.11.3"
serde_ini = "0.2.0" serde_ini = "0.2.0"
serde_json = "1.0.48" serde_json = "1.0.51"
serde_urlencoded = "0.6.1" serde_urlencoded = "0.6.1"
serde_yaml = "0.8" serde_yaml = "0.8"
shellexpand = "2.0.0" shellexpand = "2.0.0"
@ -83,28 +79,30 @@ term = "0.5.2"
termcolor = "1.1.0" termcolor = "1.1.0"
textwrap = {version = "0.11.0", features = ["term_size"]} textwrap = {version = "0.11.0", features = ["term_size"]}
toml = "0.5.6" toml = "0.5.6"
trash = "1.0.0"
typetag = "0.1.4" typetag = "0.1.4"
umask = "0.1" umask = "0.1"
unicode-xid = "0.2.0" unicode-xid = "0.2.0"
trash = { version = "1.0.0", optional = true }
clipboard = { version = "0.5", 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] [target.'cfg(unix)'.dependencies]
users = "0.9" users = "0.10.0"
[dependencies.rusqlite] [dependencies.rusqlite]
version = "0.21.0" version = "0.22.0"
features = ["bundled", "blob"] features = ["bundled", "blob"]
[dev-dependencies]
pretty_assertions = "0.6.1"
[build-dependencies] [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] [features]
stable = [] stable = []
starship-prompt = ["starship"] starship-prompt = ["starship"]
clipboard-cli = ["clipboard"] 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::external::{MaybeTextCodec, StringOrBinary};
use crate::commands::classified::pipeline::run_pipeline;
use crate::commands::plugin::JsonRpc; use crate::commands::plugin::JsonRpc;
use crate::commands::plugin::{PluginCommand, PluginSink}; use crate::commands::plugin::{PluginCommand, PluginSink};
use crate::commands::whole_stream_command; use crate::commands::whole_stream_command;
@ -10,13 +10,10 @@ use crate::prelude::*;
use futures_codec::FramedRead; use futures_codec::FramedRead;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_parser::{ use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
ClassifiedCommand, ClassifiedPipeline, ExternalCommand, PipelineShape, SpannedToken, use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
TokensIterator,
};
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use log::{debug, log_enabled, trace}; use log::{debug, trace};
use rustyline::error::ReadlineError; use rustyline::error::ReadlineError;
use rustyline::{ use rustyline::{
self, config::Configurer, config::EditMode, At, Cmd, ColorMode, CompletionType, Config, Editor, 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(What),
whole_stream_command(Which), whole_stream_command(Which),
whole_stream_command(Debug), whole_stream_command(Debug),
per_item_command(Alias),
// Statistics // Statistics
whole_stream_command(Size), whole_stream_command(Size),
whole_stream_command(Count), whole_stream_command(Count),
@ -307,6 +305,7 @@ pub fn create_default_context(
whole_stream_command(Range), whole_stream_command(Range),
whole_stream_command(Rename), whole_stream_command(Rename),
whole_stream_command(Uniq), whole_stream_command(Uniq),
per_item_command(Each),
// Table manipulation // Table manipulation
whole_stream_command(Shuffle), whole_stream_command(Shuffle),
whole_stream_command(Wrap), whole_stream_command(Wrap),
@ -345,6 +344,8 @@ pub fn create_default_context(
whole_stream_command(FromYML), whole_stream_command(FromYML),
whole_stream_command(FromIcs), whole_stream_command(FromIcs),
whole_stream_command(FromVcf), whole_stream_command(FromVcf),
// "Private" commands (not intended to be accessed directly)
whole_stream_command(RunExternalCommand),
]); ]);
cfg_if::cfg_if! { cfg_if::cfg_if! {
@ -369,10 +370,64 @@ pub fn create_default_context(
Ok(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( pub async fn run_pipeline_standalone(
pipeline: String, pipeline: String,
redirect_stdin: bool, redirect_stdin: bool,
context: &mut Context, context: &mut Context,
exit_on_error: bool,
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
let line = process_line(Ok(pipeline), context, redirect_stdin, false).await; 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)); 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"); .expect("Error setting Ctrl-C handler");
let mut ctrlcbreak = false; 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 { loop {
if context.ctrl_c.load(Ordering::SeqCst) { if context.ctrl_c.load(Ordering::SeqCst) {
context.ctrl_c.store(false, Ordering::SeqCst); context.ctrl_c.store(false, Ordering::SeqCst);
@ -614,9 +699,9 @@ async fn process_line(
Ok(line) => { Ok(line) => {
let line = chomp_newline(line); let line = chomp_newline(line);
let result = match nu_parser::parse(&line) { let result = match nu_parser::lite_parse(&line, 0) {
Err(err) => { Err(err) => {
return LineResult::Error(line.to_string(), err); return LineResult::Error(line.to_string(), err.into());
} }
Ok(val) => val, Ok(val) => val,
@ -625,9 +710,12 @@ async fn process_line(
debug!("=== Parsed ==="); debug!("=== Parsed ===");
debug!("{:#?}", result); 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()); return LineResult::Error(line.to_string(), failure.into());
} }
@ -637,15 +725,43 @@ async fn process_line(
// ...and it doesn't have any arguments // ...and it doesn't have any arguments
// ...and we're in the CLI // ...and we're in the CLI
// ...then change to this directory // ...then change to this directory
if cli_mode && pipeline.commands.list.len() == 1 { if cli_mode
if let ClassifiedCommand::External(ExternalCommand { && classified_block.block.block.len() == 1
&& classified_block.block.block[0].list.len() == 1
{
if let ClassifiedCommand::Internal(InternalCommand {
ref name, ref args, .. ref name, ref args, ..
}) = pipeline.commands.list[0] }) = classified_block.block.block[0].list[0]
{ {
if dunce::canonicalize(name).is_ok() let internal_name = name;
&& PathBuf::from(name).is_dir() let name = args
&& ichwh::which(name).await.unwrap_or(None).is_none() .positional
&& args.list.is_empty() .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 // Here we work differently if we're in Windows because of the expected Windows behavior
#[cfg(windows)] #[cfg(windows)]
@ -687,6 +803,7 @@ async fn process_line(
} }
} }
} }
let input_stream = if redirect_stdin { let input_stream = if redirect_stdin {
let file = futures::io::AllowStdIo::new(std::io::stdin()); let file = futures::io::AllowStdIo::new(std::io::stdin());
let stream = FramedRead::new(file, MaybeTextCodec).map(|line| { 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") panic!("Internal error: could not read lines of text from stdin")
} }
}); });
Some(stream.to_input_stream()) stream.to_input_stream()
} else { } else {
None InputStream::empty()
}; };
match run_pipeline(pipeline, ctx, input_stream, line).await { match run_block(&classified_block.block, ctx, input_stream, &Scope::empty()).await {
Ok(Some(input)) => { Ok(input) => {
// Running a pipeline gives us back a stream that we can then // 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 // work through. At the top level, we just want to pull on the
// values to compute them. // values to compute them.
@ -724,9 +841,8 @@ async fn process_line(
shell_manager: ctx.shell_manager.clone(), shell_manager: ctx.shell_manager.clone(),
host: ctx.host.clone(), host: ctx.host.clone(),
ctrl_c: ctx.ctrl_c.clone(), ctrl_c: ctx.ctrl_c.clone(),
commands: ctx.registry.clone(), registry: ctx.registry.clone(),
name: Tag::unknown(), name: Tag::unknown(),
source: Text::from(String::new()),
}; };
if let Ok(mut output_stream) = crate::commands::autoview::autoview(context) { if let Ok(mut output_stream) = crate::commands::autoview::autoview(context) {
@ -749,7 +865,6 @@ async fn process_line(
LineResult::Success(line.to_string()) LineResult::Success(line.to_string())
} }
Ok(None) => LineResult::Success(line.to_string()),
Err(err) => LineResult::Error(line.to_string(), err), 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) { pub fn print_err(err: ShellError, host: &dyn Host, source: &Text) {
let diag = err.into_diagnostic(); if let Some(diag) = err.into_diagnostic() {
let writer = host.err_termcolor();
let writer = host.err_termcolor(); let mut source = source.to_string();
let mut source = source.to_string(); source.push_str(" ");
source.push_str(" "); let files = nu_parser::Files::new(source);
let files = nu_parser::Files::new(source); let _ = std::panic::catch_unwind(move || {
let _ = std::panic::catch_unwind(move || { let _ = language_reporting::emit(
let _ = language_reporting::emit( &mut writer.lock(),
&mut writer.lock(), &files,
&files, &diag,
&diag, &language_reporting::DefaultConfig,
&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 from_delimited_data;
mod to_delimited_data; mod to_delimited_data;
pub(crate) mod alias;
pub(crate) mod append; pub(crate) mod append;
pub(crate) mod args; pub(crate) mod args;
pub(crate) mod autoview; pub(crate) mod autoview;
@ -20,6 +21,7 @@ pub(crate) mod date;
pub(crate) mod debug; pub(crate) mod debug;
pub(crate) mod default; pub(crate) mod default;
pub(crate) mod du; pub(crate) mod du;
pub(crate) mod each;
pub(crate) mod echo; pub(crate) mod echo;
pub(crate) mod edit; pub(crate) mod edit;
pub(crate) mod enter; pub(crate) mod enter;
@ -74,6 +76,8 @@ pub(crate) mod reject;
pub(crate) mod rename; pub(crate) mod rename;
pub(crate) mod reverse; pub(crate) mod reverse;
pub(crate) mod rm; pub(crate) mod rm;
pub(crate) mod run_alias;
pub(crate) mod run_external;
pub(crate) mod save; pub(crate) mod save;
pub(crate) mod shells; pub(crate) mod shells;
pub(crate) mod shuffle; pub(crate) mod shuffle;
@ -114,6 +118,7 @@ pub(crate) use command::{
WholeStreamCommand, WholeStreamCommand,
}; };
pub(crate) use alias::Alias;
pub(crate) use append::Append; pub(crate) use append::Append;
pub(crate) use calc::Calc; pub(crate) use calc::Calc;
pub(crate) use compact::Compact; pub(crate) use compact::Compact;
@ -124,6 +129,7 @@ pub(crate) use date::Date;
pub(crate) use debug::Debug; pub(crate) use debug::Debug;
pub(crate) use default::Default; pub(crate) use default::Default;
pub(crate) use du::Du; pub(crate) use du::Du;
pub(crate) use each::Each;
pub(crate) use echo::Echo; pub(crate) use echo::Echo;
pub(crate) use edit::Edit; pub(crate) use edit::Edit;
pub(crate) mod kill; pub(crate) mod kill;
@ -184,6 +190,7 @@ pub(crate) use reject::Reject;
pub(crate) use rename::Rename; pub(crate) use rename::Rename;
pub(crate) use reverse::Reverse; pub(crate) use reverse::Reverse;
pub(crate) use rm::Remove; pub(crate) use rm::Remove;
pub(crate) use run_external::RunExternalCommand;
pub(crate) use save::Save; pub(crate) use save::Save;
pub(crate) use shells::Shells; pub(crate) use shells::Shells;
pub(crate) use shuffle::Shuffle; 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); after.push_back(row);
let after = futures::stream::iter(after); 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::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_parser::{hir, hir::Expression, hir::Literal, hir::SpannedExpression}; use nu_protocol::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value}; use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
@ -29,10 +29,9 @@ impl WholeStreamCommand for Autoview {
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
autoview(RunnableContext { autoview(RunnableContext {
input: args.input, input: args.input,
commands: registry.clone(), registry: registry.clone(),
shell_manager: args.shell_manager, shell_manager: args.shell_manager,
host: args.host, host: args.host,
source: args.call_info.source,
ctrl_c: args.ctrl_c, ctrl_c: args.ctrl_c,
name: args.call_info.name_tag, name: args.call_info.name_tag,
}) })
@ -42,9 +41,8 @@ impl WholeStreamCommand for Autoview {
pub struct RunnableContextWithoutInput { pub struct RunnableContextWithoutInput {
pub shell_manager: ShellManager, pub shell_manager: ShellManager,
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>, pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub source: Text,
pub ctrl_c: Arc<AtomicBool>, pub ctrl_c: Arc<AtomicBool>,
pub commands: CommandRegistry, pub registry: CommandRegistry,
pub name: Tag, pub name: Tag,
} }
@ -53,9 +51,8 @@ impl RunnableContextWithoutInput {
let new_context = RunnableContextWithoutInput { let new_context = RunnableContextWithoutInput {
shell_manager: context.shell_manager, shell_manager: context.shell_manager,
host: context.host, host: context.host,
source: context.source,
ctrl_c: context.ctrl_c, ctrl_c: context.ctrl_c,
commands: context.commands, registry: context.registry,
name: context.name, name: context.name,
}; };
(context.input, new_context) (context.input, new_context)
@ -95,7 +92,7 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
if let Some(table) = table { if let Some(table) = table {
let command_args = create_default_command_args(&context).with_input(stream); 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; result.collect::<Vec<_>>().await;
} }
} }
@ -109,7 +106,7 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
let mut stream = VecDeque::new(); let mut stream = VecDeque::new();
stream.push_back(UntaggedValue::string(s).into_value(Tag { anchor, span })); stream.push_back(UntaggedValue::string(s).into_value(Tag { anchor, span }));
let command_args = create_default_command_args(&context).with_input(stream); 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; result.collect::<Vec<_>>().await;
} else { } else {
out!("{}", s); out!("{}", s);
@ -129,7 +126,7 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
let mut stream = VecDeque::new(); let mut stream = VecDeque::new();
stream.push_back(UntaggedValue::string(s).into_value(Tag { anchor, span })); stream.push_back(UntaggedValue::string(s).into_value(Tag { anchor, span }));
let command_args = create_default_command_args(&context).with_input(stream); 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; result.collect::<Vec<_>>().await;
} else { } else {
out!("{}\n", s); out!("{}\n", s);
@ -159,13 +156,19 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
} => { } => {
out!("{}", n); out!("{}", n);
} }
Value {
value: UntaggedValue::Primitive(Primitive::Boolean(b)),
..
} => {
out!("{}", b);
}
Value { value: UntaggedValue::Primitive(Primitive::Binary(ref b)), .. } => { Value { value: UntaggedValue::Primitive(Primitive::Binary(ref b)), .. } => {
if let Some(binary) = binary { if let Some(binary) = binary {
let mut stream = VecDeque::new(); let mut stream = VecDeque::new();
stream.push_back(x); stream.push_back(x);
let command_args = create_default_command_args(&context).with_input(stream); 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; result.collect::<Vec<_>>().await;
} else { } else {
use pretty_hex::*; use pretty_hex::*;
@ -251,7 +254,7 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
let mut stream = VecDeque::new(); let mut stream = VecDeque::new();
stream.push_back(x); stream.push_back(x);
let command_args = create_default_command_args(&context).with_input(stream); 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; result.collect::<Vec<_>>().await;
} else { } else {
out!("{:?}", item); out!("{:?}", item);
@ -282,15 +285,16 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
call_info: UnevaluatedCallInfo { call_info: UnevaluatedCallInfo {
args: hir::Call { args: hir::Call {
head: Box::new(SpannedExpression::new( head: Box::new(SpannedExpression::new(
Expression::Literal(Literal::String(span)), Expression::Literal(Literal::String(String::new())),
span, span,
)), )),
positional: None, positional: None,
named: None, named: None,
span, span,
is_last: true,
}, },
source: context.source.clone(),
name_tag: context.name.clone(), name_tag: context.name.clone(),
scope: Scope::empty(),
}, },
} }
} }

View File

@ -1,7 +1,6 @@
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_macros::signature;
use nu_protocol::{Signature, SyntaxShape}; use nu_protocol::{Signature, SyntaxShape};
pub struct Cd; pub struct Cd;
@ -12,17 +11,11 @@ impl WholeStreamCommand for Cd {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
signature! { Signature::build("cd").optional(
def cd { "directory",
"the directory to change to" SyntaxShape::Path,
directory(optional Path) - "the directory to change to" "the directory to change to",
} )
}
// Signature::build("cd").optional(
// "directory",
// SyntaxShape::Path,
// "the directory to change to",
// )
} }
fn usage(&self) -> &str { 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 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) struct Command {
pub(crate) args: hir::Call, 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::futures::ThreadedReceiver;
use crate::prelude::*; 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 bytes::{BufMut, Bytes, BytesMut};
use futures::executor::block_on_stream; use futures::executor::block_on_stream;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use futures_codec::FramedRead; use futures_codec::FramedRead;
use log::trace; use log::trace;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_parser::commands::classified::external::ExternalArg; use nu_protocol::hir::ExternalCommand;
use nu_parser::ExternalCommand; use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
use nu_protocol::{ColumnPath, Primitive, ShellTypeName, UntaggedValue, Value}; use nu_source::Tag;
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;
pub enum StringOrBinary { pub enum StringOrBinary {
String(String), 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 { match &from.value {
UntaggedValue::Primitive(Primitive::Int(i)) => Ok(i.to_string()), UntaggedValue::Primitive(Primitive::Int(i)) => Ok(i.to_string()),
UntaggedValue::Primitive(Primitive::String(s)) 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( unsupported => Err(ShellError::labeled_error(
format!("needs string data (given: {})", unsupported.type_name()), format!("needs string data (given: {})", unsupported.type_name()),
"expected a string", "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( pub(crate) async fn run_external_command(
command: ExternalCommand, command: ExternalCommand,
context: &mut Context, context: &mut Context,
input: Option<InputStream>, input: InputStream,
scope: &Scope,
is_last: bool, is_last: bool,
) -> Result<Option<InputStream>, ShellError> { ) -> Result<InputStream, ShellError> {
trace!(target: "nu::run::external", "-> {}", command.name); trace!(target: "nu::run::external", "-> {}", command.name);
if !did_find_command(&command.name).await { 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() { if command.has_it_argument() {
run_with_iterator_arg(command, context, input, is_last) run_with_iterator_arg(command, context, input, scope, is_last)
} else { } 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( fn run_with_iterator_arg(
command: ExternalCommand, command: ExternalCommand,
context: &mut Context, context: &mut Context,
input: Option<InputStream>, input: InputStream,
scope: &Scope,
is_last: bool, is_last: bool,
) -> Result<Option<InputStream>, ShellError> { ) -> Result<InputStream, ShellError> {
let path = context.shell_manager.path(); let path = context.shell_manager.path();
let mut inputs: InputStream = if let Some(input) = input { let mut inputs: InputStream =
trace_stream!(target: "nu::trace_stream::external::it", "input" = input) trace_stream!(target: "nu::trace_stream::external::it", "input" = input);
} else {
InputStream::empty() let name_tag = command.name_tag.clone();
}; let scope = scope.clone();
let context = context.clone();
let stream = async_stream! { let stream = async_stream! {
while let Some(value) = inputs.next().await { while let Some(value) = inputs.next().await {
let name = command.name.clone(); // Evaluate the expressions into values, and from values into strings for each iteration
let name_tag = command.name_tag.clone(); let mut command_args = vec![];
let home_dir = dirs::home_dir(); let scope = scope.clone().set_it(value);
let path = &path; for arg in command.args.iter() {
let args = command.args.clone(); let value = evaluate_baseline_expr(arg, &context.registry, &scope)?;
command_args.push(nu_value_to_string(&name_tag, &value)?);
}
let it_replacement = { let process_args = command_args
if command.has_it_argument() { .iter()
let empty_arg = ExternalArg { .map(|arg| {
arg: "".to_string(), let arg = expand_tilde(arg.deref(), dirs::home_dir);
tag: name_tag.clone()
};
let key = args.iter() #[cfg(not(windows))]
.find(|arg| arg.looks_like_it()) {
.unwrap_or_else(|| &empty_arg); if argument_contains_whitespace(&arg) && argument_is_quoted(&arg) {
if let Some(unquoted) = remove_quotes(&arg) {
if args.iter().all(|arg| !arg.is_it()) { format!(r#""{}""#, unquoted)
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)
} else { } else {
None arg.as_ref().to_string()
}
} 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
} }
} else { } else {
Some(arg.to_string()) arg.as_ref().to_string()
}; }
arg
} }
}).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) { match spawn(&command, &path, &process_args[..], InputStream::empty(), is_last) {
Ok(res) => { Ok(mut res) => {
if let Some(mut res) = res { while let Some(item) = res.next().await {
while let Some(item) = res.next().await { yield Ok(item)
yield Ok(item)
}
} }
} }
Err(reason) => { 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( fn run_with_stdin(
command: ExternalCommand, command: ExternalCommand,
context: &mut Context, context: &mut Context,
input: Option<InputStream>, input: InputStream,
scope: &Scope,
is_last: bool, is_last: bool,
) -> Result<Option<InputStream>, ShellError> { ) -> Result<InputStream, ShellError> {
let path = context.shell_manager.path(); let path = context.shell_manager.path();
let input = input let input = trace_stream!(target: "nu::trace_stream::external::stdin", "input" = input);
.map(|input| trace_stream!(target: "nu::trace_stream::external::stdin", "input" = input));
let process_args = command let mut command_args = vec![];
.args 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() .iter()
.map(|arg| { .map(|arg| {
let arg = expand_tilde(arg.deref(), dirs::home_dir); let arg = expand_tilde(arg.deref(), dirs::home_dir);
#[cfg(not(windows))] #[cfg(not(windows))]
{ {
if argument_contains_whitespace(&arg) && argument_is_quoted(&arg) { if argument_contains_whitespace(&arg) && !argument_is_quoted(&arg) {
if let Some(unquoted) = remove_quotes(&arg) { add_quotes(&arg)
format!(r#""{}""#, unquoted)
} else {
arg.as_ref().to_string()
}
} else { } else {
arg.as_ref().to_string() arg.as_ref().to_string()
} }
@ -432,9 +242,9 @@ fn spawn(
command: &ExternalCommand, command: &ExternalCommand,
path: &str, path: &str,
args: &[String], args: &[String],
input: Option<InputStream>, input: InputStream,
is_last: bool, is_last: bool,
) -> Result<Option<InputStream>, ShellError> { ) -> Result<InputStream, ShellError> {
let command = command.clone(); let command = command.clone();
let mut process = { let mut process = {
@ -444,6 +254,8 @@ fn spawn(
process.arg("/c"); process.arg("/c");
process.arg(&command.name); process.arg(&command.name);
for arg in args { for arg in args {
// Clean the args before we use them:
let arg = arg.replace("|", "\\|");
process.arg(&arg); process.arg(&arg);
} }
process process
@ -469,7 +281,7 @@ fn spawn(
} }
// open since we have some contents for stdin // open since we have some contents for stdin
if input.is_some() { if !input.is_empty() {
process.stdin(Stdio::piped()); process.stdin(Stdio::piped());
trace!(target: "nu::run::external", "set up stdin pipe"); trace!(target: "nu::run::external", "set up stdin pipe");
} }
@ -488,7 +300,7 @@ fn spawn(
let stdout_name_tag = command.name_tag; let stdout_name_tag = command.name_tag;
std::thread::spawn(move || { std::thread::spawn(move || {
if let Some(input) = input { if !input.is_empty() {
let mut stdin_write = stdin let mut stdin_write = stdin
.take() .take()
.expect("Internal error: could not get stdin pipe for external command"); .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 // We can give an error when we see a non-zero exit code, but this is different
// than what other shells will do. // 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()); let cfg = crate::data::config::config(Tag::unknown());
if let Ok(cfg) = cfg { if let Ok(cfg) = cfg {
if cfg.contains_key("nonzero_exit_errors") { if cfg.contains_key("nonzero_exit_errors") {
@ -620,17 +440,21 @@ fn spawn(
"command failed", "command failed",
&stdout_name_tag, &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(()) Ok(())
}); });
let stream = ThreadedReceiver::new(rx); let stream = ThreadedReceiver::new(rx);
Ok(Some(stream.to_input_stream())) Ok(stream.to_input_stream())
} else { } else {
Err(ShellError::labeled_error( Err(ShellError::labeled_error(
"Failed to spawn process", "Failed to spawn process",
@ -654,6 +478,7 @@ async fn did_find_command(name: &str) -> bool {
let cmd_builtins = [ let cmd_builtins = [
"call", "cls", "color", "date", "dir", "echo", "find", "hostname", "pause", "call", "cls", "color", "date", "dir", "echo", "find", "hostname", "pause",
"start", "time", "title", "ver", "copy", "mkdir", "rename", "rd", "rmdir", "type", "start", "time", "title", "ver", "copy", "mkdir", "rename", "rd", "rmdir", "type",
"mklink",
]; ];
cmd_builtins.contains(&name) cmd_builtins.contains(&name)
@ -714,10 +539,11 @@ fn shell_os_paths() -> Vec<std::path::PathBuf> {
mod tests { mod tests {
use super::{ use super::{
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes, 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 futures::executor::block_on;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::Scope;
use nu_test_support::commands::ExternalBuilder; use nu_test_support::commands::ExternalBuilder;
// async fn read(mut stream: OutputStream) -> Option<Value> { // async fn read(mut stream: OutputStream) -> Option<Value> {
@ -736,11 +562,14 @@ mod tests {
async fn non_existent_run() -> Result<(), ShellError> { async fn non_existent_run() -> Result<(), ShellError> {
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build(); 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."); let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
assert!(run_external_command(cmd, &mut ctx, None, false) assert!(
.await run_external_command(cmd, &mut ctx, input, &Scope::empty(), false)
.is_err()); .await
.is_err()
);
Ok(()) 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::commands::UnevaluatedCallInfo;
use crate::prelude::*; use crate::prelude::*;
use log::{log_enabled, trace}; use log::{log_enabled, trace};
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_parser::InternalCommand; use nu_protocol::hir::InternalCommand;
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, UntaggedValue, Value}; use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue, Value};
pub(crate) fn run_internal_command( pub(crate) fn run_internal_command(
command: InternalCommand, command: InternalCommand,
context: &mut Context, context: &mut Context,
input: Option<InputStream>, input: InputStream,
source: Text, scope: &Scope,
) -> Result<Option<InputStream>, ShellError> { ) -> Result<InputStream, ShellError> {
if log_enabled!(log::Level::Trace) { if log_enabled!(log::Level::Trace) {
trace!(target: "nu::run::internal", "->"); trace!(target: "nu::run::internal", "->");
trace!(target: "nu::run::internal", "{}", command.name); trace!(target: "nu::run::internal", "{}", command.name);
trace!(target: "nu::run::internal", "{}", command.args.debug(&source));
} }
let objects: InputStream = if let Some(input) = input { let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
trace_stream!(target: "nu::trace_stream::internal", "input" = input)
} else {
InputStream::empty()
};
let internal_command = context.expect_command(&command.name); let internal_command = context.expect_command(&command.name);
let result = { let result = {
context.run_command( context.run_command(
internal_command?, internal_command?,
command.name_tag.clone(), Tag::unknown_anchor(command.name_span),
command.args.clone(), command.args.clone(),
&source, scope,
objects, objects,
) )
}; };
let result = trace_out_stream!(target: "nu::trace_stream::internal", "output" = result); let mut result = trace_out_stream!(target: "nu::trace_stream::internal", "output" = result);
let mut result = result.values;
let mut context = context.clone(); let mut context = context.clone();
let stream = async_stream! { let stream = async_stream! {
@ -64,14 +59,15 @@ pub(crate) fn run_internal_command(
ctrl_c: context.ctrl_c.clone(), ctrl_c: context.ctrl_c.clone(),
shell_manager: context.shell_manager.clone(), shell_manager: context.shell_manager.clone(),
call_info: UnevaluatedCallInfo { call_info: UnevaluatedCallInfo {
args: nu_parser::hir::Call { args: nu_protocol::hir::Call {
head: command.args.head, head: command.args.head,
positional: None, positional: None,
named: None, named: None,
span: Span::unknown() span: Span::unknown(),
is_last: false,
}, },
source: source.clone(), name_tag: Tag::unknown_anchor(command.name_span),
name_tag: command.name_tag, scope: Scope::empty(),
} }
}; };
let mut result = converter.run(new_args.with_input(vec![tagged_contents]), &context.registry); 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()), 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 => { CommandAction::PreviousShell => {
context.shell_manager.prev(); context.shell_manager.prev();
} }
@ -142,7 +147,8 @@ pub(crate) fn run_internal_command(
value: UntaggedValue::Error(err), value: UntaggedValue::Error(err),
.. ..
})) => { })) => {
context.error(err); context.error(err.clone());
yield Err(err);
break; 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; mod dynamic;
pub(crate) mod expr;
pub(crate) mod external; pub(crate) mod external;
pub(crate) mod internal; pub(crate) mod internal;
pub(crate) mod pipeline;
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) use dynamic::Command as DynamicCommand; 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, RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let stream = async_stream! { 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; let mut clip_stream = inner_clip(values, name).await;
while let Some(value) = clip_stream.next().await { while let Some(value) = clip_stream.next().await {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -47,7 +47,6 @@ fn default(
RunnableContext { input, .. }: RunnableContext, RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let stream = input let stream = input
.values
.map(move |item| { .map(move |item| {
let mut result = VecDeque::new(); 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, ctrl_c: raw_args.ctrl_c,
shell_manager: raw_args.shell_manager, shell_manager: raw_args.shell_manager,
call_info: UnevaluatedCallInfo { call_info: UnevaluatedCallInfo {
args: nu_parser::hir::Call { args: nu_protocol::hir::Call {
head: raw_args.call_info.args.head, head: raw_args.call_info.args.head,
positional: None, positional: None,
named: 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, name_tag: raw_args.call_info.name_tag,
scope: raw_args.call_info.scope.clone()
}, },
}; };
let mut result = converter.run( let mut result = converter.run(

View File

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

View File

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

View File

@ -1,12 +1,45 @@
use crate::prelude::*; use crate::prelude::*;
use csv::{ErrorKind, ReaderBuilder};
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_parser::hir::syntax_shape::{ExpandContext, SignatureRegistry}; use nu_protocol::{ReturnSuccess, TaggedDictBuilder, UntaggedValue, Value};
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 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( pub fn from_delimited_data(
headerless: bool, headerless: bool,
@ -20,20 +53,19 @@ pub fn from_delimited_data(
let concat_string = input.collect_string(name_tag.clone()).await?; let concat_string = input.collect_string(name_tag.clone()).await?;
match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) { match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) {
Ok(rows) => { Ok(x) => match x {
for row in rows { Value { value: UntaggedValue::Table(list), .. } => {
match row { for l in list {
Value { value: UntaggedValue::Table(list), .. } => { yield ReturnSuccess::value(l);
for l in list {
yield ReturnSuccess::value(l);
}
}
x => yield ReturnSuccess::value(x),
} }
} }
x => yield ReturnSuccess::value(x),
}, },
Err(err) => { 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); let line_two = format!("input cannot be parsed as {}", format_name);
yield Err(ShellError::labeled_error_with_secondary( yield Err(ShellError::labeled_error_with_secondary(
line_one, line_one,
@ -49,121 +81,25 @@ pub fn from_delimited_data(
Ok(stream.to_output_stream()) Ok(stream.to_output_stream())
} }
#[derive(Debug, Clone, new)] fn pretty_csv_error(err: csv::Error) -> Option<String> {
pub struct EmptyRegistry { match err.kind() {
#[new(default)] ErrorKind::UnequalLengths {
signatures: indexmap::IndexMap<String, Signature>, pos,
} expected_len,
len,
impl EmptyRegistry {} } => {
if let Some(pos) = pos {
impl SignatureRegistry for EmptyRegistry { Some(format!(
fn has(&self, _name: &str) -> bool { "Line {}: expected {} fields, found {}",
false pos.line(),
} expected_len,
fn get(&self, _name: &str) -> Option<Signature> { len
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]
} else { } else {
match fields.get(idx) { Some(format!("Expected {} fields, found {}", expected_len, len))
Some(key) => key, }
None => &fallback_columns[idx],
}
};
row.insert_value(key, field.into_value(&tag));
} }
ErrorKind::Seek => Some("Internal error while parsing csv".to_string()),
out.push(row.into_value()) _ => None,
} }
Ok(out)
} }

View File

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

View File

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

View File

@ -35,7 +35,7 @@ pub fn headers(
RunnableContext { input, .. }: RunnableContext, RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let stream = async_stream! { let stream = async_stream! {
let rows: Vec<Value> = input.values.collect().await; let rows: Vec<Value> = input.collect().await;
if rows.len() < 1 { if rows.len() < 1 {
yield Err(ShellError::untagged_runtime_error("Couldn't find headers, was the input a properly formatted, non-empty table?")); 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( return Ok(
get_help(&command.name(), &command.usage(), command.signature()).into(), 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); let help = futures::stream::iter(help);
Ok(help.to_output_stream()) Ok(help.to_output_stream())

View File

@ -30,7 +30,7 @@ impl WholeStreamCommand for Histogram {
"the name of the column to graph by", "the name of the column to graph by",
) )
.rest( .rest(
SyntaxShape::Member, SyntaxShape::String,
"column name to give the histogram's frequency column", "column name to give the histogram's frequency column",
) )
} }
@ -53,7 +53,7 @@ pub fn histogram(
RunnableContext { input, name, .. }: RunnableContext, RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let stream = async_stream! { 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(); 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 mut leftover_string = String::new();
let stream = async_stream! { let stream = async_stream! {
loop { loop {
match input.values.next().await { match input.next().await {
Some(Value { value: UntaggedValue::Primitive(Primitive::String(st)), ..}) => { Some(Value { value: UntaggedValue::Primitive(Primitive::String(st)), ..}) => {
let mut st = leftover_string.clone() + &st; let mut st = leftover_string.clone() + &st;
leftover.clear(); leftover.clear();

View File

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

View File

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

View File

@ -6,10 +6,6 @@ use nu_protocol::{
CallInfo, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value, CallInfo, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
}; };
use nom::{
bytes::complete::{tag, take_while},
IResult,
};
use regex::Regex; use regex::Regex;
#[derive(Debug)] #[derive(Debug)]
@ -18,32 +14,44 @@ enum ParseCommand {
Column(String), Column(String),
} }
fn parse(input: &str) -> IResult<&str, Vec<ParseCommand>> { fn parse(input: &str) -> Vec<ParseCommand> {
let mut output = vec![]; let mut output = vec![];
let mut loop_input = input; //let mut loop_input = input;
let mut loop_input = input.chars();
loop { 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() { if !before.is_empty() {
output.push(ParseCommand::Text(before.to_string())); output.push(ParseCommand::Text(before.to_string()));
} }
if input != "" { // Look for column as we're now at one
// Look for column as we're now at one let mut column = String::new();
let (input, _) = tag("{")(input)?;
let (input, column) = take_while(|c| c != '}')(input)?;
let (input, _) = tag("}")(input)?;
output.push(ParseCommand::Column(column.to_string())); while let Some(c) = loop_input.next() {
loop_input = input; if c == '}' {
} else { break;
loop_input = input; }
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; break;
} }
} }
Ok((loop_input, output)) output
} }
fn column_names(commands: &[ParseCommand]) -> Vec<String> { fn column_names(commands: &[ParseCommand]) -> Vec<String> {
@ -103,16 +111,10 @@ impl PerItemCommand for Parse {
//let value_tag = value.tag(); //let value_tag = value.tag();
let pattern = call_info.args.expect_nth(0)?.as_string()?; let pattern = call_info.args.expect_nth(0)?.as_string()?;
let parse_pattern = parse(&pattern).map_err(|_| { let parse_pattern = parse(&pattern);
ShellError::labeled_error( let parse_regex = build_regex(&parse_pattern);
"Could not create parse pattern",
"could not create parse pattern",
&value.tag,
)
})?;
let parse_regex = build_regex(&parse_pattern.1);
let column_names = column_names(&parse_pattern.1); let column_names = column_names(&parse_pattern);
let regex = Regex::new(&parse_regex).map_err(|_| { let regex = Regex::new(&parse_regex).map_err(|_| {
ShellError::labeled_error("Could not parse regex", "could not parse regex", &value.tag) 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::commands::WholeStreamCommand;
use crate::context::CommandRegistry; use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use futures_util::pin_mut;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{
ColumnPath, PathMember, Primitive, ReturnSuccess, ReturnValue, Signature, SyntaxShape, ColumnPath, PathMember, Primitive, ReturnSuccess, ReturnValue, Signature, SyntaxShape,
@ -44,7 +43,9 @@ impl WholeStreamCommand for Pick {
fn pick( fn pick(
PickArgs { rest: mut fields }: PickArgs, PickArgs { rest: mut fields }: PickArgs,
RunnableContext { input, name, .. }: RunnableContext, RunnableContext {
mut input, name, ..
}: RunnableContext,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
if fields.is_empty() { if fields.is_empty() {
return Err(ShellError::labeled_error( return Err(ShellError::labeled_error(
@ -64,13 +65,10 @@ fn pick(
.collect::<Vec<ColumnPath>>(); .collect::<Vec<ColumnPath>>();
let stream = async_stream! { let stream = async_stream! {
let values = input.values;
pin_mut!(values);
let mut empty = true; let mut empty = true;
let mut bring_back: indexmap::IndexMap<String, Vec<Value>> = indexmap::IndexMap::new(); 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 { for path in &column_paths {
let path_members_span = span_for_spanned_list(path.members().iter().map(|p| p.span)); 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 derive_new::new;
use log::trace; use log::trace;
use nu_errors::ShellError; 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 serde::{self, Deserialize, Serialize};
use std::io::prelude::*; use std::io::prelude::*;
use std::io::BufReader; use std::io::BufReader;
@ -71,10 +71,13 @@ pub fn filter_plugin(
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
trace!("filter_plugin :: {}", path); trace!("filter_plugin :: {}", path);
let args = args.evaluate_once_with_scope( let scope = &args
registry, .call_info
&Scope::it_value(UntaggedValue::string("$it").into_untagged_value()), .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) let mut child = std::process::Command::new(path)
.stdin(std::process::Stdio::piped()) .stdin(std::process::Stdio::piped())
@ -95,7 +98,7 @@ pub fn filter_plugin(
trace!("filtering :: {:?}", call_info); trace!("filtering :: {:?}", call_info);
let stream = bos let stream = bos
.chain(args.input.values) .chain(args.input)
.chain(eos) .chain(eos)
.map(move |v| match v { .map(move |v| match v {
Value { Value {
@ -340,7 +343,7 @@ pub fn sink_plugin(
let call_info = args.call_info.clone(); let call_info = args.call_info.clone();
let stream = async_stream! { 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 = JsonRpc::new("sink", (call_info.clone(), input));
let request_raw = serde_json::to_string(&request); let request_raw = serde_json::to_string(&request);

View File

@ -43,5 +43,5 @@ fn prepend(
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let prepend = futures::stream::iter(vec![row]); 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 from = *from as usize;
let to = *to as usize; let to = *to as usize;
Ok(OutputStream::from_input( Ok(input.skip(from).take(to - from + 1).to_output_stream())
input.values.skip(from).take(to - from + 1),
))
} }

View File

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

View File

@ -18,7 +18,7 @@ impl WholeStreamCommand for Reject {
} }
fn signature(&self) -> Signature { 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 { fn usage(&self) -> &str {
@ -48,9 +48,7 @@ fn reject(
let fields: Vec<_> = fields.iter().map(|f| f.item.clone()).collect(); let fields: Vec<_> = fields.iter().map(|f| f.item.clone()).collect();
let stream = input let stream = input.map(move |item| reject_fields(&item, &fields, &item.tag));
.values
.map(move |item| reject_fields(&item, &fields, &item.tag));
Ok(stream.from_input_stream()) Ok(stream.from_input_stream())
} }

View File

@ -26,7 +26,7 @@ impl WholeStreamCommand for Rename {
"the name of the column to rename for", "the name of the column to rename for",
) )
.rest( .rest(
SyntaxShape::Member, SyntaxShape::String,
"Additional column name(s) to rename for", "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 new_column_names = new_column_names.into_iter().flatten().collect::<Vec<_>>();
let stream = input let stream = input
.values
.map(move |item| { .map(move |item| {
let mut result = VecDeque::new(); 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 args = args.evaluate_once(registry)?;
let (input, _args) = args.parts(); let (input, _args) = args.parts();
let input = input.values.collect::<Vec<_>>(); let input = input.collect::<Vec<_>>();
let output = input.map(move |mut vec| { let output = input.map(move |mut vec| {
vec.reverse(); vec.reverse();

View File

@ -12,6 +12,7 @@ pub struct Remove;
pub struct RemoveArgs { pub struct RemoveArgs {
pub rest: Vec<Tagged<PathBuf>>, pub rest: Vec<Tagged<PathBuf>>,
pub recursive: Tagged<bool>, pub recursive: Tagged<bool>,
#[allow(unused)]
pub trash: Tagged<bool>, 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::commands::{UnevaluatedCallInfo, WholeStreamCommand};
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; 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 nu_source::Tagged;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -168,7 +168,7 @@ fn save(
shell_manager, shell_manager,
host, host,
ctrl_c, ctrl_c,
commands: registry, registry,
.. ..
}: RunnableContext, }: RunnableContext,
raw_args: RawCommandArgs, raw_args: RawCommandArgs,
@ -177,7 +177,7 @@ fn save(
let name_tag = name.clone(); let name_tag = name.clone();
let stream = async_stream! { let stream = async_stream! {
let input: Vec<Value> = input.values.collect().await; let input: Vec<Value> = input.collect().await;
if path.is_none() { if path.is_none() {
// If there is no filename, check the metadata for the anchor filename // If there is no filename, check the metadata for the anchor filename
if input.len() > 0 { if input.len() > 0 {
@ -228,14 +228,15 @@ fn save(
ctrl_c, ctrl_c,
shell_manager, shell_manager,
call_info: UnevaluatedCallInfo { call_info: UnevaluatedCallInfo {
args: nu_parser::hir::Call { args: nu_protocol::hir::Call {
head: raw_args.call_info.args.head, head: raw_args.call_info.args.head,
positional: None, positional: None,
named: 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, name_tag: raw_args.call_info.name_tag,
scope: Scope::empty(), // FIXME?
} }
}; };
let mut result = converter.run(new_args.with_input(input), &registry); 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); let mut dict = TaggedDictBuilder::new(&tag);
if index == (*args.shell_manager.current_shell).load(Ordering::SeqCst) { if index == (*args.shell_manager.current_shell).load(Ordering::SeqCst) {
dict.insert_untagged(" ", "X".to_string()); dict.insert_untagged("active", "X".to_string());
} else { } else {
dict.insert_untagged(" ", " ".to_string()); dict.insert_untagged("active", " ".to_string());
} }
dict.insert_untagged("name", shell.name()); dict.insert_untagged("name", shell.name());
dict.insert_untagged("path", shell.path()); dict.insert_untagged("path", shell.path());

View File

@ -48,7 +48,7 @@ fn shuffle(
RunnableContext { input, .. }: RunnableContext, RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let stream = async_stream! { 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 out = if let Some(n) = limit {
let (shuffled, _) = values.partial_shuffle(&mut thread_rng(), *n as usize); 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; let name_span = tag.span;
Ok(input Ok(input
.values
.map(move |v| { .map(move |v| {
if let Ok(s) = v.as_string() { if let Ok(s) = v.as_string() {
ReturnSuccess::value(count(&s, &v.tag)) ReturnSuccess::value(count(&s, &v.tag))

View File

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

View File

@ -1,16 +1,12 @@
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*; use crate::prelude::*;
use log::trace; use log::trace;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Evaluate, Scope, Signature, SyntaxShape}; use nu_protocol::{hir::ClassifiedCommand, Scope, Signature, SyntaxShape, UntaggedValue, Value};
pub struct SkipWhile; pub struct SkipWhile;
#[derive(Deserialize)]
pub struct SkipWhileArgs {
condition: Evaluate,
}
impl WholeStreamCommand for SkipWhile { impl WholeStreamCommand for SkipWhile {
fn name(&self) -> &str { fn name(&self) -> &str {
"skip-while" "skip-while"
@ -20,7 +16,7 @@ impl WholeStreamCommand for SkipWhile {
Signature::build("skip-while") Signature::build("skip-while")
.required( .required(
"condition", "condition",
SyntaxShape::Block, SyntaxShape::Math,
"the condition that must be met to continue skipping", "the condition that must be met to continue skipping",
) )
.filter() .filter()
@ -35,26 +31,66 @@ impl WholeStreamCommand for SkipWhile {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, skip_while)?.run() let registry = registry.clone();
} let call_info = args.evaluate_once(&registry)?;
}
pub fn skip_while( let block = call_info.args.expect_nth(0)?.clone();
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 return_value = match result { let condition = match block {
Ok(ref v) if v.is_true() => true, Value {
_ => false, 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, RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let stream = async_stream! { 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() { if values.len() > 1 || values.is_empty() {
yield Err(ShellError::labeled_error( yield Err(ShellError::labeled_error(

View File

@ -30,7 +30,7 @@ impl WholeStreamCommand for SplitColumn {
"the character that denotes what separates columns", "the character that denotes what separates columns",
) )
.switch("collapse-empty", "remove empty columns", Some('c')) .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 { fn usage(&self) -> &str {
@ -57,7 +57,6 @@ fn split_column(
let name_span = name.span; let name_span = name.span;
Ok(input Ok(input
.values
.map(move |v| { .map(move |v| {
if let Ok(s) = v.as_string() { if let Ok(s) = v.as_string() {
let splitter = separator.replace("\\n", "\n"); let splitter = separator.replace("\\n", "\n");

View File

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

View File

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

View File

@ -69,7 +69,7 @@ fn t_sort_by(
RunnableContext { input, name, .. }: RunnableContext, RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
Ok(OutputStream::new(async_stream! { 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 { let column_grouped_by_name = if let Some(grouped_by) = group_by {
Some(grouped_by.item().clone()) Some(grouped_by.item().clone())

View File

@ -30,7 +30,6 @@ impl WholeStreamCommand for Tags {
fn tags(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> { fn tags(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
Ok(args Ok(args
.input .input
.values
.map(move |v| { .map(move |v| {
let mut tags = TaggedDictBuilder::new(v.tag()); 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 name_span = name_tag.span;
let stream = async_stream! { 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 to_process_input = if input.len() > 1 {
let tag = input[0].tag.clone(); let tag = input[0].tag.clone();

View File

@ -175,7 +175,7 @@ pub fn to_delimited_data(
let name_span = name_tag.span; let name_span = name_tag.span;
let stream = async_stream! { 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 to_process_input = if input.len() > 1 {
let tag = input[0].tag.clone(); 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_tag = args.name_tag();
//let name_span = name_tag.span; //let name_span = name_tag.span;
let stream = async_stream! { 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 headers = nu_protocol::merge_descriptors(&input);
let mut output_string = "<html><body>".to_string(); 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_tag = args.name_tag();
let name_span = name_tag.span; let name_span = name_tag.span;
let stream = async_stream! { 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 to_process_input = if input.len() > 1 {
let tag = input[0].tag.clone(); 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_tag = args.name_tag();
//let name_span = name_tag.span; //let name_span = name_tag.span;
let stream = async_stream! { 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 headers = nu_protocol::merge_descriptors(&input);
let mut output_string = String::new(); 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 args = args.evaluate_once(registry)?;
let name_tag = args.name_tag(); let name_tag = args.name_tag();
let stream = async_stream! { 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) { match sqlite_input_stream_to_bytes(input) {
Ok(out) => yield ReturnSuccess::value(out), 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_tag = args.name_tag();
let name_span = name_tag.span; let name_span = name_tag.span;
let stream = async_stream! { 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 to_process_input = if input.len() > 1 {
let tag = input[0].tag.clone(); 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 input = args.input;
let stream = async_stream! { let stream = async_stream! {
let input: Vec<Value> = input.values.collect().await; let input: Vec<Value> = input.collect().await;
for value in input { for value in input {
match value { match value {

View File

@ -130,7 +130,7 @@ fn to_yaml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
let name_span = name_tag.span; let name_span = name_tag.span;
let stream = async_stream! { 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 to_process_input = if input.len() > 1 {
let tag = input[0].tag.clone(); 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> { fn trim(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let input = args.input; Ok(args
.input
Ok(input
.values
.map(move |v| { .map(move |v| {
let string = String::extract(&v)?; let string = String::extract(&v)?;
ReturnSuccess::value(UntaggedValue::string(string.trim()).into_value(v.tag())) ReturnSuccess::value(UntaggedValue::string(string.trim()).into_value(v.tag()))

View File

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

View File

@ -1,8 +1,6 @@
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use futures::StreamExt;
use futures_util::pin_mut;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, ReturnValue, Signature, UntaggedValue}; use nu_protocol::{ReturnSuccess, ReturnValue, Signature, UntaggedValue};
@ -34,14 +32,11 @@ impl WholeStreamCommand for What {
} }
pub fn what( pub fn what(
WhatArgs {}: WhatArgs, _: WhatArgs,
RunnableContext { input, .. }: RunnableContext, RunnableContext { mut input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let stream = async_stream! { let stream = async_stream! {
let values = input.values; while let Some(row) = input.next().await {
pin_mut!(values);
while let Some(row) = values.next().await {
let name = value::format_type(&row, 100); let name = value::format_type(&row, 100);
yield ReturnSuccess::value(UntaggedValue::string(name).into_value(Tag::unknown_anchor(row.tag.span))); 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::commands::PerItemCommand;
use crate::context::CommandRegistry; use crate::context::CommandRegistry;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; 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; pub struct Where;
@ -14,7 +18,7 @@ impl PerItemCommand for Where {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("where").required( Signature::build("where").required(
"condition", "condition",
SyntaxShape::Block, SyntaxShape::Math,
"the condition that must match", "the condition that must match",
) )
} }
@ -26,37 +30,67 @@ impl PerItemCommand for Where {
fn run( fn run(
&self, &self,
call_info: &CallInfo, call_info: &CallInfo,
_registry: &CommandRegistry, registry: &CommandRegistry,
_raw_args: &RawCommandArgs, _raw_args: &RawCommandArgs,
input: Value, input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let condition = call_info.args.expect_nth(0)?; let block = call_info.args.expect_nth(0)?.clone();
let stream = match condition {
let condition = match block {
Value { Value {
value: UntaggedValue::Block(block), value: UntaggedValue::Block(block),
.. tag,
} => { } => {
let result = block.invoke(&Scope::new(input.clone())); if block.block.len() != 1 {
match result { return Err(ShellError::labeled_error(
Ok(v) => { "Expected a condition",
if v.is_true() { "expected a condition",
VecDeque::from(vec![Ok(ReturnSuccess::Value(input))]) tag,
} else { ));
VecDeque::new() }
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, .. } => { Value { tag, .. } => {
return Err(ShellError::labeled_error( return Err(ShellError::labeled_error(
"Expected a condition", "Expected a condition",
"where needs a condition", "expected a condition",
tag, 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()) Ok(stream.into())
} }
} }

View File

@ -78,7 +78,7 @@ struct WhichArgs {
fn which( fn which(
WhichArgs { application, all }: WhichArgs, WhichArgs { application, all }: WhichArgs,
RunnableContext { commands, .. }: RunnableContext, RunnableContext { registry, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let external = application.starts_with('^'); let external = application.starts_with('^');
let item = if external { let item = if external {
@ -89,7 +89,7 @@ fn which(
let stream = async_stream! { let stream = async_stream! {
if !external { if !external {
let builtin = commands.has(&item); let builtin = registry.has(&item);
if builtin { if builtin {
yield ReturnSuccess::value(entry_builtin!(item, application.tag.clone())); 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::env::host::Host;
use crate::shell::shell_manager::ShellManager; use crate::shell::shell_manager::ShellManager;
use crate::stream::{InputStream, OutputStream}; use crate::stream::{InputStream, OutputStream};
use indexmap::IndexMap; use indexmap::IndexMap;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_parser::{hir, hir::syntax_shape::ExpandContext, hir::syntax_shape::SignatureRegistry}; use nu_parser::SignatureRegistry;
use nu_protocol::Signature; use nu_protocol::{hir, Scope, Signature};
use nu_source::{Tag, Text}; use nu_source::{Tag, Text};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::error::Error; use std::error::Error;
@ -40,12 +42,6 @@ impl CommandRegistry {
} }
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>> { pub(crate) fn get_command(&self, name: &str) -> Option<Arc<Command>> {
let registry = self.registry.lock(); let registry = self.registry.lock();
@ -92,15 +88,28 @@ impl Context {
&self.registry &self.registry
} }
pub(crate) fn expand_context<'context>( pub(crate) fn from_raw(raw_args: &RawCommandArgs, registry: &CommandRegistry) -> Context {
&'context self, #[cfg(windows)]
source: &'context Text, {
) -> ExpandContext { Context {
ExpandContext::new( registry: registry.clone(),
Box::new(self.registry.clone()), host: raw_args.host.clone(),
source, current_errors: Arc::new(Mutex::new(vec![])),
self.shell_manager.homedir(), 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>> { pub(crate) fn basic() -> Result<Context, Box<dyn Error>> {
@ -138,6 +147,14 @@ impl Context {
self.with_errors(|errors| errors.push(error)) 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 { pub(crate) fn maybe_print_errors(&mut self, source: Text) -> bool {
let errors = self.current_errors.clone(); let errors = self.current_errors.clone();
let mut errors = errors.lock(); let mut errors = errors.lock();
@ -187,18 +204,18 @@ impl Context {
command: Arc<Command>, command: Arc<Command>,
name_tag: Tag, name_tag: Tag,
args: hir::Call, args: hir::Call,
source: &Text, scope: &Scope,
input: InputStream, input: InputStream,
) -> OutputStream { ) -> 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()) 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 { UnevaluatedCallInfo {
args, args,
source: source.clone(),
name_tag, name_tag,
scope: scope.clone(),
} }
} }
@ -206,14 +223,14 @@ impl Context {
&self, &self,
args: hir::Call, args: hir::Call,
input: InputStream, input: InputStream,
source: &Text,
name_tag: Tag, name_tag: Tag,
scope: &Scope,
) -> CommandArgs { ) -> CommandArgs {
CommandArgs { CommandArgs {
host: self.host.clone(), host: self.host.clone(),
ctrl_c: self.ctrl_c.clone(), ctrl_c: self.ctrl_c.clone(),
shell_manager: self.shell_manager.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, input,
} }
} }

View File

@ -1,18 +1,13 @@
pub(crate) mod shape; pub(crate) mod shape;
use crate::context::CommandRegistry;
use crate::evaluate::evaluate_baseline_expr;
use bigdecimal::BigDecimal; use bigdecimal::BigDecimal;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use derive_new::new; use derive_new::new;
use log::trace;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_parser::{hir, CompareOperator};
use nu_protocol::{ use nu_protocol::{
Evaluate, EvaluateTrait, Primitive, Scope, ShellTypeName, SpannedTypeName, TaggedDictBuilder, hir, Primitive, ShellTypeName, SpannedTypeName, TaggedDictBuilder, UntaggedValue, Value,
UntaggedValue, Value,
}; };
use nu_source::{Tag, Text}; use nu_source::Tag;
use nu_value_ext::ValueExt; use nu_value_ext::ValueExt;
use num_bigint::BigInt; use num_bigint::BigInt;
use num_traits::Zero; use num_traits::Zero;
@ -23,49 +18,18 @@ use std::time::SystemTime;
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, new, Serialize)] #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, new, Serialize)]
pub struct Operation { pub struct Operation {
pub(crate) left: Value, pub(crate) left: Value,
pub(crate) operator: CompareOperator, pub(crate) operator: hir::Operator,
pub(crate) right: Value, pub(crate) right: Value,
} }
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Serialize, Deserialize, new)] #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Serialize, Deserialize, new)]
pub struct Block { pub struct Block {
pub(crate) expressions: Vec<hir::SpannedExpression>, pub(crate) commands: hir::Commands,
pub(crate) source: Text,
pub(crate) tag: Tag, pub(crate) tag: Tag,
} }
interfaces!(Block: dyn ObjectHash); 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)] #[derive(Serialize, Deserialize)]
pub enum Switch { pub enum Switch {
Present, Present,
@ -123,7 +87,8 @@ pub(crate) enum CompareValues {
Decimals(BigDecimal, BigDecimal), Decimals(BigDecimal, BigDecimal),
String(String, String), String(String, String),
Date(DateTime<Utc>, DateTime<Utc>), Date(DateTime<Utc>, DateTime<Utc>),
DateDuration(DateTime<Utc>, u64), DateDuration(DateTime<Utc>, i64),
Booleans(bool, bool),
} }
impl CompareValues { impl CompareValues {
@ -137,9 +102,14 @@ impl CompareValues {
use std::time::Duration; use std::time::Duration;
// Create the datetime we're comparing against, as duration is an offset from now // 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) right.cmp(left)
} }
CompareValues::Booleans(left, right) => left.cmp(right),
} }
} }
} }
@ -176,11 +146,15 @@ fn coerce_compare_primitive(
(Decimal(left), Bytes(right)) => { (Decimal(left), Bytes(right)) => {
CompareValues::Decimals(left.clone(), BigDecimal::from(*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), Int(right)) => CompareValues::Ints(BigInt::from(*left), right.clone()),
(Bytes(left), Decimal(right)) => { (Bytes(left), Decimal(right)) => {
CompareValues::Decimals(BigDecimal::from(*left), right.clone()) CompareValues::Decimals(BigDecimal::from(*left), right.clone())
} }
(Bytes(left), Nothing) => CompareValues::Ints(BigInt::from(*left), BigInt::from(0)), (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)), (Nothing, Bytes(right)) => CompareValues::Ints(BigInt::from(0), BigInt::from(*right)),
(Int(left), Nothing) => CompareValues::Ints(left.clone(), BigInt::from(0)), (Int(left), Nothing) => CompareValues::Ints(left.clone(), BigInt::from(0)),
(Nothing, Int(right)) => CompareValues::Ints(BigInt::from(0), right.clone()), (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()), (Line(left), Line(right)) => CompareValues::String(left.clone(), right.clone()),
(Date(left), Date(right)) => CompareValues::Date(*left, *right), (Date(left), Date(right)) => CompareValues::Date(*left, *right),
(Date(left), Duration(right)) => CompareValues::DateDuration(*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())), _ => return Err((left.type_name(), right.type_name())),
}) })
} }

View File

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

View File

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

View File

@ -3,7 +3,8 @@ use crate::data::base::shape::{Column, InlineShape};
use crate::data::primitive::style_primitive; use crate::data::primitive::style_primitive;
use chrono::DateTime; use chrono::DateTime;
use nu_errors::ShellError; 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_protocol::{Primitive, Type, UntaggedValue};
use nu_source::{DebugDocBuilder, PrettyDebug, Tagged}; 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))) 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( pub fn compare_values(
operator: CompareOperator, operator: Operator,
left: &UntaggedValue, left: &UntaggedValue,
right: &UntaggedValue, right: &UntaggedValue,
) -> Result<bool, (&'static str, &'static str)> { ) -> Result<bool, (&'static str, &'static str)> {
@ -34,15 +118,16 @@ pub fn compare_values(
use std::cmp::Ordering; use std::cmp::Ordering;
let result = match (operator, ordering) { let result = match (operator, ordering) {
(CompareOperator::Equal, Ordering::Equal) => true, (Operator::Equal, Ordering::Equal) => true,
(CompareOperator::NotEqual, Ordering::Less) (Operator::NotEqual, Ordering::Less) | (Operator::NotEqual, Ordering::Greater) => {
| (CompareOperator::NotEqual, Ordering::Greater) => true, true
(CompareOperator::LessThan, Ordering::Less) => true, }
(CompareOperator::GreaterThan, Ordering::Greater) => true, (Operator::LessThan, Ordering::Less) => true,
(CompareOperator::GreaterThanOrEqual, Ordering::Greater) (Operator::GreaterThan, Ordering::Greater) => true,
| (CompareOperator::GreaterThanOrEqual, Ordering::Equal) => true, (Operator::GreaterThanOrEqual, Ordering::Greater)
(CompareOperator::LessThanOrEqual, Ordering::Less) | (Operator::GreaterThanOrEqual, Ordering::Equal) => true,
| (CompareOperator::LessThanOrEqual, Ordering::Equal) => true, (Operator::LessThanOrEqual, Ordering::Less)
| (Operator::LessThanOrEqual, Ordering::Equal) => true,
_ => false, _ => false,
}; };

View File

@ -1,7 +1,8 @@
use log::trace; use log::trace;
use nu_errors::{CoerceInto, ShellError}; use nu_errors::{CoerceInto, ShellError};
use nu_protocol::{ 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_source::{HasSpan, Spanned, SpannedItem, Tagged, TaggedItem};
use nu_value_ext::ValueExt; 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" { if name == "ColumnPath" {

View File

@ -275,7 +275,7 @@ mod tests {
assert_eq!( assert_eq!(
actual.path(), actual.path(),
Some( Some(
UntaggedValue::table(&vec![ UntaggedValue::table(&[
UntaggedValue::string("/Users/andresrobalino/.volta/bin") UntaggedValue::string("/Users/andresrobalino/.volta/bin")
.into_untagged_value(), .into_untagged_value(),
UntaggedValue::string("/users/mosqueteros/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 crate::evaluate::evaluate_baseline_expr;
use indexmap::IndexMap; use indexmap::IndexMap;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_parser::hir; use nu_protocol::{hir, EvaluatedArgs, Scope, UntaggedValue, Value};
use nu_protocol::{EvaluatedArgs, Scope, UntaggedValue, Value};
use nu_source::Text;
pub(crate) fn evaluate_args( pub(crate) fn evaluate_args(
call: &hir::Call, call: &hir::Call,
registry: &CommandRegistry, registry: &CommandRegistry,
scope: &Scope, scope: &Scope,
source: &Text,
) -> Result<EvaluatedArgs, ShellError> { ) -> Result<EvaluatedArgs, ShellError> {
let positional: Result<Option<Vec<_>>, _> = call let positional: Result<Option<Vec<_>>, _> = call
.positional .positional
.as_ref() .as_ref()
.map(|p| { .map(|p| {
p.iter() p.iter()
.map(|e| evaluate_baseline_expr(e, registry, scope, source)) .map(|e| evaluate_baseline_expr(e, registry, scope))
.collect() .collect()
}) })
.transpose(); .transpose();
@ -36,11 +33,9 @@ pub(crate) fn evaluate_args(
hir::NamedValue::PresentSwitch(tag) => { hir::NamedValue::PresentSwitch(tag) => {
results.insert(name.clone(), UntaggedValue::boolean(true).into_value(tag)); results.insert(name.clone(), UntaggedValue::boolean(true).into_value(tag));
} }
hir::NamedValue::Value(expr) => { hir::NamedValue::Value(_, expr) => {
results.insert( results
name.clone(), .insert(name.clone(), evaluate_baseline_expr(expr, registry, scope)?);
evaluate_baseline_expr(expr, registry, scope, source)?,
);
} }
_ => {} _ => {}

View File

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

View File

@ -1,30 +1,43 @@
use crate::data::value; use crate::data::value;
use nu_parser::CompareOperator; use nu_protocol::hir::Operator;
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value}; use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
use std::ops::Not; use std::ops::Not;
pub fn apply_operator( pub fn apply_operator(
op: CompareOperator, op: Operator,
left: &Value, left: &Value,
right: &Value, right: &Value,
) -> Result<UntaggedValue, (&'static str, &'static str)> { ) -> Result<UntaggedValue, (&'static str, &'static str)> {
match op { match op {
CompareOperator::Equal Operator::Equal
| CompareOperator::NotEqual | Operator::NotEqual
| CompareOperator::LessThan | Operator::LessThan
| CompareOperator::GreaterThan | Operator::GreaterThan
| CompareOperator::LessThanOrEqual | Operator::LessThanOrEqual
| CompareOperator::GreaterThanOrEqual => { | Operator::GreaterThanOrEqual => {
value::compare_values(op, left, right).map(UntaggedValue::boolean) value::compare_values(op, left, right).map(UntaggedValue::boolean)
} }
CompareOperator::Contains => contains(left, right).map(UntaggedValue::boolean), Operator::Contains => string_contains(left, right).map(UntaggedValue::boolean),
CompareOperator::NotContains => contains(left, right) Operator::NotContains => string_contains(left, right)
.map(Not::not) .map(Not::not)
.map(UntaggedValue::boolean), .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, left: &UntaggedValue,
right: &UntaggedValue, right: &UntaggedValue,
) -> Result<bool, (&'static str, &'static str)> { ) -> Result<bool, (&'static str, &'static str)> {
@ -48,3 +61,14 @@ fn contains(
_ => Err((left.type_name(), right.type_name())), _ => 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; pub(crate) mod table;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
pub(crate) use entries::EntriesView;
pub(crate) use table::TableView; pub(crate) use table::TableView;
pub(crate) trait RenderView { 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::format::{Alignment, FormatBuilder, LinePosition, LineSeparator};
use prettytable::{color, Attr, Cell, Row, Table}; use prettytable::{color, Attr, Cell, Row, Table};
use term::color::Color;
type Entries = Vec<Vec<(String, &'static str)>>; 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 { 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![]; let mut entries = vec![];
if headers.is_empty() { if headers.is_empty() {
@ -118,12 +129,16 @@ fn values_to_entries(values: &[Value], headers: &mut Vec<String>, starting_idx:
.collect(); .collect();
// Indices are green, bold, right-aligned: // 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); entries.push(row);
} }
headers.insert(0, "#".to_owned()); if !disable_indexes {
headers.insert(0, "#".to_owned());
}
entries 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() { match s.as_str() {
"g" | "green" => Some(color::GREEN), "g" | "green" => Some(color::GREEN),
"r" | "red" => Some(color::RED), "r" | "red" => Some(color::RED),

View File

@ -7,6 +7,12 @@ extern crate indexmap;
#[macro_use] #[macro_use]
mod prelude; mod prelude;
#[cfg(test)]
extern crate quickcheck;
#[cfg(test)]
#[macro_use(quickcheck)]
extern crate quickcheck_macros;
mod cli; mod cli;
mod commands; mod commands;
mod context; mod context;
@ -17,17 +23,19 @@ mod evaluate;
mod format; mod format;
mod futures; mod futures;
mod git; mod git;
mod path;
mod shell; mod shell;
mod stream; mod stream;
mod utils; 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::dict::TaggedListBuilder;
pub use crate::data::primitive; pub use crate::data::primitive;
pub use crate::data::value; pub use crate::data::value;
pub use crate::env::environment_syncer::EnvironmentSyncer; pub use crate::env::environment_syncer::EnvironmentSyncer;
pub use crate::env::host::BasicHost; pub use crate::env::host::BasicHost;
pub use nu_parser::TokenTreeBuilder;
pub use nu_value_ext::ValueExt; pub use nu_value_ext::ValueExt;
pub use num_traits::cast::ToPrimitive; 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) { if log::log_enabled!(target: $target, log::Level::Trace) {
use futures::stream::StreamExt; use futures::stream::StreamExt;
let objects = $expr.values.inspect(move |o| { let objects = $expr.inspect(move |o| {
trace!( trace!(
target: $target, target: $target,
"{} = {}", "{} = {}",
@ -49,7 +49,7 @@ macro_rules! trace_out_stream {
if log::log_enabled!(target: $target, log::Level::Trace) { if log::log_enabled!(target: $target, log::Level::Trace) {
use futures::stream::StreamExt; use futures::stream::StreamExt;
let objects = $expr.values.inspect(move |o| { let objects = $expr.inspect(move |o| {
trace!( trace!(
target: $target, target: $target,
"{} = {}", "{} = {}",
@ -91,10 +91,10 @@ pub(crate) use async_stream::stream as async_stream;
pub(crate) use bigdecimal::BigDecimal; pub(crate) use bigdecimal::BigDecimal;
pub(crate) use futures::stream::BoxStream; pub(crate) use futures::stream::BoxStream;
pub(crate) use futures::{FutureExt, Stream, StreamExt}; 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::{ pub(crate) use nu_source::{
b, AnchorLocation, DebugDocBuilder, HasSpan, PrettyDebug, PrettyDebugWithSource, Span, b, AnchorLocation, DebugDocBuilder, PrettyDebug, PrettyDebugWithSource, Span, SpannedItem, Tag,
SpannedItem, Tag, TaggedItem, Text, TaggedItem, Text,
}; };
pub(crate) use nu_value_ext::ValueExt; pub(crate) use nu_value_ext::ValueExt;
pub(crate) use num_bigint::BigInt; pub(crate) use num_bigint::BigInt;
@ -132,17 +132,13 @@ where
U: Into<Result<nu_protocol::Value, nu_errors::ShellError>>, U: Into<Result<nu_protocol::Value, nu_errors::ShellError>>,
{ {
fn to_input_stream(self) -> InputStream { fn to_input_stream(self) -> InputStream {
InputStream { InputStream::from_stream(self.map(|item| match item.into() {
values: self Ok(result) => result,
.map(|item| match item.into() { Err(err) => match HasFallibleSpan::maybe_span(&err) {
Ok(result) => result, Some(span) => nu_protocol::UntaggedValue::Error(err).into_value(span),
Err(err) => match HasFallibleSpan::maybe_span(&err) { None => nu_protocol::UntaggedValue::Error(err).into_untagged_value(),
Some(span) => nu_protocol::UntaggedValue::Error(err).into_value(span), },
None => nu_protocol::UntaggedValue::Error(err).into_untagged_value(), }))
},
})
.boxed(),
}
} }
} }

View File

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

View File

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

View File

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

View File

@ -1,9 +1,8 @@
use crate::context::Context; use crate::context::Context;
use ansi_term::{Color, Style}; use ansi_term::{Color, Style};
use log::log_enabled; use nu_parser::SignatureRegistry;
use nu_parser::{FlatShape, PipelineShape, ShapeResult, Token, TokensIterator}; use nu_protocol::hir::FlatShape;
use nu_protocol::{errln, outln}; use nu_source::{Span, Spanned, Tag, Tagged};
use nu_source::{nom_input, HasSpan, Tag, Tagged, Text};
use rustyline::completion::Completer; use rustyline::completion::Completer;
use rustyline::error::ReadlineError; use rustyline::error::ReadlineError;
use rustyline::highlight::Highlighter; use rustyline::highlight::Highlighter;
@ -38,10 +37,7 @@ impl Completer for Helper {
impl Hinter for Helper { impl Hinter for Helper {
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> { 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
.shell_manager
.hint(line, pos, ctx, self.context.expand_context(&text))
} }
} }
@ -65,49 +61,19 @@ impl Highlighter for Helper {
} }
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { 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), Err(_) => Cow::Borrowed(line),
Ok((_rest, v)) => { Ok(lb) => {
let pipeline = match v.as_pipeline() { let classified =
Err(_) => return Cow::Borrowed(line), nu_parser::classify_block(&lb, &self.context.registry().clone_box());
Ok(v) => v,
};
let text = Text::from(line); let shapes = nu_parser::shapes(&classified.block);
let expand_context = self.context.expand_context(&text); let mut painter = Painter::new(line);
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();
for shape in shapes { for shape in shapes {
painter.paint_shape(&shape, line); painter.paint_shape(&shape);
} }
Cow::Owned(painter.into_string()) Cow::Owned(painter.into_string())
@ -133,73 +99,94 @@ fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
} }
struct Painter { struct Painter {
current: Style, original: Vec<u8>,
buffer: String, styles: Vec<Style>,
} }
impl Painter { impl Painter {
fn new() -> Painter { fn new(original: &str) -> Painter {
let bytes: Vec<u8> = original.bytes().collect();
let bytes_count = bytes.len();
Painter { Painter {
current: Style::default(), original: bytes,
buffer: String::new(), 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 { 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) { if self.original.is_empty() {
let style = match &shape { String::new()
ShapeResult::Success(shape) => match shape.item { } else {
FlatShape::OpenDelimiter(_) => Color::White.normal(), let mut builder = String::new();
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);
self.paint(Color::Purple.bold(), number); let mut current_style = self.styles[0];
self.paint(Color::Cyan.bold(), unit);
return; 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) { builder
let infix = self.current.infix(style); }
self.current = style;
self.buffer
.push_str(&format!("{}{}", infix, style.paint(body)));
} }
} }

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