Compare commits

...

197 Commits

Author SHA1 Message Date
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
a3e1a3f266 Bump start plugin to 0.15.0 (#1956) 2020-06-10 08:39:15 +12:00
e5a18eb3c2 Bump to 0.15.0 (#1955) 2020-06-10 05:33:59 +12:00
16ba274170 Bump heim dependency version. (#1954)
Most important change is a fix for processes CPU usage calculation (see https://github.com/heim-rs/heim/issues/246)
2020-06-10 04:34:05 +12:00
3bb2c9beed Rename env file to .nu-env (#1953) 2020-06-09 15:54:20 +12:00
2fa83b0bbe Pub expose InterruptibleStream and InputStream. (#1952)
This allows crate users to make sure their long-running
streams can be interrupted with ctrl-c.
2020-06-09 05:17:19 +12:00
bf459e09cb WIP: Per directory env-variables (#1943)
* 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.
2020-06-08 19:55:25 +12:00
ec7ff5960d Remove async_stream! from some commands (#1951)
* Remove async_stream! from open.rs

* Ran rustfmt

* Fix Clippy warning

* Removed async_stream! from evaluate_by.rs

* Removed async_stream! from exit.rs

* Removed async_stream! from from_eml.rs

* Removed async_stream! from group_by_date.rs

* Removed async_stream! from group_by.rs

* Removed async_stream! from map_max.rs

* Removed async_stream! from to_sqlite.rs

* Removed async_stream! from to_md.rs

* Removed async_stream! from to_html.rs
2020-06-08 16:48:10 +12:00
545f70705e ISSUE-1907 Disallow invalid top level TOML (#1946)
* Do not allow invalid top-level toml

Move recursive toml conversion into a helper func

* Forgot to format

* Forgot to use helper inside collect values

Added some additional tests
2020-06-08 08:02:37 +12:00
48672f8e30 Assign variables when passed as an argument. (#1947) 2020-06-08 04:15:57 +12:00
160191e9f4 Cal updates (#1945)
* Clean up `use` statements

* Update cal code to be ready for future data coloring
2020-06-07 15:52:42 +12:00
bef9669b85 When the nushell is located in a path that has a space in it, these tests break, this fixes it (#1944) 2020-06-07 15:50:52 +12:00
15e66ae065 Implement an option to show paths made of mkdir. (#1932) 2020-06-06 15:13:38 -04:00
ba6370621f Removing async_stream! from some commands (#1940)
* Removing async_stream! from some commands

* Revert row.rs code

* Simplify logic for first.rs and skip.rs
2020-06-06 19:42:06 +12:00
2a8ea88413 Bring back parse as built-in. 2020-06-04 15:21:13 -05:00
05959d6a61 Bump to latest rustyline (#1937) 2020-06-05 05:50:12 +12:00
012c99839c Moving some commands off of async stream (#1934)
* Remove async_stream from rm

* Remove async_stream from sort_by

* Remove async_stream from split_by

* Remove dbg!() statement

* Remove async_stream from uniq

* Remove async_stream from mkdir

* Don't change functions from private to public

* Clippy fixes

* Peer-review updates
2020-06-04 20:42:23 +12:00
5dd346094e Cut out a function to generate a pharase in the Flags section. (#1930) 2020-06-04 19:09:43 +12:00
b6f9d0ca58 Remove no_auto_pivot (#1931)
no_auto_pivot does not exist any longer. It was rolled into pivot_mode.
2020-06-03 12:27:54 -04:00
ae72593831 changed to-float to to-decimal (#1926)
* changed to-float to to-decimal

* changed to-float to to-decimal
2020-06-02 09:02:57 +12:00
ac22319a5d Update Cargo.lock (#1923) 2020-06-02 04:32:24 +12:00
7e8c84e394 Bump actions/checkout version from v1 to v2. (#1924) 2020-06-02 04:31:48 +12:00
ef4eefa96a Bump more deps (#1921) 2020-05-31 08:54:47 +12:00
2dc43775e3 Bump to latest heim (#1920)
* Bump to latest heim

* Fix pinning issue
2020-05-31 08:54:33 +12:00
4bdf27b173 Batch of moving commands off async_stream #3 (#1919)
* Batch of moving commands off async_stream #3

* remove commented-out section

* merge master
2020-05-31 06:31:50 +12:00
741d7b9f10 Add rm_always_trash option to config (#1869)
* Add `rm_always_trash` option to config

* Add `--permanent` flag to `rm`

* `rm`: error if both `-t` and `-p` are present

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-05-31 06:31:34 +12:00
ecb67fee40 ISSUE-1744-Glob support for start command (#1912)
* Possible implementation of globbing for start command

* Whoops forgot to remove Error used for debugging

* Use string lossy

* Run clippy

* Pin glob

* Better error messages

* Remove unneeded comment
2020-05-31 05:41:25 +12:00
ad43ef08e5 Support average for tables. 2020-05-30 10:33:09 -05:00
092ee127ee Batch of moving commands off async_stream (#1917) 2020-05-30 16:34:39 +12:00
b84ff99e7f Batch of moving commands off async_stream (#1916) 2020-05-30 11:36:04 +12:00
3a6a3d7409 Implement login for the fetch command (#1915) 2020-05-30 11:22:38 +12:00
48ee20782f Ensure end_filter plugin lifecycle stage gets called. 2020-05-29 04:03:25 -05:00
360e8340d1 Move run to be async (#1913) 2020-05-29 20:22:52 +12:00
fbc0dd57e9 Add plugin_dir to docs (#1911) 2020-05-29 08:46:27 +12:00
3f9871f60d Simplify plugin directory scanning (#1910) 2020-05-29 07:14:32 +12:00
fe01a223a4 Str plugin promoted to built-in Nu command. 2020-05-28 11:18:58 -05:00
0a6692ac44 Simplify parse plugin code. (#1904)
Primarily, instead of building a parse pattern enum, we just build the regex
directly, with the appropriate capture group names so that the column name
codepaths can be shared between simple and `--regex` patterns.

Also removed capture group count compared to column name count. I don't think
this codepath can possibly be reached with the regex we now use for the
simplified capture form.
2020-05-28 09:58:06 -04:00
98a3d9fff6 Allow echo to iterate ranges (#1905) 2020-05-28 06:07:53 +12:00
e2dabecc0b Make it-expansion work when in a list (#1903) 2020-05-27 20:29:05 +12:00
49b0592e3c Implement ctrl+c for the du command (#1901)
* Implement ctrl+c for the du command

* Ignore the "too many arguments" Clippy warning
2020-05-27 16:52:20 +12:00
fa812849b8 Fix warnings and split Scope (#1902) 2020-05-27 16:50:26 +12:00
9567c1f564 Fix for inconsistency when quoted strings are used with with_env shorthand (#1900) 2020-05-26 15:03:55 -04:00
a915471b38 Cal documentation updates (#1895) 2020-05-26 07:21:36 -04:00
bf212a5a3a change the test to use the origin column (#1878) 2020-05-25 18:50:54 -04:00
f0fc9e1038 Merge pivot options (#1888) 2020-05-25 18:40:25 -04:00
cb6ccc3c5a Improve the simplified parse form. (#1875) 2020-05-25 14:19:49 -04:00
07996ea93d Remove as many of the typecasts as possible in the cal command (#1886)
* Remove as many of the typecasts as possible in the cal command

* Run rustfmt on cal.rs
2020-05-25 18:51:23 +12:00
c71510c65c Update CODE_OF_CONDUCT.md 2020-05-25 18:50:14 +12:00
9c9941cf97 Update CODE_OF_CONDUCT.md 2020-05-25 18:49:36 +12:00
005d76cf57 Fix broken ordering of args when parsing command with env vars. (#1841) 2020-05-24 19:26:27 -04:00
8a99d112fc Add --to-float to str plugin (#1872) 2020-05-24 18:11:49 -04:00
fb09d7d1a1 docs: add alias --save flag (#1874) 2020-05-24 13:42:20 -04:00
9c14fb6c02 Show error when trying to sort by invalid column (#1880)
* Show error when trying to sort by invalid column

* Added test for changes

* Addressed comments, updated test

* Removed unnecessary mutable keyword

* Changed split-column to solt column after rebase from upstream
2020-05-25 05:37:08 +12:00
d488fddfe1 Add useful commands to CONTRIBUTING.md (#1865)
* Add useful commands to CONTRIBUTING.md

* Add some formatting commands
2020-05-25 05:34:26 +12:00
e1b598d478 added examples and explanation to trim (#1876)
* added examples and explanation to trim

inserted examples (taken from parse in cookbook) for lists and tables using trim

* Move `to-json` to `to json`

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-05-25 05:10:30 +12:00
edbecda14d Split split command to sub commands. 2020-05-24 02:02:44 -05:00
74c2daf665 Add completion for binaries on PATH (#1866) 2020-05-23 20:27:52 -04:00
aadbcf5ce8 Issue 1787 (#1827) 2020-05-23 20:08:39 -04:00
460daf029b Add space to bottom of table in 'light' mode (#1871) 2020-05-22 21:12:26 -04:00
9e6ab33fd7 Add --regex flag to parse (#1863) 2020-05-22 10:13:58 -04:00
5de30d0ae5 Tweak auto-rotate for single row output (#1861)
* added helper to convert data to strings
added ability to auto-rotate single row output
if row will be greater than terminal width

* Added pivot_to_fit config value

* Added ColumnPath to convert_to_string helper

* Figured out I had to run `cargo fmt --all -- --check`

Co-authored-by: Darren Schroeder <fdncred@hotmail.com>
2020-05-22 04:30:58 +12:00
97b9c078b1 Fix completer handling of paths with spaces (#1858)
* Fix completer handling of paths with spaces

* Satisfy Clippy for completer

* Satisfy cargo fmt for completer
2020-05-21 08:32:21 +12:00
8dc5c34932 Save alias (#1852)
* figuring out error with lines

* make progress in printing of block

* support for external commands; fix some tiny bugs in formatting

* basic printing of block; going to experiment with bubbling raw input to the command itself to avoid potential edge cases

* remove fmt::Display impls for hir structs; bubbled raw_input to command args

* compiling checkpoint :)

* process raw input alias to remove save flag; do duplicates stored

* fix warnings; run clippy

* removed tmux log file

* fix bug in looking for same alias; changed unwraps to safe unwraps
2020-05-21 05:31:04 +12:00
3239e5055c Added a count column on the histogram command (#1853)
* Adding iniitial draft for the addition of the count column on the histogram command

* Update histogram documentation

* Add count column test to histogram command

* Fix error in histogram documentation
2020-05-20 18:02:36 +12:00
b22db39775 Progress readme (#1854)
* Add some progress indicators to the readme

* Add some progress indicators to the readme
2020-05-20 16:46:55 +12:00
7c61f427c6 Update minimum Rust version requirement in README (#1851)
a4c1b092ba introduced uses of `map_or` on `Result`, which was only stabilised in Rust 1.41.
2020-05-20 10:55:46 +12:00
ae8c864934 default history size to 100k (#1845) 2020-05-20 07:28:06 +12:00
ed80933806 String interpolation (#1849)
* Add string interpolation

* fix coloring

* A few more fixups + tests

* merge master again
2020-05-20 07:27:26 +12:00
ae87582cb6 Fix missing invocation errors (#1846) 2020-05-19 08:57:25 -04:00
b89976daef let format access variables also (#1842) 2020-05-19 16:20:09 +12:00
76b170cea0 Update test command (#1840) 2020-05-18 22:01:27 -04:00
Sam
3302586379 added documentation for no_auto_pivot (#1828) 2020-05-18 21:21:35 -04:00
3144dc7f93 add support for specifying max history size in config (#1829) (#1837) 2020-05-19 10:27:08 +12:00
6efabef8d3 Remove interpretation of Primitive::Nothing as the number 0. (#1836) 2020-05-18 15:18:46 -04:00
0743b69ad5 Move from language-reporting to codespan (#1825) 2020-05-19 06:44:27 +12:00
5f1136dcb0 Fix newly added examples. (#1830) 2020-05-18 11:40:44 -04:00
acf13a6fcf Add (near) automatic testing for command examples (#1777) 2020-05-18 08:56:01 -04:00
Sam
3fc4a9f142 added config check to disable auto pivoting of single row outputs (#1791)
* added config check to disable auto pivoting of single row outputs

* fixed change to use built-in boolean values
2020-05-18 20:42:22 +12:00
1d781e8797 Add docs for the shuffle command (#1824) 2020-05-18 19:13:03 +12:00
b6cdfb1b19 Remove the -n flag from shuffle (#1823) 2020-05-18 19:12:35 +12:00
334685af23 Add some examples (#1821)
* Adds some examples

* Run rustfmt

* Fixed a few descriptions in the examples
2020-05-18 19:11:37 +12:00
c475be2df8 Fix starship not getting the correct pwd (#1822) 2020-05-18 17:22:54 +12:00
6ec6eb5199 Call external correctly. 2020-05-17 23:32:55 -05:00
f18424a6f6 Remove test-bins feature. 2020-05-17 23:32:55 -05:00
d1b1438ce5 Check capture group count (#1814) 2020-05-17 14:52:17 -04:00
af6aff8ca3 Allow user to specify the indentation setting on the pretty flag for the to json command (#1818)
* Allow user to specify the indentation setting on the pretty flag for the to json command

* Use "JSON" over "json"
2020-05-18 06:48:58 +12:00
d4dd8284a6 create Palette trait (#1813)
* create Pallet trait

* correct spelling to palette

* move palette to it's own module
2020-05-18 05:48:57 +12:00
e728cecb4d add docs for start command (#1816) 2020-05-18 05:37:15 +12:00
41e1aef369 Fix the insert command (#1815) 2020-05-17 08:30:52 -04:00
e50db1a4de Adds support to pretty format the json in to json (#1812)
* Current work on the --pretty flag for to json

* Deleted notes that were pushed by accident

* Fixed some errors
2020-05-17 16:43:10 +12:00
41412bc577 Update issue templates 2020-05-17 11:30:09 +12:00
e12aa87abe Update issue templates 2020-05-17 11:28:14 +12:00
0abc94f0c6 Bump some of our dependencies (#1809) 2020-05-17 10:34:10 +12:00
48d06f40b1 Remove the old it-hacks from fetch and post (#1807) 2020-05-17 06:18:46 +12:00
f43ed23ed7 Fix parsing of invocations with a dot (#1804) 2020-05-16 19:25:18 +12:00
40ec8c41a0 Cal command updates (#1758)
* Calculate the quarter directly

* Group some data together, remove attribute to ignore Clippy warning

* Group items into structs and add methods

* Updates to cal command

* Update cal.rs

* Update cal.rs

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-05-16 16:00:06 +12:00
076fde16dd Evaluation of command arguments (#1801)
* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* Finish adding the baseline refactors for argument invocation

* Finish cleanup and add test

* Add missing plugin references
2020-05-16 15:18:24 +12:00
Sam
822440d5ff added nothing value for incalcuable dir sizes (#1789) 2020-05-15 12:53:18 -04:00
fc8ee8e4b9 Extracted grouping by date to it's own subcommand. (#1792) 2020-05-15 04:16:09 -05:00
5fbe5cf785 Use the directories crate instead of app_dirs (#1782)
The app_dirs crate is abandonned since quite a bit of time. Use the directories
crate instead, which is maintained and have more OS support.
2020-05-14 20:17:23 +12:00
f78536333a doc: add rename command page (#1781) 2020-05-14 12:36:08 +12:00
e7f08cb21d Allow external binary to register custom commands. (#1780)
This changeset contains everything that a separate binary needs to
register its own commands (including the new help function). It is
very possible that this commit misses other pub use exports, but
the contained ones work for our use cases so far.
2020-05-14 12:35:22 +12:00
f5a1d2f4fb Update README.md 2020-05-14 05:37:18 +12:00
8abbfd3ec9 Update README examples (#1779) 2020-05-14 05:36:24 +12:00
6826a9aeac doc: add more from command pages (#1778)
* doc: add from-url command page

* doc: add missing link to existing from-xml page.

* doc: add from-ini command page
2020-05-14 05:23:33 +12:00
e3b7e47515 cal: fix thursday typo (#1776) 2020-05-13 08:06:31 -04:00
196991ae1e Bump to 0.14.1 (#1772) 2020-05-13 20:03:45 +12:00
34b5e5c34e doc: add headers command page (#1775) 2020-05-13 20:03:29 +12:00
cb24a9c7ea doc: rename edit command to update (#1774) 2020-05-13 20:02:41 +12:00
9700b74407 Fix type in config flag description (#1769) 2020-05-13 14:21:57 +12:00
803c6539eb doc: fix nth examples (#1768) 2020-05-12 16:47:45 -04:00
75edcbc0d0 Simplify mv in FilesystemShell (#1587) 2020-05-12 16:40:45 -04:00
425 changed files with 20889 additions and 10465 deletions

View File

@ -46,13 +46,13 @@ steps:
echo "##vso[task.prependpath]$HOME/.cargo/bin"
rustup component add rustfmt
displayName: Install Rust
- bash: RUSTFLAGS="-D warnings" cargo test --all --features stable,test-bins
- bash: RUSTFLAGS="-D warnings" cargo test --all --features stable
condition: eq(variables['style'], 'unflagged')
displayName: Run tests
- bash: RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
condition: eq(variables['style'], 'unflagged')
displayName: Check clippy lints
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo test --all --features stable,test-bins
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo test --all --features stable
condition: eq(variables['style'], 'canary')
displayName: Run tests
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used

View File

@ -23,8 +23,8 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Configuration (please complete the following information):**
- OS: [e.g. Windows]
- Version [e.g. 0.4.0]
- Optional features (if any)
- OS (e.g. Windows):
- Nu version (you can use the `version` command to find out):
- Optional features (if any):
Add any other context about the problem here.

View File

@ -13,7 +13,7 @@ jobs:
- x86_64-unknown-linux-musl
- x86_64-unknown-linux-gnu
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Install rust-embedded/cross
env: { VERSION: v0.1.16 }
run: >-
@ -62,7 +62,7 @@ jobs:
- { tag: glibc, base-image: scratch, arch: x86_64-unknown-linux-gnu, plugin: false, use-patch: false}
- { tag: musl, base-image: scratch, arch: x86_64-unknown-linux-musl, plugin: false, use-patch: false}
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- uses: actions/download-artifact@master
with: { name: '${{ matrix.arch }}', path: target/release }
- name: Build and publish exact version

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

@ -0,0 +1,240 @@
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
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
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.1/less.exe" -OutFile "target\release\less.exe"
- name: Copy files to output
run: |
cp target\release\nu.exe 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 }}
# TODO: Remove Show
- name: Show Windows Artifacts
run: ls -la ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
- name: Create macOS Archive
run: zip -r ${{ steps.info.outputs.windowsdir }}.zip ${{ steps.info.outputs.windowsdir }}
- name: Upload Windows zip
uses: actions/upload-release-asset@v1
env:
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

@ -55,7 +55,7 @@ a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at wycats@gmail.com. All
reported by contacting the project team at wycats@gmail.com via email or by reaching out to @jturner, @gedge, or @andras_io on discord. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
@ -68,9 +68,9 @@ members of the project's leadership.
## Attribution
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
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!
*Note: for a more complete guide see [The nu contributor book](https://github.com/nushell/contributor-book)*
@ -9,20 +11,52 @@ 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)!
<!--WIP-->
# Developing
## Set up
## Developing
### Set up
This is no different than other Rust projects.
```shell
```bash
git clone https://github.com/nushell/nushell
cd nushell
cargo build
```
## Tests
### Useful Commands
Run tests with:
Build and run Nushell:
```shell
cargo test --all --features=stable,test-bins
cargo build --release && cargo run --release
```
Run Clippy on Nushell:
```shell
cargo clippy --all --features=stable
```
Run all tests:
```shell
cargo test --all --features=stable
```
Run all tests for a specific command
```shell
cargo test --package nu-cli --test main -- commands::<command_name_here>
```
Check to see if there are code formatting issues
```shell
cargo fmt --all -- --check
```
Format the code in the project
```shell
cargo fmt --all
```

2013
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package]
name = "nu"
version = "0.14.0"
version = "0.16.0"
authors = ["The Nu Project Contributors"]
description = "A new type of shell"
license = "MIT"
@ -18,62 +18,56 @@ members = ["crates/*/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-cli = { version = "0.14.0", path = "./crates/nu-cli" }
nu-source = { version = "0.14.0", path = "./crates/nu-source" }
nu-plugin = { version = "0.14.0", path = "./crates/nu-plugin" }
nu-protocol = { version = "0.14.0", path = "./crates/nu-protocol" }
nu-errors = { version = "0.14.0", path = "./crates/nu-errors" }
nu-parser = { version = "0.14.0", path = "./crates/nu-parser" }
nu-value-ext = { version = "0.14.0", path = "./crates/nu-value-ext" }
nu_plugin_average = { version = "0.14.0", path = "./crates/nu_plugin_average", optional=true }
nu_plugin_binaryview = { version = "0.14.0", path = "./crates/nu_plugin_binaryview", optional=true }
nu_plugin_fetch = { version = "0.14.0", path = "./crates/nu_plugin_fetch", optional=true }
nu_plugin_inc = { version = "0.14.0", path = "./crates/nu_plugin_inc", optional=true }
nu_plugin_match = { version = "0.14.0", path = "./crates/nu_plugin_match", optional=true }
nu_plugin_post = { version = "0.14.0", path = "./crates/nu_plugin_post", optional=true }
nu_plugin_ps = { version = "0.14.0", path = "./crates/nu_plugin_ps", optional=true }
nu_plugin_start = { version = "0.1.0", path = "./crates/nu_plugin_start", optional=true }
nu_plugin_str = { version = "0.14.0", path = "./crates/nu_plugin_str", optional=true }
nu_plugin_sys = { version = "0.14.0", path = "./crates/nu_plugin_sys", optional=true }
nu_plugin_textview = { version = "0.14.0", path = "./crates/nu_plugin_textview", optional=true }
nu_plugin_tree = { version = "0.14.0", path = "./crates/nu_plugin_tree", optional=true }
nu-cli = { version = "0.16.0", path = "./crates/nu-cli" }
nu-source = { version = "0.16.0", path = "./crates/nu-source" }
nu-plugin = { version = "0.16.0", path = "./crates/nu-plugin" }
nu-protocol = { version = "0.16.0", path = "./crates/nu-protocol" }
nu-errors = { version = "0.16.0", path = "./crates/nu-errors" }
nu-parser = { version = "0.16.0", path = "./crates/nu-parser" }
nu-value-ext = { version = "0.16.0", path = "./crates/nu-value-ext" }
nu_plugin_binaryview = { version = "0.16.0", path = "./crates/nu_plugin_binaryview", optional=true }
nu_plugin_fetch = { version = "0.16.0", path = "./crates/nu_plugin_fetch", optional=true }
nu_plugin_inc = { version = "0.16.0", path = "./crates/nu_plugin_inc", optional=true }
nu_plugin_match = { version = "0.16.0", path = "./crates/nu_plugin_match", optional=true }
nu_plugin_post = { version = "0.16.0", path = "./crates/nu_plugin_post", optional=true }
nu_plugin_ps = { version = "0.16.0", path = "./crates/nu_plugin_ps", optional=true }
nu_plugin_start = { version = "0.16.0", path = "./crates/nu_plugin_start", optional=true }
nu_plugin_sys = { version = "0.16.0", path = "./crates/nu_plugin_sys", optional=true }
nu_plugin_textview = { version = "0.16.0", path = "./crates/nu_plugin_textview", optional=true }
nu_plugin_tree = { version = "0.16.0", path = "./crates/nu_plugin_tree", optional=true }
crossterm = { version = "0.17.2", optional = true }
semver = { version = "0.9.0", optional = true }
syntect = { version = "4.1", default-features = false, features = ["default-fancy"], optional = true}
crossterm = { version = "0.17.5", optional = true }
semver = { version = "0.10.0", optional = true }
syntect = { version = "4.2", default-features = false, features = ["default-fancy"], optional = true}
url = { version = "2.1.1", optional = true }
clap = "2.33.0"
clap = "2.33.1"
ctrlc = "3.1.4"
dunce = "1.0.0"
dunce = "1.0.1"
futures = { version = "0.3", features = ["compat", "io-compat"] }
log = "0.4.8"
pretty_env_logger = "0.4.0"
starship = "0.43.0"
[dev-dependencies]
nu-test-support = { version = "0.14.0", path = "./crates/nu-test-support" }
nu-test-support = { version = "0.16.0", path = "./crates/nu-test-support" }
[build-dependencies]
toml = "0.5.6"
serde = { version = "1.0.106", features = ["derive"] }
nu-build = { version = "0.14.0", path = "./crates/nu-build" }
serde = { version = "1.0.110", features = ["derive"] }
nu-build = { version = "0.16.0", path = "./crates/nu-build" }
[features]
# Test executables
test-bins = []
default = ["sys", "ps", "textview", "inc", "str"]
stable = ["default", "starship-prompt", "binaryview", "match", "tree", "average", "post", "fetch", "clipboard-cli", "trash-support", "start"]
default = ["sys", "ps", "textview", "inc"]
stable = ["default", "binaryview", "match", "tree", "post", "fetch", "clipboard-cli", "trash-support", "start"]
# Default
textview = ["crossterm", "syntect", "url", "nu_plugin_textview"]
sys = ["nu_plugin_sys"]
ps = ["nu_plugin_ps"]
inc = ["semver", "nu_plugin_inc"]
str = ["nu_plugin_str"]
# Stable
average = ["nu_plugin_average"]
binaryview = ["nu_plugin_binaryview"]
fetch = ["nu_plugin_fetch"]
match = ["nu_plugin_match"]
@ -83,34 +77,9 @@ tree = ["nu_plugin_tree"]
start = ["nu_plugin_start"]
clipboard-cli = ["nu-cli/clipboard-cli"]
starship-prompt = ["nu-cli/starship-prompt"]
# starship-prompt = ["nu-cli/starship-prompt"]
trash-support = ["nu-cli/trash-support"]
[[bin]]
name = "fail"
path = "crates/nu-test-support/src/bins/fail.rs"
required-features = ["test-bins"]
[[bin]]
name = "chop"
path = "crates/nu-test-support/src/bins/chop.rs"
required-features = ["test-bins"]
[[bin]]
name = "cococo"
path = "crates/nu-test-support/src/bins/cococo.rs"
required-features = ["test-bins"]
[[bin]]
name = "nonu"
path = "crates/nu-test-support/src/bins/nonu.rs"
required-features = ["test-bins"]
[[bin]]
name = "iecho"
path = "crates/nu-test-support/src/bins/iecho.rs"
required-features = ["test-bins"]
# Core plugins that ship with `cargo install nu` by default
# Currently, Cargo limits us to installing only one binary
# unless we use [[bin]], so we use this as a workaround
@ -129,22 +98,12 @@ name = "nu_plugin_core_ps"
path = "src/plugins/nu_plugin_core_ps.rs"
required-features = ["ps"]
[[bin]]
name = "nu_plugin_core_str"
path = "src/plugins/nu_plugin_core_str.rs"
required-features = ["str"]
[[bin]]
name = "nu_plugin_core_sys"
path = "src/plugins/nu_plugin_core_sys.rs"
required-features = ["sys"]
# Stable plugins
[[bin]]
name = "nu_plugin_stable_average"
path = "src/plugins/nu_plugin_stable_average.rs"
required-features = ["average"]
[[bin]]
name = "nu_plugin_stable_fetch"
path = "src/plugins/nu_plugin_stable_fetch.rs"

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.

203
README.md
View File

@ -1,17 +1,18 @@
# 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)
[![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)
[![Discord](https://img.shields.io/discord/601130461678272522.svg?logo=discord)](https://discord.gg/NtAbbGn)
[![The Changelog #363](https://img.shields.io/badge/The%20Changelog-%23363-61c192.svg)](https://changelog.com/podcast/363)
# Nu Shell
## Nu Shell
A new type of shell.
![Example of nushell](images/nushell-autocomplete.gif "Example of nushell")
# Status
## Status
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.
@ -21,7 +22,7 @@ Its design is also subject to change as it matures.
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.
# Learning more
## Learning more
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.
@ -38,13 +39,13 @@ Try it in Gitpod.
[![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.
To build Nu, you will need to use the **latest stable (1.39 or later)** version of the compiler.
To build Nu, you will need to use the **latest stable (1.41 or later)** version of the compiler.
Required dependencies:
@ -58,19 +59,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`):
```
```bash
cargo install nu
```
You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/en/installation.html#dependencies) for your platform), once you have checked out this repo with git:
```
```bash
cargo build --workspace --features=stable
```
## Docker
### Docker
### Quickstart
#### Quickstart
Want to try Nu right away? Execute the following to get started.
@ -78,14 +79,14 @@ Want to try Nu right away? Execute the following to get started.
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)
on Quay.io. Pulling a container would come down to:
```bash
$ docker pull quay.io/nushell/nu
$ docker pull quay.io/nushell/nu-base
docker pull quay.io/nushell/nu
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`
@ -95,41 +96,41 @@ Optionally, you can also build the containers locally using the [dockerfiles pro
To build the base image:
```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):
```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:
```bash
$ docker run -it nushell/nu-base
$ docker run -it nushell/nu
docker run -it nushell/nu-base
docker run -it nushell/nu
/> exit
```
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)
### Fedora
#### Fedora
[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.
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.
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.
Nu takes this a step further and builds heavily on the idea of _pipelines_.
@ -138,104 +139,110 @@ 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 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 are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
```
/home/jonathan/Source/nushell(master)> ls | where type == "Directory" | autoview
────┬───────────┬───────────┬──────────┬────────┬──────────────┬────────────────
# │ name │ type │ readonly │ size │ accessed │ modified
────┼───────────┼───────────┼──────────┼────────┼──────────────┼────────────────
0 │ .azure │ Directory │ │ 4.1 KB │ 2 months ago │ a day ago
1 │ target │ Directory │ │ 4.1 KB │ 3 days ago │ 3 days ago
2 │ images │ Directory │ 4.1 KB │ 2 months ago │ 2 weeks ago
3 │ tests │ Directory │ 4.1 KB │ 2 months ago │ 37 minutes ago
4 │ tmp │ Directory │ │ 4.1 KB │ 2 weeks ago │ 2 weeks ago
5 │ src │ Directory │ │ 4.1 KB │ 2 months ago │ 37 minutes ago
6 │ assets │ Directory │ │ 4.1 KB │ a month ago │ a month ago
7 │ docs │ Directory │ │ 4.1 KB │ 2 months ago │ 2 months ago
────┴───────────┴───────────┴──────────┴────────┴──────────────┴────────────────
```shell
> ls | where type == "Dir" | autoview
───────────┬──────┬───────┬──────────────
# │ name │ type │ size │ modified
───────────┼──────┼───────┼──────────────
0 │ assets │ Dir │ 128 B │ 5 months ago
1 │ crates │ Dir │ 704 B │ 50 mins ago
2 │ debian │ Dir352 B │ 5 months ago
3 │ docker │ Dir288 B │ 3 months ago
4 │ docs │ Dir │ 192 B │ 50 mins ago
5 │ images │ Dir │ 160 B │ 5 months ago
6 │ src │ Dir │ 128 B │ 1 day ago
7 │ target │ Dir │ 160 B │ 5 days 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.
We could have also written the above:
```
/home/jonathan/Source/nushell(master)> ls | where type == Directory
```shell
> ls | where type == Dir
```
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.
```text
/home/jonathan/Source/nushell(master)> ps | where cpu > 0
───┬───────┬─────────────────┬──────────┬──────────
# │ pid │ name │ status │ cpu
───┼───────┼─────────────────┼──────────┼──────────
0992 │ chrome │ Sleeping │ 6.988768
14240 │ chrome │ Sleeping │ 5.645982
2 │ 13973 │ qemu-system-x86 │ Sleeping │ 4.996551
3 │ 15746 │ nu │ Sleeping │ 84.59905
───┴───────┴─────────────────┴──────────┴──────────
```shell
> ps | where cpu > 0
───┬───────┬───────────────────┬──────────┬─────────┬──────────┬──────────
# │ pid │ name │ status │ cpu │ mem │ virtual
───┼───────┼───────────────────┼──────────┼─────────┼──────────┼──────────
0 435 │ irq/142-SYNA327 │ Sleeping │ 7.5699 │ 0 B │ 0 B
1 1609 │ pulseaudio │ Sleeping │ 6.5605 │ 10.6 MB │ 2.3 GB
21625 │ gnome-shell │ Sleeping │ 6.5684 │ 639.6 MB │ 7.3 GB
32202 │ Web Content │ Sleeping │ 6.8157 │ 320.8 MB │ 3.0 GB
4328788 │ nu_plugin_core_ps │ Sleeping │ 92.5750 │ 5.9 MB │ 633.2 MB
───┴────────┴───────────────────┴──────────┴─────────┴──────────┴──────────
```
## Opening files
### Opening files
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:
```
/home/jonathan/Source/nushell(master)> open Cargo.toml
──────────────────┬────────────────┬──────────────────
bin │ dependencies │ dev-dependencies
──────────────────┼────────────────┼──────────────────
[table: 12 rows] │ [table: 1 row] │ [table: 1 row]
──────────────────┴────────────────┴──────────────────
```shell
> open Cargo.toml
────────────────────┬───────────────────────────
bin [table 18 rows]
build-dependencies │ [row nu-build serde toml]
dependencies │ [row 29 columns]
dev-dependencies │ [row nu-test-support]
features │ [row 19 columns]
package │ [row 12 columns]
workspace │ [row members]
────────────────────┴───────────────────────────
```
We can pipeline this into a command that gets the contents of one of the columns:
```
/home/jonathan/Source/nushell(master)> open Cargo.toml | get package
─────────────────┬────────────────────────────┬─────────┬─────────┬──────┬─────────
authors │ description │ edition │ license │ name │ version
─────────────────┼────────────────────────────┼─────────┼─────────┼──────┼─────────
[table: 3 rows] │ A shell for the GitHub era │ 2018 │ MIT │ nu │ 0.9.0
─────────────────┴────────────────────────────┴─────────┴─────────┴──────┴─────────
```shell
> open Cargo.toml | get package
───────────────┬────────────────────────────────────
authors [table 1 rows]
default-run │ nu
description │ A new type of shell
documentation │ https://www.nushell.sh/book/
edition │ 2018
exclude │ [table 1 rows]
homepage │ https://www.nushell.sh
license │ MIT
name │ nu
readme │ README.md
repository │ https://github.com/nushell/nushell
version │ 0.15.1
───────────────┴────────────────────────────────────
```
Finally, we can use commands outside of Nu once we have the data we want:
```
/home/jonathan/Source/nushell(master)> open Cargo.toml | get package.version | echo $it
0.9.0
```shell
> open Cargo.toml | get package.version | echo $it
0.15.1
```
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. It currently supports the following settings:
| Variable | Type | Description |
| --------------- | -------------------- | -------------------------------------------------------------- |
| path | table of strings | PATH to use to find binaries |
| env | row | the environment variables to pass to external commands |
| ctrlc_exit | boolean | whether or not to exit Nu after multiple ctrl-c presses |
| table_mode | "light" or other | enable lightweight or normal tables |
| edit_mode | "vi" or "emacs" | changes line editing to "vi" or "emacs" mode |
| completion_mode | "circular" or "list" | changes completion type to "circular" (default) or "list" mode |
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:
```
```shell
> config --set [edit_mode "vi"]
> 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 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.
@ -246,7 +253,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.
## Plugins
### Plugins
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.
@ -258,7 +265,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 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.
@ -272,15 +279,39 @@ 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.
# 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).
# Contributing
## 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:
| Features | Not started | Prototype | MVP | Preview | Mature | Notes
| -------- |:-----------:|:---------:|:---:|:-------:|:------:| -----
| Aliases | | X | | | | Initial implementation but lacks necessary 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
| 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
| Protocol | | | X | | | Streaming protocol is serviceable
| 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
| Documentation | | X | | | | Book and related are barebones and lack task-based lessons
| Paging | | X | | | | Textview has paging, but we'd like paging for tables
| Functions| X | | | | | No functions, yet, only aliases
| Variables| X | | | | | Nu doesn't yet support variables
| Completions | | X | | | | Completions are currently barebones, at best
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked
## 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.
# License
## License
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,6 +1,6 @@
[package]
name = "nu-build"
version = "0.14.0"
version = "0.16.0"
authors = ["The Nu Project Contributors"]
edition = "2018"
description = "Core build system for nushell"
@ -10,7 +10,7 @@ license = "MIT"
doctest = false
[dependencies]
serde = { version = "1.0.106", features = ["derive"] }
serde = { version = "1.0.114", features = ["derive"] }
lazy_static = "1.4.0"
serde_json = "1.0.51"
serde_json = "1.0.55"
toml = "0.5.6"

View File

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

View File

@ -4,15 +4,14 @@ use crate::commands::plugin::JsonRpc;
use crate::commands::plugin::{PluginCommand, PluginSink};
use crate::commands::whole_stream_command;
use crate::context::Context;
#[cfg(not(feature = "starship-prompt"))]
use crate::git::current_branch;
use crate::path::canonicalize;
use crate::prelude::*;
use crate::EnvironmentSyncer;
use futures_codec::FramedRead;
use nu_errors::ShellError;
use nu_errors::{ProximateShellError, ShellDiagnostic, ShellError};
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use log::{debug, trace};
use rustyline::error::ReadlineError;
@ -48,8 +47,9 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
let mut input = String::new();
let result = match reader.read_line(&mut input) {
Ok(count) => {
trace!("processing response ({} bytes)", count);
trace!("response: {}", input);
trace!(target: "nu::load", "plugin infrastructure -> config response");
trace!(target: "nu::load", "plugin infrastructure -> processing response ({} bytes)", count);
trace!(target: "nu::load", "plugin infrastructure -> response: {}", input);
let response = serde_json::from_str::<JsonRpc<Result<Signature, ShellError>>>(&input);
match response {
@ -57,13 +57,13 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
Ok(params) => {
let fname = path.to_string_lossy();
trace!("processing {:?}", params);
trace!(target: "nu::load", "plugin infrastructure -> processing {:?}", params);
let name = params.name.clone();
let fname = fname.to_string();
if context.get_command(&name).is_some() {
trace!("plugin {:?} already loaded.", &name);
trace!(target: "nu::load", "plugin infrastructure -> {:?} already loaded.", &name);
} else if params.is_filter {
context.add_commands(vec![whole_stream_command(PluginCommand::new(
name, fname, params,
@ -78,7 +78,7 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
Err(e) => Err(e),
},
Err(e) => {
trace!("incompatible plugin {:?}", input);
trace!(target: "nu::load", "plugin infrastructure -> incompatible {:?}", input);
Err(ShellError::untagged_runtime_error(format!(
"Error: {:?}",
e
@ -109,13 +109,19 @@ fn search_paths() -> Vec<std::path::PathBuf> {
}
}
#[cfg(not(debug_assertions))]
{
match env::var_os("PATH") {
Some(paths) => {
search_paths.extend(env::split_paths(&paths).collect::<Vec<_>>());
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
if let Some(plugin_dirs) = config.get("plugin_dirs") {
if let Value {
value: UntaggedValue::Table(pipelines),
..
} = plugin_dirs
{
for pipeline in pipelines {
if let Ok(plugin_dir) = pipeline.as_string() {
search_paths.push(PathBuf::from(plugin_dir));
}
}
}
None => println!("PATH is not defined in the environment."),
}
}
@ -181,7 +187,7 @@ pub fn load_plugins(context: &mut Context) -> Result<(), ShellError> {
};
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
let _ = load_plugin(&path, &mut context.clone());
@ -263,6 +269,7 @@ pub fn create_default_context(
whole_stream_command(Debug),
whole_stream_command(Alias),
whole_stream_command(WithEnv),
whole_stream_command(Do),
// Statistics
whole_stream_command(Size),
whole_stream_command(Count),
@ -278,12 +285,28 @@ pub fn create_default_context(
whole_stream_command(Autoview),
whole_stream_command(Table),
// Text manipulation
whole_stream_command(Split),
whole_stream_command(SplitColumn),
whole_stream_command(SplitRow),
whole_stream_command(Lines),
whole_stream_command(Trim),
whole_stream_command(Echo),
whole_stream_command(Parse),
whole_stream_command(Str),
whole_stream_command(StrToDecimal),
whole_stream_command(StrToInteger),
whole_stream_command(StrDowncase),
whole_stream_command(StrUpcase),
whole_stream_command(StrCapitalize),
whole_stream_command(StrFindReplace),
whole_stream_command(StrSubstring),
whole_stream_command(StrSet),
whole_stream_command(StrToDatetime),
whole_stream_command(StrTrim),
whole_stream_command(StrCollect),
whole_stream_command(BuildString),
whole_stream_command(Ansi),
whole_stream_command(Char),
// Column manipulation
whole_stream_command(Reject),
whole_stream_command(Select),
@ -297,8 +320,10 @@ pub fn create_default_context(
whole_stream_command(Prepend),
whole_stream_command(SortBy),
whole_stream_command(GroupBy),
whole_stream_command(GroupByDate),
whole_stream_command(First),
whole_stream_command(Last),
whole_stream_command(Every),
whole_stream_command(Nth),
whole_stream_command(Drop),
whole_stream_command(Format),
@ -324,7 +349,13 @@ pub fn create_default_context(
whole_stream_command(Headers),
// Data processing
whole_stream_command(Histogram),
whole_stream_command(Sum),
whole_stream_command(Math),
whole_stream_command(MathAverage),
whole_stream_command(MathMedian),
whole_stream_command(MathMinimum),
whole_stream_command(MathMode),
whole_stream_command(MathMaximum),
whole_stream_command(MathSummation),
// File format output
whole_stream_command(To),
whole_stream_command(ToBSON),
@ -360,6 +391,11 @@ pub fn create_default_context(
whole_stream_command(FromVcf),
// "Private" commands (not intended to be accessed directly)
whole_stream_command(RunExternalCommand { interactive }),
// Random value generation
whole_stream_command(Random),
whole_stream_command(RandomBool),
whole_stream_command(RandomDice),
whole_stream_command(RandomUUID),
]);
cfg_if::cfg_if! {
@ -375,9 +411,7 @@ pub fn create_default_context(
#[cfg(feature = "clipboard")]
{
context.add_commands(vec![whole_stream_command(
crate::commands::clip::clipboard::Clip,
)]);
context.add_commands(vec![whole_stream_command(crate::commands::clip::Clip)]);
}
}
@ -465,8 +499,8 @@ pub async fn run_pipeline_standalone(
}
LineResult::Error(line, err) => {
context.with_host(|host| {
print_err(err, host, &Text::from(line.clone()));
context.with_host(|_host| {
print_err(err, &Text::from(line.clone()));
});
context.maybe_print_errors(Text::from(line));
@ -482,15 +516,15 @@ pub async fn run_pipeline_standalone(
}
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input.
pub async fn cli() -> Result<(), Box<dyn Error>> {
pub async fn cli(
mut syncer: EnvironmentSyncer,
mut context: Context,
) -> Result<(), Box<dyn Error>> {
#[cfg(windows)]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
#[cfg(not(windows))]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
let mut syncer = crate::EnvironmentSyncer::new();
let mut context = create_default_context(&mut syncer, true)?;
let _ = load_plugins(&mut context);
let config = Config::builder().color_mode(ColorMode::Forced).build();
@ -558,7 +592,30 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
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")
.map(|s| match s.value.expect_string() {
"vi" => EditMode::Vi,
@ -569,14 +626,21 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
rl.set_edit_mode(edit_mode);
let key_timeout = config::config(Tag::unknown())?
let max_history_size = config
.get("history_size")
.map(|i| i.value.expect_int())
.unwrap_or(100_000);
rl.set_max_history_size(max_history_size as usize);
let key_timeout = config
.get("key_timeout")
.map(|s| s.value.expect_int())
.unwrap_or(1);
rl.set_keyseq_timeout(key_timeout as i32);
let completion_mode = config::config(Tag::unknown())?
let completion_mode = config
.get("completion_mode")
.map(|s| match s.value.expect_string() {
"list" => CompletionType::List,
@ -588,9 +652,9 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
rl.set_completion_type(completion_mode);
let colored_prompt = {
#[cfg(feature = "starship-prompt")]
{
if use_starship {
std::env::set_var("STARSHIP_SHELL", "");
std::env::set_var("PWD", &cwd);
let mut starship_context =
starship::context::Context::new_with_dir(clap::ArgMatches::default(), cwd);
@ -604,9 +668,63 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
_ => {}
};
starship::print::get_prompt(starship_context)
}
#[cfg(not(feature = "starship-prompt"))]
{
} 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()
}
}
}
Err(e) => {
crate::cli::print_err(e, &Text::from(prompt_line));
context.clear_errors();
"> ".to_string()
}
}
} else {
format!(
"\x1b[32m{}{}\x1b[m> ",
cwd,
@ -645,17 +763,17 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
match line {
LineResult::Success(line) => {
rl.add_history_entry(line.clone());
rl.add_history_entry(&line);
let _ = rl.save_history(&History::path());
context.maybe_print_errors(Text::from(line));
}
LineResult::Error(line, err) => {
rl.add_history_entry(line.clone());
rl.add_history_entry(&line);
let _ = rl.save_history(&History::path());
context.with_host(|host| {
print_err(err, host, &Text::from(line.clone()));
context.with_host(|_host| {
print_err(err, &Text::from(line.clone()));
});
context.maybe_print_errors(Text::from(line.clone()));
@ -705,7 +823,7 @@ fn chomp_newline(s: &str) -> &str {
}
}
enum LineResult {
pub enum LineResult {
Success(String),
Error(String, ShellError),
CtrlC,
@ -713,7 +831,7 @@ enum LineResult {
}
/// 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>,
ctx: &mut Context,
redirect_stdin: bool,
@ -724,6 +842,7 @@ async fn process_line(
Ok(line) => {
let line = chomp_newline(line);
ctx.raw_input = line.to_string();
let result = match nu_parser::lite_parse(&line, 0) {
Err(err) => {
@ -860,7 +979,16 @@ async fn process_line(
trace!("{:#?}", classified_block);
let env = ctx.get_env();
match run_block(&classified_block.block, ctx, input_stream, &Scope::env(env)).await {
match run_block(
&classified_block.block,
ctx,
input_stream,
&Value::nothing(),
&IndexMap::new(),
&env,
)
.await
{
Ok(input) => {
// Running a pipeline gives us back a stream that we can then
// work through. At the top level, we just want to pull on the
@ -872,11 +1000,15 @@ async fn process_line(
shell_manager: ctx.shell_manager.clone(),
host: ctx.host.clone(),
ctrl_c: ctx.ctrl_c.clone(),
current_errors: ctx.current_errors.clone(),
registry: ctx.registry.clone(),
name: Tag::unknown(),
raw_input: line.to_string(),
};
if let Ok(mut output_stream) = crate::commands::autoview::autoview(context) {
if let Ok(mut output_stream) =
crate::commands::autoview::autoview(context).await
{
loop {
match output_stream.try_next().await {
Ok(Some(ReturnSuccess::Value(Value {
@ -908,19 +1040,19 @@ async fn process_line(
}
}
pub fn print_err(err: ShellError, host: &dyn Host, source: &Text) {
pub fn print_err(err: ShellError, source: &Text) {
if let Some(diag) = err.into_diagnostic() {
let writer = host.err_termcolor();
let mut source = source.to_string();
source.push_str(" ");
let files = nu_parser::Files::new(source);
let source = source.to_string();
let mut files = codespan_reporting::files::SimpleFiles::new();
files.add("shell", source);
let writer = codespan_reporting::term::termcolor::StandardStream::stderr(
codespan_reporting::term::termcolor::ColorChoice::Always,
);
let config = codespan_reporting::term::Config::default();
let _ = std::panic::catch_unwind(move || {
let _ = language_reporting::emit(
&mut writer.lock(),
&files,
&diag,
&language_reporting::DefaultConfig,
);
let _ = codespan_reporting::term::emit(&mut writer.lock(), &config, &files, &diag);
});
}
}

View File

@ -5,13 +5,17 @@ mod from_delimited_data;
mod to_delimited_data;
pub(crate) mod alias;
pub(crate) mod ansi;
pub(crate) mod append;
pub(crate) mod args;
pub(crate) mod autoview;
pub(crate) mod build_string;
pub(crate) mod cal;
pub(crate) mod calc;
pub(crate) mod cd;
pub(crate) mod char_;
pub(crate) mod classified;
#[cfg(feature = "clipboard")]
pub(crate) mod clip;
pub(crate) mod command;
pub(crate) mod compact;
@ -21,6 +25,7 @@ pub(crate) mod cp;
pub(crate) mod date;
pub(crate) mod debug;
pub(crate) mod default;
pub(crate) mod do_;
pub(crate) mod drop;
pub(crate) mod du;
pub(crate) mod each;
@ -28,6 +33,7 @@ pub(crate) mod echo;
pub(crate) mod enter;
#[allow(unused)]
pub(crate) mod evaluate_by;
pub(crate) mod every;
pub(crate) mod exit;
pub(crate) mod first;
pub(crate) mod format;
@ -50,6 +56,7 @@ pub(crate) mod from_xml;
pub(crate) mod from_yaml;
pub(crate) mod get;
pub(crate) mod group_by;
pub(crate) mod group_by_date;
pub(crate) mod headers;
pub(crate) mod help;
pub(crate) mod histogram;
@ -64,6 +71,7 @@ pub(crate) mod lines;
pub(crate) mod ls;
#[allow(unused)]
pub(crate) mod map_max_by;
pub(crate) mod math;
pub(crate) mod merge;
pub(crate) mod mkdir;
pub(crate) mod mv;
@ -76,6 +84,7 @@ pub(crate) mod plugin;
pub(crate) mod prepend;
pub(crate) mod prev;
pub(crate) mod pwd;
pub(crate) mod random;
pub(crate) mod range;
#[allow(unused)]
pub(crate) mod reduce_by;
@ -94,10 +103,9 @@ pub(crate) mod skip;
pub(crate) mod skip_until;
pub(crate) mod skip_while;
pub(crate) mod sort_by;
pub(crate) mod split;
pub(crate) mod split_by;
pub(crate) mod split_column;
pub(crate) mod split_row;
pub(crate) mod sum;
pub(crate) mod str_;
#[allow(unused)]
pub(crate) mod t_sort_by;
pub(crate) mod table;
@ -130,9 +138,12 @@ pub(crate) use command::{
};
pub(crate) use alias::Alias;
pub(crate) use ansi::Ansi;
pub(crate) use append::Append;
pub(crate) use build_string::BuildString;
pub(crate) use cal::Cal;
pub(crate) use calc::Calc;
pub(crate) use char_::Char;
pub(crate) use compact::Compact;
pub(crate) use config::Config;
pub(crate) use count::Count;
@ -140,6 +151,7 @@ pub(crate) use cp::Cpy;
pub(crate) use date::Date;
pub(crate) use debug::Debug;
pub(crate) use default::Default;
pub(crate) use do_::Do;
pub(crate) use drop::Drop;
pub(crate) use du::Du;
pub(crate) use each::Each;
@ -154,6 +166,7 @@ pub(crate) mod touch;
pub(crate) use enter::Enter;
#[allow(unused_imports)]
pub(crate) use evaluate_by::EvaluateBy;
pub(crate) use every::Every;
pub(crate) use exit::Exit;
pub(crate) use first::First;
pub(crate) use format::Format;
@ -178,6 +191,7 @@ pub(crate) use from_yaml::FromYAML;
pub(crate) use from_yaml::FromYML;
pub(crate) use get::Get;
pub(crate) use group_by::GroupBy;
pub(crate) use group_by_date::GroupByDate;
pub(crate) use headers::Headers;
pub(crate) use help::Help;
pub(crate) use histogram::Histogram;
@ -191,6 +205,9 @@ pub(crate) use lines::Lines;
pub(crate) use ls::Ls;
#[allow(unused_imports)]
pub(crate) use map_max_by::MapMaxBy;
pub(crate) use math::{
Math, MathAverage, MathMaximum, MathMedian, MathMinimum, MathMode, MathSummation,
};
pub(crate) use merge::Merge;
pub(crate) use mkdir::Mkdir;
pub(crate) use mv::Move;
@ -202,6 +219,7 @@ pub(crate) use pivot::Pivot;
pub(crate) use prepend::Prepend;
pub(crate) use prev::Previous;
pub(crate) use pwd::Pwd;
pub(crate) use random::{Random, RandomBool, RandomDice, RandomUUID};
pub(crate) use range::Range;
#[allow(unused_imports)]
pub(crate) use reduce_by::ReduceBy;
@ -219,10 +237,14 @@ pub(crate) use skip::Skip;
pub(crate) use skip_until::SkipUntil;
pub(crate) use skip_while::SkipWhile;
pub(crate) use sort_by::SortBy;
pub(crate) use split::Split;
pub(crate) use split::SplitColumn;
pub(crate) use split::SplitRow;
pub(crate) use split_by::SplitBy;
pub(crate) use split_column::SplitColumn;
pub(crate) use split_row::SplitRow;
pub(crate) use sum::Sum;
pub(crate) use str_::{
Str, StrCapitalize, StrCollect, StrDowncase, StrFindReplace, StrSet, StrSubstring,
StrToDatetime, StrToDecimal, StrToInteger, StrTrim, StrUpcase,
};
#[allow(unused_imports)]
pub(crate) use t_sort_by::TSortBy;
pub(crate) use table::Table;

View File

@ -1,8 +1,11 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::data::config;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, Value};
use nu_protocol::{
hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::Tagged;
pub struct Alias;
@ -12,8 +15,10 @@ pub struct AliasArgs {
pub name: Tagged<String>,
pub args: Vec<Value>,
pub block: Block,
pub save: Option<bool>,
}
#[async_trait]
impl WholeStreamCommand for Alias {
fn name(&self) -> &str {
"alias"
@ -28,53 +33,119 @@ impl WholeStreamCommand for Alias {
SyntaxShape::Block,
"the block to run as the body of the alias",
)
.switch("save", "save the alias to your config", Some('s'))
}
fn usage(&self) -> &str {
"Define a shortcut for another command."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, alias)?.run()
alias(args, registry).await
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "An alias without parameters",
example: "alias say-hi [] { echo 'Hello!' }",
result: None,
},
Example {
description: "An alias with a single parameter",
example: "alias l [x] { ls $x }",
result: None,
},
]
}
}
pub fn alias(
AliasArgs {
name,
args: list,
block,
}: AliasArgs,
_: RunnableContext,
pub async fn alias(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let mut args: Vec<String> = vec![];
for item in list.iter() {
if let Ok(string) = item.as_string() {
args.push(format!("${}", string));
} else {
yield Err(ShellError::labeled_error("Expected a string", "expected a string", item.tag()));
let registry = registry.clone();
let mut raw_input = args.raw_input.clone();
let (
AliasArgs {
name,
args: list,
block,
save,
},
_ctx,
) = args.process(&registry).await?;
let mut processed_args: Vec<String> = vec![];
if let Some(true) = save {
let mut result = crate::data::config::read(name.clone().tag, &None)?;
// process the alias to remove the --save flag
let left_brace = raw_input.find('{').unwrap_or(0);
let right_brace = raw_input.rfind('}').unwrap_or_else(|| raw_input.len());
let left = raw_input[..left_brace]
.replace("--save", "")
.replace("-s", "");
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
let alias: Value = raw_input.trim().to_string().into();
let alias_start = raw_input.find('[').unwrap_or(0); // used to check if the same alias already exists
// add to startup if alias doesn't exist and replce if it does
match result.get_mut("startup") {
Some(startup) => {
if let UntaggedValue::Table(ref mut commands) = startup.value {
if let Some(command) = commands.iter_mut().find(|command| {
let cmd_str = command.as_string().unwrap_or_default();
cmd_str.starts_with(&raw_input[..alias_start])
}) {
*command = alias;
} else {
commands.push(alias);
}
}
}
None => {
let table = UntaggedValue::table(&[alias]);
result.insert("startup".to_string(), table.into_value(Tag::default()));
}
}
yield ReturnSuccess::action(CommandAction::AddAlias(name.to_string(), 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)]
mod tests {
use super::Alias;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Alias {})
}
}

View File

@ -0,0 +1,140 @@
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")]),
},
]
}
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

@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, Value};
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
#[derive(Deserialize)]
struct AppendArgs {
@ -11,6 +11,7 @@ struct AppendArgs {
pub struct Append;
#[async_trait]
impl WholeStreamCommand for Append {
fn name(&self) -> &str {
"append"
@ -28,29 +29,40 @@ impl WholeStreamCommand for Append {
"Append the given row to the table"
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, append)?.run()
let (AppendArgs { row }, input) = args.process(registry).await?;
let eos = futures::stream::iter(vec![row]);
Ok(input.chain(eos).to_output_stream())
}
fn examples(&self) -> &[Example] {
&[Example {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Add something to the end of a list or table",
example: "echo [1 2 3] | append 4",
result: Some(vec![
UntaggedValue::int(1).into(),
UntaggedValue::int(2).into(),
UntaggedValue::int(3).into(),
UntaggedValue::int(4).into(),
]),
}]
}
}
fn append(
AppendArgs { row }: AppendArgs,
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let mut after: VecDeque<Value> = VecDeque::new();
after.push_back(row);
let after = futures::stream::iter(after);
#[cfg(test)]
mod tests {
use super::Append;
Ok(OutputStream::from_input(input.chain(after)))
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Append {})
}
}

View File

@ -4,12 +4,13 @@ use crate::data::value::format_leaf;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
use nu_protocol::{Primitive, Scope, Signature, UntaggedValue, Value};
use parking_lot::Mutex;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
pub struct Autoview;
#[async_trait]
impl WholeStreamCommand for Autoview {
fn name(&self) -> &str {
"autoview"
@ -23,7 +24,7 @@ impl WholeStreamCommand for Autoview {
"View the contents of the pipeline as a table or list."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
@ -34,19 +35,24 @@ impl WholeStreamCommand for Autoview {
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,
})
.await
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Automatically view the results",
example: "ls | autoview",
result: None,
},
Example {
description: "Autoview is also implied. The above can be written as",
example: "ls",
result: None,
},
]
}
@ -55,6 +61,7 @@ impl WholeStreamCommand for Autoview {
pub struct RunnableContextWithoutInput {
pub shell_manager: ShellManager,
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub ctrl_c: Arc<AtomicBool>,
pub registry: CommandRegistry,
pub name: Tag,
@ -66,6 +73,7 @@ impl RunnableContextWithoutInput {
shell_manager: context.shell_manager,
host: context.host,
ctrl_c: context.ctrl_c,
current_errors: context.current_errors,
registry: context.registry,
name: context.name,
};
@ -73,241 +81,235 @@ impl RunnableContextWithoutInput {
}
}
pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
let binary = context.get_command("binaryview");
let text = context.get_command("textview");
let table = context.get_command("table");
Ok(OutputStream::new(async_stream! {
let (mut input_stream, context) = RunnableContextWithoutInput::convert(context);
#[derive(PartialEq)]
enum AutoPivotMode {
Auto,
Always,
Never,
}
let pivot_mode = crate::data::config::config(Tag::unknown());
let pivot_mode = if let Some(v) = pivot_mode?.get("pivot_mode") {
match v.as_string() {
Ok(m) if m.to_lowercase() == "auto" => AutoPivotMode::Auto,
Ok(m) if m.to_lowercase() == "always" => AutoPivotMode::Always,
Ok(m) if m.to_lowercase() == "never" => AutoPivotMode::Never,
_ => AutoPivotMode::Always,
}
} else {
AutoPivotMode::Always
};
let (mut input_stream, context) = RunnableContextWithoutInput::convert(context);
let term_width = context.host.lock().width();
if let Some(x) = input_stream.next().await {
match input_stream.next().await {
Some(x) => {
match input_stream.next().await {
Some(y) => {
let ctrl_c = context.ctrl_c.clone();
let stream = async_stream! {
yield Ok(x);
yield Ok(y);
Some(y) => {
let ctrl_c = context.ctrl_c.clone();
let xy = vec![x, y];
let xy_stream = futures::stream::iter(xy)
.chain(input_stream)
.interruptible(ctrl_c);
loop {
match input_stream.next().await {
Some(z) => {
if ctrl_c.load(Ordering::SeqCst) {
break;
}
yield Ok(z);
}
_ => break,
}
}
};
let stream = stream.to_input_stream();
let stream = InputStream::from_stream(xy_stream);
if let Some(table) = table {
let command_args = create_default_command_args(&context).with_input(stream);
let result = table.run(command_args, &context.registry);
if let Some(table) = table {
let command_args = create_default_command_args(&context).with_input(stream);
let result = table.run(command_args, &context.registry).await?;
result.collect::<Vec<_>>().await;
}
}
_ => {
match x {
Value {
value: UntaggedValue::Primitive(Primitive::String(ref s)),
tag: Tag { anchor, span },
} if anchor.is_some() => {
if let Some(text) = text {
let mut stream = VecDeque::new();
stream.push_back(
UntaggedValue::string(s).into_value(Tag { anchor, span }),
);
let command_args =
create_default_command_args(&context).with_input(stream);
let result = text.run(command_args, &context.registry).await?;
result.collect::<Vec<_>>().await;
} else {
out!("{}", s);
}
}
_ => {
match x {
Value {
value: UntaggedValue::Primitive(Primitive::String(ref s)),
tag: Tag { anchor, span },
} if anchor.is_some() => {
if let Some(text) = text {
let mut stream = VecDeque::new();
stream.push_back(UntaggedValue::string(s).into_value(Tag { anchor, span }));
let command_args = create_default_command_args(&context).with_input(stream);
let result = text.run(command_args, &context.registry);
result.collect::<Vec<_>>().await;
} else {
out!("{}", s);
}
}
Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
..
} => {
out!("{}", s);
}
Value {
value: UntaggedValue::Primitive(Primitive::Line(ref s)),
tag: Tag { anchor, span },
} if anchor.is_some() => {
if let Some(text) = text {
let mut stream = VecDeque::new();
stream.push_back(UntaggedValue::string(s).into_value(Tag { anchor, span }));
let command_args = create_default_command_args(&context).with_input(stream);
let result = text.run(command_args, &context.registry);
result.collect::<Vec<_>>().await;
} else {
out!("{}\n", s);
}
}
Value {
value: UntaggedValue::Primitive(Primitive::Line(s)),
..
} => {
out!("{}\n", s);
}
Value {
value: UntaggedValue::Primitive(Primitive::Path(s)),
..
} => {
out!("{}", s.display());
}
Value {
value: UntaggedValue::Primitive(Primitive::Int(n)),
..
} => {
out!("{}", n);
}
Value {
value: UntaggedValue::Primitive(Primitive::Decimal(n)),
..
} => {
out!("{}", n);
}
Value {
value: UntaggedValue::Primitive(Primitive::Boolean(b)),
..
} => {
out!("{}", b);
}
Value {
value: UntaggedValue::Primitive(Primitive::Duration(d)),
..
} => {
let output = format_leaf(&x).plain_string(100_000);
out!("{}", output);
}
Value {
value: UntaggedValue::Primitive(Primitive::Date(d)),
..
} => {
out!("{}", d);
}
Value {
value: UntaggedValue::Primitive(Primitive::Range(_)),
..
} => {
let output = format_leaf(&x).plain_string(100_000);
out!("{}", output);
}
Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
..
} => {
out!("{}", s);
}
Value {
value: UntaggedValue::Primitive(Primitive::Line(ref s)),
tag: Tag { anchor, span },
} if anchor.is_some() => {
if let Some(text) = text {
let mut stream = VecDeque::new();
stream.push_back(
UntaggedValue::string(s).into_value(Tag { anchor, span }),
);
let command_args =
create_default_command_args(&context).with_input(stream);
let result = text.run(command_args, &context.registry).await?;
result.collect::<Vec<_>>().await;
} else {
out!("{}\n", s);
}
}
Value {
value: UntaggedValue::Primitive(Primitive::Line(s)),
..
} => {
out!("{}\n", s);
}
Value {
value: UntaggedValue::Primitive(Primitive::Path(s)),
..
} => {
out!("{}", s.display());
}
Value {
value: UntaggedValue::Primitive(Primitive::Int(n)),
..
} => {
out!("{}", n);
}
Value {
value: UntaggedValue::Primitive(Primitive::Decimal(n)),
..
} => {
// TODO: normalize decimal to remove trailing zeros.
// normalization will be available in next release of bigdecimal crate
let mut output = n.to_string();
if output.contains('.') {
output = output.trim_end_matches('0').to_owned();
}
if output.ends_with('.') {
output.push('0');
}
out!("{}", output);
}
Value {
value: UntaggedValue::Primitive(Primitive::Boolean(b)),
..
} => {
out!("{}", b);
}
Value {
value: UntaggedValue::Primitive(Primitive::Duration(_)),
..
} => {
let output = format_leaf(&x).plain_string(100_000);
out!("{}", output);
}
Value {
value: UntaggedValue::Primitive(Primitive::Date(d)),
..
} => {
out!("{}", d);
}
Value {
value: UntaggedValue::Primitive(Primitive::Range(_)),
..
} => {
let output = format_leaf(&x).plain_string(100_000);
out!("{}", output);
}
Value { value: UntaggedValue::Primitive(Primitive::Binary(ref b)), .. } => {
if let Some(binary) = binary {
let mut stream = VecDeque::new();
stream.push_back(x);
let command_args = create_default_command_args(&context).with_input(stream);
let result = binary.run(command_args, &context.registry);
result.collect::<Vec<_>>().await;
} else {
use pretty_hex::*;
out!("{:?}", b.hex_dump());
}
}
Value {
value: UntaggedValue::Primitive(Primitive::Binary(ref b)),
..
} => {
if let Some(binary) = binary {
let mut stream = VecDeque::new();
stream.push_back(x);
let command_args =
create_default_command_args(&context).with_input(stream);
let result = binary.run(command_args, &context.registry).await?;
result.collect::<Vec<_>>().await;
} else {
use pretty_hex::*;
out!("{:?}", b.hex_dump());
}
}
Value { value: UntaggedValue::Error(e), .. } => {
yield Err(e);
}
Value {
value: UntaggedValue::Error(e),
..
} => {
return Err(e);
}
Value { value: UntaggedValue::Row(row), ..} => {
use prettytable::format::{FormatBuilder, LinePosition, LineSeparator};
use prettytable::{color, Attr, Cell, Row, Table};
use crate::data::value::{format_leaf, style_leaf};
use textwrap::fill;
Value {
value: UntaggedValue::Row(row),
..
} if pivot_mode == AutoPivotMode::Always
|| (pivot_mode == AutoPivotMode::Auto
&& (row
.entries
.iter()
.map(|(_, v)| v.convert_to_string())
.collect::<Vec<_>>()
.iter()
.fold(0usize, |acc, len| acc + len.len())
+ row.entries.iter().count() * 2)
> term_width) =>
{
let mut entries = vec![];
for (key, value) in row.entries.iter() {
entries.push(vec![
nu_table::StyledString::new(
key.to_string(),
nu_table::TextStyle {
alignment: nu_table::Alignment::Left,
color: Some(ansi_term::Color::Green),
is_bold: true,
},
),
nu_table::StyledString::new(
format_leaf(value).plain_string(100_000),
nu_table::TextStyle::basic(),
),
]);
}
let termwidth = std::cmp::max(textwrap::termwidth(), 20);
let table =
nu_table::Table::new(vec![], entries, nu_table::Theme::compact());
enum TableMode {
Light,
Normal,
}
nu_table::draw_table(&table, term_width);
}
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('─', '─', ' ', ' '))
.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() {
table.add_row(Row::new(vec![Cell::new(&fill(&key, max_key_len)).with_style(Attr::ForegroundColor(color::GREEN)).with_style(Attr::Bold),
Cell::new(&fill(&format_leaf(value).plain_string(100_000), max_val_len))]));
}
table.printstd();
// table.print_term(&mut *context.host.lock().out_terminal().ok_or_else(|| ShellError::untagged_runtime_error("Could not open terminal for output"))?)
// .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: ref item, .. } => {
if let Some(table) = table {
let mut stream = VecDeque::new();
stream.push_back(x);
let command_args = create_default_command_args(&context).with_input(stream);
let result = table.run(command_args, &context.registry);
result.collect::<Vec<_>>().await;
} else {
out!("{:?}", item);
}
}
Value {
value: ref item, ..
} => {
if let Some(table) = table {
let mut stream = VecDeque::new();
stream.push_back(x);
let command_args =
create_default_command_args(&context).with_input(stream);
let result = table.run(command_args, &context.registry).await?;
result.collect::<Vec<_>>().await;
} else {
out!("{:?}", item);
}
}
}
}
_ => {
//out!("<no results>");
}
}
}
// Needed for async_stream to type check
if false {
yield ReturnSuccess::value(UntaggedValue::nothing().into_untagged_value());
}
}))
Ok(OutputStream::empty())
}
fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawCommandArgs {
@ -315,6 +317,7 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
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: hir::Call {
@ -328,7 +331,19 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
is_last: true,
},
name_tag: context.name.clone(),
scope: Scope::empty(),
scope: Scope::new(),
},
}
}
#[cfg(test)]
mod tests {
use super::Autoview;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Autoview {})
}
}

View File

@ -0,0 +1,56 @@
use crate::prelude::*;
use nu_errors::ShellError;
use crate::commands::WholeStreamCommand;
use crate::data::value::format_leaf;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
#[derive(Deserialize)]
pub struct BuildStringArgs {
rest: Vec<Value>,
}
pub struct BuildString;
#[async_trait]
impl WholeStreamCommand for BuildString {
fn name(&self) -> &str {
"build-string"
}
fn signature(&self) -> Signature {
Signature::build("build-string")
.rest(SyntaxShape::Any, "all values to form into the string")
}
fn usage(&self) -> &str {
"Builds a string from the arguments"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (BuildStringArgs { rest }, _) = args.process(&registry).await?;
let mut output_string = String::new();
for r in rest {
output_string.push_str(&format_leaf(&r).plain_string(100_000))
}
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output_string).into_value(tag),
)))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Builds a string from a string and a number, without spaces between them",
example: "build-string 'foo' 3",
result: None,
}]
}
}

View File

@ -1,14 +1,13 @@
use crate::prelude::*;
use chrono::{DateTime, Datelike, Local, NaiveDate};
use nu_errors::ShellError;
use nu_protocol::Dictionary;
use crate::commands::{command::EvaluatedWholeStreamCommandArgs, WholeStreamCommand};
use crate::prelude::*;
use chrono::{Datelike, Local, NaiveDate};
use indexmap::IndexMap;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use nu_errors::ShellError;
use nu_protocol::{Dictionary, Signature, SyntaxShape, UntaggedValue, Value};
pub struct Cal;
#[async_trait]
impl WholeStreamCommand for Cal {
fn name(&self) -> &str {
"cal"
@ -25,6 +24,12 @@ impl WholeStreamCommand for Cal {
"Display a year-long calendar for the specified year",
None,
)
.named(
"week-start",
SyntaxShape::String,
"Display the calendar with the specified day as the first day of the week",
None,
)
.switch(
"month-names",
"Display the month names instead of integers",
@ -36,104 +41,151 @@ impl WholeStreamCommand for Cal {
"Display a calendar."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
cal(args, registry)
cal(args, registry).await
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "This month's calendar",
example: "cal",
result: None,
},
Example {
description: "The calendar for all of 2012",
example: "cal --full-year 2012",
result: None,
},
Example {
description: "This month's calendar with the week starting on monday",
example: "cal --week-start monday",
result: None,
},
]
}
}
pub fn cal(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
pub async fn cal(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let mut calendar_vec_deque = VecDeque::new();
let tag = args.call_info.name_tag.clone();
let (current_year, current_month, current_day) = get_current_date();
if args.has("full-year") {
let mut day_value: Option<u32> = Some(current_day);
let mut year_value = current_year as u64;
let mut selected_year: i32 = current_year;
let mut current_day_option: Option<u32> = Some(current_day);
if let Some(year) = args.get("full-year") {
if let Ok(year_u64) = year.as_u64() {
year_value = year_u64;
}
let month_range = if let Some(full_year_value) = args.get("full-year") {
if let Ok(year_u64) = full_year_value.as_u64() {
selected_year = year_u64 as i32;
if year_value != current_year as u64 {
day_value = None
if selected_year != current_year {
current_day_option = None
}
} else {
return Err(get_invalid_year_shell_error(&full_year_value.tag()));
}
add_year_to_table(
&mut calendar_vec_deque,
&tag,
year_value as i32,
current_year,
current_month,
day_value,
&args,
);
(1, 12)
} else {
let (day_start_offset, number_of_days_in_month, _) =
get_month_information(current_year, current_month, current_year);
(current_month, current_month)
};
add_month_to_table(
&mut calendar_vec_deque,
&tag,
current_year,
current_month,
Some(current_day),
day_start_offset,
number_of_days_in_month as usize,
&args,
);
let add_months_of_year_to_table_result = add_months_of_year_to_table(
&args,
&mut calendar_vec_deque,
&tag,
selected_year,
month_range,
current_month,
current_day_option,
);
match add_months_of_year_to_table_result {
Ok(()) => Ok(futures::stream::iter(calendar_vec_deque).to_output_stream()),
Err(error) => Err(error),
}
}
fn get_invalid_year_shell_error(year_tag: &Tag) -> ShellError {
ShellError::labeled_error("The year is invalid", "invalid year", year_tag)
}
struct MonthHelper {
selected_year: i32,
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 {
pub fn new(selected_year: i32, selected_month: u32) -> Result<MonthHelper, ()> {
let naive_date = NaiveDate::from_ymd_opt(selected_year, selected_month, 1).ok_or(())?;
let number_of_days_in_month =
MonthHelper::calculate_number_of_days_in_month(selected_year, selected_month)?;
Ok(MonthHelper {
selected_year,
selected_month,
day_number_of_week_month_starts_on: naive_date.weekday().num_days_from_sunday(),
number_of_days_in_month,
quarter_number: ((selected_month - 1) / 3) + 1,
month_name: naive_date.format("%B").to_string().to_ascii_lowercase(),
})
}
Ok(futures::stream::iter(calendar_vec_deque).to_output_stream())
fn calculate_number_of_days_in_month(
mut selected_year: i32,
mut selected_month: u32,
) -> Result<u32, ()> {
// 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:
// https://docs.rs/chrono/0.3.0/chrono/naive/date/struct.NaiveDate.html#example-30
if selected_month == 12 {
selected_year += 1;
selected_month = 1;
} else {
selected_month += 1;
};
let next_month_naive_date =
NaiveDate::from_ymd_opt(selected_year, selected_month, 1).ok_or(())?;
Ok(next_month_naive_date.pred().day())
}
}
fn get_current_date() -> (i32, u32, u32) {
let local_now: DateTime<Local> = Local::now();
let local_now_date = Local::now().date();
let current_year: i32 = local_now.date().year();
let current_month: u32 = local_now.date().month();
let current_day: u32 = local_now.date().day();
let current_year: i32 = local_now_date.year();
let current_month: u32 = local_now_date.month();
let current_day: u32 = local_now_date.day();
(current_year, current_month, current_day)
}
fn add_year_to_table(
fn add_months_of_year_to_table(
args: &EvaluatedWholeStreamCommandArgs,
mut calendar_vec_deque: &mut VecDeque<Value>,
tag: &Tag,
mut selected_year: i32,
current_year: i32,
selected_year: i32,
(start_month, end_month): (u32, u32),
current_month: u32,
current_day_option: Option<u32>,
args: &EvaluatedWholeStreamCommandArgs,
) {
for month_number in 1..=12 {
let (day_start_offset, number_of_days_in_month, chosen_date_is_valid) =
get_month_information(selected_year, month_number, current_year);
if !chosen_date_is_valid {
selected_year = current_year;
}
) -> Result<(), ShellError> {
for month_number in start_month..=end_month {
let mut new_current_day_option: Option<u32> = None;
if let Some(current_day) = current_day_option {
@ -142,159 +194,159 @@ fn add_year_to_table(
}
}
add_month_to_table(
let add_month_to_table_result = add_month_to_table(
&args,
&mut calendar_vec_deque,
&tag,
selected_year,
month_number,
new_current_day_option,
day_start_offset,
number_of_days_in_month,
&args,
);
add_month_to_table_result?
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn add_month_to_table(
args: &EvaluatedWholeStreamCommandArgs,
calendar_vec_deque: &mut VecDeque<Value>,
tag: &Tag,
year: i32,
month: u32,
_current_day_option: Option<u32>, // Can be used in the future to display current day
day_start_offset: usize,
number_of_days_in_month: usize,
args: &EvaluatedWholeStreamCommandArgs,
) {
let day_limit = number_of_days_in_month + day_start_offset;
let mut day_count: usize = 1;
selected_year: i32,
current_month: u32,
current_day_option: Option<u32>,
) -> Result<(), ShellError> {
let month_helper_result = MonthHelper::new(selected_year, current_month);
let days_of_the_week = [
let month_helper = match month_helper_result {
Ok(month_helper) => month_helper,
Err(()) => match args.get("full-year") {
Some(full_year_value) => {
return Err(get_invalid_year_shell_error(&full_year_value.tag()))
}
None => {
return Err(ShellError::labeled_error(
"Issue parsing command",
"invalid command",
tag,
))
}
},
};
let mut days_of_the_week = [
"sunday",
"monday",
"tuesday",
"wednesday",
"thurday",
"thursday",
"friday",
"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_month_column = args.has("month");
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");
while day_count <= day_limit {
while day_number <= day_limit {
let mut indexmap = IndexMap::new();
if should_show_year_column {
indexmap.insert("year".to_string(), UntaggedValue::int(year).into_value(tag));
indexmap.insert(
"year".to_string(),
UntaggedValue::int(month_helper.selected_year).into_value(tag),
);
}
if should_show_quarter_column {
indexmap.insert(
"quarter".to_string(),
UntaggedValue::int(get_quarter_number(month)).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 {
UntaggedValue::string(get_month_name(month)).into_value(tag)
UntaggedValue::string(month_helper.month_name.clone()).into_value(tag)
} else {
UntaggedValue::int(month).into_value(tag)
UntaggedValue::int(month_helper.selected_month).into_value(tag)
};
indexmap.insert("month".to_string(), month_value);
}
for day in &days_of_the_week {
let value = if (day_count <= day_limit) && (day_count > day_start_offset) {
UntaggedValue::int(day_count - day_start_offset).into_value(tag)
} else {
UntaggedValue::nothing().into_value(tag)
};
let should_add_day_number_to_table =
(day_number > total_start_offset) && (day_number <= day_limit);
let mut value = UntaggedValue::nothing().into_value(tag);
if should_add_day_number_to_table {
let adjusted_day_number = day_number - total_start_offset;
value = UntaggedValue::int(adjusted_day_number).into_value(tag);
if let Some(current_day) = current_day_option {
if current_day == adjusted_day_number {
// TODO: Update the value here with a color when color support is added
// This colors the current day
}
}
}
indexmap.insert((*day).to_string(), value);
day_count += 1;
day_number += 1;
}
calendar_vec_deque
.push_back(UntaggedValue::Row(Dictionary::from(indexmap)).into_value(tag));
}
Ok(())
}
fn get_quarter_number(month_number: u32) -> u8 {
match month_number {
1..=3 => 1,
4..=6 => 2,
7..=9 => 3,
_ => 4,
#[cfg(test)]
mod tests {
use super::Cal;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Cal {})
}
}
fn get_month_name(month_number: u32) -> String {
let month_name = match month_number {
1 => "january",
2 => "february",
3 => "march",
4 => "april",
5 => "may",
6 => "june",
7 => "july",
8 => "august",
9 => "september",
10 => "october",
11 => "november",
_ => "december",
};
month_name.to_string()
}
fn get_month_information(
selected_year: i32,
month: u32,
current_year: i32,
) -> (usize, usize, bool) {
let (naive_date, chosen_date_is_valid_one) =
get_safe_naive_date(selected_year, month, current_year);
let weekday = naive_date.weekday();
let (days_in_month, chosen_date_is_valid_two) =
get_days_in_month(selected_year, month, current_year);
(
weekday.num_days_from_sunday() as usize,
days_in_month,
chosen_date_is_valid_one && chosen_date_is_valid_two,
)
}
fn get_days_in_month(selected_year: i32, month: u32, current_year: i32) -> (usize, bool) {
// 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:
// https://docs.rs/chrono/0.3.0/chrono/naive/date/struct.NaiveDate.html#example-30
let (adjusted_year, adjusted_month) = if month == 12 {
(selected_year + 1, 1)
} else {
(selected_year, month + 1)
};
let (naive_date, chosen_date_is_valid) =
get_safe_naive_date(adjusted_year, adjusted_month, current_year);
(naive_date.pred().day() as usize, chosen_date_is_valid)
}
fn get_safe_naive_date(
selected_year: i32,
selected_month: u32,
current_year: i32,
) -> (NaiveDate, bool) {
if let Some(naive_date) = NaiveDate::from_ymd_opt(selected_year, selected_month, 1) {
return (naive_date, true);
}
(NaiveDate::from_ymd(current_year, selected_month, 1), false)
}

View File

@ -5,9 +5,7 @@ use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
pub struct Calc;
#[derive(Deserialize)]
pub struct CalcArgs {}
#[async_trait]
impl WholeStreamCommand for Calc {
fn name(&self) -> &str {
"calc"
@ -17,26 +15,30 @@ impl WholeStreamCommand for Calc {
"Parse a math expression into a number"
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, calc)?.run()
calc(args, registry).await
}
fn examples(&self) -> &[Example] {
&[Example {
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 fn calc(
_: CalcArgs,
RunnableContext { input, name, .. }: RunnableContext,
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() {
@ -52,7 +54,7 @@ pub fn calc(
Err(ShellError::labeled_error(
"Expected a string from pipeline",
"requires string input",
name.clone(),
name,
))
}
})
@ -72,3 +74,15 @@ pub fn parse(math_expression: &str, tag: impl Into<Tag>) -> Result<Value, String
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

@ -14,6 +14,7 @@ pub struct CdArgs {
pub struct Cd;
#[async_trait]
impl WholeStreamCommand for Cd {
fn name(&self) -> &str {
"cd"
@ -31,36 +32,51 @@ impl WholeStreamCommand for Cd {
"Change to a new path."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, cd)?.run()
let name = args.call_info.name_tag.clone();
let shell_manager = args.shell_manager.clone();
let (args, _): (CdArgs, _) = args.process(&registry).await?;
shell_manager.cd(args, name)
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Change to a new directory called 'dirname'",
example: "cd dirname",
result: None,
},
Example {
description: "Change to your home directory",
example: "cd",
result: None,
},
Example {
description: "Change to your home directory (alternate version)",
example: "cd ~",
result: None,
},
Example {
description: "Change to the previous directory",
example: "cd -",
result: None,
},
]
}
}
fn cd(args: CdArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
context.shell_manager.cd(args, &context)
#[cfg(test)]
mod tests {
use super::Cd;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Cd {})
}
}

View File

@ -0,0 +1,81 @@
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")]),
}]
}
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()),
_ => 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

@ -6,14 +6,16 @@ use crate::stream::InputStream;
use futures::stream::TryStreamExt;
use nu_errors::ShellError;
use nu_protocol::hir::{Block, ClassifiedCommand, Commands};
use nu_protocol::{ReturnSuccess, Scope, UntaggedValue, Value};
use nu_protocol::{ReturnSuccess, UntaggedValue, Value};
use std::sync::atomic::Ordering;
pub(crate) async fn run_block(
block: &Block,
ctx: &mut Context,
mut input: InputStream,
scope: &Scope,
it: &Value,
vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>,
) -> Result<InputStream, ShellError> {
let mut output: Result<InputStream, ShellError> = Ok(InputStream::empty());
for pipeline in &block.block {
@ -52,7 +54,7 @@ pub(crate) async fn run_block(
return Err(e);
}
}
output = run_pipeline(pipeline, ctx, input, scope).await;
output = run_pipeline(pipeline, ctx, input, it, vars, env).await;
input = InputStream::empty();
}
@ -64,10 +66,11 @@ async fn run_pipeline(
commands: &Commands,
ctx: &mut Context,
mut input: InputStream,
scope: &Scope,
it: &Value,
vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>,
) -> Result<InputStream, ShellError> {
let mut iter = commands.list.clone().into_iter().peekable();
loop {
let item: Option<ClassifiedCommand> = iter.next();
let next: Option<&ClassifiedCommand> = iter.peek();
@ -78,13 +81,13 @@ async fn run_pipeline(
}
(Some(ClassifiedCommand::Expr(expr)), _) => {
run_expression_block(*expr, ctx, input, scope)?
run_expression_block(*expr, ctx, it, vars, env).await?
}
(Some(ClassifiedCommand::Error(err)), _) => return Err(err.into()),
(_, Some(ClassifiedCommand::Error(err))) => return Err(err.clone().into()),
(Some(ClassifiedCommand::Internal(left)), _) => {
run_internal_command(left, ctx, input, scope)?
run_internal_command(left, ctx, input, it, vars, env).await?
}
(None, _) => break,

View File

@ -6,22 +6,22 @@ use log::{log_enabled, trace};
use futures::stream::once;
use nu_errors::ShellError;
use nu_protocol::hir::SpannedExpression;
use nu_protocol::Scope;
use nu_protocol::Value;
pub(crate) fn run_expression_block(
pub(crate) async fn run_expression_block(
expr: SpannedExpression,
context: &mut Context,
_input: InputStream,
scope: &Scope,
it: &Value,
vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>,
) -> Result<InputStream, ShellError> {
if log_enabled!(log::Level::Trace) {
trace!(target: "nu::run::expr", "->");
trace!(target: "nu::run::expr", "{:?}", expr);
}
let scope = scope.clone();
let registry = context.registry().clone();
let output = evaluate_baseline_expr(&expr, &registry, &scope)?;
let output = evaluate_baseline_expr(&expr, &registry, it, vars, env).await?;
Ok(once(async { Ok(output) }).to_input_stream())
}

View File

@ -99,10 +99,10 @@ pub(crate) async fn run_external_command(
));
}
run_with_stdin(command, context, input, scope, is_last)
run_with_stdin(command, context, input, scope, is_last).await
}
fn run_with_stdin(
async fn run_with_stdin(
command: ExternalCommand,
context: &mut Context,
input: InputStream,
@ -115,7 +115,9 @@ fn run_with_stdin(
let mut command_args = vec![];
for arg in command.args.iter() {
let value = evaluate_baseline_expr(arg, &context.registry, scope)?;
let value =
evaluate_baseline_expr(arg, &context.registry, &scope.it, &scope.vars, &scope.env)
.await?;
// Skip any arguments that don't really exist, treating them as optional
// FIXME: we may want to preserve the gap in the future, though it's hard to say
// what value we would put in its place.
@ -212,6 +214,9 @@ fn spawn(
if !is_last {
process.stdout(Stdio::piped());
trace!(target: "nu::run::external", "set up stdout pipe");
process.stderr(Stdio::piped());
trace!(target: "nu::run::external", "set up stderr pipe");
}
// open since we have some contents for stdin
@ -310,6 +315,20 @@ fn spawn(
return Err(());
};
let stderr = if let Some(stderr) = child.stderr.take() {
stderr
} else {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
"Can't redirect the stderr for external command",
"can't redirect stderr",
&stdout_name_tag,
)),
tag: stdout_name_tag,
}));
return Err(());
};
let file = futures::io::AllowStdIo::new(stdout);
let stream = FramedRead::new(file, MaybeTextCodec);
@ -363,6 +382,64 @@ fn spawn(
}
}
}
let file = futures::io::AllowStdIo::new(stderr);
let err_stream = FramedRead::new(file, MaybeTextCodec);
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
@ -509,7 +586,7 @@ mod tests {
let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
assert!(
run_external_command(cmd, &mut ctx, input, &Scope::empty(), false)
run_external_command(cmd, &mut ctx, input, &Scope::new(), false)
.await
.is_err()
);

View File

@ -7,180 +7,247 @@ use nu_errors::ShellError;
use nu_protocol::hir::InternalCommand;
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue, Value};
pub(crate) fn run_internal_command(
pub(crate) async fn run_internal_command(
command: InternalCommand,
context: &mut Context,
input: InputStream,
scope: &Scope,
it: &Value,
vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>,
) -> Result<InputStream, ShellError> {
if log_enabled!(log::Level::Trace) {
trace!(target: "nu::run::internal", "->");
trace!(target: "nu::run::internal", "{}", command.name);
}
let scope = Scope {
it: it.clone(),
vars: vars.clone(),
env: env.clone(),
};
let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
let internal_command = context.expect_command(&command.name);
let result = {
context.run_command(
internal_command?,
Tag::unknown_anchor(command.name_span),
command.args.clone(),
scope,
objects,
)
context
.run_command(
internal_command?,
Tag::unknown_anchor(command.name_span),
command.args.clone(),
&scope,
objects,
)
.await?
};
let mut result = trace_out_stream!(target: "nu::trace_stream::internal", "output" = result);
let mut context = context.clone();
let scope = scope.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 stream = async_stream! {
let mut soft_errs: Vec<ShellError> = vec![];
let mut yielded = false;
Ok(InputStream::from_stream(
result
.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 item {
Ok(ReturnSuccess::Action(action)) => match action {
CommandAction::ChangePath(path) => {
context.shell_manager.set_path(path);
}
CommandAction::Exit => std::process::exit(0), // TODO: save history.txt
CommandAction::Error(err) => {
context.error(err);
break;
}
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(),
shell_manager: context.shell_manager.clone(),
call_info: UnevaluatedCallInfo {
args: nu_protocol::hir::Call {
head: command.args.head,
positional: None,
named: None,
span: Span::unknown(),
is_last: false,
},
name_tag: Tag::unknown_anchor(command.name_span),
scope: scope.clone(),
}
};
let mut result = converter.run(new_args.with_input(vec![tagged_contents]), &context.registry);
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);
match result {
Ok(mut result) => {
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
result.drain_vec().await;
let mut output = vec![];
for res in result_vec {
match res {
Ok(ReturnSuccess::Value(Value {
value: UntaggedValue::Table(list),
..
})) => {
for l in list {
output.push(Ok(l));
}
}
Ok(ReturnSuccess::Value(Value {
value,
..
})) => {
output
.push(Ok(value
.into_value(contents_tag.clone())));
}
Err(e) => output.push(Err(e)),
_ => {}
}
}
futures::stream::iter(output).to_input_stream()
}
Err(e) => {
context.add_error(e);
InputStream::empty()
}
}
Ok(ReturnSuccess::Value(Value { value, .. })) => {
yield Ok(value.into_value(contents_tag.clone()));
}
Err(e) => yield Err(e),
_ => {}
} else {
InputStream::one(tagged_contents)
}
}
} else {
yield Ok(tagged_contents)
}
}
CommandAction::EnterHelpShell(value) => {
match value {
Value {
value: UntaggedValue::Primitive(Primitive::String(cmd)),
tag,
} => {
context.shell_manager.insert_at_current(Box::new(
HelpShell::for_command(
UntaggedValue::string(cmd).into_value(tag),
&context.registry(),
)?,
));
CommandAction::EnterHelpShell(value) => match value {
Value {
value: UntaggedValue::Primitive(Primitive::String(cmd)),
tag,
} => {
context.shell_manager.insert_at_current(Box::new(
match HelpShell::for_command(
UntaggedValue::string(cmd).into_value(tag),
&context.registry(),
) {
Ok(v) => v,
Err(err) => {
return InputStream::one(
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(
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)) => {
yielded = true;
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())
})
.flatten()
.take_while(|x| futures::future::ready(!x.is_error())),
))
}

View File

@ -6,41 +6,52 @@ use std::process::Command;
pub struct Clear;
#[async_trait]
impl WholeStreamCommand for Clear {
fn name(&self) -> &str {
"clear"
}
fn signature(&self) -> Signature {
Signature::build("clear")
}
fn usage(&self) -> &str {
"clears the terminal"
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
clear(args, registry)
async fn run(&self, _: CommandArgs, _: &CommandRegistry) -> Result<OutputStream, ShellError> {
if cfg!(windows) {
Command::new("cmd")
.args(&["/C", "cls"])
.status()
.expect("failed to execute process");
} else if cfg!(unix) {
Command::new("/bin/sh")
.args(&["-c", "clear"])
.status()
.expect("failed to execute process");
}
Ok(OutputStream::empty())
}
fn examples(&self) -> &[Example] {
&[Example {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Clear the screen",
example: "clear",
result: None,
}]
}
}
fn clear(_args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
if cfg!(windows) {
Command::new("cmd")
.args(&["/C", "cls"])
.status()
.expect("failed to execute process");
} else if cfg!(unix) {
Command::new("/bin/sh")
.args(&["-c", "clear"])
.status()
.expect("failed to execute process");
#[cfg(test)]
mod tests {
use super::Clear;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Clear {})
}
Ok(OutputStream::empty())
}

View File

@ -1,113 +1,109 @@
#[cfg(feature = "clipboard")]
pub mod clipboard {
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use futures::stream::StreamExt;
use nu_errors::ShellError;
use nu_protocol::{ReturnValue, Signature, Value};
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use futures::stream::StreamExt;
use nu_errors::ShellError;
use nu_protocol::{Signature, Value};
use clipboard::{ClipboardContext, ClipboardProvider};
use clipboard::{ClipboardContext, ClipboardProvider};
pub struct Clip;
pub struct Clip;
#[derive(Deserialize)]
pub struct ClipArgs {}
impl WholeStreamCommand for Clip {
fn name(&self) -> &str {
"clip"
}
fn signature(&self) -> Signature {
Signature::build("clip")
}
fn usage(&self) -> &str {
"Copy the contents of the pipeline to the copy/paste buffer"
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, clip)?.run()
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Save text to the clipboard",
example: "echo 'secret value' | clip",
}]
}
#[async_trait]
impl WholeStreamCommand for Clip {
fn name(&self) -> &str {
"clip"
}
pub fn clip(
ClipArgs {}: ClipArgs,
RunnableContext { input, name, .. }: RunnableContext,
fn signature(&self) -> Signature {
Signature::build("clip")
}
fn usage(&self) -> &str {
"Copy the contents of the pipeline to the copy/paste buffer"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let values: Vec<Value> = input.collect().await;
let mut clip_stream = inner_clip(values, name).await;
while let Some(value) = clip_stream.next().await {
yield value;
}
};
let stream: BoxStream<'static, ReturnValue> = stream.boxed();
Ok(OutputStream::from(stream))
clip(args, registry).await
}
async fn inner_clip(input: Vec<Value>, name: Tag) -> OutputStream {
if let Ok(clip_context) = ClipboardProvider::new() {
let mut clip_context: ClipboardContext = clip_context;
let mut new_copy_data = String::new();
if !input.is_empty() {
let mut first = true;
for i in input.iter() {
if !first {
new_copy_data.push_str("\n");
} else {
first = false;
}
let string: String = match i.as_string() {
Ok(string) => string.to_string(),
Err(_) => {
return OutputStream::one(Err(ShellError::labeled_error(
"Given non-string data",
"expected strings from pipeline",
name,
)))
}
};
new_copy_data.push_str(&string);
}
}
match clip_context.set_contents(new_copy_data) {
Ok(_) => {}
Err(_) => {
return OutputStream::one(Err(ShellError::labeled_error(
"Could not set contents of clipboard",
"could not set contents of clipboard",
name,
)));
}
}
OutputStream::empty()
} else {
OutputStream::one(Err(ShellError::labeled_error(
"Could not open clipboard",
"could not open clipboard",
name,
)))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Save text to the clipboard",
example: "echo 'secret value' | clip",
result: None,
}]
}
}
pub async fn clip(
args: CommandArgs,
_registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let input = args.input;
let name = args.call_info.name_tag.clone();
let values: Vec<Value> = input.collect().await;
if let Ok(clip_context) = ClipboardProvider::new() {
let mut clip_context: ClipboardContext = clip_context;
let mut new_copy_data = String::new();
if !values.is_empty() {
let mut first = true;
for i in values.iter() {
if !first {
new_copy_data.push_str("\n");
} else {
first = false;
}
let string: String = match i.as_string() {
Ok(string) => string.to_string(),
Err(_) => {
return Err(ShellError::labeled_error(
"Given non-string data",
"expected strings from pipeline",
name,
))
}
};
new_copy_data.push_str(&string);
}
}
match clip_context.set_contents(new_copy_data) {
Ok(_) => {}
Err(_) => {
return Err(ShellError::labeled_error(
"Could not set contents of clipboard",
"could not set contents of clipboard",
name,
));
}
}
} else {
return Err(ShellError::labeled_error(
"Could not open clipboard",
"could not open clipboard",
name,
));
}
Ok(OutputStream::empty())
}
#[cfg(test)]
mod tests {
use super::Clip;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Clip {})
}
}

View File

@ -7,7 +7,8 @@ use derive_new::new;
use getset::Getters;
use nu_errors::ShellError;
use nu_protocol::hir;
use nu_protocol::{CallInfo, EvaluatedArgs, ReturnValue, Scope, Signature, Value};
use nu_protocol::{CallInfo, EvaluatedArgs, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use std::sync::atomic::AtomicBool;
@ -20,8 +21,8 @@ pub struct UnevaluatedCallInfo {
}
impl UnevaluatedCallInfo {
pub fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
let args = evaluate_args(&self.args, registry, &self.scope)?;
pub async fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
let args = evaluate_args(&self.args, registry, &self.scope).await?;
Ok(CallInfo {
args,
@ -29,14 +30,14 @@ impl UnevaluatedCallInfo {
})
}
pub fn evaluate_with_new_it(
pub async fn evaluate_with_new_it(
self,
registry: &CommandRegistry,
it: &Value,
) -> Result<CallInfo, ShellError> {
let mut scope = self.scope.clone();
scope = scope.set_it(it.clone());
let args = evaluate_args(&self.args, registry, &scope)?;
scope.it = it.clone();
let args = evaluate_args(&self.args, registry, &scope).await?;
Ok(CallInfo {
args,
@ -54,9 +55,11 @@ impl UnevaluatedCallInfo {
pub struct CommandArgs {
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub shell_manager: ShellManager,
pub call_info: UnevaluatedCallInfo,
pub input: InputStream,
pub raw_input: String,
}
#[derive(Getters, Clone)]
@ -64,6 +67,7 @@ pub struct CommandArgs {
pub struct RawCommandArgs {
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub shell_manager: ShellManager,
pub call_info: UnevaluatedCallInfo,
}
@ -73,9 +77,11 @@ impl RawCommandArgs {
CommandArgs {
host: self.host,
ctrl_c: self.ctrl_c,
current_errors: self.current_errors,
shell_manager: self.shell_manager,
call_info: self.call_info,
input: input.into(),
raw_input: String::default(),
}
}
}
@ -87,7 +93,7 @@ impl std::fmt::Debug for CommandArgs {
}
impl CommandArgs {
pub fn evaluate_once(
pub async fn evaluate_once(
self,
registry: &CommandRegistry,
) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
@ -95,7 +101,7 @@ impl CommandArgs {
let ctrl_c = self.ctrl_c.clone();
let shell_manager = self.shell_manager.clone();
let input = self.input;
let call_info = self.call_info.evaluate(registry)?;
let call_info = self.call_info.evaluate(registry).await?;
Ok(EvaluatedWholeStreamCommandArgs::new(
host,
@ -106,7 +112,7 @@ impl CommandArgs {
))
}
pub fn evaluate_once_with_scope(
pub async fn evaluate_once_with_scope(
self,
registry: &CommandRegistry,
scope: &Scope,
@ -120,7 +126,7 @@ impl CommandArgs {
args: self.call_info.args,
scope: scope.clone(),
};
let call_info = call_info.evaluate(registry)?;
let call_info = call_info.evaluate(registry).await?;
Ok(EvaluatedWholeStreamCommandArgs::new(
host,
@ -131,69 +137,16 @@ impl CommandArgs {
))
}
pub fn process<'de, T: Deserialize<'de>, O: ToOutputStream>(
pub async fn process<'de, T: Deserialize<'de>>(
self,
registry: &CommandRegistry,
callback: fn(T, RunnableContext) -> Result<O, ShellError>,
) -> Result<RunnableArgs<T, O>, ShellError> {
let shell_manager = self.shell_manager.clone();
let host = self.host.clone();
let ctrl_c = self.ctrl_c.clone();
let args = self.evaluate_once(registry)?;
let call_info = args.call_info.clone();
let (input, args) = args.split();
let name_tag = args.call_info.name_tag;
let mut deserializer = ConfigDeserializer::from_call_info(call_info);
Ok(RunnableArgs {
args: T::deserialize(&mut deserializer)?,
context: RunnableContext {
input,
registry: registry.clone(),
shell_manager,
name: name_tag,
host,
ctrl_c,
},
callback,
})
}
pub fn process_raw<'de, T: Deserialize<'de>>(
self,
registry: &CommandRegistry,
callback: fn(T, RunnableContext, RawCommandArgs) -> Result<OutputStream, ShellError>,
) -> Result<RunnableRawArgs<T>, ShellError> {
let raw_args = RawCommandArgs {
host: self.host.clone(),
ctrl_c: self.ctrl_c.clone(),
shell_manager: self.shell_manager.clone(),
call_info: self.call_info.clone(),
};
let shell_manager = self.shell_manager.clone();
let host = self.host.clone();
let ctrl_c = self.ctrl_c.clone();
let args = self.evaluate_once(registry)?;
) -> Result<(T, InputStream), ShellError> {
let args = self.evaluate_once(registry).await?;
let call_info = args.call_info.clone();
let (input, args) = args.split();
let name_tag = args.call_info.name_tag;
let mut deserializer = ConfigDeserializer::from_call_info(call_info);
Ok(RunnableRawArgs {
args: T::deserialize(&mut deserializer)?,
context: RunnableContext {
input,
registry: registry.clone(),
shell_manager,
name: name_tag,
host,
ctrl_c,
},
raw_args,
callback,
})
Ok((T::deserialize(&mut deserializer)?, args.input))
}
}
@ -202,8 +155,10 @@ pub struct RunnableContext {
pub shell_manager: ShellManager,
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub registry: CommandRegistry,
pub name: Tag,
pub raw_input: String,
}
impl RunnableContext {
@ -212,34 +167,6 @@ impl RunnableContext {
}
}
pub struct RunnableArgs<T, O: ToOutputStream> {
args: T,
context: RunnableContext,
callback: fn(T, RunnableContext) -> Result<O, ShellError>,
}
impl<T, O: ToOutputStream> RunnableArgs<T, O> {
pub fn run(self) -> Result<OutputStream, ShellError> {
(self.callback)(self.args, self.context).map(|v| v.to_output_stream())
}
}
pub struct RunnableRawArgs<T> {
args: T,
raw_args: RawCommandArgs,
context: RunnableContext,
callback: fn(T, RunnableContext, RawCommandArgs) -> Result<OutputStream, ShellError>,
}
impl<T> RunnableRawArgs<T> {
pub fn run(self) -> OutputStream {
match (self.callback)(self.args, self.context, self.raw_args) {
Ok(stream) => stream,
Err(err) => OutputStream::one(Err(err)),
}
}
}
pub struct EvaluatedWholeStreamCommandArgs {
pub args: EvaluatedCommandArgs,
pub input: InputStream,
@ -353,8 +280,10 @@ impl EvaluatedCommandArgs {
pub struct Example {
pub example: &'static str,
pub description: &'static str,
pub result: Option<Vec<Value>>,
}
#[async_trait]
pub trait WholeStreamCommand: Send + Sync {
fn name(&self) -> &str;
@ -364,7 +293,7 @@ pub trait WholeStreamCommand: Send + Sync {
fn usage(&self) -> &str;
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
@ -374,8 +303,8 @@ pub trait WholeStreamCommand: Send + Sync {
false
}
fn examples(&self) -> &[Example] {
&[]
fn examples(&self) -> Vec<Example> {
Vec::new()
}
}
@ -414,14 +343,19 @@ impl Command {
self.0.usage()
}
pub fn run(&self, args: CommandArgs, registry: &CommandRegistry) -> OutputStream {
pub async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
if args.call_info.switch_present("help") {
get_help(&*self.0, registry).into()
let cl = self.0.clone();
let registry = registry.clone();
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(get_help(&*cl, &registry)).into_value(Tag::unknown()),
))))
} else {
match self.0.run(args, registry) {
Ok(stream) => stream,
Err(err) => OutputStream::one(Err(err)),
}
self.0.run(args, registry).await
}
}
@ -439,6 +373,7 @@ pub struct FnFilterCommand {
func: fn(EvaluatedFilterCommandArgs) -> Result<OutputStream, ShellError>,
}
#[async_trait]
impl WholeStreamCommand for FnFilterCommand {
fn name(&self) -> &str {
&self.name
@ -448,47 +383,51 @@ impl WholeStreamCommand for FnFilterCommand {
"usage"
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let CommandArgs {
CommandArgs {
host,
ctrl_c,
shell_manager,
call_info,
input,
} = args;
let host: Arc<parking_lot::Mutex<dyn Host>> = host.clone();
let registry: CommandRegistry = registry.clone();
..
}: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = Arc::new(registry.clone());
let func = self.func;
let result = input.map(move |it| {
let registry = registry.clone();
let call_info = match call_info.clone().evaluate_with_new_it(&registry, &it) {
Err(err) => return OutputStream::from(vec![Err(err)]).values,
Ok(args) => args,
};
Ok(input
.then(move |it| {
let host = host.clone();
let registry = registry.clone();
let ctrl_c = ctrl_c.clone();
let shell_manager = shell_manager.clone();
let call_info = call_info.clone();
async move {
let call_info = match call_info.evaluate_with_new_it(&*registry, &it).await {
Err(err) => {
return OutputStream::one(Err(err));
}
Ok(args) => args,
};
let args = EvaluatedFilterCommandArgs::new(
host.clone(),
ctrl_c.clone(),
shell_manager.clone(),
call_info,
);
let args = EvaluatedFilterCommandArgs::new(
host.clone(),
ctrl_c.clone(),
shell_manager.clone(),
call_info,
);
match func(args) {
Err(err) => OutputStream::from(vec![Err(err)]).values,
Ok(stream) => stream.values,
}
});
let result = result.flatten();
let result: BoxStream<ReturnValue> = result.boxed();
Ok(result.into())
match func(args) {
Err(err) => return OutputStream::one(Err(err)),
Ok(stream) => stream,
}
}
})
.flatten()
.to_output_stream())
}
}

View File

@ -1,9 +1,10 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use futures::future;
use futures::stream::StreamExt;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct Compact;
@ -13,6 +14,7 @@ pub struct CompactArgs {
rest: Vec<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for Compact {
fn name(&self) -> &str {
"compact"
@ -26,43 +28,78 @@ impl WholeStreamCommand for Compact {
"Creates a table with non-empty rows"
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, compact)?.run()
compact(args, registry).await
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Remove all directory entries, except those with a 'target'",
example: "ls -af | compact target",
}]
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Filter out all null entries in a list",
example: "echo [1 2 $null 3 $null $null] | compact target",
result: Some(vec![
UntaggedValue::int(1).into(),
UntaggedValue::int(2).into(),
UntaggedValue::int(3).into(),
]),
},
Example {
description: "Filter out all directory entries having no 'target'",
example: "ls -af | compact target",
result: None,
},
]
}
}
pub fn compact(
CompactArgs { rest: columns }: CompactArgs,
RunnableContext { input, .. }: RunnableContext,
pub async fn compact(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let objects = input.filter(move |item| {
let keep = if columns.is_empty() {
item.is_some()
} else {
match item {
Value {
value: UntaggedValue::Row(ref r),
..
} => columns
.iter()
.all(|field| r.get_data(field).borrow().is_some()),
_ => false,
}
};
futures::future::ready(keep)
});
Ok(objects.from_input_stream())
let registry = registry.clone();
let (CompactArgs { rest: columns }, input) = args.process(&registry).await?;
Ok(input
.filter_map(move |item| {
future::ready(if columns.is_empty() {
if !item.is_empty() {
Some(ReturnSuccess::value(item))
} else {
None
}
} else {
match item {
Value {
value: UntaggedValue::Row(ref r),
..
} => {
if columns
.iter()
.all(|field| r.get_data(field).borrow().is_some())
{
Some(ReturnSuccess::value(item))
} else {
None
}
}
_ => None,
}
})
})
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Compact;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Compact {})
}
}

View File

@ -20,6 +20,7 @@ pub struct ConfigArgs {
path: Tagged<bool>,
}
#[async_trait]
impl WholeStreamCommand for Config {
fn name(&self) -> &str {
"config"
@ -30,7 +31,7 @@ impl WholeStreamCommand for Config {
.named(
"load",
SyntaxShape::Path,
"load the config from the path give",
"load the config from the path given",
Some('l'),
)
.named(
@ -65,157 +66,195 @@ impl WholeStreamCommand for Config {
"Configuration management."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, config)?.run()
config(args, registry).await
}
fn examples(&self) -> &[Example] {
&[
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 fn config(
ConfigArgs {
load,
set,
set_into,
get,
clear,
remove,
path,
}: ConfigArgs,
RunnableContext { name, input, .. }: RunnableContext,
pub async fn config(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name_span = name.clone();
let name_span = args.call_info.name_tag.clone();
let name = args.call_info.name_tag.clone();
let registry = registry.clone();
let stream = async_stream! {
let configuration = if let Some(supplied) = load {
Some(supplied.item().clone())
} else {
None
};
let (
ConfigArgs {
load,
set,
set_into,
get,
clear,
remove,
path,
},
input,
) = args.process(&registry).await?;
let mut result = crate::data::config::read(name_span, &configuration)?;
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),
..
} => {
for l in list {
let value = l.clone();
yield ReturnSuccess::value(l.clone());
}
}
x => yield ReturnSuccess::value(x.clone()),
}
}
else if let Some((key, value)) = set {
result.insert(key.to_string(), value.clone());
config::write(&result, &configuration)?;
yield 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.len() == 0 {
yield 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.to_string(), value.clone());
config::write(&result, &configuration)?;
yield 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.to_string(), value.clone());
config::write(&result, &configuration)?;
yield ReturnSuccess::value(UntaggedValue::Row(result.into()).into_value(name));
}
}
else if let Tagged { item: true, tag } = clear {
result.clear();
config::write(&result, &configuration)?;
yield ReturnSuccess::value(UntaggedValue::Row(result.into()).into_value(tag));
return;
}
else if let Tagged { item: true, tag } = path {
let path = config::default_path_for(&configuration)?;
yield 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)?
} else {
yield Err(ShellError::labeled_error(
"Key does not exist in config",
"key",
v.tag(),
));
}
yield ReturnSuccess::value(UntaggedValue::Row(result.into()).into_value(v.tag()));
}
else {
yield ReturnSuccess::value(UntaggedValue::Row(result.into()).into_value(name));
}
let configuration = if let Some(supplied) = load {
Some(supplied.item().clone())
} else {
None
};
Ok(stream.to_output_stream())
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

@ -3,13 +3,11 @@ use crate::context::CommandRegistry;
use crate::prelude::*;
use futures::stream::StreamExt;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
use nu_protocol::{Signature, UntaggedValue, Value};
pub struct Count;
#[derive(Deserialize)]
pub struct CountArgs {}
#[async_trait]
impl WholeStreamCommand for Count {
fn name(&self) -> &str {
"count"
@ -23,31 +21,36 @@ impl WholeStreamCommand for Count {
"Show the total number of rows or items."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
_registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, count)?.run()
let name = args.call_info.name_tag.clone();
let rows: Vec<Value> = args.input.collect().await;
Ok(OutputStream::one(
UntaggedValue::int(rows.len()).into_value(name),
))
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Count the number of files/directories in the current directory",
example: "ls | count",
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Count the number of entries in a list",
example: "echo [1 2 3 4 5] | count",
result: Some(vec![UntaggedValue::int(5).into()]),
}]
}
}
pub fn count(
CountArgs {}: CountArgs,
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let rows: Vec<Value> = input.collect().await;
#[cfg(test)]
mod tests {
use super::Count;
yield ReturnSuccess::value(UntaggedValue::int(rows.len()).into_value(name))
};
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
Ok(stream.to_output_stream())
test_examples(Count {})
}
}

View File

@ -15,6 +15,7 @@ pub struct CopyArgs {
pub recursive: Tagged<bool>,
}
#[async_trait]
impl WholeStreamCommand for Cpy {
fn name(&self) -> &str {
"cp"
@ -35,29 +36,41 @@ impl WholeStreamCommand for Cpy {
"Copy files."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, cp)?.run()
let shell_manager = args.shell_manager.clone();
let name = args.call_info.name_tag.clone();
let (args, _) = args.process(&registry).await?;
shell_manager.cp(args, name)
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Copy myfile to dir_b",
example: "cp myfile dir_b",
result: None,
},
Example {
description: "Recursively copy dir_a to dir_b",
example: "cp -r dir_a dir_b",
result: None,
},
]
}
}
pub fn cp(args: CopyArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
let shell_manager = context.shell_manager.clone();
shell_manager.cp(args, &context)
#[cfg(test)]
mod tests {
use super::Cpy;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Cpy {})
}
}

View File

@ -7,10 +7,11 @@ use crate::commands::WholeStreamCommand;
use chrono::{Datelike, TimeZone, Timelike};
use core::fmt::Display;
use indexmap::IndexMap;
use nu_protocol::{Signature, UntaggedValue};
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
pub struct Date;
#[async_trait]
impl WholeStreamCommand for Date {
fn name(&self) -> &str {
"date"
@ -20,89 +21,155 @@ impl WholeStreamCommand for Date {
Signature::build("date")
.switch("utc", "use universal time (UTC)", Some('u'))
.switch("local", "use the local time", Some('l'))
.named(
"format",
SyntaxShape::String,
"report datetime in supplied strftime format",
Some('f'),
)
.switch("raw", "print date without tables", Some('r'))
}
fn usage(&self) -> &str {
"Get the current datetime."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
date(args, registry)
date(args, registry).await
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get the current local time and date",
example: "date",
result: None,
},
Example {
description: "Get the current UTC time and date",
example: "date --utc",
result: None,
},
Example {
description: "Get the current time and date and report it based on format",
example: "date --format '%Y-%m-%d %H:%M:%S.%f %z'",
result: None,
},
Example {
description: "Get the current time and date and report it without a table",
example: "date --format '%Y-%m-%d %H:%M:%S.%f %z' --raw",
result: None,
},
]
}
}
pub fn date_to_value<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
T::Offset: Display,
{
let mut indexmap = IndexMap::new();
indexmap.insert(
"year".to_string(),
UntaggedValue::int(dt.year()).into_value(&tag),
);
indexmap.insert(
"month".to_string(),
UntaggedValue::int(dt.month()).into_value(&tag),
);
indexmap.insert(
"day".to_string(),
UntaggedValue::int(dt.day()).into_value(&tag),
);
indexmap.insert(
"hour".to_string(),
UntaggedValue::int(dt.hour()).into_value(&tag),
);
indexmap.insert(
"minute".to_string(),
UntaggedValue::int(dt.minute()).into_value(&tag),
);
indexmap.insert(
"second".to_string(),
UntaggedValue::int(dt.second()).into_value(&tag),
);
if dt_format.is_empty() {
indexmap.insert(
"year".to_string(),
UntaggedValue::int(dt.year()).into_value(&tag),
);
indexmap.insert(
"month".to_string(),
UntaggedValue::int(dt.month()).into_value(&tag),
);
indexmap.insert(
"day".to_string(),
UntaggedValue::int(dt.day()).into_value(&tag),
);
indexmap.insert(
"hour".to_string(),
UntaggedValue::int(dt.hour()).into_value(&tag),
);
indexmap.insert(
"minute".to_string(),
UntaggedValue::int(dt.minute()).into_value(&tag),
);
indexmap.insert(
"second".to_string(),
UntaggedValue::int(dt.second()).into_value(&tag),
);
let tz = dt.offset();
indexmap.insert(
"timezone".to_string(),
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
);
let tz = dt.offset();
indexmap.insert(
"timezone".to_string(),
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
);
} else {
let result = dt.format(&dt_format);
indexmap.insert(
"formatted".to_string(),
UntaggedValue::string(format!("{}", result)).into_value(&tag),
);
}
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
}
pub fn date(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
let mut date_out = VecDeque::new();
pub async fn date(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.call_info.name_tag.clone();
let raw = args.has("raw");
let dt_fmt = if args.has("format") {
if let Some(dt_fmt) = args.get("format") {
dt_fmt.convert_to_string()
} else {
"".to_string()
}
} else {
"".to_string()
};
let value = if args.has("utc") {
let utc: DateTime<Utc> = Utc::now();
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 {
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)
}
};
date_out.push_back(value);
Ok(futures::stream::iter(date_out).to_output_stream())
Ok(OutputStream::one(value))
}
#[cfg(test)]
mod tests {
use super::Date;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Date {})
}
}

View File

@ -10,6 +10,7 @@ pub struct DebugArgs {
raw: bool,
}
#[async_trait]
impl WholeStreamCommand for Debug {
fn name(&self) -> &str {
"debug"
@ -23,19 +24,21 @@ impl WholeStreamCommand for Debug {
"Print the Rust debug representation of the values"
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, debug_value)?.run()
debug_value(args, registry).await
}
}
fn debug_value(
DebugArgs { raw }: DebugArgs,
RunnableContext { input, .. }: RunnableContext,
) -> Result<impl ToOutputStream, ShellError> {
async fn debug_value(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (DebugArgs { raw }, input) = args.process(&registry).await?;
Ok(input
.map(move |v| {
if raw {
@ -48,3 +51,15 @@ fn debug_value(
})
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Debug;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Debug {})
}
}

View File

@ -14,6 +14,7 @@ struct DefaultArgs {
pub struct Default;
#[async_trait]
impl WholeStreamCommand for Default {
fn name(&self) -> &str {
"default"
@ -33,30 +34,32 @@ impl WholeStreamCommand for Default {
"Sets a default row's column if missing."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, default)?.run()
default(args, registry).await
}
fn examples(&self) -> &[Example] {
&[Example {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Give a default 'target' to all file entries",
example: "ls -af | default target 'nothing'",
result: None,
}]
}
}
fn default(
DefaultArgs { column, value }: DefaultArgs,
RunnableContext { input, .. }: RunnableContext,
async fn default(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let stream = input
.map(move |item| {
let mut result = VecDeque::new();
let registry = registry.clone();
let (DefaultArgs { column, value }, input) = args.process(&registry).await?;
Ok(input
.map(move |item| {
let should_add = match item {
Value {
value: UntaggedValue::Row(ref r),
@ -67,16 +70,24 @@ fn default(
if should_add {
match item.insert_data_at_path(&column.item, value.clone()) {
Some(new_value) => result.push_back(ReturnSuccess::value(new_value)),
None => result.push_back(ReturnSuccess::value(item)),
Some(new_value) => ReturnSuccess::value(new_value),
None => ReturnSuccess::value(item),
}
} else {
result.push_back(ReturnSuccess::value(item));
ReturnSuccess::value(item)
}
futures::stream::iter(result)
})
.flatten();
Ok(stream.to_output_stream())
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Default;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Default {})
}
}

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("with-env")
.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

@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
pub struct Drop;
@ -12,6 +12,7 @@ pub struct DropArgs {
rows: Option<Tagged<u64>>,
}
#[async_trait]
impl WholeStreamCommand for Drop {
fn name(&self) -> &str {
"drop"
@ -29,31 +30,13 @@ impl WholeStreamCommand for Drop {
"Drop the last number of rows."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, drop)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Remove the last item of a list/table",
example: "echo [1 2 3] | drop",
},
Example {
description: "Remove the last 2 items of a list/table",
example: "echo [1 2 3] | drop 2",
},
]
}
}
fn drop(DropArgs { rows }: DropArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let v: Vec<_> = context.input.into_vec().await;
let (DropArgs { rows }, input) = args.process(&registry).await?;
let mut v: Vec<_> = input.into_vec().await;
let rows_to_drop = if let Some(quantity) = rows {
*quantity as usize
@ -61,13 +44,40 @@ fn drop(DropArgs { rows }: DropArgs, context: RunnableContext) -> Result<OutputS
1
};
if rows_to_drop < v.len() {
let k = v.len() - rows_to_drop;
for x in v[0..k].iter() {
let y: Value = x.clone();
yield ReturnSuccess::value(y)
}
for _ in 0..rows_to_drop {
v.pop();
}
};
Ok(stream.to_output_stream())
Ok(futures::stream::iter(v).to_output_stream())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Remove the last item of a list/table",
example: "echo [1 2 3] | drop",
result: Some(vec![
UntaggedValue::int(1).into(),
UntaggedValue::int(2).into(),
]),
},
Example {
description: "Remove the last 2 items of a list/table",
example: "echo [1 2 3] | drop 2",
result: Some(vec![UntaggedValue::int(1).into()]),
},
]
}
}
#[cfg(test)]
mod tests {
use super::Drop;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Drop {})
}
}

View File

@ -7,6 +7,7 @@ use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use std::path::PathBuf;
use std::sync::atomic::Ordering;
const NAME: &str = "du";
const GLOB_PARAMS: MatchOptions = MatchOptions {
@ -29,6 +30,7 @@ pub struct DuArgs {
min_size: Option<Tagged<u64>>,
}
#[async_trait]
impl WholeStreamCommand for Du {
fn name(&self) -> &str {
NAME
@ -71,25 +73,30 @@ impl WholeStreamCommand for Du {
"Find disk usage sizes of specified items"
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, du)?.run()
du(args, registry).await
}
fn examples(&self) -> &[Example] {
&[Example {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Disk usage of the current directory",
example: "du *",
example: "du",
result: None,
}]
}
}
fn du(args: DuArgs, ctx: RunnableContext) -> Result<OutputStream, ShellError> {
let tag = ctx.name.clone();
async fn du(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let tag = args.call_info.name_tag.clone();
let ctrl_c = args.ctrl_c.clone();
let ctrl_c_copy = ctrl_c.clone();
let (args, _): (DuArgs, _) = args.process(&registry).await?;
let exclude = args.exclude.map_or(Ok(None), move |x| {
Pattern::new(&x.item)
.map(Option::Some)
@ -118,7 +125,6 @@ fn du(args: DuArgs, ctx: RunnableContext) -> Result<OutputStream, ShellError> {
})
.map(|v| v.map_err(glob_err_into));
let ctrl_c = ctx.ctrl_c;
let all = args.all;
let deref = args.deref;
let max_depth = args.max_depth.map(|f| f.item);
@ -132,22 +138,27 @@ fn du(args: DuArgs, ctx: RunnableContext) -> Result<OutputStream, ShellError> {
all,
};
let stream = futures::stream::iter(paths)
.interruptible(ctrl_c)
.map(move |path| match path {
Ok(p) => {
if p.is_dir() {
Ok(ReturnSuccess::Value(
DirInfo::new(p, &params, max_depth).into(),
))
} else {
FileInfo::new(p, deref, tag.clone()).map(|v| ReturnSuccess::Value(v.into()))
}
}
Err(e) => Err(e),
});
let inp = futures::stream::iter(paths);
Ok(stream.to_output_stream())
Ok(inp
.flat_map(move |path| match path {
Ok(p) => {
let mut output = vec![];
if p.is_dir() {
output.push(Ok(ReturnSuccess::Value(
DirInfo::new(p, &params, max_depth, ctrl_c.clone()).into(),
)));
} else {
for v in FileInfo::new(p, deref, tag.clone()).into_iter() {
output.push(Ok(ReturnSuccess::Value(v.into())));
}
}
futures::stream::iter(output)
}
Err(e) => futures::stream::iter(vec![Err(e)]),
})
.interruptible(ctrl_c_copy)
.to_output_stream())
}
pub struct DirBuilder {
@ -219,7 +230,12 @@ impl FileInfo {
}
impl DirInfo {
pub fn new(path: impl Into<PathBuf>, params: &DirBuilder, depth: Option<u64>) -> Self {
pub fn new(
path: impl Into<PathBuf>,
params: &DirBuilder,
depth: Option<u64>,
ctrl_c: Arc<AtomicBool>,
) -> Self {
let path = path.into();
let mut s = Self {
@ -235,9 +251,15 @@ impl DirInfo {
match std::fs::read_dir(&s.path) {
Ok(d) => {
for f in d {
if ctrl_c.load(Ordering::SeqCst) {
break;
}
match f {
Ok(i) => match i.file_type() {
Ok(t) if t.is_dir() => s = s.add_dir(i.path(), depth, &params),
Ok(t) if t.is_dir() => {
s = s.add_dir(i.path(), depth, &params, ctrl_c.clone())
}
Ok(_t) => s = s.add_file(i.path(), &params),
Err(e) => s = s.add_error(e.into()),
},
@ -255,6 +277,7 @@ impl DirInfo {
path: impl Into<PathBuf>,
mut depth: Option<u64>,
params: &DirBuilder,
ctrl_c: Arc<AtomicBool>,
) -> Self {
if let Some(current) = depth {
if let Some(new) = current.checked_sub(1) {
@ -264,7 +287,7 @@ impl DirInfo {
}
}
let d = DirInfo::new(path, &params, depth);
let d = DirInfo::new(path, &params, depth, ctrl_c);
self.size += d.size;
self.blocks += d.blocks;
self.dirs.push(d);
@ -397,3 +420,15 @@ impl From<FileInfo> for Value {
UntaggedValue::row(r).retag(&f.tag)
}
}
#[cfg(test)]
mod tests {
use super::Du;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Du {})
}
}

View File

@ -6,8 +6,8 @@ use crate::prelude::*;
use futures::stream::once;
use nu_errors::ShellError;
use nu_protocol::{
hir::Block, hir::Expression, hir::SpannedExpression, hir::Synthetic, ReturnSuccess, Signature,
SyntaxShape,
hir::Block, hir::Expression, hir::SpannedExpression, hir::Synthetic, Scope, Signature,
SyntaxShape, UntaggedValue, Value,
};
pub struct Each;
@ -17,6 +17,7 @@ pub struct EachArgs {
block: Block,
}
#[async_trait]
impl WholeStreamCommand for Each {
fn name(&self) -> &str {
"each"
@ -34,19 +35,34 @@ impl WholeStreamCommand for Each {
"Run a block on each row of the table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
Ok(args.process_raw(registry, each)?.run())
each(args, registry).await
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Print the name of each file",
example: "ls | each { echo $it.name }",
}]
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Echo the square of each integer",
example: "echo [1 2 3] | each { echo $(= $it * $it) }",
result: Some(vec![
UntaggedValue::int(1).into(),
UntaggedValue::int(4).into(),
UntaggedValue::int(9).into(),
]),
},
Example {
description: "Echo the sum of each row",
example: "echo [[1 2] [3 4]] | each { echo $it | math sum }",
result: Some(vec![
UntaggedValue::int(3).into(),
UntaggedValue::int(7).into(),
]),
},
]
}
}
@ -60,50 +76,66 @@ fn is_expanded_it_usage(head: &SpannedExpression) -> bool {
}
}
fn each(
each_args: EachArgs,
context: RunnableContext,
raw_args: RawCommandArgs,
async fn process_row(
block: Arc<Block>,
scope: Arc<Scope>,
head: Arc<Box<SpannedExpression>>,
mut context: Arc<Context>,
input: Value,
) -> Result<OutputStream, ShellError> {
let block = each_args.block;
let scope = raw_args.call_info.scope.clone();
let registry = context.registry.clone();
let mut input_stream = context.input;
let stream = async_stream! {
while let Some(input) = input_stream.next().await {
let mut context = Context::from_raw(&raw_args, &registry);
let input_clone = input.clone();
let input_stream = if is_expanded_it_usage(&head) {
InputStream::empty()
} else {
once(async { Ok(input_clone) }).to_input_stream()
};
Ok(run_block(
&block,
Arc::make_mut(&mut context),
input_stream,
&input,
&scope.vars,
&scope.env,
)
.await?
.to_output_stream())
}
let input_clone = input.clone();
let input_stream = if is_expanded_it_usage(&raw_args.call_info.args.head) {
InputStream::empty()
} else {
once(async { Ok(input) }).to_input_stream()
};
let result = run_block(
&block,
&mut context,
input_stream,
&scope.clone().set_it(input_clone),
).await;
match result {
Ok(mut stream) => {
while let Some(result) = stream.next().await {
yield Ok(ReturnSuccess::Value(result));
}
let errors = context.get_errors();
if let Some(error) = errors.first() {
yield Err(error.clone());
}
}
Err(e) => {
yield Err(e);
async fn each(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let head = Arc::new(raw_args.call_info.args.head.clone());
let scope = Arc::new(raw_args.call_info.scope.clone());
let context = Arc::new(Context::from_raw(&raw_args, &registry));
let (each_args, input): (EachArgs, _) = raw_args.process(&registry).await?;
let block = Arc::new(each_args.block);
Ok(input
.then(move |input| {
let block = block.clone();
let scope = scope.clone();
let head = head.clone();
let context = context.clone();
async {
match process_row(block, scope, head, context, input).await {
Ok(s) => s,
Err(e) => OutputStream::one(Err(e)),
}
}
}
};
Ok(stream.to_output_stream())
})
.flatten()
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Each;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Each {})
}
}

View File

@ -1,7 +1,10 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::hir::Operator;
use nu_protocol::{
Primitive, RangeInclusion, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
pub struct Echo;
@ -10,6 +13,7 @@ pub struct EchoArgs {
pub rest: Vec<Value>,
}
#[async_trait]
impl WholeStreamCommand for Echo {
fn name(&self) -> &str {
"echo"
@ -23,56 +27,96 @@ impl WholeStreamCommand for Echo {
"Echo the arguments back to the user."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, echo)?.run()
echo(args, registry).await
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Put a hello message in the pipeline",
example: "echo 'hello'",
result: Some(vec![Value::from("hello")]),
},
Example {
description: "Print the value of the special '$nu' variable",
example: "echo $nu",
result: None,
},
]
}
}
fn echo(args: EchoArgs, _: RunnableContext) -> Result<OutputStream, ShellError> {
let mut output = vec![];
async fn echo(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (args, _): (EchoArgs, _) = args.process(&registry).await?;
for i in args.rest {
let stream = args.rest.into_iter().map(|i| {
match i.as_string() {
Ok(s) => {
output.push(Ok(ReturnSuccess::Value(
OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(s).into_value(i.tag.clone()),
)));
)))
}
_ => match i {
Value {
value: UntaggedValue::Table(table),
..
} => {
for value in table {
output.push(Ok(ReturnSuccess::Value(value.clone())));
futures::stream::iter(table.into_iter().map(ReturnSuccess::value)).to_output_stream()
}
Value {
value: UntaggedValue::Primitive(Primitive::Range(range)),
tag
} => {
let mut output_vec = vec![];
let mut current = range.from.0.item;
while current != range.to.0.item {
output_vec.push(Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag))));
current = match crate::data::value::compute_values(Operator::Plus, &UntaggedValue::Primitive(current), &UntaggedValue::int(1)) {
Ok(result) => match result {
UntaggedValue::Primitive(p) => p,
_ => {
return OutputStream::one(Err(ShellError::unimplemented("Internal error: expected a primitive result from increment")));
}
},
Err((left_type, right_type)) => {
return OutputStream::one(Err(ShellError::coerce_error(
left_type.spanned(tag.span),
right_type.spanned(tag.span),
)));
}
}
}
if let RangeInclusion::Inclusive = range.to.1 {
output_vec.push(Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current).into_value(&tag))));
}
futures::stream::iter(output_vec.into_iter()).to_output_stream()
}
_ => {
output.push(Ok(ReturnSuccess::Value(i.clone())));
OutputStream::one(Ok(ReturnSuccess::Value(i.clone())))
}
},
}
}
});
// TODO: This whole block can probably be replaced with `.map()`
let stream = futures::stream::iter(output);
Ok(stream.to_output_stream())
Ok(futures::stream::iter(stream).flatten().to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Echo;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Echo {})
}
}

View File

@ -14,56 +14,83 @@ pub struct Enter;
#[derive(Deserialize)]
pub struct EnterArgs {
location: Tagged<PathBuf>,
encoding: Option<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for Enter {
fn name(&self) -> &str {
"enter"
}
fn signature(&self) -> Signature {
Signature::build("enter").required(
"location",
SyntaxShape::Path,
"the location to create a new shell from",
)
Signature::build("enter")
.required(
"location",
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 {
"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"#
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
Ok(args.process_raw(registry, enter)?.run())
enter(args, registry).await
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Enter a path as a new shell",
example: "enter ../projectB",
result: None,
},
Example {
description: "Enter a file as a new shell",
example: "enter package.json",
result: None,
},
Example {
description: "Enters file with iso-8859-1 encoding",
example: "enter file.csv --encoding iso-8859-1",
result: None,
},
]
}
}
fn enter(
EnterArgs { location }: EnterArgs,
RunnableContext {
registry,
name: tag,
..
}: RunnableContext,
raw_args: RawCommandArgs,
async fn enter(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = raw_args.call_info.scope.clone();
let shell_manager = raw_args.shell_manager.clone();
let head = raw_args.call_info.args.head.clone();
let ctrl_c = raw_args.ctrl_c.clone();
let current_errors = raw_args.current_errors.clone();
let host = raw_args.host.clone();
let tag = raw_args.call_info.name_tag.clone();
let (EnterArgs { location, encoding }, _) = raw_args.process(&registry).await?;
let location_string = location.display().to_string();
let location_clone = location_string.clone();
@ -74,95 +101,109 @@ fn enter(
let (_, command) = (spec[0], spec[1]);
if registry.has(command) {
return Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
UntaggedValue::string(command).into_value(Tag::unknown()),
)))]
.into());
return Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::EnterHelpShell(
UntaggedValue::string(command).into_value(Tag::unknown()),
),
)));
}
}
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
UntaggedValue::nothing().into_value(Tag::unknown()),
)))]
.into())
Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::EnterHelpShell(UntaggedValue::nothing().into_value(Tag::unknown())),
)))
} else if location.is_dir() {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterShell(
location_clone,
)))]
.into())
Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::EnterShell(location_clone),
)))
} else {
let stream = async_stream! {
// If it's a file, attempt to open the file as a value and enter it
let cwd = raw_args.shell_manager.path();
// 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 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?;
let (file_extension, contents, contents_tag) = crate::commands::open::fetch(
&full_path,
&PathBuf::from(location_clone),
tag.span,
match encoding {
Some(e) => e.to_string(),
_ => "".to_string(),
},
)
.await?;
match contents {
UntaggedValue::Primitive(Primitive::String(_)) => {
let tagged_contents = contents.into_value(&contents_tag);
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: raw_args.host,
ctrl_c: raw_args.ctrl_c,
shell_manager: raw_args.shell_manager,
call_info: UnevaluatedCallInfo {
args: nu_protocol::hir::Call {
head: raw_args.call_info.args.head,
positional: None,
named: None,
span: Span::unknown(),
is_last: false,
},
name_tag: raw_args.call_info.name_tag,
scope: raw_args.call_info.scope.clone()
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,
},
};
let mut result = converter.run(
new_args.with_input(vec![tagged_contents]),
&registry,
);
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);
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;
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
Ok(futures::stream::iter(result_vec.into_iter().map(
move |res| match res {
Ok(ReturnSuccess::Value(Value { value, .. })) => Ok(
ReturnSuccess::Action(CommandAction::EnterValueShell(Value {
value,
tag: contents_tag.clone(),
})),
),
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(stream.to_output_stream())
_ => {
let tagged_contents = contents.into_value(contents_tag);
Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::EnterValueShell(tagged_contents),
)))
}
}
}
}
#[cfg(test)]
mod tests {
use super::Enter;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Enter {})
}
}

View File

@ -13,6 +13,7 @@ pub struct EvaluateByArgs {
evaluate_with: Option<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for EvaluateBy {
fn name(&self) -> &str {
"evaluate-by"
@ -31,42 +32,52 @@ impl WholeStreamCommand for EvaluateBy {
"Creates a new table with the data from the tables rows evaluated by the column given."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, evaluate_by)?.run()
evaluate_by(args, registry).await
}
}
pub fn evaluate_by(
EvaluateByArgs { evaluate_with }: EvaluateByArgs,
RunnableContext { input, name, .. }: RunnableContext,
pub async fn evaluate_by(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let values: Vec<Value> = input.collect().await;
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() {
yield Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
name
))
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
};
let evaluate_with = if let Some(evaluator) = evaluate_with {
Some(evaluator.item().clone())
} else {
None
};
match evaluate(&values[0], evaluate_with, name) {
Ok(evaluated) => yield ReturnSuccess::value(evaluated),
Err(err) => yield Err(err)
}
match evaluate(&values[0], evaluate_with, name) {
Ok(evaluated) => Ok(OutputStream::one(ReturnSuccess::value(evaluated))),
Err(err) => Err(err),
}
};
Ok(stream.to_output_stream())
}
}
#[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,105 @@
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 v: Vec<_> = input.into_vec().await;
let stride_desired = if stride.item < 1 { 1 } else { stride.item } as usize;
let mut values_vec_deque = VecDeque::new();
for (i, x) in v.iter().enumerate() {
let should_include = if skip.item {
i % stride_desired != 0
} else {
i % stride_desired == 0
};
if should_include {
values_vec_deque.push_back(ReturnSuccess::value(x.clone()));
}
}
Ok(futures::stream::iter(values_vec_deque).to_output_stream())
}
#[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

@ -6,6 +6,7 @@ use nu_protocol::{CommandAction, ReturnSuccess, Signature};
pub struct Exit;
#[async_trait]
impl WholeStreamCommand for Exit {
fn name(&self) -> &str {
"exit"
@ -19,34 +20,54 @@ impl WholeStreamCommand for Exit {
"Exit the current shell (or all shells)"
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
exit(args, registry)
exit(args, registry).await
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Exit the current shell",
example: "exit",
result: None,
},
Example {
description: "Exit all shells (exiting Nu)",
example: "exit --now",
result: None,
},
]
}
}
pub fn exit(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
pub async fn exit(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
if args.call_info.args.has("now") {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::Exit))].into())
let command_action = if args.call_info.args.has("now") {
CommandAction::Exit
} else {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::LeaveShell))].into())
CommandAction::LeaveShell
};
Ok(OutputStream::one(ReturnSuccess::action(command_action)))
}
#[cfg(test)]
mod tests {
use super::Exit;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Exit {})
}
}

View File

@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape};
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
pub struct First;
@ -12,6 +12,7 @@ pub struct FirstArgs {
rows: Option<Tagged<usize>>,
}
#[async_trait]
impl WholeStreamCommand for First {
fn name(&self) -> &str {
"first"
@ -29,37 +30,53 @@ impl WholeStreamCommand for First {
"Show only the first number of rows."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, first)?.run()
first(args, registry).await
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Return the first item of a list/table",
example: "echo [1 2 3] | first",
result: Some(vec![UntaggedValue::int(1).into()]),
},
Example {
description: "Return the first 2 items of a list/table",
example: "echo [1 2 3] | first 2",
result: Some(vec![
UntaggedValue::int(1).into(),
UntaggedValue::int(2).into(),
]),
},
]
}
}
fn first(
FirstArgs { rows }: FirstArgs,
context: RunnableContext,
) -> Result<OutputStream, ShellError> {
async fn first(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (FirstArgs { rows }, input) = args.process(&registry).await?;
let rows_desired = if let Some(quantity) = rows {
*quantity
} else {
1
};
Ok(OutputStream::from_input(context.input.take(rows_desired)))
Ok(input.take(rows_desired).to_output_stream())
}
#[cfg(test)]
mod tests {
use super::First;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(First {})
}
}

View File

@ -1,10 +1,10 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
use nu_value_ext::{as_column_path, get_data_by_column_path};
use std::borrow::Borrow;
pub struct Format;
@ -14,6 +14,7 @@ pub struct FormatArgs {
pattern: Tagged<String>,
}
#[async_trait]
impl WholeStreamCommand for Format {
fn name(&self) -> &str {
"format"
@ -31,76 +32,77 @@ impl WholeStreamCommand for Format {
"Format columns into a string using a simple pattern."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, format_command)?.run()
format_command(args, registry).await
}
fn examples(&self) -> &[Example] {
&[Example {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Print filenames with their sizes",
example: "ls | format '{name}: {size}'",
result: None,
}]
}
}
fn format_command(
FormatArgs { pattern }: FormatArgs,
RunnableContext { input, .. }: RunnableContext,
async fn format_command(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let pattern_tag = pattern.tag.clone();
let registry = Arc::new(registry.clone());
let scope = Arc::new(args.call_info.scope.clone());
let (FormatArgs { pattern }, input) = args.process(&registry).await?;
let format_pattern = format(&pattern);
let commands = format_pattern;
let mut input = input;
let commands = Arc::new(format_pattern);
let stream = async_stream! {
while let Some(value) = input.next().await {
match value {
value
@
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut output = String::new();
Ok(input
.then(move |value| {
let mut output = String::new();
let commands = commands.clone();
let registry = registry.clone();
let scope = scope.clone();
for command in &commands {
match command {
FormatCommand::Text(s) => {
output.push_str(&s);
}
FormatCommand::Column(c) => {
let key = to_column_path(&c, &pattern_tag)?;
async move {
for command in &*commands {
match command {
FormatCommand::Text(s) => {
output.push_str(&s);
}
FormatCommand::Column(c) => {
// FIXME: use the correct spans
let full_column_path = nu_parser::parse_full_column_path(
&(c.to_string()).spanned(Span::unknown()),
&*registry,
);
let fetcher = get_data_by_column_path(
&value,
&key,
Box::new(move |(_, _, error)| error),
);
let result = evaluate_baseline_expr(
&full_column_path.0,
&registry,
&value,
&scope.vars,
&scope.env,
)
.await;
if let Ok(c) = fetcher {
output
.push_str(&value::format_leaf(c.borrow()).plain_string(100_000))
}
if let Ok(c) = result {
output
.push_str(&value::format_leaf(c.borrow()).plain_string(100_000))
} else {
// That column doesn't match, so don't emit anything
}
}
}
yield ReturnSuccess::value(
UntaggedValue::string(output).into_untagged_value())
}
_ => yield ReturnSuccess::value(
UntaggedValue::string(String::new()).into_untagged_value()),
};
}
};
Ok(stream.to_output_stream())
ReturnSuccess::value(UntaggedValue::string(output).into_untagged_value())
}
})
.to_output_stream())
}
#[derive(Debug)]
@ -148,26 +150,14 @@ fn format(input: &str) -> Vec<FormatCommand> {
output
}
fn to_column_path(
path_members: &str,
tag: impl Into<Tag>,
) -> Result<Tagged<ColumnPath>, ShellError> {
let tag = tag.into();
#[cfg(test)]
mod tests {
use super::Format;
as_column_path(
&UntaggedValue::Table(
path_members
.split('.')
.map(|x| {
let member = match x.parse::<u64>() {
Ok(v) => UntaggedValue::int(v),
Err(_) => UntaggedValue::string(x),
};
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
member.into_value(&tag)
})
.collect(),
)
.into_value(&tag),
)
test_examples(Format {})
}
}

View File

@ -1,10 +1,11 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::Signature;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
pub struct From;
#[async_trait]
impl WholeStreamCommand for From {
fn name(&self) -> &str {
"from"
@ -18,11 +19,27 @@ impl WholeStreamCommand for From {
"Parse content (string or binary) as a table (input format based on subcommand, like csv, ini, json, toml)"
}
fn run(
async fn run(
&self,
_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
Ok(crate::commands::help::get_help(&*self, registry).into())
let registry = registry.clone();
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(crate::commands::help::get_help(&From, &registry))
.into_value(Tag::unknown()),
)))
}
}
#[cfg(test)]
mod tests {
use super::From;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(From {})
}
}

View File

@ -8,6 +8,7 @@ use std::str::FromStr;
pub struct FromBSON;
#[async_trait]
impl WholeStreamCommand for FromBSON {
fn name(&self) -> &str {
"from bson"
@ -21,18 +22,19 @@ impl WholeStreamCommand for FromBSON {
"Parse binary as .bson and create table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_bson(args, registry)
from_bson(args, registry).await
}
fn examples(&self) -> &[Example] {
&[Example {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Convert bson data to a table",
example: "open file.bin | from bson",
result: None,
}]
}
}
@ -206,27 +208,37 @@ pub fn from_bson_bytes_to_value(bytes: Vec<u8>, tag: impl Into<Tag>) -> Result<V
convert_bson_value_to_nu_value(&Bson::Array(docs), tag)
}
fn from_bson(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
async fn from_bson(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag();
let input = args.input;
let stream = async_stream! {
let bytes = input.collect_binary(tag.clone()).await?;
let bytes = input.collect_binary(tag.clone()).await?;
match from_bson_bytes_to_value(bytes.item, tag.clone()) {
Ok(x) => yield ReturnSuccess::value(x),
Err(_) => {
yield Err(ShellError::labeled_error_with_secondary(
"Could not parse as BSON",
"input cannot be parsed as BSON",
tag.clone(),
"value originates from here",
bytes.tag,
))
}
}
};
Ok(stream.to_output_stream())
match from_bson_bytes_to_value(bytes.item, tag.clone()) {
Ok(x) => Ok(OutputStream::one(ReturnSuccess::value(x))),
Err(_) => Err(ShellError::labeled_error_with_secondary(
"Could not parse as BSON",
"input cannot be parsed as BSON",
tag.clone(),
"value originates from here",
bytes.tag,
)),
}
}
#[cfg(test)]
mod tests {
use super::FromBSON;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(FromBSON {})
}
}

View File

@ -12,6 +12,7 @@ pub struct FromCSVArgs {
separator: Option<Value>,
}
#[async_trait]
impl WholeStreamCommand for FromCSV {
fn name(&self) -> &str {
"from csv"
@ -36,39 +37,49 @@ impl WholeStreamCommand for FromCSV {
"Parse text as .csv and create table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, from_csv)?.run()
from_csv(args, registry).await
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Convert comma-separated data to a table",
example: "open data.txt | from csv",
result: None,
},
Example {
description: "Convert comma-separated data to a table, ignoring headers",
example: "open data.txt | from csv --headerless",
result: None,
},
Example {
description: "Convert semicolon-separated data to a table",
example: "open data.txt | from csv --separator ';'",
result: None,
},
]
}
}
fn from_csv(
FromCSVArgs {
headerless,
separator,
}: FromCSVArgs,
runnable_context: RunnableContext,
async fn from_csv(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let (
FromCSVArgs {
headerless,
separator,
},
input,
) = args.process(&registry).await?;
let sep = match separator {
Some(Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
@ -92,5 +103,17 @@ fn from_csv(
_ => ',',
};
from_delimited_data(headerless, sep, "CSV", runnable_context)
from_delimited_data(headerless, sep, "CSV", input, name).await
}
#[cfg(test)]
mod tests {
use super::FromCSV;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(FromCSV {})
}
}

View File

@ -1,7 +1,7 @@
use crate::prelude::*;
use csv::{ErrorKind, ReaderBuilder};
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, TaggedDictBuilder, UntaggedValue, Value};
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
fn from_delimited_string_to_value(
s: String,
@ -41,44 +41,40 @@ fn from_delimited_string_to_value(
Ok(UntaggedValue::Table(rows).into_value(&tag))
}
pub fn from_delimited_data(
pub async fn from_delimited_data(
headerless: bool,
sep: char,
format_name: &'static str,
RunnableContext { input, name, .. }: RunnableContext,
input: InputStream,
name: Tag,
) -> Result<OutputStream, ShellError> {
let name_tag = name;
let concat_string = input.collect_string(name_tag.clone()).await?;
let stream = async_stream! {
let concat_string = input.collect_string(name_tag.clone()).await?;
match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) {
Ok(x) => match x {
Value {
value: UntaggedValue::Table(list),
..
} => Ok(futures::stream::iter(list).to_output_stream()),
x => Ok(OutputStream::one(x)),
},
Err(err) => {
let line_one = match pretty_csv_error(err) {
Some(pretty) => format!("Could not parse as {} ({})", format_name, pretty),
None => format!("Could not parse as {}", format_name),
};
let line_two = format!("input cannot be parsed as {}", format_name);
match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) {
Ok(x) => match x {
Value { value: UntaggedValue::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
}
x => yield ReturnSuccess::value(x),
},
Err(err) => {
let line_one = match pretty_csv_error(err) {
Some(pretty) => format!("Could not parse as {} ({})", format_name,pretty),
None => format!("Could not parse as {}", format_name),
};
let line_two = format!("input cannot be parsed as {}", format_name);
yield Err(ShellError::labeled_error_with_secondary(
line_one,
line_two,
name_tag.clone(),
"value originates from here",
concat_string.tag,
))
} ,
Err(ShellError::labeled_error_with_secondary(
line_one,
line_two,
name_tag.clone(),
"value originates from here",
concat_string.tag,
))
}
};
Ok(stream.to_output_stream())
}
}
fn pretty_csv_error(err: csv::Error) -> Option<String> {

View File

@ -16,6 +16,7 @@ pub struct FromEMLArgs {
preview_body: Option<Tagged<usize>>,
}
#[async_trait]
impl WholeStreamCommand for FromEML {
fn name(&self) -> &str {
"from eml"
@ -34,12 +35,12 @@ impl WholeStreamCommand for FromEML {
"Parse text as .eml and create table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, from_eml)?.run()
from_eml(args, registry).await
}
}
@ -76,47 +77,64 @@ fn headerfieldvalue_to_value(tag: &Tag, value: &HeaderFieldValue) -> UntaggedVal
}
}
fn from_eml(
eml_args: FromEMLArgs,
runnable_context: RunnableContext,
async fn from_eml(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let input = runnable_context.input;
let tag = runnable_context.name;
let tag = args.call_info.name_tag.clone();
let registry = registry.clone();
let (eml_args, input): (FromEMLArgs, _) = args.process(&registry).await?;
let value = input.collect_string(tag.clone()).await?;
let stream = async_stream! {
let value = input.collect_string(tag.clone()).await?;
let body_preview = eml_args
.preview_body
.map(|b| b.item)
.unwrap_or(DEFAULT_BODY_PREVIEW);
let body_preview = eml_args.preview_body.map(|b| b.item).unwrap_or(DEFAULT_BODY_PREVIEW);
let eml = EmlParser::from_string(value.item)
let eml = EmlParser::from_string(value.item)
.with_body_preview(body_preview)
.parse()
.map_err(|_| ShellError::labeled_error("Could not parse .eml file", "could not parse .eml file", &tag))?;
.map_err(|_| {
ShellError::labeled_error(
"Could not parse .eml file",
"could not parse .eml file",
&tag,
)
})?;
let mut dict = TaggedDictBuilder::new(&tag);
let mut dict = TaggedDictBuilder::new(&tag);
if let Some(subj) = eml.subject {
dict.insert_untagged("Subject", UntaggedValue::string(subj));
}
if let Some(subj) = eml.subject {
dict.insert_untagged("Subject", UntaggedValue::string(subj));
}
if let Some(from) = eml.from {
dict.insert_untagged("From", headerfieldvalue_to_value(&tag, &from));
}
if let Some(from) = eml.from {
dict.insert_untagged("From", headerfieldvalue_to_value(&tag, &from));
}
if let Some(to) = eml.to {
dict.insert_untagged("To", headerfieldvalue_to_value(&tag, &to));
}
if let Some(to) = eml.to {
dict.insert_untagged("To", headerfieldvalue_to_value(&tag, &to));
}
for HeaderField{ name, value } in eml.headers.iter() {
dict.insert_untagged(name, headerfieldvalue_to_value(&tag, &value));
}
for HeaderField { name, value } in eml.headers.iter() {
dict.insert_untagged(name, headerfieldvalue_to_value(&tag, &value));
}
if let Some(body) = eml.body {
dict.insert_untagged("Body", UntaggedValue::string(body));
}
if let Some(body) = eml.body {
dict.insert_untagged("Body", UntaggedValue::string(body));
}
yield ReturnSuccess::value(dict.into_value());
};
Ok(stream.to_output_stream())
Ok(OutputStream::one(ReturnSuccess::value(dict.into_value())))
}
#[cfg(test)]
mod tests {
use super::FromEML;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(FromEML {})
}
}

View File

@ -9,6 +9,7 @@ use std::io::BufReader;
pub struct FromIcs;
#[async_trait]
impl WholeStreamCommand for FromIcs {
fn name(&self) -> &str {
"from ics"
@ -22,39 +23,45 @@ impl WholeStreamCommand for FromIcs {
"Parse text as .ics and create table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_ics(args, registry)
from_ics(args, registry).await
}
}
fn from_ics(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
async fn from_ics(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag();
let input = args.input;
let stream = async_stream! {
let input_string = input.collect_string(tag.clone()).await?.item;
let input_bytes = input_string.as_bytes();
let buf_reader = BufReader::new(input_bytes);
let parser = ical::IcalParser::new(buf_reader);
let input_string = input.collect_string(tag.clone()).await?.item;
let input_bytes = input_string.as_bytes();
let buf_reader = BufReader::new(input_bytes);
let parser = ical::IcalParser::new(buf_reader);
for calendar in parser {
match calendar {
Ok(c) => yield ReturnSuccess::value(calendar_to_value(c, tag.clone())),
Err(_) => yield Err(ShellError::labeled_error(
"Could not parse as .ics",
"input cannot be parsed as .ics",
tag.clone()
)),
}
// TODO: it should be possible to make this a stream, but the some of the lifetime requirements make this tricky.
// Pre-computing for now
let mut output = vec![];
for calendar in parser {
match calendar {
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 {
@ -238,3 +245,15 @@ fn params_to_value(params: Vec<(String, Vec<String>)>, tag: Tag) -> Value {
row.into_value()
}
#[cfg(test)]
mod tests {
use super::FromIcs;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(FromIcs {})
}
}

View File

@ -1,11 +1,12 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value};
use std::collections::HashMap;
pub struct FromINI;
#[async_trait]
impl WholeStreamCommand for FromINI {
fn name(&self) -> &str {
"from ini"
@ -19,12 +20,12 @@ impl WholeStreamCommand for FromINI {
"Parse text as .ini and create table"
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_ini(args, registry)
from_ini(args, registry).await
}
}
@ -63,34 +64,42 @@ pub fn from_ini_string_to_value(
Ok(convert_ini_top_to_nu_value(&v, tag))
}
fn from_ini(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
async fn from_ini(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag();
let input = args.input;
let concat_string = input.collect_string(tag.clone()).await?;
let stream = async_stream! {
let concat_string = input.collect_string(tag.clone()).await?;
match from_ini_string_to_value(concat_string.item, tag.clone()) {
Ok(x) => match x {
Value { value: UntaggedValue::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
}
x => yield ReturnSuccess::value(x),
},
Err(_) => {
yield Err(ShellError::labeled_error_with_secondary(
"Could not parse as INI",
"input cannot be parsed as INI",
&tag,
"value originates from here",
concat_string.tag,
))
}
}
};
Ok(stream.to_output_stream())
match from_ini_string_to_value(concat_string.item, tag.clone()) {
Ok(x) => match x {
Value {
value: UntaggedValue::Table(list),
..
} => Ok(futures::stream::iter(list).to_output_stream()),
x => Ok(OutputStream::one(x)),
},
Err(_) => Err(ShellError::labeled_error_with_secondary(
"Could not parse as INI",
"input cannot be parsed as INI",
&tag,
"value originates from here",
concat_string.tag,
)),
}
}
#[cfg(test)]
mod tests {
use super::FromINI;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(FromINI {})
}
}

View File

@ -10,6 +10,7 @@ pub struct FromJSONArgs {
objects: bool,
}
#[async_trait]
impl WholeStreamCommand for FromJSON {
fn name(&self) -> &str {
"from json"
@ -27,12 +28,12 @@ impl WholeStreamCommand for FromJSON {
"Parse text as .json and create table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, from_json)?.run()
from_json(args, registry).await
}
}
@ -70,64 +71,83 @@ pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> serde_hjson:
Ok(convert_json_value_to_nu_value(&v, tag))
}
fn from_json(
FromJSONArgs { objects }: FromJSONArgs,
RunnableContext { input, name, .. }: RunnableContext,
async fn from_json(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name_tag = name;
let name_tag = args.call_info.name_tag.clone();
let registry = registry.clone();
let stream = async_stream! {
let concat_string = input.collect_string(name_tag.clone()).await?;
let (FromJSONArgs { objects }, input) = args.process(&registry).await?;
let concat_string = input.collect_string(name_tag.clone()).await?;
if objects {
for json_str in concat_string.item.lines() {
let string_clone: Vec<_> = concat_string.item.lines().map(|x| x.to_string()).collect();
if objects {
Ok(
futures::stream::iter(string_clone.into_iter().filter_map(move |json_str| {
if json_str.is_empty() {
continue;
return None;
}
match from_json_string_to_value(json_str.to_string(), &name_tag) {
Ok(x) =>
yield ReturnSuccess::value(x),
match from_json_string_to_value(json_str, &name_tag) {
Ok(x) => Some(ReturnSuccess::value(x)),
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(
Some(Err(ShellError::labeled_error_with_secondary(
message,
"input cannot be parsed as JSON",
&name_tag,
name_tag.clone(),
"value originates from here",
concat_string.tag.clone()))
concat_string.tag.clone(),
)))
}
}
}
} else {
match from_json_string_to_value(concat_string.item, name_tag.clone()) {
Ok(x) =>
match x {
Value { value: UntaggedValue::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
}
x => yield ReturnSuccess::value(x),
}
Err(e) => {
let mut message = "Could not parse as JSON (".to_string();
message.push_str(&e.to_string());
message.push_str(")");
}))
.to_output_stream(),
)
} else {
match from_json_string_to_value(concat_string.item, name_tag.clone()) {
Ok(x) => match x {
Value {
value: UntaggedValue::Table(list),
..
} => Ok(
futures::stream::iter(list.into_iter().map(ReturnSuccess::value))
.to_output_stream(),
),
x => Ok(OutputStream::one(ReturnSuccess::value(x))),
},
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,
"input cannot be parsed as JSON",
name_tag,
"value originates from here",
concat_string.tag))
}
concat_string.tag,
),
)))
}
}
};
Ok(stream.to_output_stream())
}
}
#[cfg(test)]
mod tests {
use super::FromJSON;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(FromJSON {})
}
}

View File

@ -13,6 +13,7 @@ pub struct FromODSArgs {
headerless: bool,
}
#[async_trait]
impl WholeStreamCommand for FromODS {
fn name(&self) -> &str {
"from ods"
@ -30,69 +31,81 @@ impl WholeStreamCommand for FromODS {
"Parse OpenDocument Spreadsheet(.ods) data and create table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, from_ods)?.run()
from_ods(args, registry).await
}
}
fn from_ods(
FromODSArgs {
headerless: _headerless,
}: FromODSArgs,
runnable_context: RunnableContext,
async fn from_ods(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let input = runnable_context.input;
let tag = runnable_context.name;
let tag = args.call_info.name_tag.clone();
let registry = registry.clone();
let stream = async_stream! {
let bytes = input.collect_binary(tag.clone()).await?;
let mut 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 (
FromODSArgs {
headerless: _headerless,
},
input,
) = args.process(&registry).await?;
let bytes = input.collect_binary(tag.clone()).await?;
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 {
let mut sheet_output = TaggedListBuilder::new(&tag);
for sheet_name in &sheet_names {
let mut sheet_output = TaggedListBuilder::new(&tag);
if let Some(Ok(current_sheet)) = ods.worksheet_range(sheet_name) {
for row in current_sheet.rows() {
let mut row_output = TaggedDictBuilder::new(&tag);
for (i, cell) in row.iter().enumerate() {
let value = match cell {
DataType::Empty => UntaggedValue::nothing(),
DataType::String(s) => UntaggedValue::string(s),
DataType::Float(f) => UntaggedValue::decimal(*f),
DataType::Int(i) => UntaggedValue::int(*i),
DataType::Bool(b) => UntaggedValue::boolean(*b),
_ => UntaggedValue::nothing(),
};
if let Some(Ok(current_sheet)) = ods.worksheet_range(sheet_name) {
for row in current_sheet.rows() {
let mut row_output = TaggedDictBuilder::new(&tag);
for (i, cell) in row.iter().enumerate() {
let value = match cell {
DataType::Empty => UntaggedValue::nothing(),
DataType::String(s) => UntaggedValue::string(s),
DataType::Float(f) => UntaggedValue::decimal(*f),
DataType::Int(i) => UntaggedValue::int(*i),
DataType::Bool(b) => UntaggedValue::boolean(*b),
_ => UntaggedValue::nothing(),
};
row_output.insert_untagged(&format!("Column{}", i), value);
}
sheet_output.push_untagged(row_output.into_untagged_value());
row_output.insert_untagged(&format!("Column{}", i), value);
}
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value());
} else {
yield Err(ShellError::labeled_error(
"Could not load sheet",
"could not load sheet",
&tag));
sheet_output.push_untagged(row_output.into_untagged_value());
}
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(stream.to_output_stream())
Ok(OutputStream::one(ReturnSuccess::value(dict.into_value())))
}
#[cfg(test)]
mod tests {
use super::FromODS;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(FromODS {})
}
}

View File

@ -1,13 +1,14 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value};
use rusqlite::{types::ValueRef, Connection, Row, NO_PARAMS};
use std::io::Write;
use std::path::Path;
pub struct FromSQLite;
#[async_trait]
impl WholeStreamCommand for FromSQLite {
fn name(&self) -> &str {
"from sqlite"
@ -21,17 +22,18 @@ impl WholeStreamCommand for FromSQLite {
"Parse binary data as sqlite .db and create table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_sqlite(args, registry)
from_sqlite(args, registry).await
}
}
pub struct FromDB;
#[async_trait]
impl WholeStreamCommand for FromDB {
fn name(&self) -> &str {
"from db"
@ -45,12 +47,12 @@ impl WholeStreamCommand for FromDB {
"Parse binary data as db and create table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_sqlite(args, registry)
from_sqlite(args, registry).await
}
}
@ -63,6 +65,7 @@ pub fn convert_sqlite_file_to_nu_value(
let mut meta_out = Vec::new();
let mut meta_stmt = conn.prepare("select name from sqlite_master where type='table'")?;
let mut meta_rows = meta_stmt.query(NO_PARAMS)?;
while let Some(meta_row) = meta_rows.next()? {
let table_name: String = meta_row.get(0)?;
let mut meta_dict = TaggedDictBuilder::new(tag.clone());
@ -132,34 +135,47 @@ pub fn from_sqlite_bytes_to_value(
}
}
fn from_sqlite(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
async fn from_sqlite(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag();
let input = args.input;
let stream = async_stream! {
let bytes = input.collect_binary(tag.clone()).await?;
match from_sqlite_bytes_to_value(bytes.item, tag.clone()) {
Ok(x) => match x {
Value { value: UntaggedValue::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
}
_ => yield ReturnSuccess::value(x),
}
Err(err) => {
println!("{:?}", err);
yield Err(ShellError::labeled_error_with_secondary(
"Could not parse as SQLite",
"input cannot be parsed as SQLite",
&tag,
"value originates from here",
bytes.tag,
))
}
}
};
let bytes = input.collect_binary(tag.clone()).await?;
Ok(stream.to_output_stream())
match from_sqlite_bytes_to_value(bytes.item, tag.clone()) {
Ok(x) => match x {
Value {
value: UntaggedValue::Table(list),
..
} => Ok(futures::stream::iter(list).to_output_stream()),
_ => Ok(OutputStream::one(x)),
},
Err(err) => {
println!("{:?}", err);
Err(ShellError::labeled_error_with_secondary(
"Could not parse as SQLite",
"input cannot be parsed as SQLite",
&tag,
"value originates from here",
bytes.tag,
))
}
}
}
#[cfg(test)]
mod tests {
use super::FromSQLite;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(FromSQLite {})
}
}

View File

@ -20,6 +20,7 @@ pub struct FromSSVArgs {
const STRING_REPRESENTATION: &str = "from ssv";
const DEFAULT_MINIMUM_SPACES: usize = 2;
#[async_trait]
impl WholeStreamCommand for FromSSV {
fn name(&self) -> &str {
STRING_REPRESENTATION
@ -45,12 +46,12 @@ impl WholeStreamCommand for FromSSV {
"Parse text as space-separated values and create a table. The default minimum number of spaces counted as a separator is 2."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, from_ssv)?.run()
from_ssv(args, registry).await
}
}
@ -250,41 +251,53 @@ fn from_ssv_string_to_value(
Some(UntaggedValue::Table(rows).into_value(&tag))
}
fn from_ssv(
FromSSVArgs {
headerless,
aligned_columns,
minimum_spaces,
}: FromSSVArgs,
RunnableContext { input, name, .. }: RunnableContext,
async fn from_ssv(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let concat_string = input.collect_string(name.clone()).await?;
let split_at = match minimum_spaces {
Some(number) => number.item,
None => DEFAULT_MINIMUM_SPACES
};
let name = args.call_info.name_tag.clone();
let registry = registry.clone();
let (
FromSSVArgs {
headerless,
aligned_columns,
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 {
Value { value: UntaggedValue::Table(list), ..} => {
for l in list { yield ReturnSuccess::value(l) }
}
x => yield ReturnSuccess::value(x)
Value {
value: UntaggedValue::Table(list),
..
} => futures::stream::iter(list.into_iter().map(ReturnSuccess::value))
.to_output_stream(),
x => OutputStream::one(ReturnSuccess::value(x)),
},
None => {
yield Err(ShellError::labeled_error_with_secondary(
return Err(ShellError::labeled_error_with_secondary(
"Could not parse as SSV",
"input cannot be parsed ssv",
&name,
"value originates from here",
&concat_string.tag,
))
},
}
};
Ok(stream.to_output_stream())
));
}
},
)
}
#[cfg(test)]
@ -489,4 +502,12 @@ mod tests {
assert_eq!(aligned_columns_headerless, separator_headerless);
assert_eq!(aligned_columns_with_headers, separator_with_headers);
}
#[test]
fn examples_work_as_expected() {
use super::FromSSV;
use crate::examples::test as test_examples;
test_examples(FromSSV {})
}
}

View File

@ -5,6 +5,7 @@ use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, Untagg
pub struct FromTOML;
#[async_trait]
impl WholeStreamCommand for FromTOML {
fn name(&self) -> &str {
"from toml"
@ -18,12 +19,12 @@ impl WholeStreamCommand for FromTOML {
"Parse text as .toml and create table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_toml(args, registry)
from_toml(args, registry).await
}
}
@ -63,27 +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))
}
pub fn from_toml(
pub async fn from_toml(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag();
let input = args.input;
let stream = async_stream! {
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()) {
Ok(x) => match x {
Value { value: UntaggedValue::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
}
x => yield ReturnSuccess::value(x),
Value {
value: UntaggedValue::Table(list),
..
} => futures::stream::iter(list.into_iter().map(ReturnSuccess::value))
.to_output_stream(),
x => OutputStream::one(ReturnSuccess::value(x)),
},
Err(_) => {
yield Err(ShellError::labeled_error_with_secondary(
return Err(ShellError::labeled_error_with_secondary(
"Could not parse as TOML",
"input cannot be parsed as TOML",
&tag,
@ -91,8 +93,18 @@ pub fn from_toml(
concat_string.tag,
))
}
}
};
Ok(stream.to_output_stream())
},
)
}
#[cfg(test)]
mod tests {
use super::FromTOML;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(FromTOML {})
}
}

View File

@ -11,6 +11,7 @@ pub struct FromTSVArgs {
headerless: bool,
}
#[async_trait]
impl WholeStreamCommand for FromTSV {
fn name(&self) -> &str {
"from tsv"
@ -28,18 +29,34 @@ impl WholeStreamCommand for FromTSV {
"Parse text as .tsv and create table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, from_tsv)?.run()
from_tsv(args, registry).await
}
}
fn from_tsv(
FromTSVArgs { headerless }: FromTSVArgs,
runnable_context: RunnableContext,
async fn from_tsv(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_delimited_data(headerless, '\t', "TSV", runnable_context)
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let (FromTSVArgs { headerless }, input) = args.process(&registry).await?;
from_delimited_data(headerless, '\t', "TSV", input, name).await
}
#[cfg(test)]
mod tests {
use super::FromTSV;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(FromTSV {})
}
}

View File

@ -5,6 +5,7 @@ use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
pub struct FromURL;
#[async_trait]
impl WholeStreamCommand for FromURL {
fn name(&self) -> &str {
"from url"
@ -18,46 +19,56 @@ impl WholeStreamCommand for FromURL {
"Parse url-encoded string as a table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_url(args, registry)
from_url(args, registry).await
}
}
fn from_url(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
async fn from_url(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag();
let input = args.input;
let stream = async_stream! {
let concat_string = input.collect_string(tag.clone()).await?;
let concat_string = input.collect_string(tag.clone()).await?;
let result = serde_urlencoded::from_str::<Vec<(String, String)>>(&concat_string.item);
let result = serde_urlencoded::from_str::<Vec<(String, String)>>(&concat_string.item);
match result {
Ok(result) => {
let mut row = TaggedDictBuilder::new(tag);
match result {
Ok(result) => {
let mut row = TaggedDictBuilder::new(tag);
for (k,v) in result {
row.insert_untagged(k, UntaggedValue::string(v));
}
yield ReturnSuccess::value(row.into_value());
}
_ => {
yield Err(ShellError::labeled_error_with_secondary(
"String not compatible with url-encoding",
"input not url-encoded",
tag,
"value originates from here",
concat_string.tag,
));
for (k, v) in result {
row.insert_untagged(k, UntaggedValue::string(v));
}
Ok(OutputStream::one(ReturnSuccess::value(row.into_value())))
}
};
Ok(stream.to_output_stream())
_ => Err(ShellError::labeled_error_with_secondary(
"String not compatible with url-encoding",
"input not url-encoded",
tag,
"value originates from here",
concat_string.tag,
)),
}
}
#[cfg(test)]
mod tests {
use super::FromURL;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(FromURL {})
}
}

View File

@ -9,6 +9,7 @@ use std::io::BufReader;
pub struct FromVcf;
#[async_trait]
impl WholeStreamCommand for FromVcf {
fn name(&self) -> &str {
"from vcf"
@ -22,39 +23,47 @@ impl WholeStreamCommand for FromVcf {
"Parse text as .vcf and create table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_vcf(args, registry)
from_vcf(args, registry).await
}
}
fn from_vcf(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
async fn from_vcf(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag();
let input = args.input;
let stream = async_stream! {
let input_string = input.collect_string(tag.clone()).await?.item;
let input_bytes = input_string.as_bytes();
let buf_reader = BufReader::new(input_bytes);
let parser = ical::VcardParser::new(buf_reader);
let input_string = input.collect_string(tag.clone()).await?.item;
let input_bytes = input_string.as_bytes();
let buf_reader = BufReader::new(input_bytes);
let parser = ical::VcardParser::new(buf_reader);
for contact in parser {
match contact {
Ok(c) => yield ReturnSuccess::value(contact_to_value(c, tag.clone())),
Err(_) => yield Err(ShellError::labeled_error(
let mut values_vec_deque = VecDeque::new();
for contact in parser {
match contact {
Ok(c) => {
values_vec_deque.push_back(ReturnSuccess::value(contact_to_value(c, tag.clone())))
}
Err(_) => {
return Err(ShellError::labeled_error(
"Could not parse as .vcf",
"input cannot be parsed as .vcf",
tag.clone()
)),
tag.clone(),
))
}
}
};
}
Ok(stream.to_output_stream())
Ok(futures::stream::iter(values_vec_deque).to_output_stream())
}
fn contact_to_value(contact: VcardContact, tag: Tag) -> Value {
@ -100,3 +109,15 @@ fn params_to_value(params: Vec<(String, Vec<String>)>, tag: Tag) -> Value {
row.into_value()
}
#[cfg(test)]
mod tests {
use super::FromVcf;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(FromVcf {})
}
}

View File

@ -13,6 +13,7 @@ pub struct FromXLSXArgs {
headerless: bool,
}
#[async_trait]
impl WholeStreamCommand for FromXLSX {
fn name(&self) -> &str {
"from xlsx"
@ -30,70 +31,81 @@ impl WholeStreamCommand for FromXLSX {
"Parse binary Excel(.xlsx) data and create table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, from_xlsx)?.run()
from_xlsx(args, registry).await
}
}
fn from_xlsx(
FromXLSXArgs {
headerless: _headerless,
}: FromXLSXArgs,
runnable_context: RunnableContext,
async fn from_xlsx(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let input = runnable_context.input;
let tag = runnable_context.name;
let tag = args.call_info.name_tag.clone();
let registry = registry.clone();
let (
FromXLSXArgs {
headerless: _headerless,
},
input,
) = args.process(&registry).await?;
let value = input.collect_binary(tag.clone()).await?;
let stream = async_stream! {
let value = input.collect_binary(tag.clone()).await?;
let buf: Cursor<Vec<u8>> = Cursor::new(value.item);
let mut xls = Xlsx::<_>::new(buf).map_err(|_| {
ShellError::labeled_error("Could not load xlsx file", "could not load xlsx file", &tag)
})?;
let mut buf: Cursor<Vec<u8>> = Cursor::new(value.item);
let mut xls = Xlsx::<_>::new(buf).map_err(|_| {
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 {
let mut sheet_output = TaggedListBuilder::new(&tag);
for sheet_name in &sheet_names {
let mut sheet_output = TaggedListBuilder::new(&tag);
if let Some(Ok(current_sheet)) = xls.worksheet_range(sheet_name) {
for row in current_sheet.rows() {
let mut row_output = TaggedDictBuilder::new(&tag);
for (i, cell) in row.iter().enumerate() {
let value = match cell {
DataType::Empty => UntaggedValue::nothing(),
DataType::String(s) => UntaggedValue::string(s),
DataType::Float(f) => UntaggedValue::decimal(*f),
DataType::Int(i) => UntaggedValue::int(*i),
DataType::Bool(b) => UntaggedValue::boolean(*b),
_ => UntaggedValue::nothing(),
};
if let Some(Ok(current_sheet)) = xls.worksheet_range(sheet_name) {
for row in current_sheet.rows() {
let mut row_output = TaggedDictBuilder::new(&tag);
for (i, cell) in row.iter().enumerate() {
let value = match cell {
DataType::Empty => UntaggedValue::nothing(),
DataType::String(s) => UntaggedValue::string(s),
DataType::Float(f) => UntaggedValue::decimal(*f),
DataType::Int(i) => UntaggedValue::int(*i),
DataType::Bool(b) => UntaggedValue::boolean(*b),
_ => UntaggedValue::nothing(),
};
row_output.insert_untagged(&format!("Column{}", i), value);
}
sheet_output.push_untagged(row_output.into_untagged_value());
row_output.insert_untagged(&format!("Column{}", i), value);
}
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value());
} else {
yield Err(ShellError::labeled_error(
"Could not load sheet",
"could not load sheet",
&tag,
));
sheet_output.push_untagged(row_output.into_untagged_value());
}
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(stream.to_output_stream())
Ok(OutputStream::one(ReturnSuccess::value(dict.into_value())))
}
#[cfg(test)]
mod tests {
use super::FromXLSX;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(FromXLSX {})
}
}

View File

@ -5,6 +5,7 @@ use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, Untagg
pub struct FromXML;
#[async_trait]
impl WholeStreamCommand for FromXML {
fn name(&self) -> &str {
"from xml"
@ -18,12 +19,12 @@ impl WholeStreamCommand for FromXML {
"Parse text as .xml and create table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_xml(args, registry)
from_xml(args, registry).await
}
}
@ -98,36 +99,38 @@ pub fn from_xml_string_to_value(s: String, tag: impl Into<Tag>) -> Result<Value,
Ok(from_document_to_value(&parsed, tag))
}
fn from_xml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
async fn from_xml(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag();
let input = args.input;
let stream = async_stream! {
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()) {
Ok(x) => match x {
Value { value: UntaggedValue::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
}
x => yield ReturnSuccess::value(x),
Value {
value: UntaggedValue::Table(list),
..
} => futures::stream::iter(list.into_iter().map(ReturnSuccess::value))
.to_output_stream(),
x => OutputStream::one(ReturnSuccess::value(x)),
},
Err(_) => {
yield Err(ShellError::labeled_error_with_secondary(
return Err(ShellError::labeled_error_with_secondary(
"Could not parse as XML",
"input cannot be parsed as XML",
&tag,
"value originates from here",
&concat_string.tag,
))
} ,
}
};
Ok(stream.to_output_stream())
}
},
)
}
#[cfg(test)]
@ -300,4 +303,12 @@ mod tests {
Ok(())
}
#[test]
fn examples_work_as_expected() {
use super::FromXML;
use crate::examples::test as test_examples;
test_examples(FromXML {})
}
}

View File

@ -1,10 +1,11 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value};
pub struct FromYAML;
#[async_trait]
impl WholeStreamCommand for FromYAML {
fn name(&self) -> &str {
"from yaml"
@ -18,17 +19,18 @@ impl WholeStreamCommand for FromYAML {
"Parse text as .yaml/.yml and create table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_yaml(args, registry)
from_yaml(args, registry).await
}
}
pub struct FromYML;
#[async_trait]
impl WholeStreamCommand for FromYML {
fn name(&self) -> &str {
"from yml"
@ -42,12 +44,12 @@ impl WholeStreamCommand for FromYML {
"Parse text as .yaml/.yml and create table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_yaml(args, registry)
from_yaml(args, registry).await
}
}
@ -57,27 +59,19 @@ fn convert_yaml_value_to_nu_value(
) -> Result<Value, ShellError> {
let tag = tag.into();
let err_not_compatible_number = ShellError::labeled_error(
"Expected a compatible number",
"expected a compatible number",
&tag,
);
Ok(match v {
serde_yaml::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(tag),
serde_yaml::Value::Number(n) if n.is_i64() => {
UntaggedValue::int(n.as_i64().ok_or_else(|| {
ShellError::labeled_error(
"Expected a compatible number",
"expected a compatible number",
&tag,
)
})?)
.into_value(tag)
UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag)
}
serde_yaml::Value::Number(n) if n.is_f64() => {
UntaggedValue::decimal(n.as_f64().ok_or_else(|| {
ShellError::labeled_error(
"Expected a compatible number",
"expected a compatible number",
&tag,
)
})?)
.into_value(tag)
UntaggedValue::decimal(n.as_f64().ok_or_else(|| err_not_compatible_number)?)
.into_value(tag)
}
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
serde_yaml::Value::Sequence(a) => {
@ -91,11 +85,39 @@ fn convert_yaml_value_to_nu_value(
let mut collected = TaggedDictBuilder::new(&tag);
for (k, v) in t.iter() {
match k {
serde_yaml::Value::String(k) => {
// A ShellError that we re-use multiple times in the Mapping scenario
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)?);
}
_ => 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);
}
}
}
@ -118,34 +140,79 @@ pub fn from_yaml_string_to_value(s: String, tag: impl Into<Tag>) -> Result<Value
Ok(convert_yaml_value_to_nu_value(&v, tag)?)
}
fn from_yaml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
async fn from_yaml(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag();
let input = args.input;
let stream = async_stream! {
let concat_string = input.collect_string(tag.clone()).await?;
let concat_string = input.collect_string(tag.clone()).await?;
match from_yaml_string_to_value(concat_string.item, tag.clone()) {
Ok(x) => match x {
Value { value: UntaggedValue::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
}
x => yield ReturnSuccess::value(x),
match from_yaml_string_to_value(concat_string.item, tag.clone()) {
Ok(x) => match x {
Value {
value: UntaggedValue::Table(list),
..
} => Ok(futures::stream::iter(list).to_output_stream()),
x => Ok(OutputStream::one(x)),
},
Err(_) => Err(ShellError::labeled_error_with_secondary(
"Could not parse as YAML",
"input cannot be parsed as YAML",
&tag,
"value originates from here",
&concat_string.tag,
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use nu_plugin::row;
use nu_plugin::test_helpers::value::string;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
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 }}")]),
},
Err(_) => {
yield Err(ShellError::labeled_error_with_secondary(
"Could not parse as YAML",
"input cannot be parsed as YAML",
&tag,
"value originates from here",
&concat_string.tag,
))
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);
}
}
};
Ok(stream.to_output_stream())
}
}

View File

@ -4,8 +4,8 @@ use indexmap::set::IndexSet;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, ReturnValue, Signature,
SyntaxShape, UnspannedPathMember, UntaggedValue, Value,
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape,
UnspannedPathMember, UntaggedValue, Value,
};
use nu_source::span_for_spanned_list;
use nu_value_ext::get_data_by_column_path;
@ -17,6 +17,7 @@ pub struct GetArgs {
rest: Vec<ColumnPath>,
}
#[async_trait]
impl WholeStreamCommand for Get {
fn name(&self) -> &str {
"get"
@ -33,23 +34,25 @@ impl WholeStreamCommand for Get {
"Open given cells as text."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, get)?.run()
get(args, registry).await
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Extract the name of files as a list",
example: "ls | get name",
result: None,
},
Example {
description: "Extract the cpu list from the sys information",
example: "sys | get cpu",
result: None,
},
]
}
@ -189,30 +192,24 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
)
}
pub fn get(
GetArgs { rest: mut fields }: GetArgs,
RunnableContext { mut input, .. }: RunnableContext,
pub async fn get(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (GetArgs { rest: mut fields }, mut input) = args.process(&registry).await?;
if fields.is_empty() {
let stream = async_stream! {
let mut vec = input.drain_vec().await;
let vec = input.drain_vec().await;
let descs = nu_protocol::merge_descriptors(&vec);
for desc in descs {
yield ReturnSuccess::value(desc);
}
};
let descs = nu_protocol::merge_descriptors(&vec);
let stream: BoxStream<'static, ReturnValue> = stream.boxed();
Ok(stream.to_output_stream())
Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
} else {
let member = fields.remove(0);
trace!("get {:?} {:?}", member, fields);
let stream = input
.map(move |item| {
let mut result = VecDeque::new();
Ok(input
.map(move |item| {
let member = vec![member.clone()];
let column_paths = vec![&member, &fields]
@ -220,6 +217,7 @@ pub fn get(
.flatten()
.collect::<Vec<&ColumnPath>>();
let mut output = vec![];
for path in column_paths {
let res = get_column_path(&path, &item);
@ -230,25 +228,36 @@ pub fn get(
..
} => {
for item in rows {
result.push_back(ReturnSuccess::value(item.clone()));
output.push(ReturnSuccess::value(item.clone()));
}
}
Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
..
} => {}
other => result.push_back(ReturnSuccess::value(other.clone())),
other => output.push(ReturnSuccess::value(other.clone())),
},
Err(reason) => result.push_back(ReturnSuccess::value(
Err(reason) => output.push(ReturnSuccess::value(
UntaggedValue::Error(reason).into_untagged_value(),
)),
}
}
futures::stream::iter(result)
futures::stream::iter(output)
})
.flatten();
Ok(stream.to_output_stream())
.flatten()
.to_output_stream())
}
}
#[cfg(test)]
mod tests {
use super::Get;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Get {})
}
}

View File

@ -1,132 +1,169 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use indexmap::indexmap;
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_value_ext::as_string;
pub struct GroupBy;
#[derive(Deserialize)]
pub struct GroupByArgs {
column_name: Tagged<String>,
date: Tagged<bool>,
format: Option<Tagged<String>>,
column_name: Option<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for GroupBy {
fn name(&self) -> &str {
"group-by"
}
fn signature(&self) -> Signature {
Signature::build("group-by")
.required(
"column_name",
SyntaxShape::String,
"the name of the column to group by",
)
.named(
"format",
SyntaxShape::String,
"Specify date and time formatting",
Some('f'),
)
.switch("date", "by date", Some('d'))
Signature::build("group-by").optional(
"column_name",
SyntaxShape::String,
"the name of the column to group by",
)
}
fn usage(&self) -> &str {
"Creates a new table with the data from the table rows grouped by the column given."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, group_by)?.run()
group_by(args, registry).await
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Group files by type",
example: "ls | group-by type",
}]
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Group items by type",
example: r#"ls | group-by type"#,
result: None,
},
Example {
description: "Group items by their value",
example: "echo [1 3 1 3 2 1 1] | group-by",
result: Some(vec![UntaggedValue::row(indexmap! {
"1".to_string() => UntaggedValue::Table(vec![
UntaggedValue::int(1).into(),
UntaggedValue::int(1).into(),
UntaggedValue::int(1).into(),
UntaggedValue::int(1).into(),
]).into(),
"3".to_string() => UntaggedValue::Table(vec![
UntaggedValue::int(3).into(),
UntaggedValue::int(3).into(),
]).into(),
"2".to_string() => UntaggedValue::Table(vec![
UntaggedValue::int(2).into(),
]).into(),
})
.into()]),
},
]
}
}
enum Grouper {
Default,
ByDate(Option<String>),
ByColumn(Option<Tagged<String>>),
}
pub fn group_by(
GroupByArgs {
column_name,
date,
format,
}: GroupByArgs,
RunnableContext { input, name, .. }: RunnableContext,
pub async fn group_by(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let values: Vec<Value> = input.collect().await;
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let (GroupByArgs { column_name }, input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
if values.is_empty() {
yield Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
column_name.span()
))
} else {
if values.is_empty() {
return Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
name,
));
}
let grouper = if let Tagged { item: true, tag } = date {
if let Some(Tagged { item: fmt, tag }) = format {
Grouper::ByDate(Some(fmt))
} else {
Grouper::ByDate(None)
}
} else {
Grouper::Default
};
let values = UntaggedValue::table(&values).into_value(&name);
match grouper {
Grouper::Default => {
match crate::utils::data::group(column_name, &values, None, &name) {
Ok(grouped) => yield ReturnSuccess::value(grouped),
Err(err) => yield Err(err),
}
}
Grouper::ByDate(None) => {
match crate::utils::data::group(column_name, &values, Some(Box::new(|row: &Value| row.format("%Y-%b-%d"))), &name) {
Ok(grouped) => yield ReturnSuccess::value(grouped),
Err(err) => yield Err(err),
}
}
Grouper::ByDate(Some(fmt)) => {
match crate::utils::data::group(column_name, &values, Some(Box::new(move |row: &Value| {
row.format(&fmt)
})), &name) {
Ok(grouped) => yield ReturnSuccess::value(grouped),
Err(err) => yield Err(err),
}
}
}
}
};
match group(&column_name, &values, name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(reason) => Err(reason),
}
}
Ok(stream.to_output_stream())
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(
column_name: &Tagged<String>,
values: Vec<Value>,
column_name: &Option<Tagged<String>>,
values: &Value,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
crate::utils::data::group(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)
}
}
}
#[cfg(test)]
mod tests {
use crate::commands::group_by::group;
use super::group;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value};
@ -144,7 +181,7 @@ mod tests {
UntaggedValue::table(list).into_untagged_value()
}
fn nu_releases_commiters() -> Vec<Value> {
fn nu_releases_committers() -> Vec<Value> {
vec![
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")},
@ -178,10 +215,11 @@ mod tests {
#[test]
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!(
group(&for_key, nu_releases_commiters(), Tag::unknown())?,
group(&for_key, &sample, Tag::unknown())?,
row(indexmap! {
"August 23-2019".into() => table(&[
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}),
@ -206,10 +244,11 @@ mod tests {
#[test]
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!(
group(&for_key, nu_releases_commiters(), Tag::unknown())?,
group(&for_key, &sample, Tag::unknown())?,
row(indexmap! {
"EC".into() => table(&[
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}),
@ -231,4 +270,12 @@ mod tests {
Ok(())
}
#[test]
fn examples_work_as_expected() {
use super::GroupBy;
use crate::examples::test as test_examples;
test_examples(GroupBy {})
}
}

View File

@ -0,0 +1,187 @@
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 GroupByDate;
#[derive(Deserialize)]
pub struct GroupByDateArgs {
column_name: Option<Tagged<String>>,
format: Option<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for GroupByDate {
fn name(&self) -> &str {
"group-by date"
}
fn signature(&self) -> Signature {
Signature::build("group-by date")
.optional(
"column_name",
SyntaxShape::String,
"the name of the column to group by",
)
.named(
"format",
SyntaxShape::String,
"Specify date and time formatting",
Some('f'),
)
}
fn usage(&self) -> &str {
"Creates a new table with the data from the table rows grouped by the column given."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
group_by_date(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Group files by type",
example: "ls | group-by date --format '%d/%m/%Y'",
result: None,
}]
}
}
enum Grouper {
ByDate(Option<Tagged<String>>),
}
enum GroupByColumn {
Name(Option<Tagged<String>>),
}
pub async fn group_by_date(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let (
GroupByDateArgs {
column_name,
format,
},
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 values = UntaggedValue::table(&values).into_value(&name);
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 {
Grouper::ByDate(None)
};
match (grouper_date, grouper_column) {
(Grouper::ByDate(None), GroupByColumn::Name(None)) => {
let block = Box::new(move |row: &Value| row.format("%Y-%b-%d"));
match crate::utils::data::group(&values, &Some(block), &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
}
(Grouper::ByDate(None), GroupByColumn::Name(Some(column_name))) => {
let block = Box::new(move |row: &Value| {
let group_key = match row.get_data_by_key(column_name.borrow_spanned()) {
Some(group_key) => Ok(group_key),
None => Err(suggestions(column_name.borrow_tagged(), &row)),
};
group_key?.format("%Y-%b-%d")
});
match crate::utils::data::group(&values, &Some(block), &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
}
(Grouper::ByDate(Some(fmt)), GroupByColumn::Name(None)) => {
let block = Box::new(move |row: &Value| row.format(&fmt));
match crate::utils::data::group(&values, &Some(block), &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
}
(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)
});
match crate::utils::data::group(&values, &Some(block), &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
}
}
}
}
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(),
)
}
}
#[cfg(test)]
mod tests {
use super::GroupByDate;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(GroupByDate {})
}
}

View File

@ -8,9 +8,8 @@ use nu_protocol::Dictionary;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
pub struct Headers;
#[derive(Deserialize)]
pub struct HeadersArgs {}
#[async_trait]
impl WholeStreamCommand for Headers {
fn name(&self) -> &str {
"headers"
@ -24,67 +23,92 @@ impl WholeStreamCommand for Headers {
"Use the first row of the table as column names"
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, headers)?.run()
headers(args, registry).await
}
fn examples(&self) -> &[Example] {
&[Example {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Create headers for a raw string",
example: "echo \"a b c|1 2 3\" | split-row \"|\" | split-column \" \" | headers",
example: r#"echo "a b c|1 2 3" | split row "|" | split column " " | headers"#,
result: None,
}]
}
}
pub fn headers(
HeadersArgs {}: HeadersArgs,
RunnableContext { input, .. }: RunnableContext,
pub async fn headers(
args: CommandArgs,
_registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let rows: Vec<Value> = input.collect().await;
let input = args.input;
let rows: Vec<Value> = input.collect().await;
if rows.len() < 1 {
yield Err(ShellError::untagged_runtime_error("Couldn't find headers, was the input a properly formatted, non-empty table?"));
}
if rows.is_empty() {
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
let headers: Vec<String> = match &rows[0].value {
UntaggedValue::Row(d) => {
Ok(d.entries.iter().map(|(k, v)| {
//the headers are the first row in the table
let headers: Vec<String> = match &rows[0].value {
UntaggedValue::Row(d) => {
Ok(d.entries
.iter()
.map(|(k, v)| {
match v.as_string() {
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) {
Some((index, _, _)) => format!("Column{}", index),
None => "unknownColumn".to_string()
None => "unknownColumn".to_string(),
}
}
}
}).collect())
}
_ => Err(ShellError::unexpected_eof("Could not get headers, is the table empty?", rows[0].tag.span))
}?;
})
.collect())
}
_ => 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
for r in rows.iter().skip(1) {
Ok(
futures::stream::iter(rows.into_iter().skip(1).map(move |r| {
//Each row is a dictionary with the headers as keys
match &r.value {
UntaggedValue::Row(d) => {
let mut i = 0;
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());
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,
)),
}
}
};
Ok(stream.to_output_stream())
}))
.to_output_stream(),
)
}
#[cfg(test)]
mod tests {
use super::Headers;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Headers {})
}
}

View File

@ -17,6 +17,7 @@ pub struct HelpArgs {
rest: Vec<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for Help {
fn name(&self) -> &str {
"help"
@ -30,75 +31,96 @@ impl WholeStreamCommand for Help {
"Display help information about commands."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, help)?.run()
help(args, registry).await
}
}
fn help(
HelpArgs { rest }: HelpArgs,
RunnableContext { registry, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
if let Some(document) = rest.get(0) {
let mut help = VecDeque::new();
if document.item == "commands" {
async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let (HelpArgs { rest }, ..) = args.process(&registry).await?;
if !rest.is_empty() {
if rest[0].item == "commands" {
let mut sorted_names = registry.names();
sorted_names.sort();
for cmd in sorted_names {
// If it's a subcommand, don't list it during the commands list
if cmd.contains(' ') {
continue;
}
let mut short_desc = TaggedDictBuilder::new(name.clone());
let document_tag = document.tag.clone();
let value = command_dict(
registry.get_command(&cmd).ok_or_else(|| {
ShellError::labeled_error(
format!("Could not load {}", cmd),
"could not load command",
document_tag,
)
})?,
name.clone(),
);
short_desc.insert_untagged("name", cmd);
short_desc.insert_untagged(
"description",
get_data_by_key(&value, "usage".spanned_unknown())
.ok_or_else(|| {
Ok(
futures::stream::iter(sorted_names.into_iter().filter_map(move |cmd| {
// If it's a subcommand, don't list it during the commands list
if cmd.contains(' ') {
return None;
}
let mut short_desc = TaggedDictBuilder::new(name.clone());
let document_tag = rest[0].tag.clone();
let value = command_dict(
match registry.get_command(&cmd).ok_or_else(|| {
ShellError::labeled_error(
"Expected a usage key",
"expected a 'usage' key",
&value.tag,
format!("Could not load {}", cmd),
"could not load command",
document_tag,
)
})?
.as_string()?,
);
}) {
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
},
name.clone(),
);
help.push_back(ReturnSuccess::value(short_desc.into_value()));
}
short_desc.insert_untagged("name", cmd);
short_desc.insert_untagged(
"description",
match match get_data_by_key(&value, "usage".spanned_unknown()).ok_or_else(
|| {
ShellError::labeled_error(
"Expected a usage key",
"expected a 'usage' key",
&value.tag,
)
},
) {
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
}
.as_string()
{
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
},
);
Some(ReturnSuccess::value(short_desc.into_value()))
}))
.to_output_stream(),
)
} else if rest.len() == 2 {
// Check for a subcommand
let command_name = format!("{} {}", rest[0].item, rest[1].item);
if let Some(command) = registry.get_command(&command_name) {
return Ok(get_help(command.stream_command(), &registry).into());
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(command.stream_command(), &registry))
.into_value(Tag::unknown()),
)))
} else {
Ok(OutputStream::empty())
}
} else if let Some(command) = registry.get_command(&document.item) {
return Ok(get_help(command.stream_command(), &registry).into());
} 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 {
return Err(ShellError::labeled_error(
Err(ShellError::labeled_error(
"Can't find command (use 'help commands' for full list)",
"can't find command",
document.tag.span,
));
rest[0].tag.span,
))
}
let help = futures::stream::iter(help);
Ok(help.to_output_stream())
} else {
let msg = r#"Welcome to Nushell.
@ -122,22 +144,16 @@ Get the processes on your system actively using CPU:
You can also learn more at https://www.nushell.sh/book/"#;
let output_stream = futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::string(msg).into_value(name),
)]);
Ok(output_stream.to_output_stream())
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(msg).into_value(Tag::unknown()),
)))
}
}
#[allow(clippy::cognitive_complexity)]
pub(crate) fn get_help(
cmd: &dyn WholeStreamCommand,
registry: &CommandRegistry,
) -> impl Into<OutputStream> {
pub fn get_help(cmd: &dyn WholeStreamCommand, registry: &CommandRegistry) -> String {
let cmd_name = cmd.name();
let signature = cmd.signature();
let mut help = VecDeque::new();
let mut long_desc = String::new();
long_desc.push_str(&cmd.usage());
@ -188,8 +204,8 @@ pub(crate) fn get_help(
if !signature.positional.is_empty() || signature.rest_positional.is_some() {
long_desc.push_str("\nParameters:\n");
for positional in signature.positional {
match positional.0 {
for positional in &signature.positional {
match &positional.0 {
PositionalType::Mandatory(name, _m) => {
long_desc.push_str(&format!(" <{}> {}\n", name, positional.1));
}
@ -199,77 +215,15 @@ pub(crate) fn get_help(
}
}
if let Some(rest_positional) = signature.rest_positional {
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("\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.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:");
@ -279,14 +233,92 @@ pub(crate) fn get_help(
long_desc.push_str(" ");
long_desc.push_str(example.description);
let colored_example =
crate::shell::helper::Painter::paint_string(example.example, registry);
crate::shell::helper::Painter::paint_string(example.example, registry, &palette);
long_desc.push_str(&format!("\n > {}\n", colored_example));
}
long_desc.push_str("\n");
help.push_back(ReturnSuccess::value(
UntaggedValue::string(long_desc).into_value(Tag::from((0, cmd_name.len(), None))),
));
help
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)]
mod tests {
use super::Help;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Help {})
}
}

View File

@ -17,6 +17,7 @@ pub struct HistogramArgs {
rest: Vec<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for Histogram {
fn name(&self) -> &str {
"histogram"
@ -39,91 +40,166 @@ impl WholeStreamCommand for Histogram {
"Creates a new table with a histogram based on the column name passed in."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, histogram)?.run()
histogram(args, registry).await
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get a histogram for the types of files",
example: "ls | histogram type",
result: None,
},
Example {
description:
"Get a histogram for the types of files, with frequency column named count",
example: "ls | histogram type count",
result: None,
},
Example {
description: "Get a histogram for a list of numbers",
example: "echo [1 2 3 1 2 3 1 1 1 1 3 2 1 1 3] | wrap | histogram Column",
example: "echo [1 2 3 1 1 1 2 2 1 1] | histogram",
result: None,
},
]
}
}
pub fn histogram(
HistogramArgs { column_name, rest }: HistogramArgs,
RunnableContext { input, name, .. }: RunnableContext,
pub async fn histogram(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let values: Vec<Value> = input.collect().await;
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
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 group_labels = columns_sorted(Some(group_by.clone()), &groups, &name);
let sorted = t_sort(Some(group_by.clone()), None, &groups, &name)?;
let evaled = evaluate(&sorted, None, &name)?;
let reduced = reduce(&evaled, None, &name)?;
let maxima = map_max(&reduced, None, &name)?;
let percents = percentages(&reduced, maxima, &name)?;
let groups = group(&Some(column_name.clone()), &values, &name)?;
let group_labels = columns_sorted(Some(column_name.clone()), &groups, &name);
let sorted = t_sort(Some(column_name.clone()), None, &groups, &name)?;
let evaled = evaluate(&sorted, None, &name)?;
let reduced = reduce(&evaled, None, &name)?;
let maxima = map_max(&reduced, None, &name)?;
let percents = percentages(&reduced, maxima, &name)?;
match percents {
Value {
value: UntaggedValue::Table(datasets),
match percents {
Value {
value: UntaggedValue::Table(datasets),
..
} => {
let mut idx = 0;
let column_names_supplied: Vec<_> = rest.iter().map(|f| f.item.clone()).collect();
let frequency_column_name = if column_names_supplied.is_empty() {
"frequency".to_string()
} else {
column_names_supplied[0].clone()
};
let column = (*column_name).clone();
let count_column_name = "count".to_string();
let count_shell_error = ShellError::labeled_error(
"Unable to load group count",
"unabled to load group count",
&name,
);
let mut count_values: Vec<u64> = Vec::new();
for table_entry in reduced.table_entries() {
match table_entry {
Value {
value: UntaggedValue::Table(list),
..
} => {
for i in list {
if let Ok(count) = i.value.clone().into_value(&name).as_u64() {
count_values.push(count);
} else {
return Err(count_shell_error);
}
}
}
_ => {
return Err(count_shell_error);
}
}
}
if let Value {
value: UntaggedValue::Table(start),
..
} => {
let mut idx = 0;
let column_names_supplied: Vec<_> = rest.iter().map(|f| f.item.clone()).collect();
let frequency_column_name = if column_names_supplied.is_empty() {
"frequency".to_string()
} else {
column_names_supplied[0].clone()
};
let column = (*column_name).clone();
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))? {
for percentage in start.iter() {
} = datasets.get(0).ok_or_else(|| {
ShellError::labeled_error(
"Unable to load dataset",
"unabled to load dataset",
&name,
)
})? {
let start = start.clone();
Ok(
futures::stream::iter(start.into_iter().map(move |percentage| {
let mut fact = TaggedDictBuilder::new(&name);
let value: Tagged<String> = group_labels.get(idx).ok_or_else(|| ShellError::labeled_error("Unable to load group labels", "unabled to load group labels", &name))?.clone();
fact.insert_value(&column, UntaggedValue::string(value.item).into_value(value.tag));
let value: Tagged<String> = group_labels
.get(idx)
.ok_or_else(|| {
ShellError::labeled_error(
"Unable to load group labels",
"unabled to load group labels",
&name,
)
})?
.clone();
fact.insert_value(
&column,
UntaggedValue::string(value.item).into_value(value.tag),
);
if let Value { value: UntaggedValue::Primitive(Primitive::Int(ref num)), ref tag } = percentage.clone() {
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));
fact.insert_untagged(
&count_column_name,
UntaggedValue::int(count_values[idx]),
);
if let Value {
value: UntaggedValue::Primitive(Primitive::Int(ref num)),
ref tag,
} = percentage
{
let string = std::iter::repeat("*")
.take(num.to_i32().ok_or_else(|| {
ShellError::labeled_error(
"Expected a number",
"expected a number",
tag,
)
})? as usize)
.collect::<String>();
fact.insert_untagged(
&frequency_column_name,
UntaggedValue::string(string),
);
}
idx += 1;
yield ReturnSuccess::value(fact.into_value());
}
}
ReturnSuccess::value(fact.into_value())
}))
.to_output_stream(),
)
} else {
Ok(OutputStream::empty())
}
_ => {}
}
};
Ok(stream.to_output_stream())
_ => Ok(OutputStream::empty()),
}
}
fn percentages(values: &Value, max: Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
@ -176,3 +252,15 @@ fn percentages(values: &Value, max: Value, tag: impl Into<Tag>) -> Result<Value,
Ok(results)
}
#[cfg(test)]
mod tests {
use super::Histogram;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Histogram {})
}
}

View File

@ -8,9 +8,7 @@ use std::io::{BufRead, BufReader};
pub struct History;
#[derive(Deserialize)]
pub struct HistoryArgs {}
#[async_trait]
impl WholeStreamCommand for History {
fn name(&self) -> &str {
"history"
@ -24,32 +22,46 @@ impl WholeStreamCommand for History {
"Display command history."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, history)?.run()
history(args, registry)
}
}
fn history(
_: HistoryArgs,
RunnableContext { name: tag, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let history_path = HistoryFile::path();
let file = File::open(history_path);
if let Ok(file) = file {
let reader = BufReader::new(file);
for line in reader.lines() {
if let Ok(line) = line {
yield ReturnSuccess::value(UntaggedValue::string(line).into_value(tag.clone()));
}
}
} else {
yield Err(ShellError::labeled_error("Could not open history", "history file could not be opened", tag.clone()));
}
};
Ok(stream.to_output_stream())
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag;
let history_path = HistoryFile::path();
let file = File::open(history_path);
if let Ok(file) = file {
let reader = BufReader::new(file);
let output = reader.lines().filter_map(move |line| match line {
Ok(line) => Some(ReturnSuccess::value(
UntaggedValue::string(line).into_value(tag.clone()),
)),
Err(_) => None,
});
Ok(futures::stream::iter(output).to_output_stream())
} else {
Err(ShellError::labeled_error(
"Could not open history",
"history file could not be opened",
tag,
))
}
}
#[cfg(test)]
mod tests {
use super::History;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(History {})
}
}

View File

@ -13,6 +13,7 @@ pub struct InsertArgs {
value: Value,
}
#[async_trait]
impl WholeStreamCommand for Insert {
fn name(&self) -> &str {
"insert"
@ -36,42 +37,47 @@ impl WholeStreamCommand for Insert {
"Insert a new column with a given value."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, insert)?.run()
insert(args, registry).await
}
}
fn insert(
InsertArgs { column, value }: InsertArgs,
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let mut input = input;
async fn insert(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
match input.next().await {
Some(obj @ Value {
let (InsertArgs { column, value }, input) = args.process(&registry).await?;
Ok(input
.map(move |row| match row {
Value {
value: UntaggedValue::Row(_),
..
}) => match obj.insert_data_at_column_path(&column, value.clone()) {
Ok(v) => yield Ok(ReturnSuccess::Value(v)),
Err(err) => yield Err(err),
} => match row.insert_data_at_column_path(&column, value.clone()) {
Ok(v) => Ok(ReturnSuccess::Value(v)),
Err(err) => Err(err),
},
Some(Value { tag, ..}) => {
yield Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
tag,
));
}
None => {}
};
};
Ok(stream.to_output_stream())
Value { tag, .. } => Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
tag,
)),
})
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Insert;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Insert {})
}
}

View File

@ -20,6 +20,7 @@ pub struct IsEmptyArgs {
rest: Vec<Value>,
}
#[async_trait]
impl WholeStreamCommand for IsEmpty {
fn name(&self) -> &str {
"empty?"
@ -36,19 +37,22 @@ impl WholeStreamCommand for IsEmpty {
"Checks emptiness. The last value is the replacement value for any empty column(s) given to check against the table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, is_empty)?.run()
is_empty(args, registry).await
}
}
fn is_empty(
IsEmptyArgs { rest }: IsEmptyArgs,
RunnableContext { input, .. }: RunnableContext,
async fn is_empty(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (IsEmptyArgs { rest }, input) = args.process(&registry).await?;
Ok(input
.map(move |value| {
let value_tag = value.tag();
@ -92,8 +96,7 @@ fn is_empty(
let mut out = value;
for field in fields.iter() {
let val =
out.get_data_by_column_path(&field, Box::new(move |(_, _, err)| err))?;
let val = crate::commands::get::get_column_path(&field, &out)?;
let emptiness_value = match out {
obj
@ -128,8 +131,7 @@ fn is_empty(
Ok(ReturnSuccess::Value(out))
}
IsEmptyFor::RowWithField(field) => {
let val =
value.get_data_by_column_path(&field, Box::new(move |(_, _, err)| err))?;
let val = crate::commands::get::get_column_path(&field, &value)?;
match &value {
obj
@ -162,8 +164,7 @@ fn is_empty(
}
}
IsEmptyFor::RowWithFieldAndFallback(field, default) => {
let val =
value.get_data_by_column_path(&field, Box::new(move |(_, _, err)| err))?;
let val = crate::commands::get::get_column_path(&field, &value)?;
match &value {
obj
@ -196,3 +197,15 @@ fn is_empty(
})
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::IsEmpty;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(IsEmpty {})
}
}

View File

@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape};
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
pub struct Keep;
@ -12,6 +12,7 @@ pub struct KeepArgs {
rows: Option<Tagged<usize>>,
}
#[async_trait]
impl WholeStreamCommand for Keep {
fn name(&self) -> &str {
"keep"
@ -29,34 +30,55 @@ impl WholeStreamCommand for Keep {
"Keep the number of rows only"
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, keep)?.run()
keep(args, registry).await
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Keep the first row",
example: "ls | keep",
example: "echo [1 2 3] | keep",
result: Some(vec![UntaggedValue::int(1).into()]),
},
Example {
description: "Keep the first four rows",
example: "ls | keep 4",
example: "echo [1 2 3 4 5] | keep 4",
result: Some(vec![
UntaggedValue::int(1).into(),
UntaggedValue::int(2).into(),
UntaggedValue::int(3).into(),
UntaggedValue::int(4).into(),
]),
},
]
}
}
fn keep(KeepArgs { rows }: KeepArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
async fn keep(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (KeepArgs { rows }, input) = args.process(&registry).await?;
let rows_desired = if let Some(quantity) = rows {
*quantity
} else {
1
};
Ok(OutputStream::from_input(context.input.take(rows_desired)))
Ok(input.take(rows_desired).to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Keep;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Keep {})
}
}

View File

@ -7,6 +7,7 @@ use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue,
pub struct KeepUntil;
#[async_trait]
impl WholeStreamCommand for KeepUntil {
fn name(&self) -> &str {
"keep-until"
@ -26,18 +27,19 @@ impl WholeStreamCommand for KeepUntil {
"Keeps rows until the condition matches."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = args.call_info.scope.clone();
let call_info = args.evaluate_once(&registry)?;
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 = match block {
let condition = Arc::new(match block {
Value {
value: UntaggedValue::Block(block),
tag,
@ -57,7 +59,7 @@ impl WholeStreamCommand for KeepUntil {
"Expected a condition",
"expected a condition",
tag,
))
));
}
},
None => {
@ -76,23 +78,46 @@ impl WholeStreamCommand for KeepUntil {
tag,
));
}
};
let objects = call_info.input.take_while(move |item| {
let condition = condition.clone();
trace!("ITEM = {:?}", item);
let result =
evaluate_baseline_expr(&*condition, &registry, &scope.clone().set_it(item.clone()));
trace!("RESULT = {:?}", result);
let return_value = match result {
Ok(ref v) if v.is_true() => false,
_ => true,
};
futures::future::ready(return_value)
});
Ok(objects.from_input_stream())
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);
match result {
Ok(ref v) if v.is_true() => false,
_ => true,
}
}
})
.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

@ -7,6 +7,7 @@ use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue,
pub struct KeepWhile;
#[async_trait]
impl WholeStreamCommand for KeepWhile {
fn name(&self) -> &str {
"keep-while"
@ -26,18 +27,18 @@ impl WholeStreamCommand for KeepWhile {
"Keeps rows while the condition matches."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = args.call_info.scope.clone();
let call_info = args.evaluate_once(&registry)?;
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 = match block {
let condition = Arc::new(match block {
Value {
value: UntaggedValue::Block(block),
tag,
@ -57,7 +58,7 @@ impl WholeStreamCommand for KeepWhile {
"Expected a condition",
"expected a condition",
tag,
))
));
}
},
None => {
@ -76,23 +77,47 @@ impl WholeStreamCommand for KeepWhile {
tag,
));
}
};
let objects = call_info.input.take_while(move |item| {
let condition = condition.clone();
trace!("ITEM = {:?}", item);
let result =
evaluate_baseline_expr(&*condition, &registry, &scope.clone().set_it(item.clone()));
trace!("RESULT = {:?}", result);
let return_value = match result {
Ok(ref v) if v.is_true() => true,
_ => false,
};
futures::future::ready(return_value)
});
Ok(objects.from_input_stream())
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);
match result {
Ok(ref v) if v.is_true() => true,
_ => false,
}
}
})
.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

@ -16,6 +16,7 @@ pub struct KillArgs {
pub quiet: Tagged<bool>,
}
#[async_trait]
impl WholeStreamCommand for Kill {
fn name(&self) -> &str {
"kill"
@ -37,37 +38,42 @@ impl WholeStreamCommand for Kill {
"Kill a process using the process id."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, kill)?.run()
kill(args, registry).await
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Kill the pid using the most memory",
example: "ps | sort-by mem | last | kill $it.pid",
result: None,
},
Example {
description: "Force kill a given pid",
example: "kill --force 12345",
result: None,
},
]
}
}
fn kill(
KillArgs {
pid,
rest,
force,
quiet,
}: KillArgs,
_context: RunnableContext,
) -> Result<OutputStream, ShellError> {
async fn kill(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (
KillArgs {
pid,
rest,
force,
quiet,
},
..,
) = args.process(&registry).await?;
let mut cmd = if cfg!(windows) {
let mut cmd = Command::new("taskkill");
@ -111,3 +117,15 @@ fn kill(
Ok(OutputStream::empty())
}
#[cfg(test)]
mod tests {
use super::Kill;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Kill {})
}
}

View File

@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct Last;
@ -12,6 +12,7 @@ pub struct LastArgs {
rows: Option<Tagged<u64>>,
}
#[async_trait]
impl WholeStreamCommand for Last {
fn name(&self) -> &str {
"last"
@ -29,46 +30,68 @@ impl WholeStreamCommand for Last {
"Show only the last number of rows."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, last)?.run()
last(args, registry).await
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get the last row",
example: "ls | last",
example: "echo [1 2 3] | last",
result: Some(vec![Value::from(UntaggedValue::from(BigInt::from(3)))]),
},
Example {
description: "Get the last three rows",
example: "ls | last 3",
example: "echo [1 2 3 4 5] | last 3",
result: Some(vec![
UntaggedValue::int(3).into(),
UntaggedValue::int(4).into(),
UntaggedValue::int(5).into(),
]),
},
]
}
}
fn last(LastArgs { rows }: LastArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let v: Vec<_> = context.input.into_vec().await;
async fn last(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (LastArgs { rows }, input) = args.process(&registry).await?;
let v: Vec<_> = input.into_vec().await;
let rows_desired = if let Some(quantity) = rows {
*quantity
} else {
1
};
let count = (rows_desired as usize);
if count < v.len() {
let k = v.len() - count;
for x in v[k..].iter() {
let y: Value = x.clone();
yield ReturnSuccess::value(y)
}
}
let rows_desired = if let Some(quantity) = rows {
*quantity
} else {
1
};
Ok(stream.to_output_stream())
let mut values_vec_deque = VecDeque::new();
let count = rows_desired as usize;
if count < v.len() {
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)]
mod tests {
use super::Last;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Last {})
}
}

View File

@ -5,6 +5,7 @@ use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
pub struct Lines;
#[async_trait]
impl WholeStreamCommand for Lines {
fn name(&self) -> &str {
"lines"
@ -18,18 +19,19 @@ impl WholeStreamCommand for Lines {
"Split single string into rows, one per line."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
lines(args, registry)
lines(args, registry).await
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Split output from an external command into lines",
example: "^ls -l | lines",
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Split multi-line string into lines",
example: r#"^echo "two\nlines" | lines"#,
result: None,
}]
}
}
@ -44,80 +46,131 @@ fn ends_with_line_ending(st: &str) -> bool {
}
}
fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
async fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let leftover = Arc::new(vec![]);
let leftover_string = Arc::new(String::new());
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag();
let name_span = tag.span;
let mut input = args.input;
let mut leftover = vec![];
let mut leftover_string = String::new();
let stream = async_stream! {
loop {
match input.next().await {
Some(Value { value: UntaggedValue::Primitive(Primitive::String(st)), ..}) => {
let mut st = leftover_string.clone() + &st;
leftover.clear();
let eos = futures::stream::iter(vec![
UntaggedValue::Primitive(Primitive::EndOfStream).into_untagged_value()
]);
Ok(args
.input
.chain(eos)
.map(move |item| {
let mut leftover = leftover.clone();
let mut leftover_string = leftover_string.clone();
match item {
Value {
value: UntaggedValue::Primitive(Primitive::String(st)),
..
} => {
let st = (&*leftover_string).clone() + &st;
if let Some(leftover) = Arc::get_mut(&mut leftover) {
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 {
if let Some(leftover_string) = Arc::get_mut(&mut leftover_string) {
leftover_string.clear();
leftover_string.push_str(&last);
}
} else if let Some(leftover_string) = Arc::get_mut(&mut leftover_string) {
leftover_string.clear();
}
} else {
} else if let Some(leftover_string) = Arc::get_mut(&mut leftover_string) {
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 { value: UntaggedValue::Primitive(Primitive::Line(st)), ..}) => {
let mut st = leftover_string.clone() + &st;
leftover.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::Line(st)),
..
} => {
let st = (&*leftover_string).clone() + &st;
if let Some(leftover) = Arc::get_mut(&mut leftover) {
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 {
if let Some(leftover_string) = Arc::get_mut(&mut leftover_string) {
leftover_string.clear();
leftover_string.push_str(&last);
}
} else if let Some(leftover_string) = Arc::get_mut(&mut leftover_string) {
leftover_string.clear();
}
} else {
} else if let Some(leftover_string) = Arc::get_mut(&mut leftover_string) {
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)
let success_lines: Vec<_> = lines
.iter()
.map(|x| ReturnSuccess::value(UntaggedValue::line(x).into_untagged_value()))
.collect();
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 => {
Value {
value: UntaggedValue::Primitive(Primitive::EndOfStream),
..
} => {
if !leftover.is_empty() {
let mut st = leftover_string.clone();
if let Ok(extra) = String::from_utf8(leftover) {
let mut st = (&*leftover_string).clone();
if let Ok(extra) = String::from_utf8((&*leftover).clone()) {
st.push_str(&extra);
}
yield futures::stream::iter(vec![ReturnSuccess::value(UntaggedValue::string(st).into_untagged_value())])
// futures::stream::iter(vec![ReturnSuccess::value(
// UntaggedValue::string(st).into_untagged_value(),
// )])
if !st.is_empty() {
futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::string(&*leftover_string).into_untagged_value(),
)])
} else {
futures::stream::iter(vec![])
}
} else {
futures::stream::iter(vec![])
}
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() {
yield futures::stream::iter(vec![ReturnSuccess::value(UntaggedValue::string(leftover_string).into_untagged_value())]);
}
}
.flatten();
Ok(stream.to_output_stream())
})
.flatten()
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Lines;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Lines {})
}
}

View File

@ -20,6 +20,7 @@ pub struct LsArgs {
pub du: bool,
}
#[async_trait]
impl WholeStreamCommand for Ls {
fn name(&self) -> &str {
"ls"
@ -59,32 +60,47 @@ impl WholeStreamCommand for Ls {
"View the contents of the current or given path."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, ls)?.run()
let name = args.call_info.name_tag.clone();
let ctrl_c = args.ctrl_c.clone();
let shell_manager = args.shell_manager.clone();
let (args, _) = args.process(&registry).await?;
shell_manager.ls(args, name, ctrl_c)
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "List all files in the current directory",
example: "ls",
result: None,
},
Example {
description: "List all files in a subdirectory",
example: "ls subdir",
result: None,
},
Example {
description: "List all rust files",
example: "ls *.rs",
result: None,
},
]
}
}
fn ls(args: LsArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
context.shell_manager.ls(args, &context)
#[cfg(test)]
mod tests {
use super::Ls;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Ls {})
}
}

View File

@ -14,6 +14,7 @@ pub struct MapMaxByArgs {
column_name: Option<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for MapMaxBy {
fn name(&self) -> &str {
"map-max-by"
@ -32,43 +33,52 @@ impl WholeStreamCommand for MapMaxBy {
"Creates a new table with the data from the tables rows maxed by the column given."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, map_max_by)?.run()
map_max_by(args, registry).await
}
}
pub fn map_max_by(
MapMaxByArgs { column_name }: MapMaxByArgs,
RunnableContext { input, name, .. }: RunnableContext,
pub async fn map_max_by(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let values: Vec<Value> = input.collect().await;
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() {
yield Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
name
))
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
};
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) => yield ReturnSuccess::value(table_maxed),
Err(err) => yield Err(err)
}
match map_max(&values[0], map_by_column, name) {
Ok(table_maxed) => Ok(OutputStream::one(ReturnSuccess::value(table_maxed))),
Err(err) => Err(err),
}
};
Ok(stream.to_output_stream())
}
}
#[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::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::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,189 @@
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, sum::summation,
utils::calculate, utils::MathFunction,
};
use nu_plugin::row;
use nu_plugin::test_helpers::value::{decimal, int, table};
use nu_protocol::Value;
#[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(int(10)),
],
},
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(int(60)),
],
},
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(63)),
],
},
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(int(-15)),
],
},
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(-15)),
],
},
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() => int(10), "col2".to_owned() => int(26)]),
],
},
// 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, summation];
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,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)?;
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::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 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,16 @@
pub mod avg;
pub mod command;
pub mod max;
pub mod median;
pub mod min;
pub mod mode;
pub mod sum;
pub mod utils;
pub use avg::SubCommand as MathAverage;
pub use command::Command as Math;
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 sum::SubCommand as MathSummation;

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)?;
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,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,66 @@
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 {
match mf(&col_vals, &name) {
Ok(result) => {
column_totals.insert(col_name, result);
}
Err(err) => return Err(err),
}
}
Ok(UntaggedValue::Row(Dictionary {
entries: column_totals,
})
.into_untagged_value())
}
}

View File

@ -14,6 +14,7 @@ pub struct MergeArgs {
block: Block,
}
#[async_trait]
impl WholeStreamCommand for Merge {
fn name(&self) -> &str {
"merge"
@ -31,73 +32,88 @@ impl WholeStreamCommand for Merge {
"Merge a table."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
Ok(args.process_raw(registry, merge)?.run())
merge(args, registry).await
}
fn examples(&self) -> &[Example] {
&[Example {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Merge a 1-based index column with some ls output",
example: "ls | select name | keep 3 | merge { echo [1 2 3] | wrap index }",
result: None,
}]
}
}
fn merge(
merge_args: MergeArgs,
context: RunnableContext,
raw_args: RawCommandArgs,
async fn merge(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let block = merge_args.block;
let registry = context.registry.clone();
let mut input = context.input;
let registry = registry.clone();
let scope = raw_args.call_info.scope.clone();
let mut context = Context::from_raw(&raw_args, &registry);
let name_tag = raw_args.call_info.name_tag.clone();
let (merge_args, input): (MergeArgs, _) = raw_args.process(&registry).await?;
let block = merge_args.block;
let stream = async_stream! {
let table: Option<Vec<Value>> = match run_block(&block,
&mut context,
InputStream::empty(),
&scope).await {
Ok(mut stream) => Some(stream.drain_vec().await),
Err(err) => {
yield Err(err);
return;
}
};
let table = table.unwrap_or_else(|| vec![Value {
value: UntaggedValue::row(IndexMap::default()),
tag: raw_args.call_info.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;
let table: Option<Vec<Value>> = match run_block(
&block,
&mut context,
InputStream::empty(),
&scope.it,
&scope.vars,
&scope.env,
)
.await
{
Ok(mut stream) => Some(stream.drain_vec().await),
Err(err) => {
return Err(err);
}
};
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)]
mod tests {
use super::Merge;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Merge {})
}
}

View File

@ -11,38 +11,60 @@ pub struct Mkdir;
#[derive(Deserialize)]
pub struct MkdirArgs {
pub rest: Vec<Tagged<PathBuf>>,
#[serde(rename = "show-created-paths")]
pub show_created_paths: bool,
}
#[async_trait]
impl WholeStreamCommand for Mkdir {
fn name(&self) -> &str {
"mkdir"
}
fn signature(&self) -> Signature {
Signature::build("mkdir").rest(SyntaxShape::Path, "the name(s) of the path(s) to create")
Signature::build("mkdir")
.rest(SyntaxShape::Path, "the name(s) of the path(s) to create")
.switch("show-created-paths", "show the path(s) created.", Some('s'))
}
fn usage(&self) -> &str {
"Make directories, creates intermediary directories as required."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, mkdir)?.run()
mkdir(args, registry).await
}
fn examples(&self) -> &[Example] {
&[Example {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Make a directory named foo",
example: "mkdir foo",
result: None,
}]
}
}
fn mkdir(args: MkdirArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
let shell_manager = context.shell_manager.clone();
shell_manager.mkdir(args, &context)
async fn mkdir(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let shell_manager = args.shell_manager.clone();
let (args, _) = args.process(&registry).await?;
shell_manager.mkdir(args, name)
}
#[cfg(test)]
mod tests {
use super::Mkdir;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Mkdir {})
}
}

View File

@ -14,6 +14,7 @@ pub struct MoveArgs {
pub dst: Tagged<PathBuf>,
}
#[async_trait]
impl WholeStreamCommand for Move {
fn name(&self) -> &str {
"mv"
@ -37,33 +38,52 @@ impl WholeStreamCommand for Move {
"Move files or directories."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, mv)?.run()
mv(args, registry).await
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Rename a file",
example: "mv before.txt after.txt",
result: None,
},
Example {
description: "Move a file into a directory",
example: "mv test.txt my/subdirectory",
result: None,
},
Example {
description: "Move many files into a directory",
example: "mv *.txt my/subdirectory",
result: None,
},
]
}
}
fn mv(args: MoveArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
let shell_manager = context.shell_manager.clone();
shell_manager.mv(args, &context)
async fn mv(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let shell_manager = args.shell_manager.clone();
let (args, _) = args.process(&registry).await?;
shell_manager.mv(args, name)
}
#[cfg(test)]
mod tests {
use super::Move;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Move {})
}
}

View File

@ -5,6 +5,7 @@ use nu_protocol::{CommandAction, ReturnSuccess, Signature};
pub struct Next;
#[async_trait]
impl WholeStreamCommand for Next {
fn name(&self) -> &str {
"n"
@ -18,7 +19,7 @@ impl WholeStreamCommand for Next {
"Go to next shell."
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
@ -30,3 +31,15 @@ impl WholeStreamCommand for Next {
fn next(_args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::NextShell))].into())
}
#[cfg(test)]
mod tests {
use super::Next;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Next {})
}
}

View File

@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
use nu_source::Tagged;
#[derive(Deserialize)]
@ -13,6 +13,7 @@ struct NthArgs {
pub struct Nth;
#[async_trait]
impl WholeStreamCommand for Nth {
fn name(&self) -> &str {
"nth"
@ -32,57 +33,70 @@ impl WholeStreamCommand for Nth {
"Return only the selected rows"
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, nth)?.run()
nth(args, registry).await
}
fn examples(&self) -> &[Example] {
&[
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get the second row",
example: "echo [first second third] | get 1",
example: "echo [first second third] | nth 1",
result: Some(vec![Value::from("second")]),
},
Example {
description: "Get the first and third rows",
example: "echo [first second third] | get 0 2",
example: "echo [first second third] | nth 0 2",
result: Some(vec![Value::from("first"), Value::from("third")]),
},
]
}
}
fn nth(
NthArgs {
row_number,
rest: and_rows,
}: NthArgs,
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = input
async fn nth(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (
NthArgs {
row_number,
rest: and_rows,
},
input,
) = args.process(&registry).await?;
let row_numbers = vec![vec![row_number], and_rows]
.into_iter()
.flatten()
.collect::<Vec<Tagged<u64>>>();
Ok(input
.enumerate()
.map(move |(idx, item)| {
let row_number = vec![row_number.clone()];
let row_numbers = vec![&row_number, &and_rows]
.into_iter()
.flatten()
.collect::<Vec<&Tagged<u64>>>();
let mut result = VecDeque::new();
if row_numbers
.iter()
.any(|requested| requested.item == idx as u64)
{
result.push_back(ReturnSuccess::value(item));
}
futures::stream::iter(result)
.filter_map(move |(idx, item)| {
futures::future::ready(
if row_numbers
.iter()
.any(|requested| requested.item == idx as u64)
{
Some(ReturnSuccess::value(item))
} else {
None
},
)
})
.flatten();
Ok(stream.to_output_stream())
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Nth;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Nth {})
}
}

View File

@ -4,6 +4,12 @@ use nu_errors::ShellError;
use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::{AnchorLocation, Span, Tagged};
use std::path::{Path, PathBuf};
extern crate encoding_rs;
use encoding_rs::*;
use std::fs::File;
use std::io::BufWriter;
use std::io::Read;
use std::io::Write;
pub struct Open;
@ -11,8 +17,10 @@ pub struct Open;
pub struct OpenArgs {
path: Tagged<PathBuf>,
raw: Tagged<bool>,
encoding: Option<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for Open {
fn name(&self) -> &str {
"open"
@ -30,191 +38,371 @@ impl WholeStreamCommand for Open {
"load content as a string instead of a table",
Some('r'),
)
.named(
"encoding",
SyntaxShape::String,
"encoding to use to open file",
Some('e'),
)
}
fn usage(&self) -> &str {
"Load a file into a cell, convert to table if possible (avoid by appending '--raw')"
r#"Load a file into a cell, convert to table if possible (avoid by appending '--raw').
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"#
}
fn run(
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, open)?.run()
open(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Opens \"users.csv\" and creates a table from the data",
example: "open users.csv",
result: None,
},
Example {
description: "Opens file with iso-8859-1 encoding",
example: "open file.csv --encoding iso-8859-1 | from csv",
result: None,
},
]
}
}
fn open(
OpenArgs { path, raw }: OpenArgs,
RunnableContext { shell_manager, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let cwd = PathBuf::from(shell_manager.path());
pub fn get_encoding(opt: Option<String>) -> &'static Encoding {
match opt {
None => UTF_8,
Some(label) => match Encoding::for_label((&label).as_bytes()) {
None => {
//print!("{} is not a known encoding label. Trying UTF-8.", label);
//std::process::exit(-2);
get_encoding(Some("utf-8".to_string()))
}
Some(encoding) => encoding,
},
}
}
async fn open(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let cwd = PathBuf::from(args.shell_manager.path());
let full_path = cwd;
let registry = registry.clone();
let stream = async_stream! {
let (
OpenArgs {
path,
raw,
encoding,
},
_,
) = args.process(&registry).await?;
let enc = match encoding {
Some(e) => e.to_string(),
_ => "".to_string(),
};
let result = fetch(&full_path, &path.item, path.tag.span, enc).await;
let result = fetch(&full_path, &path.item, path.tag.span).await;
let (file_extension, contents, contents_tag) = result?;
if let Err(e) = result {
yield Err(e);
return;
}
let (file_extension, contents, contents_tag) = result?;
let file_extension = if raw.item {
None
} else {
// If the extension could not be determined via mimetype, try to use the path
// extension. Some file types do not declare their mimetypes (such as bson files).
file_extension.or(path.extension().map(|x| x.to_string_lossy().to_string()))
};
let tagged_contents = contents.into_value(&contents_tag);
if let Some(extension) = file_extension {
yield Ok(ReturnSuccess::Action(CommandAction::AutoConvert(tagged_contents, extension)))
} else {
yield ReturnSuccess::value(tagged_contents);
}
let file_extension = if raw.item {
None
} else {
// If the extension could not be determined via mimetype, try to use the path
// extension. Some file types do not declare their mimetypes (such as bson files).
file_extension.or_else(|| path.extension().map(|x| x.to_string_lossy().to_string()))
};
Ok(stream.to_output_stream())
let tagged_contents = contents.into_value(&contents_tag);
if let Some(extension) = file_extension {
Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::AutoConvert(tagged_contents, extension),
)))
} else {
Ok(OutputStream::one(ReturnSuccess::value(tagged_contents)))
}
}
pub async fn fetch(
cwd: &PathBuf,
location: &PathBuf,
span: Span,
encoding: String,
) -> Result<(Option<String>, UntaggedValue, Tag), ShellError> {
let mut cwd = cwd.clone();
let output_encoding: &Encoding = get_encoding(Some("utf-8".to_string()));
let input_encoding: &Encoding = get_encoding(Some(encoding.clone()));
let mut decoder = input_encoding.new_decoder();
let mut encoder = output_encoding.new_encoder();
let mut _file: File;
let buf = Vec::new();
let mut bufwriter = BufWriter::new(buf);
cwd.push(Path::new(location));
if let Ok(cwd) = dunce::canonicalize(cwd) {
match std::fs::read(&cwd) {
Ok(bytes) => match std::str::from_utf8(&bytes) {
Ok(s) => Ok((
cwd.extension()
.map(|name| name.to_string_lossy().to_string()),
UntaggedValue::string(s),
Tag {
span,
anchor: Some(AnchorLocation::File(cwd.to_string_lossy().to_string())),
},
)),
Err(_) => {
//Non utf8 data.
match (bytes.get(0), bytes.get(1)) {
(Some(x), Some(y)) if *x == 0xff && *y == 0xfe => {
// Possibly UTF-16 little endian
let utf16 = read_le_u16(&bytes[2..]);
if let Some(utf16) = utf16 {
match std::string::String::from_utf16(&utf16) {
Ok(s) => Ok((
cwd.extension()
.map(|name| name.to_string_lossy().to_string()),
UntaggedValue::string(s),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
)),
Err(_) => Ok((
None,
UntaggedValue::binary(bytes),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
)),
}
} else {
Ok((
None,
UntaggedValue::binary(bytes),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
))
}
}
(Some(x), Some(y)) if *x == 0xfe && *y == 0xff => {
// Possibly UTF-16 big endian
let utf16 = read_be_u16(&bytes[2..]);
if let Some(utf16) = utf16 {
match std::string::String::from_utf16(&utf16) {
Ok(s) => Ok((
cwd.extension()
.map(|name| name.to_string_lossy().to_string()),
UntaggedValue::string(s),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
)),
Err(_) => Ok((
None,
UntaggedValue::binary(bytes),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
)),
}
} else {
Ok((
None,
UntaggedValue::binary(bytes),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
))
}
}
_ => Ok((
None,
UntaggedValue::binary(bytes),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
)),
}
if let Ok(cwd) = dunce::canonicalize(&cwd) {
if !encoding.is_empty() {
// use the encoding string
match File::open(&Path::new(&cwd)) {
Ok(mut _file) => {
convert_via_utf8(
&mut decoder,
&mut encoder,
&mut _file,
&mut bufwriter,
false,
);
//bufwriter.flush()?;
Ok((
cwd.extension()
.map(|name| name.to_string_lossy().to_string()),
UntaggedValue::string(String::from_utf8_lossy(&bufwriter.buffer())),
Tag {
span,
anchor: Some(AnchorLocation::File(cwd.to_string_lossy().to_string())),
},
))
}
},
Err(_) => Err(ShellError::labeled_error(
"File could not be opened",
"file not found",
span,
)),
Err(_) => Err(ShellError::labeled_error(
format!("Cannot open {:?} for reading.", &cwd),
"file not found",
span,
)),
}
} else {
// Do the old stuff
match std::fs::read(&cwd) {
Ok(bytes) => match std::str::from_utf8(&bytes) {
Ok(s) => Ok((
cwd.extension()
.map(|name| name.to_string_lossy().to_string()),
UntaggedValue::string(s),
Tag {
span,
anchor: Some(AnchorLocation::File(cwd.to_string_lossy().to_string())),
},
)),
Err(_) => {
//Non utf8 data.
match (bytes.get(0), bytes.get(1)) {
(Some(x), Some(y)) if *x == 0xff && *y == 0xfe => {
// Possibly UTF-16 little endian
let utf16 = read_le_u16(&bytes[2..]);
if let Some(utf16) = utf16 {
match std::string::String::from_utf16(&utf16) {
Ok(s) => Ok((
cwd.extension()
.map(|name| name.to_string_lossy().to_string()),
UntaggedValue::string(s),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
)),
Err(_) => Ok((
None,
UntaggedValue::binary(bytes),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
)),
}
} else {
Ok((
None,
UntaggedValue::binary(bytes),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
))
}
}
(Some(x), Some(y)) if *x == 0xfe && *y == 0xff => {
// Possibly UTF-16 big endian
let utf16 = read_be_u16(&bytes[2..]);
if let Some(utf16) = utf16 {
match std::string::String::from_utf16(&utf16) {
Ok(s) => Ok((
cwd.extension()
.map(|name| name.to_string_lossy().to_string()),
UntaggedValue::string(s),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
)),
Err(_) => Ok((
None,
UntaggedValue::binary(bytes),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
)),
}
} else {
Ok((
None,
UntaggedValue::binary(bytes),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
))
}
}
_ => Ok((
None,
UntaggedValue::binary(bytes),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
)),
}
}
},
Err(_) => Err(ShellError::labeled_error(
format!("Cannot open {:?} for reading.", &cwd),
"file not found",
span,
)),
}
}
} else {
Err(ShellError::labeled_error(
"File could not be opened",
format!("Cannot open {:?} for reading.", &cwd),
"file not found",
span,
))
}
}
fn convert_via_utf8(
decoder: &mut Decoder,
encoder: &mut Encoder,
read: &mut dyn Read,
write: &mut dyn Write,
last: bool,
) {
let mut input_buffer = [0u8; 2048];
let mut intermediate_buffer_bytes = [0u8; 4096];
// Is there a safe way to create a stack-allocated &mut str?
let mut intermediate_buffer: &mut str =
//unsafe { std::mem::transmute(&mut intermediate_buffer_bytes[..]) };
std::str::from_utf8_mut(&mut intermediate_buffer_bytes[..]).expect("error with from_utf8_mut");
let mut output_buffer = [0u8; 4096];
let mut current_input_ended = false;
while !current_input_ended {
match read.read(&mut input_buffer) {
Err(_) => {
print!("Error reading input.");
//std::process::exit(-5);
}
Ok(decoder_input_end) => {
current_input_ended = decoder_input_end == 0;
let input_ended = last && current_input_ended;
let mut decoder_input_start = 0usize;
loop {
let (decoder_result, decoder_read, decoder_written, _) = decoder.decode_to_str(
&input_buffer[decoder_input_start..decoder_input_end],
&mut intermediate_buffer,
input_ended,
);
decoder_input_start += decoder_read;
let last_output = if input_ended {
match decoder_result {
CoderResult::InputEmpty => true,
CoderResult::OutputFull => false,
}
} else {
false
};
// Regardless of whether the intermediate buffer got full
// or the input buffer was exhausted, let's process what's
// in the intermediate buffer.
if encoder.encoding() == UTF_8 {
// If the target is UTF-8, optimize out the encoder.
if write
.write_all(&intermediate_buffer.as_bytes()[..decoder_written])
.is_err()
{
print!("Error writing output.");
//std::process::exit(-7);
}
} else {
let mut encoder_input_start = 0usize;
loop {
let (encoder_result, encoder_read, encoder_written, _) = encoder
.encode_from_utf8(
&intermediate_buffer[encoder_input_start..decoder_written],
&mut output_buffer,
last_output,
);
encoder_input_start += encoder_read;
if write.write_all(&output_buffer[..encoder_written]).is_err() {
print!("Error writing output.");
//std::process::exit(-6);
}
match encoder_result {
CoderResult::InputEmpty => {
break;
}
CoderResult::OutputFull => {
continue;
}
}
}
}
// Now let's see if we should read again or process the
// rest of the current input buffer.
match decoder_result {
CoderResult::InputEmpty => {
break;
}
CoderResult::OutputFull => {
continue;
}
}
}
}
}
}
}
fn read_le_u16(input: &[u8]) -> Option<Vec<u16>> {
if input.len() % 2 != 0 || input.len() < 2 {
None
@ -244,3 +432,15 @@ fn read_be_u16(input: &[u8]) -> Option<Vec<u16>> {
Some(result)
}
}
#[cfg(test)]
mod tests {
use super::Open;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Open {})
}
}

View File

@ -1,167 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue};
use nu_source::Tagged;
use regex::Regex;
#[derive(Debug)]
enum ParseCommand {
Text(String),
Column(String),
}
fn parse(input: &str) -> Vec<ParseCommand> {
let mut output = vec![];
//let mut loop_input = input;
let mut loop_input = input.chars();
loop {
let mut before = String::new();
while let Some(c) = loop_input.next() {
if c == '{' {
break;
}
before.push(c);
}
if !before.is_empty() {
output.push(ParseCommand::Text(before.to_string()));
}
// Look for column as we're now at one
let mut column = String::new();
while let Some(c) = loop_input.next() {
if c == '}' {
break;
}
column.push(c);
}
if !column.is_empty() {
output.push(ParseCommand::Column(column.to_string()));
}
if before.is_empty() && column.is_empty() {
break;
}
}
output
}
fn column_names(commands: &[ParseCommand]) -> Vec<String> {
let mut output = vec![];
for command in commands {
if let ParseCommand::Column(c) = command {
output.push(c.clone());
}
}
output
}
fn build_regex(commands: &[ParseCommand]) -> String {
let mut output = String::new();
for command in commands {
match command {
ParseCommand::Text(s) => {
output.push_str(&s.replace("(", "\\("));
}
ParseCommand::Column(_) => {
output.push_str("(.*)");
}
}
}
output
}
pub struct Parse;
#[derive(Deserialize)]
pub struct ParseArgs {
pattern: Tagged<String>,
}
impl WholeStreamCommand for Parse {
fn name(&self) -> &str {
"parse"
}
fn signature(&self) -> Signature {
Signature::build("parse").required(
"pattern",
SyntaxShape::String,
"the pattern to match. Eg) \"{foo}: {bar}\"",
)
}
fn usage(&self) -> &str {
"Parse columns from string data using a simple pattern."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, parse_command)?.run()
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Parse values from a string into a table",
example: r#"echo "data: 123" | parse "{key}: {value}""#,
}]
}
}
fn parse_command(
ParseArgs { pattern }: ParseArgs,
RunnableContext { name, input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let parse_pattern = parse(&pattern.item);
let parse_regex = build_regex(&parse_pattern);
let column_names = column_names(&parse_pattern);
let name = name.span;
let regex = Regex::new(&parse_regex).map_err(|_| {
ShellError::labeled_error(
"Could not parse regex",
"could not parse regex",
&pattern.tag,
)
})?;
Ok(input
.map(move |value| {
if let Ok(s) = value.as_string() {
let mut output = vec![];
for cap in regex.captures_iter(&s) {
let mut dict = TaggedDictBuilder::new(value.tag());
for (idx, column_name) in column_names.iter().enumerate() {
dict.insert_untagged(
column_name,
UntaggedValue::string(cap[idx + 1].to_string()),
);
}
output.push(Ok(ReturnSuccess::Value(dict.into_value())));
}
output
} else {
vec![Err(ShellError::labeled_error_with_secondary(
"Expected string input",
"expected string input",
name,
"value originated here",
value.tag,
))]
}
})
.map(futures::stream::iter)
.flatten()
.to_output_stream())
}

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