Compare commits

...

78 Commits

Author SHA1 Message Date
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
279 changed files with 7102 additions and 2636 deletions

View File

@ -37,6 +37,8 @@ steps:
fi
if [ "$(uname)" == "Darwin" ]; then
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
fi
rustup update

9
.gitignore vendored
View File

@ -11,3 +11,12 @@ debian/debhelper-build-stamp
debian/files
debian/nu.substvars
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 \
libssl-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:
file: .gitpod.Dockerfile
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
github:
prebuilds:
# enable for the master/default branch (defaults to true)
master: true
# enable for all branches in this repo (defaults to false)
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
# 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
vscode:
extensions:
@ -25,5 +21,5 @@ vscode:
- Swellaby.vscode-rust-test-adapter@0.11.0:Xg+YeZZQiVpVUsIkH+uiiw==
- serayuzgur.crates@0.4.7:HMkoguLcXp9M3ud7ac3eIw==
- belfz.search-crates-io@1.2.1:kSLnyrOhXtYPjQpKnMr4eQ==
- vadimcn.vscode-lldb@1.4.5:lwHCNwtm0kmOBXeQUIPGMQ==
- 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

@ -9,3 +9,20 @@ For speedy contributions open it in Gitpod, nu will be pre-installed with the la
To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)!
<!--WIP-->
# Developing
## Set up
This is no different than other Rust projects.
```shell
git clone https://github.com/nushell/nushell
cd nushell
cargo build
```
## Tests
Run tests with:
```shell
cargo test --all --features=stable,test-bins
```

84
Cargo.lock generated
View File

