Compare commits

...

311 Commits

Author SHA1 Message Date
ebba89ea31 Bump to 0.20 (#2588) 2020-09-22 19:54:46 +12:00
8388afc9d9 add support for the text/csv content-type (#2587) 2020-09-22 15:26:20 +12:00
b133724b38 Add space between column suggestions (#2586) 2020-09-22 14:14:11 +12:00
09429d08aa Implement passthrough for benchmark (#2580)
* Implement passthrough for benchmark

Add a new option -p,--passthrough to benchmark.
With this option, the benchmark command prints its results to stdout and passes the block's output to the next command in the pipeline.

* Add execution block for benchmark -p

`benchmark --passthrough` now takes a block where the benchmark output is sent.
Example:
`benchmark -p {save bench.toml} {ls}`
2020-09-22 05:30:16 +12:00
9b577b8679 Update bigint/bigdecimal (#2585)
* Update bigint/bigdecimal

* clippy
2020-09-22 05:28:31 +12:00
7a595827f1 Fix subcommands column on help commands (#2584) 2020-09-21 19:57:26 +12:00
332e12ded0 Added test for ls -a (#2582) 2020-09-21 19:56:37 +12:00
a508e15efe CtrlD exits current shell (#2583) 2020-09-21 19:56:10 +12:00
a5b6bb6209 Add global mode to str trim (#2576)
* Add global mode to str trim

The global mode allows skipping non-string values,
and processes rows and tables as well

* Add tests to action with ActionMode::Global
2020-09-20 21:04:26 +12:00
1882a32b83 Context cleanup (#2581)
* Specialize 'Context' to EvaluationContext and CompletionContext

* Specialize 'Context' to EvaluationContext and CompletionContext

* fmt
2020-09-20 09:29:51 +12:00
798766b4b5 Remove panics from random integer and make the constraint more idiomatic (#2578)
* Remove panics from random integer and make the constraint more idiomatic

* Add open intervals to NumericRange
2020-09-20 08:41:49 +12:00
193c4cc6d5 Include subcommands in help commands (#2575)
* Add minor fixes to comments

* Include subcommands in `help commands`
2020-09-20 08:37:47 +12:00
422b6ca871 Add system, user and idle times to benchmark command (#2571)
* Add system, user and idle times to benchmark command

* Feature-gate dependency on heim in benchmark

* Reorder let bindings in benchmark

* Fully feature-gate rich-benchmark and print 0sec on zero duration
2020-09-20 05:13:14 +12:00
2b13ac3856 more table themes rounded and reinforced (#2579) 2020-09-19 12:10:34 -05:00
4c10351579 Exclude internal commands from 'help command' (#2573) 2020-09-19 11:48:30 +12:00
dd27aaef1b Limit open streaming to non-files, and files > 32mb (#2570) 2020-09-19 07:51:28 +12:00
6eb4a0e87b add replace all option to str find-replace (#2569) 2020-09-18 11:28:50 -05:00
15f3a545f0 Cleanup code in get and nu-value-ext (#2563)
* Cleanup code in get and nu-value-ext

* Remove unnecessary return statements from get
2020-09-18 18:40:20 +12:00
365f76ad19 Tidy up help command text (#2566) 2020-09-18 18:13:53 +12:00
df2845a9b4 implementing case-sensitive & case-insensitive completion matching (#2556)
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-09-17 10:52:58 -04:00
8453261211 Update rustyline to latest (#2565)
* Update rustyline to latest

* Go ahead and use rustyline for testing
2020-09-17 18:02:30 +12:00
1dc8f3300e Make the sleep command pass data through. (#2558) 2020-09-16 19:34:37 -04:00
10d4edc7af Slim down configuration readings and nu_cli clean up. (#2559)
We continue refactoring nu_cli and slim down a bit configuration
readings with a naive metadata `modified` field check.
2020-09-16 18:22:58 -05:00
50cbf91bc5 Remove trim in favor of str trim (#2560) 2020-09-16 15:59:32 -04:00
d05f9b3b1e Make the sleep command respond to Ctrl+C (#2550) 2020-09-16 12:14:33 -04:00
f5fad393d0 Refactor completion trait (#2555)
* Remove completion code from help/value shells

* Tweak the `Completer` trait for nushell.

Previously, this trait was built around rustyline's completion traits, and for
`Shell` instances. Now it is built for individual completers inside of nushell
that will complete a specific location based on a partial string. For example,
for completing a partially typed command in the command position.
2020-09-16 16:37:43 +12:00
d19a5f4c2f add a few more ansi escape sequences (#2553) 2020-09-15 16:01:57 -05:00
04451af776 Ctrl c exit issue (#2548)
* fix ctrl_c problem on windows

* updated cargo.lock
2020-09-15 09:59:51 -05:00
232aca76a4 Update rawkey for ARM support (#2547)
v0.1.2 of rawkey currently doesn't compile on ARM; v0.1.3 remedies this.
2020-09-15 11:20:32 +12:00
0178b53289 Core nu plugin load capability. (#2544)
We introduce the `plugin` nu sub command (`nu plugin`) with basic plugin
loading support. We can choose to load plugins from a directory. Originally
introduced to make integration tests faster (by not loading any plugins on startup at all)
but `nu plugin --load some_path ; test_pipeline_that_uses_plugins_just_loaded` does not see it.

Therefore, a `nu_with_plugins!` macro for tests was introduced on top of nu`s `--skip-plugins`
switch executable which is set to true when running the integration tests that use the `nu!` macro now..
2020-09-14 09:07:02 -05:00
e05e6b42fe Simplify a few boolean creations (#2543) 2020-09-14 13:15:44 +12:00
dd79afb503 Fix yanked crossterm version (#2542) 2020-09-14 13:14:59 +12:00
599bb9797d Implement exclusive and inclusive ranges with ..< and .. (#2541)
* Implement exclusive and inclusive ranges with .. and ..=

This commit adds right-exclusive ranges.

The original a..b inclusive syntax was changed to reflect the Rust notation.
New a..=b syntax was introduced to have the old behavior.

Currently, both a.. and b..= is valid, and it is unclear whether it's valid
to impose restrictions.

The original issue suggests .. for inclusive and ..< for exclusive ranges,
this can be implemented by making simple changes to this commit.

* Fix collect tests by changing ranges to ..=

* Fix clippy lints in exclusive range matching

* Implement exclusive ranges using `..<`
2020-09-14 09:53:08 +12:00
c355585112 Set active shell column to boolean (#2540) 2020-09-13 16:25:38 +12:00
45f32c9541 Allow math avg to work on durations (#2529)
* Allow `math avg` to work on durations

* formatting

* fix linting issue and implemented `math sum` for duration

* fix linting issue

* applied requested changes

* applied requested change for avg.rs

* formatting
2020-09-12 18:56:05 -05:00
7528094e12 Allow folding with tables. (#2538) 2020-09-12 01:40:52 -05:00
dcfa135ab9 support key-value argument in with-env(#2490) (#2530)
* support key-value argument in with-env

* remove unused import

* fix format for lint
2020-09-11 18:17:35 +12:00
e9bb4f25eb accept multiple variables in with-env(#2490) (#2526)
* accept multiple variables in with-env(#2490)

* add examples for test

* fix format(#2490)
2020-09-10 19:23:28 +12:00
0f7a9bbd31 Further clarify duration conversion (#2522) 2020-09-10 15:33:37 +12:00
73e65df5f6 Fix path completions for cd command. (#2525)
Previously, we weren't expanding `~`, so `std::fs::metadata` was failing. We now
make use of `PathSuggestion` to get the actual path, as represented by a
`PathBuf`.
2020-09-09 18:32:20 -04:00
a63a5adafa remove unused dependencies (#2520)
* remove unused dependencies

* moved umask to cfg(unix)

* changed Inflector to inflector, hoping it fixes the issue.

* roll back Inflector

* removed commented out deps now that everything looks good.
2020-09-09 13:57:51 -05:00
2eb4f8d28a updated dependencies (#2517) 2020-09-09 10:35:45 +12:00
d9ae66791a allow decimals as a range boundary (#2509) 2020-09-08 05:30:11 +12:00
2c5939dc7d each group and each window subcommands. (#2508)
* First commit updating `config` to use subcommands (#2119)
    - Implemented `get` subcommand

* Implmented `config set` as a subcommand.

* Implemented `config set_into` as subcommand

* Fixed base `config` command
 - Instead of outputting help, it now outputs the list of all
 configuration parameters.

* Added `config clear` subcommand

* Added `config load` and `config remove` subcommands

* Added `config path` subcommand

* fixed clippy

* initial commit for implementing groups

* each group works

* each group is slightly cleaner + added example

* Added `each window` subcommand
    - No support for stride flag yet

* each window stride implemented

* Added tests and minor documentation changes

* fixed clippy

* fixed clippy again
2020-09-07 17:54:52 +12:00
3150e70fc7 Add cpu time to ps -l (#2507) 2020-09-07 17:02:45 +12:00
c9ffd6afc0 Improve range parsing and handling (#2506)
* Improve range parsing and handling

* linting
2020-09-07 14:43:58 +12:00
986b427038 Add modulo operator and simplify in/not-in (#2505) 2020-09-07 12:12:55 +12:00
c973850571 Show completions more than one level below ~ (#2503)
Previously, we'd check for a `~/` prefix and then return the home directory as
the base `PathBuf`. We failed to consider pushing any of the other possible path
components into this base dir, which prevent completions more than one level
deep (for example, `~/.config/<TAB>` would fail).
2020-09-06 20:06:13 -04:00
5a725f9651 Num links added to ls -l output (#2496) 2020-09-06 12:36:50 -04:00
79cc725aff Interpreting ranges for substring (#2499) 2020-09-06 12:35:11 -04:00
e2cbc4e853 Weather symbol cleanup (#2502)
* 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

* clean up some comments
2020-09-05 14:52:49 -05:00
b5a27f0ccb pr to get my main in sync (#2501)
* 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
2020-09-05 13:13:34 -05:00
bdb12f4bff Weather chars (#2500)
* 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

* added weather symbols/chars
2020-09-05 13:13:07 -05:00
c9c29f9e4c Remove space from command name completion suggestion. (#2498)
Although convenient, since the user doesn't have to type the space, it could be
a little surprising to users since they may think that was the only completion
in certain completions modes (for example, `cycle`).
2020-09-05 15:15:20 +12:00
32951f1161 implement exec for unix platforms (#2495) 2020-09-04 22:27:01 -04:00
56f85b3108 Provide path as part of the suggestion from the path completer. (#2497) 2020-09-04 22:10:26 -04:00
16f85f32a2 ls **/* does not show hidden files without the -a flag (#2407)
* fixed: .*.(ext|*)

* ls **/* does not return hidden files without the -a flag

* fixed formatting

* fixed clippy issues

* fixed clippy issues, v2

* added `#[cfg(unix)]` to windows-failing test
2020-09-05 07:32:58 +12:00
2ae2f2ea9d Ensure ansi mode windows (#2494)
* 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

* ensure ansi mode is enabled on windows
ansi mode sometimes gets out of sync in Windows.
I'm not sure why but this appears to fix it.
2020-09-04 14:24:46 -05:00
4696c9069b use fs_extra to recursively move folders (#2487) 2020-09-04 11:44:53 +12:00
1ffbb66e64 Initial implementation of random integer subcommand. (#2489)
* Initial implementation of random integer subcommand.

* Added additional examples.

Co-authored-by: Stacy Maydew <stacy.maydew@starlab.io>
2020-09-04 07:23:02 +12:00
8dc7b8a7cd Update clipboard feature (#2491)
* 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

* shouldn't these both bet clipboard-cli?
2020-09-04 07:21:32 +12:00
666e6a7b57 Size: count unicode graphmemes as single char (#2482) 2020-09-03 04:54:00 +12:00
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
2a084fc838 Bump to 0.17.0 (#2237) 2020-07-22 06:41:49 +12:00
a36d2a1586 Revert "hopefully the final fix for history (#2222)" (#2235)
This reverts commit 6829ad7a30.
2020-07-21 18:19:04 +12:00
32b875ada9 sample config settigns (#2233) 2020-07-20 21:15:58 -05:00
aaed9c4e8a added ansi example (#2230)
* added ansi example

* added strcollect to example
2020-07-20 18:33:39 -05:00
b9278bdfe1 Char example (#2231)
* added ansi example

* added another example

* changed example

* ansi changes here by mistake
2020-07-20 14:25:38 -05:00
6eb2c94209 Add flag for case-insensitive sort-by (#2225)
* Add flag for case-insensitive sort-by

* Fix test names

* Fix documentation comments
2020-07-21 05:31:58 +12:00
7b1a15b223 Campbell colors (#2219)
* added campbell theme to html colors

* updated test results. had to make change for ci.

* hopefully the last changes for this stupid test :)

* moved tests to html.rs

* remove unnecessary using statement.

* still fighting with tests and tests are winning.
2020-07-20 07:57:29 -05:00
836efd237c fix internal command parsing (args.is_last) (#2224) 2020-07-20 05:49:40 +12:00
aad3cca793 Add benchmark command (#2223) 2020-07-20 05:39:43 +12:00
6829ad7a30 hopefully the final fix for history (#2222) 2020-07-19 07:47:55 -05:00
1f0962eb08 Add some tests for parse_arg (#2220)
* add some tests for parse

* Format

* fix warnings
2020-07-19 19:12:56 +12:00
c65acc174d Add hex pretty print to 'to html' (#2221) 2020-07-19 16:44:15 +12:00
2dea392e40 Add hex pretty print to 'to html' (#2217) 2020-07-19 12:14:40 +12:00
0c43a4d04b Add hex pretty print to 'to html' (#2216) 2020-07-19 10:12:17 +12:00
ebc2d40875 Expose more registry APIs (#2215) 2020-07-19 06:01:05 +12:00
3432078e77 Fix uniq to work with simple values (#2214) 2020-07-19 05:19:03 +12:00
9e5170b3dc Clean up lines command (#2207) 2020-07-19 05:17:56 +12:00
0ae7c5d836 Fix if description (#2204) (#2213) 2020-07-19 05:16:35 +12:00
d0712a00f4 made it easier to change colors (#2212)
* made it easier to change colors
and the beginning of html theming

* fmt
2020-07-18 11:05:45 -05:00
5e722181cb Export more defs from nu-cli (#2205) 2020-07-18 16:47:03 +12:00
ffe3e2c16b Rename calc to math eval and allow it to optionally take an expression as an argument (#2195)
* Rename `calc` to `math eval` and allow it to optionally take the expression as an argument

* Moved calc tests to math eval
Also added 2 tests and changed 1 test

* Move calc docs to math eval
2020-07-18 16:11:19 +12:00
04e8aa31fe update history max size with two different calls. (#2202)
Closes #2193
2020-07-18 15:26:32 +12:00
9d24b440bb Introduce completion abstractions to nushell. (#2198)
* Introduce completion abstractions to nushell.

Currently, we rely on rustyline's completion structures. By abstracting this
away, we are more flexible to introduce someone elses completion engine, or our
own.

* Update value_shell.rs

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-07-18 14:55:10 +12:00
d8594a62c2 Add wasm support (#2199)
* Working towards a PoC for wasm

* Move bson and sqlite to plugins

* proof of concept now working

* tests are green

* Add CI test for --no-default-features

* Fix some tests

* Fix clippy and windows build

* More fixes

* Fix the windows build

* Fix the windows test
2020-07-18 13:59:23 +12:00
dbe0effd67 User error propagation operator (#2201) 2020-07-18 13:12:06 +12:00
b358804904 Auto-Generate Documentation for nushell.com (#2139)
* Very rough idea

* Remove colour codes

* Work on command for generating docs

* Quick comment

* Use nested collapsible markdown

* Refine documentation command

* Clippy and rename docs

* This layout probably seems best

Also moved some code to documentation.rs to avoid making help.rs massive

* Delete summaries.md

* Add usage strings

* Remove static annotations

* get_documentation produces value

Which will be used like
'help generate_docs | save "something"'
The resulting yaml can be passed to a script for generating HTML/MD files in the website

* Fix subcommands

* DRY code

* Address clippy:

* Fix links

* Clippy lints

* Move documentation to more central location
2020-07-18 10:22:43 +12:00
7b02604e6d changed colors as per Jörn's suggestion. (#2200)
* changed colors as per Jörn's suggestion.

* cleaned up old comments
2020-07-17 15:02:54 -05:00
6497421615 Keep until and while as subcommands of keep (#2197) 2020-07-18 07:06:48 +12:00
f26151e36d Silence Rust 1.45 Clippy warnings (#2196)
* Silence Rust 1.45 Clippy warnings dealing with using `map_err()`

* Silence false Clippy warning

* Fix last Clippy error for unnecessary conversion

* Fix `and_then` clippy warnings
2020-07-18 05:57:15 +12:00
0f688d7da7 Use '?' for error propagation, remove match (#2194) 2020-07-17 05:39:51 +12:00
a04dfca63a added ability to supply --dark_bg to to html (#2189)
* added ability to supply --dark_bg to to html

* fmt + fixed tests

* updated other html tests

* fmt
2020-07-16 08:19:29 -05:00
72f6513d2a Keybindings and invocation fix (#2186) 2020-07-15 19:51:59 +12:00
7c0a830d84 Match cleanup (#2184)
* Use `unwrap_or()` to remove `match`

* Use `?` for error propogation, and remove `match`
2020-07-15 19:51:41 +12:00
c299d207f7 Remove unnecessary match (#2183) 2020-07-15 19:50:38 +12:00
42a1adf2e9 Indices are (now) green, bold, right-aligned (#2181)
With https://github.com/nushell/nushell/pull/355 the (numeric) index column of tables was changed to be right-aligned. After the move to `nu-table` the index column is now centered instead of right-aligned. I think this is a copy-paste bug where [this line](71e55541d7/crates/nu-cli/src/commands/table.rs (L190)) has been copied from [this line](71e55541d7/crates/nu-cli/src/commands/table.rs (L207)), since the code is out-of-sync with the comment. This change restores harmony between the description and the function of the code.
2020-07-15 15:48:20 +12:00
b4761f9d8a Remove commands meant for internal use. (#2182) 2020-07-14 21:49:46 -05:00
71e55541d7 Merge skip command varieties into one command with sub commands. (#2179) 2020-07-14 20:44:49 -05:00
5f1075544c Remove unnecessary match statement (#2177) 2020-07-14 20:17:28 -04:00
0934410b38 Use matches!() for true/false returning match statements (#2176) 2020-07-14 20:11:41 -04:00
17e6c53b62 Add str reverse subcommand (#2170)
* Add str reverse subcommand

* rustfmt
2020-07-15 08:47:04 +12:00
80d2a7ee7a Fix autoenv executing scripts multiple times (#2171)
* Fix autoenv executing scripts multiple times

Previously, if the user had only specified entry or exitscripts the scripts
would execute many times. This should be fixed now

* Add tests

* Run exitscripts

* More tests and fixes to existing tests

* Test solution with visited dirs

* Track visited directories

* Comments and fmt
2020-07-15 07:16:50 +12:00
8fd22b61be Add variance and stddev subcommands to math command (#2154)
* add variance (population)
subcommand to math

* impl variance subcommand with spanning errors for invalid types

* add stddev subcommand to math

* rename bytes to filesize

* clippy fix -- use expect instead of unwrap in variance tests
2020-07-15 07:15:02 +12:00
e9313a61af Make str more strict. (#2173) 2020-07-14 10:04:00 -05:00
f2c4d22739 group-by can generate custom grouping key by block evaluation. (#2172) 2020-07-14 08:45:19 -05:00
8551e06d9e Ensure source buffer is cleared after reading in MaybeTextCodec. (#2168) 2020-07-14 11:24:52 +12:00
97cedeb324 Fix str --to-int usages (#2167) 2020-07-13 15:07:34 -04:00
07594222c0 Extend 'Shell' with open and save capabilities (#2165)
* Extend 'Shell' with open and save capabilities

* clippy fix
2020-07-13 21:07:44 +12:00
7a207a673b Update documentation to properly refer to subcommands with spaces (#2164) 2020-07-13 18:39:36 +12:00
78f13407e6 Documentation for autoenv (#2163)
* Documentation

* Somewhat nicer?

* cat
2020-07-13 18:23:19 +12:00
5a34744d8c add --char flag to 'str trim' (#2162) 2020-07-13 17:45:34 +12:00
0456f4a007 To html with color (#2158)
* adding color to html output

* latest changes

* seems to be working now

* WIP - close. Good is the enemy of Great.

* fixed the final issues... hopefully
2020-07-13 17:40:59 +12:00
f3f40df4dd Tests for autoenv (and fixes for bugs the tests found) (#2148)
* add test basic_autoenv_vars_are_added

* Tests

* Entry and exit scripts

* Recursive set and overwrite

* Make sure that overwritten vals are restored

* Move tests to autoenv

* Move tests out of cli crate

* Tests help, apparently. Windows has issues

On windows, .nu-env is not applied immediately after running autoenv trust.
You have to cd out of the directory for it to work.

* Sort paths non-lexicographically

* Sibling dir test

* Revert "Sort paths non-lexicographically"

This reverts commit 72e4b856af.

* Rename test

* Change conditions

* Revert "Revert "Sort paths non-lexicographically""

This reverts commit 71606bc62f.

* Set vars as they are discovered

This means that if a parent directory is untrusted,
the variables in its child directories are still set properly.

* format

* Fix cleanup issues too

* Run commands in their separate functions

* Make everything into one large function like all the cool kids

* Refactoring

* fmt

* Debugging windows path issue

* Canonicalize

* Trim whitespace

* On windows, use echo nul instead of touch to create file in test

* Avoid cloning by using drain()
2020-07-12 16:14:09 +12:00
bdef5d7d72 Add 'str from' subcommand (#2125)
* add human, precision commands

* add 'str from' subcommand (converted from human/precision commands)

move human tests to str from

* add default locale, platform-specific SystemLocale use

* fix platform specific num-format dependency, remove invalid test

* change 'str from' localization to static num_format::Locale::en

* minor cleanup, nudge ci

* re-attempt ci
2020-07-12 15:57:39 +12:00
8d03cf5b02 added more verbose message to assert (#2157)
* added more verbose message to assert

having a -h short is bad for any command since it's already used by --help.

* updated for fmt
2020-07-11 16:49:44 -05:00
3ec0242960 fix the name of 'do' (#2152)
* fix the name of 'do'

* try to fix ci
2020-07-11 17:09:05 +12:00
0bc2e29f99 Rename 'bytes' to 'filesize' (#2153) 2020-07-11 14:17:37 +12:00
1bb6a2d9ed Split key/value in 'config set' (#2151) 2020-07-11 13:15:51 +12:00
e848fc0bbe Updates config to use subcommands (#2146)
* First commit updating `config` to use subcommands (#2119)
    - Implemented `get` subcommand

* Implmented `config set` as a subcommand.

* Implemented `config set_into` as subcommand

* Fixed base `config` command
 - Instead of outputting help, it now outputs the list of all
 configuration parameters.

* Added `config clear` subcommand

* Added `config load` and `config remove` subcommands

* Added `config path` subcommand

* fixed clippy
2020-07-11 12:11:04 +12:00
6820d70e7d make duration pretty print clearer (#2150)
* make duration pretty print clearer

* fix typo and tests

* fix typo and tests
2020-07-11 09:06:52 +12:00
f32ab696d3 Return iter from sort by (#2149) 2020-07-11 06:49:55 +12:00
e07a9e4ee7 1747 add ns to duration (#2128)
* Added nanos to Duration

* Removed unwraps

* Added nanos to Duration

* Removed unwraps

* Fixed errors

* Removed unwraps

* Changed serialization to String

* Fixed Date and Duration comparison
2020-07-11 05:48:11 +12:00
6a89b1b010 Remove duplicate method (retag) (#2147) 2020-07-10 06:21:13 -04:00
b1b93931cb Return an iter from last command (#2143) 2020-07-09 09:07:51 -04:00
1e62a8fb6e Fix variable name (#2142) 2020-07-08 19:55:01 -04:00
ed6f337a48 Refactor Fetch command (No logic changes) (#2131)
* Refactoring unrelated to Streaming...

Mainly keeping code DRY

* Remove cli as dependency
2020-07-09 04:49:27 +12:00
b004236927 Return iter from from vcf (#2137) 2020-07-08 09:20:57 -04:00
0fdb9ac5e2 str substring additions. (#2140) 2020-07-08 04:45:45 -05:00
28be39494c add requoting for completions (#2129) 2020-07-07 17:13:39 -04:00
32f18536e1 Add space to special prompt chars (#2122)
So spaces don't have to be escaped with quotes in the config.toml
2020-07-06 10:30:47 -05:00
34e1e6e426 Add "move column" command. (#2123) 2020-07-06 10:27:01 -05:00
c3ba1e476f Make every stream-able (#2120)
* Make every stream-able

* Make each over ranges stream-able
2020-07-06 20:23:27 +12:00
a1a0710ee6 Return iter from every command (#2118)
* Return iter from `every` command

* Clippy

* Better variable name
2020-07-06 14:25:39 +12:00
455b1ac294 Update config.yml 2020-07-06 08:21:46 +12:00
b2e0dc5b77 Update azure-pipelines.yml 2020-07-06 08:20:38 +12:00
d30c40b40e Bump to 0.16.1 (#2116) 2020-07-06 08:12:44 +12:00
85d848dd7d Stream results of drop command (#2114)
* Stream results of drop command

* When the amount of rows to drop is equal to or greaten than the size of the table, output nothing
2020-07-06 05:46:06 +12:00
74717582ac Slightly nicer "rm" message (#2113)
* maybe this was root issue

* quotes

* formatting
2020-07-06 05:42:37 +12:00
ee18f16378 Autoenv rewrite, security and scripting (#2083)
* Add args in .nurc file to environment

* Working dummy version

* Add add_nurc to sync_env command

* Parse .nurc file

* Delete env vars after leaving directory

* Removing vals not working, strangely

* Refactoring, add comment

* Debugging

* Debug by logging to file

* Add and remove env var behavior appears correct

However, it does not use existing code that well.

* Move work to cli.rs

* Parse config directories

* I am in a state of distress

* Rename .nurc to .nu

* Some notes for me

* Refactoring

* Removing vars works, but not done in a very nice fashion

* Refactor env_vars_to_delete

* Refactor env_vars_to_add()

* Move directory environment code to separate file

* Refactor from_config

* Restore env values

* Working?

* Working?

* Update comments and change var name

* Formatting

* Remove vars after leaving dir

* Remove notes I made

* Rename config function

* Clippy

* Cleanup and handle errors

* cargo fmt

* Better error messages, remove last (?) unwrap

* FORMAT PLZ

* Rename whitelisted_directories to allowed_directories

* Add comment to clarify how overwritten values are restored.

* Change list of allowed dirs to indexmap

* Rewrite starting

* rewrite everything

* Overwritten env values tracks an indexmap instead of vector

* Refactor restore function

* Untrack removed vars properly

* Performance concerns

* Performance concerns

* Error handling

* Clippy

* Add type aliases for String and OsString

* Deletion almost works

* Working?

* Error handling and refactoring

* nicer errors

* Add TODO file

* Move outside of loop

* Error handling

* Reworking adding of vars

* Reworking adding of vars

* Ready for testing

* Refactoring

* Restore overwritten vals code

* todo.org

* Remove overwritten values tracking, as it is not needed

* Cleanup, stop tracking overwritten values as nu takes care of it

* Init autoenv command

* Initialize autoenv and autoenv trust

* autoenv trust toml

* toml

* Use serde for autoenv

* Optional directory arg

* Add autoenv untrust command

* ... actually add autoenv untrust this time

* OsString and paths

* Revert "OsString and paths"

This reverts commit e6eedf8824.

* Fix path

* Fix path

* Autoenv trust and untrust

* Start using autoenv

* Check hashes

* Use trust functionality when setting vars

* Remove unused code

* Clippy

* Nicer errors for autoenv commands

* Non-working errors

* Update error description

* Satisfy fmt

* Errors

* Errors print, but not nicely

* Nicer errors

* fmt

* Delete accidentally added todo.org file

* Rename direnv to autoenv

* Use ShellError instead of Error

* Change tests to pass, danger zone?

* Clippy and errors

* Clippy... again

* Replace match with or_else

* Use sha2 crate for hashing

* parsing and error msg

* Refactoring

* Only apply vars once

* if parent dir

* Delete vars

* Rework exit code

* Adding works

* restore

* Fix possibility of infinite loop

* Refactoring

* Non-working

* Revert "Non-working"

This reverts commit e231b85570.

* Revert "Revert "Non-working""

This reverts commit 804092e46a.

* Autoenv trust works without restart

* Cargo fix

* Script vars

* Serde

* Serde errors

* Entry and exitscripts

* Clippy

* Support windows and handle errors

* Formatting

* Fix infinite loop on windows

* Debugging windows loop

* More windows infinite loop debugging

* Windows loop debugging #3

* windows loop #4

* Don't return err

* Cleanup unused code

* Infinite loop debug

* Loop debugging

* Check if infinite loop is vars_to_add

* env_vars_to_add does not terminate, skip loop as test

* Hypothesis: std::env::current_dir() is messing with something

* Hypothesis: std::env::current_dir() is messing with something

* plz

* make clippy happy

* debugging in env_vars_to_add

* Debbuging env_vars_to_add #2

* clippy

* clippy..

* Fool clippy

* Fix another infinite loop

* Binary search for error location x)

* Binary search #3

* fmt

* Binary search #4

* more searching...

* closing in... maybe

* PLZ

* Cleanup

* Restore commented out functionality

* Handle case when user gives the directory "."

* fmt

* Use fs::canonicalize for paths

* Create optional script section

* fmt

* Add exitscripts even if no entryscripts are defined

* All sections in .nu-env are now optional

* Re-read config file each directory change

* Hot reload after autoenv untrust, don't run exitscripts if untrusted

* Debugging

* Fix issue with recursive adding of vars

* Thank you for finding my issues Mr. Azure

* use std::env
2020-07-06 05:34:00 +12:00
9e82e5a2fa Show entire table if number of rows requested for last is greater than table size (#2112)
* Show entire table if number of rows requested for last is greater than table size

* rustfmt

* Add test
2020-07-05 13:04:17 +12:00
8ea2307815 More informative error messages when "rm" fails (#2109)
* add a nicer error message

* small fix to message and remove log
2020-07-05 13:03:12 +12:00
bbc5a28fe9 Fix buffering in lines command (#2111) 2020-07-05 12:20:58 +12:00
04120e00e4 Oops, fix crash in parser updates (#2108) 2020-07-05 08:56:54 +12:00
efd8a633f2 Align tables in "ls -f" (#2105)
* Stuff column with nothing if we have nothing

* Stuff columns at the very start

Remove unnecessary else clauses.
Add the unix cfg portion

* Added some tests and cfg windows

Not sure how I feel about these tests but it's better than nothing
2020-07-05 08:17:36 +12:00
e75c44c95b If command and touchups (#2106) 2020-07-05 07:40:04 +12:00
0629c896eb Return error for unterminated string in bareword parser (#2103)
* add some tests

* add failing tests

* use some; fix test

* clean up code, flesh out tests

* cargo fmt
2020-07-04 17:14:31 +12:00
eb02c773d0 Add 'str length' command (#2102) 2020-07-04 08:17:44 +12:00
e31e8d1550 Convert open/fetch to stream (#2028)
* Types lined up for open with stream

* Chunking stream

* Maybe I didn't need most of the Stream stuff after all?

* Some clean-up

* Merge weird cargo.lock

* Start moving some encoding logic to MaybeTextCodec

Will we lose the nice table formatting if we Stream? How do we get it back? Collect the Stream at the end?

* Clean-up and small refinements

* Put in auto-convert workaround

* Workaround to make sure bat functionality works

* Handle some easy error cases

* All tests pass

* Remove guessing logic

* Address clippy comments

* Pull latest master and fix MaybeTextCodec usage

* Add tag to enable autoview
2020-07-04 07:53:20 +12:00
8775991c2d Add 'split chars' command (#2101) 2020-07-04 07:09:38 +12:00
de8e2841a0 Numbered each (#2100)
* Add --numbered to each

* Fix example tester and add numbered each
2020-07-03 20:43:55 +12:00
5cafead4a4 Added license and license-for-less to wix build (#2097) 2020-07-03 11:30:00 +12:00
180290f3a8 Remove custom escaping for external args. (#2095)
Our own custom escaping unfortunately is far too simple to cover all cases.
Instead, the parser will now do no transforms on the args passed to an external
command, letting the process spawning library deal with doing the appropriate
escaping.
2020-07-03 11:29:28 +12:00
7813063c93 updated less license to raw gh link (#2088) 2020-07-02 16:25:07 +12:00
ba5d774fe1 Add a histogram example to the random dice documentation (#2087) 2020-07-02 16:24:28 +12:00
7be49e43fd added a few more command chars (#2086)
* added a few more command chars

* forgot my ole' friend clippy

* added unicode name synonums to characters
2020-07-01 17:34:11 -05:00
dcd2227201 Update README.md 2020-07-02 09:00:44 +12:00
2dd28c2909 updated to include less and nushell licenses (#2085) 2020-07-01 10:45:42 +12:00
515 changed files with 26502 additions and 9954 deletions

View File

@ -1,11 +1,20 @@
trigger:
- master
- main
strategy:
matrix:
linux-stable:
image: ubuntu-18.04
style: 'unflagged'
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'
@ -34,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"
@ -41,23 +51,32 @@ steps:
rustup component add clippy --toolchain stable-x86_64-apple-darwin
export PATH=$HOME/.cargo/bin:$PATH
fi
rustup update
rustc -Vv
echo "##vso[task.prependpath]$HOME/.cargo/bin"
rustup component add rustfmt
# rustup update
# rustc -Vv
# 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 --features=rustyline-support
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,3 +0,0 @@
[build]
#rustflags = ["--cfg", "data_processing_primitives"]

View File

@ -27,7 +27,7 @@ orbs:
workflows:
version: 2.0
# This builds on all pull requests to test, and ignores master
# This builds on all pull requests to test, and ignores main
build_without_deploy:
jobs:
- docker/publish:
@ -39,7 +39,7 @@ workflows:
filters:
branches:
ignore:
- master
- main
before_build:
- pull_cache
after_build:
@ -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
@ -98,11 +98,11 @@ workflows:
docker push quay.io/nushell/nu
# publish devel to Docker Hub on merge to master (doesn't build --release)
# publish devel to Docker Hub on merge to main (doesn't build --release)
build_with_deploy_devel:
jobs:
# Deploy devel tag on merge to master
# Deploy devel tag on merge to main
- docker/publish:
image: nushell/nu-base
registry: quay.io
@ -113,7 +113,7 @@ workflows:
- pull_cache
filters:
branches:
only: master
only: main
after_build:
- run:
name: Build Multistage (smaller) container
@ -137,7 +137,7 @@ workflows:
filters:
branches:
only:
- master
- main
jobs:
- docker/publish:
image: nushell/nu-base

View File

@ -11,31 +11,39 @@ jobs:
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Install libxcb
run: sudo apt-get install libxcb-composite0-dev
- name: Set up cargo
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Build
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
- name: Copy files to output
run: |
cp target/release/nu target/release/nu_plugin_* output/
cp README.build.txt output/README.txt
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
run: cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 output/
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
@ -48,26 +56,32 @@ jobs:
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Set up cargo
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Build
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
- name: Copy files to output
run: |
cp target/release/nu target/release/nu_plugin_* output/
cp README.build.txt output/README.txt
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
with:
@ -80,47 +94,61 @@ jobs:
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Set up cargo
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Add cargo-wix subcommand
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-wix
- name: Build
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
- name: Download Less Binary
run: Invoke-WebRequest -Uri "https://github.com/jftuga/less-Windows/releases/download/less-v562.1/less.exe" -OutFile "target\release\less.exe"
run: Invoke-WebRequest -Uri "https://github.com/jftuga/less-Windows/releases/download/less-v562.0/less.exe" -OutFile "target\release\less.exe"
- name: Download Less License
run: Invoke-WebRequest -Uri "https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE" -OutFile "target\release\LICENSE-for-less.txt"
- name: Copy files to output
run: |
cp target\release\nu.exe output\
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\
# Note: If the version of `less.exe` needs to be changed, update this URL
# Similarly, if `less.exe` is checked into the repo, copy from the local path here
# moved this stuff down to create wix after we download less
- name: Create msi with wix
uses: actions-rs/cargo@v1
with:
command: wix
args: --no-build --nocapture --output target\wix\nushell-windows.msi
- name: Upload installer
uses: actions/upload-artifact@v2
with:
name: windows-installer
path: target\wix\nushell-windows.msi
- name: Upload zip
uses: actions/upload-artifact@v2
with:
@ -137,6 +165,7 @@ jobs:
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Determine Release Info
id: info
env:
@ -152,6 +181,7 @@ jobs:
echo "::set-output name=macosdir::nu_${MAJOR}_${MINOR}_${PATCH}_macOS"
echo "::set-output name=windowsdir::nu_${MAJOR}_${MINOR}_${PATCH}_windows"
echo "::set-output name=innerdir::nushell-${VERSION}"
- name: Create Release Draft
id: create_release
uses: actions/create-release@v1
@ -161,19 +191,24 @@ jobs:
tag_name: ${{ github.ref }}
release_name: ${{ steps.info.outputs.version }} Release
draft: true
- name: Create Linux Directory
run: mkdir -p ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
- name: Download Linux Artifacts
uses: actions/download-artifact@v2
with:
name: linux
path: ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
- name: Restore Linux File Modes
run: |
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/nu*
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/libssl*
- name: Create Linux tarball
run: tar -zcvf ${{ steps.info.outputs.linuxdir }}.tar.gz ${{ steps.info.outputs.linuxdir }}
- name: Upload Linux Artifact
uses: actions/upload-release-asset@v1
env:
@ -183,17 +218,22 @@ jobs:
asset_path: ./${{ steps.info.outputs.linuxdir }}.tar.gz
asset_name: ${{ steps.info.outputs.linuxdir }}.tar.gz
asset_content_type: application/gzip
- name: Create macOS Directory
run: mkdir -p ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
- name: Download macOS Artifacts
uses: actions/download-artifact@v2
with:
name: macos
path: ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
- name: Restore macOS File Modes
run: chmod 755 ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}/nu*
- name: Create macOS Archive
run: zip -r ${{ steps.info.outputs.macosdir }}.zip ${{ steps.info.outputs.macosdir }}
- name: Upload macOS Artifact
uses: actions/upload-release-asset@v1
env:
@ -203,18 +243,22 @@ jobs:
asset_path: ./${{ steps.info.outputs.macosdir }}.zip
asset_name: ${{ steps.info.outputs.macosdir }}.zip
asset_content_type: application/zip
- name: Create Windows Directory
run: mkdir -p ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
- name: Download Windows zip
uses: actions/download-artifact@v2
with:
name: windows-zip
path: ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
# TODO: Remove Show
- name: Show Windows Artifacts
run: ls -la ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
- name: Create macOS Archive
run: zip -r ${{ steps.info.outputs.windowsdir }}.zip ${{ steps.info.outputs.windowsdir }}
- name: Upload Windows zip
uses: actions/upload-release-asset@v1
env:
@ -224,11 +268,13 @@ jobs:
asset_path: ./${{ steps.info.outputs.windowsdir }}.zip
asset_name: ${{ steps.info.outputs.windowsdir }}.zip
asset_content_type: application/zip
- name: Download Windows installer
uses: actions/download-artifact@v2
with:
name: windows-installer
path: ./
- name: Upload Windows installer
uses: actions/upload-release-asset@v1
env:

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

2788
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,16 @@
[package]
name = "nu"
version = "0.16.0"
authors = ["The Nu Project Contributors"]
description = "A new type of shell"
license = "MIT"
edition = "2018"
readme = "README.md"
default-run = "nu"
repository = "https://github.com/nushell/nushell"
homepage = "https://www.nushell.sh"
description = "A new type of shell"
documentation = "https://www.nushell.sh/book/"
edition = "2018"
exclude = ["images"]
homepage = "https://www.nushell.sh"
license = "MIT"
name = "nu"
readme = "README.md"
repository = "https://github.com/nushell/nushell"
version = "0.20.0"
[workspace]
members = ["crates/*/"]
@ -18,67 +18,101 @@ members = ["crates/*/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-cli = { version = "0.16.0", path = "./crates/nu-cli" }
nu-source = { version = "0.16.0", path = "./crates/nu-source" }
nu-plugin = { version = "0.16.0", path = "./crates/nu-plugin" }
nu-protocol = { version = "0.16.0", path = "./crates/nu-protocol" }
nu-errors = { version = "0.16.0", path = "./crates/nu-errors" }
nu-parser = { version = "0.16.0", path = "./crates/nu-parser" }
nu-value-ext = { version = "0.16.0", path = "./crates/nu-value-ext" }
nu_plugin_binaryview = { version = "0.16.0", path = "./crates/nu_plugin_binaryview", optional=true }
nu_plugin_fetch = { version = "0.16.0", path = "./crates/nu_plugin_fetch", optional=true }
nu_plugin_inc = { version = "0.16.0", path = "./crates/nu_plugin_inc", optional=true }
nu_plugin_match = { version = "0.16.0", path = "./crates/nu_plugin_match", optional=true }
nu_plugin_post = { version = "0.16.0", path = "./crates/nu_plugin_post", optional=true }
nu_plugin_ps = { version = "0.16.0", path = "./crates/nu_plugin_ps", optional=true }
nu_plugin_start = { version = "0.16.0", path = "./crates/nu_plugin_start", optional=true }
nu_plugin_sys = { version = "0.16.0", path = "./crates/nu_plugin_sys", optional=true }
nu_plugin_textview = { version = "0.16.0", path = "./crates/nu_plugin_textview", optional=true }
nu_plugin_tree = { version = "0.16.0", path = "./crates/nu_plugin_tree", optional=true }
nu-cli = {version = "0.20.0", path = "./crates/nu-cli"}
nu-data = {version = "0.20.0", path = "./crates/nu-data"}
nu-errors = {version = "0.20.0", path = "./crates/nu-errors"}
nu-parser = {version = "0.20.0", path = "./crates/nu-parser"}
nu-plugin = {version = "0.20.0", path = "./crates/nu-plugin"}
nu-protocol = {version = "0.20.0", path = "./crates/nu-protocol"}
nu-source = {version = "0.20.0", path = "./crates/nu-source"}
nu-value-ext = {version = "0.20.0", path = "./crates/nu-value-ext"}
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 }
nu_plugin_binaryview = {version = "0.20.0", path = "./crates/nu_plugin_binaryview", optional = true}
nu_plugin_fetch = {version = "0.20.0", path = "./crates/nu_plugin_fetch", optional = true}
nu_plugin_from_bson = {version = "0.20.0", path = "./crates/nu_plugin_from_bson", optional = true}
nu_plugin_from_sqlite = {version = "0.20.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
nu_plugin_inc = {version = "0.20.0", path = "./crates/nu_plugin_inc", optional = true}
nu_plugin_match = {version = "0.20.0", path = "./crates/nu_plugin_match", optional = true}
nu_plugin_post = {version = "0.20.0", path = "./crates/nu_plugin_post", optional = true}
nu_plugin_ps = {version = "0.20.0", path = "./crates/nu_plugin_ps", optional = true}
nu_plugin_s3 = {version = "0.20.0", path = "./crates/nu_plugin_s3", optional = true}
nu_plugin_start = {version = "0.20.0", path = "./crates/nu_plugin_start", optional = true}
nu_plugin_sys = {version = "0.20.0", path = "./crates/nu_plugin_sys", optional = true}
nu_plugin_textview = {version = "0.20.0", path = "./crates/nu_plugin_textview", optional = true}
nu_plugin_to_bson = {version = "0.20.0", path = "./crates/nu_plugin_to_bson", optional = true}
nu_plugin_to_sqlite = {version = "0.20.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
nu_plugin_tree = {version = "0.20.0", path = "./crates/nu_plugin_tree", optional = true}
clap = "2.33.1"
ctrlc = "3.1.4"
crossterm = {version = "0.17", optional = true}
semver = {version = "0.10.0", optional = true}
url = {version = "2.1.1", optional = true}
clap = "2.33.3"
ctrlc = "3.1.6"
dunce = "1.0.1"
futures = { version = "0.3", features = ["compat", "io-compat"] }
log = "0.4.8"
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
log = "0.4.11"
pretty_env_logger = "0.4.0"
starship = "0.43.0"
quick-xml = "0.18.1"
[dev-dependencies]
nu-test-support = { version = "0.16.0", path = "./crates/nu-test-support" }
nu-test-support = {version = "0.20.0", path = "./crates/nu-test-support"}
[build-dependencies]
serde = {version = "1.0.115", features = ["derive"]}
toml = "0.5.6"
serde = { version = "1.0.110", features = ["derive"] }
nu-build = { version = "0.16.0", path = "./crates/nu-build" }
[features]
default = ["sys", "ps", "textview", "inc"]
stable = ["default", "binaryview", "match", "tree", "post", "fetch", "clipboard-cli", "trash-support", "start"]
default = [
"sys",
"ps",
"textview",
"inc",
"git-support",
"directories-support",
"ctrlc-support",
"which-support",
"ptree-support",
"term-support",
"uuid-support",
"rustyline-support",
"match",
"post",
"fetch",
"rich-benchmark",
]
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3"]
stable = ["default"]
# Default
textview = ["crossterm", "syntect", "url", "nu_plugin_textview"]
sys = ["nu_plugin_sys"]
ps = ["nu_plugin_ps"]
inc = ["semver", "nu_plugin_inc"]
ps = ["nu_plugin_ps"]
sys = ["nu_plugin_sys"]
textview = ["crossterm", "url", "nu_plugin_textview"]
# Stable
binaryview = ["nu_plugin_binaryview"]
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"]
tree = ["nu_plugin_tree"]
start = ["nu_plugin_start"]
clipboard-cli = ["nu-cli/clipboard-cli"]
# starship-prompt = ["nu-cli/starship-prompt"]
ctrlc-support = ["nu-cli/ctrlc"]
directories-support = ["nu-cli/directories", "nu-cli/dirs", "nu-data/directories", "nu-data/dirs"]
git-support = ["nu-cli/git2"]
ptree-support = ["nu-cli/ptree"]
rich-benchmark = ["nu-cli/rich-benchmark"]
rustyline-support = ["nu-cli/rustyline-support"]
term-support = ["nu-cli/term"]
trash-support = ["nu-cli/trash-support"]
uuid-support = ["nu-cli/uuid_crate"]
which-support = ["nu-cli/ichwh", "nu-cli/which"]
# Core plugins that ship with `cargo install nu` by default
# Currently, Cargo limits us to installing only one binary
@ -103,37 +137,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

@ -5,6 +5,7 @@
[![Build Status](https://dev.azure.com/nushell/nushell/_apis/build/status/nushell.nushell?branchName=master)](https://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=master)
[![Discord](https://img.shields.io/discord/601130461678272522.svg?logo=discord)](https://discord.gg/NtAbbGn)
[![The Changelog #363](https://img.shields.io/badge/The%20Changelog-%23363-61c192.svg)](https://changelog.com/podcast/363)
[![@nu_shell](https://img.shields.io/badge/twitter-@nu_shell-1DA1F3?style=flat-square)](https://twitter.com/nu_shell)
## Nu Shell
@ -50,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`):
@ -66,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
@ -193,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]
@ -235,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]
name = "nu-build"
version = "0.16.0"
authors = ["The Nu Project Contributors"]
edition = "2018"
description = "Core build system for nushell"
license = "MIT"
[lib]
doctest = false
[dependencies]
serde = { version = "1.0.114", features = ["derive"] }
lazy_static = "1.4.0"
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

@ -1,115 +1,128 @@
[package]
name = "nu-cli"
version = "0.16.0"
authors = ["The Nu Project Contributors"]
description = "CLI for nushell"
edition = "2018"
license = "MIT"
name = "nu-cli"
version = "0.20.0"
[lib]
doctest = false
[dependencies]
nu-source = { version = "0.16.0", path = "../nu-source" }
nu-plugin = { version = "0.16.0", path = "../nu-plugin" }
nu-protocol = { version = "0.16.0", path = "../nu-protocol" }
nu-errors = { version = "0.16.0", path = "../nu-errors" }
nu-parser = { version = "0.16.0", path = "../nu-parser" }
nu-value-ext = { version = "0.16.0", path = "../nu-value-ext" }
nu-test-support = { version = "0.16.0", path = "../nu-test-support" }
nu-table = {version = "0.16.0", path = "../nu-table"}
nu-data = {version = "0.20.0", path = "../nu-data"}
nu-errors = {version = "0.20.0", path = "../nu-errors"}
nu-parser = {version = "0.20.0", path = "../nu-parser"}
nu-plugin = {version = "0.20.0", path = "../nu-plugin"}
nu-protocol = {version = "0.20.0", path = "../nu-protocol"}
nu-source = {version = "0.20.0", path = "../nu-source"}
nu-table = {version = "0.20.0", path = "../nu-table"}
nu-test-support = {version = "0.20.0", path = "../nu-test-support"}
nu-value-ext = {version = "0.20.0", path = "../nu-value-ext"}
ansi_term = "0.12.1"
app_dirs = "1.2.1"
async-recursion = "0.3.1"
async-trait = "0.1.36"
directories = "2.0.2"
async-trait = "0.1.40"
base64 = "0.12.3"
bigdecimal = { version = "0.1.2", features = ["serde"] }
bson = { version = "0.14.1", features = ["decimal128"] }
byte-unit = "3.1.3"
bytes = "0.5.5"
calamine = "0.16"
cfg-if = "0.1"
chrono = { version = "0.4.11", features = ["serde"] }
clap = "2.33.1"
csv = "1.1"
ctrlc = "3.1.4"
bigdecimal = {version = "0.2.0", features = ["serde"]}
byte-unit = "4.0.9"
bytes = "0.5.6"
calamine = "0.16.1"
chrono = {version = "0.4.15", features = ["serde"]}
clap = "2.33.3"
codespan-reporting = "0.9.5"
csv = "1.1.3"
ctrlc = {version = "3.1.6", optional = true}
derive-new = "0.5.8"
dirs = "2.0.2"
directories = {version = "3.0.1", optional = true}
dirs = {version = "3.0.1", optional = true}
dtparse = "1.1.0"
dunce = "1.0.1"
eml-parser = "0.1.0"
filesize = "0.2.0"
futures = { version = "0.3", features = ["compat", "io-compat"] }
fs_extra = "1.2.0"
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
futures-util = "0.3.5"
futures_codec = "0.4"
futures_codec = "0.4.1"
getset = "0.1.1"
git2 = { version = "0.13.6", default_features = false }
git2 = {version = "0.13.11", default_features = false, optional = true}
glob = "0.3.0"
hex = "0.4"
heim = {version = "0.1.0-beta.3", optional = true}
htmlescape = "0.3.1"
ical = "0.6.*"
ichwh = "0.3.4"
indexmap = { version = "1.4.0", features = ["serde-1"] }
ical = "0.6.0"
ichwh = {version = "0.3.4", optional = true}
indexmap = {version = "1.6.0", features = ["serde-1"]}
itertools = "0.9.0"
codespan-reporting = "0.9.5"
log = "0.4.8"
meval = "0.2"
log = "0.4.11"
meval = "0.2.0"
natural = "0.5.0"
num-bigint = { version = "0.2.6", features = ["serde"] }
num-traits = "0.2.11"
num-bigint = {version = "0.3.0", features = ["serde"]}
num-format = {version = "0.4.0", features = ["with-num-bigint"]}
num-traits = "0.2.12"
parking_lot = "0.11.0"
pin-utils = "0.1.0"
pretty-hex = "0.1.1"
pretty_env_logger = "0.4.0"
ptree = {version = "0.2" }
pretty-hex = "0.2.0"
ptree = {version = "0.3.0", optional = true}
query_interface = "0.3.5"
rand = "0.7"
regex = "1"
rand = "0.7.3"
regex = "1.3.9"
roxmltree = "0.13.0"
rustyline = "6.2.0"
serde = { version = "1.0.114", features = ["derive"] }
rust-embed = "5.6.0"
rustyline = {version = "6.3.0", optional = true}
serde = {version = "1.0.115", features = ["derive"]}
serde-hjson = "0.9.1"
serde_bytes = "0.11.5"
serde_ini = "0.2.0"
serde_json = "1.0.55"
serde_urlencoded = "0.6.1"
serde_yaml = "0.8"
serde_json = "1.0.57"
serde_urlencoded = "0.7.0"
serde_yaml = "0.8.13"
sha2 = "0.9.1"
shellexpand = "2.0.0"
strip-ansi-escapes = "0.1.0"
tempfile = "3.1.0"
term = "0.5.2"
termcolor = "1.1.0"
term = {version = "0.6.1", optional = true}
term_size = "0.3.2"
termcolor = "1.1.0"
toml = "0.5.6"
typetag = "0.1.5"
umask = "1.0.0"
unicode-xid = "0.2.1"
uuid_crate = { package = "uuid", version = "0.8.1", features = ["v4"] }
which = "4.0.1"
unicode-segmentation = "1.6.0"
uom = {version = "0.28.0", features = ["f64", "try-from"]}
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
which = {version = "4.0.2", optional = true}
zip = {version = "0.5.7", optional = true}
trash = { version = "1.0.1", optional = true }
clipboard = { version = "0.5", optional = true }
starship = "0.43.0"
rayon = "1.3.1"
encoding_rs = "0.8.23"
Inflector = "0.11"
clipboard = {version = "0.5.0", optional = true}
encoding_rs = "0.8.24"
quick-xml = "0.18.1"
rayon = "1.4.0"
trash = {version = "1.1.1", optional = true}
url = "2.1.1"
[target.'cfg(unix)'.dependencies]
umask = "1.0.0"
users = "0.10.0"
# TODO this will be possible with new dependency resolver
# (currently on nightly behind -Zfeatures=itarget):
# https://github.com/rust-lang/cargo/issues/7914
#[target.'cfg(not(windows))'.dependencies]
#num-format = {version = "0.4", features = ["with-system-locale"]}
[dependencies.rusqlite]
version = "0.23.1"
features = ["bundled", "blob"]
optional = true
version = "0.24.0"
[build-dependencies]
nu-build = { version = "0.16.0", path = "../nu-build" }
git2 = {version = "0.13.11", optional = true}
[dev-dependencies]
quickcheck = "0.9"
quickcheck_macros = "0.9"
quickcheck = "0.9.2"
quickcheck_macros = "0.9.1"
[features]
stable = []
# starship-prompt = ["starship"]
clipboard-cli = ["clipboard"]
rich-benchmark = ["heim"]
rustyline-support = ["rustyline"]
stable = []
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())
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,64 @@
use crate::commands::Command;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_parser::SignatureRegistry;
use nu_protocol::Signature;
use parking_lot::Mutex;
use std::sync::Arc;
#[derive(Debug, Clone, Default)]
pub struct CommandRegistry {
registry: Arc<Mutex<IndexMap<String, Command>>>,
}
impl SignatureRegistry for CommandRegistry {
fn has(&self, name: &str) -> bool {
let registry = self.registry.lock();
registry.contains_key(name)
}
fn get(&self, name: &str) -> Option<Signature> {
let registry = self.registry.lock();
registry.get(name).map(|command| command.signature())
}
fn clone_box(&self) -> Box<dyn SignatureRegistry> {
Box::new(self.clone())
}
}
impl CommandRegistry {
pub fn new() -> CommandRegistry {
CommandRegistry {
registry: Arc::new(Mutex::new(IndexMap::default())),
}
}
}
impl CommandRegistry {
pub fn get_command(&self, name: &str) -> Option<Command> {
let registry = self.registry.lock();
registry.get(name).cloned()
}
pub fn expect_command(&self, name: &str) -> Result<Command, ShellError> {
self.get_command(name).ok_or_else(|| {
ShellError::untagged_runtime_error(format!("Could not load command: {}", name))
})
}
pub fn has(&self, name: &str) -> bool {
let registry = self.registry.lock();
registry.contains_key(name)
}
pub fn insert(&mut self, name: impl Into<String>, command: Command) {
let mut registry = self.registry.lock();
registry.insert(name.into(), command);
}
pub fn names(&self) -> Vec<String> {
let registry = self.registry.lock();
registry.keys().cloned().collect()
}
}

View File

@ -8,18 +8,22 @@ pub(crate) mod alias;
pub(crate) mod ansi;
pub(crate) mod append;
pub(crate) mod args;
pub(crate) mod autoenv;
pub(crate) mod autoenv_trust;
pub(crate) mod autoenv_untrust;
pub(crate) mod autoview;
pub(crate) mod benchmark;
pub(crate) mod build_string;
pub(crate) mod cal;
pub(crate) mod calc;
pub(crate) mod cd;
pub(crate) mod char_;
pub(crate) mod classified;
#[cfg(feature = "clipboard")]
#[cfg(feature = "clipboard-cli")]
pub(crate) mod clip;
pub(crate) mod command;
pub(crate) mod compact;
pub(crate) mod config;
pub(crate) mod constants;
pub(crate) mod count;
pub(crate) mod cp;
pub(crate) mod date;
@ -31,21 +35,18 @@ pub(crate) mod du;
pub(crate) mod each;
pub(crate) mod echo;
pub(crate) mod enter;
#[allow(unused)]
pub(crate) mod evaluate_by;
pub(crate) mod every;
pub(crate) mod exec;
pub(crate) mod exit;
pub(crate) mod first;
pub(crate) mod format;
pub(crate) mod from;
pub(crate) mod from_bson;
pub(crate) mod from_csv;
pub(crate) mod from_eml;
pub(crate) mod from_ics;
pub(crate) mod from_ini;
pub(crate) mod from_json;
pub(crate) mod from_ods;
pub(crate) mod from_sqlite;
pub(crate) mod from_ssv;
pub(crate) mod from_toml;
pub(crate) mod from_tsv;
@ -61,33 +62,31 @@ pub(crate) mod headers;
pub(crate) mod help;
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 keep_until;
pub(crate) mod keep_while;
pub(crate) mod last;
pub(crate) mod lines;
pub(crate) mod ls;
#[allow(unused)]
pub(crate) mod map_max_by;
pub(crate) mod math;
pub(crate) mod merge;
pub(crate) mod mkdir;
pub(crate) mod mv;
pub(crate) mod move_;
pub(crate) mod next;
pub(crate) mod nth;
pub(crate) mod nu;
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;
#[allow(unused)]
pub(crate) mod reduce_by;
pub(crate) mod reduce;
pub(crate) mod reject;
pub(crate) mod rename;
pub(crate) mod reverse;
@ -100,30 +99,26 @@ pub(crate) mod shells;
pub(crate) mod shuffle;
pub(crate) mod size;
pub(crate) mod skip;
pub(crate) mod skip_until;
pub(crate) mod skip_while;
pub(crate) mod sleep;
pub(crate) mod sort_by;
pub(crate) mod split;
pub(crate) mod split_by;
pub(crate) mod str_;
#[allow(unused)]
pub(crate) mod t_sort_by;
pub(crate) mod table;
pub(crate) mod tags;
pub(crate) mod to;
pub(crate) mod to_bson;
pub(crate) mod to_csv;
pub(crate) mod to_html;
pub(crate) mod to_json;
pub(crate) mod to_md;
pub(crate) mod to_sqlite;
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_;
@ -140,23 +135,32 @@ pub(crate) use command::{
pub(crate) use alias::Alias;
pub(crate) use ansi::Ansi;
pub(crate) use append::Append;
pub(crate) use autoenv::Autoenv;
pub(crate) use autoenv_trust::AutoenvTrust;
pub(crate) use autoenv_untrust::AutoenvUnTrust;
pub(crate) use benchmark::Benchmark;
pub(crate) use build_string::BuildString;
pub(crate) use cal::Cal;
pub(crate) use calc::Calc;
pub(crate) use char_::Char;
pub(crate) use compact::Compact;
pub(crate) use config::Config;
pub(crate) use config::{
Config, ConfigClear, ConfigGet, ConfigLoad, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto,
};
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;
pub(crate) use drop::Drop;
pub(crate) use du::Du;
pub(crate) use each::Each;
pub(crate) use each::EachGroup;
pub(crate) use each::EachWindow;
pub(crate) use echo::Echo;
pub(crate) use if_::If;
pub(crate) use is_empty::IsEmpty;
pub(crate) use nu::NuPlugin;
pub(crate) use update::Update;
pub(crate) mod kill;
pub(crate) use kill::Kill;
@ -164,22 +168,18 @@ pub(crate) mod clear;
pub(crate) use clear::Clear;
pub(crate) mod touch;
pub(crate) use enter::Enter;
#[allow(unused_imports)]
pub(crate) use evaluate_by::EvaluateBy;
pub(crate) use every::Every;
pub(crate) use exec::Exec;
pub(crate) use exit::Exit;
pub(crate) use first::First;
pub(crate) use format::Format;
pub(crate) use from::From;
pub(crate) use from_bson::FromBSON;
pub(crate) use from_csv::FromCSV;
pub(crate) use from_eml::FromEML;
pub(crate) use from_ics::FromIcs;
pub(crate) use from_ini::FromINI;
pub(crate) use from_json::FromJSON;
pub(crate) use from_ods::FromODS;
pub(crate) use from_sqlite::FromDB;
pub(crate) use from_sqlite::FromSQLite;
pub(crate) use from_ssv::FromSSV;
pub(crate) use from_toml::FromTOML;
pub(crate) use from_tsv::FromTSV;
@ -197,32 +197,35 @@ pub(crate) use help::Help;
pub(crate) use histogram::Histogram;
pub(crate) use history::History;
pub(crate) use insert::Insert;
pub(crate) use keep::Keep;
pub(crate) use keep_until::KeepUntil;
pub(crate) use keep_while::KeepWhile;
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;
#[allow(unused_imports)]
pub(crate) use map_max_by::MapMaxBy;
pub(crate) use math::{
Math, MathAverage, MathMaximum, MathMedian, MathMinimum, MathMode, MathSummation,
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathProduct,
MathStddev, MathSummation, MathVariance,
};
pub(crate) use merge::Merge;
pub(crate) use mkdir::Mkdir;
pub(crate) use mv::Move;
pub(crate) use move_::{Move, MoveColumn, Mv};
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;
pub(crate) use pwd::Pwd;
pub(crate) use random::{Random, RandomBool, RandomDice, RandomUUID};
#[cfg(feature = "uuid_crate")]
pub(crate) use random::RandomUUID;
pub(crate) use random::{Random, RandomBool, RandomDice, RandomInteger};
pub(crate) use range::Range;
#[allow(unused_imports)]
pub(crate) use reduce_by::ReduceBy;
pub(crate) use reduce::Reduce;
pub(crate) use reject::Reject;
pub(crate) use rename::Rename;
pub(crate) use reverse::Reverse;
@ -233,37 +236,32 @@ pub(crate) use select::Select;
pub(crate) use shells::Shells;
pub(crate) use shuffle::Shuffle;
pub(crate) use size::Size;
pub(crate) use skip::Skip;
pub(crate) use skip_until::SkipUntil;
pub(crate) use skip_while::SkipWhile;
pub(crate) use skip::{Skip, SkipUntil, SkipWhile};
pub(crate) use sleep::Sleep;
pub(crate) use sort_by::SortBy;
pub(crate) use split::Split;
pub(crate) use split::SplitColumn;
pub(crate) use split::SplitRow;
pub(crate) use split::{Split, SplitChars, SplitColumn, SplitRow};
pub(crate) use split_by::SplitBy;
pub(crate) use str_::{
Str, StrCapitalize, StrCollect, StrDowncase, StrFindReplace, 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,
};
#[allow(unused_imports)]
pub(crate) use t_sort_by::TSortBy;
pub(crate) use table::Table;
pub(crate) use tags::Tags;
pub(crate) use to::To;
pub(crate) use to_bson::ToBSON;
pub(crate) use to_csv::ToCSV;
pub(crate) use to_html::ToHTML;
pub(crate) use to_json::ToJSON;
pub(crate) use to_md::ToMarkdown;
pub(crate) use to_sqlite::ToDB;
pub(crate) use to_sqlite::ToSQLite;
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::command_registry::CommandRegistry;
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};
@ -40,6 +41,14 @@ impl WholeStreamCommand for Ansi {
example: r#"ansi reset"#,
result: Some(vec![Value::from("\u{1b}[0m")]),
},
Example {
description:
"Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)",
example: r#"echo [$(ansi rb) Hello " " $(ansi gb) Nu " " $(ansi pb) World] | str collect"#,
result: Some(vec![Value::from(
"\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld",
)]),
},
]
}
@ -68,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

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};

View File

@ -0,0 +1,92 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
use serde::Deserialize;
use serde::Serialize;
use sha2::{Digest, Sha256};
use std::io::Read;
use std::path::PathBuf;
pub struct Autoenv;
#[derive(Deserialize, Serialize, Debug, Default)]
pub struct Trusted {
pub files: IndexMap<String, Vec<u8>>,
}
impl Trusted {
pub fn new() -> Self {
Trusted {
files: IndexMap::new(),
}
}
}
pub fn file_is_trusted(nu_env_file: &PathBuf, content: &[u8]) -> Result<bool, ShellError> {
let contentdigest = Sha256::digest(&content).as_slice().to_vec();
let nufile = std::fs::canonicalize(nu_env_file)?;
let trusted = read_trusted()?;
Ok(trusted.files.get(&nufile.to_string_lossy().to_string()) == Some(&contentdigest))
}
pub fn read_trusted() -> Result<Trusted, ShellError> {
let config_path = config::default_path_for(&Some(PathBuf::from("nu-env.toml")))?;
let mut file = std::fs::OpenOptions::new()
.read(true)
.create(true)
.write(true)
.open(config_path)
.map_err(|_| ShellError::untagged_runtime_error("Couldn't open nu-env.toml"))?;
let mut doc = String::new();
file.read_to_string(&mut doc)?;
let allowed = toml::de::from_str(doc.as_str()).unwrap_or_else(|_| Trusted::new());
Ok(allowed)
}
#[async_trait]
impl WholeStreamCommand for Autoenv {
fn name(&self) -> &str {
"autoenv"
}
fn usage(&self) -> &str {
// "Mark a .nu-env file in a directory as trusted. Needs to be re-run after each change to the file or its filepath."
r#"Manage directory specific environment variables and scripts. Create a file called .nu-env in any directory and run 'autoenv trust' to let nushell read it when entering the directory.
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."#
}
fn signature(&self) -> Signature {
Signature::build("autoenv")
}
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(&Autoenv, &registry))
.into_value(Tag::unknown()),
)))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Example .nu-env file",
example: r#"cat .nu-env
[env]
mykey = "myvalue"
[scriptvars]
myscript = "echo myval"
[scripts]
entryscripts = ["touch hello.txt", "touch hello2.txt"]
exitscripts = ["touch bye.txt"]"#,
result: None,
}]
}
}

View File

@ -0,0 +1,83 @@
use super::autoenv::read_trusted;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::SyntaxShape;
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use sha2::{Digest, Sha256};
use std::{fs, path::PathBuf};
pub struct AutoenvTrust;
#[async_trait]
impl WholeStreamCommand for AutoenvTrust {
fn name(&self) -> &str {
"autoenv trust"
}
fn signature(&self) -> Signature {
Signature::build("autoenv trust").optional("dir", SyntaxShape::String, "Directory to allow")
}
fn usage(&self) -> &str {
"Trust a .nu-env file in the current or given directory"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let file_to_trust = match args.call_info.evaluate(registry).await?.args.nth(0) {
Some(Value {
value: UntaggedValue::Primitive(Primitive::String(ref path)),
tag: _,
}) => {
let mut dir = fs::canonicalize(path)?;
dir.push(".nu-env");
dir
}
_ => {
let mut dir = fs::canonicalize(std::env::current_dir()?)?;
dir.push(".nu-env");
dir
}
};
let content = std::fs::read(&file_to_trust)?;
let filename = file_to_trust.to_string_lossy().to_string();
let mut allowed = read_trusted()?;
allowed
.files
.insert(filename, Sha256::digest(&content).as_slice().to_vec());
let config_path = config::default_path_for(&Some(PathBuf::from("nu-env.toml")))?;
let tomlstr = toml::to_string(&allowed).map_err(|_| {
ShellError::untagged_runtime_error("Couldn't serialize allowed dirs to nu-env.toml")
})?;
fs::write(config_path, tomlstr).expect("Couldn't write to toml file");
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(".nu-env trusted!").into_value(tag),
)))
}
fn is_binary(&self) -> bool {
false
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Allow .nu-env file in current directory",
example: "autoenv trust",
result: None,
},
Example {
description: "Allow .nu-env file in directory foo",
example: "autoenv trust foo",
result: None,
},
]
}
}

View File

@ -0,0 +1,107 @@
use super::autoenv::Trusted;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::SyntaxShape;
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use std::io::Read;
use std::{fs, path::PathBuf};
pub struct AutoenvUnTrust;
#[async_trait]
impl WholeStreamCommand for AutoenvUnTrust {
fn name(&self) -> &str {
"autoenv untrust"
}
fn signature(&self) -> Signature {
Signature::build("autoenv untrust").optional(
"dir",
SyntaxShape::String,
"Directory to disallow",
)
}
fn usage(&self) -> &str {
"Untrust a .nu-env file in the current or given directory"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let file_to_untrust = match args.call_info.evaluate(registry).await?.args.nth(0) {
Some(Value {
value: UntaggedValue::Primitive(Primitive::String(ref path)),
tag: _,
}) => {
let mut dir = fs::canonicalize(path)?;
dir.push(".nu-env");
dir
}
_ => {
let mut dir = std::env::current_dir()?;
dir.push(".nu-env");
dir
}
};
let config_path = config::default_path_for(&Some(PathBuf::from("nu-env.toml")))?;
let mut file = match std::fs::OpenOptions::new()
.read(true)
.create(true)
.write(true)
.open(config_path.clone())
{
Ok(p) => p,
Err(_) => {
return Err(ShellError::untagged_runtime_error(
"Couldn't open nu-env.toml",
));
}
};
let mut doc = String::new();
file.read_to_string(&mut doc)?;
let mut allowed: Trusted = toml::from_str(doc.as_str()).unwrap_or_else(|_| Trusted::new());
let file_to_untrust = file_to_untrust.to_string_lossy().to_string();
if allowed.files.remove(&file_to_untrust).is_none() {
return
Err(ShellError::untagged_runtime_error(
"No .nu-env file to untrust in the given directory. Is it missing, or already untrusted?",
));
}
let tomlstr = toml::to_string(&allowed).map_err(|_| {
ShellError::untagged_runtime_error("Couldn't serialize allowed dirs to nu-env.toml")
})?;
fs::write(config_path, tomlstr).expect("Couldn't write to toml file");
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(".nu-env untrusted!").into_value(tag),
)))
}
fn is_binary(&self) -> bool {
false
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Disallow .nu-env file in current directory",
example: "autoenv untrust",
result: None,
},
Example {
description: "Disallow .nu-env file in directory foo",
example: "autoenv untrust foo",
result: None,
},
]
}
}

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

@ -0,0 +1,202 @@
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
#[cfg(feature = "rich-benchmark")]
use heim::cpu::time;
use nu_errors::ShellError;
use nu_protocol::{
hir::{Block, ClassifiedCommand, Commands, InternalCommand},
Dictionary, Scope, Signature, SyntaxShape, UntaggedValue, Value,
};
use std::convert::TryInto;
use std::time::{Duration, Instant};
pub struct Benchmark;
#[derive(Deserialize, Debug)]
struct BenchmarkArgs {
block: Block,
passthrough: Option<Block>,
}
#[async_trait]
impl WholeStreamCommand for Benchmark {
fn name(&self) -> &str {
"benchmark"
}
fn signature(&self) -> Signature {
Signature::build("benchmark")
.required(
"block",
SyntaxShape::Block,
"the block to run and benchmark",
)
.named(
"passthrough",
SyntaxShape::Block,
"Display the benchmark results and pass through the block's output",
Some('p'),
)
}
fn usage(&self) -> &str {
"Runs a block and returns the time it took to execute it"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
benchmark(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Benchmarks a command within a block",
example: "benchmark { sleep 500ms }",
result: None,
},
Example {
description: "Benchmarks a command within a block and passes its output through",
example: "echo 45 | benchmark { sleep 500ms } --passthrough {}",
result: Some(vec![UntaggedValue::int(45).into()]),
},
]
}
}
async fn benchmark(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let tag = raw_args.call_info.args.span;
let mut context = EvaluationContext::from_raw(&raw_args, &registry);
let scope = raw_args.call_info.scope.clone();
let (BenchmarkArgs { block, passthrough }, input) = raw_args.process(&registry).await?;
let start_time = Instant::now();
#[cfg(feature = "rich-benchmark")]
let start = time().await;
let result = run_block(
&block,
&mut context,
input,
&scope.it,
&scope.vars,
&scope.env,
)
.await;
let output = result?.into_vec().await;
#[cfg(feature = "rich-benchmark")]
let end = time().await;
let end_time = Instant::now();
context.clear_errors();
// return basic runtime
#[cfg(not(feature = "rich-benchmark"))]
{
let mut indexmap = IndexMap::with_capacity(1);
let real_time = into_big_int(end_time - start_time);
indexmap.insert("real time".to_string(), real_time);
benchmark_output(indexmap, output, passthrough, &tag, &mut context, &scope).await
}
// return advanced stats
#[cfg(feature = "rich-benchmark")]
if let (Ok(start), Ok(end)) = (start, end) {
let mut indexmap = IndexMap::with_capacity(4);
let real_time = into_big_int(end_time - start_time);
indexmap.insert("real time".to_string(), real_time);
let user_time = into_big_int(end.user() - start.user());
indexmap.insert("user time".to_string(), user_time);
let system_time = into_big_int(end.system() - start.system());
indexmap.insert("system time".to_string(), system_time);
let idle_time = into_big_int(end.idle() - start.idle());
indexmap.insert("idle time".to_string(), idle_time);
benchmark_output(indexmap, output, passthrough, &tag, &mut context, &scope).await
} else {
Err(ShellError::untagged_runtime_error(
"Could not retreive CPU time",
))
}
}
async fn benchmark_output<T, Output>(
indexmap: IndexMap<String, BigInt>,
block_output: Output,
passthrough: Option<Block>,
tag: T,
context: &mut EvaluationContext,
scope: &Scope,
) -> Result<OutputStream, ShellError>
where
T: Into<Tag> + Copy,
Output: Into<OutputStream>,
{
let value = UntaggedValue::Row(Dictionary::from(
indexmap
.into_iter()
.map(|(k, v)| (k, UntaggedValue::duration(v).into_value(tag)))
.collect::<IndexMap<String, Value>>(),
))
.into_value(tag);
if let Some(time_block) = passthrough {
let benchmark_output = InputStream::one(value);
// add autoview for an empty block
let time_block = add_implicit_autoview(time_block);
let _ = run_block(
&time_block,
context,
benchmark_output,
&scope.it,
&scope.vars,
&scope.env,
)
.await?;
context.clear_errors();
Ok(block_output.into())
} else {
let benchmark_output = OutputStream::one(value);
Ok(benchmark_output)
}
}
fn add_implicit_autoview(mut block: Block) -> Block {
if block.block.is_empty() {
block.push({
let mut commands = Commands::new(block.span);
commands.push(ClassifiedCommand::Internal(InternalCommand::new(
"autoview".to_string(),
block.span,
block.span,
)));
commands
});
}
block
}
fn into_big_int<T: TryInto<Duration>>(time: T) -> BigInt {
time.try_into()
.unwrap_or_else(|_| Duration::new(0, 0))
.as_nanos()
.into()
}

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

@ -1,88 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
pub struct Calc;
#[async_trait]
impl WholeStreamCommand for Calc {
fn name(&self) -> &str {
"calc"
}
fn usage(&self) -> &str {
"Parse a math expression into a number"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
calc(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Calculate math in the pipeline",
example: "echo '10 / 4' | calc",
result: Some(vec![UntaggedValue::decimal(2.5).into()]),
}]
}
}
pub async fn calc(
args: CommandArgs,
_registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let input = args.input;
let name = args.call_info.name_tag.span;
Ok(input
.map(move |input| {
if let Ok(string) = input.as_string() {
match parse(&string, &input.tag) {
Ok(value) => ReturnSuccess::value(value),
Err(err) => Err(ShellError::labeled_error(
"Calculation error",
err,
&input.tag.span,
)),
}
} else {
Err(ShellError::labeled_error(
"Expected a string from pipeline",
"requires string input",
name,
))
}
})
.to_output_stream())
}
pub fn parse(math_expression: &str, tag: impl Into<Tag>) -> Result<Value, String> {
use std::f64;
let num = meval::eval_str(math_expression);
match num {
Ok(num) => {
if num == f64::INFINITY || num == f64::NEG_INFINITY {
return Err(String::from("cannot represent result"));
}
Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag))
}
Err(error) => Err(error.to_string()),
}
}
#[cfg(test)]
mod tests {
use super::Calc;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Calc {})
}
}

View File

@ -30,11 +30,22 @@ impl WholeStreamCommand for Char {
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Output newline",
example: r#"char newline"#,
result: Some(vec![Value::from("\n")]),
}]
vec![
Example {
description: "Output newline",
example: r#"char newline"#,
result: Some(vec![Value::from("\n")]),
},
Example {
description: "Output prompt character, newline and a hamburger character",
example: r#"echo $(char prompt) $(char newline) $(char hamburger)"#,
result: Some(vec![
UntaggedValue::string("\u{25b6}").into(),
UntaggedValue::string("\n").into(),
UntaggedValue::string("\u{2261}").into(),
]),
},
]
}
async fn run(
@ -64,6 +75,54 @@ fn str_to_character(s: &str) -> Option<String> {
match s {
"newline" | "enter" | "nl" => Some("\n".into()),
"tab" => Some("\t".into()),
"sp" | "space" => Some(" ".into()),
// Unicode names came from https://www.compart.com/en/unicode
// Private Use Area (U+E000-U+F8FF)
"branch" => Some('\u{e0a0}'.to_string()), // 
"segment" => Some('\u{e0b0}'.to_string()), // 
"identical_to" | "hamburger" => Some('\u{2261}'.to_string()), // ≡
"not_identical_to" | "branch_untracked" => Some('\u{2262}'.to_string()), // ≢
"strictly_equivalent_to" | "branch_identical" => Some('\u{2263}'.to_string()), // ≣
"upwards_arrow" | "branch_ahead" => Some('\u{2191}'.to_string()), // ↑
"downwards_arrow" | "branch_behind" => Some('\u{2193}'.to_string()), // ↓
"up_down_arrow" | "branch_ahead_behind" => Some('\u{2195}'.to_string()), // ↕
"black_right_pointing_triangle" | "prompt" => Some('\u{25b6}'.to_string()), // ▶
"vector_or_cross_product" | "failed" => Some('\u{2a2f}'.to_string()), //
"high_voltage_sign" | "elevated" => Some('\u{26a1}'.to_string()), // ⚡
"tilde" | "twiddle" | "squiggly" | "home" => Some("~".into()), // ~
"hash" | "hashtag" | "pound_sign" | "sharp" | "root" => Some("#".into()), // #
// Weather symbols
"sun" => Some("\x1b[33;1m\u{2600}\x1b[0m".to_string()), // Yellow Bold ☀
"moon" => Some("\x1b[36m\u{263d}\x1b[0m".to_string()), // Cyan ☽
"clouds" => Some("\x1b[37;1m\u{2601}\x1b[0m".to_string()), // White Bold ☁
"rain" => Some("\x1b[37;1m\u{2614}\x1b[0m".to_string()), // White Bold ☔
"fog" => Some("\x1b[37;1m\u{2592}\x1b[0m".to_string()), // White Bold ▒
"mist" => Some("\x1b[34m\u{2591}\x1b[0m".to_string()), // Blue ░
"haze" => Some("\x1b[33m\u{2591}\x1b[0m".to_string()), // Yellow ░
"snow" => Some("\x1b[37;1m\u{2744}\x1b[0m".to_string()), // White Bold ❄
"thunderstorm" => Some("\x1b[33;1m\u{26a1}\x1b[0m".to_string()), // Yellow Bold ⚡
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
// Another good reference http://ascii-table.com/ansi-escape-sequences.php
// For setting title like `echo [$(char title) $(pwd) $(char bel)] | str collect`
"title" => Some("\x1b]2;".to_string()), // ESC]2; xterm sets window title
"bel" => Some('\x07'.to_string()), // Terminal Bell
"backspace" => Some('\x08'.to_string()), // Backspace
// Ansi Erase Sequences
"clear_screen" => Some("\x1b[J".to_string()), // clears the screen
"clear_screen_from_cursor_to_end" => Some("\x1b[0J".to_string()), // clears from cursor until end of screen
"clear_screen_from_cursor_to_beginning" => Some("\x1b[1J".to_string()), // clears from cursor to beginning of screen
"cls" | "clear_entire_screen" => Some("\x1b[2J".to_string()), // clears the entire screen
"erase_line" => Some("\x1b[K".to_string()), // clears the current line
"erase_line_from_cursor_to_end" => Some("\x1b[0K".to_string()), // clears from cursor to end of line
"erase_line_from_cursor_to_beginning" => Some("\x1b[1K".to_string()), // clears from cursor to start of line
"erase_entire_line" => Some("\x1b[2K".to_string()), // clears entire line
_ => None,
}
}

View File

@ -1,6 +1,6 @@
use crate::commands::classified::expr::run_expression_block;
use crate::commands::classified::internal::run_internal_command;
use crate::context::Context;
use crate::evaluation_context::EvaluationContext;
use crate::prelude::*;
use crate::stream::InputStream;
use futures::stream::TryStreamExt;
@ -11,7 +11,7 @@ use std::sync::atomic::Ordering;
pub(crate) async fn run_block(
block: &Block,
ctx: &mut Context,
ctx: &mut EvaluationContext,
mut input: InputStream,
it: &Value,
vars: &IndexMap<String, Value>,
@ -64,33 +64,27 @@ pub(crate) async fn run_block(
async fn run_pipeline(
commands: &Commands,
ctx: &mut Context,
ctx: &mut EvaluationContext,
mut input: InputStream,
it: &Value,
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

@ -10,7 +10,7 @@ use nu_protocol::Value;
pub(crate) async fn run_expression_block(
expr: SpannedExpression,
context: &mut Context,
context: &mut EvaluationContext,
it: &Value,
vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>,

View File

@ -1,3 +1,4 @@
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
use crate::evaluate::evaluate_baseline_expr;
use crate::futures::ThreadedReceiver;
use crate::prelude::*;
@ -7,91 +8,25 @@ use std::ops::Deref;
use std::process::{Command, Stdio};
use std::sync::mpsc;
use bytes::{BufMut, Bytes, BytesMut};
use futures::executor::block_on_stream;
// use futures::stream::StreamExt;
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;
pub enum StringOrBinary {
String(String),
Binary(Vec<u8>),
}
pub struct MaybeTextCodec;
impl futures_codec::Encoder for MaybeTextCodec {
type Item = StringOrBinary;
type Error = std::io::Error;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
match item {
StringOrBinary::String(s) => {
dst.reserve(s.len());
dst.put(s.as_bytes());
Ok(())
}
StringOrBinary::Binary(b) => {
dst.reserve(b.len());
dst.put(Bytes::from(b));
Ok(())
}
}
}
}
impl futures_codec::Decoder for MaybeTextCodec {
type Item = StringOrBinary;
type Error = std::io::Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
let v: Vec<u8> = src.to_vec();
match String::from_utf8(v) {
Ok(s) => {
src.clear();
if s.is_empty() {
Ok(None)
} else {
Ok(Some(StringOrBinary::String(s)))
}
}
Err(err) => {
// Note: the longest UTF-8 character per Unicode spec is currently 6 bytes. If we fail somewhere earlier than the last 6 bytes,
// we know that we're failing to understand the string encoding and not just seeing a partial character. When this happens, let's
// fall back to assuming it's a binary buffer.
if src.is_empty() {
Ok(None)
} else if src.len() > 6 && (src.len() - err.utf8_error().valid_up_to() > 6) {
// Fall back to assuming binary
let buf = src.to_vec();
src.clear();
Ok(Some(StringOrBinary::Binary(buf)))
} else {
// Looks like a utf-8 string, so let's assume that
let buf = src.split_to(err.utf8_error().valid_up_to() + 1);
String::from_utf8(buf.to_vec())
.map(|x| Some(StringOrBinary::String(x)))
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
}
}
}
}
}
pub(crate) async fn run_external_command(
command: ExternalCommand,
context: &mut Context,
context: &mut EvaluationContext,
input: InputStream,
scope: &Scope,
is_last: bool,
external_redirection: ExternalRedirection,
) -> Result<InputStream, ShellError> {
trace!(target: "nu::run::external", "-> {}", command.name);
if !did_find_command(&command.name).await {
if !did_find_command(&command.name) {
return Err(ShellError::labeled_error(
"Command not found",
"command not found",
@ -99,15 +34,15 @@ 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(
command: ExternalCommand,
context: &mut Context,
context: &mut EvaluationContext,
input: InputStream,
scope: &Scope,
is_last: bool,
external_redirection: ExternalRedirection,
) -> Result<InputStream, ShellError> {
let path = context.shell_manager.path();
@ -118,35 +53,55 @@ async fn run_with_stdin(
let value =
evaluate_baseline_expr(arg, &context.registry, &scope.it, &scope.vars, &scope.env)
.await?;
// Skip any arguments that don't really exist, treating them as optional
// FIXME: we may want to preserve the gap in the future, though it's hard to say
// what value we would put in its place.
if value.value.is_none() {
continue;
}
// 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();
let value_string;
#[cfg(not(windows))]
{
value_string = trimmed_value_string
.replace('$', "\\$")
.replace('"', "\\\"")
.to_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);
}
}
#[cfg(windows)]
{
value_string = trimmed_value_string
}
command_args.push(value_string);
}
let process_args = command_args
.iter()
.map(|arg| {
let arg = expand_tilde(arg.deref(), dirs::home_dir);
let home_dir;
#[cfg(feature = "dirs")]
{
home_dir = dirs::home_dir;
}
#[cfg(not(feature = "dirs"))]
{
home_dir = || Some(std::path::PathBuf::from("/"));
}
let arg = expand_tilde(arg.deref(), home_dir);
#[cfg(not(windows))]
{
@ -167,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(
@ -175,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();
@ -211,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
@ -300,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 {
@ -315,22 +289,8 @@ 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);
let stream = FramedRead::new(file, MaybeTextCodec::default());
for line in block_on_stream(stream) {
match line {
@ -382,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);
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(),
}));
@ -404,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(),
}));
@ -428,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(),
@ -450,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 {
@ -483,21 +458,28 @@ fn spawn(
}
}
async fn did_find_command(name: &str) -> bool {
#[cfg(not(windows))]
pub fn did_find_command(#[allow(unused)] name: &str) -> bool {
#[cfg(not(feature = "which"))]
{
// we can't perform this check, so just assume it can be found
true
}
#[cfg(all(feature = "which", unix))]
{
which::which(name).is_ok()
}
#[cfg(windows)]
#[cfg(all(feature = "which", windows))]
{
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)
@ -559,11 +541,17 @@ fn shell_os_paths() -> Vec<std::path::PathBuf> {
mod tests {
use super::{
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
run_external_command, Context, InputStream,
};
#[cfg(feature = "which")]
use super::{run_external_command, EvaluationContext, InputStream};
#[cfg(feature = "which")]
use futures::executor::block_on;
#[cfg(feature = "which")]
use nu_errors::ShellError;
#[cfg(feature = "which")]
use nu_protocol::Scope;
#[cfg(feature = "which")]
use nu_test_support::commands::ExternalBuilder;
// async fn read(mut stream: OutputStream) -> Option<Value> {
@ -579,17 +567,24 @@ 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.");
let mut ctx =
EvaluationContext::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(())
}
@ -597,7 +592,7 @@ mod tests {
// async fn failure_run() -> Result<(), ShellError> {
// let cmd = ExternalBuilder::for_name("fail").build();
// let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
// let mut ctx = EvaluationContext::basic().expect("There was a problem creating a basic context.");
// let stream = run_external_command(cmd, &mut ctx, None, false)
// .await?
// .expect("There was a problem running the external command.");
@ -618,6 +613,7 @@ mod tests {
// block_on(failure_run())
// }
#[cfg(feature = "which")]
#[test]
fn identifies_command_not_found() -> Result<(), ShellError> {
block_on(non_existent_run())

View File

@ -4,12 +4,12 @@ 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(
command: InternalCommand,
context: &mut Context,
context: &mut EvaluationContext,
input: InputStream,
it: &Value,
vars: &IndexMap<String, Value>,
@ -28,6 +28,10 @@ pub(crate) async fn run_internal_command(
let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
let internal_command = context.expect_command(&command.name);
if command.name == "autoenv untrust" {
context.user_recently_used_autoenv_untrust = true;
}
let result = {
context
.run_command(
@ -83,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(),
@ -178,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(
@ -199,6 +200,28 @@ pub(crate) async fn run_internal_command(
)]);
InputStream::from_stream(futures::stream::iter(vec![]))
}
CommandAction::AddPlugins(path) => {
match crate::plugin::scan(vec![std::path::PathBuf::from(path)]) {
Ok(plugins) => {
context.add_commands(
plugins
.into_iter()
.filter(|p| {
!context.is_command_registered(p.name())
})
.collect(),
);
InputStream::from_stream(futures::stream::iter(vec![]))
}
Err(reason) => {
context.error(reason.clone());
InputStream::one(
UntaggedValue::Error(reason).into_untagged_value(),
)
}
}
}
CommandAction::PreviousShell => {
context.shell_manager.prev();
InputStream::from_stream(futures::stream::iter(vec![]))

View File

@ -0,0 +1,127 @@
use bytes::{BufMut, Bytes, BytesMut};
use nu_errors::ShellError;
extern crate encoding_rs;
use encoding_rs::{CoderResult, Decoder, Encoding, UTF_8};
#[cfg(not(test))]
const OUTPUT_BUFFER_SIZE: usize = 8192;
#[cfg(test)]
const OUTPUT_BUFFER_SIZE: usize = 4;
#[derive(Debug, Eq, PartialEq)]
pub enum StringOrBinary {
String(String),
Binary(Vec<u8>),
}
pub struct MaybeTextCodec {
decoder: Decoder,
}
impl MaybeTextCodec {
// The constructor takes an Option<&'static Encoding>, because an absence of an encoding indicates that we want BOM sniffing enabled
pub fn new(encoding: Option<&'static Encoding>) -> Self {
let decoder = match encoding {
Some(e) => e.new_decoder_with_bom_removal(),
None => UTF_8.new_decoder(),
};
MaybeTextCodec { decoder }
}
}
impl Default for MaybeTextCodec {
fn default() -> Self {
MaybeTextCodec {
decoder: UTF_8.new_decoder(),
}
}
}
impl futures_codec::Encoder for MaybeTextCodec {
type Item = StringOrBinary;
type Error = std::io::Error;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
match item {
StringOrBinary::String(s) => {
dst.reserve(s.len());
dst.put(s.as_bytes());
Ok(())
}
StringOrBinary::Binary(b) => {
dst.reserve(b.len());
dst.put(Bytes::from(b));
Ok(())
}
}
}
}
impl futures_codec::Decoder for MaybeTextCodec {
type Item = StringOrBinary;
type Error = ShellError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
if src.is_empty() {
return Ok(None);
}
let mut s = String::with_capacity(OUTPUT_BUFFER_SIZE);
let (res, _read, replacements) = self.decoder.decode_to_string(src, &mut s, false);
let result = if replacements {
// If we had to make replacements when converting to utf8, fall back to binary
StringOrBinary::Binary(src.to_vec())
} else {
// If original buffer size is too small, we continue to allocate new Strings and append
// them to the result until the input buffer is smaller than the allocated String
if let CoderResult::OutputFull = res {
let mut buffer = String::with_capacity(OUTPUT_BUFFER_SIZE);
loop {
let (res, _read, _replacements) =
self.decoder
.decode_to_string(&src[s.len()..], &mut buffer, false);
s.push_str(&buffer);
if let CoderResult::InputEmpty = res {
break;
}
buffer.clear();
}
}
StringOrBinary::String(s)
};
src.clear();
Ok(Some(result))
}
}
#[cfg(test)]
mod tests {
use super::{MaybeTextCodec, StringOrBinary};
use bytes::BytesMut;
use futures_codec::Decoder;
// TODO: Write some more tests
#[test]
fn should_consume_all_bytes_from_source_when_temporary_buffer_overflows() {
let mut maybe_text = MaybeTextCodec::new(None);
let mut bytes = BytesMut::from("0123456789");
let text = maybe_text.decode(&mut bytes);
assert_eq!(
Ok(Some(StringOrBinary::String("0123456789".to_string()))),
text
);
assert!(bytes.is_empty());
}
}

View File

@ -3,6 +3,8 @@ mod dynamic;
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

@ -17,7 +17,7 @@ impl WholeStreamCommand for Clear {
}
fn usage(&self) -> &str {
"clears the terminal"
"Clears the terminal"
}
async fn run(&self, _: CommandArgs, _: &CommandRegistry) -> Result<OutputStream, ShellError> {

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use futures::stream::StreamExt;
use nu_errors::ShellError;

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::help::get_help;
use crate::context::CommandRegistry;
use crate::deserializer::ConfigDeserializer;
use crate::evaluate::evaluate_args::evaluate_args;
use crate::prelude::*;
@ -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> {
@ -303,6 +303,11 @@ pub trait WholeStreamCommand: Send + Sync {
false
}
// Commands that are not meant to be run by users
fn is_internal(&self) -> bool {
false
}
fn examples(&self) -> Vec<Example> {
Vec::new()
}
@ -343,6 +348,10 @@ impl Command {
self.0.usage()
}
pub fn examples(&self) -> Vec<Example> {
self.0.examples()
}
pub async fn run(
&self,
args: CommandArgs,
@ -363,6 +372,10 @@ impl Command {
self.0.is_binary()
}
pub fn is_internal(&self) -> bool {
self.0.is_internal()
}
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
&*self.0
}

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use futures::future;
use futures::stream::StreamExt;
@ -40,7 +40,7 @@ impl WholeStreamCommand for Compact {
vec![
Example {
description: "Filter out all null entries in a list",
example: "echo [1 2 $null 3 $null $null] | compact target",
example: "echo [1 2 $null 3 $null $null] | compact",
result: Some(vec![
UntaggedValue::int(1).into(),
UntaggedValue::int(2).into(),
@ -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

@ -1,260 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::data::config;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use std::path::PathBuf;
pub struct Config;
#[derive(Deserialize)]
pub struct ConfigArgs {
load: Option<Tagged<PathBuf>>,
set: Option<(Tagged<String>, Value)>,
set_into: Option<Tagged<String>>,
get: Option<Tagged<String>>,
clear: Tagged<bool>,
remove: Option<Tagged<String>>,
path: Tagged<bool>,
}
#[async_trait]
impl WholeStreamCommand for Config {
fn name(&self) -> &str {
"config"
}
fn signature(&self) -> Signature {
Signature::build("config")
.named(
"load",
SyntaxShape::Path,
"load the config from the path given",
Some('l'),
)
.named(
"set",
SyntaxShape::Any,
"set a value in the config, eg) --set [key value]",
Some('s'),
)
.named(
"set_into",
SyntaxShape::String,
"sets a variable from values in the pipeline",
Some('i'),
)
.named(
"get",
SyntaxShape::Any,
"get a value from the config",
Some('g'),
)
.named(
"remove",
SyntaxShape::Any,
"remove a value from the config",
Some('r'),
)
.switch("clear", "clear the config", Some('c'))
.switch("path", "return the path to the config file", Some('p'))
}
fn usage(&self) -> &str {
"Configuration management."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
config(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "See all config values",
example: "config",
result: None,
},
Example {
description: "Set completion_mode to circular",
example: "config --set [completion_mode circular]",
result: None,
},
Example {
description: "Store the contents of the pipeline as a path",
example: "echo ['/usr/bin' '/bin'] | config --set_into path",
result: None,
},
Example {
description: "Get the current startup commands",
example: "config --get startup",
result: None,
},
Example {
description: "Remove the startup commands",
example: "config --remove startup",
result: None,
},
Example {
description: "Clear the config (be careful!)",
example: "config --clear",
result: None,
},
Example {
description: "Get the path to the current config file",
example: "config --path",
result: None,
},
]
}
}
pub async fn config(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone();
let name = args.call_info.name_tag.clone();
let registry = registry.clone();
let (
ConfigArgs {
load,
set,
set_into,
get,
clear,
remove,
path,
},
input,
) = args.process(&registry).await?;
let configuration = if let Some(supplied) = load {
Some(supplied.item().clone())
} else {
None
};
let mut result = crate::data::config::read(name_span, &configuration)?;
Ok(if let Some(v) = get {
let key = v.to_string();
let value = result
.get(&key)
.ok_or_else(|| ShellError::labeled_error("Missing key in config", "key", v.tag()))?;
match value {
Value {
value: UntaggedValue::Table(list),
..
} => {
let list: Vec<_> = list
.iter()
.map(|x| ReturnSuccess::value(x.clone()))
.collect();
futures::stream::iter(list).to_output_stream()
}
x => {
let x = x.clone();
OutputStream::one(ReturnSuccess::value(x))
}
}
} else if let Some((key, value)) = set {
result.insert(key.to_string(), value.clone());
config::write(&result, &configuration)?;
OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(&value.tag),
))
} else if let Some(v) = set_into {
let rows: Vec<Value> = input.collect().await;
let key = v.to_string();
if rows.is_empty() {
return Err(ShellError::labeled_error(
"No values given for set_into",
"needs value(s) from pipeline",
v.tag(),
));
} else if rows.len() == 1 {
// A single value
let value = &rows[0];
result.insert(key, value.clone());
config::write(&result, &configuration)?;
OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),
))
} else {
// Take in the pipeline as a table
let value = UntaggedValue::Table(rows).into_value(name.clone());
result.insert(key, value);
config::write(&result, &configuration)?;
OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),
))
}
} else if let Tagged { item: true, tag } = clear {
result.clear();
config::write(&result, &configuration)?;
OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(tag),
))
} else if let Tagged { item: true, tag } = path {
let path = config::default_path_for(&configuration)?;
OutputStream::one(ReturnSuccess::value(
UntaggedValue::Primitive(Primitive::Path(path)).into_value(tag),
))
} else if let Some(v) = remove {
let key = v.to_string();
if result.contains_key(&key) {
result.swap_remove(&key);
config::write(&result, &configuration)?;
futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(v.tag()),
)])
.to_output_stream()
} else {
return Err(ShellError::labeled_error(
"Key does not exist in config",
"key",
v.tag(),
));
}
} else {
futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),
)])
.to_output_stream()
})
}
#[cfg(test)]
mod tests {
use super::Config;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Config {})
}
}

View File

@ -0,0 +1,57 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"config clear"
}
fn signature(&self) -> Signature {
Signature::build("config clear")
}
fn usage(&self) -> &str {
"clear the config"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
clear(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Clear the config (be careful!)",
example: "config clear",
result: None,
}]
}
}
pub async fn clear(
args: CommandArgs,
_registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone();
// NOTE: None because we are not loading a new config file, we just want to read from the
// existing config
let mut result = nu_data::config::read(name_span, &None)?;
result.clear();
config::write(&result, &None)?;
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(args.call_info.name_tag),
)))
}

View File

@ -0,0 +1,37 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::{CommandArgs, CommandRegistry, OutputStream};
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
pub struct Command;
#[async_trait]
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"config"
}
fn signature(&self) -> Signature {
Signature::build("config")
}
fn usage(&self) -> &str {
"Configuration management."
}
async fn run(
&self,
args: CommandArgs,
_registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone();
let name = args.call_info.name_tag;
let result = nu_data::config::read(name_span, &None)?;
Ok(futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),
)])
.to_output_stream())
}
}

View File

@ -0,0 +1,83 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct GetArgs {
get: Tagged<String>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"config get"
}
fn signature(&self) -> Signature {
Signature::build("config get").required(
"get",
SyntaxShape::Any,
"value to get from the config",
)
}
fn usage(&self) -> &str {
"Gets a value from the config"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
get(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the current startup commands",
example: "config get startup",
result: None,
}]
}
}
pub async fn get(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone();
let (GetArgs { get }, _) = args.process(&registry).await?;
// NOTE: None because we are not loading a new config file, we just want to read from the
// existing config
let result = nu_data::config::read(name_span, &None)?;
let key = get.to_string();
let value = result
.get(&key)
.ok_or_else(|| ShellError::labeled_error("Missing key in config", "key", get.tag()))?;
Ok(match value {
Value {
value: UntaggedValue::Table(list),
..
} => {
let list: Vec<_> = list
.iter()
.map(|x| ReturnSuccess::value(x.clone()))
.collect();
futures::stream::iter(list).to_output_stream()
}
x => {
let x = x.clone();
OutputStream::one(ReturnSuccess::value(x))
}
})
}

View File

@ -0,0 +1,59 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
use std::path::PathBuf;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct LoadArgs {
load: Tagged<PathBuf>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"config load"
}
fn signature(&self) -> Signature {
Signature::build("config load").required(
"load",
SyntaxShape::Path,
"Path to load the config from",
)
}
fn usage(&self) -> &str {
"Loads the config from the path given"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
set(args, registry).await
}
}
pub async fn set(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let name_span = args.call_info.name_tag.clone();
let (LoadArgs { load }, _) = args.process(&registry).await?;
let configuration = load.item().clone();
let result = nu_data::config::read(name_span, &Some(configuration))?;
Ok(futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),
)])
.to_output_stream())
}

View File

@ -0,0 +1,17 @@
pub mod clear;
pub mod command;
pub mod get;
pub mod load;
pub mod path;
pub mod remove;
pub mod set;
pub mod set_into;
pub use clear::SubCommand as ConfigClear;
pub use command::Command as Config;
pub use get::SubCommand as ConfigGet;
pub use load::SubCommand as ConfigLoad;
pub use path::SubCommand as ConfigPath;
pub use remove::SubCommand as ConfigRemove;
pub use set::SubCommand as ConfigSet;
pub use set_into::SubCommand as ConfigSetInto;

View File

@ -0,0 +1,49 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"config path"
}
fn signature(&self) -> Signature {
Signature::build("config path")
}
fn usage(&self) -> &str {
"return the path to the config file"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
path(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the path to the current config file",
example: "config path",
result: None,
}]
}
}
pub async fn path(
args: CommandArgs,
_registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let path = config::default_path()?;
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::Primitive(Primitive::Path(path)).into_value(args.call_info.name_tag),
)))
}

View File

@ -0,0 +1,75 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct RemoveArgs {
remove: Tagged<String>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"config remove"
}
fn signature(&self) -> Signature {
Signature::build("config remove").required(
"remove",
SyntaxShape::Any,
"remove a value from the config",
)
}
fn usage(&self) -> &str {
"Removes a value from the config"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
remove(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Remove the startup commands",
example: "config remove startup",
result: None,
}]
}
}
pub async fn remove(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone();
let (RemoveArgs { remove }, _) = args.process(&registry).await?;
let mut result = nu_data::config::read(name_span, &None)?;
let key = remove.to_string();
if result.contains_key(&key) {
result.swap_remove(&key);
config::write(&result, &None)?;
Ok(futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(remove.tag()),
)])
.to_output_stream())
} else {
Err(ShellError::labeled_error(
"Key does not exist in config",
"key",
remove.tag(),
))
}
}

View File

@ -0,0 +1,67 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct SetArgs {
key: Tagged<String>,
value: Value,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"config set"
}
fn signature(&self) -> Signature {
Signature::build("config set")
.required("key", SyntaxShape::String, "variable name to set")
.required("value", SyntaxShape::Any, "value to use")
}
fn usage(&self) -> &str {
"Sets a value in the config"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
set(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Set nonzero_exit_errors to true",
example: "config set nonzero_exit_errors $true",
result: None,
}]
}
}
pub async fn set(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone();
let (SetArgs { key, value }, _) = args.process(&registry).await?;
// NOTE: None because we are not loading a new config file, we just want to read from the
// existing config
let mut result = nu_data::config::read(name_span, &None)?;
result.insert(key.to_string(), value.clone());
config::write(&result, &None)?;
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(&value.tag),
)))
}

View File

@ -0,0 +1,98 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct SetIntoArgs {
set_into: Tagged<String>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"config set_into"
}
fn signature(&self) -> Signature {
Signature::build("config set_into").required(
"set_into",
SyntaxShape::String,
"sets a variable from values in the pipeline",
)
}
fn usage(&self) -> &str {
"Sets a value in the config"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
set_into(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Store the contents of the pipeline as a path",
example: "echo ['/usr/bin' '/bin'] | config set_into path",
result: None,
}]
}
}
pub async fn set_into(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone();
let name = args.call_info.name_tag.clone();
let (SetIntoArgs { set_into: v }, input) = args.process(&registry).await?;
// NOTE: None because we are not loading a new config file, we just want to read from the
// existing config
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`
let configuration = None;
let rows: Vec<Value> = input.collect().await;
let key = v.to_string();
Ok(if rows.is_empty() {
return Err(ShellError::labeled_error(
"No values given for set_into",
"needs value(s) from pipeline",
v.tag(),
));
} else if rows.len() == 1 {
// A single value
let value = &rows[0];
result.insert(key, value.clone());
config::write(&result, &configuration)?;
OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),
))
} else {
// Take in the pipeline as a table
let value = UntaggedValue::Table(rows).into_value(name.clone());
result.insert(key, value);
config::write(&result, &configuration)?;
OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),
))
})
}

View File

@ -0,0 +1,358 @@
pub const BAT_LANGUAGES: &[&str] = &[
"as",
"csv",
"tsv",
"applescript",
"script editor",
"s",
"S",
"adoc",
"asciidoc",
"asc",
"asa",
"yasm",
"nasm",
"asm",
"inc",
"mac",
"awk",
"bat",
"cmd",
"bib",
"sh",
"bash",
"zsh",
".bash_aliases",
".bash_completions",
".bash_functions",
".bash_login",
".bash_logout",
".bash_profile",
".bash_variables",
".bashrc",
".profile",
".textmate_init",
".zshrc",
"PKGBUILD",
".ebuild",
".eclass",
"c",
"h",
"cs",
"csx",
"cpp",
"cc",
"cp",
"cxx",
"c++",
"C",
"h",
"hh",
"hpp",
"hxx",
"h++",
"inl",
"ipp",
"cabal",
"clj",
"cljc",
"cljs",
"edn",
"CMakeLists.txt",
"cmake",
"h.in",
"hh.in",
"hpp.in",
"hxx.in",
"h++.in",
"CMakeCache.txt",
"cr",
"css",
"css.erb",
"css.liquid",
"d",
"di",
"dart",
"diff",
"patch",
"Dockerfile",
"dockerfile",
"ex",
"exs",
"elm",
"erl",
"hrl",
"Emakefile",
"emakefile",
"fs",
"fsi",
"fsx",
"fs",
"fsi",
"fsx",
"fish",
"attributes",
"gitattributes",
".gitattributes",
"COMMIT_EDITMSG",
"MERGE_MSG",
"TAG_EDITMSG",
"gitconfig",
".gitconfig",
".gitmodules",
"exclude",
"gitignore",
".gitignore",
".git",
"gitlog",
"git-rebase-todo",
"go",
"dot",
"DOT",
"gv",
"groovy",
"gvy",
"gradle",
"Jenkinsfile",
"hs",
"hs",
"hsc",
"show-nonprintable",
"html",
"htm",
"shtml",
"xhtml",
"asp",
"html.eex",
"yaws",
"rails",
"rhtml",
"erb",
"html.erb",
"adp",
"twig",
"html.twig",
"ini",
"INI",
"INF",
"reg",
"REG",
"lng",
"cfg",
"CFG",
"desktop",
"url",
"URL",
".editorconfig",
".hgrc",
"hgrc",
"java",
"bsh",
"properties",
"jsp",
"js",
"htc",
"js",
"jsx",
"babel",
"es6",
"js.erb",
"json",
"sublime-settings",
"sublime-menu",
"sublime-keymap",
"sublime-mousemap",
"sublime-theme",
"sublime-build",
"sublime-project",
"sublime-completions",
"sublime-commands",
"sublime-macro",
"sublime-color-scheme",
"ipynb",
"Pipfile.lock",
"jsonnet",
"libsonnet",
"libjsonnet",
"jl",
"kt",
"kts",
"tex",
"ltx",
"less",
"css.less",
"lisp",
"cl",
"clisp",
"l",
"mud",
"el",
"scm",
"ss",
"lsp",
"fasl",
"lhs",
"lua",
"make",
"GNUmakefile",
"makefile",
"Makefile",
"makefile.am",
"Makefile.am",
"makefile.in",
"Makefile.in",
"OCamlMakefile",
"mak",
"mk",
"md",
"mdown",
"markdown",
"markdn",
"matlab",
"build",
"nix",
"m",
"h",
"mm",
"M",
"h",
"ml",
"mli",
"mll",
"mly",
"pas",
"p",
"dpr",
"pl",
"pm",
"pod",
"t",
"PL",
"php",
"php3",
"php4",
"php5",
"php7",
"phps",
"phpt",
"phtml",
"txt",
"ps1",
"psm1",
"psd1",
"proto",
"protodevel",
"pb.txt",
"proto.text",
"textpb",
"pbtxt",
"prototxt",
"pp",
"epp",
"purs",
"py",
"py3",
"pyw",
"pyi",
"pyx",
"pyx.in",
"pxd",
"pxd.in",
"pxi",
"pxi.in",
"rpy",
"cpy",
"SConstruct",
"Sconstruct",
"sconstruct",
"SConscript",
"gyp",
"gypi",
"Snakefile",
"wscript",
"R",
"r",
"s",
"S",
"Rprofile",
"rd",
"re",
"rst",
"rest",
"robot",
"rb",
"Appfile",
"Appraisals",
"Berksfile",
"Brewfile",
"capfile",
"cgi",
"Cheffile",
"config.ru",
"Deliverfile",
"Fastfile",
"fcgi",
"Gemfile",
"gemspec",
"Guardfile",
"irbrc",
"jbuilder",
"Podfile",
"podspec",
"prawn",
"rabl",
"rake",
"Rakefile",
"Rantfile",
"rbx",
"rjs",
"ruby.rail",
"Scanfile",
"simplecov",
"Snapfile",
"thor",
"Thorfile",
"Vagrantfile",
"haml",
"sass",
"rxml",
"builder",
"rs",
"scala",
"sbt",
"sql",
"ddl",
"dml",
"erbsql",
"sql.erb",
"swift",
"log",
"tcl",
"tf",
"tfvars",
"hcl",
"sty",
"cls",
"textile",
"toml",
"tml",
"Cargo.lock",
"Gopkg.lock",
"Pipfile",
"ts",
"tsx",
"varlink",
"vim",
".vimrc",
"xml",
"xsd",
"xslt",
"tld",
"dtml",
"rss",
"opml",
"svg",
"yaml",
"yml",
"sublime-syntax",
];

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use futures::stream::StreamExt;
use nu_errors::ShellError;
@ -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,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape};

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 {
"Apply date function"
}
async fn run(
&self,
_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(crate::commands::help::get_help(&Command, &registry))
.into_value(Tag::unknown()),
)))
}
}
#[cfg(test)]
mod tests {
use super::Command;
#[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

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
@ -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;
@ -19,7 +21,7 @@ impl WholeStreamCommand for Do {
}
fn signature(&self) -> Signature {
Signature::build("with-env")
Signature::build("do")
.required("block", SyntaxShape::Block, "the block to run ")
.switch(
"ignore_errors",
@ -61,9 +63,9 @@ 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 mut context = EvaluationContext::from_raw(&raw_args, &registry);
let scope = raw_args.call_info.scope.clone();
let (
DoArgs {
@ -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

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
@ -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(
@ -35,20 +35,7 @@ impl WholeStreamCommand for Drop {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let (DropArgs { rows }, input) = args.process(&registry).await?;
let mut v: Vec<_> = input.into_vec().await;
let rows_to_drop = if let Some(quantity) = rows {
*quantity as usize
} else {
1
};
for _ in 0..rows_to_drop {
v.pop();
}
Ok(futures::stream::iter(v).to_output_stream())
drop(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@ -70,6 +57,31 @@ impl WholeStreamCommand for Drop {
}
}
async fn drop(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let (DropArgs { rows }, input) = args.process(&registry).await?;
let v: Vec<_> = input.into_vec().await;
let rows_to_drop = if let Some(quantity) = rows {
*quantity as usize
} else {
1
};
Ok(if rows_to_drop == 0 {
futures::stream::iter(v).to_output_stream()
} else {
let k = if v.len() < rows_to_drop {
0
} else {
v.len() - rows_to_drop
};
let iter = v.into_iter().take(k);
futures::stream::iter(iter).to_output_stream()
})
}
#[cfg(test)]
mod tests {
use super::Drop;

View File

@ -343,7 +343,7 @@ where
let values = vec.into_iter().map(Into::into).collect::<Vec<Value>>();
UntaggedValue::Table(values)
}
.retag(tag)
.into_value(tag)
}
impl From<DirInfo> for Value {
@ -352,17 +352,17 @@ impl From<DirInfo> for Value {
r.insert(
"path".to_string(),
UntaggedValue::path(d.path).retag(&d.tag),
UntaggedValue::path(d.path).into_value(&d.tag),
);
r.insert(
"apparent".to_string(),
UntaggedValue::bytes(d.size).retag(&d.tag),
UntaggedValue::filesize(d.size).into_value(&d.tag),
);
r.insert(
"physical".to_string(),
UntaggedValue::bytes(d.blocks).retag(&d.tag),
UntaggedValue::filesize(d.blocks).into_value(&d.tag),
);
r.insert("directories".to_string(), value_from_vec(d.dirs, &d.tag));
@ -376,7 +376,7 @@ impl From<DirInfo> for Value {
.map(move |e| UntaggedValue::Error(e).into_untagged_value())
.collect::<Vec<Value>>(),
)
.retag(&d.tag);
.into_value(&d.tag);
r.insert("errors".to_string(), v);
}
@ -394,30 +394,33 @@ impl From<FileInfo> for Value {
r.insert(
"path".to_string(),
UntaggedValue::path(f.path).retag(&f.tag),
UntaggedValue::path(f.path).into_value(&f.tag),
);
r.insert(
"apparent".to_string(),
UntaggedValue::bytes(f.size).retag(&f.tag),
UntaggedValue::filesize(f.size).into_value(&f.tag),
);
let b = f
.blocks
.map(UntaggedValue::bytes)
.map(UntaggedValue::filesize)
.unwrap_or_else(UntaggedValue::nothing)
.retag(&f.tag);
.into_value(&f.tag);
r.insert("physical".to_string(), b);
r.insert(
"directories".to_string(),
UntaggedValue::nothing().retag(&f.tag),
UntaggedValue::nothing().into_value(&f.tag),
);
r.insert("files".to_string(), UntaggedValue::nothing().retag(&f.tag));
r.insert(
"files".to_string(),
UntaggedValue::nothing().into_value(&f.tag),
);
UntaggedValue::row(r).retag(&f.tag)
UntaggedValue::row(r).into_value(&f.tag)
}
}

View File

@ -1,20 +1,22 @@
use crate::command_registry::CommandRegistry;
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use futures::stream::once;
use nu_errors::ShellError;
use nu_protocol::{
hir::Block, hir::Expression, hir::SpannedExpression, hir::Synthetic, Scope, Signature,
SyntaxShape, UntaggedValue, Value,
SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
};
use nu_source::Tagged;
pub struct Each;
#[derive(Deserialize)]
pub struct EachArgs {
block: Block,
numbered: Tagged<bool>,
}
#[async_trait]
@ -24,11 +26,13 @@ impl WholeStreamCommand for Each {
}
fn signature(&self) -> Signature {
Signature::build("each").required(
"block",
SyntaxShape::Block,
"the block to run on each row",
)
Signature::build("each")
.required("block", SyntaxShape::Block, "the block to run on each row")
.switch(
"numbered",
"returned a numbered item ($it.index and $it.item)",
Some('n'),
)
}
fn usage(&self) -> &str {
@ -45,6 +49,11 @@ impl WholeStreamCommand for Each {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Echo the sum of each row",
example: "echo [[1 2] [3 4]] | each { echo $it | math sum }",
result: None,
},
Example {
description: "Echo the square of each integer",
example: "echo [1 2 3] | each { echo $(= $it * $it) }",
@ -55,32 +64,27 @@ impl WholeStreamCommand for Each {
]),
},
Example {
description: "Echo the sum of each row",
example: "echo [[1 2] [3 4]] | each { echo $it | math sum }",
result: Some(vec![
UntaggedValue::int(3).into(),
UntaggedValue::int(7).into(),
]),
description: "Number each item and echo a message",
example:
"echo ['bob' 'fred'] | each --numbered { echo `{{$it.index}} is {{$it.item}}` }",
result: Some(vec![Value::from("0 is bob"), Value::from("1 is fred")]),
},
]
}
}
fn is_expanded_it_usage(head: &SpannedExpression) -> bool {
match &*head {
SpannedExpression {
expr: Expression::Synthetic(Synthetic::String(s)),
..
} if s == "expanded-each" => true,
_ => false,
}
matches!(&*head, SpannedExpression {
expr: Expression::Synthetic(Synthetic::String(s)),
..
} if s == "expanded-each")
}
async fn process_row(
pub async fn process_row(
block: Arc<Block>,
scope: Arc<Scope>,
head: Arc<Box<SpannedExpression>>,
mut context: Arc<Context>,
mut context: Arc<EvaluationContext>,
input: Value,
) -> Result<OutputStream, ShellError> {
let input_clone = input.clone();
@ -101,6 +105,14 @@ 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,
@ -108,24 +120,47 @@ async fn each(
let registry = registry.clone();
let head = Arc::new(raw_args.call_info.args.head.clone());
let scope = Arc::new(raw_args.call_info.scope.clone());
let context = Arc::new(Context::from_raw(&raw_args, &registry));
let context = Arc::new(EvaluationContext::from_raw(&raw_args, &registry));
let (each_args, input): (EachArgs, _) = raw_args.process(&registry).await?;
let block = Arc::new(each_args.block);
Ok(input
.then(move |input| {
let block = block.clone();
let scope = scope.clone();
let head = head.clone();
let context = context.clone();
async {
match process_row(block, scope, head, context, input).await {
Ok(s) => s,
Err(e) => OutputStream::one(Err(e)),
if each_args.numbered.item {
Ok(input
.enumerate()
.then(move |input| {
let block = block.clone();
let scope = scope.clone();
let head = head.clone();
let context = context.clone();
let row = make_indexed_item(input.0, input.1);
async {
match process_row(block, scope, head, context, row).await {
Ok(s) => s,
Err(e) => OutputStream::one(Err(e)),
}
}
}
})
.flatten()
.to_output_stream())
})
.flatten()
.to_output_stream())
} else {
Ok(input
.then(move |input| {
let block = block.clone();
let scope = scope.clone();
let head = head.clone();
let context = context.clone();
async {
match process_row(block, scope, head, context, input).await {
Ok(s) => s,
Err(e) => OutputStream::one(Err(e)),
}
}
})
.flatten()
.to_output_stream())
}
}
#[cfg(test)]

View File

@ -0,0 +1,133 @@
use crate::commands::each::process_row;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
hir::Block, hir::SpannedExpression, ReturnSuccess, Scope, Signature, SyntaxShape,
UntaggedValue, Value,
};
use nu_source::Tagged;
use serde::Deserialize;
pub struct EachGroup;
#[derive(Deserialize)]
pub struct EachGroupArgs {
group_size: Tagged<usize>,
block: Block,
//numbered: Tagged<bool>,
}
#[async_trait]
impl WholeStreamCommand for EachGroup {
fn name(&self) -> &str {
"each group"
}
fn signature(&self) -> Signature {
Signature::build("each group")
.required("group_size", SyntaxShape::Int, "the size of each group")
.required(
"block",
SyntaxShape::Block,
"the block to run on each group",
)
}
fn usage(&self) -> &str {
"Runs a block on groups of `group_size` rows of a table at a time."
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Echo the sum of each pair",
example: "echo [1 2 3 4] | each group 2 { echo $it | math sum }",
result: None,
}]
}
async fn run(
&self,
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let head = Arc::new(raw_args.call_info.args.head.clone());
let scope = Arc::new(raw_args.call_info.scope.clone());
let context = Arc::new(EvaluationContext::from_raw(&raw_args, &registry));
let (each_args, input): (EachGroupArgs, _) = raw_args.process(&registry).await?;
let block = Arc::new(each_args.block);
Ok(input
.chunks(each_args.group_size.item)
.then(move |input| {
run_block_on_vec(
input,
block.clone(),
scope.clone(),
head.clone(),
context.clone(),
)
})
.flatten()
.to_output_stream())
}
}
pub(crate) fn run_block_on_vec(
input: Vec<Value>,
block: Arc<Block>,
scope: Arc<Scope>,
head: Arc<Box<SpannedExpression>>,
context: Arc<EvaluationContext>,
) -> impl Future<Output = OutputStream> {
let value = Value {
value: UntaggedValue::Table(input),
tag: Tag::unknown(),
};
async {
match process_row(block, scope, head, context, value).await {
Ok(s) => {
// We need to handle this differently depending on whether process_row
// returned just 1 value or if it returned multiple as a stream.
let vec = s.collect::<Vec<_>>().await;
// If it returned just one value, just take that value
if vec.len() == 1 {
return OutputStream::one(vec.into_iter().next().expect(
"This should be impossible, we just checked that vec.len() == 1.",
));
}
// If it returned multiple values, we need to put them into a table and
// return that.
let result = vec.into_iter().collect::<Result<Vec<ReturnSuccess>, _>>();
let result_table = match result {
Ok(t) => t,
Err(e) => return OutputStream::one(Err(e)),
};
let table = result_table
.into_iter()
.filter_map(|x| x.raw_value())
.collect();
OutputStream::one(Ok(ReturnSuccess::Value(UntaggedValue::Table(table).into())))
}
Err(e) => OutputStream::one(Err(e)),
}
}
}
#[cfg(test)]
mod tests {
use super::EachGroup;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(EachGroup {})
}
}

View File

@ -0,0 +1,9 @@
pub mod command;
pub mod group;
pub mod window;
pub(crate) use command::make_indexed_item;
pub use command::process_row;
pub use command::Each;
pub use group::EachGroup;
pub use window::EachWindow;

View File

@ -0,0 +1,113 @@
use crate::commands::each::group::run_block_on_vec;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
//use itertools::Itertools;
use nu_errors::ShellError;
use nu_protocol::{hir::Block, Primitive, Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
use serde::Deserialize;
pub struct EachWindow;
#[derive(Deserialize)]
pub struct EachWindowArgs {
window_size: Tagged<usize>,
block: Block,
stride: Option<Tagged<usize>>,
}
#[async_trait]
impl WholeStreamCommand for EachWindow {
fn name(&self) -> &str {
"each window"
}
fn signature(&self) -> Signature {
Signature::build("each window")
.required("window_size", SyntaxShape::Int, "the size of each window")
.named(
"stride",
SyntaxShape::Int,
"the number of rows to slide over between windows",
Some('s'),
)
.required(
"block",
SyntaxShape::Block,
"the block to run on each group",
)
}
fn usage(&self) -> &str {
"Runs a block on sliding windows of `window_size` rows of a table at a time."
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Echo the sum of each window",
example: "echo [1 2 3 4] | each window 2 { echo $it | math sum }",
result: None,
}]
}
async fn run(
&self,
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let head = Arc::new(raw_args.call_info.args.head.clone());
let scope = Arc::new(raw_args.call_info.scope.clone());
let context = Arc::new(EvaluationContext::from_raw(&raw_args, &registry));
let (each_args, mut input): (EachWindowArgs, _) = raw_args.process(&registry).await?;
let block = Arc::new(each_args.block);
let mut window: Vec<_> = input
.by_ref()
.take(*each_args.window_size - 1)
.collect::<Vec<_>>()
.await;
// `window` must start with dummy values, which will be removed on the first iteration
let stride = each_args.stride.map(|x| *x).unwrap_or(1);
window.insert(0, UntaggedValue::Primitive(Primitive::Nothing).into());
Ok(input
.enumerate()
.then(move |(i, input)| {
// This would probably be more efficient if `last` was a VecDeque
// But we can't have that because it needs to be put into a Table
window.remove(0);
window.push(input);
let block = block.clone();
let scope = scope.clone();
let head = head.clone();
let context = context.clone();
let local_window = window.clone();
async move {
if i % stride == 0 {
Some(run_block_on_vec(local_window, block, scope, head, context).await)
} else {
None
}
}
})
.filter_map(|x| async { x })
.flatten()
.to_output_stream())
}
}
#[cfg(test)]
mod tests {
use super::EachWindow;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(EachWindow {})
}
}

View File

@ -3,7 +3,7 @@ use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::hir::Operator;
use nu_protocol::{
Primitive, RangeInclusion, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
Primitive, Range, RangeInclusion, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
pub struct Echo;
@ -55,60 +55,110 @@ async fn echo(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
let registry = registry.clone();
let (args, _): (EchoArgs, _) = args.process(&registry).await?;
let stream = args.rest.into_iter().map(|i| {
match i.as_string() {
Ok(s) => {
OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(s).into_value(i.tag.clone()),
)))
}
_ => match i {
Value {
value: UntaggedValue::Table(table),
..
} => {
futures::stream::iter(table.into_iter().map(ReturnSuccess::value)).to_output_stream()
}
Value {
value: UntaggedValue::Primitive(Primitive::Range(range)),
tag
} => {
let mut output_vec = vec![];
let mut current = range.from.0.item;
while current != range.to.0.item {
output_vec.push(Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag))));
current = match crate::data::value::compute_values(Operator::Plus, &UntaggedValue::Primitive(current), &UntaggedValue::int(1)) {
Ok(result) => match result {
UntaggedValue::Primitive(p) => p,
_ => {
return OutputStream::one(Err(ShellError::unimplemented("Internal error: expected a primitive result from increment")));
}
},
Err((left_type, right_type)) => {
return OutputStream::one(Err(ShellError::coerce_error(
left_type.spanned(tag.span),
right_type.spanned(tag.span),
)));
}
}
}
if let RangeInclusion::Inclusive = range.to.1 {
output_vec.push(Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current).into_value(&tag))));
}
futures::stream::iter(output_vec.into_iter()).to_output_stream()
}
_ => {
OutputStream::one(Ok(ReturnSuccess::Value(i.clone())))
}
},
}
let stream = args.rest.into_iter().map(|i| match i.as_string() {
Ok(s) => OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(s).into_value(i.tag.clone()),
))),
_ => match i {
Value {
value: UntaggedValue::Table(table),
..
} => futures::stream::iter(table.into_iter().map(ReturnSuccess::value))
.to_output_stream(),
Value {
value: UntaggedValue::Primitive(Primitive::Range(range)),
tag,
} => futures::stream::iter(RangeIterator::new(*range, tag)).to_output_stream(),
_ => OutputStream::one(Ok(ReturnSuccess::Value(i.clone()))),
},
});
Ok(futures::stream::iter(stream).flatten().to_output_stream())
}
struct RangeIterator {
curr: Primitive,
end: Primitive,
tag: Tag,
is_end_inclusive: bool,
}
impl RangeIterator {
pub fn new(range: Range, tag: Tag) -> RangeIterator {
let start = match range.from.0.item {
Primitive::Nothing => Primitive::Int(0.into()),
x => x,
};
RangeIterator {
curr: start,
end: range.to.0.item,
tag,
is_end_inclusive: matches!(range.to.1, RangeInclusion::Inclusive),
}
}
}
impl Iterator for RangeIterator {
type Item = Result<ReturnSuccess, ShellError>;
fn next(&mut self) -> Option<Self::Item> {
let ordering = if self.end == Primitive::Nothing {
Ordering::Less
} else {
let result =
nu_data::base::coerce_compare_primitive(&self.curr, &self.end).map_err(|_| {
ShellError::labeled_error(
"Cannot create range",
"unsupported range",
self.tag.span,
)
});
if let Err(result) = result {
return Some(Err(result));
}
let result = result
.expect("Internal error: the error case was already protected, but that failed");
result.compare()
};
use std::cmp::Ordering;
if (ordering == Ordering::Less) || (self.is_end_inclusive && ordering == Ordering::Equal) {
let output = UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone());
let next_value = nu_data::value::compute_values(
Operator::Plus,
&UntaggedValue::Primitive(self.curr.clone()),
&UntaggedValue::int(1),
);
self.curr = match next_value {
Ok(result) => match result {
UntaggedValue::Primitive(p) => p,
_ => {
return Some(Err(ShellError::unimplemented(
"Internal error: expected a primitive result from increment",
)));
}
},
Err((left_type, right_type)) => {
return Some(Err(ShellError::coerce_error(
left_type.spanned(self.tag.span),
right_type.spanned(self.tag.span),
)));
}
};
Some(ReturnSuccess::value(output))
} else {
// TODO: add inclusive/exclusive ranges
None
}
}
}
#[cfg(test)]
mod tests {
use super::Echo;

View File

@ -1,8 +1,9 @@
use crate::command_registry::CommandRegistry;
use crate::commands::UnevaluatedCallInfo;
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,
};
@ -121,21 +122,16 @@ async fn enter(
let full_path = std::path::PathBuf::from(cwd);
let (file_extension, contents, contents_tag) = crate::commands::open::fetch(
let (file_extension, tagged_contents) = crate::commands::open::fetch(
&full_path,
&PathBuf::from(location_clone),
tag.span,
match encoding {
Some(e) => e.to_string(),
_ => "".to_string(),
},
encoding,
)
.await?;
match contents {
match tagged_contents.value {
UntaggedValue::Primitive(Primitive::String(_)) => {
let tagged_contents = contents.into_value(&contents_tag);
if let Some(extension) = file_extension {
let command_name = format!("from {}", extension);
if let Some(converter) = registry.get_command(&command_name) {
@ -150,24 +146,24 @@ async fn enter(
positional: None,
named: None,
span: Span::unknown(),
is_last: false,
external_redirection: ExternalRedirection::Stdout,
},
name_tag: tag.clone(),
scope: scope.clone(),
},
};
let tag = tagged_contents.tag.clone();
let mut result = converter
.run(new_args.with_input(vec![tagged_contents]), &registry)
.await?;
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
result.drain_vec().await;
Ok(futures::stream::iter(result_vec.into_iter().map(
move |res| match res {
Ok(ReturnSuccess::Value(Value { value, .. })) => Ok(
ReturnSuccess::Action(CommandAction::EnterValueShell(Value {
value,
tag: contents_tag.clone(),
tag: tag.clone(),
})),
),
x => x,
@ -185,13 +181,9 @@ async fn enter(
)))
}
}
_ => {
let tagged_contents = contents.into_value(contents_tag);
Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::EnterValueShell(tagged_contents),
)))
}
_ => Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::EnterValueShell(tagged_contents),
))),
}
}
}

View File

@ -1,83 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{evaluate, fetch};
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::{SpannedItem, Tagged};
use nu_value_ext::ValueExt;
pub struct EvaluateBy;
#[derive(Deserialize)]
pub struct EvaluateByArgs {
evaluate_with: Option<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for EvaluateBy {
fn name(&self) -> &str {
"evaluate-by"
}
fn signature(&self) -> Signature {
Signature::build("evaluate-by").named(
"evaluate_with",
SyntaxShape::String,
"the name of the column to evaluate by",
Some('w'),
)
}
fn usage(&self) -> &str {
"Creates a new table with the data from the tables rows evaluated by the column given."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
evaluate_by(args, registry).await
}
}
pub async fn evaluate_by(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let (EvaluateByArgs { evaluate_with }, mut input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
if values.is_empty() {
Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
name,
))
} else {
let evaluate_with = if let Some(evaluator) = evaluate_with {
Some(evaluator.item().clone())
} else {
None
};
match evaluate(&values[0], evaluate_with, name) {
Ok(evaluated) => Ok(OutputStream::one(ReturnSuccess::value(evaluated))),
Err(err) => Err(err),
}
}
}
#[cfg(test)]
mod tests {
use super::EvaluateBy;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(EvaluateBy {})
}
}

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
@ -71,25 +71,23 @@ impl WholeStreamCommand for Every {
async fn every(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (EveryArgs { stride, skip }, input) = args.process(&registry).await?;
let v: Vec<_> = input.into_vec().await;
let stride_desired = if stride.item < 1 { 1 } else { stride.item } as usize;
let stride = stride.item;
let skip = skip.item;
let mut values_vec_deque = VecDeque::new();
Ok(input
.enumerate()
.filter_map(move |(i, value)| async move {
let stride_desired = if stride < 1 { 1 } else { stride } as usize;
let should_include = skip == (i % stride_desired != 0);
for (i, x) in v.iter().enumerate() {
let should_include = if skip.item {
i % stride_desired != 0
} else {
i % stride_desired == 0
};
if should_include {
values_vec_deque.push_back(ReturnSuccess::value(x.clone()));
}
}
Ok(futures::stream::iter(values_vec_deque).to_output_stream())
if should_include {
Some(ReturnSuccess::value(value))
} else {
None
}
})
.to_output_stream())
}
#[cfg(test)]

View File

@ -0,0 +1,87 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged;
use std::path::PathBuf;
pub struct Exec;
#[derive(Deserialize)]
pub struct ExecArgs {
pub command: Tagged<PathBuf>,
pub rest: Vec<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for Exec {
fn name(&self) -> &str {
"exec"
}
fn signature(&self) -> Signature {
Signature::build("exec")
.required("command", SyntaxShape::Path, "the command to execute")
.rest(SyntaxShape::Pattern, "any additional arguments for command")
}
fn usage(&self) -> &str {
"Execute command"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
exec(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Execute 'ps aux'",
example: "exec ps aux",
result: None,
},
Example {
description: "Execute 'nautilus'",
example: "exec nautilus",
result: None,
},
]
}
}
#[cfg(unix)]
async fn exec(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
use std::os::unix::process::CommandExt;
use std::process::Command;
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let (args, _): (ExecArgs, _) = args.process(&registry).await?;
let mut command = Command::new(args.command.item);
for tagged_arg in args.rest {
command.arg(tagged_arg.item);
}
let err = command.exec(); // this replaces our process, should not return
Err(ShellError::labeled_error(
"Error on exec",
format!("{}", err),
&name,
))
}
#[cfg(not(unix))]
async fn exec(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
Err(ShellError::labeled_error(
"Error on exec",
"exec is not supported on your platform",
&args.call_info.name_tag,
))
}

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::command::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CommandAction, ReturnSuccess, Signature};

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use nu_errors::ShellError;

View File

@ -14,6 +14,7 @@ fn from_delimited_string_to_value(
.delimiter(separator as u8)
.from_reader(s.as_bytes());
let tag = tag.into();
let span = tag.span;
let headers = if headerless {
(1..=reader.headers()?.len())
@ -30,7 +31,10 @@ fn from_delimited_string_to_value(
if let Ok(i) = value.parse::<i64>() {
tagged_row.insert_value(header, UntaggedValue::int(i).into_value(&tag))
} else if let Ok(f) = value.parse::<f64>() {
tagged_row.insert_value(header, UntaggedValue::decimal(f).into_value(&tag))
tagged_row.insert_value(
header,
UntaggedValue::decimal_from_float(f, span).into_value(&tag),
)
} else {
tagged_row.insert_value(header, UntaggedValue::string(value).into_value(&tag))
}

View File

@ -39,11 +39,12 @@ impl WholeStreamCommand for FromJSON {
fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -> Value {
let tag = tag.into();
let span = tag.span;
match v {
serde_hjson::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
serde_hjson::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
serde_hjson::Value::F64(n) => UntaggedValue::decimal(*n).into_value(&tag),
serde_hjson::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag),
serde_hjson::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
serde_hjson::Value::String(s) => {

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;
@ -45,6 +45,7 @@ async fn from_ods(
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let span = tag.span;
let registry = registry.clone();
let (
@ -73,7 +74,7 @@ async fn from_ods(
let value = match cell {
DataType::Empty => UntaggedValue::nothing(),
DataType::String(s) => UntaggedValue::string(s),
DataType::Float(f) => UntaggedValue::decimal(*f),
DataType::Float(f) => UntaggedValue::decimal_from_float(*f, span),
DataType::Int(i) => UntaggedValue::int(*i),
DataType::Bool(b) => UntaggedValue::boolean(*b),
_ => UntaggedValue::nothing(),

View File

@ -30,11 +30,12 @@ impl WholeStreamCommand for FromTOML {
pub fn convert_toml_value_to_nu_value(v: &toml::Value, tag: impl Into<Tag>) -> Value {
let tag = tag.into();
let span = tag.span;
match v {
toml::Value::Boolean(b) => UntaggedValue::boolean(*b).into_value(tag),
toml::Value::Integer(n) => UntaggedValue::int(*n).into_value(tag),
toml::Value::Float(n) => UntaggedValue::decimal(*n).into_value(tag),
toml::Value::Float(n) => UntaggedValue::decimal_from_float(*n, span).into_value(tag),
toml::Value::String(s) => {
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(tag)
}

View File

@ -5,7 +5,6 @@ use ical::parser::vcard::component::*;
use ical::property::Property;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
use std::io::BufReader;
pub struct FromVcf;
@ -42,28 +41,20 @@ async fn from_vcf(
let input = args.input;
let input_string = input.collect_string(tag.clone()).await?.item;
let input_bytes = input_string.as_bytes();
let buf_reader = BufReader::new(input_bytes);
let parser = ical::VcardParser::new(buf_reader);
let input_bytes = input_string.into_bytes();
let cursor = std::io::Cursor::new(input_bytes);
let parser = ical::VcardParser::new(cursor);
let mut values_vec_deque = VecDeque::new();
let iter = parser.map(move |contact| match contact {
Ok(c) => ReturnSuccess::value(contact_to_value(c, tag.clone())),
Err(_) => Err(ShellError::labeled_error(
"Could not parse as .vcf",
"input cannot be parsed as .vcf",
tag.clone(),
)),
});
for contact in parser {
match contact {
Ok(c) => {
values_vec_deque.push_back(ReturnSuccess::value(contact_to_value(c, tag.clone())))
}
Err(_) => {
return Err(ShellError::labeled_error(
"Could not parse as .vcf",
"input cannot be parsed as .vcf",
tag.clone(),
))
}
}
}
Ok(futures::stream::iter(values_vec_deque).to_output_stream())
Ok(futures::stream::iter(iter).to_output_stream())
}
fn contact_to_value(contact: VcardContact, tag: Tag) -> Value {

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;
@ -45,6 +45,7 @@ async fn from_xlsx(
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let span = tag.span;
let registry = registry.clone();
let (
FromXLSXArgs {
@ -73,7 +74,7 @@ async fn from_xlsx(
let value = match cell {
DataType::Empty => UntaggedValue::nothing(),
DataType::String(s) => UntaggedValue::string(s),
DataType::Float(f) => UntaggedValue::decimal(*f),
DataType::Float(f) => UntaggedValue::decimal_from_float(*f, span),
DataType::Int(i) => UntaggedValue::int(*i),
DataType::Bool(b) => UntaggedValue::boolean(*b),
_ => UntaggedValue::nothing(),

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

@ -58,6 +58,7 @@ fn convert_yaml_value_to_nu_value(
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let tag = tag.into();
let span = tag.span;
let err_not_compatible_number = ShellError::labeled_error(
"Expected a compatible number",
@ -69,10 +70,11 @@ fn convert_yaml_value_to_nu_value(
serde_yaml::Value::Number(n) if n.is_i64() => {
UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag)
}
serde_yaml::Value::Number(n) if n.is_f64() => {
UntaggedValue::decimal(n.as_f64().ok_or_else(|| err_not_compatible_number)?)
.into_value(tag)
}
serde_yaml::Value::Number(n) if n.is_f64() => UntaggedValue::decimal_from_float(
n.as_f64().ok_or_else(|| err_not_compatible_number)?,
span,
)
.into_value(tag),
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
serde_yaml::Value::Sequence(a) => {
let result: Result<Vec<Value>, ShellError> = a

View File

@ -4,10 +4,10 @@ use indexmap::set::IndexSet;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape,
UnspannedPathMember, UntaggedValue, Value,
did_you_mean, ColumnPath, Dictionary, 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;
@ -58,195 +58,209 @@ impl WholeStreamCommand for Get {
}
}
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> {
let fields = path.clone();
get_data_by_column_path(
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));
match &obj_source.value {
UntaggedValue::Table(rows) => match column_path_tried {
PathMember {
unspanned: UnspannedPathMember::String(column),
..
} => {
let primary_label = format!("There isn't a column named '{}'", &column);
let suggestions: IndexSet<_> = rows
.iter()
.filter_map(|r| did_you_mean(&r, &column_path_tried))
.map(|s| s[0].1.to_owned())
.collect();
let mut existing_columns: IndexSet<_> = IndexSet::default();
let mut names: Vec<String> = vec![];
for row in rows {
for field in row.data_descriptors() {
if !existing_columns.contains(&field[..]) {
existing_columns.insert(field.clone());
names.push(field);
}
}
}
if names.is_empty() {
return ShellError::labeled_error_with_secondary(
"Unknown column",
primary_label,
column_path_tried.span,
"Appears to contain rows. Try indexing instead.",
column_path_tried.span.since(path_members_span),
);
} else {
return ShellError::labeled_error_with_secondary(
"Unknown column",
primary_label,
column_path_tried.span,
format!(
"Perhaps you meant '{}'? Columns available: {}",
suggestions
.iter()
.map(|x| x.to_owned())
.collect::<Vec<String>>()
.join(","),
names.join(",")
),
column_path_tried.span.since(path_members_span),
);
};
}
PathMember {
unspanned: UnspannedPathMember::Int(idx),
..
} => {
let total = rows.len();
let secondary_label = if total == 1 {
"The table only has 1 row".to_owned()
} else {
format!("The table only has {} rows (0 to {})", total, total - 1)
};
return ShellError::labeled_error_with_secondary(
"Row not found",
format!("There isn't a row indexed at {}", idx),
column_path_tried.span,
secondary_label,
column_path_tried.span.since(path_members_span),
);
}
},
UntaggedValue::Row(columns) => match column_path_tried {
PathMember {
unspanned: UnspannedPathMember::String(column),
..
} => {
let primary_label = format!("There isn't a column named '{}'", &column);
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
return ShellError::labeled_error_with_secondary(
"Unknown column",
primary_label,
column_path_tried.span,
format!(
"Perhaps you meant '{}'? Columns available: {}",
suggestions[0].1,
&obj_source.data_descriptors().join(",")
),
column_path_tried.span.since(path_members_span),
);
}
}
PathMember {
unspanned: UnspannedPathMember::Int(idx),
..
} => {
return ShellError::labeled_error_with_secondary(
"No rows available",
format!("A row at '{}' can't be indexed.", &idx),
column_path_tried.span,
format!(
"Appears to contain columns. Columns available: {}",
columns.keys().join(",")
),
column_path_tried.span.since(path_members_span),
)
}
},
_ => {}
}
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
return ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", suggestions[0].1),
column_path_tried.span.since(path_members_span),
);
}
error
}),
)
}
pub async fn get(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (GetArgs { rest: mut fields }, mut input) = args.process(&registry).await?;
if fields.is_empty() {
let (GetArgs { rest: column_paths }, mut input) = args.process(&registry).await?;
if column_paths.is_empty() {
let vec = input.drain_vec().await;
let descs = nu_protocol::merge_descriptors(&vec);
Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
} else {
let member = fields.remove(0);
trace!("get {:?} {:?}", member, fields);
Ok(input
trace!("get {:?}", column_paths);
let output_stream = input
.map(move |item| {
let member = vec![member.clone()];
let column_paths = vec![&member, &fields]
.into_iter()
let output = column_paths
.iter()
.map(move |path| get_output(&item, path))
.flatten()
.collect::<Vec<&ColumnPath>>();
let mut output = vec![];
for path in column_paths {
let res = get_column_path(&path, &item);
match res {
Ok(got) => match got {
Value {
value: UntaggedValue::Table(rows),
..
} => {
for item in rows {
output.push(ReturnSuccess::value(item.clone()));
}
}
Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
..
} => {}
other => output.push(ReturnSuccess::value(other.clone())),
},
Err(reason) => output.push(ReturnSuccess::value(
UntaggedValue::Error(reason).into_untagged_value(),
)),
}
}
.collect::<Vec<_>>();
futures::stream::iter(output)
})
.flatten()
.to_output_stream())
.to_output_stream();
Ok(output_stream)
}
}
fn get_output(item: &Value, path: &ColumnPath) -> Vec<Result<ReturnSuccess, ShellError>> {
match get_column_path(path, item) {
Ok(Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
..
}) => vec![],
Ok(Value {
value: UntaggedValue::Table(rows),
..
}) => rows.into_iter().map(ReturnSuccess::value).collect(),
Ok(other) => vec![ReturnSuccess::value(other)],
Err(reason) => vec![ReturnSuccess::value(
UntaggedValue::Error(reason).into_untagged_value(),
)],
}
}
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> {
get_data_by_column_path(obj, path, move |obj_source, column_path_tried, error| {
let path_members_span = path.maybe_span().unwrap_or_else(Span::unknown);
match &obj_source.value {
UntaggedValue::Table(rows) => {
return get_column_path_from_table_error(
rows,
column_path_tried,
&path_members_span,
);
}
UntaggedValue::Row(columns) => {
if let Some(error) = get_column_from_row_error(
columns,
column_path_tried,
&path_members_span,
obj_source,
) {
return error;
}
}
_ => {}
}
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", suggestions[0].1),
column_path_tried.span.since(path_members_span),
)
} else {
error
}
})
}
pub fn get_column_path_from_table_error(
rows: &[Value],
column_path_tried: &PathMember,
path_members_span: &Span,
) -> ShellError {
match column_path_tried {
PathMember {
unspanned: UnspannedPathMember::String(column),
..
} => {
let primary_label = format!("There isn't a column named '{}'", &column);
let suggestions: IndexSet<_> = rows
.iter()
.filter_map(|r| did_you_mean(&r, &column_path_tried))
.map(|s| s[0].1.to_owned())
.collect();
let mut existing_columns: IndexSet<_> = IndexSet::default();
let mut names: Vec<String> = vec![];
for row in rows {
for field in row.data_descriptors() {
if !existing_columns.contains(&field[..]) {
existing_columns.insert(field.clone());
names.push(field);
}
}
}
if names.is_empty() {
ShellError::labeled_error_with_secondary(
"Unknown column",
primary_label,
column_path_tried.span,
"Appears to contain rows. Try indexing instead.",
column_path_tried.span.since(path_members_span),
)
} else {
ShellError::labeled_error_with_secondary(
"Unknown column",
primary_label,
column_path_tried.span,
format!(
"Perhaps you meant '{}'? Columns available: {}",
suggestions
.iter()
.map(|x| x.to_owned())
.collect::<Vec<String>>()
.join(","),
names.join(", ")
),
column_path_tried.span.since(path_members_span),
)
}
}
PathMember {
unspanned: UnspannedPathMember::Int(idx),
..
} => {
let total = rows.len();
let secondary_label = if total == 1 {
"The table only has 1 row".to_owned()
} else {
format!("The table only has {} rows (0 to {})", total, total - 1)
};
ShellError::labeled_error_with_secondary(
"Row not found",
format!("There isn't a row indexed at {}", idx),
column_path_tried.span,
secondary_label,
column_path_tried.span.since(path_members_span),
)
}
}
}
pub fn get_column_from_row_error(
columns: &Dictionary,
column_path_tried: &PathMember,
path_members_span: &Span,
obj_source: &Value,
) -> Option<ShellError> {
match column_path_tried {
PathMember {
unspanned: UnspannedPathMember::String(column),
..
} => {
let primary_label = format!("There isn't a column named '{}'", &column);
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
Some(ShellError::labeled_error_with_secondary(
"Unknown column",
primary_label,
column_path_tried.span,
format!(
"Perhaps you meant '{}'? Columns available: {}",
suggestions[0].1,
&obj_source.data_descriptors().join(", ")
),
column_path_tried.span.since(path_members_span),
))
} else {
None
}
}
PathMember {
unspanned: UnspannedPathMember::Int(idx),
..
} => Some(ShellError::labeled_error_with_secondary(
"No rows available",
format!("A row at '{}' can't be indexed.", &idx),
column_path_tried.span,
format!(
"Appears to contain columns. Columns available: {}",
columns.keys().join(", ")
),
column_path_tried.span.since(path_members_span),
)),
}
}

View File

@ -10,7 +10,7 @@ pub struct GroupBy;
#[derive(Deserialize)]
pub struct GroupByArgs {
column_name: Option<Tagged<String>>,
grouper: Option<Value>,
}
#[async_trait]
@ -21,14 +21,14 @@ impl WholeStreamCommand for GroupBy {
fn signature(&self) -> Signature {
Signature::build("group-by").optional(
"column_name",
SyntaxShape::String,
"the name of the column to group by",
"grouper",
SyntaxShape::Any,
"the grouper value to use",
)
}
fn usage(&self) -> &str {
"Creates a new table with the data from the table rows grouped by the column given."
"Create a new table grouped."
}
async fn run(
@ -42,12 +42,17 @@ impl WholeStreamCommand for GroupBy {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Group items by type",
description: "group items by column named \"type\"",
example: r#"ls | group-by type"#,
result: None,
},
Example {
description: "Group items by their value",
description: "blocks can be used for generating a grouping key (same as above)",
example: r#"ls | group-by { get type }"#,
result: None,
},
Example {
description: "you can also group by raw values by leaving out the argument",
example: "echo [1 3 1 3 2 1 1] | group-by",
result: Some(vec![UntaggedValue::row(indexmap! {
"1".to_string() => UntaggedValue::Table(vec![
@ -68,26 +73,95 @@ impl WholeStreamCommand for GroupBy {
})
.into()]),
},
Example {
description: "write pipelines for a more involved grouping key",
example:
"echo [1 3 1 3 2 1 1] | group-by { echo `({{$it}} - 1) % 3` | calc | str from }",
result: None,
},
]
}
}
enum Grouper {
ByColumn(Option<Tagged<String>>),
ByBlock,
}
pub async fn group_by(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let (GroupByArgs { column_name }, input) = args.process(&registry).await?;
let registry = registry.clone();
let head = Arc::new(args.call_info.args.head.clone());
let scope = Arc::new(args.call_info.scope.clone());
let context = Arc::new(EvaluationContext::from_raw(&args, &registry));
let (GroupByArgs { grouper }, input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
let mut keys: Vec<Result<String, ShellError>> = vec![];
let mut group_strategy = Grouper::ByColumn(None);
match grouper {
Some(Value {
value: UntaggedValue::Block(block_given),
..
}) => {
let block = Arc::new(block_given);
let error_key = "error";
for value in values.iter() {
let run = block.clone();
let scope = scope.clone();
let head = head.clone();
let context = context.clone();
match crate::commands::each::process_row(run, scope, head, context, value.clone())
.await
{
Ok(mut s) => {
let collection: Vec<Result<ReturnSuccess, ShellError>> =
s.drain_vec().await;
if collection.len() > 1 {
return Err(ShellError::labeled_error(
"expected one value from the block",
"requires a table with one value for grouping",
&name,
));
}
let value = match collection.get(0) {
Some(Ok(return_value)) => {
return_value.raw_value().unwrap_or_else(|| {
UntaggedValue::string(error_key).into_value(&name)
})
}
Some(Err(_)) | None => {
UntaggedValue::string(error_key).into_value(&name)
}
};
keys.push(as_string(&value));
}
Err(_) => {
keys.push(Ok(error_key.into()));
}
}
}
group_strategy = Grouper::ByBlock;
}
Some(other) => {
group_strategy = Grouper::ByColumn(Some(as_string(&other)?.tagged(&name)));
}
_ => {}
}
if values.is_empty() {
return Err(ShellError::labeled_error(
"Expected table from pipeline",
"expected table from pipeline",
"requires a table input",
name,
));
@ -95,10 +169,22 @@ pub async fn group_by(
let values = UntaggedValue::table(&values).into_value(&name);
match group(&column_name, &values, name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(reason) => Err(reason),
}
let group_value = match group_strategy {
Grouper::ByBlock => {
let map = keys.clone();
let block = Box::new(move |idx: usize, row: &Value| match map.get(idx) {
Some(Ok(key)) => Ok(key.clone()),
Some(Err(reason)) => Err(reason.clone()),
None => as_string(row),
});
nu_data::utils::group(&values, &Some(block), &name)
}
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 {
@ -141,100 +227,55 @@ pub fn group(
match grouper {
Grouper::ByColumn(Some(column_name)) => {
let block = Box::new(move |row: &Value| {
let block = Box::new(move |_, row: &Value| {
match row.get_data_by_key(column_name.borrow_spanned()) {
Some(group_key) => Ok(as_string(&group_key)?),
None => Err(suggestions(column_name.borrow_tagged(), &row)),
}
});
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.",
)),
}
}
#[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) })
]),
})
);
@ -245,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

@ -34,7 +34,7 @@ impl WholeStreamCommand for GroupByDate {
}
fn usage(&self) -> &str {
"Creates a new table with the data from the table rows grouped by the column given."
"creates a table grouped by date."
}
async fn run(
@ -98,54 +98,42 @@ pub async fn group_by_date(
Grouper::ByDate(None)
};
match (grouper_date, grouper_column) {
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"));
match crate::utils::data::group(&values, &Some(block), &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
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 block = Box::new(move |_, row: &Value| {
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")
});
match crate::utils::data::group(&values, &Some(block), &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
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));
let block = Box::new(move |_, row: &Value| row.format(&fmt));
match crate::utils::data::group(&values, &Some(block), &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
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 block = Box::new(move |_, row: &Value| {
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)
});
match crate::utils::data::group(&values, &Some(block), &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
nu_data::utils::group(&values, &Some(block), &name)
}
}
};
Ok(OutputStream::one(ReturnSuccess::value(value_result?)))
}
}

View File

@ -1,11 +1,11 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
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,12 +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::{
NamedType, PositionalType, 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;
@ -40,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();
@ -50,54 +63,118 @@ async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
let mut sorted_names = registry.names();
sorted_names.sort();
Ok(
futures::stream::iter(sorted_names.into_iter().filter_map(move |cmd| {
// If it's a subcommand, don't list it during the commands list
if cmd.contains(' ') {
return None;
}
let mut short_desc = TaggedDictBuilder::new(name.clone());
let document_tag = rest[0].tag.clone();
let value = command_dict(
match registry.get_command(&cmd).ok_or_else(|| {
let (mut subcommand_names, command_names) = sorted_names
.into_iter()
// Internal only commands shouldn't be displayed
.filter(|cmd_name| {
registry
.get_command(&cmd_name)
.filter(|command| !command.is_internal())
.is_some()
})
.partition::<Vec<_>, _>(|cmd_name| cmd_name.contains(' '));
fn process_name(
dict: &mut TaggedDictBuilder,
cmd_name: &str,
registry: CommandRegistry,
rest: Vec<Tagged<String>>,
name: Tag,
) -> Result<(), ShellError> {
let document_tag = rest[0].tag.clone();
let value = command_dict(
registry.get_command(&cmd_name).ok_or_else(|| {
ShellError::labeled_error(
format!("Could not load {}", cmd_name),
"could not load command",
document_tag,
)
})?,
name,
);
dict.insert_untagged("name", cmd_name);
dict.insert_untagged(
"description",
get_data_by_key(&value, "usage".spanned_unknown())
.ok_or_else(|| {
ShellError::labeled_error(
format!("Could not load {}", cmd),
"could not load command",
document_tag,
"Expected a usage key",
"expected a 'usage' key",
&value.tag,
)
}) {
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
},
name.clone(),
);
})?
.as_string()?,
);
short_desc.insert_untagged("name", cmd);
short_desc.insert_untagged(
"description",
match match get_data_by_key(&value, "usage".spanned_unknown()).ok_or_else(
|| {
ShellError::labeled_error(
"Expected a usage key",
"expected a 'usage' key",
&value.tag,
)
},
) {
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
}
.as_string()
{
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
},
);
Ok(())
}
Some(ReturnSuccess::value(short_desc.into_value()))
}))
.to_output_stream(),
)
fn make_subcommands_table(
subcommand_names: &mut Vec<String>,
cmd_name: &str,
registry: CommandRegistry,
rest: Vec<Tagged<String>>,
name: Tag,
) -> Result<Value, ShellError> {
let (matching, not_matching) =
subcommand_names.drain(..).partition(|subcommand_name| {
subcommand_name.starts_with(&format!("{} ", cmd_name))
});
*subcommand_names = not_matching;
Ok(if !matching.is_empty() {
UntaggedValue::table(
&(matching
.into_iter()
.map(|cmd_name: String| -> Result<_, ShellError> {
let mut short_desc = TaggedDictBuilder::new(name.clone());
process_name(
&mut short_desc,
&cmd_name,
registry.clone(),
rest.clone(),
name.clone(),
)?;
Ok(short_desc.into_value())
})
.collect::<Result<Vec<_>, _>>()?[..]),
)
.into_value(name)
} else {
UntaggedValue::nothing().into_value(name)
})
}
let iterator =
command_names
.into_iter()
.map(move |cmd_name| -> Result<_, ShellError> {
let mut short_desc = TaggedDictBuilder::new(name.clone());
process_name(
&mut short_desc,
&cmd_name,
registry.clone(),
rest.clone(),
name.clone(),
)?;
short_desc.insert_value(
"subcommands",
make_subcommands_table(
&mut subcommand_names,
&cmd_name,
registry.clone(),
rest.clone(),
name.clone(),
)?,
);
ReturnSuccess::value(short_desc.into_value())
});
Ok(futures::stream::iter(iterator).to_output_stream())
} else if rest[0].item == "generate_docs" {
Ok(OutputStream::one(ReturnSuccess::value(generate_docs(
&registry,
))))
} else if rest.len() == 2 {
// Check for a subcommand
let command_name = format!("{} {}", rest[0].item, rest[1].item);
@ -150,165 +227,8 @@ You can also learn more at https://www.nushell.sh/book/"#;
}
}
#[allow(clippy::cognitive_complexity)]
pub fn get_help(cmd: &dyn WholeStreamCommand, registry: &CommandRegistry) -> String {
let cmd_name = cmd.name();
let signature = cmd.signature();
let mut long_desc = String::new();
long_desc.push_str(&cmd.usage());
long_desc.push_str("\n");
let mut subcommands = String::new();
for name in registry.names() {
if name.starts_with(&format!("{} ", cmd_name)) {
let subcommand = registry.get_command(&name).expect("This shouldn't happen");
subcommands.push_str(&format!(" {} - {}\n", name, subcommand.usage()));
}
}
let mut one_liner = String::new();
one_liner.push_str(&signature.name);
one_liner.push_str(" ");
for positional in &signature.positional {
match &positional.0 {
PositionalType::Mandatory(name, _m) => {
one_liner.push_str(&format!("<{}> ", name));
}
PositionalType::Optional(name, _o) => {
one_liner.push_str(&format!("({}) ", name));
}
}
}
if signature.rest_positional.is_some() {
one_liner.push_str(" ...args");
}
if !subcommands.is_empty() {
one_liner.push_str("<subcommand> ");
}
if !signature.named.is_empty() {
one_liner.push_str("{flags} ");
}
long_desc.push_str(&format!("\nUsage:\n > {}\n", one_liner));
if !subcommands.is_empty() {
long_desc.push_str("\nSubcommands:\n");
long_desc.push_str(&subcommands);
}
if !signature.positional.is_empty() || signature.rest_positional.is_some() {
long_desc.push_str("\nParameters:\n");
for positional in &signature.positional {
match &positional.0 {
PositionalType::Mandatory(name, _m) => {
long_desc.push_str(&format!(" <{}> {}\n", name, positional.1));
}
PositionalType::Optional(name, _o) => {
long_desc.push_str(&format!(" ({}) {}\n", name, positional.1));
}
}
}
if let Some(rest_positional) = &signature.rest_positional {
long_desc.push_str(&format!(" ...args: {}\n", rest_positional.1));
}
}
if !signature.named.is_empty() {
long_desc.push_str(&get_flags_section(&signature))
}
let palette = crate::shell::palette::DefaultPalette {};
let examples = cmd.examples();
if !examples.is_empty() {
long_desc.push_str("\nExamples:");
}
for example in examples {
long_desc.push_str("\n");
long_desc.push_str(" ");
long_desc.push_str(example.description);
let colored_example =
crate::shell::helper::Painter::paint_string(example.example, registry, &palette);
long_desc.push_str(&format!("\n > {}\n", colored_example));
}
long_desc.push_str("\n");
long_desc
}
fn get_flags_section(signature: &Signature) -> String {
let mut long_desc = String::new();
long_desc.push_str("\nFlags:\n");
for (flag, ty) in &signature.named {
let msg = match ty.0 {
NamedType::Switch(s) => {
if let Some(c) = s {
format!(
" -{}, --{}{} {}\n",
c,
flag,
if !ty.1.is_empty() { ":" } else { "" },
ty.1
)
} else {
format!(
" --{}{} {}\n",
flag,
if !ty.1.is_empty() { ":" } else { "" },
ty.1
)
}
}
NamedType::Mandatory(s, m) => {
if let Some(c) = s {
format!(
" -{}, --{} <{}> (required parameter){} {}\n",
c,
flag,
m.display(),
if !ty.1.is_empty() { ":" } else { "" },
ty.1
)
} else {
format!(
" --{} <{}> (required parameter){} {}\n",
flag,
m.display(),
if !ty.1.is_empty() { ":" } else { "" },
ty.1
)
}
}
NamedType::Optional(s, o) => {
if let Some(c) = s {
format!(
" -{}, --{} <{}>{} {}\n",
c,
flag,
o.display(),
if !ty.1.is_empty() { ":" } else { "" },
ty.1
)
} else {
format!(
" --{} <{}>{} {}\n",
flag,
o.display(),
if !ty.1.is_empty() { ":" } else { "" },
ty.1
)
}
}
};
long_desc.push_str(&msg);
}
long_desc
get_documentation(cmd, registry, &DocumentationConfig::default())
}
#[cfg(test)]

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,143 @@ 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, 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,31 @@
use crate::cli::History as HistoryFile;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_data::config::{Conf, 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: &dyn Conf) -> PathBuf {
let default_path = nu_data::config::user_data()
.map(|mut p| {
p.push(DEFAULT_LOCATION);
p
})
.unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION));
config
.var("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 +53,10 @@ impl WholeStreamCommand for History {
}
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let config: Box<dyn Conf> = Box::new(NuConfig::new());
let tag = args.call_info.name_tag;
let 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

@ -0,0 +1,188 @@
use crate::command_registry::CommandRegistry;
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{hir::Block, hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue};
pub struct If;
#[derive(Deserialize)]
pub struct IfArgs {
condition: Block,
then_case: Block,
else_case: Block,
}
#[async_trait]
impl WholeStreamCommand for If {
fn name(&self) -> &str {
"if"
}
fn signature(&self) -> Signature {
Signature::build("if")
.required(
"condition",
SyntaxShape::Math,
"the condition that must match",
)
.required(
"then_case",
SyntaxShape::Block,
"block to run if condition is true",
)
.required(
"else_case",
SyntaxShape::Block,
"block to run if condition is false",
)
}
fn usage(&self) -> &str {
"Run blocks if a condition is true or false."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
if_command(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Run a block if a condition is true",
example: "echo 10 | if $it > 5 { echo 'greater than 5' } { echo 'less than or equal to 5' }",
result: Some(vec![UntaggedValue::string("greater than 5").into()]),
},
Example {
description: "Run a block if a condition is false",
example: "echo 1 | if $it > 5 { echo 'greater than 5' } { echo 'less than or equal to 5' }",
result: Some(vec![UntaggedValue::string("less than or equal to 5").into()]),
},
]
}
}
async fn if_command(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = Arc::new(registry.clone());
let scope = Arc::new(raw_args.call_info.scope.clone());
let tag = raw_args.call_info.name_tag.clone();
let context = Arc::new(EvaluationContext::from_raw(&raw_args, &registry));
let (
IfArgs {
condition,
then_case,
else_case,
},
input,
) = raw_args.process(&registry).await?;
let condition = {
if condition.block.len() != 1 {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
match condition.block[0].list.get(0) {
Some(item) => match item {
ClassifiedCommand::Expr(expr) => expr.clone(),
_ => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
},
None => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
}
};
Ok(input
.then(move |input| {
let condition = condition.clone();
let then_case = then_case.clone();
let else_case = else_case.clone();
let registry = registry.clone();
let scope = scope.clone();
let mut context = context.clone();
async move {
//FIXME: should we use the scope that's brought in as well?
let condition =
evaluate_baseline_expr(&condition, &*registry, &input, &scope.vars, &scope.env)
.await;
match condition {
Ok(condition) => match condition.as_bool() {
Ok(b) => {
if b {
match run_block(
&then_case,
Arc::make_mut(&mut context),
InputStream::empty(),
&input,
&scope.vars,
&scope.env,
)
.await
{
Ok(stream) => stream.to_output_stream(),
Err(e) => futures::stream::iter(vec![Err(e)].into_iter())
.to_output_stream(),
}
} else {
match run_block(
&else_case,
Arc::make_mut(&mut context),
InputStream::empty(),
&input,
&scope.vars,
&scope.env,
)
.await
{
Ok(stream) => stream.to_output_stream(),
Err(e) => futures::stream::iter(vec![Err(e)].into_iter())
.to_output_stream(),
}
}
}
Err(e) => {
futures::stream::iter(vec![Err(e)].into_iter()).to_output_stream()
}
},
Err(e) => futures::stream::iter(vec![Err(e)].into_iter()).to_output_stream(),
}
}
})
.flatten()
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::If;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(If {})
}
}

View File

@ -1,10 +1,14 @@
use crate::command_registry::CommandRegistry;
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,27 +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<EvaluationContext>,
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(EvaluationContext::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(_),
..
} => match row.insert_data_at_column_path(&column, value.clone()) {
Ok(v) => Ok(ReturnSuccess::Value(v)),
Err(err) => Err(err),
},
.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

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
@ -50,9 +50,16 @@ async fn is_empty(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name_tag = args.call_info.name_tag.clone();
let registry = registry.clone();
let (IsEmptyArgs { rest }, input) = args.process(&registry).await?;
if input.is_empty() {
return Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::boolean(true).into_value(name_tag),
)));
}
Ok(input
.map(move |value| {
let value_tag = value.tag();
@ -72,7 +79,6 @@ async fn is_empty(
(_, _) => IsEmptyFor::Value,
}
} else {
// let no_args = vec![];
let mut arguments = rest.iter().rev();
let replacement_if_true = match arguments.next() {
Some(arg) => arg.clone(),
@ -106,14 +112,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

@ -1,19 +1,19 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
pub struct Keep;
pub struct Command;
#[derive(Deserialize)]
pub struct KeepArgs {
pub struct Arguments {
rows: Option<Tagged<usize>>,
}
#[async_trait]
impl WholeStreamCommand for Keep {
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"keep"
}
@ -22,7 +22,7 @@ impl WholeStreamCommand for Keep {
Signature::build("keep").optional(
"rows",
SyntaxShape::Int,
"starting from the front, the number of rows to keep",
"Starting from the front, the number of rows to keep",
)
}
@ -61,7 +61,7 @@ impl WholeStreamCommand for Keep {
async fn keep(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (KeepArgs { rows }, input) = args.process(&registry).await?;
let (Arguments { rows }, input) = args.process(&registry).await?;
let rows_desired = if let Some(quantity) = rows {
*quantity
} else {
@ -73,12 +73,12 @@ async fn keep(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
#[cfg(test)]
mod tests {
use super::Keep;
use super::Command;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Keep {})
test_examples(Command {})
}
}

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