Compare commits

...

189 Commits

Author SHA1 Message Date
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
b2eecfb110 Bump to 0.14 (#1766) 2020-05-13 04:32:51 +12:00
b0aa142542 Add examples for some more commands (#1765) 2020-05-13 03:54:29 +12:00
247d8b00f8 doc: fix prepend example definition (#1761)
It seems that the description was copy-pasted by mistake from the
append command.
2020-05-12 19:46:21 +12:00
0b520eeaf0 Add a batch of help examples (#1759) 2020-05-12 17:17:17 +12:00
c3535b5c67 It-expansion fixes (#1757)
* It-expansion fixes

* fix clippy
2020-05-12 15:58:16 +12:00
8b9a8daa1d Add a batch of help examples (#1755) 2020-05-12 13:00:55 +12:00
c5ea4a31bd Adding coloring to help examples (#1754) 2020-05-12 11:06:40 +12:00
2275575575 Improve list view and ranges (#1753) 2020-05-12 08:06:09 +12:00
c3a066eeb4 Add examples to commands (#1752)
* Pass &dyn WholeStreamCommand to get_help

* Add an optional example to the WholeStreamCommand trait

* Add an example to the alias command
2020-05-12 08:05:44 +12:00
42eb658c37 Add a simplified list view (#1749) 2020-05-11 14:47:27 +12:00
a2e9bbd358 Improve duration math and printing (#1748)
* Improve duration math and printing

* Fix test
2020-05-11 13:44:49 +12:00
8951a01e58 update cal documentation (#1746) 2020-05-11 13:19:14 +12:00
f702aae72f Don't include year and month by default, adds an option to display th… (#1745)
* Don't include year and month by default, adds an option to display the quarters of the year

* Add a test for cal that checks that year requested is in the return

* rustfmt the cal tests
2020-05-11 12:35:24 +12:00
f5e03aaf1c Add cal command (#1739)
* Add cal command

* Fix docmentation to show commands on two lines

* Use bullet points on flag documentation for cal

* Dereference day before calling string method

* Silence Clippy warning regarding a function with too many arguments

* Update cal flag descriptions and documentation

* Add some tests for the cal command
2020-05-10 11:05:48 +12:00
0f0847e45b Move 'start' to use ShellError (#1743)
* Move 'start' to use ShellError

* Remove unnecessary changes

* Add missing macOS change

* Add default

* More fixed

* More fixed
2020-05-10 08:08:53 +12:00
ccd5d69fd1 Bug fix start (#1738)
* fix bug on linux; added start to the stable list

* add to stable and fix clippy lint
2020-05-10 05:28:57 +12:00
55374ee54f Fix help text for alias command. (#1742)
* Fix help text for alias command.

* Rust fmt
2020-05-09 12:16:14 -05:00
f93ff9ec33 Make grouping more flexible. (#1741) 2020-05-09 12:15:47 -05:00
9a94b3c656 start command in nushell (#1727)
* mvp for start command

* modified the signature of the start command

* parse filenames

* working model for macos is done

* refactored to read from pipes

* start command works well on macos; manual testing reveals need of --args flag support

* implemented start error; color printing of warning and errors

* ran clippy and fixed warnings

* fix a clippy lint that was caught in pipeline

* fix dead code clippy lint for windows

* add cfg annotation to import
2020-05-09 06:19:48 +12:00
e04b89f747 [Gitpod] Refactor Gitpod configuration and add full Debugging support (#1728) 2020-05-09 05:41:24 +12:00
180c1204f3 Use playground instead of depending on fixture format files. (#1726) 2020-05-07 06:58:35 -05:00
96e5fc05a3 Pick->Select rename. Integration tests changes. (#1725)
Pick->Select rename. Integration tests changes.
2020-05-07 06:03:43 -05:00
c3efdf2689 Rename edit command to update. (#1724)
Rename edit command to update.
2020-05-07 00:33:30 -05:00
27fdef5479 Read exit status before failing in failed read from stdout pipe (#1723) 2020-05-07 13:42:01 +12:00
7ce8026916 Ignore empty arguments passed to externals (#1722) 2020-05-07 09:18:56 +12:00
8a9fc6a721 Fix changing to a new Windows drive (#1721)
* Fix changing to a new Windows drive

* Update cli.rs
2020-05-07 05:51:03 +12:00
c06a692709 Bash-like shorthand with-env (#1718)
* Bash-like shorthand with-env

* fix clippy warning
2020-05-06 18:57:37 +12:00
b37e420c7c Add with-env command (#1717) 2020-05-06 15:56:31 +12:00
22e70478a4 docs/commands: add to.md, update subcommands (#1715)
This adds a top-level document for the new `to` command, with a list (of links) of all the subcommands.

All the to-* subcommands keep their filename, but the content is updated to use the new subcommand syntax.

Since not all subcommands have documentation, some items in the list are just text without a link. Also filled the list for the undocumented from* commands in the same style.

Fixes #1709
2020-05-05 20:05:23 +12:00
8ab2b92405 docs/commands: add from.md, update subcommands (#1712)
This adds a top-level document for the new `from` command, with a list of links of all the subcommands.

All the from-* subcommands keep their filename, but the content is updated to use the new subcommand syntax.

Needs matching update for to*

Ref #1709
2020-05-05 09:01:31 +12:00
3201c90647 Extend to/from usage text to indicate subcommands (#1711)
Both to and from without a subcommand only print the helptext. Expand the usage line a bit, so a glance at `help commands` indicates the existance of the subcommands and mentions some common formats.

Ref a9968046ed
Ref #1708
2020-05-05 09:00:29 +12:00
454f560eaa Properly deserialize history args (#1710) 2020-05-05 07:50:10 +12:00
d2ac506de3 Changes to allow plugins to be loaded in a multi-threaded manner (#1694)
* Changes to allow plugins to be loaded in a multi-threaded manner in order to decrease startup time.

* Ran rust fmt and clippy to find and fix first pass errors.
Updated launch.jason to make debugging easier in vscode.
Also added tasks.json so tasks like clippy can be ran easily.

* ran fmt again

* Delete launch.json

Remove IDE settings file

* Remove IDE settings file

* Ignore vscode IDE settings

* Cloned the context instead of Arc/Mutexing it.

Co-authored-by: Darren Schroeder <fdncred@hotmail.com>
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-05-05 06:15:24 +12:00
a9968046ed Add subcommands. Switch from-* and to-* to them (#1708) 2020-05-04 20:44:33 +12:00
453087248a Properly drain iterating pipe so we can see errors (#1707) 2020-05-04 15:29:32 +12:00
81ff598d6c Fix column bugs associated with previous refactoring (#1705)
* Fix: the symlink target column will only dispaly if either the `full` or `with_symlink_targets` options are given

* If the metadata for every item in the size column is None, do not show the size column

* Do not show the symlink target column if the metadata is None for all the items in the table
2020-05-04 14:58:11 +12:00
d7d487de73 Basic documentation for the wrap command (#1704) 2020-05-04 04:54:21 +12:00
8d69c77989 Display either dir metadata size or dir apparent size in ls (#1696)
* Show dir size in ls command

* Add the option to show the apparent directory size via `ls --du`
2020-05-03 17:09:17 +12:00
0779a46179 docs/commands: add alias.md (#1697)
* docs/commands: add alias.md

* docs/commands/alias: drop reference to bash
2020-05-03 16:49:27 +12:00
ada92f41b4 Parse file size (byte) units in all mixes of case (#1693) 2020-05-02 14:14:07 -04:00
ef3049f5a1 Reduce some repetitive code in files.rs (#1692) 2020-05-02 11:14:29 -04:00
1dab82ffa1 Gitignore JetBrains' IDE items (#1691) 2020-05-01 09:43:53 +12:00
e9e3fac59d Remove bin.is_file() because it's expensive (#1689)
This one change takes the startup time from 2.8 seconds to 1.2 seconds in my testing on Windows.
2020-05-01 06:43:59 +12:00
7d403a6cc7 Escape some symbols in external args (#1687)
* Escape some symbols in external args

* Don't escape on Windows, which does its own

* fix warning
2020-04-30 16:54:07 +12:00
cf53264438 Table operating commands. (#1686)
* Table operating commands.

* Updated merge test for clarity.

* More clarity.

* Better like this..
2020-04-29 23:18:24 -05:00
d834708be8 Finish remove the last traces of per-item (#1685) 2020-04-30 14:23:40 +12:00
f8b4784891 Add .DS_Store to .gitignore (#1684) 2020-04-30 13:12:18 +12:00
789b28ac8a Convert if expression to match (#1683) 2020-04-30 12:59:50 +12:00
db8219e798 extend it-expansion to externals (#1682)
* extend it-expansion to externals

* trim the carriage return for external strings
2020-04-30 07:09:14 +12:00
73d5310c9c make it-expansion work through blocks when necessary (#1681) 2020-04-29 19:51:46 +12:00
8d197e1b2f Show symlink sizes in ls (#1680)
* Show symlink sizes in ls

* Reduce redundancy in the size code of ls
2020-04-29 19:23:26 +12:00
c704157bc8 Replace ichwh with which and some fixes for auto-cd (canonicalization) (#1672)
* fix: absolutize path against its parent if it was a symlink.

On Linux this happens because Rust calls readlink but doesn't canonicalize the resultant path.

* feat: playground function to create symlinks

* fix: use playground dirs

* feat: test for #1631, shift tests names

* Creation of FilesystemShell with custom location may fail

* Replace ichwh with which

* Creation of FilesystemShell with custom location may fail

* Replace ichwh with which

* fix: add ichwh again since it cannot be completely replaced

* fix: replace one more use of which
2020-04-28 05:49:53 +12:00
6abb9181d5 bump to 0.13.1 (#1670) 2020-04-27 18:50:14 +12:00
006171d669 Fix per-item run_alias (#1669)
* Fix per-item run_alias

* Fix 1609 also
2020-04-27 18:10:34 +12:00
8bd3cedce1 It expansion (#1668)
* First step in it-expansion

* Fix tests

* fix clippy warnings
2020-04-27 14:04:54 +12:00
6f2ef05195 Resolves https://github.com/nushell/nushell/issues/1658 (#1660)
For example, when running the following:

    crates/nu-cli/src

nushell currently parses this as an external command. Before running the command, we check to see if
it's a directory. If it is, we "auto cd" into that directory, otherwise we go through normal
external processing.

If we put a trailing slash on it though, shells typically interpret that as "user is explicitly
referencing directory". So

    crates/nu-cli/src/

should not be interpreted as "run an external command". We intercept a trailing slash in the head
position of a command in a pipeline as such, and inject a `cd` internal command.
2020-04-27 13:22:01 +12:00
80025ea684 Rows and values can be checked for emptiness. Allows to set a value if desired. (#1665) 2020-04-26 12:30:52 -05:00
a62745eefb make trim apply to strings contained in tables (also at deeper nesting (#1664)
* make trim apply to strings contained in tables (also at deeper nesting
levels), not just top-level strings

* remove unnecessary clone (thanks clippy)
2020-04-27 05:26:02 +12:00
2ac501f88e Adds drop number of rows command (#1663)
* Fix access to columns with quoted names

* Add a drop number of rows from end of table
2020-04-26 18:34:45 +12:00
df90d9e4b6 Fix access to columns with quoted names (#1662) 2020-04-26 18:01:55 +12:00
ad7a3fd908 Add not-in: operator (#1661) 2020-04-26 17:32:17 +12:00
ad8ab5b04d Add from-eml command (#1656)
* from-eml initial ver

* Adding tests for `from-eml`

* Add eml to prepares_and_decorates_filesystem_source_files

* Sort the file order

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-26 16:26:35 +12:00
e7767ab7b3 suppress the parser error for an insufficient number of required arguments if the named keyword argument 'help' is given (#1659) 2020-04-26 04:45:45 +12:00
846a779516 Fix cd'ing to symlinked directories (#1651)
* fix: absolutize path against its parent if it was a symlink.

On Linux this happens because Rust calls readlink but doesn't canonicalize the resultant path.

* feat: playground function to create symlinks

* fix: use playground dirs

* feat: test for #1631, shift tests names
2020-04-25 18:09:00 +12:00
e7a4f31b38 Docs: Mention how to use str --find-replace in the docs. (#1653)
Co-authored-by: Christoph Siedentop <christoph@siedentop.name>
2020-04-25 18:07:38 +12:00
10768b6ecf str plugin can capitalize and trim strings. (#1652)
* Str plugin can capitalize.

* Str plugin can trim.
2020-04-24 16:37:58 -05:00
716c4def03 Add key_timeout config option (#1649) 2020-04-25 05:28:38 +12:00
0e510ad42b Values can be used as keys for grouping. (#1648) 2020-04-23 20:55:18 -05:00
3c222916c6 [docs]: How to run tests. (#1647)
Co-authored-by: Christoph Siedentop <christoph@siedentop.name>
2020-04-24 12:20:55 +12:00
6887554888 [docs/enter] Warn about enter opening multiple shells (#1645)
Opening a JSON with a top-level list, opens one shell per list element. This can be extremely confusing to unexpected users.
2020-04-24 12:17:11 +12:00
d7bd77829f Bump rustyline (#1644)
* Bump rustyline

* Fix new clippy warnings

* Add pipeline command
2020-04-24 08:00:49 +12:00
9e8434326d Update azure-pipelines.yml 2020-04-24 07:23:56 +12:00
27bff35c79 Update azure-pipelines.yml 2020-04-24 07:21:27 +12:00
e2fae63a42 Update azure-pipelines.yml 2020-04-24 07:04:10 +12:00
701711eada Be more resilient with startup lines (#1642) 2020-04-23 17:22:01 +12:00
9ec2aca86f Support completion for paths with multiple dots (#1640)
* refactor: expand_path and expand_ndots now work for any string.

* refactor: refactor test and add new ones.

* refactor: convert expanded to owned string

* feat: pub export of expand_ndots

* feat: add completion for ndots in fs-shell
2020-04-23 16:17:38 +12:00
818171cc2c Make default completion mode OS dependent (#1636) 2020-04-23 10:53:40 +12:00
b3c623396f Fix to-csv command (#1635)
* Fix --headerless option of to-csv and to-tsv

Before to-csv --headerless split the "headerfull" output on lines,
skipped the first, and then concatenated them together. That meant
that all remaining output would be put on the same line, without
any delimiter, making it unusable. Now we replace the range of the
first line with the empty string, maintaining the rest of the
output unchanged.

* Remove extra space in indentation of to_delimited_data

* Add --separator <string> argument to to-csv

This functionaliy has been present before, but wasn't exposed
to the command.
2020-04-23 05:08:53 +12:00
390 changed files with 17197 additions and 7789 deletions

View File

@ -37,6 +37,8 @@ steps:
fi fi
if [ "$(uname)" == "Darwin" ]; then if [ "$(uname)" == "Darwin" ]; then
curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain "stable" curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain "stable"
echo "Installing clippy"
rustup component add clippy --toolchain stable-x86_64-apple-darwin
export PATH=$HOME/.cargo/bin:$PATH export PATH=$HOME/.cargo/bin:$PATH
fi fi
rustup update rustup update
@ -44,13 +46,13 @@ steps:
echo "##vso[task.prependpath]$HOME/.cargo/bin" echo "##vso[task.prependpath]$HOME/.cargo/bin"
rustup component add rustfmt rustup component add rustfmt
displayName: Install Rust displayName: Install Rust
- bash: RUSTFLAGS="-D warnings" cargo test --all --features stable,test-bins - bash: RUSTFLAGS="-D warnings" cargo test --all --features stable
condition: eq(variables['style'], 'unflagged') condition: eq(variables['style'], 'unflagged')
displayName: Run tests displayName: Run tests
- bash: RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used - bash: RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
condition: eq(variables['style'], 'unflagged') condition: eq(variables['style'], 'unflagged')
displayName: Check clippy lints 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') condition: eq(variables['style'], 'canary')
displayName: Run tests displayName: Run tests
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used - bash: 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. If applicable, add screenshots to help explain your problem.
**Configuration (please complete the following information):** **Configuration (please complete the following information):**
- OS: [e.g. Windows] - OS (e.g. Windows):
- Version [e.g. 0.4.0] - Nu version (you can use the `version` command to find out):
- Optional features (if any) - Optional features (if any):
Add any other context about the problem here. Add any other context about the problem here.

View File

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

9
.gitignore vendored
View File

@ -11,3 +11,12 @@ debian/debhelper-build-stamp
debian/files debian/files
debian/nu.substvars debian/nu.substvars
debian/nu/ debian/nu/
# macOS junk
.DS_Store
# JetBrains' IDE items
.idea/*
# VSCode's IDE items
.vscode/*

7
.gitpod.Dockerfile vendored
View File

@ -6,4 +6,9 @@ RUN sudo apt-get update && \
sudo apt-get install -y \ sudo apt-get install -y \
libssl-dev \ libssl-dev \
libxcb-composite0-dev \ libxcb-composite0-dev \
pkg-config pkg-config \
libpython3.6 \
rust-lldb \
&& sudo rm -rf /var/lib/apt/lists/*
ENV RUST_LLDB=/usr/bin/lldb-8

View File

@ -1,23 +1,19 @@
image: image:
file: .gitpod.Dockerfile file: .gitpod.Dockerfile
tasks: tasks:
- init: cargo install --path . --force --features=stable - name: Clippy
init: cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
- name: Testing
init: cargo test --all --features=stable,test-bins
- name: Build
init: cargo build --features=stable
- name: Nu
init: cargo install --path . --features=stable
command: nu command: nu
github: github:
prebuilds: prebuilds:
# enable for the master/default branch (defaults to true)
master: true
# enable for all branches in this repo (defaults to false)
branches: true branches: true
# enable for pull requests coming from this repo (defaults to true)
pullRequests: true
# enable for pull requests coming from forks (defaults to false)
pullRequestsFromForks: true pullRequestsFromForks: true
# add a "Review in Gitpod" button as a comment to pull requests (defaults to true)
addComment: true
# add a "Review in Gitpod" button to pull requests (defaults to false)
addBadge: false
# add a label once the prebuild is ready to pull requests (defaults to false)
addLabel: prebuilt-in-gitpod addLabel: prebuilt-in-gitpod
vscode: vscode:
extensions: extensions:
@ -25,5 +21,5 @@ vscode:
- Swellaby.vscode-rust-test-adapter@0.11.0:Xg+YeZZQiVpVUsIkH+uiiw== - Swellaby.vscode-rust-test-adapter@0.11.0:Xg+YeZZQiVpVUsIkH+uiiw==
- serayuzgur.crates@0.4.7:HMkoguLcXp9M3ud7ac3eIw== - serayuzgur.crates@0.4.7:HMkoguLcXp9M3ud7ac3eIw==
- belfz.search-crates-io@1.2.1:kSLnyrOhXtYPjQpKnMr4eQ== - belfz.search-crates-io@1.2.1:kSLnyrOhXtYPjQpKnMr4eQ==
- vadimcn.vscode-lldb@1.4.5:lwHCNwtm0kmOBXeQUIPGMQ==
- bungcip.better-toml@0.3.2:3QfgGxxYtGHfJKQU7H0nEw== - bungcip.better-toml@0.3.2:3QfgGxxYtGHfJKQU7H0nEw==
- webfreak.debug@0.24.0:1zVcRsAhewYEX3/A9xjMNw==

14
.theia/launch.json Normal file
View File

@ -0,0 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "gdb",
"request": "launch",
"name": "Debug Rust Code",
"preLaunchTask": "cargo",
"target": "${workspaceFolder}/target/debug/nu",
"cwd": "${workspaceFolder}",
"valuesFormatting": "parseText"
}
]
}

12
.theia/tasks.json Normal file
View File

@ -0,0 +1,12 @@
{
"tasks": [
{
"command": "cargo",
"args": [
"build"
],
"type": "process",
"label": "cargo",
}
],
}

View File

@ -55,7 +55,7 @@ a project may be further defined and clarified by project maintainers.
## Enforcement ## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be 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 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 is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident. obligated to maintain confidentiality with regard to the reporter of an incident.

View File

@ -9,3 +9,50 @@ For speedy contributions open it in Gitpod, nu will be pre-installed with the la
To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)! To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)!
<!--WIP--> <!--WIP-->
# Developing
## Set up
This is no different than other Rust projects.
```shell
git clone https://github.com/nushell/nushell
cd nushell
cargo build
```
## Useful Commands
Build and run Nushell:
```shell
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
```

1097
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

117
README.md
View File

@ -44,7 +44,7 @@ Try it in Gitpod.
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/en/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support. Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/en/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
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: Required dependencies:
@ -144,19 +144,20 @@ Commands that work in the pipeline fit into one of three categories:
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right. Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
``` ```
/home/jonathan/Source/nushell(master)> ls | where type == "Directory" | autoview /home/jonathan/Source/nushell(master)> ls | where type == "Dir" | autoview
────┬───────────┬───────────┬──────────┬────────┬──────────────┬──────────────── ───────────┬────────────────────────────
# │ name │ type │ readonly │ size │ accessed │ modified # │ name │ type │ size │ modified
────┼───────────┼───────────┼──────────┼────────┼──────────────┼──────────────── ───────────┼────────────────────────────
0 │ .azure │ Directory │ │ 4.1 KB │ 2 months ago │ a day ago 0 │ assets │ Dir │ 4.1 KB │ 1 week ago
1 │ target │ Directory │ │ 4.1 KB │ 3 days ago │ 3 days ago 1 │ crates │ Dir │ 4.1 KB │ 4 days ago
2 │ images │ Directory │ │ 4.1 KB │ 2 months ago │ 2 weeks ago 2 │ debian │ Dir │ 4.1 KB │ 1 week ago
3 │ tests │ Directory │ │ 4.1 KB │ 2 months ago │ 37 minutes ago 3 │ docker │ Dir │ 4.1 KB │ 1 week ago
4 │ tmp │ Directory │ │ 4.1 KB │ 2 weeks ago │ 2 weeks ago 4 │ docs │ Dir │ 4.1 KB │ 1 week ago
5 │ src │ Directory │ │ 4.1 KB │ 2 months ago │ 37 minutes ago 5 │ images │ Dir │ 4.1 KB │ 1 week ago
6 │ assets │ Directory │ │ 4.1 KB │ a month ago │ a month ago 6 │ src │ Dir │ 4.1 KB │ 1 week ago
7 │ docs │ Directory │ │ 4.1 KB │ 2 months ago │ 2 months ago 7 │ target │ Dir │ 4.1 KB │ 23 hours ago
────┴───────────┴───────────┴──────────┴────────┴──────────────┴──────────────── 8 │ tests │ Dir │ 4.1 KB │ 1 week ago
───┴────────┴──────┴────────┴──────────────
``` ```
Because most of the time you'll want to see the output of a pipeline, `autoview` is assumed. Because most of the time you'll want to see the output of a pipeline, `autoview` is assumed.
@ -171,14 +172,15 @@ For example, we could use the built-in `ps` command as well to get a list of the
```text ```text
/home/jonathan/Source/nushell(master)> ps | where cpu > 0 /home/jonathan/Source/nushell(master)> ps | where cpu > 0
───┬───────┬─────────────────┬──────────┬────────── ───┬───────┬───────────────────┬──────────┬─────────┬──────────┬──────────
# │ pid │ name │ status │ cpu # │ pid │ name │ status │ cpu │ mem │ virtual
───┼───────┼─────────────────┼──────────┼────────── ───┼───────┼───────────────────┼──────────┼─────────┼──────────┼──────────
0 │ 992 │ chrome │ Sleeping │ 6.988768 0 │ 435 │ irq/142-SYNA327 │ Sleeping │ 7.5699 │ 0 B │ 0 B
1 │ 4240 │ chrome │ Sleeping │ 5.645982 1 │ 1609 │ pulseaudio │ Sleeping │ 6.5605 │ 10.6 MB │ 2.3 GB
2 │ 13973 │ qemu-system-x86 │ Sleeping │ 4.996551 2 │ 1625 │ gnome-shell │ Sleeping │ 6.5684 │ 639.6 MB │ 7.3 GB
3 │ 15746 │ nu │ Sleeping │ 84.59905 3 │ 2202 │ Web Content │ Sleeping │ 6.8157 │ 320.8 MB │ 3.0 GB
───┴───────┴─────────────────┴──────────┴────────── 4 │ 328788 │ nu_plugin_core_ps │ Sleeping │ 92.5750 │ 5.9 MB │ 633.2 MB
───┴────────┴───────────────────┴──────────┴─────────┴──────────┴──────────
``` ```
## Opening files ## Opening files
@ -188,45 +190,49 @@ For example, you can load a .toml file as structured data and explore it:
``` ```
/home/jonathan/Source/nushell(master)> open Cargo.toml /home/jonathan/Source/nushell(master)> open Cargo.toml
──────────────────┬────────────────┬────────────────── ────────────────────┬───────────────────────────
bin │ dependencies │ dev-dependencies bin │ [table 18 rows]
──────────────────┼────────────────┼────────────────── build-dependencies │ [row nu-build serde toml]
[table: 12 rows] │ [table: 1 row] │ [table: 1 row] 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: 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 /home/jonathan/Source/nushell(master)> open Cargo.toml | get package
─────────────────┬────────────────────────────┬─────────┬─────────┬──────┬───────── ───────────────┬────────────────────────────────────
authors │ description │ edition │ license │ name │ version authors │ [table 1 rows]
─────────────────┼────────────────────────────┼─────────┼─────────┼──────┼───────── default-run │ nu
[table: 3 rows] │ A shell for the GitHub era │ 2018 │ MIT │ nu │ 0.9.0 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.14.1
───────────────┴────────────────────────────────────
``` ```
Finally, we can use commands outside of Nu once we have the data we want: Finally, we can use commands outside of Nu once we have the data we want:
``` ```
/home/jonathan/Source/nushell(master)> open Cargo.toml | get package.version | echo $it /home/jonathan/Source/nushell(master)> open Cargo.toml | get package.version | echo $it
0.9.0 0.14.1
``` ```
Here we use the variable `$it` to refer to the value being piped to the external command. Here we use the variable `$it` to refer to the value being piped to the external command.
## Configuration ## Configuration
Nu has early support for configuring the shell. It currently supports the following settings: 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).
| 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 |
To set one of these variables, you can use `config --set`. For example: To set one of these variables, you can use `config --set`. For example:
@ -276,6 +282,31 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat
You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/documentation.html#quick-command-references). You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/documentation.html#quick-command-references).
# Progress
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
# 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. The project is made available under the MIT license. See the `LICENSE` file for more information.

View File

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

View File

@ -1,6 +1,6 @@
[package] [package]
name = "nu-cli" name = "nu-cli"
version = "0.13.0" version = "0.15.0"
authors = ["The Nu Project Contributors"] authors = ["The Nu Project Contributors"]
description = "CLI for nushell" description = "CLI for nushell"
edition = "2018" edition = "2018"
@ -10,37 +10,41 @@ license = "MIT"
doctest = false doctest = false
[dependencies] [dependencies]
nu-source = { version = "0.13.0", path = "../nu-source" } nu-source = { version = "0.15.0", path = "../nu-source" }
nu-plugin = { version = "0.13.0", path = "../nu-plugin" } nu-plugin = { version = "0.15.0", path = "../nu-plugin" }
nu-protocol = { version = "0.13.0", path = "../nu-protocol" } nu-protocol = { version = "0.15.0", path = "../nu-protocol" }
nu-errors = { version = "0.13.0", path = "../nu-errors" } nu-errors = { version = "0.15.0", path = "../nu-errors" }
nu-parser = { version = "0.13.0", path = "../nu-parser" } nu-parser = { version = "0.15.0", path = "../nu-parser" }
nu-value-ext = { version = "0.13.0", path = "../nu-value-ext" } nu-value-ext = { version = "0.15.0", path = "../nu-value-ext" }
nu-test-support = { version = "0.13.0", path = "../nu-test-support" } nu-test-support = { version = "0.15.0", path = "../nu-test-support" }
ansi_term = "0.12.1" ansi_term = "0.12.1"
app_dirs = "1.2.1" app_dirs = "1.2.1"
async-recursion = "0.3.1"
async-trait = "0.1.31"
directories = "2.0.2"
async-stream = "0.2" async-stream = "0.2"
base64 = "0.12.0" base64 = "0.12.1"
bigdecimal = { version = "0.1.0", features = ["serde"] } bigdecimal = { version = "0.1.2", features = ["serde"] }
bson = { version = "0.14.1", features = ["decimal128"] } bson = { version = "0.14.1", features = ["decimal128"] }
byte-unit = "3.0.3" byte-unit = "3.1.3"
bytes = "0.5.4" bytes = "0.5.4"
calamine = "0.16" calamine = "0.16"
cfg-if = "0.1" cfg-if = "0.1"
chrono = { version = "0.4.11", features = ["serde"] } chrono = { version = "0.4.11", features = ["serde"] }
clap = "2.33.0" clap = "2.33.1"
csv = "1.1" csv = "1.1"
ctrlc = "3.1.4" ctrlc = "3.1.4"
derive-new = "0.5.8" derive-new = "0.5.8"
dirs = "2.0.2" dirs = "2.0.2"
dunce = "1.0.0" dunce = "1.0.0"
eml-parser = "0.1.0"
filesize = "0.2.0" filesize = "0.2.0"
futures = { version = "0.3", features = ["compat", "io-compat"] } futures = { version = "0.3", features = ["compat", "io-compat"] }
futures-util = "0.3.4" futures-util = "0.3.5"
futures_codec = "0.4" futures_codec = "0.4"
getset = "0.1.0" getset = "0.1.1"
git2 = { version = "0.13.1", default_features = false } git2 = { version = "0.13.6", default_features = false }
glob = "0.3.0" glob = "0.3.0"
hex = "0.4" hex = "0.4"
htmlescape = "0.3.1" htmlescape = "0.3.1"
@ -48,14 +52,14 @@ ical = "0.6.*"
ichwh = "0.3.4" ichwh = "0.3.4"
indexmap = { version = "1.3.2", features = ["serde-1"] } indexmap = { version = "1.3.2", features = ["serde-1"] }
itertools = "0.9.0" itertools = "0.9.0"
language-reporting = "0.4.0" codespan-reporting = "0.9.4"
log = "0.4.8" log = "0.4.8"
meval = "0.2" meval = "0.2"
natural = "0.5.0" natural = "0.5.0"
num-bigint = { version = "0.2.6", features = ["serde"] } num-bigint = { version = "0.2.6", features = ["serde"] }
num-traits = "0.2.11" num-traits = "0.2.11"
parking_lot = "0.10.0" parking_lot = "0.10.2"
pin-utils = "0.1.0-alpha.4" pin-utils = "0.1.0"
pretty-hex = "0.1.1" pretty-hex = "0.1.1"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
prettytable-rs = "0.8.0" prettytable-rs = "0.8.0"
@ -63,13 +67,13 @@ ptree = {version = "0.2" }
query_interface = "0.3.5" query_interface = "0.3.5"
rand = "0.7" rand = "0.7"
regex = "1" regex = "1"
roxmltree = "0.10.1" roxmltree = "0.11.0"
rustyline = "6.1.1" rustyline = "6.2.0"
serde = { version = "1.0.106", features = ["derive"] } serde = { version = "1.0.110", features = ["derive"] }
serde-hjson = "0.9.1" serde-hjson = "0.9.1"
serde_bytes = "0.11.3" serde_bytes = "0.11.4"
serde_ini = "0.2.0" serde_ini = "0.2.0"
serde_json = "1.0.51" serde_json = "1.0.53"
serde_urlencoded = "0.6.1" serde_urlencoded = "0.6.1"
serde_yaml = "0.8" serde_yaml = "0.8"
shellexpand = "2.0.0" shellexpand = "2.0.0"
@ -80,22 +84,24 @@ termcolor = "1.1.0"
textwrap = {version = "0.11.0", features = ["term_size"]} textwrap = {version = "0.11.0", features = ["term_size"]}
toml = "0.5.6" toml = "0.5.6"
typetag = "0.1.4" typetag = "0.1.4"
umask = "0.1" umask = "1.0.0"
unicode-xid = "0.2.0" unicode-xid = "0.2.0"
which = "3"
trash = { version = "1.0.0", optional = true } trash = { version = "1.0.1", optional = true }
clipboard = { version = "0.5", optional = true } clipboard = { version = "0.5", optional = true }
starship = { version = "0.39.0", optional = true } starship = { version = "0.41.3", optional = true }
rayon = "1.3.0"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
users = "0.10.0" users = "0.10.0"
[dependencies.rusqlite] [dependencies.rusqlite]
version = "0.22.0" version = "0.23.1"
features = ["bundled", "blob"] features = ["bundled", "blob"]
[build-dependencies] [build-dependencies]
nu-build = { version = "0.13.0", path = "../nu-build" } nu-build = { version = "0.15.0", path = "../nu-build" }
[dev-dependencies] [dev-dependencies]
quickcheck = "0.9" quickcheck = "0.9"

View File

@ -6,12 +6,14 @@ use crate::commands::whole_stream_command;
use crate::context::Context; use crate::context::Context;
#[cfg(not(feature = "starship-prompt"))] #[cfg(not(feature = "starship-prompt"))]
use crate::git::current_branch; use crate::git::current_branch;
use crate::path::canonicalize;
use crate::prelude::*; use crate::prelude::*;
use crate::EnvironmentSyncer;
use futures_codec::FramedRead; use futures_codec::FramedRead;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments}; use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, UntaggedValue, Value}; use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use log::{debug, trace}; use log::{debug, trace};
use rustyline::error::ReadlineError; use rustyline::error::ReadlineError;
@ -22,9 +24,11 @@ use rustyline::{
use std::error::Error; use std::error::Error;
use std::io::{BufRead, BufReader, Write}; use std::io::{BufRead, BufReader, Write};
use std::iter::Iterator; use std::iter::Iterator;
use std::path::PathBuf; use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use rayon::prelude::*;
fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), ShellError> { fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), ShellError> {
let mut child = std::process::Command::new(path) let mut child = std::process::Command::new(path)
.stdin(std::process::Stdio::piped()) .stdin(std::process::Stdio::piped())
@ -106,13 +110,19 @@ fn search_paths() -> Vec<std::path::PathBuf> {
} }
} }
#[cfg(not(debug_assertions))] if let Ok(config) = crate::data::config::config(Tag::unknown()) {
{ if let Some(plugin_dirs) = config.get("plugin_dirs") {
match env::var_os("PATH") { if let Value {
Some(paths) => { value: UntaggedValue::Table(pipelines),
search_paths.extend(env::split_paths(&paths).collect::<Vec<_>>()); ..
} = 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."),
} }
} }
@ -131,62 +141,60 @@ pub fn load_plugins(context: &mut Context) -> Result<(), ShellError> {
pattern.push(std::path::Path::new("nu_plugin_[a-z0-9][a-z0-9]*")); pattern.push(std::path::Path::new("nu_plugin_[a-z0-9][a-z0-9]*"));
match glob::glob_with(&pattern.to_string_lossy(), opts) { let plugs: Vec<_> = glob::glob_with(&pattern.to_string_lossy(), opts)?
Err(_) => {} .filter_map(|x| x.ok())
Ok(binaries) => { .collect();
for bin in binaries.filter_map(Result::ok) {
if !bin.is_file() { let _failures: Vec<_> = plugs
continue; .par_iter()
.map(|path| {
let bin_name = {
if let Some(name) = path.file_name() {
match name.to_str() {
Some(raw) => raw,
None => "",
}
} else {
""
}
};
let is_valid_name = {
#[cfg(windows)]
{
bin_name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
} }
let bin_name = { #[cfg(not(windows))]
if let Some(name) = bin.file_name() { {
match name.to_str() { bin_name
Some(raw) => raw, .chars()
None => continue, .all(|c| c.is_ascii_alphanumeric() || c == '_')
}
} else {
continue;
}
};
let is_valid_name = {
#[cfg(windows)]
{
bin_name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
}
#[cfg(not(windows))]
{
bin_name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
};
let is_executable = {
#[cfg(windows)]
{
bin_name.ends_with(".exe") || bin_name.ends_with(".bat")
}
#[cfg(not(windows))]
{
true
}
};
if is_valid_name && is_executable {
trace!("Trying {:?}", bin.display());
// we are ok if this plugin load fails
let _ = load_plugin(&bin, context);
} }
};
let is_executable = {
#[cfg(windows)]
{
bin_name.ends_with(".exe") || bin_name.ends_with(".bat")
}
#[cfg(not(windows))]
{
true
}
};
if is_valid_name && is_executable {
trace!("Trying {:?}", path.display());
// we are ok if this plugin load fails
let _ = load_plugin(&path, &mut context.clone());
} }
} })
} .collect();
} }
Ok(()) Ok(())
@ -223,7 +231,8 @@ fn create_default_starship_config() -> Option<toml::Value> {
} }
pub fn create_default_context( pub fn create_default_context(
syncer: &mut crate::env::environment_syncer::EnvironmentSyncer, syncer: &mut crate::EnvironmentSyncer,
interactive: bool,
) -> Result<Context, Box<dyn Error>> { ) -> Result<Context, Box<dyn Error>> {
syncer.load_environment(); syncer.load_environment();
@ -237,28 +246,30 @@ pub fn create_default_context(
context.add_commands(vec![ context.add_commands(vec![
// System/file operations // System/file operations
whole_stream_command(Pwd), whole_stream_command(Pwd),
per_item_command(Ls), whole_stream_command(Ls),
per_item_command(Du), whole_stream_command(Du),
whole_stream_command(Cd), whole_stream_command(Cd),
per_item_command(Remove), whole_stream_command(Remove),
per_item_command(Open), whole_stream_command(Open),
whole_stream_command(Config), whole_stream_command(Config),
per_item_command(Help), whole_stream_command(Help),
per_item_command(History), whole_stream_command(History),
whole_stream_command(Save), whole_stream_command(Save),
per_item_command(Touch), whole_stream_command(Touch),
per_item_command(Cpy), whole_stream_command(Cpy),
whole_stream_command(Date), whole_stream_command(Date),
per_item_command(Calc), whole_stream_command(Cal),
per_item_command(Mkdir), whole_stream_command(Calc),
per_item_command(Move), whole_stream_command(Mkdir),
per_item_command(Kill), whole_stream_command(Move),
whole_stream_command(Kill),
whole_stream_command(Version), whole_stream_command(Version),
whole_stream_command(Clear), whole_stream_command(Clear),
whole_stream_command(What), whole_stream_command(What),
whole_stream_command(Which), whole_stream_command(Which),
whole_stream_command(Debug), whole_stream_command(Debug),
per_item_command(Alias), whole_stream_command(Alias),
whole_stream_command(WithEnv),
// Statistics // Statistics
whole_stream_command(Size), whole_stream_command(Size),
whole_stream_command(Count), whole_stream_command(Count),
@ -268,24 +279,37 @@ pub fn create_default_context(
whole_stream_command(Next), whole_stream_command(Next),
whole_stream_command(Previous), whole_stream_command(Previous),
whole_stream_command(Shells), whole_stream_command(Shells),
per_item_command(Enter), whole_stream_command(Enter),
whole_stream_command(Exit), whole_stream_command(Exit),
// Viewers // Viewers
whole_stream_command(Autoview), whole_stream_command(Autoview),
whole_stream_command(Table), whole_stream_command(Table),
// Text manipulation // Text manipulation
whole_stream_command(Split),
whole_stream_command(SplitColumn), whole_stream_command(SplitColumn),
whole_stream_command(SplitRow), whole_stream_command(SplitRow),
whole_stream_command(Lines), whole_stream_command(Lines),
whole_stream_command(Trim), whole_stream_command(Trim),
per_item_command(Echo), whole_stream_command(Echo),
per_item_command(Parse), 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(BuildString),
// Column manipulation // Column manipulation
whole_stream_command(Reject), whole_stream_command(Reject),
whole_stream_command(Pick), whole_stream_command(Select),
whole_stream_command(Get), whole_stream_command(Get),
per_item_command(Edit), whole_stream_command(Update),
per_item_command(Insert), whole_stream_command(Insert),
whole_stream_command(SplitBy), whole_stream_command(SplitBy),
// Row manipulation // Row manipulation
whole_stream_command(Reverse), whole_stream_command(Reverse),
@ -293,28 +317,38 @@ pub fn create_default_context(
whole_stream_command(Prepend), whole_stream_command(Prepend),
whole_stream_command(SortBy), whole_stream_command(SortBy),
whole_stream_command(GroupBy), whole_stream_command(GroupBy),
whole_stream_command(GroupByDate),
whole_stream_command(First), whole_stream_command(First),
whole_stream_command(Last), whole_stream_command(Last),
whole_stream_command(Skip),
whole_stream_command(Nth), whole_stream_command(Nth),
per_item_command(Format), whole_stream_command(Drop),
per_item_command(Where), whole_stream_command(Format),
whole_stream_command(Where),
whole_stream_command(Compact), whole_stream_command(Compact),
whole_stream_command(Default), whole_stream_command(Default),
whole_stream_command(Skip),
whole_stream_command(SkipUntil),
whole_stream_command(SkipWhile), whole_stream_command(SkipWhile),
whole_stream_command(Keep),
whole_stream_command(KeepUntil),
whole_stream_command(KeepWhile),
whole_stream_command(Range), whole_stream_command(Range),
whole_stream_command(Rename), whole_stream_command(Rename),
whole_stream_command(Uniq), whole_stream_command(Uniq),
per_item_command(Each), whole_stream_command(Each),
whole_stream_command(IsEmpty),
// Table manipulation // Table manipulation
whole_stream_command(Merge),
whole_stream_command(Shuffle), whole_stream_command(Shuffle),
whole_stream_command(Wrap), whole_stream_command(Wrap),
whole_stream_command(Pivot), whole_stream_command(Pivot),
whole_stream_command(Headers), whole_stream_command(Headers),
// Data processing // Data processing
whole_stream_command(Histogram), whole_stream_command(Histogram),
whole_stream_command(Average),
whole_stream_command(Sum), whole_stream_command(Sum),
// File format output // File format output
whole_stream_command(To),
whole_stream_command(ToBSON), whole_stream_command(ToBSON),
whole_stream_command(ToCSV), whole_stream_command(ToCSV),
whole_stream_command(ToHTML), whole_stream_command(ToHTML),
@ -327,7 +361,9 @@ pub fn create_default_context(
whole_stream_command(ToURL), whole_stream_command(ToURL),
whole_stream_command(ToYAML), whole_stream_command(ToYAML),
// File format input // File format input
whole_stream_command(From),
whole_stream_command(FromCSV), whole_stream_command(FromCSV),
whole_stream_command(FromEML),
whole_stream_command(FromTSV), whole_stream_command(FromTSV),
whole_stream_command(FromSSV), whole_stream_command(FromSSV),
whole_stream_command(FromINI), whole_stream_command(FromINI),
@ -345,7 +381,7 @@ pub fn create_default_context(
whole_stream_command(FromIcs), whole_stream_command(FromIcs),
whole_stream_command(FromVcf), whole_stream_command(FromVcf),
// "Private" commands (not intended to be accessed directly) // "Private" commands (not intended to be accessed directly)
whole_stream_command(RunExternalCommand), whole_stream_command(RunExternalCommand { interactive }),
]); ]);
cfg_if::cfg_if! { cfg_if::cfg_if! {
@ -361,9 +397,7 @@ pub fn create_default_context(
#[cfg(feature = "clipboard")] #[cfg(feature = "clipboard")]
{ {
context.add_commands(vec![whole_stream_command( context.add_commands(vec![whole_stream_command(crate::commands::clip::Clip)]);
crate::commands::clip::clipboard::Clip,
)]);
} }
} }
@ -375,7 +409,7 @@ pub async fn run_vec_of_pipelines(
redirect_stdin: bool, redirect_stdin: bool,
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
let mut syncer = crate::EnvironmentSyncer::new(); let mut syncer = crate::EnvironmentSyncer::new();
let mut context = crate::create_default_context(&mut syncer)?; let mut context = create_default_context(&mut syncer, false)?;
let _ = crate::load_plugins(&mut context); let _ = crate::load_plugins(&mut context);
@ -445,14 +479,14 @@ pub async fn run_pipeline_standalone(
}; };
context.maybe_print_errors(Text::from(line)); context.maybe_print_errors(Text::from(line));
if error_code != 0 { if error_code != 0 && exit_on_error {
std::process::exit(error_code); std::process::exit(error_code);
} }
} }
LineResult::Error(line, err) => { LineResult::Error(line, err) => {
context.with_host(|host| { context.with_host(|_host| {
print_err(err, host, &Text::from(line.clone())); print_err(err, &Text::from(line.clone()));
}); });
context.maybe_print_errors(Text::from(line)); context.maybe_print_errors(Text::from(line));
@ -468,9 +502,14 @@ 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. /// 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(
let mut syncer = crate::env::environment_syncer::EnvironmentSyncer::new(); mut syncer: EnvironmentSyncer,
let mut context = create_default_context(&mut syncer)?; 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 _ = load_plugins(&mut context); let _ = load_plugins(&mut context);
@ -550,14 +589,28 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
rl.set_edit_mode(edit_mode); rl.set_edit_mode(edit_mode);
let max_history_size = config::config(Tag::unknown())?
.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::config(Tag::unknown())?
.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::config(Tag::unknown())?
.get("completion_mode") .get("completion_mode")
.map(|s| match s.value.expect_string() { .map(|s| match s.value.expect_string() {
"list" => CompletionType::List, "list" => CompletionType::List,
"circular" => CompletionType::Circular, "circular" => CompletionType::Circular,
_ => CompletionType::Circular, _ => DEFAULT_COMPLETION_MODE,
}) })
.unwrap_or(CompletionType::Circular); .unwrap_or(DEFAULT_COMPLETION_MODE);
rl.set_completion_type(completion_mode); rl.set_completion_type(completion_mode);
@ -565,6 +618,7 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
#[cfg(feature = "starship-prompt")] #[cfg(feature = "starship-prompt")]
{ {
std::env::set_var("STARSHIP_SHELL", ""); std::env::set_var("STARSHIP_SHELL", "");
std::env::set_var("PWD", &cwd);
let mut starship_context = let mut starship_context =
starship::context::Context::new_with_dir(clap::ArgMatches::default(), cwd); starship::context::Context::new_with_dir(clap::ArgMatches::default(), cwd);
@ -628,8 +682,8 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
rl.add_history_entry(line.clone()); rl.add_history_entry(line.clone());
let _ = rl.save_history(&History::path()); let _ = rl.save_history(&History::path());
context.with_host(|host| { context.with_host(|_host| {
print_err(err, host, &Text::from(line.clone())); print_err(err, &Text::from(line.clone()));
}); });
context.maybe_print_errors(Text::from(line.clone())); context.maybe_print_errors(Text::from(line.clone()));
@ -698,6 +752,7 @@ async fn process_line(
Ok(line) => { Ok(line) => {
let line = chomp_newline(line); let line = chomp_newline(line);
ctx.raw_input = line.to_string();
let result = match nu_parser::lite_parse(&line, 0) { let result = match nu_parser::lite_parse(&line, 0) {
Err(err) => { Err(err) => {
@ -710,7 +765,7 @@ async fn process_line(
debug!("=== Parsed ==="); debug!("=== Parsed ===");
debug!("{:#?}", result); debug!("{:#?}", result);
let classified_block = nu_parser::classify_block(&result, ctx.registry()); let mut classified_block = nu_parser::classify_block(&result, ctx.registry());
debug!("{:#?}", classified_block); debug!("{:#?}", classified_block);
//println!("{:#?}", pipeline); //println!("{:#?}", pipeline);
@ -759,9 +814,9 @@ async fn process_line(
.as_ref() .as_ref()
.map(NamedArguments::is_empty) .map(NamedArguments::is_empty)
.unwrap_or(true) .unwrap_or(true)
&& dunce::canonicalize(&name).is_ok() && canonicalize(ctx.shell_manager.path(), name).is_ok()
&& PathBuf::from(&name).is_dir() && Path::new(&name).is_dir()
&& ichwh::which(&name).await.unwrap_or(None).is_none() && which::which(&name).is_err()
{ {
// Here we work differently if we're in Windows because of the expected Windows behavior // Here we work differently if we're in Windows because of the expected Windows behavior
#[cfg(windows)] #[cfg(windows)]
@ -787,7 +842,8 @@ async fn process_line(
ctx.shell_manager.set_path(val.to_string()); ctx.shell_manager.set_path(val.to_string());
return LineResult::Success(line.to_string()); return LineResult::Success(line.to_string());
} else { } else {
ctx.shell_manager.set_path(name.to_string()); ctx.shell_manager
.set_path(format!("{}\\", name.to_string()));
return LineResult::Success(line.to_string()); return LineResult::Success(line.to_string());
} }
} else { } else {
@ -829,7 +885,20 @@ async fn process_line(
InputStream::empty() InputStream::empty()
}; };
match run_block(&classified_block.block, ctx, input_stream, &Scope::empty()).await { classified_block.block.expand_it_usage();
trace!("{:#?}", classified_block);
let env = ctx.get_env();
match run_block(
&classified_block.block,
ctx,
input_stream,
&Value::nothing(),
&IndexMap::new(),
&env,
)
.await
{
Ok(input) => { Ok(input) => {
// Running a pipeline gives us back a stream that we can then // 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 // work through. At the top level, we just want to pull on the
@ -841,11 +910,15 @@ async fn process_line(
shell_manager: ctx.shell_manager.clone(), shell_manager: ctx.shell_manager.clone(),
host: ctx.host.clone(), host: ctx.host.clone(),
ctrl_c: ctx.ctrl_c.clone(), ctrl_c: ctx.ctrl_c.clone(),
current_errors: ctx.current_errors.clone(),
registry: ctx.registry.clone(), registry: ctx.registry.clone(),
name: Tag::unknown(), 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 { loop {
match output_stream.try_next().await { match output_stream.try_next().await {
Ok(Some(ReturnSuccess::Value(Value { Ok(Some(ReturnSuccess::Value(Value {
@ -877,26 +950,25 @@ 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() { if let Some(diag) = err.into_diagnostic() {
let writer = host.err_termcolor(); let source = source.to_string();
let mut source = source.to_string(); let mut files = codespan_reporting::files::SimpleFiles::new();
source.push_str(" "); files.add("shell", source);
let files = nu_parser::Files::new(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 _ = std::panic::catch_unwind(move || {
let _ = language_reporting::emit( let _ = codespan_reporting::term::emit(&mut writer.lock(), &config, &files, &diag);
&mut writer.lock(),
&files,
&diag,
&language_reporting::DefaultConfig,
);
}); });
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#[quickcheck] #[quickcheck]
fn quickcheck_parse(data: String) -> bool { fn quickcheck_parse(data: String) -> bool {
if let Ok(lite_block) = nu_parser::lite_parse(&data, 0) { if let Ok(lite_block) = nu_parser::lite_parse(&data, 0) {

View File

@ -8,9 +8,13 @@ pub(crate) mod alias;
pub(crate) mod append; pub(crate) mod append;
pub(crate) mod args; pub(crate) mod args;
pub(crate) mod autoview; pub(crate) mod autoview;
pub(crate) mod average;
pub(crate) mod build_string;
pub(crate) mod cal;
pub(crate) mod calc; pub(crate) mod calc;
pub(crate) mod cd; pub(crate) mod cd;
pub(crate) mod classified; pub(crate) mod classified;
#[cfg(feature = "clipboard")]
pub(crate) mod clip; pub(crate) mod clip;
pub(crate) mod command; pub(crate) mod command;
pub(crate) mod compact; pub(crate) mod compact;
@ -20,18 +24,20 @@ pub(crate) mod cp;
pub(crate) mod date; pub(crate) mod date;
pub(crate) mod debug; pub(crate) mod debug;
pub(crate) mod default; pub(crate) mod default;
pub(crate) mod drop;
pub(crate) mod du; pub(crate) mod du;
pub(crate) mod each; pub(crate) mod each;
pub(crate) mod echo; pub(crate) mod echo;
pub(crate) mod edit;
pub(crate) mod enter; pub(crate) mod enter;
#[allow(unused)] #[allow(unused)]
pub(crate) mod evaluate_by; pub(crate) mod evaluate_by;
pub(crate) mod exit; pub(crate) mod exit;
pub(crate) mod first; pub(crate) mod first;
pub(crate) mod format; pub(crate) mod format;
pub(crate) mod from;
pub(crate) mod from_bson; pub(crate) mod from_bson;
pub(crate) mod from_csv; pub(crate) mod from_csv;
pub(crate) mod from_eml;
pub(crate) mod from_ics; pub(crate) mod from_ics;
pub(crate) mod from_ini; pub(crate) mod from_ini;
pub(crate) mod from_json; pub(crate) mod from_json;
@ -47,23 +53,28 @@ pub(crate) mod from_xml;
pub(crate) mod from_yaml; pub(crate) mod from_yaml;
pub(crate) mod get; pub(crate) mod get;
pub(crate) mod group_by; pub(crate) mod group_by;
pub(crate) mod group_by_date;
pub(crate) mod headers; pub(crate) mod headers;
pub(crate) mod help; pub(crate) mod help;
pub(crate) mod histogram; pub(crate) mod histogram;
pub(crate) mod history; pub(crate) mod history;
pub(crate) mod insert; pub(crate) mod insert;
pub(crate) mod is_empty;
pub(crate) mod keep;
pub(crate) mod keep_until;
pub(crate) mod keep_while;
pub(crate) mod last; pub(crate) mod last;
pub(crate) mod lines; pub(crate) mod lines;
pub(crate) mod ls; pub(crate) mod ls;
#[allow(unused)] #[allow(unused)]
pub(crate) mod map_max_by; pub(crate) mod map_max_by;
pub(crate) mod merge;
pub(crate) mod mkdir; pub(crate) mod mkdir;
pub(crate) mod mv; pub(crate) mod mv;
pub(crate) mod next; pub(crate) mod next;
pub(crate) mod nth; pub(crate) mod nth;
pub(crate) mod open; pub(crate) mod open;
pub(crate) mod parse; pub(crate) mod parse;
pub(crate) mod pick;
pub(crate) mod pivot; pub(crate) mod pivot;
pub(crate) mod plugin; pub(crate) mod plugin;
pub(crate) mod prepend; pub(crate) mod prepend;
@ -79,20 +90,23 @@ pub(crate) mod rm;
pub(crate) mod run_alias; pub(crate) mod run_alias;
pub(crate) mod run_external; pub(crate) mod run_external;
pub(crate) mod save; pub(crate) mod save;
pub(crate) mod select;
pub(crate) mod shells; pub(crate) mod shells;
pub(crate) mod shuffle; pub(crate) mod shuffle;
pub(crate) mod size; pub(crate) mod size;
pub(crate) mod skip; pub(crate) mod skip;
pub(crate) mod skip_until;
pub(crate) mod skip_while; pub(crate) mod skip_while;
pub(crate) mod sort_by; pub(crate) mod sort_by;
pub(crate) mod split;
pub(crate) mod split_by; pub(crate) mod split_by;
pub(crate) mod split_column; pub(crate) mod str_;
pub(crate) mod split_row;
pub(crate) mod sum; pub(crate) mod sum;
#[allow(unused)] #[allow(unused)]
pub(crate) mod t_sort_by; pub(crate) mod t_sort_by;
pub(crate) mod table; pub(crate) mod table;
pub(crate) mod tags; pub(crate) mod tags;
pub(crate) mod to;
pub(crate) mod to_bson; pub(crate) mod to_bson;
pub(crate) mod to_csv; pub(crate) mod to_csv;
pub(crate) mod to_html; pub(crate) mod to_html;
@ -105,21 +119,25 @@ pub(crate) mod to_url;
pub(crate) mod to_yaml; pub(crate) mod to_yaml;
pub(crate) mod trim; pub(crate) mod trim;
pub(crate) mod uniq; pub(crate) mod uniq;
pub(crate) mod update;
pub(crate) mod version; pub(crate) mod version;
pub(crate) mod what; pub(crate) mod what;
pub(crate) mod where_; pub(crate) mod where_;
pub(crate) mod which_; pub(crate) mod which_;
pub(crate) mod with_env;
pub(crate) mod wrap; pub(crate) mod wrap;
pub(crate) use autoview::Autoview; pub(crate) use autoview::Autoview;
pub(crate) use cd::Cd; pub(crate) use cd::Cd;
pub(crate) use command::{ pub(crate) use command::{
per_item_command, whole_stream_command, Command, PerItemCommand, UnevaluatedCallInfo, whole_stream_command, Command, Example, UnevaluatedCallInfo, WholeStreamCommand,
WholeStreamCommand,
}; };
pub(crate) use alias::Alias; pub(crate) use alias::Alias;
pub(crate) use append::Append; pub(crate) use append::Append;
pub(crate) use average::Average;
pub(crate) use build_string::BuildString;
pub(crate) use cal::Cal;
pub(crate) use calc::Calc; pub(crate) use calc::Calc;
pub(crate) use compact::Compact; pub(crate) use compact::Compact;
pub(crate) use config::Config; pub(crate) use config::Config;
@ -128,10 +146,12 @@ pub(crate) use cp::Cpy;
pub(crate) use date::Date; pub(crate) use date::Date;
pub(crate) use debug::Debug; pub(crate) use debug::Debug;
pub(crate) use default::Default; pub(crate) use default::Default;
pub(crate) use drop::Drop;
pub(crate) use du::Du; pub(crate) use du::Du;
pub(crate) use each::Each; pub(crate) use each::Each;
pub(crate) use echo::Echo; pub(crate) use echo::Echo;
pub(crate) use edit::Edit; pub(crate) use is_empty::IsEmpty;
pub(crate) use update::Update;
pub(crate) mod kill; pub(crate) mod kill;
pub(crate) use kill::Kill; pub(crate) use kill::Kill;
pub(crate) mod clear; pub(crate) mod clear;
@ -143,8 +163,10 @@ pub(crate) use evaluate_by::EvaluateBy;
pub(crate) use exit::Exit; pub(crate) use exit::Exit;
pub(crate) use first::First; pub(crate) use first::First;
pub(crate) use format::Format; pub(crate) use format::Format;
pub(crate) use from::From;
pub(crate) use from_bson::FromBSON; pub(crate) use from_bson::FromBSON;
pub(crate) use from_csv::FromCSV; pub(crate) use from_csv::FromCSV;
pub(crate) use from_eml::FromEML;
pub(crate) use from_ics::FromIcs; pub(crate) use from_ics::FromIcs;
pub(crate) use from_ini::FromINI; pub(crate) use from_ini::FromINI;
pub(crate) use from_json::FromJSON; pub(crate) use from_json::FromJSON;
@ -162,23 +184,27 @@ pub(crate) use from_yaml::FromYAML;
pub(crate) use from_yaml::FromYML; pub(crate) use from_yaml::FromYML;
pub(crate) use get::Get; pub(crate) use get::Get;
pub(crate) use group_by::GroupBy; pub(crate) use group_by::GroupBy;
pub(crate) use group_by_date::GroupByDate;
pub(crate) use headers::Headers; pub(crate) use headers::Headers;
pub(crate) use help::Help; pub(crate) use help::Help;
pub(crate) use histogram::Histogram; pub(crate) use histogram::Histogram;
pub(crate) use history::History; pub(crate) use history::History;
pub(crate) use insert::Insert; pub(crate) use insert::Insert;
pub(crate) use keep::Keep;
pub(crate) use keep_until::KeepUntil;
pub(crate) use keep_while::KeepWhile;
pub(crate) use last::Last; pub(crate) use last::Last;
pub(crate) use lines::Lines; pub(crate) use lines::Lines;
pub(crate) use ls::Ls; pub(crate) use ls::Ls;
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) use map_max_by::MapMaxBy; pub(crate) use map_max_by::MapMaxBy;
pub(crate) use merge::Merge;
pub(crate) use mkdir::Mkdir; pub(crate) use mkdir::Mkdir;
pub(crate) use mv::Move; pub(crate) use mv::Move;
pub(crate) use next::Next; pub(crate) use next::Next;
pub(crate) use nth::Nth; pub(crate) use nth::Nth;
pub(crate) use open::Open; pub(crate) use open::Open;
pub(crate) use parse::Parse; pub(crate) use parse::Parse;
pub(crate) use pick::Pick;
pub(crate) use pivot::Pivot; pub(crate) use pivot::Pivot;
pub(crate) use prepend::Prepend; pub(crate) use prepend::Prepend;
pub(crate) use prev::Previous; pub(crate) use prev::Previous;
@ -192,20 +218,28 @@ pub(crate) use reverse::Reverse;
pub(crate) use rm::Remove; pub(crate) use rm::Remove;
pub(crate) use run_external::RunExternalCommand; pub(crate) use run_external::RunExternalCommand;
pub(crate) use save::Save; pub(crate) use save::Save;
pub(crate) use select::Select;
pub(crate) use shells::Shells; pub(crate) use shells::Shells;
pub(crate) use shuffle::Shuffle; pub(crate) use shuffle::Shuffle;
pub(crate) use size::Size; pub(crate) use size::Size;
pub(crate) use skip::Skip; pub(crate) use skip::Skip;
pub(crate) use skip_until::SkipUntil;
pub(crate) use skip_while::SkipWhile; pub(crate) use skip_while::SkipWhile;
pub(crate) use sort_by::SortBy; pub(crate) use sort_by::SortBy;
pub(crate) use split::Split;
pub(crate) use split::SplitColumn;
pub(crate) use split::SplitRow;
pub(crate) use split_by::SplitBy; pub(crate) use split_by::SplitBy;
pub(crate) use split_column::SplitColumn; pub(crate) use str_::{
pub(crate) use split_row::SplitRow; Str, StrCapitalize, StrDowncase, StrFindReplace, StrSet, StrSubstring, StrToDatetime,
StrToDecimal, StrToInteger, StrTrim, StrUpcase,
};
pub(crate) use sum::Sum; pub(crate) use sum::Sum;
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) use t_sort_by::TSortBy; pub(crate) use t_sort_by::TSortBy;
pub(crate) use table::Table; pub(crate) use table::Table;
pub(crate) use tags::Tags; pub(crate) use tags::Tags;
pub(crate) use to::To;
pub(crate) use to_bson::ToBSON; pub(crate) use to_bson::ToBSON;
pub(crate) use to_csv::ToCSV; pub(crate) use to_csv::ToCSV;
pub(crate) use to_html::ToHTML; pub(crate) use to_html::ToHTML;
@ -224,4 +258,5 @@ pub(crate) use version::Version;
pub(crate) use what::What; pub(crate) use what::What;
pub(crate) use where_::Where; pub(crate) use where_::Where;
pub(crate) use which_::Which; pub(crate) use which_::Which;
pub(crate) use with_env::WithEnv;
pub(crate) use wrap::Wrap; pub(crate) use wrap::Wrap;

View File

@ -1,14 +1,25 @@
use crate::commands::PerItemCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry; use crate::context::CommandRegistry;
use crate::data::config;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{
CallInfo, CommandAction, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value, hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
}; };
use nu_source::Tagged;
pub struct Alias; pub struct Alias;
impl PerItemCommand for Alias { #[derive(Deserialize)]
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 { fn name(&self) -> &str {
"alias" "alias"
} }
@ -17,49 +28,110 @@ impl PerItemCommand for Alias {
Signature::build("alias") Signature::build("alias")
.required("name", SyntaxShape::String, "the name of the alias") .required("name", SyntaxShape::String, "the name of the alias")
.required("args", SyntaxShape::Table, "the arguments to the alias") .required("args", SyntaxShape::Table, "the arguments to the alias")
.required("block", SyntaxShape::Block, "the block to run on each row") .required(
"block",
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 { fn usage(&self) -> &str {
"Run a block on each row of the table." "Define a shortcut for another command."
} }
fn run( async fn run(
&self, &self,
call_info: &CallInfo, args: CommandArgs,
_registry: &CommandRegistry, registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let call_info = call_info.clone(); // args.process(registry, alias)?.run()
let stream = async_stream! { alias(args, registry)
match (call_info.args.expect_nth(0)?, call_info.args.expect_nth(1)?, call_info.args.expect_nth(2)?) { }
(Value {value: UntaggedValue::Primitive(Primitive::String(name)), .. },
Value { value: UntaggedValue::Table(list), .. },
Value {
value: UntaggedValue::Block(block),
tag
}) => {
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()));
}
}
yield ReturnSuccess::action(CommandAction::AddAlias(name.to_string(), args, block.clone()))
}
_ => {
yield Err(ShellError::labeled_error(
"Expected `name [args] {block}",
"needs a name, args, and a block",
call_info.name_tag,
))
}
};
};
Ok(stream.to_output_stream()) 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,
},
]
}
}
// <<<<<<< HEAD
// pub fn alias(alias_args: AliasArgs, ctx: RunnableContext) -> Result<OutputStream, ShellError> {
// =======
pub fn alias(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
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(raw_input.len());
let mut left = raw_input[..left_brace].replace("--save", "").replace("-s", "");
let mut 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 mut table = UntaggedValue::table(&[alias]);
result.insert("startup".to_string(), table.into_value(Tag::default()));
}
}
config::write(&result, &None)?;
}
for item in list.iter() {
if let Ok(string) = item.as_string() {
processed_args.push(format!("${}", string));
} else {
yield Err(ShellError::labeled_error("Expected a string", "expected a string", item.tag()));
}
}
yield ReturnSuccess::action(CommandAction::AddAlias(name.to_string(), processed_args, block.clone()))
};
Ok(stream.to_output_stream())
}
#[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

@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry; use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, Value}; use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
#[derive(Deserialize)] #[derive(Deserialize)]
struct AppendArgs { struct AppendArgs {
@ -11,6 +11,7 @@ struct AppendArgs {
pub struct Append; pub struct Append;
#[async_trait]
impl WholeStreamCommand for Append { impl WholeStreamCommand for Append {
fn name(&self) -> &str { fn name(&self) -> &str {
"append" "append"
@ -28,22 +29,40 @@ impl WholeStreamCommand for Append {
"Append the given row to the table" "Append the given row to the table"
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> 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) -> 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( #[cfg(test)]
AppendArgs { row }: AppendArgs, mod tests {
RunnableContext { input, .. }: RunnableContext, use super::Append;
) -> Result<OutputStream, ShellError> {
let mut after: VecDeque<Value> = VecDeque::new();
after.push_back(row);
let after = futures::stream::iter(after);
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

@ -1,14 +1,20 @@
use crate::commands::UnevaluatedCallInfo; use crate::commands::UnevaluatedCallInfo;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::data::value::format_leaf;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{hir, hir::Expression, hir::Literal, hir::SpannedExpression}; use nu_protocol::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, UntaggedValue, Value}; use nu_protocol::{Primitive, Scope, Signature, UntaggedValue, Value};
use parking_lot::Mutex;
use prettytable::format::{FormatBuilder, LinePosition, LineSeparator};
use prettytable::{color, Attr, Cell, Row, Table};
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use textwrap::fill;
pub struct Autoview; pub struct Autoview;
#[async_trait]
impl WholeStreamCommand for Autoview { impl WholeStreamCommand for Autoview {
fn name(&self) -> &str { fn name(&self) -> &str {
"autoview" "autoview"
@ -22,7 +28,7 @@ impl WholeStreamCommand for Autoview {
"View the contents of the pipeline as a table or list." "View the contents of the pipeline as a table or list."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
@ -33,14 +39,33 @@ impl WholeStreamCommand for Autoview {
shell_manager: args.shell_manager, shell_manager: args.shell_manager,
host: args.host, host: args.host,
ctrl_c: args.ctrl_c, ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag, name: args.call_info.name_tag,
raw_input: args.raw_input,
}) })
.await
}
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,
},
]
} }
} }
pub struct RunnableContextWithoutInput { pub struct RunnableContextWithoutInput {
pub shell_manager: ShellManager, pub shell_manager: ShellManager,
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>, pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub ctrl_c: Arc<AtomicBool>, pub ctrl_c: Arc<AtomicBool>,
pub registry: CommandRegistry, pub registry: CommandRegistry,
pub name: Tag, pub name: Tag,
@ -52,6 +77,7 @@ impl RunnableContextWithoutInput {
shell_manager: context.shell_manager, shell_manager: context.shell_manager,
host: context.host, host: context.host,
ctrl_c: context.ctrl_c, ctrl_c: context.ctrl_c,
current_errors: context.current_errors,
registry: context.registry, registry: context.registry,
name: context.name, name: context.name,
}; };
@ -59,221 +85,307 @@ 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 binary = context.get_command("binaryview");
let text = context.get_command("textview"); let text = context.get_command("textview");
let table = context.get_command("table"); let table = context.get_command("table");
Ok(OutputStream::new(async_stream! { #[derive(PartialEq)]
let (mut input_stream, context) = RunnableContextWithoutInput::convert(context); 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);
if let Some(x) = input_stream.next().await {
match input_stream.next().await { match input_stream.next().await {
Some(x) => { Some(y) => {
match input_stream.next().await { let ctrl_c = context.ctrl_c.clone();
Some(y) => { let stream = async_stream! {
let ctrl_c = context.ctrl_c.clone(); yield Ok(x);
let stream = async_stream! { yield Ok(y);
yield Ok(x);
yield Ok(y);
loop { loop {
match input_stream.next().await { match input_stream.next().await {
Some(z) => { Some(z) => {
if ctrl_c.load(Ordering::SeqCst) { if ctrl_c.load(Ordering::SeqCst) {
break; break;
}
yield Ok(z);
}
_ => break,
} }
yield Ok(z);
} }
}; _ => break,
let stream = stream.to_input_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);
result.collect::<Vec<_>>().await;
} }
} }
_ => { };
match x { let stream = stream.to_input_stream();
Value {
value: UntaggedValue::Primitive(Primitive::String(ref s)), if let Some(table) = table {
tag: Tag { anchor, span }, let command_args = create_default_command_args(&context).with_input(stream);
} if anchor.is_some() => { let result = table.run(command_args, &context.registry).await;
if let Some(text) = text { result.collect::<Vec<_>>().await;
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); match x {
result.collect::<Vec<_>>().await; Value {
} else { value: UntaggedValue::Primitive(Primitive::String(ref s)),
out!("{}", 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);
}
}
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).await;
result.collect::<Vec<_>>().await;
} else {
use pretty_hex::*;
out!("{:?}", b.hex_dump());
}
}
Value {
value: UntaggedValue::Error(e),
..
} => {
return Err(e);
}
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)
> textwrap::termwidth()) =>
{
let termwidth = std::cmp::max(textwrap::termwidth(), 20);
enum TableMode {
Light,
Normal,
}
let mut table = Table::new();
let table_mode = crate::data::config::config(Tag::unknown());
let table_mode = if let Some(s) = table_mode?.get("table_mode") {
match s.as_string() {
Ok(typ) if typ == "light" => TableMode::Light,
_ => TableMode::Normal,
} }
Value { } else {
value: UntaggedValue::Primitive(Primitive::String(s)), TableMode::Normal
.. };
} => {
out!("{}", s); match table_mode {
TableMode::Light => {
table.set_format(
FormatBuilder::new()
.separator(
LinePosition::Title,
LineSeparator::new('─', '─', ' ', ' '),
)
.separator(
LinePosition::Bottom,
LineSeparator::new(' ', ' ', ' ', ' '),
)
.padding(1, 1)
.build(),
);
} }
Value { _ => {
value: UntaggedValue::Primitive(Primitive::Line(ref s)), table.set_format(
tag: Tag { anchor, span }, FormatBuilder::new()
} if anchor.is_some() => { .column_separator('│')
if let Some(text) = text { .separator(
let mut stream = VecDeque::new(); LinePosition::Top,
stream.push_back(UntaggedValue::string(s).into_value(Tag { anchor, span })); LineSeparator::new('─', '┬', ' ', ' '),
let command_args = create_default_command_args(&context).with_input(stream); )
let result = text.run(command_args, &context.registry); .separator(
result.collect::<Vec<_>>().await; LinePosition::Title,
} else { LineSeparator::new('─', '┼', ' ', ' '),
out!("{}\n", s); )
} .separator(
} LinePosition::Bottom,
Value { LineSeparator::new('─', '┴', ' ', ' '),
value: UntaggedValue::Primitive(Primitive::Line(s)), )
.. .padding(1, 1)
} => { .build(),
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::Binary(ref b)), .. } => { let mut max_key_len = 0;
if let Some(binary) = binary { for (key, _) in row.entries.iter() {
let mut stream = VecDeque::new(); max_key_len = std::cmp::max(max_key_len, key.chars().count());
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::Error(e), .. } => { if max_key_len > (termwidth / 2 - 1) {
yield Err(e); max_key_len = termwidth / 2 - 1;
} }
Value { value: UntaggedValue::Row(row), ..} => { let max_val_len = termwidth - max_key_len - 5;
use prettytable::format::{FormatBuilder, LinePosition, LineSeparator};
use prettytable::{color, Attr, Cell, Row, Table};
use crate::data::value::{format_leaf, style_leaf};
use textwrap::fill;
let termwidth = std::cmp::max(textwrap::termwidth(), 20); 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,
)),
]));
}
enum TableMode { table.printstd();
Light,
Normal,
}
let mut table = Table::new(); // table.print_term(&mut *context.host.lock().out_terminal().ok_or_else(|| ShellError::untagged_runtime_error("Could not open terminal for output"))?)
let table_mode = crate::data::config::config(Tag::unknown()); // .map_err(|_| ShellError::untagged_runtime_error("Internal error: could not print to terminal (for unix systems check to make sure TERM is set)"))?;
}
let table_mode = if let Some(s) = table_mode?.get("table_mode") { Value {
match s.as_string() { value: ref item, ..
Ok(typ) if typ == "light" => TableMode::Light, } => {
_ => TableMode::Normal, if let Some(table) = table {
} let mut stream = VecDeque::new();
} else { stream.push_back(x);
TableMode::Normal let command_args =
}; create_default_command_args(&context).with_input(stream);
let result = table.run(command_args, &context.registry).await;
match table_mode { result.collect::<Vec<_>>().await;
TableMode::Light => { } else {
table.set_format( out!("{:?}", item);
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);
}
}
} }
} }
} }
} }
_ => {
//out!("<no results>");
}
} }
}
// Needed for async_stream to type check Ok(OutputStream::empty())
if false {
yield ReturnSuccess::value(UntaggedValue::nothing().into_untagged_value());
}
}))
} }
fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawCommandArgs { fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawCommandArgs {
@ -281,6 +393,7 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
RawCommandArgs { RawCommandArgs {
host: context.host.clone(), host: context.host.clone(),
ctrl_c: context.ctrl_c.clone(), ctrl_c: context.ctrl_c.clone(),
current_errors: context.current_errors.clone(),
shell_manager: context.shell_manager.clone(), shell_manager: context.shell_manager.clone(),
call_info: UnevaluatedCallInfo { call_info: UnevaluatedCallInfo {
args: hir::Call { args: hir::Call {
@ -294,7 +407,19 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
is_last: true, is_last: true,
}, },
name_tag: context.name.clone(), 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,173 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use bigdecimal::FromPrimitive;
use nu_errors::ShellError;
use nu_protocol::hir::{convert_number_to_u64, Number, Operator};
use nu_protocol::{
Dictionary, Primitive, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value,
};
use num_traits::identities::Zero;
use indexmap::map::IndexMap;
pub struct Average;
#[async_trait]
impl WholeStreamCommand for Average {
fn name(&self) -> &str {
"average"
}
fn signature(&self) -> Signature {
Signature::build("average")
}
fn usage(&self) -> &str {
"Average the values."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
average(RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
})
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Average a list of numbers",
example: "echo [100 0 100 0] | average",
result: Some(vec![UntaggedValue::decimal(50).into()]),
}]
}
}
fn average(
RunnableContext {
mut input, name, ..
}: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let mut values: Vec<Value> = input.drain_vec().await;
let action = reducer_for(Reduce::Sum);
if values.iter().all(|v| if let UntaggedValue::Primitive(_) = v.value {true} else {false}) {
match avg(&values, name) {
Ok(result) => yield ReturnSuccess::value(result),
Err(err) => yield Err(err),
}
} else {
let mut column_values = IndexMap::new();
for value in values {
match value.value {
UntaggedValue::Row(row_dict) => {
for (key, value) in row_dict.entries.iter() {
column_values
.entry(key.clone())
.and_modify(|v: &mut Vec<Value>| v.push(value.clone()))
.or_insert(vec![value.clone()]);
}
},
table => {},
};
}
let mut column_totals = IndexMap::new();
for (col_name, col_vals) in column_values {
match avg(&col_vals, &name) {
Ok(result) => {
column_totals.insert(col_name, result);
}
Err(err) => yield Err(err),
}
}
yield ReturnSuccess::value(
UntaggedValue::Row(Dictionary {entries: column_totals}).into_untagged_value())
}
};
let stream: BoxStream<'static, ReturnValue> = stream.boxed();
Ok(stream.to_output_stream())
}
fn avg(values: &[Value], name: impl Into<Tag>) -> Result<Value, ShellError> {
let name = name.into();
let sum = reducer_for(Reduce::Sum);
let number = BigDecimal::from_usize(values.len()).expect("expected a usize-sized bigdecimal");
let total_rows = UntaggedValue::decimal(number);
let total = sum(Value::zero(), values.to_vec())?;
match total {
Value {
value: UntaggedValue::Primitive(Primitive::Bytes(num)),
..
} => {
let left = UntaggedValue::from(Primitive::Int(num.into()));
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => {
let number = Number::Decimal(result);
let number = convert_number_to_u64(&number);
Ok(UntaggedValue::bytes(number).into_value(name))
}
Ok(_) => Err(ShellError::labeled_error(
"could not calculate average of non-integer or unrelated types",
"source",
name,
)),
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
)),
}
}
Value {
value: UntaggedValue::Primitive(other),
..
} => {
let left = UntaggedValue::from(other);
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(value) => Ok(value.into_value(name)),
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
)),
}
}
_ => Err(ShellError::labeled_error(
"could not calculate average of non-integer or unrelated types",
"source",
name,
)),
}
}
#[cfg(test)]
mod tests {
use super::Average;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Average {})
}
}

View File

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

@ -0,0 +1,357 @@
use crate::commands::{command::EvaluatedWholeStreamCommandArgs, WholeStreamCommand};
use crate::prelude::*;
use chrono::{Datelike, Local, NaiveDate};
use indexmap::IndexMap;
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"
}
fn signature(&self) -> Signature {
Signature::build("cal")
.switch("year", "Display the year column", Some('y'))
.switch("quarter", "Display the quarter column", Some('q'))
.switch("month", "Display the month column", Some('m'))
.named(
"full-year",
SyntaxShape::Int,
"Display a year-long calendar for the specified year",
None,
)
.switch(
"month-names",
"Display the month names instead of integers",
None,
)
}
fn usage(&self) -> &str {
"Display a calendar."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
cal(args, registry).await
}
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,
},
]
}
}
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();
let mut selected_year: i32 = current_year;
let mut current_day_option: Option<u32> = Some(current_day);
let month_range = if args.has("full-year") {
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 selected_year != current_year {
current_day_option = None
}
} else {
return Err(get_invalid_year_shell_error(&full_year_value.tag()));
}
}
(1, 12)
} else {
(current_month, current_month)
};
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 {
day_number_month_starts_on: u32,
number_of_days_in_month: u32,
selected_year: i32,
selected_month: u32,
}
impl MonthHelper {
pub fn new(selected_year: i32, selected_month: u32) -> Result<MonthHelper, ()> {
let mut month_helper = MonthHelper {
day_number_month_starts_on: 0,
number_of_days_in_month: 0,
selected_year,
selected_month,
};
let chosen_date_result_one = month_helper.update_day_number_month_starts_on();
let chosen_date_result_two = month_helper.update_number_of_days_in_month();
if chosen_date_result_one.is_ok() && chosen_date_result_two.is_ok() {
return Ok(month_helper);
}
Err(())
}
pub fn get_month_name(&self) -> String {
let month_name = match self.selected_month {
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 update_day_number_month_starts_on(&mut self) -> Result<(), ()> {
let naive_date_result =
MonthHelper::get_naive_date(self.selected_year, self.selected_month);
match naive_date_result {
Ok(naive_date) => {
self.day_number_month_starts_on = naive_date.weekday().num_days_from_sunday();
Ok(())
}
_ => Err(()),
}
}
fn update_number_of_days_in_month(&mut self) -> Result<(), ()> {
// Chrono does not provide a method to output the amount of days in a month
// 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 self.selected_month == 12 {
(self.selected_year + 1, 1)
} else {
(self.selected_year, self.selected_month + 1)
};
let naive_date_result = MonthHelper::get_naive_date(adjusted_year, adjusted_month);
match naive_date_result {
Ok(naive_date) => {
self.number_of_days_in_month = naive_date.pred().day();
Ok(())
}
_ => Err(()),
}
}
fn get_naive_date(selected_year: i32, selected_month: u32) -> Result<NaiveDate, ()> {
if let Some(naive_date) = NaiveDate::from_ymd_opt(selected_year, selected_month, 1) {
return Ok(naive_date);
}
Err(())
}
}
fn get_current_date() -> (i32, u32, u32) {
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();
(current_year, current_month, current_day)
}
fn add_months_of_year_to_table(
args: &EvaluatedWholeStreamCommandArgs,
mut calendar_vec_deque: &mut VecDeque<Value>,
tag: &Tag,
selected_year: i32,
(start_month, end_month): (u32, u32),
current_month: u32,
current_day_option: Option<u32>,
) -> 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 {
if month_number == current_month {
new_current_day_option = Some(current_day)
}
}
let add_month_to_table_result = add_month_to_table(
&args,
&mut calendar_vec_deque,
&tag,
selected_year,
month_number,
new_current_day_option,
);
add_month_to_table_result?
}
Ok(())
}
fn add_month_to_table(
args: &EvaluatedWholeStreamCommandArgs,
calendar_vec_deque: &mut VecDeque<Value>,
tag: &Tag,
selected_year: i32,
current_month: u32,
current_day_option: Option<u32>,
) -> Result<(), ShellError> {
let month_helper_result = MonthHelper::new(selected_year, current_month);
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 day_limit = month_helper.number_of_days_in_month + month_helper.day_number_month_starts_on;
let mut day_count: u32 = 1;
let days_of_the_week = [
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
];
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_names = args.has("month-names");
while day_count <= day_limit {
let mut indexmap = IndexMap::new();
if should_show_year_column {
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(((month_helper.selected_month - 1) / 3) + 1).into_value(tag),
);
}
if should_show_month_column {
let month_value = if should_show_month_names {
UntaggedValue::string(month_helper.get_month_name()).into_value(tag)
} else {
UntaggedValue::int(month_helper.selected_month).into_value(tag)
};
indexmap.insert("month".to_string(), month_value);
}
for day in &days_of_the_week {
let should_add_day_number_to_table =
(day_count <= day_limit) && (day_count > month_helper.day_number_month_starts_on);
let mut value = UntaggedValue::nothing().into_value(tag);
if should_add_day_number_to_table {
let day_count_with_offset = day_count - month_helper.day_number_month_starts_on;
value = UntaggedValue::int(day_count_with_offset).into_value(tag);
if let Some(current_day) = current_day_option {
if current_day == day_count_with_offset {
// 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;
}
calendar_vec_deque
.push_back(UntaggedValue::Row(Dictionary::from(indexmap)).into_value(tag));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::Cal;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Cal {})
}
}

View File

@ -1,11 +1,12 @@
use crate::commands::PerItemCommand; use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{CallInfo, Primitive, ReturnSuccess, UntaggedValue, Value}; use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
pub struct Calc; pub struct Calc;
impl PerItemCommand for Calc { #[async_trait]
impl WholeStreamCommand for Calc {
fn name(&self) -> &str { fn name(&self) -> &str {
"calc" "calc"
} }
@ -14,38 +15,50 @@ impl PerItemCommand for Calc {
"Parse a math expression into a number" "Parse a math expression into a number"
} }
fn run( async fn run(
&self, &self,
_call_info: &CallInfo, args: CommandArgs,
_registry: &CommandRegistry, registry: &CommandRegistry,
raw_args: &RawCommandArgs,
input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
calc(input, raw_args) calc(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Calculate math in the pipeline",
example: "echo '10 / 4' | calc",
result: Some(vec![UntaggedValue::decimal(2.5).into()]),
}]
} }
} }
fn calc(input: Value, args: &RawCommandArgs) -> Result<OutputStream, ShellError> { pub async fn calc(
let name_span = &args.call_info.name_tag.span; args: CommandArgs,
_registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let input = args.input;
let name = args.call_info.name_tag.span;
let output = if let Ok(string) = input.as_string() { Ok(input
match parse(&string, &input.tag) { .map(move |input| {
Ok(value) => ReturnSuccess::value(value), if let Ok(string) = input.as_string() {
Err(err) => Err(ShellError::labeled_error( match parse(&string, &input.tag) {
"Calculation error", Ok(value) => ReturnSuccess::value(value),
err, Err(err) => Err(ShellError::labeled_error(
&input.tag.span, "Calculation error",
)), err,
} &input.tag.span,
} else { )),
Err(ShellError::labeled_error( }
"Expected a string from pipeline", } else {
"requires string input", Err(ShellError::labeled_error(
name_span, "Expected a string from pipeline",
)) "requires string input",
}; name,
))
Ok(vec![output].into()) }
})
.to_output_stream())
} }
pub fn parse(math_expression: &str, tag: impl Into<Tag>) -> Result<Value, String> { pub fn parse(math_expression: &str, tag: impl Into<Tag>) -> Result<Value, String> {
@ -61,3 +74,15 @@ pub fn parse(math_expression: &str, tag: impl Into<Tag>) -> Result<Value, String
Err(error) => Err(error.to_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

@ -1,10 +1,20 @@
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use std::path::PathBuf;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape}; use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged;
#[derive(Deserialize)]
pub struct CdArgs {
pub(crate) path: Option<Tagged<PathBuf>>,
}
pub struct Cd; pub struct Cd;
#[async_trait]
impl WholeStreamCommand for Cd { impl WholeStreamCommand for Cd {
fn name(&self) -> &str { fn name(&self) -> &str {
"cd" "cd"
@ -22,17 +32,51 @@ impl WholeStreamCommand for Cd {
"Change to a new path." "Change to a new path."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
cd(args, registry) 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) -> 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: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { #[cfg(test)]
let shell_manager = args.shell_manager.clone(); mod tests {
let args = args.evaluate_once(registry)?; use super::Cd;
shell_manager.cd(args)
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Cd {})
}
} }

View File

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

View File

@ -3,27 +3,25 @@ use crate::prelude::*;
use log::{log_enabled, trace}; use log::{log_enabled, trace};
use futures::stream::once;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::hir::SpannedExpression; 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, expr: SpannedExpression,
context: &mut Context, context: &mut Context,
input: InputStream, it: &Value,
scope: &Scope, vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>,
) -> Result<InputStream, ShellError> { ) -> Result<InputStream, ShellError> {
if log_enabled!(log::Level::Trace) { if log_enabled!(log::Level::Trace) {
trace!(target: "nu::run::expr", "->"); trace!(target: "nu::run::expr", "->");
trace!(target: "nu::run::expr", "{:?}", expr); trace!(target: "nu::run::expr", "{:?}", expr);
} }
let scope = scope.clone();
let registry = context.registry().clone(); let registry = context.registry().clone();
let stream = input.map(move |row| { let output = evaluate_baseline_expr(&expr, &registry, it, vars, env).await?;
let scope = scope.clone().set_it(row);
evaluate_baseline_expr(&expr, &registry, &scope)
});
Ok(stream.to_input_stream()) Ok(once(async { Ok(output) }).to_input_stream())
} }

View File

@ -9,7 +9,7 @@ use std::sync::mpsc;
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{BufMut, Bytes, BytesMut};
use futures::executor::block_on_stream; use futures::executor::block_on_stream;
use futures::stream::StreamExt; // use futures::stream::StreamExt;
use futures_codec::FramedRead; use futures_codec::FramedRead;
use log::trace; use log::trace;
@ -82,20 +82,6 @@ impl futures_codec::Decoder for MaybeTextCodec {
} }
} }
pub fn nu_value_to_string(name_tag: &Tag, from: &Value) -> Result<String, ShellError> {
match &from.value {
UntaggedValue::Primitive(Primitive::Int(i)) => Ok(i.to_string()),
UntaggedValue::Primitive(Primitive::String(s))
| UntaggedValue::Primitive(Primitive::Line(s)) => Ok(s.clone()),
UntaggedValue::Primitive(Primitive::Path(p)) => Ok(p.to_string_lossy().to_string()),
unsupported => Err(ShellError::labeled_error(
format!("needs string data (given: {})", unsupported.type_name()),
"expected a string",
name_tag,
)),
}
}
pub(crate) async fn run_external_command( pub(crate) async fn run_external_command(
command: ExternalCommand, command: ExternalCommand,
context: &mut Context, context: &mut Context,
@ -113,88 +99,10 @@ pub(crate) async fn run_external_command(
)); ));
} }
if command.has_it_argument() { run_with_stdin(command, context, input, scope, is_last).await
run_with_iterator_arg(command, context, input, scope, is_last)
} else {
run_with_stdin(command, context, input, scope, is_last)
}
} }
fn run_with_iterator_arg( async fn run_with_stdin(
command: ExternalCommand,
context: &mut Context,
input: InputStream,
scope: &Scope,
is_last: bool,
) -> Result<InputStream, ShellError> {
let path = context.shell_manager.path();
let mut inputs: InputStream =
trace_stream!(target: "nu::trace_stream::external::it", "input" = input);
let name_tag = command.name_tag.clone();
let scope = scope.clone();
let context = context.clone();
let stream = async_stream! {
while let Some(value) = inputs.next().await {
// Evaluate the expressions into values, and from values into strings for each iteration
let mut command_args = vec![];
let scope = scope.clone().set_it(value);
for arg in command.args.iter() {
let value = evaluate_baseline_expr(arg, &context.registry, &scope)?;
command_args.push(nu_value_to_string(&name_tag, &value)?);
}
let process_args = command_args
.iter()
.map(|arg| {
let arg = expand_tilde(arg.deref(), dirs::home_dir);
#[cfg(not(windows))]
{
if argument_contains_whitespace(&arg) && argument_is_quoted(&arg) {
if let Some(unquoted) = remove_quotes(&arg) {
format!(r#""{}""#, unquoted)
} else {
arg.as_ref().to_string()
}
} else {
arg.as_ref().to_string()
}
}
#[cfg(windows)]
{
if let Some(unquoted) = remove_quotes(&arg) {
unquoted.to_string()
} else {
arg.as_ref().to_string()
}
}
})
.collect::<Vec<String>>();
match spawn(&command, &path, &process_args[..], InputStream::empty(), is_last) {
Ok(mut res) => {
while let Some(item) = res.next().await {
yield Ok(item)
}
}
Err(reason) => {
yield Ok(Value {
value: UntaggedValue::Error(reason),
tag: name_tag
});
return;
}
}
}
};
Ok(stream.to_input_stream())
}
fn run_with_stdin(
command: ExternalCommand, command: ExternalCommand,
context: &mut Context, context: &mut Context,
input: InputStream, input: InputStream,
@ -207,8 +115,32 @@ fn run_with_stdin(
let mut command_args = vec![]; let mut command_args = vec![];
for arg in command.args.iter() { for arg in command.args.iter() {
let value = evaluate_baseline_expr(arg, &context.registry, scope)?; let value =
command_args.push(value.as_string()?); evaluate_baseline_expr(arg, &context.registry, &scope.it, &scope.vars, &scope.env)
.await?;
// Skip any arguments that don't really exist, treating them as optional
// FIXME: we may want to preserve the gap in the future, though it's hard to say
// what value we would put in its place.
if value.value.is_none() {
continue;
}
// Do the cleanup that we need to do on any argument going out:
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
let value_string;
#[cfg(not(windows))]
{
value_string = trimmed_value_string
.replace('$', "\\$")
.replace('"', "\\\"")
.to_string()
}
#[cfg(windows)]
{
value_string = trimmed_value_string
}
command_args.push(value_string);
} }
let process_args = command_args let process_args = command_args
@ -235,7 +167,7 @@ fn run_with_stdin(
}) })
.collect::<Vec<String>>(); .collect::<Vec<String>>();
spawn(&command, &path, &process_args[..], input, is_last) spawn(&command, &path, &process_args[..], input, is_last, scope)
} }
fn spawn( fn spawn(
@ -244,6 +176,7 @@ fn spawn(
args: &[String], args: &[String],
input: InputStream, input: InputStream,
is_last: bool, is_last: bool,
scope: &Scope,
) -> Result<InputStream, ShellError> { ) -> Result<InputStream, ShellError> {
let command = command.clone(); let command = command.clone();
@ -273,6 +206,9 @@ fn spawn(
process.current_dir(path); process.current_dir(path);
trace!(target: "nu::run::external", "cwd = {:?}", &path); trace!(target: "nu::run::external", "cwd = {:?}", &path);
process.env_clear();
process.envs(scope.env.iter());
// We want stdout regardless of what // We want stdout regardless of what
// we are doing ($it case or pipe stdin) // we are doing ($it case or pipe stdin)
if !is_last { if !is_last {
@ -405,16 +341,27 @@ fn spawn(
} }
} }
}, },
Err(_) => { Err(e) => {
let _ = stdout_read_tx.send(Ok(Value { // If there's an exit status, it makes sense that we may error when
value: UntaggedValue::Error(ShellError::labeled_error( // trying to read from its stdout pipe (likely been closed). In that
"Unable to read from stdout.", // case, don't emit an error.
"unable to read from stdout", let should_error = match child.wait() {
&stdout_name_tag, Ok(exit_status) => !exit_status.success(),
)), Err(_) => true,
tag: stdout_name_tag.clone(), };
}));
break; if should_error {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
format!("Unable to read from stdout ({})", e),
"unable to read from stdout",
&stdout_name_tag,
)),
tag: stdout_name_tag.clone(),
}));
}
return Ok(());
} }
} }
} }
@ -424,10 +371,7 @@ fn spawn(
// than what other shells will do. // than what other shells will do.
let external_failed = match child.wait() { let external_failed = match child.wait() {
Err(_) => true, Err(_) => true,
Ok(exit_status) => match exit_status.code() { Ok(exit_status) => !exit_status.success(),
Some(e) if e != 0 => true,
_ => false,
},
}; };
if external_failed { if external_failed {
@ -467,12 +411,12 @@ fn spawn(
async fn did_find_command(name: &str) -> bool { async fn did_find_command(name: &str) -> bool {
#[cfg(not(windows))] #[cfg(not(windows))]
{ {
ichwh::which(name).await.unwrap_or(None).is_some() which::which(name).is_ok()
} }
#[cfg(windows)] #[cfg(windows)]
{ {
if ichwh::which(name).await.unwrap_or(None).is_some() { if which::which(name).is_ok() {
true true
} else { } else {
let cmd_builtins = [ let cmd_builtins = [
@ -514,6 +458,7 @@ fn add_quotes(argument: &str) -> String {
format!("\"{}\"", argument) format!("\"{}\"", argument)
} }
#[allow(unused)]
fn remove_quotes(argument: &str) -> Option<&str> { fn remove_quotes(argument: &str) -> Option<&str> {
if !argument_is_quoted(argument) { if !argument_is_quoted(argument) {
return None; return None;
@ -566,7 +511,7 @@ mod tests {
let mut ctx = Context::basic().expect("There was a problem creating a basic context."); let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
assert!( assert!(
run_external_command(cmd, &mut ctx, input, &Scope::empty(), false) run_external_command(cmd, &mut ctx, input, &Scope::new(), false)
.await .await
.is_err() .is_err()
); );

View File

@ -1,4 +1,4 @@
use crate::commands::command::per_item_command; use crate::commands::command::whole_stream_command;
use crate::commands::run_alias::AliasCommand; use crate::commands::run_alias::AliasCommand;
use crate::commands::UnevaluatedCallInfo; use crate::commands::UnevaluatedCallInfo;
use crate::prelude::*; use crate::prelude::*;
@ -7,32 +7,41 @@ use nu_errors::ShellError;
use nu_protocol::hir::InternalCommand; use nu_protocol::hir::InternalCommand;
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue, Value}; use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue, Value};
pub(crate) fn run_internal_command( pub(crate) async fn run_internal_command(
command: InternalCommand, command: InternalCommand,
context: &mut Context, context: &mut Context,
input: InputStream, input: InputStream,
scope: &Scope, it: &Value,
vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>,
) -> Result<InputStream, ShellError> { ) -> Result<InputStream, ShellError> {
if log_enabled!(log::Level::Trace) { if log_enabled!(log::Level::Trace) {
trace!(target: "nu::run::internal", "->"); trace!(target: "nu::run::internal", "->");
trace!(target: "nu::run::internal", "{}", command.name); 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 objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
let internal_command = context.expect_command(&command.name); let internal_command = context.expect_command(&command.name);
let result = { let mut result = {
context.run_command( context
internal_command?, .run_command(
Tag::unknown_anchor(command.name_span), internal_command?,
command.args.clone(), Tag::unknown_anchor(command.name_span),
scope, command.args.clone(),
objects, &scope,
) objects,
)
.await
}; };
let mut result = trace_out_stream!(target: "nu::trace_stream::internal", "output" = result);
let mut context = context.clone(); let mut context = context.clone();
// let scope = scope.clone();
let stream = async_stream! { let stream = async_stream! {
let mut soft_errs: Vec<ShellError> = vec![]; let mut soft_errs: Vec<ShellError> = vec![];
@ -51,12 +60,13 @@ pub(crate) fn run_internal_command(
} }
CommandAction::AutoConvert(tagged_contents, extension) => { CommandAction::AutoConvert(tagged_contents, extension) => {
let contents_tag = tagged_contents.tag.clone(); let contents_tag = tagged_contents.tag.clone();
let command_name = format!("from-{}", extension); let command_name = format!("from {}", extension);
let command = command.clone(); let command = command.clone();
if let Some(converter) = context.registry.get_command(&command_name) { if let Some(converter) = context.registry.get_command(&command_name) {
let new_args = RawCommandArgs { let new_args = RawCommandArgs {
host: context.host.clone(), host: context.host.clone(),
ctrl_c: context.ctrl_c.clone(), ctrl_c: context.ctrl_c.clone(),
current_errors: context.current_errors.clone(),
shell_manager: context.shell_manager.clone(), shell_manager: context.shell_manager.clone(),
call_info: UnevaluatedCallInfo { call_info: UnevaluatedCallInfo {
args: nu_protocol::hir::Call { args: nu_protocol::hir::Call {
@ -67,10 +77,10 @@ pub(crate) fn run_internal_command(
is_last: false, is_last: false,
}, },
name_tag: Tag::unknown_anchor(command.name_span), name_tag: Tag::unknown_anchor(command.name_span),
scope: Scope::empty(), scope: scope.clone(),
} }
}; };
let mut result = converter.run(new_args.with_input(vec![tagged_contents]), &context.registry); let mut result = converter.run(new_args.with_input(vec![tagged_contents]), &context.registry).await;
let result_vec: Vec<Result<ReturnSuccess, ShellError>> = result.drain_vec().await; let result_vec: Vec<Result<ReturnSuccess, ShellError>> = result.drain_vec().await;
for res in result_vec { for res in result_vec {
match res { match res {
@ -117,12 +127,12 @@ pub(crate) fn run_internal_command(
} }
CommandAction::EnterShell(location) => { CommandAction::EnterShell(location) => {
context.shell_manager.insert_at_current(Box::new( context.shell_manager.insert_at_current(Box::new(
FilesystemShell::with_location(location, context.registry().clone()), FilesystemShell::with_location(location, context.registry().clone())?,
)); ));
} }
CommandAction::AddAlias(name, args, block) => { CommandAction::AddAlias(name, args, block) => {
context.add_commands(vec![ context.add_commands(vec![
per_item_command(AliasCommand::new( whole_stream_command(AliasCommand::new(
name, name,
args, args,
block, block,

View File

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

View File

@ -1,106 +1,109 @@
#[cfg(feature = "clipboard")] use crate::commands::WholeStreamCommand;
pub mod clipboard { use crate::context::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::prelude::*;
use crate::context::CommandRegistry; use futures::stream::StreamExt;
use crate::prelude::*; use nu_errors::ShellError;
use futures::stream::StreamExt; use nu_protocol::{Signature, Value};
use nu_errors::ShellError;
use nu_protocol::{ReturnValue, Signature, Value};
use clipboard::{ClipboardContext, ClipboardProvider}; use clipboard::{ClipboardContext, ClipboardProvider};
pub struct Clip; pub struct Clip;
#[derive(Deserialize)] #[async_trait]
pub struct ClipArgs {} impl WholeStreamCommand for Clip {
fn name(&self) -> &str {
impl WholeStreamCommand for Clip { "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()
}
} }
pub fn clip( fn signature(&self) -> Signature {
ClipArgs {}: ClipArgs, Signature::build("clip")
RunnableContext { input, name, .. }: RunnableContext, }
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> { ) -> Result<OutputStream, ShellError> {
let stream = async_stream! { clip(args, registry).await
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))
} }
async fn inner_clip(input: Vec<Value>, name: Tag) -> OutputStream { fn examples(&self) -> Vec<Example> {
if let Ok(clip_context) = ClipboardProvider::new() { vec![Example {
let mut clip_context: ClipboardContext = clip_context; description: "Save text to the clipboard",
let mut new_copy_data = String::new(); example: "echo 'secret value' | clip",
result: None,
if !input.is_empty() { }]
let mut first = true; }
for i in input.iter() { }
if !first {
new_copy_data.push_str("\n"); pub async fn clip(
} else { args: CommandArgs,
first = false; _registry: &CommandRegistry,
} ) -> Result<OutputStream, ShellError> {
let input = args.input;
let string: String = match i.as_string() { let name = args.call_info.name_tag.clone();
Ok(string) => string.to_string(), let values: Vec<Value> = input.collect().await;
Err(_) => {
return OutputStream::one(Err(ShellError::labeled_error( if let Ok(clip_context) = ClipboardProvider::new() {
"Given non-string data", let mut clip_context: ClipboardContext = clip_context;
"expected strings from pipeline", let mut new_copy_data = String::new();
name,
))) if !values.is_empty() {
} let mut first = true;
}; for i in values.iter() {
if !first {
new_copy_data.push_str(&string); new_copy_data.push_str("\n");
} } else {
} first = false;
}
match clip_context.set_contents(new_copy_data) {
Ok(_) => {} let string: String = match i.as_string() {
Err(_) => { Ok(string) => string.to_string(),
return OutputStream::one(Err(ShellError::labeled_error( Err(_) => {
"Could not set contents of clipboard", return Err(ShellError::labeled_error(
"could not set contents of clipboard", "Given non-string data",
name, "expected strings from pipeline",
))); name,
} ))
} }
};
OutputStream::empty()
} else { new_copy_data.push_str(&string);
OutputStream::one(Err(ShellError::labeled_error( }
"Could not open clipboard", }
"could not open clipboard",
name, 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 getset::Getters;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::hir; 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 serde::{Deserialize, Serialize};
use std::ops::Deref; use std::ops::Deref;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
@ -20,8 +21,8 @@ pub struct UnevaluatedCallInfo {
} }
impl UnevaluatedCallInfo { impl UnevaluatedCallInfo {
pub fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> { pub async fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
let args = evaluate_args(&self.args, registry, &self.scope)?; let args = evaluate_args(&self.args, registry, &self.scope).await?;
Ok(CallInfo { Ok(CallInfo {
args, args,
@ -29,14 +30,14 @@ impl UnevaluatedCallInfo {
}) })
} }
pub fn evaluate_with_new_it( pub async fn evaluate_with_new_it(
self, self,
registry: &CommandRegistry, registry: &CommandRegistry,
it: &Value, it: &Value,
) -> Result<CallInfo, ShellError> { ) -> Result<CallInfo, ShellError> {
let mut scope = self.scope.clone(); let mut scope = self.scope.clone();
scope = scope.set_it(it.clone()); scope.it = it.clone();
let args = evaluate_args(&self.args, registry, &scope)?; let args = evaluate_args(&self.args, registry, &scope).await?;
Ok(CallInfo { Ok(CallInfo {
args, args,
@ -49,44 +50,16 @@ impl UnevaluatedCallInfo {
} }
} }
pub trait CallInfoExt {
fn process<'de, T: Deserialize<'de>>(
&self,
shell_manager: &ShellManager,
ctrl_c: Arc<AtomicBool>,
callback: fn(T, &RunnablePerItemContext) -> Result<OutputStream, ShellError>,
) -> Result<RunnablePerItemArgs<T>, ShellError>;
}
impl CallInfoExt for CallInfo {
fn process<'de, T: Deserialize<'de>>(
&self,
shell_manager: &ShellManager,
ctrl_c: Arc<AtomicBool>,
callback: fn(T, &RunnablePerItemContext) -> Result<OutputStream, ShellError>,
) -> Result<RunnablePerItemArgs<T>, ShellError> {
let mut deserializer = ConfigDeserializer::from_call_info(self.clone());
Ok(RunnablePerItemArgs {
args: T::deserialize(&mut deserializer)?,
context: RunnablePerItemContext {
shell_manager: shell_manager.clone(),
name: self.name_tag.clone(),
ctrl_c,
},
callback,
})
}
}
#[derive(Getters)] #[derive(Getters)]
#[get = "pub(crate)"] #[get = "pub(crate)"]
pub struct CommandArgs { pub struct CommandArgs {
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>, pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>, pub ctrl_c: Arc<AtomicBool>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub shell_manager: ShellManager, pub shell_manager: ShellManager,
pub call_info: UnevaluatedCallInfo, pub call_info: UnevaluatedCallInfo,
pub input: InputStream, pub input: InputStream,
pub raw_input: String,
} }
#[derive(Getters, Clone)] #[derive(Getters, Clone)]
@ -94,6 +67,7 @@ pub struct CommandArgs {
pub struct RawCommandArgs { pub struct RawCommandArgs {
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>, pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>, pub ctrl_c: Arc<AtomicBool>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub shell_manager: ShellManager, pub shell_manager: ShellManager,
pub call_info: UnevaluatedCallInfo, pub call_info: UnevaluatedCallInfo,
} }
@ -103,9 +77,11 @@ impl RawCommandArgs {
CommandArgs { CommandArgs {
host: self.host, host: self.host,
ctrl_c: self.ctrl_c, ctrl_c: self.ctrl_c,
current_errors: self.current_errors,
shell_manager: self.shell_manager, shell_manager: self.shell_manager,
call_info: self.call_info, call_info: self.call_info,
input: input.into(), input: input.into(),
raw_input: String::default(),
} }
} }
} }
@ -117,7 +93,7 @@ impl std::fmt::Debug for CommandArgs {
} }
impl CommandArgs { impl CommandArgs {
pub fn evaluate_once( pub async fn evaluate_once(
self, self,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> { ) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
@ -125,7 +101,7 @@ impl CommandArgs {
let ctrl_c = self.ctrl_c.clone(); let ctrl_c = self.ctrl_c.clone();
let shell_manager = self.shell_manager.clone(); let shell_manager = self.shell_manager.clone();
let input = self.input; let input = self.input;
let call_info = self.call_info.evaluate(registry)?; let call_info = self.call_info.evaluate(registry).await?;
Ok(EvaluatedWholeStreamCommandArgs::new( Ok(EvaluatedWholeStreamCommandArgs::new(
host, host,
@ -136,7 +112,7 @@ impl CommandArgs {
)) ))
} }
pub fn evaluate_once_with_scope( pub async fn evaluate_once_with_scope(
self, self,
registry: &CommandRegistry, registry: &CommandRegistry,
scope: &Scope, scope: &Scope,
@ -150,7 +126,7 @@ impl CommandArgs {
args: self.call_info.args, args: self.call_info.args,
scope: scope.clone(), scope: scope.clone(),
}; };
let call_info = call_info.evaluate(registry)?; let call_info = call_info.evaluate(registry).await?;
Ok(EvaluatedWholeStreamCommandArgs::new( Ok(EvaluatedWholeStreamCommandArgs::new(
host, host,
@ -161,133 +137,36 @@ impl CommandArgs {
)) ))
} }
pub fn process<'de, T: Deserialize<'de>, O: ToOutputStream>( pub async fn process<'de, T: Deserialize<'de>>(
self, self,
registry: &CommandRegistry, registry: &CommandRegistry,
callback: fn(T, RunnableContext) -> Result<O, ShellError>, ) -> Result<(T, InputStream), ShellError> {
) -> Result<RunnableArgs<T, O>, ShellError> { let args = self.evaluate_once(registry).await?;
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)?;
let call_info = args.call_info.clone(); 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); let mut deserializer = ConfigDeserializer::from_call_info(call_info);
Ok(RunnableRawArgs { Ok((T::deserialize(&mut deserializer)?, args.input))
args: T::deserialize(&mut deserializer)?,
context: RunnableContext {
input,
registry: registry.clone(),
shell_manager,
name: name_tag,
host,
ctrl_c,
},
raw_args,
callback,
})
} }
} }
pub struct RunnablePerItemContext {
pub shell_manager: ShellManager,
pub name: Tag,
pub ctrl_c: Arc<AtomicBool>,
}
pub struct RunnableContext { pub struct RunnableContext {
pub input: InputStream, pub input: InputStream,
pub shell_manager: ShellManager, pub shell_manager: ShellManager,
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>, pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>, pub ctrl_c: Arc<AtomicBool>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub registry: CommandRegistry, pub registry: CommandRegistry,
pub name: Tag, pub name: Tag,
pub raw_input: String,
} }
impl RunnableContext { impl RunnableContext {
pub fn get_command(&self, name: &str) -> Option<Arc<Command>> { pub fn get_command(&self, name: &str) -> Option<Command> {
self.registry.get_command(name) self.registry.get_command(name)
} }
} }
pub struct RunnablePerItemArgs<T> {
args: T,
context: RunnablePerItemContext,
callback: fn(T, &RunnablePerItemContext) -> Result<OutputStream, ShellError>,
}
impl<T> RunnablePerItemArgs<T> {
pub fn run(self) -> Result<OutputStream, ShellError> {
(self.callback)(self.args, &self.context)
}
}
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 struct EvaluatedWholeStreamCommandArgs {
pub args: EvaluatedCommandArgs, pub args: EvaluatedCommandArgs,
pub input: InputStream, pub input: InputStream,
@ -398,6 +277,13 @@ 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 { pub trait WholeStreamCommand: Send + Sync {
fn name(&self) -> &str; fn name(&self) -> &str;
@ -407,7 +293,7 @@ pub trait WholeStreamCommand: Send + Sync {
fn usage(&self) -> &str; fn usage(&self) -> &str;
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
@ -416,152 +302,68 @@ pub trait WholeStreamCommand: Send + Sync {
fn is_binary(&self) -> bool { fn is_binary(&self) -> bool {
false false
} }
}
pub trait PerItemCommand: Send + Sync { fn examples(&self) -> Vec<Example> {
fn name(&self) -> &str; Vec::new()
fn signature(&self) -> Signature {
Signature::new(self.name()).desc(self.usage()).filter()
}
fn usage(&self) -> &str;
fn run(
&self,
call_info: &CallInfo,
registry: &CommandRegistry,
raw_args: &RawCommandArgs,
input: Value,
) -> Result<OutputStream, ShellError>;
fn is_binary(&self) -> bool {
false
} }
} }
pub enum Command { #[derive(Clone)]
WholeStream(Arc<dyn WholeStreamCommand>), pub struct Command(Arc<dyn WholeStreamCommand>);
PerItem(Arc<dyn PerItemCommand>),
}
impl PrettyDebugWithSource for Command { impl PrettyDebugWithSource for Command {
fn pretty_debug(&self, source: &str) -> DebugDocBuilder { fn pretty_debug(&self, source: &str) -> DebugDocBuilder {
match self { b::typed(
Command::WholeStream(command) => b::typed( "whole stream command",
"whole stream command", b::description(self.name())
b::description(command.name()) + b::space()
+ b::space() + b::equals()
+ b::equals() + b::space()
+ b::space() + self.signature().pretty_debug(source),
+ command.signature().pretty_debug(source), )
),
Command::PerItem(command) => b::typed(
"per item command",
b::description(command.name())
+ b::space()
+ b::equals()
+ b::space()
+ command.signature().pretty_debug(source),
),
}
} }
} }
impl std::fmt::Debug for Command { impl std::fmt::Debug for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { write!(f, "Command({})", self.name())
Command::WholeStream(command) => write!(f, "WholeStream({})", command.name()),
Command::PerItem(command) => write!(f, "PerItem({})", command.name()),
}
} }
} }
impl Command { impl Command {
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
match self { self.0.name()
Command::WholeStream(command) => command.name(),
Command::PerItem(command) => command.name(),
}
} }
pub fn signature(&self) -> Signature { pub fn signature(&self) -> Signature {
match self { self.0.signature()
Command::WholeStream(command) => command.signature(),
Command::PerItem(command) => command.signature(),
}
} }
pub fn usage(&self) -> &str { pub fn usage(&self) -> &str {
match self { self.0.usage()
Command::WholeStream(command) => command.usage(),
Command::PerItem(command) => command.usage(),
}
} }
pub fn run(&self, args: CommandArgs, registry: &CommandRegistry) -> OutputStream { pub async fn run(&self, args: CommandArgs, registry: &CommandRegistry) -> OutputStream {
if args.call_info.switch_present("help") { if args.call_info.switch_present("help") {
get_help(self.name(), self.usage(), self.signature()).into() let cl = self.0.clone();
let registry = registry.clone();
OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(get_help(&*cl, &registry)).into_value(Tag::unknown()),
)))
} else { } else {
match self { match self.0.run(args, registry).await {
Command::WholeStream(command) => match command.run(args, registry) { Ok(stream) => stream,
Ok(stream) => stream, Err(err) => OutputStream::one(Err(err)),
Err(err) => OutputStream::one(Err(err)),
},
Command::PerItem(command) => {
self.run_helper(command.clone(), args, registry.clone())
}
} }
} }
} }
fn run_helper( pub fn is_binary(&self) -> bool {
&self, self.0.is_binary()
command: Arc<dyn PerItemCommand>,
args: CommandArgs,
registry: CommandRegistry,
) -> OutputStream {
let raw_args = RawCommandArgs {
host: args.host,
ctrl_c: args.ctrl_c,
shell_manager: args.shell_manager,
call_info: args.call_info,
};
let out = args
.input
.map(move |x| {
let call_info = UnevaluatedCallInfo {
args: raw_args.call_info.args.clone(),
name_tag: raw_args.call_info.name_tag.clone(),
scope: raw_args.call_info.scope.clone().set_it(x.clone()),
}
.evaluate(&registry);
// let call_info = raw_args
// .clone()
// .call_info
// .evaluate(&registry, &Scope::it_value(x.clone()));
match call_info {
Ok(call_info) => match command.run(&call_info, &registry, &raw_args, x) {
Ok(o) => o,
Err(e) => {
futures::stream::iter(vec![ReturnValue::Err(e)]).to_output_stream()
}
},
Err(e) => futures::stream::iter(vec![ReturnValue::Err(e)]).to_output_stream(),
}
})
.flatten();
out.to_output_stream()
} }
pub fn is_binary(&self) -> bool { pub fn stream_command(&self) -> &dyn WholeStreamCommand {
match self { &*self.0
Command::WholeStream(command) => command.is_binary(),
Command::PerItem(command) => command.is_binary(),
}
} }
} }
@ -570,6 +372,7 @@ pub struct FnFilterCommand {
func: fn(EvaluatedFilterCommandArgs) -> Result<OutputStream, ShellError>, func: fn(EvaluatedFilterCommandArgs) -> Result<OutputStream, ShellError>,
} }
#[async_trait]
impl WholeStreamCommand for FnFilterCommand { impl WholeStreamCommand for FnFilterCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
&self.name &self.name
@ -579,54 +382,52 @@ impl WholeStreamCommand for FnFilterCommand {
"usage" "usage"
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, CommandArgs {
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let CommandArgs {
host, host,
ctrl_c, ctrl_c,
shell_manager, shell_manager,
call_info, call_info,
input, mut input,
} = args; ..
}: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let host: Arc<parking_lot::Mutex<dyn Host>> = host.clone(); let host: Arc<parking_lot::Mutex<dyn Host>> = host.clone();
let registry: CommandRegistry = registry.clone(); let registry: CommandRegistry = registry.clone();
let func = self.func; let func = self.func;
let result = input.map(move |it| { let stream = async_stream! {
let registry = registry.clone(); while let Some(it) = input.next().await {
let call_info = match call_info.clone().evaluate_with_new_it(&registry, &it) { let registry = registry.clone();
Err(err) => return OutputStream::from(vec![Err(err)]).values, let call_info = match call_info.clone().evaluate_with_new_it(&registry, &it).await {
Ok(args) => args, Err(err) => { yield Err(err); return; },
}; Ok(args) => args,
};
let args = EvaluatedFilterCommandArgs::new( let args = EvaluatedFilterCommandArgs::new(
host.clone(), host.clone(),
ctrl_c.clone(), ctrl_c.clone(),
shell_manager.clone(), shell_manager.clone(),
call_info, call_info,
); );
match func(args) { match func(args) {
Err(err) => OutputStream::from(vec![Err(err)]).values, Err(err) => yield Err(err),
Ok(stream) => stream.values, Ok(mut stream) => {
while let Some(value) = stream.values.next().await {
yield value;
}
}
}
} }
}); };
let result = result.flatten(); Ok(stream.to_output_stream())
let result: BoxStream<ReturnValue> = result.boxed();
Ok(result.into())
} }
} }
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Arc<Command> { pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command {
Arc::new(Command::WholeStream(Arc::new(command))) Command(Arc::new(command))
}
pub fn per_item_command(command: impl PerItemCommand + 'static) -> Arc<Command> {
Arc::new(Command::PerItem(Arc::new(command)))
} }

View File

@ -1,9 +1,10 @@
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry; use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use futures::future;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged; use nu_source::Tagged;
pub struct Compact; pub struct Compact;
@ -13,6 +14,7 @@ pub struct CompactArgs {
rest: Vec<Tagged<String>>, rest: Vec<Tagged<String>>,
} }
#[async_trait]
impl WholeStreamCommand for Compact { impl WholeStreamCommand for Compact {
fn name(&self) -> &str { fn name(&self) -> &str {
"compact" "compact"
@ -26,36 +28,78 @@ impl WholeStreamCommand for Compact {
"Creates a table with non-empty rows" "Creates a table with non-empty rows"
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, compact)?.run() compact(args, registry).await
}
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( pub async fn compact(
CompactArgs { rest: columns }: CompactArgs, args: CommandArgs,
RunnableContext { input, .. }: RunnableContext, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let objects = input.filter(move |item| { let registry = registry.clone();
let keep = if columns.is_empty() { let (CompactArgs { rest: columns }, input) = args.process(&registry).await?;
item.is_some() Ok(input
} else { .filter_map(move |item| {
match item { future::ready(if columns.is_empty() {
Value { if !item.is_empty() {
value: UntaggedValue::Row(ref r), Some(ReturnSuccess::value(item))
.. } else {
} => columns None
.iter() }
.all(|field| r.get_data(field).borrow().is_some()), } else {
_ => false, match item {
} Value {
}; value: UntaggedValue::Row(ref r),
..
futures::future::ready(keep) } => {
}); if columns
.iter()
Ok(objects.from_input_stream()) .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>, path: Tagged<bool>,
} }
#[async_trait]
impl WholeStreamCommand for Config { impl WholeStreamCommand for Config {
fn name(&self) -> &str { fn name(&self) -> &str {
"config" "config"
@ -30,7 +31,7 @@ impl WholeStreamCommand for Config {
.named( .named(
"load", "load",
SyntaxShape::Path, SyntaxShape::Path,
"load the config from the path give", "load the config from the path given",
Some('l'), Some('l'),
) )
.named( .named(
@ -65,124 +66,195 @@ impl WholeStreamCommand for Config {
"Configuration management." "Configuration management."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, config)?.run() config(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "See all config values",
example: "config",
result: None,
},
Example {
description: "Set completion_mode to circular",
example: "config --set [completion_mode circular]",
result: None,
},
Example {
description: "Store the contents of the pipeline as a path",
example: "echo ['/usr/bin' '/bin'] | config --set_into path",
result: None,
},
Example {
description: "Get the current startup commands",
example: "config --get startup",
result: None,
},
Example {
description: "Remove the startup commands",
example: "config --remove startup",
result: None,
},
Example {
description: "Clear the config (be careful!)",
example: "config --clear",
result: None,
},
Example {
description: "Get the path to the current config file",
example: "config --path",
result: None,
},
]
} }
} }
pub fn config( pub async fn config(
ConfigArgs { args: CommandArgs,
load, registry: &CommandRegistry,
set,
set_into,
get,
clear,
remove,
path,
}: ConfigArgs,
RunnableContext { name, input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> { ) -> 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 (
let configuration = if let Some(supplied) = load { ConfigArgs {
Some(supplied.item().clone()) load,
} else { set,
None set_into,
}; get,
clear,
remove,
path,
},
input,
) = args.process(&registry).await?;
let mut result = crate::data::config::read(name_span, &configuration)?; let configuration = if let Some(supplied) = load {
Some(supplied.item().clone())
if let Some(v) = get { } else {
let key = v.to_string(); None
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));
}
}; };
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 crate::prelude::*;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value}; use nu_protocol::{Signature, UntaggedValue, Value};
pub struct Count; pub struct Count;
#[derive(Deserialize)] #[async_trait]
pub struct CountArgs {}
impl WholeStreamCommand for Count { impl WholeStreamCommand for Count {
fn name(&self) -> &str { fn name(&self) -> &str {
"count" "count"
@ -20,27 +18,39 @@ impl WholeStreamCommand for Count {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Show the total number of rows." "Show the total number of rows or items."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, _registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> 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) -> 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( #[cfg(test)]
CountArgs {}: CountArgs, mod tests {
RunnableContext { input, name, .. }: RunnableContext, use super::Count;
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let rows: Vec<Value> = input.collect().await;
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

@ -1,8 +1,8 @@
use crate::commands::command::RunnablePerItemContext; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry; use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{CallInfo, Signature, SyntaxShape, Value}; use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged; use nu_source::Tagged;
use std::path::PathBuf; use std::path::PathBuf;
@ -15,7 +15,8 @@ pub struct CopyArgs {
pub recursive: Tagged<bool>, pub recursive: Tagged<bool>,
} }
impl PerItemCommand for Cpy { #[async_trait]
impl WholeStreamCommand for Cpy {
fn name(&self) -> &str { fn name(&self) -> &str {
"cp" "cp"
} }
@ -35,20 +36,41 @@ impl PerItemCommand for Cpy {
"Copy files." "Copy files."
} }
fn run( async fn run(
&self, &self,
call_info: &CallInfo, args: CommandArgs,
_registry: &CommandRegistry, registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
call_info let shell_manager = args.shell_manager.clone();
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), cp)? let name = args.call_info.name_tag.clone();
.run() let (args, _) = args.process(&registry).await?;
shell_manager.cp(args, name)
}
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,
},
]
} }
} }
fn cp(args: CopyArgs, context: &RunnablePerItemContext) -> Result<OutputStream, ShellError> { #[cfg(test)]
let shell_manager = context.shell_manager.clone(); mod tests {
shell_manager.cp(args, context) use super::Cpy;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Cpy {})
}
} }

View File

@ -11,6 +11,7 @@ use nu_protocol::{Signature, UntaggedValue};
pub struct Date; pub struct Date;
#[async_trait]
impl WholeStreamCommand for Date { impl WholeStreamCommand for Date {
fn name(&self) -> &str { fn name(&self) -> &str {
"date" "date"
@ -26,12 +27,27 @@ impl WholeStreamCommand for Date {
"Get the current datetime." "Get the current datetime."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
date(args, registry) date(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get the current local time and date",
example: "date",
result: None,
},
Example {
description: "Get the current UTC time and date",
example: "date --utc",
result: None,
},
]
} }
} }
@ -75,10 +91,13 @@ where
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag) UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
} }
pub fn date(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { pub async fn date(
let args = args.evaluate_once(registry)?; args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let mut date_out = VecDeque::new();
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let value = if args.has("utc") { let value = if args.has("utc") {
@ -89,7 +108,17 @@ pub fn date(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
date_to_value(local, tag) date_to_value(local, tag)
}; };
date_out.push_back(value); Ok(OutputStream::one(value))
}
Ok(futures::stream::iter(date_out).to_output_stream())
#[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, raw: bool,
} }
#[async_trait]
impl WholeStreamCommand for Debug { impl WholeStreamCommand for Debug {
fn name(&self) -> &str { fn name(&self) -> &str {
"debug" "debug"
@ -23,19 +24,21 @@ impl WholeStreamCommand for Debug {
"Print the Rust debug representation of the values" "Print the Rust debug representation of the values"
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, debug_value)?.run() debug_value(args, registry).await
} }
} }
fn debug_value( async fn debug_value(
DebugArgs { raw }: DebugArgs, args: CommandArgs,
RunnableContext { input, .. }: RunnableContext, registry: &CommandRegistry,
) -> Result<impl ToOutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (DebugArgs { raw }, input) = args.process(&registry).await?;
Ok(input Ok(input
.map(move |v| { .map(move |v| {
if raw { if raw {
@ -48,3 +51,15 @@ fn debug_value(
}) })
.to_output_stream()) .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; pub struct Default;
#[async_trait]
impl WholeStreamCommand for Default { impl WholeStreamCommand for Default {
fn name(&self) -> &str { fn name(&self) -> &str {
"default" "default"
@ -33,23 +34,32 @@ impl WholeStreamCommand for Default {
"Sets a default row's column if missing." "Sets a default row's column if missing."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, default)?.run() default(args, registry).await
}
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( async fn default(
DefaultArgs { column, value }: DefaultArgs, args: CommandArgs,
RunnableContext { input, .. }: RunnableContext, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let stream = input let registry = registry.clone();
.map(move |item| { let (DefaultArgs { column, value }, input) = args.process(&registry).await?;
let mut result = VecDeque::new();
Ok(input
.map(move |item| {
let should_add = match item { let should_add = match item {
Value { Value {
value: UntaggedValue::Row(ref r), value: UntaggedValue::Row(ref r),
@ -60,16 +70,24 @@ fn default(
if should_add { if should_add {
match item.insert_data_at_path(&column.item, value.clone()) { match item.insert_data_at_path(&column.item, value.clone()) {
Some(new_value) => result.push_back(ReturnSuccess::value(new_value)), Some(new_value) => ReturnSuccess::value(new_value),
None => result.push_back(ReturnSuccess::value(item)), None => ReturnSuccess::value(item),
} }
} else { } else {
result.push_back(ReturnSuccess::value(item)); ReturnSuccess::value(item)
} }
futures::stream::iter(result)
}) })
.flatten(); .to_output_stream())
}
Ok(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,83 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
pub struct Drop;
#[derive(Deserialize)]
pub struct DropArgs {
rows: Option<Tagged<u64>>,
}
#[async_trait]
impl WholeStreamCommand for Drop {
fn name(&self) -> &str {
"drop"
}
fn signature(&self) -> Signature {
Signature::build("drop").optional(
"rows",
SyntaxShape::Number,
"starting from the back, the number of rows to drop",
)
}
fn usage(&self) -> &str {
"Drop the last number of rows."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let (DropArgs { rows }, input) = args.process(&registry).await?;
let mut v: Vec<_> = input.into_vec().await;
let rows_to_drop = if let Some(quantity) = rows {
*quantity as usize
} else {
1
};
for _ in 0..rows_to_drop {
v.pop();
}
Ok(futures::stream::iter(v).to_output_stream())
}
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

@ -1,14 +1,13 @@
extern crate filesize; use crate::commands::WholeStreamCommand;
use crate::commands::command::RunnablePerItemContext;
use crate::prelude::*; use crate::prelude::*;
use filesize::file_real_size_fast; use filesize::file_real_size_fast;
use glob::*; use glob::*;
use indexmap::map::IndexMap; use indexmap::map::IndexMap;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{CallInfo, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged; use nu_source::Tagged;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::atomic::Ordering;
const NAME: &str = "du"; const NAME: &str = "du";
const GLOB_PARAMS: MatchOptions = MatchOptions { const GLOB_PARAMS: MatchOptions = MatchOptions {
@ -31,7 +30,8 @@ pub struct DuArgs {
min_size: Option<Tagged<u64>>, min_size: Option<Tagged<u64>>,
} }
impl PerItemCommand for Du { #[async_trait]
impl WholeStreamCommand for Du {
fn name(&self) -> &str { fn name(&self) -> &str {
NAME NAME
} }
@ -73,22 +73,30 @@ impl PerItemCommand for Du {
"Find disk usage sizes of specified items" "Find disk usage sizes of specified items"
} }
fn run( async fn run(
&self, &self,
call_info: &CallInfo, args: CommandArgs,
_registry: &CommandRegistry, registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
call_info du(args, registry).await
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), du)? }
.run()
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Disk usage of the current directory",
example: "du",
result: None,
}]
} }
} }
fn du(args: DuArgs, ctx: &RunnablePerItemContext) -> Result<OutputStream, ShellError> { async fn du(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let tag = ctx.name.clone(); 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| { let exclude = args.exclude.map_or(Ok(None), move |x| {
Pattern::new(&x.item) Pattern::new(&x.item)
.map(Option::Some) .map(Option::Some)
@ -117,7 +125,6 @@ fn du(args: DuArgs, ctx: &RunnablePerItemContext) -> Result<OutputStream, ShellE
}) })
.map(|v| v.map_err(glob_err_into)); .map(|v| v.map_err(glob_err_into));
let ctrl_c = ctx.ctrl_c.clone();
let all = args.all; let all = args.all;
let deref = args.deref; let deref = args.deref;
let max_depth = args.max_depth.map(|f| f.item); let max_depth = args.max_depth.map(|f| f.item);
@ -131,25 +138,30 @@ fn du(args: DuArgs, ctx: &RunnablePerItemContext) -> Result<OutputStream, ShellE
all, all,
}; };
let stream = futures::stream::iter(paths) let inp = 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),
});
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())
} }
struct DirBuilder { pub struct DirBuilder {
tag: Tag, tag: Tag,
min: Option<u64>, min: Option<u64>,
deref: bool, deref: bool,
@ -157,7 +169,25 @@ struct DirBuilder {
all: bool, all: bool,
} }
struct DirInfo { impl DirBuilder {
pub fn new(
tag: Tag,
min: Option<u64>,
deref: bool,
exclude: Option<Pattern>,
all: bool,
) -> DirBuilder {
DirBuilder {
tag,
min,
deref,
exclude,
all,
}
}
}
pub struct DirInfo {
dirs: Vec<DirInfo>, dirs: Vec<DirInfo>,
files: Vec<FileInfo>, files: Vec<FileInfo>,
errors: Vec<ShellError>, errors: Vec<ShellError>,
@ -200,7 +230,12 @@ impl FileInfo {
} }
impl DirInfo { impl DirInfo {
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 path = path.into();
let mut s = Self { let mut s = Self {
@ -216,9 +251,15 @@ impl DirInfo {
match std::fs::read_dir(&s.path) { match std::fs::read_dir(&s.path) {
Ok(d) => { Ok(d) => {
for f in d { for f in d {
if ctrl_c.load(Ordering::SeqCst) {
break;
}
match f { match f {
Ok(i) => match i.file_type() { 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), Ok(_t) => s = s.add_file(i.path(), &params),
Err(e) => s = s.add_error(e.into()), Err(e) => s = s.add_error(e.into()),
}, },
@ -236,6 +277,7 @@ impl DirInfo {
path: impl Into<PathBuf>, path: impl Into<PathBuf>,
mut depth: Option<u64>, mut depth: Option<u64>,
params: &DirBuilder, params: &DirBuilder,
ctrl_c: Arc<AtomicBool>,
) -> Self { ) -> Self {
if let Some(current) = depth { if let Some(current) = depth {
if let Some(new) = current.checked_sub(1) { if let Some(new) = current.checked_sub(1) {
@ -245,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.size += d.size;
self.blocks += d.blocks; self.blocks += d.blocks;
self.dirs.push(d); self.dirs.push(d);
@ -280,6 +322,10 @@ impl DirInfo {
self.errors.push(e); self.errors.push(e);
self self
} }
pub fn get_size(&self) -> u64 {
self.size
}
} }
fn glob_err_into(e: GlobError) -> ShellError { fn glob_err_into(e: GlobError) -> ShellError {
@ -374,3 +420,15 @@ impl From<FileInfo> for Value {
UntaggedValue::row(r).retag(&f.tag) 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

@ -1,16 +1,24 @@
use crate::commands::classified::block::run_block; use crate::commands::classified::block::run_block;
use crate::commands::PerItemCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry; use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use futures::stream::once; use futures::stream::once;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{
hir::Block, hir::Expression, hir::SpannedExpression, hir::Synthetic, Scope, Signature,
SyntaxShape, UntaggedValue, Value,
};
pub struct Each; pub struct Each;
impl PerItemCommand for Each { #[derive(Deserialize)]
pub struct EachArgs {
block: Block,
}
#[async_trait]
impl WholeStreamCommand for Each {
fn name(&self) -> &str { fn name(&self) -> &str {
"each" "each"
} }
@ -27,60 +35,107 @@ impl PerItemCommand for Each {
"Run a block on each row of the table." "Run a block on each row of the table."
} }
fn run( async fn run(
&self, &self,
call_info: &CallInfo, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
raw_args: &RawCommandArgs,
input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let call_info = call_info.clone(); each(args, registry).await
let registry = registry.clone(); }
let raw_args = raw_args.clone();
let stream = async_stream! {
match call_info.args.expect_nth(0)? {
Value {
value: UntaggedValue::Block(block),
tag
} => {
let mut context = Context::from_raw(&raw_args, &registry);
let input_clone = input.clone();
let input_stream = once(async { Ok(input) }).to_input_stream();
let result = run_block( fn examples(&self) -> Vec<Example> {
block, vec![
&mut context, Example {
input_stream, description: "Echo the square of each integer",
&Scope::new(input_clone), example: "echo [1 2 3] | each { echo $(= $it * $it) }",
).await; result: Some(vec![
UntaggedValue::int(1).into(),
match result { UntaggedValue::int(4).into(),
Ok(mut stream) => { UntaggedValue::int(9).into(),
let errors = context.get_errors(); ]),
if let Some(error) = errors.first() { },
yield Err(error.clone()); Example {
return; description: "Echo the sum of each row",
} example: "echo [[1 2] [3 4]] | each { echo $it | sum }",
result: Some(vec![
while let Some(result) = stream.next().await { UntaggedValue::int(3).into(),
yield Ok(ReturnSuccess::Value(result)); UntaggedValue::int(7).into(),
} ]),
} },
Err(e) => { ]
yield Err(e); }
} }
}
} fn is_expanded_it_usage(head: &SpannedExpression) -> bool {
Value { tag, .. } => { match &*head {
yield Err(ShellError::labeled_error( SpannedExpression {
"Expected a block", expr: Expression::Synthetic(Synthetic::String(s)),
"each needs a block", ..
tag, } if s == "expanded-each" => true,
)) _ => false,
} }
}; }
};
async fn process_row(
Ok(stream.to_output_stream()) block: Arc<Block>,
scope: Arc<Scope>,
head: Arc<Box<SpannedExpression>>,
mut context: Arc<Context>,
input: Value,
) -> Result<OutputStream, ShellError> {
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())
}
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)),
}
}
})
.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,10 +1,20 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{CallInfo, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::hir::Operator;
use nu_protocol::{
Primitive, RangeInclusion, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
pub struct Echo; pub struct Echo;
impl PerItemCommand for Echo { #[derive(Deserialize)]
pub struct EchoArgs {
pub rest: Vec<Value>,
}
#[async_trait]
impl WholeStreamCommand for Echo {
fn name(&self) -> &str { fn name(&self) -> &str {
"echo" "echo"
} }
@ -17,31 +27,41 @@ impl PerItemCommand for Echo {
"Echo the arguments back to the user." "Echo the arguments back to the user."
} }
fn run( async fn run(
&self, &self,
call_info: &CallInfo, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
run(call_info, registry, raw_args) echo(args, registry)
}
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 run( fn echo(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
call_info: &CallInfo, let registry = registry.clone();
_registry: &CommandRegistry, let stream = async_stream! {
_raw_args: &RawCommandArgs, let (args, _): (EchoArgs, _) = args.process(&registry).await?;
) -> Result<OutputStream, ShellError> {
let mut output = vec![];
if let Some(ref positional) = call_info.args.positional { for i in args.rest {
for i in positional {
match i.as_string() { match i.as_string() {
Ok(s) => { Ok(s) => {
output.push(Ok(ReturnSuccess::Value( yield Ok(ReturnSuccess::Value(
UntaggedValue::string(s).into_value(i.tag.clone()), UntaggedValue::string(s).into_value(i.tag.clone()),
))); ));
} }
_ => match i { _ => match i {
Value { Value {
@ -49,19 +69,59 @@ fn run(
.. ..
} => { } => {
for value in table { for value in table {
output.push(Ok(ReturnSuccess::Value(value.clone()))); yield Ok(ReturnSuccess::Value(value.clone()));
}
}
Value {
value: UntaggedValue::Primitive(Primitive::Range(range)),
tag
} => {
let mut current = range.from.0.item;
while current != range.to.0.item {
yield 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,
_ => {
yield Err(ShellError::unimplemented("Internal error: expected a primitive result from increment"));
return;
}
},
Err((left_type, right_type)) => {
yield Err(ShellError::coerce_error(
left_type.spanned(tag.span),
right_type.spanned(tag.span),
));
return;
}
}
}
match range.to.1 {
RangeInclusion::Inclusive => {
yield Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag)));
}
_ => {}
} }
} }
_ => { _ => {
output.push(Ok(ReturnSuccess::Value(i.clone()))); yield 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(stream.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

@ -1,72 +0,0 @@
use crate::commands::PerItemCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CallInfo, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_value_ext::ValueExt;
pub struct Edit;
impl PerItemCommand for Edit {
fn name(&self) -> &str {
"edit"
}
fn signature(&self) -> Signature {
Signature::build("edit")
.required(
"Field",
SyntaxShape::ColumnPath,
"the name of the column to edit",
)
.required(
"Value",
SyntaxShape::String,
"the new value to give the cell(s)",
)
}
fn usage(&self) -> &str {
"Edit an existing column to have a new value."
}
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
value: Value,
) -> Result<OutputStream, ShellError> {
let value_tag = value.tag();
let field = call_info.args.expect_nth(0)?.as_column_path()?;
let replacement = call_info.args.expect_nth(1)?.tagged_unknown();
let stream = match value {
obj
@
Value {
value: UntaggedValue::Row(_),
..
} => match obj.replace_data_at_column_path(&field, replacement.item.clone()) {
Some(v) => futures::stream::iter(vec![Ok(ReturnSuccess::Value(v))]),
None => {
return Err(ShellError::labeled_error(
"edit could not find place to insert column",
"column name",
&field.tag,
))
}
},
_ => {
return Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
value_tag,
))
}
};
Ok(stream.to_output_stream())
}
}

View File

@ -1,15 +1,23 @@
use crate::commands::PerItemCommand;
use crate::commands::UnevaluatedCallInfo; use crate::commands::UnevaluatedCallInfo;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry; use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{
CallInfo, CommandAction, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value, CommandAction, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
}; };
use nu_source::Tagged;
use std::path::PathBuf;
pub struct Enter; pub struct Enter;
impl PerItemCommand for Enter { #[derive(Deserialize)]
pub struct EnterArgs {
location: Tagged<PathBuf>,
}
#[async_trait]
impl WholeStreamCommand for Enter {
fn name(&self) -> &str { fn name(&self) -> &str {
"enter" "enter"
} }
@ -26,132 +34,151 @@ impl PerItemCommand for Enter {
"Create a new shell and begin at this path." "Create a new shell and begin at this path."
} }
fn run( async fn run(
&self, &self,
call_info: &CallInfo, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); enter(args, registry)
let raw_args = raw_args.clone(); }
match call_info.args.expect_nth(0)? {
Value {
value: UntaggedValue::Primitive(Primitive::Path(location)),
tag,
..
} => {
let location_string = location.display().to_string();
let location_clone = location_string.clone();
let tag_clone = tag.clone();
if location_string.starts_with("help") { fn examples(&self) -> Vec<Example> {
let spec = location_string.split(':').collect::<Vec<&str>>(); vec![
Example {
if spec.len() == 2 { description: "Enter a path as a new shell",
let (_, command) = (spec[0], spec[1]); example: "enter ../projectB",
result: None,
if registry.has(command) { },
return Ok(vec![Ok(ReturnSuccess::Action( Example {
CommandAction::EnterHelpShell( description: "Enter a file as a new shell",
UntaggedValue::string(command).into_value(Tag::unknown()), example: "enter package.json",
), result: None,
))] },
.into()); ]
} }
} }
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
UntaggedValue::nothing().into_value(Tag::unknown()), fn enter(raw_args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
)))] let registry = registry.clone();
.into()) let stream = async_stream! {
} else if location.is_dir() { let scope = raw_args.call_info.scope.clone();
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterShell( let shell_manager = raw_args.shell_manager.clone();
location_clone, let head = raw_args.call_info.args.head.clone();
)))] let ctrl_c = raw_args.ctrl_c.clone();
.into()) let current_errors = raw_args.current_errors.clone();
} else { let host = raw_args.host.clone();
let stream = async_stream! { let tag = raw_args.call_info.name_tag.clone();
// If it's a file, attempt to open the file as a value and enter it let (EnterArgs { location }, _) = raw_args.process(&registry).await?;
let cwd = raw_args.shell_manager.path(); let location_string = location.display().to_string();
let location_clone = location_string.clone();
let full_path = std::path::PathBuf::from(cwd);
if location_string.starts_with("help") {
let (file_extension, contents, contents_tag) = let spec = location_string.split(':').collect::<Vec<&str>>();
crate::commands::open::fetch(
&full_path, if spec.len() == 2 {
&location_clone, let (_, command) = (spec[0], spec[1]);
tag_clone.span,
).await?; if registry.has(command) {
yield Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
match contents { UntaggedValue::string(command).into_value(Tag::unknown()),
UntaggedValue::Primitive(Primitive::String(_)) => { )));
let tagged_contents = contents.into_value(&contents_tag); return;
}
if let Some(extension) = file_extension { }
let command_name = format!("from-{}", extension); yield Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
if let Some(converter) = UntaggedValue::nothing().into_value(Tag::unknown()),
registry.get_command(&command_name) )));
{ } else if location.is_dir() {
let new_args = RawCommandArgs { yield Ok(ReturnSuccess::Action(CommandAction::EnterShell(
host: raw_args.host, location_clone,
ctrl_c: raw_args.ctrl_c, )));
shell_manager: raw_args.shell_manager, } else {
call_info: UnevaluatedCallInfo { // If it's a file, attempt to open the file as a value and enter it
args: nu_protocol::hir::Call { let cwd = shell_manager.path();
head: raw_args.call_info.args.head,
positional: None, let full_path = std::path::PathBuf::from(cwd);
named: None,
span: Span::unknown(), let (file_extension, contents, contents_tag) =
is_last: false, crate::commands::open::fetch(
}, &full_path,
name_tag: raw_args.call_info.name_tag, &PathBuf::from(location_clone),
scope: raw_args.call_info.scope.clone() tag.span,
}, ).await?;
};
let mut result = converter.run( match contents {
new_args.with_input(vec![tagged_contents]), UntaggedValue::Primitive(Primitive::String(_)) => {
&registry, let tagged_contents = contents.into_value(&contents_tag);
);
let result_vec: Vec<Result<ReturnSuccess, ShellError>> = if let Some(extension) = file_extension {
result.drain_vec().await; let command_name = format!("from {}", extension);
for res in result_vec { if let Some(converter) =
match res { registry.get_command(&command_name)
Ok(ReturnSuccess::Value(Value { {
value, let new_args = RawCommandArgs {
.. host,
})) => { ctrl_c,
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell( current_errors,
Value { shell_manager,
value, call_info: UnevaluatedCallInfo {
tag: contents_tag.clone(), args: nu_protocol::hir::Call {
}))); head,
} positional: None,
x => yield x, named: None,
} span: Span::unknown(),
} is_last: false,
} else { },
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents))); name_tag: tag.clone(),
} scope: scope.clone()
} else { },
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents))); };
} let mut result = converter.run(
} new_args.with_input(vec![tagged_contents]),
_ => { &registry,
let tagged_contents = contents.into_value(contents_tag); ).await;
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents))); result.drain_vec().await;
} for res in result_vec {
} match res {
}; Ok(ReturnSuccess::Value(Value {
Ok(stream.to_output_stream()) value,
} ..
} })) => {
x => Ok( yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(
vec![Ok(ReturnSuccess::Action(CommandAction::EnterValueShell( Value {
x.clone(), value,
)))] tag: contents_tag.clone(),
.into(), })));
), }
} x => yield x,
}
}
} else {
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
}
} else {
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
}
}
_ => {
let tagged_contents = contents.into_value(contents_tag);
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
}
}
}
};
Ok(stream.to_output_stream())
}
#[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>>, evaluate_with: Option<Tagged<String>>,
} }
#[async_trait]
impl WholeStreamCommand for EvaluateBy { impl WholeStreamCommand for EvaluateBy {
fn name(&self) -> &str { fn name(&self) -> &str {
"evaluate-by" "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." "Creates a new table with the data from the tables rows evaluated by the column given."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, evaluate_by)?.run() evaluate_by(args, registry).await
} }
} }
pub fn evaluate_by( pub async fn evaluate_by(
EvaluateByArgs { evaluate_with }: EvaluateByArgs, args: CommandArgs,
RunnableContext { input, name, .. }: RunnableContext, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let stream = async_stream! { let registry = registry.clone();
let values: Vec<Value> = input.collect().await; 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() { if values.is_empty() {
yield Err(ShellError::labeled_error( Err(ShellError::labeled_error(
"Expected table from pipeline", "Expected table from pipeline",
"requires a table input", "requires a table input",
name name,
)) ))
} else {
let evaluate_with = if let Some(evaluator) = evaluate_with {
Some(evaluator.item().clone())
} else { } else {
None
};
let evaluate_with = if let Some(evaluator) = evaluate_with { match evaluate(&values[0], evaluate_with, name) {
Some(evaluator.item().clone()) Ok(evaluated) => Ok(OutputStream::one(ReturnSuccess::value(evaluated))),
} else { Err(err) => Err(err),
None
};
match evaluate(&values[0], evaluate_with, name) {
Ok(evaluated) => yield ReturnSuccess::value(evaluated),
Err(err) => yield 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

@ -6,6 +6,7 @@ use nu_protocol::{CommandAction, ReturnSuccess, Signature};
pub struct Exit; pub struct Exit;
#[async_trait]
impl WholeStreamCommand for Exit { impl WholeStreamCommand for Exit {
fn name(&self) -> &str { fn name(&self) -> &str {
"exit" "exit"
@ -19,21 +20,54 @@ impl WholeStreamCommand for Exit {
"Exit the current shell (or all shells)" "Exit the current shell (or all shells)"
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
exit(args, registry) exit(args, registry).await
}
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> { pub async fn exit(
let args = args.evaluate_once(registry)?; 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") { let command_action = if args.call_info.args.has("now") {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::Exit))].into()) CommandAction::Exit
} else { } 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::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape}; use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged; use nu_source::Tagged;
pub struct First; pub struct First;
@ -12,6 +12,7 @@ pub struct FirstArgs {
rows: Option<Tagged<usize>>, rows: Option<Tagged<usize>>,
} }
#[async_trait]
impl WholeStreamCommand for First { impl WholeStreamCommand for First {
fn name(&self) -> &str { fn name(&self) -> &str {
"first" "first"
@ -29,24 +30,53 @@ impl WholeStreamCommand for First {
"Show only the first number of rows." "Show only the first number of rows."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, first)?.run() first(args, registry).await
}
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( async fn first(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
FirstArgs { rows }: FirstArgs, let registry = registry.clone();
context: RunnableContext, let (FirstArgs { rows }, input) = args.process(&registry).await?;
) -> Result<OutputStream, ShellError> {
let rows_desired = if let Some(quantity) = rows { let rows_desired = if let Some(quantity) = rows {
*quantity *quantity
} else { } else {
1 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,17 +1,21 @@
use crate::commands::PerItemCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry; use crate::context::CommandRegistry;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
CallInfo, ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::Tagged; use nu_source::Tagged;
use nu_value_ext::{as_column_path, get_data_by_column_path};
use std::borrow::Borrow; use std::borrow::Borrow;
pub struct Format; pub struct Format;
impl PerItemCommand for Format { #[derive(Deserialize)]
pub struct FormatArgs {
pattern: Tagged<String>,
}
#[async_trait]
impl WholeStreamCommand for Format {
fn name(&self) -> &str { fn name(&self) -> &str {
"format" "format"
} }
@ -19,7 +23,7 @@ impl PerItemCommand for Format {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("format").required( Signature::build("format").required(
"pattern", "pattern",
SyntaxShape::Any, SyntaxShape::String,
"the pattern to output. Eg) \"{foo}: {bar}\"", "the pattern to output. Eg) \"{foo}: {bar}\"",
) )
} }
@ -28,63 +32,66 @@ impl PerItemCommand for Format {
"Format columns into a string using a simple pattern." "Format columns into a string using a simple pattern."
} }
fn run( async fn run(
&self, &self,
call_info: &CallInfo, args: CommandArgs,
_registry: &CommandRegistry, registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
value: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
//let value_tag = value.tag(); format_command(args, registry)
let pattern = call_info.args.expect_nth(0)?; }
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Print filenames with their sizes",
example: "ls | format '{name}: {size}'",
result: None,
}]
}
}
fn format_command(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let scope = args.call_info.scope.clone();
let (FormatArgs { pattern }, mut input) = args.process(&registry).await?;
let pattern_tag = pattern.tag.clone(); let pattern_tag = pattern.tag.clone();
let pattern = pattern.as_string()?;
let format_pattern = format(&pattern); let format_pattern = format(&pattern);
let commands = format_pattern; let commands = format_pattern;
let output = match value { while let Some(value) = input.next().await {
value let mut output = String::new();
@
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut output = String::new();
for command in &commands { for command in &commands {
match command { match command {
FormatCommand::Text(s) => { FormatCommand::Text(s) => {
output.push_str(&s); output.push_str(&s);
} }
FormatCommand::Column(c) => { FormatCommand::Column(c) => {
let key = to_column_path(&c, &pattern_tag)?; // 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( let result = evaluate_baseline_expr(&full_column_path.0, &registry, &value, &scope.vars, &scope.env).await;
&value,
&key,
Box::new(move |(_, _, error)| error),
);
if let Ok(c) = fetcher { if let Ok(c) = result {
output output
.push_str(&value::format_leaf(c.borrow()).plain_string(100_000)) .push_str(&value::format_leaf(c.borrow()).plain_string(100_000))
} } else {
// That column doesn't match, so don't emit anything // That column doesn't match, so don't emit anything
} }
} }
} }
output
} }
_ => String::new(),
};
Ok(futures::stream::iter(vec![ReturnSuccess::value( yield ReturnSuccess::value(
UntaggedValue::string(output).into_untagged_value(), UntaggedValue::string(output).into_untagged_value())
)]) }
.to_output_stream()) };
}
Ok(stream.to_output_stream())
} }
#[derive(Debug)] #[derive(Debug)]
@ -132,26 +139,14 @@ fn format(input: &str) -> Vec<FormatCommand> {
output output
} }
fn to_column_path( #[cfg(test)]
path_members: &str, mod tests {
tag: impl Into<Tag>, use super::Format;
) -> Result<Tagged<ColumnPath>, ShellError> {
let tag = tag.into();
as_column_path( #[test]
&UntaggedValue::Table( fn examples_work_as_expected() {
path_members use crate::examples::test as test_examples;
.split('.')
.map(|x| {
let member = match x.parse::<u64>() {
Ok(v) => UntaggedValue::int(v),
Err(_) => UntaggedValue::string(x),
};
member.into_value(&tag) test_examples(Format {})
}) }
.collect(),
)
.into_value(&tag),
)
} }

View File

@ -0,0 +1,49 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
pub struct From;
#[async_trait]
impl WholeStreamCommand for From {
fn name(&self) -> &str {
"from"
}
fn signature(&self) -> Signature {
Signature::build("from")
}
fn usage(&self) -> &str {
"Parse content (string or binary) as a table (input format based on subcommand, like csv, ini, json, toml)"
}
async fn run(
&self,
_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
yield Ok(ReturnSuccess::Value(
UntaggedValue::string(crate::commands::help::get_help(&From, &registry))
.into_value(Tag::unknown()),
));
};
Ok(stream.to_output_stream())
}
}
#[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,25 +8,34 @@ use std::str::FromStr;
pub struct FromBSON; pub struct FromBSON;
#[async_trait]
impl WholeStreamCommand for FromBSON { impl WholeStreamCommand for FromBSON {
fn name(&self) -> &str { fn name(&self) -> &str {
"from-bson" "from bson"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("from-bson") Signature::build("from bson")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Parse text as .bson and create table." "Parse binary as .bson and create table."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
from_bson(args, registry) from_bson(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Convert bson data to a table",
example: "open file.bin | from bson",
result: None,
}]
} }
} }
@ -199,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) convert_bson_value_to_nu_value(&Bson::Array(docs), tag)
} }
fn from_bson(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn from_bson(
let args = args.evaluate_once(registry)?; args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag(); let tag = args.name_tag();
let input = args.input; let input = args.input;
let 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()) { match from_bson_bytes_to_value(bytes.item, tag.clone()) {
Ok(x) => yield ReturnSuccess::value(x), Ok(x) => Ok(OutputStream::one(ReturnSuccess::value(x))),
Err(_) => { Err(_) => Err(ShellError::labeled_error_with_secondary(
yield Err(ShellError::labeled_error_with_secondary( "Could not parse as BSON",
"Could not parse as BSON", "input cannot be parsed as BSON",
"input cannot be parsed as BSON", tag.clone(),
tag.clone(), "value originates from here",
"value originates from here", bytes.tag,
bytes.tag, )),
)) }
} }
}
}; #[cfg(test)]
mod tests {
Ok(stream.to_output_stream()) use super::FromBSON;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(FromBSON {})
}
} }

View File

@ -12,13 +12,14 @@ pub struct FromCSVArgs {
separator: Option<Value>, separator: Option<Value>,
} }
#[async_trait]
impl WholeStreamCommand for FromCSV { impl WholeStreamCommand for FromCSV {
fn name(&self) -> &str { fn name(&self) -> &str {
"from-csv" "from csv"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("from-csv") Signature::build("from csv")
.named( .named(
"separator", "separator",
SyntaxShape::String, SyntaxShape::String,
@ -36,22 +37,49 @@ impl WholeStreamCommand for FromCSV {
"Parse text as .csv and create table." "Parse text as .csv and create table."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, from_csv)?.run() from_csv(args, registry).await
}
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( async fn from_csv(
FromCSVArgs { args: CommandArgs,
headerless, registry: &CommandRegistry,
separator,
}: FromCSVArgs,
runnable_context: RunnableContext,
) -> Result<OutputStream, ShellError> { ) -> 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 { let sep = match separator {
Some(Value { Some(Value {
value: UntaggedValue::Primitive(Primitive::String(s)), value: UntaggedValue::Primitive(Primitive::String(s)),
@ -75,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 crate::prelude::*;
use csv::{ErrorKind, ReaderBuilder}; use csv::{ErrorKind, ReaderBuilder};
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, TaggedDictBuilder, UntaggedValue, Value}; use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
fn from_delimited_string_to_value( fn from_delimited_string_to_value(
s: String, s: String,
@ -41,44 +41,40 @@ fn from_delimited_string_to_value(
Ok(UntaggedValue::Table(rows).into_value(&tag)) Ok(UntaggedValue::Table(rows).into_value(&tag))
} }
pub fn from_delimited_data( pub async fn from_delimited_data(
headerless: bool, headerless: bool,
sep: char, sep: char,
format_name: &'static str, format_name: &'static str,
RunnableContext { input, name, .. }: RunnableContext, input: InputStream,
name: Tag,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let name_tag = name; let name_tag = name;
let concat_string = input.collect_string(name_tag.clone()).await?;
let stream = async_stream! { match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) {
let concat_string = input.collect_string(name_tag.clone()).await?; 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()) { Err(ShellError::labeled_error_with_secondary(
Ok(x) => match x { line_one,
Value { value: UntaggedValue::Table(list), .. } => { line_two,
for l in list { name_tag.clone(),
yield ReturnSuccess::value(l); "value originates from here",
} concat_string.tag,
} ))
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,
))
} ,
} }
}; }
Ok(stream.to_output_stream())
} }
fn pretty_csv_error(err: csv::Error) -> Option<String> { fn pretty_csv_error(err: csv::Error) -> Option<String> {

View File

@ -0,0 +1,140 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use ::eml_parser::eml::*;
use ::eml_parser::EmlParser;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue};
use nu_source::Tagged;
pub struct FromEML;
const DEFAULT_BODY_PREVIEW: usize = 50;
#[derive(Deserialize, Clone)]
pub struct FromEMLArgs {
#[serde(rename(deserialize = "preview-body"))]
preview_body: Option<Tagged<usize>>,
}
#[async_trait]
impl WholeStreamCommand for FromEML {
fn name(&self) -> &str {
"from eml"
}
fn signature(&self) -> Signature {
Signature::build("from eml").named(
"preview-body",
SyntaxShape::Int,
"How many bytes of the body to preview",
Some('b'),
)
}
fn usage(&self) -> &str {
"Parse text as .eml and create table."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_eml(args, registry).await
}
}
fn emailaddress_to_value(tag: &Tag, email_address: &EmailAddress) -> TaggedDictBuilder {
let mut dict = TaggedDictBuilder::with_capacity(tag, 2);
let (n, a) = match email_address {
EmailAddress::AddressOnly { address } => {
(UntaggedValue::nothing(), UntaggedValue::string(address))
}
EmailAddress::NameAndEmailAddress { name, address } => {
(UntaggedValue::string(name), UntaggedValue::string(address))
}
};
dict.insert_untagged("Name", n);
dict.insert_untagged("Address", a);
dict
}
fn headerfieldvalue_to_value(tag: &Tag, value: &HeaderFieldValue) -> UntaggedValue {
use HeaderFieldValue::*;
match value {
SingleEmailAddress(address) => emailaddress_to_value(tag, address).into_untagged_value(),
MultipleEmailAddresses(addresses) => UntaggedValue::Table(
addresses
.iter()
.map(|a| emailaddress_to_value(tag, a).into_value())
.collect(),
),
Unstructured(s) => UntaggedValue::string(s),
Empty => UntaggedValue::nothing(),
}
}
async fn from_eml(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
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 body_preview = eml_args
.preview_body
.map(|b| b.item)
.unwrap_or(DEFAULT_BODY_PREVIEW);
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,
)
})?;
let mut dict = TaggedDictBuilder::new(&tag);
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(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));
}
if let Some(body) = eml.body {
dict.insert_untagged("Body", UntaggedValue::string(body));
}
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,20 +9,21 @@ use std::io::BufReader;
pub struct FromIcs; pub struct FromIcs;
#[async_trait]
impl WholeStreamCommand for FromIcs { impl WholeStreamCommand for FromIcs {
fn name(&self) -> &str { fn name(&self) -> &str {
"from-ics" "from ics"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("from-ics") Signature::build("from ics")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Parse text as .ics and create table." "Parse text as .ics and create table."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
@ -32,11 +33,12 @@ impl WholeStreamCommand for FromIcs {
} }
fn from_ics(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { fn from_ics(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?; let registry = registry.clone();
let tag = args.name_tag();
let input = args.input;
let stream = async_stream! { let stream = async_stream! {
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag();
let input = args.input;
let input_string = input.collect_string(tag.clone()).await?.item; let input_string = input.collect_string(tag.clone()).await?.item;
let input_bytes = input_string.as_bytes(); let input_bytes = input_string.as_bytes();
let buf_reader = BufReader::new(input_bytes); let buf_reader = BufReader::new(input_bytes);
@ -238,3 +240,15 @@ fn params_to_value(params: Vec<(String, Vec<String>)>, tag: Tag) -> Value {
row.into_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,30 +1,31 @@
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value}; use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value};
use std::collections::HashMap; use std::collections::HashMap;
pub struct FromINI; pub struct FromINI;
#[async_trait]
impl WholeStreamCommand for FromINI { impl WholeStreamCommand for FromINI {
fn name(&self) -> &str { fn name(&self) -> &str {
"from-ini" "from ini"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("from-ini") Signature::build("from ini")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Parse text as .ini and create table" "Parse text as .ini and create table"
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> 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)) Ok(convert_ini_top_to_nu_value(&v, tag))
} }
fn from_ini(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn from_ini(
let args = args.evaluate_once(registry)?; args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag(); let tag = args.name_tag();
let input = args.input; let input = args.input;
let concat_string = input.collect_string(tag.clone()).await?;
let stream = async_stream! { match from_ini_string_to_value(concat_string.item, tag.clone()) {
let concat_string = input.collect_string(tag.clone()).await?; Ok(x) => match x {
Value {
match from_ini_string_to_value(concat_string.item, tag.clone()) { value: UntaggedValue::Table(list),
Ok(x) => match x { ..
Value { value: UntaggedValue::Table(list), .. } => { } => Ok(futures::stream::iter(list).to_output_stream()),
for l in list { x => Ok(OutputStream::one(x)),
yield ReturnSuccess::value(l); },
} Err(_) => Err(ShellError::labeled_error_with_secondary(
} "Could not parse as INI",
x => yield ReturnSuccess::value(x), "input cannot be parsed as INI",
}, &tag,
Err(_) => { "value originates from here",
yield Err(ShellError::labeled_error_with_secondary( concat_string.tag,
"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() {
Ok(stream.to_output_stream()) use crate::examples::test as test_examples;
test_examples(FromINI {})
}
} }

View File

@ -10,13 +10,14 @@ pub struct FromJSONArgs {
objects: bool, objects: bool,
} }
#[async_trait]
impl WholeStreamCommand for FromJSON { impl WholeStreamCommand for FromJSON {
fn name(&self) -> &str { fn name(&self) -> &str {
"from-json" "from json"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("from-json").switch( Signature::build("from json").switch(
"objects", "objects",
"treat each line as a separate value", "treat each line as a separate value",
Some('o'), Some('o'),
@ -27,12 +28,12 @@ impl WholeStreamCommand for FromJSON {
"Parse text as .json and create table." "Parse text as .json and create table."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, from_json)?.run() from_json(args, registry)
} }
} }
@ -70,13 +71,12 @@ pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> serde_hjson:
Ok(convert_json_value_to_nu_value(&v, tag)) Ok(convert_json_value_to_nu_value(&v, tag))
} }
fn from_json( fn from_json(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
FromJSONArgs { objects }: FromJSONArgs, let name_tag = args.call_info.name_tag.clone();
RunnableContext { input, name, .. }: RunnableContext, let registry = registry.clone();
) -> Result<OutputStream, ShellError> {
let name_tag = name;
let stream = async_stream! { let stream = async_stream! {
let (FromJSONArgs { objects }, mut input) = args.process(&registry).await?;
let concat_string = input.collect_string(name_tag.clone()).await?; let concat_string = input.collect_string(name_tag.clone()).await?;
if objects { if objects {
@ -131,3 +131,15 @@ fn from_json(
Ok(stream.to_output_stream()) 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,13 +13,14 @@ pub struct FromODSArgs {
headerless: bool, headerless: bool,
} }
#[async_trait]
impl WholeStreamCommand for FromODS { impl WholeStreamCommand for FromODS {
fn name(&self) -> &str { fn name(&self) -> &str {
"from-ods" "from ods"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("from-ods").switch( Signature::build("from ods").switch(
"headerless", "headerless",
"don't treat the first row as column names", "don't treat the first row as column names",
None, None,
@ -30,25 +31,21 @@ impl WholeStreamCommand for FromODS {
"Parse OpenDocument Spreadsheet(.ods) data and create table." "Parse OpenDocument Spreadsheet(.ods) data and create table."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, from_ods)?.run() from_ods(args, registry)
} }
} }
fn from_ods( fn from_ods(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
FromODSArgs { let tag = args.call_info.name_tag.clone();
headerless: _headerless, let registry = registry.clone();
}: FromODSArgs,
runnable_context: RunnableContext,
) -> Result<OutputStream, ShellError> {
let input = runnable_context.input;
let tag = runnable_context.name;
let stream = async_stream! { let stream = async_stream! {
let (FromODSArgs { headerless: _headerless }, mut input) = args.process(&registry).await?;
let bytes = input.collect_binary(tag.clone()).await?; let bytes = input.collect_binary(tag.clone()).await?;
let mut buf: Cursor<Vec<u8>> = Cursor::new(bytes.item); let mut buf: Cursor<Vec<u8>> = Cursor::new(bytes.item);
let mut ods = Ods::<_>::new(buf).map_err(|_| ShellError::labeled_error( let mut ods = Ods::<_>::new(buf).map_err(|_| ShellError::labeled_error(
@ -96,3 +93,15 @@ fn from_ods(
Ok(stream.to_output_stream()) Ok(stream.to_output_stream())
} }
#[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,56 +1,58 @@
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value}; use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value};
use rusqlite::{types::ValueRef, Connection, Row, NO_PARAMS}; use rusqlite::{types::ValueRef, Connection, Row, NO_PARAMS};
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
pub struct FromSQLite; pub struct FromSQLite;
#[async_trait]
impl WholeStreamCommand for FromSQLite { impl WholeStreamCommand for FromSQLite {
fn name(&self) -> &str { fn name(&self) -> &str {
"from-sqlite" "from sqlite"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("from-sqlite") Signature::build("from sqlite")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Parse binary data as sqlite .db and create table." "Parse binary data as sqlite .db and create table."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
from_sqlite(args, registry) from_sqlite(args, registry).await
} }
} }
pub struct FromDB; pub struct FromDB;
#[async_trait]
impl WholeStreamCommand for FromDB { impl WholeStreamCommand for FromDB {
fn name(&self) -> &str { fn name(&self) -> &str {
"from-db" "from db"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("from-db") Signature::build("from db")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Parse binary data as db and create table." "Parse binary data as db and create table."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> 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_out = Vec::new();
let mut meta_stmt = conn.prepare("select name from sqlite_master where type='table'")?; let mut meta_stmt = conn.prepare("select name from sqlite_master where type='table'")?;
let mut meta_rows = meta_stmt.query(NO_PARAMS)?; let mut meta_rows = meta_stmt.query(NO_PARAMS)?;
while let Some(meta_row) = meta_rows.next()? { while let Some(meta_row) = meta_rows.next()? {
let table_name: String = meta_row.get(0)?; let table_name: String = meta_row.get(0)?;
let mut meta_dict = TaggedDictBuilder::new(tag.clone()); 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> { async fn from_sqlite(
let args = args.evaluate_once(registry)?; args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag(); let tag = args.name_tag();
let input = args.input; let input = args.input;
let stream = async_stream! { let bytes = input.collect_binary(tag.clone()).await?;
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,
))
}
}
};
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

@ -17,9 +17,10 @@ pub struct FromSSVArgs {
minimum_spaces: Option<Tagged<usize>>, minimum_spaces: Option<Tagged<usize>>,
} }
const STRING_REPRESENTATION: &str = "from-ssv"; const STRING_REPRESENTATION: &str = "from ssv";
const DEFAULT_MINIMUM_SPACES: usize = 2; const DEFAULT_MINIMUM_SPACES: usize = 2;
#[async_trait]
impl WholeStreamCommand for FromSSV { impl WholeStreamCommand for FromSSV {
fn name(&self) -> &str { fn name(&self) -> &str {
STRING_REPRESENTATION 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." "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, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, from_ssv)?.run() from_ssv(args, registry)
} }
} }
@ -250,15 +251,11 @@ fn from_ssv_string_to_value(
Some(UntaggedValue::Table(rows).into_value(&tag)) Some(UntaggedValue::Table(rows).into_value(&tag))
} }
fn from_ssv( fn from_ssv(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
FromSSVArgs { let name = args.call_info.name_tag.clone();
headerless, let registry = registry.clone();
aligned_columns,
minimum_spaces,
}: FromSSVArgs,
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! { let stream = async_stream! {
let (FromSSVArgs { headerless, aligned_columns, minimum_spaces }, mut input) = args.process(&registry).await?;
let concat_string = input.collect_string(name.clone()).await?; let concat_string = input.collect_string(name.clone()).await?;
let split_at = match minimum_spaces { let split_at = match minimum_spaces {
Some(number) => number.item, Some(number) => number.item,
@ -489,4 +486,12 @@ mod tests {
assert_eq!(aligned_columns_headerless, separator_headerless); assert_eq!(aligned_columns_headerless, separator_headerless);
assert_eq!(aligned_columns_with_headers, separator_with_headers); 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,20 +5,21 @@ use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, Untagg
pub struct FromTOML; pub struct FromTOML;
#[async_trait]
impl WholeStreamCommand for FromTOML { impl WholeStreamCommand for FromTOML {
fn name(&self) -> &str { fn name(&self) -> &str {
"from-toml" "from toml"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("from-toml") Signature::build("from toml")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Parse text as .toml and create table." "Parse text as .toml and create table."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
@ -67,11 +68,12 @@ pub fn from_toml(
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?; let registry = registry.clone();
let tag = args.name_tag();
let input = args.input;
let stream = async_stream! { let stream = async_stream! {
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 concat_string = input.collect_string(tag.clone()).await?;
match from_toml_string_to_value(concat_string.item, tag.clone()) { match from_toml_string_to_value(concat_string.item, tag.clone()) {
Ok(x) => match x { Ok(x) => match x {
@ -96,3 +98,15 @@ pub fn from_toml(
Ok(stream.to_output_stream()) 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,13 +11,14 @@ pub struct FromTSVArgs {
headerless: bool, headerless: bool,
} }
#[async_trait]
impl WholeStreamCommand for FromTSV { impl WholeStreamCommand for FromTSV {
fn name(&self) -> &str { fn name(&self) -> &str {
"from-tsv" "from tsv"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("from-tsv").switch( Signature::build("from tsv").switch(
"headerless", "headerless",
"don't treat the first row as column names", "don't treat the first row as column names",
None, None,
@ -28,18 +29,34 @@ impl WholeStreamCommand for FromTSV {
"Parse text as .tsv and create table." "Parse text as .tsv and create table."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, from_tsv)?.run() from_tsv(args, registry).await
} }
} }
fn from_tsv( async fn from_tsv(
FromTSVArgs { headerless }: FromTSVArgs, args: CommandArgs,
runnable_context: RunnableContext, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> 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,59 +5,70 @@ use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
pub struct FromURL; pub struct FromURL;
#[async_trait]
impl WholeStreamCommand for FromURL { impl WholeStreamCommand for FromURL {
fn name(&self) -> &str { fn name(&self) -> &str {
"from-url" "from url"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("from-url") Signature::build("from url")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Parse url-encoded string as a table." "Parse url-encoded string as a table."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
from_url(args, registry) from_url(args, registry).await
} }
} }
fn from_url(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn from_url(
let args = args.evaluate_once(registry)?; args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag(); let tag = args.name_tag();
let input = args.input; let input = args.input;
let 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 { match result {
Ok(result) => { Ok(result) => {
let mut row = TaggedDictBuilder::new(tag); let mut row = TaggedDictBuilder::new(tag);
for (k,v) in result { for (k, v) in result {
row.insert_untagged(k, UntaggedValue::string(v)); 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,
));
} }
Ok(OutputStream::one(ReturnSuccess::value(row.into_value())))
} }
}; _ => Err(ShellError::labeled_error_with_secondary(
"String not compatible with url-encoding",
Ok(stream.to_output_stream()) "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,52 +9,61 @@ use std::io::BufReader;
pub struct FromVcf; pub struct FromVcf;
#[async_trait]
impl WholeStreamCommand for FromVcf { impl WholeStreamCommand for FromVcf {
fn name(&self) -> &str { fn name(&self) -> &str {
"from-vcf" "from vcf"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("from-vcf") Signature::build("from vcf")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Parse text as .vcf and create table." "Parse text as .vcf and create table."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
from_vcf(args, registry) from_vcf(args, registry).await
} }
} }
fn from_vcf(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn from_vcf(
let args = args.evaluate_once(registry)?; args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag(); let tag = args.name_tag();
let input = args.input; let input = args.input;
let stream = async_stream! { let input_string = input.collect_string(tag.clone()).await?.item;
let input_string = input.collect_string(tag.clone()).await?.item; let input_bytes = input_string.as_bytes();
let input_bytes = input_string.as_bytes(); let buf_reader = BufReader::new(input_bytes);
let buf_reader = BufReader::new(input_bytes); let parser = ical::VcardParser::new(buf_reader);
let parser = ical::VcardParser::new(buf_reader);
for contact in parser { let mut values_vec_deque = VecDeque::new();
match contact {
Ok(c) => yield ReturnSuccess::value(contact_to_value(c, tag.clone())), for contact in parser {
Err(_) => yield Err(ShellError::labeled_error( 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", "Could not parse as .vcf",
"input cannot be parsed 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 { 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() 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,13 +13,14 @@ pub struct FromXLSXArgs {
headerless: bool, headerless: bool,
} }
#[async_trait]
impl WholeStreamCommand for FromXLSX { impl WholeStreamCommand for FromXLSX {
fn name(&self) -> &str { fn name(&self) -> &str {
"from-xlsx" "from xlsx"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("from-xlsx").switch( Signature::build("from xlsx").switch(
"headerless", "headerless",
"don't treat the first row as column names", "don't treat the first row as column names",
None, None,
@ -30,25 +31,20 @@ impl WholeStreamCommand for FromXLSX {
"Parse binary Excel(.xlsx) data and create table." "Parse binary Excel(.xlsx) data and create table."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, from_xlsx)?.run() from_xlsx(args, registry)
} }
} }
fn from_xlsx( fn from_xlsx(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
FromXLSXArgs { let tag = args.call_info.name_tag.clone();
headerless: _headerless, let registry = registry.clone();
}: FromXLSXArgs,
runnable_context: RunnableContext,
) -> Result<OutputStream, ShellError> {
let input = runnable_context.input;
let tag = runnable_context.name;
let stream = async_stream! { let stream = async_stream! {
let (FromXLSXArgs { headerless: _headerless }, mut input) = args.process(&registry).await?;
let value = input.collect_binary(tag.clone()).await?; let value = input.collect_binary(tag.clone()).await?;
let mut buf: Cursor<Vec<u8>> = Cursor::new(value.item); let mut buf: Cursor<Vec<u8>> = Cursor::new(value.item);
@ -97,3 +93,15 @@ fn from_xlsx(
Ok(stream.to_output_stream()) Ok(stream.to_output_stream())
} }
#[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,20 +5,21 @@ use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, Untagg
pub struct FromXML; pub struct FromXML;
#[async_trait]
impl WholeStreamCommand for FromXML { impl WholeStreamCommand for FromXML {
fn name(&self) -> &str { fn name(&self) -> &str {
"from-xml" "from xml"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("from-xml") Signature::build("from xml")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Parse text as .xml and create table." "Parse text as .xml and create table."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
@ -99,11 +100,12 @@ pub fn from_xml_string_to_value(s: String, tag: impl Into<Tag>) -> Result<Value,
} }
fn from_xml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { fn from_xml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?; let registry = registry.clone();
let tag = args.name_tag();
let input = args.input;
let stream = async_stream! { let stream = async_stream! {
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 concat_string = input.collect_string(tag.clone()).await?;
match from_xml_string_to_value(concat_string.item, tag.clone()) { match from_xml_string_to_value(concat_string.item, tag.clone()) {
@ -300,4 +302,12 @@ mod tests {
Ok(()) Ok(())
} }
#[test]
fn examples_work_as_expected() {
use super::FromXML;
use crate::examples::test as test_examples;
test_examples(FromXML {})
}
} }

View File

@ -1,53 +1,55 @@
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value}; use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value};
pub struct FromYAML; pub struct FromYAML;
#[async_trait]
impl WholeStreamCommand for FromYAML { impl WholeStreamCommand for FromYAML {
fn name(&self) -> &str { fn name(&self) -> &str {
"from-yaml" "from yaml"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("from-yaml") Signature::build("from yaml")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Parse text as .yaml/.yml and create table." "Parse text as .yaml/.yml and create table."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
from_yaml(args, registry) from_yaml(args, registry).await
} }
} }
pub struct FromYML; pub struct FromYML;
#[async_trait]
impl WholeStreamCommand for FromYML { impl WholeStreamCommand for FromYML {
fn name(&self) -> &str { fn name(&self) -> &str {
"from-yml" "from yml"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("from-yml") Signature::build("from yml")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Parse text as .yaml/.yml and create table." "Parse text as .yaml/.yml and create table."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
from_yaml(args, registry) from_yaml(args, registry).await
} }
} }
@ -118,34 +120,43 @@ pub fn from_yaml_string_to_value(s: String, tag: impl Into<Tag>) -> Result<Value
Ok(convert_yaml_value_to_nu_value(&v, tag)?) Ok(convert_yaml_value_to_nu_value(&v, tag)?)
} }
fn from_yaml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn from_yaml(
let args = args.evaluate_once(registry)?; args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let args = args.evaluate_once(&registry).await?;
let tag = args.name_tag(); let tag = args.name_tag();
let input = args.input; let input = args.input;
let 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()) { match from_yaml_string_to_value(concat_string.item, tag.clone()) {
Ok(x) => match x { Ok(x) => match x {
Value { value: UntaggedValue::Table(list), .. } => { Value {
for l in list { value: UntaggedValue::Table(list),
yield ReturnSuccess::value(l); ..
} } => Ok(futures::stream::iter(list).to_output_stream()),
} x => Ok(OutputStream::one(x)),
x => yield ReturnSuccess::value(x), },
}, Err(_) => Err(ShellError::labeled_error_with_secondary(
Err(_) => { "Could not parse as YAML",
yield Err(ShellError::labeled_error_with_secondary( "input cannot be parsed as YAML",
"Could not parse as YAML", &tag,
"input cannot be parsed as YAML", "value originates from here",
&tag, &concat_string.tag,
"value originates from here", )),
&concat_string.tag, }
)) }
}
} #[cfg(test)]
}; mod tests {
use super::FromYAML;
Ok(stream.to_output_stream())
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(FromYAML {})
}
} }

View File

@ -4,8 +4,8 @@ use indexmap::set::IndexSet;
use log::trace; use log::trace;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, ReturnValue, Signature, did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape,
SyntaxShape, UnspannedPathMember, UntaggedValue, Value, UnspannedPathMember, UntaggedValue, Value,
}; };
use nu_source::span_for_spanned_list; use nu_source::span_for_spanned_list;
use nu_value_ext::get_data_by_column_path; use nu_value_ext::get_data_by_column_path;
@ -17,6 +17,7 @@ pub struct GetArgs {
rest: Vec<ColumnPath>, rest: Vec<ColumnPath>,
} }
#[async_trait]
impl WholeStreamCommand for Get { impl WholeStreamCommand for Get {
fn name(&self) -> &str { fn name(&self) -> &str {
"get" "get"
@ -33,12 +34,27 @@ impl WholeStreamCommand for Get {
"Open given cells as text." "Open given cells as text."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, get)?.run() get(args, registry)
}
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,
},
]
} }
} }
@ -176,30 +192,21 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
) )
} }
pub fn get( pub fn get(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
GetArgs { rest: mut fields }: GetArgs, let registry = registry.clone();
RunnableContext { mut input, .. }: RunnableContext, let stream = async_stream! {
) -> Result<OutputStream, ShellError> { let (GetArgs { rest: mut fields }, mut input) = args.process(&registry).await?;
if fields.is_empty() { if fields.is_empty() {
let stream = async_stream! {
let mut vec = input.drain_vec().await; let mut vec = input.drain_vec().await;
let descs = nu_protocol::merge_descriptors(&vec); let descs = nu_protocol::merge_descriptors(&vec);
for desc in descs { for desc in descs {
yield ReturnSuccess::value(desc); yield ReturnSuccess::value(desc);
} }
}; } else {
let member = fields.remove(0);
let stream: BoxStream<'static, ReturnValue> = stream.boxed(); trace!("get {:?} {:?}", member, fields);
while let Some(item) = input.next().await {
Ok(stream.to_output_stream())
} else {
let member = fields.remove(0);
trace!("get {:?} {:?}", member, fields);
let stream = input
.map(move |item| {
let mut result = VecDeque::new();
let member = vec![member.clone()]; let member = vec![member.clone()];
let column_paths = vec![&member, &fields] let column_paths = vec![&member, &fields]
@ -217,25 +224,34 @@ pub fn get(
.. ..
} => { } => {
for item in rows { for item in rows {
result.push_back(ReturnSuccess::value(item.clone())); yield ReturnSuccess::value(item.clone());
} }
} }
Value { Value {
value: UntaggedValue::Primitive(Primitive::Nothing), value: UntaggedValue::Primitive(Primitive::Nothing),
.. ..
} => {} } => {}
other => result.push_back(ReturnSuccess::value(other.clone())), other => yield ReturnSuccess::value(other.clone()),
}, },
Err(reason) => result.push_back(ReturnSuccess::value( Err(reason) => yield ReturnSuccess::value(
UntaggedValue::Error(reason).into_untagged_value(), UntaggedValue::Error(reason).into_untagged_value(),
)), ),
} }
} }
}
}
};
Ok(stream.to_output_stream())
}
futures::stream::iter(result) #[cfg(test)]
}) mod tests {
.flatten(); use super::Get;
Ok(stream.to_output_stream()) #[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Get {})
} }
} }

View File

@ -1,24 +1,25 @@
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use indexmap::indexmap;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged; use nu_source::Tagged;
use nu_value_ext::get_data_by_key;
pub struct GroupBy; pub struct GroupBy;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct GroupByArgs { pub struct GroupByArgs {
column_name: Tagged<String>, column_name: Option<Tagged<String>>,
} }
#[async_trait]
impl WholeStreamCommand for GroupBy { impl WholeStreamCommand for GroupBy {
fn name(&self) -> &str { fn name(&self) -> &str {
"group-by" "group-by"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("group-by").required( Signature::build("group-by").optional(
"column_name", "column_name",
SyntaxShape::String, SyntaxShape::String,
"the name of the column to group by", "the name of the column to group by",
@ -29,37 +30,68 @@ impl WholeStreamCommand for GroupBy {
"Creates a new table with the data from the table rows grouped by the column given." "Creates a new table with the data from the table rows grouped by the column given."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, group_by)?.run() group_by(args, registry).await
}
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()]),
},
]
} }
} }
pub fn group_by( pub async fn group_by(
GroupByArgs { column_name }: GroupByArgs, args: CommandArgs,
RunnableContext { input, name, .. }: RunnableContext, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let stream = async_stream! { let registry = registry.clone();
let values: Vec<Value> = input.collect().await; 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() { if values.is_empty() {
yield Err(ShellError::labeled_error( Err(ShellError::labeled_error(
"Expected table from pipeline", "Expected table from pipeline",
"requires a table input", "requires a table input",
column_name.span() name,
)) ))
} else { } else {
match group(&column_name, values, name) { match crate::utils::data::group(column_name, &values, None, &name) {
Ok(grouped) => yield ReturnSuccess::value(grouped), Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => yield Err(err) Err(err) => Err(err),
}
} }
}; }
Ok(stream.to_output_stream())
} }
pub fn group( pub fn group(
@ -67,50 +99,7 @@ pub fn group(
values: Vec<Value>, values: Vec<Value>,
tag: impl Into<Tag>, tag: impl Into<Tag>,
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
let tag = tag.into(); crate::utils::data::group(Some(column_name.clone()), &values, None, tag)
let mut groups: indexmap::IndexMap<String, Vec<Value>> = indexmap::IndexMap::new();
for value in values {
let group_key = get_data_by_key(&value, column_name.borrow_spanned());
if let Some(group_key) = group_key {
let group_key = group_key.as_string()?.to_string();
let group = groups.entry(group_key).or_insert(vec![]);
group.push(value);
} else {
let possibilities = value.data_descriptors();
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|x| (natural::distance::levenshtein_distance(x, column_name), x))
.collect();
possible_matches.sort();
if !possible_matches.is_empty() {
return Err(ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", possible_matches[0].1),
column_name.tag(),
));
} else {
return Err(ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
column_name.tag(),
));
}
}
}
let mut out = TaggedDictBuilder::new(&tag);
for (k, v) in groups.iter() {
out.insert_untagged(k, UntaggedValue::table(v));
}
Ok(out.into_value())
} }
#[cfg(test)] #[cfg(test)]
@ -220,4 +209,12 @@ mod tests {
Ok(()) 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,126 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, 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<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 grouper = if let Some(Tagged { item: fmt, tag: _ }) = format {
Grouper::ByDate(Some(fmt))
} else {
Grouper::ByDate(None)
};
match grouper {
Grouper::ByDate(None) => {
match crate::utils::data::group(
column_name,
&values,
Some(Box::new(|row: &Value| row.format("%Y-%b-%d"))),
&name,
) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
}
Grouper::ByDate(Some(fmt)) => {
match crate::utils::data::group(
column_name,
&values,
Some(Box::new(move |row: &Value| row.format(&fmt))),
&name,
) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
}
}
}
}
#[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,33 +8,41 @@ use nu_protocol::Dictionary;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value}; use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
pub struct Headers; pub struct Headers;
#[derive(Deserialize)]
pub struct HeadersArgs {}
#[async_trait]
impl WholeStreamCommand for Headers { impl WholeStreamCommand for Headers {
fn name(&self) -> &str { fn name(&self) -> &str {
"headers" "headers"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("headers") Signature::build("headers")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Use the first row of the table as column names" "Use the first row of the table as column names"
} }
fn run(
async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, headers)?.run() headers(args, registry)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Create headers for a raw string",
example: r#"echo "a b c|1 2 3" | split row "|" | split column " " | headers"#,
result: None,
}]
} }
} }
pub fn headers( pub fn headers(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
HeadersArgs {}: HeadersArgs,
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! { let stream = async_stream! {
let mut input = args.input;
let rows: Vec<Value> = input.collect().await; let rows: Vec<Value> = input.collect().await;
if rows.len() < 1 { if rows.len() < 1 {
@ -78,3 +86,15 @@ pub fn headers(
Ok(stream.to_output_stream()) Ok(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

@ -1,93 +1,105 @@
use crate::commands::PerItemCommand; use crate::commands::WholeStreamCommand;
use crate::data::command_dict; use crate::data::command_dict;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{
CallInfo, NamedType, PositionalType, Primitive, ReturnSuccess, Signature, SyntaxShape, NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder,
TaggedDictBuilder, UntaggedValue, Value, UntaggedValue,
}; };
use nu_source::SpannedItem; use nu_source::{SpannedItem, Tagged};
use nu_value_ext::get_data_by_key; use nu_value_ext::get_data_by_key;
pub struct Help; pub struct Help;
impl PerItemCommand for Help { #[derive(Deserialize)]
pub struct HelpArgs {
rest: Vec<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for Help {
fn name(&self) -> &str { fn name(&self) -> &str {
"help" "help"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("help").rest(SyntaxShape::Any, "the name of command(s) to get help on") Signature::build("help").rest(SyntaxShape::String, "the name of command to get help on")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Display help information about commands." "Display help information about commands."
} }
fn run( async fn run(
&self, &self,
call_info: &CallInfo, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let tag = &call_info.name_tag; help(args, registry)
}
}
match call_info.args.nth(0) { fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
Some(Value { let registry = registry.clone();
value: UntaggedValue::Primitive(Primitive::String(document)), let name = args.call_info.name_tag.clone();
tag, let stream = async_stream! {
}) => { let (HelpArgs { rest }, mut input) = args.process(&registry).await?;
let mut help = VecDeque::new(); if let Some(document) = rest.get(0) {
if document == "commands" { if document.item == "commands" {
let mut sorted_names = registry.names(); let mut sorted_names = registry.names();
sorted_names.sort(); sorted_names.sort();
for cmd in sorted_names { for cmd in sorted_names {
let mut short_desc = TaggedDictBuilder::new(tag.clone()); // If it's a subcommand, don't list it during the commands list
let value = command_dict( if cmd.contains(' ') {
registry.get_command(&cmd).ok_or_else(|| { continue;
ShellError::labeled_error(
format!("Could not load {}", cmd),
"could not load command",
tag,
)
})?,
tag.clone(),
);
short_desc.insert_untagged("name", cmd);
short_desc.insert_untagged(
"description",
get_data_by_key(&value, "usage".spanned_unknown())
.ok_or_else(|| {
ShellError::labeled_error(
"Expected a usage key",
"expected a 'usage' key",
&value.tag,
)
})?
.as_string()?,
);
help.push_back(ReturnSuccess::value(short_desc.into_value()));
} }
} else if let Some(command) = registry.get_command(document) { let mut short_desc = TaggedDictBuilder::new(name.clone());
return Ok( let document_tag = document.tag.clone();
get_help(&command.name(), &command.usage(), command.signature()).into(), 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(),
); );
} else {
return Err(ShellError::labeled_error( short_desc.insert_untagged("name", cmd);
"Can't find command (use 'help commands' for full list)", short_desc.insert_untagged(
"can't find command", "description",
tag, get_data_by_key(&value, "usage".spanned_unknown())
)); .ok_or_else(|| {
ShellError::labeled_error(
"Expected a usage key",
"expected a 'usage' key",
&value.tag,
)
})?
.as_string()?,
);
yield ReturnSuccess::value(short_desc.into_value());
} }
let help = futures::stream::iter(help); } else if rest.len() == 2 {
Ok(help.to_output_stream()) // Check for a subcommand
let command_name = format!("{} {}", rest[0].item, rest[1].item);
if let Some(command) = registry.get_command(&command_name) {
yield Ok(ReturnSuccess::Value(UntaggedValue::string(get_help(command.stream_command(), &registry)).into_value(Tag::unknown())));
}
} else if let Some(command) = registry.get_command(&document.item) {
yield Ok(ReturnSuccess::Value(UntaggedValue::string(get_help(command.stream_command(), &registry)).into_value(Tag::unknown())));
} else {
yield Err(ShellError::labeled_error(
"Can't find command (use 'help commands' for full list)",
"can't find command",
document.tag.span,
));
} }
_ => { } else {
let msg = r#"Welcome to Nushell. let msg = r#"Welcome to Nushell.
Here are some tips to help you get started. Here are some tips to help you get started.
* help commands - list all available commands * help commands - list all available commands
@ -109,28 +121,30 @@ Get the processes on your system actively using CPU:
You can also learn more at https://www.nushell.sh/book/"#; You can also learn more at https://www.nushell.sh/book/"#;
let output_stream = futures::stream::iter(vec![ReturnSuccess::value( yield Ok(ReturnSuccess::Value(UntaggedValue::string(msg).into_value(Tag::unknown())));
UntaggedValue::string(msg).into_value(tag),
)]);
Ok(output_stream.to_output_stream())
}
} }
} };
Ok(stream.to_output_stream())
} }
pub(crate) fn get_help( #[allow(clippy::cognitive_complexity)]
cmd_name: &str, pub fn get_help(cmd: &dyn WholeStreamCommand, registry: &CommandRegistry) -> String {
cmd_usage: &str, let cmd_name = cmd.name();
cmd_sig: Signature, let signature = cmd.signature();
) -> impl Into<OutputStream> {
let mut help = VecDeque::new();
let mut long_desc = String::new(); let mut long_desc = String::new();
long_desc.push_str(&cmd_usage); long_desc.push_str(&cmd.usage());
long_desc.push_str("\n"); long_desc.push_str("\n");
let signature = cmd_sig; let mut subcommands = String::new();
for name in registry.names() {
if name.starts_with(&format!("{} ", cmd_name)) {
let subcommand = registry.get_command(&name).expect("This shouldn't happen");
subcommands.push_str(&format!(" {} - {}\n", name, subcommand.usage()));
}
}
let mut one_liner = String::new(); let mut one_liner = String::new();
one_liner.push_str(&signature.name); one_liner.push_str(&signature.name);
@ -151,16 +165,25 @@ pub(crate) fn get_help(
one_liner.push_str(" ...args"); one_liner.push_str(" ...args");
} }
if !subcommands.is_empty() {
one_liner.push_str("<subcommand> ");
}
if !signature.named.is_empty() { if !signature.named.is_empty() {
one_liner.push_str("{flags} "); one_liner.push_str("{flags} ");
} }
long_desc.push_str(&format!("\nUsage:\n > {}\n", one_liner)); long_desc.push_str(&format!("\nUsage:\n > {}\n", one_liner));
if !subcommands.is_empty() {
long_desc.push_str("\nSubcommands:\n");
long_desc.push_str(&subcommands);
}
if !signature.positional.is_empty() || signature.rest_positional.is_some() { if !signature.positional.is_empty() || signature.rest_positional.is_some() {
long_desc.push_str("\nparameters:\n"); long_desc.push_str("\nParameters:\n");
for positional in signature.positional { for positional in &signature.positional {
match positional.0 { match &positional.0 {
PositionalType::Mandatory(name, _m) => { PositionalType::Mandatory(name, _m) => {
long_desc.push_str(&format!(" <{}> {}\n", name, positional.1)); long_desc.push_str(&format!(" <{}> {}\n", name, positional.1));
} }
@ -170,79 +193,110 @@ 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)); long_desc.push_str(&format!(" ...args: {}\n", rest_positional.1));
} }
} }
if !signature.named.is_empty() { if !signature.named.is_empty() {
long_desc.push_str("\nflags:\n"); long_desc.push_str(&get_flags_section(&signature))
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);
}
} }
help.push_back(ReturnSuccess::value( let palette = crate::shell::palette::DefaultPalette {};
UntaggedValue::string(long_desc).into_value(Tag::from((0, cmd_name.len(), None))), let examples = cmd.examples();
)); if !examples.is_empty() {
help long_desc.push_str("\nExamples:");
}
for example in examples {
long_desc.push_str("\n");
long_desc.push_str(" ");
long_desc.push_str(example.description);
let colored_example =
crate::shell::helper::Painter::paint_string(example.example, registry, &palette);
long_desc.push_str(&format!("\n > {}\n", colored_example));
}
long_desc.push_str("\n");
long_desc
}
fn get_flags_section(signature: &Signature) -> String {
let mut long_desc = String::new();
long_desc.push_str("\nFlags:\n");
for (flag, ty) in &signature.named {
let msg = match ty.0 {
NamedType::Switch(s) => {
if let Some(c) = s {
format!(
" -{}, --{}{} {}\n",
c,
flag,
if !ty.1.is_empty() { ":" } else { "" },
ty.1
)
} else {
format!(
" --{}{} {}\n",
flag,
if !ty.1.is_empty() { ":" } else { "" },
ty.1
)
}
}
NamedType::Mandatory(s, m) => {
if let Some(c) = s {
format!(
" -{}, --{} <{}> (required parameter){} {}\n",
c,
flag,
m.display(),
if !ty.1.is_empty() { ":" } else { "" },
ty.1
)
} else {
format!(
" --{} <{}> (required parameter){} {}\n",
flag,
m.display(),
if !ty.1.is_empty() { ":" } else { "" },
ty.1
)
}
}
NamedType::Optional(s, o) => {
if let Some(c) = s {
format!(
" -{}, --{} <{}>{} {}\n",
c,
flag,
o.display(),
if !ty.1.is_empty() { ":" } else { "" },
ty.1
)
} else {
format!(
" --{} <{}>{} {}\n",
flag,
o.display(),
if !ty.1.is_empty() { ":" } else { "" },
ty.1
)
}
}
};
long_desc.push_str(&msg);
}
long_desc
}
#[cfg(test)]
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>>, rest: Vec<Tagged<String>>,
} }
#[async_trait]
impl WholeStreamCommand for Histogram { impl WholeStreamCommand for Histogram {
fn name(&self) -> &str { fn name(&self) -> &str {
"histogram" "histogram"
@ -39,20 +40,44 @@ impl WholeStreamCommand for Histogram {
"Creates a new table with a histogram based on the column name passed in." "Creates a new table with a histogram based on the column name passed in."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, histogram)?.run() histogram(args, registry)
}
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 1 1 2 2 1 1] | histogram",
result: None,
},
]
} }
} }
pub fn histogram( pub fn histogram(
HistogramArgs { column_name, rest }: HistogramArgs, args: CommandArgs,
RunnableContext { input, name, .. }: RunnableContext, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let stream = async_stream! { let stream = async_stream! {
let (HistogramArgs { column_name, rest}, mut input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await; let values: Vec<Value> = input.collect().await;
let Tagged { item: group_by, .. } = column_name.clone(); let Tagged { item: group_by, .. } = column_name.clone();
@ -83,6 +108,32 @@ pub fn histogram(
let column = (*column_name).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 {
yield Err(count_shell_error);
return;
}
}
}
_ => {
yield Err(count_shell_error);
return;
}
}
}
if let Value { value: UntaggedValue::Table(start), .. } = datasets.get(0).ok_or_else(|| ShellError::labeled_error("Unable to load dataset", "unabled to load dataset", &name))? { if let Value { 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() { for percentage in start.iter() {
@ -90,6 +141,8 @@ pub fn histogram(
let value: Tagged<String> = group_labels.get(idx).ok_or_else(|| ShellError::labeled_error("Unable to load group labels", "unabled to load group labels", &name))?.clone(); let value: Tagged<String> = group_labels.get(idx).ok_or_else(|| ShellError::labeled_error("Unable to load group labels", "unabled to load group labels", &name))?.clone();
fact.insert_value(&column, UntaggedValue::string(value.item).into_value(value.tag)); fact.insert_value(&column, UntaggedValue::string(value.item).into_value(value.tag));
fact.insert_untagged(&count_column_name, UntaggedValue::int(count_values[idx]));
if let Value { value: UntaggedValue::Primitive(Primitive::Int(ref num)), ref tag } = percentage.clone() { 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>(); 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(&frequency_column_name, UntaggedValue::string(string));
@ -158,3 +211,15 @@ fn percentages(values: &Value, max: Value, tag: impl Into<Tag>) -> Result<Value,
Ok(results) 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

@ -1,14 +1,15 @@
use crate::cli::History as HistoryFile; use crate::cli::History as HistoryFile;
use crate::commands::PerItemCommand; use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{CallInfo, ReturnSuccess, Signature, UntaggedValue, Value}; use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
pub struct History; pub struct History;
impl PerItemCommand for History { #[async_trait]
impl WholeStreamCommand for History {
fn name(&self) -> &str { fn name(&self) -> &str {
"history" "history"
} }
@ -21,29 +22,42 @@ impl PerItemCommand for History {
"Display command history." "Display command history."
} }
fn run( async fn run(
&self, &self,
call_info: &CallInfo, args: CommandArgs,
_registry: &CommandRegistry, registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let tag = call_info.name_tag.clone(); history(args, registry)
}
let stream = async_stream! { }
let history_path = HistoryFile::path();
let file = File::open(history_path); fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
if let Ok(file) = file { let tag = args.call_info.name_tag;
let reader = BufReader::new(file); let stream = async_stream! {
for line in reader.lines() { let history_path = HistoryFile::path();
if let Ok(line) = line { let file = File::open(history_path);
yield ReturnSuccess::value(UntaggedValue::string(line).into_value(tag.clone())); if let Ok(file) = file {
} let reader = BufReader::new(file);
} for line in reader.lines() {
} else { if let Ok(line) = line {
yield Err(ShellError::labeled_error("Could not open history", "history file could not be opened", tag.clone())); yield ReturnSuccess::value(UntaggedValue::string(line).into_value(tag.clone()));
} }
}; }
Ok(stream.to_output_stream()) } else {
yield Err(ShellError::labeled_error("Could not open history", "history file could not be opened", tag.clone()));
}
};
Ok(stream.to_output_stream())
}
#[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

@ -1,13 +1,20 @@
use crate::commands::PerItemCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry; use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{CallInfo, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_value_ext::ValueExt; use nu_value_ext::ValueExt;
pub struct Insert; pub struct Insert;
impl PerItemCommand for Insert { #[derive(Deserialize)]
pub struct InsertArgs {
column: ColumnPath,
value: Value,
}
#[async_trait]
impl WholeStreamCommand for Insert {
fn name(&self) -> &str { fn name(&self) -> &str {
"insert" "insert"
} }
@ -27,40 +34,56 @@ impl PerItemCommand for Insert {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Edit an existing column to have a new value." "Insert a new column with a given value."
} }
fn run( async fn run(
&self, &self,
call_info: &CallInfo, args: CommandArgs,
_registry: &CommandRegistry, registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
value: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let value_tag = value.tag(); insert(args, registry)
let field = call_info.args.expect_nth(0)?.as_column_path()?; }
let replacement = call_info.args.expect_nth(1)?.tagged_unknown(); }
let stream = match value { fn insert(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
obj let registry = registry.clone();
@
Value { let stream = async_stream! {
value: UntaggedValue::Row(_), let (InsertArgs { column, value }, mut input) = args.process(&registry).await?;
.. while let Some(row) = input.next().await {
} => match obj.insert_data_at_column_path(&field, replacement.item.clone()) { match row {
Ok(v) => futures::stream::iter(vec![Ok(ReturnSuccess::Value(v))]), Value {
Err(err) => return Err(err), value: UntaggedValue::Row(_),
}, ..
} => match row.insert_data_at_column_path(&column, value.clone()) {
Ok(v) => yield Ok(ReturnSuccess::Value(v)),
Err(err) => yield Err(err),
},
Value { tag, ..} => {
yield Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
tag,
));
}
_ => {
return Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
value_tag,
))
} }
}; };
Ok(stream.to_output_stream()) };
Ok(stream.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

@ -0,0 +1,211 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use nu_value_ext::ValueExt;
enum IsEmptyFor {
Value,
RowWithFieldsAndFallback(Vec<Tagged<ColumnPath>>, Value),
RowWithField(Tagged<ColumnPath>),
RowWithFieldAndFallback(Box<Tagged<ColumnPath>>, Value),
}
pub struct IsEmpty;
#[derive(Deserialize)]
pub struct IsEmptyArgs {
rest: Vec<Value>,
}
#[async_trait]
impl WholeStreamCommand for IsEmpty {
fn name(&self) -> &str {
"empty?"
}
fn signature(&self) -> Signature {
Signature::build("empty?").rest(
SyntaxShape::Any,
"the names of the columns to check emptiness followed by the replacement value.",
)
}
fn usage(&self) -> &str {
"Checks emptiness. The last value is the replacement value for any empty column(s) given to check against the table."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
is_empty(args, registry)
}
}
fn is_empty(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (IsEmptyArgs { rest }, mut input) = args.process(&registry).await?;
while let Some(value) = input.next().await {
let value_tag = value.tag();
let action = if rest.len() <= 2 {
let field = rest.get(0);
let replacement_if_true = rest.get(1);
match (field, replacement_if_true) {
(Some(field), Some(replacement_if_true)) => {
IsEmptyFor::RowWithFieldAndFallback(
Box::new(field.as_column_path()?),
replacement_if_true.clone(),
)
}
(Some(field), None) => IsEmptyFor::RowWithField(field.as_column_path()?),
(_, _) => IsEmptyFor::Value,
}
} else {
// let no_args = vec![];
let mut arguments = rest.iter().rev();
let replacement_if_true = match arguments.next() {
Some(arg) => arg.clone(),
None => UntaggedValue::boolean(value.is_empty()).into_value(&value_tag),
};
IsEmptyFor::RowWithFieldsAndFallback(
arguments
.map(|a| a.as_column_path())
.filter_map(Result::ok)
.collect(),
replacement_if_true,
)
};
match action {
IsEmptyFor::Value => yield Ok(ReturnSuccess::Value(
UntaggedValue::boolean(value.is_empty()).into_value(value_tag),
)),
IsEmptyFor::RowWithFieldsAndFallback(fields, default) => {
let mut out = value;
for field in fields.iter() {
let val =
crate::commands::get::get_column_path(&field, &out)?;
let emptiness_value = match out {
obj
@
Value {
value: UntaggedValue::Row(_),
..
} => {
if val.is_empty() {
match obj.replace_data_at_column_path(&field, default.clone()) {
Some(v) => Ok(v),
None => Err(ShellError::labeled_error(
"empty? could not find place to check emptiness",
"column name",
&field.tag,
)),
}
} else {
Ok(obj)
}
}
_ => Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
&value_tag,
)),
};
out = emptiness_value?;
}
yield Ok(ReturnSuccess::Value(out))
}
IsEmptyFor::RowWithField(field) => {
let val =
crate::commands::get::get_column_path(&field, &value)?;
match &value {
obj
@
Value {
value: UntaggedValue::Row(_),
..
} => {
if val.is_empty() {
match obj.replace_data_at_column_path(
&field,
UntaggedValue::boolean(true).into_value(&value_tag),
) {
Some(v) => yield Ok(ReturnSuccess::Value(v)),
None => yield Err(ShellError::labeled_error(
"empty? could not find place to check emptiness",
"column name",
&field.tag,
)),
}
} else {
yield Ok(ReturnSuccess::Value(value))
}
}
_ => yield Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
&value_tag,
)),
}
}
IsEmptyFor::RowWithFieldAndFallback(field, default) => {
let val =
crate::commands::get::get_column_path(&field, &value)?;
match &value {
obj
@
Value {
value: UntaggedValue::Row(_),
..
} => {
if val.is_empty() {
match obj.replace_data_at_column_path(&field, default) {
Some(v) => yield Ok(ReturnSuccess::Value(v)),
None => yield Err(ShellError::labeled_error(
"empty? could not find place to check emptiness",
"column name",
&field.tag,
)),
}
} else {
yield Ok(ReturnSuccess::Value(value))
}
}
_ => yield Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
&value_tag,
)),
}
}
}
}
};
Ok(stream.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

@ -0,0 +1,95 @@
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 Keep;
#[derive(Deserialize)]
pub struct KeepArgs {
rows: Option<Tagged<usize>>,
}
#[async_trait]
impl WholeStreamCommand for Keep {
fn name(&self) -> &str {
"keep"
}
fn signature(&self) -> Signature {
Signature::build("keep").optional(
"rows",
SyntaxShape::Int,
"starting from the front, the number of rows to keep",
)
}
fn usage(&self) -> &str {
"Keep the number of rows only"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
keep(args, registry)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Keep the first row",
example: "echo [1 2 3] | keep",
result: Some(vec![UntaggedValue::int(1).into()]),
},
Example {
description: "Keep the first four rows",
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(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (KeepArgs { rows }, mut input) = args.process(&registry).await?;
let mut rows_desired = if let Some(quantity) = rows {
*quantity
} else {
1
};
while let Some(input) = input.next().await {
if rows_desired > 0 {
yield ReturnSuccess::value(input);
rows_desired -= 1;
} else {
break;
}
}
};
Ok(stream.to_output_stream())
}
#[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

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

View File

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

View File

@ -1,8 +1,8 @@
use crate::commands::command::RunnablePerItemContext; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry; use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{CallInfo, Signature, SyntaxShape, Value}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged; use nu_source::Tagged;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
@ -16,7 +16,8 @@ pub struct KillArgs {
pub quiet: Tagged<bool>, pub quiet: Tagged<bool>,
} }
impl PerItemCommand for Kill { #[async_trait]
impl WholeStreamCommand for Kill {
fn name(&self) -> &str { fn name(&self) -> &str {
"kill" "kill"
} }
@ -37,68 +38,97 @@ impl PerItemCommand for Kill {
"Kill a process using the process id." "Kill a process using the process id."
} }
fn run( async fn run(
&self, &self,
call_info: &CallInfo, args: CommandArgs,
_registry: &CommandRegistry, registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
call_info kill(args, registry)
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), kill)? }
.run()
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( fn kill(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
KillArgs { let registry = registry.clone();
pid,
rest,
force,
quiet,
}: KillArgs,
_context: &RunnablePerItemContext,
) -> Result<OutputStream, ShellError> {
let mut cmd = if cfg!(windows) {
let mut cmd = Command::new("taskkill");
if *force { let stream = async_stream! {
cmd.arg("/F"); let (KillArgs {
} pid,
rest,
force,
quiet,
}, mut input) = args.process(&registry).await?;
let mut cmd = if cfg!(windows) {
let mut cmd = Command::new("taskkill");
cmd.arg("/PID"); if *force {
cmd.arg(pid.item().to_string()); cmd.arg("/F");
}
// each pid must written as `/PID 0` otherwise
// taskkill will act as `killall` unix command
for id in &rest {
cmd.arg("/PID"); cmd.arg("/PID");
cmd.arg(id.item().to_string()); cmd.arg(pid.item().to_string());
// each pid must written as `/PID 0` otherwise
// taskkill will act as `killall` unix command
for id in &rest {
cmd.arg("/PID");
cmd.arg(id.item().to_string());
}
cmd
} else {
let mut cmd = Command::new("kill");
if *force {
cmd.arg("-9");
}
cmd.arg(pid.item().to_string());
cmd.args(rest.iter().map(move |id| id.item().to_string()));
cmd
};
// pipe everything to null
if *quiet {
cmd.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
} }
cmd cmd.status().expect("failed to execute shell command");
} else {
let mut cmd = Command::new("kill");
if *force { if false {
cmd.arg("-9"); yield ReturnSuccess::value(UntaggedValue::nothing().into_value(Tag::unknown()));
} }
cmd.arg(pid.item().to_string());
cmd.args(rest.iter().map(move |id| id.item().to_string()));
cmd
}; };
// pipe everything to null Ok(stream.to_output_stream())
if *quiet { }
cmd.stdin(Stdio::null())
.stdout(Stdio::null()) #[cfg(test)]
.stderr(Stdio::null()); mod tests {
} use super::Kill;
cmd.status().expect("failed to execute shell command"); #[test]
fn examples_work_as_expected() {
Ok(OutputStream::empty()) 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::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged; use nu_source::Tagged;
pub struct Last; pub struct Last;
@ -12,6 +12,7 @@ pub struct LastArgs {
rows: Option<Tagged<u64>>, rows: Option<Tagged<u64>>,
} }
#[async_trait]
impl WholeStreamCommand for Last { impl WholeStreamCommand for Last {
fn name(&self) -> &str { fn name(&self) -> &str {
"last" "last"
@ -29,33 +30,68 @@ impl WholeStreamCommand for Last {
"Show only the last number of rows." "Show only the last number of rows."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, last)?.run() last(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get the last row",
example: "echo [1 2 3] | last",
result: Some(vec![Value::from(UntaggedValue::from(BigInt::from(3)))]),
},
Example {
description: "Get the last three rows",
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> { async fn last(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let stream = async_stream! { let registry = registry.clone();
let v: Vec<_> = context.input.into_vec().await; let (LastArgs { rows }, input) = args.process(&registry).await?;
let v: Vec<_> = input.into_vec().await;
let rows_desired = if let Some(quantity) = rows { let rows_desired = if let Some(quantity) = rows {
*quantity *quantity
} else { } else {
1 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)
}
}
}; };
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; pub struct Lines;
#[async_trait]
impl WholeStreamCommand for Lines { impl WholeStreamCommand for Lines {
fn name(&self) -> &str { fn name(&self) -> &str {
"lines" "lines"
@ -18,13 +19,21 @@ impl WholeStreamCommand for Lines {
"Split single string into rows, one per line." "Split single string into rows, one per line."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
lines(args, registry) lines(args, registry)
} }
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Split multi-line string into lines",
example: r#"^echo "two\nlines" | lines"#,
result: None,
}]
}
} }
fn ends_with_line_ending(st: &str) -> bool { fn ends_with_line_ending(st: &str) -> bool {
@ -38,14 +47,14 @@ fn ends_with_line_ending(st: &str) -> bool {
} }
fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
let tag = args.name_tag();
let name_span = tag.span;
let mut input = args.input;
let mut leftover = vec![]; let mut leftover = vec![];
let mut leftover_string = String::new(); let mut leftover_string = String::new();
let registry = registry.clone();
let stream = async_stream! { let stream = async_stream! {
let args = args.evaluate_once(&registry).await.unwrap();
let tag = args.name_tag();
let name_span = tag.span;
let mut input = args.input;
loop { loop {
match input.next().await { match input.next().await {
Some(Value { value: UntaggedValue::Primitive(Primitive::String(st)), ..}) => { Some(Value { value: UntaggedValue::Primitive(Primitive::String(st)), ..}) => {
@ -65,6 +74,7 @@ fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream,
} }
let success_lines: Vec<_> = lines.iter().map(|x| ReturnSuccess::value(UntaggedValue::line(x).into_untagged_value())).collect(); let success_lines: Vec<_> = lines.iter().map(|x| ReturnSuccess::value(UntaggedValue::line(x).into_untagged_value())).collect();
yield futures::stream::iter(success_lines) yield futures::stream::iter(success_lines)
} }
Some(Value { value: UntaggedValue::Primitive(Primitive::Line(st)), ..}) => { Some(Value { value: UntaggedValue::Primitive(Primitive::Line(st)), ..}) => {
@ -111,6 +121,17 @@ fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream,
} }
} }
.flatten(); .flatten();
Ok(stream.to_output_stream()) Ok(stream.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

@ -1,7 +1,7 @@
use crate::commands::command::RunnablePerItemContext; use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{CallInfo, Signature, SyntaxShape, Value}; use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged; use nu_source::Tagged;
use std::path::PathBuf; use std::path::PathBuf;
@ -16,9 +16,12 @@ pub struct LsArgs {
pub short_names: bool, pub short_names: bool,
#[serde(rename = "with-symlink-targets")] #[serde(rename = "with-symlink-targets")]
pub with_symlink_targets: bool, pub with_symlink_targets: bool,
#[serde(rename = "du")]
pub du: bool,
} }
impl PerItemCommand for Ls { #[async_trait]
impl WholeStreamCommand for Ls {
fn name(&self) -> &str { fn name(&self) -> &str {
"ls" "ls"
} }
@ -46,25 +49,58 @@ impl PerItemCommand for Ls {
"display the paths to the target files that symlinks point to", "display the paths to the target files that symlinks point to",
Some('w'), Some('w'),
) )
.switch(
"du",
"display the apparent directory size in place of the directory metadata size",
Some('d'),
)
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"View the contents of the current or given path." "View the contents of the current or given path."
} }
fn run( async fn run(
&self, &self,
call_info: &CallInfo, args: CommandArgs,
_registry: &CommandRegistry, registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
call_info let name = args.call_info.name_tag.clone();
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), ls)? let ctrl_c = args.ctrl_c.clone();
.run() let shell_manager = args.shell_manager.clone();
let (args, _) = args.process(&registry).await?;
shell_manager.ls(args, name, ctrl_c)
}
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: &RunnablePerItemContext) -> Result<OutputStream, ShellError> { #[cfg(test)]
context.shell_manager.ls(args, context) 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>>, column_name: Option<Tagged<String>>,
} }
#[async_trait]
impl WholeStreamCommand for MapMaxBy { impl WholeStreamCommand for MapMaxBy {
fn name(&self) -> &str { fn name(&self) -> &str {
"map-max-by" "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." "Creates a new table with the data from the tables rows maxed by the column given."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, map_max_by)?.run() map_max_by(args, registry).await
} }
} }
pub fn map_max_by( pub async fn map_max_by(
MapMaxByArgs { column_name }: MapMaxByArgs, args: CommandArgs,
RunnableContext { input, name, .. }: RunnableContext, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let stream = async_stream! { let registry = registry.clone();
let values: Vec<Value> = input.collect().await; 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() {
if values.is_empty() { Err(ShellError::labeled_error(
yield Err(ShellError::labeled_error( "Expected table from pipeline",
"Expected table from pipeline", "requires a table input",
"requires a table input", name,
name ))
)) } else {
let map_by_column = if let Some(column_to_map) = column_name {
Some(column_to_map.item().clone())
} else { } else {
None
};
let map_by_column = if let Some(column_to_map) = column_name { match map_max(&values[0], map_by_column, name) {
Some(column_to_map.item().clone()) Ok(table_maxed) => Ok(OutputStream::one(ReturnSuccess::value(table_maxed))),
} else { Err(err) => Err(err),
None
};
match map_max(&values[0], map_by_column, name) {
Ok(table_maxed) => yield ReturnSuccess::value(table_maxed),
Err(err) => yield 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,115 @@
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::data::value::merge_values;
use crate::prelude::*;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{hir::Block, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
pub struct Merge;
#[derive(Deserialize)]
pub struct MergeArgs {
block: Block,
}
#[async_trait]
impl WholeStreamCommand for Merge {
fn name(&self) -> &str {
"merge"
}
fn signature(&self) -> Signature {
Signature::build("merge").required(
"block",
SyntaxShape::Block,
"the block to run and merge into the table",
)
}
fn usage(&self) -> &str {
"Merge a table."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
merge(args, registry)
}
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(raw_args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = raw_args.call_info.scope.clone();
let stream = async_stream! {
let mut context = Context::from_raw(&raw_args, &registry);
let name_tag = raw_args.call_info.name_tag.clone();
let (merge_args, mut input): (MergeArgs, _) = raw_args.process(&registry).await?;
let block = merge_args.block;
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) => {
yield Err(err);
return;
}
};
let table = table.unwrap_or_else(|| vec![Value {
value: UntaggedValue::row(IndexMap::default()),
tag: name_tag,
}]);
let mut idx = 0;
while let Some(value) = input.next().await {
let other = table.get(idx);
match other {
Some(replacement) => {
match merge_values(&value.value, &replacement.value) {
Ok(merged_value) => yield ReturnSuccess::value(merged_value.into_value(&value.tag)),
Err(err) => {
let message = format!("The row at {:?} types mismatch", idx);
yield Err(ShellError::labeled_error("Could not merge", &message, &value.tag));
}
}
}
None => yield ReturnSuccess::value(value),
}
idx += 1;
}
};
Ok(stream.to_output_stream())
}
#[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

@ -1,8 +1,8 @@
use crate::commands::command::RunnablePerItemContext; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry; use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{CallInfo, Signature, SyntaxShape, Value}; use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged; use nu_source::Tagged;
use std::path::PathBuf; use std::path::PathBuf;
@ -11,35 +11,60 @@ pub struct Mkdir;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct MkdirArgs { pub struct MkdirArgs {
pub rest: Vec<Tagged<PathBuf>>, pub rest: Vec<Tagged<PathBuf>>,
#[serde(rename = "show-created-paths")]
pub show_created_paths: bool,
} }
impl PerItemCommand for Mkdir { #[async_trait]
impl WholeStreamCommand for Mkdir {
fn name(&self) -> &str { fn name(&self) -> &str {
"mkdir" "mkdir"
} }
fn signature(&self) -> Signature { 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 { fn usage(&self) -> &str {
"Make directories, creates intermediary directories as required." "Make directories, creates intermediary directories as required."
} }
fn run( async fn run(
&self, &self,
call_info: &CallInfo, args: CommandArgs,
_registry: &CommandRegistry, registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
call_info mkdir(args, registry).await
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), mkdir)? }
.run()
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Make a directory named foo",
example: "mkdir foo",
result: None,
}]
} }
} }
fn mkdir(args: MkdirArgs, context: &RunnablePerItemContext) -> Result<OutputStream, ShellError> { async fn mkdir(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let shell_manager = context.shell_manager.clone(); let registry = registry.clone();
shell_manager.mkdir(args, context) 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

@ -1,8 +1,8 @@
use crate::commands::command::RunnablePerItemContext; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry; use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{CallInfo, Signature, SyntaxShape, Value}; use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged; use nu_source::Tagged;
use std::path::PathBuf; use std::path::PathBuf;
@ -14,7 +14,8 @@ pub struct MoveArgs {
pub dst: Tagged<PathBuf>, pub dst: Tagged<PathBuf>,
} }
impl PerItemCommand for Move { #[async_trait]
impl WholeStreamCommand for Move {
fn name(&self) -> &str { fn name(&self) -> &str {
"mv" "mv"
} }
@ -37,20 +38,59 @@ impl PerItemCommand for Move {
"Move files or directories." "Move files or directories."
} }
fn run( async fn run(
&self, &self,
call_info: &CallInfo, args: CommandArgs,
_registry: &CommandRegistry, registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
call_info mv(args, registry)
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), mv)? }
.run()
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: &RunnablePerItemContext) -> Result<OutputStream, ShellError> { fn mv(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let shell_manager = context.shell_manager.clone(); let registry = registry.clone();
shell_manager.mv(args, context) let stream = async_stream! {
let name = args.call_info.name_tag.clone();
let shell_manager = args.shell_manager.clone();
let (args, _) = args.process(&registry).await?;
let mut result = shell_manager.mv(args, name)?;
while let Some(item) = result.next().await {
yield item;
}
};
Ok(stream.to_output_stream())
}
#[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; pub struct Next;
#[async_trait]
impl WholeStreamCommand for Next { impl WholeStreamCommand for Next {
fn name(&self) -> &str { fn name(&self) -> &str {
"n" "n"
@ -18,7 +19,7 @@ impl WholeStreamCommand for Next {
"Go to next shell." "Go to next shell."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
@ -30,3 +31,15 @@ impl WholeStreamCommand for Next {
fn next(_args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> { fn next(_args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::NextShell))].into()) 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::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
use nu_source::Tagged; use nu_source::Tagged;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -13,6 +13,7 @@ struct NthArgs {
pub struct Nth; pub struct Nth;
#[async_trait]
impl WholeStreamCommand for Nth { impl WholeStreamCommand for Nth {
fn name(&self) -> &str { fn name(&self) -> &str {
"nth" "nth"
@ -32,25 +33,37 @@ impl WholeStreamCommand for Nth {
"Return only the selected rows" "Return only the selected rows"
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, nth)?.run() nth(args, registry)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get the second row",
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] | nth 0 2",
result: Some(vec![Value::from("first"), Value::from("third")]),
},
]
} }
} }
fn nth( fn nth(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
NthArgs { let registry = registry.clone();
row_number, let stream = async_stream! {
rest: and_rows, let (NthArgs { row_number, rest: and_rows}, input) = args.process(&registry).await?;
}: NthArgs,
RunnableContext { input, .. }: RunnableContext, let mut inp = input.enumerate();
) -> Result<OutputStream, ShellError> { while let Some((idx, item)) = inp.next().await {
let stream = input
.enumerate()
.map(move |(idx, item)| {
let row_number = vec![row_number.clone()]; let row_number = vec![row_number.clone()];
let row_numbers = vec![&row_number, &and_rows] let row_numbers = vec![&row_number, &and_rows]
@ -58,18 +71,26 @@ fn nth(
.flatten() .flatten()
.collect::<Vec<&Tagged<u64>>>(); .collect::<Vec<&Tagged<u64>>>();
let mut result = VecDeque::new();
if row_numbers if row_numbers
.iter() .iter()
.any(|requested| requested.item == idx as u64) .any(|requested| requested.item == idx as u64)
{ {
result.push_back(ReturnSuccess::value(item)); yield ReturnSuccess::value(item);
} }
}
futures::stream::iter(result) };
})
.flatten();
Ok(stream.to_output_stream()) Ok(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

@ -1,14 +1,20 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
CallInfo, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value, use nu_source::{AnchorLocation, Span, Tagged};
};
use nu_source::{AnchorLocation, Span};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
pub struct Open; pub struct Open;
impl PerItemCommand for Open { #[derive(Deserialize)]
pub struct OpenArgs {
path: Tagged<PathBuf>,
raw: Tagged<bool>,
}
#[async_trait]
impl WholeStreamCommand for Open {
fn name(&self) -> &str { fn name(&self) -> &str {
"open" "open"
} }
@ -31,68 +37,55 @@ impl PerItemCommand for Open {
"Load a file into a cell, convert to table if possible (avoid by appending '--raw')" "Load a file into a cell, convert to table if possible (avoid by appending '--raw')"
} }
fn run( async fn run(
&self, &self,
call_info: &CallInfo, args: CommandArgs,
_registry: &CommandRegistry, registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
run(call_info, raw_args) 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,
}]
} }
} }
fn run(call_info: &CallInfo, raw_args: &RawCommandArgs) -> Result<OutputStream, ShellError> { async fn open(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let shell_manager = &raw_args.shell_manager; let cwd = PathBuf::from(args.shell_manager.path());
let cwd = PathBuf::from(shell_manager.path());
let full_path = cwd; let full_path = cwd;
let registry = registry.clone();
let path = call_info.args.nth(0).ok_or_else(|| { let (OpenArgs { path, raw }, _) = args.process(&registry).await?;
ShellError::labeled_error( let result = fetch(&full_path, &path.item, path.tag.span).await;
"No file or directory specified",
"for command",
&call_info.name_tag,
)
})?;
let path_buf = path.as_path()?; let (file_extension, contents, contents_tag) = result?;
let path_str = path_buf.display().to_string();
let path_span = path.tag.span;
let has_raw = call_info.args.has("raw");
let stream = async_stream! { let file_extension = if raw.item {
None
let result = fetch(&full_path, &path_str, path_span).await; } else {
// If the extension could not be determined via mimetype, try to use the path
if let Err(e) = result { // extension. Some file types do not declare their mimetypes (such as bson files).
yield Err(e); file_extension.or_else(|| path.extension().map(|x| x.to_string_lossy().to_string()))
return;
}
let (file_extension, contents, contents_tag) = result?;
let file_extension = if has_raw {
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_str.split('.').last().map(String::from))
};
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);
}
}; };
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( pub async fn fetch(
cwd: &PathBuf, cwd: &PathBuf,
location: &str, location: &PathBuf,
span: Span, span: Span,
) -> Result<(Option<String>, UntaggedValue, Tag), ShellError> { ) -> Result<(Option<String>, UntaggedValue, Tag), ShellError> {
let mut cwd = cwd.clone(); let mut cwd = cwd.clone();
@ -252,3 +245,15 @@ fn read_be_u16(input: &[u8]) -> Option<Vec<u16>> {
Some(result) 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,143 +0,0 @@
use crate::commands::PerItemCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
CallInfo, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
};
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;
impl PerItemCommand for Parse {
fn name(&self) -> &str {
"parse"
}
fn signature(&self) -> Signature {
Signature::build("parse").required(
"pattern",
SyntaxShape::Any,
"the pattern to match. Eg) \"{foo}: {bar}\"",
)
}
fn usage(&self) -> &str {
"Parse columns from string data using a simple pattern."
}
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
value: Value,
) -> Result<OutputStream, ShellError> {
//let value_tag = value.tag();
let pattern = call_info.args.expect_nth(0)?.as_string()?;
let parse_pattern = parse(&pattern);
let parse_regex = build_regex(&parse_pattern);
let column_names = column_names(&parse_pattern);
let regex = Regex::new(&parse_regex).map_err(|_| {
ShellError::labeled_error("Could not parse regex", "could not parse regex", &value.tag)
})?;
let output = if let Ok(s) = value.as_string() {
let mut results = 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()),
);
}
results.push(ReturnSuccess::value(dict.into_value()));
}
VecDeque::from(results)
} else {
VecDeque::new()
};
Ok(output.into())
}
}

View File

@ -0,0 +1,175 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::Tagged;
use regex::Regex;
#[derive(Deserialize)]
struct Arguments {
pattern: Tagged<String>,
regex: Tagged<bool>,
}
pub struct Command;
#[async_trait]
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"parse"
}
fn signature(&self) -> Signature {
Signature::build("parse")
.required(
"pattern",
SyntaxShape::String,
"the pattern to match. Eg) \"{foo}: {bar}\"",
)
.switch("regex", "use full regex syntax for patterns", Some('r'))
}
fn usage(&self) -> &str {
"Parse columns from string data using a simple pattern."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry).await
}
}
pub async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name_tag = args.call_info.name_tag.clone();
let (Arguments { regex, pattern }, mut input) = args.process(&registry).await?;
let regex_pattern = if let Tagged { item: true, tag } = regex {
Regex::new(&pattern.item)
.map_err(|_| ShellError::labeled_error("Invalid regex", "invalid regex", tag.span))?
} else {
let parse_regex = build_regex(&pattern.item, name_tag.clone())?;
Regex::new(&parse_regex).map_err(|_| {
ShellError::labeled_error("Invalid pattern", "invalid pattern", name_tag.span)
})?
};
let columns = column_names(&regex_pattern);
let mut parsed: VecDeque<Value> = VecDeque::new();
while let Some(v) = input.next().await {
match v.as_string() {
Ok(s) => {
let results = regex_pattern.captures_iter(&s);
for c in results {
let mut dict = TaggedDictBuilder::new(&v.tag);
for (column_name, cap) in columns.iter().zip(c.iter().skip(1)) {
let cap_string = cap.map(|v| v.as_str()).unwrap_or("").to_string();
dict.insert_untagged(column_name, UntaggedValue::string(cap_string));
}
parsed.push_back(dict.into_value());
}
}
Err(_) => {
return Err(ShellError::labeled_error_with_secondary(
"Expected string input",
"expected string input",
&name_tag,
"value originated here",
v.tag,
))
}
}
}
Ok(futures::stream::iter(parsed).to_output_stream())
}
fn build_regex(input: &str, tag: Tag) -> Result<String, ShellError> {
let mut output = "(?s)\\A".to_string();
//let mut loop_input = input;
let mut loop_input = input.chars().peekable();
loop {
let mut before = String::new();
while let Some(c) = loop_input.next() {
if c == '{' {
// If '{{', still creating a plaintext parse command, but just for a single '{' char
if loop_input.peek() == Some(&'{') {
let _ = loop_input.next();
} else {
break;
}
}
before.push(c);
}
if !before.is_empty() {
output.push_str(&regex::escape(&before));
}
// 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 loop_input.peek().is_none() {
return Err(ShellError::labeled_error(
"Found opening `{` without an associated closing `}`",
"invalid parse pattern",
tag,
));
}
}
if !column.is_empty() {
output.push_str("(?P<");
output.push_str(&column);
output.push_str(">.*?)");
}
if before.is_empty() && column.is_empty() {
break;
}
}
output.push_str("\\z");
Ok(output)
}
fn column_names(regex: &Regex) -> Vec<String> {
regex
.capture_names()
.enumerate()
.skip(1)
.map(|(i, name)| {
name.map(String::from)
.unwrap_or_else(|| format!("Capture{}", i))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::Command;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Command {})
}
}

View File

@ -0,0 +1,3 @@
mod command;
pub use command::Command as Parse;

View File

@ -18,6 +18,7 @@ pub struct PivotArgs {
ignore_titles: bool, ignore_titles: bool,
} }
#[async_trait]
impl WholeStreamCommand for Pivot { impl WholeStreamCommand for Pivot {
fn name(&self) -> &str { fn name(&self) -> &str {
"pivot" "pivot"
@ -45,25 +46,28 @@ impl WholeStreamCommand for Pivot {
"Pivots the table contents so rows become columns and columns become rows." "Pivots the table contents so rows become columns and columns become rows."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, pivot)?.run() pivot(args, registry)
} }
} }
pub fn pivot(args: PivotArgs, context: RunnableContext) -> Result<OutputStream, ShellError> { pub fn pivot(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let stream = async_stream! { let stream = async_stream! {
let input = context.input.into_vec().await; let (args, mut input): (PivotArgs, _) = args.process(&registry).await?;
let input = input.into_vec().await;
let descs = merge_descriptors(&input); let descs = merge_descriptors(&input);
let mut headers: Vec<String> = vec![]; let mut headers: Vec<String> = vec![];
if args.rest.len() > 0 && args.header_row { if args.rest.len() > 0 && args.header_row {
yield Err(ShellError::labeled_error("Can not provide header names and use header row", "using header row", context.name)); yield Err(ShellError::labeled_error("Can not provide header names and use header row", "using header row", name));
return; return;
} }
@ -75,17 +79,17 @@ pub fn pivot(args: PivotArgs, context: RunnableContext) -> Result<OutputStream,
if let Ok(s) = x.as_string() { if let Ok(s) = x.as_string() {
headers.push(s.to_string()); headers.push(s.to_string());
} else { } else {
yield Err(ShellError::labeled_error("Header row needs string headers", "used non-string headers", context.name)); yield Err(ShellError::labeled_error("Header row needs string headers", "used non-string headers", name));
return; return;
} }
} }
_ => { _ => {
yield Err(ShellError::labeled_error("Header row is incomplete and can't be used", "using incomplete header row", context.name)); yield Err(ShellError::labeled_error("Header row is incomplete and can't be used", "using incomplete header row", name));
return; return;
} }
} }
} else { } else {
yield Err(ShellError::labeled_error("Header row is incomplete and can't be used", "using incomplete header row", context.name)); yield Err(ShellError::labeled_error("Header row is incomplete and can't be used", "using incomplete header row", name));
return; return;
} }
} }
@ -107,7 +111,7 @@ pub fn pivot(args: PivotArgs, context: RunnableContext) -> Result<OutputStream,
for desc in descs { for desc in descs {
let mut column_num: usize = 0; let mut column_num: usize = 0;
let mut dict = TaggedDictBuilder::new(&context.name); let mut dict = TaggedDictBuilder::new(&name);
if !args.ignore_titles && !args.header_row { if !args.ignore_titles && !args.header_row {
dict.insert_untagged(headers[column_num].clone(), UntaggedValue::string(desc.clone())); dict.insert_untagged(headers[column_num].clone(), UntaggedValue::string(desc.clone()));
@ -134,3 +138,15 @@ pub fn pivot(args: PivotArgs, context: RunnableContext) -> Result<OutputStream,
Ok(OutputStream::new(stream)) Ok(OutputStream::new(stream))
} }
#[cfg(test)]
mod tests {
use super::Pivot;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Pivot {})
}
}

View File

@ -3,7 +3,7 @@ use crate::prelude::*;
use derive_new::new; use derive_new::new;
use log::trace; use log::trace;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value}; use nu_protocol::{ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value};
use serde::{self, Deserialize, Serialize}; use serde::{self, Deserialize, Serialize};
use std::io::prelude::*; use std::io::prelude::*;
use std::io::BufReader; use std::io::BufReader;
@ -42,6 +42,7 @@ pub struct PluginCommand {
config: Signature, config: Signature,
} }
#[async_trait]
impl WholeStreamCommand for PluginCommand { impl WholeStreamCommand for PluginCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
&self.name &self.name
@ -55,7 +56,7 @@ impl WholeStreamCommand for PluginCommand {
&self.config.usage &self.config.usage
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
@ -70,180 +71,80 @@ pub fn filter_plugin(
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
trace!("filter_plugin :: {}", path); trace!("filter_plugin :: {}", path);
let registry = registry.clone();
let scope = &args let scope = args.call_info.scope.clone();
.call_info
.scope
.clone()
.set_it(UntaggedValue::string("$it").into_untagged_value());
let args = args.evaluate_once_with_scope(registry, &scope)?; let stream = async_stream! {
let mut args = args.evaluate_once_with_scope(&registry, &scope).await?;
let mut child = std::process::Command::new(path) let mut child = std::process::Command::new(path)
.stdin(std::process::Stdio::piped()) .stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped())
.spawn() .spawn()
.expect("Failed to spawn child process"); .expect("Failed to spawn child process");
let mut bos: VecDeque<Value> = VecDeque::new(); let call_info = args.call_info.clone();
bos.push_back(UntaggedValue::Primitive(Primitive::BeginningOfStream).into_untagged_value());
let bos = futures::stream::iter(bos);
let mut eos: VecDeque<Value> = VecDeque::new(); trace!("filtering :: {:?}", call_info);
eos.push_back(UntaggedValue::Primitive(Primitive::EndOfStream).into_untagged_value());
let eos = futures::stream::iter(eos);
let call_info = args.call_info.clone(); // Beginning of the stream
{
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
trace!("filtering :: {:?}", call_info); let mut reader = BufReader::new(stdout);
let stream = bos let request = JsonRpc::new("begin_filter", call_info.clone());
.chain(args.input) let request_raw = serde_json::to_string(&request);
.chain(eos)
.map(move |v| match v {
Value {
value: UntaggedValue::Primitive(Primitive::BeginningOfStream),
..
} => {
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
let mut reader = BufReader::new(stdout); match request_raw {
Err(_) => {
let request = JsonRpc::new("begin_filter", call_info.clone()); yield Err(ShellError::labeled_error(
let request_raw = serde_json::to_string(&request); "Could not load json from plugin",
"could not load json from plugin",
match request_raw { &call_info.name_tag,
Err(_) => { ));
let mut result = VecDeque::new();
result.push_back(Err(ShellError::labeled_error(
"Could not load json from plugin",
"could not load json from plugin",
&call_info.name_tag,
)));
return result;
}
Ok(request_raw) => match stdin.write(format!("{}\n", request_raw).as_bytes()) {
Ok(_) => {}
Err(err) => {
let mut result = VecDeque::new();
result.push_back(Err(ShellError::unexpected(format!("{}", err))));
return result;
}
},
} }
Ok(request_raw) => match stdin.write(format!("{}\n", request_raw).as_bytes()) {
let mut input = String::new(); Ok(_) => {}
match reader.read_line(&mut input) {
Ok(_) => {
let response = serde_json::from_str::<NuResult>(&input);
match response {
Ok(NuResult::response { params }) => match params {
Ok(params) => params,
Err(e) => {
let mut result = VecDeque::new();
result.push_back(ReturnValue::Err(e));
result
}
},
Err(e) => {
let mut result = VecDeque::new();
result.push_back(Err(ShellError::untagged_runtime_error(format!(
"Error while processing begin_filter response: {:?} {}",
e, input
))));
result
}
}
}
Err(e) => {
let mut result = VecDeque::new();
result.push_back(Err(ShellError::untagged_runtime_error(format!(
"Error while reading begin_filter response: {:?}",
e
))));
result
}
}
}
Value {
value: UntaggedValue::Primitive(Primitive::EndOfStream),
..
} => {
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
let mut reader = BufReader::new(stdout);
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("end_filter", vec![]);
let request_raw = match serde_json::to_string(&request) {
Ok(req) => req,
Err(err) => { Err(err) => {
let mut result = VecDeque::new(); yield Err(ShellError::unexpected(format!("{}", err)));
result.push_back(Err(ShellError::unexpected(format!("{}", err))));
return result;
} }
}; },
}
let _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error let mut input = String::new();
match reader.read_line(&mut input) {
let mut input = String::new(); Ok(_) => {
let result = match reader.read_line(&mut input) { let response = serde_json::from_str::<NuResult>(&input);
Ok(_) => { match response {
let response = serde_json::from_str::<NuResult>(&input); Ok(NuResult::response { params }) => match params {
match response { Ok(params) => for param in params { yield param },
Ok(NuResult::response { params }) => match params {
Ok(params) => {
let request: JsonRpc<std::vec::Vec<Value>> =
JsonRpc::new("quit", vec![]);
let request_raw = serde_json::to_string(&request);
match request_raw {
Ok(request_raw) => {
let _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error
}
Err(e) => {
let mut result = VecDeque::new();
result.push_back(Err(ShellError::untagged_runtime_error(format!(
"Error while processing begin_filter response: {:?} {}",
e, input
))));
return result;
}
}
params
}
Err(e) => {
let mut result = VecDeque::new();
result.push_back(ReturnValue::Err(e));
result
}
},
Err(e) => { Err(e) => {
let mut result = VecDeque::new(); yield ReturnValue::Err(e);
result.push_back(Err(ShellError::untagged_runtime_error(format!(
"Error while processing end_filter response: {:?} {}",
e, input
))));
result
} }
},
Err(e) => {
yield Err(ShellError::untagged_runtime_error(format!(
"Error while processing begin_filter response: {:?} {}",
e, input
)));
} }
} }
Err(e) => { }
let mut result = VecDeque::new(); Err(e) => {
result.push_back(Err(ShellError::untagged_runtime_error(format!( yield Err(ShellError::untagged_runtime_error(format!(
"Error while reading end_filter: {:?}", "Error while reading begin_filter response: {:?}",
e e
)))); )));
result }
}
};
let _ = child.wait();
result
} }
_ => { }
// Stream contents
{
while let Some(v) = args.input.next().await {
let stdin = child.stdin.as_mut().expect("Failed to open stdin"); let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout"); let stdout = child.stdout.as_mut().expect("Failed to open stdout");
@ -256,12 +157,10 @@ pub fn filter_plugin(
let _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error let _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error
} }
Err(e) => { Err(e) => {
let mut result = VecDeque::new(); yield Err(ShellError::untagged_runtime_error(format!(
result.push_back(Err(ShellError::untagged_runtime_error(format!(
"Error while processing filter response: {:?}", "Error while processing filter response: {:?}",
e e
)))); )));
return result;
} }
} }
@ -271,36 +170,110 @@ pub fn filter_plugin(
let response = serde_json::from_str::<NuResult>(&input); let response = serde_json::from_str::<NuResult>(&input);
match response { match response {
Ok(NuResult::response { params }) => match params { Ok(NuResult::response { params }) => match params {
Ok(params) => params, Ok(params) => for param in params { yield param },
Err(e) => { Err(e) => {
let mut result = VecDeque::new(); yield ReturnValue::Err(e);
result.push_back(ReturnValue::Err(e));
result
} }
}, },
Err(e) => { Err(e) => {
let mut result = VecDeque::new(); yield Err(ShellError::untagged_runtime_error(format!(
result.push_back(Err(ShellError::untagged_runtime_error(format!(
"Error while processing filter response: {:?}\n== input ==\n{}", "Error while processing filter response: {:?}\n== input ==\n{}",
e, input e, input
)))); )));
result
} }
} }
} }
Err(e) => { Err(e) => {
let mut result = VecDeque::new(); yield Err(ShellError::untagged_runtime_error(format!(
result.push_back(Err(ShellError::untagged_runtime_error(format!(
"Error while reading filter response: {:?}", "Error while reading filter response: {:?}",
e e
)))); )));
result
} }
} }
} }
}) }
.map(futures::stream::iter) // convert to a stream
.flatten(); // post stream contents
{
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
let mut reader = BufReader::new(stdout);
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("end_filter", vec![]);
let request_raw = serde_json::to_string(&request);
match request_raw {
Err(_) => {
yield Err(ShellError::labeled_error(
"Could not load json from plugin",
"could not load json from plugin",
&call_info.name_tag,
));
}
Ok(request_raw) => match stdin.write(format!("{}\n", request_raw).as_bytes()) {
Ok(_) => {}
Err(err) => {
yield Err(ShellError::unexpected(format!("{}", err)));
}
},
}
let mut input = String::new();
match reader.read_line(&mut input) {
Ok(_) => {
let response = serde_json::from_str::<NuResult>(&input);
match response {
Ok(NuResult::response { params }) => match params {
Ok(params) => for param in params { yield param },
Err(e) => {
yield ReturnValue::Err(e);
}
},
Err(e) => {
yield Err(ShellError::untagged_runtime_error(format!(
"Error while processing end_filter response: {:?} {}",
e, input
)));
}
}
}
Err(e) => {
yield Err(ShellError::untagged_runtime_error(format!(
"Error while reading end_filter response: {:?}",
e
)));
}
}
}
// End of the stream
{
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
let mut reader = BufReader::new(stdout);
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("quit", vec![]);
let request_raw = serde_json::to_string(&request);
match request_raw {
Ok(request_raw) => {
let _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error
}
Err(e) => {
yield Err(ShellError::untagged_runtime_error(format!(
"Error while processing quit response: {:?}",
e
)));
return;
}
}
}
let _ = child.wait();
};
Ok(stream.to_output_stream()) Ok(stream.to_output_stream())
} }
@ -312,6 +285,7 @@ pub struct PluginSink {
config: Signature, config: Signature,
} }
#[async_trait]
impl WholeStreamCommand for PluginSink { impl WholeStreamCommand for PluginSink {
fn name(&self) -> &str { fn name(&self) -> &str {
&self.name &self.name
@ -325,7 +299,7 @@ impl WholeStreamCommand for PluginSink {
&self.config.usage &self.config.usage
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
@ -339,10 +313,11 @@ pub fn sink_plugin(
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?; let registry = registry.clone();
let call_info = args.call_info.clone();
let stream = async_stream! { let stream = async_stream! {
let args = args.evaluate_once(&registry).await?;
let call_info = args.call_info.clone();
let input: Vec<Value> = args.input.collect().await; let input: Vec<Value> = args.input.collect().await;
let request = JsonRpc::new("sink", (call_info.clone(), input)); let request = JsonRpc::new("sink", (call_info.clone(), input));

View File

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

View File

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

View File

@ -5,6 +5,7 @@ use nu_protocol::Signature;
pub struct Pwd; pub struct Pwd;
#[async_trait]
impl WholeStreamCommand for Pwd { impl WholeStreamCommand for Pwd {
fn name(&self) -> &str { fn name(&self) -> &str {
"pwd" "pwd"
@ -18,17 +19,42 @@ impl WholeStreamCommand for Pwd {
"Output the current working directory." "Output the current working directory."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
pwd(args, registry) pwd(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Print the current working directory",
example: "pwd",
result: None,
}]
} }
} }
pub fn pwd(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { pub async fn pwd(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let shell_manager = args.shell_manager.clone(); let shell_manager = args.shell_manager.clone();
let args = args.evaluate_once(registry)?; let args = args.evaluate_once(&registry).await?;
shell_manager.pwd(args) shell_manager.pwd(args)
} }
#[cfg(test)]
mod tests {
use super::Pwd;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Pwd {})
}
}

View File

@ -3,7 +3,7 @@ use crate::context::CommandRegistry;
use crate::deserializer::NumericRange; use crate::deserializer::NumericRange;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
use nu_source::Tagged; use nu_source::Tagged;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -13,6 +13,7 @@ struct RangeArgs {
pub struct Range; pub struct Range;
#[async_trait]
impl WholeStreamCommand for Range { impl WholeStreamCommand for Range {
fn name(&self) -> &str { fn name(&self) -> &str {
"range" "range"
@ -30,25 +31,43 @@ impl WholeStreamCommand for Range {
"Return only the selected rows" "Return only the selected rows"
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, range)?.run() range(args, registry)
} }
} }
fn range( fn range(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
RangeArgs { area }: RangeArgs, let registry = registry.clone();
RunnableContext { input, .. }: RunnableContext, let stream = async_stream! {
) -> Result<OutputStream, ShellError> { let (RangeArgs { area }, mut input) = args.process(&registry).await?;
let range = area.item; let range = area.item;
let (from, _) = range.from; let (from, _) = range.from;
let (to, _) = range.to; let (to, _) = range.to;
let from = *from as usize; let from = *from as usize;
let to = *to as usize; let to = *to as usize;
Ok(input.skip(from).take(to - from + 1).to_output_stream()) let mut inp = input.skip(from).take(to - from + 1);
while let Some(item) = inp.next().await {
yield ReturnSuccess::value(item);
}
};
Ok(stream.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Range;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Range {})
}
} }

View File

@ -13,6 +13,7 @@ pub struct ReduceByArgs {
reduce_with: Option<Tagged<String>>, reduce_with: Option<Tagged<String>>,
} }
#[async_trait]
impl WholeStreamCommand for ReduceBy { impl WholeStreamCommand for ReduceBy {
fn name(&self) -> &str { fn name(&self) -> &str {
"reduce-by" "reduce-by"
@ -31,42 +32,52 @@ impl WholeStreamCommand for ReduceBy {
"Creates a new table with the data from the tables rows reduced by the command given." "Creates a new table with the data from the tables rows reduced by the command given."
} }
fn run( async fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, reduce_by)?.run() reduce_by(args, registry).await
} }
} }
pub fn reduce_by( pub async fn reduce_by(
ReduceByArgs { reduce_with }: ReduceByArgs, args: CommandArgs,
RunnableContext { input, name, .. }: RunnableContext, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let stream = async_stream! { let registry = registry.clone();
let values: Vec<Value> = input.collect().await; let name = args.call_info.name_tag.clone();
let (ReduceByArgs { reduce_with }, mut input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
if values.is_empty() { if values.is_empty() {
yield Err(ShellError::labeled_error( return Err(ShellError::labeled_error(
"Expected table from pipeline", "Expected table from pipeline",
"requires a table input", "requires a table input",
name name,
)) ));
} else { }
let reduce_with = if let Some(reducer) = reduce_with { let reduce_with = if let Some(reducer) = reduce_with {
Some(reducer.item().clone()) Some(reducer.item().clone())
} else { } else {
None None
};
match reduce(&values[0], reduce_with, name) {
Ok(reduced) => yield ReturnSuccess::value(reduced),
Err(err) => yield Err(err)
}
}
}; };
Ok(stream.to_output_stream()) match reduce(&values[0], reduce_with, name) {
Ok(reduced) => Ok(OutputStream::one(ReturnSuccess::value(reduced))),
Err(err) => Err(err),
}
}
#[cfg(test)]
mod tests {
use super::ReduceBy;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(ReduceBy {})
}
} }

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