Compare commits

..

57 Commits

Author SHA1 Message Date
Darren Schroeder
0e8a239ae1 Added wix to gh workflow (#2055)
Followed volta example
2020-06-26 05:51:50 +12:00
Darren Schroeder
bb08a221e2 Wix addition for creating msi (#2054)
* WIP - wix

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

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

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

* impl mode subcommand for math

* add tests for math mode subcommand

* add table/row tests for math mode subcommand

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

* cargo fmt

* add helper zero_division_error

* tests for zero division error

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

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

Usage:

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

* uniq: Add first test

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

* uniq: Also handle primitive lines.

* uniq: Update documentation

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

* uniq: Address review comments.

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

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

* uniq: Return error directly.

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

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

* Math median tests and documentation additions (#2018)

* Add math median example and unit tests

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

* Fix output of math max example

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

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

* Fix output of math max example

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

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

* Get ready to land nu-table

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

* renamed to textview, added fetch example

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

* added bat configuration

* removed debug info

* clippy fix

* changed [bat] to [textview]

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

* Move sum documentation over to math documentation

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

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

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

* Fix variable name

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

* use input_from_bytes_with_name instead of input_file

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

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

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

* Add --skip option to 'every' command

This option skips instead of selects every nth row

* Fix descriptions for 'every' command

* Add docummentation for 'every' command

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

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

* Add some comments to calculate

* Remove redudant message

Message already shows up in subcommands list

* Added not working example

Accidentantly

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

* Another batch of removing async_stream

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

* Making some progress towards math

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

* Fix typos

* Found issue with negative numbers

* Add some comments

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

* register commands in cli

* Address clippy warnings

* Fix bad examples

* Make the example failure message more helpful

* Replace unwraps

* Use compare_values to avoid coercion issues

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

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

* Fix await error

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

* compiling but panicing

* still broken

* nearly working

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

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

* When encoding is unknown default to utf-8.

* only do encoding if the user says to it

* merged some conflicts on open

* made error messages consistent

* Updated unwrap with expect

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

* change _location to location

* changed _visitor to visitor

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

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

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

@@ -0,0 +1,232 @@
name: Create Release Draft
on:
push:
tags: ['[0-9]+.[0-9]+.[0-9]+*']
jobs:
linux:
name: Build Linux
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Install libxcb
run: sudo apt-get install libxcb-composite0-dev
- name: Set up cargo
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --release --all --features=stable
- name: Create output directory
run: mkdir output
- name: Copy files to output
run: |
cp target/release/nu target/release/nu_plugin_* output/
cp README.build.txt output/README.txt
rm output/*.d
rm output/nu_plugin_core_*
rm output/nu_plugin_stable_*
# Note: If OpenSSL changes, this path will need to be updated
- name: Copy OpenSSL to output
run: cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 output/
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: linux
path: output/*
macos:
name: Build macOS
runs-on: macos-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Set up cargo
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --release --all --features=stable
- name: Create output directory
run: mkdir output
- name: Copy files to output
run: |
cp target/release/nu target/release/nu_plugin_* output/
cp README.build.txt output/README.txt
rm output/*.d
rm output/nu_plugin_core_*
rm output/nu_plugin_stable_*
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: macos
path: output/*
windows:
name: Build Windows
runs-on: windows-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Set up cargo
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Add cargo-wix subcommand
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-wix
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --release --all --features=stable
- name: Create msi with wix
uses: actions-rs/cargo@v1
with:
command: wix
args: --no-build --output target\wix\nushell-windows.msi
- name: Create zip of binaries
run: powershell Compress-Archive nushell-windows.zip
working-directory: ./target/release
- name: Upload installer
uses: actions/upload-artifact@v2
with:
name: windows-installer
path: target/wix/nushell-windows.msi
- name: Upload zip
uses: actions/upload-artifact@v2
with:
name: windows-zip
path: target/release/nushell-windows.zip
- name: Create output directory
run: mkdir output
- name: Copy files to output
run: |
cp target\release\nu.exe output\
cp target\release\nu_plugin_*.exe output\
cp README.build.txt output\README.txt
rm output\nu_plugin_core_*.exe
rm output\nu_plugin_stable_*.exe
# Note: If the version of `less.exe` needs to be changed, update this URL
# Similarly, if `less.exe` is checked into the repo, copy from the local path here
- name: Download Less Binary
run: Invoke-WebRequest -Uri "https://github.com/jftuga/less-Windows/releases/download/less-v562.1/less.exe" -OutFile "output\less.exe"
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: windows
path: output/*
release:
name: Publish Release
runs-on: ubuntu-latest
needs:
- linux
- macos
- windows
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Determine Release Info
id: info
env:
GITHUB_REF: ${{ github.ref }}
run: |
VERSION=${GITHUB_REF##*/}
MAJOR=${VERSION%%.*}
MINOR=${VERSION%.*}
MINOR=${MINOR#*.}
PATCH=${VERSION##*.}
echo "::set-output name=version::${VERSION}"
echo "::set-output name=linuxdir::nu_${MAJOR}_${MINOR}_${PATCH}_linux"
echo "::set-output name=macosdir::nu_${MAJOR}_${MINOR}_${PATCH}_macOS"
echo "::set-output name=windowsdir::nu_${MAJOR}_${MINOR}_${PATCH}_windows"
echo "::set-output name=innerdir::nushell-${VERSION}"
- name: Create Release Draft
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ steps.info.outputs.version }} Release
draft: true
- name: Create Linux Directory
run: mkdir -p ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
- name: Download Linux Artifacts
uses: actions/download-artifact@v2
with:
name: linux
path: ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
- name: Restore Linux File Modes
run: |
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/nu*
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/libssl*
- name: Create Linux tarball
run: tar -zcvf ${{ steps.info.outputs.linuxdir }}.tar.gz ${{ steps.info.outputs.linuxdir }}
- name: Upload Linux Artifact
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./${{ steps.info.outputs.linuxdir }}.tar.gz
asset_name: ${{ steps.info.outputs.linuxdir }}.tar.gz
asset_content_type: application/gzip
- name: Create macOS Directory
run: mkdir -p ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
- name: Download macOS Artifacts
uses: actions/download-artifact@v2
with:
name: macos
path: ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
- name: Restore macOS File Modes
run: chmod 755 ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}/nu*
- name: Create macOS Archive
run: zip -r ${{ steps.info.outputs.macosdir }}.zip ${{ steps.info.outputs.macosdir }}
- name: Upload macOS Artifact
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./${{ steps.info.outputs.macosdir }}.zip
asset_name: ${{ steps.info.outputs.macosdir }}.zip
asset_content_type: application/zip
- name: Create Windows Directory
run: mkdir -p ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
- name: Download Windows Artifacts
uses: actions/download-artifact@v2
with:
name: windows
path: ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
# TODO: Remove Show
- name: Show Windows Artifacts
run: ls -la ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
- name: Create Windows Archive
run: zip -r ${{ steps.info.outputs.windowsdir }}.zip ${{ steps.info.outputs.windowsdir }}
- name: Upload Windows Artifact
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./${{ steps.info.outputs.windowsdir }}.zip
asset_name: ${{ steps.info.outputs.windowsdir }}.zip
asset_content_type: application/zip

View File

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

View File

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

1134
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "nu"
version = "0.15.0"
version = "0.15.1"
authors = ["The Nu Project Contributors"]
description = "A new type of shell"
license = "MIT"
@@ -18,23 +18,23 @@ members = ["crates/*/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-cli = { version = "0.15.0", path = "./crates/nu-cli" }
nu-source = { version = "0.15.0", path = "./crates/nu-source" }
nu-plugin = { version = "0.15.0", path = "./crates/nu-plugin" }
nu-protocol = { version = "0.15.0", path = "./crates/nu-protocol" }
nu-errors = { version = "0.15.0", path = "./crates/nu-errors" }
nu-parser = { version = "0.15.0", path = "./crates/nu-parser" }
nu-value-ext = { version = "0.15.0", path = "./crates/nu-value-ext" }
nu_plugin_binaryview = { version = "0.15.0", path = "./crates/nu_plugin_binaryview", optional=true }
nu_plugin_fetch = { version = "0.15.0", path = "./crates/nu_plugin_fetch", optional=true }
nu_plugin_inc = { version = "0.15.0", path = "./crates/nu_plugin_inc", optional=true }
nu_plugin_match = { version = "0.15.0", path = "./crates/nu_plugin_match", optional=true }
nu_plugin_post = { version = "0.15.0", path = "./crates/nu_plugin_post", optional=true }
nu_plugin_ps = { version = "0.15.0", path = "./crates/nu_plugin_ps", optional=true }
nu_plugin_start = { version = "0.15.0", path = "./crates/nu_plugin_start", optional=true }
nu_plugin_sys = { version = "0.15.0", path = "./crates/nu_plugin_sys", optional=true }
nu_plugin_textview = { version = "0.15.0", path = "./crates/nu_plugin_textview", optional=true }
nu_plugin_tree = { version = "0.15.0", path = "./crates/nu_plugin_tree", optional=true }
nu-cli = { version = "0.15.1", path = "./crates/nu-cli" }
nu-source = { version = "0.15.1", path = "./crates/nu-source" }
nu-plugin = { version = "0.15.1", path = "./crates/nu-plugin" }
nu-protocol = { version = "0.15.1", path = "./crates/nu-protocol" }
nu-errors = { version = "0.15.1", path = "./crates/nu-errors" }
nu-parser = { version = "0.15.1", path = "./crates/nu-parser" }
nu-value-ext = { version = "0.15.1", path = "./crates/nu-value-ext" }
nu_plugin_binaryview = { version = "0.15.1", path = "./crates/nu_plugin_binaryview", optional=true }
nu_plugin_fetch = { version = "0.15.1", path = "./crates/nu_plugin_fetch", optional=true }
nu_plugin_inc = { version = "0.15.1", path = "./crates/nu_plugin_inc", optional=true }
nu_plugin_match = { version = "0.15.1", path = "./crates/nu_plugin_match", optional=true }
nu_plugin_post = { version = "0.15.1", path = "./crates/nu_plugin_post", optional=true }
nu_plugin_ps = { version = "0.15.1", path = "./crates/nu_plugin_ps", optional=true }
nu_plugin_start = { version = "0.15.1", path = "./crates/nu_plugin_start", optional=true }
nu_plugin_sys = { version = "0.15.1", path = "./crates/nu_plugin_sys", optional=true }
nu_plugin_textview = { version = "0.15.1", path = "./crates/nu_plugin_textview", optional=true }
nu_plugin_tree = { version = "0.15.1", path = "./crates/nu_plugin_tree", optional=true }
crossterm = { version = "0.17.5", optional = true }
semver = { version = "0.10.0", optional = true }
@@ -47,18 +47,19 @@ dunce = "1.0.0"
futures = { version = "0.3", features = ["compat", "io-compat"] }
log = "0.4.8"
pretty_env_logger = "0.4.0"
starship = "0.42.0"
[dev-dependencies]
nu-test-support = { version = "0.15.0", path = "./crates/nu-test-support" }
nu-test-support = { version = "0.15.1", path = "./crates/nu-test-support" }
[build-dependencies]
toml = "0.5.6"
serde = { version = "1.0.110", features = ["derive"] }
nu-build = { version = "0.15.0", path = "./crates/nu-build" }
nu-build = { version = "0.15.1", path = "./crates/nu-build" }
[features]
default = ["sys", "ps", "textview", "inc"]
stable = ["default", "starship-prompt", "binaryview", "match", "tree", "post", "fetch", "clipboard-cli", "trash-support", "start"]
stable = ["default", "binaryview", "match", "tree", "post", "fetch", "clipboard-cli", "trash-support", "start"]
# Default
textview = ["crossterm", "syntect", "url", "nu_plugin_textview"]
@@ -76,7 +77,7 @@ tree = ["nu_plugin_tree"]
start = ["nu_plugin_start"]
clipboard-cli = ["nu-cli/clipboard-cli"]
starship-prompt = ["nu-cli/starship-prompt"]
# starship-prompt = ["nu-cli/starship-prompt"]
trash-support = ["nu-cli/trash-support"]
# Core plugins that ship with `cargo install nu` by default

1
README.build.txt Normal file
View File

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

122
README.md
View File

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

60
TODO.md
View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "nu-build"
version = "0.15.0"
version = "0.15.1"
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.15.0"
version = "0.15.1"
authors = ["The Nu Project Contributors"]
description = "CLI for nushell"
edition = "2018"
@@ -10,20 +10,20 @@ license = "MIT"
doctest = false
[dependencies]
nu-source = { version = "0.15.0", path = "../nu-source" }
nu-plugin = { version = "0.15.0", path = "../nu-plugin" }
nu-protocol = { version = "0.15.0", path = "../nu-protocol" }
nu-errors = { version = "0.15.0", path = "../nu-errors" }
nu-parser = { version = "0.15.0", path = "../nu-parser" }
nu-value-ext = { version = "0.15.0", path = "../nu-value-ext" }
nu-test-support = { version = "0.15.0", path = "../nu-test-support" }
nu-source = { version = "0.15.1", path = "../nu-source" }
nu-plugin = { version = "0.15.1", path = "../nu-plugin" }
nu-protocol = { version = "0.15.1", path = "../nu-protocol" }
nu-errors = { version = "0.15.1", path = "../nu-errors" }
nu-parser = { version = "0.15.1", path = "../nu-parser" }
nu-value-ext = { version = "0.15.1", path = "../nu-value-ext" }
nu-test-support = { version = "0.15.1", path = "../nu-test-support" }
nu-table = {version = "0.15.1", path = "../nu-table"}
ansi_term = "0.12.1"
app_dirs = "1.2.1"
async-recursion = "0.3.1"
async-trait = "0.1.31"
directories = "2.0.2"
async-stream = "0.2"
base64 = "0.12.1"
bigdecimal = { version = "0.1.2", features = ["serde"] }
bson = { version = "0.14.1", features = ["decimal128"] }
@@ -62,7 +62,6 @@ parking_lot = "0.10.2"
pin-utils = "0.1.0"
pretty-hex = "0.1.1"
pretty_env_logger = "0.4.0"
prettytable-rs = "0.8.0"
ptree = {version = "0.2" }
query_interface = "0.3.5"
rand = "0.7"
@@ -86,12 +85,14 @@ toml = "0.5.6"
typetag = "0.1.4"
umask = "1.0.0"
unicode-xid = "0.2.0"
which = "3"
uuid_crate = { package = "uuid", version = "0.8.1", features = ["v4"] }
which = "4.0.1"
trash = { version = "1.0.1", optional = true }
clipboard = { version = "0.5", optional = true }
starship = { version = "0.41.3", optional = true }
starship = "0.42.0"
rayon = "1.3.0"
encoding_rs = "0.8.23"
[target.'cfg(unix)'.dependencies]
users = "0.10.0"
@@ -101,7 +102,7 @@ version = "0.23.1"
features = ["bundled", "blob"]
[build-dependencies]
nu-build = { version = "0.15.0", path = "../nu-build" }
nu-build = { version = "0.15.1", path = "../nu-build" }
[dev-dependencies]
quickcheck = "0.9"
@@ -109,6 +110,6 @@ quickcheck_macros = "0.9"
[features]
stable = []
starship-prompt = ["starship"]
# starship-prompt = ["starship"]
clipboard-cli = ["clipboard"]
trash-support = ["trash"]

View File

@@ -4,13 +4,11 @@ use crate::commands::plugin::JsonRpc;
use crate::commands::plugin::{PluginCommand, PluginSink};
use crate::commands::whole_stream_command;
use crate::context::Context;
#[cfg(not(feature = "starship-prompt"))]
use crate::git::current_branch;
use crate::path::canonicalize;
use crate::prelude::*;
use crate::EnvironmentSyncer;
use futures_codec::FramedRead;
use nu_errors::ShellError;
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
@@ -49,8 +47,9 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
let mut input = String::new();
let result = match reader.read_line(&mut input) {
Ok(count) => {
trace!("processing response ({} bytes)", count);
trace!("response: {}", input);
trace!(target: "nu::load", "plugin infrastructure -> config response");
trace!(target: "nu::load", "plugin infrastructure -> processing response ({} bytes)", count);
trace!(target: "nu::load", "plugin infrastructure -> response: {}", input);
let response = serde_json::from_str::<JsonRpc<Result<Signature, ShellError>>>(&input);
match response {
@@ -58,13 +57,13 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
Ok(params) => {
let fname = path.to_string_lossy();
trace!("processing {:?}", params);
trace!(target: "nu::load", "plugin infrastructure -> processing {:?}", params);
let name = params.name.clone();
let fname = fname.to_string();
if context.get_command(&name).is_some() {
trace!("plugin {:?} already loaded.", &name);
trace!(target: "nu::load", "plugin infrastructure -> {:?} already loaded.", &name);
} else if params.is_filter {
context.add_commands(vec![whole_stream_command(PluginCommand::new(
name, fname, params,
@@ -79,7 +78,7 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
Err(e) => Err(e),
},
Err(e) => {
trace!("incompatible plugin {:?}", input);
trace!(target: "nu::load", "plugin infrastructure -> incompatible {:?}", input);
Err(ShellError::untagged_runtime_error(format!(
"Error: {:?}",
e
@@ -188,7 +187,7 @@ pub fn load_plugins(context: &mut Context) -> Result<(), ShellError> {
};
if is_valid_name && is_executable {
trace!("Trying {:?}", path.display());
trace!(target: "nu::load", "plugin infrastructure -> Trying {:?}", path.display());
// we are ok if this plugin load fails
let _ = load_plugin(&path, &mut context.clone());
@@ -320,6 +319,7 @@ pub fn create_default_context(
whole_stream_command(GroupByDate),
whole_stream_command(First),
whole_stream_command(Last),
whole_stream_command(Every),
whole_stream_command(Nth),
whole_stream_command(Drop),
whole_stream_command(Format),
@@ -345,8 +345,13 @@ pub fn create_default_context(
whole_stream_command(Headers),
// Data processing
whole_stream_command(Histogram),
whole_stream_command(Average),
whole_stream_command(Sum),
whole_stream_command(Math),
whole_stream_command(MathAverage),
whole_stream_command(MathMedian),
whole_stream_command(MathMinimum),
whole_stream_command(MathMode),
whole_stream_command(MathMaximum),
whole_stream_command(MathSummation),
// File format output
whole_stream_command(To),
whole_stream_command(ToBSON),
@@ -382,6 +387,9 @@ pub fn create_default_context(
whole_stream_command(FromVcf),
// "Private" commands (not intended to be accessed directly)
whole_stream_command(RunExternalCommand { interactive }),
// Random value generation
whole_stream_command(Random),
whole_stream_command(RandomUUID),
]);
cfg_if::cfg_if! {
@@ -578,6 +586,15 @@ pub async fn cli(
rl.set_helper(Some(crate::shell::Helper::new(context.clone())));
let config = config::config(Tag::unknown())?;
let use_starship = match config.get("use_starship") {
Some(b) => match b.as_bool() {
Ok(b) => b,
_ => false,
},
_ => false,
};
let edit_mode = config::config(Tag::unknown())?
.get("edit_mode")
.map(|s| match s.value.expect_string() {
@@ -615,8 +632,7 @@ pub async fn cli(
rl.set_completion_type(completion_mode);
let colored_prompt = {
#[cfg(feature = "starship-prompt")]
{
if use_starship {
std::env::set_var("STARSHIP_SHELL", "");
std::env::set_var("PWD", &cwd);
let mut starship_context =
@@ -632,9 +648,7 @@ pub async fn cli(
_ => {}
};
starship::print::get_prompt(starship_context)
}
#[cfg(not(feature = "starship-prompt"))]
{
} else {
format!(
"\x1b[32m{}{}\x1b[m> ",
cwd,
@@ -673,13 +687,13 @@ pub async fn cli(
match line {
LineResult::Success(line) => {
rl.add_history_entry(line.clone());
rl.add_history_entry(&line);
let _ = rl.save_history(&History::path());
context.maybe_print_errors(Text::from(line));
}
LineResult::Error(line, err) => {
rl.add_history_entry(line.clone());
rl.add_history_entry(&line);
let _ = rl.save_history(&History::path());
context.with_host(|_host| {
@@ -733,7 +747,7 @@ fn chomp_newline(s: &str) -> &str {
}
}
enum LineResult {
pub enum LineResult {
Success(String),
Error(String, ShellError),
CtrlC,
@@ -741,7 +755,7 @@ enum LineResult {
}
/// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline
async fn process_line(
pub async fn process_line(
readline: Result<String, ReadlineError>,
ctx: &mut Context,
redirect_stdin: bool,

View File

@@ -8,7 +8,6 @@ pub(crate) mod alias;
pub(crate) mod append;
pub(crate) mod args;
pub(crate) mod autoview;
pub(crate) mod average;
pub(crate) mod build_string;
pub(crate) mod cal;
pub(crate) mod calc;
@@ -31,6 +30,7 @@ pub(crate) mod echo;
pub(crate) mod enter;
#[allow(unused)]
pub(crate) mod evaluate_by;
pub(crate) mod every;
pub(crate) mod exit;
pub(crate) mod first;
pub(crate) mod format;
@@ -68,6 +68,7 @@ pub(crate) mod lines;
pub(crate) mod ls;
#[allow(unused)]
pub(crate) mod map_max_by;
pub(crate) mod math;
pub(crate) mod merge;
pub(crate) mod mkdir;
pub(crate) mod mv;
@@ -80,6 +81,7 @@ pub(crate) mod plugin;
pub(crate) mod prepend;
pub(crate) mod prev;
pub(crate) mod pwd;
pub(crate) mod random;
pub(crate) mod range;
#[allow(unused)]
pub(crate) mod reduce_by;
@@ -101,7 +103,6 @@ pub(crate) mod sort_by;
pub(crate) mod split;
pub(crate) mod split_by;
pub(crate) mod str_;
pub(crate) mod sum;
#[allow(unused)]
pub(crate) mod t_sort_by;
pub(crate) mod table;
@@ -135,7 +136,6 @@ pub(crate) use command::{
pub(crate) use alias::Alias;
pub(crate) use append::Append;
pub(crate) use average::Average;
pub(crate) use build_string::BuildString;
pub(crate) use cal::Cal;
pub(crate) use calc::Calc;
@@ -160,6 +160,7 @@ pub(crate) mod touch;
pub(crate) use enter::Enter;
#[allow(unused_imports)]
pub(crate) use evaluate_by::EvaluateBy;
pub(crate) use every::Every;
pub(crate) use exit::Exit;
pub(crate) use first::First;
pub(crate) use format::Format;
@@ -198,6 +199,9 @@ pub(crate) use lines::Lines;
pub(crate) use ls::Ls;
#[allow(unused_imports)]
pub(crate) use map_max_by::MapMaxBy;
pub(crate) use math::{
Math, MathAverage, MathMaximum, MathMedian, MathMinimum, MathMode, MathSummation,
};
pub(crate) use merge::Merge;
pub(crate) use mkdir::Mkdir;
pub(crate) use mv::Move;
@@ -209,6 +213,7 @@ pub(crate) use pivot::Pivot;
pub(crate) use prepend::Prepend;
pub(crate) use prev::Previous;
pub(crate) use pwd::Pwd;
pub(crate) use random::{Random, RandomUUID};
pub(crate) use range::Range;
#[allow(unused_imports)]
pub(crate) use reduce_by::ReduceBy;
@@ -234,7 +239,6 @@ pub(crate) use str_::{
Str, StrCapitalize, StrDowncase, StrFindReplace, StrSet, StrSubstring, StrToDatetime,
StrToDecimal, StrToInteger, StrTrim, StrUpcase,
};
pub(crate) use sum::Sum;
#[allow(unused_imports)]
pub(crate) use t_sort_by::TSortBy;
pub(crate) use table::Table;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -389,20 +389,26 @@ impl WholeStreamCommand for FnFilterCommand {
ctrl_c,
shell_manager,
call_info,
mut input,
input,
..
}: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let host: Arc<parking_lot::Mutex<dyn Host>> = host.clone();
let registry: CommandRegistry = registry.clone();
let registry = Arc::new(registry.clone());
let func = self.func;
let stream = async_stream! {
while let Some(it) = input.next().await {
Ok(input
.then(move |it| {
let host = host.clone();
let registry = registry.clone();
let call_info = match call_info.clone().evaluate_with_new_it(&registry, &it).await {
Err(err) => { yield Err(err); return; },
let ctrl_c = ctrl_c.clone();
let shell_manager = shell_manager.clone();
let call_info = call_info.clone();
async move {
let call_info = match call_info.evaluate_with_new_it(&*registry, &it).await {
Err(err) => {
return OutputStream::one(Err(err));
}
Ok(args) => args,
};
@@ -414,17 +420,13 @@ impl WholeStreamCommand for FnFilterCommand {
);
match func(args) {
Err(err) => yield Err(err),
Ok(mut stream) => {
while let Some(value) = stream.values.next().await {
yield value;
Err(err) => return OutputStream::one(Err(err)),
Ok(stream) => stream,
}
}
}
}
};
Ok(stream.to_output_stream())
})
.flatten()
.to_output_stream())
}
}

View File

@@ -56,7 +56,7 @@ impl WholeStreamCommand for Each {
},
Example {
description: "Echo the sum of each row",
example: "echo [[1 2] [3 4]] | each { echo $it | sum }",
example: "echo [[1 2] [3 4]] | each { echo $it | math sum }",
result: Some(vec![
UntaggedValue::int(3).into(),
UntaggedValue::int(7).into(),

View File

@@ -32,7 +32,7 @@ impl WholeStreamCommand for Echo {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
echo(args, registry)
echo(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -51,67 +51,62 @@ impl WholeStreamCommand for Echo {
}
}
fn echo(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn echo(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (args, _): (EchoArgs, _) = args.process(&registry).await?;
for i in args.rest {
let stream = args.rest.into_iter().map(|i| {
match i.as_string() {
Ok(s) => {
yield Ok(ReturnSuccess::Value(
OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(s).into_value(i.tag.clone()),
));
)))
}
_ => match i {
Value {
value: UntaggedValue::Table(table),
..
} => {
for value in table {
yield Ok(ReturnSuccess::Value(value.clone()));
}
futures::stream::iter(table.into_iter().map(ReturnSuccess::value)).to_output_stream()
}
Value {
value: UntaggedValue::Primitive(Primitive::Range(range)),
tag
} => {
let mut output_vec = vec![];
let mut current = range.from.0.item;
while current != range.to.0.item {
yield Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag)));
output_vec.push(Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag))));
current = match crate::data::value::compute_values(Operator::Plus, &UntaggedValue::Primitive(current), &UntaggedValue::int(1)) {
Ok(result) => match result {
UntaggedValue::Primitive(p) => p,
_ => {
yield Err(ShellError::unimplemented("Internal error: expected a primitive result from increment"));
return;
return OutputStream::one(Err(ShellError::unimplemented("Internal error: expected a primitive result from increment")));
}
},
Err((left_type, right_type)) => {
yield Err(ShellError::coerce_error(
return OutputStream::one(Err(ShellError::coerce_error(
left_type.spanned(tag.span),
right_type.spanned(tag.span),
));
return;
)));
}
}
}
match range.to.1 {
RangeInclusion::Inclusive => {
yield Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag)));
}
_ => {}
if let RangeInclusion::Inclusive = range.to.1 {
output_vec.push(Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current).into_value(&tag))));
}
futures::stream::iter(output_vec.into_iter()).to_output_stream()
}
_ => {
yield Ok(ReturnSuccess::Value(i.clone()));
OutputStream::one(Ok(ReturnSuccess::Value(i.clone())))
}
},
}
}
};
});
Ok(stream.to_output_stream())
Ok(futures::stream::iter(stream).flatten().to_output_stream())
}
#[cfg(test)]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -36,22 +36,28 @@ impl WholeStreamCommand for FromODS {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_ods(args, registry)
from_ods(args, registry).await
}
}
fn from_ods(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn from_ods(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let registry = registry.clone();
let stream = async_stream! {
let (FromODSArgs { headerless: _headerless }, mut input) = args.process(&registry).await?;
let (
FromODSArgs {
headerless: _headerless,
},
input,
) = args.process(&registry).await?;
let bytes = input.collect_binary(tag.clone()).await?;
let mut buf: Cursor<Vec<u8>> = Cursor::new(bytes.item);
let mut ods = Ods::<_>::new(buf).map_err(|_| ShellError::labeled_error(
"Could not load ods file",
"could not load ods file",
&tag))?;
let buf: Cursor<Vec<u8>> = Cursor::new(bytes.item);
let mut ods = Ods::<_>::new(buf).map_err(|_| {
ShellError::labeled_error("Could not load ods file", "could not load ods file", &tag)
})?;
let mut dict = TaggedDictBuilder::new(&tag);
@@ -81,17 +87,15 @@ fn from_ods(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value());
} else {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Could not load sheet",
"could not load sheet",
&tag));
&tag,
));
}
}
yield ReturnSuccess::value(dict.into_value());
};
Ok(stream.to_output_stream())
Ok(OutputStream::one(ReturnSuccess::value(dict.into_value())))
}
#[cfg(test)]

View File

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

View File

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

View File

@@ -36,18 +36,25 @@ impl WholeStreamCommand for FromXLSX {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_xlsx(args, registry)
from_xlsx(args, registry).await
}
}
fn from_xlsx(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn from_xlsx(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let registry = registry.clone();
let stream = async_stream! {
let (FromXLSXArgs { headerless: _headerless }, mut input) = args.process(&registry).await?;
let (
FromXLSXArgs {
headerless: _headerless,
},
input,
) = args.process(&registry).await?;
let value = input.collect_binary(tag.clone()).await?;
let mut buf: Cursor<Vec<u8>> = Cursor::new(value.item);
let buf: Cursor<Vec<u8>> = Cursor::new(value.item);
let mut xls = Xlsx::<_>::new(buf).map_err(|_| {
ShellError::labeled_error("Could not load xlsx file", "could not load xlsx file", &tag)
})?;
@@ -80,7 +87,7 @@ fn from_xlsx(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStre
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value());
} else {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Could not load sheet",
"could not load sheet",
&tag,
@@ -88,10 +95,7 @@ fn from_xlsx(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStre
}
}
yield ReturnSuccess::value(dict.into_value());
};
Ok(stream.to_output_stream())
Ok(OutputStream::one(ReturnSuccess::value(dict.into_value())))
}
#[cfg(test)]

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ use indexmap::indexmap;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use nu_value_ext::as_string;
pub struct GroupBy;
@@ -71,6 +72,10 @@ impl WholeStreamCommand for GroupBy {
}
}
enum Grouper {
ByColumn(Option<Tagged<String>>),
}
pub async fn group_by(
args: CommandArgs,
registry: &CommandRegistry,
@@ -81,30 +86,84 @@ pub async fn group_by(
let values: Vec<Value> = input.collect().await;
if values.is_empty() {
Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
name,
))
} else {
match crate::utils::data::group(column_name, &values, None, &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
));
}
let values = UntaggedValue::table(&values).into_value(&name);
match group(&column_name, &values, name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(reason) => Err(reason),
}
}
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
let possibilities = for_value.data_descriptors();
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|x| (natural::distance::levenshtein_distance(x, &tried), x))
.collect();
possible_matches.sort();
if !possible_matches.is_empty() {
ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", possible_matches[0].1),
tried.tag(),
)
} else {
ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
tried.tag(),
)
}
}
pub fn group(
column_name: &Tagged<String>,
values: Vec<Value>,
column_name: &Option<Tagged<String>>,
values: &Value,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
crate::utils::data::group(Some(column_name.clone()), &values, None, tag)
let name = tag.into();
let grouper = if let Some(column_name) = column_name {
Grouper::ByColumn(Some(column_name.clone()))
} else {
Grouper::ByColumn(None)
};
match grouper {
Grouper::ByColumn(Some(column_name)) => {
let block = Box::new(move |row: &Value| {
match row.get_data_by_key(column_name.borrow_spanned()) {
Some(group_key) => Ok(as_string(&group_key)?),
None => Err(suggestions(column_name.borrow_tagged(), &row)),
}
});
crate::utils::data::group(&values, &Some(block), &name)
}
Grouper::ByColumn(None) => {
let block = Box::new(move |row: &Value| match as_string(row) {
Ok(group_key) => Ok(group_key),
Err(reason) => Err(reason),
});
crate::utils::data::group(&values, &Some(block), &name)
}
}
}
#[cfg(test)]
mod tests {
use crate::commands::group_by::group;
use super::group;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value};
@@ -122,7 +181,7 @@ mod tests {
UntaggedValue::table(list).into_untagged_value()
}
fn nu_releases_commiters() -> Vec<Value> {
fn nu_releases_committers() -> Vec<Value> {
vec![
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")},
@@ -156,10 +215,11 @@ mod tests {
#[test]
fn groups_table_by_date_column() -> Result<(), ShellError> {
let for_key = String::from("date").tagged_unknown();
let for_key = Some(String::from("date").tagged_unknown());
let sample = table(&nu_releases_committers());
assert_eq!(
group(&for_key, nu_releases_commiters(), Tag::unknown())?,
group(&for_key, &sample, Tag::unknown())?,
row(indexmap! {
"August 23-2019".into() => table(&[
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}),
@@ -184,10 +244,11 @@ mod tests {
#[test]
fn groups_table_by_country_column() -> Result<(), ShellError> {
let for_key = String::from("country").tagged_unknown();
let for_key = Some(String::from("country").tagged_unknown());
let sample = table(&nu_releases_committers());
assert_eq!(
group(&for_key, nu_releases_commiters(), Tag::unknown())?,
group(&for_key, &sample, Tag::unknown())?,
row(indexmap! {
"EC".into() => table(&[
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}),

View File

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

View File

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

View File

@@ -36,67 +36,90 @@ impl WholeStreamCommand for Help {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
help(args, registry)
help(args, registry).await
}
}
fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let stream = async_stream! {
let (HelpArgs { rest }, mut input) = args.process(&registry).await?;
if let Some(document) = rest.get(0) {
if document.item == "commands" {
let (HelpArgs { rest }, ..) = args.process(&registry).await?;
if !rest.is_empty() {
if rest[0].item == "commands" {
let mut sorted_names = registry.names();
sorted_names.sort();
for cmd in sorted_names {
Ok(
futures::stream::iter(sorted_names.into_iter().filter_map(move |cmd| {
// If it's a subcommand, don't list it during the commands list
if cmd.contains(' ') {
continue;
return None;
}
let mut short_desc = TaggedDictBuilder::new(name.clone());
let document_tag = document.tag.clone();
let document_tag = rest[0].tag.clone();
let value = command_dict(
registry.get_command(&cmd).ok_or_else(|| {
match registry.get_command(&cmd).ok_or_else(|| {
ShellError::labeled_error(
format!("Could not load {}", cmd),
"could not load command",
document_tag,
)
})?,
}) {
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
},
name.clone(),
);
short_desc.insert_untagged("name", cmd);
short_desc.insert_untagged(
"description",
get_data_by_key(&value, "usage".spanned_unknown())
.ok_or_else(|| {
match match get_data_by_key(&value, "usage".spanned_unknown()).ok_or_else(
|| {
ShellError::labeled_error(
"Expected a usage key",
"expected a 'usage' key",
&value.tag,
)
})?
.as_string()?,
},
) {
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
}
.as_string()
{
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
},
);
yield ReturnSuccess::value(short_desc.into_value());
}
Some(ReturnSuccess::value(short_desc.into_value()))
}))
.to_output_stream(),
)
} else if rest.len() == 2 {
// Check for a subcommand
let command_name = format!("{} {}", rest[0].item, rest[1].item);
if let Some(command) = registry.get_command(&command_name) {
yield Ok(ReturnSuccess::Value(UntaggedValue::string(get_help(command.stream_command(), &registry)).into_value(Tag::unknown())));
}
} else if let Some(command) = registry.get_command(&document.item) {
yield Ok(ReturnSuccess::Value(UntaggedValue::string(get_help(command.stream_command(), &registry)).into_value(Tag::unknown())));
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(command.stream_command(), &registry))
.into_value(Tag::unknown()),
)))
} else {
yield Err(ShellError::labeled_error(
Ok(OutputStream::empty())
}
} else if let Some(command) = registry.get_command(&rest[0].item) {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(command.stream_command(), &registry))
.into_value(Tag::unknown()),
)))
} else {
Err(ShellError::labeled_error(
"Can't find command (use 'help commands' for full list)",
"can't find command",
document.tag.span,
));
rest[0].tag.span,
))
}
} else {
let msg = r#"Welcome to Nushell.
@@ -121,11 +144,10 @@ Get the processes on your system actively using CPU:
You can also learn more at https://www.nushell.sh/book/"#;
yield Ok(ReturnSuccess::Value(UntaggedValue::string(msg).into_value(Tag::unknown())));
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(msg).into_value(Tag::unknown()),
)))
}
};
Ok(stream.to_output_stream())
}
#[allow(clippy::cognitive_complexity)]

View File

@@ -45,7 +45,7 @@ impl WholeStreamCommand for Histogram {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
histogram(args, registry)
histogram(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -70,21 +70,20 @@ impl WholeStreamCommand for Histogram {
}
}
pub fn histogram(
pub async fn histogram(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let stream = async_stream! {
let (HistogramArgs { column_name, rest}, mut input) = args.process(&registry).await?;
let (HistogramArgs { column_name, rest }, input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
let values = UntaggedValue::table(&values).into_value(&name);
let Tagged { item: group_by, .. } = column_name.clone();
let groups = group(&column_name, values, &name)?;
let group_labels = columns_sorted(Some(group_by.clone()), &groups, &name);
let sorted = t_sort(Some(group_by.clone()), None, &groups, &name)?;
let groups = group(&Some(column_name.clone()), &values, &name)?;
let group_labels = columns_sorted(Some(column_name.clone()), &groups, &name);
let sorted = t_sort(Some(column_name.clone()), None, &groups, &name)?;
let evaled = evaluate(&sorted, None, &name)?;
let reduced = reduce(&evaled, None, &name)?;
let maxima = map_max(&reduced, None, &name)?;
@@ -95,7 +94,6 @@ pub fn histogram(
value: UntaggedValue::Table(datasets),
..
} => {
let mut idx = 0;
let column_names_supplied: Vec<_> = rest.iter().map(|f| f.item.clone()).collect();
@@ -109,7 +107,11 @@ pub fn histogram(
let column = (*column_name).clone();
let count_column_name = "count".to_string();
let count_shell_error = ShellError::labeled_error("Unable to load group count", "unabled to load group count", &name);
let count_shell_error = ShellError::labeled_error(
"Unable to load group count",
"unabled to load group count",
&name,
);
let mut count_values: Vec<u64> = Vec::new();
for table_entry in reduced.table_entries() {
@@ -122,43 +124,82 @@ pub fn histogram(
if let Ok(count) = i.value.clone().into_value(&name).as_u64() {
count_values.push(count);
} else {
yield Err(count_shell_error);
return;
return Err(count_shell_error);
}
}
}
_ => {
yield Err(count_shell_error);
return;
return Err(count_shell_error);
}
}
}
if let Value { value: UntaggedValue::Table(start), .. } = datasets.get(0).ok_or_else(|| ShellError::labeled_error("Unable to load dataset", "unabled to load dataset", &name))? {
for percentage in start.iter() {
if let Value {
value: UntaggedValue::Table(start),
..
} = datasets.get(0).ok_or_else(|| {
ShellError::labeled_error(
"Unable to load dataset",
"unabled to load dataset",
&name,
)
})? {
let start = start.clone();
Ok(
futures::stream::iter(start.into_iter().map(move |percentage| {
let mut fact = TaggedDictBuilder::new(&name);
let value: Tagged<String> = group_labels.get(idx).ok_or_else(|| ShellError::labeled_error("Unable to load group labels", "unabled to load group labels", &name))?.clone();
fact.insert_value(&column, UntaggedValue::string(value.item).into_value(value.tag));
let value: Tagged<String> = group_labels
.get(idx)
.ok_or_else(|| {
ShellError::labeled_error(
"Unable to load group labels",
"unabled to load group labels",
&name,
)
})?
.clone();
fact.insert_value(
&column,
UntaggedValue::string(value.item).into_value(value.tag),
);
fact.insert_untagged(&count_column_name, UntaggedValue::int(count_values[idx]));
fact.insert_untagged(
&count_column_name,
UntaggedValue::int(count_values[idx]),
);
if let Value { value: UntaggedValue::Primitive(Primitive::Int(ref num)), ref tag } = percentage.clone() {
let string = std::iter::repeat("*").take(num.to_i32().ok_or_else(|| ShellError::labeled_error("Expected a number", "expected a number", tag))? as usize).collect::<String>();
fact.insert_untagged(&frequency_column_name, UntaggedValue::string(string));
if let Value {
value: UntaggedValue::Primitive(Primitive::Int(ref num)),
ref tag,
} = percentage
{
let string = std::iter::repeat("*")
.take(num.to_i32().ok_or_else(|| {
ShellError::labeled_error(
"Expected a number",
"expected a number",
tag,
)
})? as usize)
.collect::<String>();
fact.insert_untagged(
&frequency_column_name,
UntaggedValue::string(string),
);
}
idx += 1;
yield ReturnSuccess::value(fact.into_value());
ReturnSuccess::value(fact.into_value())
}))
.to_output_stream(),
)
} else {
Ok(OutputStream::empty())
}
}
_ => Ok(OutputStream::empty()),
}
_ => {}
}
};
Ok(stream.to_output_stream())
}
fn percentages(values: &Value, max: Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
pub struct Keep;
@@ -35,7 +35,7 @@ impl WholeStreamCommand for Keep {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
keep(args, registry)
keep(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -59,27 +59,16 @@ impl WholeStreamCommand for Keep {
}
}
fn keep(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn keep(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (KeepArgs { rows }, mut input) = args.process(&registry).await?;
let mut rows_desired = if let Some(quantity) = rows {
let (KeepArgs { rows }, input) = args.process(&registry).await?;
let rows_desired = if let Some(quantity) = rows {
*quantity
} else {
1
};
while let Some(input) = input.next().await {
if rows_desired > 0 {
yield ReturnSuccess::value(input);
rows_desired -= 1;
} else {
break;
}
}
};
Ok(stream.to_output_stream())
Ok(input.take(rows_desired).to_output_stream())
}
#[cfg(test)]

View File

@@ -3,9 +3,7 @@ use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{
hir::ClassifiedCommand, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
pub struct KeepUntil;
@@ -34,80 +32,81 @@ impl WholeStreamCommand for KeepUntil {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = args.call_info.scope.clone();
let stream = async_stream! {
let mut call_info = args.evaluate_once(&registry).await?;
let registry = Arc::new(registry.clone());
let scope = Arc::new(args.call_info.scope.clone());
let call_info = args.evaluate_once(&registry).await?;
let block = call_info.args.expect_nth(0)?.clone();
let condition = match block {
let condition = Arc::new(match block {
Value {
value: UntaggedValue::Block(block),
tag,
} => {
if block.block.len() != 1 {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
match block.block[0].list.get(0) {
Some(item) => match item {
ClassifiedCommand::Expr(expr) => expr.clone(),
_ => {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
},
None => {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
}
}
Value { tag, .. } => {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
};
});
while let Some(item) = call_info.input.next().await {
Ok(call_info
.input
.take_while(move |item| {
let condition = condition.clone();
let registry = registry.clone();
let scope = scope.clone();
let item = item.clone();
trace!("ITEM = {:?}", item);
let result =
evaluate_baseline_expr(&*condition, &registry, &item, &scope.vars, &scope.env)
async move {
let result = evaluate_baseline_expr(
&*condition,
&registry,
&item,
&scope.vars,
&scope.env,
)
.await;
trace!("RESULT = {:?}", result);
let return_value = match result {
match result {
Ok(ref v) if v.is_true() => false,
_ => true,
};
if return_value {
yield ReturnSuccess::value(item);
} else {
break;
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
}

View File

@@ -3,9 +3,7 @@ use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{
hir::ClassifiedCommand, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
pub struct KeepWhile;
@@ -34,80 +32,81 @@ impl WholeStreamCommand for KeepWhile {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = args.call_info.scope.clone();
let stream = async_stream! {
let mut call_info = args.evaluate_once(&registry).await?;
let registry = Arc::new(registry.clone());
let scope = Arc::new(args.call_info.scope.clone());
let call_info = args.evaluate_once(&registry).await?;
let block = call_info.args.expect_nth(0)?.clone();
let condition = match block {
let condition = Arc::new(match block {
Value {
value: UntaggedValue::Block(block),
tag,
} => {
if block.block.len() != 1 {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
match block.block[0].list.get(0) {
Some(item) => match item {
ClassifiedCommand::Expr(expr) => expr.clone(),
_ => {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
},
None => {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
}
}
Value { tag, .. } => {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
};
});
while let Some(item) = call_info.input.next().await {
Ok(call_info
.input
.take_while(move |item| {
let condition = condition.clone();
let registry = registry.clone();
let scope = scope.clone();
let item = item.clone();
trace!("ITEM = {:?}", item);
let result =
evaluate_baseline_expr(&*condition, &registry, &item, &scope.vars, &scope.env)
async move {
let result = evaluate_baseline_expr(
&*condition,
&registry,
&item,
&scope.vars,
&scope.env,
)
.await;
trace!("RESULT = {:?}", result);
let return_value = match result {
match result {
Ok(ref v) if v.is_true() => true,
_ => false,
};
if return_value {
yield ReturnSuccess::value(item);
} else {
break;
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
}

View File

@@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged;
use std::process::{Command, Stdio};
@@ -43,7 +43,7 @@ impl WholeStreamCommand for Kill {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
kill(args, registry)
kill(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -62,16 +62,18 @@ impl WholeStreamCommand for Kill {
}
}
fn kill(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn kill(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (KillArgs {
let (
KillArgs {
pid,
rest,
force,
quiet,
}, mut input) = args.process(&registry).await?;
},
..,
) = args.process(&registry).await?;
let mut cmd = if cfg!(windows) {
let mut cmd = Command::new("taskkill");
@@ -113,12 +115,7 @@ fn kill(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, S
cmd.status().expect("failed to execute shell command");
if false {
yield ReturnSuccess::value(UntaggedValue::nothing().into_value(Tag::unknown()));
}
};
Ok(stream.to_output_stream())
Ok(OutputStream::empty())
}
#[cfg(test)]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
pub mod avg;
pub mod command;
pub mod max;
pub mod median;
pub mod min;
pub mod mode;
pub mod sum;
pub mod utils;
pub use avg::SubCommand as MathAverage;
pub use command::Command as Math;
pub use max::SubCommand as MathMaximum;
pub use median::SubCommand as MathMedian;
pub use min::SubCommand as MathMinimum;
pub use mode::SubCommand as MathMode;
pub use sum::SubCommand as MathSummation;

View File

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

View File

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

View File

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

View File

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

View File

@@ -43,7 +43,7 @@ impl WholeStreamCommand for Move {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
mv(args, registry)
mv(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -67,20 +67,13 @@ impl WholeStreamCommand for Move {
}
}
fn mv(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn mv(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let name = args.call_info.name_tag.clone();
let shell_manager = args.shell_manager.clone();
let (args, _) = args.process(&registry).await?;
let mut result = shell_manager.mv(args, name)?;
while let Some(item) = result.next().await {
yield item;
}
};
Ok(stream.to_output_stream())
shell_manager.mv(args, name)
}
#[cfg(test)]

View File

@@ -38,7 +38,7 @@ impl WholeStreamCommand for Nth {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
nth(args, registry)
nth(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -57,30 +57,36 @@ impl WholeStreamCommand for Nth {
}
}
fn nth(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn nth(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (NthArgs { row_number, rest: and_rows}, input) = args.process(&registry).await?;
let (
NthArgs {
row_number,
rest: and_rows,
},
input,
) = args.process(&registry).await?;
let mut inp = input.enumerate();
while let Some((idx, item)) = inp.next().await {
let row_number = vec![row_number.clone()];
let row_numbers = vec![&row_number, &and_rows]
let row_numbers = vec![vec![row_number], and_rows]
.into_iter()
.flatten()
.collect::<Vec<&Tagged<u64>>>();
.collect::<Vec<Tagged<u64>>>();
Ok(input
.enumerate()
.filter_map(move |(idx, item)| {
futures::future::ready(
if row_numbers
.iter()
.any(|requested| requested.item == idx as u64)
{
yield ReturnSuccess::value(item);
}
}
};
Ok(stream.to_output_stream())
Some(ReturnSuccess::value(item))
} else {
None
},
)
})
.to_output_stream())
}
#[cfg(test)]

View File

@@ -4,6 +4,12 @@ use nu_errors::ShellError;
use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::{AnchorLocation, Span, Tagged};
use std::path::{Path, PathBuf};
extern crate encoding_rs;
use encoding_rs::*;
use std::fs::File;
use std::io::BufWriter;
use std::io::Read;
use std::io::Write;
pub struct Open;
@@ -11,6 +17,7 @@ pub struct Open;
pub struct OpenArgs {
path: Tagged<PathBuf>,
raw: Tagged<bool>,
encoding: Option<Tagged<String>>,
}
#[async_trait]
@@ -31,10 +38,23 @@ impl WholeStreamCommand for Open {
"load content as a string instead of a table",
Some('r'),
)
.named(
"encoding",
SyntaxShape::String,
"encoding to use to open file",
Some('e'),
)
}
fn usage(&self) -> &str {
"Load a file into a cell, convert to table if possible (avoid by appending '--raw')"
r#"Load a file into a cell, convert to table if possible (avoid by appending '--raw').
Multiple encodings are supported for reading text files by using
the '--encoding <encoding>' parameter. Here is an example of a few:
big5, euc-jp, euc-kr, gbk, iso-8859-1, utf-16, cp1252, latin5
For a more complete list of encodings please refer to the encoding_rs
documentation link at https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics"#
}
async fn run(
@@ -46,11 +66,32 @@ impl WholeStreamCommand for Open {
}
fn examples(&self) -> Vec<Example> {
vec![Example {
vec![
Example {
description: "Opens \"users.csv\" and creates a table from the data",
example: "open users.csv",
result: None,
}]
},
Example {
description: "Opens file with iso-8859-1 encoding",
example: "open file.csv --encoding iso-8859-1 | from csv",
result: None,
},
]
}
}
pub fn get_encoding(opt: Option<String>) -> &'static Encoding {
match opt {
None => UTF_8,
Some(label) => match Encoding::for_label((&label).as_bytes()) {
None => {
//print!("{} is not a known encoding label. Trying UTF-8.", label);
//std::process::exit(-2);
get_encoding(Some("utf-8".to_string()))
}
Some(encoding) => encoding,
},
}
}
@@ -59,8 +100,19 @@ async fn open(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
let full_path = cwd;
let registry = registry.clone();
let (OpenArgs { path, raw }, _) = args.process(&registry).await?;
let result = fetch(&full_path, &path.item, path.tag.span).await;
let (
OpenArgs {
path,
raw,
encoding,
},
_,
) = args.process(&registry).await?;
let enc = match encoding {
Some(e) => e.to_string(),
_ => "".to_string(),
};
let result = fetch(&full_path, &path.item, path.tag.span, enc).await;
let (file_extension, contents, contents_tag) = result?;
@@ -87,11 +139,49 @@ pub async fn fetch(
cwd: &PathBuf,
location: &PathBuf,
span: Span,
encoding: String,
) -> Result<(Option<String>, UntaggedValue, Tag), ShellError> {
let mut cwd = cwd.clone();
let output_encoding: &Encoding = get_encoding(Some("utf-8".to_string()));
let input_encoding: &Encoding = get_encoding(Some(encoding.clone()));
let mut decoder = input_encoding.new_decoder();
let mut encoder = output_encoding.new_encoder();
let mut _file: File;
let buf = Vec::new();
let mut bufwriter = BufWriter::new(buf);
cwd.push(Path::new(location));
if let Ok(cwd) = dunce::canonicalize(cwd) {
if let Ok(cwd) = dunce::canonicalize(&cwd) {
if !encoding.is_empty() {
// use the encoding string
match File::open(&Path::new(&cwd)) {
Ok(mut _file) => {
convert_via_utf8(
&mut decoder,
&mut encoder,
&mut _file,
&mut bufwriter,
false,
);
//bufwriter.flush()?;
Ok((
cwd.extension()
.map(|name| name.to_string_lossy().to_string()),
UntaggedValue::string(String::from_utf8_lossy(&bufwriter.buffer())),
Tag {
span,
anchor: Some(AnchorLocation::File(cwd.to_string_lossy().to_string())),
},
))
}
Err(_) => Err(ShellError::labeled_error(
format!("Cannot open {:?} for reading.", &cwd),
"file not found",
span,
)),
}
} else {
// Do the old stuff
match std::fs::read(&cwd) {
Ok(bytes) => match std::str::from_utf8(&bytes) {
Ok(s) => Ok((
@@ -202,20 +292,117 @@ pub async fn fetch(
}
},
Err(_) => Err(ShellError::labeled_error(
"File could not be opened",
format!("Cannot open {:?} for reading.", &cwd),
"file not found",
span,
)),
}
}
} else {
Err(ShellError::labeled_error(
"File could not be opened",
format!("Cannot open {:?} for reading.", &cwd),
"file not found",
span,
))
}
}
fn convert_via_utf8(
decoder: &mut Decoder,
encoder: &mut Encoder,
read: &mut dyn Read,
write: &mut dyn Write,
last: bool,
) {
let mut input_buffer = [0u8; 2048];
let mut intermediate_buffer_bytes = [0u8; 4096];
// Is there a safe way to create a stack-allocated &mut str?
let mut intermediate_buffer: &mut str =
//unsafe { std::mem::transmute(&mut intermediate_buffer_bytes[..]) };
std::str::from_utf8_mut(&mut intermediate_buffer_bytes[..]).expect("error with from_utf8_mut");
let mut output_buffer = [0u8; 4096];
let mut current_input_ended = false;
while !current_input_ended {
match read.read(&mut input_buffer) {
Err(_) => {
print!("Error reading input.");
//std::process::exit(-5);
}
Ok(decoder_input_end) => {
current_input_ended = decoder_input_end == 0;
let input_ended = last && current_input_ended;
let mut decoder_input_start = 0usize;
loop {
let (decoder_result, decoder_read, decoder_written, _) = decoder.decode_to_str(
&input_buffer[decoder_input_start..decoder_input_end],
&mut intermediate_buffer,
input_ended,
);
decoder_input_start += decoder_read;
let last_output = if input_ended {
match decoder_result {
CoderResult::InputEmpty => true,
CoderResult::OutputFull => false,
}
} else {
false
};
// Regardless of whether the intermediate buffer got full
// or the input buffer was exhausted, let's process what's
// in the intermediate buffer.
if encoder.encoding() == UTF_8 {
// If the target is UTF-8, optimize out the encoder.
if write
.write_all(&intermediate_buffer.as_bytes()[..decoder_written])
.is_err()
{
print!("Error writing output.");
//std::process::exit(-7);
}
} else {
let mut encoder_input_start = 0usize;
loop {
let (encoder_result, encoder_read, encoder_written, _) = encoder
.encode_from_utf8(
&intermediate_buffer[encoder_input_start..decoder_written],
&mut output_buffer,
last_output,
);
encoder_input_start += encoder_read;
if write.write_all(&output_buffer[..encoder_written]).is_err() {
print!("Error writing output.");
//std::process::exit(-6);
}
match encoder_result {
CoderResult::InputEmpty => {
break;
}
CoderResult::OutputFull => {
continue;
}
}
}
}
// Now let's see if we should read again or process the
// rest of the current input buffer.
match decoder_result {
CoderResult::InputEmpty => {
break;
}
CoderResult::OutputFull => {
continue;
}
}
}
}
}
}
}
fn read_le_u16(input: &[u8]) -> Option<Vec<u16>> {
if input.len() % 2 != 0 || input.len() < 2 {
None

View File

@@ -51,24 +51,29 @@ impl WholeStreamCommand for Pivot {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
pivot(args, registry)
pivot(args, registry).await
}
}
pub fn pivot(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
pub async fn pivot(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let stream = async_stream! {
let (args, mut input): (PivotArgs, _) = args.process(&registry).await?;
let (args, input): (PivotArgs, _) = args.process(&registry).await?;
let input = input.into_vec().await;
let descs = merge_descriptors(&input);
let mut headers: Vec<String> = vec![];
if args.rest.len() > 0 && args.header_row {
yield Err(ShellError::labeled_error("Can not provide header names and use header row", "using header row", name));
return;
if !args.rest.is_empty() && args.header_row {
return Err(ShellError::labeled_error(
"Can not provide header names and use header row",
"using header row",
name,
));
}
if args.header_row {
@@ -79,18 +84,27 @@ pub fn pivot(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStre
if let Ok(s) = x.as_string() {
headers.push(s.to_string());
} else {
yield Err(ShellError::labeled_error("Header row needs string headers", "used non-string headers", name));
return;
return Err(ShellError::labeled_error(
"Header row needs string headers",
"used non-string headers",
name,
));
}
}
_ => {
yield Err(ShellError::labeled_error("Header row is incomplete and can't be used", "using incomplete header row", name));
return;
return Err(ShellError::labeled_error(
"Header row is incomplete and can't be used",
"using incomplete header row",
name,
));
}
}
} else {
yield Err(ShellError::labeled_error("Header row is incomplete and can't be used", "using incomplete header row", name));
return;
return Err(ShellError::labeled_error(
"Header row is incomplete and can't be used",
"using incomplete header row",
name,
));
}
}
} else {
@@ -104,17 +118,20 @@ pub fn pivot(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStre
}
let descs: Vec<_> = if args.header_row {
descs.iter().skip(1).collect()
descs.into_iter().skip(1).collect()
} else {
descs.iter().collect()
descs
};
for desc in descs {
Ok(futures::stream::iter(descs.into_iter().map(move |desc| {
let mut column_num: usize = 0;
let mut dict = TaggedDictBuilder::new(&name);
if !args.ignore_titles && !args.header_row {
dict.insert_untagged(headers[column_num].clone(), UntaggedValue::string(desc.clone()));
dict.insert_untagged(
headers[column_num].clone(),
UntaggedValue::string(desc.clone()),
);
column_num += 1
}
@@ -130,13 +147,9 @@ pub fn pivot(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStre
column_num += 1;
}
yield ReturnSuccess::value(dict.into_value());
}
};
Ok(OutputStream::new(stream))
ReturnSuccess::value(dict.into_value())
}))
.to_output_stream())
}
#[cfg(test)]

View File

@@ -3,7 +3,7 @@ use crate::prelude::*;
use derive_new::new;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value};
use nu_protocol::{Primitive, ReturnValue, Signature, UntaggedValue, Value};
use serde::{self, Deserialize, Serialize};
use std::io::prelude::*;
use std::io::BufReader;
@@ -61,11 +61,11 @@ impl WholeStreamCommand for PluginCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
filter_plugin(self.path.clone(), args, registry)
filter_plugin(self.path.clone(), args, registry).await
}
}
pub fn filter_plugin(
pub async fn filter_plugin(
path: String,
args: CommandArgs,
registry: &CommandRegistry,
@@ -75,8 +75,14 @@ pub fn filter_plugin(
let scope = args.call_info.scope.clone();
let stream = async_stream! {
let mut args = args.evaluate_once_with_scope(&registry, &scope).await?;
let bos = futures::stream::iter(vec![
UntaggedValue::Primitive(Primitive::BeginningOfStream).into_untagged_value()
]);
let eos = futures::stream::iter(vec![
UntaggedValue::Primitive(Primitive::EndOfStream).into_untagged_value()
]);
let args = args.evaluate_once_with_scope(&registry, &scope).await?;
let mut child = std::process::Command::new(path)
.stdin(std::process::Stdio::piped())
@@ -88,8 +94,16 @@ pub fn filter_plugin(
trace!("filtering :: {:?}", call_info);
Ok(bos
.chain(args.input)
.chain(eos)
.map(move |item| {
match item {
Value {
value: UntaggedValue::Primitive(Primitive::BeginningOfStream),
..
} => {
// Beginning of the stream
{
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
@@ -100,18 +114,22 @@ pub fn filter_plugin(
match request_raw {
Err(_) => {
yield Err(ShellError::labeled_error(
return OutputStream::one(Err(ShellError::labeled_error(
"Could not load json from plugin",
"could not load json from plugin",
&call_info.name_tag,
));
)));
}
Ok(request_raw) => match stdin.write(format!("{}\n", request_raw).as_bytes()) {
Ok(request_raw) => {
match stdin.write(format!("{}\n", request_raw).as_bytes()) {
Ok(_) => {}
Err(err) => {
yield Err(ShellError::unexpected(format!("{}", err)));
return OutputStream::one(Err(ShellError::unexpected(
format!("{}", err),
)));
}
}
}
},
}
let mut input = String::new();
@@ -120,82 +138,29 @@ pub fn filter_plugin(
let response = serde_json::from_str::<NuResult>(&input);
match response {
Ok(NuResult::response { params }) => match params {
Ok(params) => for param in params { yield param },
Err(e) => {
yield ReturnValue::Err(e);
}
Ok(params) => futures::stream::iter(params).to_output_stream(),
Err(e) => futures::stream::iter(vec![ReturnValue::Err(e)])
.to_output_stream(),
},
Err(e) => {
yield Err(ShellError::untagged_runtime_error(format!(
Err(e) => OutputStream::one(Err(
ShellError::untagged_runtime_error(format!(
"Error while processing begin_filter response: {:?} {}",
e, input
)));
)),
)),
}
}
}
Err(e) => {
yield Err(ShellError::untagged_runtime_error(format!(
"Error while reading begin_filter response: {:?}",
e
)));
Err(e) => OutputStream::one(Err(ShellError::untagged_runtime_error(
format!("Error while reading begin_filter response: {:?}", e),
))),
}
}
}
// Stream contents
{
while let Some(v) = args.input.next().await {
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
let mut reader = BufReader::new(stdout);
let request = JsonRpc::new("filter", v);
let request_raw = serde_json::to_string(&request);
match request_raw {
Ok(request_raw) => {
let _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error
}
Err(e) => {
yield Err(ShellError::untagged_runtime_error(format!(
"Error while processing filter response: {:?}",
e
)));
}
}
let mut input = String::new();
match reader.read_line(&mut input) {
Ok(_) => {
let response = serde_json::from_str::<NuResult>(&input);
match response {
Ok(NuResult::response { params }) => match params {
Ok(params) => for param in params { yield param },
Err(e) => {
yield ReturnValue::Err(e);
}
},
Err(e) => {
yield Err(ShellError::untagged_runtime_error(format!(
"Error while processing filter response: {:?}\n== input ==\n{}",
e, input
)));
}
}
}
Err(e) => {
yield Err(ShellError::untagged_runtime_error(format!(
"Error while reading filter response: {:?}",
e
)));
}
}
}
}
Value {
value: UntaggedValue::Primitive(Primitive::EndOfStream),
..
} => {
// post stream contents
{
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
@@ -206,18 +171,91 @@ pub fn filter_plugin(
match request_raw {
Err(_) => {
yield Err(ShellError::labeled_error(
return OutputStream::one(Err(ShellError::labeled_error(
"Could not load json from plugin",
"could not load json from plugin",
&call_info.name_tag,
));
)));
}
Ok(request_raw) => match stdin.write(format!("{}\n", request_raw).as_bytes()) {
Ok(request_raw) => {
match stdin.write(format!("{}\n", request_raw).as_bytes()) {
Ok(_) => {}
Err(err) => {
yield Err(ShellError::unexpected(format!("{}", err)));
return OutputStream::one(Err(ShellError::unexpected(
format!("{}", err),
)));
}
}
}
}
let mut input = String::new();
let stream = match reader.read_line(&mut input) {
Ok(_) => {
let response = serde_json::from_str::<NuResult>(&input);
match response {
Ok(NuResult::response { params }) => match params {
Ok(params) => futures::stream::iter(params).to_output_stream(),
Err(e) => futures::stream::iter(vec![ReturnValue::Err(e)])
.to_output_stream(),
},
Err(e) => futures::stream::iter(vec![Err(
ShellError::untagged_runtime_error(format!(
"Error while processing end_filter response: {:?} {}",
e, input
)),
)])
.to_output_stream(),
}
}
Err(e) => {
futures::stream::iter(vec![Err(ShellError::untagged_runtime_error(
format!("Error while reading end_filter response: {:?}", e),
))])
.to_output_stream()
}
};
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("quit", vec![]);
let request_raw = serde_json::to_string(&request);
match request_raw {
Ok(request_raw) => {
let _ = stdin.write(format!("{}\n", request_raw).as_bytes());
// TODO: Handle error
}
Err(e) => {
return OutputStream::one(Err(ShellError::untagged_runtime_error(
format!("Error while processing quit response: {:?}", e),
)));
}
}
let _ = child.wait();
stream
}
v => {
// Stream contents
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
let mut reader = BufReader::new(stdout);
let request = JsonRpc::new("filter", v);
let request_raw = serde_json::to_string(&request);
match request_raw {
Ok(request_raw) => {
let _ = stdin.write(format!("{}\n", request_raw).as_bytes());
// TODO: Handle error
}
Err(e) => {
return OutputStream::one(Err(ShellError::untagged_runtime_error(
format!("Error while processing filter response: {:?}", e),
)));
}
}
let mut input = String::new();
@@ -226,56 +264,27 @@ pub fn filter_plugin(
let response = serde_json::from_str::<NuResult>(&input);
match response {
Ok(NuResult::response { params }) => match params {
Ok(params) => for param in params { yield param },
Err(e) => {
yield ReturnValue::Err(e);
}
Ok(params) => futures::stream::iter(params).to_output_stream(),
Err(e) => futures::stream::iter(vec![ReturnValue::Err(e)])
.to_output_stream(),
},
Err(e) => {
yield Err(ShellError::untagged_runtime_error(format!(
"Error while processing end_filter response: {:?} {}",
Err(e) => OutputStream::one(Err(
ShellError::untagged_runtime_error(format!(
"Error while processing filter response: {:?}\n== input ==\n{}",
e, input
)));
)),
)),
}
}
Err(e) => OutputStream::one(Err(ShellError::untagged_runtime_error(
format!("Error while reading filter response: {:?}", e),
))),
}
}
}
Err(e) => {
yield Err(ShellError::untagged_runtime_error(format!(
"Error while reading end_filter response: {:?}",
e
)));
}
}
}
// End of the stream
{
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
let mut reader = BufReader::new(stdout);
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("quit", vec![]);
let request_raw = serde_json::to_string(&request);
match request_raw {
Ok(request_raw) => {
let _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error
}
Err(e) => {
yield Err(ShellError::untagged_runtime_error(format!(
"Error while processing quit response: {:?}",
e
)));
return;
}
}
}
let _ = child.wait();
};
Ok(stream.to_output_stream())
})
.flatten()
.to_output_stream())
}
#[derive(new)]
@@ -304,17 +313,16 @@ impl WholeStreamCommand for PluginSink {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
sink_plugin(self.path.clone(), args, registry)
sink_plugin(self.path.clone(), args, registry).await
}
}
pub fn sink_plugin(
pub async fn sink_plugin(
path: String,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let args = args.evaluate_once(&registry).await?;
let call_info = args.call_info.clone();
@@ -327,26 +335,25 @@ pub fn sink_plugin(
let _ = writeln!(tmpfile, "{}", request_raw);
let _ = tmpfile.flush();
let mut child = std::process::Command::new(path)
.arg(tmpfile.path())
.spawn();
let child = std::process::Command::new(path).arg(tmpfile.path()).spawn();
if let Ok(mut child) = child {
let _ = child.wait();
// Needed for async_stream to type check
if false {
yield ReturnSuccess::value(UntaggedValue::nothing().into_untagged_value());
Ok(OutputStream::empty())
} else {
Err(ShellError::untagged_runtime_error(
"Could not create process for sink command",
))
}
} else {
yield Err(ShellError::untagged_runtime_error("Could not create process for sink command"));
Err(ShellError::untagged_runtime_error(
"Could not open file to send sink command message",
))
}
} else {
yield Err(ShellError::untagged_runtime_error("Could not open file to send sink command message"));
Err(ShellError::untagged_runtime_error(
"Could not create message to sink command",
))
}
} else {
yield Err(ShellError::untagged_runtime_error("Could not create message to sink command"));
}
};
Ok(OutputStream::new(stream))
}

View File

@@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
#[derive(Deserialize)]
struct PrependArgs {
@@ -34,7 +34,7 @@ impl WholeStreamCommand for Prepend {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
prepend(args, registry)
prepend(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -51,19 +51,17 @@ impl WholeStreamCommand for Prepend {
}
}
fn prepend(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn prepend(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (PrependArgs { row }, mut input) = args.process(&registry).await?;
let (PrependArgs { row }, input) = args.process(&registry).await?;
yield ReturnSuccess::value(row);
while let Some(item) = input.next().await {
yield ReturnSuccess::value(item);
}
};
let bos = futures::stream::iter(vec![row]);
Ok(stream.to_output_stream())
Ok(bos.chain(input).to_output_stream())
}
#[cfg(test)]

View File

@@ -0,0 +1,32 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
pub struct Command;
#[async_trait]
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"random"
}
fn signature(&self) -> Signature {
Signature::build("random")
}
fn usage(&self) -> &str {
"Generate random values"
}
async fn run(
&self,
_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(crate::commands::help::get_help(&Command, &registry.clone()))
.into_value(Tag::unknown()),
))))
}
}

View File

@@ -0,0 +1,5 @@
pub mod command;
pub mod uuid;
pub use command::Command as Random;
pub use uuid::SubCommand as RandomUUID;

View File

@@ -0,0 +1,59 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature};
use uuid_crate::Uuid;
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"random uuid"
}
fn signature(&self) -> Signature {
Signature::build("random uuid")
}
fn usage(&self) -> &str {
"Generate a random uuid4 string"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
uuid(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Generate a random uuid4 string",
example: "random uuid",
result: None,
}]
}
}
pub async fn uuid(
_args: CommandArgs,
_registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let uuid_4 = Uuid::new_v4().to_hyphenated().to_string();
Ok(OutputStream::one(ReturnSuccess::value(uuid_4)))
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@@ -36,14 +36,13 @@ impl WholeStreamCommand for Range {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
range(args, registry)
range(args, registry).await
}
}
fn range(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn range(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (RangeArgs { area }, mut input) = args.process(&registry).await?;
let (RangeArgs { area }, input) = args.process(&registry).await?;
let range = area.item;
let (from, _) = range.from;
let (to, _) = range.to;
@@ -51,13 +50,11 @@ fn range(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream,
let from = *from as usize;
let to = *to as usize;
let mut inp = input.skip(from).take(to - from + 1);
while let Some(item) = inp.next().await {
yield ReturnSuccess::value(item);
}
};
Ok(stream.to_output_stream())
Ok(input
.skip(from)
.take(to - from + 1)
.map(ReturnSuccess::value)
.to_output_stream())
}
#[cfg(test)]

View File

@@ -31,7 +31,7 @@ impl WholeStreamCommand for Reject {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
reject(args, registry)
reject(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -43,28 +43,23 @@ impl WholeStreamCommand for Reject {
}
}
fn reject(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn reject(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let name = args.call_info.name_tag.clone();
let (RejectArgs { rest: fields }, mut input) = args.process(&registry).await?;
let (RejectArgs { rest: fields }, input) = args.process(&registry).await?;
if fields.is_empty() {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Reject requires fields",
"needs parameter",
name,
));
return;
}
let fields: Vec<_> = fields.iter().map(|f| f.item.clone()).collect();
while let Some(item) = input.next().await {
yield ReturnSuccess::value(reject_fields(&item, &fields, &item.tag));
}
};
Ok(stream.to_output_stream())
Ok(input
.map(move |item| ReturnSuccess::value(reject_fields(&item, &fields, &item.tag)))
.to_output_stream())
}
#[cfg(test)]

View File

@@ -38,7 +38,7 @@ impl WholeStreamCommand for Rename {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
rename(args, registry)
rename(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -57,17 +57,20 @@ impl WholeStreamCommand for Rename {
}
}
pub fn rename(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
pub async fn rename(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let stream = async_stream! {
let (Arguments { column_name, rest }, mut input) = args.process(&registry).await?;
let (Arguments { column_name, rest }, input) = args.process(&registry).await?;
let mut new_column_names = vec![vec![column_name]];
new_column_names.push(rest);
let new_column_names = new_column_names.into_iter().flatten().collect::<Vec<_>>();
while let Some(item) = input.next().await {
Ok(input
.map(move |item| {
if let Value {
value: UntaggedValue::Row(row),
tag,
@@ -87,21 +90,19 @@ pub fn rename(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
let out = UntaggedValue::Row(renamed_row.into()).into_value(tag);
yield ReturnSuccess::value(out);
ReturnSuccess::value(out)
} else {
yield ReturnSuccess::value(
ReturnSuccess::value(
UntaggedValue::Error(ShellError::labeled_error(
"no column names available",
"can't rename",
&name,
))
.into_untagged_value(),
);
)
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
#[cfg(test)]

View File

@@ -25,7 +25,7 @@ impl WholeStreamCommand for Reverse {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
reverse(args, registry)
reverse(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -43,19 +43,16 @@ impl WholeStreamCommand for Reverse {
}
}
fn reverse(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn reverse(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let args = args.evaluate_once(&registry).await?;
let (input, _args) = args.parts();
let input = input.collect::<Vec<_>>().await;
for output in input.into_iter().rev() {
yield ReturnSuccess::value(output);
}
};
Ok(stream.to_output_stream())
Ok(futures::stream::iter(input.into_iter().rev().map(ReturnSuccess::value)).to_output_stream())
}
#[cfg(test)]

View File

@@ -4,7 +4,7 @@ use crate::prelude::*;
use derive_new::new;
use nu_errors::ShellError;
use nu_protocol::{hir::Block, ReturnSuccess, Signature, SyntaxShape};
use nu_protocol::{hir::Block, Signature, SyntaxShape};
#[derive(new, Clone)]
pub struct AliasCommand {
@@ -38,7 +38,6 @@ impl WholeStreamCommand for AliasCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let call_info = args.call_info.clone();
let registry = registry.clone();
let block = self.block.clone();
@@ -46,61 +45,26 @@ impl WholeStreamCommand for AliasCommand {
let mut context = Context::from_args(&args, &registry);
let input = args.input;
let stream = async_stream! {
let mut scope = call_info.scope.clone();
let evaluated = call_info.evaluate(&registry).await?;
if let Some(positional) = &evaluated.args.positional {
for (pos, arg) in positional.iter().enumerate() {
scope.vars.insert(alias_command.args[pos].to_string(), arg.clone());
scope
.vars
.insert(alias_command.args[pos].to_string(), arg.clone());
}
}
let result = run_block(
// FIXME: we need to patch up the spans to point at the top-level error
Ok(run_block(
&block,
&mut context,
input,
&scope.it,
&scope.vars,
&scope.env,
).await;
match result {
Ok(stream) if stream.is_empty() => {
yield Err(ShellError::labeled_error(
"Expected a block",
"alias needs a block",
tag,
));
}
Ok(mut stream) => {
// We collect first to ensure errors are put into the context
while let Some(result) = stream.next().await {
yield Ok(ReturnSuccess::Value(result));
}
let errors = context.get_errors();
if let Some(x) = errors.first() {
yield Err(ShellError::labeled_error_with_secondary(
"Alias failed to run",
"alias failed to run",
tag.clone(),
x.to_string(),
tag
));
}
}
Err(e) => {
yield Err(ShellError::labeled_error_with_secondary(
"Alias failed to run",
"alias failed to run",
tag.clone(),
e.to_string(),
tag
));
}
}
};
Ok(stream.to_output_stream())
)
.await?
.to_output_stream())
}
}

View File

@@ -9,7 +9,7 @@ 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_protocol::{Signature, SyntaxShape};
use nu_source::Tagged;
#[derive(Deserialize)]
@@ -99,7 +99,6 @@ impl WholeStreamCommand for RunExternalCommand {
let is_interactive = self.interactive;
let stream = async_stream! {
let command = ExternalCommand {
name,
name_tag: args.call_info.name_tag.clone(),
@@ -117,51 +116,32 @@ impl WholeStreamCommand for RunExternalCommand {
path: Some(Tagged {
item: PathBuf::from(path),
tag: args.call_info.name_tag.clone(),
})
}),
};
let result = external_context.shell_manager.cd(cd_args, args.call_info.name_tag.clone());
let result = external_context
.shell_manager
.cd(cd_args, args.call_info.name_tag.clone());
match result {
Ok(mut stream) => {
while let Some(value) = stream.next().await {
yield value;
}
},
Ok(stream) => return Ok(stream.to_output_stream()),
Err(e) => {
yield Err(e);
},
_ => {}
return 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,
input,
&scope,
is_last,
).await;
let result =
external::run_external_command(command, &mut external_context, input, &scope, is_last)
.await;
match result {
Ok(mut stream) => {
while let Some(value) = stream.next().await {
yield Ok(ReturnSuccess::Value(value));
Ok(stream) => Ok(stream.to_output_stream()),
Err(e) => Err(e),
}
},
Err(e) => {
yield Err(e);
},
_ => {}
}
};
Ok(stream.to_output_stream())
}
}

View File

@@ -154,11 +154,14 @@ impl WholeStreamCommand for Save {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
save(args, registry)
save(args, registry).await
}
}
fn save(raw_args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn save(
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let mut full_path = PathBuf::from(raw_args.shell_manager.path());
let name_tag = raw_args.call_info.name_tag.clone();
let name = raw_args.call_info.name_tag.clone();
@@ -169,50 +172,44 @@ fn save(raw_args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
let current_errors = raw_args.current_errors.clone();
let shell_manager = raw_args.shell_manager.clone();
let stream = async_stream! {
let head = raw_args.call_info.args.head.clone();
let (SaveArgs { path, raw: save_raw }, mut input) = raw_args.process(&registry).await?;
let (
SaveArgs {
path,
raw: save_raw,
},
input,
) = raw_args.process(&registry).await?;
let input: Vec<Value> = input.collect().await;
if path.is_none() {
let mut should_return_file_path_error = true;
// If there is no filename, check the metadata for the anchor filename
if input.len() > 0 {
if !input.is_empty() {
let anchor = input[0].tag.anchor();
match anchor {
Some(path) => match path {
AnchorLocation::File(file) => {
if let Some(path) = anchor {
if let AnchorLocation::File(file) = path {
should_return_file_path_error = false;
full_path.push(Path::new(&file));
}
_ => {
yield Err(ShellError::labeled_error(
}
}
if should_return_file_path_error {
return Err(ShellError::labeled_error(
"Save requires a filepath",
"needs path",
name_tag.clone(),
));
}
},
None => {
yield Err(ShellError::labeled_error(
"Save requires a filepath",
"needs path",
name_tag.clone(),
));
}
}
} else {
yield Err(ShellError::labeled_error(
"Save requires a filepath",
"needs path",
name_tag.clone(),
));
}
} else {
if let Some(file) = path {
} else if let Some(file) = path {
full_path.push(file.item());
}
}
// TODO use label_break_value once it is stable:
// https://github.com/rust-lang/rust/issues/48594
#[allow(clippy::never_loop)]
let content: Result<Vec<u8>, ShellError> = 'scope: loop {
break if !save_raw {
if let Some(extension) = full_path.extension() {
@@ -233,10 +230,11 @@ fn save(raw_args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
},
name_tag: name_tag.clone(),
scope,
}
},
};
let mut result = converter.run(new_args.with_input(input), &registry).await;
let result_vec: Vec<Result<ReturnSuccess, ShellError>> = result.drain_vec().await;
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
result.drain_vec().await;
if converter.is_binary() {
process_binary_return_success!('scope, result_vec, name_tag)
} else {
@@ -255,15 +253,15 @@ fn save(raw_args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
match content {
Ok(save_data) => match std::fs::write(full_path, save_data) {
Ok(o) => o,
Err(e) => yield Err(ShellError::labeled_error(e.to_string(), "IO error while saving", name)),
Ok(_) => Ok(OutputStream::empty()),
Err(e) => Err(ShellError::labeled_error(
e.to_string(),
"IO error while saving",
name,
)),
},
Err(e) => yield Err(e),
Err(e) => Err(e),
}
};
Ok(OutputStream::new(stream))
}
fn string_from(input: &[Value]) -> String {

View File

@@ -6,7 +6,6 @@ use nu_protocol::{
ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder,
UnspannedPathMember, UntaggedValue, Value,
};
use nu_source::span_for_spanned_list;
use nu_value_ext::{as_string, get_data_by_column_path};
#[derive(Deserialize)]
@@ -38,7 +37,7 @@ impl WholeStreamCommand for Select {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
select(args, registry)
select(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -57,18 +56,16 @@ impl WholeStreamCommand for Select {
}
}
fn select(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn select(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let stream = async_stream! {
let (SelectArgs { rest: mut fields }, mut input) = args.process(&registry).await?;
if fields.is_empty() {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Select requires columns to select",
"needs parameter",
name,
));
return;
}
let member = fields.remove(0);
@@ -79,33 +76,39 @@ fn select(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream,
.flatten()
.cloned()
.collect::<Vec<ColumnPath>>();
let mut empty = true;
let mut bring_back: indexmap::IndexMap<String, Vec<Value>> = indexmap::IndexMap::new();
while let Some(value) = input.next().await {
for path in &column_paths {
let path_members_span = span_for_spanned_list(path.members().iter().map(|p| p.span));
let fetcher = get_data_by_column_path(&value, &path, Box::new(move |(obj_source, path_member_tried, error)| {
if let PathMember { unspanned: UnspannedPathMember::String(column), .. } = path_member_tried {
let fetcher = get_data_by_column_path(
&value,
&path,
Box::new(move |(obj_source, path_member_tried, error)| {
if let PathMember {
unspanned: UnspannedPathMember::String(column),
..
} = path_member_tried
{
return ShellError::labeled_error_with_secondary(
"No data to fetch.",
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)
"How about exploring it with \"get\"? Check the input is appropriate originating from here",
obj_source.tag.span);
}
error
}));
}),
);
let field = path.clone();
let key = as_string(&UntaggedValue::Primitive(Primitive::ColumnPath(field.clone())).into_untagged_value())?;
let key = as_string(
&UntaggedValue::Primitive(Primitive::ColumnPath(field.clone()))
.into_untagged_value(),
)?;
match fetcher {
Ok(results) => {
match results.value {
Ok(results) => match results.value {
UntaggedValue::Table(records) => {
for x in records {
let mut out = TaggedDictBuilder::new(name.clone());
@@ -113,16 +116,14 @@ fn select(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream,
let group = bring_back.entry(key.clone()).or_insert(vec![]);
group.push(out.into_value());
}
},
}
x => {
let mut out = TaggedDictBuilder::new(name.clone());
out.insert_untagged(&key, x.clone());
let group = bring_back.entry(key.clone()).or_insert(vec![]);
group.push(out.into_value());
}
}
}
},
Err(reason) => {
// At the moment, we can't add switches, named flags
// and the like while already using .rest since it
@@ -133,8 +134,7 @@ fn select(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream,
let strict: Option<bool> = None;
if strict.is_some() {
yield Err(reason);
return;
return Err(reason);
}
bring_back.entry(key.clone()).or_insert(vec![]);
@@ -149,9 +149,9 @@ fn select(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream,
max = max_column.len();
}
let keys = bring_back.keys().map(|x| x.clone()).collect::<Vec<String>>();
let keys = bring_back.keys().cloned().collect::<Vec<String>>();
for mut current in 0..max {
Ok(futures::stream::iter((0..max).map(move |current| {
let mut out = TaggedDictBuilder::new(name.clone());
for k in &keys {
@@ -159,21 +159,17 @@ fn select(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream,
let subsets = bring_back.get(k);
match subsets {
Some(set) => {
match set.get(current) {
Some(set) => match set.get(current) {
Some(row) => out.insert_untagged(k, row.get_data(k).borrow().clone()),
None => out.insert_untagged(k, nothing.clone()),
}
}
},
None => out.insert_untagged(k, nothing.clone()),
}
}
yield ReturnSuccess::value(out.into_value());
}
};
Ok(stream.to_output_stream())
ReturnSuccess::value(out.into_value())
}))
.to_output_stream())
}
#[cfg(test)]

View File

@@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, ReturnValue, Value};
use nu_protocol::{ReturnSuccess, Value};
use rand::seq::SliceRandom;
use rand::thread_rng;
@@ -24,28 +24,20 @@ impl WholeStreamCommand for Shuffle {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
shuffle(args, registry)
shuffle(args, registry).await
}
}
fn shuffle(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let mut input = args.input;
async fn shuffle(
args: CommandArgs,
_registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let input = args.input;
let mut values: Vec<Value> = input.collect().await;
let out = {
values.shuffle(&mut thread_rng());
values.clone()
};
for val in out.into_iter() {
yield ReturnSuccess::value(val);
}
};
let stream: BoxStream<'static, ReturnValue> = stream.boxed();
Ok(stream.to_output_stream())
Ok(futures::stream::iter(values.into_iter().map(ReturnSuccess::value)).to_output_stream())
}
#[cfg(test)]

View File

@@ -3,9 +3,7 @@ use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{
hir::ClassifiedCommand, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
pub struct SkipUntil;
@@ -34,83 +32,80 @@ impl WholeStreamCommand for SkipUntil {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = args.call_info.scope.clone();
let stream = async_stream! {
let mut call_info = args.evaluate_once(&registry).await?;
let registry = Arc::new(registry.clone());
let scope = Arc::new(args.call_info.scope.clone());
let call_info = args.evaluate_once(&registry).await?;
let block = call_info.args.expect_nth(0)?.clone();
let condition = match block {
let condition = Arc::new(match block {
Value {
value: UntaggedValue::Block(block),
tag,
} => {
if block.block.len() != 1 {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
match block.block[0].list.get(0) {
Some(item) => match item {
ClassifiedCommand::Expr(expr) => expr.clone(),
_ => {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
},
None => {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
}
}
Value { tag, .. } => {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
};
});
let mut skipping = true;
while let Some(item) = call_info.input.next().await {
Ok(call_info
.input
.skip_while(move |item| {
let condition = condition.clone();
let registry = registry.clone();
let scope = scope.clone();
let item = item.clone();
trace!("ITEM = {:?}", item);
let result =
evaluate_baseline_expr(&*condition, &registry, &item, &scope.vars, &scope.env)
async move {
let result = evaluate_baseline_expr(
&*condition,
&registry,
&item,
&scope.vars,
&scope.env,
)
.await;
trace!("RESULT = {:?}", result);
let return_value = match result {
Ok(ref v) if v.is_true() => true,
_ => false,
};
if return_value {
skipping = false;
}
if !skipping {
yield ReturnSuccess::value(item);
match result {
Ok(ref v) if v.is_true() => false, // stop skipping
_ => true,
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
}

View File

@@ -3,9 +3,7 @@ use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{
hir::ClassifiedCommand, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
pub struct SkipWhile;
@@ -34,83 +32,80 @@ impl WholeStreamCommand for SkipWhile {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let scope = args.call_info.scope.clone();
let stream = async_stream! {
let mut call_info = args.evaluate_once(&registry).await?;
let registry = Arc::new(registry.clone());
let scope = Arc::new(args.call_info.scope.clone());
let call_info = args.evaluate_once(&registry).await?;
let block = call_info.args.expect_nth(0)?.clone();
let condition = match block {
let condition = Arc::new(match block {
Value {
value: UntaggedValue::Block(block),
tag,
} => {
if block.block.len() != 1 {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
match block.block[0].list.get(0) {
Some(item) => match item {
ClassifiedCommand::Expr(expr) => expr.clone(),
_ => {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
},
None => {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
}
}
Value { tag, .. } => {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
return;
}
};
});
let mut skipping = true;
while let Some(item) = call_info.input.next().await {
Ok(call_info
.input
.skip_while(move |item| {
let item = item.clone();
let condition = condition.clone();
let registry = registry.clone();
let scope = scope.clone();
trace!("ITEM = {:?}", item);
let result =
evaluate_baseline_expr(&*condition, &registry, &item, &scope.vars, &scope.env)
async move {
let result = evaluate_baseline_expr(
&*condition,
&registry,
&item,
&scope.vars,
&scope.env,
)
.await;
trace!("RESULT = {:?}", result);
let return_value = match result {
Ok(ref v) if v.is_true() => false,
_ => true,
};
if return_value {
skipping = false;
}
if !skipping {
yield ReturnSuccess::value(item);
match result {
Ok(ref v) if v.is_true() => true,
_ => false,
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
}

View File

@@ -1,4 +1,5 @@
use crate::commands::WholeStreamCommand;
use crate::data::base::coerce_compare;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
@@ -70,15 +71,33 @@ async fn sort_by(
let (SortByArgs { rest }, mut input) = args.process(&registry).await?;
let mut vec = input.drain_vec().await;
sort(&mut vec, &rest, &tag)?;
let mut values_vec_deque: VecDeque<Value> = VecDeque::new();
for item in vec {
values_vec_deque.push_back(item);
}
Ok(futures::stream::iter(values_vec_deque).to_output_stream())
}
pub fn sort(
vec: &mut [Value],
keys: &[Tagged<String>],
tag: impl Into<Tag>,
) -> Result<(), ShellError> {
let tag = tag.into();
if vec.is_empty() {
return Err(ShellError::labeled_error(
"Error performing sort-by command",
"sort-by error",
"no values to work with",
"no values to work with",
tag,
));
}
for sort_arg in rest.iter() {
for sort_arg in keys.iter() {
let match_test = get_data_by_key(&vec[0], sort_arg.borrow_spanned());
if match_test == None {
return Err(ShellError::labeled_error(
@@ -94,11 +113,11 @@ async fn sort_by(
value: UntaggedValue::Primitive(_),
..
} => {
vec.sort();
vec.sort_by(|a, b| coerce_compare(a, b).expect("Unimplemented BUG: What about primitives that don't have an order defined?").compare());
}
_ => {
let calc_key = |item: &Value| {
rest.iter()
keys.iter()
.map(|f| get_data_by_key(item, f.borrow_spanned()))
.collect::<Vec<Option<Value>>>()
};
@@ -106,13 +125,7 @@ async fn sort_by(
}
};
let mut values_vec_deque: VecDeque<Value> = VecDeque::new();
for item in vec {
values_vec_deque.push_back(item);
}
Ok(futures::stream::iter(values_vec_deque).to_output_stream())
Ok(())
}
#[cfg(test)]

View File

@@ -43,16 +43,27 @@ impl WholeStreamCommand for SubCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
split_column(args, registry)
split_column(args, registry).await
}
}
fn split_column(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn split_column(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.span;
let registry = registry.clone();
let stream = async_stream! {
let (SplitColumnArgs { separator, rest, collapse_empty }, mut input) = args.process(&registry).await?;
while let Some(v) = input.next().await {
let (
SplitColumnArgs {
separator,
rest,
collapse_empty,
},
input,
) = args.process(&registry).await?;
Ok(input
.map(move |v| {
if let Ok(s) = v.as_string() {
let splitter = separator.replace("\\n", "\n");
trace!("splitting with {:?}", splitter);
@@ -79,7 +90,7 @@ fn split_column(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputS
dict.insert_untagged(v.clone(), Primitive::String(k.into()));
}
yield ReturnSuccess::value(dict.into_value());
ReturnSuccess::value(dict.into_value())
} else {
let mut dict = TaggedDictBuilder::new(&v.tag);
for (&k, v) in split_result.iter().zip(positional.iter()) {
@@ -88,21 +99,19 @@ fn split_column(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputS
UntaggedValue::Primitive(Primitive::String(k.into())),
);
}
yield ReturnSuccess::value(dict.into_value());
ReturnSuccess::value(dict.into_value())
}
} else {
yield Err(ShellError::labeled_error_with_secondary(
Err(ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
name_span,
"value originates from here",
v.tag.span,
));
))
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
#[cfg(test)]

View File

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

View File

@@ -35,41 +35,52 @@ impl WholeStreamCommand for SubCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
split_row(args, registry)
split_row(args, registry).await
}
}
fn split_row(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn split_row(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let name = args.call_info.name_tag.clone();
let (SplitRowArgs { separator }, mut input) = args.process(&registry).await?;
while let Some(v) = input.next().await {
let (SplitRowArgs { separator }, input) = args.process(&registry).await?;
Ok(input
.flat_map(move |v| {
if let Ok(s) = v.as_string() {
let splitter = separator.item.replace("\\n", "\n");
trace!("splitting with {:?}", splitter);
let split_result: Vec<_> = s.split(&splitter).filter(|s| s.trim() != "").collect();
let split_result: Vec<String> = s
.split(&splitter)
.filter_map(|s| {
if s.trim() != "" {
Some(s.to_string())
} else {
None
}
})
.collect();
trace!("split result = {:?}", split_result);
for s in split_result {
yield ReturnSuccess::value(
UntaggedValue::Primitive(Primitive::String(s.into())).into_value(&v.tag),
);
}
futures::stream::iter(split_result.into_iter().map(move |s| {
ReturnSuccess::value(
UntaggedValue::Primitive(Primitive::String(s)).into_value(&v.tag),
)
}))
.to_output_stream()
} else {
yield Err(ShellError::labeled_error_with_secondary(
OutputStream::one(Err(ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
name.span,
"value originates from here",
v.tag.span,
));
)))
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
#[cfg(test)]

View File

@@ -1,16 +1,15 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
Signature, SpannedTypeName, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
};
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
use nu_source::Tagged;
use nu_value_ext::as_string;
pub struct SplitBy;
#[derive(Deserialize)]
pub struct SplitByArgs {
column_name: Tagged<String>,
column_name: Option<Tagged<String>>,
}
#[async_trait]
@@ -20,7 +19,7 @@ impl WholeStreamCommand for SplitBy {
}
fn signature(&self) -> Signature {
Signature::build("split-by").required(
Signature::build("split-by").optional(
"column_name",
SyntaxShape::String,
"the name of the column within the nested table to split by",
@@ -53,108 +52,84 @@ pub async fn split_by(
return Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
column_name.span(),
name,
));
}
match split(&column_name, &values[0], name) {
Ok(split) => Ok(OutputStream::one(split)),
match split(&column_name, &values[0], &name) {
Ok(splits) => Ok(OutputStream::one(ReturnSuccess::value(splits))),
Err(err) => Err(err),
}
}
enum Grouper {
ByColumn(Option<Tagged<String>>),
}
pub fn split(
column_name: &Tagged<String>,
value: &Value,
column_name: &Option<Tagged<String>>,
values: &Value,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let origin_tag = tag.into();
let name = tag.into();
let mut splits = indexmap::IndexMap::new();
let grouper = if let Some(column_name) = column_name {
Grouper::ByColumn(Some(column_name.clone()))
} else {
Grouper::ByColumn(None)
};
match value {
Value {
value: UntaggedValue::Row(group_sets),
..
} => {
for (group_key, group_value) in group_sets.entries.iter() {
match *group_value {
Value {
value: UntaggedValue::Table(ref dataset),
..
} => {
let group = crate::commands::group_by::group(
&column_name,
dataset.to_vec(),
&origin_tag,
)?;
match grouper {
Grouper::ByColumn(Some(column_name)) => {
let block = Box::new(move |row: &Value| {
match row.get_data_by_key(column_name.borrow_spanned()) {
Some(group_key) => Ok(as_string(&group_key)?),
None => Err(suggestions(column_name.borrow_tagged(), &row)),
}
});
match group {
Value {
value: UntaggedValue::Row(o),
..
} => {
for (split_label, subset) in o.entries.into_iter() {
match subset {
Value {
value: UntaggedValue::Table(subset),
tag,
} => {
let s = splits
.entry(split_label.clone())
.or_insert(indexmap::IndexMap::new());
s.insert(
group_key.clone(),
UntaggedValue::table(&subset).into_value(tag),
);
crate::utils::data::split(&values, &Some(block), &name)
}
other => {
return Err(ShellError::type_error(
"a table value",
other.spanned_type_name(),
))
Grouper::ByColumn(None) => {
let block = Box::new(move |row: &Value| match as_string(row) {
Ok(group_key) => Ok(group_key),
Err(reason) => Err(reason),
});
crate::utils::data::split(&values, &Some(block), &name)
}
}
}
}
_ => {
return Err(ShellError::type_error(
"a table value",
group.spanned_type_name(),
))
}
}
}
ref other => {
return Err(ShellError::type_error(
"a table value",
other.spanned_type_name(),
))
}
}
}
}
_ => {
return Err(ShellError::type_error(
"a table value",
value.spanned_type_name(),
))
}
}
let mut out = TaggedDictBuilder::new(&origin_tag);
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
let possibilities = for_value.data_descriptors();
for (k, v) in splits.into_iter() {
out.insert_untagged(k, UntaggedValue::row(v));
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|x| (natural::distance::levenshtein_distance(x, &tried), x))
.collect();
possible_matches.sort();
if !possible_matches.is_empty() {
ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", possible_matches[0].1),
tried.tag(),
)
} else {
ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
tried.tag(),
)
}
}
Ok(out.into_value())
}
#[cfg(test)]
mod tests {
use super::split;
use crate::commands::group_by::group;
use crate::commands::split_by::split;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value};
@@ -173,11 +148,12 @@ mod tests {
}
fn nu_releases_grouped_by_date() -> Result<Value, ShellError> {
let key = String::from("date").tagged_unknown();
group(&key, nu_releases_commiters(), Tag::unknown())
let key = Some(String::from("date").tagged_unknown());
let sample = table(&nu_releases_committers());
group(&key, &sample, Tag::unknown())
}
fn nu_releases_commiters() -> Vec<Value> {
fn nu_releases_committers() -> Vec<Value> {
vec![
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")},
@@ -211,7 +187,7 @@ mod tests {
#[test]
fn splits_inner_tables_by_key() -> Result<(), ShellError> {
let for_key = String::from("country").tagged_unknown();
let for_key = Some(String::from("country").tagged_unknown());
assert_eq!(
split(&for_key, &nu_releases_grouped_by_date()?, Tag::unknown())?,
@@ -257,7 +233,7 @@ mod tests {
#[test]
fn errors_if_key_within_some_inner_table_is_missing() {
let for_key = String::from("country").tagged_unknown();
let for_key = Some(String::from("country").tagged_unknown());
let nu_releases = row(indexmap! {
"August 23-2019".into() => table(&[

View File

@@ -37,7 +37,7 @@ impl WholeStreamCommand for SubCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry)
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -49,47 +49,44 @@ impl WholeStreamCommand for SubCommand {
}
}
fn operate(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (Arguments { rest }, mut input) = args.process(&registry).await?;
let (Arguments { rest }, input) = args.process(&registry).await?;
let column_paths: Vec<_> = rest.iter().map(|x| x.clone()).collect();
let column_paths: Vec<_> = rest;
while let Some(v) = input.next().await {
Ok(input
.map(move |v| {
if column_paths.is_empty() {
match action(&v, v.tag()) {
Ok(out) => yield ReturnSuccess::value(out),
Err(err) => {
yield Err(err);
return;
}
Ok(out) => ReturnSuccess::value(out),
Err(err) => Err(err),
}
} else {
let mut ret = v.clone();
let mut ret = v;
for path in &column_paths {
let swapping = ret.swap_data_by_column_path(path, Box::new(move |old| action(old, old.tag())));
let swapping = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag())),
);
match swapping {
Ok(new_value) => {
ret = new_value;
}
Err(err) => {
yield Err(err);
return;
}
Err(err) => return Err(err),
}
}
yield ReturnSuccess::value(ret);
ReturnSuccess::value(ret)
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {

View File

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

View File

@@ -37,7 +37,7 @@ impl WholeStreamCommand for SubCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry)
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -49,47 +49,46 @@ impl WholeStreamCommand for SubCommand {
}
}
fn operate(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (Arguments { rest }, mut input) = args.process(&registry).await?;
let (Arguments { rest }, input) = args.process(&registry).await?;
let column_paths: Vec<_> = rest.iter().map(|x| x.clone()).collect();
let column_paths: Vec<_> = rest;
while let Some(v) = input.next().await {
Ok(input
.map(move |v| {
if column_paths.is_empty() {
match action(&v, v.tag()) {
Ok(out) => yield ReturnSuccess::value(out),
Err(err) => {
yield Err(err);
return;
}
Ok(out) => ReturnSuccess::value(out),
Err(err) => Err(err),
}
} else {
let mut ret = v.clone();
let mut ret = v;
for path in &column_paths {
let swapping = ret.swap_data_by_column_path(path, Box::new(move |old| action(old, old.tag())));
let swapping = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag())),
);
match swapping {
Ok(new_value) => {
ret = new_value;
}
Err(err) => {
yield Err(err);
return;
return Err(err);
}
}
}
yield ReturnSuccess::value(ret);
ReturnSuccess::value(ret)
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {

View File

@@ -44,7 +44,7 @@ impl WholeStreamCommand for SubCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry)
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -59,52 +59,56 @@ impl WholeStreamCommand for SubCommand {
#[derive(Clone)]
struct FindReplace(String, String);
fn operate(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (Arguments { find, replace, rest }, mut input) = args.process(&registry).await?;
let (
Arguments {
find,
replace,
rest,
},
input,
) = args.process(&registry).await?;
let options = FindReplace(find.item, replace.item);
let column_paths: Vec<_> = rest.iter().map(|x| x.clone()).collect();
let column_paths: Vec<_> = rest;
while let Some(v) = input.next().await {
Ok(input
.map(move |v| {
if column_paths.is_empty() {
match action(&v, &options, v.tag()) {
Ok(out) => yield ReturnSuccess::value(out),
Err(err) => {
yield Err(err);
return;
}
Ok(out) => ReturnSuccess::value(out),
Err(err) => Err(err),
}
} else {
let mut ret = v.clone();
let mut ret = v;
for path in &column_paths {
let options = options.clone();
let swapping = ret.swap_data_by_column_path(path, Box::new(move |old| {
action(old, &options, old.tag())
}));
let swapping = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, &options, old.tag())),
);
match swapping {
Ok(new_value) => {
ret = new_value;
}
Err(err) => {
yield Err(err);
return;
return Err(err);
}
}
}
yield ReturnSuccess::value(ret);
ReturnSuccess::value(ret)
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
fn action(input: &Value, options: &FindReplace, tag: impl Into<Tag>) -> Result<Value, ShellError> {

View File

@@ -37,7 +37,7 @@ impl WholeStreamCommand for SubCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry)
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -59,50 +59,49 @@ impl WholeStreamCommand for SubCommand {
#[derive(Clone)]
struct Replace(String);
fn operate(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (Arguments { replace, rest }, mut input) = args.process(&registry).await?;
let (Arguments { replace, rest }, input) = args.process(&registry).await?;
let options = Replace(replace.item);
let column_paths: Vec<_> = rest.iter().map(|x| x.clone()).collect();
let column_paths: Vec<_> = rest;
while let Some(v) = input.next().await {
Ok(input
.map(move |v| {
if column_paths.is_empty() {
match action(&v, &options, v.tag()) {
Ok(out) => yield ReturnSuccess::value(out),
Err(err) => {
yield Err(err);
return;
}
Ok(out) => ReturnSuccess::value(out),
Err(err) => Err(err),
}
} else {
let mut ret = v.clone();
let mut ret = v;
for path in &column_paths {
let options = options.clone();
let swapping = ret.swap_data_by_column_path(path, Box::new(move |old| action(old, &options, old.tag())));
let swapping = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, &options, old.tag())),
);
match swapping {
Ok(new_value) => {
ret = new_value;
}
Err(err) => {
yield Err(err);
return;
return Err(err);
}
}
}
yield ReturnSuccess::value(ret);
ReturnSuccess::value(ret)
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
fn action(_input: &Value, options: &Replace, tag: impl Into<Tag>) -> Result<Value, ShellError> {

View File

@@ -46,7 +46,7 @@ impl WholeStreamCommand for SubCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry)
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -73,91 +73,83 @@ impl WholeStreamCommand for SubCommand {
#[derive(Clone)]
struct Substring(usize, usize);
fn operate(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let registry = registry.clone();
let stream = async_stream! {
let (Arguments { range, rest }, mut input) = args.process(&registry).await?;
let (Arguments { range, rest }, input) = args.process(&registry).await?;
let v: Vec<&str> = range.item.split(',').collect();
let start = match v[0] {
"" => 0,
_ => v[0]
.trim()
.parse()
.map_err(|_| {
_ => v[0].trim().parse().map_err(|_| {
ShellError::labeled_error(
"could not perform substring",
"could not perform substring",
name.span,
)
})?
})?,
};
let end = match v[1] {
"" => usize::max_value(),
_ => v[1]
.trim()
.parse()
.map_err(|_| {
_ => v[1].trim().parse().map_err(|_| {
ShellError::labeled_error(
"could not perform substring",
"could not perform substring",
name.span,
)
})?
})?,
};
if start > end {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"End must be greater than or equal to Start",
"End must be greater than or equal to Start",
name.span,
));
return;
}
let options = Substring(start, end);
let column_paths: Vec<_> = rest.iter().map(|x| x.clone()).collect();
let column_paths: Vec<_> = rest;
while let Some(v) = input.next().await {
Ok(input
.map(move |v| {
if column_paths.is_empty() {
match action(&v, &options, v.tag()) {
Ok(out) => yield ReturnSuccess::value(out),
Err(err) => {
yield Err(err);
return;
}
Ok(out) => ReturnSuccess::value(out),
Err(err) => Err(err),
}
} else {
let mut ret = v.clone();
let mut ret = v;
for path in &column_paths {
let options = options.clone();
let swapping = ret.swap_data_by_column_path(path, Box::new(move |old| action(old, &options, old.tag())));
let swapping = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, &options, old.tag())),
);
match swapping {
Ok(new_value) => {
ret = new_value;
}
Err(err) => {
yield Err(err);
return;
return Err(err);
}
}
}
yield ReturnSuccess::value(ret);
ReturnSuccess::value(ret)
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
fn action(input: &Value, options: &Substring, tag: impl Into<Tag>) -> Result<Value, ShellError> {

View File

@@ -47,7 +47,7 @@ impl WholeStreamCommand for SubCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry)
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -62,54 +62,53 @@ impl WholeStreamCommand for SubCommand {
#[derive(Clone)]
struct DatetimeFormat(String);
fn operate(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (Arguments { format, rest }, mut input) = args.process(&registry).await?;
let (Arguments { format, rest }, input) = args.process(&registry).await?;
let column_paths: Vec<_> = rest.iter().map(|x| x.clone()).collect();
let column_paths: Vec<_> = rest;
let options = if let Some(Tagged { item: fmt, tag }) = format {
let options = if let Some(Tagged { item: fmt, .. }) = format {
DatetimeFormat(fmt)
} else {
DatetimeFormat(String::from("%d.%m.%Y %H:%M %P %z"))
};
while let Some(v) = input.next().await {
Ok(input
.map(move |v| {
if column_paths.is_empty() {
match action(&v, &options, v.tag()) {
Ok(out) => yield ReturnSuccess::value(out),
Err(err) => {
yield Err(err);
return;
}
Ok(out) => ReturnSuccess::value(out),
Err(err) => Err(err),
}
} else {
let mut ret = v.clone();
let mut ret = v;
for path in &column_paths {
let options = options.clone();
let swapping = ret.swap_data_by_column_path(path, Box::new(move |old| action(old, &options, old.tag())));
let swapping = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, &options, old.tag())),
);
match swapping {
Ok(new_value) => {
ret = new_value;
}
Err(err) => {
yield Err(err);
return;
return Err(err);
}
}
}
yield ReturnSuccess::value(ret);
ReturnSuccess::value(ret)
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
fn action(

View File

@@ -40,7 +40,7 @@ impl WholeStreamCommand for SubCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry)
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -52,47 +52,46 @@ impl WholeStreamCommand for SubCommand {
}
}
fn operate(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (Arguments { rest }, mut input) = args.process(&registry).await?;
let (Arguments { rest }, input) = args.process(&registry).await?;
let column_paths: Vec<_> = rest.iter().map(|x| x.clone()).collect();
let column_paths: Vec<_> = rest;
while let Some(v) = input.next().await {
Ok(input
.map(move |v| {
if column_paths.is_empty() {
match action(&v, v.tag()) {
Ok(out) => yield ReturnSuccess::value(out),
Err(err) => {
yield Err(err);
return;
}
Ok(out) => ReturnSuccess::value(out),
Err(err) => Err(err),
}
} else {
let mut ret = v.clone();
let mut ret = v;
for path in &column_paths {
let swapping = ret.swap_data_by_column_path(path, Box::new(move |old| action(old, old.tag())));
let swapping = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag())),
);
match swapping {
Ok(new_value) => {
ret = new_value;
}
Err(err) => {
yield Err(err);
return;
return Err(err);
}
}
}
yield ReturnSuccess::value(ret);
ReturnSuccess::value(ret)
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {

View File

@@ -40,7 +40,7 @@ impl WholeStreamCommand for SubCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry)
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -52,47 +52,46 @@ impl WholeStreamCommand for SubCommand {
}
}
fn operate(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (Arguments { rest }, mut input) = args.process(&registry).await?;
let (Arguments { rest }, input) = args.process(&registry).await?;
let column_paths: Vec<_> = rest.iter().map(|x| x.clone()).collect();
let column_paths: Vec<_> = rest;
while let Some(v) = input.next().await {
Ok(input
.map(move |v| {
if column_paths.is_empty() {
match action(&v, v.tag()) {
Ok(out) => yield ReturnSuccess::value(out),
Err(err) => {
yield Err(err);
return;
}
Ok(out) => ReturnSuccess::value(out),
Err(err) => Err(err),
}
} else {
let mut ret = v.clone();
let mut ret = v;
for path in &column_paths {
let swapping = ret.swap_data_by_column_path(path, Box::new(move |old| action(old, old.tag())));
let swapping = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag())),
);
match swapping {
Ok(new_value) => {
ret = new_value;
}
Err(err) => {
yield Err(err);
return;
return Err(err);
}
}
}
yield ReturnSuccess::value(ret);
ReturnSuccess::value(ret)
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {

View File

@@ -37,7 +37,7 @@ impl WholeStreamCommand for SubCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry)
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -49,47 +49,46 @@ impl WholeStreamCommand for SubCommand {
}
}
fn operate(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (Arguments { rest }, mut input) = args.process(&registry).await?;
let (Arguments { rest }, input) = args.process(&registry).await?;
let column_paths: Vec<_> = rest.iter().map(|x| x.clone()).collect();
let column_paths: Vec<_> = rest;
while let Some(v) = input.next().await {
Ok(input
.map(move |v| {
if column_paths.is_empty() {
match action(&v, v.tag()) {
Ok(out) => yield ReturnSuccess::value(out),
Err(err) => {
yield Err(err);
return;
}
Ok(out) => ReturnSuccess::value(out),
Err(err) => Err(err),
}
} else {
let mut ret = v.clone();
let mut ret = v;
for path in &column_paths {
let swapping = ret.swap_data_by_column_path(path, Box::new(move |old| action(old, old.tag())));
let swapping = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag())),
);
match swapping {
Ok(new_value) => {
ret = new_value;
}
Err(err) => {
yield Err(err);
return;
return Err(err);
}
}
}
yield ReturnSuccess::value(ret);
ReturnSuccess::value(ret)
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {

View File

@@ -37,7 +37,7 @@ impl WholeStreamCommand for SubCommand {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry)
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -49,47 +49,46 @@ impl WholeStreamCommand for SubCommand {
}
}
fn operate(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let (Arguments { rest }, mut input) = args.process(&registry).await?;
let (Arguments { rest }, input) = args.process(&registry).await?;
let column_paths: Vec<_> = rest.iter().map(|x| x.clone()).collect();
let column_paths: Vec<_> = rest;
while let Some(v) = input.next().await {
Ok(input
.map(move |v| {
if column_paths.is_empty() {
match action(&v, v.tag()) {
Ok(out) => yield ReturnSuccess::value(out),
Err(err) => {
yield Err(err);
return;
}
Ok(out) => ReturnSuccess::value(out),
Err(err) => Err(err),
}
} else {
let mut ret = v.clone();
let mut ret = v;
for path in &column_paths {
let swapping = ret.swap_data_by_column_path(path, Box::new(move |old| action(old, old.tag())));
let swapping = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag())),
);
match swapping {
Ok(new_value) => {
ret = new_value;
}
Err(err) => {
yield Err(err);
return;
return Err(err);
}
}
}
yield ReturnSuccess::value(ret);
ReturnSuccess::value(ret)
}
}
};
Ok(stream.to_output_stream())
})
.to_output_stream())
}
fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {

View File

@@ -1,113 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use nu_errors::ShellError;
use nu_protocol::{Dictionary, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value};
use num_traits::identities::Zero;
use indexmap::map::IndexMap;
pub struct Sum;
#[async_trait]
impl WholeStreamCommand for Sum {
fn name(&self) -> &str {
"sum"
}
fn signature(&self) -> Signature {
Signature::build("sum")
}
fn usage(&self) -> &str {
"Sums the values."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
sum(RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
})
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Sum a list of numbers",
example: "echo [1 2 3] | sum",
result: Some(vec![UntaggedValue::int(6).into()]),
},
Example {
description: "Get the disk usage for the current directory",
example: "ls --all --du | get size | sum",
result: None,
},
]
}
}
fn sum(RunnableContext { mut input, .. }: RunnableContext) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let mut values: Vec<Value> = input.drain_vec().await;
let action = reducer_for(Reduce::Sum);
if values.iter().all(|v| if let UntaggedValue::Primitive(_) = v.value {true} else {false}) {
let total = action(Value::zero(), values)?;
yield ReturnSuccess::value(total)
} else {
let mut column_values = IndexMap::new();
for value in values {
match value.value {
UntaggedValue::Row(row_dict) => {
for (key, value) in row_dict.entries.iter() {
column_values
.entry(key.clone())
.and_modify(|v: &mut Vec<Value>| v.push(value.clone()))
.or_insert(vec![value.clone()]);
}
},
table => {},
};
}
let mut column_totals = IndexMap::new();
for (col_name, col_vals) in column_values {
let sum = action(Value::zero(), col_vals);
match sum {
Ok(value) => {
column_totals.insert(col_name, value);
},
Err(err) => yield Err(err),
};
}
yield ReturnSuccess::value(
UntaggedValue::Row(Dictionary {entries: column_totals}).into_untagged_value())
}
};
let stream: BoxStream<'static, ReturnValue> = stream.boxed();
Ok(stream.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Sum;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Sum {})
}
}

View File

@@ -57,36 +57,47 @@ impl WholeStreamCommand for TSortBy {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
t_sort_by(args, registry)
t_sort_by(args, registry).await
}
}
fn t_sort_by(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn t_sort_by(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let name = args.call_info.name_tag.clone();
let (TSortByArgs { show_columns, group_by, ..}, mut input) = args.process(&registry).await?;
let (
TSortByArgs {
show_columns,
group_by,
..
},
mut input,
) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
let column_grouped_by_name = if let Some(grouped_by) = group_by {
Some(grouped_by.item().clone())
Some(grouped_by)
} else {
None
};
if show_columns {
for label in columns_sorted(column_grouped_by_name, &values[0], &name).into_iter() {
yield ReturnSuccess::value(UntaggedValue::string(label.item).into_value(label.tag));
}
Ok(futures::stream::iter(
columns_sorted(column_grouped_by_name, &values[0], &name)
.into_iter()
.map(move |label| {
ReturnSuccess::value(UntaggedValue::string(label.item).into_value(label.tag))
}),
)
.to_output_stream())
} else {
match t_sort(column_grouped_by_name, None, &values[0], name) {
Ok(sorted) => yield ReturnSuccess::value(sorted),
Err(err) => yield Err(err)
Ok(sorted) => Ok(OutputStream::one(ReturnSuccess::value(sorted))),
Err(err) => Ok(OutputStream::one(Err(err))),
}
}
};
Ok(stream.to_output_stream())
}
#[cfg(test)]

View File

@@ -1,8 +1,9 @@
use crate::commands::WholeStreamCommand;
use crate::format::TableView;
use crate::data::value::{format_leaf, style_leaf};
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
use nu_table::{draw_table, Alignment, StyledString, TextStyle, Theme};
use std::time::Instant;
const STREAM_PAGE_SIZE: usize = 1000;
@@ -34,33 +35,214 @@ impl WholeStreamCommand for Table {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
table(args, registry)
table(args, registry).await
}
}
fn table(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
fn str_to_color(s: String) -> Option<ansi_term::Color> {
match s.as_str() {
"g" | "green" => Some(ansi_term::Color::Green),
"r" | "red" => Some(ansi_term::Color::Red),
"u" | "blue" => Some(ansi_term::Color::Blue),
"b" | "black" => Some(ansi_term::Color::Black),
"y" | "yellow" => Some(ansi_term::Color::Yellow),
"p" | "purple" => Some(ansi_term::Color::Purple),
"c" | "cyan" => Some(ansi_term::Color::Cyan),
"w" | "white" => Some(ansi_term::Color::White),
_ => None,
}
}
pub fn from_list(values: &[Value], starting_idx: usize) -> nu_table::Table {
let config = crate::data::config::config(Tag::unknown());
let header_style = if let Ok(config) = &config {
let header_align = config.get("header_align").map_or(Alignment::Left, |a| {
a.as_string()
.map_or(Alignment::Center, |a| match a.to_lowercase().as_str() {
"center" | "c" => Alignment::Center,
"right" | "r" => Alignment::Right,
_ => Alignment::Center,
})
});
let header_color = match config.get("header_color") {
Some(c) => match c.as_string() {
Ok(color) => str_to_color(color.to_lowercase()).unwrap_or(ansi_term::Color::Green),
_ => ansi_term::Color::Green,
},
_ => ansi_term::Color::Green,
};
let header_bold = match config.get("header_bold") {
Some(b) => match b.as_bool() {
Ok(b) => b,
_ => true,
},
_ => true,
};
TextStyle {
alignment: header_align,
color: Some(header_color),
is_bold: header_bold,
}
} else {
TextStyle::default_header()
};
let mut headers: Vec<StyledString> = nu_protocol::merge_descriptors(values)
.into_iter()
.map(|x| StyledString::new(x, header_style.clone()))
.collect();
let entries = values_to_entries(values, &mut headers, starting_idx);
if let Ok(config) = config {
if let Some(style) = config.get("table_mode") {
if let Ok(table_mode) = style.as_string() {
if table_mode == "light" {
return nu_table::Table {
headers,
data: entries,
theme: Theme::light(),
};
}
}
}
}
nu_table::Table {
headers,
data: entries,
theme: Theme::compact(),
}
}
fn are_table_indexes_disabled() -> bool {
let config = crate::data::config::config(Tag::unknown());
match config {
Ok(config) => {
let disable_indexes = config.get("disable_table_indexes");
disable_indexes.map_or(false, |x| x.as_bool().unwrap_or(false))
}
_ => false,
}
}
fn values_to_entries(
values: &[Value],
headers: &mut Vec<StyledString>,
starting_idx: usize,
) -> Vec<Vec<StyledString>> {
let disable_indexes = are_table_indexes_disabled();
let mut entries = vec![];
if headers.is_empty() {
headers.push(StyledString::new("".to_string(), TextStyle::basic()));
}
for (idx, value) in values.iter().enumerate() {
let mut row: Vec<StyledString> = headers
.iter()
.map(|d: &StyledString| {
if d.contents == "" {
match value {
Value {
value: UntaggedValue::Row(..),
..
} => StyledString::new(
format_leaf(&UntaggedValue::nothing()).plain_string(100_000),
style_leaf(&UntaggedValue::nothing()),
),
_ => StyledString::new(
format_leaf(value).plain_string(100_000),
style_leaf(value),
),
}
} else {
match value {
Value {
value: UntaggedValue::Row(..),
..
} => {
let data = value.get_data(&d.contents);
StyledString::new(
format_leaf(data.borrow()).plain_string(100_000),
style_leaf(data.borrow()),
)
}
_ => StyledString::new(
format_leaf(&UntaggedValue::nothing()).plain_string(100_000),
style_leaf(&UntaggedValue::nothing()),
),
}
}
})
.collect();
// Indices are green, bold, right-aligned:
if !disable_indexes {
row.insert(
0,
StyledString::new(
(starting_idx + idx).to_string(),
TextStyle {
alignment: Alignment::Center,
color: Some(ansi_term::Color::Green),
is_bold: true,
},
),
);
}
entries.push(row);
}
if !disable_indexes {
headers.insert(
0,
StyledString::new(
"#".to_owned(),
TextStyle {
alignment: Alignment::Center,
color: Some(ansi_term::Color::Green),
is_bold: true,
},
),
);
}
entries
}
async fn table(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let mut args = args.evaluate_once(&registry).await?;
let mut finished = false;
let host = args.host.clone();
// let host = args.host.clone();
let mut start_number = match args.get("start_number") {
Some(Value { value: UntaggedValue::Primitive(Primitive::Int(i)), .. }) => {
Some(Value {
value: UntaggedValue::Primitive(Primitive::Int(i)),
..
}) => {
if let Some(num) = i.to_usize() {
num
} else {
yield Err(ShellError::labeled_error("Expected a row number", "expected a row number", &args.args.call_info.name_tag));
0
return Err(ShellError::labeled_error(
"Expected a row number",
"expected a row number",
&args.args.call_info.name_tag,
));
}
}
_ => {
0
}
_ => 0,
};
let mut delay_slot = None;
let termwidth = std::cmp::max(textwrap::termwidth(), 20);
while !finished {
let mut new_input: VecDeque<Value> = VecDeque::new();
@@ -109,35 +291,14 @@ fn table(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream,
let input: Vec<Value> = new_input.into();
if input.len() > 0 {
let mut host = host.lock();
let view = TableView::from_list(&input, start_number);
if !input.is_empty() {
let t = from_list(&input, start_number);
if let Some(view) = view {
handle_unexpected(&mut *host, |host| crate::format::print_view(&view, host));
}
draw_table(&t, termwidth);
}
start_number += input.len();
}
// Needed for async_stream to type check
if false {
yield ReturnSuccess::value(UntaggedValue::nothing().into_value(Tag::unknown()));
}
};
Ok(OutputStream::new(stream))
}
#[cfg(test)]
mod tests {
use super::Table;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Table {})
}
Ok(OutputStream::empty())
}

View File

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

View File

@@ -29,7 +29,7 @@ impl WholeStreamCommand for ToBSON {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
to_bson(args, registry)
to_bson(args, registry).await
}
fn is_binary(&self) -> bool {
@@ -261,34 +261,37 @@ fn bson_value_to_bytes(bson: Bson, tag: Tag) -> Result<Vec<u8>, ShellError> {
Ok(out)
}
fn to_bson(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn to_bson(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let args = args.evaluate_once(&registry).await?;
let name_tag = args.name_tag();
let name_span = name_tag.span;
let input: Vec<Value> = args.input.collect().await;
let to_process_input = if input.len() > 1 {
let to_process_input = match input.len() {
x if x > 1 => {
let tag = input[0].tag.clone();
vec![Value { value: UntaggedValue::Table(input), tag } ]
} else if input.len() == 1 {
input
} else {
vec![]
vec![Value {
value: UntaggedValue::Table(input),
tag,
}]
}
1 => input,
_ => vec![],
};
for value in to_process_input {
match value_to_bson_value(&value) {
Ok(futures::stream::iter(to_process_input.into_iter().map(
move |value| match value_to_bson_value(&value) {
Ok(bson_value) => {
let value_span = value.tag.span;
match bson_value_to_bytes(bson_value, name_tag.clone()) {
Ok(x) => yield ReturnSuccess::value(
UntaggedValue::binary(x).into_value(&name_tag),
),
_ => yield Err(ShellError::labeled_error_with_secondary(
Ok(x) => ReturnSuccess::value(UntaggedValue::binary(x).into_value(&name_tag)),
_ => Err(ShellError::labeled_error_with_secondary(
"Expected a table with BSON-compatible structure from pipeline",
"requires BSON-compatible input",
name_span,
@@ -297,15 +300,14 @@ fn to_bson(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
)),
}
}
_ => yield Err(ShellError::labeled_error(
_ => Err(ShellError::labeled_error(
"Expected a table with BSON-compatible structure from pipeline",
"requires BSON-compatible input",
&name_tag))
}
}
};
Ok(stream.to_output_stream())
&name_tag,
)),
},
))
.to_output_stream())
}
#[cfg(test)]

View File

@@ -42,15 +42,20 @@ impl WholeStreamCommand for ToCSV {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
to_csv(args, registry)
to_csv(args, registry).await
}
}
fn to_csv(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn to_csv(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let name = args.call_info.name_tag.clone();
let (ToCSVArgs { separator, headerless }, mut input) = args.process(&registry).await?;
let (
ToCSVArgs {
separator,
headerless,
},
input,
) = args.process(&registry).await?;
let sep = match separator {
Some(Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
@@ -62,12 +67,11 @@ fn to_csv(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream,
} else {
let vec_s: Vec<char> = s.chars().collect();
if vec_s.len() != 1 {
yield Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected a single separator char from --separator",
"requires a single character string input",
tag,
));
return;
};
vec_s[0]
}
@@ -75,14 +79,7 @@ fn to_csv(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream,
_ => ',',
};
let mut result = to_delimited_data(headerless, sep, "CSV", input, name)?;
while let Some(item) = result.next().await {
yield item;
}
};
Ok(stream.to_output_stream())
to_delimited_data(headerless, sep, "CSV", input, name).await
}
#[cfg(test)]

View File

@@ -165,7 +165,7 @@ fn merge_descriptors(values: &[Value]) -> Vec<Spanned<String>> {
ret
}
pub fn to_delimited_data(
pub async fn to_delimited_data(
headerless: bool,
sep: char,
format_name: &'static str,
@@ -175,33 +175,41 @@ pub fn to_delimited_data(
let name_tag = name;
let name_span = name_tag.span;
let stream = async_stream! {
let input: Vec<Value> = input.collect().await;
let to_process_input = if input.len() > 1 {
let to_process_input = match input.len() {
x if x > 1 => {
let tag = input[0].tag.clone();
vec![Value { value: UntaggedValue::Table(input), tag } ]
} else if input.len() == 1 {
input
} else {
vec![]
vec![Value {
value: UntaggedValue::Table(input),
tag,
}]
}
1 => input,
_ => vec![],
};
for value in to_process_input {
Ok(
futures::stream::iter(to_process_input.into_iter().map(move |value| {
match from_value_to_delimited_string(&clone_tagged_value(&value), sep) {
Ok(mut x) => {
if headerless {
x.find('\n').map(|second_line|{
if let Some(second_line) = x.find('\n') {
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);
ReturnSuccess::value(
UntaggedValue::Primitive(Primitive::String(x)).into_value(&name_tag),
)
}
Err(_) => {
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(
Err(ShellError::labeled_error_with_secondary(
expected,
requires,
name_span,
@@ -210,8 +218,7 @@ pub fn to_delimited_data(
))
}
}
}
};
Ok(stream.to_output_stream())
}))
.to_output_stream(),
)
}

View File

@@ -38,7 +38,7 @@ impl WholeStreamCommand for ToJSON {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
to_json(args, registry)
to_json(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@@ -163,25 +163,30 @@ fn json_list(input: &[Value]) -> Result<Vec<serde_json::Value>, ShellError> {
Ok(out)
}
fn to_json(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn to_json(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let name_tag = args.call_info.name_tag.clone();
let (ToJSONArgs { pretty }, mut input) = args.process(&registry).await?;
let (ToJSONArgs { pretty }, input) = args.process(&registry).await?;
let name_span = name_tag.span;
let input: Vec<Value> = input.collect().await;
let to_process_input = if input.len() > 1 {
let to_process_input = match input.len() {
x if x > 1 => {
let tag = input[0].tag.clone();
vec![Value { value: UntaggedValue::Table(input), tag } ]
} else if input.len() == 1 {
input
} else {
vec![]
vec![Value {
value: UntaggedValue::Table(input),
tag,
}]
}
1 => input,
_ => vec![],
};
for value in to_process_input {
match value_to_json_value(&value) {
Ok(futures::stream::iter(to_process_input.into_iter().map(
move |value| match value_to_json_value(&value) {
Ok(json_value) => {
let value_span = value.tag.span;
@@ -191,15 +196,32 @@ fn to_json(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
let mut pretty_format_failed = true;
if let Ok(pretty_u64) = pretty_value.as_u64() {
if let Ok(serde_json_value) = serde_json::from_str::<serde_json::Value>(serde_json_string.as_str()) {
let indentation_string = std::iter::repeat(" ").take(pretty_u64 as usize).collect::<String>();
let serde_formatter = serde_json::ser::PrettyFormatter::with_indent(indentation_string.as_bytes());
if let Ok(serde_json_value) =
serde_json::from_str::<serde_json::Value>(
serde_json_string.as_str(),
)
{
let indentation_string = std::iter::repeat(" ")
.take(pretty_u64 as usize)
.collect::<String>();
let serde_formatter =
serde_json::ser::PrettyFormatter::with_indent(
indentation_string.as_bytes(),
);
let serde_buffer = Vec::new();
let mut serde_serializer = serde_json::Serializer::with_formatter(serde_buffer, serde_formatter);
let mut serde_serializer =
serde_json::Serializer::with_formatter(
serde_buffer,
serde_formatter,
);
let serde_json_object = json!(serde_json_value);
if let Ok(()) = serde_json_object.serialize(&mut serde_serializer) {
if let Ok(ser_json_string) = String::from_utf8(serde_serializer.into_inner()) {
if let Ok(()) =
serde_json_object.serialize(&mut serde_serializer)
{
if let Ok(ser_json_string) =
String::from_utf8(serde_serializer.into_inner())
{
pretty_format_failed = false;
serde_json_string = ser_json_string
}
@@ -208,16 +230,20 @@ fn to_json(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
}
if pretty_format_failed {
yield Err(ShellError::labeled_error("Pretty formatting failed", "failed", pretty_value.tag()));
return;
return Err(ShellError::labeled_error(
"Pretty formatting failed",
"failed",
pretty_value.tag(),
));
}
}
yield ReturnSuccess::value(
UntaggedValue::Primitive(Primitive::String(serde_json_string)).into_value(&name_tag),
ReturnSuccess::value(
UntaggedValue::Primitive(Primitive::String(serde_json_string))
.into_value(&name_tag),
)
},
_ => yield Err(ShellError::labeled_error_with_secondary(
}
_ => Err(ShellError::labeled_error_with_secondary(
"Expected a table with JSON-compatible structure.tag() from pipeline",
"requires JSON-compatible input",
name_span,
@@ -226,15 +252,14 @@ fn to_json(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
)),
}
}
_ => yield Err(ShellError::labeled_error(
_ => Err(ShellError::labeled_error(
"Expected a table with JSON-compatible structure from pipeline",
"requires JSON-compatible input",
&name_tag))
}
}
};
Ok(stream.to_output_stream())
&name_tag,
)),
},
))
.to_output_stream())
}
#[cfg(test)]

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