Compare commits

...

153 Commits

Author SHA1 Message Date
47c5346934 Update release.yml
Fix github workflow to use extra instead of stable
2020-09-02 16:17:06 +12:00
882cf74137 Bump to 0.19.0 (#2483) 2020-09-02 15:37:06 +12:00
57a26bbd42 Default alignment (#2481)
* WIP - compiling but not working

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

* fixed bug where no config.toml or not set settings made weird defaults.

* clippy & fmt again

* Header default alignment is left.

Co-authored-by: Andrés N. Robalino <andres@androbtech.com>
2020-09-01 19:08:41 -05:00
f9acb7a7a5 Support ~ (home directory) in completions.
This requires a bit of a hack in command completions, since we don't expand `~`
for the replacement, just long enough to get child entries.
2020-09-01 18:31:36 -05:00
569345e1d4 Color info for config.toml (#2478)
* WIP - compiling but not working

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

* some info on how to set colors
2020-09-01 08:48:27 -05:00
adbbcafd30 Add minor theme support (#2449)
* WIP - compiling but not working

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* removed extra comments
2020-09-01 17:09:55 +12:00
860c2a606d More bug fixes for completions (#2476)
* Use the cursor position for the span when between locations.

This fixes a bug where completing

    ls <TAB>

would result in replacing the entire line.

* Revert to the default update implementation.

Replacing the length of the elected value was intended to do replacement when
one moves inside a quote. The problem is that a long elected suggestion could
replace bits of a pipeline that are after the cursor. For example:

    ls <TAB> | get name | str collect
2020-09-01 16:10:46 +12:00
6b5d96337e Add examples to uniq (#2472) 2020-09-01 14:52:55 +12:00
f54cf8a096 Size command: rename max length to bytes (#2473)
Since max length is just getting the byte length, rename to bytes
in order to be more clear.
2020-09-01 14:51:35 +12:00
b5d591bb09 Improve support for completing within quotes. (#2474)
We ensure the partially cimpleted item doesn't include the end quote. We also
ensure that the appropriate span is replaced, not just the suggested position up
to the cursor position.
2020-09-01 14:13:00 +12:00
60ce497edc Only requote path arguments. (#2471) 2020-08-31 20:11:39 -04:00
dd4351e2b7 tolerate os error while executing ls command (#2466) 2020-09-01 05:14:37 +12:00
3f443f40d0 with_love table theme (#2468) 2020-08-31 11:50:32 -05:00
8192360b20 added compact_double table theme for funsies (#2467) 2020-08-31 09:49:27 -05:00
889b67ca92 Improve quoting for path completions. (#2464)
- Ensure quotes surround suggestion replacement when there are spaces.
- Ensure an appropriate quote character is chosen based on other quoting
  characters being in the suggestions.
2020-08-31 15:29:13 +12:00
8d1cecf643 Set auto-pivot to never by default (#2462) 2020-08-31 14:38:08 +12:00
188d33b306 Add a custom path completer to nushell. (#2463)
Previously, we used rustyline's filename completer. This allowed us to make
progress on the completion engine without building all the parts at once. We now
need our own filename completer to make progress.

The primary driver to having our own filename completer is that it can better
integrate with our path constructs. For example, if we have

    > ls .../<TAB>

we want to show a list of suggestions that includes all files two directories up
from the current working directory. The least jarring experience to a user would
be to maintain the three dots. The easiest way for us to do this is by building
our own completer and path constructs.
2020-08-30 22:28:09 -04:00
965e07d8cc Minor updates to variance (#2458) 2020-08-30 16:50:36 -04:00
d6b6b1df38 Changing the directory to '.' doesn't modify the prompt anymore (#2457)
Doing 'cd .' or an equal command used to modify the prompt: It appended an './' and was even
repeatable, leading to strange prompts (believe me, I tested it for too long).

This fixes #2432
2020-08-31 05:24:38 +12:00
c897ac6e1e Add support for multiline rustyline edits (#2456)
* Add support for multiline rustyline edits

* clippy
2020-08-30 20:00:28 +12:00
df691c6c91 Light fixes (#2455)
* Add optional commas for items in lists and tables

* A couple last fixes
2020-08-30 19:03:18 +12:00
abc05ece21 Add optional commas for items in lists and tables (#2454) 2020-08-30 18:19:54 +12:00
6f69ae8707 Add table literals (#2453)
* Add table literals

* clippy
2020-08-30 16:55:33 +12:00
84a6010f71 Minor stddev updates (#2452)
1. Add an example for getting the sample stddev
2. Opt for ? instead of generic Err match
2020-08-30 15:36:43 +12:00
634bb688c1 CONTRIBUTING: useful commands as list (#2448) 2020-08-30 15:33:28 +12:00
c14b209276 Add missing math commands to docs (#2447) 2020-08-30 15:32:38 +12:00
6535ae3d6e Show directories and executable for command completion. (#2446)
* Show directories and executable for command completion.

Previously we chose from two sets for completing the command position:

1. internal commands, and
2. executables relative to the PATH environment variable.

We now also show directories/executables that match the relative/absolute path
that has been partially typed.

* Fix for Windows
2020-08-30 15:31:42 +12:00
0390ec97f4 Create benign email test fixture (#2445)
1. The previous one was an Ebay email and flagged by AVs, create something simple.
2. Add test case for another header
2020-08-29 12:57:50 -04:00
e3c4d82798 Ensure command name available for Argument completion locations (#2443) 2020-08-28 19:50:46 -04:00
ee71a35786 make cd command complete only folders (#2431) 2020-08-28 14:38:30 -04:00
11ea5e61fc Fix out of bounds in header command when row size is different (#2437)
* Use header vec to loop over the row instead of the row itself to fix out of bounds

* Fixed formatting
2020-08-29 06:32:08 +12:00
02763b47f7 Add spans to pipelines. 2020-08-28 05:17:58 -05:00
4828a7cd29 Update config.yml 2020-08-28 06:02:06 +12:00
728852c750 Remove unnecessary reference to ichwh in command completer (#2435) 2020-08-27 10:14:04 -04:00
26cec83b63 Extract out history parts. 2020-08-27 06:28:18 -05:00
4724b3c570 Slim down cli plugin logic. 2020-08-27 06:28:18 -05:00
303a9defd3 Added math product support (#2249)
* added math product: working initial impl

* resolving merge conflicts

* impl std::ops::Mul for Filesize

* rebased with main; refactored product implementation

* fixed error msg, added docs for math product
2020-08-27 17:58:01 +12:00
a64270829e Move alias type inference (experimental) behind --infer/-i flag (#2418)
* put alias type inference behind --infer/-i flag

* revert cargo.lock
2020-08-27 17:48:13 +12:00
8f5df89a78 added -e --end to search from the end of the string for the pattern (#2430)
* added -e --end to search from the end of the string for the pattern

* tests
2020-08-27 17:46:45 +12:00
39f402c8cc Add configuration option to disable hinter (#2403) (#2405)
* Add configuration option to disable hinter

* Move hint configuration inside line_editor
2020-08-27 17:45:55 +12:00
7702d683c7 Allow the calculation of bytes and int. (#2160)
* Allow the calculation of bytes and int.

* fix clippy.

* minimal implement the into_into command.

* Revert "fix clippy."

This reverts commit 0d7cf72ed2.

* Revert "Allow the calculation of bytes and int."

This reverts commit 9c4e3787f5.

* set the argument to any type.

* if the argument is an int, return it with no change in value.

* add tests for into-int command.

* fix a faild test.
2020-08-27 17:44:18 +12:00
766533aafb Path dirname and filestem (#2428)
* add dirname and filestem to path commands

* hacked around with gedge's help to get it to compile with cargo b

* fmt
2020-08-26 14:47:23 -05:00
781e423a97 cleaned up sample ps1 script and added more comments. (#2429)
It's not complete but a good start for anyone interested in learning.
2020-08-26 14:46:01 -05:00
6e3a827e32 plugin changes to support script plugins (#2315)
* plugin changes to support script plugins
* all platforms can have plugins with file extensions
* added .cmd, .py, .ps1 for windows
* added more trace! statements to document request and response

* WIP

* pretty much working, need to figure out sink plugins

* added a home for scripting plugin examples, ran fmt

* Had a visit with my good friend Clippy. We're on speaking terms again.

* add viable plugin extensions

* clippy

* update to load plugins without extension in *nix/mac.

* fmt
2020-08-26 13:45:11 -05:00
6685f74e03 Command especific configuration extraction baseline. 2020-08-26 10:33:55 -05:00
034c33c2b5 Allow invocations and fix span error reporting. 2020-08-26 06:46:14 -05:00
08d1be79fc Add some cross-references to usage texts (#2417) 2020-08-26 09:43:41 +12:00
c563b7862e Update battery version (#2413)
Update battery. Should should move us onto the same uom version for both battery and heim.
2020-08-26 06:59:39 +12:00
a64cfb6285 Command expression need not carry span information. 2020-08-24 22:48:33 -05:00
f078aacc25 Improve the error message if alias type inference fails (#2399)
* Improve the error message if alias type inference fails

* Improve error further
2020-08-25 06:38:24 +12:00
d859bff877 Sort subcommands in the help text (#2396) 2020-08-24 08:35:16 +12:00
de5cd4ec23 Fix Dockerfile (cache clear of apt) (#2394) 2020-08-24 05:24:49 +12:00
48850becd8 Add some minor fixes (#2391) 2020-08-22 17:06:19 +12:00
2ea42f296c Date subcommands (#2383)
* refactored date command

* format files
2020-08-22 15:46:48 +12:00
eb2ba470c7 Have lite-parse complete return a complete bare form. (#2389)
Previously, lite parse would stack up opening delimiters in vec, and if we
didn't close everything off, it would simply return an error with a partial form
that didn't include the missing closing delimiters. This commits adds those
delimiters so that `classify_block` can parse correctly.
2020-08-22 15:43:40 +12:00
a951edd0d5 Fix the version command to include the commit hash (#2390)
* Fix the version command to include the commit hash when it is _not_ empty

* Format code
2020-08-22 15:21:59 +12:00
d65a38dd41 ls .file and ls **/.* show hidden files (#2379) 2020-08-21 21:49:34 -04:00
8f568f4fc5 Support completions for a single hyphen argument. (#2388)
The parser sees this as a positional argument, but when requesting completions
this could be either a filename that starts with a hyphen, or it could be a
flag. This expands the completion engine's interface to return a vec of possible
completion locations instead of an optional one, because we want to show all
possibilities instead of assuming one or the either.
2020-08-21 21:26:47 -04:00
ee26590011 touch: support multiple arguments (#2386)
Fixes #2384
2020-08-22 12:08:30 +12:00
11352f87f0 Remove rustyline context from nu's completion context (#2387) 2020-08-21 18:21:14 -04:00
9f85b10fcb Add method to convert ClassifiedBlock into completion locations. (#2316)
The completion engine maps completion locations to spans on a line, which
indicate whther to complete a command name, flag name, argument, and so on.

Initial implementation is simplistic, with some rough edges, since it relies
heavily on the parser's interpretation. For example

    du -

if asking for completions, `-` is considered a positional argument by the
parser, but the user is likely looking for a flag. These scenarios will be
addressed in a series of progressive enhancements to the engine.
2020-08-21 15:37:51 -04:00
0dd1403a69 Sleep command (#2381)
* Add deserialization of Primitive::Duration; Fixes #2373

* Implement Sleep command

* Add comment saying you should name your rest field "rest"

* Fix typo

* Add documentation for sleep command
2020-08-22 05:51:29 +12:00
cb4527fc0d Add text_color and line_color for table theming (#2378)
* created text_color and line_color functions with hopes of theming soon.

* added text_color and line_color to hastableproperties

* Refactor Tractor.

* more refactoring
2020-08-20 11:03:56 -05:00
ad395944ef SyntaxShape checking in Alias (#2377)
* initial, working for shallow internals

* add recurion on Block

* clean up/abstract, Invocations

* use Result

* inspection of Binary, tests

* improve code structure

* move arg shape inspection to Alias command

* add spanned errors, tests, cleanup for PR

* fix test, clippy
2020-08-20 15:18:55 +12:00
6126209f57 Fix column count to not break on empty tables (#2374) 2020-08-18 22:16:35 -04:00
43e061f8c6 Add wasm sample for CI (#2372)
* Add wasm sample for CI

* Add wasm sample for CI

* Add wasm sample for CI
2020-08-19 07:34:05 +12:00
738541f727 Move nu-data out of nu-cli (#2369)
* WIP for moving nu-data out

* Refactor nu-data out of nu-cli

* Remove unwraps

* Remove unwraps
2020-08-18 19:00:02 +12:00
1d5518a214 Err message for sort-by invalid types now shows the mismatched types (#2366)
* 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
2020-08-18 17:37:45 +12:00
57101d5022 Run exitscripts in original dir (#2352)
* Modify testcase

* Run exitscript in the folder it was specified

* Update documentation

* Add comment

* Borrow instead of clone

* Does this just... work on windows?

* fmt

* as_str

* Collapse if by order of clippy

* Support windows

* fmt

* refactor tests

* fmt

* This time it will work on windows FOR SURE

* Remove debug prints

* Comment

* Refactor tests

* fmt

* fix spelling

* update comment
2020-08-18 17:36:09 +12:00
f6ff6ab6e4 added various case conversion commands for str. Added the inflection … (#2363)
* added various case conversion commands for str. Added the inflection crate as a dependency

* lighten the restriction on the inflector dependency

* publishing the case commands

* fix typo

* fix kebab case test

* formatting
2020-08-18 08:18:23 +12:00
a224cd38ab Solving the issue "sort-by should fail gracefully if mismatched types are compared" (#2360) 2020-08-17 14:57:29 -04:00
e292bb46bb added the other table "themes" so that they could be used via config (#2365) 2020-08-17 11:20:00 -05:00
05aca1c157 Config refactoring baseline.
The initial configuration refactoring already gave significant benefits
with my use case. On another note, Commands should know and ask for
configuration variables. We could, as we refactor, add the ability
for commands to tell what configuration variables knows about and
their types.

This way, completers can be used too when using `config` command if we
also add a sub command that config could set variables to.

Commands stating the config variables they know about will allow us
to implement it in `help` and display them.
2020-08-17 04:49:33 -05:00
8d269f62dd Pass metadata in 'to json' (#2359) 2020-08-16 15:47:00 +12:00
b1a946f0dc Add test file for count command (#2358)
* Add test file for count command

* rustfmt + Clippy
2020-08-16 15:16:49 +12:00
c59f860b48 Renamed time units (#2356)
* Changed time units as outlined in issue #2353.
Also applied changes to to_str for Unit - not sure if that was what was wanted.

* Forgot the tests!

* Updated primitive.rs to match changes.

* Updated where example to match changes.

* And the html test!
2020-08-16 07:03:28 +12:00
c6588c661a Add column flag to count command 2020-08-15 07:52:59 -05:00
8fe269a3d8 Add random_numbers.csv to repo, so it is easier to update histogram examples 2020-08-15 07:51:12 -05:00
8f00713ad2 Update histogram examples 2020-08-15 07:51:12 -05:00
d1d98a897a Fix "occurrences" typo in histrogram test file 2020-08-15 07:51:12 -05:00
3dc95ef765 Fix typo in "occurrences" 2020-08-15 07:51:12 -05:00
84da815b22 Update README.md 2020-08-14 16:46:25 +12:00
371a951668 Split extra (#2348)
* Split default/extra plugins

* Oops, too many deletes

* Pipelines
2020-08-14 16:45:27 +12:00
baf84f05d9 removed syntect dependency, limited bat to regex-fancy and paging (#2347) 2020-08-13 15:33:35 -05:00
da4d24d082 Bump to 0.18.2. Move starship external. (#2345)
* Bump to 0.18.2. Move starship external.

* Fix failing test
2020-08-14 07:02:45 +12:00
22519c9083 Only have commit hash in version if git doesn't error (#2336)
* Add commit to version command

* Replace unwrap with expect.

* Only have commit hash if git doesn't error

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-08-13 06:51:12 +12:00
1601aa2f49 Remove with-symlink-targets flag from ls (#2334)
* Remove `with-symlink-targets` flag from `ls`

* Fix test to use `ls -l` to output all the columns of `ls`

* Fix error message

* Delete test that originally covered `ls -w`
2020-08-13 05:21:19 +12:00
88555860f3 Fetch content from S3 (#2328)
* fetch content from s3 resource

* remove submodule

* fix clippy

* update Cargo.lock

* fix s3 plugin dependency version
2020-08-13 05:20:22 +12:00
015d2ee050 Lint [result|option]_unwrap_used as been renamed to clippy::unwrap_used 2020-08-12 09:34:16 -05:00
dd7ee1808a histogram: rename 'count' column to 'ocurrences' 2020-08-12 09:34:16 -05:00
0db4180cea histogram: optionally use a valuator for histogram value. 2020-08-12 09:34:16 -05:00
8ff15c46c1 histogram gives back percentage column. (#2340) 2020-08-12 04:21:28 -05:00
48cfc9b598 apply all the block run's stream. (#2339) 2020-08-12 02:51:24 -05:00
87d71604ad Bump to 0.18.1 (#2335) 2020-08-12 15:59:28 +12:00
e372e7c448 Display built features. Long/Short commit hashes display removed due to cargo publishing. (#2333)
Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-08-12 15:13:33 +12:00
0194dee3a6 Updated version hashing and bumped nu-cli to 0.18.1 (#2331)
* Updated version hashing and bumped nu-cli to 0.18.1

* made code pertyer

* Update version.rs

* Update version.rs

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-08-12 09:43:23 +12:00
cc3c10867c Histogram no longer requires a wrap command before it on unnamed columns (#2332) 2020-08-12 09:42:59 +12:00
3c18169f63 Autoenv fix: Exitscripts incorrectly running when visiting a subdirectory (#2326)
* Add test case for issue

* Preliminary fix

* fmt

* Reorder asserts

* move insertion

* Touch nu-env.toml

* Cleanup

* touch nu-env toml

* Remove touch

* Change feature flags
2020-08-12 07:54:49 +12:00
43e9c89125 moved theme assets local to crate (#2329)
* moved theme assets local to crate

* remove the TODO comment
2020-08-11 13:57:03 -05:00
2ad07912d9 Bump to 0.18 (#2325) 2020-08-11 18:44:53 +12:00
51ad019495 Update Cargo.lock (#2322) 2020-08-11 15:00:07 +12:00
9264325e57 Make history file location configurable (#2320)
* Make history location configurable

Add history-path to your config if you want an alternate history file
location

* use IndexMap.get() instead of index

Co-authored-by: Amanita Muscaria <nope>
2020-08-11 13:58:53 +12:00
901157341b Bumped which-rs to 4.0.2 (#2321)
This should fix #1541
2020-08-11 13:55:43 +12:00
eb766b80c1 added pkg_mgr/winget + yaml (#2297) 2020-08-11 05:45:53 +12:00
f0dbffd761 Add 228 json html themes for to html (#2308)
* add 228 json html themes
removed old assets, added new zipped asset
added --list to get a list of the theme names
reworked some older theme code
added rust-embed and zip crate
removed the dark tests

* fmt

* Updated, removed excess comments
Changed usage a bit
Updated the error handling
Added some helper items in --list
2020-08-11 05:43:16 +12:00
f14c0df582 Allow disabling welcome message on launch (#2314)
* Implements #2313
2020-08-09 11:38:21 +12:00
362bb1bea3 Add commit hash to version command (#2312)
* Add commit to version command

* Replace unwrap with expect.
2020-08-08 17:39:34 +12:00
724b177c97 Sample variance and Sample standard deviation. (#2310) 2020-08-06 23:56:19 -05:00
50343f2d6a Add stderr back when using do -i (#2309)
* Add stderr back when using do -i

* Add stderr back when using do -i
2020-08-07 16:53:37 +12:00
3122525b96 removed rustyline config duplication (#2306)
* removed rustyline config duplication
set other rustyline defaults if line_editor section doesn't exist
updated keyseq_timeout to -1 if emacs mode is chosen

* change checking rustyline config to if lets

* removed some unneccessary code
2020-08-05 16:34:28 -05:00
8232c6f185 Update rustyline defaults (#2305)
Use rustyline defaults if no config exists for line_editor (for most options)
2020-08-05 13:05:13 -05:00
6202705eb6 parse most common date formats using dtparse crate (#2303)
* use dtparse crate to parse most common date formats

* use dtparse crate to parse most common date formats - cargo fmt
2020-08-05 12:44:52 +12:00
e1c5940b04 Add command "reduce" (#2292)
* initial

* fold working

* tests and cleanup

* change command to reduce, with fold flag

* move complex example to tests

* add --numbered flag
2020-08-05 05:16:19 +12:00
7f35bfc005 histogram: support regular values. (#2300) 2020-08-04 04:57:25 -05:00
c48c092125 String funcs - Contains and IndexOf (#2298)
* Contains and index of string functions

* Clippy and fmt
2020-08-04 18:36:51 +12:00
028fc9b9cd Data summarize reporting overhaul. (#2299)
Refactored out most of internal work for summarizing data opening
the door for generating charts from it. A model is introduced
to hold information needed for a summary, Histogram command is
an example of a partial usage. This is the beginning.

Removed implicit arithmetic traits on Value and Primitive to avoid
mixed types panics. The std operations traits can't fail and we
can't guarantee that. We can handle gracefully now since compute_values
was introduced after the parser changes four months ago. The handling
logic should be taken care of either explicitly or in compute_values.

The zero identity trait was also removed (and implementing this forced
us to also implement Add, Mult, etc)

Also: the `math` operations now remove in the output if a given column is not computable:

```
> ls | math sum
──────┬──────────
 size │ 150.9 KB
──────┴──────────
```
2020-08-03 17:47:19 -05:00
eeb9b4edcb Match cleanup (#2294)
* Delete unnecessary match

* Use `unwrap_or_else()`

* Whitespace was trim on file save

* Use `map_or_else()`

* Use a default to group all match arms with same output

* Clippy made me do it
2020-08-04 05:43:27 +12:00
3a7869b422 Switch to maintained app_dirs (#2293)
* Switch to maintained app_dirs

* Update app_dirs under old name
2020-08-04 05:41:57 +12:00
c48ea46c4f Match cleanup (#2290) 2020-08-02 18:34:33 -04:00
f33da33626 Add --partial to 'to html' (#2291) 2020-08-03 08:47:54 +12:00
a88f5c7ae7 Make str collect take an optional separator value (#2289)
* Make `str collect` take an optional separator value

* Make `str collect` take an optional separator value

* Add some tests

* Fix my tests
2020-08-02 19:29:29 +12:00
cda53b6cda Return incomplete parse from lite_parse (#2284)
* Move lite_parse tests into a submodule

* Have lite_parse return partial parses when error encountered.

Although a parse fails, we can generally still return what was successfully
parsed. This is useful, for example, when figuring out completions at some
cursor position, because we can map the cursor to something more structured
(e.g., cursor is at a flag name).
2020-08-02 06:39:55 +12:00
ee734873ba Fix no longer working histogram example (#2271)
* Fix no longer working histogram example

* Oops
2020-08-02 06:38:45 +12:00
9fb6f5cd09 Change f/full flag to l/long for ls and ps commands (#2283)
* Change `f`/`full` flag to `l`/`long` for `ls` and `ps` commands

* Fix a few more `--full` instances
2020-08-02 06:30:45 +12:00
4ef15b5f80 docs/alias: simplify the 'persistent' section, using --save (#2285)
All the workarounds using `config` aren't necessary anymore. Only `config path` is still of interest.
2020-08-01 08:11:26 -04:00
ba81278ffd Remove build.rs and nu-build (#2282) 2020-08-01 09:21:10 +12:00
10fbed3808 updated cmd builtin commands (#2266)
* updated cmd builtin commands

* removed cd, chdir, exit, prompt, rem

* remove more commands, what remains is useful

* cargo fmt is so finicky
2020-07-31 09:51:42 +12:00
16cfc36aec set default edit_mode to emacs instead of vi (#2278) 2020-07-30 14:59:20 -05:00
aca7f71737 🐛 Fix path command error messages (#2261). (#2276) 2020-07-31 06:51:14 +12:00
3282a509a9 Make insert take in a block (#2265)
* Make insert take in a block

* Add some tests
2020-07-30 16:58:54 +12:00
878b748a41 Add list output for to html (#2273) 2020-07-30 16:54:55 +12:00
18a4505b9b starts_with ends_with match functions for string (#2269) 2020-07-30 16:51:20 +12:00
26e77a4b05 Add url commands (#2274)
* scheme
* path
* query
* host
2020-07-30 08:56:56 +12:00
37f10cf273 Add two further path cmds - type and exists (#2264)
* Add two further path cmds - type and exists

* Update type.rs

Try a more universal directory

* Update type.rs

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-07-27 14:12:07 +12:00
5e0a9aecaa ltrim and rtrim for string (#2262)
* Trim string from left and right

* Move trim to folder

* fmt

* Clippy
2020-07-27 06:09:35 +12:00
7e2c627044 WIP: Path utility commands (#2255)
* Add new path commands

basename, expand and extension. Currently there is no real error
handling. expand returns the initial path if it didn't work, the others
return empty string

* Optionally apply to path
2020-07-26 07:29:15 +12:00
4347339e9a Make all bullet point items uppercase (#2257) 2020-07-26 06:15:12 +12:00
e66a8258ec add example to parse command, with row output (#2256) 2020-07-26 06:14:29 +12:00
e4b42b54ad Simplify NuCompleter. (#2254)
- Removing old code for dealing with escaping, since that has moved elsewhere.
- Eliminating some match statements in favour of result/option methods.
- Fix an issue where completing inside quotes could remove the quote at the
  beginning, if one already existed on the line but the replacement didn't have
  a quote at the beginning.
2020-07-25 10:41:14 -04:00
de18b9ca2c Match cleanup (#2248) 2020-07-25 08:40:35 -04:00
a77f0f7b41 to-xml.md documentation update (#2253)
* Update to-xml.md documentation to be consistent

* Capitalize bullet point items

* Add link to this document wthin `to.md`
2020-07-25 20:19:15 +12:00
6b31a006b8 Refactor all completion logic into NuCompleter (#2252)
* Refactor all completion logic into `NuCompleter`

This is the next step to improving completions. Previously, completion logic was
scattered about (`FilesystemShell`, `NuCompleter`, `Helper`, and `ShellManager`).
By unifying the core logic into a central location, it will be easier to take the
next steps in improving completion.

* Update context.rs

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-07-25 11:39:12 +12:00
2db4fe83d8 Remove unnecessary peekable iterator (#2251) 2020-07-24 12:06:12 -04:00
55a2f284d9 Add to xml command (#2141) (#2155) 2020-07-24 19:41:22 +12:00
2d3b1e090a Remove piping of stderr. (#2247)
In any other shell, stderr is inherited like normal, and only piped if you
request it explicitly (e.g., `2>/dev/null`). In the case of a command like
`fzf`, stderr is used for the interactive selection of files. By piping it,
something like

    fzf | xargs echo

does not work. By removing all stderr piping we eliminate this issue. We can
return later with a way to deal with stderr piping when an actual use case
arises.
2020-07-24 17:56:50 +12:00
ed0c1038e3 Step 1 for to html theme-ing (#2245)
* reworked theming step 1.
added theme parameter.
hard coded 4 themes

* forgot about blulocolight

* aarrrrg! test are in another place. fixed i think.
2020-07-23 13:21:58 -05:00
0c20282200 added documentation of available binding options (#2246)
straight from the rustyline source code
2020-07-23 13:13:06 -05:00
e71f44d26f if config file doesn't exist, set defaults. (#2244)
if line_editor in config doesn't exist, set defaults.
2020-07-23 08:27:45 -05:00
e3d7e46855 added defaults. fixed but of not loading history. (#2243) 2020-07-23 07:19:05 -05:00
9b35aae5e8 update sample configs (#2242)
* update sample configs

* change rustyline to line_editor
2020-07-23 06:49:25 -05:00
7e9f87c57f Expose all rustyline configuration points (#2238)
* Added all rustyline config points

* comments cleanup

* my good friend fmt keeps changing his mind
2020-07-23 09:43:52 +12:00
5d17b72852 update config documentation (#2178)
* update config documentation

* update config syntax

* update config syntax

* Update alias.md

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-07-23 09:42:04 +12:00
6b4634b293 Convert table of primitives to positional arguments for external cmd (#2232)
* Convert table of primitives to positional arguments for external cmd

* Multiple file test, fix for cococo
2020-07-23 09:41:34 +12:00
340 changed files with 14842 additions and 5842 deletions

View File

@ -9,6 +9,12 @@ strategy:
linux-minimal:
image: ubuntu-18.04
style: 'minimal'
linux-extra:
image: ubuntu-18.04
style: 'extra'
linux-wasm:
image: ubuntu-18.04
style: 'wasm'
macos-stable:
image: macos-10.14
style: 'unflagged'
@ -37,6 +43,7 @@ steps:
if [ -e /etc/debian_version ]
then
sudo apt-get -y install libxcb-composite0-dev libx11-dev
sudo npm install -g wasm-pack
fi
if [ "$(uname)" == "Darwin" ]; then
curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain "stable"
@ -49,21 +56,27 @@ steps:
# echo "##vso[task.prependpath]$HOME/.cargo/bin"
# rustup component add rustfmt
displayName: Install Rust
- bash: RUSTFLAGS="-D warnings" cargo test --all --features stable
- bash: RUSTFLAGS="-D warnings" cargo test --all
condition: eq(variables['style'], 'unflagged')
displayName: Run tests
- bash: RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
condition: eq(variables['style'], 'unflagged')
displayName: Check clippy lints
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo test --all --features stable
- bash: RUSTFLAGS="-D warnings" cargo test --all
condition: eq(variables['style'], 'canary')
displayName: Run tests
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
- bash: cd samples/wasm && wasm-pack build
condition: eq(variables['style'], 'wasm')
displayName: Wasm build
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
condition: eq(variables['style'], 'canary')
displayName: Check clippy lints
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features
condition: eq(variables['style'], 'minimal')
displayName: Run tests
- bash: RUSTFLAGS="-D warnings" cargo test --all --features=extra
condition: eq(variables['style'], 'extra')
displayName: Run tests
- bash: cargo fmt --all -- --check
condition: eq(variables['style'], 'fmt')
displayName: Lint

View File

@ -1 +0,0 @@
[build]

View File

@ -73,7 +73,7 @@ workflows:
branches:
ignore: /.*/
tags:
only: /^v.*/
only: /^\d+\.\d+\.\d+$/
before_build:
- run: docker pull quay.io/nushell/nu:latest
- run: docker pull quay.io/nushell/nu-base:latest

View File

@ -26,7 +26,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --release --all --features=stable
args: --release --all --features=extra
- name: Create output directory
run: mkdir output
@ -38,7 +38,7 @@ jobs:
cp LICENSE output/LICENSE
rm output/*.d
rm output/nu_plugin_core_*
rm output/nu_plugin_stable_*
rm output/nu_plugin_extra_*
# Note: If OpenSSL changes, this path will need to be updated
- name: Copy OpenSSL to output
@ -68,7 +68,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --release --all --features=stable
args: --release --all --features=extra
- name: Create output directory
run: mkdir output
@ -80,7 +80,7 @@ jobs:
cp LICENSE output/LICENSE
rm output/*.d
rm output/nu_plugin_core_*
rm output/nu_plugin_stable_*
rm output/nu_plugin_extra_*
- name: Upload artifact
uses: actions/upload-artifact@v2
@ -112,7 +112,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --release --all --features=stable
args: --release --all --features=extra
- name: Create output directory
run: mkdir output
@ -129,7 +129,7 @@ jobs:
cp LICENSE output\
cp target\release\LICENSE-for-less.txt output\
rm target\release\nu_plugin_core_*.exe
rm target\release\nu_plugin_stable_*.exe
rm target\release\nu_plugin_extra_*.exe
cp target\release\nu_plugin_*.exe output\
cp README.build.txt output\README.txt
cp target\release\less.exe output\

View File

@ -25,38 +25,38 @@ cargo build
### Useful Commands
Build and run Nushell:
- Build and run Nushell:
```shell
cargo build --release && cargo run --release
```
```shell
cargo build --release && cargo run --release
```
Run Clippy on Nushell:
- Run Clippy on Nushell:
```shell
cargo clippy --all --features=stable
```
```shell
cargo clippy --all --features=stable
```
Run all tests:
- Run all tests:
```shell
cargo test --all --features=stable
```
```shell
cargo test --all --features=stable
```
Run all tests for a specific command
- Run all tests for a specific command
```shell
cargo test --package nu-cli --test main -- commands::<command_name_here>
```
```shell
cargo test --package nu-cli --test main -- commands::<command_name_here>
```
Check to see if there are code formatting issues
- Check to see if there are code formatting issues
```shell
cargo fmt --all -- --check
```
```shell
cargo fmt --all -- --check
```
Format the code in the project
- Format the code in the project
```shell
cargo fmt --all
```
```shell
cargo fmt --all
```

2513
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.17.0"
version = "0.19.0"
[workspace]
members = ["crates/*/"]
@ -18,31 +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.17.0", path = "./crates/nu-cli"}
nu-errors = {version = "0.17.0", path = "./crates/nu-errors"}
nu-parser = {version = "0.17.0", path = "./crates/nu-parser"}
nu-plugin = {version = "0.17.0", path = "./crates/nu-plugin"}
nu-protocol = {version = "0.17.0", path = "./crates/nu-protocol"}
nu-source = {version = "0.17.0", path = "./crates/nu-source"}
nu-value-ext = {version = "0.17.0", path = "./crates/nu-value-ext"}
nu_plugin_binaryview = {version = "0.17.0", path = "./crates/nu_plugin_binaryview", optional = true}
nu_plugin_fetch = {version = "0.17.0", path = "./crates/nu_plugin_fetch", optional = true}
nu_plugin_from_bson = {version = "0.17.0", path = "./crates/nu_plugin_from_bson", optional = true}
nu_plugin_from_sqlite = {version = "0.17.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
nu_plugin_inc = {version = "0.17.0", path = "./crates/nu_plugin_inc", optional = true}
nu_plugin_match = {version = "0.17.0", path = "./crates/nu_plugin_match", optional = true}
nu_plugin_post = {version = "0.17.0", path = "./crates/nu_plugin_post", optional = true}
nu_plugin_ps = {version = "0.17.0", path = "./crates/nu_plugin_ps", optional = true}
nu_plugin_start = {version = "0.17.0", path = "./crates/nu_plugin_start", optional = true}
nu_plugin_sys = {version = "0.17.0", path = "./crates/nu_plugin_sys", optional = true}
nu_plugin_textview = {version = "0.17.0", path = "./crates/nu_plugin_textview", optional = true}
nu_plugin_to_bson = {version = "0.17.0", path = "./crates/nu_plugin_to_bson", optional = true}
nu_plugin_to_sqlite = {version = "0.17.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
nu_plugin_tree = {version = "0.17.0", path = "./crates/nu_plugin_tree", optional = true}
nu-cli = {version = "0.19.0", path = "./crates/nu-cli"}
nu-data = {version = "0.19.0", path = "./crates/nu-data"}
nu-errors = {version = "0.19.0", path = "./crates/nu-errors"}
nu-parser = {version = "0.19.0", path = "./crates/nu-parser"}
nu-plugin = {version = "0.19.0", path = "./crates/nu-plugin"}
nu-protocol = {version = "0.19.0", path = "./crates/nu-protocol"}
nu-source = {version = "0.19.0", path = "./crates/nu-source"}
nu-value-ext = {version = "0.19.0", path = "./crates/nu-value-ext"}
nu_plugin_binaryview = {version = "0.19.0", path = "./crates/nu_plugin_binaryview", optional = true}
nu_plugin_fetch = {version = "0.19.0", path = "./crates/nu_plugin_fetch", optional = true}
nu_plugin_from_bson = {version = "0.19.0", path = "./crates/nu_plugin_from_bson", optional = true}
nu_plugin_from_sqlite = {version = "0.19.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
nu_plugin_inc = {version = "0.19.0", path = "./crates/nu_plugin_inc", optional = true}
nu_plugin_match = {version = "0.19.0", path = "./crates/nu_plugin_match", optional = true}
nu_plugin_post = {version = "0.19.0", path = "./crates/nu_plugin_post", optional = true}
nu_plugin_ps = {version = "0.19.0", path = "./crates/nu_plugin_ps", optional = true}
nu_plugin_s3 = {version = "0.19.0", path = "./crates/nu_plugin_s3", optional = true}
nu_plugin_start = {version = "0.19.0", path = "./crates/nu_plugin_start", optional = true}
nu_plugin_sys = {version = "0.19.0", path = "./crates/nu_plugin_sys", optional = true}
nu_plugin_textview = {version = "0.19.0", path = "./crates/nu_plugin_textview", optional = true}
nu_plugin_to_bson = {version = "0.19.0", path = "./crates/nu_plugin_to_bson", optional = true}
nu_plugin_to_sqlite = {version = "0.19.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
nu_plugin_tree = {version = "0.19.0", path = "./crates/nu_plugin_tree", optional = true}
crossterm = {version = "0.17.5", optional = true}
semver = {version = "0.10.0", optional = true}
syntect = {version = "4.2", default-features = false, features = ["default-fancy"], optional = true}
url = {version = "2.1.1", optional = true}
clap = "2.33.1"
@ -51,13 +53,12 @@ dunce = "1.0.1"
futures = {version = "0.3", features = ["compat", "io-compat"]}
log = "0.4.8"
pretty_env_logger = "0.4.0"
starship = "0.43.0"
quick-xml = "0.18.1"
[dev-dependencies]
nu-test-support = {version = "0.17.0", path = "./crates/nu-test-support"}
nu-test-support = {version = "0.19.0", path = "./crates/nu-test-support"}
[build-dependencies]
nu-build = {version = "0.17.0", path = "./crates/nu-build"}
serde = {version = "1.0.110", features = ["derive"]}
toml = "0.5.6"
@ -74,14 +75,18 @@ default = [
"ptree-support",
"term-support",
"uuid-support",
"match",
"post",
"fetch",
]
stable = ["default", "binaryview", "match", "tree", "post", "fetch", "clipboard-cli", "trash-support", "start", "starship-prompt", "bson", "sqlite"]
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3"]
stable = ["default"]
# Default
inc = ["semver", "nu_plugin_inc"]
ps = ["nu_plugin_ps"]
sys = ["nu_plugin_sys"]
textview = ["crossterm", "syntect", "url", "nu_plugin_textview"]
textview = ["crossterm", "url", "nu_plugin_textview"]
# Stable
binaryview = ["nu_plugin_binaryview"]
@ -89,6 +94,7 @@ bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
fetch = ["nu_plugin_fetch"]
match = ["nu_plugin_match"]
post = ["nu_plugin_post"]
s3 = ["nu_plugin_s3"]
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
start = ["nu_plugin_start"]
trace = ["nu-parser/trace"]
@ -96,10 +102,9 @@ tree = ["nu_plugin_tree"]
clipboard-cli = ["nu-cli/clipboard-cli"]
ctrlc-support = ["nu-cli/ctrlc"]
directories-support = ["nu-cli/directories", "nu-cli/dirs"]
directories-support = ["nu-cli/directories", "nu-cli/dirs", "nu-data/directories", "nu-data/dirs"]
git-support = ["nu-cli/git2"]
ptree-support = ["nu-cli/ptree"]
starship-prompt = ["nu-cli/starship-prompt"]
term-support = ["nu-cli/term"]
trash-support = ["nu-cli/trash-support"]
uuid-support = ["nu-cli/uuid_crate"]
@ -128,37 +133,43 @@ name = "nu_plugin_core_sys"
path = "src/plugins/nu_plugin_core_sys.rs"
required-features = ["sys"]
# Stable plugins
[[bin]]
name = "nu_plugin_stable_fetch"
path = "src/plugins/nu_plugin_stable_fetch.rs"
name = "nu_plugin_core_fetch"
path = "src/plugins/nu_plugin_core_fetch.rs"
required-features = ["fetch"]
[[bin]]
name = "nu_plugin_stable_binaryview"
path = "src/plugins/nu_plugin_stable_binaryview.rs"
required-features = ["binaryview"]
[[bin]]
name = "nu_plugin_stable_match"
path = "src/plugins/nu_plugin_stable_match.rs"
name = "nu_plugin_core_match"
path = "src/plugins/nu_plugin_core_match.rs"
required-features = ["match"]
[[bin]]
name = "nu_plugin_stable_post"
path = "src/plugins/nu_plugin_stable_post.rs"
name = "nu_plugin_core_post"
path = "src/plugins/nu_plugin_core_post.rs"
required-features = ["post"]
# Extra plugins
[[bin]]
name = "nu_plugin_stable_tree"
path = "src/plugins/nu_plugin_stable_tree.rs"
name = "nu_plugin_extra_binaryview"
path = "src/plugins/nu_plugin_extra_binaryview.rs"
required-features = ["binaryview"]
[[bin]]
name = "nu_plugin_extra_tree"
path = "src/plugins/nu_plugin_extra_tree.rs"
required-features = ["tree"]
[[bin]]
name = "nu_plugin_stable_start"
path = "src/plugins/nu_plugin_stable_start.rs"
name = "nu_plugin_extra_start"
path = "src/plugins/nu_plugin_extra_start.rs"
required-features = ["start"]
[[bin]]
name = "nu_plugin_extra_s3"
path = "src/plugins/nu_plugin_extra_s3.rs"
required-features = ["s3"]
# Main nu binary
[[bin]]
name = "nu"

View File

@ -51,12 +51,12 @@ To build Nu, you will need to use the **latest stable (1.41 or later)** version
Required dependencies:
* pkg-config and libssl (only needed on Linux)
* on Debian/Ubuntu: `apt install pkg-config libssl-dev`
* On Debian/Ubuntu: `apt install pkg-config libssl-dev`
Optional dependencies:
* To use Nu with all possible optional features enabled, you'll also need the following:
* on Linux (on Debian/Ubuntu): `apt install libxcb-composite0-dev libx11-dev`
* On Linux (on Debian/Ubuntu): `apt install libxcb-composite0-dev libx11-dev`
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`):
@ -67,7 +67,7 @@ cargo install nu
You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/en/installation.html#dependencies) for your platform), once you have checked out this repo with git:
```bash
cargo build --workspace --features=stable
cargo build --workspace --features=extra
```
### Docker
@ -194,7 +194,7 @@ For example, you can load a .toml file as structured data and explore it:
> open Cargo.toml
────────────────────┬───────────────────────────
bin │ [table 18 rows]
build-dependencies │ [row nu-build serde toml]
build-dependencies │ [row serde toml]
dependencies │ [row 29 columns]
dev-dependencies │ [row nu-test-support]
features │ [row 19 columns]
@ -236,11 +236,11 @@ Here we use the variable `$it` to refer to the value being piped to the external
Nu has early support for configuring the shell. You can refer to the book for a list of [all supported variables](https://www.nushell.sh/book/en/configuration.html).
To set one of these variables, you can use `config --set`. For example:
To set one of these variables, you can use `config set`. For example:
```shell
> config --set [edit_mode "vi"]
> config --set [path $nu.path]
> config set edit_mode "vi"
> config set path $nu.path
```
### Shells

Binary file not shown.

Binary file not shown.

View File

@ -1,3 +0,0 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
nu_build::build()
}

View File

@ -1,16 +0,0 @@
[package]
authors = ["The Nu Project Contributors"]
description = "Core build system for nushell"
edition = "2018"
license = "MIT"
name = "nu-build"
version = "0.17.0"
[lib]
doctest = false
[dependencies]
lazy_static = "1.4.0"
serde = {version = "1.0.114", features = ["derive"]}
serde_json = "1.0.55"
toml = "0.5.6"

View File

@ -1,80 +0,0 @@
use lazy_static::lazy_static;
use serde::Deserialize;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::env;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
lazy_static! {
static ref WORKSPACES: Mutex<BTreeMap<String, &'static Path>> = Mutex::new(BTreeMap::new());
}
// got from https://github.com/mitsuhiko/insta/blob/b113499249584cb650150d2d01ed96ee66db6b30/src/runtime.rs#L67-L88
fn get_cargo_workspace(manifest_dir: &str) -> Result<Option<&Path>, Box<dyn std::error::Error>> {
let mut workspaces = WORKSPACES.lock()?;
if let Some(rv) = workspaces.get(manifest_dir) {
Ok(Some(rv))
} else {
#[derive(Deserialize)]
struct Manifest {
workspace_root: String,
}
let output = std::process::Command::new(env!("CARGO"))
.arg("metadata")
.arg("--format-version=1")
.current_dir(manifest_dir)
.output()?;
let manifest: Manifest = serde_json::from_slice(&output.stdout)?;
let path = Box::leak(Box::new(PathBuf::from(manifest.workspace_root)));
workspaces.insert(manifest_dir.to_string(), path.as_path());
Ok(workspaces.get(manifest_dir).cloned())
}
}
#[derive(Deserialize)]
struct Feature {
#[allow(unused)]
description: String,
enabled: bool,
}
pub fn build() -> Result<(), Box<dyn std::error::Error>> {
let input = env::var("CARGO_MANIFEST_DIR")?;
let all_on = env::var("NUSHELL_ENABLE_ALL_FLAGS").is_ok();
let flags: HashSet<String> = env::var("NUSHELL_ENABLE_FLAGS")
.map(|s| s.split(',').map(|s| s.to_string()).collect())
.unwrap_or_else(|_| HashSet::new());
if all_on && !flags.is_empty() {
println!(
"cargo:warning=Both NUSHELL_ENABLE_ALL_FLAGS and NUSHELL_ENABLE_FLAGS were set. You don't need both."
);
}
let workspace = match get_cargo_workspace(&input)? {
// If the crate is being downloaded from crates.io, it won't have a workspace root, and that's ok
None => return Ok(()),
Some(workspace) => workspace,
};
let path = Path::new(&workspace).join("features.toml");
// If the crate is being downloaded from crates.io, it won't have a features.toml, and that's ok
if !path.exists() {
return Ok(());
}
let toml: HashMap<String, Feature> = toml::from_str(&std::fs::read_to_string(path)?)?;
for (key, value) in toml.iter() {
if value.enabled || all_on || flags.contains(key) {
println!("cargo:rustc-cfg={}", key);
}
}
Ok(())
}

View File

@ -4,23 +4,24 @@ description = "CLI for nushell"
edition = "2018"
license = "MIT"
name = "nu-cli"
version = "0.17.0"
version = "0.19.0"
[lib]
doctest = false
[dependencies]
nu-errors = {version = "0.17.0", path = "../nu-errors"}
nu-parser = {version = "0.17.0", path = "../nu-parser"}
nu-plugin = {version = "0.17.0", path = "../nu-plugin"}
nu-protocol = {version = "0.17.0", path = "../nu-protocol"}
nu-source = {version = "0.17.0", path = "../nu-source"}
nu-table = {version = "0.17.0", path = "../nu-table"}
nu-test-support = {version = "0.17.0", path = "../nu-test-support"}
nu-value-ext = {version = "0.17.0", path = "../nu-value-ext"}
nu-data = {version = "0.19.0", path = "../nu-data"}
nu-errors = {version = "0.19.0", path = "../nu-errors"}
nu-parser = {version = "0.19.0", path = "../nu-parser"}
nu-plugin = {version = "0.19.0", path = "../nu-plugin"}
nu-protocol = {version = "0.19.0", path = "../nu-protocol"}
nu-source = {version = "0.19.0", path = "../nu-source"}
nu-table = {version = "0.19.0", path = "../nu-table"}
nu-test-support = {version = "0.19.0", path = "../nu-test-support"}
nu-value-ext = {version = "0.19.0", path = "../nu-value-ext"}
ansi_term = "0.12.1"
app_dirs = "1.2.1"
app_dirs = {version = "2", package = "app_dirs2"}
async-recursion = "0.3.1"
async-trait = "0.1.36"
base64 = "0.12.3"
@ -36,6 +37,7 @@ ctrlc = {version = "3.1.4", optional = true}
derive-new = "0.5.8"
directories = {version = "2.0.2", optional = true}
dirs = {version = "2.0.2", optional = true}
dtparse = "1.1.0"
dunce = "1.0.1"
eml-parser = "0.1.0"
filesize = "0.2.0"
@ -66,6 +68,7 @@ query_interface = "0.3.5"
rand = "0.7"
regex = "1"
roxmltree = "0.13.0"
rust-embed = "5.6.0"
rustyline = "6.2.0"
serde = {version = "1.0.114", features = ["derive"]}
serde-hjson = "0.9.1"
@ -86,13 +89,16 @@ typetag = "0.1.5"
umask = "1.0.0"
unicode-xid = "0.2.1"
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
which = {version = "4.0.1", optional = true}
which = {version = "4.0.2", optional = true}
zip = {version = "0.5.6", optional = true}
clipboard = {version = "0.5", optional = true}
encoding_rs = "0.8.23"
quick-xml = "0.18.1"
rayon = "1.3.1"
starship = {version = "0.43.0", optional = true}
trash = {version = "1.0.1", optional = true}
url = {version = "2.1.1"}
Inflector = "0.11"
[target.'cfg(unix)'.dependencies]
users = "0.10.0"
@ -109,7 +115,8 @@ optional = true
version = "0.23.1"
[build-dependencies]
nu-build = {version = "0.17.0", path = "../nu-build"}
git2 = {version = "0.13", optional = true}
[dev-dependencies]
quickcheck = "0.9"
@ -118,5 +125,4 @@ quickcheck_macros = "0.9"
[features]
clipboard-cli = ["clipboard"]
stable = []
starship-prompt = ["starship"]
trash-support = ["trash"]

Binary file not shown.

36
crates/nu-cli/build.rs Normal file
View File

@ -0,0 +1,36 @@
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)?;
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

@ -1,105 +1,39 @@
use crate::commands::classified::block::run_block;
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
use crate::commands::plugin::JsonRpc;
use crate::commands::plugin::{PluginCommand, PluginSink};
use crate::commands::whole_stream_command;
use crate::context::Context;
use crate::git::current_branch;
use crate::path::canonicalize;
use crate::prelude::*;
use crate::shell::Helper;
use crate::EnvironmentSyncer;
use futures_codec::FramedRead;
use nu_errors::{ProximateShellError, ShellDiagnostic, ShellError};
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
#[allow(unused)]
use nu_source::Tagged;
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
use log::{debug, trace};
use rustyline::config::{ColorMode, CompletionType, Config};
use rustyline::error::ReadlineError;
use rustyline::{
self, config::Configurer, config::EditMode, At, Cmd, ColorMode, CompletionType, Config, Editor,
KeyPress, Movement, Word,
};
use rustyline::{self, config::Configurer, At, Cmd, Editor, KeyPress, Movement, Word};
use std::error::Error;
use std::io::{BufRead, BufReader, Write};
use std::iter::Iterator;
use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering;
use rayon::prelude::*;
fn register_plugins(context: &mut Context) -> Result<(), ShellError> {
if let Ok(plugins) = crate::plugin::scan() {
context.add_commands(
plugins
.into_iter()
.filter(|p| !context.is_command_registered(p.name()))
.collect(),
);
}
fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), ShellError> {
let mut child = std::process::Command::new(path)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()
.expect("Failed to spawn child process");
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
let mut reader = BufReader::new(stdout);
let request = JsonRpc::new("config", Vec::<Value>::new());
let request_raw = serde_json::to_string(&request)?;
stdin.write_all(format!("{}\n", request_raw).as_bytes())?;
let path = dunce::canonicalize(path)?;
let mut input = String::new();
let result = match reader.read_line(&mut input) {
Ok(count) => {
trace!(target: "nu::load", "plugin infrastructure -> config response");
trace!(target: "nu::load", "plugin infrastructure -> processing response ({} bytes)", count);
trace!(target: "nu::load", "plugin infrastructure -> response: {}", input);
let response = serde_json::from_str::<JsonRpc<Result<Signature, ShellError>>>(&input);
match response {
Ok(jrpc) => match jrpc.params {
Ok(params) => {
let fname = path.to_string_lossy();
trace!(target: "nu::load", "plugin infrastructure -> processing {:?}", params);
let name = params.name.clone();
let fname = fname.to_string();
if context.get_command(&name).is_some() {
trace!(target: "nu::load", "plugin infrastructure -> {:?} already loaded.", &name);
} else if params.is_filter {
context.add_commands(vec![whole_stream_command(PluginCommand::new(
name, fname, params,
))]);
} else {
context.add_commands(vec![whole_stream_command(PluginSink::new(
name, fname, params,
))]);
}
Ok(())
}
Err(e) => Err(e),
},
Err(e) => {
trace!(target: "nu::load", "plugin infrastructure -> incompatible {:?}", input);
Err(ShellError::untagged_runtime_error(format!(
"Error: {:?}",
e
)))
}
}
}
Err(e) => Err(ShellError::untagged_runtime_error(format!(
"Error: {:?}",
e
))),
};
let _ = child.wait();
result
Ok(())
}
fn search_paths() -> Vec<std::path::PathBuf> {
pub fn search_paths() -> Vec<std::path::PathBuf> {
use std::env;
let mut search_paths = Vec::new();
@ -111,7 +45,7 @@ fn search_paths() -> Vec<std::path::PathBuf> {
}
}
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
if let Some(plugin_dirs) = config.get("plugin_dirs") {
if let Value {
value: UntaggedValue::Table(pipelines),
@ -130,104 +64,6 @@ fn search_paths() -> Vec<std::path::PathBuf> {
search_paths
}
pub fn load_plugins(context: &mut Context) -> Result<(), ShellError> {
let opts = glob::MatchOptions {
case_sensitive: false,
require_literal_separator: false,
require_literal_leading_dot: false,
};
for path in search_paths() {
let mut pattern = path.to_path_buf();
pattern.push(std::path::Path::new("nu_plugin_[a-z0-9][a-z0-9]*"));
let plugs: Vec<_> = glob::glob_with(&pattern.to_string_lossy(), opts)?
.filter_map(|x| x.ok())
.collect();
let _failures: Vec<_> = plugs
.par_iter()
.map(|path| {
let bin_name = {
if let Some(name) = path.file_name() {
name.to_str().unwrap_or("")
} else {
""
}
};
let is_valid_name = {
#[cfg(windows)]
{
bin_name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
}
#[cfg(not(windows))]
{
bin_name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
};
let is_executable = {
#[cfg(windows)]
{
bin_name.ends_with(".exe") || bin_name.ends_with(".bat")
}
#[cfg(not(windows))]
{
true
}
};
if is_valid_name && is_executable {
trace!(target: "nu::load", "plugin infrastructure -> Trying {:?}", path.display());
// we are ok if this plugin load fails
let _ = load_plugin(&path, &mut context.clone());
}
})
.collect();
}
Ok(())
}
pub struct History;
impl History {
pub fn path() -> PathBuf {
const FNAME: &str = "history.txt";
config::user_data()
.map(|mut p| {
p.push(FNAME);
p
})
.unwrap_or_else(|_| PathBuf::from(FNAME))
}
}
#[allow(dead_code)]
fn create_default_starship_config() -> Option<toml::Value> {
let mut map = toml::value::Table::new();
map.insert("add_newline".into(), toml::Value::Boolean(false));
let mut git_branch = toml::value::Table::new();
git_branch.insert("symbol".into(), toml::Value::String("📙 ".into()));
map.insert("git_branch".into(), toml::Value::Table(git_branch));
let mut git_status = toml::value::Table::new();
git_status.insert("disabled".into(), toml::Value::Boolean(true));
map.insert("git_status".into(), toml::Value::Table(git_status));
Some(toml::Value::Table(map))
}
pub fn create_default_context(
syncer: &mut crate::EnvironmentSyncer,
interactive: bool,
@ -263,6 +99,9 @@ pub fn create_default_context(
whole_stream_command(Touch),
whole_stream_command(Cpy),
whole_stream_command(Date),
whole_stream_command(DateNow),
whole_stream_command(DateUTC),
whole_stream_command(DateFormat),
whole_stream_command(Cal),
whole_stream_command(Mkdir),
whole_stream_command(Mv),
@ -275,6 +114,7 @@ pub fn create_default_context(
whole_stream_command(Alias),
whole_stream_command(WithEnv),
whole_stream_command(Do),
whole_stream_command(Sleep),
// Statistics
whole_stream_command(Size),
whole_stream_command(Count),
@ -310,10 +150,21 @@ pub fn create_default_context(
whole_stream_command(StrSubstring),
whole_stream_command(StrSet),
whole_stream_command(StrToDatetime),
whole_stream_command(StrContains),
whole_stream_command(StrIndexOf),
whole_stream_command(StrTrim),
whole_stream_command(StrTrimLeft),
whole_stream_command(StrTrimRight),
whole_stream_command(StrStartsWith),
whole_stream_command(StrEndsWith),
whole_stream_command(StrCollect),
whole_stream_command(StrLength),
whole_stream_command(StrReverse),
whole_stream_command(StrCamelCase),
whole_stream_command(StrPascalCase),
whole_stream_command(StrKebabCase),
whole_stream_command(StrSnakeCase),
whole_stream_command(StrScreamingSnakeCase),
whole_stream_command(BuildString),
whole_stream_command(Ansi),
whole_stream_command(Char),
@ -324,6 +175,7 @@ pub fn create_default_context(
whole_stream_command(Get),
whole_stream_command(Update),
whole_stream_command(Insert),
whole_stream_command(IntoInt),
whole_stream_command(SplitBy),
// Row manipulation
whole_stream_command(Reverse),
@ -360,6 +212,7 @@ pub fn create_default_context(
whole_stream_command(Wrap),
whole_stream_command(Pivot),
whole_stream_command(Headers),
whole_stream_command(Reduce),
// Data processing
whole_stream_command(Histogram),
whole_stream_command(Autoenv),
@ -375,6 +228,7 @@ pub fn create_default_context(
whole_stream_command(MathStddev),
whole_stream_command(MathSummation),
whole_stream_command(MathVariance),
whole_stream_command(MathProduct),
// File format output
whole_stream_command(To),
whole_stream_command(ToCSV),
@ -385,6 +239,7 @@ pub fn create_default_context(
whole_stream_command(ToTSV),
whole_stream_command(ToURL),
whole_stream_command(ToYAML),
whole_stream_command(ToXML),
// File format input
whole_stream_command(From),
whole_stream_command(FromCSV),
@ -410,6 +265,21 @@ pub fn create_default_context(
whole_stream_command(RandomDice),
#[cfg(feature = "uuid_crate")]
whole_stream_command(RandomUUID),
// Path
whole_stream_command(PathBasename),
whole_stream_command(PathCommand),
whole_stream_command(PathDirname),
whole_stream_command(PathExists),
whole_stream_command(PathExpand),
whole_stream_command(PathExtension),
whole_stream_command(PathFilestem),
whole_stream_command(PathType),
// Url
whole_stream_command(UrlCommand),
whole_stream_command(UrlScheme),
whole_stream_command(UrlPath),
whole_stream_command(UrlHost),
whole_stream_command(UrlQuery),
]);
#[cfg(feature = "clipboard")]
@ -428,7 +298,7 @@ pub async fn run_vec_of_pipelines(
let mut syncer = crate::EnvironmentSyncer::new();
let mut context = create_default_context(&mut syncer, false)?;
let _ = crate::load_plugins(&mut context);
let _ = register_plugins(&mut context);
#[cfg(feature = "ctrlc")]
{
@ -445,7 +315,7 @@ pub async fn run_vec_of_pipelines(
}
// before we start up, let's run our startup commands
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
if let Some(commands) = config.get("startup") {
match commands {
Value {
@ -521,18 +391,12 @@ pub async fn run_pipeline_standalone(
Ok(())
}
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input.
pub async fn cli(
mut syncer: EnvironmentSyncer,
mut context: Context,
) -> Result<(), Box<dyn Error>> {
pub fn create_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Value>) {
#[cfg(windows)]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
#[cfg(not(windows))]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
let _ = load_plugins(&mut context);
let config = Config::builder().color_mode(ColorMode::Forced).build();
let mut rl: Editor<_> = Editor::with_config(config);
@ -546,18 +410,183 @@ pub async fn cli(
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
);
// Let's set the defaults up front and then override them later if the user indicates
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
rl.set_max_history_size(100);
rl.set_history_ignore_dups(true);
rl.set_history_ignore_space(false);
rl.set_completion_type(DEFAULT_COMPLETION_MODE);
rl.set_completion_prompt_limit(100);
rl.set_keyseq_timeout(-1);
rl.set_edit_mode(rustyline::config::EditMode::Emacs);
rl.set_auto_add_history(false);
rl.set_bell_style(rustyline::config::BellStyle::default());
rl.set_color_mode(rustyline::ColorMode::Enabled);
rl.set_tab_stop(8);
if let Err(e) = crate::keybinding::load_keybindings(&mut rl) {
println!("Error loading keybindings: {:?}", e);
}
let config = match config::config(Tag::unknown()) {
Ok(config) => config,
Err(e) => {
eprintln!("Config could not be loaded.");
if let ShellError {
error: ProximateShellError::Diagnostic(ShellDiagnostic { diagnostic }),
..
} = e
{
eprintln!("{}", diagnostic.message);
}
IndexMap::new()
}
};
if let Ok(config) = config::config(Tag::unknown()) {
if let Some(line_editor_vars) = config.get("line_editor") {
for (idx, value) in line_editor_vars.row_entries() {
match idx.as_ref() {
"max_history_size" => {
if let Ok(max_history_size) = value.as_u64() {
rl.set_max_history_size(max_history_size as usize);
}
}
"history_duplicates" => {
// history_duplicates = match value.as_string() {
// Ok(s) if s.to_lowercase() == "alwaysadd" => {
// rustyline::config::HistoryDuplicates::AlwaysAdd
// }
// Ok(s) if s.to_lowercase() == "ignoreconsecutive" => {
// rustyline::config::HistoryDuplicates::IgnoreConsecutive
// }
// _ => rustyline::config::HistoryDuplicates::AlwaysAdd,
// };
if let Ok(history_duplicates) = value.as_bool() {
rl.set_history_ignore_dups(history_duplicates);
}
}
"history_ignore_space" => {
if let Ok(history_ignore_space) = value.as_bool() {
rl.set_history_ignore_space(history_ignore_space);
}
}
"completion_type" => {
let completion_type = match value.as_string() {
Ok(s) if s.to_lowercase() == "circular" => {
rustyline::config::CompletionType::Circular
}
Ok(s) if s.to_lowercase() == "list" => {
rustyline::config::CompletionType::List
}
#[cfg(all(unix, feature = "with-fuzzy"))]
Ok(s) if s.to_lowercase() == "fuzzy" => {
rustyline::config::CompletionType::Fuzzy
}
_ => DEFAULT_COMPLETION_MODE,
};
rl.set_completion_type(completion_type);
}
"completion_prompt_limit" => {
if let Ok(completion_prompt_limit) = value.as_u64() {
rl.set_completion_prompt_limit(completion_prompt_limit as usize);
}
}
"keyseq_timeout_ms" => {
if let Ok(keyseq_timeout_ms) = value.as_u64() {
rl.set_keyseq_timeout(keyseq_timeout_ms as i32);
}
}
"edit_mode" => {
let edit_mode = match value.as_string() {
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
Ok(s) if s.to_lowercase() == "emacs" => {
rustyline::config::EditMode::Emacs
}
_ => rustyline::config::EditMode::Emacs,
};
rl.set_edit_mode(edit_mode);
// Note: When edit_mode is Emacs, the keyseq_timeout_ms is set to -1
// no matter what you may have configured. This is so that key chords
// can be applied without having to do them in a given timeout. So,
// it essentially turns off the keyseq timeout.
}
"auto_add_history" => {
if let Ok(auto_add_history) = value.as_bool() {
rl.set_auto_add_history(auto_add_history);
}
}
"bell_style" => {
let bell_style = match value.as_string() {
Ok(s) if s.to_lowercase() == "audible" => {
rustyline::config::BellStyle::Audible
}
Ok(s) if s.to_lowercase() == "none" => {
rustyline::config::BellStyle::None
}
Ok(s) if s.to_lowercase() == "visible" => {
rustyline::config::BellStyle::Visible
}
_ => rustyline::config::BellStyle::default(),
};
rl.set_bell_style(bell_style);
}
"color_mode" => {
let color_mode = match value.as_string() {
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
Ok(s) if s.to_lowercase() == "forced" => rustyline::ColorMode::Forced,
Ok(s) if s.to_lowercase() == "disabled" => {
rustyline::ColorMode::Disabled
}
_ => rustyline::ColorMode::Enabled,
};
rl.set_color_mode(color_mode);
}
"tab_stop" => {
if let Ok(tab_stop) = value.as_u64() {
rl.set_tab_stop(tab_stop as usize);
}
}
_ => (),
}
}
}
}
(rl, config)
}
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input.
pub async fn cli(
mut syncer: EnvironmentSyncer,
mut context: Context,
) -> Result<(), Box<dyn Error>> {
let configuration = nu_data::config::NuConfig::new();
let history_path = crate::commands::history::history_path(&configuration);
let _ = register_plugins(&mut context);
let (mut rl, config) = create_rustyline_configuration();
// we are ok if history does not exist
let _ = rl.load_history(&history_path);
let skip_welcome_message = config
.get("skip_welcome_message")
.map(|x| x.is_true())
.unwrap_or(false);
if !skip_welcome_message {
println!(
"Welcome to Nushell {} (type 'help' for more info)",
clap::crate_version!()
);
}
#[cfg(windows)]
{
let _ = ansi_term::enable_ansi_support();
}
// we are ok if history does not exist
let _ = rl.load_history(&History::path());
#[cfg(feature = "ctrlc")]
{
let cc = context.ctrl_c.clone();
@ -570,7 +599,7 @@ pub async fn cli(
let mut ctrlcbreak = false;
// before we start up, let's run our startup commands
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
if let Some(commands) = config.get("startup") {
match commands {
Value {
@ -604,101 +633,12 @@ pub async fn cli(
let cwd = context.shell_manager.path();
rl.set_helper(Some(crate::shell::Helper::new(context.clone())));
let hinter = init_hinter(&config);
let config = match config::config(Tag::unknown()) {
Ok(config) => config,
Err(e) => {
eprintln!("Config could not be loaded.");
if let ShellError {
error: ProximateShellError::Diagnostic(ShellDiagnostic { diagnostic }),
..
} = e
{
eprintln!("{}", diagnostic.message);
}
IndexMap::new()
}
};
let use_starship = match config.get("use_starship") {
Some(b) => match b.as_bool() {
Ok(b) => b,
_ => false,
},
_ => false,
};
let edit_mode = config
.get("edit_mode")
.map(|s| match s.value.expect_string() {
"vi" => EditMode::Vi,
"emacs" => EditMode::Emacs,
_ => EditMode::Emacs,
})
.unwrap_or(EditMode::Emacs);
rl.set_edit_mode(edit_mode);
let max_history_size = config
.get("history_size")
.map(|i| i.value.expect_int())
.unwrap_or(100_000);
// rl.set_max_history_size(max_history_size as usize);
rustyline::config::Configurer::set_max_history_size(&mut rl, max_history_size as usize);
rustyline::Editor::set_max_history_size(&mut rl, max_history_size as usize);
let key_timeout = config
.get("key_timeout")
.map(|s| s.value.expect_int())
.unwrap_or(1);
rl.set_keyseq_timeout(key_timeout as i32);
let completion_mode = config
.get("completion_mode")
.map(|s| match s.value.expect_string() {
"list" => CompletionType::List,
"circular" => CompletionType::Circular,
_ => DEFAULT_COMPLETION_MODE,
})
.unwrap_or(DEFAULT_COMPLETION_MODE);
rl.set_completion_type(completion_mode);
rl.set_helper(Some(crate::shell::Helper::new(context.clone(), hinter)));
let colored_prompt = {
if use_starship {
#[cfg(feature = "starship")]
{
std::env::set_var("STARSHIP_SHELL", "");
std::env::set_var("PWD", &cwd);
let mut starship_context =
starship::context::Context::new_with_dir(clap::ArgMatches::default(), cwd);
match starship_context.config.config {
None => {
starship_context.config.config = create_default_starship_config();
}
Some(toml::Value::Table(t)) if t.is_empty() => {
starship_context.config.config = create_default_starship_config();
}
_ => {}
};
starship::print::get_prompt(starship_context)
}
#[cfg(not(feature = "starship"))]
{
format!(
"\x1b[32m{}{}\x1b[m> ",
cwd,
match current_branch() {
Some(s) => format!("({})", s),
None => "".to_string(),
}
)
}
} else if let Some(prompt) = config.get("prompt") {
if let Some(prompt) = config.get("prompt") {
let prompt_line = prompt.as_string()?;
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
@ -794,13 +734,13 @@ pub async fn cli(
match line {
LineResult::Success(line) => {
rl.add_history_entry(&line);
let _ = rl.save_history(&History::path());
let _ = rl.save_history(&history_path);
context.maybe_print_errors(Text::from(line));
}
LineResult::Error(line, err) => {
rl.add_history_entry(&line);
let _ = rl.save_history(&History::path());
let _ = rl.save_history(&history_path);
context.with_host(|_host| {
print_err(err, &Text::from(line.clone()));
@ -820,7 +760,7 @@ pub async fn cli(
}
if ctrlcbreak {
let _ = rl.save_history(&History::path());
let _ = rl.save_history(&history_path);
std::process::exit(0);
} else {
context.with_host(|host| host.stdout("CTRL-C pressed (again to quit)"));
@ -837,11 +777,23 @@ pub async fn cli(
}
// we are ok if we can not save history
let _ = rl.save_history(&History::path());
let _ = rl.save_history(&history_path);
Ok(())
}
fn init_hinter(config: &IndexMap<String, Value>) -> Option<rustyline::hint::HistoryHinter> {
// Show hints unless explicitly disabled in config
if let Some(line_editor_vars) = config.get("line_editor") {
for (idx, value) in line_editor_vars.row_entries() {
if idx == "show_hints" && value.expect_string() == "false" {
return None;
}
}
}
Some(rustyline::hint::HistoryHinter {})
}
fn chomp_newline(s: &str) -> &str {
if s.ends_with('\n') {
&s[..s.len() - 1]
@ -1012,18 +964,15 @@ pub async fn process_line(
let file = futures::io::AllowStdIo::new(std::io::stdin());
let stream = FramedRead::new(file, MaybeTextCodec::default()).map(|line| {
if let Ok(line) = line {
match line {
StringOrBinary::String(s) => Ok(Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
tag: Tag::unknown(),
}),
StringOrBinary::Binary(b) => Ok(Value {
value: UntaggedValue::Primitive(Primitive::Binary(
b.into_iter().collect(),
)),
tag: Tag::unknown(),
}),
}
let primitive = match line {
StringOrBinary::String(s) => Primitive::String(s),
StringOrBinary::Binary(b) => Primitive::Binary(b.into_iter().collect()),
};
Ok(Value {
value: UntaggedValue::Primitive(primitive),
tag: Tag::unknown(),
})
} else {
panic!("Internal error: could not read lines of text from stdin")
}
@ -1065,7 +1014,7 @@ pub async fn process_line(
};
if let Ok(mut output_stream) =
crate::commands::autoview::autoview(context).await
crate::commands::autoview::command::autoview(context).await
{
loop {
match output_stream.try_next().await {

View File

@ -63,6 +63,7 @@ pub(crate) mod histogram;
pub(crate) mod history;
pub(crate) mod if_;
pub(crate) mod insert;
pub(crate) mod into_int;
pub(crate) mod is_empty;
pub(crate) mod keep;
pub(crate) mod last;
@ -76,13 +77,14 @@ pub(crate) mod next;
pub(crate) mod nth;
pub(crate) mod open;
pub(crate) mod parse;
pub(crate) mod path;
pub(crate) mod pivot;
pub(crate) mod plugin;
pub(crate) mod prepend;
pub(crate) mod prev;
pub(crate) mod pwd;
pub(crate) mod random;
pub(crate) mod range;
pub(crate) mod reduce;
pub(crate) mod reject;
pub(crate) mod rename;
pub(crate) mod reverse;
@ -95,6 +97,7 @@ pub(crate) mod shells;
pub(crate) mod shuffle;
pub(crate) mod size;
pub(crate) mod skip;
pub(crate) mod sleep;
pub(crate) mod sort_by;
pub(crate) mod split;
pub(crate) mod split_by;
@ -109,10 +112,12 @@ pub(crate) mod to_md;
pub(crate) mod to_toml;
pub(crate) mod to_tsv;
pub(crate) mod to_url;
pub(crate) mod to_xml;
pub(crate) mod to_yaml;
pub(crate) mod trim;
pub(crate) mod uniq;
pub(crate) mod update;
pub(crate) mod url_;
pub(crate) mod version;
pub(crate) mod what;
pub(crate) mod where_;
@ -142,7 +147,7 @@ pub(crate) use config::{
};
pub(crate) use count::Count;
pub(crate) use cp::Cpy;
pub(crate) use date::Date;
pub(crate) use date::{Date, DateFormat, DateNow, DateUTC};
pub(crate) use debug::Debug;
pub(crate) use default::Default;
pub(crate) use do_::Do;
@ -187,13 +192,14 @@ pub(crate) use help::Help;
pub(crate) use histogram::Histogram;
pub(crate) use history::History;
pub(crate) use insert::Insert;
pub(crate) use into_int::IntoInt;
pub(crate) use keep::{Keep, KeepUntil, KeepWhile};
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, MathStddev,
MathSummation, MathVariance,
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathProduct,
MathStddev, MathSummation, MathVariance,
};
pub(crate) use merge::Merge;
pub(crate) use mkdir::Mkdir;
@ -202,6 +208,10 @@ pub(crate) use next::Next;
pub(crate) use nth::Nth;
pub(crate) use open::Open;
pub(crate) use parse::Parse;
pub(crate) use path::{
PathBasename, PathCommand, PathDirname, PathExists, PathExpand, PathExtension, PathFilestem,
PathType,
};
pub(crate) use pivot::Pivot;
pub(crate) use prepend::Prepend;
pub(crate) use prev::Previous;
@ -210,6 +220,7 @@ pub(crate) use pwd::Pwd;
pub(crate) use random::RandomUUID;
pub(crate) use random::{Random, RandomBool, RandomDice};
pub(crate) use range::Range;
pub(crate) use reduce::Reduce;
pub(crate) use reject::Reject;
pub(crate) use rename::Rename;
pub(crate) use reverse::Reverse;
@ -221,12 +232,15 @@ pub(crate) use shells::Shells;
pub(crate) use shuffle::Shuffle;
pub(crate) use size::Size;
pub(crate) use skip::{Skip, SkipUntil, SkipWhile};
pub(crate) use sleep::Sleep;
pub(crate) use sort_by::SortBy;
pub(crate) use split::{Split, SplitChars, SplitColumn, SplitRow};
pub(crate) use split_by::SplitBy;
pub(crate) use str_::{
Str, StrCapitalize, StrCollect, StrDowncase, StrFindReplace, StrFrom, StrLength, StrReverse,
StrSet, StrSubstring, StrToDatetime, StrToDecimal, StrToInteger, StrTrim, StrUpcase,
Str, StrCamelCase, StrCapitalize, StrCollect, StrContains, StrDowncase, StrEndsWith,
StrFindReplace, StrFrom, StrIndexOf, StrKebabCase, StrLength, StrPascalCase, StrReverse,
StrScreamingSnakeCase, StrSet, StrSnakeCase, StrStartsWith, StrSubstring, StrToDatetime,
StrToDecimal, StrToInteger, StrTrim, StrTrimLeft, StrTrimRight, StrUpcase,
};
pub(crate) use table::Table;
pub(crate) use tags::Tags;
@ -238,10 +252,12 @@ pub(crate) use to_md::ToMarkdown;
pub(crate) use to_toml::ToTOML;
pub(crate) use to_tsv::ToTSV;
pub(crate) use to_url::ToURL;
pub(crate) use to_xml::ToXML;
pub(crate) use to_yaml::ToYAML;
pub(crate) use touch::Touch;
pub(crate) use trim::Trim;
pub(crate) use uniq::Uniq;
pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme};
pub(crate) use version::Version;
pub(crate) use what::What;
pub(crate) use where_::Where;

View File

@ -1,12 +1,16 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::data::config;
use crate::prelude::*;
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, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
hir::Block, CommandAction, NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape,
UntaggedValue, Value,
};
use nu_source::Tagged;
use std::collections::HashMap;
pub struct Alias;
@ -15,6 +19,7 @@ pub struct AliasArgs {
pub name: Tagged<String>,
pub args: Vec<Value>,
pub block: Block,
pub infer: Option<bool>,
pub save: Option<bool>,
}
@ -33,6 +38,7 @@ impl WholeStreamCommand for Alias {
SyntaxShape::Block,
"the block to run as the body of the alias",
)
.switch("infer", "infer argument types (experimental)", Some('i'))
.switch("save", "save the alias to your config", Some('s'))
}
@ -75,6 +81,7 @@ pub async fn alias(
name,
args: list,
block,
infer,
save,
},
_ctx,
@ -82,17 +89,21 @@ pub async fn alias(
let mut processed_args: Vec<String> = vec![];
if let Some(true) = save {
let mut result = crate::data::config::read(name.clone().tag, &None)?;
let mut result = nu_data::config::read(name.clone().tag, &None)?;
// process the alias to remove the --save flag
let left_brace = raw_input.find('{').unwrap_or(0);
let right_brace = raw_input.rfind('}').unwrap_or_else(|| raw_input.len());
let left = raw_input[..left_brace]
.replace("--save", "")
.replace("-s", "");
.replace("--save", "") // TODO using regex (or reconstruct string from AST?)
.replace("-si", "-i")
.replace("-s ", "")
.replace("-is", "-i");
let right = raw_input[right_brace..]
.replace("--save", "")
.replace("-s", "");
.replace("-si", "-i")
.replace("-s ", "")
.replace("-is", "-i");
raw_input = format!("{}{}{}", left, &raw_input[left_brace..right_brace], right);
// create a value from raw_input alias
@ -133,9 +144,197 @@ pub async fn alias(
}
}
Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::AddAlias(name.to_string(), processed_args, block),
)))
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 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)
}
#[cfg(test)]

View File

@ -1,5 +1,6 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use ansi_term::Color;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
@ -76,60 +77,56 @@ impl WholeStreamCommand for Ansi {
}
}
fn str_to_ansi_color(s: String) -> Option<String> {
pub fn str_to_ansi_color(s: String) -> Option<String> {
match s.as_str() {
"g" | "green" => Some(ansi_term::Color::Green.prefix().to_string()),
"gb" | "green_bold" => Some(ansi_term::Color::Green.bold().prefix().to_string()),
"gu" | "green_underline" => Some(ansi_term::Color::Green.underline().prefix().to_string()),
"gi" | "green_italic" => Some(ansi_term::Color::Green.italic().prefix().to_string()),
"gd" | "green_dimmed" => Some(ansi_term::Color::Green.dimmed().prefix().to_string()),
"gr" | "green_reverse" => Some(ansi_term::Color::Green.reverse().prefix().to_string()),
"r" | "red" => Some(ansi_term::Color::Red.prefix().to_string()),
"rb" | "red_bold" => Some(ansi_term::Color::Red.bold().prefix().to_string()),
"ru" | "red_underline" => Some(ansi_term::Color::Red.underline().prefix().to_string()),
"ri" | "red_italic" => Some(ansi_term::Color::Red.italic().prefix().to_string()),
"rd" | "red_dimmed" => Some(ansi_term::Color::Red.dimmed().prefix().to_string()),
"rr" | "red_reverse" => Some(ansi_term::Color::Red.reverse().prefix().to_string()),
"u" | "blue" => Some(ansi_term::Color::Blue.prefix().to_string()),
"ub" | "blue_bold" => Some(ansi_term::Color::Blue.bold().prefix().to_string()),
"uu" | "blue_underline" => Some(ansi_term::Color::Blue.underline().prefix().to_string()),
"ui" | "blue_italic" => Some(ansi_term::Color::Blue.italic().prefix().to_string()),
"ud" | "blue_dimmed" => Some(ansi_term::Color::Blue.dimmed().prefix().to_string()),
"ur" | "blue_reverse" => Some(ansi_term::Color::Blue.reverse().prefix().to_string()),
"b" | "black" => Some(ansi_term::Color::Black.prefix().to_string()),
"bb" | "black_bold" => Some(ansi_term::Color::Black.bold().prefix().to_string()),
"bu" | "black_underline" => Some(ansi_term::Color::Black.underline().prefix().to_string()),
"bi" | "black_italic" => Some(ansi_term::Color::Black.italic().prefix().to_string()),
"bd" | "black_dimmed" => Some(ansi_term::Color::Black.dimmed().prefix().to_string()),
"br" | "black_reverse" => Some(ansi_term::Color::Black.reverse().prefix().to_string()),
"y" | "yellow" => Some(ansi_term::Color::Yellow.prefix().to_string()),
"yb" | "yellow_bold" => Some(ansi_term::Color::Yellow.bold().prefix().to_string()),
"yu" | "yellow_underline" => {
Some(ansi_term::Color::Yellow.underline().prefix().to_string())
}
"yi" | "yellow_italic" => Some(ansi_term::Color::Yellow.italic().prefix().to_string()),
"yd" | "yellow_dimmed" => Some(ansi_term::Color::Yellow.dimmed().prefix().to_string()),
"yr" | "yellow_reverse" => Some(ansi_term::Color::Yellow.reverse().prefix().to_string()),
"p" | "purple" => Some(ansi_term::Color::Purple.prefix().to_string()),
"pb" | "purple_bold" => Some(ansi_term::Color::Purple.bold().prefix().to_string()),
"pu" | "purple_underline" => {
Some(ansi_term::Color::Purple.underline().prefix().to_string())
}
"pi" | "purple_italic" => Some(ansi_term::Color::Purple.italic().prefix().to_string()),
"pd" | "purple_dimmed" => Some(ansi_term::Color::Purple.dimmed().prefix().to_string()),
"pr" | "purple_reverse" => Some(ansi_term::Color::Purple.reverse().prefix().to_string()),
"c" | "cyan" => Some(ansi_term::Color::Cyan.prefix().to_string()),
"cb" | "cyan_bold" => Some(ansi_term::Color::Cyan.bold().prefix().to_string()),
"cu" | "cyan_underline" => Some(ansi_term::Color::Cyan.underline().prefix().to_string()),
"ci" | "cyan_italic" => Some(ansi_term::Color::Cyan.italic().prefix().to_string()),
"cd" | "cyan_dimmed" => Some(ansi_term::Color::Cyan.dimmed().prefix().to_string()),
"cr" | "cyan_reverse" => Some(ansi_term::Color::Cyan.reverse().prefix().to_string()),
"w" | "white" => Some(ansi_term::Color::White.prefix().to_string()),
"wb" | "white_bold" => Some(ansi_term::Color::White.bold().prefix().to_string()),
"wu" | "white_underline" => Some(ansi_term::Color::White.underline().prefix().to_string()),
"wi" | "white_italic" => Some(ansi_term::Color::White.italic().prefix().to_string()),
"wd" | "white_dimmed" => Some(ansi_term::Color::White.dimmed().prefix().to_string()),
"wr" | "white_reverse" => Some(ansi_term::Color::White.reverse().prefix().to_string()),
"g" | "green" => Some(Color::Green.prefix().to_string()),
"gb" | "green_bold" => Some(Color::Green.bold().prefix().to_string()),
"gu" | "green_underline" => Some(Color::Green.underline().prefix().to_string()),
"gi" | "green_italic" => Some(Color::Green.italic().prefix().to_string()),
"gd" | "green_dimmed" => Some(Color::Green.dimmed().prefix().to_string()),
"gr" | "green_reverse" => Some(Color::Green.reverse().prefix().to_string()),
"r" | "red" => Some(Color::Red.prefix().to_string()),
"rb" | "red_bold" => Some(Color::Red.bold().prefix().to_string()),
"ru" | "red_underline" => Some(Color::Red.underline().prefix().to_string()),
"ri" | "red_italic" => Some(Color::Red.italic().prefix().to_string()),
"rd" | "red_dimmed" => Some(Color::Red.dimmed().prefix().to_string()),
"rr" | "red_reverse" => Some(Color::Red.reverse().prefix().to_string()),
"u" | "blue" => Some(Color::Blue.prefix().to_string()),
"ub" | "blue_bold" => Some(Color::Blue.bold().prefix().to_string()),
"uu" | "blue_underline" => Some(Color::Blue.underline().prefix().to_string()),
"ui" | "blue_italic" => Some(Color::Blue.italic().prefix().to_string()),
"ud" | "blue_dimmed" => Some(Color::Blue.dimmed().prefix().to_string()),
"ur" | "blue_reverse" => Some(Color::Blue.reverse().prefix().to_string()),
"b" | "black" => Some(Color::Black.prefix().to_string()),
"bb" | "black_bold" => Some(Color::Black.bold().prefix().to_string()),
"bu" | "black_underline" => Some(Color::Black.underline().prefix().to_string()),
"bi" | "black_italic" => Some(Color::Black.italic().prefix().to_string()),
"bd" | "black_dimmed" => Some(Color::Black.dimmed().prefix().to_string()),
"br" | "black_reverse" => Some(Color::Black.reverse().prefix().to_string()),
"y" | "yellow" => Some(Color::Yellow.prefix().to_string()),
"yb" | "yellow_bold" => Some(Color::Yellow.bold().prefix().to_string()),
"yu" | "yellow_underline" => Some(Color::Yellow.underline().prefix().to_string()),
"yi" | "yellow_italic" => Some(Color::Yellow.italic().prefix().to_string()),
"yd" | "yellow_dimmed" => Some(Color::Yellow.dimmed().prefix().to_string()),
"yr" | "yellow_reverse" => Some(Color::Yellow.reverse().prefix().to_string()),
"p" | "purple" => Some(Color::Purple.prefix().to_string()),
"pb" | "purple_bold" => Some(Color::Purple.bold().prefix().to_string()),
"pu" | "purple_underline" => Some(Color::Purple.underline().prefix().to_string()),
"pi" | "purple_italic" => Some(Color::Purple.italic().prefix().to_string()),
"pd" | "purple_dimmed" => Some(Color::Purple.dimmed().prefix().to_string()),
"pr" | "purple_reverse" => Some(Color::Purple.reverse().prefix().to_string()),
"c" | "cyan" => Some(Color::Cyan.prefix().to_string()),
"cb" | "cyan_bold" => Some(Color::Cyan.bold().prefix().to_string()),
"cu" | "cyan_underline" => Some(Color::Cyan.underline().prefix().to_string()),
"ci" | "cyan_italic" => Some(Color::Cyan.italic().prefix().to_string()),
"cd" | "cyan_dimmed" => Some(Color::Cyan.dimmed().prefix().to_string()),
"cr" | "cyan_reverse" => Some(Color::Cyan.reverse().prefix().to_string()),
"w" | "white" => Some(Color::White.prefix().to_string()),
"wb" | "white_bold" => Some(Color::White.bold().prefix().to_string()),
"wu" | "white_underline" => Some(Color::White.underline().prefix().to_string()),
"wi" | "white_italic" => Some(Color::White.italic().prefix().to_string()),
"wd" | "white_dimmed" => Some(Color::White.dimmed().prefix().to_string()),
"wr" | "white_reverse" => Some(Color::White.reverse().prefix().to_string()),
"reset" => Some("\x1b[0m".to_owned()),
_ => None,
}

View File

@ -55,7 +55,7 @@ impl WholeStreamCommand for Autoenv {
The file can contain several optional sections:
env: environment variables to set when visiting the directory. The variables are unset after leaving the directory and any overwritten values are restored.
scriptvars: environment variables that should be set to the return value of a script. After they have been set, they behave in the same way as variables set in the env section.
scripts: scripts to run when entering the directory or leaving it. Note that exitscripts are not run in the directory they are declared in."#
scripts: scripts to run when entering the directory or leaving it."#
}
fn signature(&self) -> Signature {

View File

@ -1,17 +1,19 @@
use crate::commands::UnevaluatedCallInfo;
use crate::commands::WholeStreamCommand;
use crate::data::value::format_leaf;
use crate::commands::autoview::options::{ConfigExtensions, NuConfig as AutoViewConfiguration};
use crate::commands::{UnevaluatedCallInfo, WholeStreamCommand};
use crate::prelude::*;
use crate::primitive::get_color_config;
use nu_data::value::format_leaf;
use nu_errors::ShellError;
use nu_protocol::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
use nu_protocol::hir::{self, Expression, ExternalRedirection, Literal, SpannedExpression};
use nu_protocol::{Primitive, Scope, Signature, UntaggedValue, Value};
use nu_table::TextStyle;
use parking_lot::Mutex;
use std::sync::atomic::AtomicBool;
pub struct Autoview;
pub struct Command;
#[async_trait]
impl WholeStreamCommand for Autoview {
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"autoview"
}
@ -82,31 +84,17 @@ impl RunnableContextWithoutInput {
}
pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
let configuration = AutoViewConfiguration::new();
let binary = context.get_command("binaryview");
let text = context.get_command("textview");
let table = context.get_command("table");
#[derive(PartialEq)]
enum AutoPivotMode {
Auto,
Always,
Never,
}
let pivot_mode = crate::data::config::config(Tag::unknown());
let pivot_mode = if let Some(v) = pivot_mode?.get("pivot_mode") {
match v.as_string() {
Ok(m) if m.to_lowercase() == "auto" => AutoPivotMode::Auto,
Ok(m) if m.to_lowercase() == "always" => AutoPivotMode::Always,
Ok(m) if m.to_lowercase() == "never" => AutoPivotMode::Never,
_ => AutoPivotMode::Always,
}
} else {
AutoPivotMode::Always
};
let pivot_mode = configuration.pivot_mode();
let (mut input_stream, context) = RunnableContextWithoutInput::convert(context);
let term_width = context.host.lock().width();
let color_hm = get_color_config();
if let Some(x) = input_stream.next().await {
match input_stream.next().await {
@ -254,8 +242,8 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
Value {
value: UntaggedValue::Row(row),
..
} if pivot_mode == AutoPivotMode::Always
|| (pivot_mode == AutoPivotMode::Auto
} if pivot_mode.is_always()
|| (pivot_mode.is_auto()
&& (row
.entries
.iter()
@ -271,15 +259,14 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
entries.push(vec![
nu_table::StyledString::new(
key.to_string(),
nu_table::TextStyle {
alignment: nu_table::Alignment::Left,
color: Some(ansi_term::Color::Green),
is_bold: true,
},
TextStyle::new()
.alignment(nu_table::Alignment::Left)
.fg(ansi_term::Color::Green)
.bold(Some(true)),
),
nu_table::StyledString::new(
format_leaf(value).plain_string(100_000),
nu_table::TextStyle::basic(),
nu_table::TextStyle::basic_left(),
),
]);
}
@ -287,7 +274,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
let table =
nu_table::Table::new(vec![], entries, nu_table::Theme::compact());
nu_table::draw_table(&table, term_width);
nu_table::draw_table(&table, term_width, &color_hm);
}
Value {
@ -328,7 +315,7 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
positional: None,
named: None,
span,
is_last: true,
external_redirection: ExternalRedirection::Stdout,
},
name_tag: context.name.clone(),
scope: Scope::new(),
@ -338,12 +325,12 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
#[cfg(test)]
mod tests {
use super::Autoview;
use super::Command;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Autoview {})
test_examples(Command {})
}
}

View File

@ -0,0 +1,4 @@
pub mod command;
mod options;
pub use command::Command as Autoview;

View File

@ -0,0 +1,60 @@
pub use nu_data::config::NuConfig;
use std::fmt::Debug;
#[derive(PartialEq, Debug)]
pub enum AutoPivotMode {
Auto,
Always,
Never,
}
impl AutoPivotMode {
pub fn is_auto(&self) -> bool {
match &self {
AutoPivotMode::Auto => true,
_ => false,
}
}
pub fn is_always(&self) -> bool {
match &self {
AutoPivotMode::Always => true,
_ => false,
}
}
#[allow(unused)]
pub fn is_never(&self) -> bool {
match &self {
AutoPivotMode::Never => true,
_ => false,
}
}
}
pub trait ConfigExtensions: Debug + Send {
fn pivot_mode(&self) -> AutoPivotMode;
}
pub fn pivot_mode(config: &NuConfig) -> AutoPivotMode {
let vars = config.vars.lock();
if let Some(mode) = vars.get("pivot_mode") {
let mode = match mode.as_string() {
Ok(m) if m.to_lowercase() == "auto" => AutoPivotMode::Auto,
Ok(m) if m.to_lowercase() == "always" => AutoPivotMode::Always,
Ok(m) if m.to_lowercase() == "never" => AutoPivotMode::Never,
_ => AutoPivotMode::Never,
};
return mode;
}
AutoPivotMode::Never
}
impl ConfigExtensions for NuConfig {
fn pivot_mode(&self) -> AutoPivotMode {
pivot_mode(self)
}
}

View File

@ -64,17 +64,15 @@ async fn benchmark(
)
.await;
let output = match result {
Ok(mut stream) => {
let _ = stream.drain_vec().await;
let run_duration: chrono::Duration = Utc::now().signed_duration_since(start_time);
context.clear_errors();
Ok(ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(Primitive::from(run_duration)),
tag: Tag::from(block.span),
}))
}
Err(e) => Err(e),
};
let _ = result?.drain_vec().await;
let run_duration: chrono::Duration = Utc::now().signed_duration_since(start_time);
context.clear_errors();
let output = Ok(ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(Primitive::from(run_duration)),
tag: Tag::from(block.span),
}));
Ok(OutputStream::from(vec![output]))
}

View File

@ -2,7 +2,7 @@ use crate::prelude::*;
use nu_errors::ShellError;
use crate::commands::WholeStreamCommand;
use crate::data::value::format_leaf;
use nu_data::value::format_leaf;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
#[derive(Deserialize)]

View File

@ -100,7 +100,7 @@ pub async fn cal(
(current_month, current_month)
};
let add_months_of_year_to_table_result = add_months_of_year_to_table(
add_months_of_year_to_table(
&args,
&mut calendar_vec_deque,
&tag,
@ -108,12 +108,9 @@ pub async fn cal(
month_range,
current_month,
current_day_option,
);
)?;
match add_months_of_year_to_table_result {
Ok(()) => Ok(futures::stream::iter(calendar_vec_deque).to_output_stream()),
Err(error) => Err(error),
}
Ok(futures::stream::iter(calendar_vec_deque).to_output_stream())
}
fn get_invalid_year_shell_error(year_tag: &Tag) -> ShellError {

View File

@ -70,27 +70,21 @@ async fn run_pipeline(
vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>,
) -> Result<InputStream, ShellError> {
let mut iter = commands.list.clone().into_iter().peekable();
loop {
let item: Option<ClassifiedCommand> = iter.next();
let next: Option<&ClassifiedCommand> = iter.peek();
input = match (item, next) {
(Some(ClassifiedCommand::Dynamic(_)), _) | (_, Some(ClassifiedCommand::Dynamic(_))) => {
for item in commands.list.clone() {
input = match item {
ClassifiedCommand::Dynamic(_) => {
return Err(ShellError::unimplemented("Dynamic commands"))
}
(Some(ClassifiedCommand::Expr(expr)), _) => {
ClassifiedCommand::Expr(expr) => {
run_expression_block(*expr, ctx, it, vars, env).await?
}
(Some(ClassifiedCommand::Error(err)), _) => return Err(err.into()),
(_, Some(ClassifiedCommand::Error(err))) => return Err(err.clone().into()),
(Some(ClassifiedCommand::Internal(left)), _) => {
ClassifiedCommand::Error(err) => return Err(err.into()),
ClassifiedCommand::Internal(left) => {
run_internal_command(left, ctx, input, it, vars, env).await?
}
(None, _) => break,
};
}

View File

@ -13,7 +13,7 @@ use futures_codec::FramedRead;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::hir::ExternalCommand;
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
use nu_source::Tag;
@ -22,7 +22,7 @@ pub(crate) async fn run_external_command(
context: &mut Context,
input: InputStream,
scope: &Scope,
is_last: bool,
external_redirection: ExternalRedirection,
) -> Result<InputStream, ShellError> {
trace!(target: "nu::run::external", "-> {}", command.name);
@ -34,7 +34,7 @@ pub(crate) async fn run_external_command(
));
}
run_with_stdin(command, context, input, scope, is_last).await
run_with_stdin(command, context, input, scope, external_redirection).await
}
async fn run_with_stdin(
@ -42,7 +42,7 @@ async fn run_with_stdin(
context: &mut Context,
input: InputStream,
scope: &Scope,
is_last: bool,
external_redirection: ExternalRedirection,
) -> Result<InputStream, ShellError> {
let path = context.shell_manager.path();
@ -62,9 +62,29 @@ async fn run_with_stdin(
}
// Do the cleanup that we need to do on any argument going out:
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
command_args.push(trimmed_value_string);
match &value.value {
UntaggedValue::Table(table) => {
for t in table {
match &t.value {
UntaggedValue::Primitive(_) => {
command_args
.push(t.convert_to_string().trim_end_matches('\n').to_string());
}
_ => {
return Err(ShellError::labeled_error(
"Could not convert to positional arguments",
"could not convert to positional arguments",
value.tag(),
));
}
}
}
}
_ => {
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
command_args.push(trimmed_value_string);
}
}
}
let process_args = command_args
@ -102,7 +122,14 @@ async fn run_with_stdin(
})
.collect::<Vec<String>>();
spawn(&command, &path, &process_args[..], input, is_last, scope)
spawn(
&command,
&path,
&process_args[..],
input,
external_redirection,
scope,
)
}
fn spawn(
@ -110,7 +137,7 @@ fn spawn(
path: &str,
args: &[String],
input: InputStream,
is_last: bool,
external_redirection: ExternalRedirection,
scope: &Scope,
) -> Result<InputStream, ShellError> {
let command = command.clone();
@ -146,12 +173,22 @@ fn spawn(
// We want stdout regardless of what
// we are doing ($it case or pipe stdin)
if !is_last {
process.stdout(Stdio::piped());
trace!(target: "nu::run::external", "set up stdout pipe");
process.stderr(Stdio::piped());
trace!(target: "nu::run::external", "set up stderr pipe");
match external_redirection {
ExternalRedirection::Stdout => {
process.stdout(Stdio::piped());
trace!(target: "nu::run::external", "set up stdout pipe");
}
ExternalRedirection::Stderr => {
process.stderr(Stdio::piped());
trace!(target: "nu::run::external", "set up stderr pipe");
}
ExternalRedirection::StdoutAndStderr => {
process.stdout(Stdio::piped());
trace!(target: "nu::run::external", "set up stdout pipe");
process.stderr(Stdio::piped());
trace!(target: "nu::run::external", "set up stderr pipe");
}
_ => {}
}
// open since we have some contents for stdin
@ -235,7 +272,9 @@ fn spawn(
});
std::thread::spawn(move || {
if !is_last {
if external_redirection == ExternalRedirection::Stdout
|| external_redirection == ExternalRedirection::StdoutAndStderr
{
let stdout = if let Some(stdout) = child.stdout.take() {
stdout
} else {
@ -250,20 +289,6 @@ fn spawn(
return Err(());
};
let stderr = if let Some(stderr) = child.stderr.take() {
stderr
} else {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
"Can't redirect the stderr for external command",
"can't redirect stderr",
&stdout_name_tag,
)),
tag: stdout_name_tag,
}));
return Err(());
};
let file = futures::io::AllowStdIo::new(stdout);
let stream = FramedRead::new(file, MaybeTextCodec::default());
@ -317,17 +342,34 @@ fn spawn(
}
}
}
}
if external_redirection == ExternalRedirection::Stderr
|| external_redirection == ExternalRedirection::StdoutAndStderr
{
let stderr = if let Some(stderr) = child.stderr.take() {
stderr
} else {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
"Can't redirect the stderr for external command",
"can't redirect stderr",
&stdout_name_tag,
)),
tag: stdout_name_tag,
}));
return Err(());
};
let file = futures::io::AllowStdIo::new(stderr);
let err_stream = FramedRead::new(file, MaybeTextCodec::default());
let stream = FramedRead::new(file, MaybeTextCodec::default());
for err_line in block_on_stream(err_stream) {
match err_line {
for line in block_on_stream(stream) {
match line {
Ok(line) => match line {
StringOrBinary::String(s) => {
let result = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(
ShellError::untagged_runtime_error(s.clone()),
ShellError::untagged_runtime_error(s),
),
tag: stdout_name_tag.clone(),
}));
@ -339,9 +381,7 @@ fn spawn(
StringOrBinary::Binary(_) => {
let result = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(
ShellError::untagged_runtime_error(
"Binary in stderr output",
),
ShellError::untagged_runtime_error("<binary stderr>"),
),
tag: stdout_name_tag.clone(),
}));
@ -363,8 +403,8 @@ fn spawn(
if should_error {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
format!("Unable to read from stderr ({})", e),
"unable to read from stderr",
format!("Unable to read from stdout ({})", e),
"unable to read from stdout",
&stdout_name_tag,
)),
tag: stdout_name_tag.clone(),
@ -385,7 +425,7 @@ fn spawn(
};
if external_failed {
let cfg = crate::data::config::config(Tag::unknown());
let cfg = nu_data::config::config(Tag::unknown());
if let Ok(cfg) = cfg {
if cfg.contains_key("nonzero_exit_errors") {
let _ = stdout_read_tx.send(Ok(Value {
@ -435,10 +475,11 @@ pub fn did_find_command(#[allow(unused)] name: &str) -> bool {
if which::which(name).is_ok() {
true
} else {
// Reference: https://ss64.com/nt/syntax-internal.html
let cmd_builtins = [
"call", "cls", "color", "date", "dir", "echo", "find", "hostname", "pause",
"start", "time", "title", "ver", "copy", "mkdir", "rename", "rd", "rmdir", "type",
"mklink",
"assoc", "break", "color", "copy", "date", "del", "dir", "dpath", "echo", "erase",
"for", "ftype", "md", "mkdir", "mklink", "move", "path", "ren", "rename", "rd",
"rmdir", "set", "start", "time", "title", "type", "ver", "verify", "vol",
];
cmd_builtins.contains(&name)
@ -528,16 +569,21 @@ mod tests {
#[cfg(feature = "which")]
async fn non_existent_run() -> Result<(), ShellError> {
use nu_protocol::hir::ExternalRedirection;
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
let input = InputStream::empty();
let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
assert!(
run_external_command(cmd, &mut ctx, input, &Scope::new(), false)
.await
.is_err()
);
assert!(run_external_command(
cmd,
&mut ctx,
input,
&Scope::new(),
ExternalRedirection::Stdout
)
.await
.is_err());
Ok(())
}

View File

@ -4,7 +4,7 @@ use crate::commands::UnevaluatedCallInfo;
use crate::prelude::*;
use log::{log_enabled, trace};
use nu_errors::ShellError;
use nu_protocol::hir::InternalCommand;
use nu_protocol::hir::{ExternalRedirection, InternalCommand};
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue, Value};
pub(crate) async fn run_internal_command(
@ -87,7 +87,7 @@ pub(crate) async fn run_internal_command(
positional: None,
named: None,
span: Span::unknown(),
is_last: false,
external_redirection: ExternalRedirection::Stdout,
},
name_tag: Tag::unknown_anchor(command.name_span),
scope: (&*scope).clone(),
@ -182,10 +182,7 @@ pub(crate) async fn run_internal_command(
}
CommandAction::EnterShell(location) => {
context.shell_manager.insert_at_current(Box::new(
match FilesystemShell::with_location(
location,
context.registry().clone(),
) {
match FilesystemShell::with_location(location) {
Ok(v) => v,
Err(err) => {
return InputStream::one(

View File

@ -4,6 +4,7 @@ pub(crate) mod expr;
pub(crate) mod external;
pub(crate) mod internal;
pub(crate) mod maybe_text_codec;
pub(crate) mod plugin;
#[allow(unused_imports)]
pub(crate) use dynamic::Command as DynamicCommand;

View File

@ -1,30 +1,16 @@
use crate::commands::WholeStreamCommand;
use crate::commands::command::{whole_stream_command, WholeStreamCommand};
use crate::prelude::*;
use derive_new::new;
use log::trace;
use nu_errors::ShellError;
use nu_plugin::jsonrpc::JsonRpc;
use nu_protocol::{Primitive, ReturnValue, Signature, UntaggedValue, Value};
use serde::{self, Deserialize, Serialize};
use std::io::prelude::*;
use std::io::BufReader;
use std::io::Write;
#[derive(Debug, Serialize, Deserialize)]
pub struct JsonRpc<T> {
jsonrpc: String,
pub method: String,
pub params: T,
}
impl<T> JsonRpc<T> {
pub fn new<U: Into<String>>(method: U, params: T) -> Self {
JsonRpc {
jsonrpc: "2.0".into(),
method: method.into(),
params,
}
}
}
use std::path::Path;
use std::process::{Child, Command, Stdio};
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "method")]
@ -35,15 +21,77 @@ pub enum NuResult {
},
}
enum PluginCommand {
Filter(PluginFilter),
Sink(PluginSink),
}
impl PluginCommand {
fn command(self) -> Result<crate::commands::Command, ShellError> {
match self {
PluginCommand::Filter(cmd) => Ok(whole_stream_command(cmd)),
PluginCommand::Sink(cmd) => Ok(whole_stream_command(cmd)),
}
}
}
enum PluginMode {
Filter,
Sink,
}
pub struct PluginCommandBuilder {
mode: PluginMode,
name: String,
path: String,
config: Signature,
}
impl PluginCommandBuilder {
pub fn new(
name: impl Into<String>,
path: impl Into<String>,
config: impl Into<Signature>,
) -> Self {
let config = config.into();
PluginCommandBuilder {
mode: if config.is_filter {
PluginMode::Filter
} else {
PluginMode::Sink
},
name: name.into(),
path: path.into(),
config,
}
}
pub fn build(&self) -> Result<crate::commands::Command, ShellError> {
let mode = &self.mode;
let name = self.name.clone();
let path = self.path.clone();
let config = self.config.clone();
let cmd = match mode {
PluginMode::Filter => PluginCommand::Filter(PluginFilter { name, path, config }),
PluginMode::Sink => PluginCommand::Sink(PluginSink { name, path, config }),
};
cmd.command()
}
}
#[derive(new)]
pub struct PluginCommand {
pub struct PluginFilter {
name: String,
path: String,
config: Signature,
}
#[async_trait]
impl WholeStreamCommand for PluginCommand {
impl WholeStreamCommand for PluginFilter {
fn name(&self) -> &str {
&self.name
}
@ -61,11 +109,11 @@ impl WholeStreamCommand for PluginCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
filter_plugin(self.path.clone(), args, registry).await
run_filter(self.path.clone(), args, registry).await
}
}
pub async fn filter_plugin(
async fn run_filter(
path: String,
args: CommandArgs,
registry: &CommandRegistry,
@ -84,11 +132,34 @@ pub async fn filter_plugin(
let args = args.evaluate_once_with_scope(&registry, &scope).await?;
let mut child = std::process::Command::new(path)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()
.expect("Failed to spawn child process");
let real_path = Path::new(&path);
let ext = real_path.extension();
let ps1_file = match ext {
Some(ext) => ext == "ps1",
None => false,
};
let mut child: Child = if ps1_file {
Command::new("pwsh")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.args(&[
"-NoLogo",
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
&real_path.to_string_lossy(),
])
.spawn()
.expect("Failed to spawn PowerShell process")
} else {
Command::new(path)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Failed to spawn child process")
};
let call_info = args.call_info.clone();
@ -111,6 +182,7 @@ pub async fn filter_plugin(
let request = JsonRpc::new("begin_filter", call_info.clone());
let request_raw = serde_json::to_string(&request);
trace!("begin_filter:request {:?}", &request_raw);
match request_raw {
Err(_) => {
@ -136,6 +208,8 @@ pub async fn filter_plugin(
match reader.read_line(&mut input) {
Ok(_) => {
let response = serde_json::from_str::<NuResult>(&input);
trace!("begin_filter:response {:?}", &response);
match response {
Ok(NuResult::response { params }) => match params {
Ok(params) => futures::stream::iter(params).to_output_stream(),
@ -168,6 +242,7 @@ pub async fn filter_plugin(
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("end_filter", vec![]);
let request_raw = serde_json::to_string(&request);
trace!("end_filter:request {:?}", &request_raw);
match request_raw {
Err(_) => {
@ -193,6 +268,8 @@ pub async fn filter_plugin(
let stream = match reader.read_line(&mut input) {
Ok(_) => {
let response = serde_json::from_str::<NuResult>(&input);
trace!("end_filter:response {:?}", &response);
match response {
Ok(NuResult::response { params }) => match params {
Ok(params) => futures::stream::iter(params).to_output_stream(),
@ -220,6 +297,7 @@ pub async fn filter_plugin(
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("quit", vec![]);
let request_raw = serde_json::to_string(&request);
trace!("quit:request {:?}", &request_raw);
match request_raw {
Ok(request_raw) => {
@ -246,6 +324,8 @@ pub async fn filter_plugin(
let request = JsonRpc::new("filter", v);
let request_raw = serde_json::to_string(&request);
trace!("filter:request {:?}", &request_raw);
match request_raw {
Ok(request_raw) => {
let _ = stdin.write(format!("{}\n", request_raw).as_bytes());
@ -262,6 +342,8 @@ pub async fn filter_plugin(
match reader.read_line(&mut input) {
Ok(_) => {
let response = serde_json::from_str::<NuResult>(&input);
trace!("filter:response {:?}", &response);
match response {
Ok(NuResult::response { params }) => match params {
Ok(params) => futures::stream::iter(params).to_output_stream(),
@ -313,11 +395,11 @@ impl WholeStreamCommand for PluginSink {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
sink_plugin(self.path.clone(), args, registry).await
run_sink(self.path.clone(), args, registry).await
}
}
pub async fn sink_plugin(
async fn run_sink(
path: String,
args: CommandArgs,
registry: &CommandRegistry,
@ -335,7 +417,33 @@ pub async fn sink_plugin(
let _ = writeln!(tmpfile, "{}", request_raw);
let _ = tmpfile.flush();
let child = std::process::Command::new(path).arg(tmpfile.path()).spawn();
let real_path = Path::new(&path);
let ext = real_path.extension();
let ps1_file = match ext {
Some(ext) => ext == "ps1",
None => false,
};
// TODO: This sink may not work in powershell, trying to find
// an example of what CallInfo would look like in this temp file
let child = if ps1_file {
Command::new("pwsh")
.args(&[
"-NoLogo",
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
&real_path.to_string_lossy(),
&tmpfile
.path()
.to_str()
.expect("Failed getting tmpfile path"),
])
.spawn()
} else {
Command::new(path).arg(&tmpfile.path()).spawn()
};
if let Ok(mut child) = child {
let _ = child.wait();

View File

@ -262,10 +262,10 @@ impl EvaluatedCommandArgs {
/// Get the nth positional argument, error if not possible
pub fn expect_nth(&self, pos: usize) -> Result<&Value, ShellError> {
match self.call_info.args.nth(pos) {
None => Err(ShellError::unimplemented("Better error: expect_nth")),
Some(item) => Ok(item),
}
self.call_info
.args
.nth(pos)
.ok_or_else(|| ShellError::unimplemented("Better error: expect_nth"))
}
pub fn get(&self, name: &str) -> Option<&Value> {

View File

@ -49,7 +49,7 @@ impl WholeStreamCommand for Compact {
},
Example {
description: "Filter out all directory entries having no 'target'",
example: "ls -af | compact target",
example: "ls -la | compact target",
result: None,
},
]

View File

@ -45,7 +45,7 @@ pub async fn clear(
// NOTE: None because we are not loading a new config file, we just want to read from the
// existing config
let mut result = crate::data::config::read(name_span, &None)?;
let mut result = nu_data::config::read(name_span, &None)?;
result.clear();

View File

@ -27,7 +27,7 @@ impl WholeStreamCommand for Command {
) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone();
let name = args.call_info.name_tag;
let result = crate::data::config::read(name_span, &None)?;
let result = nu_data::config::read(name_span, &None)?;
Ok(futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),

View File

@ -56,7 +56,7 @@ pub async fn get(
// NOTE: None because we are not loading a new config file, we just want to read from the
// existing config
let result = crate::data::config::read(name_span, &None)?;
let result = nu_data::config::read(name_span, &None)?;
let key = get.to_string();
let value = result

View File

@ -50,7 +50,7 @@ pub async fn set(
let configuration = load.item().clone();
let result = crate::data::config::read(name_span, &Some(configuration))?;
let result = nu_data::config::read(name_span, &Some(configuration))?;
Ok(futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),

View File

@ -41,7 +41,7 @@ impl WholeStreamCommand for SubCommand {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Remove the startup commands",
example: "config --remove startup",
example: "config remove startup",
result: None,
}]
}
@ -54,7 +54,7 @@ pub async fn remove(
let name_span = args.call_info.name_tag.clone();
let (RemoveArgs { remove }, _) = args.process(&registry).await?;
let mut result = crate::data::config::read(name_span, &None)?;
let mut result = nu_data::config::read(name_span, &None)?;
let key = remove.to_string();

View File

@ -39,8 +39,8 @@ impl WholeStreamCommand for SubCommand {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Set completion_mode to circular",
example: "config set [completion_mode circular]",
description: "Set nonzero_exit_errors to true",
example: "config set nonzero_exit_errors $true",
result: None,
}]
}
@ -55,7 +55,7 @@ pub async fn set(
// NOTE: None because we are not loading a new config file, we just want to read from the
// existing config
let mut result = crate::data::config::read(name_span, &None)?;
let mut result = nu_data::config::read(name_span, &None)?;
result.insert(key.to_string(), value.clone());

View File

@ -58,9 +58,9 @@ pub async fn set_into(
// NOTE: None because we are not loading a new config file, we just want to read from the
// existing config
let mut result = crate::data::config::read(name_span, &None)?;
let mut result = nu_data::config::read(name_span, &None)?;
// In the original code, this is set to `Some` if the `--load flag is set`
// In the original code, this is set to `Some` if the `load flag is set`
let configuration = None;
let rows: Vec<Value> = input.collect().await;

View File

@ -7,6 +7,11 @@ use nu_protocol::{Signature, UntaggedValue, Value};
pub struct Count;
#[derive(Deserialize)]
pub struct CountArgs {
column: bool,
}
#[async_trait]
impl WholeStreamCommand for Count {
fn name(&self) -> &str {
@ -14,7 +19,11 @@ impl WholeStreamCommand for Count {
}
fn signature(&self) -> Signature {
Signature::build("count")
Signature::build("count").switch(
"column",
"Calculate number of columns in table",
Some('c'),
)
}
fn usage(&self) -> &str {
@ -24,22 +33,47 @@ impl WholeStreamCommand for Count {
async fn run(
&self,
args: CommandArgs,
_registry: &CommandRegistry,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let rows: Vec<Value> = args.input.collect().await;
let tag = args.call_info.name_tag.clone();
let (CountArgs { column }, input) = args.process(&registry).await?;
let rows: Vec<Value> = input.collect().await;
Ok(OutputStream::one(
UntaggedValue::int(rows.len()).into_value(name),
))
let count = if column {
if rows.is_empty() {
0
} else {
match &rows[0].value {
UntaggedValue::Row(dictionary) => dictionary.length(),
_ => {
return Err(ShellError::labeled_error(
"Cannot obtain column count",
"cannot obtain column count",
tag,
));
}
}
}
} else {
rows.len()
};
Ok(OutputStream::one(UntaggedValue::int(count).into_value(tag)))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Count the number of entries in a list",
example: "echo [1 2 3 4 5] | count",
result: Some(vec![UntaggedValue::int(5).into()]),
}]
vec![
Example {
description: "Count the number of entries in a list",
example: "echo [1 2 3 4 5] | count",
result: Some(vec![UntaggedValue::int(5).into()]),
},
Example {
description: "Count the number of columns in the calendar table",
example: "cal | count -c",
result: None,
},
]
}
}

View File

@ -1,175 +0,0 @@
use crate::prelude::*;
use chrono::{DateTime, Local, Utc};
use nu_errors::ShellError;
use nu_protocol::{Dictionary, Value};
use crate::commands::WholeStreamCommand;
use chrono::{Datelike, TimeZone, Timelike};
use core::fmt::Display;
use indexmap::IndexMap;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
pub struct Date;
#[async_trait]
impl WholeStreamCommand for Date {
fn name(&self) -> &str {
"date"
}
fn signature(&self) -> Signature {
Signature::build("date")
.switch("utc", "use universal time (UTC)", Some('u'))
.switch("local", "use the local time", Some('l'))
.named(
"format",
SyntaxShape::String,
"report datetime in supplied strftime format",
Some('f'),
)
.switch("raw", "print date without tables", Some('r'))
}
fn usage(&self) -> &str {
"Get the current datetime."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
date(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get the current local time and date",
example: "date",
result: None,
},
Example {
description: "Get the current UTC time and date",
example: "date --utc",
result: None,
},
Example {
description: "Get the current time and date and report it based on format",
example: "date --format '%Y-%m-%d %H:%M:%S.%f %z'",
result: None,
},
Example {
description: "Get the current time and date and report it without a table",
example: "date --format '%Y-%m-%d %H:%M:%S.%f %z' --raw",
result: None,
},
]
}
}
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)
}
pub async fn date(
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 raw = args.has("raw");
let dt_fmt = if args.has("format") {
if let Some(dt_fmt) = args.get("format") {
dt_fmt.convert_to_string()
} else {
"".to_string()
}
} else {
"".to_string()
};
let value = if args.has("utc") {
let utc: DateTime<Utc> = Utc::now();
if raw {
UntaggedValue::string(date_to_value_raw(utc, dt_fmt)).into_untagged_value()
} else {
date_to_value(utc, tag, dt_fmt)
}
} else {
let local: DateTime<Local> = Local::now();
if raw {
UntaggedValue::string(date_to_value_raw(local, dt_fmt)).into_untagged_value()
} else {
date_to_value(local, tag, dt_fmt)
}
};
Ok(OutputStream::one(value))
}
#[cfg(test)]
mod tests {
use super::Date;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Date {})
}
}

View File

@ -0,0 +1,46 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
pub struct Command;
#[async_trait]
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"date"
}
fn signature(&self) -> Signature {
Signature::build("date")
}
fn usage(&self) -> &str {
"Work with dates."
}
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;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Command {})
}
}

View File

@ -0,0 +1,63 @@
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 nu_source::Tagged;
pub struct Date;
#[derive(Deserialize)]
pub struct FormatArgs {
format: Tagged<String>,
raw: Option<bool>,
}
#[async_trait]
impl WholeStreamCommand for Date {
fn name(&self) -> &str {
"date format"
}
fn signature(&self) -> Signature {
Signature::build("date format")
.required("format", SyntaxShape::String, "strftime format")
.switch("raw", "print date without tables", Some('r'))
}
fn usage(&self) -> &str {
"format the current date using the given format string."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
format(args, registry).await
}
}
pub async fn format(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let tag = args.call_info.name_tag.clone();
let (FormatArgs { format, raw }, _) = args.process(&registry).await?;
let dt_fmt = format.to_string();
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)
}
};
Ok(OutputStream::one(value))
}

View File

@ -0,0 +1,11 @@
pub mod command;
pub mod format;
pub mod now;
pub mod utc;
mod utils;
pub use command::Command as Date;
pub use format::Date as DateFormat;
pub use now::Date as DateNow;
pub use utc::Date as DateUTC;

View File

@ -0,0 +1,50 @@
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;
pub struct Date;
#[async_trait]
impl WholeStreamCommand for Date {
fn name(&self) -> &str {
"date now"
}
fn signature(&self) -> Signature {
Signature::build("date now")
}
fn usage(&self) -> &str {
"return the current date."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
now(args, registry).await
}
}
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 value = {
let local: DateTime<Local> = Local::now();
date_to_value(local, tag, no_fmt)
};
Ok(OutputStream::one(value))
}

View File

@ -0,0 +1,50 @@
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))
}

View File

@ -0,0 +1,64 @@
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

@ -45,7 +45,7 @@ impl WholeStreamCommand for Default {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Give a default 'target' to all file entries",
example: "ls -af | default target 'nothing'",
example: "ls -la | default target 'nothing'",
result: None,
}]
}
@ -60,13 +60,13 @@ async fn default(
Ok(input
.map(move |item| {
let should_add = match item {
let should_add = matches!(
item,
Value {
value: UntaggedValue::Row(ref r),
..
} => r.get_data(&column.item).borrow().is_none(),
_ => false,
};
} if r.get_data(&column.item).borrow().is_none()
);
if should_add {
match item.insert_data_at_path(&column.item, value.clone()) {

View File

@ -2,7 +2,9 @@ use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{hir::Block, ReturnSuccess, Signature, SyntaxShape, Value};
use nu_protocol::{
hir::Block, hir::ExternalRedirection, ReturnSuccess, Signature, SyntaxShape, Value,
};
pub struct Do;
@ -61,7 +63,7 @@ async fn do_(
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let is_last = raw_args.call_info.args.is_last;
let external_redirection = raw_args.call_info.args.external_redirection;
let mut context = Context::from_raw(&raw_args, &registry);
let scope = raw_args.call_info.scope.clone();
@ -72,7 +74,26 @@ async fn do_(
},
input,
) = raw_args.process(&registry).await?;
block.set_is_last(is_last);
let block_redirection = match external_redirection {
ExternalRedirection::None => {
if ignore_errors {
ExternalRedirection::Stderr
} else {
ExternalRedirection::None
}
}
ExternalRedirection::Stdout => {
if ignore_errors {
ExternalRedirection::StdoutAndStderr
} else {
ExternalRedirection::Stdout
}
}
x => x,
};
block.set_redirect(block_redirection);
let result = run_block(
&block,
@ -85,6 +106,9 @@ async fn do_(
.await;
if ignore_errors {
// To properly ignore errors we need to redirect stderr, consume it, and remove
// any errors we see in the process.
match result {
Ok(mut stream) => {
let output = stream.drain_vec().await;

View File

@ -22,12 +22,12 @@ impl WholeStreamCommand for Drop {
Signature::build("drop").optional(
"rows",
SyntaxShape::Number,
"starting from the back, the number of rows to drop",
"starting from the back, the number of rows to remove",
)
}
fn usage(&self) -> &str {
"Drop the last number of rows."
"Remove the last number of rows. If you want to remove columns, try 'reject'."
}
async fn run(

View File

@ -105,6 +105,14 @@ pub async fn process_row(
.to_output_stream())
}
pub(crate) fn make_indexed_item(index: usize, item: Value) -> Value {
let mut dict = TaggedDictBuilder::new(item.tag());
dict.insert_untagged("index", UntaggedValue::int(index));
dict.insert_value("item", item);
dict.into_value()
}
async fn each(
raw_args: CommandArgs,
registry: &CommandRegistry,
@ -124,13 +132,10 @@ async fn each(
let scope = scope.clone();
let head = head.clone();
let context = context.clone();
let mut dict = TaggedDictBuilder::new(input.1.tag());
dict.insert_untagged("index", UntaggedValue::int(input.0));
dict.insert_value("item", input.1);
let row = make_indexed_item(input.0, input.1);
async {
match process_row(block, scope, head, context, dict.into_value()).await {
match process_row(block, scope, head, context, row).await {
Ok(s) => s,
Err(e) => OutputStream::one(Err(e)),
}

View File

@ -102,7 +102,7 @@ impl Iterator for RangeIterator {
if self.curr != self.end {
let output = UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone());
self.curr = match crate::data::value::compute_values(
self.curr = match nu_data::value::compute_values(
Operator::Plus,
&UntaggedValue::Primitive(self.curr.clone()),
&UntaggedValue::int(1),

View File

@ -3,6 +3,7 @@ use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::hir::ExternalRedirection;
use nu_protocol::{
CommandAction, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
@ -145,7 +146,7 @@ async fn enter(
positional: None,
named: None,
span: Span::unknown(),
is_last: false,
external_redirection: ExternalRedirection::Stdout,
},
name_tag: tag.clone(),
scope: scope.clone(),

View File

@ -1,7 +1,7 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::TaggedListBuilder;
use calamine::*;
use nu_data::TaggedListBuilder;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
use std::io::Cursor;

View File

@ -1,7 +1,7 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::TaggedListBuilder;
use calamine::*;
use nu_data::TaggedListBuilder;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
use std::io::Cursor;

View File

@ -135,7 +135,6 @@ async fn from_xml(
#[cfg(test)]
mod tests {
use crate::commands::from_xml;
use indexmap::IndexMap;
use nu_protocol::{UntaggedValue, Value};

View File

@ -7,7 +7,7 @@ use nu_protocol::{
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape,
UnspannedPathMember, UntaggedValue, Value,
};
use nu_source::span_for_spanned_list;
use nu_source::HasFallibleSpan;
use nu_value_ext::get_data_by_column_path;
pub struct Get;
@ -65,7 +65,7 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
obj,
path,
Box::new(move |(obj_source, column_path_tried, error)| {
let path_members_span = span_for_spanned_list(fields.members().iter().map(|p| p.span));
let path_members_span = fields.maybe_span().unwrap_or_else(Span::unknown);
match &obj_source.value {
UntaggedValue::Table(rows) => match column_path_tried {

View File

@ -169,7 +169,7 @@ pub async fn group_by(
let values = UntaggedValue::table(&values).into_value(&name);
match group_strategy {
let group_value = match group_strategy {
Grouper::ByBlock => {
let map = keys.clone();
@ -179,16 +179,12 @@ pub async fn group_by(
None => as_string(row),
});
match crate::utils::data::group(&values, &Some(block), &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(reason) => Err(reason),
}
nu_data::utils::group(&values, &Some(block), &name)
}
Grouper::ByColumn(column_name) => match group(&column_name, &values, name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(reason) => Err(reason),
},
}
Grouper::ByColumn(column_name) => group(&column_name, &values, name),
};
Ok(OutputStream::one(ReturnSuccess::value(group_value?)))
}
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
@ -238,15 +234,12 @@ pub fn group(
}
});
crate::utils::data::group(&values, &Some(block), &name)
nu_data::utils::group(&values, &Some(block), &name)
}
Grouper::ByColumn(None) => {
let block = Box::new(move |_, row: &Value| match as_string(row) {
Ok(group_key) => Ok(group_key),
Err(reason) => Err(reason),
});
let block = Box::new(move |_, row: &Value| as_string(row));
crate::utils::data::group(&values, &Some(block), &name)
nu_data::utils::group(&values, &Some(block), &name)
}
Grouper::ByBlock => Err(ShellError::unimplemented(
"Block not implemented: This should never happen.",
@ -257,77 +250,32 @@ pub fn group(
#[cfg(test)]
mod tests {
use super::group;
use indexmap::IndexMap;
use nu_data::utils::helpers::{committers, date, int, row, string, table};
use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value};
use nu_source::*;
fn string(input: impl Into<String>) -> Value {
UntaggedValue::string(input.into()).into_untagged_value()
}
fn row(entries: IndexMap<String, Value>) -> Value {
UntaggedValue::row(entries).into_untagged_value()
}
fn table(list: &[Value]) -> Value {
UntaggedValue::table(list).into_untagged_value()
}
fn nu_releases_committers() -> Vec<Value> {
vec![
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")},
),
row(
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")},
),
row(
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")},
),
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("Sept 24-2019")},
),
row(
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")},
),
row(
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("Sept 24-2019")},
),
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")},
),
row(
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("Sept 24-2019")},
),
row(
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")},
),
]
}
#[test]
fn groups_table_by_date_column() -> Result<(), ShellError> {
let for_key = Some(String::from("date").tagged_unknown());
let sample = table(&nu_releases_committers());
let sample = table(&committers());
assert_eq!(
group(&for_key, &sample, Tag::unknown())?,
row(indexmap! {
"August 23-2019".into() => table(&[
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}),
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")}),
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")})
"2019-07-23".into() => table(&[
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => date("2019-07-23"), "chickens".into() => int(10) }),
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => date("2019-07-23"), "chickens".into() => int(5) }),
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => date("2019-07-23"), "chickens".into() => int(2) })
]),
"October 10-2019".into() => table(&[
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")}),
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")}),
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")})
"2019-10-10".into() => table(&[
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => date("2019-10-10"), "chickens".into() => int(6) }),
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => date("2019-10-10"), "chickens".into() => int(15) }),
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => date("2019-10-10"), "chickens".into() => int(30) })
]),
"Sept 24-2019".into() => table(&[
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("Sept 24-2019")}),
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("Sept 24-2019")}),
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("Sept 24-2019")})
"2019-09-24".into() => table(&[
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => date("2019-09-24"), "chickens".into() => int(20) }),
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => date("2019-09-24"), "chickens".into() => int(4) }),
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => date("2019-09-24"), "chickens".into() => int(10) })
]),
})
);
@ -338,25 +286,25 @@ mod tests {
#[test]
fn groups_table_by_country_column() -> Result<(), ShellError> {
let for_key = Some(String::from("country").tagged_unknown());
let sample = table(&nu_releases_committers());
let sample = table(&committers());
assert_eq!(
group(&for_key, &sample, Tag::unknown())?,
row(indexmap! {
"EC".into() => table(&[
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}),
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("Sept 24-2019")}),
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")})
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => date("2019-07-23"), "chickens".into() => int(10) }),
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => date("2019-09-24"), "chickens".into() => int(20) }),
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => date("2019-10-10"), "chickens".into() => int(30) })
]),
"NZ".into() => table(&[
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")}),
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")}),
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("Sept 24-2019")})
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => date("2019-07-23"), "chickens".into() => int(5) }),
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => date("2019-10-10"), "chickens".into() => int(15) }),
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => date("2019-09-24"), "chickens".into() => int(10) })
]),
"US".into() => table(&[
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")}),
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("Sept 24-2019")}),
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")}),
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => date("2019-10-10"), "chickens".into() => int(6) }),
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => date("2019-09-24"), "chickens".into() => int(4) }),
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => date("2019-07-23"), "chickens".into() => int(2) }),
]),
})
);

View File

@ -100,38 +100,36 @@ pub async fn group_by_date(
let value_result = match (grouper_date, grouper_column) {
(Grouper::ByDate(None), GroupByColumn::Name(None)) => {
let block = Box::new(move |_, row: &Value| row.format("%Y-%b-%d"));
let block = Box::new(move |_, row: &Value| row.format("%Y-%m-%d"));
crate::utils::data::group(&values, &Some(block), &name)
nu_data::utils::group(&values, &Some(block), &name)
}
(Grouper::ByDate(None), GroupByColumn::Name(Some(column_name))) => {
let block = Box::new(move |_, row: &Value| {
let group_key = match row.get_data_by_key(column_name.borrow_spanned()) {
Some(group_key) => Ok(group_key),
None => Err(suggestions(column_name.borrow_tagged(), &row)),
};
let group_key = row
.get_data_by_key(column_name.borrow_spanned())
.ok_or_else(|| suggestions(column_name.borrow_tagged(), &row));
group_key?.format("%Y-%b-%d")
group_key?.format("%Y-%m-%d")
});
crate::utils::data::group(&values, &Some(block), &name)
nu_data::utils::group(&values, &Some(block), &name)
}
(Grouper::ByDate(Some(fmt)), GroupByColumn::Name(None)) => {
let block = Box::new(move |_, row: &Value| row.format(&fmt));
crate::utils::data::group(&values, &Some(block), &name)
nu_data::utils::group(&values, &Some(block), &name)
}
(Grouper::ByDate(Some(fmt)), GroupByColumn::Name(Some(column_name))) => {
let block = Box::new(move |_, row: &Value| {
let group_key = match row.get_data_by_key(column_name.borrow_spanned()) {
Some(group_key) => Ok(group_key),
None => Err(suggestions(column_name.borrow_tagged(), &row)),
};
let group_key = row
.get_data_by_key(column_name.borrow_spanned())
.ok_or_else(|| suggestions(column_name.borrow_tagged(), &row));
group_key?.format(&fmt)
});
crate::utils::data::group(&values, &Some(block), &name)
nu_data::utils::group(&values, &Some(block), &name)
}
};

View File

@ -5,7 +5,7 @@ use futures::stream::StreamExt;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::Dictionary;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
pub struct Headers;
@ -32,11 +32,18 @@ impl WholeStreamCommand for Headers {
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Create headers for a raw string",
example: r#"echo "a b c|1 2 3" | split row "|" | split column " " | headers"#,
result: None,
}]
vec![
Example {
description: "Create headers for a raw string",
example: r#"echo "a b c|1 2 3" | split row "|" | split column " " | headers"#,
result: None,
},
Example {
description: "Don't panic on rows with different headers",
example: r#"echo "a b c|1 2 3|1 2 3 4" | split row "|" | split column " " | headers"#,
result: None,
},
]
}
}
@ -84,8 +91,13 @@ pub async fn headers(
match &r.value {
UntaggedValue::Row(d) => {
let mut entries = IndexMap::new();
for (i, (_, v)) in d.entries.iter().enumerate() {
entries.insert(headers[i].clone(), v.clone());
for (i, header) in headers.iter().enumerate() {
let value = match d.entries.get_index(i) {
Some((_, value)) => value.clone(),
None => UntaggedValue::Primitive(Primitive::Nothing).into(),
};
entries.insert(header.clone(), value);
}
Ok(ReturnSuccess::Value(
UntaggedValue::Row(Dictionary { entries }).into_value(r.tag.clone()),

View File

@ -1,10 +1,10 @@
use crate::commands::command::Command;
use crate::commands::WholeStreamCommand;
use crate::data::command_dict;
use crate::documentation::{generate_docs, get_documentation, DocumentationConfig};
use crate::prelude::*;
use nu_data::command::signature_dict;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue};
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::{SpannedItem, Tagged};
use nu_value_ext::get_data_by_key;
@ -38,6 +38,21 @@ impl WholeStreamCommand for Help {
}
}
pub(crate) fn command_dict(command: Command, tag: impl Into<Tag>) -> Value {
let tag = tag.into();
let mut cmd_dict = TaggedDictBuilder::new(&tag);
cmd_dict.insert_untagged("name", UntaggedValue::string(command.name()));
cmd_dict.insert_untagged("type", UntaggedValue::string("Command"));
cmd_dict.insert_value("signature", signature_dict(command.signature(), tag));
cmd_dict.insert_untagged("usage", UntaggedValue::string(command.usage()));
cmd_dict.into_value()
}
async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();

View File

@ -1,22 +1,13 @@
use crate::commands::group_by::group;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{columns_sorted, evaluate, map_max, reduce, t_sort};
use nu_errors::ShellError;
use nu_protocol::{
Primitive, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
ColumnPath, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
};
use nu_source::Tagged;
use num_traits::{ToPrimitive, Zero};
pub struct Histogram;
#[derive(Deserialize)]
pub struct HistogramArgs {
column_name: Tagged<String>,
rest: Vec<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for Histogram {
fn name(&self) -> &str {
@ -25,13 +16,14 @@ impl WholeStreamCommand for Histogram {
fn signature(&self) -> Signature {
Signature::build("histogram")
.required(
"column_name",
SyntaxShape::String,
"the name of the column to graph by",
.named(
"use",
SyntaxShape::ColumnPath,
"Use data at the column path given as valuator",
None,
)
.rest(
SyntaxShape::String,
SyntaxShape::ColumnPath,
"column name to give the histogram's frequency column",
)
}
@ -57,8 +49,8 @@ impl WholeStreamCommand for Histogram {
},
Example {
description:
"Get a histogram for the types of files, with frequency column named count",
example: "ls | histogram type count",
"Get a histogram for the types of files, with frequency column named percentage",
example: "ls | histogram type percentage",
result: None,
},
Example {
@ -76,181 +68,147 @@ pub async fn histogram(
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let (input, args) = args.evaluate_once(&registry).await?.parts();
let (HistogramArgs { column_name, rest }, input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
let values = UntaggedValue::table(&values).into_value(&name);
let groups = group(&Some(column_name.clone()), &values, &name)?;
let group_labels = columns_sorted(Some(column_name.clone()), &groups, &name);
let sorted = t_sort(Some(column_name.clone()), None, &groups, &name)?;
let evaled = evaluate(&sorted, None, &name)?;
let reduced = reduce(&evaled, None, &name)?;
let maxima = map_max(&reduced, None, &name)?;
let percents = percentages(&reduced, maxima, &name)?;
let mut columns = args
.positional_iter()
.map(|c| c.as_column_path())
.filter_map(Result::ok)
.collect::<Vec<_>>();
match percents {
Value {
value: UntaggedValue::Table(datasets),
..
} => {
let mut idx = 0;
let column_names_supplied: Vec<_> = rest.iter().map(|f| f.item.clone()).collect();
let frequency_column_name = if column_names_supplied.is_empty() {
"frequency".to_string()
} else {
column_names_supplied[0].clone()
};
let column = (*column_name).clone();
let count_column_name = "count".to_string();
let count_shell_error = ShellError::labeled_error(
"Unable to load group count",
"unabled to load group count",
&name,
);
let mut count_values: Vec<u64> = Vec::new();
for table_entry in reduced.table_entries() {
match table_entry {
Value {
value: UntaggedValue::Table(list),
..
} => {
for i in list {
if let Ok(count) = i.value.clone().into_value(&name).as_u64() {
count_values.push(count);
} else {
return Err(count_shell_error);
}
}
}
_ => {
return Err(count_shell_error);
}
}
}
if let Value {
value: UntaggedValue::Table(start),
..
} = datasets.get(0).ok_or_else(|| {
ShellError::labeled_error(
"Unable to load dataset",
"unabled to load dataset",
&name,
)
})? {
let start = start.clone();
Ok(
futures::stream::iter(start.into_iter().map(move |percentage| {
let mut fact = TaggedDictBuilder::new(&name);
let value: Tagged<String> = group_labels
.get(idx)
.ok_or_else(|| {
ShellError::labeled_error(
"Unable to load group labels",
"unabled to load group labels",
&name,
)
})?
.clone();
fact.insert_value(
&column,
UntaggedValue::string(value.item).into_value(value.tag),
);
fact.insert_untagged(
&count_column_name,
UntaggedValue::int(count_values[idx]),
);
if let Value {
value: UntaggedValue::Primitive(Primitive::Int(ref num)),
ref tag,
} = percentage
{
let string = std::iter::repeat("*")
.take(num.to_i32().ok_or_else(|| {
ShellError::labeled_error(
"Expected a number",
"expected a number",
tag,
)
})? as usize)
.collect::<String>();
fact.insert_untagged(
&frequency_column_name,
UntaggedValue::string(string),
);
}
idx += 1;
ReturnSuccess::value(fact.into_value())
}))
.to_output_stream(),
)
} else {
Ok(OutputStream::empty())
}
}
_ => Ok(OutputStream::empty()),
}
}
fn percentages(values: &Value, max: Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let tag = tag.into();
let results: Value = match values {
Value {
value: UntaggedValue::Table(datasets),
..
} => {
let datasets: Vec<_> = datasets
.iter()
.map(|subsets| match subsets {
Value {
value: UntaggedValue::Table(data),
..
} => {
let data = data
.iter()
.map(|d| match d {
Value {
value: UntaggedValue::Primitive(Primitive::Int(n)),
..
} => {
let max = match &max {
Value {
value: UntaggedValue::Primitive(Primitive::Int(maxima)),
..
} => maxima.clone(),
_ => Zero::zero(),
};
let n = (n * 100) / max;
UntaggedValue::int(n).into_value(&tag)
}
_ => UntaggedValue::int(0).into_value(&tag),
})
.collect::<Vec<_>>();
UntaggedValue::Table(data).into_value(&tag)
}
_ => UntaggedValue::Table(vec![]).into_value(&tag),
})
.collect();
UntaggedValue::Table(datasets).into_value(&tag)
}
other => other.clone(),
let evaluate_with = if let Some(path) = args.get("use") {
Some(evaluator(path.as_column_path()?.item))
} else {
None
};
Ok(results)
let column_grouper = if !columns.is_empty() {
match columns.remove(0).split_last() {
Some((key, _)) => Some(key.as_string().tagged(&name)),
None => None,
}
} else {
None
};
let frequency_column_name = if columns.is_empty() {
"frequency".to_string()
} else if let Some((key, _)) = columns[0].split_last() {
key.as_string()
} else {
"frecuency".to_string()
};
let column = if let Some(ref column) = column_grouper {
column.clone()
} else {
"value".to_string().tagged(&name)
};
let results = nu_data::utils::report(
&UntaggedValue::table(&values).into_value(&name),
nu_data::utils::Operation {
grouper: Some(Box::new(move |_, _| Ok(String::from("frequencies")))),
splitter: Some(splitter(column_grouper)),
format: None,
eval: &evaluate_with,
},
&name,
)?;
let labels = results.labels.y.clone();
let mut idx = 0;
Ok(futures::stream::iter(
results
.percentages
.table_entries()
.map(move |value| {
let values = value.table_entries().cloned().collect::<Vec<_>>();
let occurrences = values.len();
(occurrences, values[occurrences - 1].clone())
})
.collect::<Vec<_>>()
.into_iter()
.map(move |(occurrences, value)| {
let mut fact = TaggedDictBuilder::new(&name);
let column_value = labels
.get(idx)
.ok_or_else(|| {
ShellError::labeled_error(
"Unable to load group labels",
"unabled to load group labels",
&name,
)
})?
.clone();
fact.insert_value(&column.item, column_value);
fact.insert_untagged("occurrences", UntaggedValue::int(occurrences));
let percentage = format!(
"{}%",
// Some(2) < the number of digits
// true < group the digits
crate::commands::str_::from::action(&value, &name, Some(2), true)?
.as_string()?
);
fact.insert_untagged("percentage", UntaggedValue::string(percentage));
let string = std::iter::repeat("*")
.take(value.as_u64().map_err(|_| {
ShellError::labeled_error("expected a number", "expected a number", &name)
})? as usize)
.collect::<String>();
fact.insert_untagged(&frequency_column_name, UntaggedValue::string(string));
idx += 1;
ReturnSuccess::value(fact.into_value())
}),
)
.to_output_stream())
}
fn evaluator(by: ColumnPath) -> Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send> {
Box::new(move |_: usize, value: &Value| {
let path = by.clone();
let eval = nu_value_ext::get_data_by_column_path(
value,
&path,
Box::new(move |(_, _, error)| error),
);
match eval {
Ok(with_value) => Ok(with_value),
Err(reason) => Err(reason),
}
})
}
fn splitter(
by: Option<Tagged<String>>,
) -> Box<dyn Fn(usize, &Value) -> Result<String, ShellError> + Send> {
match by {
Some(column) => Box::new(move |_, row: &Value| {
let key = &column;
match row.get_data_by_key(key.borrow_spanned()) {
Some(key) => nu_value_ext::as_string(&key),
None => Err(ShellError::labeled_error(
"unknown column",
"unknown column",
key.tag(),
)),
}
}),
None => Box::new(move |_, row: &Value| nu_value_ext::as_string(&row)),
}
}
#[cfg(test)]

View File

@ -1,10 +1,32 @@
use crate::cli::History as HistoryFile;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_data::config::NuConfig;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
const DEFAULT_LOCATION: &str = "history.txt";
pub fn history_path(config: &NuConfig) -> PathBuf {
let vars = config.vars.lock();
let default_path = nu_data::config::user_data()
.map(|mut p| {
p.push(DEFAULT_LOCATION);
p
})
.unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION));
vars.get("history-path")
.map_or(default_path.clone(), |custom_path| {
match custom_path.as_string() {
Ok(path) => PathBuf::from(path),
Err(_) => default_path,
}
})
}
pub struct History;
@ -32,9 +54,10 @@ impl WholeStreamCommand for History {
}
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let config = NuConfig::new();
let tag = args.call_info.name_tag;
let history_path = HistoryFile::path();
let file = File::open(history_path);
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 {

View File

@ -1,10 +1,14 @@
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{
ColumnPath, Primitive, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_value_ext::ValueExt;
use futures::stream::once;
pub struct Insert;
#[derive(Deserialize)]
@ -26,11 +30,7 @@ impl WholeStreamCommand for Insert {
SyntaxShape::ColumnPath,
"the column name to insert",
)
.required(
"value",
SyntaxShape::String,
"the value to give the cell(s)",
)
.required("value", SyntaxShape::Any, "the value to give the cell(s)")
}
fn usage(&self) -> &str {
@ -46,26 +46,116 @@ impl WholeStreamCommand for Insert {
}
}
async fn insert(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
async fn process_row(
scope: Arc<Scope>,
mut context: Arc<Context>,
input: Value,
mut value: Arc<Value>,
field: Arc<ColumnPath>,
) -> Result<OutputStream, ShellError> {
let value = Arc::make_mut(&mut value);
let (InsertArgs { column, value }, input) = args.process(&registry).await?;
Ok(match value {
Value {
value: UntaggedValue::Block(block),
tag: block_tag,
} => {
let for_block = input.clone();
let input_stream = once(async { Ok(for_block) }).to_input_stream();
let result = run_block(
&block,
Arc::make_mut(&mut context),
input_stream,
&input,
&scope.vars,
&scope.env,
)
.await;
match result {
Ok(mut stream) => {
let values = stream.drain_vec().await;
let errors = context.get_errors();
if let Some(error) = errors.first() {
return Err(error.clone());
}
let result = if values.len() == 1 {
let value = values
.get(0)
.ok_or_else(|| ShellError::unexpected("No value to insert with"))?;
value.clone()
} else if values.is_empty() {
UntaggedValue::nothing().into_untagged_value()
} else {
UntaggedValue::table(&values).into_untagged_value()
};
match input {
obj
@
Value {
value: UntaggedValue::Row(_),
..
} => match obj.insert_data_at_column_path(&field, result) {
Ok(v) => OutputStream::one(ReturnSuccess::value(v)),
Err(e) => OutputStream::one(Err(e)),
},
_ => OutputStream::one(Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
block_tag.clone(),
))),
}
}
Err(e) => OutputStream::one(Err(e)),
}
}
value => match input {
Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
..
} => match scope.it.insert_data_at_column_path(&field, value.clone()) {
Ok(v) => OutputStream::one(ReturnSuccess::value(v)),
Err(e) => OutputStream::one(Err(e)),
},
_ => match input.insert_data_at_column_path(&field, value.clone()) {
Ok(v) => OutputStream::one(ReturnSuccess::value(v)),
Err(e) => OutputStream::one(Err(e)),
},
},
})
}
async fn insert(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = Arc::new(raw_args.call_info.scope.clone());
let context = Arc::new(Context::from_raw(&raw_args, &registry));
let (InsertArgs { column, value }, input) = raw_args.process(&registry).await?;
let value = Arc::new(value);
let column = Arc::new(column);
Ok(input
.map(move |row| match row {
Value {
value: UntaggedValue::Row(_),
..
} => Ok(ReturnSuccess::Value(
row.insert_data_at_column_path(&column, value.clone())?,
)),
.then(move |input| {
let scope = scope.clone();
let context = context.clone();
let value = value.clone();
let column = column.clone();
Value { tag, .. } => Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
tag,
)),
async {
match process_row(scope, context, input, value, column).await {
Ok(s) => s,
Err(e) => OutputStream::one(Err(e)),
}
}
})
.flatten()
.to_output_stream())
}

View File

@ -0,0 +1,88 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use num_bigint::ToBigInt;
pub struct IntoInt;
#[derive(Deserialize)]
pub struct IntoIntArgs {
pub rest: Vec<Value>,
}
#[async_trait]
impl WholeStreamCommand for IntoInt {
fn name(&self) -> &str {
"into-int"
}
fn signature(&self) -> Signature {
Signature::build("into-int").rest(SyntaxShape::Any, "the values to into-int")
}
fn usage(&self) -> &str {
"Convert value to integer"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
into_int(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Convert filesize to integer",
example: "echo 1kb | into-int $it | = $it / 1024",
result: Some(vec![UntaggedValue::int(1).into()]),
}]
}
}
async fn into_int(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (args, _): (IntoIntArgs, _) = args.process(&registry).await?;
let stream = args.rest.into_iter().map(|i| match i {
Value {
value: UntaggedValue::Primitive(primitive_val),
tag,
} => match primitive_val {
Primitive::Filesize(size) => OutputStream::one(Ok(ReturnSuccess::Value(Value {
value: UntaggedValue::int(size.to_bigint().expect("Conversion should never fail.")),
tag,
}))),
Primitive::Int(_) => OutputStream::one(Ok(ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(primitive_val),
tag,
}))),
_ => OutputStream::one(Err(ShellError::labeled_error(
"Could not convert int value",
"original value",
tag,
))),
},
_ => OutputStream::one(Ok(ReturnSuccess::Value(i))),
});
Ok(futures::stream::iter(stream).flatten().to_output_stream())
}
#[cfg(test)]
mod tests {
use super::IntoInt;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(IntoInt {})
}
}

View File

@ -106,14 +106,14 @@ async fn is_empty(
..
} => {
if val.is_empty() {
match obj.replace_data_at_column_path(&field, default.clone()) {
Some(v) => Ok(v),
None => Err(ShellError::labeled_error(
"empty? could not find place to check emptiness",
"column name",
&field.tag,
)),
}
obj.replace_data_at_column_path(&field, default.clone())
.ok_or_else(|| {
ShellError::labeled_error(
"empty? could not find place to check emptiness",
"column name",
&field.tag,
)
})
} else {
Ok(obj)
}

View File

@ -11,11 +11,9 @@ pub struct Ls;
pub struct LsArgs {
pub path: Option<Tagged<PathBuf>>,
pub all: bool,
pub full: bool,
pub long: bool,
#[serde(rename = "short-names")]
pub short_names: bool,
#[serde(rename = "with-symlink-targets")]
pub with_symlink_targets: bool,
#[serde(rename = "du")]
pub du: bool,
}
@ -33,25 +31,20 @@ impl WholeStreamCommand for Ls {
SyntaxShape::Pattern,
"a path to get the directory contents from",
)
.switch("all", "also show hidden files", Some('a'))
.switch("all", "Show hidden files", Some('a'))
.switch(
"full",
"list all available columns for each entry",
Some('f'),
"long",
"List all available columns for each entry",
Some('l'),
)
.switch(
"short-names",
"only print the file names and not the path",
"Only print the file names and not the path",
Some('s'),
)
.switch(
"with-symlink-targets",
"display the paths to the target files that symlinks point to",
Some('w'),
)
.switch(
"du",
"display the apparent directory size in place of the directory metadata size",
"Display the apparent directory size in place of the directory metadata size",
Some('d'),
)
}

View File

@ -266,7 +266,7 @@ macro_rules! command {
Extract {
$($extract:tt)* {
use $crate::data::types::ExtractType;
use $nu_data::types::ExtractType;
let value = $args.expect_nth($($positional_count)*)?;
Block::extract(value)?
}
@ -321,7 +321,7 @@ macro_rules! command {
Extract {
$($extract:tt)* {
use $crate::data::types::ExtractType;
use $nu_data::types::ExtractType;
let value = $args.expect_nth($($positional_count)*)?;
<$param_kind>::extract(&value)?
}

View File

@ -1,14 +1,17 @@
use crate::prelude::*;
use crate::commands::math::reducers::{reducer_for, Reduce};
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use bigdecimal::{FromPrimitive, Zero};
use nu_errors::ShellError;
use nu_protocol::{
hir::{convert_number_to_u64, Number, Operator},
Primitive, Signature, UntaggedValue, Value,
};
use bigdecimal::FromPrimitive;
pub struct SubCommand;
#[async_trait]
@ -55,19 +58,59 @@ impl WholeStreamCommand for SubCommand {
}
}
fn to_byte(value: &Value) -> Option<Value> {
match &value.value {
UntaggedValue::Primitive(Primitive::Int(num)) => Some(
UntaggedValue::Primitive(Primitive::Filesize(convert_number_to_u64(&Number::Int(
num.clone(),
))))
.into_untagged_value(),
),
_ => None,
}
}
pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let sum = reducer_for(Reduce::Summation);
let number = BigDecimal::from_usize(values.len()).ok_or_else(|| {
ShellError::labeled_error(
"could not convert to big decimal",
"could not convert to big decimal",
&name.span,
)
ShellError::labeled_error("nothing to average", "nothing to average", &name.span)
})?;
let total_rows = UntaggedValue::decimal(number);
let total = sum(Value::zero(), values.to_vec())?;
let are_bytes = values
.get(0)
.ok_or_else(|| {
ShellError::unexpected("Cannot perform aggregate math operation on empty data")
})?
.is_filesize();
let total = if are_bytes {
to_byte(&sum(
UntaggedValue::int(0).into_untagged_value(),
values
.to_vec()
.iter()
.map(|v| match v {
Value {
value: UntaggedValue::Primitive(Primitive::Filesize(num)),
..
} => UntaggedValue::int(*num as usize).into_untagged_value(),
other => other.clone(),
})
.collect::<Vec<_>>(),
)?)
.ok_or_else(|| {
ShellError::labeled_error(
"could not convert to big decimal",
"could not convert to big decimal",
&name.span,
)
})
} else {
sum(UntaggedValue::int(0).into_untagged_value(), values.to_vec())
}?;
match total {
Value {
@ -75,7 +118,7 @@ pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
..
} => {
let left = UntaggedValue::from(Primitive::Int(num.into()));
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => {
@ -99,7 +142,7 @@ pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
..
} => {
let left = UntaggedValue::from(other);
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(value) => Ok(value.into_value(name)),

View File

@ -1,7 +1,7 @@
use crate::commands::math::reducers::{reducer_for, Reduce};
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use nu_errors::ShellError;
use nu_protocol::{Signature, UntaggedValue, Value};

View File

@ -1,8 +1,8 @@
use crate::commands::math::reducers::{reducer_for, Reduce};
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use bigdecimal::{FromPrimitive, Zero};
use bigdecimal::FromPrimitive;
use nu_errors::ShellError;
use nu_protocol::{
hir::{convert_number_to_u64, Number, Operator},
@ -130,7 +130,7 @@ fn compute_average(values: &[Value], name: impl Into<Tag>) -> Result<Value, Shel
)
})?;
let total_rows = UntaggedValue::decimal(number);
let total = sum(Value::zero(), values.to_vec())?;
let total = sum(UntaggedValue::int(0).into_untagged_value(), values.to_vec())?;
match total {
Value {
@ -138,7 +138,7 @@ fn compute_average(values: &[Value], name: impl Into<Tag>) -> Result<Value, Shel
..
} => {
let left = UntaggedValue::from(Primitive::Int(num.into()));
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => {
@ -162,7 +162,7 @@ fn compute_average(values: &[Value], name: impl Into<Tag>) -> Result<Value, Shel
..
} => {
let left = UntaggedValue::from(other);
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(value) => Ok(value.into_value(name)),

View File

@ -1,7 +1,7 @@
use crate::commands::math::reducers::{reducer_for, Reduce};
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use nu_errors::ShellError;
use nu_protocol::{Signature, UntaggedValue, Value};

View File

@ -5,11 +5,14 @@ pub mod max;
pub mod median;
pub mod min;
pub mod mode;
pub mod product;
pub mod stddev;
pub mod sum;
pub mod utils;
pub mod variance;
mod reducers;
mod utils;
pub use avg::SubCommand as MathAverage;
pub use command::Command as Math;
pub use eval::SubCommand as MathEval;
@ -17,6 +20,7 @@ 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 stddev::SubCommand as MathStddev;
pub use sum::SubCommand as MathSummation;
pub use variance::SubCommand as MathVariance;

View File

@ -0,0 +1,126 @@
use crate::commands::math::reducers::{reducer_for, Reduce};
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
hir::{convert_number_to_u64, Number},
Primitive, Signature, UntaggedValue, Value,
};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math product"
}
fn signature(&self) -> Signature {
Signature::build("math product")
}
fn usage(&self) -> &str {
"Finds the product of a list of numbers or tables"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_function(
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,
},
product,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the product of a list of numbers",
example: "echo [2 3 3 4] | math product",
result: Some(vec![UntaggedValue::int(72).into()]),
}]
}
}
fn to_byte(value: &Value) -> Option<Value> {
match &value.value {
UntaggedValue::Primitive(Primitive::Int(num)) => Some(
UntaggedValue::Primitive(Primitive::Filesize(convert_number_to_u64(&Number::Int(
num.clone(),
))))
.into_untagged_value(),
),
_ => None,
}
}
/// Calculate product of given values
pub fn product(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let prod = reducer_for(Reduce::Product);
let first = values.get(0).ok_or_else(|| {
ShellError::unexpected("Cannot perform aggregate math operation on empty data")
})?;
match first {
v if v.is_filesize() => to_byte(&prod(
UntaggedValue::int(1).into_untagged_value(),
values
.iter()
.map(|v| match v {
Value {
value: UntaggedValue::Primitive(Primitive::Filesize(num)),
..
} => UntaggedValue::int(*num as usize).into_untagged_value(),
other => other.clone(),
})
.collect::<Vec<_>>(),
)?)
.ok_or_else(|| {
ShellError::labeled_error(
"could not convert to decimal",
"could not convert to decimal",
&name.span,
)
}),
v if v.is_none() => prod(
UntaggedValue::int(1).into_untagged_value(),
values
.iter()
.map(|v| match v {
Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
..
} => UntaggedValue::int(1).into_untagged_value(),
other => other.clone(),
})
.collect::<Vec<_>>(),
),
_ => prod(UntaggedValue::int(1).into_untagged_value(), values.to_vec()),
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,168 @@
use nu_data::value::{compare_values, compute_values};
use nu_errors::ShellError;
use nu_protocol::hir::Operator;
use nu_protocol::{UntaggedValue, Value};
use nu_source::{SpannedItem, Tag};
// Re-usable error messages
const ERR_EMPTY_DATA: &str = "Cannot perform aggregate math operation on empty data";
fn formula(
acc_begin: Value,
calculator: Box<dyn Fn(Vec<Value>) -> Result<Value, ShellError> + Send + Sync + 'static>,
) -> Box<dyn Fn(Value, Vec<Value>) -> Result<Value, ShellError> + Send + Sync + 'static> {
Box::new(move |acc, datax| -> Result<Value, ShellError> {
let result = match compute_values(Operator::Multiply, &acc, &acc_begin) {
Ok(v) => v.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned_unknown(),
right_type.spanned_unknown(),
))
}
};
match calculator(datax) {
Ok(total) => Ok(match compute_values(Operator::Plus, &result, &total) {
Ok(v) => v.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned_unknown(),
right_type.spanned_unknown(),
))
}
}),
Err(reason) => Err(reason),
}
})
}
pub fn reducer_for(
command: Reduce,
) -> Box<dyn Fn(Value, Vec<Value>) -> Result<Value, ShellError> + Send + Sync + 'static> {
match command {
Reduce::Summation | Reduce::Default => Box::new(formula(
UntaggedValue::int(0).into_untagged_value(),
Box::new(sum),
)),
Reduce::Minimum => Box::new(|_, values| min(values)),
Reduce::Maximum => Box::new(|_, values| max(values)),
Reduce::Product => Box::new(|_, values| product(values)),
}
}
pub enum Reduce {
Summation,
Minimum,
Maximum,
Product,
Default,
}
pub fn sum(data: Vec<Value>) -> Result<Value, ShellError> {
let mut acc = UntaggedValue::int(0).into_untagged_value();
for value in data {
match value.value {
UntaggedValue::Primitive(_) => {
acc = match compute_values(Operator::Plus, &acc, &value) {
Ok(v) => v.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned_unknown(),
right_type.spanned_unknown(),
))
}
};
}
_ => {
return Err(ShellError::labeled_error(
"Attempted to compute the sum of a value that cannot be summed.",
"value appears here",
value.tag.span,
))
}
}
}
Ok(acc)
}
pub fn max(data: Vec<Value>) -> Result<Value, ShellError> {
let mut biggest = data
.first()
.ok_or_else(|| ShellError::unexpected(ERR_EMPTY_DATA))?
.value
.clone();
for value in data.iter() {
if let Ok(greater_than) = compare_values(Operator::GreaterThan, &value.value, &biggest) {
if greater_than {
biggest = value.value.clone();
}
} else {
return Err(ShellError::unexpected(format!(
"Could not compare\nleft: {:?}\nright: {:?}",
biggest, value.value
)));
}
}
Ok(Value {
value: biggest,
tag: Tag::unknown(),
})
}
pub fn min(data: Vec<Value>) -> Result<Value, ShellError> {
let mut smallest = data
.first()
.ok_or_else(|| ShellError::unexpected(ERR_EMPTY_DATA))?
.value
.clone();
for value in data.iter() {
if let Ok(greater_than) = compare_values(Operator::LessThan, &value.value, &smallest) {
if greater_than {
smallest = value.value.clone();
}
} else {
return Err(ShellError::unexpected(format!(
"Could not compare\nleft: {:?}\nright: {:?}",
smallest, value.value
)));
}
}
Ok(Value {
value: smallest,
tag: Tag::unknown(),
})
}
pub fn product(data: Vec<Value>) -> Result<Value, ShellError> {
if data.is_empty() {
return Err(ShellError::unexpected(ERR_EMPTY_DATA));
}
let mut prod = UntaggedValue::int(1).into_untagged_value();
for value in data {
match value.value {
UntaggedValue::Primitive(_) => {
prod = match compute_values(Operator::Multiply, &prod, &value) {
Ok(v) => v.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned_unknown(),
right_type.spanned_unknown(),
))
}
};
}
_ => {
return Err(ShellError::labeled_error(
"Attempted to compute the product of a value that cannot be multiplied.",
"value appears here",
value.tag.span,
))
}
}
}
Ok(prod)
}

View File

@ -1,13 +1,18 @@
use super::variance::variance;
use crate::commands::math::utils::run_with_function;
use super::variance::compute_variance as variance;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, UntaggedValue, Value};
use nu_protocol::{Dictionary, Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use nu_source::Tagged;
use std::str::FromStr;
pub struct SubCommand;
#[derive(Deserialize)]
struct Arguments {
sample: Tagged<bool>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
@ -15,7 +20,11 @@ impl WholeStreamCommand for SubCommand {
}
fn signature(&self) -> Signature {
Signature::build("math stddev")
Signature::build("math stddev").switch(
"sample",
"calculate sample standard deviation",
Some('s'),
)
}
fn usage(&self) -> &str {
@ -27,33 +36,89 @@ impl WholeStreamCommand for SubCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_function(
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,
},
stddev,
)
.await
let name = args.call_info.name_tag.clone();
let (Arguments { sample }, mut input) = args.process(&registry).await?;
let values: Vec<Value> = input.drain_vec().await;
let n = if let Tagged { item: true, .. } = sample {
values.len() - 1
} else {
values.len()
};
let res = if values.iter().all(|v| v.is_primitive()) {
compute_stddev(&values, n, &name)
} else {
// If we are not dealing with Primitives, then perhaps we are dealing with a table
// Create a key for each column name
let mut column_values = IndexMap::new();
for value in values {
if let UntaggedValue::Row(row_dict) = &value.value {
for (key, value) in row_dict.entries.iter() {
column_values
.entry(key.clone())
.and_modify(|v: &mut Vec<Value>| v.push(value.clone()))
.or_insert(vec![value.clone()]);
}
}
}
// The mathematical function operates over the columns of the table
let mut column_totals = IndexMap::new();
for (col_name, col_vals) in column_values {
if let Ok(out) = compute_stddev(&col_vals, n, &name) {
column_totals.insert(col_name, out);
}
}
if column_totals.keys().len() == 0 {
return Err(ShellError::labeled_error(
"Attempted to compute values that can't be operated on",
"value appears here",
name.span,
));
}
Ok(UntaggedValue::Row(Dictionary {
entries: column_totals,
})
.into_untagged_value())
}?;
if res.value.is_table() {
Ok(OutputStream::from(
res.table_entries()
.map(|v| ReturnSuccess::value(v.clone()))
.collect::<Vec<_>>(),
))
} else {
Ok(OutputStream::one(ReturnSuccess::value(res)))
}
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the stddev of a list of numbers",
example: "echo [1 2 3 4 5] | math stddev",
result: Some(vec![UntaggedValue::decimal(BigDecimal::from_str("1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641573").expect("Could not convert to decimal from string")).into()]),
}]
vec![
Example {
description: "Get the stddev of a list of numbers",
example: "echo [1 2 3 4 5] | math stddev",
result: Some(vec![UntaggedValue::decimal(BigDecimal::from_str("1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641573").expect("Could not convert to decimal from string")).into()]),
},
Example {
description: "Get the sample stddev of a list of numbers",
example: "echo [1 2 3 4 5] | math stddev -s",
result: Some(vec![UntaggedValue::decimal(BigDecimal::from_str("1.581138830084189665999446772216359266859777569662608413428752426396297219319619110672124054189650148").expect("Could not convert to decimal from string")).into()]),
},
]
}
}
#[cfg(test)]
pub fn stddev(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let variance = variance(values, name)?.as_primitive()?;
compute_stddev(values, values.len(), name)
}
pub fn compute_stddev(values: &[Value], n: usize, name: &Tag) -> Result<Value, ShellError> {
let variance = variance(values, n, name)?.as_primitive()?;
let sqrt_var = match variance {
Primitive::Decimal(var) => var.sqrt(),
_ => {

View File

@ -1,10 +1,13 @@
use crate::commands::math::reducers::{reducer_for, Reduce};
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use nu_errors::ShellError;
use nu_protocol::{Dictionary, Signature, UntaggedValue, Value};
use num_traits::identities::Zero;
use nu_protocol::{
hir::{convert_number_to_u64, Number},
Primitive, Signature, UntaggedValue, Value,
};
pub struct SubCommand;
@ -59,37 +62,63 @@ impl WholeStreamCommand for SubCommand {
}
}
fn to_byte(value: &Value) -> Option<Value> {
match &value.value {
UntaggedValue::Primitive(Primitive::Int(num)) => Some(
UntaggedValue::Primitive(Primitive::Filesize(convert_number_to_u64(&Number::Int(
num.clone(),
))))
.into_untagged_value(),
),
_ => None,
}
}
pub fn summation(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let sum = reducer_for(Reduce::Summation);
if values.iter().all(|v| v.is_primitive()) {
Ok(sum(Value::zero(), values.to_vec())?)
} else {
let mut column_values = IndexMap::new();
let first = values.get(0).ok_or_else(|| {
ShellError::unexpected("Cannot perform aggregate math operation on empty data")
})?;
for value in values {
if let UntaggedValue::Row(row_dict) = value.value.clone() {
for (key, value) in row_dict.entries.iter() {
column_values
.entry(key.clone())
.and_modify(|v: &mut Vec<Value>| v.push(value.clone()))
.or_insert(vec![value.clone()]);
}
};
}
let mut column_totals = IndexMap::new();
for (col_name, col_vals) in column_values {
let sum = sum(Value::zero(), col_vals)?;
column_totals.insert(col_name, sum);
}
Ok(UntaggedValue::Row(Dictionary {
entries: column_totals,
})
.into_value(name))
match first {
v if v.is_filesize() => to_byte(&sum(
UntaggedValue::int(0).into_untagged_value(),
values
.to_vec()
.iter()
.map(|v| match v {
Value {
value: UntaggedValue::Primitive(Primitive::Filesize(num)),
..
} => UntaggedValue::int(*num as usize).into_untagged_value(),
other => other.clone(),
})
.collect::<Vec<_>>(),
)?)
.ok_or_else(|| {
ShellError::labeled_error(
"could not convert to big decimal",
"could not convert to big decimal",
&name.span,
)
}),
// v is nothing primitive
v if v.is_none() => sum(
UntaggedValue::int(0).into_untagged_value(),
values
.to_vec()
.iter()
.map(|v| match v {
Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
..
} => UntaggedValue::int(0).into_untagged_value(),
other => other.clone(),
})
.collect::<Vec<_>>(),
),
_ => sum(UntaggedValue::int(0).into_untagged_value(), values.to_vec()),
}
}

View File

@ -13,6 +13,7 @@ pub async fn run_with_function(
mf: MathFunction,
) -> Result<OutputStream, ShellError> {
let values: Vec<Value> = input.drain_vec().await;
let res = calculate(&values, &name, mf);
match res {
Ok(v) => {
@ -50,7 +51,17 @@ pub fn calculate(values: &[Value], name: &Tag, mf: MathFunction) -> Result<Value
// The mathematical function operates over the columns of the table
let mut column_totals = IndexMap::new();
for (col_name, col_vals) in column_values {
column_totals.insert(col_name, mf(&col_vals, &name)?);
if let Ok(out) = mf(&col_vals, &name) {
column_totals.insert(col_name, out);
}
}
if column_totals.keys().len() == 0 {
return Err(ShellError::labeled_error(
"Attempted to compute values that can't be operated on",
"value appears here",
name.span,
));
}
Ok(UntaggedValue::Row(Dictionary {

View File

@ -1,13 +1,20 @@
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::data::value::compute_values;
use crate::prelude::*;
use bigdecimal::{FromPrimitive, Zero};
use bigdecimal::FromPrimitive;
use nu_data::value::compute_values;
use nu_errors::ShellError;
use nu_protocol::{hir::Operator, Primitive, Signature, UntaggedValue, Value};
use nu_protocol::{
hir::Operator, Dictionary, Primitive, ReturnSuccess, Signature, UntaggedValue, Value,
};
use nu_source::Tagged;
pub struct SubCommand;
#[derive(Deserialize)]
struct Arguments {
sample: Tagged<bool>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
@ -15,7 +22,7 @@ impl WholeStreamCommand for SubCommand {
}
fn signature(&self) -> Signature {
Signature::build("math variance")
Signature::build("math variance").switch("sample", "calculate sample variance", Some('s'))
}
fn usage(&self) -> &str {
@ -27,28 +34,79 @@ impl WholeStreamCommand for SubCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_function(
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,
},
variance,
)
.await
let name = args.call_info.name_tag.clone();
let (Arguments { sample }, mut input) = args.process(&registry).await?;
let values: Vec<Value> = input.drain_vec().await;
let n = if let Tagged { item: true, .. } = sample {
values.len() - 1
} else {
values.len()
};
let res = if values.iter().all(|v| v.is_primitive()) {
compute_variance(&values, n, &name)
} else {
// If we are not dealing with Primitives, then perhaps we are dealing with a table
// Create a key for each column name
let mut column_values = IndexMap::new();
for value in values {
if let UntaggedValue::Row(row_dict) = &value.value {
for (key, value) in row_dict.entries.iter() {
column_values
.entry(key.clone())
.and_modify(|v: &mut Vec<Value>| v.push(value.clone()))
.or_insert(vec![value.clone()]);
}
}
}
// The mathematical function operates over the columns of the table
let mut column_totals = IndexMap::new();
for (col_name, col_vals) in column_values {
if let Ok(out) = compute_variance(&col_vals, n, &name) {
column_totals.insert(col_name, out);
}
}
if column_totals.keys().len() == 0 {
return Err(ShellError::labeled_error(
"Attempted to compute values that can't be operated on",
"value appears here",
name.span,
));
}
Ok(UntaggedValue::Row(Dictionary {
entries: column_totals,
})
.into_untagged_value())
}?;
if res.value.is_table() {
Ok(OutputStream::from(
res.table_entries()
.map(|v| ReturnSuccess::value(v.clone()))
.collect::<Vec<_>>(),
))
} else {
Ok(OutputStream::one(ReturnSuccess::value(res)))
}
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the variance of a list of numbers",
example: "echo [1 2 3 4 5] | math variance",
result: Some(vec![UntaggedValue::decimal(2).into()]),
}]
vec![
Example {
description: "Get the variance of a list of numbers",
example: "echo [1 2 3 4 5] | math variance",
result: Some(vec![UntaggedValue::decimal(2).into()]),
},
Example {
description: "Get the sample variance of a list of numbers",
example: "echo [1 2 3 4 5] | math variance -s",
result: Some(vec![UntaggedValue::decimal(2.5).into()]),
},
]
}
}
@ -60,8 +118,8 @@ fn sum_of_squares(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
&name.span,
)
})?;
let mut sum_x = Value::zero();
let mut sum_x2 = Value::zero();
let mut sum_x = UntaggedValue::int(0).into_untagged_value();
let mut sum_x2 = UntaggedValue::int(0).into_untagged_value();
for value in values {
let v = match value {
Value {
@ -87,7 +145,17 @@ fn sum_of_squares(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let v_squared = compute_values(Operator::Multiply, &v, &v);
match v_squared {
// X^2
Ok(x2) => sum_x2 = sum_x2 + x2.into_untagged_value(),
Ok(x2) => {
sum_x2 = match compute_values(Operator::Plus, &sum_x2, &x2) {
Ok(v) => v.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
))
}
};
}
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned(value.tag.span),
@ -95,7 +163,15 @@ fn sum_of_squares(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
))
}
};
sum_x = sum_x + v.into_untagged_value();
sum_x = match compute_values(Operator::Plus, &sum_x, &v) {
Ok(v) => v.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
))
}
};
}
let sum_x_squared = match compute_values(Operator::Multiply, &sum_x, &sum_x) {
@ -129,9 +205,14 @@ fn sum_of_squares(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
Ok(ss)
}
#[cfg(test)]
pub fn variance(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
compute_variance(values, values.len(), name)
}
pub fn compute_variance(values: &[Value], n: usize, name: &Tag) -> Result<Value, ShellError> {
let ss = sum_of_squares(values, name)?;
let n = BigDecimal::from_usize(values.len()).ok_or_else(|| {
let n = BigDecimal::from_usize(n).ok_or_else(|| {
ShellError::labeled_error(
"could not convert to big decimal",
"could not convert to big decimal",

View File

@ -1,8 +1,8 @@
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::data::value::merge_values;
use crate::prelude::*;
use nu_data::value::merge_values;
use indexmap::IndexMap;
use nu_errors::ShellError;

View File

@ -1,10 +1,10 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::data::base::select_fields;
use crate::prelude::*;
use nu_data::base::select_fields;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, Value};
use nu_source::span_for_spanned_list;
use nu_source::HasFallibleSpan;
pub struct SubCommand;
@ -106,7 +106,7 @@ async fn operate(
.flatten()
.collect::<Vec<&ColumnPath>>();
let after_span = span_for_spanned_list(after.members().iter().map(|p| p.span));
let after_span = after.maybe_span().unwrap_or_else(Span::unknown);
if after.members().len() == 1 {
let keys = column_paths
@ -154,7 +154,7 @@ async fn operate(
.flatten()
.collect::<Vec<&ColumnPath>>();
let before_span = span_for_spanned_list(before.members().iter().map(|p| p.span));
let before_span = before.maybe_span().unwrap_or_else(Span::unknown);
if before.members().len() == 1 {
let keys = column_paths
@ -207,7 +207,7 @@ fn move_after(
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let tag = tag.into();
let from_fields = span_for_spanned_list(from.members().iter().map(|p| p.span));
let from_fields = from.maybe_span().unwrap_or_else(Span::unknown);
let from = if let Some((last, _)) = from.split_last() {
last.as_string()
} else {
@ -270,7 +270,7 @@ fn move_before(
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let tag = tag.into();
let from_fields = span_for_spanned_list(from.members().iter().map(|p| p.span));
let from_fields = from.maybe_span().unwrap_or_else(Span::unknown);
let from = if let Some((last, _)) = from.split_last() {
last.as_string()
} else {

View File

@ -41,6 +41,17 @@ impl WholeStreamCommand for Command {
) -> Result<OutputStream, ShellError> {
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
let mut row = IndexMap::new();
row.insert("foo".to_string(), Value::from("hi"));
row.insert("bar".to_string(), Value::from("there"));
vec![Example {
description: "Parse a string into two named columns",
example: "echo \"hi there\" | parse \"{foo} {bar}\"",
result: Some(vec![UntaggedValue::row(row).into()]),
}]
}
}
pub async fn operate(

View File

@ -0,0 +1,61 @@
use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use std::path::Path;
pub struct PathBasename;
#[async_trait]
impl WholeStreamCommand for PathBasename {
fn name(&self) -> &str {
"path basename"
}
fn signature(&self) -> Signature {
Signature::build("path basename")
.rest(SyntaxShape::ColumnPath, "optionally operate by path")
}
fn usage(&self) -> &str {
"gets the filename of a path"
}
async fn run(
&self,
args: CommandArgs,
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
}
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")]),
}]
}
}
fn action(path: &Path) -> UntaggedValue {
UntaggedValue::string(match path.file_name() {
Some(filename) => filename.to_string_lossy().to_string(),
_ => "".to_string(),
})
}
#[cfg(test)]
mod tests {
use super::PathBasename;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(PathBasename {})
}
}

View File

@ -0,0 +1,46 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
pub struct Path;
#[async_trait]
impl WholeStreamCommand for Path {
fn name(&self) -> &str {
"path"
}
fn signature(&self) -> Signature {
Signature::build("path")
}
fn usage(&self) -> &str {
"Apply path 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(&Path, &registry))
.into_value(Tag::unknown()),
)))
}
}
#[cfg(test)]
mod tests {
use super::Path;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Path {})
}
}

View File

@ -0,0 +1,60 @@
use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use std::path::Path;
pub struct PathDirname;
#[async_trait]
impl WholeStreamCommand for PathDirname {
fn name(&self) -> &str {
"path dirname"
}
fn signature(&self) -> Signature {
Signature::build("path dirname").rest(SyntaxShape::ColumnPath, "optionally operate by path")
}
fn usage(&self) -> &str {
"gets the dirname of a path"
}
async fn run(
&self,
args: CommandArgs,
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
}
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")]),
}]
}
}
fn action(path: &Path) -> UntaggedValue {
UntaggedValue::string(match path.parent() {
Some(dirname) => dirname.to_string_lossy().to_string(),
_ => "".to_string(),
})
}
#[cfg(test)]
mod tests {
use super::PathDirname;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(PathDirname {})
}
}

View File

@ -0,0 +1,45 @@
use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use std::path::Path;
pub struct PathExists;
#[async_trait]
impl WholeStreamCommand for PathExists {
fn name(&self) -> &str {
"path exists"
}
fn signature(&self) -> Signature {
Signature::build("path exists").rest(SyntaxShape::ColumnPath, "optionally operate by path")
}
fn usage(&self) -> &str {
"checks whether the path exists"
}
async fn run(
&self,
args: CommandArgs,
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
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Check if file exists",
example: "echo '/home/joe/todo.txt' | path exists",
result: Some(vec![Value::from(UntaggedValue::boolean(false))]),
}]
}
}
fn action(path: &Path) -> UntaggedValue {
UntaggedValue::boolean(path.exists())
}

View File

@ -0,0 +1,51 @@
use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use std::path::Path;
pub struct PathExpand;
#[async_trait]
impl WholeStreamCommand for PathExpand {
fn name(&self) -> &str {
"path expand"
}
fn signature(&self) -> Signature {
Signature::build("path expand").rest(SyntaxShape::ColumnPath, "optionally operate by path")
}
fn usage(&self) -> &str {
"expands the path to its absolute form"
}
async fn run(
&self,
args: CommandArgs,
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
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Expand relative directories",
example: "echo '/home/joe/foo/../bar' | path expand",
result: Some(vec![Value::from("/home/joe/bar")]),
}]
}
}
fn action(path: &Path) -> 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(),
})
}

View File

@ -0,0 +1,68 @@
use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use std::path::Path;
pub struct PathExtension;
#[async_trait]
impl WholeStreamCommand for PathExtension {
fn name(&self) -> &str {
"path extension"
}
fn signature(&self) -> Signature {
Signature::build("path extension")
.rest(SyntaxShape::ColumnPath, "optionally operate by path")
}
fn usage(&self) -> &str {
"gets the extension of a path"
}
async fn run(
&self,
args: CommandArgs,
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
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get extension of a path",
example: "echo 'test.txt' | path extension",
result: Some(vec![Value::from("txt")]),
},
Example {
description: "You get an empty string if there is no extension",
example: "echo 'test' | path extension",
result: Some(vec![Value::from("")]),
},
]
}
}
fn action(path: &Path) -> UntaggedValue {
UntaggedValue::string(match path.extension() {
Some(ext) => ext.to_string_lossy().to_string(),
_ => "".to_string(),
})
}
#[cfg(test)]
mod tests {
use super::PathExtension;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(PathExtension {})
}
}

View File

@ -0,0 +1,61 @@
use super::{operate, DefaultArguments};
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use std::path::Path;
pub struct PathFilestem;
#[async_trait]
impl WholeStreamCommand for PathFilestem {
fn name(&self) -> &str {
"path filestem"
}
fn signature(&self) -> Signature {
Signature::build("path filestem")
.rest(SyntaxShape::ColumnPath, "optionally operate by path")
}
fn usage(&self) -> &str {
"gets the filestem of a path"
}
async fn run(
&self,
args: CommandArgs,
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
}
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")]),
}]
}
}
fn action(path: &Path) -> UntaggedValue {
UntaggedValue::string(match path.file_stem() {
Some(stem) => stem.to_string_lossy().to_string(),
_ => "".to_string(),
})
}
#[cfg(test)]
mod tests {
use super::PathFilestem;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(PathFilestem {})
}
}

View File

@ -0,0 +1,79 @@
mod basename;
mod command;
mod dirname;
mod exists;
mod expand;
mod extension;
mod filestem;
mod r#type;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, ShellTypeName, UntaggedValue, Value};
use nu_source::Span;
use std::path::Path;
pub use basename::PathBasename;
pub use command::Path as PathCommand;
pub use dirname::PathDirname;
pub use exists::PathExists;
pub use expand::PathExpand;
pub use extension::PathExtension;
pub use filestem::PathFilestem;
pub use r#type::PathType;
#[derive(Deserialize)]
struct DefaultArguments {
rest: Vec<ColumnPath>,
}
fn handle_value<F>(action: &F, v: &Value, span: Span) -> Result<Value, ShellError>
where
F: Fn(&Path) -> UntaggedValue + Send + 'static,
{
let v = match &v.value {
UntaggedValue::Primitive(Primitive::Path(buf)) => action(buf).into_value(v.tag()),
UntaggedValue::Primitive(Primitive::String(s))
| UntaggedValue::Primitive(Primitive::Line(s)) => action(s.as_ref()).into_value(v.tag()),
other => {
let got = format!("got {}", other.type_name());
return Err(ShellError::labeled_error_with_secondary(
"value is not string or path",
got,
span,
"originates from here".to_string(),
v.tag().span,
));
}
};
Ok(v)
}
async fn operate<F>(
input: crate::InputStream,
paths: Vec<ColumnPath>,
action: &'static F,
span: Span,
) -> Result<OutputStream, ShellError>
where
F: Fn(&Path) -> UntaggedValue + Send + Sync + 'static,
{
Ok(input
.map(move |v| {
if paths.is_empty() {
ReturnSuccess::value(handle_value(&action, &v, span)?)
} else {
let mut ret = v;
for path in &paths {
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| handle_value(&action, &old, span)),
)?;
}
ReturnSuccess::value(ret)
}
})
.to_output_stream())
}

View File

@ -0,0 +1,62 @@
use super::{operate, DefaultArguments};
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 std::path::Path;
pub struct PathType;
#[async_trait]
impl WholeStreamCommand for PathType {
fn name(&self) -> &str {
"path type"
}
fn signature(&self) -> Signature {
Signature::build("path type").rest(SyntaxShape::ColumnPath, "optionally operate by path")
}
fn usage(&self) -> &str {
"gives the type of the object the path refers to (eg file, dir, symlink)"
}
async fn run(
&self,
args: CommandArgs,
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
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Show type of a filepath",
example: "echo '.' | path type",
result: Some(vec![Value::from("Dir")]),
}]
}
}
fn action(path: &Path) -> UntaggedValue {
let meta = std::fs::symlink_metadata(path);
UntaggedValue::string(match &meta {
Ok(md) => get_file_type(md),
Err(_) => "",
})
}
#[cfg(test)]
mod tests {
use super::PathType;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(PathType {})
}
}

View File

@ -0,0 +1,178 @@
use crate::commands::classified::block::run_block;
use crate::commands::each;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::{CommandArgs, CommandRegistry, Example, OutputStream};
use futures::stream::once;
use nu_errors::ShellError;
use nu_protocol::{hir::Block, Primitive, Scope, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct Reduce;
#[derive(Deserialize)]
pub struct ReduceArgs {
block: Block,
fold: Option<Value>,
numbered: Tagged<bool>,
}
#[async_trait]
impl WholeStreamCommand for Reduce {
fn name(&self) -> &str {
"reduce"
}
fn signature(&self) -> Signature {
Signature::build("reduce")
.named(
"fold",
SyntaxShape::Any,
"reduce with initial value",
Some('f'),
)
.required("block", SyntaxShape::Block, "reducing function")
.switch(
"numbered",
"returned a numbered item ($it.index and $it.item)",
Some('n'),
)
}
fn usage(&self) -> &str {
"Aggregate a list table to a single value using an accumulator block. Block must be
(A, A) -> A unless --fold is selected, in which case it may be A, B -> A."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
reduce(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Simple summation (equivalent to math sum)",
example: "echo 1 2 3 4 | reduce { = $acc + $it }",
result: Some(vec![UntaggedValue::int(10).into()]),
},
Example {
description: "Summation from starting value using fold",
example: "echo 1 2 3 4 | reduce -f $(= -1) { = $acc + $it }",
result: Some(vec![UntaggedValue::int(9).into()]),
},
Example {
description: "Folding with rows",
example: "<table> | reduce -f 1.6 { = $acc * $(echo $it.a | str to-int) + $(echo $it.b | str to-int) }",
result: None,
},
Example {
description: "Numbered reduce to find index of longest word",
example: "echo one longest three bar | reduce -n { if $(echo $it.item | str length) > $(echo $acc.item | str length) {echo $it} {echo $acc}} | get index",
result: None,
},
]
}
}
async fn process_row(
block: Arc<Block>,
scope: Arc<Scope>,
mut context: Arc<Context>,
row: Value,
) -> Result<InputStream, ShellError> {
let row_clone = row.clone();
let input_stream = once(async { Ok(row_clone) }).to_input_stream();
Ok(run_block(
&block,
Arc::make_mut(&mut context),
input_stream,
&row,
&scope.vars,
&scope.env,
)
.await?)
}
async fn reduce(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let base_scope = raw_args.call_info.scope.clone();
let context = Arc::new(Context::from_raw(&raw_args, &registry));
let (reduce_args, mut input): (ReduceArgs, _) = raw_args.process(&registry).await?;
let block = Arc::new(reduce_args.block);
let (ioffset, start) = match reduce_args.fold {
None => {
let first = input
.next()
.await
.expect("empty stream expected to contain Primitive::Nothing");
if let UntaggedValue::Primitive(Primitive::Nothing) = first.value {
return Err(ShellError::missing_value(None, "empty input"));
}
(1, first)
}
Some(acc) => (0, acc),
};
if reduce_args.numbered.item {
// process_row returns Result<InputStream, ShellError>, so we must fold with one
let initial = Ok(InputStream::one(each::make_indexed_item(
ioffset - 1,
start,
)));
Ok(input
.enumerate()
.fold(initial, move |acc, input| {
let block = Arc::clone(&block);
let mut scope = base_scope.clone();
let context = Arc::clone(&context);
let row = each::make_indexed_item(input.0 + ioffset, input.1);
async {
let f = acc?.into_vec().await[0].clone();
scope.vars.insert(String::from("$acc"), f);
process_row(block, Arc::new(scope), context, row).await
}
})
.await?
.to_output_stream())
} else {
let initial = Ok(InputStream::one(start));
Ok(input
.fold(initial, move |acc, row| {
let block = Arc::clone(&block);
let mut scope = base_scope.clone();
let context = Arc::clone(&context);
async {
scope
.vars
.insert(String::from("$acc"), acc?.into_vec().await[0].clone());
process_row(block, Arc::new(scope), context, row).await
}
})
.await?
.to_output_stream())
}
}
#[cfg(test)]
mod tests {
use super::Reduce;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Reduce {})
}
}

View File

@ -1,6 +1,6 @@
use crate::commands::WholeStreamCommand;
use crate::data::base::reject_fields;
use crate::prelude::*;
use nu_data::base::reject_fields;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
use nu_source::Tagged;
@ -23,7 +23,7 @@ impl WholeStreamCommand for Reject {
}
fn usage(&self) -> &str {
"Remove the given columns from the table."
"Remove the given columns from the table. If you want to remove rows, try 'drop'."
}
async fn run(

View File

@ -9,7 +9,7 @@ use nu_protocol::{hir::Block, Signature, SyntaxShape};
#[derive(new, Clone)]
pub struct AliasCommand {
name: String,
args: Vec<String>,
args: Vec<(String, SyntaxShape)>,
block: Block,
}
@ -22,8 +22,8 @@ impl WholeStreamCommand for AliasCommand {
fn signature(&self) -> Signature {
let mut alias = Signature::build(&self.name);
for arg in &self.args {
alias = alias.optional(arg, SyntaxShape::Any, "");
for (arg, shape) in &self.args {
alias = alias.optional(arg, *shape, "");
}
alias
@ -41,7 +41,7 @@ impl WholeStreamCommand for AliasCommand {
let call_info = args.call_info.clone();
let registry = registry.clone();
let mut block = self.block.clone();
block.set_is_last(call_info.args.is_last);
block.set_redirect(call_info.args.external_redirection);
let alias_command = self.clone();
let mut context = Context::from_args(&args, &registry);
@ -53,7 +53,7 @@ impl WholeStreamCommand for AliasCommand {
for (pos, arg) in positional.iter().enumerate() {
scope
.vars
.insert(alias_command.args[pos].to_string(), arg.clone());
.insert(alias_command.args[pos].0.to_string(), arg.clone());
}
}

View File

@ -62,6 +62,8 @@ impl WholeStreamCommand for RunExternalCommand {
let mut positionals = positionals.into_iter();
let external_redirection = args.call_info.args.external_redirection;
let name = positionals
.next()
.ok_or_else(|| {
@ -124,26 +126,24 @@ impl WholeStreamCommand for RunExternalCommand {
let result = external_context
.shell_manager
.cd(cd_args, args.call_info.name_tag.clone());
match result {
Ok(stream) => return Ok(stream.to_output_stream()),
Err(e) => {
return Err(e);
}
}
return Ok(result?.to_output_stream());
}
}
let scope = args.call_info.scope.clone();
let is_last = args.call_info.args.is_last;
let input = args.input;
let result =
external::run_external_command(command, &mut external_context, input, &scope, is_last)
.await;
match result {
Ok(stream) => Ok(stream.to_output_stream()),
Err(e) => Err(e),
}
let input = args.input;
let result = external::run_external_command(
command,
&mut external_context,
input,
&scope,
external_redirection,
)
.await;
Ok(result?.to_output_stream())
}
}

View File

@ -1,7 +1,10 @@
use crate::commands::{UnevaluatedCallInfo, WholeStreamCommand};
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{
hir::ExternalRedirection, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue,
Value,
};
use nu_source::Tagged;
use std::path::{Path, PathBuf};
@ -226,7 +229,7 @@ async fn save(
positional: None,
named: None,
span: Span::unknown(),
is_last: false,
external_redirection: ExternalRedirection::Stdout,
},
name_tag: name_tag.clone(),
scope,
@ -251,10 +254,7 @@ async fn save(
};
};
match content {
Ok(save_data) => shell_manager.save(&full_path, &save_data, name.span),
Err(e) => Err(e),
}
shell_manager.save(&full_path, &content?, name.span)
}
fn string_from(input: &[Value]) -> String {

View File

@ -36,7 +36,7 @@ impl WholeStreamCommand for Size {
"lines".to_string() => UntaggedValue::int(0).into(),
"words".to_string() => UntaggedValue::int(7).into(),
"chars".to_string() => UntaggedValue::int(38).into(),
"max length".to_string() => UntaggedValue::int(38).into(),
"bytes".to_string() => UntaggedValue::int(38).into(),
})
.into()]),
}]
@ -96,7 +96,7 @@ fn count(contents: &str, tag: impl Into<Tag>) -> Value {
dict.insert_untagged("lines", UntaggedValue::int(lines));
dict.insert_untagged("words", UntaggedValue::int(words));
dict.insert_untagged("chars", UntaggedValue::int(chars));
dict.insert_untagged("max length", UntaggedValue::int(bytes));
dict.insert_untagged("bytes", UntaggedValue::int(bytes));
dict.into_value()
}

View File

@ -0,0 +1,86 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged;
use std::{thread, time};
pub struct Sleep;
#[derive(Deserialize)]
pub struct SleepArgs {
pub dur: Tagged<u64>,
pub rest: Vec<Tagged<u64>>,
}
#[async_trait]
impl WholeStreamCommand for Sleep {
fn name(&self) -> &str {
"sleep"
}
fn signature(&self) -> Signature {
Signature::build("sleep")
.required("duration", SyntaxShape::Unit, "time to sleep")
.rest(SyntaxShape::Unit, "additional time")
}
fn usage(&self) -> &str {
"delay for a specified amount of time"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
sleep(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Sleep for 1sec",
example: "sleep 1sec",
result: None,
},
Example {
description: "Sleep for 3sec",
example: "sleep 1sec 1sec 1sec",
result: None,
},
]
}
}
async fn sleep(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (SleepArgs { dur, rest }, ..) = args.process(&registry).await?;
let total_dur = dur.item + rest.iter().map(|val| val.item).sum::<u64>();
let total_dur = time::Duration::from_nanos(total_dur);
thread::sleep(total_dur);
Ok(OutputStream::empty())
}
#[cfg(test)]
mod tests {
use super::Sleep;
use std::time::Instant;
#[test]
#[ignore]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
let start = Instant::now();
test_examples(Sleep {});
let elapsed = start.elapsed();
println!("{:?}", elapsed);
assert!(elapsed >= std::time::Duration::from_secs(4));
}
}

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