Compare commits

...

182 Commits

Author SHA1 Message Date
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
0522023d4c Bump to 0.16.0 (#2084) 2020-07-01 06:25:09 +12:00
9876169f5d Add dice subcommand to random command (#2082)
* Add dice subcommand to random command

* Update random dice test name

* Stream results of random dice

* Thanks Clippy!
2020-06-30 16:12:51 +12:00
ed10aafa6f Bubble errors even if pipeline isn't used (#2080) 2020-06-30 05:39:11 +12:00
bcddeb3c1f WIP (#2077) 2020-06-29 09:06:05 +12:00
3f170c7fb8 Cal improvements (#2074)
* .get() already checks for the argument, don't need to use .has() as well

* Supplying the month-names flag should also cause the months column to show up, it should not require the -m flag first
2020-06-29 05:16:10 +12:00
8d91d151bf added raw to date for string output (#2075) 2020-06-28 09:01:13 -05:00
821d44af54 Docs autoview pwd touch (#2068)
* [ADD] Add draft documentation for autoview

* [ADD] Add draft documentation for pwd

* [ADD] Add draft documentation for touch

* [MOD] Improve description and add examples

Add the use of `textview` and `binaryview`.
Add examples for single value, source file and binary file.
2020-06-28 14:22:26 +12:00
a30901ff7d added ability to request the date in a format (#2073) 2020-06-27 20:36:15 -05:00
94a1968a88 Add command for printing special characters (#2072) 2020-06-28 09:46:30 +12:00
dffc9c9b1c Properly redirect invocations (#2070)
* Properly redirect invocations

* Don't convert with-env yet, as there's a random test failure
2020-06-28 09:04:57 +12:00
8b3964f518 More ansi (#2067)
* added a few more options for ansi colors
not sure italic works, maybe it's font dependent

* fmt
2020-06-27 10:29:09 -05:00
7fed9992c9 Bump deps and touchup (#2066) 2020-06-27 19:54:31 +12:00
4e2a4236f8 Fix it expansion and add collect (#2065) 2020-06-27 17:38:19 +12:00
05781607f4 Configurable built-in prompts (#2064)
* Add ansi, do, and prompt customization

* Fix test

* Cleanups
2020-06-27 10:37:31 +12:00
6daec399e6 added match plugin and readme.txt for msi (#2063) 2020-06-26 08:39:19 -05:00
306dc89ede Add bool subcommand to random (#2061)
* Add bool subcommand to random

* Fix function name copy paste error

* Fix issue 2062: allow deserialization of a decimal

* Add bias flag to `random bool`
2020-06-26 16:51:05 +12:00
80ce8acf57 Add ThemedPalette (#1873)
* add theme module

* reorganize theme palette code

* improve tests

* move to newtype implementation for ThemeColor and fix Palette name.

* add dead code ignore for now

* fix allow dead code macro

* remove redundant import and unnecessary return

* fix ok_or clippy error

Co-authored-by: Kurtis Nusbaum <kcommiter@gmail.com>
2020-06-26 16:40:12 +12:00
8dfc90a322 Update release.yml 2020-06-26 15:55:18 +12:00
ad5e485594 Update release.yml 2020-06-26 15:24:45 +12:00
60ed40f8bd Update release.yml 2020-06-26 14:34:39 +12:00
a6228cab9e Update main.wxs (#2060) 2020-06-26 11:34:39 +12:00
1857ac69d1 updated wxs to have the right exes (#2059) 2020-06-25 17:38:32 -05:00
e33e80ab24 Update release.yml 2020-06-26 09:40:59 +12:00
d18bc78e7c Update release.yml 2020-06-26 09:28:09 +12:00
3b2a87b6d4 Update release.yml 2020-06-26 09:08:20 +12:00
62c76be7ca Update release.yml 2020-06-26 09:06:33 +12:00
733f93e673 update to make closer to volta's (#2058) 2020-06-26 08:08:59 +12:00
2c88b2fae7 Gh actions with wix (#2057)
* Added wix to gh workflow
Followed volta example

* added --nocapture to see more error detail

* move creation of wix to after we download less.exe

* moved create wix down
2020-06-26 07:30:07 +12:00
501da433d4 Gh actions with wix, added --nocapture (#2056)
* Added wix to gh workflow
Followed volta example

* added --nocapture to see more error detail
2020-06-26 06:26:48 +12:00
0e8a239ae1 Added wix to gh workflow (#2055)
Followed volta example
2020-06-26 05:51:50 +12:00
bb08a221e2 Wix addition for creating msi (#2054)
* WIP - wix

* Updated wxs to have less. Added less.exe.

* removed binary less.exe since we're downloading it
updated wxs to point to output/* for less.exe

Co-authored-by: Darren Schroeder <fdncred@hotmail.com>
2020-06-26 05:50:45 +12:00
0dbe347f84 Update Cargo.lock (#2053) 2020-06-25 19:46:20 +12:00
72a21ad619 Adds random command with uuid subcommand (#2050) 2020-06-25 17:51:09 +12:00
6372d2a18c Made starship usage configurable (#2049)
Co-authored-by: Darren Schroeder <fdncred@hotmail.com>
2020-06-25 17:44:55 +12:00
4468947ad4 Add release automation with GitHub Actions (#2048) 2020-06-25 11:43:25 +12:00
93144a0132 Make mode subcommand: math mode (#2043)
* Update calculate to return a table when Value is a table

* impl mode subcommand for math

* add tests for math mode subcommand

* add table/row tests for math mode subcommand

* fix formatting
2020-06-25 05:57:27 +12:00
72f7406057 Fix cal command week-start example (#2040) 2020-06-24 17:15:19 +12:00
c56cbd0f6b Cleanup header to work like before (#2039) 2020-06-24 06:51:06 +12:00
1420cbafe4 Refactor out RunnableContext from calculate (#2037) 2020-06-24 06:24:33 +12:00
053bd926ec First pass at updating all documentation formatting and cleaning up output of examples (#2031) 2020-06-24 06:21:47 +12:00
d095cb91e4 bump which from 3 to 4.0.1 (#2035)
This release should work with reparse points like WinGet.
2020-06-22 12:58:10 -05:00
e8476d8fbb removed some comments (#2032)
removed comments that i shouldn't have left in
2020-06-22 08:30:43 -05:00
7532618bdc Add error for division by zero using "=" (#2002) (#2030)
* error for division by zero with =

* cargo fmt

* add helper zero_division_error

* tests for zero division error

* fix test names (zero div error)
2020-06-22 08:50:43 +12:00
e3e1e6f81b Adds a test case for changing drives using drive letter in Windows (#2022)
* added test case to test check switching drives using drive letter in windows

* modified test case to not use config, assert file exists
2020-06-22 07:02:58 +12:00
bce6f5a3e6 Uniq: --count flag to count occurences (#2017)
* uniq: Add counting option (WIP!)

Usage:

fetch https://raw.githubusercontent.com/timbray/topfew/master/test/data/access-1k | lines | wrap item | uniq | sort-by count | last 10

* uniq: Add first test

* uniq: Re-enable the non-counting variant.

* uniq: Also handle primitive lines.

* uniq: Update documentation

* uniq: Final comment about error handling. Let's get some feedback

* uniq: Address review comments.

Not happy with the way I create a TypeError. There must be a cleaner
way. Anyway, good for shipping.

* uniq: Use Labeled_error as suggested by jturner in chat.

* uniq: Return error directly.

Co-authored-by: Christoph Siedentop <christoph@siedentop.name>
2020-06-21 12:22:06 +12:00
480600c465 Fix wrap of carriage returns in cells (#2027)
* Fix carriage returns in cells

* Fix carriage returns in cells
2020-06-21 09:33:58 +12:00
89c737f456 Finish move to nu-table (#2025) 2020-06-21 07:25:07 +12:00
4e83363dd3 Upgrade heim to 0.1.0-beta.3 (#2019) 2020-06-21 06:55:16 +12:00
de6d8738c4 Simplify textview match (#2024)
* simplify textview match code

* Math median tests and documentation additions (#2018)

* Add math median example and unit tests

* Update output of other all math ls command examples to keep consistent with math median output

* Fix output of math max example

* Update output of other math commands using pwd examples to keep data consistent
2020-06-20 12:16:36 -05:00
853d7e7120 Math median tests and documentation additions (#2018)
* Add math median example and unit tests

* Update output of other all math ls command examples to keep consistent with math median output

* Fix output of math max example

* Update output of other math commands using pwd examples to keep data consistent
2020-06-20 00:28:03 -05:00
b0c30098e4 Sort primitives explictly. (#2016)
* Sort primitives explictly.

* Write backing up test.
2020-06-19 23:34:36 -05:00
fcbaefed52 Nu table (#2015)
* WIP

* Get ready to land nu-table

* Remove unwrap
2020-06-20 15:41:53 +12:00
77e02ac1c1 Fixed grammar (#2012) 2020-06-19 20:54:25 -05:00
088901b24f Rename average to avg 2020-06-19 18:59:00 -05:00
ed7a62bca3 textview config docs (#2011)
* documentation for bat config changes

* renamed to textview, added fetch example

Co-authored-by: Darren Schroeder <fdncred@hotmail.com>
2020-06-19 15:45:56 -05:00
6bfd8532e4 Bat config (#2010)
* WIP - changes to support bat config

* added bat configuration

* removed debug info

* clippy fix

* changed [bat] to [textview]

Co-authored-by: Darren Schroeder <fdncred@hotmail.com>
2020-06-19 15:08:59 -05:00
bc9cc75c8a Minor Math Sum Additions (#2007)
* Move sum tests into math directory

* Move sum documentation over to math documentation

One sum example already existed in the math examples and a few of the others were outdated and didn't work, so I only moved one over, and updated their output

* Remove no-longer-in-use mod statement
2020-06-20 06:00:18 +12:00
53a6e9f0bd Convert sum command into subcommand of the math command (#2004)
* Convert sum command into subcommand of the math command

* Add bullet points to math.md documentation
2020-06-18 21:02:01 -05:00
5f9de80d9b Math#media - ability to compute median value. 2020-06-18 16:59:43 -05:00
353b33be1b Add support to allow the week day start in cal to be configured via a flag (#1996)
* Add support to allow the week day start in cal to be configurable

* Fix variable name

* Use a flag instead of a configuration setting for specifying the starting day of the week
2020-06-19 05:34:51 +12:00
96d58094cf Fix regression. skip-until 'skips' until condition is met. 2020-06-17 14:08:09 -05:00
94aac0e8dd Remove unused pattern matched tag fields. 2020-06-17 13:34:17 -05:00
9f54d238ba Refactoring and more split-by flexibility. 2020-06-17 13:34:17 -05:00
778e497903 Refactoring and more group-by flexibility. 2020-06-17 13:34:17 -05:00
6914099e28 Cat with wings (#1993)
* WIP - Modified textview to use bat crate

* use input_from_bytes_with_name instead of input_file

* removed old paging
added prettyprint on else blocks
duplicated  too much code
hard coded defaults

Co-authored-by: Darren Schroeder <fdncred@hotmail.com>
2020-06-16 16:17:32 -05:00
1b6f94b46c Cal command code cleanup (#1990)
* Cal command code cleanup

* Reverting "index_map" back to "indexmap", since that is the convention used in the system
2020-06-17 08:00:49 +12:00
3d63901b3b Add 'every' command to select (or skip) every nth row (#1992)
* Add 'every' command

* Add --skip option to 'every' command

This option skips instead of selects every nth row

* Fix descriptions for 'every' command

* Add docummentation for 'every' command

* Check actual filenames in 'every' command tests
2020-06-17 07:58:41 +12:00
eb1ada6115 issue1332 - Fix for yamls with unquoted double curly braces (#1988)
* Gnarly hardcoded fix

* Whoops remove println
2020-06-17 07:12:04 +12:00
831111edec Pass the borrow instead of clone. (#1986) 2020-06-16 05:35:24 +12:00
29ea29261d Bump to 0.15.1 (#1984) 2020-06-15 09:54:30 +12:00
ee835f75db Last batch of removing async_stream (#1983) 2020-06-15 09:00:42 +12:00
bd7ac0d48e Math Documentation (#1982)
* Adding math docs

* Add some comments to calculate

* Remove redudant message

Message already shows up in subcommands list

* Added not working example

Accidentantly

* Remove table
2020-06-15 08:42:15 +12:00
d7b1480ad0 Another batch of removing async_stream (#1981) 2020-06-14 11:54:35 +12:00
86b316e930 Another batch of removing async_stream (#1979)
* Another batch of removing async_stream

* Another batch of removing async_stream

* Another batch of removing async_stream
2020-06-14 10:01:44 +12:00
a042f407c1 1882-Add min, max in addition to average for acting lists (#1969)
* Converting average.rs to math.rs

* Making some progress towards math

Examples and unit tests failing, also think I found a bug with passing in strings

* Fix typos

* Found issue with negative numbers

* Add some comments

* Split commands like in split and str_ but do not register?

* register commands in cli

* Address clippy warnings

* Fix bad examples

* Make the example failure message more helpful

* Replace unwraps

* Use compare_values to avoid coercion issues

* Remove unneeded code
2020-06-14 09:49:57 +12:00
40673e4599 Another batch of removing async_stream (#1978) 2020-06-14 07:13:36 +12:00
bcfb084d4c Remove async_stream from some commands (#1976) 2020-06-14 04:30:24 +12:00
a1fd5ad128 Updated Readme to include Roadmap project board. (#1975) 2020-06-13 08:24:30 -05:00
fe6d96e996 Another batch of converting commands away from async_stream (#1974)
* Another batch of removing async_stream

* merge master
2020-06-13 20:43:21 +12:00
e24e0242d1 Removing async_stream! from some more commands (#1973)
* Removing async_stream! from some more commands

* Fix await error

* Fix Clippy warnings
2020-06-13 20:03:13 +12:00
c959dc1ee3 Another batch of removing async_stream (#1972) 2020-06-13 16:03:39 +12:00
d82ce26b44 Another batch of removing async_stream (#1971) 2020-06-13 11:40:23 +12:00
935a5f6b9e Another batch of removing async_stream (#1970) 2020-06-12 20:34:41 +12:00
731aa6bbdd use encoding on open for #1939 (#1949)
* WIP - not compiling

* compiling but panicing

* still broken

* nearly working

* reverted deserializer_string changes
updated enter.rs and open.rs to use Option<Tagged<String>>
Accepted Clippy suggestions
Accepted fmt suggestions
Left original code from open.rs
 We may want to use some of it and only fallback to encoding.

* Don't exit when there is an unknown encoding.

* When encoding is unknown default to utf-8.

* only do encoding if the user says to it

* merged some conflicts on open

* made error messages consistent

* Updated unwrap with expect

* updated open test to pass with more descriptive err
updated enter test to not fail

* change _location to location

* changed _visitor to visitor

* Added a more verbose usage statement for encoding
Linked to docs.rs/encoding_rs for details

Co-authored-by: Darren Schroeder <fdncred@hotmail.com>
2020-06-11 19:37:43 -05:00
a268e825aa Allow config to be readonly (#1967) 2020-06-12 05:50:57 +12:00
982f067d0e Proper precedence history in math (#1966) 2020-06-12 05:17:08 +12:00
429 changed files with 20081 additions and 9101 deletions

View File

@ -1,11 +1,14 @@
trigger: trigger:
- master - main
strategy: strategy:
matrix: matrix:
linux-stable: linux-stable:
image: ubuntu-18.04 image: ubuntu-18.04
style: 'unflagged' style: 'unflagged'
linux-minimal:
image: ubuntu-18.04
style: 'minimal'
macos-stable: macos-stable:
image: macos-10.14 image: macos-10.14
style: 'unflagged' style: 'unflagged'
@ -41,10 +44,10 @@ steps:
rustup component add clippy --toolchain stable-x86_64-apple-darwin rustup component add clippy --toolchain stable-x86_64-apple-darwin
export PATH=$HOME/.cargo/bin:$PATH export PATH=$HOME/.cargo/bin:$PATH
fi fi
rustup update # rustup update
rustc -Vv # rustc -Vv
echo "##vso[task.prependpath]$HOME/.cargo/bin" # echo "##vso[task.prependpath]$HOME/.cargo/bin"
rustup component add rustfmt # rustup component add rustfmt
displayName: Install Rust displayName: Install Rust
- bash: RUSTFLAGS="-D warnings" cargo test --all --features stable - bash: RUSTFLAGS="-D warnings" cargo test --all --features stable
condition: eq(variables['style'], 'unflagged') condition: eq(variables['style'], 'unflagged')
@ -58,6 +61,9 @@ steps:
- 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: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
condition: eq(variables['style'], 'canary') condition: eq(variables['style'], 'canary')
displayName: Check clippy lints displayName: Check clippy lints
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features
condition: eq(variables['style'], 'minimal')
displayName: Run tests
- bash: cargo fmt --all -- --check - bash: cargo fmt --all -- --check
condition: eq(variables['style'], 'fmt') condition: eq(variables['style'], 'fmt')
displayName: Lint displayName: Lint

View File

@ -1,3 +1 @@
[build] [build]
#rustflags = ["--cfg", "data_processing_primitives"]

View File

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

286
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,286 @@
name: Create Release Draft
on:
push:
tags: ['[0-9]+.[0-9]+.[0-9]+*']
jobs:
linux:
name: Build Linux
runs-on: ubuntu-latest
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
- 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_*
# 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:
name: linux
path: output/*
macos:
name: Build macOS
runs-on: macos-latest
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
- 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_*
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: macos
path: output/*
windows:
name: Build Windows
runs-on: windows-latest
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
- 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.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
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:
name: windows-zip
path: output\*
release:
name: Publish Release
runs-on: ubuntu-latest
needs:
- linux
- macos
- windows
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Determine Release Info
id: info
env:
GITHUB_REF: ${{ github.ref }}
run: |
VERSION=${GITHUB_REF##*/}
MAJOR=${VERSION%%.*}
MINOR=${VERSION%.*}
MINOR=${MINOR#*.}
PATCH=${VERSION##*.}
echo "::set-output name=version::${VERSION}"
echo "::set-output name=linuxdir::nu_${MAJOR}_${MINOR}_${PATCH}_linux"
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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
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:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
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:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
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 }}
- 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:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
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:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./nushell-windows.msi
asset_name: ${{ steps.info.outputs.windowsdir }}.msi
asset_content_type: applictaion/x-msi

View File

@ -68,9 +68,9 @@ members of the project's leadership.
## Attribution ## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html available at <https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>
[homepage]: https://www.contributor-covenant.org [homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq <https://www.contributor-covenant.org/faq>

View File

@ -1,3 +1,5 @@
# Contributing
Welcome to nushell! Welcome to nushell!
*Note: for a more complete guide see [The nu contributor book](https://github.com/nushell/contributor-book)* *Note: for a more complete guide see [The nu contributor book](https://github.com/nushell/contributor-book)*
@ -9,17 +11,19 @@ For speedy contributions open it in Gitpod, nu will be pre-installed with the la
To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)! To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)!
<!--WIP--> <!--WIP-->
# Developing ## Developing
## Set up
### Set up
This is no different than other Rust projects. This is no different than other Rust projects.
```shell ```bash
git clone https://github.com/nushell/nushell git clone https://github.com/nushell/nushell
cd nushell cd nushell
cargo build cargo build
``` ```
## Useful Commands ### Useful Commands
Build and run Nushell: Build and run Nushell:

1484
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

1
README.build.txt Normal file
View File

@ -0,0 +1 @@
Nu will look for the plugins in your PATH on startup. While nu will have some functionality without them, for full functionality you'll need to copy them into your path so they can be loaded.

133
README.md
View File

@ -1,17 +1,19 @@
# README
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/nushell/nushell) [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/nushell/nushell)
[![Crates.io](https://img.shields.io/crates/v/nu.svg)](https://crates.io/crates/nu) [![Crates.io](https://img.shields.io/crates/v/nu.svg)](https://crates.io/crates/nu)
[![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) [![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) [![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) [![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
# Nu Shell
A new type of shell. A new type of shell.
![Example of nushell](images/nushell-autocomplete.gif "Example of nushell") ![Example of nushell](images/nushell-autocomplete.gif "Example of nushell")
# Status ## Status
This project has reached a minimum-viable product level of quality. This project has reached a minimum-viable product level of quality.
While contributors dogfood it as their daily driver, it may be unstable for some commands. While contributors dogfood it as their daily driver, it may be unstable for some commands.
@ -21,7 +23,7 @@ Its design is also subject to change as it matures.
Nu comes with a set of built-in commands (listed below). Nu comes with a set of built-in commands (listed below).
If a command is unknown, the command will shell-out and execute it (using cmd on Windows or bash on Linux and macOS), correctly passing through stdin, stdout, and stderr, so things like your daily git workflows and even `vim` will work just fine. If a command is unknown, the command will shell-out and execute it (using cmd on Windows or bash on Linux and macOS), correctly passing through stdin, stdout, and stderr, so things like your daily git workflows and even `vim` will work just fine.
# Learning more ## Learning more
There are a few good resources to learn about Nu. There are a few good resources to learn about Nu.
There is a [book](https://www.nushell.sh/book/) about Nu that is currently in progress. There is a [book](https://www.nushell.sh/book/) about Nu that is currently in progress.
@ -38,9 +40,9 @@ Try it in Gitpod.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/nushell/nushell) [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/nushell/nushell)
# Installation ## Installation
## Local ### Local
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/en/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support. Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/en/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
@ -58,19 +60,19 @@ Optional dependencies:
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`): To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`):
``` ```bash
cargo install nu 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: 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=stable
``` ```
## Docker ### Docker
### Quickstart #### Quickstart
Want to try Nu right away? Execute the following to get started. Want to try Nu right away? Execute the following to get started.
@ -78,14 +80,14 @@ Want to try Nu right away? Execute the following to get started.
docker run -it quay.io/nushell/nu:latest docker run -it quay.io/nushell/nu:latest
``` ```
### Guide #### Guide
If you want to pull a pre-built container, you can browse tags for the [nushell organization](https://quay.io/organization/nushell) If you want to pull a pre-built container, you can browse tags for the [nushell organization](https://quay.io/organization/nushell)
on Quay.io. Pulling a container would come down to: on Quay.io. Pulling a container would come down to:
```bash ```bash
$ docker pull quay.io/nushell/nu docker pull quay.io/nushell/nu
$ docker pull quay.io/nushell/nu-base docker pull quay.io/nushell/nu-base
``` ```
Both "nu-base" and "nu" provide the nu binary, however nu-base also includes the source code at `/code` Both "nu-base" and "nu" provide the nu binary, however nu-base also includes the source code at `/code`
@ -95,41 +97,41 @@ Optionally, you can also build the containers locally using the [dockerfiles pro
To build the base image: To build the base image:
```bash ```bash
$ docker build -f docker/Dockerfile.nu-base -t nushell/nu-base . docker build -f docker/Dockerfile.nu-base -t nushell/nu-base .
``` ```
And then to build the smaller container (using a Multistage build): And then to build the smaller container (using a Multistage build):
```bash ```bash
$ docker build -f docker/Dockerfile -t nushell/nu . docker build -f docker/Dockerfile -t nushell/nu .
``` ```
Either way, you can run either container as follows: Either way, you can run either container as follows:
```bash ```bash
$ docker run -it nushell/nu-base docker run -it nushell/nu-base
$ docker run -it nushell/nu docker run -it nushell/nu
/> exit /> exit
``` ```
The second container is a bit smaller if the size is important to you. The second container is a bit smaller if the size is important to you.
## Packaging status ### Packaging status
[![Packaging status](https://repology.org/badge/vertical-allrepos/nushell.svg)](https://repology.org/project/nushell/versions) [![Packaging status](https://repology.org/badge/vertical-allrepos/nushell.svg)](https://repology.org/project/nushell/versions)
### Fedora #### Fedora
[COPR repo](https://copr.fedorainfracloud.org/coprs/atim/nushell/): `sudo dnf copr enable atim/nushell -y && sudo dnf install nushell -y` [COPR repo](https://copr.fedorainfracloud.org/coprs/atim/nushell/): `sudo dnf copr enable atim/nushell -y && sudo dnf install nushell -y`
# Philosophy ## Philosophy
Nu draws inspiration from projects like PowerShell, functional programming languages, and modern CLI tools. Nu draws inspiration from projects like PowerShell, functional programming languages, and modern CLI tools.
Rather than thinking of files and services as raw streams of text, Nu looks at each input as something with structure. Rather than thinking of files and services as raw streams of text, Nu looks at each input as something with structure.
For example, when you list the contents of a directory, what you get back is a table of rows, where each row represents an item in that directory. For example, when you list the contents of a directory, what you get back is a table of rows, where each row represents an item in that directory.
These values can be piped through a series of steps, in a series of commands called a 'pipeline'. These values can be piped through a series of steps, in a series of commands called a 'pipeline'.
## Pipelines ### Pipelines
In Unix, it's common to pipe between commands to split up a sophisticated command over multiple steps. In Unix, it's common to pipe between commands to split up a sophisticated command over multiple steps.
Nu takes this a step further and builds heavily on the idea of _pipelines_. Nu takes this a step further and builds heavily on the idea of _pipelines_.
@ -138,40 +140,40 @@ Additionally, commands can output structured data (you can think of this as a th
Commands that work in the pipeline fit into one of three categories: Commands that work in the pipeline fit into one of three categories:
* Commands that produce a stream (eg, `ls`) * Commands that produce a stream (eg, `ls`)
* Commands that filter a stream (eg, `where type == "Directory"`) * Commands that filter a stream (eg, `where type == "Dir"`)
* Commands that consume the output of the pipeline (eg, `autoview`) * Commands that consume the output of the pipeline (eg, `autoview`)
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right. Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
``` ```shell
/home/jonathan/Source/nushell(master)> ls | where type == "Dir" | autoview > ls | where type == "Dir" | autoview
───┬────────┬──────┬───────┬────────────── ───┬────────┬──────┬───────┬──────────────
# │ name │ type │ size │ modified # │ name │ type │ size │ modified
───┼────────┼──────┼───────┼────────────── ───┼────────┼──────┼───────┼──────────────
0 │ assets │ Dir │ 4.1 KB │ 1 week ago 0 │ assets │ Dir │ 128 B │ 5 months ago
1 │ crates │ Dir │ 4.1 KB │ 4 days ago 1 │ crates │ Dir │ 704 B │ 50 mins ago
2 │ debian │ Dir │ 4.1 KB │ 1 week ago 2 │ debian │ Dir │ 352 B │ 5 months ago
3 │ docker │ Dir │ 4.1 KB │ 1 week ago 3 │ docker │ Dir │ 288 B │ 3 months ago
4 │ docs │ Dir │ 4.1 KB │ 1 week ago 4 │ docs │ Dir │ 192 B │ 50 mins ago
5 │ images │ Dir │ 4.1 KB │ 1 week ago 5 │ images │ Dir │ 160 B │ 5 months ago
6 │ src │ Dir │ 4.1 KB │ 1 week ago 6 │ src │ Dir │ 128 B │ 1 day ago
7 │ target │ Dir │ 4.1 KB │ 23 hours ago 7 │ target │ Dir │ 160 B │ 5 days ago
8 │ tests │ Dir │ 4.1 KB │ 1 week ago 8 │ tests │ Dir │ 192 B │ 3 months ago
───┴────────┴──────┴───────┴────────────── ───┴────────┴──────┴───────┴──────────────
``` ```
Because most of the time you'll want to see the output of a pipeline, `autoview` is assumed. Because most of the time you'll want to see the output of a pipeline, `autoview` is assumed.
We could have also written the above: We could have also written the above:
``` ```shell
/home/jonathan/Source/nushell(master)> ls | where type == Directory > ls | where type == Dir
``` ```
Being able to use the same commands and compose them differently is an important philosophy in Nu. Being able to use the same commands and compose them differently is an important philosophy in Nu.
For example, we could use the built-in `ps` command as well to get a list of the running processes, using the same `where` as above. For example, we could use the built-in `ps` command as well to get a list of the running processes, using the same `where` as above.
```text ```shell
/home/jonathan/Source/nushell(master)> ps | where cpu > 0 > ps | where cpu > 0
───┬────────┬───────────────────┬──────────┬─────────┬──────────┬────────── ───┬────────┬───────────────────┬──────────┬─────────┬──────────┬──────────
# │ pid │ name │ status │ cpu │ mem │ virtual # │ pid │ name │ status │ cpu │ mem │ virtual
───┼────────┼───────────────────┼──────────┼─────────┼──────────┼────────── ───┼────────┼───────────────────┼──────────┼─────────┼──────────┼──────────
@ -183,13 +185,13 @@ For example, we could use the built-in `ps` command as well to get a list of the
───┴────────┴───────────────────┴──────────┴─────────┴──────────┴────────── ───┴────────┴───────────────────┴──────────┴─────────┴──────────┴──────────
``` ```
## Opening files ### Opening files
Nu can load file and URL contents as raw text or as structured data (if it recognizes the format). Nu can load file and URL contents as raw text or as structured data (if it recognizes the format).
For example, you can load a .toml file as structured data and explore it: For example, you can load a .toml file as structured data and explore it:
``` ```shell
/home/jonathan/Source/nushell(master)> open Cargo.toml > open Cargo.toml
────────────────────┬─────────────────────────── ────────────────────┬───────────────────────────
bin │ [table 18 rows] bin │ [table 18 rows]
build-dependencies │ [row nu-build serde toml] build-dependencies │ [row nu-build serde toml]
@ -203,8 +205,8 @@ For example, you can load a .toml file as structured data and explore it:
We can pipeline this into a command that gets the contents of one of the columns: We can pipeline this into a command that gets the contents of one of the columns:
``` ```shell
/home/jonathan/Source/nushell(master)> open Cargo.toml | get package > open Cargo.toml | get package
───────────────┬──────────────────────────────────── ───────────────┬────────────────────────────────────
authors │ [table 1 rows] authors │ [table 1 rows]
default-run │ nu default-run │ nu
@ -217,31 +219,31 @@ We can pipeline this into a command that gets the contents of one of the columns
name │ nu name │ nu
readme │ README.md readme │ README.md
repository │ https://github.com/nushell/nushell repository │ https://github.com/nushell/nushell
version │ 0.14.1 version │ 0.15.1
───────────────┴──────────────────────────────────── ───────────────┴────────────────────────────────────
``` ```
Finally, we can use commands outside of Nu once we have the data we want: Finally, we can use commands outside of Nu once we have the data we want:
``` ```shell
/home/jonathan/Source/nushell(master)> open Cargo.toml | get package.version | echo $it > open Cargo.toml | get package.version | echo $it
0.14.1 0.15.1
``` ```
Here we use the variable `$it` to refer to the value being piped to the external command. Here we use the variable `$it` to refer to the value being piped to the external command.
## Configuration ### Configuration
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). 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 [edit_mode "vi"]
> config --set [path $nu.path] > config --set [path $nu.path]
``` ```
## Shells ### Shells
Nu will work inside of a single directory and allow you to navigate around your filesystem by default. Nu will work inside of a single directory and allow you to navigate around your filesystem by default.
Nu also offers a way of adding additional working directories that you can jump between, allowing you to work in multiple directories at the same time. Nu also offers a way of adding additional working directories that you can jump between, allowing you to work in multiple directories at the same time.
@ -252,7 +254,7 @@ Once you're done with a shell, you can `exit` it and remove it from the ring buf
Finally, to get a list of all the current shells, you can use the `shells` command. Finally, to get a list of all the current shells, you can use the `shells` command.
## Plugins ### Plugins
Nu supports plugins that offer additional functionality to the shell and follow the same structured data model that built-in commands use. Nu supports plugins that offer additional functionality to the shell and follow the same structured data model that built-in commands use.
This allows you to extend nu for your needs. This allows you to extend nu for your needs.
@ -264,7 +266,7 @@ These binaries interact with nu via a simple JSON-RPC protocol where the command
If the plugin is a filter, data streams to it one element at a time, and it can stream data back in return via stdin/stdout. If the plugin is a filter, data streams to it one element at a time, and it can stream data back in return via stdin/stdout.
If the plugin is a sink, it is given the full vector of final data and is given free reign over stdin/stdout to use as it pleases. If the plugin is a sink, it is given the full vector of final data and is given free reign over stdin/stdout to use as it pleases.
# Goals ## Goals
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals. Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
@ -278,11 +280,11 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat
* Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state. * Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
# Commands ## Commands
You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/documentation.html#quick-command-references). You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/documentation.html#quick-command-references).
# Progress ## Progress
Nu is in heavy development, and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion: Nu is in heavy development, and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
@ -290,24 +292,27 @@ Nu is in heavy development, and will naturally change as it matures and people u
| -------- |:-----------:|:---------:|:---:|:-------:|:------:| ----- | -------- |:-----------:|:---------:|:---:|:-------:|:------:| -----
| Aliases | | X | | | | Initial implementation but lacks necessary features | Aliases | | X | | | | Initial implementation but lacks necessary features
| Notebook | | X | | | | Initial jupyter support, but it loses state and lacks features | Notebook | | X | | | | Initial jupyter support, but it loses state and lacks features
| File ops | | | X | | | cp, mv, rm, mkdir have some support, but lacking others | File ops | | | X | | | cp, mv, rm, mkdir have some support, but lacking others
| Environment | | X | | | | Temporary environment, but no session-wide env variables | Environment | | X | | | | Temporary environment, but no session-wide env variables
| Shells | | X | | | | Basic value and file shells, but no opt-in/opt-out for commands | Shells | | X | | | | Basic value and file shells, but no opt-in/opt-out for commands
| Protocol | | | X | | | Streaming protocol is serviceable | Protocol | | | X | | | Streaming protocol is serviceable
| Plugins | | X | | | | Plugins work on one row at a time, lack batching and expression eval | Plugins | | X | | | | Plugins work on one row at a time, lack batching and expression eval
| Errors | | | X | | | Error reporting works, but could use usability polish | Errors | | | X | | | Error reporting works, but could use usability polish
| Documentation | | X | | | | Book and related are barebones and lack task-based lessons | Documentation | | X | | | | Book and related are barebones and lack task-based lessons
| Paging | | X | | | | Textview has paging, but we'd like paging for tables | Paging | | X | | | | Textview has paging, but we'd like paging for tables
| Functions| X | | | | | No functions, yet, only aliases | Functions| X | | | | | No functions, yet, only aliases
| Variables| X | | | | | Nu doesn't yet support variables | Variables| X | | | | | Nu doesn't yet support variables
| Completions | | X | | | | Completions are currently barebones, at best | Completions | | X | | | | Completions are currently barebones, at best
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked | Type-checking | | X | | | | Commands check basic types, but input/output isn't checked
# Contributing ## Current Roadmap
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in NuShell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
## Contributing
See [Contributing](CONTRIBUTING.md) for details. See [Contributing](CONTRIBUTING.md) for details.
# License ## License
The project is made available under the MIT license. See the `LICENSE` file for more information. The project is made available under the MIT license. See the `LICENSE` file for more information.

60
TODO.md
View File

@ -1,60 +0,0 @@
This pattern is extremely repetitive and can be abstracted:
```rs
let args = args.evaluate_once(registry)?;
let tag = args.name_tag();
let input = args.input;
let stream = async_stream! {
let values: Vec<Value> = input.values.collect().await;
let mut concat_string = String::new();
let mut latest_tag: Option<Tag> = None;
for value in values {
latest_tag = Some(value_tag.clone());
let value_span = value.tag.span;
match &value.value {
UntaggedValue::Primitive(Primitive::String(s)) => {
concat_string.push_str(&s);
concat_string.push_str("\n");
}
_ => yield Err(ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
name_span,
"value originates from here",
value_span,
)),
}
}
```
Mandatory and Optional in parse_command
trace_remaining?
select_fields and select_fields take unnecessary Tag
Value#value should be Value#untagged
Unify dictionary building, probably around a macro
sys plugin in own crate
textview in own crate
Combine atomic and atomic_parse in parser
at_end_possible_ws needs to be comment and separator sensitive
Eliminate unnecessary `nodes` parser
#[derive(HasSpan)]
Figure out a solution for the duplication in stuff like NumberShape vs. NumberExpressionShape
use `struct Expander` from signature.rs

View File

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

View File

@ -1,114 +1,122 @@
[package] [package]
name = "nu-cli"
version = "0.15.0"
authors = ["The Nu Project Contributors"] authors = ["The Nu Project Contributors"]
description = "CLI for nushell" description = "CLI for nushell"
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
name = "nu-cli"
version = "0.17.0"
[lib] [lib]
doctest = false doctest = false
[dependencies] [dependencies]
nu-source = { version = "0.15.0", path = "../nu-source" } nu-errors = {version = "0.17.0", path = "../nu-errors"}
nu-plugin = { version = "0.15.0", path = "../nu-plugin" } nu-parser = {version = "0.17.0", path = "../nu-parser"}
nu-protocol = { version = "0.15.0", path = "../nu-protocol" } nu-plugin = {version = "0.17.0", path = "../nu-plugin"}
nu-errors = { version = "0.15.0", path = "../nu-errors" } nu-protocol = {version = "0.17.0", path = "../nu-protocol"}
nu-parser = { version = "0.15.0", path = "../nu-parser" } nu-source = {version = "0.17.0", path = "../nu-source"}
nu-value-ext = { version = "0.15.0", path = "../nu-value-ext" } nu-table = {version = "0.17.0", path = "../nu-table"}
nu-test-support = { version = "0.15.0", path = "../nu-test-support" } nu-test-support = {version = "0.17.0", path = "../nu-test-support"}
nu-value-ext = {version = "0.17.0", path = "../nu-value-ext"}
ansi_term = "0.12.1" ansi_term = "0.12.1"
app_dirs = "1.2.1" app_dirs = "1.2.1"
async-recursion = "0.3.1" async-recursion = "0.3.1"
async-trait = "0.1.31" async-trait = "0.1.36"
directories = "2.0.2" base64 = "0.12.3"
async-stream = "0.2" bigdecimal = {version = "0.1.2", features = ["serde"]}
base64 = "0.12.1"
bigdecimal = { version = "0.1.2", features = ["serde"] }
bson = { version = "0.14.1", features = ["decimal128"] }
byte-unit = "3.1.3" byte-unit = "3.1.3"
bytes = "0.5.4" bytes = "0.5.5"
calamine = "0.16" calamine = "0.16"
cfg-if = "0.1" chrono = {version = "0.4.11", features = ["serde"]}
chrono = { version = "0.4.11", features = ["serde"] }
clap = "2.33.1" clap = "2.33.1"
codespan-reporting = "0.9.5"
csv = "1.1" csv = "1.1"
ctrlc = "3.1.4" ctrlc = {version = "3.1.4", optional = true}
derive-new = "0.5.8" derive-new = "0.5.8"
dirs = "2.0.2" directories = {version = "2.0.2", optional = true}
dunce = "1.0.0" dirs = {version = "2.0.2", optional = true}
dunce = "1.0.1"
eml-parser = "0.1.0" eml-parser = "0.1.0"
filesize = "0.2.0" filesize = "0.2.0"
futures = { version = "0.3", features = ["compat", "io-compat"] } futures = {version = "0.3", features = ["compat", "io-compat"]}
futures-util = "0.3.5" futures-util = "0.3.5"
futures_codec = "0.4" futures_codec = "0.4"
getset = "0.1.1" getset = "0.1.1"
git2 = { version = "0.13.6", default_features = false } git2 = {version = "0.13.6", default_features = false, optional = true}
glob = "0.3.0" glob = "0.3.0"
hex = "0.4" hex = "0.4"
htmlescape = "0.3.1" htmlescape = "0.3.1"
ical = "0.6.*" ical = "0.6.*"
ichwh = "0.3.4" ichwh = {version = "0.3.4", optional = true}
indexmap = { version = "1.3.2", features = ["serde-1"] } indexmap = {version = "1.4.0", features = ["serde-1"]}
itertools = "0.9.0" itertools = "0.9.0"
codespan-reporting = "0.9.4"
log = "0.4.8" log = "0.4.8"
meval = "0.2" meval = "0.2"
natural = "0.5.0" natural = "0.5.0"
num-bigint = { version = "0.2.6", features = ["serde"] } num-bigint = {version = "0.2.6", features = ["serde"]}
num-format = {version = "0.4", features = ["with-num-bigint"]}
num-traits = "0.2.11" num-traits = "0.2.11"
parking_lot = "0.10.2" parking_lot = "0.11.0"
pin-utils = "0.1.0" pin-utils = "0.1.0"
pretty-hex = "0.1.1" pretty-hex = "0.1.1"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
prettytable-rs = "0.8.0" ptree = {version = "0.2", optional = true}
ptree = {version = "0.2" }
query_interface = "0.3.5" query_interface = "0.3.5"
rand = "0.7" rand = "0.7"
regex = "1" regex = "1"
roxmltree = "0.11.0" roxmltree = "0.13.0"
rustyline = "6.2.0" rustyline = "6.2.0"
serde = { version = "1.0.110", features = ["derive"] } serde = {version = "1.0.114", features = ["derive"]}
serde-hjson = "0.9.1" serde-hjson = "0.9.1"
serde_bytes = "0.11.4" serde_bytes = "0.11.5"
serde_ini = "0.2.0" serde_ini = "0.2.0"
serde_json = "1.0.53" serde_json = "1.0.55"
serde_urlencoded = "0.6.1" serde_urlencoded = "0.6.1"
serde_yaml = "0.8" serde_yaml = "0.8"
sha2 = "0.9.1"
shellexpand = "2.0.0" shellexpand = "2.0.0"
strip-ansi-escapes = "0.1.0" strip-ansi-escapes = "0.1.0"
tempfile = "3.1.0" tempfile = "3.1.0"
term = "0.5.2" term = {version = "0.5.2", optional = true}
term_size = "0.3.2"
termcolor = "1.1.0" termcolor = "1.1.0"
textwrap = {version = "0.11.0", features = ["term_size"]}
toml = "0.5.6" toml = "0.5.6"
typetag = "0.1.4" typetag = "0.1.5"
umask = "1.0.0" umask = "1.0.0"
unicode-xid = "0.2.0" unicode-xid = "0.2.1"
which = "3" uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
which = {version = "4.0.1", optional = true}
trash = { version = "1.0.1", optional = true } clipboard = {version = "0.5", optional = true}
clipboard = { version = "0.5", optional = true } encoding_rs = "0.8.23"
starship = { version = "0.41.3", optional = true } rayon = "1.3.1"
rayon = "1.3.0" starship = {version = "0.43.0", optional = true}
trash = {version = "1.0.1", optional = true}
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
users = "0.10.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] [dependencies.rusqlite]
version = "0.23.1"
features = ["bundled", "blob"] features = ["bundled", "blob"]
optional = true
version = "0.23.1"
[build-dependencies] [build-dependencies]
nu-build = { version = "0.15.0", path = "../nu-build" } nu-build = {version = "0.17.0", path = "../nu-build"}
[dev-dependencies] [dev-dependencies]
quickcheck = "0.9" quickcheck = "0.9"
quickcheck_macros = "0.9" quickcheck_macros = "0.9"
[features] [features]
clipboard-cli = ["clipboard"]
stable = [] stable = []
starship-prompt = ["starship"] starship-prompt = ["starship"]
clipboard-cli = ["clipboard"]
trash-support = ["trash"] trash-support = ["trash"]

View File

@ -1,19 +1,19 @@
use crate::commands::classified::block::run_block; use crate::commands::classified::block::run_block;
use crate::commands::classified::external::{MaybeTextCodec, StringOrBinary}; use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
use crate::commands::plugin::JsonRpc; use crate::commands::plugin::JsonRpc;
use crate::commands::plugin::{PluginCommand, PluginSink}; use crate::commands::plugin::{PluginCommand, PluginSink};
use crate::commands::whole_stream_command; use crate::commands::whole_stream_command;
use crate::context::Context; use crate::context::Context;
#[cfg(not(feature = "starship-prompt"))]
use crate::git::current_branch; use crate::git::current_branch;
use crate::path::canonicalize; use crate::path::canonicalize;
use crate::prelude::*; use crate::prelude::*;
use crate::EnvironmentSyncer; use crate::EnvironmentSyncer;
use futures_codec::FramedRead; use futures_codec::FramedRead;
use nu_errors::{ProximateShellError, ShellDiagnostic, ShellError};
use nu_errors::ShellError;
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments}; use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value}; use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
#[allow(unused)]
use nu_source::Tagged;
use log::{debug, trace}; use log::{debug, trace};
use rustyline::error::ReadlineError; use rustyline::error::ReadlineError;
@ -49,8 +49,9 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
let mut input = String::new(); let mut input = String::new();
let result = match reader.read_line(&mut input) { let result = match reader.read_line(&mut input) {
Ok(count) => { Ok(count) => {
trace!("processing response ({} bytes)", count); trace!(target: "nu::load", "plugin infrastructure -> config response");
trace!("response: {}", input); trace!(target: "nu::load", "plugin infrastructure -> processing response ({} bytes)", count);
trace!(target: "nu::load", "plugin infrastructure -> response: {}", input);
let response = serde_json::from_str::<JsonRpc<Result<Signature, ShellError>>>(&input); let response = serde_json::from_str::<JsonRpc<Result<Signature, ShellError>>>(&input);
match response { match response {
@ -58,13 +59,13 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
Ok(params) => { Ok(params) => {
let fname = path.to_string_lossy(); let fname = path.to_string_lossy();
trace!("processing {:?}", params); trace!(target: "nu::load", "plugin infrastructure -> processing {:?}", params);
let name = params.name.clone(); let name = params.name.clone();
let fname = fname.to_string(); let fname = fname.to_string();
if context.get_command(&name).is_some() { if context.get_command(&name).is_some() {
trace!("plugin {:?} already loaded.", &name); trace!(target: "nu::load", "plugin infrastructure -> {:?} already loaded.", &name);
} else if params.is_filter { } else if params.is_filter {
context.add_commands(vec![whole_stream_command(PluginCommand::new( context.add_commands(vec![whole_stream_command(PluginCommand::new(
name, fname, params, name, fname, params,
@ -79,7 +80,7 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
Err(e) => Err(e), Err(e) => Err(e),
}, },
Err(e) => { Err(e) => {
trace!("incompatible plugin {:?}", input); trace!(target: "nu::load", "plugin infrastructure -> incompatible {:?}", input);
Err(ShellError::untagged_runtime_error(format!( Err(ShellError::untagged_runtime_error(format!(
"Error: {:?}", "Error: {:?}",
e e
@ -150,10 +151,7 @@ pub fn load_plugins(context: &mut Context) -> Result<(), ShellError> {
.map(|path| { .map(|path| {
let bin_name = { let bin_name = {
if let Some(name) = path.file_name() { if let Some(name) = path.file_name() {
match name.to_str() { name.to_str().unwrap_or("")
Some(raw) => raw,
None => "",
}
} else { } else {
"" ""
} }
@ -188,7 +186,7 @@ pub fn load_plugins(context: &mut Context) -> Result<(), ShellError> {
}; };
if is_valid_name && is_executable { if is_valid_name && is_executable {
trace!("Trying {:?}", path.display()); trace!(target: "nu::load", "plugin infrastructure -> Trying {:?}", path.display());
// we are ok if this plugin load fails // we are ok if this plugin load fails
let _ = load_plugin(&path, &mut context.clone()); let _ = load_plugin(&path, &mut context.clone());
@ -252,6 +250,13 @@ pub fn create_default_context(
whole_stream_command(Remove), whole_stream_command(Remove),
whole_stream_command(Open), whole_stream_command(Open),
whole_stream_command(Config), whole_stream_command(Config),
whole_stream_command(ConfigGet),
whole_stream_command(ConfigSet),
whole_stream_command(ConfigSetInto),
whole_stream_command(ConfigClear),
whole_stream_command(ConfigLoad),
whole_stream_command(ConfigRemove),
whole_stream_command(ConfigPath),
whole_stream_command(Help), whole_stream_command(Help),
whole_stream_command(History), whole_stream_command(History),
whole_stream_command(Save), whole_stream_command(Save),
@ -259,9 +264,8 @@ pub fn create_default_context(
whole_stream_command(Cpy), whole_stream_command(Cpy),
whole_stream_command(Date), whole_stream_command(Date),
whole_stream_command(Cal), whole_stream_command(Cal),
whole_stream_command(Calc),
whole_stream_command(Mkdir), whole_stream_command(Mkdir),
whole_stream_command(Move), whole_stream_command(Mv),
whole_stream_command(Kill), whole_stream_command(Kill),
whole_stream_command(Version), whole_stream_command(Version),
whole_stream_command(Clear), whole_stream_command(Clear),
@ -270,9 +274,11 @@ pub fn create_default_context(
whole_stream_command(Debug), whole_stream_command(Debug),
whole_stream_command(Alias), whole_stream_command(Alias),
whole_stream_command(WithEnv), whole_stream_command(WithEnv),
whole_stream_command(Do),
// Statistics // Statistics
whole_stream_command(Size), whole_stream_command(Size),
whole_stream_command(Count), whole_stream_command(Count),
whole_stream_command(Benchmark),
// Metadata // Metadata
whole_stream_command(Tags), whole_stream_command(Tags),
// Shells // Shells
@ -288,6 +294,7 @@ pub fn create_default_context(
whole_stream_command(Split), whole_stream_command(Split),
whole_stream_command(SplitColumn), whole_stream_command(SplitColumn),
whole_stream_command(SplitRow), whole_stream_command(SplitRow),
whole_stream_command(SplitChars),
whole_stream_command(Lines), whole_stream_command(Lines),
whole_stream_command(Trim), whole_stream_command(Trim),
whole_stream_command(Echo), whole_stream_command(Echo),
@ -299,12 +306,19 @@ pub fn create_default_context(
whole_stream_command(StrUpcase), whole_stream_command(StrUpcase),
whole_stream_command(StrCapitalize), whole_stream_command(StrCapitalize),
whole_stream_command(StrFindReplace), whole_stream_command(StrFindReplace),
whole_stream_command(StrFrom),
whole_stream_command(StrSubstring), whole_stream_command(StrSubstring),
whole_stream_command(StrSet), whole_stream_command(StrSet),
whole_stream_command(StrToDatetime), whole_stream_command(StrToDatetime),
whole_stream_command(StrTrim), whole_stream_command(StrTrim),
whole_stream_command(StrCollect),
whole_stream_command(StrLength),
whole_stream_command(StrReverse),
whole_stream_command(BuildString), whole_stream_command(BuildString),
whole_stream_command(Ansi),
whole_stream_command(Char),
// Column manipulation // Column manipulation
whole_stream_command(MoveColumn),
whole_stream_command(Reject), whole_stream_command(Reject),
whole_stream_command(Select), whole_stream_command(Select),
whole_stream_command(Get), whole_stream_command(Get),
@ -320,10 +334,12 @@ pub fn create_default_context(
whole_stream_command(GroupByDate), whole_stream_command(GroupByDate),
whole_stream_command(First), whole_stream_command(First),
whole_stream_command(Last), whole_stream_command(Last),
whole_stream_command(Every),
whole_stream_command(Nth), whole_stream_command(Nth),
whole_stream_command(Drop), whole_stream_command(Drop),
whole_stream_command(Format), whole_stream_command(Format),
whole_stream_command(Where), whole_stream_command(Where),
whole_stream_command(If),
whole_stream_command(Compact), whole_stream_command(Compact),
whole_stream_command(Default), whole_stream_command(Default),
whole_stream_command(Skip), whole_stream_command(Skip),
@ -338,6 +354,7 @@ pub fn create_default_context(
whole_stream_command(Each), whole_stream_command(Each),
whole_stream_command(IsEmpty), whole_stream_command(IsEmpty),
// Table manipulation // Table manipulation
whole_stream_command(Move),
whole_stream_command(Merge), whole_stream_command(Merge),
whole_stream_command(Shuffle), whole_stream_command(Shuffle),
whole_stream_command(Wrap), whole_stream_command(Wrap),
@ -345,16 +362,24 @@ pub fn create_default_context(
whole_stream_command(Headers), whole_stream_command(Headers),
// Data processing // Data processing
whole_stream_command(Histogram), whole_stream_command(Histogram),
whole_stream_command(Average), whole_stream_command(Autoenv),
whole_stream_command(Sum), whole_stream_command(AutoenvTrust),
whole_stream_command(AutoenvUnTrust),
whole_stream_command(Math),
whole_stream_command(MathAverage),
whole_stream_command(MathEval),
whole_stream_command(MathMedian),
whole_stream_command(MathMinimum),
whole_stream_command(MathMode),
whole_stream_command(MathMaximum),
whole_stream_command(MathStddev),
whole_stream_command(MathSummation),
whole_stream_command(MathVariance),
// File format output // File format output
whole_stream_command(To), whole_stream_command(To),
whole_stream_command(ToBSON),
whole_stream_command(ToCSV), whole_stream_command(ToCSV),
whole_stream_command(ToHTML), whole_stream_command(ToHTML),
whole_stream_command(ToJSON), whole_stream_command(ToJSON),
whole_stream_command(ToSQLite),
whole_stream_command(ToDB),
whole_stream_command(ToMarkdown), whole_stream_command(ToMarkdown),
whole_stream_command(ToTOML), whole_stream_command(ToTOML),
whole_stream_command(ToTSV), whole_stream_command(ToTSV),
@ -367,11 +392,8 @@ pub fn create_default_context(
whole_stream_command(FromTSV), whole_stream_command(FromTSV),
whole_stream_command(FromSSV), whole_stream_command(FromSSV),
whole_stream_command(FromINI), whole_stream_command(FromINI),
whole_stream_command(FromBSON),
whole_stream_command(FromJSON), whole_stream_command(FromJSON),
whole_stream_command(FromODS), whole_stream_command(FromODS),
whole_stream_command(FromDB),
whole_stream_command(FromSQLite),
whole_stream_command(FromTOML), whole_stream_command(FromTOML),
whole_stream_command(FromURL), whole_stream_command(FromURL),
whole_stream_command(FromXLSX), whole_stream_command(FromXLSX),
@ -382,19 +404,14 @@ pub fn create_default_context(
whole_stream_command(FromVcf), whole_stream_command(FromVcf),
// "Private" commands (not intended to be accessed directly) // "Private" commands (not intended to be accessed directly)
whole_stream_command(RunExternalCommand { interactive }), whole_stream_command(RunExternalCommand { interactive }),
// Random value generation
whole_stream_command(Random),
whole_stream_command(RandomBool),
whole_stream_command(RandomDice),
#[cfg(feature = "uuid_crate")]
whole_stream_command(RandomUUID),
]); ]);
cfg_if::cfg_if! {
if #[cfg(data_processing_primitives)] {
context.add_commands(vec![
whole_stream_command(ReduceBy),
whole_stream_command(EvaluateBy),
whole_stream_command(TSortBy),
whole_stream_command(MapMaxBy),
]);
}
}
#[cfg(feature = "clipboard")] #[cfg(feature = "clipboard")]
{ {
context.add_commands(vec![whole_stream_command(crate::commands::clip::Clip)]); context.add_commands(vec![whole_stream_command(crate::commands::clip::Clip)]);
@ -413,15 +430,18 @@ pub async fn run_vec_of_pipelines(
let _ = crate::load_plugins(&mut context); let _ = crate::load_plugins(&mut context);
let cc = context.ctrl_c.clone(); #[cfg(feature = "ctrlc")]
{
let cc = context.ctrl_c.clone();
ctrlc::set_handler(move || { ctrlc::set_handler(move || {
cc.store(true, Ordering::SeqCst); cc.store(true, Ordering::SeqCst);
}) })
.expect("Error setting Ctrl-C handler"); .expect("Error setting Ctrl-C handler");
if context.ctrl_c.load(Ordering::SeqCst) { if context.ctrl_c.load(Ordering::SeqCst) {
context.ctrl_c.store(false, Ordering::SeqCst); context.ctrl_c.store(false, Ordering::SeqCst);
}
} }
// before we start up, let's run our startup commands // before we start up, let's run our startup commands
@ -526,6 +546,10 @@ pub async fn cli(
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)), Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
); );
if let Err(e) = crate::keybinding::load_keybindings(&mut rl) {
println!("Error loading keybindings: {:?}", e);
}
#[cfg(windows)] #[cfg(windows)]
{ {
let _ = ansi_term::enable_ansi_support(); let _ = ansi_term::enable_ansi_support();
@ -534,11 +558,15 @@ pub async fn cli(
// we are ok if history does not exist // we are ok if history does not exist
let _ = rl.load_history(&History::path()); let _ = rl.load_history(&History::path());
let cc = context.ctrl_c.clone(); #[cfg(feature = "ctrlc")]
ctrlc::set_handler(move || { {
cc.store(true, Ordering::SeqCst); let cc = context.ctrl_c.clone();
})
.expect("Error setting Ctrl-C handler"); ctrlc::set_handler(move || {
cc.store(true, Ordering::SeqCst);
})
.expect("Error setting Ctrl-C handler");
}
let mut ctrlcbreak = false; let mut ctrlcbreak = false;
// before we start up, let's run our startup commands // before we start up, let's run our startup commands
@ -578,7 +606,30 @@ pub async fn cli(
rl.set_helper(Some(crate::shell::Helper::new(context.clone()))); rl.set_helper(Some(crate::shell::Helper::new(context.clone())));
let edit_mode = config::config(Tag::unknown())? let config = match config::config(Tag::unknown()) {
Ok(config) => config,
Err(e) => {
eprintln!("Config could not be loaded.");
if let ShellError {
error: ProximateShellError::Diagnostic(ShellDiagnostic { diagnostic }),
..
} = e
{
eprintln!("{}", diagnostic.message);
}
IndexMap::new()
}
};
let use_starship = match config.get("use_starship") {
Some(b) => match b.as_bool() {
Ok(b) => b,
_ => false,
},
_ => false,
};
let edit_mode = config
.get("edit_mode") .get("edit_mode")
.map(|s| match s.value.expect_string() { .map(|s| match s.value.expect_string() {
"vi" => EditMode::Vi, "vi" => EditMode::Vi,
@ -589,21 +640,23 @@ pub async fn cli(
rl.set_edit_mode(edit_mode); rl.set_edit_mode(edit_mode);
let max_history_size = config::config(Tag::unknown())? let max_history_size = config
.get("history_size") .get("history_size")
.map(|i| i.value.expect_int()) .map(|i| i.value.expect_int())
.unwrap_or(100_000); .unwrap_or(100_000);
rl.set_max_history_size(max_history_size as usize); // rl.set_max_history_size(max_history_size as usize);
rustyline::config::Configurer::set_max_history_size(&mut rl, max_history_size as usize);
rustyline::Editor::set_max_history_size(&mut rl, max_history_size as usize);
let key_timeout = config::config(Tag::unknown())? let key_timeout = config
.get("key_timeout") .get("key_timeout")
.map(|s| s.value.expect_int()) .map(|s| s.value.expect_int())
.unwrap_or(1); .unwrap_or(1);
rl.set_keyseq_timeout(key_timeout as i32); rl.set_keyseq_timeout(key_timeout as i32);
let completion_mode = config::config(Tag::unknown())? let completion_mode = config
.get("completion_mode") .get("completion_mode")
.map(|s| match s.value.expect_string() { .map(|s| match s.value.expect_string() {
"list" => CompletionType::List, "list" => CompletionType::List,
@ -615,26 +668,93 @@ pub async fn cli(
rl.set_completion_type(completion_mode); rl.set_completion_type(completion_mode);
let colored_prompt = { let colored_prompt = {
#[cfg(feature = "starship-prompt")] if use_starship {
{ #[cfg(feature = "starship")]
std::env::set_var("STARSHIP_SHELL", ""); {
std::env::set_var("PWD", &cwd); std::env::set_var("STARSHIP_SHELL", "");
let mut starship_context = std::env::set_var("PWD", &cwd);
starship::context::Context::new_with_dir(clap::ArgMatches::default(), cwd); let mut starship_context =
starship::context::Context::new_with_dir(clap::ArgMatches::default(), cwd);
match starship_context.config.config { match starship_context.config.config {
None => { None => {
starship_context.config.config = create_default_starship_config(); starship_context.config.config = create_default_starship_config();
}
Some(toml::Value::Table(t)) if t.is_empty() => {
starship_context.config.config = create_default_starship_config();
}
_ => {}
};
starship::print::get_prompt(starship_context)
}
#[cfg(not(feature = "starship"))]
{
format!(
"\x1b[32m{}{}\x1b[m> ",
cwd,
match current_branch() {
Some(s) => format!("({})", s),
None => "".to_string(),
}
)
}
} else if let Some(prompt) = config.get("prompt") {
let prompt_line = prompt.as_string()?;
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
Ok(result) => {
let mut prompt_block =
nu_parser::classify_block(&result, context.registry());
let env = context.get_env();
prompt_block.block.expand_it_usage();
match run_block(
&prompt_block.block,
&mut context,
InputStream::empty(),
&Value::nothing(),
&IndexMap::new(),
&env,
)
.await
{
Ok(result) => match result.collect_string(Tag::unknown()).await {
Ok(string_result) => {
let errors = context.get_errors();
context.maybe_print_errors(Text::from(prompt_line));
context.clear_errors();
if !errors.is_empty() {
"> ".to_string()
} else {
string_result.item
}
}
Err(e) => {
crate::cli::print_err(e, &Text::from(prompt_line));
context.clear_errors();
"> ".to_string()
}
},
Err(e) => {
crate::cli::print_err(e, &Text::from(prompt_line));
context.clear_errors();
"> ".to_string()
}
}
} }
Some(toml::Value::Table(t)) if t.is_empty() => { Err(e) => {
starship_context.config.config = create_default_starship_config(); crate::cli::print_err(e, &Text::from(prompt_line));
context.clear_errors();
"> ".to_string()
} }
_ => {} }
}; } else {
starship::print::get_prompt(starship_context)
}
#[cfg(not(feature = "starship-prompt"))]
{
format!( format!(
"\x1b[32m{}{}\x1b[m> ", "\x1b[32m{}{}\x1b[m> ",
cwd, cwd,
@ -673,13 +793,13 @@ pub async fn cli(
match line { match line {
LineResult::Success(line) => { LineResult::Success(line) => {
rl.add_history_entry(line.clone()); rl.add_history_entry(&line);
let _ = rl.save_history(&History::path()); let _ = rl.save_history(&History::path());
context.maybe_print_errors(Text::from(line)); context.maybe_print_errors(Text::from(line));
} }
LineResult::Error(line, err) => { LineResult::Error(line, err) => {
rl.add_history_entry(line.clone()); rl.add_history_entry(&line);
let _ = rl.save_history(&History::path()); let _ = rl.save_history(&History::path());
context.with_host(|_host| { context.with_host(|_host| {
@ -692,10 +812,7 @@ pub async fn cli(
LineResult::CtrlC => { LineResult::CtrlC => {
let config_ctrlc_exit = config::config(Tag::unknown())? let config_ctrlc_exit = config::config(Tag::unknown())?
.get("ctrlc_exit") .get("ctrlc_exit")
.map(|s| match s.value.expect_string() { .map(|s| s.value.expect_string() == "true")
"true" => true,
_ => false,
})
.unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells .unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells
if !config_ctrlc_exit { if !config_ctrlc_exit {
@ -733,15 +850,46 @@ fn chomp_newline(s: &str) -> &str {
} }
} }
enum LineResult { #[derive(Debug)]
pub enum LineResult {
Success(String), Success(String),
Error(String, ShellError), Error(String, ShellError),
CtrlC, CtrlC,
Break, Break,
} }
pub async fn parse_and_eval(line: &str, ctx: &mut Context) -> Result<String, ShellError> {
let line = if line.ends_with('\n') {
&line[..line.len() - 1]
} else {
line
};
let lite_result = nu_parser::lite_parse(&line, 0)?;
// TODO ensure the command whose examples we're testing is actually in the pipeline
let mut classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
classified_block.block.expand_it_usage();
let input_stream = InputStream::empty();
let env = ctx.get_env();
run_block(
&classified_block.block,
ctx,
input_stream,
&Value::nothing(),
&IndexMap::new(),
&env,
)
.await?
.collect_string(Tag::unknown())
.await
.map(|x| x.item)
}
/// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline /// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline
async fn process_line( pub async fn process_line(
readline: Result<String, ReadlineError>, readline: Result<String, ReadlineError>,
ctx: &mut Context, ctx: &mut Context,
redirect_stdin: bool, redirect_stdin: bool,
@ -816,7 +964,7 @@ async fn process_line(
.unwrap_or(true) .unwrap_or(true)
&& canonicalize(ctx.shell_manager.path(), name).is_ok() && canonicalize(ctx.shell_manager.path(), name).is_ok()
&& Path::new(&name).is_dir() && Path::new(&name).is_dir()
&& which::which(&name).is_err() && !crate::commands::classified::external::did_find_command(&name)
{ {
// Here we work differently if we're in Windows because of the expected Windows behavior // Here we work differently if we're in Windows because of the expected Windows behavior
#[cfg(windows)] #[cfg(windows)]
@ -862,7 +1010,7 @@ async fn process_line(
let input_stream = if redirect_stdin { let input_stream = if redirect_stdin {
let file = futures::io::AllowStdIo::new(std::io::stdin()); let file = futures::io::AllowStdIo::new(std::io::stdin());
let stream = FramedRead::new(file, MaybeTextCodec).map(|line| { let stream = FramedRead::new(file, MaybeTextCodec::default()).map(|line| {
if let Ok(line) = line { if let Ok(line) = line {
match line { match line {
StringOrBinary::String(s) => Ok(Value { StringOrBinary::String(s) => Ok(Value {

View File

@ -5,44 +5,47 @@ mod from_delimited_data;
mod to_delimited_data; mod to_delimited_data;
pub(crate) mod alias; pub(crate) mod alias;
pub(crate) mod ansi;
pub(crate) mod append; pub(crate) mod append;
pub(crate) mod args; 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 autoview;
pub(crate) mod average; pub(crate) mod benchmark;
pub(crate) mod build_string; pub(crate) mod build_string;
pub(crate) mod cal; pub(crate) mod cal;
pub(crate) mod calc;
pub(crate) mod cd; pub(crate) mod cd;
pub(crate) mod char_;
pub(crate) mod classified; pub(crate) mod classified;
#[cfg(feature = "clipboard")] #[cfg(feature = "clipboard")]
pub(crate) mod clip; pub(crate) mod clip;
pub(crate) mod command; pub(crate) mod command;
pub(crate) mod compact; pub(crate) mod compact;
pub(crate) mod config; pub(crate) mod config;
pub(crate) mod constants;
pub(crate) mod count; pub(crate) mod count;
pub(crate) mod cp; pub(crate) mod cp;
pub(crate) mod date; pub(crate) mod date;
pub(crate) mod debug; pub(crate) mod debug;
pub(crate) mod default; pub(crate) mod default;
pub(crate) mod do_;
pub(crate) mod drop; pub(crate) mod drop;
pub(crate) mod du; pub(crate) mod du;
pub(crate) mod each; pub(crate) mod each;
pub(crate) mod echo; pub(crate) mod echo;
pub(crate) mod enter; pub(crate) mod enter;
#[allow(unused)] pub(crate) mod every;
pub(crate) mod evaluate_by;
pub(crate) mod exit; pub(crate) mod exit;
pub(crate) mod first; pub(crate) mod first;
pub(crate) mod format; pub(crate) mod format;
pub(crate) mod from; pub(crate) mod from;
pub(crate) mod from_bson;
pub(crate) mod from_csv; pub(crate) mod from_csv;
pub(crate) mod from_eml; pub(crate) mod from_eml;
pub(crate) mod from_ics; pub(crate) mod from_ics;
pub(crate) mod from_ini; pub(crate) mod from_ini;
pub(crate) mod from_json; pub(crate) mod from_json;
pub(crate) mod from_ods; pub(crate) mod from_ods;
pub(crate) mod from_sqlite;
pub(crate) mod from_ssv; pub(crate) mod from_ssv;
pub(crate) mod from_toml; pub(crate) mod from_toml;
pub(crate) mod from_tsv; pub(crate) mod from_tsv;
@ -58,19 +61,17 @@ pub(crate) mod headers;
pub(crate) mod help; pub(crate) mod help;
pub(crate) mod histogram; pub(crate) mod histogram;
pub(crate) mod history; pub(crate) mod history;
pub(crate) mod if_;
pub(crate) mod insert; pub(crate) mod insert;
pub(crate) mod is_empty; pub(crate) mod is_empty;
pub(crate) mod keep; pub(crate) mod keep;
pub(crate) mod keep_until;
pub(crate) mod keep_while;
pub(crate) mod last; pub(crate) mod last;
pub(crate) mod lines; pub(crate) mod lines;
pub(crate) mod ls; pub(crate) mod ls;
#[allow(unused)] pub(crate) mod math;
pub(crate) mod map_max_by;
pub(crate) mod merge; pub(crate) mod merge;
pub(crate) mod mkdir; pub(crate) mod mkdir;
pub(crate) mod mv; pub(crate) mod move_;
pub(crate) mod next; pub(crate) mod next;
pub(crate) mod nth; pub(crate) mod nth;
pub(crate) mod open; pub(crate) mod open;
@ -80,9 +81,8 @@ pub(crate) mod plugin;
pub(crate) mod prepend; pub(crate) mod prepend;
pub(crate) mod prev; pub(crate) mod prev;
pub(crate) mod pwd; pub(crate) mod pwd;
pub(crate) mod random;
pub(crate) mod range; pub(crate) mod range;
#[allow(unused)]
pub(crate) mod reduce_by;
pub(crate) mod reject; pub(crate) mod reject;
pub(crate) mod rename; pub(crate) mod rename;
pub(crate) mod reverse; pub(crate) mod reverse;
@ -95,24 +95,17 @@ pub(crate) mod shells;
pub(crate) mod shuffle; pub(crate) mod shuffle;
pub(crate) mod size; pub(crate) mod size;
pub(crate) mod skip; pub(crate) mod skip;
pub(crate) mod skip_until;
pub(crate) mod skip_while;
pub(crate) mod sort_by; pub(crate) mod sort_by;
pub(crate) mod split; pub(crate) mod split;
pub(crate) mod split_by; pub(crate) mod split_by;
pub(crate) mod str_; pub(crate) mod str_;
pub(crate) mod sum;
#[allow(unused)]
pub(crate) mod t_sort_by;
pub(crate) mod table; pub(crate) mod table;
pub(crate) mod tags; pub(crate) mod tags;
pub(crate) mod to; pub(crate) mod to;
pub(crate) mod to_bson;
pub(crate) mod to_csv; pub(crate) mod to_csv;
pub(crate) mod to_html; pub(crate) mod to_html;
pub(crate) mod to_json; pub(crate) mod to_json;
pub(crate) mod to_md; pub(crate) mod to_md;
pub(crate) mod to_sqlite;
pub(crate) mod to_toml; pub(crate) mod to_toml;
pub(crate) mod to_tsv; pub(crate) mod to_tsv;
pub(crate) mod to_url; pub(crate) mod to_url;
@ -134,22 +127,30 @@ pub(crate) use command::{
}; };
pub(crate) use alias::Alias; pub(crate) use alias::Alias;
pub(crate) use ansi::Ansi;
pub(crate) use append::Append; pub(crate) use append::Append;
pub(crate) use average::Average; 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 build_string::BuildString;
pub(crate) use cal::Cal; pub(crate) use cal::Cal;
pub(crate) use calc::Calc; pub(crate) use char_::Char;
pub(crate) use compact::Compact; 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 count::Count;
pub(crate) use cp::Cpy; pub(crate) use cp::Cpy;
pub(crate) use date::Date; pub(crate) use date::Date;
pub(crate) use debug::Debug; pub(crate) use debug::Debug;
pub(crate) use default::Default; pub(crate) use default::Default;
pub(crate) use do_::Do;
pub(crate) use drop::Drop; pub(crate) use drop::Drop;
pub(crate) use du::Du; pub(crate) use du::Du;
pub(crate) use each::Each; pub(crate) use each::Each;
pub(crate) use echo::Echo; pub(crate) use echo::Echo;
pub(crate) use if_::If;
pub(crate) use is_empty::IsEmpty; pub(crate) use is_empty::IsEmpty;
pub(crate) use update::Update; pub(crate) use update::Update;
pub(crate) mod kill; pub(crate) mod kill;
@ -158,21 +159,17 @@ pub(crate) mod clear;
pub(crate) use clear::Clear; pub(crate) use clear::Clear;
pub(crate) mod touch; pub(crate) mod touch;
pub(crate) use enter::Enter; pub(crate) use enter::Enter;
#[allow(unused_imports)] pub(crate) use every::Every;
pub(crate) use evaluate_by::EvaluateBy;
pub(crate) use exit::Exit; pub(crate) use exit::Exit;
pub(crate) use first::First; pub(crate) use first::First;
pub(crate) use format::Format; pub(crate) use format::Format;
pub(crate) use from::From; pub(crate) use from::From;
pub(crate) use from_bson::FromBSON;
pub(crate) use from_csv::FromCSV; pub(crate) use from_csv::FromCSV;
pub(crate) use from_eml::FromEML; pub(crate) use from_eml::FromEML;
pub(crate) use from_ics::FromIcs; pub(crate) use from_ics::FromIcs;
pub(crate) use from_ini::FromINI; pub(crate) use from_ini::FromINI;
pub(crate) use from_json::FromJSON; pub(crate) use from_json::FromJSON;
pub(crate) use from_ods::FromODS; 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_ssv::FromSSV;
pub(crate) use from_toml::FromTOML; pub(crate) use from_toml::FromTOML;
pub(crate) use from_tsv::FromTSV; pub(crate) use from_tsv::FromTSV;
@ -190,17 +187,17 @@ pub(crate) use help::Help;
pub(crate) use histogram::Histogram; pub(crate) use histogram::Histogram;
pub(crate) use history::History; pub(crate) use history::History;
pub(crate) use insert::Insert; pub(crate) use insert::Insert;
pub(crate) use keep::Keep; pub(crate) use keep::{Keep, KeepUntil, KeepWhile};
pub(crate) use keep_until::KeepUntil;
pub(crate) use keep_while::KeepWhile;
pub(crate) use last::Last; pub(crate) use last::Last;
pub(crate) use lines::Lines; pub(crate) use lines::Lines;
pub(crate) use ls::Ls; pub(crate) use ls::Ls;
#[allow(unused_imports)] pub(crate) use math::{
pub(crate) use map_max_by::MapMaxBy; Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathStddev,
MathSummation, MathVariance,
};
pub(crate) use merge::Merge; pub(crate) use merge::Merge;
pub(crate) use mkdir::Mkdir; pub(crate) use mkdir::Mkdir;
pub(crate) use mv::Move; pub(crate) use move_::{Move, MoveColumn, Mv};
pub(crate) use next::Next; pub(crate) use next::Next;
pub(crate) use nth::Nth; pub(crate) use nth::Nth;
pub(crate) use open::Open; pub(crate) use open::Open;
@ -209,9 +206,10 @@ pub(crate) use pivot::Pivot;
pub(crate) use prepend::Prepend; pub(crate) use prepend::Prepend;
pub(crate) use prev::Previous; pub(crate) use prev::Previous;
pub(crate) use pwd::Pwd; pub(crate) use pwd::Pwd;
#[cfg(feature = "uuid_crate")]
pub(crate) use random::RandomUUID;
pub(crate) use random::{Random, RandomBool, RandomDice};
pub(crate) use range::Range; pub(crate) use range::Range;
#[allow(unused_imports)]
pub(crate) use reduce_by::ReduceBy;
pub(crate) use reject::Reject; pub(crate) use reject::Reject;
pub(crate) use rename::Rename; pub(crate) use rename::Rename;
pub(crate) use reverse::Reverse; pub(crate) use reverse::Reverse;
@ -222,31 +220,21 @@ pub(crate) use select::Select;
pub(crate) use shells::Shells; pub(crate) use shells::Shells;
pub(crate) use shuffle::Shuffle; pub(crate) use shuffle::Shuffle;
pub(crate) use size::Size; pub(crate) use size::Size;
pub(crate) use skip::Skip; pub(crate) use skip::{Skip, SkipUntil, SkipWhile};
pub(crate) use skip_until::SkipUntil;
pub(crate) use skip_while::SkipWhile;
pub(crate) use sort_by::SortBy; pub(crate) use sort_by::SortBy;
pub(crate) use split::Split; pub(crate) use split::{Split, SplitChars, SplitColumn, SplitRow};
pub(crate) use split::SplitColumn;
pub(crate) use split::SplitRow;
pub(crate) use split_by::SplitBy; pub(crate) use split_by::SplitBy;
pub(crate) use str_::{ pub(crate) use str_::{
Str, StrCapitalize, StrDowncase, StrFindReplace, StrSet, StrSubstring, StrToDatetime, Str, StrCapitalize, StrCollect, StrDowncase, StrFindReplace, StrFrom, StrLength, StrReverse,
StrToDecimal, StrToInteger, StrTrim, StrUpcase, StrSet, StrSubstring, StrToDatetime, StrToDecimal, StrToInteger, StrTrim, StrUpcase,
}; };
pub(crate) use sum::Sum;
#[allow(unused_imports)]
pub(crate) use t_sort_by::TSortBy;
pub(crate) use table::Table; pub(crate) use table::Table;
pub(crate) use tags::Tags; pub(crate) use tags::Tags;
pub(crate) use to::To; pub(crate) use to::To;
pub(crate) use to_bson::ToBSON;
pub(crate) use to_csv::ToCSV; pub(crate) use to_csv::ToCSV;
pub(crate) use to_html::ToHTML; pub(crate) use to_html::ToHTML;
pub(crate) use to_json::ToJSON; pub(crate) use to_json::ToJSON;
pub(crate) use to_md::ToMarkdown; 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_toml::ToTOML;
pub(crate) use to_tsv::ToTSV; pub(crate) use to_tsv::ToTSV;
pub(crate) use to_url::ToURL; pub(crate) use to_url::ToURL;

View File

@ -45,8 +45,7 @@ impl WholeStreamCommand for Alias {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
// args.process(registry, alias)?.run() alias(args, registry).await
alias(args, registry)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -65,63 +64,78 @@ impl WholeStreamCommand for Alias {
} }
} }
// <<<<<<< HEAD pub async fn alias(
// pub fn alias(alias_args: AliasArgs, ctx: RunnableContext) -> Result<OutputStream, ShellError> { args: CommandArgs,
// ======= registry: &CommandRegistry,
pub fn alias(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let mut raw_input = args.raw_input.clone();
let mut raw_input = args.raw_input.clone(); let (
let (AliasArgs { name, args: list, block, save}, ctx) = args.process(&registry).await?; AliasArgs {
let mut processed_args: Vec<String> = vec![]; name,
args: list,
block,
save,
},
_ctx,
) = args.process(&registry).await?;
let mut processed_args: Vec<String> = vec![];
if let Some(true) = save { if let Some(true) = save {
let mut result = crate::data::config::read(name.clone().tag, &None)?; let mut result = crate::data::config::read(name.clone().tag, &None)?;
// process the alias to remove the --save flag // process the alias to remove the --save flag
let left_brace = raw_input.find('{').unwrap_or(0); let left_brace = raw_input.find('{').unwrap_or(0);
let right_brace = raw_input.rfind('}').unwrap_or(raw_input.len()); let right_brace = raw_input.rfind('}').unwrap_or_else(|| raw_input.len());
let mut left = raw_input[..left_brace].replace("--save", "").replace("-s", ""); let left = raw_input[..left_brace]
let mut right = raw_input[right_brace..].replace("--save", "").replace("-s", ""); .replace("--save", "")
raw_input = format!("{}{}{}", left, &raw_input[left_brace..right_brace], right); .replace("-s", "");
let right = raw_input[right_brace..]
.replace("--save", "")
.replace("-s", "");
raw_input = format!("{}{}{}", left, &raw_input[left_brace..right_brace], right);
// create a value from raw_input alias // create a value from raw_input alias
let alias: Value = raw_input.trim().to_string().into(); let alias: Value = raw_input.trim().to_string().into();
let alias_start = raw_input.find("[").unwrap_or(0); // used to check if the same alias already exists let alias_start = raw_input.find('[').unwrap_or(0); // used to check if the same alias already exists
// add to startup if alias doesn't exist and replce if it does // add to startup if alias doesn't exist and replce if it does
match result.get_mut("startup") { match result.get_mut("startup") {
Some(startup) => { Some(startup) => {
if let UntaggedValue::Table(ref mut commands) = startup.value { if let UntaggedValue::Table(ref mut commands) = startup.value {
if let Some(command) = commands.iter_mut().find(|command| { if let Some(command) = commands.iter_mut().find(|command| {
let cmd_str = command.as_string().unwrap_or_default(); let cmd_str = command.as_string().unwrap_or_default();
cmd_str.starts_with(&raw_input[..alias_start]) cmd_str.starts_with(&raw_input[..alias_start])
}) { }) {
*command = alias; *command = alias;
} else { } else {
commands.push(alias); commands.push(alias);
}
} }
} }
None => {
let mut table = UntaggedValue::table(&[alias]);
result.insert("startup".to_string(), table.into_value(Tag::default()));
}
} }
config::write(&result, &None)?; None => {
} let table = UntaggedValue::table(&[alias]);
result.insert("startup".to_string(), table.into_value(Tag::default()));
for item in list.iter() {
if let Ok(string) = item.as_string() {
processed_args.push(format!("${}", string));
} else {
yield Err(ShellError::labeled_error("Expected a string", "expected a string", item.tag()));
} }
} }
yield ReturnSuccess::action(CommandAction::AddAlias(name.to_string(), processed_args, block.clone())) config::write(&result, &None)?;
}; }
Ok(stream.to_output_stream()) for item in list.iter() {
if let Ok(string) = item.as_string() {
processed_args.push(format!("${}", string));
} else {
return Err(ShellError::labeled_error(
"Expected a string",
"expected a string",
item.tag(),
));
}
}
Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::AddAlias(name.to_string(), processed_args, block),
)))
} }
#[cfg(test)] #[cfg(test)]

View File

@ -0,0 +1,148 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
pub struct Ansi;
#[derive(Deserialize)]
struct AnsiArgs {
color: Value,
}
#[async_trait]
impl WholeStreamCommand for Ansi {
fn name(&self) -> &str {
"ansi"
}
fn signature(&self) -> Signature {
Signature::build("ansi").required(
"color",
SyntaxShape::Any,
"the name of the color to use or 'reset' to reset the color",
)
}
fn usage(&self) -> &str {
"Output ANSI codes to change color"
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Change color to green",
example: r#"ansi green"#,
result: Some(vec![Value::from("\u{1b}[32m")]),
},
Example {
description: "Reset the color",
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",
)]),
},
]
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let (AnsiArgs { color }, _) = args.process(&registry).await?;
let color_string = color.as_string()?;
let ansi_code = str_to_ansi_color(color_string);
if let Some(output) = ansi_code {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(color.tag()),
)))
} else {
Err(ShellError::labeled_error(
"Unknown color",
"unknown color",
color.tag(),
))
}
}
}
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()),
"reset" => Some("\x1b[0m".to_owned()),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::Ansi;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Ansi {})
}
}

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. Note that exitscripts are not run in the directory they are declared in."#
}
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

@ -6,11 +6,7 @@ use nu_errors::ShellError;
use nu_protocol::{hir, hir::Expression, hir::Literal, hir::SpannedExpression}; use nu_protocol::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
use nu_protocol::{Primitive, Scope, Signature, UntaggedValue, Value}; use nu_protocol::{Primitive, Scope, Signature, UntaggedValue, Value};
use parking_lot::Mutex; use parking_lot::Mutex;
use prettytable::format::{FormatBuilder, LinePosition, LineSeparator};
use prettytable::{color, Attr, Cell, Row, Table};
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use textwrap::fill;
pub struct Autoview; pub struct Autoview;
@ -110,32 +106,22 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
}; };
let (mut input_stream, context) = RunnableContextWithoutInput::convert(context); let (mut input_stream, context) = RunnableContextWithoutInput::convert(context);
let term_width = context.host.lock().width();
if let Some(x) = input_stream.next().await { if let Some(x) = input_stream.next().await {
match input_stream.next().await { match input_stream.next().await {
Some(y) => { Some(y) => {
let ctrl_c = context.ctrl_c.clone(); let ctrl_c = context.ctrl_c.clone();
let stream = async_stream! { let xy = vec![x, y];
yield Ok(x); let xy_stream = futures::stream::iter(xy)
yield Ok(y); .chain(input_stream)
.interruptible(ctrl_c);
loop { let stream = InputStream::from_stream(xy_stream);
match input_stream.next().await {
Some(z) => {
if ctrl_c.load(Ordering::SeqCst) {
break;
}
yield Ok(z);
}
_ => break,
}
}
};
let stream = stream.to_input_stream();
if let Some(table) = table { if let Some(table) = table {
let command_args = create_default_command_args(&context).with_input(stream); let command_args = create_default_command_args(&context).with_input(stream);
let result = table.run(command_args, &context.registry).await; let result = table.run(command_args, &context.registry).await?;
result.collect::<Vec<_>>().await; result.collect::<Vec<_>>().await;
} }
} }
@ -152,7 +138,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
); );
let command_args = let command_args =
create_default_command_args(&context).with_input(stream); create_default_command_args(&context).with_input(stream);
let result = text.run(command_args, &context.registry).await; let result = text.run(command_args, &context.registry).await?;
result.collect::<Vec<_>>().await; result.collect::<Vec<_>>().await;
} else { } else {
out!("{}", s); out!("{}", s);
@ -175,7 +161,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
); );
let command_args = let command_args =
create_default_command_args(&context).with_input(stream); create_default_command_args(&context).with_input(stream);
let result = text.run(command_args, &context.registry).await; let result = text.run(command_args, &context.registry).await?;
result.collect::<Vec<_>>().await; result.collect::<Vec<_>>().await;
} else { } else {
out!("{}\n", s); out!("{}\n", s);
@ -250,7 +236,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
stream.push_back(x); stream.push_back(x);
let command_args = let command_args =
create_default_command_args(&context).with_input(stream); create_default_command_args(&context).with_input(stream);
let result = binary.run(command_args, &context.registry).await; let result = binary.run(command_args, &context.registry).await?;
result.collect::<Vec<_>>().await; result.collect::<Vec<_>>().await;
} else { } else {
use pretty_hex::*; use pretty_hex::*;
@ -278,92 +264,30 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
.iter() .iter()
.fold(0usize, |acc, len| acc + len.len()) .fold(0usize, |acc, len| acc + len.len())
+ row.entries.iter().count() * 2) + row.entries.iter().count() * 2)
> textwrap::termwidth()) => > term_width) =>
{ {
let termwidth = std::cmp::max(textwrap::termwidth(), 20); let mut entries = vec![];
enum TableMode {
Light,
Normal,
}
let mut table = Table::new();
let table_mode = crate::data::config::config(Tag::unknown());
let table_mode = if let Some(s) = table_mode?.get("table_mode") {
match s.as_string() {
Ok(typ) if typ == "light" => TableMode::Light,
_ => TableMode::Normal,
}
} else {
TableMode::Normal
};
match table_mode {
TableMode::Light => {
table.set_format(
FormatBuilder::new()
.separator(
LinePosition::Title,
LineSeparator::new('─', '─', ' ', ' '),
)
.separator(
LinePosition::Bottom,
LineSeparator::new(' ', ' ', ' ', ' '),
)
.padding(1, 1)
.build(),
);
}
_ => {
table.set_format(
FormatBuilder::new()
.column_separator('│')
.separator(
LinePosition::Top,
LineSeparator::new('─', '┬', ' ', ' '),
)
.separator(
LinePosition::Title,
LineSeparator::new('─', '┼', ' ', ' '),
)
.separator(
LinePosition::Bottom,
LineSeparator::new('─', '┴', ' ', ' '),
)
.padding(1, 1)
.build(),
);
}
}
let mut max_key_len = 0;
for (key, _) in row.entries.iter() {
max_key_len = std::cmp::max(max_key_len, key.chars().count());
}
if max_key_len > (termwidth / 2 - 1) {
max_key_len = termwidth / 2 - 1;
}
let max_val_len = termwidth - max_key_len - 5;
for (key, value) in row.entries.iter() { for (key, value) in row.entries.iter() {
table.add_row(Row::new(vec![ entries.push(vec![
Cell::new(&fill(&key, max_key_len)) nu_table::StyledString::new(
.with_style(Attr::ForegroundColor(color::GREEN)) key.to_string(),
.with_style(Attr::Bold), nu_table::TextStyle {
Cell::new(&fill( alignment: nu_table::Alignment::Left,
&format_leaf(value).plain_string(100_000), color: Some(ansi_term::Color::Green),
max_val_len, is_bold: true,
)), },
])); ),
nu_table::StyledString::new(
format_leaf(value).plain_string(100_000),
nu_table::TextStyle::basic(),
),
]);
} }
table.printstd(); let table =
nu_table::Table::new(vec![], entries, nu_table::Theme::compact());
// table.print_term(&mut *context.host.lock().out_terminal().ok_or_else(|| ShellError::untagged_runtime_error("Could not open terminal for output"))?) nu_table::draw_table(&table, term_width);
// .map_err(|_| ShellError::untagged_runtime_error("Internal error: could not print to terminal (for unix systems check to make sure TERM is set)"))?;
} }
Value { Value {
@ -374,7 +298,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
stream.push_back(x); stream.push_back(x);
let command_args = let command_args =
create_default_command_args(&context).with_input(stream); create_default_command_args(&context).with_input(stream);
let result = table.run(command_args, &context.registry).await; let result = table.run(command_args, &context.registry).await?;
result.collect::<Vec<_>>().await; result.collect::<Vec<_>>().await;
} else { } else {
out!("{:?}", item); out!("{:?}", item);

View File

@ -1,173 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use bigdecimal::FromPrimitive;
use nu_errors::ShellError;
use nu_protocol::hir::{convert_number_to_u64, Number, Operator};
use nu_protocol::{
Dictionary, Primitive, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value,
};
use num_traits::identities::Zero;
use indexmap::map::IndexMap;
pub struct Average;
#[async_trait]
impl WholeStreamCommand for Average {
fn name(&self) -> &str {
"average"
}
fn signature(&self) -> Signature {
Signature::build("average")
}
fn usage(&self) -> &str {
"Average the values."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
average(RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
})
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Average a list of numbers",
example: "echo [100 0 100 0] | average",
result: Some(vec![UntaggedValue::decimal(50).into()]),
}]
}
}
fn average(
RunnableContext {
mut input, name, ..
}: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let mut values: Vec<Value> = input.drain_vec().await;
let action = reducer_for(Reduce::Sum);
if values.iter().all(|v| if let UntaggedValue::Primitive(_) = v.value {true} else {false}) {
match avg(&values, name) {
Ok(result) => yield ReturnSuccess::value(result),
Err(err) => yield Err(err),
}
} else {
let mut column_values = IndexMap::new();
for value in values {
match value.value {
UntaggedValue::Row(row_dict) => {
for (key, value) in row_dict.entries.iter() {
column_values
.entry(key.clone())
.and_modify(|v: &mut Vec<Value>| v.push(value.clone()))
.or_insert(vec![value.clone()]);
}
},
table => {},
};
}
let mut column_totals = IndexMap::new();
for (col_name, col_vals) in column_values {
match avg(&col_vals, &name) {
Ok(result) => {
column_totals.insert(col_name, result);
}
Err(err) => yield Err(err),
}
}
yield ReturnSuccess::value(
UntaggedValue::Row(Dictionary {entries: column_totals}).into_untagged_value())
}
};
let stream: BoxStream<'static, ReturnValue> = stream.boxed();
Ok(stream.to_output_stream())
}
fn avg(values: &[Value], name: impl Into<Tag>) -> Result<Value, ShellError> {
let name = name.into();
let sum = reducer_for(Reduce::Sum);
let number = BigDecimal::from_usize(values.len()).expect("expected a usize-sized bigdecimal");
let total_rows = UntaggedValue::decimal(number);
let total = sum(Value::zero(), values.to_vec())?;
match total {
Value {
value: UntaggedValue::Primitive(Primitive::Bytes(num)),
..
} => {
let left = UntaggedValue::from(Primitive::Int(num.into()));
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => {
let number = Number::Decimal(result);
let number = convert_number_to_u64(&number);
Ok(UntaggedValue::bytes(number).into_value(name))
}
Ok(_) => Err(ShellError::labeled_error(
"could not calculate average of non-integer or unrelated types",
"source",
name,
)),
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
)),
}
}
Value {
value: UntaggedValue::Primitive(other),
..
} => {
let left = UntaggedValue::from(other);
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(value) => Ok(value.into_value(name)),
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
)),
}
}
_ => Err(ShellError::labeled_error(
"could not calculate average of non-integer or unrelated types",
"source",
name,
)),
}
}
#[cfg(test)]
mod tests {
use super::Average;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Average {})
}
}

View File

@ -0,0 +1,80 @@
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
hir::Block, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use chrono::prelude::*;
pub struct Benchmark;
#[derive(Deserialize, Debug)]
struct BenchmarkArgs {
block: 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",
)
}
fn usage(&self) -> &str {
"Runs a block and return the time it took to do execute it. Eg) benchmark { echo $nu.env.NAME }"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
benchmark(args, registry).await
}
}
async fn benchmark(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let mut context = Context::from_raw(&raw_args, &registry);
let scope = raw_args.call_info.scope.clone();
let (BenchmarkArgs { block }, input) = raw_args.process(&registry).await?;
let start_time: chrono::DateTime<_> = Utc::now();
let result = run_block(
&block,
&mut context,
input,
&scope.it,
&scope.vars,
&scope.env,
)
.await;
let output = match result {
Ok(mut stream) => {
let _ = stream.drain_vec().await;
let run_duration: chrono::Duration = Utc::now().signed_duration_since(start_time);
context.clear_errors();
Ok(ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(Primitive::from(run_duration)),
tag: Tag::from(block.span),
}))
}
Err(e) => Err(e),
};
Ok(OutputStream::from(vec![output]))
}

View File

@ -24,6 +24,12 @@ impl WholeStreamCommand for Cal {
"Display a year-long calendar for the specified year", "Display a year-long calendar for the specified year",
None, None,
) )
.named(
"week-start",
SyntaxShape::String,
"Display the calendar with the specified day as the first day of the week",
None,
)
.switch( .switch(
"month-names", "month-names",
"Display the month names instead of integers", "Display the month names instead of integers",
@ -55,6 +61,11 @@ impl WholeStreamCommand for Cal {
example: "cal --full-year 2012", example: "cal --full-year 2012",
result: None, result: None,
}, },
Example {
description: "This month's calendar with the week starting on monday",
example: "cal --week-start monday",
result: None,
},
] ]
} }
} }
@ -73,17 +84,15 @@ pub async fn cal(
let mut selected_year: i32 = current_year; let mut selected_year: i32 = current_year;
let mut current_day_option: Option<u32> = Some(current_day); let mut current_day_option: Option<u32> = Some(current_day);
let month_range = if args.has("full-year") { let month_range = if let Some(full_year_value) = args.get("full-year") {
if let Some(full_year_value) = args.get("full-year") { if let Ok(year_u64) = full_year_value.as_u64() {
if let Ok(year_u64) = full_year_value.as_u64() { selected_year = year_u64 as i32;
selected_year = year_u64 as i32;
if selected_year != current_year { if selected_year != current_year {
current_day_option = None current_day_option = None
}
} else {
return Err(get_invalid_year_shell_error(&full_year_value.tag()));
} }
} else {
return Err(get_invalid_year_shell_error(&full_year_value.tag()));
} }
(1, 12) (1, 12)
@ -112,90 +121,48 @@ fn get_invalid_year_shell_error(year_tag: &Tag) -> ShellError {
} }
struct MonthHelper { struct MonthHelper {
day_number_month_starts_on: u32,
number_of_days_in_month: u32,
selected_year: i32, selected_year: i32,
selected_month: u32, selected_month: u32,
day_number_of_week_month_starts_on: u32,
number_of_days_in_month: u32,
quarter_number: u32,
month_name: String,
} }
impl MonthHelper { impl MonthHelper {
pub fn new(selected_year: i32, selected_month: u32) -> Result<MonthHelper, ()> { pub fn new(selected_year: i32, selected_month: u32) -> Result<MonthHelper, ()> {
let mut month_helper = MonthHelper { let naive_date = NaiveDate::from_ymd_opt(selected_year, selected_month, 1).ok_or(())?;
day_number_month_starts_on: 0, let number_of_days_in_month =
number_of_days_in_month: 0, MonthHelper::calculate_number_of_days_in_month(selected_year, selected_month)?;
Ok(MonthHelper {
selected_year, selected_year,
selected_month, selected_month,
}; day_number_of_week_month_starts_on: naive_date.weekday().num_days_from_sunday(),
number_of_days_in_month,
let chosen_date_result_one = month_helper.update_day_number_month_starts_on(); quarter_number: ((selected_month - 1) / 3) + 1,
let chosen_date_result_two = month_helper.update_number_of_days_in_month(); month_name: naive_date.format("%B").to_string().to_ascii_lowercase(),
})
if chosen_date_result_one.is_ok() && chosen_date_result_two.is_ok() {
return Ok(month_helper);
}
Err(())
} }
pub fn get_month_name(&self) -> String { fn calculate_number_of_days_in_month(
let month_name = match self.selected_month { mut selected_year: i32,
1 => "january", mut selected_month: u32,
2 => "february", ) -> Result<u32, ()> {
3 => "march",
4 => "april",
5 => "may",
6 => "june",
7 => "july",
8 => "august",
9 => "september",
10 => "october",
11 => "november",
_ => "december",
};
month_name.to_string()
}
fn update_day_number_month_starts_on(&mut self) -> Result<(), ()> {
let naive_date_result =
MonthHelper::get_naive_date(self.selected_year, self.selected_month);
match naive_date_result {
Ok(naive_date) => {
self.day_number_month_starts_on = naive_date.weekday().num_days_from_sunday();
Ok(())
}
_ => Err(()),
}
}
fn update_number_of_days_in_month(&mut self) -> Result<(), ()> {
// Chrono does not provide a method to output the amount of days in a month // Chrono does not provide a method to output the amount of days in a month
// This is a workaround taken from the example code from the Chrono docs here: // This is a workaround taken from the example code from the Chrono docs here:
// https://docs.rs/chrono/0.3.0/chrono/naive/date/struct.NaiveDate.html#example-30 // https://docs.rs/chrono/0.3.0/chrono/naive/date/struct.NaiveDate.html#example-30
let (adjusted_year, adjusted_month) = if self.selected_month == 12 { if selected_month == 12 {
(self.selected_year + 1, 1) selected_year += 1;
selected_month = 1;
} else { } else {
(self.selected_year, self.selected_month + 1) selected_month += 1;
}; };
let naive_date_result = MonthHelper::get_naive_date(adjusted_year, adjusted_month); let next_month_naive_date =
NaiveDate::from_ymd_opt(selected_year, selected_month, 1).ok_or(())?;
match naive_date_result { Ok(next_month_naive_date.pred().day())
Ok(naive_date) => {
self.number_of_days_in_month = naive_date.pred().day();
Ok(())
}
_ => Err(()),
}
}
fn get_naive_date(selected_year: i32, selected_month: u32) -> Result<NaiveDate, ()> {
if let Some(naive_date) = NaiveDate::from_ymd_opt(selected_year, selected_month, 1) {
return Ok(naive_date);
}
Err(())
} }
} }
@ -268,10 +235,7 @@ fn add_month_to_table(
}, },
}; };
let day_limit = month_helper.number_of_days_in_month + month_helper.day_number_month_starts_on; let mut days_of_the_week = [
let mut day_count: u32 = 1;
let days_of_the_week = [
"sunday", "sunday",
"monday", "monday",
"tuesday", "tuesday",
@ -281,12 +245,43 @@ fn add_month_to_table(
"saturday", "saturday",
]; ];
let mut week_start_day = days_of_the_week[0].to_string();
if let Some(week_start_value) = args.get("week-start") {
if let Ok(day) = week_start_value.as_string() {
if days_of_the_week.contains(&day.as_str()) {
week_start_day = day;
} else {
return Err(ShellError::labeled_error(
"The specified week start day is invalid",
"invalid week start day",
week_start_value.tag(),
));
}
}
}
let week_start_day_offset = days_of_the_week.len()
- days_of_the_week
.iter()
.position(|day| *day == week_start_day)
.unwrap_or(0);
days_of_the_week.rotate_right(week_start_day_offset);
let mut total_start_offset: u32 =
month_helper.day_number_of_week_month_starts_on + week_start_day_offset as u32;
total_start_offset %= days_of_the_week.len() as u32;
let mut day_number: u32 = 1;
let day_limit: u32 = total_start_offset + month_helper.number_of_days_in_month;
let should_show_year_column = args.has("year"); let should_show_year_column = args.has("year");
let should_show_month_column = args.has("month");
let should_show_quarter_column = args.has("quarter"); let should_show_quarter_column = args.has("quarter");
let should_show_month_column = args.has("month");
let should_show_month_names = args.has("month-names"); let should_show_month_names = args.has("month-names");
while day_count <= day_limit { while day_number <= day_limit {
let mut indexmap = IndexMap::new(); let mut indexmap = IndexMap::new();
if should_show_year_column { if should_show_year_column {
@ -299,13 +294,13 @@ fn add_month_to_table(
if should_show_quarter_column { if should_show_quarter_column {
indexmap.insert( indexmap.insert(
"quarter".to_string(), "quarter".to_string(),
UntaggedValue::int(((month_helper.selected_month - 1) / 3) + 1).into_value(tag), UntaggedValue::int(month_helper.quarter_number).into_value(tag),
); );
} }
if should_show_month_column { if should_show_month_column || should_show_month_names {
let month_value = if should_show_month_names { let month_value = if should_show_month_names {
UntaggedValue::string(month_helper.get_month_name()).into_value(tag) UntaggedValue::string(month_helper.month_name.clone()).into_value(tag)
} else { } else {
UntaggedValue::int(month_helper.selected_month).into_value(tag) UntaggedValue::int(month_helper.selected_month).into_value(tag)
}; };
@ -315,17 +310,17 @@ fn add_month_to_table(
for day in &days_of_the_week { for day in &days_of_the_week {
let should_add_day_number_to_table = let should_add_day_number_to_table =
(day_count <= day_limit) && (day_count > month_helper.day_number_month_starts_on); (day_number > total_start_offset) && (day_number <= day_limit);
let mut value = UntaggedValue::nothing().into_value(tag); let mut value = UntaggedValue::nothing().into_value(tag);
if should_add_day_number_to_table { if should_add_day_number_to_table {
let day_count_with_offset = day_count - month_helper.day_number_month_starts_on; let adjusted_day_number = day_number - total_start_offset;
value = UntaggedValue::int(day_count_with_offset).into_value(tag); value = UntaggedValue::int(adjusted_day_number).into_value(tag);
if let Some(current_day) = current_day_option { if let Some(current_day) = current_day_option {
if current_day == day_count_with_offset { if current_day == adjusted_day_number {
// TODO: Update the value here with a color when color support is added // TODO: Update the value here with a color when color support is added
// This colors the current day // This colors the current day
} }
@ -334,7 +329,7 @@ fn add_month_to_table(
indexmap.insert((*day).to_string(), value); indexmap.insert((*day).to_string(), value);
day_count += 1; day_number += 1;
} }
calendar_vec_deque calendar_vec_deque

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

@ -0,0 +1,111 @@
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 Char;
#[derive(Deserialize)]
struct CharArgs {
name: Tagged<String>,
}
#[async_trait]
impl WholeStreamCommand for Char {
fn name(&self) -> &str {
"char"
}
fn signature(&self) -> Signature {
Signature::build("ansi").required(
"character",
SyntaxShape::Any,
"the name of the character to output",
)
}
fn usage(&self) -> &str {
"Output special characters (eg. 'newline')"
}
fn examples(&self) -> Vec<Example> {
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(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let (CharArgs { name }, _) = args.process(&registry).await?;
let special_character = str_to_character(&name.item);
if let Some(output) = special_character {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(name.tag()),
)))
} else {
Err(ShellError::labeled_error(
"Unknown character",
"unknown character",
name.tag(),
))
}
}
}
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()), // #
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::Char;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Char {})
}
}

View File

@ -1,3 +1,4 @@
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
use crate::evaluate::evaluate_baseline_expr; use crate::evaluate::evaluate_baseline_expr;
use crate::futures::ThreadedReceiver; use crate::futures::ThreadedReceiver;
use crate::prelude::*; use crate::prelude::*;
@ -7,9 +8,7 @@ use std::ops::Deref;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::sync::mpsc; use std::sync::mpsc;
use bytes::{BufMut, Bytes, BytesMut};
use futures::executor::block_on_stream; use futures::executor::block_on_stream;
// use futures::stream::StreamExt;
use futures_codec::FramedRead; use futures_codec::FramedRead;
use log::trace; use log::trace;
@ -18,70 +17,6 @@ use nu_protocol::hir::ExternalCommand;
use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value}; use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
use nu_source::Tag; 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( pub(crate) async fn run_external_command(
command: ExternalCommand, command: ExternalCommand,
context: &mut Context, context: &mut Context,
@ -91,7 +26,7 @@ pub(crate) async fn run_external_command(
) -> Result<InputStream, ShellError> { ) -> Result<InputStream, ShellError> {
trace!(target: "nu::run::external", "-> {}", command.name); 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( return Err(ShellError::labeled_error(
"Command not found", "Command not found",
"command not found", "command not found",
@ -118,35 +53,35 @@ async fn run_with_stdin(
let value = let value =
evaluate_baseline_expr(arg, &context.registry, &scope.it, &scope.vars, &scope.env) evaluate_baseline_expr(arg, &context.registry, &scope.it, &scope.vars, &scope.env)
.await?; .await?;
// Skip any arguments that don't really exist, treating them as optional // Skip any arguments that don't really exist, treating them as optional
// FIXME: we may want to preserve the gap in the future, though it's hard to say // 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. // what value we would put in its place.
if value.value.is_none() { if value.value.is_none() {
continue; continue;
} }
// Do the cleanup that we need to do on any argument going out: // 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 trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
let value_string; command_args.push(trimmed_value_string);
#[cfg(not(windows))]
{
value_string = trimmed_value_string
.replace('$', "\\$")
.replace('"', "\\\"")
.to_string()
}
#[cfg(windows)]
{
value_string = trimmed_value_string
}
command_args.push(value_string);
} }
let process_args = command_args let process_args = command_args
.iter() .iter()
.map(|arg| { .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))] #[cfg(not(windows))]
{ {
@ -214,6 +149,9 @@ fn spawn(
if !is_last { if !is_last {
process.stdout(Stdio::piped()); process.stdout(Stdio::piped());
trace!(target: "nu::run::external", "set up stdout pipe"); 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 // open since we have some contents for stdin
@ -312,8 +250,22 @@ fn spawn(
return Err(()); 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 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) { for line in block_on_stream(stream) {
match line { match line {
@ -365,6 +317,64 @@ fn spawn(
} }
} }
} }
let file = futures::io::AllowStdIo::new(stderr);
let err_stream = FramedRead::new(file, MaybeTextCodec::default());
for err_line in block_on_stream(err_stream) {
match err_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()),
),
tag: stdout_name_tag.clone(),
}));
if result.is_err() {
break;
}
}
StringOrBinary::Binary(_) => {
let result = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(
ShellError::untagged_runtime_error(
"Binary in stderr output",
),
),
tag: stdout_name_tag.clone(),
}));
if result.is_err() {
break;
}
}
},
Err(e) => {
// If there's an exit status, it makes sense that we may error when
// trying to read from its stdout pipe (likely been closed). In that
// case, don't emit an error.
let should_error = match child.wait() {
Ok(exit_status) => !exit_status.success(),
Err(_) => true,
};
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",
&stdout_name_tag,
)),
tag: stdout_name_tag.clone(),
}));
}
return Ok(());
}
}
}
} }
// We can give an error when we see a non-zero exit code, but this is different // We can give an error when we see a non-zero exit code, but this is different
@ -408,13 +418,19 @@ fn spawn(
} }
} }
async fn did_find_command(name: &str) -> bool { pub fn did_find_command(#[allow(unused)] name: &str) -> bool {
#[cfg(not(windows))] #[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() which::which(name).is_ok()
} }
#[cfg(windows)] #[cfg(all(feature = "which", windows))]
{ {
if which::which(name).is_ok() { if which::which(name).is_ok() {
true true
@ -484,11 +500,17 @@ fn shell_os_paths() -> Vec<std::path::PathBuf> {
mod tests { mod tests {
use super::{ use super::{
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes, 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, Context, InputStream};
#[cfg(feature = "which")]
use futures::executor::block_on; use futures::executor::block_on;
#[cfg(feature = "which")]
use nu_errors::ShellError; use nu_errors::ShellError;
#[cfg(feature = "which")]
use nu_protocol::Scope; use nu_protocol::Scope;
#[cfg(feature = "which")]
use nu_test_support::commands::ExternalBuilder; use nu_test_support::commands::ExternalBuilder;
// async fn read(mut stream: OutputStream) -> Option<Value> { // async fn read(mut stream: OutputStream) -> Option<Value> {
@ -504,6 +526,7 @@ mod tests {
// } // }
// } // }
#[cfg(feature = "which")]
async fn non_existent_run() -> Result<(), ShellError> { async fn non_existent_run() -> Result<(), ShellError> {
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build(); let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
@ -543,6 +566,7 @@ mod tests {
// block_on(failure_run()) // block_on(failure_run())
// } // }
#[cfg(feature = "which")]
#[test] #[test]
fn identifies_command_not_found() -> Result<(), ShellError> { fn identifies_command_not_found() -> Result<(), ShellError> {
block_on(non_existent_run()) block_on(non_existent_run())

View File

@ -28,7 +28,11 @@ pub(crate) async fn run_internal_command(
let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input); let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
let internal_command = context.expect_command(&command.name); let internal_command = context.expect_command(&command.name);
let mut result = { if command.name == "autoenv untrust" {
context.user_recently_used_autoenv_untrust = true;
}
let result = {
context context
.run_command( .run_command(
internal_command?, internal_command?,
@ -37,159 +41,217 @@ pub(crate) async fn run_internal_command(
&scope, &scope,
objects, objects,
) )
.await .await?
}; };
let mut context = context.clone(); let head = Arc::new(command.args.head.clone());
//let context = Arc::new(context.clone());
let context = context.clone();
let command = Arc::new(command);
let scope = Arc::new(scope);
// let scope = scope.clone(); // let scope = scope.clone();
let stream = async_stream! { Ok(InputStream::from_stream(
let mut soft_errs: Vec<ShellError> = vec![]; result
let mut yielded = false; .then(move |item| {
let head = head.clone();
let command = command.clone();
let mut context = context.clone();
let scope = scope.clone();
async move {
match item {
Ok(ReturnSuccess::Action(action)) => match action {
CommandAction::ChangePath(path) => {
context.shell_manager.set_path(path);
InputStream::from_stream(futures::stream::iter(vec![]))
}
CommandAction::Exit => std::process::exit(0), // TODO: save history.txt
CommandAction::Error(err) => {
context.error(err.clone());
InputStream::one(UntaggedValue::Error(err).into_untagged_value())
}
CommandAction::AutoConvert(tagged_contents, extension) => {
let contents_tag = tagged_contents.tag.clone();
let command_name = format!("from {}", extension);
let command = command.clone();
if let Some(converter) = context.registry.get_command(&command_name)
{
let new_args = RawCommandArgs {
host: context.host.clone(),
ctrl_c: context.ctrl_c.clone(),
current_errors: context.current_errors.clone(),
shell_manager: context.shell_manager.clone(),
call_info: UnevaluatedCallInfo {
args: nu_protocol::hir::Call {
head: (&*head).clone(),
positional: None,
named: None,
span: Span::unknown(),
is_last: false,
},
name_tag: Tag::unknown_anchor(command.name_span),
scope: (&*scope).clone(),
},
};
let result = converter
.run(
new_args.with_input(vec![tagged_contents]),
&context.registry,
)
.await;
while let Some(item) = result.next().await { match result {
match item { Ok(mut result) => {
Ok(ReturnSuccess::Action(action)) => match action { let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
CommandAction::ChangePath(path) => { result.drain_vec().await;
context.shell_manager.set_path(path);
} let mut output = vec![];
CommandAction::Exit => std::process::exit(0), // TODO: save history.txt for res in result_vec {
CommandAction::Error(err) => { match res {
context.error(err); Ok(ReturnSuccess::Value(Value {
break; value: UntaggedValue::Table(list),
} ..
CommandAction::AutoConvert(tagged_contents, extension) => { })) => {
let contents_tag = tagged_contents.tag.clone(); for l in list {
let command_name = format!("from {}", extension); output.push(Ok(l));
let command = command.clone(); }
if let Some(converter) = context.registry.get_command(&command_name) { }
let new_args = RawCommandArgs { Ok(ReturnSuccess::Value(Value {
host: context.host.clone(), value,
ctrl_c: context.ctrl_c.clone(), ..
current_errors: context.current_errors.clone(), })) => {
shell_manager: context.shell_manager.clone(), output
call_info: UnevaluatedCallInfo { .push(Ok(value
args: nu_protocol::hir::Call { .into_value(contents_tag.clone())));
head: command.args.head, }
positional: None, Err(e) => output.push(Err(e)),
named: None, _ => {}
span: Span::unknown(), }
is_last: false, }
},
name_tag: Tag::unknown_anchor(command.name_span), futures::stream::iter(output).to_input_stream()
scope: scope.clone(), }
} Err(e) => {
}; context.add_error(e);
let mut result = converter.run(new_args.with_input(vec![tagged_contents]), &context.registry).await; InputStream::empty()
let result_vec: Vec<Result<ReturnSuccess, ShellError>> = result.drain_vec().await;
for res in result_vec {
match res {
Ok(ReturnSuccess::Value(Value { value: UntaggedValue::Table(list), ..})) => {
for l in list {
yield Ok(l);
} }
} }
Ok(ReturnSuccess::Value(Value { value, .. })) => { } else {
yield Ok(value.into_value(contents_tag.clone())); InputStream::one(tagged_contents)
}
Err(e) => yield Err(e),
_ => {}
} }
} }
} else { CommandAction::EnterHelpShell(value) => match value {
yield Ok(tagged_contents) Value {
} value: UntaggedValue::Primitive(Primitive::String(cmd)),
} tag,
CommandAction::EnterHelpShell(value) => { } => {
match value { context.shell_manager.insert_at_current(Box::new(
Value { match HelpShell::for_command(
value: UntaggedValue::Primitive(Primitive::String(cmd)), UntaggedValue::string(cmd).into_value(tag),
tag, &context.registry(),
} => { ) {
context.shell_manager.insert_at_current(Box::new( Ok(v) => v,
HelpShell::for_command( Err(err) => {
UntaggedValue::string(cmd).into_value(tag), return InputStream::one(
&context.registry(), UntaggedValue::Error(err).into_untagged_value(),
)?, )
)); }
},
));
InputStream::from_stream(futures::stream::iter(vec![]))
}
_ => {
context.shell_manager.insert_at_current(Box::new(
match HelpShell::index(&context.registry()) {
Ok(v) => v,
Err(err) => {
return InputStream::one(
UntaggedValue::Error(err).into_untagged_value(),
)
}
},
));
InputStream::from_stream(futures::stream::iter(vec![]))
}
},
CommandAction::EnterValueShell(value) => {
context
.shell_manager
.insert_at_current(Box::new(ValueShell::new(value)));
InputStream::from_stream(futures::stream::iter(vec![]))
} }
_ => { CommandAction::EnterShell(location) => {
context.shell_manager.insert_at_current(Box::new( context.shell_manager.insert_at_current(Box::new(
HelpShell::index(&context.registry())?, match FilesystemShell::with_location(
location,
context.registry().clone(),
) {
Ok(v) => v,
Err(err) => {
return InputStream::one(
UntaggedValue::Error(err.into())
.into_untagged_value(),
)
}
},
)); ));
InputStream::from_stream(futures::stream::iter(vec![]))
} }
CommandAction::AddAlias(name, args, block) => {
context.add_commands(vec![whole_stream_command(
AliasCommand::new(name, args, block),
)]);
InputStream::from_stream(futures::stream::iter(vec![]))
}
CommandAction::PreviousShell => {
context.shell_manager.prev();
InputStream::from_stream(futures::stream::iter(vec![]))
}
CommandAction::NextShell => {
context.shell_manager.next();
InputStream::from_stream(futures::stream::iter(vec![]))
}
CommandAction::LeaveShell => {
context.shell_manager.remove_at_current();
if context.shell_manager.is_empty() {
std::process::exit(0); // TODO: save history.txt
}
InputStream::from_stream(futures::stream::iter(vec![]))
}
},
Ok(ReturnSuccess::Value(Value {
value: UntaggedValue::Error(err),
tag,
})) => {
context.error(err.clone());
InputStream::one(UntaggedValue::Error(err).into_value(tag))
}
Ok(ReturnSuccess::Value(v)) => InputStream::one(v),
Ok(ReturnSuccess::DebugValue(v)) => {
let doc = PrettyDebug::pretty_doc(&v);
let mut buffer = termcolor::Buffer::ansi();
let _ = doc.render_raw(
context.with_host(|host| host.width() - 5),
&mut nu_source::TermColored::new(&mut buffer),
);
let value = String::from_utf8_lossy(buffer.as_slice());
InputStream::one(UntaggedValue::string(value).into_untagged_value())
}
Err(err) => {
context.error(err.clone());
InputStream::one(UntaggedValue::Error(err).into_untagged_value())
} }
} }
CommandAction::EnterValueShell(value) => {
context
.shell_manager
.insert_at_current(Box::new(ValueShell::new(value)));
}
CommandAction::EnterShell(location) => {
context.shell_manager.insert_at_current(Box::new(
FilesystemShell::with_location(location, context.registry().clone())?,
));
}
CommandAction::AddAlias(name, args, block) => {
context.add_commands(vec![
whole_stream_command(AliasCommand::new(
name,
args,
block,
))
]);
}
CommandAction::PreviousShell => {
context.shell_manager.prev();
}
CommandAction::NextShell => {
context.shell_manager.next();
}
CommandAction::LeaveShell => {
context.shell_manager.remove_at_current();
if context.shell_manager.is_empty() {
std::process::exit(0); // TODO: save history.txt
}
}
},
Ok(ReturnSuccess::Value(Value {
value: UntaggedValue::Error(err),
..
})) => {
context.error(err.clone());
yield Err(err);
break;
} }
})
Ok(ReturnSuccess::Value(v)) => { .flatten()
yielded = true; .take_while(|x| futures::future::ready(!x.is_error())),
yield Ok(v); ))
}
Ok(ReturnSuccess::DebugValue(v)) => {
yielded = true;
let doc = PrettyDebug::pretty_doc(&v);
let mut buffer = termcolor::Buffer::ansi();
let _ = doc.render_raw(
context.with_host(|host| host.width() - 5),
&mut nu_source::TermColored::new(&mut buffer),
);
let value = String::from_utf8_lossy(buffer.as_slice());
yield Ok(UntaggedValue::string(value).into_untagged_value())
}
Err(err) => {
context.error(err);
break;
}
}
}
};
Ok(stream.to_input_stream())
} }

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,7 @@ mod dynamic;
pub(crate) mod expr; pub(crate) mod expr;
pub(crate) mod external; pub(crate) mod external;
pub(crate) mod internal; pub(crate) mod internal;
pub(crate) mod maybe_text_codec;
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) use dynamic::Command as DynamicCommand; pub(crate) use dynamic::Command as DynamicCommand;

View File

@ -343,18 +343,23 @@ impl Command {
self.0.usage() self.0.usage()
} }
pub async fn run(&self, args: CommandArgs, registry: &CommandRegistry) -> OutputStream { pub fn examples(&self) -> Vec<Example> {
self.0.examples()
}
pub async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
if args.call_info.switch_present("help") { if args.call_info.switch_present("help") {
let cl = self.0.clone(); let cl = self.0.clone();
let registry = registry.clone(); let registry = registry.clone();
OutputStream::one(Ok(ReturnSuccess::Value( Ok(OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(get_help(&*cl, &registry)).into_value(Tag::unknown()), UntaggedValue::string(get_help(&*cl, &registry)).into_value(Tag::unknown()),
))) ))))
} else { } else {
match self.0.run(args, registry).await { self.0.run(args, registry).await
Ok(stream) => stream,
Err(err) => OutputStream::one(Err(err)),
}
} }
} }
@ -389,42 +394,44 @@ impl WholeStreamCommand for FnFilterCommand {
ctrl_c, ctrl_c,
shell_manager, shell_manager,
call_info, call_info,
mut input, input,
.. ..
}: CommandArgs, }: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let host: Arc<parking_lot::Mutex<dyn Host>> = host.clone(); let registry = Arc::new(registry.clone());
let registry: CommandRegistry = registry.clone();
let func = self.func; let func = self.func;
let stream = async_stream! { Ok(input
while let Some(it) = input.next().await { .then(move |it| {
let host = host.clone();
let registry = registry.clone(); let registry = registry.clone();
let call_info = match call_info.clone().evaluate_with_new_it(&registry, &it).await { let ctrl_c = ctrl_c.clone();
Err(err) => { yield Err(err); return; }, let shell_manager = shell_manager.clone();
Ok(args) => args, let call_info = call_info.clone();
}; async move {
let call_info = match call_info.evaluate_with_new_it(&*registry, &it).await {
let args = EvaluatedFilterCommandArgs::new( Err(err) => {
host.clone(), return OutputStream::one(Err(err));
ctrl_c.clone(),
shell_manager.clone(),
call_info,
);
match func(args) {
Err(err) => yield Err(err),
Ok(mut stream) => {
while let Some(value) = stream.values.next().await {
yield value;
} }
Ok(args) => args,
};
let args = EvaluatedFilterCommandArgs::new(
host.clone(),
ctrl_c.clone(),
shell_manager.clone(),
call_info,
);
match func(args) {
Err(err) => return OutputStream::one(Err(err)),
Ok(stream) => stream,
} }
} }
} })
}; .flatten()
.to_output_stream())
Ok(stream.to_output_stream())
} }
} }

View File

@ -40,7 +40,7 @@ impl WholeStreamCommand for Compact {
vec![ vec![
Example { Example {
description: "Filter out all null entries in a list", 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![ result: Some(vec![
UntaggedValue::int(1).into(), UntaggedValue::int(1).into(),
UntaggedValue::int(2).into(), UntaggedValue::int(2).into(),

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::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
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 = crate::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 = crate::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::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
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 = crate::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::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
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 = crate::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::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
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::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
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 = crate::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::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
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 completion_mode to circular",
example: "config set [completion_mode circular]",
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 = crate::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::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
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 = crate::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

@ -7,7 +7,7 @@ use crate::commands::WholeStreamCommand;
use chrono::{Datelike, TimeZone, Timelike}; use chrono::{Datelike, TimeZone, Timelike};
use core::fmt::Display; use core::fmt::Display;
use indexmap::IndexMap; use indexmap::IndexMap;
use nu_protocol::{Signature, UntaggedValue}; use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
pub struct Date; pub struct Date;
@ -21,6 +21,13 @@ impl WholeStreamCommand for Date {
Signature::build("date") Signature::build("date")
.switch("utc", "use universal time (UTC)", Some('u')) .switch("utc", "use universal time (UTC)", Some('u'))
.switch("local", "use the local time", Some('l')) .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 { fn usage(&self) -> &str {
@ -47,46 +54,72 @@ impl WholeStreamCommand for Date {
example: "date --utc", example: "date --utc",
result: None, 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<T: TimeZone>(dt: DateTime<T>, tag: Tag) -> Value 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 where
T::Offset: Display, T::Offset: Display,
{ {
let mut indexmap = IndexMap::new(); let mut indexmap = IndexMap::new();
indexmap.insert( if dt_format.is_empty() {
"year".to_string(), indexmap.insert(
UntaggedValue::int(dt.year()).into_value(&tag), "year".to_string(),
); UntaggedValue::int(dt.year()).into_value(&tag),
indexmap.insert( );
"month".to_string(), indexmap.insert(
UntaggedValue::int(dt.month()).into_value(&tag), "month".to_string(),
); UntaggedValue::int(dt.month()).into_value(&tag),
indexmap.insert( );
"day".to_string(), indexmap.insert(
UntaggedValue::int(dt.day()).into_value(&tag), "day".to_string(),
); UntaggedValue::int(dt.day()).into_value(&tag),
indexmap.insert( );
"hour".to_string(), indexmap.insert(
UntaggedValue::int(dt.hour()).into_value(&tag), "hour".to_string(),
); UntaggedValue::int(dt.hour()).into_value(&tag),
indexmap.insert( );
"minute".to_string(), indexmap.insert(
UntaggedValue::int(dt.minute()).into_value(&tag), "minute".to_string(),
); UntaggedValue::int(dt.minute()).into_value(&tag),
indexmap.insert( );
"second".to_string(), indexmap.insert(
UntaggedValue::int(dt.second()).into_value(&tag), "second".to_string(),
); UntaggedValue::int(dt.second()).into_value(&tag),
);
let tz = dt.offset(); let tz = dt.offset();
indexmap.insert( indexmap.insert(
"timezone".to_string(), "timezone".to_string(),
UntaggedValue::string(format!("{}", tz)).into_value(&tag), 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) UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
} }
@ -97,15 +130,33 @@ pub async fn date(
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let args = args.evaluate_once(&registry).await?; let args = args.evaluate_once(&registry).await?;
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let 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 value = if args.has("utc") {
let utc: DateTime<Utc> = Utc::now(); let utc: DateTime<Utc> = Utc::now();
date_to_value(utc, tag) if raw {
UntaggedValue::string(date_to_value_raw(utc, dt_fmt)).into_untagged_value()
} else {
date_to_value(utc, tag, dt_fmt)
}
} else { } else {
let local: DateTime<Local> = Local::now(); let local: DateTime<Local> = Local::now();
date_to_value(local, tag) 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)) Ok(OutputStream::one(value))

View File

@ -0,0 +1,111 @@
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};
pub struct Do;
#[derive(Deserialize, Debug)]
struct DoArgs {
block: Block,
ignore_errors: bool,
}
#[async_trait]
impl WholeStreamCommand for Do {
fn name(&self) -> &str {
"do"
}
fn signature(&self) -> Signature {
Signature::build("do")
.required("block", SyntaxShape::Block, "the block to run ")
.switch(
"ignore_errors",
"ignore errors as the block runs",
Some('i'),
)
}
fn usage(&self) -> &str {
"Runs a block, optionally ignoring errors"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
do_(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Run the block",
example: r#"do { echo hello }"#,
result: Some(vec![Value::from("hello")]),
},
Example {
description: "Run the block and ignore errors",
example: r#"do -i { thisisnotarealcommand }"#,
result: Some(vec![Value::nothing()]),
},
]
}
}
async fn do_(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let is_last = raw_args.call_info.args.is_last;
let mut context = Context::from_raw(&raw_args, &registry);
let scope = raw_args.call_info.scope.clone();
let (
DoArgs {
ignore_errors,
mut block,
},
input,
) = raw_args.process(&registry).await?;
block.set_is_last(is_last);
let result = run_block(
&block,
&mut context,
input,
&scope.it,
&scope.vars,
&scope.env,
)
.await;
if ignore_errors {
match result {
Ok(mut stream) => {
let output = stream.drain_vec().await;
context.clear_errors();
Ok(futures::stream::iter(output).to_output_stream())
}
Err(_) => Ok(OutputStream::one(ReturnSuccess::value(Value::nothing()))),
}
} else {
result.map(|x| x.to_output_stream())
}
}
#[cfg(test)]
mod tests {
use super::Do;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Do {})
}
}

View File

@ -35,20 +35,7 @@ impl WholeStreamCommand for Drop {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let (DropArgs { rows }, input) = args.process(&registry).await?; drop(args, 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())
} }
fn examples(&self) -> Vec<Example> { 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)] #[cfg(test)]
mod tests { mod tests {
use super::Drop; use super::Drop;

View File

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

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

View File

@ -3,7 +3,7 @@ use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::hir::Operator; use nu_protocol::hir::Operator;
use nu_protocol::{ use nu_protocol::{
Primitive, RangeInclusion, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value, Primitive, Range, RangeInclusion, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
}; };
pub struct Echo; pub struct Echo;
@ -32,7 +32,7 @@ impl WholeStreamCommand for Echo {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
echo(args, registry) echo(args, registry).await
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -51,67 +51,88 @@ impl WholeStreamCommand for Echo {
} }
} }
fn echo(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn echo(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let (args, _): (EchoArgs, _) = args.process(&registry).await?;
let (args, _): (EchoArgs, _) = args.process(&registry).await?;
for i in args.rest { let stream = args.rest.into_iter().map(|i| match i.as_string() {
match i.as_string() { Ok(s) => OutputStream::one(Ok(ReturnSuccess::Value(
Ok(s) => { UntaggedValue::string(s).into_value(i.tag.clone()),
yield Ok(ReturnSuccess::Value( ))),
UntaggedValue::string(s).into_value(i.tag.clone()), _ => match i {
)); Value {
} value: UntaggedValue::Table(table),
_ => match i { ..
Value { } => futures::stream::iter(table.into_iter().map(ReturnSuccess::value))
value: UntaggedValue::Table(table), .to_output_stream(),
.. Value {
} => { value: UntaggedValue::Primitive(Primitive::Range(range)),
for value in table { tag,
yield Ok(ReturnSuccess::Value(value.clone())); } => futures::stream::iter(RangeIterator::new(*range, tag)).to_output_stream(),
} _ => OutputStream::one(Ok(ReturnSuccess::Value(i.clone()))),
} },
Value { });
value: UntaggedValue::Primitive(Primitive::Range(range)),
tag Ok(futures::stream::iter(stream).flatten().to_output_stream())
} => { }
let mut current = range.from.0.item;
while current != range.to.0.item { struct RangeIterator {
yield Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag))); curr: Primitive,
current = match crate::data::value::compute_values(Operator::Plus, &UntaggedValue::Primitive(current), &UntaggedValue::int(1)) { end: Primitive,
Ok(result) => match result { tag: Tag,
UntaggedValue::Primitive(p) => p, is_end_inclusive: bool,
_ => { is_done: bool,
yield Err(ShellError::unimplemented("Internal error: expected a primitive result from increment")); }
return;
} impl RangeIterator {
}, pub fn new(range: Range, tag: Tag) -> RangeIterator {
Err((left_type, right_type)) => { RangeIterator {
yield Err(ShellError::coerce_error( curr: range.from.0.item,
left_type.spanned(tag.span), end: range.to.0.item,
right_type.spanned(tag.span), tag,
)); is_end_inclusive: matches!(range.to.1, RangeInclusion::Inclusive),
return; is_done: false,
} }
} }
} }
match range.to.1 {
RangeInclusion::Inclusive => { impl Iterator for RangeIterator {
yield Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag))); type Item = Result<ReturnSuccess, ShellError>;
} fn next(&mut self) -> Option<Self::Item> {
_ => {} if self.curr != self.end {
} let output = UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone());
}
self.curr = match crate::data::value::compute_values(
Operator::Plus,
&UntaggedValue::Primitive(self.curr.clone()),
&UntaggedValue::int(1),
) {
Ok(result) => match result {
UntaggedValue::Primitive(p) => p,
_ => { _ => {
yield Ok(ReturnSuccess::Value(i.clone())); 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 if self.is_end_inclusive && !self.is_done {
self.is_done = true;
Some(ReturnSuccess::value(
UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone()),
))
} else {
// TODO: add inclusive/exclusive ranges
None
} }
}; }
Ok(stream.to_output_stream())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -14,6 +14,7 @@ pub struct Enter;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct EnterArgs { pub struct EnterArgs {
location: Tagged<PathBuf>, location: Tagged<PathBuf>,
encoding: Option<Tagged<String>>,
} }
#[async_trait] #[async_trait]
@ -23,15 +24,29 @@ impl WholeStreamCommand for Enter {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("enter").required( Signature::build("enter")
"location", .required(
SyntaxShape::Path, "location",
"the location to create a new shell from", SyntaxShape::Path,
) "the location to create a new shell from",
)
.named(
"encoding",
SyntaxShape::String,
"encoding to use to open file",
Some('e'),
)
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Create a new shell and begin at this path." r#"Create a new shell and begin at this path.
Multiple encodings are supported for reading text files by using
the '--encoding <encoding>' parameter. Here is an example of a few:
big5, euc-jp, euc-kr, gbk, iso-8859-1, utf-16, cp1252, latin5
For a more complete list of encodings please refer to the encoding_rs
documentation link at https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics"#
} }
async fn run( async fn run(
@ -39,7 +54,7 @@ impl WholeStreamCommand for Enter {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
enter(args, registry) enter(args, registry).await
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -54,121 +69,122 @@ impl WholeStreamCommand for Enter {
example: "enter package.json", example: "enter package.json",
result: None, result: None,
}, },
Example {
description: "Enters file with iso-8859-1 encoding",
example: "enter file.csv --encoding iso-8859-1",
result: None,
},
] ]
} }
} }
fn enter(raw_args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn enter(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let scope = raw_args.call_info.scope.clone();
let scope = raw_args.call_info.scope.clone(); let shell_manager = raw_args.shell_manager.clone();
let shell_manager = raw_args.shell_manager.clone(); let head = raw_args.call_info.args.head.clone();
let head = raw_args.call_info.args.head.clone(); let ctrl_c = raw_args.ctrl_c.clone();
let ctrl_c = raw_args.ctrl_c.clone(); let current_errors = raw_args.current_errors.clone();
let current_errors = raw_args.current_errors.clone(); let host = raw_args.host.clone();
let host = raw_args.host.clone(); let tag = raw_args.call_info.name_tag.clone();
let tag = raw_args.call_info.name_tag.clone(); let (EnterArgs { location, encoding }, _) = raw_args.process(&registry).await?;
let (EnterArgs { location }, _) = raw_args.process(&registry).await?; let location_string = location.display().to_string();
let location_string = location.display().to_string(); let location_clone = location_string.clone();
let location_clone = location_string.clone();
if location_string.starts_with("help") { if location_string.starts_with("help") {
let spec = location_string.split(':').collect::<Vec<&str>>(); let spec = location_string.split(':').collect::<Vec<&str>>();
if spec.len() == 2 { if spec.len() == 2 {
let (_, command) = (spec[0], spec[1]); let (_, command) = (spec[0], spec[1]);
if registry.has(command) { if registry.has(command) {
yield Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell( return Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::EnterHelpShell(
UntaggedValue::string(command).into_value(Tag::unknown()), UntaggedValue::string(command).into_value(Tag::unknown()),
))); ),
return; )));
}
}
yield Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
UntaggedValue::nothing().into_value(Tag::unknown()),
)));
} else if location.is_dir() {
yield Ok(ReturnSuccess::Action(CommandAction::EnterShell(
location_clone,
)));
} else {
// If it's a file, attempt to open the file as a value and enter it
let cwd = shell_manager.path();
let full_path = std::path::PathBuf::from(cwd);
let (file_extension, contents, contents_tag) =
crate::commands::open::fetch(
&full_path,
&PathBuf::from(location_clone),
tag.span,
).await?;
match contents {
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)
{
let new_args = RawCommandArgs {
host,
ctrl_c,
current_errors,
shell_manager,
call_info: UnevaluatedCallInfo {
args: nu_protocol::hir::Call {
head,
positional: None,
named: None,
span: Span::unknown(),
is_last: false,
},
name_tag: tag.clone(),
scope: scope.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;
for res in result_vec {
match res {
Ok(ReturnSuccess::Value(Value {
value,
..
})) => {
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(
Value {
value,
tag: contents_tag.clone(),
})));
}
x => yield x,
}
}
} else {
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
}
} else {
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
}
}
_ => {
let tagged_contents = contents.into_value(contents_tag);
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
}
} }
} }
}; Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::EnterHelpShell(UntaggedValue::nothing().into_value(Tag::unknown())),
)))
} else if location.is_dir() {
Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::EnterShell(location_clone),
)))
} else {
// If it's a file, attempt to open the file as a value and enter it
let cwd = shell_manager.path();
Ok(stream.to_output_stream()) let full_path = std::path::PathBuf::from(cwd);
let (file_extension, tagged_contents) = crate::commands::open::fetch(
&full_path,
&PathBuf::from(location_clone),
tag.span,
encoding,
)
.await?;
match tagged_contents.value {
UntaggedValue::Primitive(Primitive::String(_)) => {
if let Some(extension) = file_extension {
let command_name = format!("from {}", extension);
if let Some(converter) = registry.get_command(&command_name) {
let new_args = RawCommandArgs {
host,
ctrl_c,
current_errors,
shell_manager,
call_info: UnevaluatedCallInfo {
args: nu_protocol::hir::Call {
head,
positional: None,
named: None,
span: Span::unknown(),
is_last: false,
},
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: tag.clone(),
})),
),
x => x,
},
))
.to_output_stream())
} else {
Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::EnterValueShell(tagged_contents),
)))
}
} else {
Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::EnterValueShell(tagged_contents),
)))
}
}
_ => Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::EnterValueShell(tagged_contents),
))),
}
}
} }
#[cfg(test)] #[cfg(test)]

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

@ -0,0 +1,103 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
pub struct Every;
#[derive(Deserialize)]
pub struct EveryArgs {
stride: Tagged<u64>,
skip: Tagged<bool>,
}
#[async_trait]
impl WholeStreamCommand for Every {
fn name(&self) -> &str {
"every"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required(
"stride",
SyntaxShape::Int,
"how many rows to skip between (and including) each row returned",
)
.switch(
"skip",
"skip the rows that would be returned, instead of selecting them",
Some('s'),
)
}
fn usage(&self) -> &str {
"Show (or skip) every n-th row, starting from the first one."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
every(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get every second row",
example: "echo [1 2 3 4 5] | every 2",
result: Some(vec![
UntaggedValue::int(1).into(),
UntaggedValue::int(3).into(),
UntaggedValue::int(5).into(),
]),
},
Example {
description: "Skip every second row",
example: "echo [1 2 3 4 5] | every 2 --skip",
result: Some(vec![
UntaggedValue::int(2).into(),
UntaggedValue::int(4).into(),
]),
},
]
}
}
async fn every(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (EveryArgs { stride, skip }, input) = args.process(&registry).await?;
let stride = stride.item;
let skip = skip.item;
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);
if should_include {
Some(ReturnSuccess::value(value))
} else {
None
}
})
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Every;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Every {})
}
}

View File

@ -37,7 +37,7 @@ impl WholeStreamCommand for Format {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
format_command(args, registry) format_command(args, registry).await
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -49,49 +49,60 @@ impl WholeStreamCommand for Format {
} }
} }
fn format_command( async fn format_command(
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = Arc::new(registry.clone());
let stream = async_stream! { let scope = Arc::new(args.call_info.scope.clone());
let scope = args.call_info.scope.clone(); let (FormatArgs { pattern }, input) = args.process(&registry).await?;
let (FormatArgs { pattern }, mut input) = args.process(&registry).await?;
let pattern_tag = pattern.tag.clone();
let format_pattern = format(&pattern); let format_pattern = format(&pattern);
let commands = format_pattern; let commands = Arc::new(format_pattern);
while let Some(value) = input.next().await { Ok(input
.then(move |value| {
let mut output = String::new(); let mut output = String::new();
let commands = commands.clone();
let registry = registry.clone();
let scope = scope.clone();
for command in &commands { async move {
match command { for command in &*commands {
FormatCommand::Text(s) => { match command {
output.push_str(&s); FormatCommand::Text(s) => {
} output.push_str(&s);
FormatCommand::Column(c) => { }
// FIXME: use the correct spans FormatCommand::Column(c) => {
let full_column_path = nu_parser::parse_full_column_path(&(c.to_string()).spanned(Span::unknown()), &registry); // FIXME: use the correct spans
let full_column_path = nu_parser::parse_full_column_path(
&(c.to_string()).spanned(Span::unknown()),
&*registry,
);
let result = evaluate_baseline_expr(&full_column_path.0, &registry, &value, &scope.vars, &scope.env).await; let result = evaluate_baseline_expr(
&full_column_path.0,
&registry,
&value,
&scope.vars,
&scope.env,
)
.await;
if let Ok(c) = result { if let Ok(c) = result {
output output
.push_str(&value::format_leaf(c.borrow()).plain_string(100_000)) .push_str(&value::format_leaf(c.borrow()).plain_string(100_000))
} else { } else {
// That column doesn't match, so don't emit anything // That column doesn't match, so don't emit anything
}
} }
} }
} }
ReturnSuccess::value(UntaggedValue::string(output).into_untagged_value())
} }
})
yield ReturnSuccess::value( .to_output_stream())
UntaggedValue::string(output).into_untagged_value())
}
};
Ok(stream.to_output_stream())
} }
#[derive(Debug)] #[derive(Debug)]

View File

@ -25,14 +25,10 @@ impl WholeStreamCommand for From {
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { Ok(OutputStream::one(ReturnSuccess::value(
yield Ok(ReturnSuccess::Value( UntaggedValue::string(crate::commands::help::get_help(&From, &registry))
UntaggedValue::string(crate::commands::help::get_help(&From, &registry)) .into_value(Tag::unknown()),
.into_value(Tag::unknown()), )))
));
};
Ok(stream.to_output_stream())
} }
} }

View File

@ -28,35 +28,40 @@ impl WholeStreamCommand for FromIcs {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
from_ics(args, registry) from_ics(args, registry).await
} }
} }
fn from_ics(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn from_ics(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let args = args.evaluate_once(&registry).await?;
let args = args.evaluate_once(&registry).await?; let tag = args.name_tag();
let tag = args.name_tag(); let input = args.input;
let input = args.input;
let input_string = input.collect_string(tag.clone()).await?.item; let input_string = input.collect_string(tag.clone()).await?.item;
let input_bytes = input_string.as_bytes(); let input_bytes = input_string.as_bytes();
let buf_reader = BufReader::new(input_bytes); let buf_reader = BufReader::new(input_bytes);
let parser = ical::IcalParser::new(buf_reader); let parser = ical::IcalParser::new(buf_reader);
for calendar in parser { // TODO: it should be possible to make this a stream, but the some of the lifetime requirements make this tricky.
match calendar { // Pre-computing for now
Ok(c) => yield ReturnSuccess::value(calendar_to_value(c, tag.clone())), let mut output = vec![];
Err(_) => yield Err(ShellError::labeled_error(
"Could not parse as .ics", for calendar in parser {
"input cannot be parsed as .ics", match calendar {
tag.clone() Ok(c) => output.push(ReturnSuccess::value(calendar_to_value(c, tag.clone()))),
)), Err(_) => output.push(Err(ShellError::labeled_error(
} "Could not parse as .ics",
"input cannot be parsed as .ics",
tag.clone(),
))),
} }
}; }
Ok(stream.to_output_stream()) Ok(futures::stream::iter(output).to_output_stream())
} }
fn calendar_to_value(calendar: IcalCalendar, tag: Tag) -> Value { fn calendar_to_value(calendar: IcalCalendar, tag: Tag) -> Value {

View File

@ -33,7 +33,7 @@ impl WholeStreamCommand for FromJSON {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
from_json(args, registry) from_json(args, registry).await
} }
} }
@ -71,65 +71,73 @@ pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> serde_hjson:
Ok(convert_json_value_to_nu_value(&v, tag)) Ok(convert_json_value_to_nu_value(&v, tag))
} }
fn from_json(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn from_json(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name_tag = args.call_info.name_tag.clone(); let name_tag = args.call_info.name_tag.clone();
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let (FromJSONArgs { objects }, input) = args.process(&registry).await?;
let (FromJSONArgs { objects }, mut input) = args.process(&registry).await?; let concat_string = input.collect_string(name_tag.clone()).await?;
let concat_string = input.collect_string(name_tag.clone()).await?;
if objects { let string_clone: Vec<_> = concat_string.item.lines().map(|x| x.to_string()).collect();
for json_str in concat_string.item.lines() {
if objects {
Ok(
futures::stream::iter(string_clone.into_iter().filter_map(move |json_str| {
if json_str.is_empty() { if json_str.is_empty() {
continue; return None;
} }
match from_json_string_to_value(json_str.to_string(), &name_tag) { match from_json_string_to_value(json_str, &name_tag) {
Ok(x) => Ok(x) => Some(ReturnSuccess::value(x)),
yield ReturnSuccess::value(x),
Err(e) => { Err(e) => {
let mut message = "Could not parse as JSON (".to_string(); let mut message = "Could not parse as JSON (".to_string();
message.push_str(&e.to_string()); message.push_str(&e.to_string());
message.push_str(")"); message.push_str(")");
yield Err(ShellError::labeled_error_with_secondary( Some(Err(ShellError::labeled_error_with_secondary(
message, message,
"input cannot be parsed as JSON", "input cannot be parsed as JSON",
&name_tag, name_tag.clone(),
"value originates from here", "value originates from here",
concat_string.tag.clone())) concat_string.tag.clone(),
)))
} }
} }
} }))
} else { .to_output_stream(),
match from_json_string_to_value(concat_string.item, name_tag.clone()) { )
Ok(x) => } else {
match x { match from_json_string_to_value(concat_string.item, name_tag.clone()) {
Value { value: UntaggedValue::Table(list), .. } => { Ok(x) => match x {
for l in list { Value {
yield ReturnSuccess::value(l); value: UntaggedValue::Table(list),
} ..
} } => Ok(
x => yield ReturnSuccess::value(x), futures::stream::iter(list.into_iter().map(ReturnSuccess::value))
} .to_output_stream(),
Err(e) => { ),
let mut message = "Could not parse as JSON (".to_string(); x => Ok(OutputStream::one(ReturnSuccess::value(x))),
message.push_str(&e.to_string()); },
message.push_str(")"); Err(e) => {
let mut message = "Could not parse as JSON (".to_string();
message.push_str(&e.to_string());
message.push_str(")");
yield Err(ShellError::labeled_error_with_secondary( Ok(OutputStream::one(Err(
ShellError::labeled_error_with_secondary(
message, message,
"input cannot be parsed as JSON", "input cannot be parsed as JSON",
name_tag, name_tag,
"value originates from here", "value originates from here",
concat_string.tag)) concat_string.tag,
} ),
)))
} }
} }
}; }
Ok(stream.to_output_stream())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -36,62 +36,66 @@ impl WholeStreamCommand for FromODS {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
from_ods(args, registry) from_ods(args, registry).await
} }
} }
fn from_ods(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn from_ods(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let (
let (FromODSArgs { headerless: _headerless }, mut input) = args.process(&registry).await?; FromODSArgs {
let bytes = input.collect_binary(tag.clone()).await?; headerless: _headerless,
let mut buf: Cursor<Vec<u8>> = Cursor::new(bytes.item); },
let mut ods = Ods::<_>::new(buf).map_err(|_| ShellError::labeled_error( input,
"Could not load ods file", ) = args.process(&registry).await?;
"could not load ods file", let bytes = input.collect_binary(tag.clone()).await?;
&tag))?; let buf: Cursor<Vec<u8>> = Cursor::new(bytes.item);
let mut ods = Ods::<_>::new(buf).map_err(|_| {
ShellError::labeled_error("Could not load ods file", "could not load ods file", &tag)
})?;
let mut dict = TaggedDictBuilder::new(&tag); let mut dict = TaggedDictBuilder::new(&tag);
let sheet_names = ods.sheet_names().to_owned(); let sheet_names = ods.sheet_names().to_owned();
for sheet_name in &sheet_names { for sheet_name in &sheet_names {
let mut sheet_output = TaggedListBuilder::new(&tag); let mut sheet_output = TaggedListBuilder::new(&tag);
if let Some(Ok(current_sheet)) = ods.worksheet_range(sheet_name) { if let Some(Ok(current_sheet)) = ods.worksheet_range(sheet_name) {
for row in current_sheet.rows() { for row in current_sheet.rows() {
let mut row_output = TaggedDictBuilder::new(&tag); let mut row_output = TaggedDictBuilder::new(&tag);
for (i, cell) in row.iter().enumerate() { for (i, cell) in row.iter().enumerate() {
let value = match cell { let value = match cell {
DataType::Empty => UntaggedValue::nothing(), DataType::Empty => UntaggedValue::nothing(),
DataType::String(s) => UntaggedValue::string(s), DataType::String(s) => UntaggedValue::string(s),
DataType::Float(f) => UntaggedValue::decimal(*f), DataType::Float(f) => UntaggedValue::decimal(*f),
DataType::Int(i) => UntaggedValue::int(*i), DataType::Int(i) => UntaggedValue::int(*i),
DataType::Bool(b) => UntaggedValue::boolean(*b), DataType::Bool(b) => UntaggedValue::boolean(*b),
_ => UntaggedValue::nothing(), _ => UntaggedValue::nothing(),
}; };
row_output.insert_untagged(&format!("Column{}", i), value); row_output.insert_untagged(&format!("Column{}", i), value);
}
sheet_output.push_untagged(row_output.into_untagged_value());
} }
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value()); sheet_output.push_untagged(row_output.into_untagged_value());
} else {
yield Err(ShellError::labeled_error(
"Could not load sheet",
"could not load sheet",
&tag));
} }
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value());
} else {
return Err(ShellError::labeled_error(
"Could not load sheet",
"could not load sheet",
&tag,
));
} }
}
yield ReturnSuccess::value(dict.into_value()); Ok(OutputStream::one(ReturnSuccess::value(dict.into_value())))
};
Ok(stream.to_output_stream())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -51,7 +51,7 @@ impl WholeStreamCommand for FromSSV {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
from_ssv(args, registry) from_ssv(args, registry).await
} }
} }
@ -251,37 +251,53 @@ fn from_ssv_string_to_value(
Some(UntaggedValue::Table(rows).into_value(&tag)) Some(UntaggedValue::Table(rows).into_value(&tag))
} }
fn from_ssv(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn from_ssv(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let (
let (FromSSVArgs { headerless, aligned_columns, minimum_spaces }, mut input) = args.process(&registry).await?; FromSSVArgs {
let concat_string = input.collect_string(name.clone()).await?; headerless,
let split_at = match minimum_spaces { aligned_columns,
Some(number) => number.item, minimum_spaces,
None => DEFAULT_MINIMUM_SPACES },
}; input,
) = args.process(&registry).await?;
let concat_string = input.collect_string(name.clone()).await?;
let split_at = match minimum_spaces {
Some(number) => number.item,
None => DEFAULT_MINIMUM_SPACES,
};
match from_ssv_string_to_value(&concat_string.item, headerless, aligned_columns, split_at, name.clone()) { Ok(
match from_ssv_string_to_value(
&concat_string.item,
headerless,
aligned_columns,
split_at,
name.clone(),
) {
Some(x) => match x { Some(x) => match x {
Value { value: UntaggedValue::Table(list), ..} => { Value {
for l in list { yield ReturnSuccess::value(l) } value: UntaggedValue::Table(list),
} ..
x => yield ReturnSuccess::value(x) } => futures::stream::iter(list.into_iter().map(ReturnSuccess::value))
.to_output_stream(),
x => OutputStream::one(ReturnSuccess::value(x)),
}, },
None => { None => {
yield Err(ShellError::labeled_error_with_secondary( return Err(ShellError::labeled_error_with_secondary(
"Could not parse as SSV", "Could not parse as SSV",
"input cannot be parsed ssv", "input cannot be parsed ssv",
&name, &name,
"value originates from here", "value originates from here",
&concat_string.tag, &concat_string.tag,
)) ));
}, }
} },
}; )
Ok(stream.to_output_stream())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -24,7 +24,7 @@ impl WholeStreamCommand for FromTOML {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
from_toml(args, registry) from_toml(args, registry).await
} }
} }
@ -64,28 +64,28 @@ pub fn from_toml_string_to_value(s: String, tag: impl Into<Tag>) -> Result<Value
Ok(convert_toml_value_to_nu_value(&v, tag)) Ok(convert_toml_value_to_nu_value(&v, tag))
} }
pub fn from_toml( pub async fn from_toml(
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let args = args.evaluate_once(&registry).await?;
let args = args.evaluate_once(&registry).await?; let tag = args.name_tag();
let tag = args.name_tag(); let input = args.input;
let input = args.input;
let concat_string = input.collect_string(tag.clone()).await?; let concat_string = input.collect_string(tag.clone()).await?;
Ok(
match from_toml_string_to_value(concat_string.item, tag.clone()) { match from_toml_string_to_value(concat_string.item, tag.clone()) {
Ok(x) => match x { Ok(x) => match x {
Value { value: UntaggedValue::Table(list), .. } => { Value {
for l in list { value: UntaggedValue::Table(list),
yield ReturnSuccess::value(l); ..
} } => futures::stream::iter(list.into_iter().map(ReturnSuccess::value))
} .to_output_stream(),
x => yield ReturnSuccess::value(x), x => OutputStream::one(ReturnSuccess::value(x)),
}, },
Err(_) => { Err(_) => {
yield Err(ShellError::labeled_error_with_secondary( return Err(ShellError::labeled_error_with_secondary(
"Could not parse as TOML", "Could not parse as TOML",
"input cannot be parsed as TOML", "input cannot be parsed as TOML",
&tag, &tag,
@ -93,10 +93,8 @@ pub fn from_toml(
concat_string.tag, concat_string.tag,
)) ))
} }
} },
}; )
Ok(stream.to_output_stream())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -5,7 +5,6 @@ use ical::parser::vcard::component::*;
use ical::property::Property; use ical::property::Property;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value}; use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
use std::io::BufReader;
pub struct FromVcf; pub struct FromVcf;
@ -42,28 +41,20 @@ async fn from_vcf(
let input = args.input; let input = args.input;
let input_string = input.collect_string(tag.clone()).await?.item; let input_string = input.collect_string(tag.clone()).await?.item;
let input_bytes = input_string.as_bytes(); let input_bytes = input_string.into_bytes();
let buf_reader = BufReader::new(input_bytes); let cursor = std::io::Cursor::new(input_bytes);
let parser = ical::VcardParser::new(buf_reader); 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 { Ok(futures::stream::iter(iter).to_output_stream())
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())
} }
fn contact_to_value(contact: VcardContact, tag: Tag) -> Value { fn contact_to_value(contact: VcardContact, tag: Tag) -> Value {

View File

@ -36,62 +36,66 @@ impl WholeStreamCommand for FromXLSX {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
from_xlsx(args, registry) from_xlsx(args, registry).await
} }
} }
fn from_xlsx(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn from_xlsx(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let (
let (FromXLSXArgs { headerless: _headerless }, mut input) = args.process(&registry).await?; FromXLSXArgs {
let value = input.collect_binary(tag.clone()).await?; headerless: _headerless,
},
input,
) = args.process(&registry).await?;
let value = input.collect_binary(tag.clone()).await?;
let mut buf: Cursor<Vec<u8>> = Cursor::new(value.item); let buf: Cursor<Vec<u8>> = Cursor::new(value.item);
let mut xls = Xlsx::<_>::new(buf).map_err(|_| { let mut xls = Xlsx::<_>::new(buf).map_err(|_| {
ShellError::labeled_error("Could not load xlsx file", "could not load xlsx file", &tag) ShellError::labeled_error("Could not load xlsx file", "could not load xlsx file", &tag)
})?; })?;
let mut dict = TaggedDictBuilder::new(&tag); let mut dict = TaggedDictBuilder::new(&tag);
let sheet_names = xls.sheet_names().to_owned(); let sheet_names = xls.sheet_names().to_owned();
for sheet_name in &sheet_names { for sheet_name in &sheet_names {
let mut sheet_output = TaggedListBuilder::new(&tag); let mut sheet_output = TaggedListBuilder::new(&tag);
if let Some(Ok(current_sheet)) = xls.worksheet_range(sheet_name) { if let Some(Ok(current_sheet)) = xls.worksheet_range(sheet_name) {
for row in current_sheet.rows() { for row in current_sheet.rows() {
let mut row_output = TaggedDictBuilder::new(&tag); let mut row_output = TaggedDictBuilder::new(&tag);
for (i, cell) in row.iter().enumerate() { for (i, cell) in row.iter().enumerate() {
let value = match cell { let value = match cell {
DataType::Empty => UntaggedValue::nothing(), DataType::Empty => UntaggedValue::nothing(),
DataType::String(s) => UntaggedValue::string(s), DataType::String(s) => UntaggedValue::string(s),
DataType::Float(f) => UntaggedValue::decimal(*f), DataType::Float(f) => UntaggedValue::decimal(*f),
DataType::Int(i) => UntaggedValue::int(*i), DataType::Int(i) => UntaggedValue::int(*i),
DataType::Bool(b) => UntaggedValue::boolean(*b), DataType::Bool(b) => UntaggedValue::boolean(*b),
_ => UntaggedValue::nothing(), _ => UntaggedValue::nothing(),
}; };
row_output.insert_untagged(&format!("Column{}", i), value); row_output.insert_untagged(&format!("Column{}", i), value);
}
sheet_output.push_untagged(row_output.into_untagged_value());
} }
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value()); sheet_output.push_untagged(row_output.into_untagged_value());
} else {
yield Err(ShellError::labeled_error(
"Could not load sheet",
"could not load sheet",
&tag,
));
} }
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value());
} else {
return Err(ShellError::labeled_error(
"Could not load sheet",
"could not load sheet",
&tag,
));
} }
}
yield ReturnSuccess::value(dict.into_value()); Ok(OutputStream::one(ReturnSuccess::value(dict.into_value())))
};
Ok(stream.to_output_stream())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -24,7 +24,7 @@ impl WholeStreamCommand for FromXML {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
from_xml(args, registry) from_xml(args, registry).await
} }
} }
@ -99,37 +99,38 @@ pub fn from_xml_string_to_value(s: String, tag: impl Into<Tag>) -> Result<Value,
Ok(from_document_to_value(&parsed, tag)) Ok(from_document_to_value(&parsed, tag))
} }
fn from_xml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn from_xml(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let args = args.evaluate_once(&registry).await?;
let args = args.evaluate_once(&registry).await?; let tag = args.name_tag();
let tag = args.name_tag(); let input = args.input;
let input = args.input;
let concat_string = input.collect_string(tag.clone()).await?; let concat_string = input.collect_string(tag.clone()).await?;
Ok(
match from_xml_string_to_value(concat_string.item, tag.clone()) { match from_xml_string_to_value(concat_string.item, tag.clone()) {
Ok(x) => match x { Ok(x) => match x {
Value { value: UntaggedValue::Table(list), .. } => { Value {
for l in list { value: UntaggedValue::Table(list),
yield ReturnSuccess::value(l); ..
} } => futures::stream::iter(list.into_iter().map(ReturnSuccess::value))
} .to_output_stream(),
x => yield ReturnSuccess::value(x), x => OutputStream::one(ReturnSuccess::value(x)),
}, },
Err(_) => { Err(_) => {
yield Err(ShellError::labeled_error_with_secondary( return Err(ShellError::labeled_error_with_secondary(
"Could not parse as XML", "Could not parse as XML",
"input cannot be parsed as XML", "input cannot be parsed as XML",
&tag, &tag,
"value originates from here", "value originates from here",
&concat_string.tag, &concat_string.tag,
)) ))
} , }
} },
}; )
Ok(stream.to_output_stream())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -59,27 +59,19 @@ fn convert_yaml_value_to_nu_value(
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
let tag = tag.into(); let tag = tag.into();
let err_not_compatible_number = ShellError::labeled_error(
"Expected a compatible number",
"expected a compatible number",
&tag,
);
Ok(match v { Ok(match v {
serde_yaml::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(tag), serde_yaml::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(tag),
serde_yaml::Value::Number(n) if n.is_i64() => { serde_yaml::Value::Number(n) if n.is_i64() => {
UntaggedValue::int(n.as_i64().ok_or_else(|| { UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag)
ShellError::labeled_error(
"Expected a compatible number",
"expected a compatible number",
&tag,
)
})?)
.into_value(tag)
} }
serde_yaml::Value::Number(n) if n.is_f64() => { serde_yaml::Value::Number(n) if n.is_f64() => {
UntaggedValue::decimal(n.as_f64().ok_or_else(|| { UntaggedValue::decimal(n.as_f64().ok_or_else(|| err_not_compatible_number)?)
ShellError::labeled_error( .into_value(tag)
"Expected a compatible number",
"expected a compatible number",
&tag,
)
})?)
.into_value(tag)
} }
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag), serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
serde_yaml::Value::Sequence(a) => { serde_yaml::Value::Sequence(a) => {
@ -93,11 +85,39 @@ fn convert_yaml_value_to_nu_value(
let mut collected = TaggedDictBuilder::new(&tag); let mut collected = TaggedDictBuilder::new(&tag);
for (k, v) in t.iter() { for (k, v) in t.iter() {
match k { // A ShellError that we re-use multiple times in the Mapping scenario
serde_yaml::Value::String(k) => { let err_unexpected_map = ShellError::labeled_error(
format!("Unexpected YAML:\nKey: {:?}\nValue: {:?}", k, v),
"unexpected",
tag.clone(),
);
match (k, v) {
(serde_yaml::Value::String(k), _) => {
collected.insert_value(k.clone(), convert_yaml_value_to_nu_value(v, &tag)?); collected.insert_value(k.clone(), convert_yaml_value_to_nu_value(v, &tag)?);
} }
_ => unimplemented!("Unknown key type"), // Hard-code fix for cases where "v" is a string without quotations with double curly braces
// e.g. k = value
// value: {{ something }}
// Strangely, serde_yaml returns
// "value" -> Mapping(Mapping { map: {Mapping(Mapping { map: {String("something"): Null} }): Null} })
(serde_yaml::Value::Mapping(m), serde_yaml::Value::Null) => {
return m
.iter()
.take(1)
.collect_vec()
.first()
.and_then(|e| match e {
(serde_yaml::Value::String(s), serde_yaml::Value::Null) => Some(
UntaggedValue::string("{{ ".to_owned() + &s + " }}")
.into_value(tag),
),
_ => None,
})
.ok_or(err_unexpected_map);
}
(_, _) => {
return Err(err_unexpected_map);
}
} }
} }
@ -151,7 +171,9 @@ async fn from_yaml(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::FromYAML; use super::*;
use nu_plugin::row;
use nu_plugin::test_helpers::value::string;
#[test] #[test]
fn examples_work_as_expected() { fn examples_work_as_expected() {
@ -159,4 +181,38 @@ mod tests {
test_examples(FromYAML {}) test_examples(FromYAML {})
} }
#[test]
fn test_problematic_yaml() {
struct TestCase {
description: &'static str,
input: &'static str,
expected: Result<Value, ShellError>,
}
let tt: Vec<TestCase> = vec![
TestCase {
description: "Double Curly Braces With Quotes",
input: r#"value: "{{ something }}""#,
expected: Ok(row!["value".to_owned() => string("{{ something }}")]),
},
TestCase {
description: "Double Curly Braces Without Quotes",
input: r#"value: {{ something }}"#,
expected: Ok(row!["value".to_owned() => string("{{ something }}")]),
},
];
for tc in tt.into_iter() {
let actual = from_yaml_string_to_value(tc.input.to_owned(), Tag::default());
if actual.is_err() {
assert!(
tc.expected.is_err(),
"actual is Err for test:\nTest Description {}\nErr: {:?}",
tc.description,
actual
);
} else {
assert_eq!(actual, tc.expected, "{}", tc.description);
}
}
}
} }

View File

@ -39,7 +39,7 @@ impl WholeStreamCommand for Get {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
get(args, registry) get(args, registry).await
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -192,21 +192,24 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
) )
} }
pub fn get(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { pub async fn get(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let (GetArgs { rest: mut fields }, mut input) = args.process(&registry).await?;
let (GetArgs { rest: mut fields }, mut input) = args.process(&registry).await?; if fields.is_empty() {
if fields.is_empty() { let vec = input.drain_vec().await;
let mut vec = input.drain_vec().await;
let descs = nu_protocol::merge_descriptors(&vec); let descs = nu_protocol::merge_descriptors(&vec);
for desc in descs {
yield ReturnSuccess::value(desc); Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
} } else {
} else { let member = fields.remove(0);
let member = fields.remove(0); trace!("get {:?} {:?}", member, fields);
trace!("get {:?} {:?}", member, fields);
while let Some(item) = input.next().await { Ok(input
.map(move |item| {
let member = vec![member.clone()]; let member = vec![member.clone()];
let column_paths = vec![&member, &fields] let column_paths = vec![&member, &fields]
@ -214,6 +217,7 @@ pub fn get(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
.flatten() .flatten()
.collect::<Vec<&ColumnPath>>(); .collect::<Vec<&ColumnPath>>();
let mut output = vec![];
for path in column_paths { for path in column_paths {
let res = get_column_path(&path, &item); let res = get_column_path(&path, &item);
@ -224,24 +228,26 @@ pub fn get(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
.. ..
} => { } => {
for item in rows { for item in rows {
yield ReturnSuccess::value(item.clone()); output.push(ReturnSuccess::value(item.clone()));
} }
} }
Value { Value {
value: UntaggedValue::Primitive(Primitive::Nothing), value: UntaggedValue::Primitive(Primitive::Nothing),
.. ..
} => {} } => {}
other => yield ReturnSuccess::value(other.clone()), other => output.push(ReturnSuccess::value(other.clone())),
}, },
Err(reason) => yield ReturnSuccess::value( Err(reason) => output.push(ReturnSuccess::value(
UntaggedValue::Error(reason).into_untagged_value(), UntaggedValue::Error(reason).into_untagged_value(),
), )),
} }
} }
}
} futures::stream::iter(output)
}; })
Ok(stream.to_output_stream()) .flatten()
.to_output_stream())
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -4,12 +4,13 @@ use indexmap::indexmap;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged; use nu_source::Tagged;
use nu_value_ext::as_string;
pub struct GroupBy; pub struct GroupBy;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct GroupByArgs { pub struct GroupByArgs {
column_name: Option<Tagged<String>>, grouper: Option<Value>,
} }
#[async_trait] #[async_trait]
@ -20,14 +21,14 @@ impl WholeStreamCommand for GroupBy {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("group-by").optional( Signature::build("group-by").optional(
"column_name", "grouper",
SyntaxShape::String, SyntaxShape::Any,
"the name of the column to group by", "the grouper value to use",
) )
} }
fn usage(&self) -> &str { 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( async fn run(
@ -41,12 +42,17 @@ impl WholeStreamCommand for GroupBy {
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
description: "Group items by type", description: "group items by column named \"type\"",
example: r#"ls | group-by type"#, example: r#"ls | group-by type"#,
result: None, result: None,
}, },
Example { 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", example: "echo [1 3 1 3 2 1 1] | group-by",
result: Some(vec![UntaggedValue::row(indexmap! { result: Some(vec![UntaggedValue::row(indexmap! {
"1".to_string() => UntaggedValue::Table(vec![ "1".to_string() => UntaggedValue::Table(vec![
@ -67,44 +73,190 @@ impl WholeStreamCommand for GroupBy {
}) })
.into()]), .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( pub async fn group_by(
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.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(Context::from_raw(&args, &registry));
let (GroupByArgs { grouper }, input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().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() { if values.is_empty() {
Err(ShellError::labeled_error( return Err(ShellError::labeled_error(
"Expected table from pipeline", "expected table from pipeline",
"requires a table input", "requires a table input",
name, name,
)) ));
} else { }
match crate::utils::data::group(column_name, &values, None, &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))), let values = UntaggedValue::table(&values).into_value(&name);
Err(err) => Err(err),
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),
});
match crate::utils::data::group(&values, &Some(block), &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(reason) => Err(reason),
}
} }
Grouper::ByColumn(column_name) => match group(&column_name, &values, name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(reason) => Err(reason),
},
}
}
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
let possibilities = for_value.data_descriptors();
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|x| (natural::distance::levenshtein_distance(x, &tried), x))
.collect();
possible_matches.sort();
if !possible_matches.is_empty() {
ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", possible_matches[0].1),
tried.tag(),
)
} else {
ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
tried.tag(),
)
} }
} }
pub fn group( pub fn group(
column_name: &Tagged<String>, column_name: &Option<Tagged<String>>,
values: Vec<Value>, values: &Value,
tag: impl Into<Tag>, tag: impl Into<Tag>,
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
crate::utils::data::group(Some(column_name.clone()), &values, None, tag) let name = tag.into();
let grouper = if let Some(column_name) = column_name {
Grouper::ByColumn(Some(column_name.clone()))
} else {
Grouper::ByColumn(None)
};
match grouper {
Grouper::ByColumn(Some(column_name)) => {
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)
}
Grouper::ByColumn(None) => {
let block = Box::new(move |_, row: &Value| match as_string(row) {
Ok(group_key) => Ok(group_key),
Err(reason) => Err(reason),
});
crate::utils::data::group(&values, &Some(block), &name)
}
Grouper::ByBlock => Err(ShellError::unimplemented(
"Block not implemented: This should never happen.",
)),
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::commands::group_by::group; use super::group;
use indexmap::IndexMap; use indexmap::IndexMap;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value}; use nu_protocol::{UntaggedValue, Value};
@ -122,7 +274,7 @@ mod tests {
UntaggedValue::table(list).into_untagged_value() UntaggedValue::table(list).into_untagged_value()
} }
fn nu_releases_commiters() -> Vec<Value> { fn nu_releases_committers() -> Vec<Value> {
vec![ vec![
row( row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}, indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")},
@ -156,10 +308,11 @@ mod tests {
#[test] #[test]
fn groups_table_by_date_column() -> Result<(), ShellError> { fn groups_table_by_date_column() -> Result<(), ShellError> {
let for_key = String::from("date").tagged_unknown(); let for_key = Some(String::from("date").tagged_unknown());
let sample = table(&nu_releases_committers());
assert_eq!( assert_eq!(
group(&for_key, nu_releases_commiters(), Tag::unknown())?, group(&for_key, &sample, Tag::unknown())?,
row(indexmap! { row(indexmap! {
"August 23-2019".into() => table(&[ "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("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}),
@ -184,10 +337,11 @@ mod tests {
#[test] #[test]
fn groups_table_by_country_column() -> Result<(), ShellError> { fn groups_table_by_country_column() -> Result<(), ShellError> {
let for_key = String::from("country").tagged_unknown(); let for_key = Some(String::from("country").tagged_unknown());
let sample = table(&nu_releases_committers());
assert_eq!( assert_eq!(
group(&for_key, nu_releases_commiters(), Tag::unknown())?, group(&for_key, &sample, Tag::unknown())?,
row(indexmap! { row(indexmap! {
"EC".into() => table(&[ "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("August 23-2019")}),

View File

@ -1,7 +1,7 @@
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged; use nu_source::Tagged;
pub struct GroupByDate; pub struct GroupByDate;
@ -34,7 +34,7 @@ impl WholeStreamCommand for GroupByDate {
} }
fn usage(&self) -> &str { 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( async fn run(
@ -55,7 +55,11 @@ impl WholeStreamCommand for GroupByDate {
} }
enum Grouper { enum Grouper {
ByDate(Option<String>), ByDate(Option<Tagged<String>>),
}
enum GroupByColumn {
Name(Option<Tagged<String>>),
} }
pub async fn group_by_date( pub async fn group_by_date(
@ -80,36 +84,83 @@ pub async fn group_by_date(
name, name,
)) ))
} else { } else {
let grouper = if let Some(Tagged { item: fmt, tag: _ }) = format { let values = UntaggedValue::table(&values).into_value(&name);
Grouper::ByDate(Some(fmt))
let grouper_column = if let Some(column_name) = column_name {
GroupByColumn::Name(Some(column_name))
} else {
GroupByColumn::Name(None)
};
let grouper_date = if let Some(date_format) = format {
Grouper::ByDate(Some(date_format))
} else { } else {
Grouper::ByDate(None) Grouper::ByDate(None)
}; };
match grouper { let value_result = match (grouper_date, grouper_column) {
Grouper::ByDate(None) => { (Grouper::ByDate(None), GroupByColumn::Name(None)) => {
match crate::utils::data::group( let block = Box::new(move |_, row: &Value| row.format("%Y-%b-%d"));
column_name,
&values, crate::utils::data::group(&values, &Some(block), &name)
Some(Box::new(|row: &Value| row.format("%Y-%b-%d"))),
&name,
) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
} }
Grouper::ByDate(Some(fmt)) => { (Grouper::ByDate(None), GroupByColumn::Name(Some(column_name))) => {
match crate::utils::data::group( let block = Box::new(move |_, row: &Value| {
column_name, let group_key = match row.get_data_by_key(column_name.borrow_spanned()) {
&values, Some(group_key) => Ok(group_key),
Some(Box::new(move |row: &Value| row.format(&fmt))), None => Err(suggestions(column_name.borrow_tagged(), &row)),
&name, };
) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))), group_key?.format("%Y-%b-%d")
Err(err) => Err(err), });
}
crate::utils::data::group(&values, &Some(block), &name)
} }
} (Grouper::ByDate(Some(fmt)), GroupByColumn::Name(None)) => {
let block = Box::new(move |_, row: &Value| row.format(&fmt));
crate::utils::data::group(&values, &Some(block), &name)
}
(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)),
};
group_key?.format(&fmt)
});
crate::utils::data::group(&values, &Some(block), &name)
}
};
Ok(OutputStream::one(ReturnSuccess::value(value_result?)))
}
}
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
let possibilities = for_value.data_descriptors();
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|x| (natural::distance::levenshtein_distance(x, &tried), x))
.collect();
possible_matches.sort();
if !possible_matches.is_empty() {
ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", possible_matches[0].1),
tried.tag(),
)
} else {
ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
tried.tag(),
)
} }
} }

View File

@ -28,7 +28,7 @@ impl WholeStreamCommand for Headers {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
headers(args, registry) headers(args, registry).await
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -40,51 +40,65 @@ impl WholeStreamCommand for Headers {
} }
} }
pub fn headers(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> { pub async fn headers(
let stream = async_stream! { args: CommandArgs,
let mut input = args.input; _registry: &CommandRegistry,
let rows: Vec<Value> = input.collect().await; ) -> Result<OutputStream, ShellError> {
let input = args.input;
let rows: Vec<Value> = input.collect().await;
if rows.len() < 1 { if rows.is_empty() {
yield Err(ShellError::untagged_runtime_error("Couldn't find headers, was the input a properly formatted, non-empty table?")); return Err(ShellError::untagged_runtime_error(
} "Couldn't find headers, was the input a properly formatted, non-empty table?",
));
}
//the headers are the first row in the table //the headers are the first row in the table
let headers: Vec<String> = match &rows[0].value { let headers: Vec<String> = match &rows[0].value {
UntaggedValue::Row(d) => { UntaggedValue::Row(d) => {
Ok(d.entries.iter().map(|(k, v)| { Ok(d.entries
.iter()
.map(|(k, v)| {
match v.as_string() { match v.as_string() {
Ok(s) => s, Ok(s) => s,
Err(_) => { //If a cell that should contain a header name is empty, we name the column Column[index] Err(_) => {
//If a cell that should contain a header name is empty, we name the column Column[index]
match d.entries.get_full(k) { match d.entries.get_full(k) {
Some((index, _, _)) => format!("Column{}", index), Some((index, _, _)) => format!("Column{}", index),
None => "unknownColumn".to_string() None => "unknownColumn".to_string(),
} }
} }
} }
}).collect()) })
} .collect())
_ => Err(ShellError::unexpected_eof("Could not get headers, is the table empty?", rows[0].tag.span)) }
}?; _ => Err(ShellError::unexpected_eof(
"Could not get headers, is the table empty?",
rows[0].tag.span,
)),
}?;
//Each row is a dictionary with the headers as keys Ok(
for r in rows.iter().skip(1) { futures::stream::iter(rows.into_iter().skip(1).map(move |r| {
//Each row is a dictionary with the headers as keys
match &r.value { match &r.value {
UntaggedValue::Row(d) => { UntaggedValue::Row(d) => {
let mut i = 0;
let mut entries = IndexMap::new(); let mut entries = IndexMap::new();
for (_, v) in d.entries.iter() { for (i, (_, v)) in d.entries.iter().enumerate() {
entries.insert(headers[i].clone(), v.clone()); entries.insert(headers[i].clone(), v.clone());
i += 1;
} }
yield Ok(ReturnSuccess::Value(UntaggedValue::Row(Dictionary{entries}).into_value(r.tag.clone()))) Ok(ReturnSuccess::Value(
UntaggedValue::Row(Dictionary { entries }).into_value(r.tag.clone()),
))
} }
_ => yield Err(ShellError::unexpected_eof("Couldn't iterate through rows, was the input a properly formatted table?", r.tag.span)) _ => Err(ShellError::unexpected_eof(
"Couldn't iterate through rows, was the input a properly formatted table?",
r.tag.span,
)),
} }
} }))
}; .to_output_stream(),
)
Ok(stream.to_output_stream())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -1,12 +1,10 @@
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::data::command_dict; use crate::data::command_dict;
use crate::documentation::{generate_docs, get_documentation, DocumentationConfig};
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue};
NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder,
UntaggedValue,
};
use nu_source::{SpannedItem, Tagged}; use nu_source::{SpannedItem, Tagged};
use nu_value_ext::get_data_by_key; use nu_value_ext::get_data_by_key;
@ -36,70 +34,97 @@ impl WholeStreamCommand for Help {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
help(args, registry) help(args, registry).await
} }
} }
fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let stream = async_stream! { let (HelpArgs { rest }, ..) = args.process(&registry).await?;
let (HelpArgs { rest }, mut input) = args.process(&registry).await?;
if let Some(document) = rest.get(0) { if !rest.is_empty() {
if document.item == "commands" { if rest[0].item == "commands" {
let mut sorted_names = registry.names(); let mut sorted_names = registry.names();
sorted_names.sort(); sorted_names.sort();
for cmd in sorted_names {
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 it's a subcommand, don't list it during the commands list
if cmd.contains(' ') { if cmd.contains(' ') {
continue; return None;
} }
let mut short_desc = TaggedDictBuilder::new(name.clone()); let mut short_desc = TaggedDictBuilder::new(name.clone());
let document_tag = document.tag.clone(); let document_tag = rest[0].tag.clone();
let value = command_dict( let value = command_dict(
registry.get_command(&cmd).ok_or_else(|| { match registry.get_command(&cmd).ok_or_else(|| {
ShellError::labeled_error( ShellError::labeled_error(
format!("Could not load {}", cmd), format!("Could not load {}", cmd),
"could not load command", "could not load command",
document_tag, document_tag,
) )
})?, }) {
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
},
name.clone(), name.clone(),
); );
short_desc.insert_untagged("name", cmd); short_desc.insert_untagged("name", cmd);
short_desc.insert_untagged( short_desc.insert_untagged(
"description", "description",
get_data_by_key(&value, "usage".spanned_unknown()) match match get_data_by_key(&value, "usage".spanned_unknown()).ok_or_else(
.ok_or_else(|| { || {
ShellError::labeled_error( ShellError::labeled_error(
"Expected a usage key", "Expected a usage key",
"expected a 'usage' key", "expected a 'usage' key",
&value.tag, &value.tag,
) )
})? },
.as_string()?, ) {
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
}
.as_string()
{
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
},
); );
yield ReturnSuccess::value(short_desc.into_value()); Some(ReturnSuccess::value(short_desc.into_value()))
} }))
} else if rest.len() == 2 { .to_output_stream(),
// Check for a subcommand )
let command_name = format!("{} {}", rest[0].item, rest[1].item); } else if rest[0].item == "generate_docs" {
if let Some(command) = registry.get_command(&command_name) { Ok(OutputStream::one(ReturnSuccess::value(generate_docs(
yield Ok(ReturnSuccess::Value(UntaggedValue::string(get_help(command.stream_command(), &registry)).into_value(Tag::unknown()))); &registry,
} ))))
} else if let Some(command) = registry.get_command(&document.item) { } else if rest.len() == 2 {
yield Ok(ReturnSuccess::Value(UntaggedValue::string(get_help(command.stream_command(), &registry)).into_value(Tag::unknown()))); // Check for a subcommand
let command_name = format!("{} {}", rest[0].item, rest[1].item);
if let Some(command) = registry.get_command(&command_name) {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(command.stream_command(), &registry))
.into_value(Tag::unknown()),
)))
} else { } else {
yield Err(ShellError::labeled_error( Ok(OutputStream::empty())
"Can't find command (use 'help commands' for full list)",
"can't find command",
document.tag.span,
));
} }
} else if let Some(command) = registry.get_command(&rest[0].item) {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(command.stream_command(), &registry))
.into_value(Tag::unknown()),
)))
} else { } else {
let msg = r#"Welcome to Nushell. Err(ShellError::labeled_error(
"Can't find command (use 'help commands' for full list)",
"can't find command",
rest[0].tag.span,
))
}
} else {
let msg = r#"Welcome to Nushell.
Here are some tips to help you get started. Here are some tips to help you get started.
* help commands - list all available commands * help commands - list all available commands
@ -121,172 +146,14 @@ Get the processes on your system actively using CPU:
You can also learn more at https://www.nushell.sh/book/"#; You can also learn more at https://www.nushell.sh/book/"#;
yield Ok(ReturnSuccess::Value(UntaggedValue::string(msg).into_value(Tag::unknown()))); Ok(OutputStream::one(ReturnSuccess::value(
} UntaggedValue::string(msg).into_value(Tag::unknown()),
}; )))
}
Ok(stream.to_output_stream())
} }
#[allow(clippy::cognitive_complexity)]
pub fn get_help(cmd: &dyn WholeStreamCommand, registry: &CommandRegistry) -> String { pub fn get_help(cmd: &dyn WholeStreamCommand, registry: &CommandRegistry) -> String {
let cmd_name = cmd.name(); get_documentation(cmd, registry, &DocumentationConfig::default())
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
} }
#[cfg(test)] #[cfg(test)]

