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
# 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
RUN sudo apt-get update && \
@ -11,4 +15,4 @@ RUN sudo apt-get update && \
rust-lldb \
&& 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"
readme = "README.md"
repository = "https://github.com/nushell/nushell"
version = "0.21.0"
version = "0.24.0"
[workspace]
members = ["crates/*/"]
@ -18,32 +18,33 @@ members = ["crates/*/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-cli = {version = "0.21.0", path = "./crates/nu-cli"}
nu-data = {version = "0.21.0", path = "./crates/nu-data"}
nu-errors = {version = "0.21.0", path = "./crates/nu-errors"}
nu-parser = {version = "0.21.0", path = "./crates/nu-parser"}
nu-plugin = {version = "0.21.0", path = "./crates/nu-plugin"}
nu-protocol = {version = "0.21.0", path = "./crates/nu-protocol"}
nu-source = {version = "0.21.0", path = "./crates/nu-source"}
nu-value-ext = {version = "0.21.0", path = "./crates/nu-value-ext"}
nu-cli = {version = "0.24.0", path = "./crates/nu-cli"}
nu-data = {version = "0.24.0", path = "./crates/nu-data"}
nu-errors = {version = "0.24.0", path = "./crates/nu-errors"}
nu-parser = {version = "0.24.0", path = "./crates/nu-parser"}
nu-plugin = {version = "0.24.0", path = "./crates/nu-plugin"}
nu-protocol = {version = "0.24.0", path = "./crates/nu-protocol"}
nu-source = {version = "0.24.0", path = "./crates/nu-source"}
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_chart = {version = "0.21.0", path = "./crates/nu_plugin_chart", optional = true}
nu_plugin_fetch = {version = "0.21.0", path = "./crates/nu_plugin_fetch", optional = true}
nu_plugin_from_bson = {version = "0.21.0", path = "./crates/nu_plugin_from_bson", optional = true}
nu_plugin_from_sqlite = {version = "0.21.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
nu_plugin_inc = {version = "0.21.0", path = "./crates/nu_plugin_inc", optional = true}
nu_plugin_match = {version = "0.21.0", path = "./crates/nu_plugin_match", optional = true}
nu_plugin_post = {version = "0.21.0", path = "./crates/nu_plugin_post", optional = true}
nu_plugin_ps = {version = "0.21.0", path = "./crates/nu_plugin_ps", optional = true}
nu_plugin_s3 = {version = "0.21.0", path = "./crates/nu_plugin_s3", optional = true}
nu_plugin_start = {version = "0.21.0", path = "./crates/nu_plugin_start", optional = true}
nu_plugin_sys = {version = "0.21.0", path = "./crates/nu_plugin_sys", optional = true}
nu_plugin_textview = {version = "0.21.0", path = "./crates/nu_plugin_textview", optional = true}
nu_plugin_to_bson = {version = "0.21.0", path = "./crates/nu_plugin_to_bson", optional = true}
nu_plugin_to_sqlite = {version = "0.21.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
nu_plugin_tree = {version = "0.21.0", path = "./crates/nu_plugin_tree", optional = true}
nu_plugin_xpath = {version = "0.21.0", path = "./crates/nu_plugin_xpath", optional = true}
nu_plugin_binaryview = {version = "0.24.0", path = "./crates/nu_plugin_binaryview", optional = true}
nu_plugin_chart = {version = "0.24.0", path = "./crates/nu_plugin_chart", optional = true}
nu_plugin_fetch = {version = "0.24.0", path = "./crates/nu_plugin_fetch", optional = true}
nu_plugin_from_bson = {version = "0.24.0", path = "./crates/nu_plugin_from_bson", optional = true}
nu_plugin_from_sqlite = {version = "0.24.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
nu_plugin_inc = {version = "0.24.0", path = "./crates/nu_plugin_inc", optional = true}
nu_plugin_match = {version = "0.24.0", path = "./crates/nu_plugin_match", optional = true}
nu_plugin_post = {version = "0.24.0", path = "./crates/nu_plugin_post", optional = true}
nu_plugin_ps = {version = "0.24.0", path = "./crates/nu_plugin_ps", optional = true}
nu_plugin_s3 = {version = "0.24.0", path = "./crates/nu_plugin_s3", optional = true}
nu_plugin_start = {version = "0.24.0", path = "./crates/nu_plugin_start", optional = true}
nu_plugin_sys = {version = "0.24.0", path = "./crates/nu_plugin_sys", optional = true}
nu_plugin_textview = {version = "0.24.0", path = "./crates/nu_plugin_textview", optional = true}
nu_plugin_to_bson = {version = "0.24.0", path = "./crates/nu_plugin_to_bson", optional = true}
nu_plugin_to_sqlite = {version = "0.24.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
nu_plugin_tree = {version = "0.24.0", path = "./crates/nu_plugin_tree", 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
clap = "2.33.3"
@ -51,10 +52,11 @@ ctrlc = {version = "3.1.6", optional = true}
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
log = "0.4.11"
pretty_env_logger = "0.4.0"
itertools = "0.9.0"
[dev-dependencies]
dunce = "1.0.1"
nu-test-support = {version = "0.21.0", path = "./crates/nu-test-support"}
nu-test-support = {version = "0.24.0", path = "./crates/nu-test-support"}
[build-dependencies]
@ -86,8 +88,9 @@ default = [
"post",
"fetch",
"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"]
wasi = ["inc", "match", "directories-support", "ptree-support", "match", "tree", "rustyline-support"]
@ -102,6 +105,7 @@ post = ["nu_plugin_post"]
ps = ["nu_plugin_ps"]
sys = ["nu_plugin_sys"]
textview = ["nu_plugin_textview"]
zip-support = ["nu-cli/zip"]
# Extra
binaryview = ["nu_plugin_binaryview"]
@ -114,6 +118,13 @@ start = ["nu_plugin_start"]
trash-support = ["nu-cli/trash-support"]
tree = ["nu_plugin_tree"]
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
# 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"
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
[[bin]]
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)
[![@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.
@ -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.
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.
@ -44,9 +44,9 @@ Try it in Gitpod.
### 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:
@ -219,15 +219,15 @@ We can pipeline this into a command that gets the contents of one of the columns
name │ nu
readme │ README.md
repository │ https://github.com/nushell/nushell
version │ 0.15.1
version │ 0.21.0
───────────────┴────────────────────────────────────
```
Finally, we can use commands outside of Nu once we have the data we want:
```shell
> open Cargo.toml | get package.version | echo $it
0.15.1
> open Cargo.toml | get package.version
0.21.0
```
Here we use the variable `$it` to refer to the value being piped to the external command.
@ -307,7 +307,7 @@ Nu is in heavy development, and will naturally change as it matures and people u
## 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

View File

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

View File

@ -1,36 +1,6 @@
use std::path::Path;
use std::{env, fs, io};
fn main() -> Result<(), io::Error> {
let out_dir = env::var_os("OUT_DIR").expect(
"\
OUT_DIR environment variable not found. \
OUT_DIR is guaranteed to to exist in a build script by cargo - see \
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)?;
fn main() -> shadow_rs::SdResult<()> {
let src_path = std::env::var("CARGO_MANIFEST_DIR")?;
let out_path = std::env::var("OUT_DIR")?;
shadow_rs::Shadow::build(src_path, out_path)?;
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(Cpy),
whole_stream_command(Date),
whole_stream_command(DateListTimeZone),
whole_stream_command(DateNow),
whole_stream_command(DateUTC),
whole_stream_command(DateToTable),
whole_stream_command(DateToTimeZone),
whole_stream_command(DateFormat),
whole_stream_command(Cal),
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(Table),
// Text manipulation
whole_stream_command(Hash),
whole_stream_command(HashBase64),
whole_stream_command(Split),
whole_stream_command(SplitColumn),
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(Char),
// Column manipulation
whole_stream_command(MoveColumn),
whole_stream_command(Move),
whole_stream_command(Reject),
whole_stream_command(Select),
whole_stream_command(Get),
@ -180,6 +184,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(Nth),
whole_stream_command(Drop),
whole_stream_command(Format),
whole_stream_command(FileSize),
whole_stream_command(Where),
whole_stream_command(If),
whole_stream_command(Compact),
@ -198,6 +203,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(EachWindow),
whole_stream_command(Empty),
// Table manipulation
whole_stream_command(Flatten),
whole_stream_command(Move),
whole_stream_command(Merge),
whole_stream_command(Shuffle),
@ -211,6 +217,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(AutoenvTrust),
whole_stream_command(AutoenvUnTrust),
whole_stream_command(Math),
whole_stream_command(MathAbs),
whole_stream_command(MathAverage),
whole_stream_command(MathEval),
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(MathVariance),
whole_stream_command(MathProduct),
whole_stream_command(MathRound),
whole_stream_command(MathFloor),
whole_stream_command(MathCeil),
// File format output
whole_stream_command(To),
whole_stream_command(ToCSV),
@ -258,6 +268,8 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
#[cfg(feature = "uuid_crate")]
whole_stream_command(RandomUUID),
whole_stream_command(RandomInteger),
whole_stream_command(RandomDecimal),
whole_stream_command(RandomChars),
// Path
whole_stream_command(PathBasename),
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(UrlHost),
whole_stream_command(UrlQuery),
whole_stream_command(Seq),
whole_stream_command(SeqDates),
]);
#[cfg(feature = "clipboard-cli")]
@ -317,6 +331,7 @@ pub async fn run_vec_of_pipelines(
#[cfg(feature = "rustyline-support")]
fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>) -> LineResult {
match input {
Ok(s) if s == "history -c" || s == "history --clear" => LineResult::ClearHistory,
Ok(s) => LineResult::Success(s),
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
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") {
let prompt_line = prompt.as_string()?;
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
Ok(result) => {
let mut prompt_block =
nu_parser::classify_block(&result, context.registry());
let (result, err) = nu_parser::lite_parse(&prompt_line, 0);
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(
&prompt_block.block,
&mut context,
InputStream::empty(),
Scope::from_env(env),
)
.await
{
Ok(result) => match result.collect_string(Tag::unknown()).await {
Ok(string_result) => {
let errors = context.get_errors();
context.maybe_print_errors(Text::from(prompt_line));
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();
match run_block(
&prompt_block.block,
&mut context,
InputStream::empty(),
Scope::from_env(env),
)
.await
{
Ok(result) => match result.collect_string(Tag::unknown()).await {
Ok(string_result) => {
let errors = context.get_errors();
context.maybe_print_errors(Text::from(prompt_line));
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();
"> ".to_string()
}
}
}
Err(e) => {
crate::cli::print_err(e, &Text::from(prompt_line));
context.clear_errors();
},
Err(e) => {
crate::cli::print_err(e, &Text::from(prompt_line));
context.clear_errors();
"> ".to_string()
"> ".to_string()
}
}
}
} else {
@ -496,6 +512,11 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
context.maybe_print_errors(Text::from(line));
}
LineResult::ClearHistory => {
rl.clear_history();
let _ = rl.save_history(&history_path);
}
LineResult::Error(line, err) => {
rl.add_history_entry(&line);
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 {
if s.ends_with('\n') {
&s[..s.len() - 1]
if let Some(s) = s.strip_suffix('\n') {
s
} else {
s
}
@ -845,20 +866,23 @@ pub enum LineResult {
Break,
CtrlC,
CtrlD,
ClearHistory,
}
pub async fn parse_and_eval(line: &str, ctx: &mut EvaluationContext) -> Result<String, ShellError> {
let line = if line.ends_with('\n') {
&line[..line.len() - 1]
let line = if let Some(s) = line.strip_suffix('\n') {
s
} else {
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
let mut classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
classified_block.block.expand_it_usage();
let classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
let input_stream = InputStream::empty();
let env = ctx.get_env();
@ -888,18 +912,16 @@ pub async fn process_line(
let line = chomp_newline(line);
ctx.raw_input = line.to_string();
let result = match nu_parser::lite_parse(&line, 0) {
Err(err) => {
return LineResult::Error(line.to_string(), err.into());
}
let (result, err) = nu_parser::lite_parse(&line, 0);
Ok(val) => val,
};
if let Some(err) = err {
return LineResult::Error(line.to_string(), err.into());
}
debug!("=== Parsed ===");
debug!("{:#?}", result);
let mut classified_block = nu_parser::classify_block(&result, ctx.registry());
let classified_block = nu_parser::classify_block(&result, ctx.registry());
debug!("{:#?}", classified_block);
//println!("{:#?}", pipeline);
@ -1016,8 +1038,6 @@ pub async fn process_line(
InputStream::empty()
};
classified_block.block.expand_it_usage();
trace!("{:#?}", classified_block);
let env = ctx.get_env();
match run_block(
@ -1093,7 +1113,8 @@ pub fn print_err(err: ShellError, source: &Text) {
mod tests {
#[quickcheck]
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 _ = 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 exit;
pub(crate) mod first;
pub(crate) mod flatten;
pub(crate) mod format;
pub(crate) mod from;
pub(crate) mod from_csv;
@ -61,6 +62,7 @@ pub(crate) mod from_yaml;
pub(crate) mod get;
pub(crate) mod group_by;
pub(crate) mod group_by_date;
pub(crate) mod hash_;
pub(crate) mod headers;
pub(crate) mod help;
pub(crate) mod histogram;
@ -97,6 +99,8 @@ pub(crate) mod run_alias;
pub(crate) mod run_external;
pub(crate) mod save;
pub(crate) mod select;
pub(crate) mod seq;
pub(crate) mod seq_dates;
pub(crate) mod shells;
pub(crate) mod shuffle;
pub(crate) mod size;
@ -150,7 +154,7 @@ pub(crate) use config::{
};
pub(crate) use count::Count;
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 default::Default;
pub(crate) use describe::Describe;
@ -175,7 +179,8 @@ pub(crate) use every::Every;
pub(crate) use exec::Exec;
pub(crate) use exit::Exit;
pub(crate) use first::First;
pub(crate) use format::Format;
pub(crate) use flatten::Command as Flatten;
pub(crate) use format::{FileSize, Format};
pub(crate) use from::From;
pub(crate) use from_csv::FromCSV;
pub(crate) use from_eml::FromEML;
@ -195,6 +200,7 @@ pub(crate) use from_yaml::FromYML;
pub(crate) use get::Get;
pub(crate) use group_by::Command as GroupBy;
pub(crate) use group_by_date::GroupByDate;
pub(crate) use hash_::{Hash, HashBase64};
pub(crate) use headers::Headers;
pub(crate) use help::Help;
pub(crate) use histogram::Histogram;
@ -206,12 +212,12 @@ pub(crate) use last::Last;
pub(crate) use lines::Lines;
pub(crate) use ls::Ls;
pub(crate) use math::{
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathProduct,
MathStddev, MathSummation, MathVariance,
Math, MathAbs, MathAverage, MathCeil, MathEval, MathFloor, MathMaximum, MathMedian,
MathMinimum, MathMode, MathProduct, MathRound, MathStddev, MathSummation, MathVariance,
};
pub(crate) use merge::Merge;
pub(crate) use mkdir::Mkdir;
pub(crate) use move_::{Move, MoveColumn, Mv};
pub(crate) use move_::{Move, Mv};
pub(crate) use next::Next;
pub(crate) use nth::Nth;
pub(crate) use open::Open;
@ -226,7 +232,9 @@ pub(crate) use prev::Previous;
pub(crate) use pwd::Pwd;
#[cfg(feature = "uuid_crate")]
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 reduce::Reduce;
pub(crate) use reject::Reject;
@ -236,6 +244,8 @@ pub(crate) use rm::Remove;
pub(crate) use run_external::RunExternalCommand;
pub(crate) use save::Save;
pub(crate) use select::Select;
pub(crate) use seq::Seq;
pub(crate) use seq_dates::SeqDates;
pub(crate) use shells::Shells;
pub(crate) use shuffle::Shuffle;
pub(crate) use size::Size;
@ -278,19 +288,26 @@ mod tests {
use crate::examples::{test_anchors, test_examples};
use nu_errors::ShellError;
fn commands() -> Vec<Command> {
fn full_tests() -> Vec<Command> {
vec![
whole_stream_command(Append),
whole_stream_command(GroupBy),
whole_stream_command(Insert),
whole_stream_command(Move),
whole_stream_command(Update),
whole_stream_command(Empty),
]
}
fn only_examples() -> Vec<Command> {
let mut commands = full_tests();
commands.extend(vec![whole_stream_command(Flatten)]);
commands
}
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
for cmd in commands() {
for cmd in only_examples() {
test_examples(cmd)?;
}
@ -299,7 +316,7 @@ mod tests {
#[test]
fn tracks_metadata() -> Result<(), ShellError> {
for cmd in commands() {
for cmd in full_tests() {
test_anchors(cmd)?;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -63,7 +63,7 @@ pub async fn clip(
let mut first = true;
for i in values.iter() {
if !first {
new_copy_data.push_str("\n");
new_copy_data.push('\n');
} else {
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 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 std::fmt::{self, write};
pub struct Date;
#[derive(Deserialize)]
pub struct FormatArgs {
format: Tagged<String>,
raw: Option<bool>,
table: bool,
}
#[async_trait]
@ -24,11 +24,11 @@ impl WholeStreamCommand for Date {
fn signature(&self) -> Signature {
Signature::build("date 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 {
"format the current date using the given format string."
"Format a given date using the given format string."
}
async fn run(
@ -38,6 +38,21 @@ impl WholeStreamCommand for Date {
) -> Result<OutputStream, ShellError> {
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(
@ -46,20 +61,46 @@ pub async fn format(
) -> Result<OutputStream, ShellError> {
let registry = registry.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 = {
let local: DateTime<Local> = Local::now();
if let Some(true) = raw {
UntaggedValue::string(date_to_value_raw(local, dt_fmt)).into_untagged_value()
} else {
date_to_value(local, tag, dt_fmt)
}
};
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
} else {
UntaggedValue::string(&output).into_value(&tag)
};
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)]

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -54,6 +54,7 @@ pub async fn from_delimited_data(
) -> Result<OutputStream, ShellError> {
let name_tag = name;
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()) {
Ok(x) => match x {
@ -65,10 +66,16 @@ pub async fn from_delimited_data(
},
Err(err) => {
let line_one = match pretty_csv_error(err) {
Some(pretty) => format!("Could not parse as {} ({})", format_name, pretty),
None => format!("Could not parse as {}", format_name),
Some(pretty) => format!(
"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(
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 span = tag.span;
match v {
serde_hjson::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
serde_hjson::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
serde_hjson::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag),
serde_hjson::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
serde_hjson::Value::String(s) => {
nu_json::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
nu_json::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
nu_json::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag),
nu_json::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
nu_json::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
nu_json::Value::String(s) => {
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()
.map(|x| convert_json_value_to_nu_value(x, &tag))
.collect(),
)
.into_value(tag),
serde_hjson::Value::Object(o) => {
nu_json::Value::Object(o) => {
let mut collected = TaggedDictBuilder::new(&tag);
for (k, v) in o.iter() {
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> {
let v: serde_hjson::Value = serde_hjson::from_str(&s)?;
pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> nu_json::Result<Value> {
let v: nu_json::Value = nu_json::from_str(&s)?;
Ok(convert_json_value_to_nu_value(&v, tag))
}
@ -96,7 +96,7 @@ async fn from_json(
Err(e) => {
let mut message = "Could not parse as JSON (".to_string();
message.push_str(&e.to_string());
message.push_str(")");
message.push(')');
Some(Err(ShellError::labeled_error_with_secondary(
message,
@ -125,7 +125,7 @@ async fn from_json(
Err(e) => {
let mut message = "Could not parse as JSON (".to_string();
message.push_str(&e.to_string());
message.push_str(")");
message.push(')');
Ok(OutputStream::one(Err(
ShellError::labeled_error_with_secondary(

View File

@ -68,13 +68,12 @@ fn convert_yaml_value_to_nu_value(
Ok(match v {
serde_yaml::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(tag),
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::Sequence(a) => {
let result: Result<Vec<Value>, ShellError> = a
@ -175,8 +174,8 @@ async fn from_yaml(
mod tests {
use super::ShellError;
use super::*;
use nu_plugin::row;
use nu_plugin::test_helpers::value::string;
use nu_protocol::row;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {

View File

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

View File

@ -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;
#[async_trait]
@ -36,7 +41,7 @@ impl WholeStreamCommand for History {
}
fn signature(&self) -> Signature {
Signature::build("history")
Signature::build("history").switch("clear", "Clears out the history entries", Some('c'))
}
fn usage(&self) -> &str {
@ -48,31 +53,45 @@ impl WholeStreamCommand for History {
args: CommandArgs,
registry: &CommandRegistry,
) -> 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 tag = args.call_info.name_tag;
let path = history_path(&config);
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,
});
let tag = args.call_info.name_tag.clone();
let (Arguments { clear }, _) = args.process(&_registry).await?;
Ok(futures::stream::iter(output).to_output_stream())
} else {
Err(ShellError::labeled_error(
"Could not open history",
"history file could not be opened",
tag,
))
let path = history_path(&config);
match clear {
Some(_) => {
// This is a NOOP, the logic to clear is handled in cli.rs
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 else_case = else_case.clone();
let registry = registry.clone();
let scope = Scope::append_it(scope.clone(), input);
let scope = Scope::append_var(scope.clone(), "$it", input);
let mut context = context.clone();
async move {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,11 +2,18 @@ use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
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;
pub struct PathBasename;
#[derive(Deserialize)]
struct PathBasenameArguments {
replace: Option<Tagged<String>>,
rest: Vec<ColumnPath>,
}
#[async_trait]
impl WholeStreamCommand for PathBasename {
fn name(&self) -> &str {
@ -15,11 +22,17 @@ impl WholeStreamCommand for PathBasename {
fn signature(&self) -> Signature {
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 {
"gets the filename of a path"
"Gets the final component of a path"
}
async fn run(
@ -28,24 +41,60 @@ impl WholeStreamCommand for PathBasename {
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await
let (PathBasenameArguments { replace, rest }, input) = args.process(&registry).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> {
vec![Example {
description: "Get basename of a path",
example: "echo '/home/joe/test.txt' | path basename",
result: Some(vec![Value::from("test.txt")]),
}]
vec![
Example {
description: "Get basename of a path",
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 {
UntaggedValue::string(match path.file_name() {
Some(filename) => filename.to_string_lossy().to_string(),
_ => "".to_string(),
})
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
match args.replace {
Some(ref basename) => UntaggedValue::path(path.with_file_name(basename)),
None => UntaggedValue::string(match path.file_name() {
Some(filename) => filename.to_string_lossy(),
None => "".into(),
}),
}
}
#[cfg(test)]

View File

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

View File

@ -2,11 +2,20 @@ use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
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;
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]
impl WholeStreamCommand for PathDirname {
fn name(&self) -> &str {
@ -14,11 +23,24 @@ impl WholeStreamCommand for PathDirname {
}
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 {
"gets the dirname of a path"
"Gets the parent directory of a path"
}
async fn run(
@ -27,24 +49,100 @@ impl WholeStreamCommand for PathDirname {
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await
let (
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> {
vec![Example {
description: "Get dirname of a path",
example: "echo '/home/joe/test.txt' | path dirname",
result: Some(vec![Value::from("/home/joe")]),
}]
vec![
Example {
description: "Get dirname of a path",
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 {
UntaggedValue::string(match path.parent() {
Some(dirname) => dirname.to_string_lossy().to_string(),
_ => "".to_string(),
})
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
let num_levels = args.num_levels.unwrap_or(1);
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)]

View File

@ -2,11 +2,16 @@ use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
use std::path::Path;
pub struct PathExists;
#[derive(Deserialize)]
struct PathExistsArguments {
rest: Vec<ColumnPath>,
}
#[async_trait]
impl WholeStreamCommand for PathExists {
fn name(&self) -> &str {
@ -14,11 +19,12 @@ impl WholeStreamCommand for PathExists {
}
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 {
"checks whether the path exists"
"Checks whether a path exists"
}
async fn run(
@ -27,10 +33,27 @@ impl WholeStreamCommand for PathExists {
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await
let (PathExistsArguments { rest }, input) = args.process(&registry).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> {
vec![Example {
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())
}

View File

@ -2,11 +2,16 @@ use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
use std::path::Path;
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue};
use std::path::{Path, PathBuf};
pub struct PathExpand;
#[derive(Deserialize)]
struct PathExpandArguments {
rest: Vec<ColumnPath>,
}
#[async_trait]
impl WholeStreamCommand for PathExpand {
fn name(&self) -> &str {
@ -14,11 +19,12 @@ impl WholeStreamCommand for PathExpand {
}
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 {
"expands the path to its absolute form"
"Expands a path to its absolute form"
}
async fn run(
@ -27,28 +33,43 @@ impl WholeStreamCommand for PathExpand {
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await
let (PathExpandArguments { rest }, input) = args.process(&registry).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> {
vec![Example {
description: "Expand relative directories",
example: "echo '/home/joe/foo/../bar' | path expand",
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 expanded = shellexpand::tilde(&ps);
let path: &Path = expanded.as_ref().as_ref();
UntaggedValue::string(match path.canonicalize() {
Ok(p) => p.to_string_lossy().to_string(),
Err(_) => ps.to_string(),
})
UntaggedValue::path(dunce::canonicalize(path).unwrap_or_else(|_| PathBuf::from(path)))
}
#[cfg(test)]

View File

@ -2,11 +2,18 @@ use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
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;
pub struct PathExtension;
#[derive(Deserialize)]
struct PathExtensionArguments {
replace: Option<Tagged<String>>,
rest: Vec<ColumnPath>,
}
#[async_trait]
impl WholeStreamCommand for PathExtension {
fn name(&self) -> &str {
@ -15,11 +22,17 @@ impl WholeStreamCommand for PathExtension {
fn signature(&self) -> Signature {
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 {
"gets the extension of a path"
"Gets the extension of a path"
}
async fn run(
@ -28,8 +41,15 @@ impl WholeStreamCommand for PathExtension {
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await
let (PathExtensionArguments { replace, rest }, input) = args.process(&registry).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> {
@ -44,15 +64,28 @@ impl WholeStreamCommand for PathExtension {
example: "echo 'test' | path extension",
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 {
UntaggedValue::string(match path.extension() {
Some(ext) => ext.to_string_lossy().to_string(),
_ => "".to_string(),
})
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
match args.replace {
Some(ref extension) => UntaggedValue::path(path.with_extension(extension)),
None => UntaggedValue::string(match path.extension() {
Some(extension) => extension.to_string_lossy(),
None => "".into(),
}),
}
}
#[cfg(test)]

View File

@ -2,11 +2,20 @@ use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
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;
pub struct PathFilestem;
#[derive(Deserialize)]
struct PathFilestemArguments {
prefix: Option<Tagged<String>>,
suffix: Option<Tagged<String>>,
replace: Option<Tagged<String>>,
rest: Vec<ColumnPath>,
}
#[async_trait]
impl WholeStreamCommand for PathFilestem {
fn name(&self) -> &str {
@ -15,11 +24,29 @@ impl WholeStreamCommand for PathFilestem {
fn signature(&self) -> Signature {
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 {
"gets the filestem of a path"
"Gets the file stem of a path"
}
async fn run(
@ -28,24 +55,111 @@ impl WholeStreamCommand for PathFilestem {
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await
let (
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> {
vec![Example {
description: "Get filestem of a path",
example: "echo '/home/joe/test.txt' | path filestem",
result: Some(vec![Value::from("test")]),
}]
vec![
Example {
description: "Get filestem of a path",
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 {
UntaggedValue::string(match path.file_stem() {
Some(stem) => stem.to_string_lossy().to_string(),
_ => "".to_string(),
})
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
let basename = match path.file_name() {
Some(name) => name.to_string_lossy().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)]

View File

@ -12,6 +12,7 @@ use nu_errors::ShellError;
use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, ShellTypeName, UntaggedValue, Value};
use nu_source::Span;
use std::path::Path;
use std::sync::Arc;
pub use basename::PathBasename;
pub use command::Path as PathCommand;
@ -24,17 +25,32 @@ pub use r#type::PathType;
#[derive(Deserialize)]
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
F: Fn(&Path) -> UntaggedValue + Send + 'static,
F: Fn(&Path, Arc<DefaultArguments>) -> UntaggedValue + Send + 'static,
{
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::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 => {
let got = format!("got {}", other.type_name());
return Err(ShellError::labeled_error_with_secondary(
@ -51,24 +67,25 @@ where
async fn operate<F>(
input: crate::InputStream,
paths: Vec<ColumnPath>,
action: &'static F,
span: Span,
args: Arc<DefaultArguments>,
) -> Result<OutputStream, ShellError>
where
F: Fn(&Path) -> UntaggedValue + Send + Sync + 'static,
F: Fn(&Path, Arc<DefaultArguments>) -> UntaggedValue + Send + Sync + 'static,
{
Ok(input
.map(move |v| {
if paths.is_empty() {
ReturnSuccess::value(handle_value(&action, &v, span)?)
if args.paths.is_empty() {
ReturnSuccess::value(handle_value(&action, &v, span, Arc::clone(&args))?)
} else {
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(
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::shell::filesystem_shell::get_file_type;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
use std::path::Path;
pub struct PathType;
#[derive(Deserialize)]
struct PathTypeArguments {
rest: Vec<ColumnPath>,
}
#[async_trait]
impl WholeStreamCommand for PathType {
fn name(&self) -> &str {
@ -15,11 +20,12 @@ impl WholeStreamCommand for PathType {
}
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 {
"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(
@ -28,8 +34,15 @@ impl WholeStreamCommand for PathType {
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (DefaultArguments { rest }, input) = args.process(&registry).await?;
operate(input, rest, &action, tag.span).await
let (PathTypeArguments { rest }, input) = args.process(&registry).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> {
@ -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);
UntaggedValue::string(match &meta {
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 bool;
pub mod chars;
pub mod decimal;
pub mod dice;
pub mod integer;
#[cfg(feature = "uuid_crate")]
@ -9,6 +11,8 @@ pub mod uuid;
pub use command::Command as Random;
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 integer::SubCommand as RandomInteger;
#[cfg(feature = "uuid_crate")]

View File

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

View File

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

View File

@ -264,7 +264,7 @@ fn string_from(input: &[Value]) -> String {
let mut first = true;
for i in input.iter() {
if !first {
save_data.push_str("\n");
save_data.push('\n');
} else {
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| {
let condition = condition.clone();
let registry = registry.clone();
let scope = Scope::append_it(scope.clone(), item.clone());
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
trace!("ITEM = {:?}", item);
async move {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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",
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()
.skip(start as usize)
.take((end - start) as usize)
.collect::<String>(),
)
.collect::<String>()
})
.into_value(tag)),
}
} else {
@ -285,8 +287,8 @@ fn process_arguments(range: Value, name: impl Into<Tag>) -> Result<(isize, isize
mod tests {
use super::ShellError;
use super::{action, SubCommand, Substring};
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
@ -330,6 +332,9 @@ mod tests {
expectation("and", (0, -3)),
expectation("andr", (0, -2)),
expectation("andre", (0, -1)),
// str substring [ -4 , _ ]
// str substring -4 ,
expectation("dres", (-4, isize::max_value())),
expectation("", (0, -110)),
expectation("", (6, 0)),
expectation("", (6, -1)),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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