@ -809,6 +809,15 @@ version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
[[package]]
name = "eml-parser"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9e30d14e24cd200f2351837a02feacf8f043410f2a56441868c93ef33f90239"
dependencies = [
"regex",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
@ -2060,7 +2069,7 @@ dependencies = [
[[package]]
name = "nu"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"clap",
"crossterm",
@ -2084,6 +2093,7 @@ dependencies = [
"nu_plugin_match",
"nu_plugin_post",
"nu_plugin_ps",
"nu_plugin_start",
"nu_plugin_str",
"nu_plugin_sys",
"nu_plugin_textview",
@ -2098,7 +2108,7 @@ dependencies = [
[[package]]
name = "nu-build"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"lazy_static 1.4.0",
"serde 1.0.106",
@ -2108,7 +2118,7 @@ dependencies = [
[[package]]
name = "nu-cli"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"ansi_term 0.12.1",
"app_dirs",
@ -2128,6 +2138,7 @@ dependencies = [
"derive-new",
"dirs 2.0.2",
"dunce",
"eml-parser",
"filesize",
"futures 0.3.4",
"futures-util",
@ -2165,6 +2176,7 @@ dependencies = [
"quickcheck",
"quickcheck_macros",
"rand",
"rayon",
"regex",
"roxmltree",
"rusqlite",
@ -2189,16 +2201,18 @@ dependencies = [
"umask",
"unicode-xid",
"users",
"which",
]
[[package]]
name = "nu-errors"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"ansi_term 0.12.1",
"bigdecimal",
"derive-new",
"getset",
"glob",
"language-reporting",
"nu-build",
"nu-source",
@ -2212,7 +2226,7 @@ dependencies = [
[[package]]
name = "nu-parser"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"bigdecimal",
"derive-new",
@ -2231,7 +2245,7 @@ dependencies = [
[[package]]
name = "nu-plugin"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"indexmap",
"nu-build",
@ -2246,7 +2260,7 @@ dependencies = [
[[package]]
name = "nu-protocol"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"ansi_term 0.12.1",
"bigdecimal",
@ -2274,7 +2288,7 @@ dependencies = [
[[package]]
name = "nu-source"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"derive-new",
"getset",
@ -2287,7 +2301,7 @@ dependencies = [
[[package]]
name = "nu-test-support"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"app_dirs",
"dunce",
@ -2303,7 +2317,7 @@ dependencies = [
[[package]]
name = "nu-value-ext"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"indexmap",
"itertools 0.9.0",
@ -2317,7 +2331,7 @@ dependencies = [
[[package]]
name = "nu_plugin_average"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"nu-build",
"nu-errors",
@ -2328,7 +2342,7 @@ dependencies = [
[[package]]
name = "nu_plugin_binaryview"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"ansi_term 0.12.1",
"crossterm",
@ -2345,7 +2359,7 @@ dependencies = [
[[package]]
name = "nu_plugin_fetch"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"futures 0.3.4",
"nu-build",
@ -2359,7 +2373,7 @@ dependencies = [
[[package]]
name = "nu_plugin_inc"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"nu-build",
"nu-errors",
@ -2372,7 +2386,7 @@ dependencies = [
[[package]]
name = "nu_plugin_match"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"futures 0.3.4",
"nu-build",
@ -2385,7 +2399,7 @@ dependencies = [
[[package]]
name = "nu_plugin_post"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"base64 0.12.0",
"futures 0.3.4",
@ -2402,7 +2416,7 @@ dependencies = [
[[package]]
name = "nu_plugin_ps"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"futures 0.3.4",
"futures-timer 3.0.2",
@ -2414,9 +2428,22 @@ dependencies = [
"nu-source",
]
[[package]]
name = "nu_plugin_start"
version = "0.1.0"
dependencies = [
"nu-build",
"nu-errors",
"nu-plugin",
"nu-protocol",
"nu-source",
"open",
"url",
]
[[package]]
name = "nu_plugin_str"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"chrono",
"nu-build",
@ -2431,7 +2458,7 @@ dependencies = [
[[package]]
name = "nu_plugin_sys"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"battery",
"futures 0.3.4",
@ -2446,7 +2473,7 @@ dependencies = [
[[package]]
name = "nu_plugin_textview"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"ansi_term 0.12.1",
"crossterm",
@ -2461,7 +2488,7 @@ dependencies = [
[[package]]
name = "nu_plugin_tree"
version = "0.13.0"
version = "0.14.0"
dependencies = [
"derive-new",
"nu-build",
@ -3175,9 +3202,9 @@ dependencies = [
[[package]]
name = "rustyline"
version = "6.1.1"
version = "6.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a7384a84da856bd163ef2c22982c816b0bcedaf17978ec13d8e0e277ddc332"
checksum = "1cd20b28d972040c627e209eb29f19c24a71a19d661cc5a220089176e20ee202"
dependencies = [
"cfg-if",
"dirs 2.0.2",
@ -3185,6 +3212,7 @@ dependencies = [
"log",
"memchr",
"nix 0.17.0",
"scopeguard",
"unicode-segmentation",
"unicode-width",
"utf8parse 0.2.0",
@ -4073,6 +4101,16 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "which"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
dependencies = [
"failure",
"libc",
]
[[package]]
name = "widestring"
version = "0.4.0"

View File

@ -1,6 +1,6 @@
[package]
name = "nu"
version = "0.13.0"
version = "0.14.0"
authors = ["The Nu Project Contributors"]
description = "A new type of shell"
license = "MIT"
@ -18,24 +18,25 @@ members = ["crates/*/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-cli = { version = "0.13.0", path = "./crates/nu-cli" }
nu-source = { version = "0.13.0", path = "./crates/nu-source" }
nu-plugin = { version = "0.13.0", path = "./crates/nu-plugin" }
nu-protocol = { version = "0.13.0", path = "./crates/nu-protocol" }
nu-errors = { version = "0.13.0", path = "./crates/nu-errors" }
nu-parser = { version = "0.13.0", path = "./crates/nu-parser" }
nu-value-ext = { version = "0.13.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.13.0", path = "./crates/nu_plugin_binaryview", optional=true }
nu_plugin_fetch = { version = "0.13.0", path = "./crates/nu_plugin_fetch", optional=true }
nu_plugin_inc = { version = "0.13.0", path = "./crates/nu_plugin_inc", optional=true }
nu_plugin_match = { version = "0.13.0", path = "./crates/nu_plugin_match", optional=true }
nu_plugin_post = { version = "0.13.0", path = "./crates/nu_plugin_post", optional=true }
nu_plugin_ps = { version = "0.13.0", path = "./crates/nu_plugin_ps", optional=true }
nu_plugin_str = { version = "0.13.0", path = "./crates/nu_plugin_str", optional=true }
nu_plugin_sys = { version = "0.13.0", path = "./crates/nu_plugin_sys", optional=true }
nu_plugin_textview = { version = "0.13.0", path = "./crates/nu_plugin_textview", optional=true }
nu_plugin_tree = { version = "0.13.0", path = "./crates/nu_plugin_tree", optional=true }
nu-cli = { version = "0.14.0", path = "./crates/nu-cli" }
nu-source = { version = "0.14.0", path = "./crates/nu-source" }
nu-plugin = { version = "0.14.0", path = "./crates/nu-plugin" }
nu-protocol = { version = "0.14.0", path = "./crates/nu-protocol" }
nu-errors = { version = "0.14.0", path = "./crates/nu-errors" }
nu-parser = { version = "0.14.0", path = "./crates/nu-parser" }
nu-value-ext = { version = "0.14.0", path = "./crates/nu-value-ext" }
nu_plugin_average = { version = "0.14.0", path = "./crates/nu_plugin_average", optional=true }
nu_plugin_binaryview = { version = "0.14.0", path = "./crates/nu_plugin_binaryview", optional=true }
nu_plugin_fetch = { version = "0.14.0", path = "./crates/nu_plugin_fetch", optional=true }
nu_plugin_inc = { version = "0.14.0", path = "./crates/nu_plugin_inc", optional=true }
nu_plugin_match = { version = "0.14.0", path = "./crates/nu_plugin_match", optional=true }
nu_plugin_post = { version = "0.14.0", path = "./crates/nu_plugin_post", optional=true }
nu_plugin_ps = { version = "0.14.0", path = "./crates/nu_plugin_ps", optional=true }
nu_plugin_start = { version = "0.1.0", path = "./crates/nu_plugin_start", optional=true }
nu_plugin_str = { version = "0.14.0", path = "./crates/nu_plugin_str", optional=true }
nu_plugin_sys = { version = "0.14.0", path = "./crates/nu_plugin_sys", optional=true }
nu_plugin_textview = { version = "0.14.0", path = "./crates/nu_plugin_textview", optional=true }
nu_plugin_tree = { version = "0.14.0", path = "./crates/nu_plugin_tree", optional=true }
crossterm = { version = "0.17.2", optional = true }
semver = { version = "0.9.0", optional = true }
@ -50,19 +51,19 @@ log = "0.4.8"
pretty_env_logger = "0.4.0"
[dev-dependencies]
nu-test-support = { version = "0.13.0", path = "./crates/nu-test-support" }
nu-test-support = { version = "0.14.0", path = "./crates/nu-test-support" }
[build-dependencies]
toml = "0.5.6"
serde = { version = "1.0.106", features = ["derive"] }
nu-build = { version = "0.13.0", path = "./crates/nu-build" }
nu-build = { version = "0.14.0", path = "./crates/nu-build" }
[features]
# Test executables
test-bins = []
default = ["sys", "ps", "textview", "inc", "str"]
stable = ["default", "starship-prompt", "binaryview", "match", "tree", "average", "post", "fetch", "clipboard-cli", "trash-support"]
stable = ["default", "starship-prompt", "binaryview", "match", "tree", "average", "post", "fetch", "clipboard-cli", "trash-support", "start"]
# Default
textview = ["crossterm", "syntect", "url", "nu_plugin_textview"]
@ -79,6 +80,7 @@ match = ["nu_plugin_match"]
post = ["nu_plugin_post"]
trace = ["nu-parser/trace"]
tree = ["nu_plugin_tree"]
start = ["nu_plugin_start"]
clipboard-cli = ["nu-cli/clipboard-cli"]
starship-prompt = ["nu-cli/starship-prompt"]
@ -168,6 +170,11 @@ name = "nu_plugin_stable_tree"
path = "src/plugins/nu_plugin_stable_tree.rs"
required-features = ["tree"]
[[bin]]
name = "nu_plugin_stable_start"
path = "src/plugins/nu_plugin_stable_start.rs"
required-features = ["start"]
# Main nu binary
[[bin]]
name = "nu"

View File

@ -276,6 +276,10 @@ 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).
# Contributing
See [Contributing](CONTRIBUTING.md) for details.
# License
The project is made available under the MIT license. See the `LICENSE` file for more information.

View File

@ -1,6 +1,6 @@
[package]
name = "nu-build"
version = "0.13.0"
version = "0.14.0"
authors = ["The Nu Project Contributors"]
edition = "2018"
description = "Core build system for nushell"

View File

@ -1,6 +1,6 @@
[package]
name = "nu-cli"
version = "0.13.0"
version = "0.14.0"
authors = ["The Nu Project Contributors"]
description = "CLI for nushell"
edition = "2018"
@ -10,13 +10,14 @@ license = "MIT"
doctest = false
[dependencies]
nu-source = { version = "0.13.0", path = "../nu-source" }
nu-plugin = { version = "0.13.0", path = "../nu-plugin" }
nu-protocol = { version = "0.13.0", path = "../nu-protocol" }
nu-errors = { version = "0.13.0", path = "../nu-errors" }
nu-parser = { version = "0.13.0", path = "../nu-parser" }
nu-value-ext = { version = "0.13.0", path = "../nu-value-ext" }
nu-test-support = { version = "0.13.0", path = "../nu-test-support" }
nu-source = { version = "0.14.0", path = "../nu-source" }
nu-plugin = { version = "0.14.0", path = "../nu-plugin" }
nu-protocol = { version = "0.14.0", path = "../nu-protocol" }
nu-errors = { version = "0.14.0", path = "../nu-errors" }
nu-parser = { version = "0.14.0", path = "../nu-parser" }
nu-value-ext = { version = "0.14.0", path = "../nu-value-ext" }
nu-test-support = { version = "0.14.0", path = "../nu-test-support" }
ansi_term = "0.12.1"
app_dirs = "1.2.1"
@ -35,6 +36,7 @@ ctrlc = "3.1.4"
derive-new = "0.5.8"
dirs = "2.0.2"
dunce = "1.0.0"
eml-parser = "0.1.0"
filesize = "0.2.0"
futures = { version = "0.3", features = ["compat", "io-compat"] }
futures-util = "0.3.4"
@ -64,7 +66,7 @@ query_interface = "0.3.5"
rand = "0.7"
regex = "1"
roxmltree = "0.10.1"
rustyline = "6.1.1"
rustyline = "6.1.2"
serde = { version = "1.0.106", features = ["derive"] }
serde-hjson = "0.9.1"
serde_bytes = "0.11.3"
@ -82,10 +84,12 @@ toml = "0.5.6"
typetag = "0.1.4"
umask = "0.1"
unicode-xid = "0.2.0"
which = "3"
trash = { version = "1.0.0", optional = true }
clipboard = { version = "0.5", optional = true }
starship = { version = "0.39.0", optional = true }
rayon = "1.3.0"
[target.'cfg(unix)'.dependencies]
users = "0.10.0"
@ -95,7 +99,7 @@ version = "0.22.0"
features = ["bundled", "blob"]
[build-dependencies]
nu-build = { version = "0.13.0", path = "../nu-build" }
nu-build = { version = "0.14.0", path = "../nu-build" }
[dev-dependencies]
quickcheck = "0.9"

View File

@ -6,6 +6,7 @@ use crate::commands::whole_stream_command;
use crate::context::Context;
#[cfg(not(feature = "starship-prompt"))]
use crate::git::current_branch;
use crate::path::canonicalize;
use crate::prelude::*;
use futures_codec::FramedRead;
@ -22,9 +23,11 @@ use rustyline::{
use std::error::Error;
use std::io::{BufRead, BufReader, Write};
use std::iter::Iterator;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering;
use rayon::prelude::*;
fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), ShellError> {
let mut child = std::process::Command::new(path)
.stdin(std::process::Stdio::piped())
@ -131,62 +134,60 @@ pub fn load_plugins(context: &mut Context) -> Result<(), ShellError> {
pattern.push(std::path::Path::new("nu_plugin_[a-z0-9][a-z0-9]*"));
match glob::glob_with(&pattern.to_string_lossy(), opts) {
Err(_) => {}
Ok(binaries) => {
for bin in binaries.filter_map(Result::ok) {
if !bin.is_file() {
continue;
let plugs: Vec<_> = glob::glob_with(&pattern.to_string_lossy(), opts)?
.filter_map(|x| x.ok())
.collect();
let _failures: Vec<_> = plugs
.par_iter()
.map(|path| {
let bin_name = {
if let Some(name) = path.file_name() {
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 = {
if let Some(name) = bin.file_name() {
match name.to_str() {
Some(raw) => raw,
None => continue,
}
} 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);
#[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 {:?}", path.display());
// we are ok if this plugin load fails
let _ = load_plugin(&path, &mut context.clone());
}
}
}
})
.collect();
}
Ok(())
@ -223,7 +224,8 @@ fn create_default_starship_config() -> Option<toml::Value> {
}
pub fn create_default_context(
syncer: &mut crate::env::environment_syncer::EnvironmentSyncer,
syncer: &mut crate::EnvironmentSyncer,
interactive: bool,
) -> Result<Context, Box<dyn Error>> {
syncer.load_environment();
@ -237,28 +239,30 @@ pub fn create_default_context(
context.add_commands(vec![
// System/file operations
whole_stream_command(Pwd),
per_item_command(Ls),
per_item_command(Du),
whole_stream_command(Ls),
whole_stream_command(Du),
whole_stream_command(Cd),
per_item_command(Remove),
per_item_command(Open),
whole_stream_command(Remove),
whole_stream_command(Open),
whole_stream_command(Config),
per_item_command(Help),
per_item_command(History),
whole_stream_command(Help),
whole_stream_command(History),
whole_stream_command(Save),
per_item_command(Touch),
per_item_command(Cpy),
whole_stream_command(Touch),
whole_stream_command(Cpy),
whole_stream_command(Date),
per_item_command(Calc),
per_item_command(Mkdir),
per_item_command(Move),
per_item_command(Kill),
whole_stream_command(Cal),
whole_stream_command(Calc),
whole_stream_command(Mkdir),
whole_stream_command(Move),
whole_stream_command(Kill),
whole_stream_command(Version),
whole_stream_command(Clear),
whole_stream_command(What),
whole_stream_command(Which),
whole_stream_command(Debug),
per_item_command(Alias),
whole_stream_command(Alias),
whole_stream_command(WithEnv),
// Statistics
whole_stream_command(Size),
whole_stream_command(Count),
@ -268,7 +272,7 @@ pub fn create_default_context(
whole_stream_command(Next),
whole_stream_command(Previous),
whole_stream_command(Shells),
per_item_command(Enter),
whole_stream_command(Enter),
whole_stream_command(Exit),
// Viewers
whole_stream_command(Autoview),
@ -278,14 +282,14 @@ pub fn create_default_context(
whole_stream_command(SplitRow),
whole_stream_command(Lines),
whole_stream_command(Trim),
per_item_command(Echo),
per_item_command(Parse),
whole_stream_command(Echo),
whole_stream_command(Parse),
// Column manipulation
whole_stream_command(Reject),
whole_stream_command(Pick),
whole_stream_command(Select),
whole_stream_command(Get),
per_item_command(Edit),
per_item_command(Insert),
whole_stream_command(Update),
whole_stream_command(Insert),
whole_stream_command(SplitBy),
// Row manipulation
whole_stream_command(Reverse),
@ -295,18 +299,25 @@ pub fn create_default_context(
whole_stream_command(GroupBy),
whole_stream_command(First),
whole_stream_command(Last),
whole_stream_command(Skip),
whole_stream_command(Nth),
per_item_command(Format),
per_item_command(Where),
whole_stream_command(Drop),
whole_stream_command(Format),
whole_stream_command(Where),
whole_stream_command(Compact),
whole_stream_command(Default),
whole_stream_command(Skip),
whole_stream_command(SkipUntil),
whole_stream_command(SkipWhile),
whole_stream_command(Keep),
whole_stream_command(KeepUntil),
whole_stream_command(KeepWhile),
whole_stream_command(Range),
whole_stream_command(Rename),
whole_stream_command(Uniq),
per_item_command(Each),
whole_stream_command(Each),
whole_stream_command(IsEmpty),
// Table manipulation
whole_stream_command(Merge),
whole_stream_command(Shuffle),
whole_stream_command(Wrap),
whole_stream_command(Pivot),
@ -315,6 +326,7 @@ pub fn create_default_context(
whole_stream_command(Histogram),
whole_stream_command(Sum),
// File format output
whole_stream_command(To),
whole_stream_command(ToBSON),
whole_stream_command(ToCSV),
whole_stream_command(ToHTML),
@ -327,7 +339,9 @@ pub fn create_default_context(
whole_stream_command(ToURL),
whole_stream_command(ToYAML),
// File format input
whole_stream_command(From),
whole_stream_command(FromCSV),
whole_stream_command(FromEML),
whole_stream_command(FromTSV),
whole_stream_command(FromSSV),
whole_stream_command(FromINI),
@ -345,7 +359,7 @@ pub fn create_default_context(
whole_stream_command(FromIcs),
whole_stream_command(FromVcf),
// "Private" commands (not intended to be accessed directly)
whole_stream_command(RunExternalCommand),
whole_stream_command(RunExternalCommand { interactive }),
]);
cfg_if::cfg_if! {
@ -375,7 +389,7 @@ pub async fn run_vec_of_pipelines(
redirect_stdin: bool,
) -> Result<(), Box<dyn Error>> {
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);
@ -445,7 +459,7 @@ pub async fn run_pipeline_standalone(
};
context.maybe_print_errors(Text::from(line));
if error_code != 0 {
if error_code != 0 && exit_on_error {
std::process::exit(error_code);
}
}
@ -469,8 +483,13 @@ pub async fn run_pipeline_standalone(
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input.
pub async fn cli() -> Result<(), Box<dyn Error>> {
let mut syncer = crate::env::environment_syncer::EnvironmentSyncer::new();
let mut context = create_default_context(&mut syncer)?;
#[cfg(windows)]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
#[cfg(not(windows))]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
let mut syncer = crate::EnvironmentSyncer::new();
let mut context = create_default_context(&mut syncer, true)?;
let _ = load_plugins(&mut context);
@ -550,14 +569,21 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
rl.set_edit_mode(edit_mode);
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())?
.get("completion_mode")
.map(|s| match s.value.expect_string() {
"list" => CompletionType::List,
"circular" => CompletionType::Circular,
_ => CompletionType::Circular,
_ => DEFAULT_COMPLETION_MODE,
})
.unwrap_or(CompletionType::Circular);
.unwrap_or(DEFAULT_COMPLETION_MODE);
rl.set_completion_type(completion_mode);
@ -710,7 +736,7 @@ async fn process_line(
debug!("=== Parsed ===");
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);
//println!("{:#?}", pipeline);
@ -759,9 +785,9 @@ async fn process_line(
.as_ref()
.map(NamedArguments::is_empty)
.unwrap_or(true)
&& dunce::canonicalize(&name).is_ok()
&& PathBuf::from(&name).is_dir()
&& ichwh::which(&name).await.unwrap_or(None).is_none()
&& canonicalize(ctx.shell_manager.path(), name).is_ok()
&& Path::new(&name).is_dir()
&& which::which(&name).is_err()
{
// Here we work differently if we're in Windows because of the expected Windows behavior
#[cfg(windows)]
@ -787,7 +813,8 @@ async fn process_line(
ctx.shell_manager.set_path(val.to_string());
return LineResult::Success(line.to_string());
} else {
ctx.shell_manager.set_path(name.to_string());
ctx.shell_manager
.set_path(format!("{}\\", name.to_string()));
return LineResult::Success(line.to_string());
}
} else {
@ -829,7 +856,11 @@ async fn process_line(
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, &Scope::env(env)).await {
Ok(input) => {
// Running a pipeline gives us back a stream that we can then
// work through. At the top level, we just want to pull on the
@ -896,7 +927,6 @@ pub fn print_err(err: ShellError, host: &dyn Host, source: &Text) {
#[cfg(test)]
mod tests {
#[quickcheck]
fn quickcheck_parse(data: String) -> bool {
if let Ok(lite_block) = nu_parser::lite_parse(&data, 0) {

View File

@ -8,6 +8,7 @@ pub(crate) mod alias;
pub(crate) mod append;
pub(crate) mod args;
pub(crate) mod autoview;
pub(crate) mod cal;
pub(crate) mod calc;
pub(crate) mod cd;
pub(crate) mod classified;
@ -20,18 +21,20 @@ pub(crate) mod cp;
pub(crate) mod date;
pub(crate) mod debug;
pub(crate) mod default;
pub(crate) mod drop;
pub(crate) mod du;
pub(crate) mod each;
pub(crate) mod echo;
pub(crate) mod edit;
pub(crate) mod enter;
#[allow(unused)]
pub(crate) mod evaluate_by;
pub(crate) mod exit;
pub(crate) mod first;
pub(crate) mod format;
pub(crate) mod from;
pub(crate) mod from_bson;
pub(crate) mod from_csv;
pub(crate) mod from_eml;
pub(crate) mod from_ics;
pub(crate) mod from_ini;
pub(crate) mod from_json;
@ -52,18 +55,22 @@ pub(crate) mod help;
pub(crate) mod histogram;
pub(crate) mod history;
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 lines;
pub(crate) mod ls;
#[allow(unused)]
pub(crate) mod map_max_by;
pub(crate) mod merge;
pub(crate) mod mkdir;
pub(crate) mod mv;
pub(crate) mod next;
pub(crate) mod nth;
pub(crate) mod open;
pub(crate) mod parse;
pub(crate) mod pick;
pub(crate) mod pivot;
pub(crate) mod plugin;
pub(crate) mod prepend;
@ -79,10 +86,12 @@ pub(crate) mod rm;
pub(crate) mod run_alias;
pub(crate) mod run_external;
pub(crate) mod save;
pub(crate) mod select;
pub(crate) mod shells;
pub(crate) mod shuffle;
pub(crate) mod size;
pub(crate) mod skip;
pub(crate) mod skip_until;
pub(crate) mod skip_while;
pub(crate) mod sort_by;
pub(crate) mod split_by;
@ -93,6 +102,7 @@ pub(crate) mod sum;
pub(crate) mod t_sort_by;
pub(crate) mod table;
pub(crate) mod tags;
pub(crate) mod to;
pub(crate) mod to_bson;
pub(crate) mod to_csv;
pub(crate) mod to_html;
@ -105,21 +115,23 @@ pub(crate) mod to_url;
pub(crate) mod to_yaml;
pub(crate) mod trim;
pub(crate) mod uniq;
pub(crate) mod update;
pub(crate) mod version;
pub(crate) mod what;
pub(crate) mod where_;
pub(crate) mod which_;
pub(crate) mod with_env;
pub(crate) mod wrap;
pub(crate) use autoview::Autoview;
pub(crate) use cd::Cd;
pub(crate) use command::{
per_item_command, whole_stream_command, Command, PerItemCommand, UnevaluatedCallInfo,
WholeStreamCommand,
whole_stream_command, Command, Example, UnevaluatedCallInfo, WholeStreamCommand,
};
pub(crate) use alias::Alias;
pub(crate) use append::Append;
pub(crate) use cal::Cal;
pub(crate) use calc::Calc;
pub(crate) use compact::Compact;
pub(crate) use config::Config;
@ -128,10 +140,12 @@ pub(crate) use cp::Cpy;
pub(crate) use date::Date;
pub(crate) use debug::Debug;
pub(crate) use default::Default;
pub(crate) use drop::Drop;
pub(crate) use du::Du;
pub(crate) use each::Each;
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) use kill::Kill;
pub(crate) mod clear;
@ -143,8 +157,10 @@ pub(crate) use evaluate_by::EvaluateBy;
pub(crate) use exit::Exit;
pub(crate) use first::First;
pub(crate) use format::Format;
pub(crate) use from::From;
pub(crate) use from_bson::FromBSON;
pub(crate) use from_csv::FromCSV;
pub(crate) use from_eml::FromEML;
pub(crate) use from_ics::FromIcs;
pub(crate) use from_ini::FromINI;
pub(crate) use from_json::FromJSON;
@ -167,18 +183,21 @@ pub(crate) use help::Help;
pub(crate) use histogram::Histogram;
pub(crate) use history::History;
pub(crate) use insert::Insert;
pub(crate) use keep::Keep;
pub(crate) use keep_until::KeepUntil;
pub(crate) use keep_while::KeepWhile;
pub(crate) use last::Last;
pub(crate) use lines::Lines;
pub(crate) use ls::Ls;
#[allow(unused_imports)]
pub(crate) use map_max_by::MapMaxBy;
pub(crate) use merge::Merge;
pub(crate) use mkdir::Mkdir;
pub(crate) use mv::Move;
pub(crate) use next::Next;
pub(crate) use nth::Nth;
pub(crate) use open::Open;
pub(crate) use parse::Parse;
pub(crate) use pick::Pick;
pub(crate) use pivot::Pivot;
pub(crate) use prepend::Prepend;
pub(crate) use prev::Previous;
@ -192,10 +211,12 @@ pub(crate) use reverse::Reverse;
pub(crate) use rm::Remove;
pub(crate) use run_external::RunExternalCommand;
pub(crate) use save::Save;
pub(crate) use select::Select;
pub(crate) use shells::Shells;
pub(crate) use shuffle::Shuffle;
pub(crate) use size::Size;
pub(crate) use skip::Skip;
pub(crate) use skip_until::SkipUntil;
pub(crate) use skip_while::SkipWhile;
pub(crate) use sort_by::SortBy;
pub(crate) use split_by::SplitBy;
@ -206,6 +227,7 @@ pub(crate) use sum::Sum;
pub(crate) use t_sort_by::TSortBy;
pub(crate) use table::Table;
pub(crate) use tags::Tags;
pub(crate) use to::To;
pub(crate) use to_bson::ToBSON;
pub(crate) use to_csv::ToCSV;
pub(crate) use to_html::ToHTML;
@ -224,4 +246,5 @@ pub(crate) use version::Version;
pub(crate) use what::What;
pub(crate) use where_::Where;
pub(crate) use which_::Which;
pub(crate) use with_env::WithEnv;
pub(crate) use wrap::Wrap;

View File

@ -1,14 +1,20 @@
use crate::commands::PerItemCommand;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
CallInfo, CommandAction, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_protocol::{hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, Value};
use nu_source::Tagged;
pub struct Alias;
impl PerItemCommand for Alias {
#[derive(Deserialize)]
pub struct AliasArgs {
pub name: Tagged<String>,
pub args: Vec<Value>,
pub block: Block,
}
impl WholeStreamCommand for Alias {
fn name(&self) -> &str {
"alias"
}
@ -17,49 +23,58 @@ impl PerItemCommand for Alias {
Signature::build("alias")
.required("name", SyntaxShape::String, "the name of 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",
)
}
fn usage(&self) -> &str {
"Run a block on each row of the table."
"Define a shortcut for another command."
}
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
_input: Value,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let call_info = call_info.clone();
let stream = async_stream! {
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,
))
}
};
};
args.process(registry, alias)?.run()
}
Ok(stream.to_output_stream())
fn examples(&self) -> &[Example] {
&[
Example {
description: "An alias without parameters",
example: "alias say-hi [] { echo 'Hello!' }",
},
Example {
description: "An alias with a single parameter",
example: "alias l [x] { ls $x }",
},
]
}
}
pub fn alias(
AliasArgs {
name,
args: list,
block,
}: AliasArgs,
_: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let mut args: Vec<String> = vec![];
for item in list.iter() {
if let Ok(string) = item.as_string() {
args.push(format!("${}", string));
} else {
yield Err(ShellError::labeled_error("Expected a string", "expected a string", item.tag()));
}
}
yield ReturnSuccess::action(CommandAction::AddAlias(name.to_string(), args, block.clone()))
};
Ok(stream.to_output_stream())
}

View File

@ -35,6 +35,13 @@ impl WholeStreamCommand for Append {
) -> Result<OutputStream, ShellError> {
args.process(registry, append)?.run()
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Add something to the end of a list or table",
example: "echo [1 2 3] | append 4",
}]
}
}
fn append(

View File

@ -1,5 +1,6 @@
use crate::commands::UnevaluatedCallInfo;
use crate::commands::WholeStreamCommand;
use crate::data::value::format_leaf;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
@ -36,6 +37,19 @@ impl WholeStreamCommand for Autoview {
name: args.call_info.name_tag,
})
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Automatically view the results",
example: "ls | autoview",
},
Example {
description: "Autoview is also implied. The above can be written as",
example: "ls",
},
]
}
}
pub struct RunnableContextWithoutInput {
@ -162,6 +176,26 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
} => {
out!("{}", b);
}
Value {
value: UntaggedValue::Primitive(Primitive::Duration(d)),
..
} => {
let output = format_leaf(&x).plain_string(100_000);
out!("{}", output);
}
Value {
value: UntaggedValue::Primitive(Primitive::Date(d)),
..
} => {
out!("{}", d);
}
Value {
value: UntaggedValue::Primitive(Primitive::Range(_)),
..
} => {
let output = format_leaf(&x).plain_string(100_000);
out!("{}", output);
}
Value { value: UntaggedValue::Primitive(Primitive::Binary(ref b)), .. } => {
if let Some(binary) = binary {

View File

@ -0,0 +1,300 @@
use crate::prelude::*;
use chrono::{DateTime, Datelike, Local, NaiveDate};
use nu_errors::ShellError;
use nu_protocol::Dictionary;
use crate::commands::{command::EvaluatedWholeStreamCommandArgs, WholeStreamCommand};
use indexmap::IndexMap;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
pub struct Cal;
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."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
cal(args, registry)
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "This month's calendar",
example: "cal",
},
Example {
description: "The calendar for all of 2012",
example: "cal --full-year 2012",
},
]
}
}
pub fn cal(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
let mut calendar_vec_deque = VecDeque::new();
let tag = args.call_info.name_tag.clone();
let (current_year, current_month, current_day) = get_current_date();
if args.has("full-year") {
let mut day_value: Option<u32> = Some(current_day);
let mut year_value = current_year as u64;
if let Some(year) = args.get("full-year") {
if let Ok(year_u64) = year.as_u64() {
year_value = year_u64;
}
if year_value != current_year as u64 {
day_value = None
}
}
add_year_to_table(
&mut calendar_vec_deque,
&tag,
year_value as i32,
current_year,
current_month,
day_value,
&args,
);
} else {
let (day_start_offset, number_of_days_in_month, _) =
get_month_information(current_year, current_month, current_year);
add_month_to_table(
&mut calendar_vec_deque,
&tag,
current_year,
current_month,
Some(current_day),
day_start_offset,
number_of_days_in_month as usize,
&args,
);
}
Ok(futures::stream::iter(calendar_vec_deque).to_output_stream())
}
fn get_current_date() -> (i32, u32, u32) {
let local_now: DateTime<Local> = Local::now();
let current_year: i32 = local_now.date().year();
let current_month: u32 = local_now.date().month();
let current_day: u32 = local_now.date().day();
(current_year, current_month, current_day)
}
fn add_year_to_table(
mut calendar_vec_deque: &mut VecDeque<Value>,
tag: &Tag,
mut selected_year: i32,
current_year: i32,
current_month: u32,
current_day_option: Option<u32>,
args: &EvaluatedWholeStreamCommandArgs,
) {
for month_number in 1..=12 {
let (day_start_offset, number_of_days_in_month, chosen_date_is_valid) =
get_month_information(selected_year, month_number, current_year);
if !chosen_date_is_valid {
selected_year = current_year;
}
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)
}
}
add_month_to_table(
&mut calendar_vec_deque,
&tag,
selected_year,
month_number,
new_current_day_option,
day_start_offset,
number_of_days_in_month,
&args,
);
}
}
#[allow(clippy::too_many_arguments)]
fn add_month_to_table(
calendar_vec_deque: &mut VecDeque<Value>,
tag: &Tag,
year: i32,
month: u32,
_current_day_option: Option<u32>, // Can be used in the future to display current day
day_start_offset: usize,
number_of_days_in_month: usize,
args: &EvaluatedWholeStreamCommandArgs,
) {
let day_limit = number_of_days_in_month + day_start_offset;
let mut day_count: usize = 1;
let days_of_the_week = [
"sunday",
"monday",
"tuesday",
"wednesday",
"thurday",
"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(year).into_value(tag));
}
if should_show_quarter_column {
indexmap.insert(
"quarter".to_string(),
UntaggedValue::int(get_quarter_number(month)).into_value(tag),
);
}
if should_show_month_column {
let month_value = if should_show_month_names {
UntaggedValue::string(get_month_name(month)).into_value(tag)
} else {
UntaggedValue::int(month).into_value(tag)
};
indexmap.insert("month".to_string(), month_value);
}
for day in &days_of_the_week {
let value = if (day_count <= day_limit) && (day_count > day_start_offset) {
UntaggedValue::int(day_count - day_start_offset).into_value(tag)
} else {
UntaggedValue::nothing().into_value(tag)
};
indexmap.insert((*day).to_string(), value);
day_count += 1;
}
calendar_vec_deque
.push_back(UntaggedValue::Row(Dictionary::from(indexmap)).into_value(tag));
}
}
fn get_quarter_number(month_number: u32) -> u8 {
match month_number {
1..=3 => 1,
4..=6 => 2,
7..=9 => 3,
_ => 4,
}
}
fn get_month_name(month_number: u32) -> String {
let month_name = match month_number {
1 => "january",
2 => "february",
3 => "march",
4 => "april",
5 => "may",
6 => "june",
7 => "july",
8 => "august",
9 => "september",
10 => "october",
11 => "november",
_ => "december",
};
month_name.to_string()
}
fn get_month_information(
selected_year: i32,
month: u32,
current_year: i32,
) -> (usize, usize, bool) {
let (naive_date, chosen_date_is_valid_one) =
get_safe_naive_date(selected_year, month, current_year);
let weekday = naive_date.weekday();
let (days_in_month, chosen_date_is_valid_two) =
get_days_in_month(selected_year, month, current_year);
(
weekday.num_days_from_sunday() as usize,
days_in_month,
chosen_date_is_valid_one && chosen_date_is_valid_two,
)
}
fn get_days_in_month(selected_year: i32, month: u32, current_year: i32) -> (usize, bool) {
// Chrono does not provide a method to output the amount of days in a month
// This is a workaround taken from the example code from the Chrono docs here:
// https://docs.rs/chrono/0.3.0/chrono/naive/date/struct.NaiveDate.html#example-30
let (adjusted_year, adjusted_month) = if month == 12 {
(selected_year + 1, 1)
} else {
(selected_year, month + 1)
};
let (naive_date, chosen_date_is_valid) =
get_safe_naive_date(adjusted_year, adjusted_month, current_year);
(naive_date.pred().day() as usize, chosen_date_is_valid)
}
fn get_safe_naive_date(
selected_year: i32,
selected_month: u32,
current_year: i32,
) -> (NaiveDate, bool) {
if let Some(naive_date) = NaiveDate::from_ymd_opt(selected_year, selected_month, 1) {
return (naive_date, true);
}
(NaiveDate::from_ymd(current_year, selected_month, 1), false)
}

View File

@ -1,11 +1,14 @@
use crate::commands::PerItemCommand;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CallInfo, Primitive, ReturnSuccess, UntaggedValue, Value};
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
pub struct Calc;
impl PerItemCommand for Calc {
#[derive(Deserialize)]
pub struct CalcArgs {}
impl WholeStreamCommand for Calc {
fn name(&self) -> &str {
"calc"
}
@ -16,36 +19,44 @@ impl PerItemCommand for Calc {
fn run(
&self,
_call_info: &CallInfo,
_registry: &CommandRegistry,
raw_args: &RawCommandArgs,
input: Value,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
calc(input, raw_args)
args.process(registry, calc)?.run()
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Calculate math in the pipeline",
example: "echo '10 / 4' | calc",
}]
}
}
fn calc(input: Value, args: &RawCommandArgs) -> Result<OutputStream, ShellError> {
let name_span = &args.call_info.name_tag.span;
let output = if let Ok(string) = input.as_string() {
match parse(&string, &input.tag) {
Ok(value) => ReturnSuccess::value(value),
Err(err) => Err(ShellError::labeled_error(
"Calculation error",
err,
&input.tag.span,
)),
}
} else {
Err(ShellError::labeled_error(
"Expected a string from pipeline",
"requires string input",
name_span,
))
};
Ok(vec![output].into())
pub fn calc(
_: CalcArgs,
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
Ok(input
.map(move |input| {
if let Ok(string) = input.as_string() {
match parse(&string, &input.tag) {
Ok(value) => ReturnSuccess::value(value),
Err(err) => Err(ShellError::labeled_error(
"Calculation error",
err,
&input.tag.span,
)),
}
} else {
Err(ShellError::labeled_error(
"Expected a string from pipeline",
"requires string input",
name.clone(),
))
}
})
.to_output_stream())
}
pub fn parse(math_expression: &str, tag: impl Into<Tag>) -> Result<Value, String> {

View File

@ -1,7 +1,16 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use std::path::PathBuf;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged;
#[derive(Deserialize)]
pub struct CdArgs {
pub(crate) path: Option<Tagged<PathBuf>>,
}
pub struct Cd;
@ -27,12 +36,31 @@ impl WholeStreamCommand for Cd {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
cd(args, registry)
args.process(registry, cd)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Change to a new directory called 'dirname'",
example: "cd dirname",
},
Example {
description: "Change to your home directory",
example: "cd",
},
Example {
description: "Change to your home directory (alternate version)",
example: "cd ~",
},
Example {
description: "Change to the previous directory",
example: "cd -",
},
]
}
}
fn cd(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let shell_manager = args.shell_manager.clone();
let args = args.evaluate_once(registry)?;
shell_manager.cd(args)
fn cd(args: CdArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
context.shell_manager.cd(args, &context)
}

View File

@ -1,5 +1,4 @@
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::context::Context;
use crate::prelude::*;

View File

@ -3,6 +3,7 @@ use crate::prelude::*;
use log::{log_enabled, trace};
use futures::stream::once;
use nu_errors::ShellError;
use nu_protocol::hir::SpannedExpression;
use nu_protocol::Scope;
@ -10,7 +11,7 @@ use nu_protocol::Scope;
pub(crate) fn run_expression_block(
expr: SpannedExpression,
context: &mut Context,
input: InputStream,
_input: InputStream,
scope: &Scope,
) -> Result<InputStream, ShellError> {
if log_enabled!(log::Level::Trace) {
@ -20,10 +21,7 @@ pub(crate) fn run_expression_block(
let scope = scope.clone();
let registry = context.registry().clone();
let stream = input.map(move |row| {
let scope = scope.clone().set_it(row);
evaluate_baseline_expr(&expr, &registry, &scope)
});
let output = 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 futures::executor::block_on_stream;
use futures::stream::StreamExt;
// use futures::stream::StreamExt;
use futures_codec::FramedRead;
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(
command: ExternalCommand,
context: &mut Context,
@ -113,85 +99,7 @@ pub(crate) async fn run_external_command(
));
}
if command.has_it_argument() {
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(
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())
run_with_stdin(command, context, input, scope, is_last)
}
fn run_with_stdin(
@ -208,7 +116,29 @@ fn run_with_stdin(
let mut command_args = vec![];
for arg in command.args.iter() {
let value = evaluate_baseline_expr(arg, &context.registry, scope)?;
command_args.push(value.as_string()?);
// 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
@ -235,7 +165,7 @@ fn run_with_stdin(
})
.collect::<Vec<String>>();
spawn(&command, &path, &process_args[..], input, is_last)
spawn(&command, &path, &process_args[..], input, is_last, scope)
}
fn spawn(
@ -244,6 +174,7 @@ fn spawn(
args: &[String],
input: InputStream,
is_last: bool,
scope: &Scope,
) -> Result<InputStream, ShellError> {
let command = command.clone();
@ -273,6 +204,9 @@ fn spawn(
process.current_dir(path);
trace!(target: "nu::run::external", "cwd = {:?}", &path);
process.env_clear();
process.envs(scope.env.iter());
// We want stdout regardless of what
// we are doing ($it case or pipe stdin)
if !is_last {
@ -405,16 +339,27 @@ fn spawn(
}
}
},
Err(_) => {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
"Unable to read from stdout.",
"unable to read from stdout",
&stdout_name_tag,
)),
tag: stdout_name_tag.clone(),
}));
break;
Err(e) => {
// If there's an exit status, it makes sense that we may error when
// trying to read from its stdout pipe (likely been closed). In that
// case, don't emit an error.
let should_error = match child.wait() {
Ok(exit_status) => !exit_status.success(),
Err(_) => true,
};
if should_error {
let _ = stdout_read_tx.send(Ok(Value {
value: UntaggedValue::Error(ShellError::labeled_error(
format!("Unable to read from stdout ({})", e),
"unable to read from stdout",
&stdout_name_tag,
)),
tag: stdout_name_tag.clone(),
}));
}
return Ok(());
}
}
}
@ -424,10 +369,7 @@ fn spawn(
// than what other shells will do.
let external_failed = match child.wait() {
Err(_) => true,
Ok(exit_status) => match exit_status.code() {
Some(e) if e != 0 => true,
_ => false,
},
Ok(exit_status) => !exit_status.success(),
};
if external_failed {
@ -467,12 +409,12 @@ fn spawn(
async fn did_find_command(name: &str) -> bool {
#[cfg(not(windows))]
{
ichwh::which(name).await.unwrap_or(None).is_some()
which::which(name).is_ok()
}
#[cfg(windows)]
{
if ichwh::which(name).await.unwrap_or(None).is_some() {
if which::which(name).is_ok() {
true
} else {
let cmd_builtins = [
@ -514,6 +456,7 @@ fn add_quotes(argument: &str) -> String {
format!("\"{}\"", argument)
}
#[allow(unused)]
fn remove_quotes(argument: &str) -> Option<&str> {
if !argument_is_quoted(argument) {
return None;

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::UnevaluatedCallInfo;
use crate::prelude::*;
@ -33,6 +33,7 @@ pub(crate) fn run_internal_command(
let mut result = trace_out_stream!(target: "nu::trace_stream::internal", "output" = result);
let mut context = context.clone();
let scope = scope.clone();
let stream = async_stream! {
let mut soft_errs: Vec<ShellError> = vec![];
@ -51,7 +52,7 @@ pub(crate) fn run_internal_command(
}
CommandAction::AutoConvert(tagged_contents, extension) => {
let contents_tag = tagged_contents.tag.clone();
let command_name = format!("from-{}", extension);
let command_name = format!("from {}", extension);
let command = command.clone();
if let Some(converter) = context.registry.get_command(&command_name) {
let new_args = RawCommandArgs {
@ -67,7 +68,7 @@ pub(crate) fn run_internal_command(
is_last: false,
},
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);
@ -117,12 +118,12 @@ pub(crate) fn run_internal_command(
}
CommandAction::EnterShell(location) => {
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) => {
context.add_commands(vec![
per_item_command(AliasCommand::new(
whole_stream_command(AliasCommand::new(
name,
args,
block,

View File

@ -23,6 +23,12 @@ impl WholeStreamCommand for Clear {
) -> Result<OutputStream, ShellError> {
clear(args, registry)
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Clear the screen",
example: "clear",
}]
}
}
fn clear(_args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
if cfg!(windows) {

View File

@ -34,6 +34,13 @@ pub mod clipboard {
) -> Result<OutputStream, ShellError> {
args.process(registry, clip)?.run()
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Save text to the clipboard",
example: "echo 'secret value' | clip",
}]
}
}
pub fn clip(

View File

@ -49,36 +49,6 @@ 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)]
#[get = "pub(crate)"]
pub struct CommandArgs {
@ -227,12 +197,6 @@ impl CommandArgs {
}
}
pub struct RunnablePerItemContext {
pub shell_manager: ShellManager,
pub name: Tag,
pub ctrl_c: Arc<AtomicBool>,
}
pub struct RunnableContext {
pub input: InputStream,
pub shell_manager: ShellManager,
@ -243,23 +207,11 @@ pub struct 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)
}
}
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,
@ -398,6 +350,11 @@ impl EvaluatedCommandArgs {
}
}
pub struct Example {
pub example: &'static str,
pub description: &'static str,
}
pub trait WholeStreamCommand: Send + Sync {
fn name(&self) -> &str;
@ -416,152 +373,64 @@ pub trait WholeStreamCommand: Send + Sync {
fn is_binary(&self) -> bool {
false
}
}
pub trait PerItemCommand: Send + Sync {
fn name(&self) -> &str;
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
fn examples(&self) -> &[Example] {
&[]
}
}
pub enum Command {
WholeStream(Arc<dyn WholeStreamCommand>),
PerItem(Arc<dyn PerItemCommand>),
}
#[derive(Clone)]
pub struct Command(Arc<dyn WholeStreamCommand>);
impl PrettyDebugWithSource for Command {
fn pretty_debug(&self, source: &str) -> DebugDocBuilder {
match self {
Command::WholeStream(command) => b::typed(
"whole stream command",
b::description(command.name())
+ b::space()
+ b::equals()
+ b::space()
+ 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),
),
}
b::typed(
"whole stream command",
b::description(self.name())
+ b::space()
+ b::equals()
+ b::space()
+ self.signature().pretty_debug(source),
)
}
}
impl std::fmt::Debug for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Command::WholeStream(command) => write!(f, "WholeStream({})", command.name()),
Command::PerItem(command) => write!(f, "PerItem({})", command.name()),
}
write!(f, "Command({})", self.name())
}
}
impl Command {
pub fn name(&self) -> &str {
match self {
Command::WholeStream(command) => command.name(),
Command::PerItem(command) => command.name(),
}
self.0.name()
}
pub fn signature(&self) -> Signature {
match self {
Command::WholeStream(command) => command.signature(),
Command::PerItem(command) => command.signature(),
}
self.0.signature()
}
pub fn usage(&self) -> &str {
match self {
Command::WholeStream(command) => command.usage(),
Command::PerItem(command) => command.usage(),
}
self.0.usage()
}
pub fn run(&self, args: CommandArgs, registry: &CommandRegistry) -> OutputStream {
if args.call_info.switch_present("help") {
get_help(self.name(), self.usage(), self.signature()).into()
get_help(&*self.0, registry).into()
} else {
match self {
Command::WholeStream(command) => match command.run(args, registry) {
Ok(stream) => stream,
Err(err) => OutputStream::one(Err(err)),
},
Command::PerItem(command) => {
self.run_helper(command.clone(), args, registry.clone())
}
match self.0.run(args, registry) {
Ok(stream) => stream,
Err(err) => OutputStream::one(Err(err)),
}
}
}
fn run_helper(
&self,
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 {
self.0.is_binary()
}
pub fn is_binary(&self) -> bool {
match self {
Command::WholeStream(command) => command.is_binary(),
Command::PerItem(command) => command.is_binary(),
}
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
&*self.0
}
}
@ -623,10 +492,6 @@ impl WholeStreamCommand for FnFilterCommand {
}
}
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Arc<Command> {
Arc::new(Command::WholeStream(Arc::new(command)))
}
pub fn per_item_command(command: impl PerItemCommand + 'static) -> Arc<Command> {
Arc::new(Command::PerItem(Arc::new(command)))
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command {
Command(Arc::new(command))
}

View File

@ -33,6 +33,13 @@ impl WholeStreamCommand for Compact {
) -> Result<OutputStream, ShellError> {
args.process(registry, compact)?.run()
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Remove all directory entries, except those with a 'target'",
example: "ls -af | compact target",
}]
}
}
pub fn compact(

View File

@ -72,6 +72,39 @@ impl WholeStreamCommand for Config {
) -> Result<OutputStream, ShellError> {
args.process(registry, config)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "See all config values",
example: "config",
},
Example {
description: "Set completion_mode to circular",
example: "config --set [completion_mode circular]",
},
Example {
description: "Store the contents of the pipeline as a path",
example: "echo ['/usr/bin' '/bin'] | config --set_into path",
},
Example {
description: "Get the current startup commands",
example: "config --get startup",
},
Example {
description: "Remove the startup commands",
example: "config --remove startup",
},
Example {
description: "Clear the config (be careful!)",
example: "config --clear",
},
Example {
description: "Get the path to the current config file",
example: "config --path",
},
]
}
}
pub fn config(

View File

@ -20,7 +20,7 @@ impl WholeStreamCommand for Count {
}
fn usage(&self) -> &str {
"Show the total number of rows."
"Show the total number of rows or items."
}
fn run(
@ -30,6 +30,13 @@ impl WholeStreamCommand for Count {
) -> Result<OutputStream, ShellError> {
args.process(registry, count)?.run()
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Count the number of files/directories in the current directory",
example: "ls | count",
}]
}
}
pub fn count(

View File

@ -1,8 +1,8 @@
use crate::commands::command::RunnablePerItemContext;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CallInfo, Signature, SyntaxShape, Value};
use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged;
use std::path::PathBuf;
@ -15,7 +15,7 @@ pub struct CopyArgs {
pub recursive: Tagged<bool>,
}
impl PerItemCommand for Cpy {
impl WholeStreamCommand for Cpy {
fn name(&self) -> &str {
"cp"
}
@ -37,18 +37,27 @@ impl PerItemCommand for Cpy {
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
call_info
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), cp)?
.run()
args.process(registry, cp)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Copy myfile to dir_b",
example: "cp myfile dir_b",
},
Example {
description: "Recursively copy dir_a to dir_b",
example: "cp -r dir_a dir_b",
},
]
}
}
fn cp(args: CopyArgs, context: &RunnablePerItemContext) -> Result<OutputStream, ShellError> {
pub fn cp(args: CopyArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
let shell_manager = context.shell_manager.clone();
shell_manager.cp(args, context)
shell_manager.cp(args, &context)
}

View File

@ -33,6 +33,19 @@ impl WholeStreamCommand for Date {
) -> Result<OutputStream, ShellError> {
date(args, registry)
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Get the current local time and date",
example: "date",
},
Example {
description: "Get the current UTC time and date",
example: "date --utc",
},
]
}
}
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, tag: Tag) -> Value

View File

@ -40,6 +40,13 @@ impl WholeStreamCommand for Default {
) -> Result<OutputStream, ShellError> {
args.process(registry, default)?.run()
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Give a default 'target' to all file entries",
example: "ls -af | default target 'nothing'",
}]
}
}
fn default(

View File

@ -0,0 +1,73 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
use nu_source::Tagged;
pub struct Drop;
#[derive(Deserialize)]
pub struct DropArgs {
rows: Option<Tagged<u64>>,
}
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."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, drop)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Remove the last item of a list/table",
example: "echo [1 2 3] | drop",
},
Example {
description: "Remove the last 2 items of a list/table",
example: "echo [1 2 3] | drop 2",
},
]
}
}
fn drop(DropArgs { rows }: DropArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let v: Vec<_> = context.input.into_vec().await;
let rows_to_drop = if let Some(quantity) = rows {
*quantity as usize
} else {
1
};
if rows_to_drop < v.len() {
let k = v.len() - rows_to_drop;
for x in v[0..k].iter() {
let y: Value = x.clone();
yield ReturnSuccess::value(y)
}
}
};
Ok(stream.to_output_stream())
}

View File

@ -1,12 +1,10 @@
extern crate filesize;
use crate::commands::command::RunnablePerItemContext;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use filesize::file_real_size_fast;
use glob::*;
use indexmap::map::IndexMap;
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 std::path::PathBuf;
@ -31,7 +29,7 @@ pub struct DuArgs {
min_size: Option<Tagged<u64>>,
}
impl PerItemCommand for Du {
impl WholeStreamCommand for Du {
fn name(&self) -> &str {
NAME
}
@ -75,18 +73,21 @@ impl PerItemCommand for Du {
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
call_info
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), du)?
.run()
args.process(registry, du)?.run()
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Disk usage of the current directory",
example: "du *",
}]
}
}
fn du(args: DuArgs, ctx: &RunnablePerItemContext) -> Result<OutputStream, ShellError> {
fn du(args: DuArgs, ctx: RunnableContext) -> Result<OutputStream, ShellError> {
let tag = ctx.name.clone();
let exclude = args.exclude.map_or(Ok(None), move |x| {
@ -117,7 +118,7 @@ fn du(args: DuArgs, ctx: &RunnablePerItemContext) -> Result<OutputStream, ShellE
})
.map(|v| v.map_err(glob_err_into));
let ctrl_c = ctx.ctrl_c.clone();
let ctrl_c = ctx.ctrl_c;
let all = args.all;
let deref = args.deref;
let max_depth = args.max_depth.map(|f| f.item);
@ -149,7 +150,7 @@ fn du(args: DuArgs, ctx: &RunnablePerItemContext) -> Result<OutputStream, ShellE
Ok(stream.to_output_stream())
}
struct DirBuilder {
pub struct DirBuilder {
tag: Tag,
min: Option<u64>,
deref: bool,
@ -157,7 +158,25 @@ struct DirBuilder {
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>,
files: Vec<FileInfo>,
errors: Vec<ShellError>,
@ -200,7 +219,7 @@ impl FileInfo {
}
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>) -> Self {
let path = path.into();
let mut s = Self {
@ -280,6 +299,10 @@ impl DirInfo {
self.errors.push(e);
self
}
pub fn get_size(&self) -> u64 {
self.size
}
}
fn glob_err_into(e: GlobError) -> ShellError {

View File

@ -1,16 +1,23 @@
use crate::commands::classified::block::run_block;
use crate::commands::PerItemCommand;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use futures::stream::once;
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, ReturnSuccess, Signature,
SyntaxShape,
};
pub struct Each;
impl PerItemCommand for Each {
#[derive(Deserialize)]
pub struct EachArgs {
block: Block,
}
impl WholeStreamCommand for Each {
fn name(&self) -> &str {
"each"
}
@ -29,58 +36,74 @@ impl PerItemCommand for Each {
fn run(
&self,
call_info: &CallInfo,
args: CommandArgs,
registry: &CommandRegistry,
raw_args: &RawCommandArgs,
input: Value,
) -> Result<OutputStream, ShellError> {
let call_info = call_info.clone();
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();
Ok(args.process_raw(registry, each)?.run())
}
let result = run_block(
block,
&mut context,
input_stream,
&Scope::new(input_clone),
).await;
match result {
Ok(mut stream) => {
let errors = context.get_errors();
if let Some(error) = errors.first() {
yield Err(error.clone());
return;
}
while let Some(result) = stream.next().await {
yield Ok(ReturnSuccess::Value(result));
}
}
Err(e) => {
yield Err(e);
}
}
}
Value { tag, .. } => {
yield Err(ShellError::labeled_error(
"Expected a block",
"each needs a block",
tag,
))
}
};
};
Ok(stream.to_output_stream())
fn examples(&self) -> &[Example] {
&[Example {
description: "Print the name of each file",
example: "ls | each { echo $it.name }",
}]
}
}
fn is_expanded_it_usage(head: &SpannedExpression) -> bool {
match &*head {
SpannedExpression {
expr: Expression::Synthetic(Synthetic::String(s)),
..
} if s == "expanded-each" => true,
_ => false,
}
}
fn each(
each_args: EachArgs,
context: RunnableContext,
raw_args: RawCommandArgs,
) -> Result<OutputStream, ShellError> {
let block = each_args.block;
let scope = raw_args.call_info.scope.clone();
let registry = context.registry.clone();
let mut input_stream = context.input;
let stream = async_stream! {
while let Some(input) = input_stream.next().await {
let mut context = Context::from_raw(&raw_args, &registry);
let input_clone = input.clone();
let input_stream = if is_expanded_it_usage(&raw_args.call_info.args.head) {
InputStream::empty()
} else {
once(async { Ok(input) }).to_input_stream()
};
let result = run_block(
&block,
&mut context,
input_stream,
&scope.clone().set_it(input_clone),
).await;
match result {
Ok(mut stream) => {
while let Some(result) = stream.next().await {
yield Ok(ReturnSuccess::Value(result));
}
let errors = context.get_errors();
if let Some(error) = errors.first() {
yield Err(error.clone());
}
}
Err(e) => {
yield Err(e);
}
}
}
};
Ok(stream.to_output_stream())
}

View File

@ -1,10 +1,16 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CallInfo, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
pub struct Echo;
impl PerItemCommand for Echo {
#[derive(Deserialize)]
pub struct EchoArgs {
pub rest: Vec<Value>,
}
impl WholeStreamCommand for Echo {
fn name(&self) -> &str {
"echo"
}
@ -19,44 +25,49 @@ impl PerItemCommand for Echo {
fn run(
&self,
call_info: &CallInfo,
args: CommandArgs,
registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> {
run(call_info, registry, raw_args)
args.process(registry, echo)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Put a hello message in the pipeline",
example: "echo 'hello'",
},
Example {
description: "Print the value of the special '$nu' variable",
example: "echo $nu",
},
]
}
}
fn run(
call_info: &CallInfo,
_registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
) -> Result<OutputStream, ShellError> {
fn echo(args: EchoArgs, _: RunnableContext) -> Result<OutputStream, ShellError> {
let mut output = vec![];
if let Some(ref positional) = call_info.args.positional {
for i in positional {
match i.as_string() {
Ok(s) => {
output.push(Ok(ReturnSuccess::Value(
UntaggedValue::string(s).into_value(i.tag.clone()),
)));
}
_ => match i {
Value {
value: UntaggedValue::Table(table),
..
} => {
for value in table {
output.push(Ok(ReturnSuccess::Value(value.clone())));
}
}
_ => {
output.push(Ok(ReturnSuccess::Value(i.clone())));
}
},
for i in args.rest {
match i.as_string() {
Ok(s) => {
output.push(Ok(ReturnSuccess::Value(
UntaggedValue::string(s).into_value(i.tag.clone()),
)));
}
_ => match i {
Value {
value: UntaggedValue::Table(table),
..
} => {
for value in table {
output.push(Ok(ReturnSuccess::Value(value.clone())));
}
}
_ => {
output.push(Ok(ReturnSuccess::Value(i.clone())));
}
},
}
}

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,22 @@
use crate::commands::PerItemCommand;
use crate::commands::UnevaluatedCallInfo;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
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;
impl PerItemCommand for Enter {
#[derive(Deserialize)]
pub struct EnterArgs {
location: Tagged<PathBuf>,
}
impl WholeStreamCommand for Enter {
fn name(&self) -> &str {
"enter"
}
@ -28,130 +35,134 @@ impl PerItemCommand for Enter {
fn run(
&self,
call_info: &CallInfo,
args: CommandArgs,
registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
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();
Ok(args.process_raw(registry, enter)?.run())
}
if location_string.starts_with("help") {
let spec = location_string.split(':').collect::<Vec<&str>>();
if spec.len() == 2 {
let (_, command) = (spec[0], spec[1]);
if registry.has(command) {
return Ok(vec![Ok(ReturnSuccess::Action(
CommandAction::EnterHelpShell(
UntaggedValue::string(command).into_value(Tag::unknown()),
),
))]
.into());
}
}
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
UntaggedValue::nothing().into_value(Tag::unknown()),
)))]
.into())
} else if location.is_dir() {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterShell(
location_clone,
)))]
.into())
} else {
let stream = async_stream! {
// If it's a file, attempt to open the file as a value and enter it
let cwd = raw_args.shell_manager.path();
let full_path = std::path::PathBuf::from(cwd);
let (file_extension, contents, contents_tag) =
crate::commands::open::fetch(
&full_path,
&location_clone,
tag_clone.span,
).await?;
match contents {
UntaggedValue::Primitive(Primitive::String(_)) => {
let tagged_contents = contents.into_value(&contents_tag);
if let Some(extension) = file_extension {
let command_name = format!("from-{}", extension);
if let Some(converter) =
registry.get_command(&command_name)
{
let new_args = RawCommandArgs {
host: raw_args.host,
ctrl_c: raw_args.ctrl_c,
shell_manager: raw_args.shell_manager,
call_info: UnevaluatedCallInfo {
args: nu_protocol::hir::Call {
head: raw_args.call_info.args.head,
positional: None,
named: None,
span: Span::unknown(),
is_last: false,
},
name_tag: raw_args.call_info.name_tag,
scope: raw_args.call_info.scope.clone()
},
};
let mut result = converter.run(
new_args.with_input(vec![tagged_contents]),
&registry,
);
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
result.drain_vec().await;
for res in result_vec {
match res {
Ok(ReturnSuccess::Value(Value {
value,
..
})) => {
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(
Value {
value,
tag: contents_tag.clone(),
})));
}
x => yield x,
}
}
} else {
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
}
} else {
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
}
}
_ => {
let tagged_contents = contents.into_value(contents_tag);
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
}
}
};
Ok(stream.to_output_stream())
}
}
x => Ok(
vec![Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(
x.clone(),
)))]
.into(),
),
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Enter a path as a new shell",
example: "enter ../projectB",
},
Example {
description: "Enter a file as a new shell",
example: "enter package.json",
},
]
}
}
fn enter(
EnterArgs { location }: EnterArgs,
RunnableContext {
registry,
name: tag,
..
}: RunnableContext,
raw_args: RawCommandArgs,
) -> Result<OutputStream, ShellError> {
let location_string = location.display().to_string();
let location_clone = location_string.clone();
if location_string.starts_with("help") {
let spec = location_string.split(':').collect::<Vec<&str>>();
if spec.len() == 2 {
let (_, command) = (spec[0], spec[1]);
if registry.has(command) {
return Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
UntaggedValue::string(command).into_value(Tag::unknown()),
)))]
.into());
}
}
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
UntaggedValue::nothing().into_value(Tag::unknown()),
)))]
.into())
} else if location.is_dir() {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterShell(
location_clone,
)))]
.into())
} else {
let stream = async_stream! {
// If it's a file, attempt to open the file as a value and enter it
let cwd = raw_args.shell_manager.path();
let full_path = std::path::PathBuf::from(cwd);
let (file_extension, contents, contents_tag) =
crate::commands::open::fetch(
&full_path,
&PathBuf::from(location_clone),
tag.span,
).await?;
match contents {
UntaggedValue::Primitive(Primitive::String(_)) => {
let tagged_contents = contents.into_value(&contents_tag);
if let Some(extension) = file_extension {
let command_name = format!("from {}", extension);
if let Some(converter) =
registry.get_command(&command_name)
{
let new_args = RawCommandArgs {
host: raw_args.host,
ctrl_c: raw_args.ctrl_c,
shell_manager: raw_args.shell_manager,
call_info: UnevaluatedCallInfo {
args: nu_protocol::hir::Call {
head: raw_args.call_info.args.head,
positional: None,
named: None,
span: Span::unknown(),
is_last: false,
},
name_tag: raw_args.call_info.name_tag,
scope: raw_args.call_info.scope.clone()
},
};
let mut result = converter.run(
new_args.with_input(vec![tagged_contents]),
&registry,
);
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
result.drain_vec().await;
for res in result_vec {
match res {
Ok(ReturnSuccess::Value(Value {
value,
..
})) => {
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(
Value {
value,
tag: contents_tag.clone(),
})));
}
x => yield x,
}
}
} else {
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
}
} else {
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
}
}
_ => {
let tagged_contents = contents.into_value(contents_tag);
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
}
}
};
Ok(stream.to_output_stream())
}
}