View File

@ -45,7 +45,7 @@ impl WholeStreamCommand for Histogram {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
histogram(args, registry) histogram(args, registry).await
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -70,95 +70,136 @@ impl WholeStreamCommand for Histogram {
} }
} }
pub fn histogram( pub async fn histogram(
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let stream = async_stream! {
let (HistogramArgs { column_name, rest}, mut input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
let Tagged { item: group_by, .. } = column_name.clone(); 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(&column_name, values, &name)?; let groups = group(&Some(column_name.clone()), &values, &name)?;
let group_labels = columns_sorted(Some(group_by.clone()), &groups, &name); let group_labels = columns_sorted(Some(column_name.clone()), &groups, &name);
let sorted = t_sort(Some(group_by.clone()), None, &groups, &name)?; let sorted = t_sort(Some(column_name.clone()), None, &groups, &name)?;
let evaled = evaluate(&sorted, None, &name)?; let evaled = evaluate(&sorted, None, &name)?;
let reduced = reduce(&evaled, None, &name)?; let reduced = reduce(&evaled, None, &name)?;
let maxima = map_max(&reduced, None, &name)?; let maxima = map_max(&reduced, None, &name)?;
let percents = percentages(&reduced, maxima, &name)?; let percents = percentages(&reduced, maxima, &name)?;
match percents { match percents {
Value { Value {
value: UntaggedValue::Table(datasets), value: UntaggedValue::Table(datasets),
.. ..
} => { } => {
let mut idx = 0;
let mut idx = 0; let column_names_supplied: Vec<_> = rest.iter().map(|f| f.item.clone()).collect();
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 frequency_column_name = if column_names_supplied.is_empty() { let column = (*column_name).clone();
"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();
let count_column_name = "count".to_string(); for table_entry in reduced.table_entries() {
let count_shell_error = ShellError::labeled_error("Unable to load group count", "unabled to load group count", &name); match table_entry {
let mut count_values: Vec<u64> = Vec::new(); Value {
value: UntaggedValue::Table(list),
for table_entry in reduced.table_entries() { ..
match table_entry { } => {
Value { for i in list {
value: UntaggedValue::Table(list), if let Ok(count) = i.value.clone().into_value(&name).as_u64() {
.. count_values.push(count);
} => { } else {
for i in list { return Err(count_shell_error);
if let Ok(count) = i.value.clone().into_value(&name).as_u64() {
count_values.push(count);
} else {
yield Err(count_shell_error);
return;
}
} }
} }
_ => { }
yield Err(count_shell_error); _ => {
return; 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))? { if let Value {
for percentage in start.iter() { 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 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(); let value: Tagged<String> = group_labels
fact.insert_value(&column, UntaggedValue::string(value.item).into_value(value.tag)); .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])); fact.insert_untagged(
&count_column_name,
UntaggedValue::int(count_values[idx]),
);
if let Value { value: UntaggedValue::Primitive(Primitive::Int(ref num)), ref tag } = percentage.clone() { if let Value {
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>(); value: UntaggedValue::Primitive(Primitive::Int(ref num)),
fact.insert_untagged(&frequency_column_name, UntaggedValue::string(string)); 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; idx += 1;
yield ReturnSuccess::value(fact.into_value()); ReturnSuccess::value(fact.into_value())
} }))
} .to_output_stream(),
)
} else {
Ok(OutputStream::empty())
} }
_ => {}
} }
}; _ => Ok(OutputStream::empty()),
}
Ok(stream.to_output_stream())
} }
fn percentages(values: &Value, max: Value, tag: impl Into<Tag>) -> Result<Value, ShellError> { fn percentages(values: &Value, max: Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {

View File

@ -33,21 +33,25 @@ impl WholeStreamCommand for History {
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> { fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag; let tag = args.call_info.name_tag;
let stream = async_stream! { let history_path = HistoryFile::path();
let history_path = HistoryFile::path(); let file = File::open(history_path);
let file = File::open(history_path); if let Ok(file) = file {
if let Ok(file) = file { let reader = BufReader::new(file);
let reader = BufReader::new(file); let output = reader.lines().filter_map(move |line| match line {
for line in reader.lines() { Ok(line) => Some(ReturnSuccess::value(
if let Ok(line) = line { UntaggedValue::string(line).into_value(tag.clone()),
yield ReturnSuccess::value(UntaggedValue::string(line).into_value(tag.clone())); )),
} Err(_) => None,
} });
} else {
yield Err(ShellError::labeled_error("Could not open history", "history file could not be opened", tag.clone())); Ok(futures::stream::iter(output).to_output_stream())
} } else {
}; Err(ShellError::labeled_error(
Ok(stream.to_output_stream()) "Could not open history",
"history file could not be opened",
tag,
))
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -0,0 +1,188 @@
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
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(Context::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

@ -42,38 +42,31 @@ impl WholeStreamCommand for Insert {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
insert(args, registry) insert(args, registry).await
} }
} }
fn insert(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn insert(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let (InsertArgs { column, value }, input) = args.process(&registry).await?;
let (InsertArgs { column, value }, mut input) = args.process(&registry).await?;
while let Some(row) = input.next().await {
match row {
Value {
value: UntaggedValue::Row(_),
..
} => match row.insert_data_at_column_path(&column, value.clone()) {
Ok(v) => yield Ok(ReturnSuccess::Value(v)),
Err(err) => yield Err(err),
},
Value { tag, ..} => { Ok(input
yield Err(ShellError::labeled_error( .map(move |row| match row {
"Unrecognized type in stream", Value {
"original value", value: UntaggedValue::Row(_),
tag, ..
)); } => Ok(ReturnSuccess::Value(
} row.insert_data_at_column_path(&column, value.clone())?,
)),
} Value { tag, .. } => Err(ShellError::labeled_error(
}; "Unrecognized type in stream",
"original value",
}; tag,
Ok(stream.to_output_stream()) )),
})
.to_output_stream())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -42,15 +42,19 @@ impl WholeStreamCommand for IsEmpty {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
is_empty(args, registry) is_empty(args, registry).await
} }
} }
fn is_empty(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn is_empty(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let (IsEmptyArgs { rest }, input) = args.process(&registry).await?;
let (IsEmptyArgs { rest }, mut input) = args.process(&registry).await?;
while let Some(value) = input.next().await { Ok(input
.map(move |value| {
let value_tag = value.tag(); let value_tag = value.tag();
let action = if rest.len() <= 2 { let action = if rest.len() <= 2 {
@ -85,15 +89,14 @@ fn is_empty(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
}; };
match action { match action {
IsEmptyFor::Value => yield Ok(ReturnSuccess::Value( IsEmptyFor::Value => Ok(ReturnSuccess::Value(
UntaggedValue::boolean(value.is_empty()).into_value(value_tag), UntaggedValue::boolean(value.is_empty()).into_value(value_tag),
)), )),
IsEmptyFor::RowWithFieldsAndFallback(fields, default) => { IsEmptyFor::RowWithFieldsAndFallback(fields, default) => {
let mut out = value; let mut out = value;
for field in fields.iter() { for field in fields.iter() {
let val = let val = crate::commands::get::get_column_path(&field, &out)?;
crate::commands::get::get_column_path(&field, &out)?;
let emptiness_value = match out { let emptiness_value = match out {
obj obj
@ -125,11 +128,10 @@ fn is_empty(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
out = emptiness_value?; out = emptiness_value?;
} }
yield Ok(ReturnSuccess::Value(out)) Ok(ReturnSuccess::Value(out))
} }
IsEmptyFor::RowWithField(field) => { IsEmptyFor::RowWithField(field) => {
let val = let val = crate::commands::get::get_column_path(&field, &value)?;
crate::commands::get::get_column_path(&field, &value)?;
match &value { match &value {
obj obj
@ -143,18 +145,18 @@ fn is_empty(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
&field, &field,
UntaggedValue::boolean(true).into_value(&value_tag), UntaggedValue::boolean(true).into_value(&value_tag),
) { ) {
Some(v) => yield Ok(ReturnSuccess::Value(v)), Some(v) => Ok(ReturnSuccess::Value(v)),
None => yield Err(ShellError::labeled_error( None => Err(ShellError::labeled_error(
"empty? could not find place to check emptiness", "empty? could not find place to check emptiness",
"column name", "column name",
&field.tag, &field.tag,
)), )),
} }
} else { } else {
yield Ok(ReturnSuccess::Value(value)) Ok(ReturnSuccess::Value(value))
} }
} }
_ => yield Err(ShellError::labeled_error( _ => Err(ShellError::labeled_error(
"Unrecognized type in stream", "Unrecognized type in stream",
"original value", "original value",
&value_tag, &value_tag,
@ -162,8 +164,7 @@ fn is_empty(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
} }
} }
IsEmptyFor::RowWithFieldAndFallback(field, default) => { IsEmptyFor::RowWithFieldAndFallback(field, default) => {
let val = let val = crate::commands::get::get_column_path(&field, &value)?;
crate::commands::get::get_column_path(&field, &value)?;
match &value { match &value {
obj obj
@ -174,18 +175,18 @@ fn is_empty(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
} => { } => {
if val.is_empty() { if val.is_empty() {
match obj.replace_data_at_column_path(&field, default) { match obj.replace_data_at_column_path(&field, default) {
Some(v) => yield Ok(ReturnSuccess::Value(v)), Some(v) => Ok(ReturnSuccess::Value(v)),
None => yield Err(ShellError::labeled_error( None => Err(ShellError::labeled_error(
"empty? could not find place to check emptiness", "empty? could not find place to check emptiness",
"column name", "column name",
&field.tag, &field.tag,
)), )),
} }
} else { } else {
yield Ok(ReturnSuccess::Value(value)) Ok(ReturnSuccess::Value(value))
} }
} }
_ => yield Err(ShellError::labeled_error( _ => Err(ShellError::labeled_error(
"Unrecognized type in stream", "Unrecognized type in stream",
"original value", "original value",
&value_tag, &value_tag,
@ -193,9 +194,8 @@ fn is_empty(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
} }
} }
} }
} })
}; .to_output_stream())
Ok(stream.to_output_stream())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -2,18 +2,18 @@ use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry; use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue}; use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged; use nu_source::Tagged;
pub struct Keep; pub struct Command;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct KeepArgs { pub struct Arguments {
rows: Option<Tagged<usize>>, rows: Option<Tagged<usize>>,
} }
#[async_trait] #[async_trait]
impl WholeStreamCommand for Keep { impl WholeStreamCommand for Command {
fn name(&self) -> &str { fn name(&self) -> &str {
"keep" "keep"
} }
@ -22,7 +22,7 @@ impl WholeStreamCommand for Keep {
Signature::build("keep").optional( Signature::build("keep").optional(
"rows", "rows",
SyntaxShape::Int, SyntaxShape::Int,
"starting from the front, the number of rows to keep", "Starting from the front, the number of rows to keep",
) )
} }
@ -35,7 +35,7 @@ impl WholeStreamCommand for Keep {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
keep(args, registry) keep(args, registry).await
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -59,37 +59,26 @@ impl WholeStreamCommand for Keep {
} }
} }
fn keep(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn keep(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let (Arguments { rows }, input) = args.process(&registry).await?;
let (KeepArgs { rows }, mut input) = args.process(&registry).await?; let rows_desired = if let Some(quantity) = rows {
let mut rows_desired = if let Some(quantity) = rows { *quantity
*quantity } else {
} else { 1
1
};
while let Some(input) = input.next().await {
if rows_desired > 0 {
yield ReturnSuccess::value(input);
rows_desired -= 1;
} else {
break;
}
}
}; };
Ok(stream.to_output_stream()) Ok(input.take(rows_desired).to_output_stream())
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Keep; use super::Command;
#[test] #[test]
fn examples_work_as_expected() { fn examples_work_as_expected() {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(Keep {}) test_examples(Command {})
} }
} }

View File

@ -0,0 +1,7 @@
mod command;
mod until;
mod while_;
pub use command::Command as Keep;
pub use until::SubCommand as KeepUntil;
pub use while_::SubCommand as KeepWhile;

View File

@ -0,0 +1,120 @@
use crate::commands::WholeStreamCommand;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"keep until"
}
fn signature(&self) -> Signature {
Signature::build("keep until")
.required(
"condition",
SyntaxShape::Math,
"The condition that must be met to stop keeping rows",
)
.filter()
}
fn usage(&self) -> &str {
"Keeps rows until the condition matches."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = Arc::new(registry.clone());
let scope = Arc::new(args.call_info.scope.clone());
let call_info = args.evaluate_once(&registry).await?;
let block = call_info.args.expect_nth(0)?.clone();
let condition = Arc::new(match block {
Value {
value: UntaggedValue::Block(block),
tag,
} => {
if block.block.len() != 1 {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
match block.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,
));
}
}
}
Value { tag, .. } => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
});
Ok(call_info
.input
.take_while(move |item| {
let condition = condition.clone();
let registry = registry.clone();
let scope = scope.clone();
let item = item.clone();
trace!("ITEM = {:?}", item);
async move {
let result = evaluate_baseline_expr(
&*condition,
&registry,
&item,
&scope.vars,
&scope.env,
)
.await;
trace!("RESULT = {:?}", result);
!matches!(result, Ok(ref v) if v.is_true())
}
})
.to_output_stream())
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,120 @@
use crate::commands::WholeStreamCommand;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"keep while"
}
fn signature(&self) -> Signature {
Signature::build("keep while")
.required(
"condition",
SyntaxShape::Math,
"The condition that must be met to keep rows",
)
.filter()
}
fn usage(&self) -> &str {
"Keeps rows while the condition matches."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = Arc::new(registry.clone());
let scope = Arc::new(args.call_info.scope.clone());
let call_info = args.evaluate_once(&registry).await?;
let block = call_info.args.expect_nth(0)?.clone();
let condition = Arc::new(match block {
Value {
value: UntaggedValue::Block(block),
tag,
} => {
if block.block.len() != 1 {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
match block.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,
));
}
}
}
Value { tag, .. } => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
});
Ok(call_info
.input
.take_while(move |item| {
let condition = condition.clone();
let registry = registry.clone();
let scope = scope.clone();
let item = item.clone();
trace!("ITEM = {:?}", item);
async move {
let result = evaluate_baseline_expr(
&*condition,
&registry,
&item,
&scope.vars,
&scope.env,
)
.await;
trace!("RESULT = {:?}", result);
matches!(result, Ok(ref v) if v.is_true())
}
})
.to_output_stream())
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -1,124 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{
hir::ClassifiedCommand, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
pub struct KeepUntil;
#[async_trait]
impl WholeStreamCommand for KeepUntil {
fn name(&self) -> &str {
"keep-until"
}
fn signature(&self) -> Signature {
Signature::build("keep-until")
.required(
"condition",
SyntaxShape::Math,
"the condition that must be met to stop keeping rows",
)
.filter()
}
fn usage(&self) -> &str {
"Keeps rows until the condition matches."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = args.call_info.scope.clone();
let stream = async_stream! {
let mut call_info = args.evaluate_once(&registry).await?;
let block = call_info.args.expect_nth(0)?.clone();
let condition = match block {
Value {
value: UntaggedValue::Block(block),
tag,
} => {
if block.block.len() != 1 {
yield Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
match block.block[0].list.get(0) {
Some(item) => match item {
ClassifiedCommand::Expr(expr) => expr.clone(),
_ => {
yield Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
},
None => {
yield Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
}
}
Value { tag, .. } => {
yield Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
};
while let Some(item) = call_info.input.next().await {
let condition = condition.clone();
trace!("ITEM = {:?}", item);
let result =
evaluate_baseline_expr(&*condition, &registry, &item, &scope.vars, &scope.env)
.await;
trace!("RESULT = {:?}", result);
let return_value = match result {
Ok(ref v) if v.is_true() => false,
_ => true,
};
if return_value {
yield ReturnSuccess::value(item);
} else {
break;
}
}
};
Ok(stream.to_output_stream())
}
}
#[cfg(test)]
mod tests {
use super::KeepUntil;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(KeepUntil {})
}
}

View File

@ -1,124 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{
hir::ClassifiedCommand, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
pub struct KeepWhile;
#[async_trait]
impl WholeStreamCommand for KeepWhile {
fn name(&self) -> &str {
"keep-while"
}
fn signature(&self) -> Signature {
Signature::build("keep-while")
.required(
"condition",
SyntaxShape::Math,
"the condition that must be met to keep rows",
)
.filter()
}
fn usage(&self) -> &str {
"Keeps rows while the condition matches."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = args.call_info.scope.clone();
let stream = async_stream! {
let mut call_info = args.evaluate_once(&registry).await?;
let block = call_info.args.expect_nth(0)?.clone();
let condition = match block {
Value {
value: UntaggedValue::Block(block),
tag,
} => {
if block.block.len() != 1 {
yield Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
match block.block[0].list.get(0) {
Some(item) => match item {
ClassifiedCommand::Expr(expr) => expr.clone(),
_ => {
yield Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
},
None => {
yield Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
}
}
Value { tag, .. } => {
yield Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
};
while let Some(item) = call_info.input.next().await {
let condition = condition.clone();
trace!("ITEM = {:?}", item);
let result =
evaluate_baseline_expr(&*condition, &registry, &item, &scope.vars, &scope.env)
.await;
trace!("RESULT = {:?}", result);
let return_value = match result {
Ok(ref v) if v.is_true() => true,
_ => false,
};
if return_value {
yield ReturnSuccess::value(item);
} else {
break;
}
}
};
Ok(stream.to_output_stream())
}
}
#[cfg(test)]
mod tests {
use super::KeepWhile;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(KeepWhile {})
}
}

View File

@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry; use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue}; use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged; use nu_source::Tagged;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
@ -43,7 +43,7 @@ impl WholeStreamCommand for Kill {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
kill(args, registry) kill(args, registry).await
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -62,63 +62,60 @@ impl WholeStreamCommand for Kill {
} }
} }
fn kill(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn kill(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let (
let (KillArgs { KillArgs {
pid, pid,
rest, rest,
force, force,
quiet, quiet,
}, mut input) = args.process(&registry).await?; },
let mut cmd = if cfg!(windows) { ..,
let mut cmd = Command::new("taskkill"); ) = args.process(&registry).await?;
let mut cmd = if cfg!(windows) {
let mut cmd = Command::new("taskkill");
if *force { if *force {
cmd.arg("/F"); cmd.arg("/F");
} }
cmd.arg("/PID");
cmd.arg(pid.item().to_string());
// each pid must written as `/PID 0` otherwise
// taskkill will act as `killall` unix command
for id in &rest {
cmd.arg("/PID"); cmd.arg("/PID");
cmd.arg(pid.item().to_string()); cmd.arg(id.item().to_string());
// each pid must written as `/PID 0` otherwise
// taskkill will act as `killall` unix command
for id in &rest {
cmd.arg("/PID");
cmd.arg(id.item().to_string());
}
cmd
} else {
let mut cmd = Command::new("kill");
if *force {
cmd.arg("-9");
}
cmd.arg(pid.item().to_string());
cmd.args(rest.iter().map(move |id| id.item().to_string()));
cmd
};
// pipe everything to null
if *quiet {
cmd.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
} }
cmd.status().expect("failed to execute shell command"); cmd
} else {
let mut cmd = Command::new("kill");
if false { if *force {
yield ReturnSuccess::value(UntaggedValue::nothing().into_value(Tag::unknown())); cmd.arg("-9");
} }
cmd.arg(pid.item().to_string());
cmd.args(rest.iter().map(move |id| id.item().to_string()));
cmd
}; };
Ok(stream.to_output_stream()) // pipe everything to null
if *quiet {
cmd.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
}
cmd.status().expect("failed to execute shell command");
Ok(OutputStream::empty())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry; use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged; use nu_source::Tagged;
pub struct Last; pub struct Last;
@ -63,25 +63,21 @@ async fn last(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
let (LastArgs { rows }, input) = args.process(&registry).await?; let (LastArgs { rows }, input) = args.process(&registry).await?;
let v: Vec<_> = input.into_vec().await; let v: Vec<_> = input.into_vec().await;
let rows_desired = if let Some(quantity) = rows { let end_rows_desired = if let Some(quantity) = rows {
*quantity *quantity as usize
} else { } else {
1 1
}; };
let mut values_vec_deque = VecDeque::new(); let beginning_rows_to_skip = if end_rows_desired < v.len() {
v.len() - end_rows_desired
} else {
0
};
let count = rows_desired as usize; let iter = v.into_iter().skip(beginning_rows_to_skip);
if count < v.len() { Ok(futures::stream::iter(iter).to_output_stream())
let k = v.len() - count;
for x in v[k..].iter() {
values_vec_deque.push_back(ReturnSuccess::value(x.clone()));
}
}
Ok(futures::stream::iter(values_vec_deque).to_output_stream())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -2,6 +2,7 @@ use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value}; use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use parking_lot::Mutex;
pub struct Lines; pub struct Lines;
@ -24,7 +25,7 @@ impl WholeStreamCommand for Lines {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
lines(args, registry) lines(args, registry).await
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -46,82 +47,79 @@ fn ends_with_line_ending(st: &str) -> bool {
} }
} }
fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let mut leftover = vec![]; let leftover_string = Arc::new(Mutex::new(String::new()));
let mut leftover_string = String::new();
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let args = args.evaluate_once(&registry).await?;
let args = args.evaluate_once(&registry).await.unwrap(); let tag = args.name_tag();
let tag = args.name_tag(); let name_span = tag.span;
let name_span = tag.span;
let mut input = args.input; let eos = futures::stream::iter(vec![
loop { UntaggedValue::Primitive(Primitive::EndOfStream).into_untagged_value()
match input.next().await { ]);
Some(Value { value: UntaggedValue::Primitive(Primitive::String(st)), ..}) => {
let mut st = leftover_string.clone() + &st; Ok(args
leftover.clear(); .input
.chain(eos)
.map(move |item| {
let leftover_string = leftover_string.clone();
match item {
Value {
value: UntaggedValue::Primitive(Primitive::String(st)),
..
}
| Value {
value: UntaggedValue::Primitive(Primitive::Line(st)),
..
} => {
let mut leftover_string = leftover_string.lock();
let st = (&*leftover_string).clone() + &st;
let mut lines: Vec<String> = st.lines().map(|x| x.to_string()).collect(); let mut lines: Vec<String> = st.lines().map(|x| x.to_string()).collect();
leftover_string.clear();
if !ends_with_line_ending(&st) { if !ends_with_line_ending(&st) {
if let Some(last) = lines.pop() { if let Some(last) = lines.pop() {
leftover_string = last; leftover_string.push_str(&last);
} else {
leftover_string.clear();
} }
}
let success_lines: Vec<_> = lines
.iter()
.map(|x| ReturnSuccess::value(UntaggedValue::line(x).into_untagged_value()))
.collect();
futures::stream::iter(success_lines)
}
Value {
value: UntaggedValue::Primitive(Primitive::EndOfStream),
..
} => {
let st = (&*leftover_string).lock().clone();
if !st.is_empty() {
futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::string(st).into_untagged_value(),
)])
} else { } else {
leftover_string.clear(); futures::stream::iter(vec![])
} }
let success_lines: Vec<_> = lines.iter().map(|x| ReturnSuccess::value(UntaggedValue::line(x).into_untagged_value())).collect();
yield futures::stream::iter(success_lines)
}
Some(Value { value: UntaggedValue::Primitive(Primitive::Line(st)), ..}) => {
let mut st = leftover_string.clone() + &st;
leftover.clear();
let mut lines: Vec<String> = st.lines().map(|x| x.to_string()).collect();
if !ends_with_line_ending(&st) {
if let Some(last) = lines.pop() {
leftover_string = last;
} else {
leftover_string.clear();
}
} else {
leftover_string.clear();
}
let success_lines: Vec<_> = lines.iter().map(|x| ReturnSuccess::value(UntaggedValue::line(x).into_untagged_value())).collect();
yield futures::stream::iter(success_lines)
}
Some( Value { tag: value_span, ..}) => {
yield futures::stream::iter(vec![Err(ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
name_span,
"value originates from here",
value_span,
))]);
}
None => {
if !leftover.is_empty() {
let mut st = leftover_string.clone();
if let Ok(extra) = String::from_utf8(leftover) {
st.push_str(&extra);
}
yield futures::stream::iter(vec![ReturnSuccess::value(UntaggedValue::string(st).into_untagged_value())])
}
break;
} }
Value {
tag: value_span, ..
} => futures::stream::iter(vec![Err(ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
name_span,
"value originates from here",
value_span,
))]),
} }
} })
if !leftover_string.is_empty() { .flatten()
yield futures::stream::iter(vec![ReturnSuccess::value(UntaggedValue::string(leftover_string).into_untagged_value())]); .to_output_stream())
}
}
.flatten();
Ok(stream.to_output_stream())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -1,84 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::data::value;
use crate::prelude::*;
use crate::utils::data_processing::map_max;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use num_traits::cast::ToPrimitive;
pub struct MapMaxBy;
#[derive(Deserialize)]
pub struct MapMaxByArgs {
column_name: Option<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for MapMaxBy {
fn name(&self) -> &str {
"map-max-by"
}
fn signature(&self) -> Signature {
Signature::build("map-max-by").named(
"column_name",
SyntaxShape::String,
"the name of the column to map-max the table's rows",
Some('c'),
)
}
fn usage(&self) -> &str {
"Creates a new table with the data from the tables rows maxed by the column given."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
map_max_by(args, registry).await
}
}
pub async fn map_max_by(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let (MapMaxByArgs { column_name }, 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 map_by_column = if let Some(column_to_map) = column_name {
Some(column_to_map.item().clone())
} else {
None
};
match map_max(&values[0], map_by_column, name) {
Ok(table_maxed) => Ok(OutputStream::one(ReturnSuccess::value(table_maxed))),
Err(err) => Err(err),
}
}
}
#[cfg(test)]
mod tests {
use super::MapMaxBy;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(MapMaxBy {})
}
}

View File

@ -0,0 +1,130 @@
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use bigdecimal::{FromPrimitive, Zero};
use nu_errors::ShellError;
use nu_protocol::{
hir::{convert_number_to_u64, Number, Operator},
Primitive, Signature, UntaggedValue, Value,
};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math avg"
}
fn signature(&self) -> Signature {
Signature::build("math avg")
}
fn usage(&self) -> &str {
"Finds the average of a list of numbers or tables"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
},
average,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the average of a list of numbers",
example: "echo [-50 100.0 25] | math avg",
result: Some(vec![UntaggedValue::decimal(25).into()]),
}]
}
}
pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let sum = reducer_for(Reduce::Summation);
let number = BigDecimal::from_usize(values.len()).ok_or_else(|| {
ShellError::labeled_error(
"could not convert to big decimal",
"could not convert to big decimal",
&name.span,
)
})?;
let total_rows = UntaggedValue::decimal(number);
let total = sum(Value::zero(), values.to_vec())?;
match total {
Value {
value: UntaggedValue::Primitive(Primitive::Filesize(num)),
..
} => {
let left = UntaggedValue::from(Primitive::Int(num.into()));
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => {
let number = Number::Decimal(result);
let number = convert_number_to_u64(&number);
Ok(UntaggedValue::filesize(number).into_value(name))
}
Ok(_) => Err(ShellError::labeled_error(
"could not calculate average of non-integer or unrelated types",
"source",
name,
)),
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
)),
}
}
Value {
value: UntaggedValue::Primitive(other),
..
} => {
let left = UntaggedValue::from(other);
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(value) => Ok(value.into_value(name)),
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
)),
}
}
_ => Err(ShellError::labeled_error(
"could not calculate average of non-integer or unrelated types",
"source",
name,
)),
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,206 @@
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 {
"math"
}
fn signature(&self) -> Signature {
Signature::build("math")
}
fn usage(&self) -> &str {
"Use mathematical functions as aggregate functions on a list of numbers or tables"
}
async fn run(
&self,
_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(crate::commands::help::get_help(&Command, &registry.clone()))
.into_value(Tag::unknown()),
))))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::commands::math::{
avg::average, max::maximum, median::median, min::minimum, mode::mode, stddev::stddev,
sum::summation, utils::calculate, utils::MathFunction, variance::variance,
};
use nu_plugin::row;
use nu_plugin::test_helpers::value::{decimal, int, table};
use nu_protocol::Value;
use std::str::FromStr;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Command {})
}
#[test]
fn test_math_functions() {
struct TestCase {
description: &'static str,
values: Vec<Value>,
expected_err: Option<ShellError>,
// Order is: average, minimum, maximum, median, summation
expected_res: Vec<Result<Value, ShellError>>,
}
let tt: Vec<TestCase> = vec![
TestCase {
description: "Empty data should throw an error",
values: Vec::new(),
expected_err: Some(ShellError::unexpected("Expected data")),
expected_res: Vec::new(),
},
TestCase {
description: "Single value",
values: vec![int(10)],
expected_err: None,
expected_res: vec![
Ok(decimal(10)),
Ok(int(10)),
Ok(int(10)),
Ok(int(10)),
Ok(table(&[int(10)])),
Ok(decimal(0)),
Ok(int(10)),
Ok(decimal(0)),
],
},
TestCase {
description: "Multiple Values",
values: vec![int(10), int(20), int(30)],
expected_err: None,
expected_res: vec![
Ok(decimal(20)),
Ok(int(10)),
Ok(int(30)),
Ok(int(20)),
Ok(table(&[int(10), int(20), int(30)])),
Ok(decimal(BigDecimal::from_str("8.164965809277260327324280249019637973219824935522233761442308557503201258191050088466198110348800783").expect("Could not convert to decimal from string"))),
Ok(int(60)),
Ok(decimal(BigDecimal::from_str("66.66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666667").expect("Could not convert to decimal from string"))),
],
},
TestCase {
description: "Mixed Values",
values: vec![int(10), decimal(26.5), decimal(26.5)],
expected_err: None,
expected_res: vec![
Ok(decimal(21)),
Ok(int(10)),
Ok(decimal(26.5)),
Ok(decimal(26.5)),
Ok(table(&[decimal(26.5)])),
Ok(decimal(BigDecimal::from_str("7.77817459305202276840928798315333943213319531457321440247173855894902863154158871367713143880202865").expect("Could not convert to decimal from string"))),
Ok(decimal(63)),
Ok(decimal(60.5)),
],
},
TestCase {
description: "Negative Values",
values: vec![int(-14), int(-11), int(10)],
expected_err: None,
expected_res: vec![
Ok(decimal(-5)),
Ok(int(-14)),
Ok(int(10)),
Ok(int(-11)),
Ok(table(&[int(-14), int(-11), int(10)])),
Ok(decimal(BigDecimal::from_str("10.67707825203131121081152396559571062628228776946058011397810604284900898365140801704064843595778374").expect("Could not convert to decimal from string"))),
Ok(int(-15)),
Ok(decimal(114)),
],
},
TestCase {
description: "Mixed Negative Values",
values: vec![decimal(-13.5), decimal(-11.5), int(10)],
expected_err: None,
expected_res: vec![
Ok(decimal(-5)),
Ok(decimal(-13.5)),
Ok(int(10)),
Ok(decimal(-11.5)),
Ok(table(&[decimal(-13.5), decimal(-11.5), int(10)])),
Ok(decimal(BigDecimal::from_str("10.63798226482196513098036125801342585449179971588207816421068645273754903468375890632981926875247027").expect("Could not convert to decimal from string"))),
Ok(decimal(-15)),
Ok(decimal(BigDecimal::from_str("113.1666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666667").expect("Could not convert to decimal from string"))),
],
},
TestCase {
description: "Tables Or Rows",
values: vec![
row!["col1".to_owned() => int(1), "col2".to_owned() => int(5)],
row!["col1".to_owned() => int(2), "col2".to_owned() => int(6)],
row!["col1".to_owned() => int(3), "col2".to_owned() => int(7)],
row!["col1".to_owned() => int(4), "col2".to_owned() => int(8)],
],
expected_err: None,
expected_res: vec![
Ok(row!["col1".to_owned() => decimal(2.5), "col2".to_owned() => decimal(6.5)]),
Ok(row!["col1".to_owned() => int(1), "col2".to_owned() => int(5)]),
Ok(row!["col1".to_owned() => int(4), "col2".to_owned() => int(8)]),
Ok(row!["col1".to_owned() => decimal(2.5), "col2".to_owned() => decimal(6.5)]),
Ok(row![
"col1".to_owned() => table(&[int(1), int(2), int(3), int(4)]),
"col2".to_owned() => table(&[int(5), int(6), int(7), int(8)])
]),
Ok(row![
"col1".to_owned() => decimal(BigDecimal::from_str("1.118033988749894848204586834365638117720309179805762862135448622705260462818902449707207204189391137").expect("Could not convert to decimal from string")),
"col2".to_owned() => decimal(BigDecimal::from_str("1.118033988749894848204586834365638117720309179805762862135448622705260462818902449707207204189391137").expect("Could not convert to decimal from string"))
]),
Ok(row!["col1".to_owned() => int(10), "col2".to_owned() => int(26)]),
Ok(row!["col1".to_owned() => decimal(1.25), "col2".to_owned() => decimal(1.25)]),
],
},
// TODO-Uncomment once Issue: https://github.com/nushell/nushell/issues/1883 is resolved
// TestCase {
// description: "Invalid Mixed Values",
// values: vec![int(10), decimal(26.5), decimal(26.5), string("math")],
// expected_err: Some(ShellError::unimplemented("something")),
// expected_res: vec![],
// },
];
let test_tag = Tag::unknown();
for tc in tt.iter() {
let tc: &TestCase = tc; // Just for type annotations
let math_functions: Vec<MathFunction> = vec![
average, minimum, maximum, median, mode, stddev, summation, variance,
];
let results = math_functions
.into_iter()
.map(|mf| calculate(&tc.values, &test_tag, mf))
.collect_vec();
if tc.expected_err.is_some() {
assert!(
results.iter().all(|r| r.is_err()),
"Expected all functions to error for test-case: {}",
tc.description,
);
} else {
for (i, res) in results.into_iter().enumerate() {
assert_eq!(
res, tc.expected_res[i],
"math function {} failed on test-case {}",
i, tc.description
);
}
}
}
}
}

