Compare commits

...

40 Commits

Author SHA1 Message Date
b674cee9d2 Remove the recursely-dep'd tests (#2727) 2020-11-04 09:26:07 +13:00
cb8491cfee Bump to 0.22 (#2726) 2020-11-04 07:31:41 +13:00
8196b031f8 Delete comments showing output of older nu version (#2717) 2020-11-03 19:29:13 +13:00
50dd56d3c4 bugfix for when pathext ends in ';' (#2723) 2020-11-02 13:00:47 -06:00
0f7e1d4d01 Support broad range of escape sequences (#2719)
* WIP

* changed to matches

* fixed a bug with osc

* changed back to if let because: clippy

* fixed example test
2020-10-30 15:06:15 -05:00
ec77c572b9 handle precision a tiny bit better than just hard coding to 4 decimal places. (#2712) 2020-10-31 06:40:28 +13:00
f97561c416 Inode added to ls -l (#2711) 2020-10-31 06:39:01 +13:00
5faa82e323 Update required rust version (#2718)
Co-authored-by: Joshua Shanks <jjshanks@stripe.com>
2020-10-30 12:17:49 -05:00
4e17292a12 Seq for nushell (#2704)
* seq command - WIP

* why, oh why

* works with parameters

* widths should've been optional

* dbg messages

* working. rest had to be first.

* updated so that it outputs a table instead of just strings

* made to work with floats, allowed separator be more than 1 char

* clippy

* fixed tests

* changed terminator help desc

* commit to get ci moving again
2020-10-29 15:51:48 -05:00
666fbbb0d1 Precision added to round cmd (#2710) 2020-10-29 16:14:08 +13:00
c6fe58467b Change alias shape inference to proposal of RFC#4 (#2685)
* Change alias shape inference to proposal of RFC#4

* Remove commented code

* Fix typo

* Change comment to be more informative

* Make match statement to lookup in table

* Remove resolved question

https://github.com/nushell/nushell/pull/2685#discussion_r509832054

* Pick ...or_insert_dependency functions into pieces

Previously there was get_shape_of_expr_or_insert dependency, now there is
get_shape_of_expr and get_shape_of_expr_or_insert_dependency

2 new functions have been added: get_result_shape_of_math_expr and
get_result_shape_of_math_expr_or_insert_dependency

* Remove flattening of deep binary expressions

Previously deep binary expressions have been flattened through the insertion of
fake vars. This logic was quite complicated. Now if a variable depends on the
result shape of a binary expression and the result shape can't be computed,
the variable simply depends on the whole binary.

* Change Expression::Variable(Variable::It(...)) to Expression::Variable(...)

* Simplify get_result_shapes_in_math_expr

* Simplify infer_shapes_in_binary_expr

* Clarify comment

* Clarify comment

* Fix clippy lint

* Move check for real var into checked_insert

* Remove comment

* Rename var
2020-10-29 06:49:38 +13:00
46d1938f5c add unicode to char command to print any unicode character (#2709)
* parsing unicode literal strings into chars

* refactored code to use -u option

* nudge ci
2020-10-28 09:08:09 -05:00
8229af7591 Improve parameter inference for blocks (#2708) 2020-10-28 07:47:11 +13:00
ee76523507 Add in parameter inference for blocks (#2706) 2020-10-27 20:37:35 +13:00
c283db373b Always escape non-literal arguments when running external command (#2697) 2020-10-27 16:33:40 +13:00
1b0ed30516 Added a bunch of extensions as helpers (#2698)
* Added a bunch of extensions as helpers

* change to restart ci
2020-10-26 09:25:06 -05:00
a6fdee4a51 bump to 0.21.1 (#2702)
* bump to 0.21.1

* bump trash version
2020-10-26 21:10:06 +13:00
6951fb440c Remove it expansion (#2701)
* Remove it-expansion, take 2

* Cleanup

* silly update to test CI
2020-10-26 19:55:52 +13:00
502c9ea706 Radix added to str decimal conversion (#2696) 2020-10-26 16:35:18 +13:00
22f67be461 added some weather symbols back and changed to emoji (#2695) 2020-10-22 15:10:19 -05:00
77ffd06715 Allow appending table literals. (#2693) 2020-10-22 03:26:30 -05:00
1d833ef972 Set weather chars as emoji only (#2691) 2020-10-22 14:36:27 +13:00
0d8064ed2d Add rounding functionalties (#2672)
* added math round

* added math floor

* added math ceil

* added math.md examples

* moved the detection of nonnumerical values in ceil/floor/round

* math round now works on streams

* math floor now works on streams

* math ceil now works on streams
2020-10-22 13:18:27 +13:00
cc06ea4d87 Add Tau constant (#2673)
Adds Tau constant using meval::Context.

Also adds a test to match pi's.

Note: Tau ends up not being more precise than 2*pi.

Resolves: #2258
2020-10-22 13:16:51 +13:00
3cf7652e86 fixed a bug where 'B' wasn't showing up (#2690)
right when get_appropriate_unit was called
2020-10-21 14:19:35 -05:00
1eb28c6cb6 add heavy & none table border options (#2686) 2020-10-21 08:53:08 -05:00
db590369a8 Fix filesize "B" regression (#2688) 2020-10-21 20:26:10 +13:00
f4d654d2a2 fix: remove duplicated "to" (#2682) 2020-10-21 05:35:43 +13:00
5725e55abb Flatten rows containing same sub-table columns with distinct column names. (#2684) 2020-10-20 05:37:40 -05:00
b6d19cc9fa Move command changes. Refactorings. (#2683)
Continuing on anchoring and improvements on Nu's overall internal commands (#2635).
`move column` sub command has been turned into the command `move` since
we use it to move exclusively columns. Examples added as well.

Fixed it to carry along any anchor locations that might be in place if
table to be moved originates from other sources.
2020-10-20 04:07:13 -05:00
bc6c884a14 added num-format to allow bytes to be formatted with commas. (#2681) 2020-10-19 12:52:11 -05:00
cb78bf8fd6 add filesize_format to config (#2676) 2020-10-19 11:34:39 -05:00
400bc97e35 Add parser improvements (#2679)
* Add parser improvements

Previously everything starting with "$" was parsed as a column path.
With this commit applied, the lite_arg starting with $ is parsed as
the most appropriate thing
- $true/$false ==> Expression::Boolean
- $(...) ==> Invocation
- $it ==> ColumnPath
- Anything with at least one '.' ==> ColumnPath
- Anything else ==> Variable

* Ignore failing tests
2020-10-19 20:03:14 +13:00
2fd464bf7b Refactor to md and Add Padding for Pretty Flag (#2678)
* refactor and cleanup to md

* Add padding around values in each row

* Add padding to test

* Update code to satisfy Clippy and pass other failing tests
2020-10-19 19:58:24 +13:00
e626522b3a LS support for other number formatting (#2650)
* make sort-by fail gracefully if mismatched types are compared

* Added a test to check if sorted-by with invalid types exists gracefully

* Linter changes

* removed redundant pattern matching

* Changed the error message

* Added a comma after every argument

* Changed the test to accomodate the new err messages

* Err message for sort-by invalid types now shows the mismatched types

* Lints problems

* Changed unwrap to expect

* Added the -f flag to rm command

Now when you a use rm -f there will be no error message, even if the
file doesnt actually exist

* Lint problems

* Fixed the wrong line

* Removed println

* Spelling mistake

* Fix problems when you mv a file into itself

* Lint mistakes

* Remove unecessary filtering in most cases

* Allow the removal of sockets

* Conditional compilations to systems without socket

* Add a size-format option to ls command

* Added kib and mib formating

* Make patterns lowercase

* New subcommand to format, filesize

* Forgot the linter once more

* Remove the ls changes since its no longer needed

* CI mistakes

* Lint stuff

* Fix lint

* Added formatting for bytes

* fix lint

* Changed the usage comment
2020-10-17 06:15:40 +13:00
791e07650d ColumnPath creation flexibility. (#2674) 2020-10-15 17:25:17 -05:00
bf2363947b Add pretty flag to to md (#2640)
* First draft for adding a `pretty` flag to `to md`

* rustfmt

* Fix Clippy warnings

* rustfmt

* Using Clippy suggestion broken code, reverting and putting in a statement to ignore clippy warning

* Add test for `to md -p`
2020-10-15 16:20:55 +13:00
a2cc2259e7 add bson and sqlite to wix (#2668)
* add bson and sqlite to wix

* add sqlite and bson from and to
2020-10-14 04:46:06 -05:00
808fe496a6 Fix typo in test support crate description (#2669) 2020-10-14 04:45:32 -05:00
2fb48bd6ac Flatten command. (#2670) 2020-10-14 04:36:11 -05:00
214 changed files with 5302 additions and 2033 deletions

66
Cargo.lock generated
View File

@ -2858,12 +2858,13 @@ dependencies = [
[[package]]
name = "nu"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"clap",
"ctrlc",
"dunce",
"futures 0.3.5",
"itertools",
"log 0.4.11",
"nu-cli",
"nu-data",
@ -2896,7 +2897,7 @@ dependencies = [
[[package]]
name = "nu-cli"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"Inflector",
"ansi_term 0.12.1",
@ -2934,6 +2935,7 @@ dependencies = [
"ichwh",
"indexmap",
"itertools",
"lazy_static 1.4.0",
"log 0.4.11",
"meval",
"nu-data",
@ -2993,7 +2995,7 @@ dependencies = [
[[package]]
name = "nu-data"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"ansi_term 0.12.1",
"bigdecimal",
@ -3012,6 +3014,7 @@ dependencies = [
"nu-test-support",
"nu-value-ext",
"num-bigint 0.3.0",
"num-format",
"num-traits 0.2.12",
"parking_lot 0.11.0",
"query_interface",
@ -3022,7 +3025,7 @@ dependencies = [
[[package]]
name = "nu-errors"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"ansi_term 0.12.1",
"bigdecimal",
@ -3041,7 +3044,7 @@ dependencies = [
[[package]]
name = "nu-parser"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"bigdecimal",
"codespan-reporting",
@ -3059,13 +3062,14 @@ dependencies = [
[[package]]
name = "nu-plugin"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"bigdecimal",
"indexmap",
"nu-errors",
"nu-protocol",
"nu-source",
"nu-test-support",
"nu-value-ext",
"num-bigint 0.3.0",
"serde 1.0.115",
@ -3074,7 +3078,7 @@ dependencies = [
[[package]]
name = "nu-protocol"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"bigdecimal",
"byte-unit",
@ -3097,7 +3101,7 @@ dependencies = [
[[package]]
name = "nu-source"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"derive-new",
"getset",
@ -3108,7 +3112,7 @@ dependencies = [
[[package]]
name = "nu-table"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"ansi_term 0.12.1",
"unicode-width",
@ -3116,8 +3120,9 @@ dependencies = [
[[package]]
name = "nu-test-support"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"bigdecimal",
"chrono",
"dunce",
"getset",
@ -3133,7 +3138,7 @@ dependencies = [
[[package]]
name = "nu-value-ext"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"indexmap",
"itertools",
@ -3145,7 +3150,7 @@ dependencies = [
[[package]]
name = "nu_plugin_binaryview"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"ansi_term 0.12.1",
"crossterm 0.18.0",
@ -3161,7 +3166,7 @@ dependencies = [
[[package]]
name = "nu_plugin_chart"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"crossterm 0.18.0",
"nu-cli",
@ -3176,7 +3181,7 @@ dependencies = [
[[package]]
name = "nu_plugin_fetch"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"base64 0.12.3",
"futures 0.3.5",
@ -3190,7 +3195,7 @@ dependencies = [
[[package]]
name = "nu_plugin_from_bson"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"bigdecimal",
"bson",
@ -3204,7 +3209,7 @@ dependencies = [
[[package]]
name = "nu_plugin_from_sqlite"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"bigdecimal",
"nu-errors",
@ -3219,19 +3224,20 @@ dependencies = [
[[package]]
name = "nu_plugin_inc"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"nu-errors",
"nu-plugin",
"nu-protocol",
"nu-source",
"nu-test-support",
"nu-value-ext",
"semver 0.10.0",
]
[[package]]
name = "nu_plugin_match"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"nu-errors",
"nu-plugin",
@ -3242,7 +3248,7 @@ dependencies = [
[[package]]
name = "nu_plugin_post"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"base64 0.12.3",
"futures 0.3.5",
@ -3258,7 +3264,7 @@ dependencies = [
[[package]]
name = "nu_plugin_ps"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"futures 0.3.5",
"futures-timer",
@ -3272,7 +3278,7 @@ dependencies = [
[[package]]
name = "nu_plugin_s3"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"futures 0.3.5",
"nu-errors",
@ -3284,7 +3290,7 @@ dependencies = [
[[package]]
name = "nu_plugin_start"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"glob",
"nu-errors",
@ -3297,7 +3303,7 @@ dependencies = [
[[package]]
name = "nu_plugin_sys"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"battery",
"futures 0.3.5",
@ -3312,7 +3318,7 @@ dependencies = [
[[package]]
name = "nu_plugin_textview"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"ansi_term 0.12.1",
"bat",
@ -3327,7 +3333,7 @@ dependencies = [
[[package]]
name = "nu_plugin_to_bson"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"bson",
"nu-errors",
@ -3340,7 +3346,7 @@ dependencies = [
[[package]]
name = "nu_plugin_to_sqlite"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"hex 0.4.2",
"nu-errors",
@ -3355,7 +3361,7 @@ dependencies = [
[[package]]
name = "nu_plugin_tree"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"derive-new",
"nu-errors",
@ -3367,7 +3373,7 @@ dependencies = [
[[package]]
name = "nu_plugin_xpath"
version = "0.21.0"
version = "0.22.0"
dependencies = [
"bigdecimal",
"indexmap",
@ -5533,9 +5539,9 @@ dependencies = [
[[package]]
name = "trash"
version = "1.1.1"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "329be7bb48445d16bf4c241ba9514af46f2189c715bf5fd854e38f7c95f60194"
checksum = "bbf511f5673142be74cd6a46cfb7e3da8d2b95bc08d2d46c072798e6ce8b5b9f"
dependencies = [
"winapi 0.3.9",
]

View File

@ -10,7 +10,7 @@ license = "MIT"
name = "nu"
readme = "README.md"
repository = "https://github.com/nushell/nushell"
version = "0.21.0"
version = "0.22.0"
[workspace]
members = ["crates/*/"]
@ -18,32 +18,32 @@ members = ["crates/*/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-cli = {version = "0.21.0", path = "./crates/nu-cli"}
nu-data = {version = "0.21.0", path = "./crates/nu-data"}
nu-errors = {version = "0.21.0", path = "./crates/nu-errors"}
nu-parser = {version = "0.21.0", path = "./crates/nu-parser"}
nu-plugin = {version = "0.21.0", path = "./crates/nu-plugin"}
nu-protocol = {version = "0.21.0", path = "./crates/nu-protocol"}
nu-source = {version = "0.21.0", path = "./crates/nu-source"}
nu-value-ext = {version = "0.21.0", path = "./crates/nu-value-ext"}
nu-cli = {version = "0.22.0", path = "./crates/nu-cli"}
nu-data = {version = "0.22.0", path = "./crates/nu-data"}
nu-errors = {version = "0.22.0", path = "./crates/nu-errors"}
nu-parser = {version = "0.22.0", path = "./crates/nu-parser"}
nu-plugin = {version = "0.22.0", path = "./crates/nu-plugin"}
nu-protocol = {version = "0.22.0", path = "./crates/nu-protocol"}
nu-source = {version = "0.22.0", path = "./crates/nu-source"}
nu-value-ext = {version = "0.22.0", path = "./crates/nu-value-ext"}
nu_plugin_binaryview = {version = "0.21.0", path = "./crates/nu_plugin_binaryview", optional = true}
nu_plugin_chart = {version = "0.21.0", path = "./crates/nu_plugin_chart", optional = true}
nu_plugin_fetch = {version = "0.21.0", path = "./crates/nu_plugin_fetch", optional = true}
nu_plugin_from_bson = {version = "0.21.0", path = "./crates/nu_plugin_from_bson", optional = true}
nu_plugin_from_sqlite = {version = "0.21.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
nu_plugin_inc = {version = "0.21.0", path = "./crates/nu_plugin_inc", optional = true}
nu_plugin_match = {version = "0.21.0", path = "./crates/nu_plugin_match", optional = true}
nu_plugin_post = {version = "0.21.0", path = "./crates/nu_plugin_post", optional = true}
nu_plugin_ps = {version = "0.21.0", path = "./crates/nu_plugin_ps", optional = true}
nu_plugin_s3 = {version = "0.21.0", path = "./crates/nu_plugin_s3", optional = true}
nu_plugin_start = {version = "0.21.0", path = "./crates/nu_plugin_start", optional = true}
nu_plugin_sys = {version = "0.21.0", path = "./crates/nu_plugin_sys", optional = true}
nu_plugin_textview = {version = "0.21.0", path = "./crates/nu_plugin_textview", optional = true}
nu_plugin_to_bson = {version = "0.21.0", path = "./crates/nu_plugin_to_bson", optional = true}
nu_plugin_to_sqlite = {version = "0.21.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
nu_plugin_tree = {version = "0.21.0", path = "./crates/nu_plugin_tree", optional = true}
nu_plugin_xpath = {version = "0.21.0", path = "./crates/nu_plugin_xpath", optional = true}
nu_plugin_binaryview = {version = "0.22.0", path = "./crates/nu_plugin_binaryview", optional = true}
nu_plugin_chart = {version = "0.22.0", path = "./crates/nu_plugin_chart", optional = true}
nu_plugin_fetch = {version = "0.22.0", path = "./crates/nu_plugin_fetch", optional = true}
nu_plugin_from_bson = {version = "0.22.0", path = "./crates/nu_plugin_from_bson", optional = true}
nu_plugin_from_sqlite = {version = "0.22.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
nu_plugin_inc = {version = "0.22.0", path = "./crates/nu_plugin_inc", optional = true}
nu_plugin_match = {version = "0.22.0", path = "./crates/nu_plugin_match", optional = true}
nu_plugin_post = {version = "0.22.0", path = "./crates/nu_plugin_post", optional = true}
nu_plugin_ps = {version = "0.22.0", path = "./crates/nu_plugin_ps", optional = true}
nu_plugin_s3 = {version = "0.22.0", path = "./crates/nu_plugin_s3", optional = true}
nu_plugin_start = {version = "0.22.0", path = "./crates/nu_plugin_start", optional = true}
nu_plugin_sys = {version = "0.22.0", path = "./crates/nu_plugin_sys", optional = true}
nu_plugin_textview = {version = "0.22.0", path = "./crates/nu_plugin_textview", optional = true}
nu_plugin_to_bson = {version = "0.22.0", path = "./crates/nu_plugin_to_bson", optional = true}
nu_plugin_to_sqlite = {version = "0.22.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
nu_plugin_tree = {version = "0.22.0", path = "./crates/nu_plugin_tree", optional = true}
nu_plugin_xpath = {version = "0.22.0", path = "./crates/nu_plugin_xpath", optional = true}
# Required to bootstrap the main binary
clap = "2.33.3"
@ -51,10 +51,11 @@ ctrlc = {version = "3.1.6", optional = true}
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
log = "0.4.11"
pretty_env_logger = "0.4.0"
itertools = "0.9.0"
[dev-dependencies]
dunce = "1.0.1"
nu-test-support = {version = "0.21.0", path = "./crates/nu-test-support"}
nu-test-support = {version = "0.22.0", path = "./crates/nu-test-support"}
[build-dependencies]
@ -190,6 +191,26 @@ name = "nu_plugin_extra_xpath"
path = "src/plugins/nu_plugin_extra_xpath.rs"
required-features = ["xpath"]
[[bin]]
name = "nu_plugin_extra_from_bson"
path = "src/plugins/nu_plugin_extra_from_bson.rs"
required-features = ["bson"]
[[bin]]
name = "nu_plugin_extra_to_bson"
path = "src/plugins/nu_plugin_extra_to_bson.rs"
required-features = ["bson"]
[[bin]]
name = "nu_plugin_extra_from_sqlite"
path = "src/plugins/nu_plugin_extra_from_sqlite.rs"
required-features = ["sqlite"]
[[bin]]
name = "nu_plugin_extra_to_sqlite"
path = "src/plugins/nu_plugin_extra_to_sqlite.rs"
required-features = ["sqlite"]
# Main nu binary
[[bin]]
name = "nu"

View File

@ -46,7 +46,7 @@ Try it in Gitpod.
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/en/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
To build Nu, you will need to use the **latest stable (1.41 or later)** version of the compiler.
To build Nu, you will need to use the **latest stable (1.47 or later)** version of the compiler.
Required dependencies:
@ -219,15 +219,15 @@ We can pipeline this into a command that gets the contents of one of the columns
name │ nu
readme │ README.md
repository │ https://github.com/nushell/nushell
version │ 0.15.1
version │ 0.21.0
───────────────┴────────────────────────────────────
```
Finally, we can use commands outside of Nu once we have the data we want:
```shell
> open Cargo.toml | get package.version | echo $it
0.15.1
> open Cargo.toml | get package.version
0.21.0
```
Here we use the variable `$it` to refer to the value being piped to the external command.

View File

@ -4,21 +4,21 @@ description = "CLI for nushell"
edition = "2018"
license = "MIT"
name = "nu-cli"
version = "0.21.0"
version = "0.22.0"
[lib]
doctest = false
[dependencies]
nu-data = {version = "0.21.0", path = "../nu-data"}
nu-errors = {version = "0.21.0", path = "../nu-errors"}
nu-parser = {version = "0.21.0", path = "../nu-parser"}
nu-plugin = {version = "0.21.0", path = "../nu-plugin"}
nu-protocol = {version = "0.21.0", path = "../nu-protocol"}
nu-source = {version = "0.21.0", path = "../nu-source"}
nu-table = {version = "0.21.0", path = "../nu-table"}
nu-test-support = {version = "0.21.0", path = "../nu-test-support"}
nu-value-ext = {version = "0.21.0", path = "../nu-value-ext"}
nu-data = {version = "0.22.0", path = "../nu-data"}
nu-errors = {version = "0.22.0", path = "../nu-errors"}
nu-parser = {version = "0.22.0", path = "../nu-parser"}
nu-plugin = {version = "0.22.0", path = "../nu-plugin"}
nu-protocol = {version = "0.22.0", path = "../nu-protocol"}
nu-source = {version = "0.22.0", path = "../nu-source"}
nu-table = {version = "0.22.0", path = "../nu-table"}
nu-test-support = {version = "0.22.0", path = "../nu-test-support"}
nu-value-ext = {version = "0.22.0", path = "../nu-value-ext"}
ansi_term = "0.12.1"
async-recursion = "0.3.1"
@ -91,12 +91,13 @@ uom = {version = "0.28.0", features = ["f64", "try-from"]}
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
which = {version = "4.0.2", optional = true}
zip = {version = "0.5.7", optional = true}
lazy_static = "1.*"
Inflector = "0.11"
clipboard = {version = "0.5.0", optional = true}
encoding_rs = "0.8.24"
rayon = "1.4.0"
trash = {version = "1.1.1", optional = true}
trash = {version = "1.2.0", optional = true}
url = "2.1.1"
[target.'cfg(unix)'.dependencies]

View File

@ -5,7 +5,7 @@ fn main() -> Result<(), io::Error> {
let out_dir = env::var_os("OUT_DIR").expect(
"\
OUT_DIR environment variable not found. \
OUT_DIR is guaranteed to to exist in a build script by cargo - see \
OUT_DIR is guaranteed to exist in a build script by cargo - see \
https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts\
");

View File

@ -159,7 +159,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(Ansi),
whole_stream_command(Char),
// Column manipulation
whole_stream_command(MoveColumn),
whole_stream_command(Move),
whole_stream_command(Reject),
whole_stream_command(Select),
whole_stream_command(Get),
@ -180,6 +180,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(Nth),
whole_stream_command(Drop),
whole_stream_command(Format),
whole_stream_command(FileSize),
whole_stream_command(Where),
whole_stream_command(If),
whole_stream_command(Compact),
@ -198,6 +199,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(EachWindow),
whole_stream_command(Empty),
// Table manipulation
whole_stream_command(Flatten),
whole_stream_command(Move),
whole_stream_command(Merge),
whole_stream_command(Shuffle),
@ -221,6 +223,9 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(MathSummation),
whole_stream_command(MathVariance),
whole_stream_command(MathProduct),
whole_stream_command(MathRound),
whole_stream_command(MathFloor),
whole_stream_command(MathCeil),
// File format output
whole_stream_command(To),
whole_stream_command(ToCSV),
@ -273,6 +278,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(UrlPath),
whole_stream_command(UrlHost),
whole_stream_command(UrlQuery),
whole_stream_command(Seq),
]);
#[cfg(feature = "clipboard-cli")]
@ -388,13 +394,10 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
Ok(result) => {
let mut prompt_block =
nu_parser::classify_block(&result, context.registry());
let prompt_block = nu_parser::classify_block(&result, context.registry());
let env = context.get_env();
prompt_block.block.expand_it_usage();
match run_block(
&prompt_block.block,
&mut context,
@ -857,8 +860,7 @@ pub async fn parse_and_eval(line: &str, ctx: &mut EvaluationContext) -> Result<S
let lite_result = nu_parser::lite_parse(&line, 0)?;
// TODO ensure the command whose examples we're testing is actually in the pipeline
let mut classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
classified_block.block.expand_it_usage();
let classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
let input_stream = InputStream::empty();
let env = ctx.get_env();
@ -899,7 +901,7 @@ pub async fn process_line(
debug!("=== Parsed ===");
debug!("{:#?}", result);
let mut classified_block = nu_parser::classify_block(&result, ctx.registry());
let classified_block = nu_parser::classify_block(&result, ctx.registry());
debug!("{:#?}", classified_block);
//println!("{:#?}", pipeline);
@ -1016,8 +1018,6 @@ pub async fn process_line(
InputStream::empty()
};
classified_block.block.expand_it_usage();
trace!("{:#?}", classified_block);
let env = ctx.get_env();
match run_block(

View File

@ -42,6 +42,7 @@ pub(crate) mod every;
pub(crate) mod exec;
pub(crate) mod exit;
pub(crate) mod first;
pub(crate) mod flatten;
pub(crate) mod format;
pub(crate) mod from;
pub(crate) mod from_csv;
@ -97,6 +98,7 @@ pub(crate) mod run_alias;
pub(crate) mod run_external;
pub(crate) mod save;
pub(crate) mod select;
pub(crate) mod seq;
pub(crate) mod shells;
pub(crate) mod shuffle;
pub(crate) mod size;
@ -175,7 +177,8 @@ pub(crate) use every::Every;
pub(crate) use exec::Exec;
pub(crate) use exit::Exit;
pub(crate) use first::First;
pub(crate) use format::Format;
pub(crate) use flatten::Command as Flatten;
pub(crate) use format::{FileSize, Format};
pub(crate) use from::From;
pub(crate) use from_csv::FromCSV;
pub(crate) use from_eml::FromEML;
@ -206,12 +209,12 @@ pub(crate) use last::Last;
pub(crate) use lines::Lines;
pub(crate) use ls::Ls;
pub(crate) use math::{
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathProduct,
MathStddev, MathSummation, MathVariance,
Math, MathAverage, MathCeil, MathEval, MathFloor, MathMaximum, MathMedian, MathMinimum,
MathMode, MathProduct, MathRound, MathStddev, MathSummation, MathVariance,
};
pub(crate) use merge::Merge;
pub(crate) use mkdir::Mkdir;
pub(crate) use move_::{Move, MoveColumn, Mv};
pub(crate) use move_::{Move, Mv};
pub(crate) use next::Next;
pub(crate) use nth::Nth;
pub(crate) use open::Open;
@ -236,6 +239,7 @@ pub(crate) use rm::Remove;
pub(crate) use run_external::RunExternalCommand;
pub(crate) use save::Save;
pub(crate) use select::Select;
pub(crate) use seq::Seq;
pub(crate) use shells::Shells;
pub(crate) use shuffle::Shuffle;
pub(crate) use size::Size;
@ -278,19 +282,26 @@ mod tests {
use crate::examples::{test_anchors, test_examples};
use nu_errors::ShellError;
fn commands() -> Vec<Command> {
fn full_tests() -> Vec<Command> {
vec![
whole_stream_command(Append),
whole_stream_command(GroupBy),
whole_stream_command(Insert),
whole_stream_command(Move),
whole_stream_command(Update),
whole_stream_command(Empty),
]
}
fn only_examples() -> Vec<Command> {
let mut commands = full_tests();
commands.extend(vec![whole_stream_command(Flatten)]);
commands
}
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
for cmd in commands() {
for cmd in only_examples() {
test_examples(cmd)?;
}
@ -299,7 +310,7 @@ mod tests {
#[test]
fn tracks_metadata() -> Result<(), ShellError> {
for cmd in commands() {
for cmd in full_tests() {
test_anchors(cmd)?;
}

View File

@ -1,16 +1,16 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::types::deduction::{VarDeclaration, VarSyntaxShapeDeductor};
use deduction_to_signature::DeductionToSignature;
use log::trace;
use nu_data::config;
use nu_errors::ShellError;
use nu_parser::SignatureRegistry;
use nu_protocol::hir::{ClassifiedCommand, Expression, NamedValue, SpannedExpression, Variable};
use nu_protocol::{
hir::Block, CommandAction, NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape,
UntaggedValue, Value,
hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::Tagged;
use std::collections::HashMap;
pub struct Alias;
@ -86,7 +86,6 @@ pub async fn alias(
},
_ctx,
) = args.process(&registry).await?;
let mut processed_args: Vec<String> = vec![];
if let Some(true) = save {
let mut result = nu_data::config::read(name.clone().tag, &None)?;
@ -110,7 +109,7 @@ pub async fn alias(
let alias: Value = raw_input.trim().to_string().into();
let alias_start = raw_input.find('[').unwrap_or(0); // used to check if the same alias already exists
// add to startup if alias doesn't exist and replce if it does
// add to startup if alias doesn't exist and replace if it does
match result.get_mut("startup") {
Some(startup) => {
if let UntaggedValue::Table(ref mut commands) = startup.value {
@ -132,209 +131,41 @@ pub async fn alias(
config::write(&result, &None)?;
}
for item in list.iter() {
if let Ok(string) = item.as_string() {
processed_args.push(format!("${}", string));
let mut processed_args: Vec<VarDeclaration> = vec![];
for (_, item) in list.iter().enumerate() {
match item.as_string() {
Ok(var_name) => {
let dollar_var_name = format!("${}", var_name);
processed_args.push(VarDeclaration {
name: dollar_var_name,
// type_decl: None,
span: item.tag.span,
});
}
Err(_) => {
return Err(ShellError::labeled_error(
"Expected a string",
"expected a string",
item.tag(),
));
}
}
}
trace!("Found vars: {:?}", processed_args);
let inferred_shapes = {
if let Some(true) = infer {
VarSyntaxShapeDeductor::infer_vars(&processed_args, &block, &registry)?
} else {
return Err(ShellError::labeled_error(
"Expected a string",
"expected a string",
item.tag(),
));
processed_args.into_iter().map(|arg| (arg, None)).collect()
}
}
if let Some(true) = infer {
Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::AddAlias(
name.to_string(),
to_arg_shapes(processed_args, &block, &registry)?,
block,
),
)))
} else {
Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::AddAlias(
name.to_string(),
processed_args
.into_iter()
.map(|arg| (arg, SyntaxShape::Any))
.collect(),
block,
),
)))
}
}
fn to_arg_shapes(
args: Vec<String>,
block: &Block,
registry: &CommandRegistry,
) -> Result<Vec<(String, SyntaxShape)>, ShellError> {
match find_block_shapes(block, registry) {
Ok(found) => Ok(args
.iter()
.map(|arg| {
(
arg.clone(),
match found.get(arg) {
None | Some((_, None)) => SyntaxShape::Any,
Some((_, Some(shape))) => *shape,
},
)
})
.collect()),
Err(err) => Err(err),
}
}
type ShapeMap = HashMap<String, (Span, Option<SyntaxShape>)>;
fn check_insert(
existing: &mut ShapeMap,
to_add: (String, (Span, Option<SyntaxShape>)),
) -> Result<(), ShellError> {
match (to_add.1).1 {
None => match existing.get(&to_add.0) {
None => {
existing.insert(to_add.0, to_add.1);
Ok(())
}
Some(_) => Ok(()),
},
Some(new) => match existing.insert(to_add.0.clone(), ((to_add.1).0, Some(new))) {
None => Ok(()),
Some(exist) => match exist.1 {
None => Ok(()),
Some(shape) => match shape {
SyntaxShape::Any => Ok(()),
shape if shape == new => Ok(()),
_ => Err(ShellError::labeled_error_with_secondary(
"Type conflict in alias variable use",
format!("{:?}", new),
(to_add.1).0,
format!("{:?}", shape),
exist.0,
)),
},
},
},
}
}
fn check_merge(existing: &mut ShapeMap, new: &ShapeMap) -> Result<(), ShellError> {
for (k, v) in new.iter() {
check_insert(existing, (k.clone(), *v))?;
}
Ok(())
}
fn find_expr_shapes(
spanned_expr: &SpannedExpression,
registry: &CommandRegistry,
) -> Result<ShapeMap, ShellError> {
match &spanned_expr.expr {
// TODO range will need similar if/when invocations can be parsed within range expression
Expression::Binary(bin) => find_expr_shapes(&bin.left, registry).and_then(|mut left| {
find_expr_shapes(&bin.right, registry)
.and_then(|right| check_merge(&mut left, &right).map(|()| left))
}),
Expression::Block(b) => find_block_shapes(&b, registry),
Expression::Path(path) => match &path.head.expr {
Expression::Invocation(b) => find_block_shapes(&b, registry),
Expression::Variable(Variable::Other(var, _)) => {
let mut result = HashMap::new();
result.insert(var.to_string(), (spanned_expr.span, None));
Ok(result)
}
_ => Ok(HashMap::new()),
},
_ => Ok(HashMap::new()),
}
}
fn find_block_shapes(block: &Block, registry: &CommandRegistry) -> Result<ShapeMap, ShellError> {
let apply_shape = |found: ShapeMap, sig_shape: SyntaxShape| -> ShapeMap {
found
.iter()
.map(|(v, sh)| match sh.1 {
None => (v.clone(), (sh.0, Some(sig_shape))),
Some(shape) => (v.clone(), (sh.0, Some(shape))),
})
.collect()
};
let signature = DeductionToSignature::get(&name.item, &inferred_shapes);
trace!("Inferred signature: {:?}", signature);
let mut arg_shapes = HashMap::new();
for pipeline in &block.block {
for classified in &pipeline.list {
match classified {
ClassifiedCommand::Expr(spanned_expr) => {
let found = find_expr_shapes(&spanned_expr, registry)?;
check_merge(&mut arg_shapes, &found)?
}
ClassifiedCommand::Internal(internal) => {
if let Some(signature) = registry.get(&internal.name) {
if let Some(positional) = &internal.args.positional {
for (i, spanned_expr) in positional.iter().enumerate() {
let found = find_expr_shapes(&spanned_expr, registry)?;
if i >= signature.positional.len() {
if let Some((sig_shape, _)) = &signature.rest_positional {
check_merge(
&mut arg_shapes,
&apply_shape(found, *sig_shape),
)?;
} else {
unreachable!("should have error'd in parsing");
}
} else {
let (pos_type, _) = &signature.positional[i];
match pos_type {
// TODO pass on mandatory/optional?
PositionalType::Mandatory(_, sig_shape)
| PositionalType::Optional(_, sig_shape) => {
check_merge(
&mut arg_shapes,
&apply_shape(found, *sig_shape),
)?;
}
}
}
}
}
if let Some(named) = &internal.args.named {
for (name, val) in named.iter() {
if let NamedValue::Value(_, spanned_expr) = val {
let found = find_expr_shapes(&spanned_expr, registry)?;
match signature.named.get(name) {
None => {
unreachable!("should have error'd in parsing");
}
Some((named_type, _)) => {
if let NamedType::Mandatory(_, sig_shape)
| NamedType::Optional(_, sig_shape) = named_type
{
check_merge(
&mut arg_shapes,
&apply_shape(found, *sig_shape),
)?;
}
}
}
}
}
}
} else {
unreachable!("registry has lost name it provided");
}
}
ClassifiedCommand::Dynamic(_) | ClassifiedCommand::Error(_) => (),
}
}
}
Ok(arg_shapes)
Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::AddAlias(Box::new(signature), block),
)))
}
#[cfg(test)]
@ -349,3 +180,42 @@ mod tests {
Ok(test_examples(Alias {})?)
}
}
mod deduction_to_signature {
//For now this logic is relativly simple.
//For each var, one mandatory positional is added.
//As soon as more support for optional positional arguments is arrived,
//this logic might be a little bit more tricky.
use crate::types::deduction::{Deduction, VarDeclaration};
use nu_protocol::{PositionalType, Signature, SyntaxShape};
pub struct DeductionToSignature {}
impl DeductionToSignature {
pub fn get(
cmd_name: &str,
deductions: &[(VarDeclaration, Option<Deduction>)],
) -> Signature {
let mut signature = Signature::build(cmd_name);
for (decl, deduction) in deductions {
match deduction {
None => signature.positional.push((
PositionalType::optional(&decl.name, SyntaxShape::Any),
decl.name.clone(),
)),
Some(deduction) => match deduction {
Deduction::VarShapeDeduction(normal_var_deduction) => {
signature.positional.push((
PositionalType::optional(
&decl.name,
normal_var_deduction[0].deduction,
),
decl.name.clone(),
))
}
},
}
}
signature
}
}
}

View File

@ -3,12 +3,15 @@ use crate::prelude::*;
use ansi_term::Color;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct Ansi;
#[derive(Deserialize)]
struct AnsiArgs {
color: Value,
escape: Option<Tagged<String>>,
osc: Option<Tagged<String>>,
}
#[async_trait]
@ -18,15 +21,70 @@ impl WholeStreamCommand for Ansi {
}
fn signature(&self) -> Signature {
Signature::build("ansi").required(
"color",
SyntaxShape::Any,
"the name of the color to use or 'reset' to reset the color",
)
Signature::build("ansi")
.optional(
"color",
SyntaxShape::Any,
"the name of the color to use or 'reset' to reset the color",
)
.named(
"escape", // \x1b
SyntaxShape::Any,
"escape sequence without the escape character(s)",
Some('e'),
)
.named(
"osc",
SyntaxShape::Any,
"operating system command (ocs) escape sequence without the escape character(s)",
Some('o'),
)
}
fn usage(&self) -> &str {
"Output ANSI codes to change color"
r#"Output ANSI codes to change color
For escape sequences:
Escape: '\x1b[' is not required for --escape parameter
Format: #(;#)m
Example: 1;31m for bold red or 2;37;41m for dimmed white fg with red bg
There can be multiple text formatting sequence numbers
separated by a ; and ending with an m where the # is of the
following values:
attributes
0 reset / normal display
1 bold or increased intensity
2 faint or decreased intensity
3 italic on (non-mono font)
4 underline on
5 slow blink on
6 fast blink on
7 reverse video on
8 nondisplayed (invisible) on
9 strike-through on
foreground/bright colors background/bright colors
30/90 black 40/100 black
31/91 red 41/101 red
32/92 green 42/102 green
33/93 yellow 43/103 yellow
34/94 blue 44/104 blue
35/95 magenta 45/105 magenta
36/96 cyan 46/106 cyan
37/97 white 47/107 white
https://en.wikipedia.org/wiki/ANSI_escape_code
OSC: '\x1b]' is not required for --osc parameter
Example: echo [$(ansi -o '0') 'some title' $(char bel)] | str collect
Format: #
0 Set window title and icon name
1 Set icon name
2 Set window title
4 Set/read color palette
9 iTerm2 Grown notifications
10 Set foreground color (x11 color spec)
11 Set background color (x11 color spec)
... others"#
}
fn examples(&self) -> Vec<Example> {
@ -49,6 +107,14 @@ impl WholeStreamCommand for Ansi {
"\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld",
)]),
},
Example {
description:
"Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)",
example: r#"echo [$(ansi -e '3;93;41m') Hello $(ansi reset) " " $(ansi gb) Nu " " $(ansi pb) World] | str collect"#,
result: Some(vec![Value::from(
"\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld",
)]),
},
]
}
@ -57,10 +123,41 @@ impl WholeStreamCommand for Ansi {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let (AnsiArgs { color }, _) = args.process(&registry).await?;
let (AnsiArgs { color, escape, osc }, _) = args.process(&registry).await?;
if let Some(e) = escape {
let esc_vec: Vec<char> = e.item.chars().collect();
if esc_vec[0] == '\\' {
return Err(ShellError::labeled_error(
"no need for escape characters",
"no need for escape characters",
e.tag(),
));
}
let output = format!("\x1b[{}", e.item);
return Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(e.tag()),
)));
}
if let Some(o) = osc {
let osc_vec: Vec<char> = o.item.chars().collect();
if osc_vec[0] == '\\' {
return Err(ShellError::labeled_error(
"no need for escape characters",
"no need for escape characters",
o.tag(),
));
}
//Operating system command aka osc ESC ] <- note the right brace, not left brace for osc
// OCS's need to end with a bell '\x07' char
let output = format!("\x1b]{};", o.item);
return Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(o.tag()),
)));
}
let color_string = color.as_string()?;
let ansi_code = str_to_ansi_color(color_string);
if let Some(output) = ansi_code {
@ -74,6 +171,7 @@ impl WholeStreamCommand for Ansi {
color.tag(),
))
}
// }
}
}

View File

@ -6,7 +6,7 @@ use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
#[derive(Deserialize)]
struct Arguments {
row: Value,
value: Value,
}
pub struct Command;
@ -26,7 +26,7 @@ impl WholeStreamCommand for Command {
}
fn usage(&self) -> &str {
"Append the given row to the table"
"Append a row to the table"
}
async fn run(
@ -34,30 +34,57 @@ impl WholeStreamCommand for Command {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let (Arguments { mut row }, input) = args.process(registry).await?;
let (Arguments { mut value }, input) = args.process(registry).await?;
let input: Vec<Value> = input.collect().await;
if let Some(first) = input.get(0) {
row.tag = first.tag();
value.tag = first.tag();
}
Ok(
futures::stream::iter(input.into_iter().chain(vec![row]).map(ReturnSuccess::value))
.to_output_stream(),
// Checks if we are trying to append a row literal
if let Value {
value: UntaggedValue::Table(values),
tag,
} = &value
{
if values.len() == 1 && values[0].is_row() {
value = values[0].value.clone().into_value(tag);
}
}
Ok(futures::stream::iter(
input
.into_iter()
.chain(vec![value])
.map(ReturnSuccess::value),
)
.to_output_stream())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Add something to the end of a list or table",
example: "echo [1 2 3] | append 4",
result: Some(vec![
UntaggedValue::int(1).into(),
UntaggedValue::int(2).into(),
UntaggedValue::int(3).into(),
UntaggedValue::int(4).into(),
]),
}]
use nu_protocol::row;
vec![
Example {
description: "Add values to the end of the table",
example: "echo [1 2 3] | append 4",
result: Some(vec![
UntaggedValue::int(1).into(),
UntaggedValue::int(2).into(),
UntaggedValue::int(3).into(),
UntaggedValue::int(4).into(),
]),
},
Example {
description: "Add row value to the end of the table",
example: "echo [[country]; [Ecuador] ['New Zealand']] | append [[country]; [USA]]",
result: Some(vec![
row! { "country".into() => Value::from("Ecuador")},
row! { "country".into() => Value::from("New Zealand")},
row! { "country".into() => Value::from("USA")},
]),
},
]
}
}

View File

@ -9,6 +9,7 @@ pub struct Char;
#[derive(Deserialize)]
struct CharArgs {
name: Tagged<String>,
unicode: bool,
}
#[async_trait]
@ -18,11 +19,13 @@ impl WholeStreamCommand for Char {
}
fn signature(&self) -> Signature {
Signature::build("ansi").required(
"character",
SyntaxShape::Any,
"the name of the character to output",
)
Signature::build("ansi")
.required(
"character",
SyntaxShape::Any,
"the name of the character to output",
)
.switch("unicode", "unicode string i.e. 1f378", Some('u'))
}
fn usage(&self) -> &str {
@ -45,6 +48,11 @@ impl WholeStreamCommand for Char {
UntaggedValue::string("\u{2261}").into(),
]),
},
Example {
description: "Output unicode character",
example: r#"char -u 1f378"#,
result: Some(vec![Value::from("\u{1f378}")]),
},
]
}
@ -53,24 +61,44 @@ impl WholeStreamCommand for Char {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let (CharArgs { name }, _) = args.process(&registry).await?;
let (CharArgs { name, unicode }, _) = args.process(&registry).await?;
let special_character = str_to_character(&name.item);
if let Some(output) = special_character {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(name.tag()),
)))
if unicode {
let decoded_char = string_to_unicode_char(&name.item);
if let Some(output) = decoded_char {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(name.tag()),
)))
} else {
Err(ShellError::labeled_error(
"error decoding unicode character",
"error decoding unicode character",
name.tag(),
))
}
} else {
Err(ShellError::labeled_error(
"Unknown character",
"unknown character",
name.tag(),
))
let special_character = str_to_character(&name.item);
if let Some(output) = special_character {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(name.tag()),
)))
} else {
Err(ShellError::labeled_error(
"error finding named character",
"error finding named character",
name.tag(),
))
}
}
}
}
fn string_to_unicode_char(s: &str) -> Option<char> {
u32::from_str_radix(s, 16)
.ok()
.and_then(std::char::from_u32)
}
fn str_to_character(s: &str) -> Option<String> {
match s {
"newline" | "enter" | "nl" => Some("\n".into()),
@ -78,6 +106,7 @@ fn str_to_character(s: &str) -> Option<String> {
"sp" | "space" => Some(" ".into()),
// Unicode names came from https://www.compart.com/en/unicode
// Private Use Area (U+E000-U+F8FF)
// Unicode can't be mixed with Ansi or it will break width calculation
"branch" => Some('\u{e0a0}'.to_string()), // 
"segment" => Some('\u{e0b0}'.to_string()), // 
@ -96,15 +125,14 @@ fn str_to_character(s: &str) -> Option<String> {
"hash" | "hashtag" | "pound_sign" | "sharp" | "root" => Some("#".into()), // #
// Weather symbols
"sun" => Some("\x1b[33;1m\u{2600}\x1b[0m".to_string()), // Yellow Bold ☀
"moon" => Some("\x1b[36m\u{263d}\x1b[0m".to_string()), // Cyan ☽
"clouds" => Some("\x1b[37;1m\u{2601}\x1b[0m".to_string()), // White Bold ☁
"rain" => Some("\x1b[37;1m\u{2614}\x1b[0m".to_string()), // White Bold ☔
"fog" => Some("\x1b[37;1m\u{2592}\x1b[0m".to_string()), // White Bold ▒
"mist" => Some("\x1b[34m\u{2591}\x1b[0m".to_string()), // Blue ░
"haze" => Some("\x1b[33m\u{2591}\x1b[0m".to_string()), // Yellow ░
"snow" => Some("\x1b[37;1m\u{2744}\x1b[0m".to_string()), // White Bold ❄
"thunderstorm" => Some("\x1b[33;1m\u{26a1}\x1b[0m".to_string()), // Yellow Bold ⚡
"sun" | "sunny" | "sunrise" => Some("☀️".to_string()),
"moon" => Some("🌛".to_string()),
"cloudy" | "cloud" | "clouds" => Some("☁️".to_string()),
"rainy" | "rain" => Some("🌦️".to_string()),
"foggy" | "fog" => Some("🌫️".to_string()),
"mist" | "haze" => Some("\u{2591}".to_string()),
"snowy" | "snow" => Some("❄️".to_string()),
"thunderstorm" | "thunder" => Some("🌩️".to_string()),
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
// Another good reference http://ascii-table.com/ansi-escape-sequences.php
@ -123,6 +151,7 @@ fn str_to_character(s: &str) -> Option<String> {
"erase_line_from_cursor_to_end" => Some("\x1b[0K".to_string()), // clears from cursor to end of line
"erase_line_from_cursor_to_beginning" => Some("\x1b[1K".to_string()), // clears from cursor to start of line
"erase_entire_line" => Some("\x1b[2K".to_string()), // clears entire line
_ => None,
}
}

View File

@ -3,6 +3,7 @@ use crate::evaluate::evaluate_baseline_expr;
use crate::futures::ThreadedReceiver;
use crate::prelude::*;
use std::borrow::Cow;
use std::io::Write;
use std::ops::Deref;
use std::process::{Command, Stdio};
@ -13,6 +14,7 @@ use futures_codec::FramedRead;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::hir::Expression;
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
use nu_source::Tag;
@ -50,6 +52,7 @@ async fn run_with_stdin(
let mut command_args = vec![];
for arg in command.args.iter() {
let is_literal = matches!(arg.expr, Expression::Literal(_));
let value = evaluate_baseline_expr(arg, &context.registry, scope.clone()).await?;
// Skip any arguments that don't really exist, treating them as optional
@ -65,8 +68,10 @@ async fn run_with_stdin(
for t in table {
match &t.value {
UntaggedValue::Primitive(_) => {
command_args
.push(t.convert_to_string().trim_end_matches('\n').to_string());
command_args.push((
t.convert_to_string().trim_end_matches('\n').to_string(),
is_literal,
));
}
_ => {
return Err(ShellError::labeled_error(
@ -80,14 +85,14 @@ async fn run_with_stdin(
}
_ => {
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
command_args.push(trimmed_value_string);
command_args.push((trimmed_value_string, is_literal));
}
}
}
let process_args = command_args
.iter()
.map(|arg| {
.map(|(arg, _is_literal)| {
let home_dir;
#[cfg(feature = "dirs")]
@ -103,8 +108,9 @@ async fn run_with_stdin(
#[cfg(not(windows))]
{
if argument_contains_whitespace(&arg) && !argument_is_quoted(&arg) {
add_quotes(&arg)
if !_is_literal {
let escaped = escape_double_quotes(&arg);
add_double_quotes(&escaped)
} else {
arg.as_ref().to_string()
}
@ -219,36 +225,19 @@ fn spawn(
UntaggedValue::Primitive(Primitive::Nothing) => continue,
UntaggedValue::Primitive(Primitive::String(s))
| UntaggedValue::Primitive(Primitive::Line(s)) => {
if let Err(e) = stdin_write.write(s.as_bytes()) {
let message = format!("Unable to write to stdin (error = {})", e);
let _ = stdin_write_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
message,
"application may have closed before completing pipeline",
&stdin_name_tag,
)),
tag: stdin_name_tag,
}));
return Err(());
if stdin_write.write(s.as_bytes()).is_err() {
// Other side has closed, so exit
return Ok(());
}
}
UntaggedValue::Primitive(Primitive::Binary(b)) => {
if let Err(e) = stdin_write.write(b) {
let message = format!("Unable to write to stdin (error = {})", e);
let _ = stdin_write_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
message,
"application may have closed before completing pipeline",
&stdin_name_tag,
)),
tag: stdin_name_tag,
}));
return Err(());
if stdin_write.write(b).is_err() {
// Other side has closed, so exit
return Ok(());
}
}
unsupported => {
println!("Unsupported: {:?}", unsupported);
let _ = stdin_write_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
format!(
@ -494,11 +483,6 @@ where
shellexpand::tilde_with_context(input, home_dir)
}
#[allow(unused)]
pub fn argument_contains_whitespace(argument: &str) -> bool {
argument.chars().any(|c| c.is_whitespace())
}
fn argument_is_quoted(argument: &str) -> bool {
if argument.len() < 2 {
return false;
@ -509,10 +493,20 @@ fn argument_is_quoted(argument: &str) -> bool {
}
#[allow(unused)]
fn add_quotes(argument: &str) -> String {
fn add_double_quotes(argument: &str) -> String {
format!("\"{}\"", argument)
}
#[allow(unused)]
fn escape_double_quotes(argument: &str) -> Cow<'_, str> {
// allocate new string only if required
if argument.contains('"') {
Cow::Owned(argument.replace('"', r#"\""#))
} else {
Cow::Borrowed(argument)
}
}
#[allow(unused)]
fn remove_quotes(argument: &str) -> Option<&str> {
if !argument_is_quoted(argument) {
@ -538,7 +532,7 @@ fn shell_os_paths() -> Vec<std::path::PathBuf> {
#[cfg(test)]
mod tests {
use super::{
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
add_double_quotes, argument_is_quoted, escape_double_quotes, expand_tilde, remove_quotes,
};
#[cfg(feature = "which")]
use super::{run_external_command, EvaluationContext, InputStream};
@ -618,10 +612,10 @@ mod tests {
}
#[test]
fn checks_contains_whitespace_from_argument_to_be_passed_in() {
assert_eq!(argument_contains_whitespace("andrés"), false);
assert_eq!(argument_contains_whitespace("and rés"), true);
assert_eq!(argument_contains_whitespace(r#"and\ rés"#), true);
fn checks_escape_double_quotes() {
assert_eq!(escape_double_quotes("andrés"), "andrés");
assert_eq!(escape_double_quotes(r#"an"drés"#), r#"an\"drés"#);
assert_eq!(escape_double_quotes(r#""an"drés""#), r#"\"an\"drés\""#);
}
#[test]
@ -649,9 +643,8 @@ mod tests {
}
#[test]
fn adds_quotes_to_argument_to_be_passed_in() {
assert_eq!(add_quotes("andrés"), "\"andrés\"");
//assert_eq!(add_quotes("\"andrés\""), "\"andrés\"");
fn adds_double_quotes_to_argument_to_be_passed_in() {
assert_eq!(add_double_quotes("andrés"), "\"andrés\"");
}
#[test]

View File

@ -185,9 +185,9 @@ pub(crate) async fn run_internal_command(
));
InputStream::from_stream(futures::stream::iter(vec![]))
}
CommandAction::AddAlias(name, args, block) => {
CommandAction::AddAlias(sig, block) => {
context.add_commands(vec![whole_stream_command(
AliasCommand::new(name, args, block),
AliasCommand::new(*sig, block),
)]);
InputStream::from_stream(futures::stream::iter(vec![]))
}

View File

@ -6,8 +6,7 @@ use crate::prelude::*;
use futures::stream::once;
use nu_errors::ShellError;
use nu_protocol::{
hir::Block, hir::Expression, hir::SpannedExpression, hir::Synthetic, Scope, Signature,
SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
hir::Block, Scope, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
};
use nu_source::Tagged;
@ -73,34 +72,35 @@ impl WholeStreamCommand for Each {
}
}
fn is_expanded_it_usage(head: &SpannedExpression) -> bool {
matches!(&*head, SpannedExpression {
expr: Expression::Synthetic(Synthetic::String(s)),
..
} if s == "expanded-each")
}
pub async fn process_row(
block: Arc<Block>,
scope: Arc<Scope>,
head: Arc<Box<SpannedExpression>>,
mut context: Arc<EvaluationContext>,
input: Value,
) -> Result<OutputStream, ShellError> {
let input_clone = input.clone();
let input_stream = if is_expanded_it_usage(&head) {
// When we process a row, we need to know whether the block wants to have the contents of the row as
// a parameter to the block (so it gets assigned to a variable that can be used inside the block) or
// if it wants the contents as as an input stream
let input_stream = if !block.params.is_empty() {
InputStream::empty()
} else {
once(async { Ok(input_clone) }).to_input_stream()
};
Ok(run_block(
&block,
Arc::make_mut(&mut context),
input_stream,
Scope::append_it(scope, input),
let scope = if !block.params.is_empty() {
// FIXME: add check for more than parameter, once that's supported
Scope::append_var(scope, block.params[0].clone(), input)
} else {
scope
};
Ok(
run_block(&block, Arc::make_mut(&mut context), input_stream, scope)
.await?
.to_output_stream(),
)
.await?
.to_output_stream())
}
pub(crate) fn make_indexed_item(index: usize, item: Value) -> Value {
@ -116,7 +116,6 @@ async fn each(
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let head = Arc::new(raw_args.call_info.args.head.clone());
let scope = raw_args.call_info.scope.clone();
let context = Arc::new(EvaluationContext::from_raw(&raw_args, &registry));
let (each_args, input): (EachArgs, _) = raw_args.process(&registry).await?;
@ -128,12 +127,11 @@ async fn each(
.then(move |input| {
let block = block.clone();
let scope = scope.clone();
let head = head.clone();
let context = context.clone();
let row = make_indexed_item(input.0, input.1);
async {
match process_row(block, scope, head, context, row).await {
match process_row(block, scope, context, row).await {
Ok(s) => s,
Err(e) => OutputStream::one(Err(e)),
}
@ -146,11 +144,10 @@ async fn each(
.then(move |input| {
let block = block.clone();
let scope = scope.clone();
let head = head.clone();
let context = context.clone();
async {
match process_row(block, scope, head, context, input).await {
match process_row(block, scope, context, input).await {
Ok(s) => s,
Err(e) => OutputStream::one(Err(e)),
}

View File

@ -2,10 +2,7 @@ use crate::commands::each::process_row;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
hir::Block, hir::SpannedExpression, ReturnSuccess, Scope, Signature, SyntaxShape,
UntaggedValue, Value,
};
use nu_protocol::{hir::Block, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use serde::Deserialize;
@ -52,7 +49,6 @@ impl WholeStreamCommand for EachGroup {
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let head = Arc::new(raw_args.call_info.args.head.clone());
let scope = raw_args.call_info.scope.clone();
let context = Arc::new(EvaluationContext::from_raw(&raw_args, &registry));
let (each_args, input): (EachGroupArgs, _) = raw_args.process(&registry).await?;
@ -61,13 +57,7 @@ impl WholeStreamCommand for EachGroup {
Ok(input
.chunks(each_args.group_size.item)
.then(move |input| {
run_block_on_vec(
input,
block.clone(),
scope.clone(),
head.clone(),
context.clone(),
)
run_block_on_vec(input, block.clone(), scope.clone(), context.clone())
})
.flatten()
.to_output_stream())
@ -78,7 +68,6 @@ pub(crate) fn run_block_on_vec(
input: Vec<Value>,
block: Arc<Block>,
scope: Arc<Scope>,
head: Arc<Box<SpannedExpression>>,
context: Arc<EvaluationContext>,
) -> impl Future<Output = OutputStream> {
let value = Value {
@ -87,7 +76,7 @@ pub(crate) fn run_block_on_vec(
};
async {
match process_row(block, scope, head, context, value).await {
match process_row(block, scope, context, value).await {
Ok(s) => {
// We need to handle this differently depending on whether process_row
// returned just 1 value or if it returned multiple as a stream.

View File

@ -56,7 +56,6 @@ impl WholeStreamCommand for EachWindow {
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let head = Arc::new(raw_args.call_info.args.head.clone());
let scope = raw_args.call_info.scope.clone();
let context = Arc::new(EvaluationContext::from_raw(&raw_args, &registry));
let (each_args, mut input): (EachWindowArgs, _) = raw_args.process(&registry).await?;
@ -82,13 +81,12 @@ impl WholeStreamCommand for EachWindow {
let block = block.clone();
let scope = scope.clone();
let head = head.clone();
let context = context.clone();
let local_window = window.clone();
async move {
if i % stride == 0 {
Some(run_block_on_vec(local_window, block, scope, head, context).await)
Some(run_block_on_vec(local_window, block, scope, context).await)
} else {
None
}

View File

@ -187,7 +187,7 @@ async fn process_row(
let for_block = input.clone();
let input_stream = once(async { Ok(for_block) }).to_input_stream();
let scope = Scope::append_it(scope, input.clone());
let scope = Scope::append_var(scope, "$it", input.clone());
let mut stream = run_block(
&default_block,

View File

@ -0,0 +1,192 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
Dictionary, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
};
use nu_source::Tagged;
pub struct Command;
#[derive(Deserialize)]
pub struct Arguments {
rest: Vec<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"flatten"
}
fn signature(&self) -> Signature {
Signature::build("flatten").rest(SyntaxShape::String, "optionally flatten data by column")
}
fn usage(&self) -> &str {
"Flatten the table."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
flatten(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "flatten a table",
example: "echo [[N, u, s, h, e, l, l]] | flatten | first",
result: Some(vec![Value::from("N")]),
},
Example {
description: "flatten a column having a nested table",
example: "echo [[origin, people]; [Ecuador, $(echo [[name, meal]; ['Andres', 'arepa']])]] | flatten | get meal",
result: Some(vec![Value::from("arepa")]),
},
Example {
description: "restrict the flattening by passing column names",
example: "echo [[origin, crate, versions]; [World, $(echo [[name]; ['nu-cli']]), ['0.21', '0.22']]] | flatten versions | last | get versions",
result: Some(vec![Value::from("0.22")]),
}
]
}
}
async fn flatten(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let registry = registry.clone();
let (Arguments { rest: columns }, input) = args.process(&registry).await?;
Ok(input
.map(move |item| {
futures::stream::iter(flat_value(&columns, &item, &tag).into_iter().flatten())
})
.flatten()
.to_output_stream())
}
enum TableInside<'a> {
Entries(&'a str, &'a Tag, Vec<&'a Value>),
}
fn flat_value(
columns: &[Tagged<String>],
item: &Value,
name_tag: impl Into<Tag>,
) -> Result<Vec<Result<ReturnSuccess, ShellError>>, ShellError> {
let tag = item.tag.clone();
let name_tag = name_tag.into();
let res = {
if item.is_row() {
let mut out = TaggedDictBuilder::new(tag);
let mut a_table = None;
let mut tables_explicitly_flattened = 0;
for (column, value) in item.row_entries() {
let column_requested = columns.iter().find(|c| c.item == *column);
if let Value {
value: UntaggedValue::Row(Dictionary { entries: mapa }),
..
} = value
{
if column_requested.is_none() && !columns.is_empty() {
if out.contains_key(&column) {
out.insert_value(format!("{}_{}", column, column), value.clone());
} else {
out.insert_value(column, value.clone());
}
continue;
}
for (k, v) in mapa.into_iter() {
if out.contains_key(k) {
out.insert_value(format!("{}_{}", column, k), v.clone());
} else {
out.insert_value(k, v.clone());
}
}
} else if value.is_table() {
if tables_explicitly_flattened >= 1 && column_requested.is_some() {
let attempted = if let Some(name) = column_requested {
name.span()
} else {
name_tag.span
};
let already_flattened =
if let Some(TableInside::Entries(_, column_tag, _)) = a_table {
column_tag.span
} else {
name_tag.span
};
return Ok(vec![ReturnSuccess::value(
UntaggedValue::Error(ShellError::labeled_error_with_secondary(
"can only flatten one inner table at the same time",
"tried flattening more than one column with inner tables",
attempted,
"...but is flattened already",
already_flattened,
))
.into_value(name_tag),
)]);
}
if !columns.is_empty() {
if let Some(requested) = column_requested {
a_table = Some(TableInside::Entries(
&requested.item,
&requested.tag,
value.table_entries().collect(),
));
tables_explicitly_flattened += 1;
} else {
out.insert_value(column, value.clone());
}
} else if a_table.is_none() {
a_table = Some(TableInside::Entries(
&column,
&value.tag,
value.table_entries().collect(),
))
} else {
out.insert_value(column, value.clone());
}
} else {
out.insert_value(column, value.clone());
}
}
let mut expanded = vec![];
if let Some(TableInside::Entries(column, _, entries)) = a_table {
for entry in entries.into_iter() {
let mut base = out.clone();
base.insert_value(column, entry.clone());
expanded.push(base.into_value());
}
} else {
expanded.push(out.into_value());
}
expanded
} else if item.is_table() {
item.table_entries().map(Clone::clone).collect()
} else {
vec![item.clone()]
}
};
Ok(res.into_iter().map(ReturnSuccess::value).collect())
}

View File

@ -83,7 +83,7 @@ async fn format_command(
let result = evaluate_baseline_expr(
&full_column_path.0,
&registry,
Scope::append_it(scope.clone(), value.clone()),
Scope::append_var(scope.clone(), "$it", value.clone()),
)
.await;

View File

@ -0,0 +1,194 @@
use crate::prelude::*;
use nu_errors::ShellError;
use crate::commands::WholeStreamCommand;
use nu_protocol::{
ColumnPath, Primitive::Filesize, ReturnSuccess, Signature, SyntaxShape, UntaggedValue,
UntaggedValue::Primitive, Value,
};
use nu_source::Tagged;
use nu_value_ext::get_data_by_column_path;
use num_format::{Locale, ToFormattedString};
pub struct FileSize;
#[derive(Deserialize)]
pub struct Arguments {
field: ColumnPath,
format: Tagged<String>,
}
#[async_trait]
impl WholeStreamCommand for FileSize {
fn name(&self) -> &str {
"format filesize"
}
fn signature(&self) -> Signature {
Signature::build("format filesize")
.required(
"field",
SyntaxShape::ColumnPath,
"the name of the column to update",
)
.required(
"format value",
SyntaxShape::String,
"the format into which convert the filesizes",
)
}
fn usage(&self) -> &str {
"Converts a column of filesizes to some specified format"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
filesize(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Convert the size row to KB",
example: "ls | format filesize size KB",
result: None,
},
Example {
description: "Convert the apparent row to B",
example: "du | format filesize apparent B",
result: None,
},
]
}
}
async fn process_row(
input: Value,
format: Tagged<String>,
field: Arc<ColumnPath>,
) -> Result<OutputStream, ShellError> {
Ok({
let replace_for = get_data_by_column_path(&input, &field, move |_, _, error| error);
match replace_for {
Ok(s) => match convert_bytes_to_string_using_format(s, format) {
Ok(b) => OutputStream::one(ReturnSuccess::value(
input.replace_data_at_column_path(&field, b).expect("Given that the existance check was already done, this souldn't trigger never"),
)),
Err(e) => OutputStream::one(Err(e)),
},
Err(e) => OutputStream::one(Err(e)),
}
})
}
async fn filesize(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (Arguments { field, format }, input) = raw_args.process(&registry).await?;
let field = Arc::new(field);
Ok(input
.then(move |input| {
let format = format.clone();
let field = field.clone();
async {
match process_row(input, format, field).await {
Ok(s) => s,
Err(e) => OutputStream::one(Err(e)),
}
}
})
.flatten()
.to_output_stream())
}
fn convert_bytes_to_string_using_format(
bytes: Value,
format: Tagged<String>,
) -> Result<Value, ShellError> {
match bytes.value {
Primitive(Filesize(b)) => {
let byte = byte_unit::Byte::from_bytes(b as u128);
let value = match format.item().to_lowercase().as_str() {
"b" => Ok(UntaggedValue::string(b.to_formatted_string(&Locale::en))),
"kb" => Ok(UntaggedValue::string(
byte.get_adjusted_unit(byte_unit::ByteUnit::KB).to_string(),
)),
"kib" => Ok(UntaggedValue::string(
byte.get_adjusted_unit(byte_unit::ByteUnit::KiB).to_string(),
)),
"mb" => Ok(UntaggedValue::string(
byte.get_adjusted_unit(byte_unit::ByteUnit::MB).to_string(),
)),
"mib" => Ok(UntaggedValue::string(
byte.get_adjusted_unit(byte_unit::ByteUnit::MiB).to_string(),
)),
"gb" => Ok(UntaggedValue::string(
byte.get_adjusted_unit(byte_unit::ByteUnit::GB).to_string(),
)),
"gib" => Ok(UntaggedValue::string(
byte.get_adjusted_unit(byte_unit::ByteUnit::GiB).to_string(),
)),
"tb" => Ok(UntaggedValue::string(
byte.get_adjusted_unit(byte_unit::ByteUnit::TB).to_string(),
)),
"tib" => Ok(UntaggedValue::string(
byte.get_adjusted_unit(byte_unit::ByteUnit::TiB).to_string(),
)),
"pb" => Ok(UntaggedValue::string(
byte.get_adjusted_unit(byte_unit::ByteUnit::PB).to_string(),
)),
"pib" => Ok(UntaggedValue::string(
byte.get_adjusted_unit(byte_unit::ByteUnit::PiB).to_string(),
)),
"eb" => Ok(UntaggedValue::string(
byte.get_adjusted_unit(byte_unit::ByteUnit::EB).to_string(),
)),
"eib" => Ok(UntaggedValue::string(
byte.get_adjusted_unit(byte_unit::ByteUnit::EiB).to_string(),
)),
"zb" => Ok(UntaggedValue::string(
byte.get_adjusted_unit(byte_unit::ByteUnit::ZB).to_string(),
)),
"zib" => Ok(UntaggedValue::string(
byte.get_adjusted_unit(byte_unit::ByteUnit::ZiB).to_string(),
)),
_ => Err(ShellError::labeled_error(
format!("Invalid format code: {:}", format.item()),
"invalid format",
format.tag(),
)),
};
match value {
Ok(b) => Ok(Value { value: b, ..bytes }),
Err(e) => Err(e),
}
}
_ => Err(ShellError::labeled_error(
"the data in this row is not of the type filesize",
"invalid row type",
bytes.tag(),
)),
}
}
#[cfg(test)]
mod tests {
use super::FileSize;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(FileSize {})?)
}
}

View File

@ -0,0 +1,5 @@
pub mod command;
pub mod format_filesize;
pub use command::Format;
pub use format_filesize::FileSize;

View File

@ -175,8 +175,8 @@ async fn from_yaml(
mod tests {
use super::ShellError;
use super::*;
use nu_plugin::row;
use nu_plugin::test_helpers::value::string;
use nu_protocol::row;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -51,30 +51,30 @@ impl WholeStreamCommand for Command {
result: Some(vec![UntaggedValue::row(indexmap! {
"File".to_string() => UntaggedValue::Table(vec![
UntaggedValue::row(indexmap! {
"modified".to_string() => date("2019-07-23".tagged_unknown()).unwrap().into(),
"name".to_string() => UntaggedValue::string("Andrés.txt").into(),
"type".to_string() => UntaggedValue::string("File").into(),
"chickens".to_string() => UntaggedValue::int(10).into(),
"modified".to_string() => date("2019-07-23".tagged_unknown()).unwrap().into(),
}).into(),
UntaggedValue::row(indexmap! {
"modified".to_string() => date("2019-09-24".tagged_unknown()).unwrap().into(),
"name".to_string() => UntaggedValue::string("Andrés.txt").into(),
"type".to_string() => UntaggedValue::string("File").into(),
"chickens".to_string() => UntaggedValue::int(20).into(),
"modified".to_string() => date("2019-09-24".tagged_unknown()).unwrap().into(),
}).into(),
]).into(),
"Dir".to_string() => UntaggedValue::Table(vec![
UntaggedValue::row(indexmap! {
"modified".to_string() => date("2019-07-23".tagged_unknown()).unwrap().into(),
"name".to_string() => UntaggedValue::string("Jonathan").into(),
"type".to_string() => UntaggedValue::string("Dir").into(),
"chickens".to_string() => UntaggedValue::int(5).into(),
"modified".to_string() => date("2019-07-23".tagged_unknown()).unwrap().into(),
}).into(),
UntaggedValue::row(indexmap! {
"modified".to_string() => date("2019-09-24".tagged_unknown()).unwrap().into(),
"name".to_string() => UntaggedValue::string("Yehuda").into(),
"type".to_string() => UntaggedValue::string("Dir").into(),
"chickens".to_string() => UntaggedValue::int(4).into(),
"modified".to_string() => date("2019-09-24".tagged_unknown()).unwrap().into(),
}).into(),
]).into(),
})
@ -139,7 +139,6 @@ pub async fn group_by(
) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let registry = registry.clone();
let head = Arc::new(args.call_info.args.head.clone());
let scope = args.call_info.scope.clone();
let context = Arc::new(EvaluationContext::from_raw(&args, &registry));
let (Arguments { grouper }, input) = args.process(&registry).await?;
@ -159,12 +158,9 @@ pub async fn group_by(
for value in values.iter() {
let run = block.clone();
let scope = scope.clone();
let head = head.clone();
let context = context.clone();
match crate::commands::each::process_row(run, scope, head, context, value.clone())
.await
{
match crate::commands::each::process_row(run, scope, context, value.clone()).await {
Ok(mut s) => {
let collection: Vec<Result<ReturnSuccess, ShellError>> =
s.drain_vec().await;
@ -278,9 +274,10 @@ pub fn group(
#[cfg(test)]
mod tests {
use super::group;
use nu_data::utils::helpers::{committers, date, int, row, string, table};
use nu_data::utils::helpers::committers;
use nu_errors::ShellError;
use nu_source::*;
use nu_test_support::value::{date, int, row, string, table};
#[test]
fn groups_table_by_date_column() -> Result<(), ShellError> {

View File

@ -121,7 +121,7 @@ async fn if_command(
let then_case = then_case.clone();
let else_case = else_case.clone();
let registry = registry.clone();
let scope = Scope::append_it(scope.clone(), input);
let scope = Scope::append_var(scope.clone(), "$it", input);
let mut context = context.clone();
async move {

View File

@ -87,7 +87,7 @@ async fn process_row(
let for_block = input.clone();
let input_stream = once(async { Ok(for_block) }).to_input_stream();
let scope = Scope::append_it(scope, input.clone());
let scope = Scope::append_var(scope, "$it", input.clone());
let result = run_block(&block, Arc::make_mut(&mut context), input_stream, scope).await;
@ -140,7 +140,7 @@ async fn process_row(
value: UntaggedValue::Primitive(Primitive::Nothing),
..
} => match scope
.it()
.var("$it")
.unwrap_or_else(|| UntaggedValue::nothing().into_untagged_value())
.insert_data_at_column_path(&field, value.clone())
{

View File

@ -37,7 +37,7 @@ impl WholeStreamCommand for IntoInt {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Convert filesize to integer",
example: "echo 1kb | into-int $it | = $it / 1024",
example: "into-int 1kb | each { = $it / 1024 }",
result: Some(vec![UntaggedValue::int(1).into()]),
}]
}

View File

@ -85,7 +85,7 @@ impl WholeStreamCommand for SubCommand {
.take_while(move |item| {
let condition = condition.clone();
let registry = registry.clone();
let scope = Scope::append_it(scope.clone(), item.clone());
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
trace!("ITEM = {:?}", item);
async move {

View File

@ -84,7 +84,7 @@ impl WholeStreamCommand for SubCommand {
.take_while(move |item| {
let condition = condition.clone();
let registry = registry.clone();
let scope = Scope::append_it(scope.clone(), item.clone());
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
trace!("ITEM = {:?}", item);
async move {

View File

@ -0,0 +1,91 @@
use crate::commands::math::utils::run_with_numerical_functions_on_stream;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use bigdecimal::One;
use nu_errors::ShellError;
use nu_protocol::{Signature, UntaggedValue, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math ceil"
}
fn signature(&self) -> Signature {
Signature::build("math celi")
}
fn usage(&self) -> &str {
"Applies the ceil function to a list of numbers"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_numerical_functions_on_stream(
RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
},
ceil_big_int,
ceil_big_decimal,
ceil_default,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Apply the ceil function to a list of numbers",
example: "echo [1.5 2.3 -3.1] | math ceil",
result: Some(vec![
UntaggedValue::int(2).into(),
UntaggedValue::int(3).into(),
UntaggedValue::int(-3).into(),
]),
}]
}
}
fn ceil_big_int(val: BigInt) -> Value {
UntaggedValue::int(val).into()
}
fn ceil_big_decimal(val: BigDecimal) -> Value {
let mut maybe_ceiled = val.round(0);
if maybe_ceiled < val {
maybe_ceiled += BigDecimal::one();
}
let (ceiled, _) = maybe_ceiled.into_bigint_and_exponent();
UntaggedValue::int(ceiled).into()
}
fn ceil_default(_: UntaggedValue) -> Value {
UntaggedValue::Error(ShellError::unexpected(
"Only numerical values are supported",
))
.into()
}
#[cfg(test)]
mod tests {
use super::ShellError;
use super::SubCommand;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(SubCommand {})?)
}
}

View File

@ -38,9 +38,8 @@ mod tests {
avg::average, max::maximum, median::median, min::minimum, mode::mode, stddev::stddev,
sum::summation, utils::calculate, utils::MathFunction, variance::variance,
};
use nu_plugin::row;
use nu_plugin::test_helpers::value::{decimal, decimal_from_float, int, table};
use nu_protocol::Value;
use nu_protocol::{row, Value};
use nu_test_support::value::{decimal, decimal_from_float, int, table};
use std::str::FromStr;
#[test]
@ -71,14 +70,14 @@ mod tests {
values: vec![int(10)],
expected_err: None,
expected_res: vec![
Ok(decimal(10)),
Ok(decimal_from_float(10.0)),
Ok(int(10)),
Ok(int(10)),
Ok(int(10)),
Ok(table(&[int(10)])),
Ok(decimal(0)),
Ok(decimal_from_float(0.0)),
Ok(int(10)),
Ok(decimal(0)),
Ok(decimal_from_float(0.0)),
],
},
TestCase {
@ -86,7 +85,7 @@ mod tests {
values: vec![int(10), int(20), int(30)],
expected_err: None,
expected_res: vec![
Ok(decimal(20)),
Ok(decimal_from_float(20.0)),
Ok(int(10)),
Ok(int(30)),
Ok(int(20)),
@ -101,13 +100,13 @@ mod tests {
values: vec![int(10), decimal_from_float(26.5), decimal_from_float(26.5)],
expected_err: None,
expected_res: vec![
Ok(decimal(21)),
Ok(decimal_from_float(21.0)),
Ok(int(10)),
Ok(decimal_from_float(26.5)),
Ok(decimal_from_float(26.5)),
Ok(table(&[decimal_from_float(26.5)])),
Ok(decimal(BigDecimal::from_str("7.77817459305202276840928798315333943213319531457321440247173855894902863154158871367713143880202865").expect("Could not convert to decimal from string"))),
Ok(decimal(63)),
Ok(decimal_from_float(63.0)),
Ok(decimal_from_float(60.5)),
],
},
@ -116,14 +115,14 @@ mod tests {
values: vec![int(-14), int(-11), int(10)],
expected_err: None,
expected_res: vec![
Ok(decimal(-5)),
Ok(decimal_from_float(-5.0)),
Ok(int(-14)),
Ok(int(10)),
Ok(int(-11)),
Ok(table(&[int(-14), int(-11), int(10)])),
Ok(decimal(BigDecimal::from_str("10.67707825203131121081152396559571062628228776946058011397810604284900898365140801704064843595778374").expect("Could not convert to decimal from string"))),
Ok(int(-15)),
Ok(decimal(114)),
Ok(decimal_from_float(114.0)),
],
},
TestCase {
@ -131,13 +130,13 @@ mod tests {
values: vec![decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)],
expected_err: None,
expected_res: vec![
Ok(decimal(-5)),
Ok(decimal_from_float(-5.0)),
Ok(decimal_from_float(-13.5)),
Ok(int(10)),
Ok(decimal_from_float(-11.5)),
Ok(table(&[decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)])),
Ok(decimal(BigDecimal::from_str("10.63798226482196513098036125801342585449179971588207816421068645273754903468375890632981926875247027").expect("Could not convert to decimal from string"))),
Ok(decimal(-15)),
Ok(decimal_from_float(-15.0)),
Ok(decimal(BigDecimal::from_str("113.1666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666667").expect("Could not convert to decimal from string"))),
],
},

View File

@ -91,7 +91,9 @@ pub async fn eval(
}
pub fn parse<T: Into<Tag>>(math_expression: &str, tag: T) -> Result<Value, String> {
match meval::eval_str(math_expression) {
let mut ctx = meval::Context::new();
ctx.var("tau", std::f64::consts::TAU);
match meval::eval_str_with_context(math_expression, &ctx) {
Ok(num) if num.is_infinite() || num.is_nan() => Err("cannot represent result".to_string()),
Ok(num) => Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag)),
Err(error) => Err(error.to_string().to_lowercase()),

View File

@ -0,0 +1,91 @@
use crate::commands::math::utils::run_with_numerical_functions_on_stream;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use bigdecimal::One;
use nu_errors::ShellError;
use nu_protocol::{Signature, UntaggedValue, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math floor"
}
fn signature(&self) -> Signature {
Signature::build("math floor")
}
fn usage(&self) -> &str {
"Applies the floor function to a list of numbers"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_numerical_functions_on_stream(
RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
},
floor_big_int,
floor_big_decimal,
floor_default,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Apply the floor function to a list of numbers",
example: "echo [1.5 2.3 -3.1] | math floor",
result: Some(vec![
UntaggedValue::int(1).into(),
UntaggedValue::int(2).into(),
UntaggedValue::int(-4).into(),
]),
}]
}
}
fn floor_big_int(val: BigInt) -> Value {
UntaggedValue::int(val).into()
}
fn floor_big_decimal(val: BigDecimal) -> Value {
let mut maybe_floored = val.round(0);
if maybe_floored > val {
maybe_floored -= BigDecimal::one();
}
let (floored, _) = maybe_floored.into_bigint_and_exponent();
UntaggedValue::int(floored).into()
}
fn floor_default(_: UntaggedValue) -> Value {
UntaggedValue::Error(ShellError::unexpected(
"Only numerical values are supported",
))
.into()
}
#[cfg(test)]
mod tests {
use super::ShellError;
use super::SubCommand;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(SubCommand {})?)
}
}

View File

@ -1,11 +1,14 @@
pub mod avg;
pub mod ceil;
pub mod command;
pub mod eval;
pub mod floor;
pub mod max;
pub mod median;
pub mod min;
pub mod mode;
pub mod product;
pub mod round;
pub mod stddev;
pub mod sum;
pub mod variance;
@ -14,13 +17,16 @@ mod reducers;
mod utils;
pub use avg::SubCommand as MathAverage;
pub use ceil::SubCommand as MathCeil;
pub use command::Command as Math;
pub use eval::SubCommand as MathEval;
pub use floor::SubCommand as MathFloor;
pub use max::SubCommand as MathMaximum;
pub use median::SubCommand as MathMedian;
pub use min::SubCommand as MathMinimum;
pub use mode::SubCommand as MathMode;
pub use product::SubCommand as MathProduct;
pub use round::SubCommand as MathRound;
pub use stddev::SubCommand as MathStddev;
pub use sum::SubCommand as MathSummation;
pub use variance::SubCommand as MathVariance;

View File

@ -0,0 +1,111 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct SubCommand;
#[derive(Deserialize)]
struct Arguments {
precision: Option<Tagged<i64>>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math round"
}
fn signature(&self) -> Signature {
Signature::build("math round").named(
"precision",
SyntaxShape::Number,
"digits of precision",
Some('p'),
)
}
fn usage(&self) -> &str {
"Applies the round function to a list of numbers"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Apply the round function to a list of numbers",
example: "echo [1.5 2.3 -3.1] | math round",
result: Some(vec![
UntaggedValue::int(2).into(),
UntaggedValue::int(2).into(),
UntaggedValue::int(-3).into(),
]),
},
Example {
description: "Apply the round function with precision specified",
example: "echo [1.555 2.333 -3.111] | math round -p 2",
result: Some(vec![
UntaggedValue::decimal_from_float(1.56, Span::default()).into(),
UntaggedValue::decimal_from_float(2.33, Span::default()).into(),
UntaggedValue::decimal_from_float(-3.11, Span::default()).into(),
]),
},
]
}
}
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let (Arguments { precision }, input) = args.process(&registry).await?;
let precision = precision.map(|p| p.item).unwrap_or(0);
let mapped = input.map(move |val| match val.value {
UntaggedValue::Primitive(Primitive::Int(val)) => round_big_int(val),
UntaggedValue::Primitive(Primitive::Decimal(val)) => round_big_decimal(val, precision),
other => round_default(other),
});
Ok(OutputStream::from_input(mapped))
}
fn round_big_int(val: BigInt) -> Value {
UntaggedValue::int(val).into()
}
fn round_big_decimal(val: BigDecimal, precision: i64) -> Value {
if precision > 0 {
UntaggedValue::decimal(val.round(precision)).into()
} else {
let (rounded, _) = val.round(precision).as_bigint_and_exponent();
UntaggedValue::int(rounded).into()
}
}
fn round_default(_: UntaggedValue) -> Value {
UntaggedValue::Error(ShellError::unexpected(
"Only numerical values are supported",
))
.into()
}
#[cfg(test)]
mod tests {
use super::ShellError;
use super::SubCommand;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(SubCommand {})?)
}
}

View File

@ -1,6 +1,6 @@
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Dictionary, ReturnSuccess, UntaggedValue, Value};
use nu_protocol::{Dictionary, Primitive, ReturnSuccess, UntaggedValue, Value};
use indexmap::map::IndexMap;
@ -31,6 +31,26 @@ pub async fn run_with_function(
}
}
pub type IntFunction = fn(val: BigInt) -> Value;
pub type DecimalFunction = fn(val: BigDecimal) -> Value;
pub type DefaultFunction = fn(val: UntaggedValue) -> Value;
pub async fn run_with_numerical_functions_on_stream(
RunnableContext { input, .. }: RunnableContext,
int_function: IntFunction,
decimal_function: DecimalFunction,
default_function: DefaultFunction,
) -> Result<OutputStream, ShellError> {
let mapped = input.map(move |val| match val.value {
UntaggedValue::Primitive(Primitive::Int(val)) => int_function(val),
UntaggedValue::Primitive(Primitive::Decimal(val)) => decimal_function(val),
other => default_function(other),
});
Ok(OutputStream::from_input(mapped))
}
pub fn calculate(values: &[Value], name: &Tag, mf: MathFunction) -> Result<Value, ShellError> {
if values.iter().all(|v| v.is_primitive()) {
mf(&values, &name)

View File

@ -1,328 +0,0 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_data::base::select_fields;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, Value};
use nu_source::HasFallibleSpan;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct Arguments {
rest: Vec<ColumnPath>,
after: Option<ColumnPath>,
before: Option<ColumnPath>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"move column"
}
fn signature(&self) -> Signature {
Signature::build("move column")
.rest(SyntaxShape::ColumnPath, "the columns to move")
.named(
"after",
SyntaxShape::ColumnPath,
"the column that will precede the columns moved",
None,
)
.named(
"before",
SyntaxShape::ColumnPath,
"the column that will be next the columns moved",
None,
)
}
fn usage(&self) -> &str {
"Move columns."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry).await
}
}
async fn operate(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name = raw_args.call_info.name_tag.clone();
let registry = registry.clone();
let (
Arguments {
rest: mut columns,
before,
after,
},
input,
) = raw_args.process(&registry).await?;
if columns.is_empty() {
return Err(ShellError::labeled_error(
"expected columns",
"expected columns",
name,
));
}
if columns.iter().any(|c| c.members().len() > 1) {
return Err(ShellError::labeled_error(
"expected columns",
"expected columns",
name,
));
}
if vec![&after, &before]
.iter()
.map(|o| if o.is_some() { 1 } else { 0 })
.sum::<usize>()
> 1
{
return Err(ShellError::labeled_error(
"can't move column(s)",
"pick exactly one (before, after)",
name,
));
}
if let Some(after) = after {
let member = columns.remove(0);
Ok(input
.map(move |item| {
let member = vec![member.clone()];
let column_paths = vec![&member, &columns]
.into_iter()
.flatten()
.collect::<Vec<&ColumnPath>>();
let after_span = after.maybe_span().unwrap_or_else(Span::unknown);
if after.members().len() == 1 {
let keys = column_paths
.iter()
.filter_map(|c| c.last())
.map(|c| c.as_string())
.collect::<Vec<_>>();
if let Some(column) = after.last() {
if !keys.contains(&column.as_string()) {
ReturnSuccess::value(move_after(&item, &keys, &after, &name)?)
} else {
let msg =
format!("can't move column {} after itself", column.as_string());
Err(ShellError::labeled_error(
"can't move column",
msg,
after_span,
))
}
} else {
Err(ShellError::labeled_error(
"expected column",
"expected column",
after_span,
))
}
} else {
Err(ShellError::labeled_error(
"expected column",
"expected column",
after_span,
))
}
})
.to_output_stream())
} else if let Some(before) = before {
let member = columns.remove(0);
Ok(input
.map(move |item| {
let member = vec![member.clone()];
let column_paths = vec![&member, &columns]
.into_iter()
.flatten()
.collect::<Vec<&ColumnPath>>();
let before_span = before.maybe_span().unwrap_or_else(Span::unknown);
if before.members().len() == 1 {
let keys = column_paths
.iter()
.filter_map(|c| c.last())
.map(|c| c.as_string())
.collect::<Vec<_>>();
if let Some(column) = before.last() {
if !keys.contains(&column.as_string()) {
ReturnSuccess::value(move_before(&item, &keys, &before, &name)?)
} else {
let msg =
format!("can't move column {} before itself", column.as_string());
Err(ShellError::labeled_error(
"can't move column",
msg,
before_span,
))
}
} else {
Err(ShellError::labeled_error(
"expected column",
"expected column",
before_span,
))
}
} else {
Err(ShellError::labeled_error(
"expected column",
"expected column",
before_span,
))
}
})
.to_output_stream())
} else {
Err(ShellError::labeled_error(
"no columns given",
"no columns given",
name,
))
}
}
fn move_after(
table: &Value,
columns: &[String],
from: &ColumnPath,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let tag = tag.into();
let from_fields = from.maybe_span().unwrap_or_else(Span::unknown);
let from = if let Some((last, _)) = from.split_last() {
last.as_string()
} else {
return Err(ShellError::labeled_error(
"unknown column",
"unknown column",
from_fields,
));
};
let columns_moved = table.data_descriptors().into_iter().map(|name| {
if columns.contains(&name) {
None
} else {
Some(name)
}
});
let mut reordered_columns = vec![];
let mut insert = false;
let mut inserted = false;
for name in columns_moved.into_iter() {
if let Some(name) = name {
reordered_columns.push(Some(name.clone()));
if !inserted && name == from {
insert = true;
}
} else {
reordered_columns.push(None);
}
if insert {
for column in columns {
reordered_columns.push(Some(column.clone()));
}
inserted = true;
}
}
Ok(select_fields(
table,
&reordered_columns
.into_iter()
.filter_map(|v| v)
.collect::<Vec<_>>(),
&tag,
))
}
fn move_before(
table: &Value,
columns: &[String],
from: &ColumnPath,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let tag = tag.into();
let from_fields = from.maybe_span().unwrap_or_else(Span::unknown);
let from = if let Some((last, _)) = from.split_last() {
last.as_string()
} else {
return Err(ShellError::labeled_error(
"unknown column",
"unknown column",
from_fields,
));
};
let columns_moved = table.data_descriptors().into_iter().map(|name| {
if columns.contains(&name) {
None
} else {
Some(name)
}
});
let mut reordered_columns = vec![];
let mut inserted = false;
for name in columns_moved.into_iter() {
if let Some(name) = name {
if !inserted && name == from {
for column in columns {
reordered_columns.push(Some(column.clone()));
}
inserted = true;
}
reordered_columns.push(Some(name.clone()));
} else {
reordered_columns.push(None);
}
}
Ok(select_fields(
table,
&reordered_columns
.into_iter()
.filter_map(|v| v)
.collect::<Vec<_>>(),
&tag,
))
}
#[cfg(test)]
mod tests {
use super::ShellError;
use super::SubCommand;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(SubCommand {})?)
}
}

View File

@ -1,11 +1,20 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_data::base::select_fields;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, Value};
use nu_source::HasFallibleSpan;
#[derive(Clone)]
pub struct Command;
#[derive(Deserialize)]
pub struct Arguments {
rest: Vec<ColumnPath>,
after: Option<ColumnPath>,
before: Option<ColumnPath>,
}
#[async_trait]
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
@ -14,34 +23,318 @@ impl WholeStreamCommand for Command {
fn signature(&self) -> Signature {
Signature::build("move")
.rest(SyntaxShape::ColumnPath, "the columns to move")
.named(
"after",
SyntaxShape::ColumnPath,
"the column that will precede the columns moved",
None,
)
.named(
"before",
SyntaxShape::ColumnPath,
"the column that will be next the columns moved",
None,
)
}
fn usage(&self) -> &str {
"Moves across desired subcommand."
"Move columns."
}
async fn run(
&self,
_args: CommandArgs,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(crate::commands::help::get_help(&Command, &registry))
.into_value(Tag::unknown()),
))))
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
use nu_test_support::value::*;
vec![
Example {
description: "Move the column \"type\" before the column \"name\"",
example: r#"ls | move type --before name | first"#,
result: Some(vec![row! {
"type".into() => string("File"),
"name".into() => string("Andrés.txt"),
"chickens".into() => int(10),
"modified".into() => date("2019-07-23")
}]),
},
Example {
description: "or move the column \"chickens\" after \"name\"",
example: r#"ls | move chickens --after name | first"#,
result: Some(vec![row! {
"name".into() => string("Andrés.txt"),
"chickens".into() => int(10),
"type".into() => string("File"),
"modified".into() => date("2019-07-23")
}]),
},
Example {
description: "you can selectively move many columns in either direction",
example: r#"ls | move name chickens --after type | first"#,
result: Some(vec![row! {
"type".into() => string("File"),
"name".into() => string("Andrés.txt"),
"chickens".into() => int(10),
"modified".into() => date("2019-07-23")
}]),
},
]
}
}
#[cfg(test)]
mod tests {
use super::Command;
use super::ShellError;
async fn operate(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name = raw_args.call_info.name_tag.clone();
let registry = registry.clone();
let (
Arguments {
rest: mut columns,
before,
after,
},
input,
) = raw_args.process(&registry).await?;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
if columns.is_empty() {
return Err(ShellError::labeled_error(
"expected columns",
"expected columns",
name,
));
}
Ok(test_examples(Command {})?)
if columns.iter().any(|c| c.members().len() > 1) {
return Err(ShellError::labeled_error(
"expected columns",
"expected columns",
name,
));
}
if vec![&after, &before]
.iter()
.map(|o| if o.is_some() { 1 } else { 0 })
.sum::<usize>()
> 1
{
return Err(ShellError::labeled_error(
"can't move column(s)",
"pick exactly one (before, after)",
name,
));
}
if let Some(after) = after {
let member = columns.remove(0);
Ok(input
.map(move |item| {
let member = vec![member.clone()];
let column_paths = vec![&member, &columns]
.into_iter()
.flatten()
.collect::<Vec<&ColumnPath>>();
let after_span = after.maybe_span().unwrap_or_else(Span::unknown);
if after.members().len() == 1 {
let keys = column_paths
.iter()
.filter_map(|c| c.last())
.map(|c| c.as_string())
.collect::<Vec<_>>();
if let Some(column) = after.last() {
if !keys.contains(&column.as_string()) {
ReturnSuccess::value(move_after(&item, &keys, &after)?)
} else {
let msg =
format!("can't move column {} after itself", column.as_string());
Err(ShellError::labeled_error(
"can't move column",
msg,
after_span,
))
}
} else {
Err(ShellError::labeled_error(
"expected column",
"expected column",
after_span,
))
}
} else {
Err(ShellError::labeled_error(
"expected column",
"expected column",
after_span,
))
}
})
.to_output_stream())
} else if let Some(before) = before {
let member = columns.remove(0);
Ok(input
.map(move |item| {
let member = vec![member.clone()];
let column_paths = vec![&member, &columns]
.into_iter()
.flatten()
.collect::<Vec<&ColumnPath>>();
let before_span = before.maybe_span().unwrap_or_else(Span::unknown);
if before.members().len() == 1 {
let keys = column_paths
.iter()
.filter_map(|c| c.last())
.map(|c| c.as_string())
.collect::<Vec<_>>();
if let Some(column) = before.last() {
if !keys.contains(&column.as_string()) {
ReturnSuccess::value(move_before(&item, &keys, &before)?)
} else {
let msg =
format!("can't move column {} before itself", column.as_string());
Err(ShellError::labeled_error(
"can't move column",
msg,
before_span,
))
}
} else {
Err(ShellError::labeled_error(
"expected column",
"expected column",
before_span,
))
}
} else {
Err(ShellError::labeled_error(
"expected column",
"expected column",
before_span,
))
}
})
.to_output_stream())
} else {
Err(ShellError::labeled_error(
"no columns given",
"no columns given",
name,
))
}
}
fn move_after(table: &Value, columns: &[String], from: &ColumnPath) -> Result<Value, ShellError> {
let from_fields = from.maybe_span().unwrap_or_else(Span::unknown);
let from = if let Some((last, _)) = from.split_last() {
last.as_string()
} else {
return Err(ShellError::labeled_error(
"unknown column",
"unknown column",
from_fields,
));
};
let columns_moved = table.data_descriptors().into_iter().map(|name| {
if columns.contains(&name) {
None
} else {
Some(name)
}
});
let mut reordered_columns = vec![];
let mut insert = false;
let mut inserted = false;
for name in columns_moved.into_iter() {
if let Some(name) = name {
reordered_columns.push(Some(name.clone()));
if !inserted && name == from {
insert = true;
}
} else {
reordered_columns.push(None);
}
if insert {
for column in columns {
reordered_columns.push(Some(column.clone()));
}
inserted = true;
}
}
Ok(select_fields(
table,
&reordered_columns
.into_iter()
.filter_map(|v| v)
.collect::<Vec<_>>(),
&table.tag,
))
}
fn move_before(table: &Value, columns: &[String], from: &ColumnPath) -> Result<Value, ShellError> {
let from_fields = from.maybe_span().unwrap_or_else(Span::unknown);
let from = if let Some((last, _)) = from.split_last() {
last.as_string()
} else {
return Err(ShellError::labeled_error(
"unknown column",
"unknown column",
from_fields,
));
};
let columns_moved = table.data_descriptors().into_iter().map(|name| {
if columns.contains(&name) {
None
} else {
Some(name)
}
});
let mut reordered_columns = vec![];
let mut inserted = false;
for name in columns_moved.into_iter() {
if let Some(name) = name {
if !inserted && name == from {
for column in columns {
reordered_columns.push(Some(column.clone()));
}
inserted = true;
}
reordered_columns.push(Some(name.clone()));
} else {
reordered_columns.push(None);
}
}
Ok(select_fields(
table,
&reordered_columns
.into_iter()
.filter_map(|v| v)
.collect::<Vec<_>>(),
&table.tag,
))
}

View File

@ -1,7 +1,5 @@
mod column;
mod command;
pub mod mv;
pub use column::SubCommand as MoveColumn;
pub use command::Command as Move;
pub use mv::Mv;

View File

@ -213,7 +213,8 @@ pub async fn fetch(
)),
};
let res = std::fs::read(location)?;
let res = std::fs::read(location)
.map_err(|_| ShellError::labeled_error("Can't open filename given", "can't open", span))?;
// If no encoding is provided we try to guess the encoding to read the file with
let encoding = if encoding_choice.is_none() {

View File

@ -87,7 +87,7 @@ async fn process_row(
let row_clone = row.clone();
let input_stream = once(async { Ok(row_clone) }).to_input_stream();
let scope = Scope::append_it(scope, row);
let scope = Scope::append_var(scope, "$it", row);
Ok(run_block(&block, Arc::make_mut(&mut context), input_stream, scope).await?)
}
@ -145,7 +145,7 @@ async fn reduce(
UntaggedValue::table(&values).into_untagged_value()
};
let scope = Scope::append_var(scope, "$acc".into(), f);
let scope = Scope::append_var(scope, "$acc", f);
process_row(block, scope, context, row).await
}
})
@ -173,7 +173,7 @@ async fn reduce(
UntaggedValue::table(&values).into_untagged_value()
};
let scope = Scope::append_var(scope, "$acc".into(), f);
let scope = Scope::append_var(scope, "$acc", f);
process_row(block, scope, context, row).await
}
})

View File

@ -4,29 +4,22 @@ use crate::prelude::*;
use derive_new::new;
use nu_errors::ShellError;
use nu_protocol::{hir::Block, Scope, Signature, SyntaxShape, UntaggedValue};
use nu_protocol::{hir::Block, PositionalType, Scope, Signature, UntaggedValue};
#[derive(new, Clone)]
pub struct AliasCommand {
name: String,
args: Vec<(String, SyntaxShape)>,
sig: Signature,
block: Block,
}
#[async_trait]
impl WholeStreamCommand for AliasCommand {
fn name(&self) -> &str {
&self.name
&self.sig.name
}
fn signature(&self) -> Signature {
let mut alias = Signature::build(&self.name);
for (arg, shape) in &self.args {
alias = alias.optional(arg, *shape, "");
}
alias
self.sig.clone()
}
fn usage(&self) -> &str {
@ -43,7 +36,7 @@ impl WholeStreamCommand for AliasCommand {
let mut block = self.block.clone();
block.set_redirect(call_info.args.external_redirection);
let alias_command = self.clone();
// let alias_command = self.clone();
let mut context = EvaluationContext::from_args(&args, &registry);
let input = args.input;
@ -51,21 +44,27 @@ impl WholeStreamCommand for AliasCommand {
let evaluated = call_info.evaluate(&registry).await?;
let mut vars = IndexMap::new();
let mut num_positionals = 0;
if let Some(positional) = &evaluated.args.positional {
num_positionals = positional.len();
for (pos, arg) in positional.iter().enumerate() {
vars.insert(alias_command.args[pos].0.to_string(), arg.clone());
for (idx, arg) in positional.iter().enumerate() {
let pos_type = &self.sig.positional[idx].0;
match pos_type {
PositionalType::Mandatory(name, _) | PositionalType::Optional(name, _) => {
vars.insert(name.clone(), arg.clone());
}
}
}
}
if alias_command.args.len() > num_positionals {
for idx in 0..(alias_command.args.len() - num_positionals) {
vars.insert(
alias_command.args[idx + num_positionals].0.to_string(),
UntaggedValue::nothing().into_untagged_value(),
);
//Fill out every missing argument with empty value
if self.sig.positional.len() > num_positionals {
for idx in num_positionals..self.sig.positional.len() {
let pos_type = &self.sig.positional[idx].0;
match pos_type {
PositionalType::Mandatory(name, _) | PositionalType::Optional(name, _) => {
vars.insert(name.clone(), UntaggedValue::nothing().into_untagged_value());
}
}
}
}

View File

@ -0,0 +1,327 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::value::StrExt;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use std::cmp;
pub struct Seq;
#[derive(Deserialize)]
pub struct SeqArgs {
rest: Vec<Tagged<f64>>,
separator: Option<Tagged<String>>,
terminator: Option<Tagged<String>>,
widths: Tagged<bool>,
}
#[async_trait]
impl WholeStreamCommand for Seq {
fn name(&self) -> &str {
"seq"
}
fn signature(&self) -> Signature {
Signature::build("seq")
.rest(SyntaxShape::Number, "sequence values")
.named(
"separator",
SyntaxShape::String,
"separator character (defaults to \\n)",
Some('s'),
)
.named(
"terminator",
SyntaxShape::String,
"terminator character (defaults to \\n)",
Some('t'),
)
.switch(
"widths",
"equalize widths of all numbers by padding with zeros",
Some('w'),
)
}
fn usage(&self) -> &str {
"print sequences of numbers"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
seq(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "sequence 1 to 10 with newline separator",
example: "seq 1 10",
result: Some(vec![
UntaggedValue::string("1").into(),
UntaggedValue::string("2").into(),
UntaggedValue::string("3").into(),
UntaggedValue::string("4").into(),
UntaggedValue::string("5").into(),
UntaggedValue::string("6").into(),
UntaggedValue::string("7").into(),
UntaggedValue::string("8").into(),
UntaggedValue::string("9").into(),
UntaggedValue::string("10").into(),
]),
},
Example {
description: "sequence 1 to 10 with pipe separator",
example: "seq -s '|' 1 10",
result: Some(vec![Value::from("1|2|3|4|5|6|7|8|9|10")]),
},
Example {
description: "sequence 1 to 10 with pipe separator padded with 0",
example: "seq -s '|' -w 1 10",
result: Some(vec![Value::from("01|02|03|04|05|06|07|08|09|10")]),
},
Example {
description: "sequence 1 to 10 with pipe separator padded by 2s",
example: "seq -s ' | ' -w 1 2 10",
result: Some(vec![Value::from("01 | 03 | 05 | 07 | 09")]),
},
]
}
}
async fn seq(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let (
SeqArgs {
rest: rest_nums,
separator,
terminator,
widths,
},
_,
) = args.process(&registry).await?;
if rest_nums.is_empty() {
return Err(ShellError::labeled_error(
"seq requires some parameters",
"needs parameter",
name,
));
}
let sep: String = match separator {
Some(s) => {
if s.item == r"\t" {
'\t'.to_string()
} else if s.item == r"\n" {
'\n'.to_string()
} else if s.item == r"\r" {
'\r'.to_string()
} else {
let vec_s: Vec<char> = s.chars().collect();
if vec_s.is_empty() {
return Err(ShellError::labeled_error(
"Expected a single separator char from --separator",
"requires a single character string input",
&s.tag,
));
};
vec_s.iter().collect()
}
}
_ => '\n'.to_string(),
};
let term: String = match terminator {
Some(t) => {
if t.item == r"\t" {
'\t'.to_string()
} else if t.item == r"\n" {
'\n'.to_string()
} else if t.item == r"\r" {
'\r'.to_string()
} else {
let vec_t: Vec<char> = t.chars().collect();
if vec_t.is_empty() {
return Err(ShellError::labeled_error(
"Expected a single terminator char from --terminator",
"requires a single character string input",
&t.tag,
));
};
vec_t.iter().collect()
}
}
_ => '\n'.to_string(),
};
let rest_nums: Vec<String> = rest_nums.iter().map(|n| n.item.to_string()).collect();
run_seq(sep, Some(term), widths.item, rest_nums)
}
#[cfg(test)]
mod tests {
use super::Seq;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(Seq {})?)
}
}
fn parse_float(mut s: &str) -> Result<f64, String> {
if s.starts_with('+') {
s = &s[1..];
}
match s.parse() {
Ok(n) => Ok(n),
Err(e) => Err(format!(
"seq: invalid floating point argument `{}`: {}",
s, e
)),
}
}
fn escape_sequences(s: &str) -> String {
s.replace("\\n", "\n").replace("\\t", "\t")
}
pub fn run_seq(
sep: String,
termy: Option<String>,
widths: bool,
free: Vec<String>,
) -> Result<OutputStream, ShellError> {
let mut largest_dec = 0;
let mut padding = 0;
let first = if free.len() > 1 {
let slice = &free[0][..];
let len = slice.len();
let dec = slice.find('.').unwrap_or(len);
largest_dec = len - dec;
padding = dec;
match parse_float(slice) {
Ok(n) => n,
Err(s) => {
return Err(ShellError::labeled_error(
s,
"error parsing float",
Tag::unknown(),
));
}
}
} else {
1.0
};
let step = if free.len() > 2 {
let slice = &free[1][..];
let len = slice.len();
let dec = slice.find('.').unwrap_or(len);
largest_dec = cmp::max(largest_dec, len - dec);
padding = cmp::max(padding, dec);
match parse_float(slice) {
Ok(n) => n,
Err(s) => {
return Err(ShellError::labeled_error(
s,
"error parsing float",
Tag::unknown(),
));
}
}
} else {
1.0
};
let last = {
let slice = &free[free.len() - 1][..];
padding = cmp::max(padding, slice.find('.').unwrap_or_else(|| slice.len()));
match parse_float(slice) {
Ok(n) => n,
Err(s) => {
return Err(ShellError::labeled_error(
s,
"error parsing float",
Tag::unknown(),
));
}
}
};
if largest_dec > 0 {
largest_dec -= 1;
}
let separator = escape_sequences(&sep[..]);
let terminator = match termy {
Some(term) => escape_sequences(&term[..]),
None => separator.clone(),
};
print_seq(
first,
step,
last,
largest_dec,
separator,
terminator,
widths,
padding,
)
}
fn done_printing(next: f64, step: f64, last: f64) -> bool {
if step >= 0f64 {
next > last
} else {
next < last
}
}
#[allow(clippy::too_many_arguments)]
fn print_seq(
first: f64,
step: f64,
last: f64,
largest_dec: usize,
separator: String,
terminator: String,
pad: bool,
padding: usize,
) -> Result<OutputStream, ShellError> {
let mut i = 0isize;
let mut value = first + i as f64 * step;
let mut ret_str = "".to_owned();
while !done_printing(value, step, last) {
let istr = format!("{:.*}", largest_dec, value);
let ilen = istr.len();
let before_dec = istr.find('.').unwrap_or(ilen);
if pad && before_dec < padding {
for _ in 0..(padding - before_dec) {
ret_str.push_str("0");
}
}
ret_str.push_str(&istr);
i += 1;
value = first + i as f64 * step;
if !done_printing(value, step, last) {
ret_str.push_str(&separator);
}
}
if (first >= last && step < 0f64) || (first <= last && step > 0f64) {
ret_str.push_str(&terminator);
}
let rows: Vec<Value> = ret_str
.lines()
.map(|v| v.to_str_value_create_tag())
.collect();
Ok(futures::stream::iter(rows.into_iter().map(ReturnSuccess::value)).to_output_stream())
}

View File

@ -84,7 +84,7 @@ impl WholeStreamCommand for SubCommand {
.skip_while(move |item| {
let condition = condition.clone();
let registry = registry.clone();
let scope = Scope::append_it(scope.clone(), item.clone());
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
trace!("ITEM = {:?}", item);
async move {

View File

@ -85,7 +85,7 @@ impl WholeStreamCommand for SubCommand {
let item = item.clone();
let condition = condition.clone();
let registry = registry.clone();
let scope = Scope::append_it(scope.clone(), item.clone());
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
trace!("ITEM = {:?}", item);
async move {

View File

@ -101,9 +101,10 @@ pub fn split(
mod tests {
use super::split;
use super::ShellError;
use nu_data::utils::helpers::{committers_grouped_by_date, date, int, row, string, table};
use nu_data::utils::helpers::committers_grouped_by_date;
use nu_protocol::UntaggedValue;
use nu_source::*;
use nu_test_support::value::{date, int, row, string, table};
#[test]
fn splits_inner_tables_by_key() {

View File

@ -112,8 +112,8 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
mod tests {
use super::ShellError;
use super::{action, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -46,8 +46,8 @@ mod tests {
use super::ShellError;
use super::{to_camel_case, SubCommand};
use crate::commands::str_::case::action;
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -46,8 +46,8 @@ mod tests {
use super::ShellError;
use super::{to_kebab_case, SubCommand};
use crate::commands::str_::case::action;
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -46,8 +46,8 @@ mod tests {
use super::ShellError;
use super::{to_pascal_case, SubCommand};
use crate::commands::str_::case::action;
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -46,8 +46,8 @@ mod tests {
use super::ShellError;
use super::{to_screaming_snake_case, SubCommand};
use crate::commands::str_::case::action;
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -46,8 +46,8 @@ mod tests {
use super::ShellError;
use super::{to_snake_case, SubCommand};
use crate::commands::str_::case::action;
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -130,9 +130,9 @@ fn action(
mod tests {
use super::ShellError;
use super::{action, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_protocol::UntaggedValue;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -100,8 +100,8 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
mod tests {
use super::ShellError;
use super::{action, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -104,9 +104,9 @@ fn action(input: &Value, pattern: &str, tag: impl Into<Tag>) -> Result<Value, Sh
mod tests {
use super::ShellError;
use super::{action, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_protocol::UntaggedValue;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -149,8 +149,8 @@ fn action(
mod tests {
use super::ShellError;
use super::{action, FindReplace, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -248,9 +248,9 @@ fn process_range(input: &Value, range: &Value) -> Result<IndexOfOptionalBounds,
mod tests {
use super::ShellError;
use super::{action, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_protocol::{Primitive, UntaggedValue};
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -145,9 +145,9 @@ fn action(
mod tests {
use super::{action, SubCommand};
use nu_errors::ShellError;
use nu_plugin::test_helpers::value::string;
use nu_protocol::UntaggedValue;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -145,9 +145,9 @@ fn action(
mod tests {
use super::{action, SubCommand};
use nu_errors::ShellError;
use nu_plugin::test_helpers::value::string;
use nu_protocol::UntaggedValue;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -101,8 +101,8 @@ fn action(_input: &Value, options: &Replace, tag: impl Into<Tag>) -> Result<Valu
mod tests {
use super::ShellError;
use super::{action, Replace, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -104,9 +104,9 @@ fn action(input: &Value, pattern: &str, tag: impl Into<Tag>) -> Result<Value, Sh
mod tests {
use super::ShellError;
use super::{action, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_protocol::UntaggedValue;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -285,8 +285,8 @@ fn process_arguments(range: Value, name: impl Into<Tag>) -> Result<(isize, isize
mod tests {
use super::ShellError;
use super::{action, SubCommand, Substring};
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -176,9 +176,9 @@ fn action(
mod tests {
use super::ShellError;
use super::{action, DatetimeFormat, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_protocol::{Primitive, UntaggedValue};
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -114,8 +114,8 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
mod tests {
use super::ShellError;
use super::{action, SubCommand};
use nu_plugin::test_helpers::value::{decimal_from_float, string};
use nu_source::Tag;
use nu_test_support::value::{decimal_from_float, string};
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -5,15 +5,16 @@ use nu_protocol::ShellTypeName;
use nu_protocol::{
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::Tag;
use nu_source::{Tag, Tagged};
use nu_value_ext::ValueExt;
use num_bigint::BigInt;
use std::str::FromStr;
use num_traits::Num;
#[derive(Deserialize)]
struct Arguments {
rest: Vec<ColumnPath>,
radix: Option<Tagged<u32>>,
}
pub struct SubCommand;
@ -25,10 +26,12 @@ impl WholeStreamCommand for SubCommand {
}
fn signature(&self) -> Signature {
Signature::build("str to-int").rest(
SyntaxShape::ColumnPath,
"optionally convert text into integer by column paths",
)
Signature::build("str to-int")
.named("radix", SyntaxShape::Number, "radix of integer", Some('r'))
.rest(
SyntaxShape::ColumnPath,
"optionally convert text into integer by column paths",
)
}
fn usage(&self) -> &str {
@ -44,11 +47,28 @@ impl WholeStreamCommand for SubCommand {
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Convert to an integer",
example: "echo '255' | str to-int",
result: None,
}]
vec![
Example {
description: "Convert to an integer",
example: "echo '255' | str to-int",
result: Some(vec![UntaggedValue::int(255).into()]),
},
Example {
description: "Convert str column to an integer",
example: "echo [['count']; ['255']] | str to-int count | get count",
result: Some(vec![UntaggedValue::int(255).into()]),
},
Example {
description: "Convert to integer from binary",
example: "echo '1101' | str to-int -r 2",
result: Some(vec![UntaggedValue::int(13).into()]),
},
Example {
description: "Convert to integer from hex",
example: "echo 'FF' | str to-int -r 16",
result: Some(vec![UntaggedValue::int(255).into()]),
},
]
}
}
@ -58,21 +78,23 @@ async fn operate(
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (Arguments { rest }, input) = args.process(&registry).await?;
let (Arguments { rest, radix }, input) = args.process(&registry).await?;
let column_paths: Vec<_> = rest;
let radix = radix.map(|r| r.item).unwrap_or(10);
let column_paths: Vec<ColumnPath> = rest;
Ok(input
.map(move |v| {
if column_paths.is_empty() {
ReturnSuccess::value(action(&v, v.tag())?)
ReturnSuccess::value(action(&v, v.tag(), radix)?)
} else {
let mut ret = v;
for path in &column_paths {
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag())),
Box::new(move |old| action(old, old.tag(), radix)),
)?;
}
@ -82,21 +104,54 @@ async fn operate(
.to_output_stream())
}
fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
fn action(input: &Value, tag: impl Into<Tag>, radix: u32) -> Result<Value, ShellError> {
match &input.value {
UntaggedValue::Primitive(Primitive::Line(s))
| UntaggedValue::Primitive(Primitive::String(s)) => {
let other = s.trim();
let out = match BigInt::from_str(other) {
Ok(v) => UntaggedValue::int(v),
Err(reason) => {
return Err(ShellError::labeled_error(
"could not parse as an integer",
reason.to_string(),
tag.into().span,
))
let trimmed = s.trim();
let out = match trimmed {
b if b.starts_with("0b") => {
let num = match BigInt::from_str_radix(b.trim_start_matches("0b"), 2) {
Ok(n) => n,
Err(reason) => {
return Err(ShellError::labeled_error(
"could not parse as integer",
reason.to_string(),
tag.into().span,
))
}
};
UntaggedValue::int(num)
}
h if h.starts_with("0x") => {
let num = match BigInt::from_str_radix(h.trim_start_matches("0x"), 16) {
Ok(n) => n,
Err(reason) => {
return Err(ShellError::labeled_error(
"could not parse as int",
reason.to_string(),
tag.into().span,
))
}
};
UntaggedValue::int(num)
}
_ => {
let num = match BigInt::from_str_radix(trimmed, radix) {
Ok(n) => n,
Err(reason) => {
return Err(ShellError::labeled_error(
"could not parse as int",
reason.to_string(),
tag.into().span,
))
}
};
UntaggedValue::int(num)
}
};
Ok(out.into_value(tag))
}
other => {
@ -114,8 +169,8 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
mod tests {
use super::ShellError;
use super::{action, SubCommand};
use nu_plugin::test_helpers::value::{int, string};
use nu_source::Tag;
use nu_test_support::value::{int, string};
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
@ -129,15 +184,29 @@ mod tests {
let word = string("10");
let expected = int(10);
let actual = action(&word, Tag::unknown()).unwrap();
let actual = action(&word, Tag::unknown(), 10).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn turns_binary_to_integer() {
let s = string("0b101");
let actual = action(&s, Tag::unknown(), 10).unwrap();
assert_eq!(actual, int(5));
}
#[test]
fn turns_hex_to_integer() {
let s = string("0xFF");
let actual = action(&s, Tag::unknown(), 16).unwrap();
assert_eq!(actual, int(255));
}
#[test]
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
let integer_str = string("36anra");
let actual = action(&integer_str, Tag::unknown());
let actual = action(&integer_str, Tag::unknown(), 10);
assert!(actual.is_err());
}

View File

@ -65,11 +65,9 @@ mod tests {
use super::ShellError;
use super::{trim, SubCommand};
use crate::commands::str_::trim::{action, ActionMode};
use nu_plugin::{
row,
test_helpers::value::{int, string, table},
};
use nu_protocol::row;
use nu_source::Tag;
use nu_test_support::value::{int, string, table};
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -66,11 +66,9 @@ mod tests {
use super::ShellError;
use super::{trim_left, SubCommand};
use crate::commands::str_::trim::{action, ActionMode};
use nu_plugin::{
row,
test_helpers::value::{int, string, table},
};
use nu_protocol::row;
use nu_source::Tag;
use nu_test_support::value::{int, string, table};
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -66,11 +66,9 @@ mod tests {
use super::ShellError;
use super::{trim_right, SubCommand};
use crate::commands::str_::trim::{action, ActionMode};
use nu_plugin::{
row,
test_helpers::value::{int, string, table},
};
use nu_protocol::row;
use nu_source::Tag;
use nu_test_support::value::{int, string, table};
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -100,8 +100,8 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
mod tests {
use super::ShellError;
use super::{action, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

@ -59,6 +59,8 @@ pub fn table_mode(config: &NuConfig) -> nu_table::Theme {
Ok(m) if m == "compact_double" => nu_table::Theme::compact_double(),
Ok(m) if m == "rounded" => nu_table::Theme::rounded(),
Ok(m) if m == "reinforced" => nu_table::Theme::reinforced(),
Ok(m) if m == "heavy" => nu_table::Theme::heavy(),
Ok(m) if m == "none" => nu_table::Theme::none(),
_ => nu_table::Theme::compact(),
})
}

View File

@ -7,6 +7,11 @@ use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
pub struct ToMarkdown;
#[derive(Deserialize)]
pub struct ToMarkdownArgs {
pretty: bool,
}
#[async_trait]
impl WholeStreamCommand for ToMarkdown {
fn name(&self) -> &str {
@ -14,7 +19,11 @@ impl WholeStreamCommand for ToMarkdown {
}
fn signature(&self) -> Signature {
Signature::build("to md")
Signature::build("to md").switch(
"pretty",
"Formats the Markdown table to vertically align items",
Some('p'),
)
}
fn usage(&self) -> &str {
@ -28,55 +37,161 @@ impl WholeStreamCommand for ToMarkdown {
) -> Result<OutputStream, ShellError> {
to_md(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Outputs an unformatted md string representing the contents of ls",
example: "ls | to md",
result: None,
},
Example {
description: "Outputs a formatted md string representing the contents of ls",
example: "ls | to md -p",
result: None,
},
]
}
}
async fn to_md(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let name_tag = args.name_tag();
let input: Vec<Value> = args.input.collect().await;
let name_tag = args.call_info.name_tag.clone();
let (ToMarkdownArgs { pretty }, input) = args.process(&registry).await?;
let input: Vec<Value> = input.collect().await;
let headers = nu_protocol::merge_descriptors(&input);
let mut output_string = String::new();
let mut escaped_headers: Vec<String> = Vec::new();
let mut column_widths: Vec<usize> = Vec::new();
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
output_string.push_str("|");
for header in &headers {
output_string.push_str(&htmlescape::encode_minimal(&header));
output_string.push_str("|");
let escaped_header_string = htmlescape::encode_minimal(&header);
column_widths.push(escaped_header_string.len());
escaped_headers.push(escaped_header_string);
}
output_string.push_str("\n|");
for _ in &headers {
output_string.push_str("-");
output_string.push_str("|");
}
output_string.push_str("\n");
}
for row in input {
match row.value {
let mut escaped_rows: Vec<Vec<String>> = Vec::new();
for row in &input {
let mut escaped_row: Vec<String> = Vec::new();
match row.value.clone() {
UntaggedValue::Row(row) => {
output_string.push_str("|");
for header in &headers {
let data = row.get_data(header);
output_string.push_str(&format_leaf(data.borrow()).plain_string(100_000));
output_string.push_str("|");
for i in 0..headers.len() {
let data = row.get_data(&headers[i]);
let value_string = format_leaf(data.borrow()).plain_string(100_000);
let new_column_width = value_string.len();
escaped_row.push(value_string);
if column_widths[i] < new_column_width {
column_widths[i] = new_column_width;
}
}
output_string.push_str("\n");
}
p => {
output_string.push_str(
&(htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000))),
);
output_string.push_str("\n");
let value_string =
htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000));
escaped_row.push(value_string);
}
}
escaped_rows.push(escaped_row);
}
let output_string = get_output_string(&escaped_headers, &escaped_rows, &column_widths, pretty);
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output_string).into_value(name_tag),
)))
}
fn get_output_string(
headers: &[String],
rows: &[Vec<String>],
column_widths: &[usize],
pretty: bool,
) -> String {
let mut output_string = String::new();
if !headers.is_empty() {
output_string.push_str("|");
for i in 0..headers.len() {
if pretty {
output_string.push_str(" ");
output_string.push_str(&get_padded_string(
headers[i].clone(),
column_widths[i],
' ',
));
output_string.push_str(" ");
} else {
output_string.push_str(headers[i].as_str());
}
output_string.push_str("|");
}
output_string.push_str("\n|");
#[allow(clippy::needless_range_loop)]
for i in 0..headers.len() {
if pretty {
output_string.push_str(" ");
output_string.push_str(&get_padded_string(
String::from("-"),
column_widths[i],
'-',
));
output_string.push_str(" ");
} else {
output_string.push_str("-");
}
output_string.push_str("|");
}
output_string.push_str("\n");
}
for row in rows {
if !headers.is_empty() {
output_string.push_str("|");
}
for i in 0..row.len() {
if pretty {
output_string.push_str(" ");
output_string.push_str(&get_padded_string(row[i].clone(), column_widths[i], ' '));
output_string.push_str(" ");
} else {
output_string.push_str(row[i].as_str());
}
if !headers.is_empty() {
output_string.push_str("|");
}
}
output_string.push_str("\n");
}
output_string
}
fn get_padded_string(text: String, desired_length: usize, padding_character: char) -> String {
format!(
"{}{}",
text,
padding_character
.to_string()
.repeat(desired_length - text.len())
)
}
#[cfg(test)]
mod tests {
use super::ShellError;

View File

@ -92,7 +92,7 @@ async fn process_row(
let for_block = input.clone();
let input_stream = once(async { Ok(for_block) }).to_input_stream();
let scope = Scope::append_it(scope, input.clone());
let scope = Scope::append_var(scope, "$it", input.clone());
let result = run_block(&block, Arc::make_mut(&mut context), input_stream, scope).await;
@ -149,7 +149,7 @@ async fn process_row(
value: UntaggedValue::Primitive(Primitive::Nothing),
..
} => match scope
.it()
.var("$it")
.unwrap_or_else(|| UntaggedValue::nothing().into_untagged_value())
.replace_data_at_column_path(&field, replacement.clone())
{

View File

@ -106,7 +106,7 @@ async fn where_command(
.filter_map(move |input| {
let condition = condition.clone();
let registry = registry.clone();
let scope = Scope::append_it(scope.clone(), input.clone());
let scope = Scope::append_var(scope.clone(), "$it", input.clone());
async move {
//FIXME: should we use the scope that's brought in as well?

View File

@ -65,6 +65,8 @@ fn pathext() -> Option<Vec<String>> {
std::env::var_os("PATHEXT").map(|v| {
v.to_string_lossy()
.split(';')
// Filter out empty tokens and ';' at the end
.filter(|f| f.len() > 1)
// Cut off the leading '.' character
.map(|ext| ext[1..].to_string())
.collect::<Vec<_>>()

View File

@ -38,7 +38,7 @@ impl<'s> Flatten<'s> {
.collect(),
Expression::Command => vec![LocationType::Command.spanned(e.span)],
Expression::Path(path) => self.expression(&path.head),
Expression::Variable(_) => vec![LocationType::Variable.spanned(e.span)],
Expression::Variable(_, _) => vec![LocationType::Variable.spanned(e.span)],
Expression::Boolean(_)
| Expression::FilePath(_)

View File

@ -32,7 +32,7 @@ pub(crate) async fn evaluate_baseline_expr(
Expression::Synthetic(hir::Synthetic::String(s)) => {
Ok(UntaggedValue::string(s).into_untagged_value())
}
Expression::Variable(var) => evaluate_reference(&var, scope, tag),
Expression::Variable(var, _) => evaluate_reference(&var, scope, tag),
Expression::Command => unimplemented!(),
Expression::Invocation(block) => evaluate_invocation(block, registry, scope).await,
Expression::ExternalCommand(_) => unimplemented!(),
@ -172,7 +172,7 @@ pub(crate) async fn evaluate_baseline_expr(
Ok(item.value.into_value(tag))
}
Expression::Boolean(_boolean) => unimplemented!(),
Expression::Boolean(_boolean) => Ok(UntaggedValue::boolean(*_boolean).into_value(tag)),
Expression::Garbage => unimplemented!(),
}
}
@ -199,38 +199,36 @@ fn evaluate_literal(literal: &hir::Literal, span: Span) -> Value {
}
}
fn evaluate_reference(
name: &hir::Variable,
scope: Arc<Scope>,
tag: Tag,
) -> Result<Value, ShellError> {
fn evaluate_reference(name: &str, scope: Arc<Scope>, tag: Tag) -> Result<Value, ShellError> {
match name {
hir::Variable::It(_) => match scope.it() {
"$nu" => crate::evaluate::variables::nu(&scope.env(), tag),
"$true" => Ok(Value {
value: UntaggedValue::boolean(true),
tag,
}),
"$false" => Ok(Value {
value: UntaggedValue::boolean(false),
tag,
}),
"$it" => match scope.var("$it") {
Some(v) => Ok(v),
None => Err(ShellError::labeled_error(
"$it variable not in scope",
"not in scope (are you missing an 'each'?)",
"Variable not in scope",
"missing '$it' (note: $it is only available inside of a block)",
tag.span,
)),
},
hir::Variable::Other(name, _) => match name {
x if x == "$nu" => crate::evaluate::variables::nu(&scope.env(), tag),
x if x == "$true" => Ok(Value {
value: UntaggedValue::boolean(true),
tag,
}),
x if x == "$false" => Ok(Value {
value: UntaggedValue::boolean(false),
tag,
}),
x => match scope.var(x) {
Some(v) => Ok(v),
None => Err(ShellError::labeled_error(
"Variable not in scope",
"unknown variable",
tag.span,
)),
},
x => match scope.var(x) {
Some(v) => Ok(v),
None => Err(ShellError::labeled_error(
"Variable not in scope",
"unknown variable",
tag.span,
)),
},
}
}
@ -244,7 +242,7 @@ async fn evaluate_invocation(
let mut context = EvaluationContext::basic()?;
context.registry = registry.clone();
let input = match scope.it() {
let input = match scope.var("$it") {
Some(it) => InputStream::one(it),
None => InputStream::empty(),
};

View File

@ -7,17 +7,14 @@ use nu_source::{AnchorLocation, TaggedItem};
use crate::prelude::*;
use indexmap::indexmap;
use num_bigint::BigInt;
use indexmap::IndexMap;
use crate::command_registry::CommandRegistry;
use crate::commands::classified::block::run_block;
use crate::commands::command::CommandArgs;
use crate::commands::{
whole_stream_command, BuildString, Command, Each, Echo, Get, Keep, StrCollect,
WholeStreamCommand, Wrap,
whole_stream_command, BuildString, Command, Each, Echo, First, Get, Keep, Last, Nth,
StrCollect, WholeStreamCommand, Wrap,
};
use crate::evaluation_context::EvaluationContext;
use crate::stream::{InputStream, OutputStream};
@ -37,9 +34,12 @@ pub fn test_examples(cmd: Command) -> Result<(), ShellError> {
// Minimal restricted commands to aid in testing
whole_stream_command(Echo {}),
whole_stream_command(BuildString {}),
whole_stream_command(First {}),
whole_stream_command(Get {}),
whole_stream_command(Keep {}),
whole_stream_command(Each {}),
whole_stream_command(Last {}),
whole_stream_command(Nth {}),
whole_stream_command(StrCollect),
whole_stream_command(Wrap),
cmd,
@ -150,9 +150,12 @@ pub fn test_anchors(cmd: Command) -> Result<(), ShellError> {
whole_stream_command(MockEcho {}),
whole_stream_command(MockLs {}),
whole_stream_command(BuildString {}),
whole_stream_command(First {}),
whole_stream_command(Get {}),
whole_stream_command(Keep {}),
whole_stream_command(Each {}),
whole_stream_command(Last {}),
whole_stream_command(Nth {}),
whole_stream_command(StrCollect),
whole_stream_command(Wrap),
cmd,
@ -197,8 +200,7 @@ fn parse_line(line: &str, ctx: &mut EvaluationContext) -> Result<ClassifiedBlock
let lite_result = nu_parser::lite_parse(&line, 0)?;
// TODO ensure the command whose examples we're testing is actually in the pipeline
let mut classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
classified_block.block.expand_it_usage();
let classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
Ok(classified_block)
}
@ -351,16 +353,33 @@ impl WholeStreamCommand for MockEcho {
Value {
value: UntaggedValue::Table(table),
..
} => futures::stream::iter(
table
.into_iter()
.map(move |mut v| {
} => {
if table.len() == 1 && table[0].is_table() {
let mut values: Vec<Value> =
table[0].table_entries().map(Clone::clone).collect();
for v in values.iter_mut() {
v.tag = base_value.tag();
v
})
.map(ReturnSuccess::value),
)
.to_output_stream(),
}
let subtable =
vec![UntaggedValue::Table(values).into_value(base_value.tag())];
futures::stream::iter(subtable.into_iter().map(ReturnSuccess::value))
.to_output_stream()
} else {
futures::stream::iter(
table
.into_iter()
.map(move |mut v| {
v.tag = base_value.tag();
v
})
.map(ReturnSuccess::value),
)
.to_output_stream()
}
}
_ => OutputStream::one(Ok(ReturnSuccess::Value(Value {
value: i.value.clone(),
tag: base_value.tag,
@ -427,10 +446,6 @@ fn string(input: impl Into<String>) -> Value {
UntaggedValue::string(input.into()).into_untagged_value()
}
fn row(entries: IndexMap<String, Value>) -> Value {
UntaggedValue::row(entries).into_untagged_value()
}
fn date(input: impl Into<String>) -> Value {
let key = input.into().tagged_unknown();
crate::value::Date::naive_from_str(key.borrow_tagged())
@ -440,30 +455,30 @@ fn date(input: impl Into<String>) -> Value {
fn file_listing() -> Vec<Value> {
vec![
row(indexmap! {
"modified".to_string() => date("2019-07-23"),
row! {
"name".to_string() => string("Andrés.txt"),
"type".to_string() => string("File"),
"chickens".to_string() => int(10),
}),
row(indexmap! {
"modified".to_string() => date("2019-07-23"),
"modified".to_string() => date("2019-07-23")
},
row! {
"name".to_string() => string("Jonathan"),
"type".to_string() => string("Dir"),
"chickens".to_string() => int(5),
}),
row(indexmap! {
"modified".to_string() => date("2019-09-24"),
"modified".to_string() => date("2019-07-23")
},
row! {
"name".to_string() => string("Andrés.txt"),
"type".to_string() => string("File"),
"chickens".to_string() => int(20),
}),
row(indexmap! {
"modified".to_string() => date("2019-09-24"),
"modified".to_string() => date("2019-09-24")
},
row! {
"name".to_string() => string("Yehuda"),
"type".to_string() => string("Dir"),
"chickens".to_string() => int(4),
}),
"modified".to_string() => date("2019-09-24")
},
]
}

View File

@ -33,6 +33,7 @@ mod path;
mod plugin;
mod shell;
mod stream;
pub mod types;
pub mod utils;
#[cfg(test)]

View File

@ -68,7 +68,7 @@ macro_rules! trace_out_stream {
}};
}
pub(crate) use nu_protocol::{errln, out, outln};
pub(crate) use nu_protocol::{errln, out, outln, row};
use nu_source::HasFallibleSpan;
pub(crate) use crate::command_registry::CommandRegistry;

View File

@ -615,7 +615,7 @@ impl Shell for FilesystemShell {
.map(|val| val.is_true())
.unwrap_or(false);
result = if _trash.item || (rm_always_trash && !_permanent.item) {
trash::remove(&f).map_err(|e: trash::Error| {
trash::delete(&f).map_err(|e: trash::Error| {
Error::new(ErrorKind::Other, format!("{:?}", e))
})
} else if metadata.is_file() {
@ -737,15 +737,14 @@ impl Shell for FilesystemShell {
let mut codec = MaybeTextCodec::new(with_encoding);
match codec.decode(&mut bytes_mut).map_err(|e| {
ShellError::unexpected(format!("AsyncRead failed in open function: {:?}", e))
match codec.decode(&mut bytes_mut).map_err(|_| {
ShellError::labeled_error("Error opening file", "error opening file", name)
})? {
Some(sb) => Ok(futures::stream::iter(vec![Ok(sb)].into_iter()).boxed()),
None => Ok(futures::stream::iter(vec![].into_iter()).boxed()),
}
} else {
// We don't know that this is a finite file, so treat it as a stream
let f = std::fs::File::open(&path).map_err(|e| {
ShellError::labeled_error(
format!("Error opening file: {:?}", e),
@ -755,8 +754,8 @@ impl Shell for FilesystemShell {
})?;
let async_reader = futures::io::AllowStdIo::new(f);
let sob_stream = FramedRead::new(async_reader, MaybeTextCodec::new(with_encoding))
.map_err(|e| {
ShellError::unexpected(format!("AsyncRead failed in open function: {:?}", e))
.map_err(move |_| {
ShellError::labeled_error("Error opening file", "error opening file", name)
})
.into_stream();
@ -943,6 +942,7 @@ pub(crate) fn dir_entry_dict(
"type",
"target",
"num_links",
"inode",
"readonly",
"mode",
"uid",
@ -1020,6 +1020,9 @@ pub(crate) fn dir_entry_dict(
let nlinks = md.nlink();
dict.insert_untagged("num_links", UntaggedValue::string(nlinks.to_string()));
let inode = md.ino();
dict.insert_untagged("inode", UntaggedValue::string(inode.to_string()));
if let Some(user) = users::get_user_by_uid(md.uid()) {
dict.insert_untagged(
"uid",

View File

@ -7,7 +7,7 @@ use nu_source::{Tagged, TaggedItem};
pub struct InputStream {
values: BoxStream<'static, Value>,
// Whether or not an empty stream was explicitly requeted via InputStream::empty
// Whether or not an empty stream was explicitly requested via InputStream::empty
empty: bool,
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,114 +1,299 @@
use nu_test_support::nu;
use nu_test_support::playground::Playground;
#[cfg(test)]
mod tests {
use nu_test_support::nu;
use nu_test_support::playground::Playground;
#[test]
fn alias_args_work() {
Playground::setup("append_test_1", |dirs, _| {
#[test]
fn alias_without_args() {
let actual = nu!(
cwd: dirs.root(),
cwd: ".",
r#"
alias double_echo [a b] {echo $a $b}
alias -i e [] {^echo hi nushell | to json}
e
"#
);
#[cfg(not(windows))]
assert_eq!(actual.out, "\"hi nushell\\n\"");
#[cfg(windows)]
assert_eq!(actual.out, "\"hi nushell\\r\\n\"");
}
#[test]
fn alias_args_work() {
Playground::setup("append_test_2", |dirs, _| {
let actual = nu!(
cwd: dirs.root(),
r#"
alias -i double_echo [b] {echo $b | to json}
double_echo 1kb
"#
);
assert_eq!(actual.out, "1024");
})
}
#[test]
fn alias_args_double_echo() {
Playground::setup("append_test_1", |dirs, _| {
let actual = nu!(
cwd: dirs.root(),
r#"
alias -i double_echo [a b] {echo $a $b}
double_echo 1 2 | to json
"#
);
);
assert_eq!(actual.out, "[1,2]");
})
}
assert_eq!(actual.out, "[1,2]");
})
}
#[test]
fn alias_missing_args_work() {
Playground::setup("append_test_1", |dirs, _| {
#[test]
#[cfg(not(windows))]
fn alias_parses_path_tilde() {
let actual = nu!(
cwd: dirs.root(),
r#"
alias double_echo [a b] {^echo $a $b}
double_echo bob
"#
);
assert_eq!(actual.out, "bob");
})
}
#[test]
#[cfg(not(windows))]
fn alias_parses_path_tilde() {
let actual = nu!(
cwd: "tests/fixtures/formats",
r#"
alias -i new-cd [dir] { cd $dir }
new-cd ~
pwd
"#
);
);
#[cfg(target_os = "linux")]
assert!(actual.out.contains("home"));
#[cfg(target_os = "macos")]
assert!(actual.out.contains("Users"));
}
//If this fails for you, check for any special unicode characters in your ~ path
assert!(actual.out.chars().filter(|c| c.clone() == '/').count() == 2);
#[cfg(target_os = "linux")]
assert!(actual.out.contains("home"));
#[cfg(target_os = "macos")]
assert!(actual.out.contains("Users"));
}
#[test]
fn error_alias_wrong_shape_shallow() {
let actual = nu!(
cwd: ".",
r#"
#[test]
fn alias_missing_args_work() {
Playground::setup("append_test_1", |dirs, _| {
let actual = nu!(
cwd: dirs.root(),
r#"
alias double_echo [a b] {^echo $a $b}
double_echo bob
"#
);
assert_eq!(actual.out, "bob");
})
}
#[test]
#[ignore]
fn alias_with_in_str_var_right() {
//Failing
let actual = nu!(
cwd: ".",
r#"
alias -i lw [newbie] {echo 1 2 3 | where "hello_world" in $newbie | to json}
lw [hello_world_test_repo]
"#
);
assert_eq!(actual.out, "[1,2,3]");
}
#[test]
fn alias_with_in_str_var_right_mismatch() {
let actual = nu!(
cwd: ".",
r#"
alias -i lw [rust_newbie] { echo 1 2 3 | where "hello_world" in $rust_newbie | to json }
lw [ big_brain_programmer ]
"#
);
assert_eq!(actual.out, "");
}
#[test]
fn alias_with_in_err() {
//in operator only applicable for strings atm
let actual = nu!(
cwd: ".",
r#"
alias -i lw [p] {echo 1 2 3 | where $p in [1 3 2] | to json}
lw /root/sys
"#
);
assert!(actual.err.contains("Type"));
}
#[test]
#[ignore]
fn alias_with_contains() {
//Failing
let actual = nu!(
cwd: ".",
r#"
alias -i lw [p] {echo 1 2 3 | where $p in [1 hi 3] | to json}
lw 1
"#
);
assert_eq!(actual.out, "[1,2,3]");
}
#[test]
#[ignore]
fn alias_with_contains_and_var_is_right_side() {
//Failing
let actual = nu!(
cwd: ".",
r#"
alias -i lw [p] {echo 1 2 3 | where 1 in $p | to json}
lw [1 2 hi]
"#
);
assert_eq!(actual.out, "[1,2,3]");
}
#[test]
fn error_alias_wrong_shape_shallow() {
let actual = nu!(
cwd: ".",
r#"
alias -i round-to [num digits] { echo $num | str from -d $digits }
round-to 3.45 a
"#
);
);
assert!(actual.err.contains("Type"));
}
assert!(actual.err.contains("Type"));
}
#[test]
fn error_alias_wrong_shape_deep_invocation() {
let actual = nu!(
#[test]
fn error_alias_wrong_shape_deep_invocation() {
let actual = nu!(
cwd: ".",
r#"
alias -i round-to [nums digits] { echo $nums | each {= $(str from -d $digits)}}
round-to 3.45 a
"#
);
);
assert!(actual.err.contains("Type"));
}
assert!(actual.err.contains("Type"));
}
#[test]
fn error_alias_wrong_shape_deep_binary() {
let actual = nu!(
#[test]
fn error_alias_wrong_shape_deep_binary() {
let actual = nu!(
cwd: ".",
r#"
alias -i round-plus-one [nums digits] { echo $nums | each {= $(str from -d $digits | str to-decimal) + 1}}
round-plus-one 3.45 a
"#
);
);
assert!(actual.err.contains("Type"));
}
assert!(actual.err.contains("Type"));
}
#[test]
fn error_alias_wrong_shape_deeper_binary() {
let actual = nu!(
#[test]
fn error_alias_wrong_shape_deeper_binary() {
let actual = nu!(
cwd: ".",
r#"
alias -i round-one-more [num digits] { echo $num | str from -d $(= $digits + 1) }
round-one-more 3.45 a
"#
);
);
assert!(actual.err.contains("Type"));
}
assert!(actual.err.contains("Type"));
}
#[test]
fn error_alias_syntax_shape_clash() {
let actual = nu!(
cwd: ".",
r#"
alias -i clash [a] { echo 1.1 2 3 | each { str from -d $a } | range $a } }
#[test]
fn error_alias_syntax_shape_clash() {
let actual = nu!(
cwd: ".",
r#"
alias -i clash [a] { echo 1.1 2 3 | each { str from -d $a } | range $a }
"#
);
);
assert!(actual.err.contains("alias"));
assert!(actual.err.contains("Contrary types for variable $a"));
}
#[test]
#[ignore]
fn alias_with_math_var() {
//Failing
let actual = nu!(
cwd: ".",
r#"
alias -i echo_math [math] { echo {= 1 + $math}}
echo_math 1 + 1 | to json
"#
);
assert_eq!(actual.out, "3");
}
#[test]
#[ignore]
fn alias_with_math_var2() {
//Failing
let actual = nu!(
cwd: ".",
r#"
alias -i round-plus-one [nums digits math] { echo $nums | each {= $(str from -d $digits | str to-decimal) + $math}}
round-plus-one 3.45 2 1 + 1 | to json
"#
);
assert_eq!(actual.out, "5.45");
}
#[test]
fn alias_with_true_and_false() {
//https://github.com/nushell/nushell/issues/2416
let actual = nu!(
cwd: ".",
r#"
alias -i is_empty [a] {if $(echo $a | empty?) == $true { echo $true } { echo $false }}
is_empty ""
"#
);
assert!(actual.out.contains("true"));
}
#[test]
fn alias_sent_env() {
//https://github.com/nushell/nushell/issues/1835
let actual = nu!(
cwd: ".",
r#"
alias -i set-env [name value] { echo $nu.env | insert $name $value | get SHELL | to json }
set-env SHELL /bin/nu
"#
);
assert_eq!(actual.out, "\"/bin/nu\"");
}
#[test]
#[ignore]
fn alias_with_math_arg() {
//Failing
let actual = nu!(
cwd: ".",
r#"
alias -i lswh [math] { echo 1 2 3 | where $math | to json }
lswh $it > 2
"#
);
assert_eq!(actual.out, "3");
}
#[test]
#[cfg(not(windows))]
fn alias_ls() {
//https://github.com/nushell/nushell/issues/1632
let actual = nu!(
cwd: ".",
r#"
touch /tmp/nushell_alias_test
alias -i l [x] { ls $x }
l /tmp | to json
"#
);
assert!(actual.out.contains("nushell_alias_test"));
}
}

View File

@ -21,7 +21,6 @@ fn adds_a_row_to_the_end() {
| lines
| append "pollo loco"
| nth 3
| echo $it
"#
));

View File

@ -71,7 +71,7 @@ fn cal_sees_pipeline_year() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo 1020 | cal --full-year $it | get monday | first 3 | to json
cal --full-year 1020 | get monday | first 3 | to json
"#
));

View File

@ -10,7 +10,7 @@ fn filesystem_change_from_current_directory_using_relative_path() {
cwd: dirs.root(),
r#"
cd cd_test_1
pwd | echo $it
echo $(pwd)
"#
);
@ -25,7 +25,7 @@ fn filesystem_change_from_current_directory_using_absolute_path() {
cwd: dirs.test(),
r#"
cd "{}"
pwd | echo $it
echo $(pwd)
"#,
dirs.formats()
);
@ -44,7 +44,7 @@ fn filesystem_switch_back_to_previous_working_directory() {
r#"
cd {}
cd -
pwd | echo $it
echo $(pwd)
"#,
dirs.test()
);
@ -62,7 +62,7 @@ fn filesytem_change_from_current_directory_using_relative_path_and_dash() {
cwd: dirs.test(),
r#"
cd odin/-
pwd | echo $it
echo $(pwd)
"#
);
@ -80,7 +80,7 @@ fn filesystem_change_current_directory_to_parent_directory() {
cwd: dirs.test(),
r#"
cd ..
pwd | echo $it
echo $(pwd)
"#
);
@ -97,7 +97,7 @@ fn filesystem_change_current_directory_to_two_parents_up_using_multiple_dots() {
cwd: dirs.test().join("foo/bar"),
r#"
cd ...
pwd | echo $it
echo $(pwd)
"#
);
@ -116,7 +116,7 @@ fn filesystem_change_current_directory_to_parent_directory_after_delete_cwd() {
rm {}/foo/bar
echo ","
cd ..
pwd | echo $it
echo $(pwd)
"#,
dirs.test()
);
@ -135,7 +135,7 @@ fn filesystem_change_to_home_directory() {
cwd: dirs.test(),
r#"
cd ~
pwd | echo $it
echo $(pwd)
"#
);
@ -152,7 +152,7 @@ fn filesystem_change_to_a_directory_containing_spaces() {
cwd: dirs.test(),
r#"
cd "robalino turner katz"
pwd | echo $it
echo $(pwd)
"#
);
@ -219,7 +219,7 @@ fn filesystem_change_directory_to_symlink_relative() {
cwd: dirs.test().join("boo"),
r#"
cd ../foo_link
pwd | echo $it
echo $(pwd)
"#
);
@ -249,7 +249,7 @@ fn valuesystem_change_from_current_path_using_relative_path() {
r#"
enter sample.toml
cd bin
pwd | echo $it
pwd
exit
"#
);
@ -283,7 +283,7 @@ fn valuesystem_change_from_current_path_using_absolute_path() {
enter sample.toml
cd bin
cd /dependencies
pwd | echo $it
pwd
exit
"#
);
@ -319,7 +319,7 @@ fn valuesystem_switch_back_to_previous_working_path() {
cd dependencies
cd /bin
cd -
pwd | echo $it
pwd
exit
"#
);
@ -353,7 +353,7 @@ fn valuesystem_change_from_current_path_using_relative_path_and_dash() {
cd package/-
cd /bin
cd -
pwd | echo $it
pwd
exit
"#
);
@ -380,7 +380,7 @@ fn valuesystem_change_current_path_to_parent_path() {
enter sample.toml
cd package/emberenios
cd ..
pwd | echo $it
pwd
exit
"#
);
@ -405,7 +405,7 @@ fn valuesystem_change_to_a_path_containing_spaces() {
r#"
enter sample.toml
cd "pa que te"
pwd | echo $it
pwd
exit
"#
);

View File

@ -26,7 +26,6 @@ fn discards_rows_where_given_column_is_empty() {
| get amigos
| compact rusty_luck
| count
| echo $it
"#
));
@ -43,7 +42,6 @@ fn discards_empty_rows_by_default() {
| from json
| compact
| count
| echo $it
"#
));

View File

@ -27,7 +27,6 @@ fn adds_row_data_if_column_missing() {
| default rusty_luck 1
| where rusty_luck == 1
| count
| echo $it
"#
));

View File

@ -4,7 +4,7 @@ use nu_test_support::{nu, pipeline};
fn drop_rows() {
let actual = nu!(
cwd: "tests/fixtures/formats",
r#"echo '[{"foo": 3}, {"foo": 8}, {"foo": 4}]' | from json | drop 2 | get foo | math sum | echo $it"#
r#"echo '[{"foo": 3}, {"foo": 8}, {"foo": 4}]' | from json | drop 2 | get foo | math sum "#
);
assert_eq!(actual.out, "3");

View File

@ -5,7 +5,7 @@ fn each_works_separately() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
echo [1 2 3] | each { echo $it 10 | math sum } | to json | echo $it
echo [1 2 3] | each { echo $it 10 | math sum } | to json
"#
));
@ -47,3 +47,27 @@ fn each_window_stride() {
assert_eq!(actual.out, "[[1,2,3],[3,4,5]]");
}
#[test]
fn each_no_args_in_block() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
echo [[foo bar]; [a b] [c d] [e f]] | each { to json } | nth 1 | str collect
"#
));
assert_eq!(actual.out, r#"{"foo":"c","bar":"d"}"#);
}
#[test]
fn each_implicit_it_in_block() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
echo [[foo bar]; [a b] [c d] [e f]] | each { nu --testbin cococo $it.foo }
"#
));
assert_eq!(actual.out, "ace");
}

View File

@ -5,7 +5,7 @@ fn echo_range_is_lazy() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
echo 1..10000000000 | first 3 | echo $it | to json
echo 1..10000000000 | first 3 | to json
"#
));

View File

@ -75,7 +75,7 @@ fn passing_a_block_will_set_contents_on_empty_cells_and_leave_non_empty_ones_unt
]
| empty? LVL { = 9 }
| empty? HP {
get LVL | = $it * 1000
= $it.LVL * 1000
}
| math sum
| get HP

View File

@ -18,7 +18,6 @@ fn gets_first_rows_by_amount() {
ls
| first 3
| count
| echo $it
"#
));
@ -42,7 +41,6 @@ fn gets_all_rows_if_amount_higher_than_all_rows() {
ls
| first 99
| count
| echo $it
"#
));
@ -61,7 +59,6 @@ fn gets_first_row_when_no_amount_given() {
ls
| first
| count
| echo $it
"#
));

View File

@ -0,0 +1,184 @@
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
use nu_test_support::playground::Playground;
use nu_test_support::{nu, pipeline};
#[test]
fn flatten_nested_tables_with_columns() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo [[origin, people]; [Ecuador, $(= 'Andres' | wrap name)]]
[[origin, people]; [Nu, $(= 'nuno' | wrap name)]]
| flatten
| get name
| str collect ','
"#
));
assert_eq!(actual.out, "Andres,nuno");
}
#[test]
fn flatten_nested_tables_that_have_many_columns() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo [[origin, people]; [Ecuador, $(echo [[name, meal]; ['Andres', 'arepa']])]]
[[origin, people]; [USA, $(echo [[name, meal]; ['Katz', 'nurepa']])]]
| flatten
| get meal
| str collect ','
"#
));
assert_eq!(actual.out, "arepa,nurepa");
}
#[test]
fn flatten_nested_tables() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo [[Andrés, Nicolás, Robalino]] | flatten | nth 1
"#
));
assert_eq!(actual.out, "Nicolás");
}
#[test]
fn flatten_row_column_explictly() {
Playground::setup("flatten_test_1", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContentToBeTrimmed(
"katz.json",
r#"
[
{
"people": {
"name": "Andres",
"meal": "arepa"
}
},
{
"people": {
"name": "Katz",
"meal": "nurepa"
}
}
]
"#,
)]);
let actual = nu!(
cwd: dirs.test(),
"open katz.json | flatten people | where name == Andres | count"
);
assert_eq!(actual.out, "1");
})
}
#[test]
fn flatten_row_columns_having_same_column_names_flats_separately() {
Playground::setup("flatten_test_2", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContentToBeTrimmed(
"katz.json",
r#"
[
{
"people": {
"name": "Andres",
"meal": "arepa"
},
"city": [{"name": "Guayaquil"}, {"name": "Samborondón"}]
},
{
"people": {
"name": "Katz",
"meal": "nurepa"
},
"city": [{"name": "Oregon"}, {"name": "Brooklin"}]
}
]
"#,
)]);
let actual = nu!(
cwd: dirs.test(),
"open katz.json | flatten | flatten people city | get city_name | count"
);
assert_eq!(actual.out, "4");
})
}
#[test]
fn flatten_table_columns_explictly() {
Playground::setup("flatten_test_3", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContentToBeTrimmed(
"katz.json",
r#"
[
{
"people": {
"name": "Andres",
"meal": "arepa"
},
"city": ["Guayaquil", "Samborondón"]
},
{
"people": {
"name": "Katz",
"meal": "nurepa"
},
"city": ["Oregon", "Brooklin"]
}
]
"#,
)]);
let actual = nu!(
cwd: dirs.test(),
"open katz.json | flatten city | where people.name == Katz | count"
);
assert_eq!(actual.out, "2");
})
}
#[test]
fn flatten_more_than_one_column_that_are_subtables_not_supported() {
Playground::setup("flatten_test_4", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContentToBeTrimmed(
"katz.json",
r#"
[
{
"people": {
"name": "Andres",
"meal": "arepa"
}
"tags": ["carbohydrate", "corn", "maiz"],
"city": ["Guayaquil", "Samborondón"]
},
{
"people": {
"name": "Katz",
"meal": "nurepa"
},
"tags": ["carbohydrate", "shell food", "amigos flavor"],
"city": ["Oregon", "Brooklin"]
}
]
"#,
)]);
let actual = nu!(
cwd: dirs.test(),
"open katz.json | flatten tags city"
);
assert!(actual.err.contains("tried flattening"));
assert!(actual.err.contains("but is flattened already"));
})
}

View File

@ -1,3 +1,5 @@
use nu_test_support::fs::Stub::EmptyFile;
use nu_test_support::playground::Playground;
use nu_test_support::{nu, pipeline};
#[test]
@ -8,7 +10,6 @@ fn creates_the_resulting_string_from_the_given_fields() {
open cargo_sample.toml
| get package
| format "{name} has license {license}"
| echo $it
"#
));
@ -22,7 +23,6 @@ fn given_fields_can_be_column_paths() {
r#"
open cargo_sample.toml
| format "{package.name} is {package.description}"
| echo $it
"#
));
@ -36,9 +36,31 @@ fn can_use_variables() {
r#"
open cargo_sample.toml
| format "{$it.package.name} is {$it.package.description}"
| echo $it
"#
));
assert_eq!(actual.out, "nu is a new type of shell");
}
#[test]
fn format_filesize_works() {
Playground::setup("format_filesize_test_1", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("yehuda.txt"),
EmptyFile("jonathan.txt"),
EmptyFile("andres.txt"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
ls
| format filesize size KB
| get size
| first
"#
));
assert_eq!(actual.out, "0.01 KB");
})
}

View File

@ -17,7 +17,6 @@ fn fetches_a_row() {
r#"
open sample.toml
| get nu_party_venue
| echo $it
"#
));
@ -44,7 +43,6 @@ fn fetches_by_index() {
r#"
open sample.toml
| get package.authors.2
| echo $it
"#
));
@ -67,7 +65,6 @@ fn fetches_by_column_path() {
r#"
open sample.toml
| get package.name
| echo $it
"#
));
@ -93,7 +90,6 @@ fn column_paths_are_either_double_quoted_or_regular_unquoted_words_separated_by_
open sample.toml
| get package."9999"
| count
| echo $it
"#
));
@ -127,7 +123,6 @@ fn fetches_more_than_one_column_path() {
open sample.toml
| get fortune_tellers.2.name fortune_tellers.0.name fortune_tellers.1.name
| nth 2
| echo $it
"#
));
@ -250,7 +245,7 @@ fn errors_fetching_by_index_out_of_bounds() {
fn quoted_column_access() {
let actual = nu!(
cwd: "tests/fixtures/formats",
r#"echo '[{"foo bar": {"baz": 4}}]' | from json | get "foo bar".baz | echo $it"#
r#"echo '[{"foo bar": {"baz": 4}}]' | from json | get "foo bar".baz "#
);
assert_eq!(actual.out, "4");

View File

@ -22,7 +22,6 @@ fn groups() {
| group-by rusty_at
| get "10/11/2013"
| count
| echo $it
"#
));

View File

@ -22,7 +22,6 @@ fn summarizes_by_column_given() {
| histogram rusty_at countries
| where rusty_at == "Ecuador"
| get countries
| echo $it
"#
));
@ -55,7 +54,6 @@ fn summarizes_by_values() {
| histogram
| where value == "Estados Unidos"
| get count
| echo $it
"#
));

View File

@ -8,7 +8,6 @@ fn sets_the_column_from_a_block_run_output() {
open cargo_sample.toml
| insert dev-dependencies.newdep "1"
| get dev-dependencies.newdep
| echo $it
"#
));
@ -24,7 +23,6 @@ fn sets_the_column_from_a_block_full_stream_output() {
| insert content { open --raw cargo_sample.toml | lines | first 5 }
| get content.1
| str contains "nu"
| echo $it
"#
));
@ -40,7 +38,6 @@ fn sets_the_column_from_an_invocation() {
| insert content $(open --raw cargo_sample.toml | lines | first 5)
| get content.1
| str contains "nu"
| echo $it
"#
));

View File

@ -5,7 +5,7 @@ fn into_int_filesize() {
let actual = nu!(
cwd: ".", pipeline(
r#"
into-int 1kb | = $it / 1024
into-int 1kb | each {= $it / 1024 }
"#
));
@ -17,7 +17,7 @@ fn into_int_int() {
let actual = nu!(
cwd: ".", pipeline(
r#"
into-int 1024 | = $it / 1024
into-int 1024 | each {= $it / 1024 }
"#
));

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