View File

@ -26,6 +26,19 @@ impl WholeStreamCommand for Exit {
) -> Result<OutputStream, ShellError> {
exit(args, registry)
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Exit the current shell",
example: "exit",
},
Example {
description: "Exit all shells (exiting Nu)",
example: "exit --now",
},
]
}
}
pub fn exit(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {

View File

@ -36,6 +36,19 @@ impl WholeStreamCommand for First {
) -> Result<OutputStream, ShellError> {
args.process(registry, first)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Return the first item of a list/table",
example: "echo [1 2 3] | first",
},
Example {
description: "Return the first 2 items of a list/table",
example: "echo [1 2 3] | first 2",
},
]
}
}
fn first(

View File

@ -1,17 +1,20 @@
use crate::commands::PerItemCommand;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
CallInfo, ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use nu_value_ext::{as_column_path, get_data_by_column_path};
use std::borrow::Borrow;
pub struct Format;
impl PerItemCommand for Format {
#[derive(Deserialize)]
pub struct FormatArgs {
pattern: Tagged<String>,
}
impl WholeStreamCommand for Format {
fn name(&self) -> &str {
"format"
}
@ -19,7 +22,7 @@ impl PerItemCommand for Format {
fn signature(&self) -> Signature {
Signature::build("format").required(
"pattern",
SyntaxShape::Any,
SyntaxShape::String,
"the pattern to output. Eg) \"{foo}: {bar}\"",
)
}
@ -30,61 +33,74 @@ impl PerItemCommand for Format {
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
value: Value,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
//let value_tag = value.tag();
let pattern = call_info.args.expect_nth(0)?;
let pattern_tag = pattern.tag.clone();
let pattern = pattern.as_string()?;
args.process(registry, format_command)?.run()
}
let format_pattern = format(&pattern);
let commands = format_pattern;
fn examples(&self) -> &[Example] {
&[Example {
description: "Print filenames with their sizes",
example: "ls | format '{name}: {size}'",
}]
}
}
let output = match value {
value
@
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut output = String::new();
fn format_command(
FormatArgs { pattern }: FormatArgs,
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let pattern_tag = pattern.tag.clone();
for command in &commands {
match command {
FormatCommand::Text(s) => {
output.push_str(&s);
}
FormatCommand::Column(c) => {
let key = to_column_path(&c, &pattern_tag)?;
let format_pattern = format(&pattern);
let commands = format_pattern;
let mut input = input;
let fetcher = get_data_by_column_path(
&value,
&key,
Box::new(move |(_, _, error)| error),
);
let stream = async_stream! {
while let Some(value) = input.next().await {
match value {
value
@
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut output = String::new();
if let Ok(c) = fetcher {
output
.push_str(&value::format_leaf(c.borrow()).plain_string(100_000))
for command in &commands {
match command {
FormatCommand::Text(s) => {
output.push_str(&s);
}
FormatCommand::Column(c) => {
let key = to_column_path(&c, &pattern_tag)?;
let fetcher = get_data_by_column_path(
&value,
&key,
Box::new(move |(_, _, error)| error),
);
if let Ok(c) = fetcher {
output
.push_str(&value::format_leaf(c.borrow()).plain_string(100_000))
}
// That column doesn't match, so don't emit anything
}
// That column doesn't match, so don't emit anything
}
}
yield ReturnSuccess::value(
UntaggedValue::string(output).into_untagged_value())
}
_ => yield ReturnSuccess::value(
UntaggedValue::string(String::new()).into_untagged_value()),
};
}
};
output
}
_ => String::new(),
};
Ok(futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::string(output).into_untagged_value(),
)])
.to_output_stream())
}
Ok(stream.to_output_stream())
}
#[derive(Debug)]

View File

@ -0,0 +1,28 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::Signature;
pub struct From;
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)"
}
fn run(
&self,
_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
Ok(crate::commands::help::get_help(&*self, registry).into())
}
}