View File

@ -0,0 +1,107 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct SubCommandArgs {
expression: Option<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math eval"
}
fn usage(&self) -> &str {
"Evaluate a math expression into a number"
}
fn signature(&self) -> Signature {
Signature::build("math eval").desc(self.usage()).optional(
"math expression",
SyntaxShape::String,
"the math expression to evaluate",
)
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
eval(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Evalulate math in the pipeline",
example: "echo '10 / 4' | math eval",
result: Some(vec![UntaggedValue::decimal(2.5).into()]),
}]
}
}
pub async fn eval(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.span;
let (SubCommandArgs { expression }, input) = args.process(registry).await?;
Ok(input
.map(move |x| {
if let Some(Tagged {
tag,
item: expression,
}) = &expression
{
UntaggedValue::string(expression).into_value(tag)
} else {
x
}
})
.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(
"Math evaluation 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<T: Into<Tag>>(math_expression: &str, tag: T) -> Result<Value, String> {
match meval::eval_str(math_expression) {
Ok(num) if num.is_infinite() || num.is_nan() => Err("cannot represent result".to_string()),
Ok(num) => Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag)),
Err(error) => Err(error.to_string().to_lowercase()),
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,69 @@
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use nu_errors::ShellError;
use nu_protocol::{Signature, UntaggedValue, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math max"
}
fn signature(&self) -> Signature {
Signature::build("math max")
}
fn usage(&self) -> &str {
"Finds the maximum within a list of numbers or tables"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
},
maximum,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Find the maximum of list of numbers",
example: "echo [-50 100 25] | math max",
result: Some(vec![UntaggedValue::int(100).into()]),
}]
}
}
pub fn maximum(values: &[Value], _name: &Tag) -> Result<Value, ShellError> {
let max_func = reducer_for(Reduce::Maximum);
max_func(Value::nothing(), values.to_vec())
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,193 @@
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use bigdecimal::{FromPrimitive, Zero};
use nu_errors::ShellError;
use nu_protocol::{
hir::{convert_number_to_u64, Number, Operator},
Primitive, Signature, UntaggedValue, Value,
};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math median"
}
fn signature(&self) -> Signature {
Signature::build("math median")
}
fn usage(&self) -> &str {
"Gets the median of a list of numbers"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
},
median,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the median of a list of numbers",
example: "echo [3 8 9 12 12 15] | math median",
result: Some(vec![UntaggedValue::decimal(10.5).into()]),
}]
}
}
enum Pick {
MedianAverage,
Median,
}
pub fn median(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let take = if values.len() % 2 == 0 {
Pick::MedianAverage
} else {
Pick::Median
};
let mut sorted = vec![];
for item in values {
sorted.push(item.clone());
}
crate::commands::sort_by::sort(&mut sorted, &[], name, false)?;
match take {
Pick::Median => {
let idx = (values.len() as f64 / 2.0).floor() as usize;
let out = sorted.get(idx).ok_or_else(|| {
ShellError::labeled_error(
"could not extract value",
"could not extract value",
&name.span,
)
})?;
Ok(out.clone())
}
Pick::MedianAverage => {
let idx_end = (values.len() / 2) as usize;
let idx_start = idx_end - 1;
let left = sorted
.get(idx_start)
.ok_or_else(|| {
ShellError::labeled_error(
"could not extract value",
"could not extract value",
&name.span,
)
})?
.clone();
let right = sorted
.get(idx_end)
.ok_or_else(|| {
ShellError::labeled_error(
"could not extract value",
"could not extract value",
&name.span,
)
})?
.clone();
compute_average(&[left, right], name)
}
}
}
fn compute_average(values: &[Value], name: impl Into<Tag>) -> Result<Value, ShellError> {
let name = name.into();
let sum = reducer_for(Reduce::Summation);
let number = BigDecimal::from_usize(2).ok_or_else(|| {
ShellError::labeled_error(
"could not convert to big decimal",
"could not convert to big decimal",
&name,
)
})?;
let total_rows = UntaggedValue::decimal(number);
let total = sum(Value::zero(), values.to_vec())?;
match total {
Value {
value: UntaggedValue::Primitive(Primitive::Filesize(num)),
..
} => {
let left = UntaggedValue::from(Primitive::Int(num.into()));
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => {
let number = Number::Decimal(result);
let number = convert_number_to_u64(&number);
Ok(UntaggedValue::filesize(number).into_value(name))
}
Ok(_) => Err(ShellError::labeled_error(
"could not calculate median of non-numeric or unrelated types",
"source",
name,
)),
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
)),
}
}
Value {
value: UntaggedValue::Primitive(other),
..
} => {
let left = UntaggedValue::from(other);
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(value) => Ok(value.into_value(name)),
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
)),
}
}
_ => Err(ShellError::labeled_error(
"could not calculate median of non-numeric or unrelated types",
"source",
name,
)),
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,69 @@
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use nu_errors::ShellError;
use nu_protocol::{Signature, UntaggedValue, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math min"
}
fn signature(&self) -> Signature {
Signature::build("math min")
}
fn usage(&self) -> &str {
"Finds the minimum within a list of numbers or tables"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
},
minimum,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the minimum of a list of numbers",
example: "echo [-50 100 25] | math min",
result: Some(vec![UntaggedValue::int(-50).into()]),
}]
}
}
pub fn minimum(values: &[Value], _name: &Tag) -> Result<Value, ShellError> {
let min_func = reducer_for(Reduce::Minimum);
min_func(Value::nothing(), values.to_vec())
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,22 @@
pub mod avg;
pub mod command;
pub mod eval;
pub mod max;
pub mod median;
pub mod min;
pub mod mode;
pub mod stddev;
pub mod sum;
pub mod utils;
pub mod variance;
pub use avg::SubCommand as MathAverage;
pub use command::Command as Math;
pub use eval::SubCommand as MathEval;
pub use max::SubCommand as MathMaximum;
pub use median::SubCommand as MathMedian;
pub use min::SubCommand as MathMinimum;
pub use mode::SubCommand as MathMode;
pub use stddev::SubCommand as MathStddev;
pub use sum::SubCommand as MathSummation;
pub use variance::SubCommand as MathVariance;

View File

@ -0,0 +1,94 @@
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, UntaggedValue, Value};
use std::cmp::Ordering;
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math mode"
}
fn signature(&self) -> Signature {
Signature::build("math mode")
}
fn usage(&self) -> &str {
"Gets the most frequent element(s) from a list of numbers or tables"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
},
mode,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the mode(s) of a list of numbers",
example: "echo [3 3 9 12 12 15] | math mode",
result: Some(vec![
UntaggedValue::int(3).into_untagged_value(),
UntaggedValue::int(12).into_untagged_value(),
]),
}]
}
}
pub fn mode(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let mut frequency_map = std::collections::HashMap::new();
for v in values {
let counter = frequency_map.entry(v.value.clone()).or_insert(0);
*counter += 1;
}
let mut max_freq = -1;
let mut modes = Vec::<Value>::new();
for (value, frequency) in frequency_map.iter() {
match max_freq.cmp(&frequency) {
Ordering::Less => {
max_freq = *frequency;
modes.clear();
modes.push(value.clone().into_value(name));
}
Ordering::Equal => {
modes.push(value.clone().into_value(name));
}
Ordering::Greater => (),
}
}
crate::commands::sort_by::sort(&mut modes, &[], name, false)?;
Ok(UntaggedValue::Table(modes).into_value(name))
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,87 @@
use super::variance::variance;
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, UntaggedValue, Value};
use std::str::FromStr;
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math stddev"
}
fn signature(&self) -> Signature {
Signature::build("math stddev")
}
fn usage(&self) -> &str {
"Finds the stddev of a list of numbers or tables"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
},
stddev,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the stddev of a list of numbers",
example: "echo [1 2 3 4 5] | math stddev",
result: Some(vec![UntaggedValue::decimal(BigDecimal::from_str("1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641573").expect("Could not convert to decimal from string")).into()]),
}]
}
}
pub fn stddev(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let variance = variance(values, name)?.as_primitive()?;
let sqrt_var = match variance {
Primitive::Decimal(var) => var.sqrt(),
_ => {
return Err(ShellError::labeled_error(
"Could not take square root of variance",
"error occured here",
name.span,
))
}
};
match sqrt_var {
Some(stddev) => Ok(UntaggedValue::from(Primitive::Decimal(stddev)).into_value(name)),
None => Err(ShellError::labeled_error(
"Could not calculate stddev",
"error occured here",
name.span,
)),
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,106 @@
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use nu_errors::ShellError;
use nu_protocol::{Dictionary, Signature, UntaggedValue, Value};
use num_traits::identities::Zero;
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math sum"
}
fn signature(&self) -> Signature {
Signature::build("math sum")
}
fn usage(&self) -> &str {
"Finds the sum of a list of numbers or tables"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
},
summation,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Sum a list of numbers",
example: "echo [1 2 3] | math sum",
result: Some(vec![UntaggedValue::int(6).into()]),
},
Example {
description: "Get the disk usage for the current directory",
example: "ls --all --du | get size | math sum",
result: None,
},
]
}
}
pub fn summation(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let sum = reducer_for(Reduce::Summation);
if values.iter().all(|v| v.is_primitive()) {
Ok(sum(Value::zero(), values.to_vec())?)
} else {
let mut column_values = IndexMap::new();
for value in values {
if let UntaggedValue::Row(row_dict) = value.value.clone() {
for (key, value) in row_dict.entries.iter() {
column_values
.entry(key.clone())
.and_modify(|v: &mut Vec<Value>| v.push(value.clone()))
.or_insert(vec![value.clone()]);
}
};
}
let mut column_totals = IndexMap::new();
for (col_name, col_vals) in column_values {
let sum = sum(Value::zero(), col_vals)?;
column_totals.insert(col_name, sum);
}
Ok(UntaggedValue::Row(Dictionary {
entries: column_totals,
})
.into_value(name))
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,61 @@
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Dictionary, ReturnSuccess, UntaggedValue, Value};
use indexmap::map::IndexMap;
pub type MathFunction = fn(values: &[Value], tag: &Tag) -> Result<Value, ShellError>;
pub async fn run_with_function(
RunnableContext {
mut input, name, ..
}: RunnableContext,
mf: MathFunction,
) -> Result<OutputStream, ShellError> {
let values: Vec<Value> = input.drain_vec().await;
let res = calculate(&values, &name, mf);
match res {
Ok(v) => {
if v.value.is_table() {
Ok(OutputStream::from(
v.table_entries()
.map(|v| ReturnSuccess::value(v.clone()))
.collect::<Vec<_>>(),
))
} else {
Ok(OutputStream::one(ReturnSuccess::value(v)))
}
}
Err(e) => Err(e),
}
}
pub fn calculate(values: &[Value], name: &Tag, mf: MathFunction) -> Result<Value, ShellError> {
if values.iter().all(|v| v.is_primitive()) {
mf(&values, &name)
} else {
// If we are not dealing with Primitives, then perhaps we are dealing with a table
// Create a key for each column name
let mut column_values = IndexMap::new();
for value in values {
if let UntaggedValue::Row(row_dict) = &value.value {
for (key, value) in row_dict.entries.iter() {
column_values
.entry(key.clone())
.and_modify(|v: &mut Vec<Value>| v.push(value.clone()))
.or_insert(vec![value.clone()]);
}
}
}
// The mathematical function operates over the columns of the table
let mut column_totals = IndexMap::new();
for (col_name, col_vals) in column_values {
column_totals.insert(col_name, mf(&col_vals, &name)?);
}
Ok(UntaggedValue::Row(Dictionary {
entries: column_totals,
})
.into_untagged_value())
}
}

View File

@ -0,0 +1,162 @@
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::data::value::compute_values;
use crate::prelude::*;
use bigdecimal::{FromPrimitive, Zero};
use nu_errors::ShellError;
use nu_protocol::{hir::Operator, Primitive, Signature, UntaggedValue, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math variance"
}
fn signature(&self) -> Signature {
Signature::build("math variance")
}
fn usage(&self) -> &str {
"Finds the variance of a list of numbers or tables"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
},
variance,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the variance of a list of numbers",
example: "echo [1 2 3 4 5] | math variance",
result: Some(vec![UntaggedValue::decimal(2).into()]),
}]
}
}
fn sum_of_squares(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let n = BigDecimal::from_usize(values.len()).ok_or_else(|| {
ShellError::labeled_error(
"could not convert to big decimal",
"could not convert to big decimal",
&name.span,
)
})?;
let mut sum_x = Value::zero();
let mut sum_x2 = Value::zero();
for value in values {
let v = match value {
Value {
value: UntaggedValue::Primitive(Primitive::Filesize(num)),
..
} => {
UntaggedValue::from(Primitive::Int(num.clone().into()))
},
Value {
value: UntaggedValue::Primitive(num),
..
} => {
UntaggedValue::from(num.clone())
},
_ => {
return Err(ShellError::labeled_error(
"Attempted to compute the sum of squared values of a value that cannot be summed or squared.",
"value appears here",
value.tag.span,
))
}
};
let v_squared = compute_values(Operator::Multiply, &v, &v);
match v_squared {
// X^2
Ok(x2) => sum_x2 = sum_x2 + x2.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned(value.tag.span),
right_type.spanned(value.tag.span),
))
}
};
sum_x = sum_x + v.into_untagged_value();
}
let sum_x_squared = match compute_values(Operator::Multiply, &sum_x, &sum_x) {
Ok(v) => v.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
))
}
};
let sum_x_squared_div_n = match compute_values(Operator::Divide, &sum_x_squared, &n.into()) {
Ok(v) => v.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
))
}
};
let ss = match compute_values(Operator::Minus, &sum_x2, &sum_x_squared_div_n) {
Ok(v) => v.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
))
}
};
Ok(ss)
}
pub fn variance(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let ss = sum_of_squares(values, name)?;
let n = BigDecimal::from_usize(values.len()).ok_or_else(|| {
ShellError::labeled_error(
"could not convert to big decimal",
"could not convert to big decimal",
&name.span,
)
})?;
let variance = compute_values(Operator::Divide, &ss, &n.into());
match variance {
Ok(value) => Ok(value.into_value(name)),
Err((_, _)) => Err(ShellError::labeled_error(
"could not calculate variance of non-integer or unrelated types",
"source",
name,
)),
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -37,7 +37,7 @@ impl WholeStreamCommand for Merge {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
merge(args, registry) merge(args, registry).await
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -49,57 +49,61 @@ impl WholeStreamCommand for Merge {
} }
} }
fn merge(raw_args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn merge(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let scope = raw_args.call_info.scope.clone(); let scope = raw_args.call_info.scope.clone();
let stream = async_stream! { let mut context = Context::from_raw(&raw_args, &registry);
let mut context = Context::from_raw(&raw_args, &registry); let name_tag = raw_args.call_info.name_tag.clone();
let name_tag = raw_args.call_info.name_tag.clone(); let (merge_args, input): (MergeArgs, _) = raw_args.process(&registry).await?;
let (merge_args, mut input): (MergeArgs, _) = raw_args.process(&registry).await?; let block = merge_args.block;
let block = merge_args.block;
let table: Option<Vec<Value>> = match run_block(&block, let table: Option<Vec<Value>> = match run_block(
&mut context, &block,
InputStream::empty(), &mut context,
&scope.it, InputStream::empty(),
&scope.vars, &scope.it,
&scope.env).await { &scope.vars,
Ok(mut stream) => Some(stream.drain_vec().await), &scope.env,
Err(err) => { )
yield Err(err); .await
return; {
} Ok(mut stream) => Some(stream.drain_vec().await),
}; Err(err) => {
return Err(err);
let table = table.unwrap_or_else(|| vec![Value {
value: UntaggedValue::row(IndexMap::default()),
tag: name_tag,
}]);
let mut idx = 0;
while let Some(value) = input.next().await {
let other = table.get(idx);
match other {
Some(replacement) => {
match merge_values(&value.value, &replacement.value) {
Ok(merged_value) => yield ReturnSuccess::value(merged_value.into_value(&value.tag)),
Err(err) => {
let message = format!("The row at {:?} types mismatch", idx);
yield Err(ShellError::labeled_error("Could not merge", &message, &value.tag));
}
}
}
None => yield ReturnSuccess::value(value),
}
idx += 1;
} }
}; };
Ok(stream.to_output_stream()) let table = table.unwrap_or_else(|| {
vec![Value {
value: UntaggedValue::row(IndexMap::default()),
tag: name_tag,
}]
});
Ok(input
.enumerate()
.map(move |(idx, value)| {
let other = table.get(idx);
match other {
Some(replacement) => match merge_values(&value.value, &replacement.value) {
Ok(merged_value) => ReturnSuccess::value(merged_value.into_value(&value.tag)),
Err(_) => {
let message = format!("The row at {:?} types mismatch", idx);
Err(ShellError::labeled_error(
"Could not merge",
&message,
&value.tag,
))
}
},
None => ReturnSuccess::value(value),
}
})
.to_output_stream())
} }
#[cfg(test)] #[cfg(test)]

