Compare commits

...

70 Commits

Author SHA1 Message Date
a30837298d Bump version (#2791) 2020-12-16 06:30:50 +13:00
f377a3a7b4 Added math abs command. (#2789) 2020-12-16 05:37:12 +13:00
83c874666a Date utility commands (#2780)
* updated & added date related commands based on the new design

* added proper error handling when date format string is invalid

* fixed format issue

* fixed an issue caused due to the change in primitive Date type

* added `date list-timezone` command to list all supported time zones and updated `date to-timezone` accordingly
2020-12-12 12:18:03 -06:00
e000ed47cd Fix Gitpod dev setup by forcing a dev image rebuild (#2783) 2020-12-09 06:44:23 +13:00
af2f064f42 Add random chars cmd (#2782) 2020-12-09 06:43:46 +13:00
9c7b25134b Parsing: Explain parsing errors and show sample lines. (#2774)
This makes the errors slightly better. It took me a while to realize I was missing the `--raw` flag.

```
open "data.csv" | from csv --separator ';'
```

    error: Could not parse as CSV split by ',' (Line 1: expected 1 fields, found 14)
      ┌─ shell:1:1
      │
    1 │ open "data.csv" | from csv --separator ';'
      │ ^^^^ ------------------------------------------------- value originates from here
      │ │
      │ input cannot be parsed as CSV split by ','. Sample input:
    Name;Data
    Ugly;row
    AnotherUgly;row

I think this still needs some refinement. Maybe we don't want to show
the separator all the time, omitting the defaults or the separator
on other formats.
2020-12-07 07:19:04 +13:00
2d15df9e6c Revert "Bump Rustyline to 7.0.0 (#2776)" (#2778)
This reverts commit e73278990c.
2020-12-05 17:12:42 +13:00
d2ab287756 Tell Nu to look for hash's rest columns paths first. (#2777) 2020-12-04 13:49:58 -05:00
e73278990c Bump Rustyline to 7.0.0 (#2776)
* Bump Rustyline to 7.0.0

* Append history instead of always save

* Add associated type to Hinter

* Convert to using Rustyline KeyEvent

* Use AcceptOrInsertLine as struct

* Cargo fmt

* Make convert_keyevent pub

* Better naming for RL conversion
2020-12-05 06:29:40 +13:00
12bc92df35 Make run_block public (#2772) 2020-12-02 23:00:30 +13:00
f19a801022 enhanced version command with more info (#2773) 2020-12-01 13:57:49 -06:00
b193303aa3 Add hash command with base64 subcommand (#2769)
* WIP try testing hash command

Ensure test worked

fmt

WIP get it working for other types of base64

Use optional named arg

WIP

* rebased and refactored a little with encoding and decoding

Fix some typos

Add some more charactersets

refactor several args into the encoding config struct and fix character_set arg. It needs to match the field

Add main hash command so it can be found via help

Added tests for running the whole pipeline

* add test case to cover invalid character sets

* clippy and fmt
2020-12-01 06:47:35 +13:00
e299e76fcf Bump to 0.23 (#2766) 2020-11-25 07:22:27 +13:00
c857e18c4a Avoid subtract overflow when no ending index given. (#2764) 2020-11-24 05:50:38 -05:00
5fb3df4054 Initial implementation of the random decimal subcommand. (#2762)
Co-authored-by: Stacy Maydew <stacy.maydew@starlab.io>
2020-11-24 22:19:48 +13:00
8b597187fc Path Command Enhancement Project (#2742)
* Add string argument support for path subcommands

* Add --replace option to 'path extension' command

* Add examples of replacing for path extension

* Refactor path extension and its example

* Add replacement functionality to path basename

* Refactor path subcommands to support more args

This adds a lot of redundancy to non-relevant subcommands such as type,
exists or expand.

* Add replace and num_levels options to path dirname

* Rename num_levels option to num-levels

* Remove commented code

* Clean up path basename

* Fix path dirname description

* Add path filestem opts; Rename extension -> suffix

* Add prefix option and examples to path filestem

* Fix broken num-levels of path dirname

* Fix failing example test of path filestem

* Fix failing test of path extension

* Formatting

* Add Windows-specific path subcommand examples

`path expand` is still broken but otherwise seems to fix all examples
on Windows

* Fix weird path expand on Windows

Also disable example tests for path expand. Failed caconicalization
(e.g., due to path not existing) returns the original path so the
examples always fail.

* Formatting

* Return path datatype when appropriate

* Do not append empty remainder to path dirname

* Add tests for path subcommands

* Formatting

* Revisit path subcommand description strings

* Apply clippy suggestions; Formatting

* Remove problematic test checking '~' expansion

Wouldn't run on minimal due to useing optional dependency.
The test success was also deending on the presence of home dir on the
testing machine which might not be completely robust.

* Add missing newline to file
2020-11-24 22:18:38 +13:00
930f9f0063 Fix new clippy warnings (#2760)
* Fix new clippy warnings

* Fork serde-hjson and bring in

* Fork serde-hjson and bring in

* Fix clippy lint again
2020-11-22 13:37:16 +13:00
63d4df9810 Fix broken links to the documentation (#2755)
- fix the "installation chapter of the book" link, the current link has no "en/" for English as it does for other languages
- correct the link "learning resources in our [documentation]", missing in the new site, where the documentation is well highlighted in the top bar. Rephrased to point to the cookbook
2020-11-19 17:21:05 +13:00
13ba533fc4 helps table columns align a little bit better (#2753)
* helps table columns align a little bit better

* no change to push CI to work again.
2020-11-18 07:18:12 -06:00
6d60bab2fd fix zipped themes by adding zip feature (#2752) 2020-11-16 13:00:48 -06:00
5be774b2e5 these changes reduce size by 24mb (#2747) 2020-11-12 09:39:42 -06:00
b412ff92c0 Seq with dates (#2746)
* seq with dates - wip

* everything seems to be working, yay!

* clippy
2020-11-11 14:35:02 -06:00
5a75e11b0e Revert "Getting closer to multiline scripts (#2738)" (#2745)
This reverts commit e66bf70589.
2020-11-10 18:22:13 +13:00
e66bf70589 Getting closer to multiline scripts (#2738)
* Begin allowing comments and multiline scripts.

* clippy

* Finish moving to groups. Test pass
2020-11-10 16:52:42 +13:00
3924e9d50a added as_html switch so a selector can be passed to a selector (#2739) 2020-11-09 13:37:32 -06:00
8df748463d Getting ready for multiline scripts (#2737)
* WIP

* WIP

* WIP

* Tests are passing

* make parser more resilient

* lint
2020-11-10 05:27:07 +13:00
0113661c81 Flag to clear history file (#2720) 2020-11-10 05:23:41 +13:00
0ee054b14d Fix to md errors (#2729)
* Fix to md errors

* Fix variable name and avoid typecasts
2020-11-07 06:40:53 +13:00
80b39454ff Change Nu Shell and NuShell to Nushell (#2728) 2020-11-07 06:39:49 +13:00
97f3671e2c web scraping with css selectors (#2725)
* first step of making selector

* wip

* wip tests working

* probably good enough for a first pass

* oops, missed something.

* and something else...

* grrrr version errors
2020-11-03 15:46:42 -06:00
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
299 changed files with 14731 additions and 3803 deletions

6
.gitpod.Dockerfile vendored
View File

@ -1,5 +1,9 @@
FROM gitpod/workspace-full FROM gitpod/workspace-full
# Gitpod will not rebuild Nushell's dev image unless *some* change is made to this Dockerfile.
# To force a rebuild, simply increase this counter:
ENV TRIGGER_REBUILD 1
USER gitpod USER gitpod
RUN sudo apt-get update && \ RUN sudo apt-get update && \
@ -11,4 +15,4 @@ RUN sudo apt-get update && \
rust-lldb \ rust-lldb \
&& sudo rm -rf /var/lib/apt/lists/* && sudo rm -rf /var/lib/apt/lists/*
ENV RUST_LLDB=/usr/bin/lldb-8 ENV RUST_LLDB=/usr/bin/lldb-11

2166
Cargo.lock generated

File diff suppressed because it is too large Load Diff

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.24.0"
[workspace] [workspace]
members = ["crates/*/"] members = ["crates/*/"]
@ -18,32 +18,33 @@ 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.24.0", path = "./crates/nu-cli"}
nu-data = {version = "0.21.0", path = "./crates/nu-data"} nu-data = {version = "0.24.0", path = "./crates/nu-data"}
nu-errors = {version = "0.21.0", path = "./crates/nu-errors"} nu-errors = {version = "0.24.0", path = "./crates/nu-errors"}
nu-parser = {version = "0.21.0", path = "./crates/nu-parser"} nu-parser = {version = "0.24.0", path = "./crates/nu-parser"}
nu-plugin = {version = "0.21.0", path = "./crates/nu-plugin"} nu-plugin = {version = "0.24.0", path = "./crates/nu-plugin"}
nu-protocol = {version = "0.21.0", path = "./crates/nu-protocol"} nu-protocol = {version = "0.24.0", path = "./crates/nu-protocol"}
nu-source = {version = "0.21.0", path = "./crates/nu-source"} nu-source = {version = "0.24.0", path = "./crates/nu-source"}
nu-value-ext = {version = "0.21.0", path = "./crates/nu-value-ext"} nu-value-ext = {version = "0.24.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.24.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.24.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.24.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.24.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.24.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.24.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.24.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.24.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.24.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.24.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.24.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.24.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.24.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.24.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.24.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.24.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.24.0", path = "./crates/nu_plugin_xpath", optional = true}
nu_plugin_selector = {version = "0.24.0", path = "./crates/nu_plugin_selector", optional = true}
# Required to bootstrap the main binary # Required to bootstrap the main binary
clap = "2.33.3" clap = "2.33.3"
@ -51,10 +52,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.24.0", path = "./crates/nu-test-support"}
[build-dependencies] [build-dependencies]
@ -86,8 +88,9 @@ default = [
"post", "post",
"fetch", "fetch",
"rich-benchmark", "rich-benchmark",
"zip-support"
] ]
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3", "chart", "xpath"] extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3", "chart", "xpath", "selector"]
stable = ["default"] stable = ["default"]
wasi = ["inc", "match", "directories-support", "ptree-support", "match", "tree", "rustyline-support"] wasi = ["inc", "match", "directories-support", "ptree-support", "match", "tree", "rustyline-support"]
@ -102,6 +105,7 @@ post = ["nu_plugin_post"]
ps = ["nu_plugin_ps"] ps = ["nu_plugin_ps"]
sys = ["nu_plugin_sys"] sys = ["nu_plugin_sys"]
textview = ["nu_plugin_textview"] textview = ["nu_plugin_textview"]
zip-support = ["nu-cli/zip"]
# Extra # Extra
binaryview = ["nu_plugin_binaryview"] binaryview = ["nu_plugin_binaryview"]
@ -114,6 +118,13 @@ start = ["nu_plugin_start"]
trash-support = ["nu-cli/trash-support"] trash-support = ["nu-cli/trash-support"]
tree = ["nu_plugin_tree"] tree = ["nu_plugin_tree"]
xpath = ["nu_plugin_xpath"] xpath = ["nu_plugin_xpath"]
selector = ["nu_plugin_selector"]
[profile.release]
#strip = "symbols" #Couldn't get working +nightly
opt-level = 'z' #Optimize for size
lto = true #Link Time Optimization
codegen-units = 1 #Reduce parallel codegen units
# Core plugins that ship with `cargo install nu` by default # Core plugins that ship with `cargo install nu` by default
# Currently, Cargo limits us to installing only one binary # Currently, Cargo limits us to installing only one binary
@ -190,6 +201,31 @@ 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_selector"
path = "src/plugins/nu_plugin_extra_selector.rs"
required-features = ["selector"]
[[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

@ -7,7 +7,7 @@
[![The Changelog #363](https://img.shields.io/badge/The%20Changelog-%23363-61c192.svg)](https://changelog.com/podcast/363) [![The Changelog #363](https://img.shields.io/badge/The%20Changelog-%23363-61c192.svg)](https://changelog.com/podcast/363)
[![@nu_shell](https://img.shields.io/badge/twitter-@nu_shell-1DA1F3?style=flat-square)](https://twitter.com/nu_shell) [![@nu_shell](https://img.shields.io/badge/twitter-@nu_shell-1DA1F3?style=flat-square)](https://twitter.com/nu_shell)
## Nu Shell ## Nushell
A new type of shell. A new type of shell.
@ -34,7 +34,7 @@ There are also [good first issues](https://github.com/nushell/nushell/issues?q=i
We also have an active [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell) if you'd like to come and chat with us. We also have an active [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell) if you'd like to come and chat with us.
You can also find more learning resources in our [documentation](https://www.nushell.sh/documentation.html) site. You can also find information on more specific topics in our [cookbook](https://www.nushell.sh/cookbook/).
Try it in Gitpod. Try it in Gitpod.
@ -44,9 +44,9 @@ Try it in Gitpod.
### Local ### Local
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/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.
@ -307,7 +307,7 @@ Nu is in heavy development, and will naturally change as it matures and people u
## Current Roadmap ## Current Roadmap
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in NuShell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2). We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in Nushell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
## Contributing ## Contributing

View File

@ -4,32 +4,36 @@ description = "CLI for nushell"
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
name = "nu-cli" name = "nu-cli"
version = "0.21.0" version = "0.24.0"
build = "build.rs"
[lib] [lib]
doctest = false doctest = false
[dependencies] [dependencies]
nu-data = {version = "0.21.0", path = "../nu-data"} nu-data = {version = "0.24.0", path = "../nu-data"}
nu-errors = {version = "0.21.0", path = "../nu-errors"} nu-errors = {version = "0.24.0", path = "../nu-errors"}
nu-parser = {version = "0.21.0", path = "../nu-parser"} nu-json = {version = "0.24.0", path = "../nu-json"}
nu-plugin = {version = "0.21.0", path = "../nu-plugin"} nu-parser = {version = "0.24.0", path = "../nu-parser"}
nu-protocol = {version = "0.21.0", path = "../nu-protocol"} nu-plugin = {version = "0.24.0", path = "../nu-plugin"}
nu-source = {version = "0.21.0", path = "../nu-source"} nu-protocol = {version = "0.24.0", path = "../nu-protocol"}
nu-table = {version = "0.21.0", path = "../nu-table"} nu-source = {version = "0.24.0", path = "../nu-source"}
nu-test-support = {version = "0.21.0", path = "../nu-test-support"} nu-table = {version = "0.24.0", path = "../nu-table"}
nu-value-ext = {version = "0.21.0", path = "../nu-value-ext"} nu-test-support = {version = "0.24.0", path = "../nu-test-support"}
nu-value-ext = {version = "0.24.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"
async-trait = "0.1.40" async-trait = "0.1.40"
base64 = "0.12.3" base64 = "0.13.0"
bigdecimal = {version = "0.2.0", features = ["serde"]} bigdecimal = {version = "0.2.0", features = ["serde"]}
byte-unit = "4.0.9" byte-unit = "4.0.9"
bytes = "0.5.6" bytes = "0.5.6"
calamine = "0.16.1" calamine = "0.16.1"
chrono = {version = "0.4.15", features = ["serde"]} chrono = {version = "0.4.15", features = ["serde"]}
chrono-tz = "0.5.3"
clap = "2.33.3" clap = "2.33.3"
clipboard = {version = "0.5.0", optional = true}
codespan-reporting = "0.9.5" codespan-reporting = "0.9.5"
csv = "1.1.3" csv = "1.1.3"
ctrlc = {version = "3.1.6", optional = true} ctrlc = {version = "3.1.6", optional = true}
@ -39,11 +43,12 @@ dirs = {version = "3.0.1", optional = true}
dtparse = "1.2.0" dtparse = "1.2.0"
dunce = "1.0.1" dunce = "1.0.1"
eml-parser = "0.1.0" eml-parser = "0.1.0"
encoding_rs = "0.8.24"
filesize = "0.2.0" filesize = "0.2.0"
fs_extra = "1.2.0" fs_extra = "1.2.0"
futures = {version = "0.3.5", features = ["compat", "io-compat"]} futures = {version = "0.3.5", features = ["compat", "io-compat"]}
futures-util = "0.3.5"
futures_codec = "0.4.1" futures_codec = "0.4.1"
futures-util = "0.3.5"
getset = "0.1.1" getset = "0.1.1"
git2 = {version = "0.13.11", default_features = false, optional = true} git2 = {version = "0.13.11", default_features = false, optional = true}
glob = "0.3.0" glob = "0.3.0"
@ -52,7 +57,9 @@ htmlescape = "0.3.1"
ical = "0.6.0" ical = "0.6.0"
ichwh = {version = "0.3.4", optional = true} ichwh = {version = "0.3.4", optional = true}
indexmap = {version = "1.6.0", features = ["serde-1"]} indexmap = {version = "1.6.0", features = ["serde-1"]}
Inflector = "0.11"
itertools = "0.9.0" itertools = "0.9.0"
lazy_static = "1.*"
log = "0.4.11" log = "0.4.11"
meval = "0.2.0" meval = "0.2.0"
num-bigint = {version = "0.3.0", features = ["serde"]} num-bigint = {version = "0.3.0", features = ["serde"]}
@ -65,12 +72,12 @@ ptree = {version = "0.3.0", optional = true}
query_interface = "0.3.5" query_interface = "0.3.5"
quick-xml = "0.18.1" quick-xml = "0.18.1"
rand = "0.7.3" rand = "0.7.3"
rayon = "1.4.0"
regex = "1.3.9" regex = "1.3.9"
roxmltree = "0.13.0" roxmltree = "0.13.0"
rust-embed = "5.6.0" rust-embed = "5.6.0"
rustyline = {version = "6.3.0", optional = true} rustyline = {version = "6.3.0", optional = true}
serde = {version = "1.0.115", features = ["derive"]} serde = {version = "1.0.115", features = ["derive"]}
serde-hjson = "0.9.1"
serde_bytes = "0.11.5" serde_bytes = "0.11.5"
serde_ini = "0.2.0" serde_ini = "0.2.0"
serde_json = "1.0.57" serde_json = "1.0.57"
@ -85,20 +92,16 @@ tempfile = "3.1.0"
term = {version = "0.6.1", optional = true} term = {version = "0.6.1", optional = true}
term_size = "0.3.2" term_size = "0.3.2"
termcolor = "1.1.0" termcolor = "1.1.0"
titlecase = "1.0"
toml = "0.5.6" toml = "0.5.6"
trash = {version = "1.2.0", optional = true}
unicode-segmentation = "1.6.0" unicode-segmentation = "1.6.0"
uom = {version = "0.28.0", features = ["f64", "try-from"]} uom = {version = "0.28.0", features = ["f64", "try-from"]}
url = "2.1.1"
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}
Inflector = "0.11"
clipboard = {version = "0.5.0", optional = true}
encoding_rs = "0.8.24"
rayon = "1.4.0"
trash = {version = "1.1.1", optional = true}
url = "2.1.1"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
umask = "1.0.0" umask = "1.0.0"
users = "0.10.0" users = "0.10.0"
@ -115,7 +118,7 @@ optional = true
version = "0.24.0" version = "0.24.0"
[build-dependencies] [build-dependencies]
git2 = {version = "0.13.11", optional = true} shadow-rs = "0.3.20"
[dev-dependencies] [dev-dependencies]
quickcheck = "0.9.2" quickcheck = "0.9.2"

View File

@ -1,36 +1,6 @@
use std::path::Path; fn main() -> shadow_rs::SdResult<()> {
use std::{env, fs, io}; let src_path = std::env::var("CARGO_MANIFEST_DIR")?;
let out_path = std::env::var("OUT_DIR")?;
fn main() -> Result<(), io::Error> { shadow_rs::Shadow::build(src_path, out_path)?;
let out_dir = env::var_os("OUT_DIR").expect(
"\
OUT_DIR environment variable not found. \
OUT_DIR is guaranteed to to exist in a build script by cargo - see \
https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts\
");
let latest_commit_hash = latest_commit_hash(env::current_dir()?).unwrap_or_default();
let commit_hash_path = Path::new(&out_dir).join("git_commit_hash");
fs::write(commit_hash_path, latest_commit_hash)?;
Ok(()) Ok(())
} }
#[allow(unused_variables)]
fn latest_commit_hash<P: AsRef<Path>>(dir: P) -> Result<String, Box<dyn std::error::Error>> {
#[cfg(feature = "git2")]
{
use git2::Repository;
let dir = dir.as_ref();
Ok(Repository::discover(dir)?
.head()?
.peel_to_commit()?
.id()
.to_string())
}
#[cfg(not(feature = "git2"))]
{
Ok(String::new())
}
}

View File

@ -86,8 +86,10 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(Touch), whole_stream_command(Touch),
whole_stream_command(Cpy), whole_stream_command(Cpy),
whole_stream_command(Date), whole_stream_command(Date),
whole_stream_command(DateListTimeZone),
whole_stream_command(DateNow), whole_stream_command(DateNow),
whole_stream_command(DateUTC), whole_stream_command(DateToTable),
whole_stream_command(DateToTimeZone),
whole_stream_command(DateFormat), whole_stream_command(DateFormat),
whole_stream_command(Cal), whole_stream_command(Cal),
whole_stream_command(Mkdir), whole_stream_command(Mkdir),
@ -120,6 +122,8 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(Autoview), whole_stream_command(Autoview),
whole_stream_command(Table), whole_stream_command(Table),
// Text manipulation // Text manipulation
whole_stream_command(Hash),
whole_stream_command(HashBase64),
whole_stream_command(Split), whole_stream_command(Split),
whole_stream_command(SplitColumn), whole_stream_command(SplitColumn),
whole_stream_command(SplitRow), whole_stream_command(SplitRow),
@ -159,7 +163,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 +184,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 +203,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),
@ -211,6 +217,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(AutoenvTrust), whole_stream_command(AutoenvTrust),
whole_stream_command(AutoenvUnTrust), whole_stream_command(AutoenvUnTrust),
whole_stream_command(Math), whole_stream_command(Math),
whole_stream_command(MathAbs),
whole_stream_command(MathAverage), whole_stream_command(MathAverage),
whole_stream_command(MathEval), whole_stream_command(MathEval),
whole_stream_command(MathMedian), whole_stream_command(MathMedian),
@ -221,6 +228,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),
@ -258,6 +268,8 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
#[cfg(feature = "uuid_crate")] #[cfg(feature = "uuid_crate")]
whole_stream_command(RandomUUID), whole_stream_command(RandomUUID),
whole_stream_command(RandomInteger), whole_stream_command(RandomInteger),
whole_stream_command(RandomDecimal),
whole_stream_command(RandomChars),
// Path // Path
whole_stream_command(PathBasename), whole_stream_command(PathBasename),
whole_stream_command(PathCommand), whole_stream_command(PathCommand),
@ -273,6 +285,8 @@ 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),
whole_stream_command(SeqDates),
]); ]);
#[cfg(feature = "clipboard-cli")] #[cfg(feature = "clipboard-cli")]
@ -317,6 +331,7 @@ pub async fn run_vec_of_pipelines(
#[cfg(feature = "rustyline-support")] #[cfg(feature = "rustyline-support")]
fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>) -> LineResult { fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>) -> LineResult {
match input { match input {
Ok(s) if s == "history -c" || s == "history --clear" => LineResult::ClearHistory,
Ok(s) => LineResult::Success(s), Ok(s) => LineResult::Success(s),
Err(ReadlineError::Interrupted) => LineResult::CtrlC, Err(ReadlineError::Interrupted) => LineResult::CtrlC,
Err(ReadlineError::Eof) => LineResult::CtrlD, Err(ReadlineError::Eof) => LineResult::CtrlD,
@ -386,55 +401,56 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
if let Some(prompt) = configuration.var("prompt") { if let Some(prompt) = configuration.var("prompt") {
let prompt_line = prompt.as_string()?; let prompt_line = prompt.as_string()?;
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) { let (result, err) = nu_parser::lite_parse(&prompt_line, 0);
Ok(result) => {
let mut prompt_block =
nu_parser::classify_block(&result, context.registry());
let env = context.get_env(); if err.is_some() {
use crate::git::current_branch;
format!(
"\x1b[32m{}{}\x1b[m> ",
cwd,
match current_branch() {
Some(s) => format!("({})", s),
None => "".to_string(),
}
)
} else {
let prompt_block = nu_parser::classify_block(&result, context.registry());
prompt_block.block.expand_it_usage(); let env = context.get_env();
match run_block( match run_block(
&prompt_block.block, &prompt_block.block,
&mut context, &mut context,
InputStream::empty(), InputStream::empty(),
Scope::from_env(env), Scope::from_env(env),
) )
.await .await
{ {
Ok(result) => match result.collect_string(Tag::unknown()).await { Ok(result) => match result.collect_string(Tag::unknown()).await {
Ok(string_result) => { Ok(string_result) => {
let errors = context.get_errors(); let errors = context.get_errors();
context.maybe_print_errors(Text::from(prompt_line)); context.maybe_print_errors(Text::from(prompt_line));
context.clear_errors(); context.clear_errors();
if !errors.is_empty() {
"> ".to_string()
} else {
string_result.item
}
}
Err(e) => {
crate::cli::print_err(e, &Text::from(prompt_line));
context.clear_errors();
if !errors.is_empty() {
"> ".to_string() "> ".to_string()
} else {
string_result.item
} }
}, }
Err(e) => { Err(e) => {
crate::cli::print_err(e, &Text::from(prompt_line)); crate::cli::print_err(e, &Text::from(prompt_line));
context.clear_errors(); context.clear_errors();
"> ".to_string() "> ".to_string()
} }
} },
} Err(e) => {
Err(e) => { crate::cli::print_err(e, &Text::from(prompt_line));
crate::cli::print_err(e, &Text::from(prompt_line)); context.clear_errors();
context.clear_errors();
"> ".to_string() "> ".to_string()
}
} }
} }
} else { } else {
@ -496,6 +512,11 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
context.maybe_print_errors(Text::from(line)); context.maybe_print_errors(Text::from(line));
} }
LineResult::ClearHistory => {
rl.clear_history();
let _ = rl.save_history(&history_path);
}
LineResult::Error(line, err) => { LineResult::Error(line, err) => {
rl.add_history_entry(&line); rl.add_history_entry(&line);
let _ = rl.save_history(&history_path); let _ = rl.save_history(&history_path);
@ -831,8 +852,8 @@ fn rustyline_hinter(config: &dyn nu_data::config::Conf) -> Option<rustyline::hin
} }
fn chomp_newline(s: &str) -> &str { fn chomp_newline(s: &str) -> &str {
if s.ends_with('\n') { if let Some(s) = s.strip_suffix('\n') {
&s[..s.len() - 1] s
} else { } else {
s s
} }
@ -845,20 +866,23 @@ pub enum LineResult {
Break, Break,
CtrlC, CtrlC,
CtrlD, CtrlD,
ClearHistory,
} }
pub async fn parse_and_eval(line: &str, ctx: &mut EvaluationContext) -> Result<String, ShellError> { pub async fn parse_and_eval(line: &str, ctx: &mut EvaluationContext) -> Result<String, ShellError> {
let line = if line.ends_with('\n') { let line = if let Some(s) = line.strip_suffix('\n') {
&line[..line.len() - 1] s
} else { } else {
line line
}; };
let lite_result = nu_parser::lite_parse(&line, 0)?; let (lite_result, err) = nu_parser::lite_parse(&line, 0);
if let Some(err) = err {
return Err(err.into());
}
// 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();
@ -888,18 +912,16 @@ pub async fn process_line(
let line = chomp_newline(line); let line = chomp_newline(line);
ctx.raw_input = line.to_string(); ctx.raw_input = line.to_string();
let result = match nu_parser::lite_parse(&line, 0) { let (result, err) = nu_parser::lite_parse(&line, 0);
Err(err) => {
return LineResult::Error(line.to_string(), err.into());
}
Ok(val) => val, if let Some(err) = err {
}; return LineResult::Error(line.to_string(), err.into());
}
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 +1038,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(
@ -1093,7 +1113,8 @@ pub fn print_err(err: ShellError, source: &Text) {
mod tests { mod tests {
#[quickcheck] #[quickcheck]
fn quickcheck_parse(data: String) -> bool { fn quickcheck_parse(data: String) -> bool {
if let Ok(lite_block) = nu_parser::lite_parse(&data, 0) { let (lite_block, err) = nu_parser::lite_parse(&data, 0);
if err.is_none() {
let context = crate::evaluation_context::EvaluationContext::basic().unwrap(); let context = crate::evaluation_context::EvaluationContext::basic().unwrap();
let _ = nu_parser::classify_block(&lite_block, context.registry()); let _ = nu_parser::classify_block(&lite_block, context.registry());
} }

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;
@ -61,6 +62,7 @@ pub(crate) mod from_yaml;
pub(crate) mod get; pub(crate) mod get;
pub(crate) mod group_by; pub(crate) mod group_by;
pub(crate) mod group_by_date; pub(crate) mod group_by_date;
pub(crate) mod hash_;
pub(crate) mod headers; pub(crate) mod headers;
pub(crate) mod help; pub(crate) mod help;
pub(crate) mod histogram; pub(crate) mod histogram;
@ -97,6 +99,8 @@ 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 seq_dates;
pub(crate) mod shells; pub(crate) mod shells;
pub(crate) mod shuffle; pub(crate) mod shuffle;
pub(crate) mod size; pub(crate) mod size;
@ -150,7 +154,7 @@ pub(crate) use config::{
}; };
pub(crate) use count::Count; pub(crate) use count::Count;
pub(crate) use cp::Cpy; pub(crate) use cp::Cpy;
pub(crate) use date::{Date, DateFormat, DateNow, DateUTC}; pub(crate) use date::{Date, DateFormat, DateListTimeZone, DateNow, DateToTable, DateToTimeZone};
pub(crate) use debug::Debug; pub(crate) use debug::Debug;
pub(crate) use default::Default; pub(crate) use default::Default;
pub(crate) use describe::Describe; pub(crate) use describe::Describe;
@ -175,7 +179,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;
@ -195,6 +200,7 @@ pub(crate) use from_yaml::FromYML;
pub(crate) use get::Get; pub(crate) use get::Get;
pub(crate) use group_by::Command as GroupBy; pub(crate) use group_by::Command as GroupBy;
pub(crate) use group_by_date::GroupByDate; pub(crate) use group_by_date::GroupByDate;
pub(crate) use hash_::{Hash, HashBase64};
pub(crate) use headers::Headers; pub(crate) use headers::Headers;
pub(crate) use help::Help; pub(crate) use help::Help;
pub(crate) use histogram::Histogram; pub(crate) use histogram::Histogram;
@ -206,12 +212,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, MathAbs, MathAverage, MathCeil, MathEval, MathFloor, MathMaximum, MathMedian,
MathStddev, MathSummation, MathVariance, MathMinimum, 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;
@ -226,7 +232,9 @@ pub(crate) use prev::Previous;
pub(crate) use pwd::Pwd; pub(crate) use pwd::Pwd;
#[cfg(feature = "uuid_crate")] #[cfg(feature = "uuid_crate")]
pub(crate) use random::RandomUUID; pub(crate) use random::RandomUUID;
pub(crate) use random::{Random, RandomBool, RandomDice, RandomInteger}; pub(crate) use random::{
Random, RandomBool, RandomChars, RandomDecimal, RandomDice, RandomInteger,
};
pub(crate) use range::Range; pub(crate) use range::Range;
pub(crate) use reduce::Reduce; pub(crate) use reduce::Reduce;
pub(crate) use reject::Reject; pub(crate) use reject::Reject;
@ -236,6 +244,8 @@ 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 seq_dates::SeqDates;
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 +288,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 +316,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

@ -9,7 +9,7 @@ use nu_protocol::hir::{Block, ClassifiedCommand, Commands};
use nu_protocol::{ReturnSuccess, Scope, UntaggedValue, Value}; use nu_protocol::{ReturnSuccess, Scope, UntaggedValue, Value};
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
pub(crate) async fn run_block( pub async fn run_block(
block: &Block, block: &Block,
ctx: &mut EvaluationContext, ctx: &mut EvaluationContext,
mut input: InputStream, mut input: InputStream,

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

@ -63,7 +63,7 @@ pub async fn clip(
let mut first = true; let mut first = true;
for i in values.iter() { for i in values.iter() {
if !first { if !first {
new_copy_data.push_str("\n"); new_copy_data.push('\n');
} else { } else {
first = false; first = false;
} }

View File

@ -1,18 +1,18 @@
use crate::prelude::*;
use chrono::{DateTime, Local};
use nu_errors::ShellError;
use crate::commands::date::utils::{date_to_value, date_to_value_raw};
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue}; use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
Dictionary, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::Tagged; use nu_source::Tagged;
use std::fmt::{self, write};
pub struct Date; pub struct Date;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct FormatArgs { pub struct FormatArgs {
format: Tagged<String>, format: Tagged<String>,
raw: Option<bool>, table: bool,
} }
#[async_trait] #[async_trait]
@ -24,11 +24,11 @@ impl WholeStreamCommand for Date {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("date format") Signature::build("date format")
.required("format", SyntaxShape::String, "strftime format") .required("format", SyntaxShape::String, "strftime format")
.switch("raw", "print date without tables", Some('r')) .switch("table", "print date in a table", Some('t'))
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"format the current date using the given format string." "Format a given date using the given format string."
} }
async fn run( async fn run(
@ -38,6 +38,21 @@ impl WholeStreamCommand for Date {
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
format(args, registry).await format(args, registry).await
} }
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Format the current date",
example: "date now | date format '%Y.%m.%d_%H %M %S,%z'",
result: None,
},
Example {
description: "Format the current date and print in a table",
example: "date now | date format -t '%Y-%m-%d_%H:%M:%S %z'",
result: None,
},
]
}
} }
pub async fn format( pub async fn format(
@ -46,20 +61,46 @@ pub async fn format(
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let (FormatArgs { format, raw }, _) = args.process(&registry).await?; let (FormatArgs { format, table }, input) = args.process(&registry).await?;
let dt_fmt = format.to_string(); Ok(input
.map(move |value| match value {
Value {
value: UntaggedValue::Primitive(Primitive::Date(dt)),
..
} => {
let mut output = String::new();
if let Err(fmt::Error) =
write(&mut output, format_args!("{}", dt.format(&format.item)))
{
Err(ShellError::labeled_error(
"The date format is invalid",
"invalid strftime format",
&format.tag,
))
} else {
let value = if table {
let mut indexmap = IndexMap::new();
indexmap.insert(
"formatted".to_string(),
UntaggedValue::string(&output).into_value(&tag),
);
let value = { UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
let local: DateTime<Local> = Local::now(); } else {
if let Some(true) = raw { UntaggedValue::string(&output).into_value(&tag)
UntaggedValue::string(date_to_value_raw(local, dt_fmt)).into_untagged_value() };
} else {
date_to_value(local, tag, dt_fmt)
}
};
Ok(OutputStream::one(value)) ReturnSuccess::value(value)
}
}
_ => Err(ShellError::labeled_error(
"Expected a date from pipeline",
"requires date input",
&tag,
)),
})
.to_output_stream())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -0,0 +1,82 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use chrono_tz::TZ_VARIANTS;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{Dictionary, ReturnSuccess, Signature, UntaggedValue};
pub struct Date;
#[async_trait]
impl WholeStreamCommand for Date {
fn name(&self) -> &str {
"date list-timezone"
}
fn signature(&self) -> Signature {
Signature::build("date list-timezone")
}
fn usage(&self) -> &str {
"List supported time zones."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
list_timezone(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "List all supported time zones",
example: "date list-timezone",
result: None,
},
Example {
description: "List all supported European time zones",
example: "date list-timezone | where timezone =~ Europe",
result: None,
},
]
}
}
async fn list_timezone(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(&registry).await?;
let tag = args.call_info.name_tag.clone();
let list = TZ_VARIANTS.iter().map(move |tz| {
let mut entries = IndexMap::new();
entries.insert(
"timezone".to_string(),
UntaggedValue::string(tz.name()).into_value(&tag),
);
Ok(ReturnSuccess::Value(
UntaggedValue::Row(Dictionary { entries }).into_value(&tag),
))
});
Ok(futures::stream::iter(list).to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Date;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(Date {})?)
}
}

View File

@ -1,11 +1,15 @@
pub mod command; pub mod command;
pub mod format; pub mod format;
pub mod list_timezone;
pub mod now; pub mod now;
pub mod utc; pub mod to_table;
pub mod to_timezone;
mod utils; mod parser;
pub use command::Command as Date; pub use command::Command as Date;
pub use format::Date as DateFormat; pub use format::Date as DateFormat;
pub use list_timezone::Date as DateListTimeZone;
pub use now::Date as DateNow; pub use now::Date as DateNow;
pub use utc::Date as DateUTC; pub use to_table::Date as DateToTable;
pub use to_timezone::Date as DateToTimeZone;

View File

@ -1,10 +1,8 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use chrono::{DateTime, Local}; use chrono::{DateTime, Local};
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, UntaggedValue};
use crate::commands::date::utils::date_to_value;
use crate::commands::WholeStreamCommand;
use nu_protocol::Signature;
pub struct Date; pub struct Date;
@ -19,7 +17,7 @@ impl WholeStreamCommand for Date {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"return the current date." "Get the current date."
} }
async fn run( async fn run(
@ -35,16 +33,12 @@ pub async fn now(
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?; let args = args.evaluate_once(&registry).await?;
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let no_fmt = "".to_string(); let now: DateTime<Local> = Local::now();
let value = { let value = UntaggedValue::date(now.with_timezone(now.offset())).into_value(&tag);
let local: DateTime<Local> = Local::now();
date_to_value(local, tag, no_fmt)
};
Ok(OutputStream::one(value)) Ok(OutputStream::one(value))
} }

View File

@ -0,0 +1,107 @@
// Modified from chrono::format::scan
use chrono::{DateTime, FixedOffset, Local, Offset, TimeZone};
use chrono_tz::Tz;
use titlecase::titlecase;
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub enum ParseErrorKind {
/// Given field is out of permitted range.
OutOfRange,
/// The input string has some invalid character sequence for given formatting items.
Invalid,
/// The input string has been prematurely ended.
TooShort,
}
pub fn datetime_in_timezone(
dt: &DateTime<FixedOffset>,
s: &str,
) -> Result<DateTime<FixedOffset>, ParseErrorKind> {
match timezone_offset_internal(s, true, true) {
Ok(offset) => match FixedOffset::east_opt(offset) {
Some(offset) => Ok(dt.with_timezone(&offset)),
None => Err(ParseErrorKind::OutOfRange),
},
Err(ParseErrorKind::Invalid) => {
if s.to_lowercase() == "local" {
Ok(dt.with_timezone(Local::now().offset()))
} else {
let tz: Tz = parse_timezone_internal(s)?;
let offset = tz.offset_from_utc_datetime(&dt.naive_utc()).fix();
Ok(dt.with_timezone(&offset))
}
}
Err(e) => Err(e),
}
}
fn parse_timezone_internal(s: &str) -> Result<Tz, ParseErrorKind> {
if let Ok(tz) = s.parse() {
Ok(tz)
} else if let Ok(tz) = titlecase(s).parse() {
Ok(tz)
} else if let Ok(tz) = s.to_uppercase().parse() {
Ok(tz)
} else {
Err(ParseErrorKind::Invalid)
}
}
fn timezone_offset_internal(
mut s: &str,
consume_colon: bool,
allow_missing_minutes: bool,
) -> Result<i32, ParseErrorKind> {
fn digits(s: &str) -> Result<(u8, u8), ParseErrorKind> {
let b = s.as_bytes();
if b.len() < 2 {
Err(ParseErrorKind::TooShort)
} else {
Ok((b[0], b[1]))
}
}
let negative = match s.as_bytes().first() {
Some(&b'+') => false,
Some(&b'-') => true,
Some(_) => return Err(ParseErrorKind::Invalid),
None => return Err(ParseErrorKind::TooShort),
};
s = &s[1..];
// hours (00--99)
let hours = match digits(s)? {
(h1 @ b'0'..=b'9', h2 @ b'0'..=b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')),
_ => return Err(ParseErrorKind::Invalid),
};
s = &s[2..];
// colons (and possibly other separators)
if consume_colon {
s = s.trim_start_matches(|c: char| c == ':' || c.is_whitespace());
}
// minutes (00--59)
// if the next two items are digits then we have to add minutes
let minutes = if let Ok(ds) = digits(s) {
match ds {
(m1 @ b'0'..=b'5', m2 @ b'0'..=b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')),
(b'6'..=b'9', b'0'..=b'9') => return Err(ParseErrorKind::OutOfRange),
_ => return Err(ParseErrorKind::Invalid),
}
} else if allow_missing_minutes {
0
} else {
return Err(ParseErrorKind::TooShort);
};
match s.len() {
len if len >= 2 => &s[2..],
len if len == 0 => s,
_ => return Err(ParseErrorKind::TooShort),
};
let seconds = hours * 3600 + minutes * 60;
Ok(if negative { -seconds } else { seconds })
}

View File

@ -0,0 +1,113 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use chrono::{Datelike, Timelike};
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{Dictionary, Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
pub struct Date;
#[async_trait]
impl WholeStreamCommand for Date {
fn name(&self) -> &str {
"date to-table"
}
fn signature(&self) -> Signature {
Signature::build("date to-table")
}
fn usage(&self) -> &str {
"Print the date in a structured table."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
to_table(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Print the current date in a table",
example: "date now | date to-table",
result: None,
}]
}
}
async fn to_table(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.call_info.name_tag.clone();
let input = args.input;
Ok(input
.map(move |value| match value {
Value {
value: UntaggedValue::Primitive(Primitive::Date(dt)),
..
} => {
let mut indexmap = IndexMap::new();
indexmap.insert(
"year".to_string(),
UntaggedValue::int(dt.year()).into_value(&tag),
);
indexmap.insert(
"month".to_string(),
UntaggedValue::int(dt.month()).into_value(&tag),
);
indexmap.insert(
"day".to_string(),
UntaggedValue::int(dt.day()).into_value(&tag),
);
indexmap.insert(
"hour".to_string(),
UntaggedValue::int(dt.hour()).into_value(&tag),
);
indexmap.insert(
"minute".to_string(),
UntaggedValue::int(dt.minute()).into_value(&tag),
);
indexmap.insert(
"second".to_string(),
UntaggedValue::int(dt.second()).into_value(&tag),
);
let tz = dt.offset();
indexmap.insert(
"timezone".to_string(),
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
);
let value = UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag);
ReturnSuccess::value(value)
}
_ => Err(ShellError::labeled_error(
"Expected a date from pipeline",
"requires date input",
&tag,
)),
})
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Date;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(Date {})?)
}
}

View File

@ -0,0 +1,118 @@
use crate::commands::date::parser::{datetime_in_timezone, ParseErrorKind};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct Date;
#[derive(Deserialize)]
struct DateToTimeZoneArgs {
timezone: Tagged<String>,
}
#[async_trait]
impl WholeStreamCommand for Date {
fn name(&self) -> &str {
"date to-timezone"
}
fn signature(&self) -> Signature {
Signature::build("date to-timezone").required(
"time zone",
SyntaxShape::String,
"time zone description",
)
}
fn usage(&self) -> &str {
"Convert a date to a given time zone.
Use `date list-timezone` to list all supported time zones.
"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
to_timezone(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get the current date in UTC+05:00",
example: "date now | date to-timezone +0500",
result: None,
},
Example {
description: "Get the current local date",
example: "date now | date to-timezone local",
result: None,
},
Example {
description: "Get the current date in Hawaii",
example: "date now | date to-timezone US/Hawaii",
result: None,
},
]
}
}
async fn to_timezone(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let tag = args.call_info.name_tag.clone();
let (DateToTimeZoneArgs { timezone }, input) = args.process(&registry).await?;
Ok(input
.map(move |value| match value {
Value {
value: UntaggedValue::Primitive(Primitive::Date(dt)),
..
} => match datetime_in_timezone(&dt, &timezone.item) {
Ok(dt) => {
let value = UntaggedValue::date(dt).into_value(&tag);
ReturnSuccess::value(value)
}
Err(e) => Err(ShellError::labeled_error(
error_message(e),
"invalid time zone",
&timezone.tag,
)),
},
_ => Err(ShellError::labeled_error(
"Expected a date from pipeline",
"requires date input",
&tag,
)),
})
.to_output_stream())
}
fn error_message(err: ParseErrorKind) -> &'static str {
match err {
ParseErrorKind::Invalid => "The time zone description is invalid",
ParseErrorKind::OutOfRange => "The time zone offset is out of range",
ParseErrorKind::TooShort => "The format of the time zone is invalid",
}
}
#[cfg(test)]
mod tests {
use super::Date;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(Date {})?)
}
}

View File

@ -1,63 +0,0 @@
use crate::prelude::*;
use chrono::{DateTime, Utc};
use nu_errors::ShellError;
use crate::commands::date::utils::date_to_value;
use crate::commands::WholeStreamCommand;
use nu_protocol::Signature;
pub struct Date;
#[async_trait]
impl WholeStreamCommand for Date {
fn name(&self) -> &str {
"date utc"
}
fn signature(&self) -> Signature {
Signature::build("date utc")
}
fn usage(&self) -> &str {
"return the current date in utc."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
utc(args, registry).await
}
}
pub async fn utc(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.call_info.name_tag.clone();
let no_fmt = "".to_string();
let value = {
let local: DateTime<Utc> = Utc::now();
date_to_value(local, tag, no_fmt)
};
Ok(OutputStream::one(value))
}
#[cfg(test)]
mod tests {
use super::Date;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(Date {})?)
}
}

View File

@ -1,64 +0,0 @@
use crate::prelude::*;
use chrono::DateTime;
use nu_protocol::{Dictionary, Value};
use chrono::{Datelike, TimeZone, Timelike};
use core::fmt::Display;
use indexmap::IndexMap;
use nu_protocol::UntaggedValue;
pub fn date_to_value_raw<T: TimeZone>(dt: DateTime<T>, dt_format: String) -> String
where
T::Offset: Display,
{
let result = dt.format(&dt_format);
format!("{}", result)
}
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, tag: Tag, dt_format: String) -> Value
where
T::Offset: Display,
{
let mut indexmap = IndexMap::new();
if dt_format.is_empty() {
indexmap.insert(
"year".to_string(),
UntaggedValue::int(dt.year()).into_value(&tag),
);
indexmap.insert(
"month".to_string(),
UntaggedValue::int(dt.month()).into_value(&tag),
);
indexmap.insert(
"day".to_string(),
UntaggedValue::int(dt.day()).into_value(&tag),
);
indexmap.insert(
"hour".to_string(),
UntaggedValue::int(dt.hour()).into_value(&tag),
);
indexmap.insert(
"minute".to_string(),
UntaggedValue::int(dt.minute()).into_value(&tag),
);
indexmap.insert(
"second".to_string(),
UntaggedValue::int(dt.second()).into_value(&tag),
);
let tz = dt.offset();
indexmap.insert(
"timezone".to_string(),
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
);
} else {
let result = dt.format(&dt_format);
indexmap.insert(
"formatted".to_string(),
UntaggedValue::string(format!("{}", result)).into_value(&tag),
);
}
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
}

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

@ -54,6 +54,7 @@ pub async fn from_delimited_data(
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let name_tag = name; let name_tag = name;
let concat_string = input.collect_string(name_tag.clone()).await?; let concat_string = input.collect_string(name_tag.clone()).await?;
let sample_lines = concat_string.item.lines().take(3).collect_vec().join("\n");
match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) { match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) {
Ok(x) => match x { Ok(x) => match x {
@ -65,10 +66,16 @@ pub async fn from_delimited_data(
}, },
Err(err) => { Err(err) => {
let line_one = match pretty_csv_error(err) { let line_one = match pretty_csv_error(err) {
Some(pretty) => format!("Could not parse as {} ({})", format_name, pretty), Some(pretty) => format!(
None => format!("Could not parse as {}", format_name), "Could not parse as {} split by '{}' ({})",
format_name, sep, pretty
),
None => format!("Could not parse as {} split by '{}'", format_name, sep),
}; };
let line_two = format!("input cannot be parsed as {}", format_name); let line_two = format!(
"input cannot be parsed as {} split by '{}'. Input's first lines:\n{}",
format_name, sep, sample_lines
);
Err(ShellError::labeled_error_with_secondary( Err(ShellError::labeled_error_with_secondary(
line_one, line_one,

View File

@ -37,26 +37,26 @@ impl WholeStreamCommand for FromJSON {
} }
} }
fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -> Value { fn convert_json_value_to_nu_value(v: &nu_json::Value, tag: impl Into<Tag>) -> Value {
let tag = tag.into(); let tag = tag.into();
let span = tag.span; let span = tag.span;
match v { match v {
serde_hjson::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag), nu_json::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
serde_hjson::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag), nu_json::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
serde_hjson::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag), nu_json::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag),
serde_hjson::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag), nu_json::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag), nu_json::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
serde_hjson::Value::String(s) => { nu_json::Value::String(s) => {
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(&tag) UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(&tag)
} }
serde_hjson::Value::Array(a) => UntaggedValue::Table( nu_json::Value::Array(a) => UntaggedValue::Table(
a.iter() a.iter()
.map(|x| convert_json_value_to_nu_value(x, &tag)) .map(|x| convert_json_value_to_nu_value(x, &tag))
.collect(), .collect(),
) )
.into_value(tag), .into_value(tag),
serde_hjson::Value::Object(o) => { nu_json::Value::Object(o) => {
let mut collected = TaggedDictBuilder::new(&tag); let mut collected = TaggedDictBuilder::new(&tag);
for (k, v) in o.iter() { for (k, v) in o.iter() {
collected.insert_value(k.clone(), convert_json_value_to_nu_value(v, &tag)); collected.insert_value(k.clone(), convert_json_value_to_nu_value(v, &tag));
@ -67,8 +67,8 @@ fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -
} }
} }
pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> serde_hjson::Result<Value> { pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> nu_json::Result<Value> {
let v: serde_hjson::Value = serde_hjson::from_str(&s)?; let v: nu_json::Value = nu_json::from_str(&s)?;
Ok(convert_json_value_to_nu_value(&v, tag)) Ok(convert_json_value_to_nu_value(&v, tag))
} }
@ -96,7 +96,7 @@ async fn from_json(
Err(e) => { Err(e) => {
let mut message = "Could not parse as JSON (".to_string(); let mut message = "Could not parse as JSON (".to_string();
message.push_str(&e.to_string()); message.push_str(&e.to_string());
message.push_str(")"); message.push(')');
Some(Err(ShellError::labeled_error_with_secondary( Some(Err(ShellError::labeled_error_with_secondary(
message, message,
@ -125,7 +125,7 @@ async fn from_json(
Err(e) => { Err(e) => {
let mut message = "Could not parse as JSON (".to_string(); let mut message = "Could not parse as JSON (".to_string();
message.push_str(&e.to_string()); message.push_str(&e.to_string());
message.push_str(")"); message.push(')');
Ok(OutputStream::one(Err( Ok(OutputStream::one(Err(
ShellError::labeled_error_with_secondary( ShellError::labeled_error_with_secondary(

View File

@ -68,13 +68,12 @@ fn convert_yaml_value_to_nu_value(
Ok(match v { Ok(match v {
serde_yaml::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(tag), serde_yaml::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(tag),
serde_yaml::Value::Number(n) if n.is_i64() => { serde_yaml::Value::Number(n) if n.is_i64() => {
UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag) UntaggedValue::int(n.as_i64().ok_or(err_not_compatible_number)?).into_value(tag)
}
serde_yaml::Value::Number(n) if n.is_f64() => {
UntaggedValue::decimal_from_float(n.as_f64().ok_or(err_not_compatible_number)?, span)
.into_value(tag)
} }
serde_yaml::Value::Number(n) if n.is_f64() => UntaggedValue::decimal_from_float(
n.as_f64().ok_or_else(|| err_not_compatible_number)?,
span,
)
.into_value(tag),
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag), serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
serde_yaml::Value::Sequence(a) => { serde_yaml::Value::Sequence(a) => {
let result: Result<Vec<Value>, ShellError> = a let result: Result<Vec<Value>, ShellError> = a
@ -175,8 +174,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

@ -0,0 +1,314 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::ShellTypeName;
use nu_protocol::{
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::{Tag, Tagged};
use base64::{decode_config, encode_config};
#[derive(Deserialize)]
pub struct Arguments {
pub rest: Vec<ColumnPath>,
pub character_set: Option<Tagged<String>>,
pub encode: Tagged<bool>,
pub decode: Tagged<bool>,
}
#[derive(Clone)]
pub struct Base64Config {
pub character_set: String,
pub action_type: ActionType,
}
#[derive(Clone, Copy, PartialEq)]
pub enum ActionType {
Encode,
Decode,
}
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"hash base64"
}
fn signature(&self) -> Signature {
Signature::build("hash base64")
.named(
"character_set",
SyntaxShape::String,
"specify the character rules for encoding the input.\n\
\tValid values are 'standard', 'standard-no-padding', 'url-safe', 'url-safe-no-padding',\
'binhex', 'bcrypt', 'crypt'",
Some('c'),
)
.switch(
"encode",
"encode the input as base64. This is the default behavior if not specified.",
Some('e')
)
.switch(
"decode",
"decode the input from base64",
Some('d'))
.rest(
SyntaxShape::ColumnPath,
"optionally base64 encode / decode data by column paths",
)
}
fn usage(&self) -> &str {
"base64 encode or decode a value"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Base64 encode a string with default settings",
example: "echo 'username:password' | hash base64",
result: Some(vec![
UntaggedValue::string("dXNlcm5hbWU6cGFzc3dvcmQ=").into_untagged_value()
]),
},
Example {
description: "Base64 encode a string with the binhex character set",
example: "echo 'username:password' | hash base64 --character_set binhex --encode",
result: Some(vec![
UntaggedValue::string("F@0NEPjJD97kE'&bEhFZEP3").into_untagged_value()
]),
},
Example {
description: "Base64 decode a value",
example: "echo 'dXNlcm5hbWU6cGFzc3dvcmQ=' | hash base64 --decode",
result: Some(vec![
UntaggedValue::string("username:password").into_untagged_value()
]),
},
]
}
}
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name_tag = &args.call_info.name_tag.clone();
let (
Arguments {
encode,
decode,
character_set,
rest,
},
input,
) = args.process(&registry).await?;
if encode.item && decode.item {
return Ok(OutputStream::one(Err(ShellError::labeled_error(
"only one of --decode and --encode flags can be used",
"conflicting flags",
name_tag,
))));
}
// Default the action to be encoding if no flags are specified.
let action_type = if *decode.item() {
ActionType::Decode
} else {
ActionType::Encode
};
// Default the character set to standard if the argument is not specified.
let character_set = match character_set {
Some(inner_tag) => inner_tag.item().to_string(),
None => "standard".to_string(),
};
let encoding_config = Base64Config {
character_set,
action_type,
};
let column_paths: Vec<_> = rest;
Ok(input
.map(move |v| {
if column_paths.is_empty() {
ReturnSuccess::value(action(&v, &encoding_config, v.tag())?)
} else {
let mut ret = v;
for path in &column_paths {
let config = encoding_config.clone();
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, &config, old.tag())),
)?;
}
ReturnSuccess::value(ret)
}
})
.to_output_stream())
}
fn action(
input: &Value,
base64_config: &Base64Config,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
match &input.value {
UntaggedValue::Primitive(Primitive::Line(s))
| UntaggedValue::Primitive(Primitive::String(s)) => {
let base64_config_enum: base64::Config = if &base64_config.character_set == "standard" {
base64::STANDARD
} else if &base64_config.character_set == "standard-no-padding" {
base64::STANDARD_NO_PAD
} else if &base64_config.character_set == "url-safe" {
base64::URL_SAFE
} else if &base64_config.character_set == "url-safe-no-padding" {
base64::URL_SAFE_NO_PAD
} else if &base64_config.character_set == "binhex" {
base64::BINHEX
} else if &base64_config.character_set == "bcrypt" {
base64::BCRYPT
} else if &base64_config.character_set == "crypt" {
base64::CRYPT
} else {
return Err(ShellError::labeled_error(
"value is not an accepted character set",
format!(
"{} is not a valid character-set.\nPlease use `help hash base64` to see a list of valid character sets.",
&base64_config.character_set
),
tag.into().span,
));
};
match base64_config.action_type {
ActionType::Encode => Ok(UntaggedValue::string(encode_config(
&s,
base64_config_enum,
))
.into_value(tag)),
ActionType::Decode => {
let decode_result = decode_config(&s, base64_config_enum);
match decode_result {
Ok(decoded_value) => Ok(UntaggedValue::string(
std::string::String::from_utf8_lossy(&decoded_value),
)
.into_value(tag)),
Err(_) => Err(ShellError::labeled_error(
"value could not be base64 decoded",
format!(
"invalid base64 input for character set {}",
&base64_config.character_set
),
tag.into().span,
)),
}
}
}
}
other => {
let got = format!("got {}", other.type_name());
Err(ShellError::labeled_error(
"value is not string",
got,
tag.into().span,
))
}
}
}
#[cfg(test)]
mod tests {
use super::{action, ActionType, Base64Config};
use nu_protocol::UntaggedValue;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn base64_encode_standard() {
let word = string("username:password");
let expected = UntaggedValue::string("dXNlcm5hbWU6cGFzc3dvcmQ=").into_untagged_value();
let actual = action(
&word,
&Base64Config {
character_set: "standard".to_string(),
action_type: ActionType::Encode,
},
Tag::unknown(),
)
.unwrap();
assert_eq!(actual, expected);
}
#[test]
fn base64_encode_standard_no_padding() {
let word = string("username:password");
let expected = UntaggedValue::string("dXNlcm5hbWU6cGFzc3dvcmQ").into_untagged_value();
let actual = action(
&word,
&Base64Config {
character_set: "standard-no-padding".to_string(),
action_type: ActionType::Encode,
},
Tag::unknown(),
)
.unwrap();
assert_eq!(actual, expected);
}
#[test]
fn base64_encode_url_safe() {
let word = string("this is for url");
let expected = UntaggedValue::string("dGhpcyBpcyBmb3IgdXJs").into_untagged_value();
let actual = action(
&word,
&Base64Config {
character_set: "url-safe".to_string(),
action_type: ActionType::Encode,
},
Tag::unknown(),
)
.unwrap();
assert_eq!(actual, expected);
}
#[test]
fn base64_decode_binhex() {
let word = string("A5\"KC9jRB@IIF'8bF!");
let expected = UntaggedValue::string("a binhex test").into_untagged_value();
let actual = action(
&word,
&Base64Config {
character_set: "binhex".to_string(),
action_type: ActionType::Decode,
},
Tag::unknown(),
)
.unwrap();
assert_eq!(actual, expected);
}
}

View File

@ -0,0 +1,50 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
pub struct Command;
#[async_trait]
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"hash"
}
fn signature(&self) -> Signature {
Signature::build("hash").rest(
SyntaxShape::ColumnPath,
"optionally convert by column paths",
)
}
fn usage(&self) -> &str {
"Apply hash function."
}
async fn run(
&self,
_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(crate::commands::help::get_help(&Command, &registry))
.into_value(Tag::unknown()),
)))
}
}
#[cfg(test)]
mod tests {
use super::Command;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(Command {})?)
}
}

View File

@ -0,0 +1,5 @@
mod base64_;
mod command;
pub use base64_::SubCommand as HashBase64;
pub use command::Command as Hash;

View File

@ -27,6 +27,11 @@ pub fn history_path(config: &dyn Conf) -> PathBuf {
}) })
} }
#[derive(Deserialize)]
struct Arguments {
clear: Option<bool>,
}
pub struct History; pub struct History;
#[async_trait] #[async_trait]
@ -36,7 +41,7 @@ impl WholeStreamCommand for History {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("history") Signature::build("history").switch("clear", "Clears out the history entries", Some('c'))
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -48,31 +53,45 @@ impl WholeStreamCommand for History {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
history(args, registry) history(args, registry).await
} }
} }
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn history(
args: CommandArgs,
_registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let config: Box<dyn Conf> = Box::new(NuConfig::new()); let config: Box<dyn Conf> = Box::new(NuConfig::new());
let tag = args.call_info.name_tag; let tag = args.call_info.name_tag.clone();
let path = history_path(&config); let (Arguments { clear }, _) = args.process(&_registry).await?;
let file = File::open(path);
if let Ok(file) = file {
let reader = BufReader::new(file);
let output = reader.lines().filter_map(move |line| match line {
Ok(line) => Some(ReturnSuccess::value(
UntaggedValue::string(line).into_value(tag.clone()),
)),
Err(_) => None,
});
Ok(futures::stream::iter(output).to_output_stream()) let path = history_path(&config);
} else {
Err(ShellError::labeled_error( match clear {
"Could not open history", Some(_) => {
"history file could not be opened", // This is a NOOP, the logic to clear is handled in cli.rs
tag, Ok(OutputStream::empty())
)) }
None => {
if let Ok(file) = File::open(path) {
let reader = BufReader::new(file);
// Skips the first line, which is a Rustyline internal
let output = reader.lines().skip(1).filter_map(move |line| match line {
Ok(line) => Some(ReturnSuccess::value(
UntaggedValue::string(line).into_value(tag.clone()),
)),
Err(_) => None,
});
Ok(futures::stream::iter(output).to_output_stream())
} else {
Err(ShellError::labeled_error(
"Could not open history",
"history file could not be opened",
tag,
))
}
}
} }
} }

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,73 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, UntaggedValue, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math abs"
}
fn signature(&self) -> Signature {
Signature::build("math abs")
}
fn usage(&self) -> &str {
"Returns absolute values of a list of numbers"
}
async fn run(
&self,
args: CommandArgs,
_: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let mapped = args.input.map(move |val| match val.value {
UntaggedValue::Primitive(Primitive::Int(val)) => {
UntaggedValue::int(val.magnitude().clone()).into()
}
UntaggedValue::Primitive(Primitive::Decimal(val)) => {
UntaggedValue::decimal(val.abs()).into()
}
UntaggedValue::Primitive(Primitive::Duration(val)) => {
UntaggedValue::duration(val.magnitude().clone()).into()
}
other => abs_default(other),
});
Ok(OutputStream::from_input(mapped))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get absolute of each value in a list of numbers",
example: "echo [-50 -100.0 25] | math abs",
result: Some(vec![
UntaggedValue::int(50).into(),
UntaggedValue::decimal_from_float(100.0, Span::default()).into(),
UntaggedValue::int(25).into(),
]),
}]
}
}
fn abs_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

@ -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,15 @@
pub mod abs;
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;
@ -13,14 +17,18 @@ pub mod variance;
mod reducers; mod reducers;
mod utils; mod utils;
pub use abs::SubCommand as MathAbs;
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

@ -2,11 +2,18 @@ use super::{operate, DefaultArguments};
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::{Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use std::path::Path; use std::path::Path;
pub struct PathBasename; pub struct PathBasename;
#[derive(Deserialize)]
struct PathBasenameArguments {
replace: Option<Tagged<String>>,
rest: Vec<ColumnPath>,
}
#[async_trait] #[async_trait]
impl WholeStreamCommand for PathBasename { impl WholeStreamCommand for PathBasename {
fn name(&self) -> &str { fn name(&self) -> &str {
@ -15,11 +22,17 @@ impl WholeStreamCommand for PathBasename {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("path basename") Signature::build("path basename")
.rest(SyntaxShape::ColumnPath, "optionally operate by path") .named(
"replace",
SyntaxShape::String,
"Return original path with basename replaced by this string",
Some('r'),
)
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"gets the filename of a path" "Gets the final component of a path"
} }
async fn run( async fn run(
@ -28,24 +41,60 @@ impl WholeStreamCommand for PathBasename {
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?; let (PathBasenameArguments { replace, rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await let args = Arc::new(DefaultArguments {
replace: replace.map(|v| v.item),
prefix: None,
suffix: None,
num_levels: None,
paths: rest,
});
operate(input, &action, tag.span, args).await
} }
#[cfg(windows)]
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![
description: "Get basename of a path", Example {
example: "echo '/home/joe/test.txt' | path basename", description: "Get basename of a path",
result: Some(vec![Value::from("test.txt")]), example: "echo 'C:\\Users\\joe\\test.txt' | path basename",
}] result: Some(vec![Value::from("test.txt")]),
},
Example {
description: "Replace basename of a path",
example: "echo 'C:\\Users\\joe\\test.txt' | path basename -r 'spam.png'",
result: Some(vec![Value::from(UntaggedValue::path(
"C:\\Users\\joe\\spam.png",
))]),
},
]
}
#[cfg(not(windows))]
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get basename of a path",
example: "echo '/home/joe/test.txt' | path basename",
result: Some(vec![Value::from("test.txt")]),
},
Example {
description: "Replace basename of a path",
example: "echo '/home/joe/test.txt' | path basename -r 'spam.png'",
result: Some(vec![Value::from(UntaggedValue::path("/home/joe/spam.png"))]),
},
]
} }
} }
fn action(path: &Path) -> UntaggedValue { fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
UntaggedValue::string(match path.file_name() { match args.replace {
Some(filename) => filename.to_string_lossy().to_string(), Some(ref basename) => UntaggedValue::path(path.with_file_name(basename)),
_ => "".to_string(), None => UntaggedValue::string(match path.file_name() {
}) Some(filename) => filename.to_string_lossy(),
None => "".into(),
}),
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -16,7 +16,7 @@ impl WholeStreamCommand for Path {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Apply path function" "Explore and manipulate paths"
} }
async fn run( async fn run(

View File

@ -2,11 +2,20 @@ use super::{operate, DefaultArguments};
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::{Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use std::path::Path; use std::path::Path;
pub struct PathDirname; pub struct PathDirname;
#[derive(Deserialize)]
struct PathDirnameArguments {
replace: Option<Tagged<String>>,
#[serde(rename = "num-levels")]
num_levels: Option<Tagged<u32>>,
rest: Vec<ColumnPath>,
}
#[async_trait] #[async_trait]
impl WholeStreamCommand for PathDirname { impl WholeStreamCommand for PathDirname {
fn name(&self) -> &str { fn name(&self) -> &str {
@ -14,11 +23,24 @@ impl WholeStreamCommand for PathDirname {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("path dirname").rest(SyntaxShape::ColumnPath, "optionally operate by path") Signature::build("path dirname")
.named(
"replace",
SyntaxShape::String,
"Return original path with dirname replaced by this string",
Some('r'),
)
.named(
"num-levels",
SyntaxShape::Int,
"Number of directories to walk up",
Some('n'),
)
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"gets the dirname of a path" "Gets the parent directory of a path"
} }
async fn run( async fn run(
@ -27,24 +49,100 @@ impl WholeStreamCommand for PathDirname {
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?; let (
operate(input, rest, &action, tag.span).await PathDirnameArguments {
replace,
num_levels,
rest,
},
input,
) = args.process(&registry).await?;
let args = Arc::new(DefaultArguments {
replace: replace.map(|v| v.item),
prefix: None,
suffix: None,
num_levels: num_levels.map(|v| v.item),
paths: rest,
});
operate(input, &action, tag.span, args).await
} }
#[cfg(windows)]
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![
description: "Get dirname of a path", Example {
example: "echo '/home/joe/test.txt' | path dirname", description: "Get dirname of a path",
result: Some(vec![Value::from("/home/joe")]), example: "echo 'C:\\Users\\joe\\code\\test.txt' | path dirname",
}] result: Some(vec![Value::from(UntaggedValue::path(
"C:\\Users\\joe\\code",
))]),
},
Example {
description: "Set how many levels up to skip",
example: "echo 'C:\\Users\\joe\\code\\test.txt' | path dirname -n 2",
result: Some(vec![Value::from(UntaggedValue::path("C:\\Users\\joe"))]),
},
Example {
description: "Replace the part that would be returned with custom string",
example:
"echo 'C:\\Users\\joe\\code\\test.txt' | path dirname -n 2 -r C:\\Users\\viking",
result: Some(vec![Value::from(UntaggedValue::path(
"C:\\Users\\viking\\code\\test.txt",
))]),
},
]
}
#[cfg(not(windows))]
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get dirname of a path",
example: "echo '/home/joe/code/test.txt' | path dirname",
result: Some(vec![Value::from(UntaggedValue::path("/home/joe/code"))]),
},
Example {
description: "Set how many levels up to skip",
example: "echo '/home/joe/code/test.txt' | path dirname -n 2",
result: Some(vec![Value::from(UntaggedValue::path("/home/joe"))]),
},
Example {
description: "Replace the part that would be returned with custom string",
example: "echo '/home/joe/code/test.txt' | path dirname -n 2 -r /home/viking",
result: Some(vec![Value::from(UntaggedValue::path(
"/home/viking/code/test.txt",
))]),
},
]
} }
} }
fn action(path: &Path) -> UntaggedValue { fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
UntaggedValue::string(match path.parent() { let num_levels = args.num_levels.unwrap_or(1);
Some(dirname) => dirname.to_string_lossy().to_string(),
_ => "".to_string(), let mut dirname = path;
}) let mut reached_top = false; // end early if somebody passes -n 99999999
for _ in 0..num_levels {
dirname = dirname.parent().unwrap_or_else(|| {
reached_top = true;
dirname
});
if reached_top {
break;
}
}
match args.replace {
Some(ref newdir) => {
let remainder = path.strip_prefix(dirname).unwrap_or(dirname);
if !remainder.as_os_str().is_empty() {
UntaggedValue::path(Path::new(newdir).join(remainder))
} else {
UntaggedValue::path(Path::new(newdir))
}
}
None => UntaggedValue::path(dirname),
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -2,11 +2,16 @@ use super::{operate, DefaultArguments};
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::{Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
use std::path::Path; use std::path::Path;
pub struct PathExists; pub struct PathExists;
#[derive(Deserialize)]
struct PathExistsArguments {
rest: Vec<ColumnPath>,
}
#[async_trait] #[async_trait]
impl WholeStreamCommand for PathExists { impl WholeStreamCommand for PathExists {
fn name(&self) -> &str { fn name(&self) -> &str {
@ -14,11 +19,12 @@ impl WholeStreamCommand for PathExists {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("path exists").rest(SyntaxShape::ColumnPath, "optionally operate by path") Signature::build("path exists")
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"checks whether the path exists" "Checks whether a path exists"
} }
async fn run( async fn run(
@ -27,10 +33,27 @@ impl WholeStreamCommand for PathExists {
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?; let (PathExistsArguments { rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await let args = Arc::new(DefaultArguments {
replace: None,
prefix: None,
suffix: None,
num_levels: None,
paths: rest,
});
operate(input, &action, tag.span, args).await
} }
#[cfg(windows)]
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Check if file exists",
example: "echo 'C:\\Users\\joe\\todo.txt' | path exists",
result: Some(vec![Value::from(UntaggedValue::boolean(false))]),
}]
}
#[cfg(not(windows))]
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "Check if file exists", description: "Check if file exists",
@ -40,7 +63,7 @@ impl WholeStreamCommand for PathExists {
} }
} }
fn action(path: &Path) -> UntaggedValue { fn action(path: &Path, _args: Arc<DefaultArguments>) -> UntaggedValue {
UntaggedValue::boolean(path.exists()) UntaggedValue::boolean(path.exists())
} }

View File

@ -2,11 +2,16 @@ use super::{operate, DefaultArguments};
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::{Signature, SyntaxShape, UntaggedValue}; use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue};
use std::path::Path; use std::path::{Path, PathBuf};
pub struct PathExpand; pub struct PathExpand;
#[derive(Deserialize)]
struct PathExpandArguments {
rest: Vec<ColumnPath>,
}
#[async_trait] #[async_trait]
impl WholeStreamCommand for PathExpand { impl WholeStreamCommand for PathExpand {
fn name(&self) -> &str { fn name(&self) -> &str {
@ -14,11 +19,12 @@ impl WholeStreamCommand for PathExpand {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("path expand").rest(SyntaxShape::ColumnPath, "optionally operate by path") Signature::build("path expand")
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"expands the path to its absolute form" "Expands a path to its absolute form"
} }
async fn run( async fn run(
@ -27,28 +33,43 @@ impl WholeStreamCommand for PathExpand {
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?; let (PathExpandArguments { rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await let args = Arc::new(DefaultArguments {
replace: None,
prefix: None,
suffix: None,
num_levels: None,
paths: rest,
});
operate(input, &action, tag.span, args).await
} }
#[cfg(windows)]
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Expand relative directories",
example: "echo 'C:\\Users\\joe\\foo\\..\\bar' | path expand",
result: None,
// fails to canonicalize into Some(vec![Value::from("C:\\Users\\joe\\bar")]) due to non-existing path
}]
}
#[cfg(not(windows))]
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "Expand relative directories", description: "Expand relative directories",
example: "echo '/home/joe/foo/../bar' | path expand", example: "echo '/home/joe/foo/../bar' | path expand",
result: None, result: None,
//Some(vec![Value::from("/home/joe/bar")]), // fails to canonicalize into Some(vec![Value::from("/home/joe/bar")]) due to non-existing path
}] }]
} }
} }
fn action(path: &Path) -> UntaggedValue { fn action(path: &Path, _args: Arc<DefaultArguments>) -> UntaggedValue {
let ps = path.to_string_lossy(); let ps = path.to_string_lossy();
let expanded = shellexpand::tilde(&ps); let expanded = shellexpand::tilde(&ps);
let path: &Path = expanded.as_ref().as_ref(); let path: &Path = expanded.as_ref().as_ref();
UntaggedValue::string(match path.canonicalize() { UntaggedValue::path(dunce::canonicalize(path).unwrap_or_else(|_| PathBuf::from(path)))
Ok(p) => p.to_string_lossy().to_string(),
Err(_) => ps.to_string(),
})
} }
#[cfg(test)] #[cfg(test)]

View File

@ -2,11 +2,18 @@ use super::{operate, DefaultArguments};
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::{Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use std::path::Path; use std::path::Path;
pub struct PathExtension; pub struct PathExtension;
#[derive(Deserialize)]
struct PathExtensionArguments {
replace: Option<Tagged<String>>,
rest: Vec<ColumnPath>,
}
#[async_trait] #[async_trait]
impl WholeStreamCommand for PathExtension { impl WholeStreamCommand for PathExtension {
fn name(&self) -> &str { fn name(&self) -> &str {
@ -15,11 +22,17 @@ impl WholeStreamCommand for PathExtension {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("path extension") Signature::build("path extension")
.rest(SyntaxShape::ColumnPath, "optionally operate by path") .named(
"replace",
SyntaxShape::String,
"Return original path with extension replaced by this string",
Some('r'),
)
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"gets the extension of a path" "Gets the extension of a path"
} }
async fn run( async fn run(
@ -28,8 +41,15 @@ impl WholeStreamCommand for PathExtension {
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?; let (PathExtensionArguments { replace, rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await let args = Arc::new(DefaultArguments {
replace: replace.map(|v| v.item),
prefix: None,
suffix: None,
num_levels: None,
paths: rest,
});
operate(input, &action, tag.span, args).await
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -44,15 +64,28 @@ impl WholeStreamCommand for PathExtension {
example: "echo 'test' | path extension", example: "echo 'test' | path extension",
result: Some(vec![Value::from("")]), result: Some(vec![Value::from("")]),
}, },
Example {
description: "Replace an extension with a custom string",
example: "echo 'test.txt' | path extension -r md",
result: Some(vec![Value::from(UntaggedValue::path("test.md"))]),
},
Example {
description: "To replace more complex extensions:",
example: "echo 'test.tar.gz' | path extension -r '' | path extension -r txt",
result: Some(vec![Value::from(UntaggedValue::path("test.txt"))]),
},
] ]
} }
} }
fn action(path: &Path) -> UntaggedValue { fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
UntaggedValue::string(match path.extension() { match args.replace {
Some(ext) => ext.to_string_lossy().to_string(), Some(ref extension) => UntaggedValue::path(path.with_extension(extension)),
_ => "".to_string(), None => UntaggedValue::string(match path.extension() {
}) Some(extension) => extension.to_string_lossy(),
None => "".into(),
}),
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -2,11 +2,20 @@ use super::{operate, DefaultArguments};
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::{Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use std::path::Path; use std::path::Path;
pub struct PathFilestem; pub struct PathFilestem;
#[derive(Deserialize)]
struct PathFilestemArguments {
prefix: Option<Tagged<String>>,
suffix: Option<Tagged<String>>,
replace: Option<Tagged<String>>,
rest: Vec<ColumnPath>,
}
#[async_trait] #[async_trait]
impl WholeStreamCommand for PathFilestem { impl WholeStreamCommand for PathFilestem {
fn name(&self) -> &str { fn name(&self) -> &str {
@ -15,11 +24,29 @@ impl WholeStreamCommand for PathFilestem {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("path filestem") Signature::build("path filestem")
.rest(SyntaxShape::ColumnPath, "optionally operate by path") .named(
"replace",
SyntaxShape::String,
"Return original path with filestem replaced by this string",
Some('r'),
)
.named(
"prefix",
SyntaxShape::String,
"Strip this string from from the beginning of a file name",
Some('p'),
)
.named(
"suffix",
SyntaxShape::String,
"Strip this string from from the end of a file name",
Some('s'),
)
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"gets the filestem of a path" "Gets the file stem of a path"
} }
async fn run( async fn run(
@ -28,24 +55,111 @@ impl WholeStreamCommand for PathFilestem {
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?; let (
operate(input, rest, &action, tag.span).await PathFilestemArguments {
replace,
prefix,
suffix,
rest,
},
input,
) = args.process(&registry).await?;
let args = Arc::new(DefaultArguments {
replace: replace.map(|v| v.item),
prefix: prefix.map(|v| v.item),
suffix: suffix.map(|v| v.item),
num_levels: None,
paths: rest,
});
operate(input, &action, tag.span, args).await
} }
#[cfg(windows)]
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![
description: "Get filestem of a path", Example {
example: "echo '/home/joe/test.txt' | path filestem", description: "Get filestem of a path",
result: Some(vec![Value::from("test")]), example: "echo 'C:\\Users\\joe\\bacon_lettuce.egg' | path filestem",
}] result: Some(vec![Value::from("bacon_lettuce")]),
},
Example {
description: "Get filestem of a path, stripped of prefix and suffix",
example: "echo 'C:\\Users\\joe\\bacon_lettuce.egg.gz' | path filestem -p bacon_ -s .egg.gz",
result: Some(vec![Value::from("lettuce")]),
},
Example {
description: "Replace the filestem that would be returned",
example: "echo 'C:\\Users\\joe\\bacon_lettuce.egg.gz' | path filestem -p bacon_ -s .egg.gz -r spam",
result: Some(vec![Value::from(UntaggedValue::path("C:\\Users\\joe\\bacon_spam.egg.gz"))]),
},
]
}
#[cfg(not(windows))]
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get filestem of a path",
example: "echo '/home/joe/bacon_lettuce.egg' | path filestem",
result: Some(vec![Value::from("bacon_lettuce")]),
},
Example {
description: "Get filestem of a path, stripped of prefix and suffix",
example: "echo '/home/joe/bacon_lettuce.egg.gz' | path filestem -p bacon_ -s .egg.gz",
result: Some(vec![Value::from("lettuce")]),
},
Example {
description: "Replace the filestem that would be returned",
example: "echo '/home/joe/bacon_lettuce.egg.gz' | path filestem -p bacon_ -s .egg.gz -r spam",
result: Some(vec![Value::from(UntaggedValue::path("/home/joe/bacon_spam.egg.gz"))]),
},
]
} }
} }
fn action(path: &Path) -> UntaggedValue { fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
UntaggedValue::string(match path.file_stem() { let basename = match path.file_name() {
Some(stem) => stem.to_string_lossy().to_string(), Some(name) => name.to_string_lossy().to_string(),
_ => "".to_string(), None => "".to_string(),
}) };
let suffix = match args.suffix {
Some(ref suf) => match basename.rmatch_indices(suf).next() {
Some((i, _)) => basename.split_at(i).1.to_string(),
None => "".to_string(),
},
None => match path.extension() {
// Prepend '.' since the extension returned comes without it
Some(ext) => ".".to_string() + &ext.to_string_lossy().to_string(),
None => "".to_string(),
},
};
let prefix = match args.prefix {
Some(ref pre) => match basename.matches(pre).next() {
Some(m) => basename.split_at(m.len()).0.to_string(),
None => "".to_string(),
},
None => "".to_string(),
};
let basename_without_prefix = match basename.matches(&prefix).next() {
Some(m) => basename.split_at(m.len()).1.to_string(),
None => basename,
};
let stem = match basename_without_prefix.rmatch_indices(&suffix).next() {
Some((i, _)) => basename_without_prefix.split_at(i).0.to_string(),
None => basename_without_prefix,
};
match args.replace {
Some(ref replace) => {
let new_name = prefix + replace + &suffix;
UntaggedValue::path(path.with_file_name(&new_name))
}
None => UntaggedValue::string(stem),
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -12,6 +12,7 @@ use nu_errors::ShellError;
use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, ShellTypeName, UntaggedValue, Value}; use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, ShellTypeName, UntaggedValue, Value};
use nu_source::Span; use nu_source::Span;
use std::path::Path; use std::path::Path;
use std::sync::Arc;
pub use basename::PathBasename; pub use basename::PathBasename;
pub use command::Path as PathCommand; pub use command::Path as PathCommand;
@ -24,17 +25,32 @@ pub use r#type::PathType;
#[derive(Deserialize)] #[derive(Deserialize)]
struct DefaultArguments { struct DefaultArguments {
rest: Vec<ColumnPath>, // used by basename, dirname, extension and filestem
replace: Option<String>,
// used by filestem
prefix: Option<String>,
suffix: Option<String>,
// used by dirname
num_levels: Option<u32>,
// used by all
paths: Vec<ColumnPath>,
} }
fn handle_value<F>(action: &F, v: &Value, span: Span) -> Result<Value, ShellError> fn handle_value<F>(
action: &F,
v: &Value,
span: Span,
args: Arc<DefaultArguments>,
) -> Result<Value, ShellError>
where where
F: Fn(&Path) -> UntaggedValue + Send + 'static, F: Fn(&Path, Arc<DefaultArguments>) -> UntaggedValue + Send + 'static,
{ {
let v = match &v.value { let v = match &v.value {
UntaggedValue::Primitive(Primitive::Path(buf)) => action(buf).into_value(v.tag()), UntaggedValue::Primitive(Primitive::Path(buf)) => action(buf, args).into_value(v.tag()),
UntaggedValue::Primitive(Primitive::String(s)) UntaggedValue::Primitive(Primitive::String(s))
| UntaggedValue::Primitive(Primitive::Line(s)) => action(s.as_ref()).into_value(v.tag()), | UntaggedValue::Primitive(Primitive::Line(s)) => {
action(s.as_ref(), args).into_value(v.tag())
}
other => { other => {
let got = format!("got {}", other.type_name()); let got = format!("got {}", other.type_name());
return Err(ShellError::labeled_error_with_secondary( return Err(ShellError::labeled_error_with_secondary(
@ -51,24 +67,25 @@ where
async fn operate<F>( async fn operate<F>(
input: crate::InputStream, input: crate::InputStream,
paths: Vec<ColumnPath>,
action: &'static F, action: &'static F,
span: Span, span: Span,
args: Arc<DefaultArguments>,
) -> Result<OutputStream, ShellError> ) -> Result<OutputStream, ShellError>
where where
F: Fn(&Path) -> UntaggedValue + Send + Sync + 'static, F: Fn(&Path, Arc<DefaultArguments>) -> UntaggedValue + Send + Sync + 'static,
{ {
Ok(input Ok(input
.map(move |v| { .map(move |v| {
if paths.is_empty() { if args.paths.is_empty() {
ReturnSuccess::value(handle_value(&action, &v, span)?) ReturnSuccess::value(handle_value(&action, &v, span, Arc::clone(&args))?)
} else { } else {
let mut ret = v; let mut ret = v;
for path in &paths { for path in &args.paths {
let cloned_args = Arc::clone(&args);
ret = ret.swap_data_by_column_path( ret = ret.swap_data_by_column_path(
path, path,
Box::new(move |old| handle_value(&action, &old, span)), Box::new(move |old| handle_value(&action, &old, span, cloned_args)),
)?; )?;
} }

View File

@ -3,11 +3,16 @@ use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use crate::shell::filesystem_shell::get_file_type; use crate::shell::filesystem_shell::get_file_type;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
use std::path::Path; use std::path::Path;
pub struct PathType; pub struct PathType;
#[derive(Deserialize)]
struct PathTypeArguments {
rest: Vec<ColumnPath>,
}
#[async_trait] #[async_trait]
impl WholeStreamCommand for PathType { impl WholeStreamCommand for PathType {
fn name(&self) -> &str { fn name(&self) -> &str {
@ -15,11 +20,12 @@ impl WholeStreamCommand for PathType {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("path type").rest(SyntaxShape::ColumnPath, "optionally operate by path") Signature::build("path type")
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"gives the type of the object the path refers to (eg file, dir, symlink)" "Gives the type of the object a path refers to (e.g., file, dir, symlink)"
} }
async fn run( async fn run(
@ -28,8 +34,15 @@ impl WholeStreamCommand for PathType {
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?; let (PathTypeArguments { rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await let args = Arc::new(DefaultArguments {
replace: None,
prefix: None,
suffix: None,
num_levels: None,
paths: rest,
});
operate(input, &action, tag.span, args).await
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -41,7 +54,7 @@ impl WholeStreamCommand for PathType {
} }
} }
fn action(path: &Path) -> UntaggedValue { fn action(path: &Path, _args: Arc<DefaultArguments>) -> UntaggedValue {
let meta = std::fs::symlink_metadata(path); let meta = std::fs::symlink_metadata(path);
UntaggedValue::string(match &meta { UntaggedValue::string(match &meta {
Ok(md) => get_file_type(md), Ok(md) => get_file_type(md),

View File

@ -0,0 +1,89 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
use rand::distributions::Alphanumeric;
use rand::prelude::{thread_rng, Rng};
pub struct SubCommand;
#[derive(Deserialize)]
pub struct CharsArgs {
length: Option<Tagged<u32>>,
}
const DEFAULT_CHARS_LENGTH: u32 = 25;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"random chars"
}
fn signature(&self) -> Signature {
Signature::build("random chars").named(
"length",
SyntaxShape::Int,
"Number of chars",
Some('l'),
)
}
fn usage(&self) -> &str {
"Generate random chars"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
chars(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Generate random chars",
example: "random chars",
result: None,
},
Example {
description: "Generate random chars with specified length",
example: "random chars -l 20",
result: None,
},
]
}
}
pub async fn chars(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let (CharsArgs { length }, _) = args.process(&registry).await?;
let chars_length = length.map_or(DEFAULT_CHARS_LENGTH, |l| l.item);
let random_string: String = thread_rng()
.sample_iter(&Alphanumeric)
.take(chars_length as usize)
.collect();
let result = UntaggedValue::string(random_string);
Ok(OutputStream::one(ReturnSuccess::value(result)))
}
#[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

@ -0,0 +1,111 @@
use crate::commands::WholeStreamCommand;
use crate::deserializer::NumericRange;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
use rand::prelude::{thread_rng, Rng};
use std::cmp::Ordering;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct DecimalArgs {
range: Option<Tagged<NumericRange>>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"random decimal"
}
fn signature(&self) -> Signature {
Signature::build("random decimal").optional("range", SyntaxShape::Range, "Range of values")
}
fn usage(&self) -> &str {
"Generate a random decimal within a range [min..max]"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
decimal(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Generate a default decimal value between 0 and 1",
example: "random decimal",
result: None,
},
Example {
description: "Generate a random decimal less than or equal to 500",
example: "random decimal ..500",
result: None,
},
Example {
description: "Generate a random decimal greater than or equal to 100000",
example: "random decimal 100000..",
result: None,
},
Example {
description: "Generate a random decimal between 1 and 10",
example: "random decimal 1..10",
result: None,
},
]
}
}
pub async fn decimal(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let (DecimalArgs { range }, _) = args.process(&registry).await?;
let (min, max) = if let Some(range) = &range {
(range.item.min() as f64, range.item.max() as f64)
} else {
(0.0, 1.0)
};
match min.partial_cmp(&max) {
Some(Ordering::Greater) => Err(ShellError::labeled_error(
format!("Invalid range {}..{}", min, max),
"expected a valid range",
range
.expect("Unexpected ordering error in random decimal")
.span(),
)),
Some(Ordering::Equal) => {
let untagged_result = UntaggedValue::decimal_from_float(min, Span::new(64, 64));
Ok(OutputStream::one(ReturnSuccess::value(untagged_result)))
}
_ => {
let mut thread_rng = thread_rng();
let result: f64 = thread_rng.gen_range(min, max);
let untagged_result = UntaggedValue::decimal_from_float(result, Span::new(64, 64));
Ok(OutputStream::one(ReturnSuccess::value(untagged_result)))
}
}
}
#[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,8 @@
pub mod command; pub mod command;
pub mod bool; pub mod bool;
pub mod chars;
pub mod decimal;
pub mod dice; pub mod dice;
pub mod integer; pub mod integer;
#[cfg(feature = "uuid_crate")] #[cfg(feature = "uuid_crate")]
@ -9,6 +11,8 @@ pub mod uuid;
pub use command::Command as Random; pub use command::Command as Random;
pub use self::bool::SubCommand as RandomBool; pub use self::bool::SubCommand as RandomBool;
pub use chars::SubCommand as RandomChars;
pub use decimal::SubCommand as RandomDecimal;
pub use dice::SubCommand as RandomDice; pub use dice::SubCommand as RandomDice;
pub use integer::SubCommand as RandomInteger; pub use integer::SubCommand as RandomInteger;
#[cfg(feature = "uuid_crate")] #[cfg(feature = "uuid_crate")]

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

@ -264,7 +264,7 @@ fn string_from(input: &[Value]) -> String {
let mut first = true; let mut first = true;
for i in input.iter() { for i in input.iter() {
if !first { if !first {
save_data.push_str("\n"); save_data.push('\n');
} else { } else {
first = false; first = false;
} }

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('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

@ -0,0 +1,379 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use chrono::naive::NaiveDate;
use chrono::{Duration, Local};
use nu_errors::ShellError;
use nu_protocol::{value::I64Ext, value::StrExt, value::StringExt, value::U64Ext};
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct SeqDates;
#[derive(Deserialize)]
pub struct SeqDatesArgs {
separator: Option<Tagged<String>>,
output_format: Option<Tagged<String>>,
input_format: Option<Tagged<String>>,
begin_date: Option<Tagged<String>>,
end_date: Option<Tagged<String>>,
increment: Option<Tagged<i64>>,
days: Option<Tagged<u64>>,
reverse: Tagged<bool>,
}
#[async_trait]
impl WholeStreamCommand for SeqDates {
fn name(&self) -> &str {
"seq date"
}
fn signature(&self) -> Signature {
Signature::build("seq date")
.named(
"separator",
SyntaxShape::String,
"separator character (defaults to \\n)",
Some('s'),
)
.named(
"output_format",
SyntaxShape::String,
"prints dates in this format (defaults to %Y-%m-%d)",
Some('o'),
)
.named(
"input_format",
SyntaxShape::String,
"give argument dates in this format (defaults to %Y-%m-%d)",
Some('i'),
)
.named(
"begin_date",
SyntaxShape::String,
"beginning date range",
Some('b'),
)
.named("end_date", SyntaxShape::String, "ending date", Some('e'))
.named(
"increment",
SyntaxShape::Int,
"increment dates by this number",
Some('n'),
)
.named(
"days",
SyntaxShape::Int,
"number of days to print",
Some('d'),
)
.switch("reverse", "print dates in reverse", Some('r'))
}
fn usage(&self) -> &str {
"print sequences of dates"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
seq_dates(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "print the next 10 days in YYYY-MM-DD format with newline separator",
example: "seq date --days 10",
result: None,
},
Example {
description: "print the previous 10 days in YYYY-MM-DD format with newline separator",
example: "seq date --days 10 -r",
result: None,
},
Example {
description: "print the previous 10 days starting today in MM/DD/YYYY format with newline separator",
example: "seq date --days 10 -o '%m/%d/%Y' -r",
result: None,
},
Example {
description: "print the first 10 days in January, 2020",
example: "seq date -b '2020-01-01' -e '2020-01-10'",
result: Some(vec![
UntaggedValue::string("2020-01-01").into(),
UntaggedValue::string("2020-01-02").into(),
UntaggedValue::string("2020-01-03").into(),
UntaggedValue::string("2020-01-04").into(),
UntaggedValue::string("2020-01-05").into(),
UntaggedValue::string("2020-01-06").into(),
UntaggedValue::string("2020-01-07").into(),
UntaggedValue::string("2020-01-08").into(),
UntaggedValue::string("2020-01-09").into(),
UntaggedValue::string("2020-01-10").into(),
]),
},
Example {
description: "print every fifth day between January 1st 2020 and January 31st 2020",
example: "seq date -b '2020-01-01' -e '2020-01-31' -n 5",
result: Some(vec![
UntaggedValue::string("2020-01-01").into(),
UntaggedValue::string("2020-01-06").into(),
UntaggedValue::string("2020-01-11").into(),
UntaggedValue::string("2020-01-16").into(),
UntaggedValue::string("2020-01-21").into(),
UntaggedValue::string("2020-01-26").into(),
UntaggedValue::string("2020-01-31").into(),
]),
},
Example {
description: "starting on May 5th, 2020, print the next 10 days in your locale's date format, colon separated",
example: "seq date -o %x -s ':' -d 10 -b '2020-05-01'",
result: None,
},
]
}
}
async fn seq_dates(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let _name = args.call_info.name_tag.clone();
let (
SeqDatesArgs {
separator,
output_format,
input_format,
begin_date,
end_date,
increment,
days,
reverse,
},
_,
) = args.process(&registry).await?;
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 outformat = match output_format {
Some(s) => Some(s.item.to_string_value(s.tag)),
_ => None,
};
let informat = match input_format {
Some(s) => Some(s.item.to_string_value(s.tag)),
_ => None,
};
let begin = match begin_date {
Some(s) => Some(s.item),
_ => None,
};
let end = match end_date {
Some(s) => Some(s.item),
_ => None,
};
let inc = match increment {
Some(i) => {
let clone = i.clone();
i.to_value(clone.tag)
}
_ => (1 as i64).to_value_create_tag(),
};
let day_count: Option<Value> = match days {
Some(i) => Some(i.item.to_value(i.tag)),
_ => None,
};
let mut rev = false;
if *reverse {
rev = *reverse;
}
run_seq_dates(sep, outformat, informat, begin, end, inc, day_count, rev)
}
pub fn parse_date_string(s: &str, format: &str) -> Result<NaiveDate, &'static str> {
let d = match NaiveDate::parse_from_str(s, format) {
Ok(d) => d,
Err(_) => return Err("Failed to parse date."),
};
Ok(d)
}
#[allow(clippy::too_many_arguments)]
pub fn run_seq_dates(
separator: String,
output_format: Option<Value>,
input_format: Option<Value>,
beginning_date: Option<String>,
ending_date: Option<String>,
increment: Value,
day_count: Option<Value>,
reverse: bool,
) -> Result<OutputStream, ShellError> {
let today = Local::today().naive_local();
let mut step_size: i64 = increment
.as_i64()
.expect("unable to change increment to i64");
if step_size == 0 {
return Err(ShellError::labeled_error(
"increment cannot be 0",
"increment cannot be 0",
increment.tag,
));
}
let in_format = match input_format {
Some(i) => i.as_string().map_err(|e| {
ShellError::labeled_error(
e.to_string(),
"error with input_format as_string",
i.tag.span,
)
})?,
None => "%Y-%m-%d".to_string(),
};
let out_format = match output_format {
Some(o) => o.as_string().map_err(|e| {
ShellError::labeled_error(
e.to_string(),
"error with output_format as_string",
o.tag.span,
)
})?,
None => "%Y-%m-%d".to_string(),
};
let start_date = match beginning_date {
Some(d) => match parse_date_string(&d, &in_format) {
Ok(nd) => nd,
Err(e) => {
return Err(ShellError::labeled_error(
e,
"Failed to parse date",
Tag::unknown(),
))
}
},
_ => today,
};
let mut end_date = match ending_date {
Some(d) => match parse_date_string(&d, &in_format) {
Ok(nd) => nd,
Err(e) => {
return Err(ShellError::labeled_error(
e,
"Failed to parse date",
Tag::unknown(),
))
}
},
_ => today,
};
let mut days_to_output = match day_count {
Some(d) => d.as_i64()?,
None => 0i64,
};
// Make the signs opposite if we're created dates in reverse direction
if reverse {
step_size *= -1;
days_to_output *= -1;
}
if days_to_output != 0 {
end_date = match start_date.checked_add_signed(Duration::days(days_to_output)) {
Some(date) => date,
None => {
return Err(ShellError::labeled_error(
"integer value too large",
"integer value too large",
Tag::unknown(),
));
}
}
}
// conceptually counting down with a positive step or counting up with a negative step
// makes no sense, attempt to do what one means by inverting the signs in those cases.
if (start_date > end_date) && (step_size > 0) || (start_date < end_date) && step_size < 0 {
step_size = -step_size;
}
let is_out_of_range =
|next| (step_size > 0 && next > end_date) || (step_size < 0 && next < end_date);
let mut next = start_date;
if is_out_of_range(next) {
return Err(ShellError::labeled_error(
"date is out of range",
"date is out of range",
Tag::unknown(),
));
}
let mut ret_str = String::from("");
loop {
ret_str.push_str(&format!("{}", next.format(&out_format)));
// TODO: check this value is good
next += Duration::days(step_size);
if is_out_of_range(next) {
break;
}
ret_str.push_str(&separator);
}
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())
}
#[cfg(test)]
mod tests {
use super::SeqDates;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
Ok(test_examples(SeqDates {})?)
}
}

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

@ -159,12 +159,14 @@ fn action(input: &Value, options: &Substring, tag: impl Into<Tag>) -> Result<Val
"End must be greater than or equal to Start", "End must be greater than or equal to Start",
tag.span, tag.span,
)), )),
Ordering::Less => Ok(UntaggedValue::string( Ordering::Less => Ok(UntaggedValue::string(if end == isize::max_value() {
s.chars().skip(start as usize).collect::<String>()
} else {
s.chars() s.chars()
.skip(start as usize) .skip(start as usize)
.take((end - start) as usize) .take((end - start) as usize)
.collect::<String>(), .collect::<String>()
) })
.into_value(tag)), .into_value(tag)),
} }
} else { } else {
@ -285,8 +287,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> {
@ -330,6 +332,9 @@ mod tests {
expectation("and", (0, -3)), expectation("and", (0, -3)),
expectation("andr", (0, -2)), expectation("andr", (0, -2)),
expectation("andre", (0, -1)), expectation("andre", (0, -1)),
// str substring [ -4 , _ ]
// str substring -4 ,
expectation("dres", (-4, isize::max_value())),
expectation("", (0, -110)), expectation("", (0, -110)),
expectation("", (6, 0)), expectation("", (6, 0)),
expectation("", (6, -1)), expectation("", (6, -1)),

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> {

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