View File

@ -10,15 +10,15 @@ pub struct FromBSON;
impl WholeStreamCommand for FromBSON {
fn name(&self) -> &str {
"from-bson"
"from bson"
}
fn signature(&self) -> Signature {
Signature::build("from-bson")
Signature::build("from bson")
}
fn usage(&self) -> &str {
"Parse text as .bson and create table."
"Parse binary as .bson and create table."
}
fn run(
@ -28,6 +28,13 @@ impl WholeStreamCommand for FromBSON {
) -> Result<OutputStream, ShellError> {
from_bson(args, registry)
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Convert bson data to a table",
example: "open file.bin | from bson",
}]
}
}
fn bson_array(input: &[Bson], tag: Tag) -> Result<Vec<Value>, ShellError> {

View File

@ -14,11 +14,11 @@ pub struct FromCSVArgs {
impl WholeStreamCommand for FromCSV {
fn name(&self) -> &str {
"from-csv"
"from csv"
}
fn signature(&self) -> Signature {
Signature::build("from-csv")
Signature::build("from csv")
.named(
"separator",
SyntaxShape::String,
@ -43,6 +43,23 @@ impl WholeStreamCommand for FromCSV {
) -> Result<OutputStream, ShellError> {
args.process(registry, from_csv)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Convert comma-separated data to a table",
example: "open data.txt | from csv",
},
Example {
description: "Convert comma-separated data to a table, ignoring headers",
example: "open data.txt | from csv --headerless",
},
Example {
description: "Convert semicolon-separated data to a table",
example: "open data.txt | from csv --separator ';'",
},
]
}
}
fn from_csv(

View File

@ -0,0 +1,122 @@
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>>,
}
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."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, from_eml)?.run()
}
}
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(),
}
}
fn from_eml(
eml_args: FromEMLArgs,
runnable_context: RunnableContext,
) -> Result<OutputStream, ShellError> {
let input = runnable_context.input;
let tag = runnable_context.name;
let stream = async_stream! {
let value = input.collect_string(tag.clone()).await?;
let body_preview = eml_args.preview_body.map(|b| b.item).unwrap_or(DEFAULT_BODY_PREVIEW);
let 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));
}
yield ReturnSuccess::value(dict.into_value());
};
Ok(stream.to_output_stream())
}