View File

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

View File

@ -0,0 +1,46 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
#[derive(Clone)]
pub struct Command;
#[async_trait]
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"move"
}
fn signature(&self) -> Signature {
Signature::build("move")
}
fn usage(&self) -> &str {
"moves across desired subcommand."
}
async fn run(
&self,
_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(crate::commands::help::get_help(&Command, &registry))
.into_value(Tag::unknown()),
))))
}
}
#[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,7 @@
mod column;
mod command;
pub mod mv;
pub use column::SubCommand as MoveColumn;
pub use command::Command as Move;
pub use mv::Mv;

View File

@ -6,16 +6,16 @@ use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged; use nu_source::Tagged;
use std::path::PathBuf; use std::path::PathBuf;
pub struct Move; pub struct Mv;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct MoveArgs { pub struct Arguments {
pub src: Tagged<PathBuf>, pub src: Tagged<PathBuf>,
pub dst: Tagged<PathBuf>, pub dst: Tagged<PathBuf>,
} }
#[async_trait] #[async_trait]
impl WholeStreamCommand for Move { impl WholeStreamCommand for Mv {
fn name(&self) -> &str { fn name(&self) -> &str {
"mv" "mv"
} }
@ -43,7 +43,7 @@ impl WholeStreamCommand for Move {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
mv(args, registry) mv(args, registry).await
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -67,30 +67,23 @@ impl WholeStreamCommand for Move {
} }
} }
fn mv(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn mv(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let stream = async_stream! { let name = args.call_info.name_tag.clone();
let name = args.call_info.name_tag.clone(); let shell_manager = args.shell_manager.clone();
let shell_manager = args.shell_manager.clone(); let (args, _) = args.process(&registry).await?;
let (args, _) = args.process(&registry).await?;
let mut result = shell_manager.mv(args, name)?;
while let Some(item) = result.next().await { shell_manager.mv(args, name)
yield item;
}
};
Ok(stream.to_output_stream())
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Move; use super::Mv;
#[test] #[test]
fn examples_work_as_expected() { fn examples_work_as_expected() {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(Move {}) test_examples(Mv {})
} }
} }

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