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

View File

@ -10,7 +10,7 @@ license = "MIT"
name = "nu" name = "nu"
readme = "README.md" readme = "README.md"
repository = "https://github.com/nushell/nushell" repository = "https://github.com/nushell/nushell"
version = "0.21.0" version = "0.22.0"
[workspace] [workspace]
members = ["crates/*/"] members = ["crates/*/"]
@ -18,32 +18,32 @@ members = ["crates/*/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-cli = {version = "0.21.0", path = "./crates/nu-cli"} nu-cli = {version = "0.22.0", path = "./crates/nu-cli"}
nu-data = {version = "0.21.0", path = "./crates/nu-data"} nu-data = {version = "0.22.0", path = "./crates/nu-data"}
nu-errors = {version = "0.21.0", path = "./crates/nu-errors"} nu-errors = {version = "0.22.0", path = "./crates/nu-errors"}
nu-parser = {version = "0.21.0", path = "./crates/nu-parser"} nu-parser = {version = "0.22.0", path = "./crates/nu-parser"}
nu-plugin = {version = "0.21.0", path = "./crates/nu-plugin"} nu-plugin = {version = "0.22.0", path = "./crates/nu-plugin"}
nu-protocol = {version = "0.21.0", path = "./crates/nu-protocol"} nu-protocol = {version = "0.22.0", path = "./crates/nu-protocol"}
nu-source = {version = "0.21.0", path = "./crates/nu-source"} nu-source = {version = "0.22.0", path = "./crates/nu-source"}
nu-value-ext = {version = "0.21.0", path = "./crates/nu-value-ext"} 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_binaryview = {version = "0.22.0", path = "./crates/nu_plugin_binaryview", optional = true}
nu_plugin_chart = {version = "0.21.0", path = "./crates/nu_plugin_chart", optional = true} nu_plugin_chart = {version = "0.22.0", path = "./crates/nu_plugin_chart", optional = true}
nu_plugin_fetch = {version = "0.21.0", path = "./crates/nu_plugin_fetch", optional = true} nu_plugin_fetch = {version = "0.22.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_bson = {version = "0.22.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_from_sqlite = {version = "0.22.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_inc = {version = "0.22.0", path = "./crates/nu_plugin_inc", optional = true}
nu_plugin_match = {version = "0.21.0", path = "./crates/nu_plugin_match", optional = true} nu_plugin_match = {version = "0.22.0", path = "./crates/nu_plugin_match", optional = true}
nu_plugin_post = {version = "0.21.0", path = "./crates/nu_plugin_post", optional = true} nu_plugin_post = {version = "0.22.0", path = "./crates/nu_plugin_post", optional = true}
nu_plugin_ps = {version = "0.21.0", path = "./crates/nu_plugin_ps", optional = true} nu_plugin_ps = {version = "0.22.0", path = "./crates/nu_plugin_ps", optional = true}
nu_plugin_s3 = {version = "0.21.0", path = "./crates/nu_plugin_s3", optional = true} nu_plugin_s3 = {version = "0.22.0", path = "./crates/nu_plugin_s3", optional = true}
nu_plugin_start = {version = "0.21.0", path = "./crates/nu_plugin_start", optional = true} nu_plugin_start = {version = "0.22.0", path = "./crates/nu_plugin_start", optional = true}
nu_plugin_sys = {version = "0.21.0", path = "./crates/nu_plugin_sys", optional = true} nu_plugin_sys = {version = "0.22.0", path = "./crates/nu_plugin_sys", optional = true}
nu_plugin_textview = {version = "0.21.0", path = "./crates/nu_plugin_textview", optional = true} nu_plugin_textview = {version = "0.22.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_bson = {version = "0.22.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_to_sqlite = {version = "0.22.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_tree = {version = "0.22.0", path = "./crates/nu_plugin_tree", optional = true}
nu_plugin_xpath = {version = "0.21.0", path = "./crates/nu_plugin_xpath", optional = true} nu_plugin_xpath = {version = "0.22.0", path = "./crates/nu_plugin_xpath", optional = true}
# Required to bootstrap the main binary # Required to bootstrap the main binary
clap = "2.33.3" clap = "2.33.3"
@ -51,10 +51,11 @@ ctrlc = {version = "3.1.6", optional = true}
futures = {version = "0.3.5", features = ["compat", "io-compat"]} futures = {version = "0.3.5", features = ["compat", "io-compat"]}
log = "0.4.11" log = "0.4.11"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
itertools = "0.9.0"
[dev-dependencies] [dev-dependencies]
dunce = "1.0.1" 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] [build-dependencies]
@ -190,6 +191,26 @@ name = "nu_plugin_extra_xpath"
path = "src/plugins/nu_plugin_extra_xpath.rs" path = "src/plugins/nu_plugin_extra_xpath.rs"
required-features = ["xpath"] 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 # Main nu binary
[[bin]] [[bin]]
name = "nu" 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. 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: Required dependencies:
@ -219,15 +219,15 @@ We can pipeline this into a command that gets the contents of one of the columns
name │ nu name │ nu
readme │ README.md readme │ README.md
repository │ https://github.com/nushell/nushell 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: Finally, we can use commands outside of Nu once we have the data we want:
```shell ```shell
> open Cargo.toml | get package.version | echo $it > open Cargo.toml | get package.version
0.15.1 0.21.0
``` ```
Here we use the variable `$it` to refer to the value being piped to the external command. 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" edition = "2018"
license = "MIT" license = "MIT"
name = "nu-cli" name = "nu-cli"
version = "0.21.0" version = "0.22.0"
[lib] [lib]
doctest = false doctest = false
[dependencies] [dependencies]
nu-data = {version = "0.21.0", path = "../nu-data"} nu-data = {version = "0.22.0", path = "../nu-data"}
nu-errors = {version = "0.21.0", path = "../nu-errors"} nu-errors = {version = "0.22.0", path = "../nu-errors"}
nu-parser = {version = "0.21.0", path = "../nu-parser"} nu-parser = {version = "0.22.0", path = "../nu-parser"}
nu-plugin = {version = "0.21.0", path = "../nu-plugin"} nu-plugin = {version = "0.22.0", path = "../nu-plugin"}
nu-protocol = {version = "0.21.0", path = "../nu-protocol"} nu-protocol = {version = "0.22.0", path = "../nu-protocol"}
nu-source = {version = "0.21.0", path = "../nu-source"} nu-source = {version = "0.22.0", path = "../nu-source"}
nu-table = {version = "0.21.0", path = "../nu-table"} nu-table = {version = "0.22.0", path = "../nu-table"}
nu-test-support = {version = "0.21.0", path = "../nu-test-support"} nu-test-support = {version = "0.22.0", path = "../nu-test-support"}
nu-value-ext = {version = "0.21.0", path = "../nu-value-ext"} nu-value-ext = {version = "0.22.0", path = "../nu-value-ext"}
ansi_term = "0.12.1" ansi_term = "0.12.1"
async-recursion = "0.3.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} uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
which = {version = "4.0.2", optional = true} which = {version = "4.0.2", optional = true}
zip = {version = "0.5.7", optional = true} zip = {version = "0.5.7", optional = true}
lazy_static = "1.*"
Inflector = "0.11" Inflector = "0.11"
clipboard = {version = "0.5.0", optional = true} clipboard = {version = "0.5.0", optional = true}
encoding_rs = "0.8.24" encoding_rs = "0.8.24"
rayon = "1.4.0" rayon = "1.4.0"
trash = {version = "1.1.1", optional = true} trash = {version = "1.2.0", optional = true}
url = "2.1.1" url = "2.1.1"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]

View File

@ -5,7 +5,7 @@ fn main() -> Result<(), io::Error> {
let out_dir = env::var_os("OUT_DIR").expect( let out_dir = env::var_os("OUT_DIR").expect(
"\ "\
OUT_DIR environment variable not found. \ 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\ 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(Ansi),
whole_stream_command(Char), whole_stream_command(Char),
// Column manipulation // Column manipulation
whole_stream_command(MoveColumn), whole_stream_command(Move),
whole_stream_command(Reject), whole_stream_command(Reject),
whole_stream_command(Select), whole_stream_command(Select),
whole_stream_command(Get), 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(Nth),
whole_stream_command(Drop), whole_stream_command(Drop),
whole_stream_command(Format), whole_stream_command(Format),
whole_stream_command(FileSize),
whole_stream_command(Where), whole_stream_command(Where),
whole_stream_command(If), whole_stream_command(If),
whole_stream_command(Compact), 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(EachWindow),
whole_stream_command(Empty), whole_stream_command(Empty),
// Table manipulation // Table manipulation
whole_stream_command(Flatten),
whole_stream_command(Move), whole_stream_command(Move),
whole_stream_command(Merge), whole_stream_command(Merge),
whole_stream_command(Shuffle), 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(MathSummation),
whole_stream_command(MathVariance), whole_stream_command(MathVariance),
whole_stream_command(MathProduct), whole_stream_command(MathProduct),
whole_stream_command(MathRound),
whole_stream_command(MathFloor),
whole_stream_command(MathCeil),
// File format output // File format output
whole_stream_command(To), whole_stream_command(To),
whole_stream_command(ToCSV), 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(UrlPath),
whole_stream_command(UrlHost), whole_stream_command(UrlHost),
whole_stream_command(UrlQuery), whole_stream_command(UrlQuery),
whole_stream_command(Seq),
]); ]);
#[cfg(feature = "clipboard-cli")] #[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) { match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
Ok(result) => { Ok(result) => {
let mut prompt_block = let prompt_block = nu_parser::classify_block(&result, context.registry());
nu_parser::classify_block(&result, context.registry());
let env = context.get_env(); let env = context.get_env();
prompt_block.block.expand_it_usage();
match run_block( match run_block(
&prompt_block.block, &prompt_block.block,
&mut context, &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)?; let lite_result = nu_parser::lite_parse(&line, 0)?;
// TODO ensure the command whose examples we're testing is actually in the pipeline // TODO ensure the command whose examples we're testing is actually in the pipeline
let mut classified_block = nu_parser::classify_block(&lite_result, ctx.registry()); let classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
classified_block.block.expand_it_usage();
let input_stream = InputStream::empty(); let input_stream = InputStream::empty();
let env = ctx.get_env(); let env = ctx.get_env();
@ -899,7 +901,7 @@ pub async fn process_line(
debug!("=== Parsed ==="); debug!("=== Parsed ===");
debug!("{:#?}", result); 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); debug!("{:#?}", classified_block);
//println!("{:#?}", pipeline); //println!("{:#?}", pipeline);
@ -1016,8 +1018,6 @@ pub async fn process_line(
InputStream::empty() InputStream::empty()
}; };
classified_block.block.expand_it_usage();
trace!("{:#?}", classified_block); trace!("{:#?}", classified_block);
let env = ctx.get_env(); let env = ctx.get_env();
match run_block( match run_block(

View File

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

View File

@ -1,16 +1,16 @@
use crate::command_registry::CommandRegistry; use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use crate::types::deduction::{VarDeclaration, VarSyntaxShapeDeductor};
use deduction_to_signature::DeductionToSignature;
use log::trace;
use nu_data::config; use nu_data::config;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_parser::SignatureRegistry;
use nu_protocol::hir::{ClassifiedCommand, Expression, NamedValue, SpannedExpression, Variable};
use nu_protocol::{ use nu_protocol::{
hir::Block, CommandAction, NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape, hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
UntaggedValue, Value,
}; };
use nu_source::Tagged; use nu_source::Tagged;
use std::collections::HashMap;
pub struct Alias; pub struct Alias;
@ -86,7 +86,6 @@ pub async fn alias(
}, },
_ctx, _ctx,
) = args.process(&registry).await?; ) = args.process(&registry).await?;
let mut processed_args: Vec<String> = vec![];
if let Some(true) = save { if let Some(true) = save {
let mut result = nu_data::config::read(name.clone().tag, &None)?; 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: 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 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") { match result.get_mut("startup") {
Some(startup) => { Some(startup) => {
if let UntaggedValue::Table(ref mut commands) = startup.value { if let UntaggedValue::Table(ref mut commands) = startup.value {
@ -132,209 +131,41 @@ pub async fn alias(
config::write(&result, &None)?; config::write(&result, &None)?;
} }
for item in list.iter() { let mut processed_args: Vec<VarDeclaration> = vec![];
if let Ok(string) = item.as_string() { for (_, item) in list.iter().enumerate() {
processed_args.push(format!("${}", string)); 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 { } else {
return Err(ShellError::labeled_error( processed_args.into_iter().map(|arg| (arg, None)).collect()
"Expected a string",
"expected a string",
item.tag(),
));
} }
}
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(); Ok(OutputStream::one(ReturnSuccess::action(
for pipeline in &block.block { CommandAction::AddAlias(Box::new(signature), 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)
} }
#[cfg(test)] #[cfg(test)]
@ -349,3 +180,42 @@ mod tests {
Ok(test_examples(Alias {})?) 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 ansi_term::Color;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct Ansi; pub struct Ansi;
#[derive(Deserialize)] #[derive(Deserialize)]
struct AnsiArgs { struct AnsiArgs {
color: Value, color: Value,
escape: Option<Tagged<String>>,
osc: Option<Tagged<String>>,
} }
#[async_trait] #[async_trait]
@ -18,15 +21,70 @@ impl WholeStreamCommand for Ansi {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("ansi").required( Signature::build("ansi")
"color", .optional(
SyntaxShape::Any, "color",
"the name of the color to use or 'reset' to reset the 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 { 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> { 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", "\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, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> 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 color_string = color.as_string()?;
let ansi_code = str_to_ansi_color(color_string); let ansi_code = str_to_ansi_color(color_string);
if let Some(output) = ansi_code { if let Some(output) = ansi_code {
@ -74,6 +171,7 @@ impl WholeStreamCommand for Ansi {
color.tag(), color.tag(),
)) ))
} }
// }
} }
} }

View File

@ -6,7 +6,7 @@ use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
#[derive(Deserialize)] #[derive(Deserialize)]
struct Arguments { struct Arguments {
row: Value, value: Value,
} }
pub struct Command; pub struct Command;
@ -26,7 +26,7 @@ impl WholeStreamCommand for Command {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Append the given row to the table" "Append a row to the table"
} }
async fn run( async fn run(
@ -34,30 +34,57 @@ impl WholeStreamCommand for Command {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> 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; let input: Vec<Value> = input.collect().await;
if let Some(first) = input.get(0) { if let Some(first) = input.get(0) {
row.tag = first.tag(); value.tag = first.tag();
} }
Ok( // Checks if we are trying to append a row literal
futures::stream::iter(input.into_iter().chain(vec![row]).map(ReturnSuccess::value)) if let Value {
.to_output_stream(), 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> { fn examples(&self) -> Vec<Example> {
vec![Example { use nu_protocol::row;
description: "Add something to the end of a list or table",
example: "echo [1 2 3] | append 4", vec![
result: Some(vec![ Example {
UntaggedValue::int(1).into(), description: "Add values to the end of the table",
UntaggedValue::int(2).into(), example: "echo [1 2 3] | append 4",
UntaggedValue::int(3).into(), result: Some(vec![
UntaggedValue::int(4).into(), 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)] #[derive(Deserialize)]
struct CharArgs { struct CharArgs {
name: Tagged<String>, name: Tagged<String>,
unicode: bool,
} }
#[async_trait] #[async_trait]
@ -18,11 +19,13 @@ impl WholeStreamCommand for Char {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("ansi").required( Signature::build("ansi")
"character", .required(
SyntaxShape::Any, "character",
"the name of the character to output", SyntaxShape::Any,
) "the name of the character to output",
)
.switch("unicode", "unicode string i.e. 1f378", Some('u'))
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -45,6 +48,11 @@ impl WholeStreamCommand for Char {
UntaggedValue::string("\u{2261}").into(), 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, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> 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 unicode {
let decoded_char = string_to_unicode_char(&name.item);
if let Some(output) = special_character { if let Some(output) = decoded_char {
Ok(OutputStream::one(ReturnSuccess::value( Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(name.tag()), UntaggedValue::string(output).into_value(name.tag()),
))) )))
} else {
Err(ShellError::labeled_error(
"error decoding unicode character",
"error decoding unicode character",
name.tag(),
))
}
} else { } else {
Err(ShellError::labeled_error( let special_character = str_to_character(&name.item);
"Unknown character", if let Some(output) = special_character {
"unknown character", Ok(OutputStream::one(ReturnSuccess::value(
name.tag(), 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> { fn str_to_character(s: &str) -> Option<String> {
match s { match s {
"newline" | "enter" | "nl" => Some("\n".into()), "newline" | "enter" | "nl" => Some("\n".into()),
@ -78,6 +106,7 @@ fn str_to_character(s: &str) -> Option<String> {
"sp" | "space" => Some(" ".into()), "sp" | "space" => Some(" ".into()),
// Unicode names came from https://www.compart.com/en/unicode // Unicode names came from https://www.compart.com/en/unicode
// Private Use Area (U+E000-U+F8FF) // 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()), //  "branch" => Some('\u{e0a0}'.to_string()), // 
"segment" => Some('\u{e0b0}'.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()), // # "hash" | "hashtag" | "pound_sign" | "sharp" | "root" => Some("#".into()), // #
// Weather symbols // Weather symbols
"sun" => Some("\x1b[33;1m\u{2600}\x1b[0m".to_string()), // Yellow Bold ☀ "sun" | "sunny" | "sunrise" => Some("☀️".to_string()),
"moon" => Some("\x1b[36m\u{263d}\x1b[0m".to_string()), // Cyan ☽ "moon" => Some("🌛".to_string()),
"clouds" => Some("\x1b[37;1m\u{2601}\x1b[0m".to_string()), // White Bold ☁ "cloudy" | "cloud" | "clouds" => Some("☁️".to_string()),
"rain" => Some("\x1b[37;1m\u{2614}\x1b[0m".to_string()), // White Bold ☔ "rainy" | "rain" => Some("🌦️".to_string()),
"fog" => Some("\x1b[37;1m\u{2592}\x1b[0m".to_string()), // White Bold ▒ "foggy" | "fog" => Some("🌫️".to_string()),
"mist" => Some("\x1b[34m\u{2591}\x1b[0m".to_string()), // Blue ░ "mist" | "haze" => Some("\u{2591}".to_string()),
"haze" => Some("\x1b[33m\u{2591}\x1b[0m".to_string()), // Yellow ░ "snowy" | "snow" => Some("❄️".to_string()),
"snow" => Some("\x1b[37;1m\u{2744}\x1b[0m".to_string()), // White Bold ❄ "thunderstorm" | "thunder" => Some("🌩️".to_string()),
"thunderstorm" => Some("\x1b[33;1m\u{26a1}\x1b[0m".to_string()), // Yellow Bold ⚡
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 // Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
// Another good reference http://ascii-table.com/ansi-escape-sequences.php // 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_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_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 "erase_entire_line" => Some("\x1b[2K".to_string()), // clears entire line
_ => None, _ => None,
} }
} }

View File

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

View File

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

View File

@ -6,8 +6,7 @@ use crate::prelude::*;
use futures::stream::once; use futures::stream::once;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{
hir::Block, hir::Expression, hir::SpannedExpression, hir::Synthetic, Scope, Signature, hir::Block, Scope, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
}; };
use nu_source::Tagged; 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( pub async fn process_row(
block: Arc<Block>, block: Arc<Block>,
scope: Arc<Scope>, scope: Arc<Scope>,
head: Arc<Box<SpannedExpression>>,
mut context: Arc<EvaluationContext>, mut context: Arc<EvaluationContext>,
input: Value, input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let input_clone = input.clone(); 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() InputStream::empty()
} else { } else {
once(async { Ok(input_clone) }).to_input_stream() once(async { Ok(input_clone) }).to_input_stream()
}; };
Ok(run_block(
&block, let scope = if !block.params.is_empty() {
Arc::make_mut(&mut context), // FIXME: add check for more than parameter, once that's supported
input_stream, Scope::append_var(scope, block.params[0].clone(), input)
Scope::append_it(scope, 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 { pub(crate) fn make_indexed_item(index: usize, item: Value) -> Value {
@ -116,7 +116,6 @@ async fn each(
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let head = Arc::new(raw_args.call_info.args.head.clone());
let scope = raw_args.call_info.scope.clone(); let scope = raw_args.call_info.scope.clone();
let context = Arc::new(EvaluationContext::from_raw(&raw_args, &registry)); let context = Arc::new(EvaluationContext::from_raw(&raw_args, &registry));
let (each_args, input): (EachArgs, _) = raw_args.process(&registry).await?; let (each_args, input): (EachArgs, _) = raw_args.process(&registry).await?;
@ -128,12 +127,11 @@ async fn each(
.then(move |input| { .then(move |input| {
let block = block.clone(); let block = block.clone();
let scope = scope.clone(); let scope = scope.clone();
let head = head.clone();
let context = context.clone(); let context = context.clone();
let row = make_indexed_item(input.0, input.1); let row = make_indexed_item(input.0, input.1);
async { async {
match process_row(block, scope, head, context, row).await { match process_row(block, scope, context, row).await {
Ok(s) => s, Ok(s) => s,
Err(e) => OutputStream::one(Err(e)), Err(e) => OutputStream::one(Err(e)),
} }
@ -146,11 +144,10 @@ async fn each(
.then(move |input| { .then(move |input| {
let block = block.clone(); let block = block.clone();
let scope = scope.clone(); let scope = scope.clone();
let head = head.clone();
let context = context.clone(); let context = context.clone();
async { async {
match process_row(block, scope, head, context, input).await { match process_row(block, scope, context, input).await {
Ok(s) => s, Ok(s) => s,
Err(e) => OutputStream::one(Err(e)), Err(e) => OutputStream::one(Err(e)),
} }

View File

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

View File

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

View File

@ -187,7 +187,7 @@ async fn process_row(
let for_block = input.clone(); let for_block = input.clone();
let input_stream = once(async { Ok(for_block) }).to_input_stream(); 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( let mut stream = run_block(
&default_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( let result = evaluate_baseline_expr(
&full_column_path.0, &full_column_path.0,
&registry, &registry,
Scope::append_it(scope.clone(), value.clone()), Scope::append_var(scope.clone(), "$it", value.clone()),
) )
.await; .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 { mod tests {
use super::ShellError; use super::ShellError;
use super::*; use super::*;
use nu_plugin::row; use nu_protocol::row;
use nu_plugin::test_helpers::value::string; use nu_test_support::value::string;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {

View File

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

View File

@ -87,7 +87,7 @@ async fn process_row(
let for_block = input.clone(); let for_block = input.clone();
let input_stream = once(async { Ok(for_block) }).to_input_stream(); 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; 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), value: UntaggedValue::Primitive(Primitive::Nothing),
.. ..
} => match scope } => match scope
.it() .var("$it")
.unwrap_or_else(|| UntaggedValue::nothing().into_untagged_value()) .unwrap_or_else(|| UntaggedValue::nothing().into_untagged_value())
.insert_data_at_column_path(&field, value.clone()) .insert_data_at_column_path(&field, value.clone())
{ {

View File

@ -37,7 +37,7 @@ impl WholeStreamCommand for IntoInt {
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "Convert filesize to integer", 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()]), result: Some(vec![UntaggedValue::int(1).into()]),
}] }]
} }

View File

@ -85,7 +85,7 @@ impl WholeStreamCommand for SubCommand {
.take_while(move |item| { .take_while(move |item| {
let condition = condition.clone(); let condition = condition.clone();
let registry = registry.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); trace!("ITEM = {:?}", item);
async move { async move {

View File

@ -84,7 +84,7 @@ impl WholeStreamCommand for SubCommand {
.take_while(move |item| { .take_while(move |item| {
let condition = condition.clone(); let condition = condition.clone();
let registry = registry.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); trace!("ITEM = {:?}", item);
async move { 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, avg::average, max::maximum, median::median, min::minimum, mode::mode, stddev::stddev,
sum::summation, utils::calculate, utils::MathFunction, variance::variance, sum::summation, utils::calculate, utils::MathFunction, variance::variance,
}; };
use nu_plugin::row; use nu_protocol::{row, Value};
use nu_plugin::test_helpers::value::{decimal, decimal_from_float, int, table}; use nu_test_support::value::{decimal, decimal_from_float, int, table};
use nu_protocol::Value;
use std::str::FromStr; use std::str::FromStr;
#[test] #[test]
@ -71,14 +70,14 @@ mod tests {
values: vec![int(10)], values: vec![int(10)],
expected_err: None, expected_err: None,
expected_res: vec![ expected_res: vec![
Ok(decimal(10)), Ok(decimal_from_float(10.0)),
Ok(int(10)), Ok(int(10)),
Ok(int(10)), Ok(int(10)),
Ok(int(10)), Ok(int(10)),
Ok(table(&[int(10)])), Ok(table(&[int(10)])),
Ok(decimal(0)), Ok(decimal_from_float(0.0)),
Ok(int(10)), Ok(int(10)),
Ok(decimal(0)), Ok(decimal_from_float(0.0)),
], ],
}, },
TestCase { TestCase {
@ -86,7 +85,7 @@ mod tests {
values: vec![int(10), int(20), int(30)], values: vec![int(10), int(20), int(30)],
expected_err: None, expected_err: None,
expected_res: vec![ expected_res: vec![
Ok(decimal(20)), Ok(decimal_from_float(20.0)),
Ok(int(10)), Ok(int(10)),
Ok(int(30)), Ok(int(30)),
Ok(int(20)), Ok(int(20)),
@ -101,13 +100,13 @@ mod tests {
values: vec![int(10), decimal_from_float(26.5), decimal_from_float(26.5)], values: vec![int(10), decimal_from_float(26.5), decimal_from_float(26.5)],
expected_err: None, expected_err: None,
expected_res: vec![ expected_res: vec![
Ok(decimal(21)), Ok(decimal_from_float(21.0)),
Ok(int(10)), Ok(int(10)),
Ok(decimal_from_float(26.5)), Ok(decimal_from_float(26.5)),
Ok(decimal_from_float(26.5)), Ok(decimal_from_float(26.5)),
Ok(table(&[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(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)), Ok(decimal_from_float(60.5)),
], ],
}, },
@ -116,14 +115,14 @@ mod tests {
values: vec![int(-14), int(-11), int(10)], values: vec![int(-14), int(-11), int(10)],
expected_err: None, expected_err: None,
expected_res: vec![ expected_res: vec![
Ok(decimal(-5)), Ok(decimal_from_float(-5.0)),
Ok(int(-14)), Ok(int(-14)),
Ok(int(10)), Ok(int(10)),
Ok(int(-11)), Ok(int(-11)),
Ok(table(&[int(-14), int(-11), int(10)])), Ok(table(&[int(-14), int(-11), int(10)])),
Ok(decimal(BigDecimal::from_str("10.67707825203131121081152396559571062628228776946058011397810604284900898365140801704064843595778374").expect("Could not convert to decimal from string"))), Ok(decimal(BigDecimal::from_str("10.67707825203131121081152396559571062628228776946058011397810604284900898365140801704064843595778374").expect("Could not convert to decimal from string"))),
Ok(int(-15)), Ok(int(-15)),
Ok(decimal(114)), Ok(decimal_from_float(114.0)),
], ],
}, },
TestCase { TestCase {
@ -131,13 +130,13 @@ mod tests {
values: vec![decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)], values: vec![decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)],
expected_err: None, expected_err: None,
expected_res: vec![ expected_res: vec![
Ok(decimal(-5)), Ok(decimal_from_float(-5.0)),
Ok(decimal_from_float(-13.5)), Ok(decimal_from_float(-13.5)),
Ok(int(10)), Ok(int(10)),
Ok(decimal_from_float(-11.5)), Ok(decimal_from_float(-11.5)),
Ok(table(&[decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)])), 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(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"))), 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> { 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) if num.is_infinite() || num.is_nan() => Err("cannot represent result".to_string()),
Ok(num) => Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag)), Ok(num) => Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag)),
Err(error) => Err(error.to_string().to_lowercase()), 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 avg;
pub mod ceil;
pub mod command; pub mod command;
pub mod eval; pub mod eval;
pub mod floor;
pub mod max; pub mod max;
pub mod median; pub mod median;
pub mod min; pub mod min;
pub mod mode; pub mod mode;
pub mod product; pub mod product;
pub mod round;
pub mod stddev; pub mod stddev;
pub mod sum; pub mod sum;
pub mod variance; pub mod variance;
@ -14,13 +17,16 @@ mod reducers;
mod utils; mod utils;
pub use avg::SubCommand as MathAverage; pub use avg::SubCommand as MathAverage;
pub use ceil::SubCommand as MathCeil;
pub use command::Command as Math; pub use command::Command as Math;
pub use eval::SubCommand as MathEval; pub use eval::SubCommand as MathEval;
pub use floor::SubCommand as MathFloor;
pub use max::SubCommand as MathMaximum; pub use max::SubCommand as MathMaximum;
pub use median::SubCommand as MathMedian; pub use median::SubCommand as MathMedian;
pub use min::SubCommand as MathMinimum; pub use min::SubCommand as MathMinimum;
pub use mode::SubCommand as MathMode; pub use mode::SubCommand as MathMode;
pub use product::SubCommand as MathProduct; pub use product::SubCommand as MathProduct;
pub use round::SubCommand as MathRound;
pub use stddev::SubCommand as MathStddev; pub use stddev::SubCommand as MathStddev;
pub use sum::SubCommand as MathSummation; pub use sum::SubCommand as MathSummation;
pub use variance::SubCommand as MathVariance; 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 crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Dictionary, ReturnSuccess, UntaggedValue, Value}; use nu_protocol::{Dictionary, Primitive, ReturnSuccess, UntaggedValue, Value};
use indexmap::map::IndexMap; 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> { pub fn calculate(values: &[Value], name: &Tag, mf: MathFunction) -> Result<Value, ShellError> {
if values.iter().all(|v| v.is_primitive()) { if values.iter().all(|v| v.is_primitive()) {
mf(&values, &name) 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::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use nu_data::base::select_fields;
use nu_errors::ShellError; 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; pub struct Command;
#[derive(Deserialize)]
pub struct Arguments {
rest: Vec<ColumnPath>,
after: Option<ColumnPath>,
before: Option<ColumnPath>,
}
#[async_trait] #[async_trait]
impl WholeStreamCommand for Command { impl WholeStreamCommand for Command {
fn name(&self) -> &str { fn name(&self) -> &str {
@ -14,34 +23,318 @@ impl WholeStreamCommand for Command {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("move") 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 { fn usage(&self) -> &str {
"Moves across desired subcommand." "Move columns."
} }
async fn run( async fn run(
&self, &self,
_args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); operate(args, registry).await
Ok(OutputStream::one(Ok(ReturnSuccess::Value( }
UntaggedValue::string(crate::commands::help::get_help(&Command, &registry))
.into_value(Tag::unknown()), 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)] async fn operate(
mod tests { raw_args: CommandArgs,
use super::Command; registry: &CommandRegistry,
use super::ShellError; ) -> 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] if columns.is_empty() {
fn examples_work_as_expected() -> Result<(), ShellError> { return Err(ShellError::labeled_error(
use crate::examples::test as test_examples; "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; mod command;
pub mod mv; pub mod mv;
pub use column::SubCommand as MoveColumn;
pub use command::Command as Move; pub use command::Command as Move;
pub use mv::Mv; 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 // If no encoding is provided we try to guess the encoding to read the file with
let encoding = if encoding_choice.is_none() { let encoding = if encoding_choice.is_none() {

View File

@ -87,7 +87,7 @@ async fn process_row(
let row_clone = row.clone(); let row_clone = row.clone();
let input_stream = once(async { Ok(row_clone) }).to_input_stream(); 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?) 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() 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 process_row(block, scope, context, row).await
} }
}) })
@ -173,7 +173,7 @@ async fn reduce(
UntaggedValue::table(&values).into_untagged_value() 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 process_row(block, scope, context, row).await
} }
}) })

View File

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

View File

@ -85,7 +85,7 @@ impl WholeStreamCommand for SubCommand {
let item = item.clone(); let item = item.clone();
let condition = condition.clone(); let condition = condition.clone();
let registry = registry.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); trace!("ITEM = {:?}", item);
async move { async move {

View File

@ -101,9 +101,10 @@ pub fn split(
mod tests { mod tests {
use super::split; use super::split;
use super::ShellError; 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_protocol::UntaggedValue;
use nu_source::*; use nu_source::*;
use nu_test_support::value::{date, int, row, string, table};
#[test] #[test]
fn splits_inner_tables_by_key() { 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 { mod tests {
use super::ShellError; use super::ShellError;
use super::{action, SubCommand}; use super::{action, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_source::Tag; use nu_source::Tag;
use nu_test_support::value::string;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -66,11 +66,9 @@ mod tests {
use super::ShellError; use super::ShellError;
use super::{trim_right, SubCommand}; use super::{trim_right, SubCommand};
use crate::commands::str_::trim::{action, ActionMode}; use crate::commands::str_::trim::{action, ActionMode};
use nu_plugin::{ use nu_protocol::row;
row,
test_helpers::value::{int, string, table},
};
use nu_source::Tag; use nu_source::Tag;
use nu_test_support::value::{int, string, table};
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { 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 { mod tests {
use super::ShellError; use super::ShellError;
use super::{action, SubCommand}; use super::{action, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_source::Tag; use nu_source::Tag;
use nu_test_support::value::string;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { 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 == "compact_double" => nu_table::Theme::compact_double(),
Ok(m) if m == "rounded" => nu_table::Theme::rounded(), Ok(m) if m == "rounded" => nu_table::Theme::rounded(),
Ok(m) if m == "reinforced" => nu_table::Theme::reinforced(), 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(), _ => nu_table::Theme::compact(),
}) })
} }

View File

@ -7,6 +7,11 @@ use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
pub struct ToMarkdown; pub struct ToMarkdown;
#[derive(Deserialize)]
pub struct ToMarkdownArgs {
pretty: bool,
}
#[async_trait] #[async_trait]
impl WholeStreamCommand for ToMarkdown { impl WholeStreamCommand for ToMarkdown {
fn name(&self) -> &str { fn name(&self) -> &str {
@ -14,7 +19,11 @@ impl WholeStreamCommand for ToMarkdown {
} }
fn signature(&self) -> Signature { 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 { fn usage(&self) -> &str {
@ -28,55 +37,161 @@ impl WholeStreamCommand for ToMarkdown {
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
to_md(args, registry).await 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> { async fn to_md(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let args = args.evaluate_once(&registry).await?; let name_tag = args.call_info.name_tag.clone();
let name_tag = args.name_tag(); let (ToMarkdownArgs { pretty }, input) = args.process(&registry).await?;
let input: Vec<Value> = args.input.collect().await; let input: Vec<Value> = input.collect().await;
let headers = nu_protocol::merge_descriptors(&input); let headers = nu_protocol::merge_descriptors(&input);
let mut output_string = String::new();
let mut escaped_headers: Vec<String> = Vec::new();
let mut column_widths: Vec<usize> = Vec::new();
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") { if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
output_string.push_str("|");
for header in &headers { for header in &headers {
output_string.push_str(&htmlescape::encode_minimal(&header)); let escaped_header_string = htmlescape::encode_minimal(&header);
output_string.push_str("|"); 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 { let mut escaped_rows: Vec<Vec<String>> = Vec::new();
match row.value {
for row in &input {
let mut escaped_row: Vec<String> = Vec::new();
match row.value.clone() {
UntaggedValue::Row(row) => { UntaggedValue::Row(row) => {
output_string.push_str("|"); for i in 0..headers.len() {
for header in &headers { let data = row.get_data(&headers[i]);
let data = row.get_data(header); let value_string = format_leaf(data.borrow()).plain_string(100_000);
output_string.push_str(&format_leaf(data.borrow()).plain_string(100_000)); let new_column_width = value_string.len();
output_string.push_str("|");
escaped_row.push(value_string);
if column_widths[i] < new_column_width {
column_widths[i] = new_column_width;
}
} }
output_string.push_str("\n");
} }
p => { p => {
output_string.push_str( let value_string =
&(htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000))), htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000));
); escaped_row.push(value_string);
output_string.push_str("\n");
} }
} }
escaped_rows.push(escaped_row);
} }
let output_string = get_output_string(&escaped_headers, &escaped_rows, &column_widths, pretty);
Ok(OutputStream::one(ReturnSuccess::value( Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output_string).into_value(name_tag), 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)] #[cfg(test)]
mod tests { mod tests {
use super::ShellError; use super::ShellError;

View File

@ -92,7 +92,7 @@ async fn process_row(
let for_block = input.clone(); let for_block = input.clone();
let input_stream = once(async { Ok(for_block) }).to_input_stream(); 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; 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), value: UntaggedValue::Primitive(Primitive::Nothing),
.. ..
} => match scope } => match scope
.it() .var("$it")
.unwrap_or_else(|| UntaggedValue::nothing().into_untagged_value()) .unwrap_or_else(|| UntaggedValue::nothing().into_untagged_value())
.replace_data_at_column_path(&field, replacement.clone()) .replace_data_at_column_path(&field, replacement.clone())
{ {

View File

@ -106,7 +106,7 @@ async fn where_command(
.filter_map(move |input| { .filter_map(move |input| {
let condition = condition.clone(); let condition = condition.clone();
let registry = registry.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 { async move {
//FIXME: should we use the scope that's brought in as well? //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| { std::env::var_os("PATHEXT").map(|v| {
v.to_string_lossy() v.to_string_lossy()
.split(';') .split(';')
// Filter out empty tokens and ';' at the end
.filter(|f| f.len() > 1)
// Cut off the leading '.' character // Cut off the leading '.' character
.map(|ext| ext[1..].to_string()) .map(|ext| ext[1..].to_string())
.collect::<Vec<_>>() .collect::<Vec<_>>()

View File

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

View File

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

View File

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

View File

@ -33,6 +33,7 @@ mod path;
mod plugin; mod plugin;
mod shell; mod shell;
mod stream; mod stream;
pub mod types;
pub mod utils; pub mod utils;
#[cfg(test)] #[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; use nu_source::HasFallibleSpan;
pub(crate) use crate::command_registry::CommandRegistry; pub(crate) use crate::command_registry::CommandRegistry;

View File

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

View File

@ -7,7 +7,7 @@ use nu_source::{Tagged, TaggedItem};
pub struct InputStream { pub struct InputStream {
values: BoxStream<'static, Value>, 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, 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; #[cfg(test)]
use nu_test_support::playground::Playground; mod tests {
use nu_test_support::nu;
use nu_test_support::playground::Playground;
#[test] #[test]
fn alias_args_work() { fn alias_without_args() {
Playground::setup("append_test_1", |dirs, _| {
let actual = nu!( let actual = nu!(
cwd: dirs.root(), cwd: ".",
r#" 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 double_echo 1 2 | to json
"# "#
); );
assert_eq!(actual.out, "[1,2]"); assert_eq!(actual.out, "[1,2]");
}) })
} }
#[test] #[test]
fn alias_missing_args_work() { #[cfg(not(windows))]
Playground::setup("append_test_1", |dirs, _| { fn alias_parses_path_tilde() {
let actual = nu!( 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", cwd: "tests/fixtures/formats",
r#" r#"
alias -i new-cd [dir] { cd $dir } alias -i new-cd [dir] { cd $dir }
new-cd ~ new-cd ~
pwd pwd
"# "#
); );
#[cfg(target_os = "linux")] //If this fails for you, check for any special unicode characters in your ~ path
assert!(actual.out.contains("home")); assert!(actual.out.chars().filter(|c| c.clone() == '/').count() == 2);
#[cfg(target_os = "macos")] #[cfg(target_os = "linux")]
assert!(actual.out.contains("Users")); assert!(actual.out.contains("home"));
} #[cfg(target_os = "macos")]
assert!(actual.out.contains("Users"));
}
#[test] #[test]
fn error_alias_wrong_shape_shallow() { fn alias_missing_args_work() {
let actual = nu!( Playground::setup("append_test_1", |dirs, _| {
cwd: ".", let actual = nu!(
r#" 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 } alias -i round-to [num digits] { echo $num | str from -d $digits }
round-to 3.45 a round-to 3.45 a
"# "#
); );
assert!(actual.err.contains("Type")); assert!(actual.err.contains("Type"));
} }
#[test] #[test]
fn error_alias_wrong_shape_deep_invocation() { fn error_alias_wrong_shape_deep_invocation() {
let actual = nu!( let actual = nu!(
cwd: ".", cwd: ".",
r#" r#"
alias -i round-to [nums digits] { echo $nums | each {= $(str from -d $digits)}} alias -i round-to [nums digits] { echo $nums | each {= $(str from -d $digits)}}
round-to 3.45 a round-to 3.45 a
"# "#
); );
assert!(actual.err.contains("Type")); assert!(actual.err.contains("Type"));
} }
#[test] #[test]
fn error_alias_wrong_shape_deep_binary() { fn error_alias_wrong_shape_deep_binary() {
let actual = nu!( let actual = nu!(
cwd: ".", cwd: ".",
r#" r#"
alias -i round-plus-one [nums digits] { echo $nums | each {= $(str from -d $digits | str to-decimal) + 1}} alias -i round-plus-one [nums digits] { echo $nums | each {= $(str from -d $digits | str to-decimal) + 1}}
round-plus-one 3.45 a round-plus-one 3.45 a
"# "#
); );
assert!(actual.err.contains("Type")); assert!(actual.err.contains("Type"));
} }
#[test] #[test]
fn error_alias_wrong_shape_deeper_binary() { fn error_alias_wrong_shape_deeper_binary() {
let actual = nu!( let actual = nu!(
cwd: ".", cwd: ".",
r#" r#"
alias -i round-one-more [num digits] { echo $num | str from -d $(= $digits + 1) } alias -i round-one-more [num digits] { echo $num | str from -d $(= $digits + 1) }
round-one-more 3.45 a round-one-more 3.45 a
"# "#
); );
assert!(actual.err.contains("Type")); assert!(actual.err.contains("Type"));
} }
#[test] #[test]
fn error_alias_syntax_shape_clash() { fn error_alias_syntax_shape_clash() {
let actual = nu!( let actual = nu!(
cwd: ".", cwd: ".",
r#" r#"
alias -i clash [a] { echo 1.1 2 3 | each { str from -d $a } | range $a } } 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 | lines
| append "pollo loco" | append "pollo loco"
| nth 3 | nth 3
| echo $it
"# "#
)); ));

View File

@ -71,7 +71,7 @@ fn cal_sees_pipeline_year() {
let actual = nu!( let actual = nu!(
cwd: ".", pipeline( cwd: ".", pipeline(
r#" 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(), cwd: dirs.root(),
r#" r#"
cd cd_test_1 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(), cwd: dirs.test(),
r#" r#"
cd "{}" cd "{}"
pwd | echo $it echo $(pwd)
"#, "#,
dirs.formats() dirs.formats()
); );
@ -44,7 +44,7 @@ fn filesystem_switch_back_to_previous_working_directory() {
r#" r#"
cd {} cd {}
cd - cd -
pwd | echo $it echo $(pwd)
"#, "#,
dirs.test() dirs.test()
); );
@ -62,7 +62,7 @@ fn filesytem_change_from_current_directory_using_relative_path_and_dash() {
cwd: dirs.test(), cwd: dirs.test(),
r#" r#"
cd odin/- cd odin/-
pwd | echo $it echo $(pwd)
"# "#
); );
@ -80,7 +80,7 @@ fn filesystem_change_current_directory_to_parent_directory() {
cwd: dirs.test(), cwd: dirs.test(),
r#" r#"
cd .. 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"), cwd: dirs.test().join("foo/bar"),
r#" r#"
cd ... cd ...
pwd | echo $it echo $(pwd)
"# "#
); );
@ -116,7 +116,7 @@ fn filesystem_change_current_directory_to_parent_directory_after_delete_cwd() {
rm {}/foo/bar rm {}/foo/bar
echo "," echo ","
cd .. cd ..
pwd | echo $it echo $(pwd)
"#, "#,
dirs.test() dirs.test()
); );
@ -135,7 +135,7 @@ fn filesystem_change_to_home_directory() {
cwd: dirs.test(), cwd: dirs.test(),
r#" r#"
cd ~ cd ~
pwd | echo $it echo $(pwd)
"# "#
); );
@ -152,7 +152,7 @@ fn filesystem_change_to_a_directory_containing_spaces() {
cwd: dirs.test(), cwd: dirs.test(),
r#" r#"
cd "robalino turner katz" 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"), cwd: dirs.test().join("boo"),
r#" r#"
cd ../foo_link cd ../foo_link
pwd | echo $it echo $(pwd)
"# "#
); );
@ -249,7 +249,7 @@ fn valuesystem_change_from_current_path_using_relative_path() {
r#" r#"
enter sample.toml enter sample.toml
cd bin cd bin
pwd | echo $it pwd
exit exit
"# "#
); );
@ -283,7 +283,7 @@ fn valuesystem_change_from_current_path_using_absolute_path() {
enter sample.toml enter sample.toml
cd bin cd bin
cd /dependencies cd /dependencies
pwd | echo $it pwd
exit exit
"# "#
); );
@ -319,7 +319,7 @@ fn valuesystem_switch_back_to_previous_working_path() {
cd dependencies cd dependencies
cd /bin cd /bin
cd - cd -
pwd | echo $it pwd
exit exit
"# "#
); );
@ -353,7 +353,7 @@ fn valuesystem_change_from_current_path_using_relative_path_and_dash() {
cd package/- cd package/-
cd /bin cd /bin
cd - cd -
pwd | echo $it pwd
exit exit
"# "#
); );
@ -380,7 +380,7 @@ fn valuesystem_change_current_path_to_parent_path() {
enter sample.toml enter sample.toml
cd package/emberenios cd package/emberenios
cd .. cd ..
pwd | echo $it pwd
exit exit
"# "#
); );
@ -405,7 +405,7 @@ fn valuesystem_change_to_a_path_containing_spaces() {
r#" r#"
enter sample.toml enter sample.toml
cd "pa que te" cd "pa que te"
pwd | echo $it pwd
exit exit
"# "#
); );

View File

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

View File

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

View File

@ -4,7 +4,7 @@ use nu_test_support::{nu, pipeline};
fn drop_rows() { fn drop_rows() {
let actual = nu!( let actual = nu!(
cwd: "tests/fixtures/formats", 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"); assert_eq!(actual.out, "3");

View File

@ -5,7 +5,7 @@ fn each_works_separately() {
let actual = nu!( let actual = nu!(
cwd: "tests/fixtures/formats", pipeline( cwd: "tests/fixtures/formats", pipeline(
r#" 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]]"); 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!( let actual = nu!(
cwd: "tests/fixtures/formats", pipeline( cwd: "tests/fixtures/formats", pipeline(
r#" 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? LVL { = 9 }
| empty? HP { | empty? HP {
get LVL | = $it * 1000 = $it.LVL * 1000
} }
| math sum | math sum
| get HP | get HP

View File

@ -18,7 +18,6 @@ fn gets_first_rows_by_amount() {
ls ls
| first 3 | first 3
| count | count
| echo $it
"# "#
)); ));
@ -42,7 +41,6 @@ fn gets_all_rows_if_amount_higher_than_all_rows() {
ls ls
| first 99 | first 99
| count | count
| echo $it
"# "#
)); ));
@ -61,7 +59,6 @@ fn gets_first_row_when_no_amount_given() {
ls ls
| first | first
| count | 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}; use nu_test_support::{nu, pipeline};
#[test] #[test]
@ -8,7 +10,6 @@ fn creates_the_resulting_string_from_the_given_fields() {
open cargo_sample.toml open cargo_sample.toml
| get package | get package
| format "{name} has license {license}" | format "{name} has license {license}"
| echo $it
"# "#
)); ));
@ -22,7 +23,6 @@ fn given_fields_can_be_column_paths() {
r#" r#"
open cargo_sample.toml open cargo_sample.toml
| format "{package.name} is {package.description}" | format "{package.name} is {package.description}"
| echo $it
"# "#
)); ));
@ -36,9 +36,31 @@ fn can_use_variables() {
r#" r#"
open cargo_sample.toml open cargo_sample.toml
| format "{$it.package.name} is {$it.package.description}" | format "{$it.package.name} is {$it.package.description}"
| echo $it
"# "#
)); ));
assert_eq!(actual.out, "nu is a new type of shell"); 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#" r#"
open sample.toml open sample.toml
| get nu_party_venue | get nu_party_venue
| echo $it
"# "#
)); ));
@ -44,7 +43,6 @@ fn fetches_by_index() {
r#" r#"
open sample.toml open sample.toml
| get package.authors.2 | get package.authors.2
| echo $it
"# "#
)); ));
@ -67,7 +65,6 @@ fn fetches_by_column_path() {
r#" r#"
open sample.toml open sample.toml
| get package.name | 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 open sample.toml
| get package."9999" | get package."9999"
| count | count
| echo $it
"# "#
)); ));
@ -127,7 +123,6 @@ fn fetches_more_than_one_column_path() {
open sample.toml open sample.toml
| get fortune_tellers.2.name fortune_tellers.0.name fortune_tellers.1.name | get fortune_tellers.2.name fortune_tellers.0.name fortune_tellers.1.name
| nth 2 | nth 2
| echo $it
"# "#
)); ));
@ -250,7 +245,7 @@ fn errors_fetching_by_index_out_of_bounds() {
fn quoted_column_access() { fn quoted_column_access() {
let actual = nu!( let actual = nu!(
cwd: "tests/fixtures/formats", 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"); assert_eq!(actual.out, "4");

View File

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

View File

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

View File

@ -8,7 +8,6 @@ fn sets_the_column_from_a_block_run_output() {
open cargo_sample.toml open cargo_sample.toml
| insert dev-dependencies.newdep "1" | insert dev-dependencies.newdep "1"
| get dev-dependencies.newdep | 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 } | insert content { open --raw cargo_sample.toml | lines | first 5 }
| get content.1 | get content.1
| str contains "nu" | 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) | insert content $(open --raw cargo_sample.toml | lines | first 5)
| get content.1 | get content.1
| str contains "nu" | str contains "nu"
| echo $it
"# "#
)); ));

View File

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