View File

@ -11,11 +11,11 @@ pub struct FromIcs;
impl WholeStreamCommand for FromIcs {
fn name(&self) -> &str {
"from-ics"
"from ics"
}
fn signature(&self) -> Signature {
Signature::build("from-ics")
Signature::build("from ics")
}
fn usage(&self) -> &str {

View File

@ -8,11 +8,11 @@ pub struct FromINI;
impl WholeStreamCommand for FromINI {
fn name(&self) -> &str {
"from-ini"
"from ini"
}
fn signature(&self) -> Signature {
Signature::build("from-ini")
Signature::build("from ini")
}
fn usage(&self) -> &str {

View File

@ -12,11 +12,11 @@ pub struct FromJSONArgs {
impl WholeStreamCommand for FromJSON {
fn name(&self) -> &str {
"from-json"
"from json"
}
fn signature(&self) -> Signature {
Signature::build("from-json").switch(
Signature::build("from json").switch(
"objects",
"treat each line as a separate value",
Some('o'),

View File

@ -15,11 +15,11 @@ pub struct FromODSArgs {
impl WholeStreamCommand for FromODS {
fn name(&self) -> &str {
"from-ods"
"from ods"
}
fn signature(&self) -> Signature {
Signature::build("from-ods").switch(
Signature::build("from ods").switch(
"headerless",
"don't treat the first row as column names",
None,

View File

@ -10,11 +10,11 @@ pub struct FromSQLite;
impl WholeStreamCommand for FromSQLite {
fn name(&self) -> &str {
"from-sqlite"
"from sqlite"
}
fn signature(&self) -> Signature {
Signature::build("from-sqlite")
Signature::build("from sqlite")
}
fn usage(&self) -> &str {
@ -34,11 +34,11 @@ pub struct FromDB;
impl WholeStreamCommand for FromDB {
fn name(&self) -> &str {
"from-db"
"from db"
}
fn signature(&self) -> Signature {
Signature::build("from-db")
Signature::build("from db")
}
fn usage(&self) -> &str {

View File

@ -17,7 +17,7 @@ pub struct FromSSVArgs {
minimum_spaces: Option<Tagged<usize>>,
}
const STRING_REPRESENTATION: &str = "from-ssv";
const STRING_REPRESENTATION: &str = "from ssv";
const DEFAULT_MINIMUM_SPACES: usize = 2;
impl WholeStreamCommand for FromSSV {

View File

@ -7,11 +7,11 @@ pub struct FromTOML;
impl WholeStreamCommand for FromTOML {
fn name(&self) -> &str {
"from-toml"
"from toml"
}
fn signature(&self) -> Signature {
Signature::build("from-toml")
Signature::build("from toml")
}
fn usage(&self) -> &str {

View File

@ -13,11 +13,11 @@ pub struct FromTSVArgs {
impl WholeStreamCommand for FromTSV {
fn name(&self) -> &str {
"from-tsv"
"from tsv"
}
fn signature(&self) -> Signature {
Signature::build("from-tsv").switch(
Signature::build("from tsv").switch(
"headerless",
"don't treat the first row as column names",
None,

View File

@ -7,11 +7,11 @@ pub struct FromURL;
impl WholeStreamCommand for FromURL {
fn name(&self) -> &str {
"from-url"
"from url"
}
fn signature(&self) -> Signature {
Signature::build("from-url")
Signature::build("from url")
}
fn usage(&self) -> &str {

View File

@ -11,11 +11,11 @@ pub struct FromVcf;
impl WholeStreamCommand for FromVcf {
fn name(&self) -> &str {
"from-vcf"
"from vcf"
}
fn signature(&self) -> Signature {
Signature::build("from-vcf")
Signature::build("from vcf")
}
fn usage(&self) -> &str {

View File

@ -15,11 +15,11 @@ pub struct FromXLSXArgs {
impl WholeStreamCommand for FromXLSX {
fn name(&self) -> &str {
"from-xlsx"
"from xlsx"
}
fn signature(&self) -> Signature {
Signature::build("from-xlsx").switch(
Signature::build("from xlsx").switch(
"headerless",
"don't treat the first row as column names",
None,

View File

@ -7,11 +7,11 @@ pub struct FromXML;
impl WholeStreamCommand for FromXML {
fn name(&self) -> &str {
"from-xml"
"from xml"
}
fn signature(&self) -> Signature {
Signature::build("from-xml")
Signature::build("from xml")
}
fn usage(&self) -> &str {

View File

@ -7,11 +7,11 @@ pub struct FromYAML;
impl WholeStreamCommand for FromYAML {
fn name(&self) -> &str {
"from-yaml"
"from yaml"
}
fn signature(&self) -> Signature {
Signature::build("from-yaml")
Signature::build("from yaml")
}
fn usage(&self) -> &str {
@ -31,11 +31,11 @@ pub struct FromYML;
impl WholeStreamCommand for FromYML {
fn name(&self) -> &str {
"from-yml"
"from yml"
}
fn signature(&self) -> Signature {
Signature::build("from-yml")
Signature::build("from yml")
}
fn usage(&self) -> &str {

View File

@ -40,6 +40,19 @@ impl WholeStreamCommand for Get {
) -> Result<OutputStream, ShellError> {
args.process(registry, get)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Extract the name of files as a list",
example: "ls | get name",
},
Example {
description: "Extract the cpu list from the sys information",
example: "sys | get cpu",
},
]
}
}
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> {

View File

@ -1,15 +1,16 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value};
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
use nu_source::Tagged;
use nu_value_ext::get_data_by_key;
pub struct GroupBy;
#[derive(Deserialize)]
pub struct GroupByArgs {
column_name: Tagged<String>,
date: Tagged<bool>,
format: Option<Tagged<String>>,
}
impl WholeStreamCommand for GroupBy {
@ -18,11 +19,19 @@ impl WholeStreamCommand for GroupBy {
}
fn signature(&self) -> Signature {
Signature::build("group-by").required(
"column_name",
SyntaxShape::String,
"the name of the column to group by",
)
Signature::build("group-by")
.required(
"column_name",
SyntaxShape::String,
"the name of the column to group by",
)
.named(
"format",
SyntaxShape::String,
"Specify date and time formatting",
Some('f'),
)
.switch("date", "by date", Some('d'))
}
fn usage(&self) -> &str {
@ -36,10 +45,26 @@ impl WholeStreamCommand for GroupBy {
) -> Result<OutputStream, ShellError> {
args.process(registry, group_by)?.run()
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Group files by type",
example: "ls | group-by type",
}]
}
}
enum Grouper {
Default,
ByDate(Option<String>),
}
pub fn group_by(
GroupByArgs { column_name }: GroupByArgs,
GroupByArgs {
column_name,
date,
format,
}: GroupByArgs,
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
@ -52,9 +77,38 @@ pub fn group_by(
column_name.span()
))
} else {
match group(&column_name, values, name) {
Ok(grouped) => yield ReturnSuccess::value(grouped),
Err(err) => yield Err(err)
let grouper = if let Tagged { item: true, tag } = date {
if let Some(Tagged { item: fmt, tag }) = format {
Grouper::ByDate(Some(fmt))
} else {
Grouper::ByDate(None)
}
} else {
Grouper::Default
};
match grouper {
Grouper::Default => {
match crate::utils::data::group(column_name, &values, None, &name) {
Ok(grouped) => yield ReturnSuccess::value(grouped),
Err(err) => yield Err(err),
}
}
Grouper::ByDate(None) => {
match crate::utils::data::group(column_name, &values, Some(Box::new(|row: &Value| row.format("%Y-%b-%d"))), &name) {
Ok(grouped) => yield ReturnSuccess::value(grouped),
Err(err) => yield Err(err),
}
}
Grouper::ByDate(Some(fmt)) => {
match crate::utils::data::group(column_name, &values, Some(Box::new(move |row: &Value| {
row.format(&fmt)
})), &name) {
Ok(grouped) => yield ReturnSuccess::value(grouped),
Err(err) => yield Err(err),
}
}
}
}
};
@ -67,50 +121,7 @@ pub fn group(
values: Vec<Value>,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let tag = tag.into();
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())
crate::utils::data::group(column_name.clone(), &values, None, tag)
}
#[cfg(test)]

View File

@ -15,12 +15,15 @@ impl WholeStreamCommand for Headers {
fn name(&self) -> &str {
"headers"
}
fn signature(&self) -> Signature {
Signature::build("headers")
}
fn usage(&self) -> &str {
"Use the first row of the table as column names"
}
fn run(
&self,
args: CommandArgs,
@ -28,6 +31,13 @@ impl WholeStreamCommand for Headers {
) -> Result<OutputStream, ShellError> {
args.process(registry, headers)?.run()
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Create headers for a raw string",
example: "echo \"a b c|1 2 3\" | split-row \"|\" | split-column \" \" | headers",
}]
}
}
pub fn headers(

View File

@ -1,24 +1,29 @@
use crate::commands::PerItemCommand;
use crate::commands::WholeStreamCommand;
use crate::data::command_dict;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
CallInfo, NamedType, PositionalType, Primitive, ReturnSuccess, Signature, SyntaxShape,
TaggedDictBuilder, UntaggedValue, Value,
NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder,
UntaggedValue,
};
use nu_source::SpannedItem;
use nu_source::{SpannedItem, Tagged};
use nu_value_ext::get_data_by_key;
pub struct Help;
impl PerItemCommand for Help {
#[derive(Deserialize)]
pub struct HelpArgs {
rest: Vec<Tagged<String>>,
}
impl WholeStreamCommand for Help {
fn name(&self) -> &str {
"help"
}
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 {
@ -27,67 +32,75 @@ impl PerItemCommand for Help {
fn run(
&self,
call_info: &CallInfo,
args: CommandArgs,
registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> {
let tag = &call_info.name_tag;
args.process(registry, help)?.run()
}
}
match call_info.args.nth(0) {
Some(Value {
value: UntaggedValue::Primitive(Primitive::String(document)),
tag,
}) => {
let mut help = VecDeque::new();
if document == "commands" {
let mut sorted_names = registry.names();
sorted_names.sort();
for cmd in sorted_names {
let mut short_desc = TaggedDictBuilder::new(tag.clone());
let value = command_dict(
registry.get_command(&cmd).ok_or_else(|| {
ShellError::labeled_error(
format!("Could not load {}", cmd),
"could not load command",
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) {
return Ok(
get_help(&command.name(), &command.usage(), command.signature()).into(),
);
} else {
return Err(ShellError::labeled_error(
"Can't find command (use 'help commands' for full list)",
"can't find command",
tag,
));
fn help(
HelpArgs { rest }: HelpArgs,
RunnableContext { registry, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
if let Some(document) = rest.get(0) {
let mut help = VecDeque::new();
if document.item == "commands" {
let mut sorted_names = registry.names();
sorted_names.sort();
for cmd in sorted_names {
// If it's a subcommand, don't list it during the commands list
if cmd.contains(' ') {
continue;
}
let help = futures::stream::iter(help);
Ok(help.to_output_stream())
let mut short_desc = TaggedDictBuilder::new(name.clone());
let document_tag = document.tag.clone();
let value = command_dict(
registry.get_command(&cmd).ok_or_else(|| {
ShellError::labeled_error(
format!("Could not load {}", cmd),
"could not load command",
document_tag,
)
})?,
name.clone(),
);
short_desc.insert_untagged("name", cmd);
short_desc.insert_untagged(
"description",
get_data_by_key(&value, "usage".spanned_unknown())
.ok_or_else(|| {
ShellError::labeled_error(
"Expected a usage key",
"expected a 'usage' key",
&value.tag,
)
})?
.as_string()?,
);
help.push_back(ReturnSuccess::value(short_desc.into_value()));
}
_ => {
let msg = r#"Welcome to Nushell.
} else if rest.len() == 2 {
// Check for a subcommand
let command_name = format!("{} {}", rest[0].item, rest[1].item);
if let Some(command) = registry.get_command(&command_name) {
return Ok(get_help(command.stream_command(), &registry).into());
}
} else if let Some(command) = registry.get_command(&document.item) {
return Ok(get_help(command.stream_command(), &registry).into());
} else {
return Err(ShellError::labeled_error(
"Can't find command (use 'help commands' for full list)",
"can't find command",
document.tag.span,
));
}
let help = futures::stream::iter(help);
Ok(help.to_output_stream())
} else {
let msg = r#"Welcome to Nushell.
Here are some tips to help you get started.
* help commands - list all available commands
@ -109,28 +122,35 @@ Get the processes on your system actively using CPU:
You can also learn more at https://www.nushell.sh/book/"#;
let output_stream = futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::string(msg).into_value(tag),
)]);
let output_stream = futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::string(msg).into_value(name),
)]);
Ok(output_stream.to_output_stream())
}
}
Ok(output_stream.to_output_stream())
}
}
#[allow(clippy::cognitive_complexity)]
pub(crate) fn get_help(
cmd_name: &str,
cmd_usage: &str,
cmd_sig: Signature,
cmd: &dyn WholeStreamCommand,
registry: &CommandRegistry,
) -> impl Into<OutputStream> {
let cmd_name = cmd.name();
let signature = cmd.signature();
let mut help = VecDeque::new();
let mut long_desc = String::new();
long_desc.push_str(&cmd_usage);
long_desc.push_str(&cmd.usage());
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();
one_liner.push_str(&signature.name);
@ -151,14 +171,23 @@ pub(crate) fn get_help(
one_liner.push_str(" ...args");
}
if !subcommands.is_empty() {
one_liner.push_str("<subcommand> ");
}
if !signature.named.is_empty() {
one_liner.push_str("{flags} ");
}
long_desc.push_str(&format!("\nUsage:\n > {}\n", one_liner));
if !subcommands.is_empty() {
long_desc.push_str("\nSubcommands:\n");
long_desc.push_str(&subcommands);
}
if !signature.positional.is_empty() || signature.rest_positional.is_some() {
long_desc.push_str("\nparameters:\n");
long_desc.push_str("\nParameters:\n");
for positional in signature.positional {
match positional.0 {
PositionalType::Mandatory(name, _m) => {
@ -175,7 +204,7 @@ pub(crate) fn get_help(
}
}
if !signature.named.is_empty() {
long_desc.push_str("\nflags:\n");
long_desc.push_str("\nFlags:\n");
for (flag, ty) in signature.named {
let msg = match ty.0 {
NamedType::Switch(s) => {
@ -241,6 +270,21 @@ pub(crate) fn get_help(
}
}
let examples = cmd.examples();
if !examples.is_empty() {
long_desc.push_str("\nExamples:");
}
for example in examples {
long_desc.push_str("\n");
long_desc.push_str(" ");
long_desc.push_str(example.description);
let colored_example =
crate::shell::helper::Painter::paint_string(example.example, registry);
long_desc.push_str(&format!("\n > {}\n", colored_example));
}
long_desc.push_str("\n");
help.push_back(ReturnSuccess::value(
UntaggedValue::string(long_desc).into_value(Tag::from((0, cmd_name.len(), None))),
));

View File

@ -46,6 +46,24 @@ impl WholeStreamCommand for Histogram {
) -> Result<OutputStream, ShellError> {
args.process(registry, histogram)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Get a histogram for the types of files",
example: "ls | histogram type",
},
Example {
description:
"Get a histogram for the types of files, with frequency column named count",
example: "ls | histogram type count",
},
Example {
description: "Get a histogram for a list of numbers",
example: "echo [1 2 3 1 2 3 1 1 1 1 3 2 1 1 3] | wrap | histogram Column",
},
]
}
}
pub fn histogram(

View File

@ -1,14 +1,17 @@
use crate::cli::History as HistoryFile;
use crate::commands::PerItemCommand;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CallInfo, ReturnSuccess, Signature, UntaggedValue, Value};
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
use std::fs::File;
use std::io::{BufRead, BufReader};
pub struct History;
impl PerItemCommand for History {
#[derive(Deserialize)]
pub struct HistoryArgs {}
impl WholeStreamCommand for History {
fn name(&self) -> &str {
"history"
}
@ -23,27 +26,30 @@ impl PerItemCommand for History {
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
_input: Value,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = call_info.name_tag.clone();
let stream = async_stream! {
let history_path = HistoryFile::path();
let file = File::open(history_path);
if let Ok(file) = file {
let reader = BufReader::new(file);
for line in reader.lines() {
if let Ok(line) = line {
yield ReturnSuccess::value(UntaggedValue::string(line).into_value(tag.clone()));
}
}
} else {
yield Err(ShellError::labeled_error("Could not open history", "history file could not be opened", tag.clone()));
}
};
Ok(stream.to_output_stream())
args.process(registry, history)?.run()
}
}
fn history(
_: HistoryArgs,
RunnableContext { name: tag, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let history_path = HistoryFile::path();
let file = File::open(history_path);
if let Ok(file) = file {
let reader = BufReader::new(file);
for line in reader.lines() {
if let Ok(line) = line {
yield ReturnSuccess::value(UntaggedValue::string(line).into_value(tag.clone()));
}
}
} else {
yield Err(ShellError::labeled_error("Could not open history", "history file could not be opened", tag.clone()));
}
};
Ok(stream.to_output_stream())
}

View File

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

View File

@ -0,0 +1,198 @@
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>,
}
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."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, is_empty)?.run()
}
}
fn is_empty(
IsEmptyArgs { rest }: IsEmptyArgs,
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
Ok(input
.map(move |value| {
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 => 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 =
out.get_data_by_column_path(&field, Box::new(move |(_, _, err)| err))?;
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?;
}
Ok(ReturnSuccess::Value(out))
}
IsEmptyFor::RowWithField(field) => {
let val =
value.get_data_by_column_path(&field, Box::new(move |(_, _, err)| err))?;
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) => Ok(ReturnSuccess::Value(v)),
None => Err(ShellError::labeled_error(
"empty? could not find place to check emptiness",
"column name",
&field.tag,
)),
}
} else {
Ok(ReturnSuccess::Value(value))
}
}
_ => Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
&value_tag,
)),
}
}
IsEmptyFor::RowWithFieldAndFallback(field, default) => {
let val =
value.get_data_by_column_path(&field, Box::new(move |(_, _, err)| err))?;
match &value {
obj
@
Value {
value: UntaggedValue::Row(_),
..
} => {
if val.is_empty() {
match obj.replace_data_at_column_path(&field, default) {
Some(v) => Ok(ReturnSuccess::Value(v)),
None => Err(ShellError::labeled_error(
"empty? could not find place to check emptiness",
"column name",
&field.tag,
)),
}
} else {
Ok(ReturnSuccess::Value(value))
}
}
_ => Err(ShellError::labeled_error(
"Unrecognized type in stream",
"original value",
&value_tag,
)),
}
}
}
})
.to_output_stream())
}

View File

@ -0,0 +1,62 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged;
pub struct Keep;
#[derive(Deserialize)]
pub struct KeepArgs {
rows: Option<Tagged<usize>>,
}
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"
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, keep)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Keep the first row",
example: "ls | keep",
},
Example {
description: "Keep the first four rows",
example: "ls | keep 4",
},
]
}
}
fn keep(KeepArgs { rows }: KeepArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
let rows_desired = if let Some(quantity) = rows {
*quantity
} else {
1
};
Ok(OutputStream::from_input(context.input.take(rows_desired)))
}

View File

@ -0,0 +1,98 @@
use crate::commands::WholeStreamCommand;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
pub struct KeepUntil;
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."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = args.call_info.scope.clone();
let call_info = args.evaluate_once(&registry)?;
let block = call_info.args.expect_nth(0)?.clone();
let condition = match block {
Value {
value: UntaggedValue::Block(block),
tag,
} => {
if block.block.len() != 1 {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
match block.block[0].list.get(0) {
Some(item) => match item {
ClassifiedCommand::Expr(expr) => expr.clone(),
_ => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
))
}
},
None => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
}
}
Value { tag, .. } => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
};
let objects = call_info.input.take_while(move |item| {
let condition = condition.clone();
trace!("ITEM = {:?}", item);
let result =
evaluate_baseline_expr(&*condition, &registry, &scope.clone().set_it(item.clone()));
trace!("RESULT = {:?}", result);
let return_value = match result {
Ok(ref v) if v.is_true() => false,
_ => true,
};
futures::future::ready(return_value)
});
Ok(objects.from_input_stream())
}
}

View File

@ -0,0 +1,98 @@
use crate::commands::WholeStreamCommand;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
pub struct KeepWhile;
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."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = args.call_info.scope.clone();
let call_info = args.evaluate_once(&registry)?;
let block = call_info.args.expect_nth(0)?.clone();
let condition = match block {
Value {
value: UntaggedValue::Block(block),
tag,
} => {
if block.block.len() != 1 {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
match block.block[0].list.get(0) {
Some(item) => match item {
ClassifiedCommand::Expr(expr) => expr.clone(),
_ => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
))
}
},
None => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
}
}
Value { tag, .. } => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
};
let objects = call_info.input.take_while(move |item| {
let condition = condition.clone();
trace!("ITEM = {:?}", item);
let result =
evaluate_baseline_expr(&*condition, &registry, &scope.clone().set_it(item.clone()));
trace!("RESULT = {:?}", result);
let return_value = match result {
Ok(ref v) if v.is_true() => true,
_ => false,
};
futures::future::ready(return_value)
});
Ok(objects.from_input_stream())
}
}

View File

@ -1,8 +1,8 @@
use crate::commands::command::RunnablePerItemContext;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CallInfo, Signature, SyntaxShape, Value};
use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged;
use std::process::{Command, Stdio};
@ -16,7 +16,7 @@ pub struct KillArgs {
pub quiet: Tagged<bool>,
}
impl PerItemCommand for Kill {
impl WholeStreamCommand for Kill {
fn name(&self) -> &str {
"kill"
}
@ -39,14 +39,23 @@ impl PerItemCommand for Kill {
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
call_info
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), kill)?
.run()
args.process(registry, kill)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Kill the pid using the most memory",
example: "ps | sort-by mem | last | kill $it.pid",
},
Example {
description: "Force kill a given pid",
example: "kill --force 12345",
},
]
}
}
@ -57,7 +66,7 @@ fn kill(
force,
quiet,
}: KillArgs,
_context: &RunnablePerItemContext,
_context: RunnableContext,
) -> Result<OutputStream, ShellError> {
let mut cmd = if cfg!(windows) {
let mut cmd = Command::new("taskkill");

View File

@ -36,6 +36,19 @@ impl WholeStreamCommand for Last {
) -> Result<OutputStream, ShellError> {
args.process(registry, last)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Get the last row",
example: "ls | last",
},
Example {
description: "Get the last three rows",
example: "ls | last 3",
},
]
}
}
fn last(LastArgs { rows }: LastArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {

View File

@ -25,6 +25,13 @@ impl WholeStreamCommand for Lines {
) -> Result<OutputStream, ShellError> {
lines(args, registry)
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Split output from an external command into lines",
example: "^ls -l | lines",
}]
}
}
fn ends_with_line_ending(st: &str) -> bool {

View File

@ -1,7 +1,7 @@
use crate::commands::command::RunnablePerItemContext;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CallInfo, Signature, SyntaxShape, Value};
use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged;
use std::path::PathBuf;
@ -16,9 +16,11 @@ pub struct LsArgs {
pub short_names: bool,
#[serde(rename = "with-symlink-targets")]
pub with_symlink_targets: bool,
#[serde(rename = "du")]
pub du: bool,
}
impl PerItemCommand for Ls {
impl WholeStreamCommand for Ls {
fn name(&self) -> &str {
"ls"
}
@ -46,6 +48,11 @@ impl PerItemCommand for Ls {
"display the paths to the target files that symlinks point to",
Some('w'),
)
.switch(
"du",
"display the apparent directory size in place of the directory metadata size",
Some('d'),
)
}
fn usage(&self) -> &str {
@ -54,17 +61,30 @@ impl PerItemCommand for Ls {
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
call_info
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), ls)?
.run()
args.process(registry, ls)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "List all files in the current directory",
example: "ls",
},
Example {
description: "List all files in a subdirectory",
example: "ls subdir",
},
Example {
description: "List all rust files",
example: "ls *.rs",
},
]
}
}
fn ls(args: LsArgs, context: &RunnablePerItemContext) -> Result<OutputStream, ShellError> {
context.shell_manager.ls(args, context)
fn ls(args: LsArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
context.shell_manager.ls(args, &context)
}

View File

@ -0,0 +1,103 @@
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,
}
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."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
Ok(args.process_raw(registry, merge)?.run())
}
fn examples(&self) -> &[Example] {
&[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 }",
}]
}
}
fn merge(
merge_args: MergeArgs,
context: RunnableContext,
raw_args: RawCommandArgs,
) -> Result<OutputStream, ShellError> {
let block = merge_args.block;
let registry = context.registry.clone();
let mut input = context.input;
let scope = raw_args.call_info.scope.clone();
let mut context = Context::from_raw(&raw_args, &registry);
let stream = async_stream! {
let table: Option<Vec<Value>> = match run_block(&block,
&mut context,
InputStream::empty(),
&scope).await {
Ok(mut stream) => Some(stream.drain_vec().await),
Err(err) => {
yield Err(err);
return;
}
};
let table = table.unwrap_or_else(|| vec![Value {
value: UntaggedValue::row(IndexMap::default()),
tag: raw_args.call_info.name_tag,
}]);
let mut idx = 0;
while let Some(value) = input.next().await {
let other = table.get(idx);
match other {
Some(replacement) => {
match merge_values(&value.value, &replacement.value) {
Ok(merged_value) => yield ReturnSuccess::value(merged_value.into_value(&value.tag)),
Err(err) => {
let message = format!("The row at {:?} types mismatch", idx);
yield Err(ShellError::labeled_error("Could not merge", &message, &value.tag));
}
}
}
None => yield ReturnSuccess::value(value),
}
idx += 1;
}
};
Ok(stream.to_output_stream())
}

View File

@ -1,8 +1,8 @@
use crate::commands::command::RunnablePerItemContext;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CallInfo, Signature, SyntaxShape, Value};
use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged;
use std::path::PathBuf;
@ -13,7 +13,7 @@ pub struct MkdirArgs {
pub rest: Vec<Tagged<PathBuf>>,
}
impl PerItemCommand for Mkdir {
impl WholeStreamCommand for Mkdir {
fn name(&self) -> &str {
"mkdir"
}
@ -28,18 +28,21 @@ impl PerItemCommand for Mkdir {
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
call_info
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), mkdir)?
.run()
args.process(registry, mkdir)?.run()
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Make a directory named foo",
example: "mkdir foo",
}]
}
}
fn mkdir(args: MkdirArgs, context: &RunnablePerItemContext) -> Result<OutputStream, ShellError> {
fn mkdir(args: MkdirArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
let shell_manager = context.shell_manager.clone();
shell_manager.mkdir(args, context)
shell_manager.mkdir(args, &context)
}

View File

@ -1,8 +1,8 @@
use crate::commands::command::RunnablePerItemContext;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CallInfo, Signature, SyntaxShape, Value};
use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged;
use std::path::PathBuf;
@ -14,7 +14,7 @@ pub struct MoveArgs {
pub dst: Tagged<PathBuf>,
}
impl PerItemCommand for Move {
impl WholeStreamCommand for Move {
fn name(&self) -> &str {
"mv"
}
@ -39,18 +39,31 @@ impl PerItemCommand for Move {
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
call_info
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), mv)?
.run()
args.process(registry, mv)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Rename a file",
example: "mv before.txt after.txt",
},
Example {
description: "Move a file into a directory",
example: "mv test.txt my/subdirectory",
},
Example {
description: "Move many files into a directory",
example: "mv *.txt my/subdirectory",
},
]
}
}
fn mv(args: MoveArgs, context: &RunnablePerItemContext) -> Result<OutputStream, ShellError> {
fn mv(args: MoveArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
let shell_manager = context.shell_manager.clone();
shell_manager.mv(args, context)
shell_manager.mv(args, &context)
}

View File

@ -39,6 +39,19 @@ impl WholeStreamCommand for Nth {
) -> Result<OutputStream, ShellError> {
args.process(registry, nth)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Get the second row",
example: "echo [first second third] | get 1",
},
Example {
description: "Get the first and third rows",
example: "echo [first second third] | get 0 2",
},
]
}
}
fn nth(

View File

@ -1,14 +1,19 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
CallInfo, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::{AnchorLocation, Span};
use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::{AnchorLocation, Span, Tagged};
use std::path::{Path, PathBuf};
pub struct Open;
impl PerItemCommand for Open {
#[derive(Deserialize)]
pub struct OpenArgs {
path: Tagged<PathBuf>,
raw: Tagged<bool>,
}
impl WholeStreamCommand for Open {
fn name(&self) -> &str {
"open"
}
@ -33,36 +38,23 @@ impl PerItemCommand for Open {
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
run(call_info, raw_args)
args.process(registry, open)?.run()
}
}
fn run(call_info: &CallInfo, raw_args: &RawCommandArgs) -> Result<OutputStream, ShellError> {
let shell_manager = &raw_args.shell_manager;
fn open(
OpenArgs { path, raw }: OpenArgs,
RunnableContext { shell_manager, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let cwd = PathBuf::from(shell_manager.path());
let full_path = cwd;
let path = call_info.args.nth(0).ok_or_else(|| {
ShellError::labeled_error(
"No file or directory specified",
"for command",
&call_info.name_tag,
)
})?;
let path_buf = path.as_path()?;
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 result = fetch(&full_path, &path_str, path_span).await;
let result = fetch(&full_path, &path.item, path.tag.span).await;
if let Err(e) = result {
yield Err(e);
@ -70,12 +62,12 @@ fn run(call_info: &CallInfo, raw_args: &RawCommandArgs) -> Result<OutputStream,
}
let (file_extension, contents, contents_tag) = result?;
let file_extension = if has_raw {
let file_extension = if raw.item {
None
} else {
// If the extension could not be determined via mimetype, try to use the path
// extension. Some file types do not declare their mimetypes (such as bson files).
file_extension.or(path_str.split('.').last().map(String::from))
file_extension.or(path.extension().map(|x| x.to_string_lossy().to_string()))
};
let tagged_contents = contents.into_value(&contents_tag);
@ -92,7 +84,7 @@ fn run(call_info: &CallInfo, raw_args: &RawCommandArgs) -> Result<OutputStream,
pub async fn fetch(
cwd: &PathBuf,
location: &str,
location: &PathBuf,
span: Span,
) -> Result<(Option<String>, UntaggedValue, Tag), ShellError> {
let mut cwd = cwd.clone();

View File

@ -1,11 +1,9 @@
use crate::commands::PerItemCommand;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
CallInfo, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
};
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue};
use nu_source::Tagged;
use regex::Regex;
#[derive(Debug)]
@ -84,7 +82,12 @@ fn build_regex(commands: &[ParseCommand]) -> String {
}
pub struct Parse;
impl PerItemCommand for Parse {
#[derive(Deserialize)]
pub struct ParseArgs {
pattern: Tagged<String>,
}
impl WholeStreamCommand for Parse {
fn name(&self) -> &str {
"parse"
}
@ -92,7 +95,7 @@ impl PerItemCommand for Parse {
fn signature(&self) -> Signature {
Signature::build("parse").required(
"pattern",
SyntaxShape::Any,
SyntaxShape::String,
"the pattern to match. Eg) \"{foo}: {bar}\"",
)
}
@ -103,41 +106,62 @@ impl PerItemCommand for Parse {
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
value: Value,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
//let value_tag = value.tag();
let pattern = call_info.args.expect_nth(0)?.as_string()?;
args.process(registry, parse_command)?.run()
}
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())
fn examples(&self) -> &[Example] {
&[Example {
description: "Parse values from a string into a table",
example: r#"echo "data: 123" | parse "{key}: {value}""#,
}]
}
}
fn parse_command(
ParseArgs { pattern }: ParseArgs,
RunnableContext { name, input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let parse_pattern = parse(&pattern.item);
let parse_regex = build_regex(&parse_pattern);
let column_names = column_names(&parse_pattern);
let name = name.span;
let regex = Regex::new(&parse_regex).map_err(|_| {
ShellError::labeled_error(
"Could not parse regex",
"could not parse regex",
&pattern.tag,
)
})?;
Ok(input
.map(move |value| {
if let Ok(s) = value.as_string() {
let mut output = vec![];
for cap in regex.captures_iter(&s) {
let mut dict = TaggedDictBuilder::new(value.tag());
for (idx, column_name) in column_names.iter().enumerate() {
dict.insert_untagged(
column_name,
UntaggedValue::string(cap[idx + 1].to_string()),
);
}
output.push(Ok(ReturnSuccess::Value(dict.into_value())));
}
output
} else {
vec![Err(ShellError::labeled_error_with_secondary(
"Expected string input",
"expected string input",
name,
"value originated here",
value.tag,
))]
}
})
.map(futures::stream::iter)
.flatten()
.to_output_stream())
}

View File

@ -35,6 +35,13 @@ impl WholeStreamCommand for Prepend {
) -> Result<OutputStream, ShellError> {
args.process(registry, prepend)?.run()
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Add something to the beginning of a list or table",
example: "echo [2 3 4] | prepend 1",
}]
}
}
fn prepend(

View File

@ -25,6 +25,13 @@ impl WholeStreamCommand for Pwd {
) -> Result<OutputStream, ShellError> {
pwd(args, registry)
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Print the current working directory",
example: "pwd",
}]
}
}
pub fn pwd(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {

View File

@ -23,12 +23,9 @@ impl WholeStreamCommand for Rename {
.required(
"column_name",
SyntaxShape::String,
"the name of the column to rename for",
)
.rest(
SyntaxShape::String,
"Additional column name(s) to rename for",
"the new name for the first column",
)
.rest(SyntaxShape::String, "the new name for additional columns")
}
fn usage(&self) -> &str {
@ -42,6 +39,19 @@ impl WholeStreamCommand for Rename {
) -> Result<OutputStream, ShellError> {
args.process(registry, rename)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Rename a column",
example: "ls | rename my_name",
},
Example {
description: "Rename many columns",
example: "echo \"{a: 1, b: 2, c: 3}\" | from json | rename spam eggs cars",
},
]
}
}
pub fn rename(

View File

@ -26,6 +26,13 @@ impl WholeStreamCommand for Reverse {
) -> Result<OutputStream, ShellError> {
reverse(args, registry)
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Sort files in descending file size",
example: "ls | sort-by size | reverse",
}]
}
}
fn reverse(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {

View File

@ -1,8 +1,8 @@
use crate::commands::command::RunnablePerItemContext;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CallInfo, Signature, SyntaxShape, Value};
use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged;
use std::path::PathBuf;
@ -16,7 +16,7 @@ pub struct RemoveArgs {
pub trash: Tagged<bool>,
}
impl PerItemCommand for Remove {
impl WholeStreamCommand for Remove {
fn name(&self) -> &str {
"rm"
}
@ -38,18 +38,27 @@ impl PerItemCommand for Remove {
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Value,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
call_info
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), rm)?
.run()
args.process(registry, rm)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Delete a file",
example: "rm file.txt",
},
Example {
description: "Move a file to the system trash",
example: "rm --trash file.txt",
},
]
}
}
fn rm(args: RemoveArgs, context: &RunnablePerItemContext) -> Result<OutputStream, ShellError> {
fn rm(args: RemoveArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
let shell_manager = context.shell_manager.clone();
shell_manager.rm(args, context)
shell_manager.rm(args, &context)
}

View File

@ -1,18 +1,19 @@
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use derive_new::new;
use nu_errors::ShellError;
use nu_protocol::{hir::Block, CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, Value};
use nu_protocol::{hir::Block, ReturnSuccess, Signature, SyntaxShape};
#[derive(new)]
#[derive(new, Clone)]
pub struct AliasCommand {
name: String,
args: Vec<String>,
block: Block,
}
impl PerItemCommand for AliasCommand {
impl WholeStreamCommand for AliasCommand {
fn name(&self) -> &str {
&self.name
}
@ -33,35 +34,31 @@ impl PerItemCommand for AliasCommand {
fn run(
&self,
call_info: &CallInfo,
args: CommandArgs,
registry: &CommandRegistry,
raw_args: &RawCommandArgs,
input: Value,
) -> Result<OutputStream, ShellError> {
let tag = call_info.name_tag.clone();
let call_info = call_info.clone();
let tag = args.call_info.name_tag.clone();
let call_info = args.call_info.clone();
let registry = registry.clone();
let raw_args = raw_args.clone();
let block = self.block.clone();
let mut scope = Scope::it_value(input.clone());
if let Some(positional) = &call_info.args.positional {
for (pos, arg) in positional.iter().enumerate() {
scope = scope.set_var(self.args[pos].to_string(), arg.clone());
}
}
let alias_command = self.clone();
let mut context = Context::from_args(&args, &registry);
let input = args.input;
let stream = async_stream! {
let mut context = Context::from_raw(&raw_args, &registry);
let input_clone = Ok(input.clone());
let input_stream = futures::stream::once(async { input_clone }).boxed().to_input_stream();
let mut scope = call_info.scope.clone();
let evaluated = call_info.evaluate(&registry)?;
if let Some(positional) = &evaluated.args.positional {
for (pos, arg) in positional.iter().enumerate() {
scope = scope.set_var(alias_command.args[pos].to_string(), arg.clone());
}
}
let result = run_block(
&block,
&mut context,
input_stream,
&scope
input,
&scope,
).await;
match result {
@ -80,7 +77,6 @@ impl PerItemCommand for AliasCommand {
let errors = context.get_errors();
if let Some(x) = errors.first() {
//yield Err(error.clone());
yield Err(ShellError::labeled_error_with_secondary(
"Alias failed to run",
"alias failed to run",
@ -91,7 +87,6 @@ impl PerItemCommand for AliasCommand {
}
}
Err(e) => {
//yield Err(e);
yield Err(ShellError::labeled_error_with_secondary(
"Alias failed to run",
"alias failed to run",

View File

@ -1,29 +1,39 @@
use crate::commands::cd::CdArgs;
use crate::commands::classified::external;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use derive_new::new;
use parking_lot::Mutex;
use std::path::PathBuf;
use nu_errors::ShellError;
use nu_protocol::hir::{Expression, ExternalArgs, ExternalCommand, Literal, SpannedExpression};
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
use nu_source::Tagged;
#[derive(Deserialize)]
pub struct RunExternalArgs {}
#[derive(new)]
pub struct RunExternalCommand;
pub struct RunExternalCommand {
/// Whether or not nushell is being used in an interactive context
pub(crate) interactive: bool,
}
fn spanned_expression_to_string(expr: SpannedExpression) -> String {
fn spanned_expression_to_string(expr: SpannedExpression) -> Result<String, ShellError> {
if let SpannedExpression {
expr: Expression::Literal(Literal::String(s)),
..
} = expr
{
s
Ok(s)
} else {
"notacommand!!!".to_string()
Err(ShellError::labeled_error(
"Expected string for command name",
"expected string",
expr.span,
))
}
}
@ -45,7 +55,7 @@ impl WholeStreamCommand for RunExternalCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let positionals = args.call_info.args.positional.ok_or_else(|| {
let positionals = args.call_info.args.positional.clone().ok_or_else(|| {
ShellError::untagged_runtime_error("positional arguments unexpectedly empty")
})?;
@ -53,49 +63,89 @@ impl WholeStreamCommand for RunExternalCommand {
let name = positionals
.next()
.map(spanned_expression_to_string)
.ok_or_else(|| {
ShellError::untagged_runtime_error(
"run_external unexpectedly missing external name positional arg",
)
})?;
ShellError::untagged_runtime_error("run_external called with no arguments")
})
.and_then(spanned_expression_to_string)?;
let command = ExternalCommand {
name,
name_tag: args.call_info.name_tag.clone(),
args: ExternalArgs {
list: positionals.collect(),
span: args.call_info.args.span,
},
let mut external_context = {
#[cfg(windows)]
{
Context {
registry: registry.clone(),
host: args.host.clone(),
shell_manager: args.shell_manager.clone(),
ctrl_c: args.ctrl_c.clone(),
current_errors: Arc::new(Mutex::new(vec![])),
windows_drives_previous_cwd: Arc::new(Mutex::new(
std::collections::HashMap::new(),
)),
}
}
#[cfg(not(windows))]
{
Context {
registry: registry.clone(),
host: args.host.clone(),
shell_manager: args.shell_manager.clone(),
ctrl_c: args.ctrl_c.clone(),
current_errors: Arc::new(Mutex::new(vec![])),
}
}
};
let mut external_context;
#[cfg(windows)]
{
external_context = Context {
registry: registry.clone(),
host: args.host.clone(),
shell_manager: args.shell_manager.clone(),
ctrl_c: args.ctrl_c.clone(),
current_errors: Arc::new(Mutex::new(vec![])),
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
};
}
#[cfg(not(windows))]
{
external_context = Context {
registry: registry.clone(),
host: args.host.clone(),
shell_manager: args.shell_manager.clone(),
ctrl_c: args.ctrl_c.clone(),
current_errors: Arc::new(Mutex::new(vec![])),
};
}
let scope = args.call_info.scope.clone();
let is_interactive = self.interactive;
let is_last = args.call_info.args.is_last;
let input = args.input;
let stream = async_stream! {
let command = ExternalCommand {
name,
name_tag: args.call_info.name_tag.clone(),
args: ExternalArgs {
list: positionals.collect(),
span: args.call_info.args.span,
},
};
// If we're in interactive mode, we will "auto cd". That is, instead of interpreting
// this as an external command, we will see it as a path and `cd` into it.
if is_interactive {
if let Some(path) = maybe_autocd_dir(&command, &mut external_context).await {
let cd_args = CdArgs {
path: Some(Tagged {
item: PathBuf::from(path),
tag: args.call_info.name_tag.clone(),
})
};
let context = RunnableContext {
input: InputStream::empty(),
shell_manager: external_context.shell_manager.clone(),
host: external_context.host.clone(),
ctrl_c: external_context.ctrl_c.clone(),
registry: external_context.registry.clone(),
name: args.call_info.name_tag.clone(),
};
let result = external_context.shell_manager.cd(cd_args, &context);
match result {
Ok(mut stream) => {
while let Some(value) = stream.next().await {
yield value;
}
},
Err(e) => {
yield Err(e);
},
_ => {}
}
return;
}
}
let scope = args.call_info.scope.clone();
let is_last = args.call_info.args.is_last;
let input = args.input;
let result = external::run_external_command(
command,
&mut external_context,
@ -120,3 +170,54 @@ impl WholeStreamCommand for RunExternalCommand {
Ok(stream.to_output_stream())
}
}
#[allow(unused_variables)]
async fn maybe_autocd_dir<'a>(cmd: &ExternalCommand, ctx: &mut Context) -> Option<String> {
// We will "auto cd" if
// - the command name ends in a path separator, or
// - it's not a command on the path and no arguments were given.
let name = &cmd.name;
let path_name = if name.ends_with(std::path::MAIN_SEPARATOR)
|| (cmd.args.is_empty()
&& PathBuf::from(name).is_dir()
&& dunce::canonicalize(name).is_ok()
&& which::which(&name).is_err())
{
Some(name)
} else {
None
};
path_name.map(|name| {
#[cfg(windows)]
{
if name.ends_with(':') {
// This looks like a drive shortcut. We need to a) switch drives and b) go back to the previous directory we were viewing on that drive
// But first, we need to save where we are now
let current_path = ctx.shell_manager.path();
let split_path: Vec<_> = current_path.split(':').collect();
if split_path.len() > 1 {
ctx.windows_drives_previous_cwd
.lock()
.insert(split_path[0].to_string(), current_path);
}
let name = name.to_uppercase();
let new_drive: Vec<_> = name.split(':').collect();
if let Some(val) = ctx.windows_drives_previous_cwd.lock().get(new_drive[0]) {
val.to_string()
} else {
name
}
} else {
name.to_string()
}
}
#[cfg(not(windows))]
{
name.to_string()
}
})
}

View File

@ -1,7 +1,7 @@
use crate::commands::{UnevaluatedCallInfo, WholeStreamCommand};
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use std::path::{Path, PathBuf};
@ -175,6 +175,7 @@ fn save(
) -> Result<OutputStream, ShellError> {
let mut full_path = PathBuf::from(shell_manager.path());
let name_tag = name.clone();
let scope = raw_args.call_info.scope.clone();
let stream = async_stream! {
let input: Vec<Value> = input.collect().await;
@ -221,7 +222,7 @@ fn save(
let content : Result<Vec<u8>, ShellError> = 'scope: loop {
break if !save_raw {
if let Some(extension) = full_path.extension() {
let command_name = format!("to-{}", extension.to_string_lossy());
let command_name = format!("to {}", extension.to_string_lossy());
if let Some(converter) = registry.get_command(&command_name) {
let new_args = RawCommandArgs {
host,
@ -236,7 +237,7 @@ fn save(
is_last: false,
},
name_tag: raw_args.call_info.name_tag,
scope: Scope::empty(), // FIXME?
scope,
}
};
let mut result = converter.run(new_args.with_input(input), &registry);

View File

@ -10,19 +10,19 @@ use nu_source::span_for_spanned_list;
use nu_value_ext::{as_string, get_data_by_column_path};
#[derive(Deserialize)]
struct PickArgs {
struct SelectArgs {
rest: Vec<ColumnPath>,
}
pub struct Pick;
pub struct Select;
impl WholeStreamCommand for Pick {
impl WholeStreamCommand for Select {
fn name(&self) -> &str {
"pick"
"select"
}
fn signature(&self) -> Signature {
Signature::build("pick").rest(
Signature::build("select").rest(
SyntaxShape::ColumnPath,
"the columns to select from the table",
)
@ -37,19 +37,32 @@ impl WholeStreamCommand for Pick {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, pick)?.run()
args.process(registry, select)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Select just the name column",
example: "ls | select name",
},
Example {
description: "Select the name and size columns",
example: "ls | select name size",
},
]
}
}
fn pick(
PickArgs { rest: mut fields }: PickArgs,
fn select(
SelectArgs { rest: mut fields }: SelectArgs,
RunnableContext {
mut input, name, ..
}: RunnableContext,
) -> Result<OutputStream, ShellError> {
if fields.is_empty() {
return Err(ShellError::labeled_error(
"Pick requires columns to pick",
"Select requires columns to select",
"needs parameter",
name,
));
@ -76,7 +89,7 @@ fn pick(
if let PathMember { unspanned: UnspannedPathMember::String(column), .. } = path_member_tried {
return ShellError::labeled_error_with_secondary(
"No data to fetch.",
format!("Couldn't pick column \"{}\"", column),
format!("Couldn't select column \"{}\"", column),
path_member_tried.span,
format!("How about exploring it with \"get\"? Check the input is appropriate originating from here"),
obj_source.tag.span)

View File

@ -25,6 +25,13 @@ impl WholeStreamCommand for Size {
) -> Result<OutputStream, ShellError> {
size(args, registry)
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Count the number of words in a string",
example: r#"echo "There are seven words in this sentence" | size"#,
}]
}
}
fn size(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {

View File

@ -32,6 +32,13 @@ impl WholeStreamCommand for Skip {
) -> Result<OutputStream, ShellError> {
args.process(registry, skip)?.run()
}
fn examples(&self) -> &[Example] {
&[Example {
description: "Skip the first 5 rows",
example: "ls | skip 5",
}]
}
}
fn skip(SkipArgs { rows }: SkipArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {

View File

@ -0,0 +1,98 @@
use crate::commands::WholeStreamCommand;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
pub struct SkipUntil;
impl WholeStreamCommand for SkipUntil {
fn name(&self) -> &str {
"skip-until"
}
fn signature(&self) -> Signature {
Signature::build("skip-until")
.required(
"condition",
SyntaxShape::Math,
"the condition that must be met to stop skipping",
)
.filter()
}
fn usage(&self) -> &str {
"Skips rows until the condition matches."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = args.call_info.scope.clone();
let call_info = args.evaluate_once(&registry)?;
let block = call_info.args.expect_nth(0)?.clone();
let condition = match block {
Value {
value: UntaggedValue::Block(block),
tag,
} => {
if block.block.len() != 1 {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
match block.block[0].list.get(0) {
Some(item) => match item {
ClassifiedCommand::Expr(expr) => expr.clone(),
_ => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
))
}
},
None => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
}
}
Value { tag, .. } => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
};
let objects = call_info.input.skip_while(move |item| {
let condition = condition.clone();
trace!("ITEM = {:?}", item);
let result =
evaluate_baseline_expr(&*condition, &registry, &scope.clone().set_it(item.clone()));
trace!("RESULT = {:?}", result);
let return_value = match result {
Ok(ref v) if v.is_true() => false,
_ => true,
};
futures::future::ready(return_value)
});
Ok(objects.from_input_stream())
}
}

View File

@ -3,7 +3,7 @@ use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{hir::ClassifiedCommand, Scope, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
pub struct SkipWhile;
@ -32,6 +32,7 @@ impl WholeStreamCommand for SkipWhile {
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = args.call_info.scope.clone();
let call_info = args.evaluate_once(&registry)?;
let block = call_info.args.expect_nth(0)?.clone();
@ -80,7 +81,8 @@ impl WholeStreamCommand for SkipWhile {
let objects = call_info.input.skip_while(move |item| {
let condition = condition.clone();
trace!("ITEM = {:?}", item);
let result = evaluate_baseline_expr(&*condition, &registry, &Scope::new(item.clone()));
let result =
evaluate_baseline_expr(&*condition, &registry, &scope.clone().set_it(item.clone()));
trace!("RESULT = {:?}", result);
let return_value = match result {

View File

@ -22,7 +22,7 @@ impl WholeStreamCommand for SortBy {
}
fn usage(&self) -> &str {
"Sort by the given columns."
"Sort by the given columns, in increasing order."
}
fn run(
@ -32,6 +32,19 @@ impl WholeStreamCommand for SortBy {
) -> Result<OutputStream, ShellError> {
args.process(registry, sort_by)?.run()
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Sort output by increasing file size",
example: "ls | sort-by size",
},
Example {
description: "Sort output by type, and then by file size for each type",
example: "ls | sort-by type size",
},
]
}
}
fn sort_by(

View File

@ -34,6 +34,19 @@ impl WholeStreamCommand for Sum {
name: args.call_info.name_tag,
})
}
fn examples(&self) -> &[Example] {
&[
Example {
description: "Sum a list of numbers",
example: "echo [1 2 3] | sum",
},
Example {
description: "Get the disk usage for the current directory",
example: "ls --all --du | get size | sum",
},
]
}
}
fn sum(RunnableContext { mut input, .. }: RunnableContext) -> Result<OutputStream, ShellError> {

View File

@ -0,0 +1,28 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::Signature;
pub struct To;
impl WholeStreamCommand for To {
fn name(&self) -> &str {
"to"
}
fn signature(&self) -> Signature {
Signature::build("to")
}
fn usage(&self) -> &str {
"Convert table into an output format (based on subcommand, like csv, html, json, yaml)."
}
fn run(
&self,
_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
Ok(crate::commands::help::get_help(self, registry).into())
}
}

View File

@ -12,11 +12,11 @@ pub struct ToBSON;
impl WholeStreamCommand for ToBSON {
fn name(&self) -> &str {
"to-bson"
"to bson"
}
fn signature(&self) -> Signature {
Signature::build("to-bson")
Signature::build("to bson")
}
fn usage(&self) -> &str {

View File

@ -2,7 +2,7 @@ use crate::commands::to_delimited_data::to_delimited_data;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, UntaggedValue, Value};
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
pub struct ToCSV;
@ -14,15 +14,22 @@ pub struct ToCSVArgs {
impl WholeStreamCommand for ToCSV {
fn name(&self) -> &str {
"to-csv"
"to csv"
}
fn signature(&self) -> Signature {
Signature::build("to-csv").switch(
"headerless",
"do not output the columns names as the first row",
None,
)
Signature::build("to csv")
.named(
"separator",
SyntaxShape::String,
"a character to separate columns, defaults to ','",
Some('s'),
)
.switch(
"headerless",
"do not output the columns names as the first row",
None,
)
}
fn usage(&self) -> &str {

View File

@ -175,40 +175,41 @@ pub fn to_delimited_data(
let name_span = name_tag.span;
let stream = async_stream! {
let input: Vec<Value> = input.collect().await;
let input: Vec<Value> = input.collect().await;
let to_process_input = if input.len() > 1 {
let tag = input[0].tag.clone();
vec![Value { value: UntaggedValue::Table(input), tag } ]
} else if input.len() == 1 {
input
} else {
vec![]
};
let to_process_input = if input.len() > 1 {
let tag = input[0].tag.clone();
vec![Value { value: UntaggedValue::Table(input), tag } ]
} else if input.len() == 1 {
input
} else {
vec![]
};
for value in to_process_input {
match from_value_to_delimited_string(&clone_tagged_value(&value), sep) {
Ok(x) => {
let converted = if headerless {
x.lines().skip(1).collect()
} else {
x
};
yield ReturnSuccess::value(UntaggedValue::Primitive(Primitive::String(converted)).into_value(&name_tag))
}
Err(x) => {
let expected = format!("Expected a table with {}-compatible structure from pipeline", format_name);
let requires = format!("requires {}-compatible input", format_name);
yield Err(ShellError::labeled_error_with_secondary(
expected,
requires,
name_span,
"originates from here".to_string(),
value.tag.span,
))
}
}
}
for value in to_process_input {
match from_value_to_delimited_string(&clone_tagged_value(&value), sep) {
Ok(mut x) => {
if headerless {
x.find('\n').map(|second_line|{
let start = second_line + 1;
x.replace_range(0..start, "");
});
}
yield ReturnSuccess::value(UntaggedValue::Primitive(Primitive::String(x)).into_value(&name_tag))
}
Err(x) => {
let expected = format!("Expected a table with {}-compatible structure from pipeline", format_name);
let requires = format!("requires {}-compatible input", format_name);
yield Err(ShellError::labeled_error_with_secondary(
expected,
requires,
name_span,
"originates from here".to_string(),
value.tag.span,
))
}
}
}
};
Ok(stream.to_output_stream())

View File

@ -10,11 +10,11 @@ pub struct ToHTML;
impl WholeStreamCommand for ToHTML {
fn name(&self) -> &str {
"to-html"
"to html"
}
fn signature(&self) -> Signature {
Signature::build("to-html")
Signature::build("to html")
}
fn usage(&self) -> &str {
@ -39,7 +39,7 @@ fn to_html(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
let headers = nu_protocol::merge_descriptors(&input);
let mut output_string = "<html><body>".to_string();
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "<value>") {
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
output_string.push_str("<table>");
output_string.push_str("<tr>");
@ -109,7 +109,7 @@ fn to_html(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
}
}
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "<value>") {
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
output_string.push_str("</table>");
}
output_string.push_str("</body></html>");

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