mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02:00
Compare commits
86 Commits
Author | SHA1 | Date | |
---|---|---|---|
0522023d4c | |||
9876169f5d | |||
ed10aafa6f | |||
bcddeb3c1f | |||
3f170c7fb8 | |||
8d91d151bf | |||
821d44af54 | |||
a30901ff7d | |||
94a1968a88 | |||
dffc9c9b1c | |||
8b3964f518 | |||
7fed9992c9 | |||
4e2a4236f8 | |||
05781607f4 | |||
6daec399e6 | |||
306dc89ede | |||
80ce8acf57 | |||
8dfc90a322 | |||
ad5e485594 | |||
60ed40f8bd | |||
a6228cab9e | |||
1857ac69d1 | |||
e33e80ab24 | |||
d18bc78e7c | |||
3b2a87b6d4 | |||
62c76be7ca | |||
733f93e673 | |||
2c88b2fae7 | |||
501da433d4 | |||
0e8a239ae1 | |||
bb08a221e2 | |||
0dbe347f84 | |||
72a21ad619 | |||
6372d2a18c | |||
4468947ad4 | |||
93144a0132 | |||
72f7406057 | |||
c56cbd0f6b | |||
1420cbafe4 | |||
053bd926ec | |||
d095cb91e4 | |||
e8476d8fbb | |||
7532618bdc | |||
e3e1e6f81b | |||
bce6f5a3e6 | |||
480600c465 | |||
89c737f456 | |||
4e83363dd3 | |||
de6d8738c4 | |||
853d7e7120 | |||
b0c30098e4 | |||
fcbaefed52 | |||
77e02ac1c1 | |||
088901b24f | |||
ed7a62bca3 | |||
6bfd8532e4 | |||
bc9cc75c8a | |||
53a6e9f0bd | |||
5f9de80d9b | |||
353b33be1b | |||
96d58094cf | |||
94aac0e8dd | |||
9f54d238ba | |||
778e497903 | |||
6914099e28 | |||
1b6f94b46c | |||
3d63901b3b | |||
eb1ada6115 | |||
831111edec | |||
29ea29261d | |||
ee835f75db | |||
bd7ac0d48e | |||
d7b1480ad0 | |||
86b316e930 | |||
a042f407c1 | |||
40673e4599 | |||
bcfb084d4c | |||
a1fd5ad128 | |||
fe6d96e996 | |||
e24e0242d1 | |||
c959dc1ee3 | |||
d82ce26b44 | |||
935a5f6b9e | |||
731aa6bbdd | |||
a268e825aa | |||
982f067d0e |
240
.github/workflows/release.yml
vendored
Normal file
240
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,240 @@
|
||||
name: Create Release Draft
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['[0-9]+.[0-9]+.[0-9]+*']
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Build Linux
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install libxcb
|
||||
run: sudo apt-get install libxcb-composite0-dev
|
||||
- name: Set up cargo
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=stable
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
- name: Copy files to output
|
||||
run: |
|
||||
cp target/release/nu target/release/nu_plugin_* output/
|
||||
cp README.build.txt output/README.txt
|
||||
rm output/*.d
|
||||
rm output/nu_plugin_core_*
|
||||
rm output/nu_plugin_stable_*
|
||||
# Note: If OpenSSL changes, this path will need to be updated
|
||||
- name: Copy OpenSSL to output
|
||||
run: cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 output/
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: linux
|
||||
path: output/*
|
||||
|
||||
macos:
|
||||
name: Build macOS
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up cargo
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=stable
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
- name: Copy files to output
|
||||
run: |
|
||||
cp target/release/nu target/release/nu_plugin_* output/
|
||||
cp README.build.txt output/README.txt
|
||||
rm output/*.d
|
||||
rm output/nu_plugin_core_*
|
||||
rm output/nu_plugin_stable_*
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: macos
|
||||
path: output/*
|
||||
|
||||
windows:
|
||||
name: Build Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up cargo
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Add cargo-wix subcommand
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: cargo-wix
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=stable
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
- name: Download Less Binary
|
||||
run: Invoke-WebRequest -Uri "https://github.com/jftuga/less-Windows/releases/download/less-v562.1/less.exe" -OutFile "target\release\less.exe"
|
||||
- name: Copy files to output
|
||||
run: |
|
||||
cp target\release\nu.exe output\
|
||||
rm target\release\nu_plugin_core_*.exe
|
||||
rm target\release\nu_plugin_stable_*.exe
|
||||
cp target\release\nu_plugin_*.exe output\
|
||||
cp README.build.txt output\README.txt
|
||||
cp target\release\less.exe output\
|
||||
# Note: If the version of `less.exe` needs to be changed, update this URL
|
||||
# Similarly, if `less.exe` is checked into the repo, copy from the local path here
|
||||
# moved this stuff down to create wix after we download less
|
||||
- name: Create msi with wix
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: wix
|
||||
args: --no-build --nocapture --output target\wix\nushell-windows.msi
|
||||
- name: Upload installer
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: windows-installer
|
||||
path: target\wix\nushell-windows.msi
|
||||
- name: Upload zip
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: windows-zip
|
||||
path: output\*
|
||||
|
||||
release:
|
||||
name: Publish Release
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- linux
|
||||
- macos
|
||||
- windows
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
- name: Determine Release Info
|
||||
id: info
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: |
|
||||
VERSION=${GITHUB_REF##*/}
|
||||
MAJOR=${VERSION%%.*}
|
||||
MINOR=${VERSION%.*}
|
||||
MINOR=${MINOR#*.}
|
||||
PATCH=${VERSION##*.}
|
||||
echo "::set-output name=version::${VERSION}"
|
||||
echo "::set-output name=linuxdir::nu_${MAJOR}_${MINOR}_${PATCH}_linux"
|
||||
echo "::set-output name=macosdir::nu_${MAJOR}_${MINOR}_${PATCH}_macOS"
|
||||
echo "::set-output name=windowsdir::nu_${MAJOR}_${MINOR}_${PATCH}_windows"
|
||||
echo "::set-output name=innerdir::nushell-${VERSION}"
|
||||
- name: Create Release Draft
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ steps.info.outputs.version }} Release
|
||||
draft: true
|
||||
- name: Create Linux Directory
|
||||
run: mkdir -p ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
|
||||
- name: Download Linux Artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux
|
||||
path: ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
|
||||
- name: Restore Linux File Modes
|
||||
run: |
|
||||
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/nu*
|
||||
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/libssl*
|
||||
- name: Create Linux tarball
|
||||
run: tar -zcvf ${{ steps.info.outputs.linuxdir }}.tar.gz ${{ steps.info.outputs.linuxdir }}
|
||||
- name: Upload Linux Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ steps.info.outputs.linuxdir }}.tar.gz
|
||||
asset_name: ${{ steps.info.outputs.linuxdir }}.tar.gz
|
||||
asset_content_type: application/gzip
|
||||
- name: Create macOS Directory
|
||||
run: mkdir -p ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
|
||||
- name: Download macOS Artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: macos
|
||||
path: ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
|
||||
- name: Restore macOS File Modes
|
||||
run: chmod 755 ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}/nu*
|
||||
- name: Create macOS Archive
|
||||
run: zip -r ${{ steps.info.outputs.macosdir }}.zip ${{ steps.info.outputs.macosdir }}
|
||||
- name: Upload macOS Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ steps.info.outputs.macosdir }}.zip
|
||||
asset_name: ${{ steps.info.outputs.macosdir }}.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Create Windows Directory
|
||||
run: mkdir -p ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
||||
- name: Download Windows zip
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: windows-zip
|
||||
path: ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
||||
# TODO: Remove Show
|
||||
- name: Show Windows Artifacts
|
||||
run: ls -la ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
||||
- name: Create macOS Archive
|
||||
run: zip -r ${{ steps.info.outputs.windowsdir }}.zip ${{ steps.info.outputs.windowsdir }}
|
||||
- name: Upload Windows zip
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ steps.info.outputs.windowsdir }}.zip
|
||||
asset_name: ${{ steps.info.outputs.windowsdir }}.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Download Windows installer
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: windows-installer
|
||||
path: ./
|
||||
- name: Upload Windows installer
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./nushell-windows.msi
|
||||
asset_name: ${{ steps.info.outputs.windowsdir }}.msi
|
||||
asset_content_type: applictaion/x-msi
|
@ -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>
|
||||
|
@ -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:
|
||||
|
||||
|
1356
Cargo.lock
generated
1356
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
47
Cargo.toml
47
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nu"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
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.16.0", path = "./crates/nu-cli" }
|
||||
nu-source = { version = "0.16.0", path = "./crates/nu-source" }
|
||||
nu-plugin = { version = "0.16.0", path = "./crates/nu-plugin" }
|
||||
nu-protocol = { version = "0.16.0", path = "./crates/nu-protocol" }
|
||||
nu-errors = { version = "0.16.0", path = "./crates/nu-errors" }
|
||||
nu-parser = { version = "0.16.0", path = "./crates/nu-parser" }
|
||||
nu-value-ext = { version = "0.16.0", path = "./crates/nu-value-ext" }
|
||||
nu_plugin_binaryview = { version = "0.16.0", path = "./crates/nu_plugin_binaryview", optional=true }
|
||||
nu_plugin_fetch = { version = "0.16.0", path = "./crates/nu_plugin_fetch", optional=true }
|
||||
nu_plugin_inc = { version = "0.16.0", path = "./crates/nu_plugin_inc", optional=true }
|
||||
nu_plugin_match = { version = "0.16.0", path = "./crates/nu_plugin_match", optional=true }
|
||||
nu_plugin_post = { version = "0.16.0", path = "./crates/nu_plugin_post", optional=true }
|
||||
nu_plugin_ps = { version = "0.16.0", path = "./crates/nu_plugin_ps", optional=true }
|
||||
nu_plugin_start = { version = "0.16.0", path = "./crates/nu_plugin_start", optional=true }
|
||||
nu_plugin_sys = { version = "0.16.0", path = "./crates/nu_plugin_sys", optional=true }
|
||||
nu_plugin_textview = { version = "0.16.0", path = "./crates/nu_plugin_textview", optional=true }
|
||||
nu_plugin_tree = { version = "0.16.0", path = "./crates/nu_plugin_tree", optional=true }
|
||||
|
||||
crossterm = { version = "0.17.5", optional = true }
|
||||
semver = { version = "0.10.0", optional = true }
|
||||
@ -43,22 +43,23 @@ url = { version = "2.1.1", optional = true }
|
||||
|
||||
clap = "2.33.1"
|
||||
ctrlc = "3.1.4"
|
||||
dunce = "1.0.0"
|
||||
dunce = "1.0.1"
|
||||
futures = { version = "0.3", features = ["compat", "io-compat"] }
|
||||
log = "0.4.8"
|
||||
pretty_env_logger = "0.4.0"
|
||||
starship = "0.43.0"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { version = "0.15.0", path = "./crates/nu-test-support" }
|
||||
nu-test-support = { version = "0.16.0", path = "./crates/nu-test-support" }
|
||||
|
||||
[build-dependencies]
|
||||
toml = "0.5.6"
|
||||
serde = { version = "1.0.110", features = ["derive"] }
|
||||
nu-build = { version = "0.15.0", path = "./crates/nu-build" }
|
||||
nu-build = { version = "0.16.0", 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
1
README.build.txt
Normal 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.
|
132
README.md
132
README.md
@ -1,17 +1,18 @@
|
||||
# README
|
||||
|
||||
[](https://gitpod.io/#https://github.com/nushell/nushell)
|
||||
[](https://crates.io/crates/nu)
|
||||
[](https://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=master)
|
||||
[](https://discord.gg/NtAbbGn)
|
||||
[](https://changelog.com/podcast/363)
|
||||
|
||||
|
||||
# Nu Shell
|
||||
## Nu Shell
|
||||
|
||||
A new type of shell.
|
||||
|
||||

|
||||
|
||||
# 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.
|
||||
|
||||
[](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
|
||||
|
||||
[](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
|
||||
───┬────────┬──────┬────────┬──────────────
|
||||
# │ 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
|
||||
───┴────────┴──────┴────────┴──────────────
|
||||
```shell
|
||||
> ls | where type == "Dir" | autoview
|
||||
───┬────────┬──────┬───────┬──────────────
|
||||
# │ name │ type │ size │ modified
|
||||
───┼────────┼──────┼───────┼──────────────
|
||||
0 │ assets │ Dir │ 128 B │ 5 months ago
|
||||
1 │ crates │ Dir │ 704 B │ 50 mins ago
|
||||
2 │ debian │ 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:
|
||||
|
||||
@ -290,24 +291,27 @@ Nu is in heavy development, and will naturally change as it matures and people u
|
||||
| -------- |:-----------:|:---------:|:---:|:-------:|:------:| -----
|
||||
| Aliases | | X | | | | Initial implementation but lacks necessary features
|
||||
| Notebook | | X | | | | Initial jupyter support, but it loses state and lacks features
|
||||
| File ops | | | X | | | cp, mv, rm, mkdir have some support, but lacking others
|
||||
| Environment | | X | | | | Temporary environment, but no session-wide env variables
|
||||
| File ops | | | X | | | cp, mv, rm, mkdir have some support, but lacking others
|
||||
| Environment | | X | | | | Temporary environment, but no session-wide env variables
|
||||
| Shells | | X | | | | Basic value and file shells, but no opt-in/opt-out for commands
|
||||
| Protocol | | | X | | | Streaming protocol is serviceable
|
||||
| Plugins | | X | | | | Plugins work on one row at a time, lack batching and expression eval
|
||||
| Plugins | | X | | | | Plugins work on one row at a time, lack batching and expression eval
|
||||
| Errors | | | X | | | Error reporting works, but could use usability polish
|
||||
| Documentation | | X | | | | Book and related are barebones and lack task-based lessons
|
||||
| Paging | | X | | | | Textview has paging, but we'd like paging for tables
|
||||
| Functions| X | | | | | No functions, yet, only aliases
|
||||
| Variables| X | | | | | Nu doesn't yet support variables
|
||||
| Variables| X | | | | | Nu doesn't yet support variables
|
||||
| Completions | | X | | | | Completions are currently barebones, at best
|
||||
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked
|
||||
|
||||
# Contributing
|
||||
## 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
60
TODO.md
@ -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
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nu-build"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
authors = ["The Nu Project Contributors"]
|
||||
edition = "2018"
|
||||
description = "Core build system for nushell"
|
||||
@ -10,7 +10,7 @@ license = "MIT"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.110", features = ["derive"] }
|
||||
serde = { version = "1.0.114", features = ["derive"] }
|
||||
lazy_static = "1.4.0"
|
||||
serde_json = "1.0.53"
|
||||
serde_json = "1.0.55"
|
||||
toml = "0.5.6"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nu-cli"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
authors = ["The Nu Project Contributors"]
|
||||
description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
@ -10,25 +10,25 @@ 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.16.0", path = "../nu-source" }
|
||||
nu-plugin = { version = "0.16.0", path = "../nu-plugin" }
|
||||
nu-protocol = { version = "0.16.0", path = "../nu-protocol" }
|
||||
nu-errors = { version = "0.16.0", path = "../nu-errors" }
|
||||
nu-parser = { version = "0.16.0", path = "../nu-parser" }
|
||||
nu-value-ext = { version = "0.16.0", path = "../nu-value-ext" }
|
||||
nu-test-support = { version = "0.16.0", path = "../nu-test-support" }
|
||||
nu-table = {version = "0.16.0", path = "../nu-table"}
|
||||
|
||||
ansi_term = "0.12.1"
|
||||
app_dirs = "1.2.1"
|
||||
async-recursion = "0.3.1"
|
||||
async-trait = "0.1.31"
|
||||
async-trait = "0.1.36"
|
||||
directories = "2.0.2"
|
||||
async-stream = "0.2"
|
||||
base64 = "0.12.1"
|
||||
base64 = "0.12.3"
|
||||
bigdecimal = { version = "0.1.2", features = ["serde"] }
|
||||
bson = { version = "0.14.1", features = ["decimal128"] }
|
||||
byte-unit = "3.1.3"
|
||||
bytes = "0.5.4"
|
||||
bytes = "0.5.5"
|
||||
calamine = "0.16"
|
||||
cfg-if = "0.1"
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
@ -37,7 +37,7 @@ csv = "1.1"
|
||||
ctrlc = "3.1.4"
|
||||
derive-new = "0.5.8"
|
||||
dirs = "2.0.2"
|
||||
dunce = "1.0.0"
|
||||
dunce = "1.0.1"
|
||||
eml-parser = "0.1.0"
|
||||
filesize = "0.2.0"
|
||||
futures = { version = "0.3", features = ["compat", "io-compat"] }
|
||||
@ -50,30 +50,29 @@ hex = "0.4"
|
||||
htmlescape = "0.3.1"
|
||||
ical = "0.6.*"
|
||||
ichwh = "0.3.4"
|
||||
indexmap = { version = "1.3.2", features = ["serde-1"] }
|
||||
indexmap = { version = "1.4.0", features = ["serde-1"] }
|
||||
itertools = "0.9.0"
|
||||
codespan-reporting = "0.9.4"
|
||||
codespan-reporting = "0.9.5"
|
||||
log = "0.4.8"
|
||||
meval = "0.2"
|
||||
natural = "0.5.0"
|
||||
num-bigint = { version = "0.2.6", features = ["serde"] }
|
||||
num-traits = "0.2.11"
|
||||
parking_lot = "0.10.2"
|
||||
parking_lot = "0.11.0"
|
||||
pin-utils = "0.1.0"
|
||||
pretty-hex = "0.1.1"
|
||||
pretty_env_logger = "0.4.0"
|
||||
prettytable-rs = "0.8.0"
|
||||
ptree = {version = "0.2" }
|
||||
query_interface = "0.3.5"
|
||||
rand = "0.7"
|
||||
regex = "1"
|
||||
roxmltree = "0.11.0"
|
||||
roxmltree = "0.13.0"
|
||||
rustyline = "6.2.0"
|
||||
serde = { version = "1.0.110", features = ["derive"] }
|
||||
serde = { version = "1.0.114", features = ["derive"] }
|
||||
serde-hjson = "0.9.1"
|
||||
serde_bytes = "0.11.4"
|
||||
serde_bytes = "0.11.5"
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.53"
|
||||
serde_json = "1.0.55"
|
||||
serde_urlencoded = "0.6.1"
|
||||
serde_yaml = "0.8"
|
||||
shellexpand = "2.0.0"
|
||||
@ -81,17 +80,19 @@ strip-ansi-escapes = "0.1.0"
|
||||
tempfile = "3.1.0"
|
||||
term = "0.5.2"
|
||||
termcolor = "1.1.0"
|
||||
textwrap = {version = "0.11.0", features = ["term_size"]}
|
||||
term_size = "0.3.2"
|
||||
toml = "0.5.6"
|
||||
typetag = "0.1.4"
|
||||
typetag = "0.1.5"
|
||||
umask = "1.0.0"
|
||||
unicode-xid = "0.2.0"
|
||||
which = "3"
|
||||
unicode-xid = "0.2.1"
|
||||
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 }
|
||||
rayon = "1.3.0"
|
||||
starship = "0.43.0"
|
||||
rayon = "1.3.1"
|
||||
encoding_rs = "0.8.23"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
users = "0.10.0"
|
||||
@ -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.16.0", 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"]
|
||||
|
@ -4,14 +4,12 @@ use crate::commands::plugin::JsonRpc;
|
||||
use crate::commands::plugin::{PluginCommand, PluginSink};
|
||||
use crate::commands::whole_stream_command;
|
||||
use crate::context::Context;
|
||||
#[cfg(not(feature = "starship-prompt"))]
|
||||
use crate::git::current_branch;
|
||||
use crate::path::canonicalize;
|
||||
use crate::prelude::*;
|
||||
use crate::EnvironmentSyncer;
|
||||
use futures_codec::FramedRead;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_errors::{ProximateShellError, ShellDiagnostic, ShellError};
|
||||
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, 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());
|
||||
@ -270,6 +269,7 @@ pub fn create_default_context(
|
||||
whole_stream_command(Debug),
|
||||
whole_stream_command(Alias),
|
||||
whole_stream_command(WithEnv),
|
||||
whole_stream_command(Do),
|
||||
// Statistics
|
||||
whole_stream_command(Size),
|
||||
whole_stream_command(Count),
|
||||
@ -303,7 +303,10 @@ pub fn create_default_context(
|
||||
whole_stream_command(StrSet),
|
||||
whole_stream_command(StrToDatetime),
|
||||
whole_stream_command(StrTrim),
|
||||
whole_stream_command(StrCollect),
|
||||
whole_stream_command(BuildString),
|
||||
whole_stream_command(Ansi),
|
||||
whole_stream_command(Char),
|
||||
// Column manipulation
|
||||
whole_stream_command(Reject),
|
||||
whole_stream_command(Select),
|
||||
@ -320,6 +323,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 +349,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 +391,11 @@ pub fn create_default_context(
|
||||
whole_stream_command(FromVcf),
|
||||
// "Private" commands (not intended to be accessed directly)
|
||||
whole_stream_command(RunExternalCommand { interactive }),
|
||||
// Random value generation
|
||||
whole_stream_command(Random),
|
||||
whole_stream_command(RandomBool),
|
||||
whole_stream_command(RandomDice),
|
||||
whole_stream_command(RandomUUID),
|
||||
]);
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
@ -578,7 +592,30 @@ pub async fn cli(
|
||||
|
||||
rl.set_helper(Some(crate::shell::Helper::new(context.clone())));
|
||||
|
||||
let edit_mode = config::config(Tag::unknown())?
|
||||
let config = match config::config(Tag::unknown()) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
eprintln!("Config could not be loaded.");
|
||||
if let ShellError {
|
||||
error: ProximateShellError::Diagnostic(ShellDiagnostic { diagnostic }),
|
||||
..
|
||||
} = e
|
||||
{
|
||||
eprintln!("{}", diagnostic.message);
|
||||
}
|
||||
IndexMap::new()
|
||||
}
|
||||
};
|
||||
|
||||
let use_starship = match config.get("use_starship") {
|
||||
Some(b) => match b.as_bool() {
|
||||
Ok(b) => b,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let edit_mode = config
|
||||
.get("edit_mode")
|
||||
.map(|s| match s.value.expect_string() {
|
||||
"vi" => EditMode::Vi,
|
||||
@ -589,21 +626,21 @@ pub async fn cli(
|
||||
|
||||
rl.set_edit_mode(edit_mode);
|
||||
|
||||
let max_history_size = config::config(Tag::unknown())?
|
||||
let max_history_size = config
|
||||
.get("history_size")
|
||||
.map(|i| i.value.expect_int())
|
||||
.unwrap_or(100_000);
|
||||
|
||||
rl.set_max_history_size(max_history_size as usize);
|
||||
|
||||
let key_timeout = config::config(Tag::unknown())?
|
||||
let key_timeout = config
|
||||
.get("key_timeout")
|
||||
.map(|s| s.value.expect_int())
|
||||
.unwrap_or(1);
|
||||
|
||||
rl.set_keyseq_timeout(key_timeout as i32);
|
||||
|
||||
let completion_mode = config::config(Tag::unknown())?
|
||||
let completion_mode = config
|
||||
.get("completion_mode")
|
||||
.map(|s| match s.value.expect_string() {
|
||||
"list" => CompletionType::List,
|
||||
@ -615,8 +652,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 +668,63 @@ pub async fn cli(
|
||||
_ => {}
|
||||
};
|
||||
starship::print::get_prompt(starship_context)
|
||||
}
|
||||
#[cfg(not(feature = "starship-prompt"))]
|
||||
{
|
||||
} else if let Some(prompt) = config.get("prompt") {
|
||||
let prompt_line = prompt.as_string()?;
|
||||
|
||||
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
|
||||
Ok(result) => {
|
||||
let mut prompt_block =
|
||||
nu_parser::classify_block(&result, context.registry());
|
||||
|
||||
let env = context.get_env();
|
||||
|
||||
prompt_block.block.expand_it_usage();
|
||||
|
||||
match run_block(
|
||||
&prompt_block.block,
|
||||
&mut context,
|
||||
InputStream::empty(),
|
||||
&Value::nothing(),
|
||||
&IndexMap::new(),
|
||||
&env,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(result) => match result.collect_string(Tag::unknown()).await {
|
||||
Ok(string_result) => {
|
||||
let errors = context.get_errors();
|
||||
context.maybe_print_errors(Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
if !errors.is_empty() {
|
||||
"> ".to_string()
|
||||
} else {
|
||||
string_result.item
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
format!(
|
||||
"\x1b[32m{}{}\x1b[m> ",
|
||||
cwd,
|
||||
@ -673,13 +763,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 +823,7 @@ fn chomp_newline(s: &str) -> &str {
|
||||
}
|
||||
}
|
||||
|
||||
enum LineResult {
|
||||
pub enum LineResult {
|
||||
Success(String),
|
||||
Error(String, ShellError),
|
||||
CtrlC,
|
||||
@ -741,7 +831,7 @@ enum LineResult {
|
||||
}
|
||||
|
||||
/// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline
|
||||
async fn process_line(
|
||||
pub async fn process_line(
|
||||
readline: Result<String, ReadlineError>,
|
||||
ctx: &mut Context,
|
||||
redirect_stdin: bool,
|
||||
|
@ -5,14 +5,15 @@ mod from_delimited_data;
|
||||
mod to_delimited_data;
|
||||
|
||||
pub(crate) mod alias;
|
||||
pub(crate) mod ansi;
|
||||
pub(crate) mod append;
|
||||
pub(crate) mod args;
|
||||
pub(crate) mod autoview;
|
||||
pub(crate) mod average;
|
||||
pub(crate) mod build_string;
|
||||
pub(crate) mod cal;
|
||||
pub(crate) mod calc;
|
||||
pub(crate) mod cd;
|
||||
pub(crate) mod char_;
|
||||
pub(crate) mod classified;
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub(crate) mod clip;
|
||||
@ -24,6 +25,7 @@ pub(crate) mod cp;
|
||||
pub(crate) mod date;
|
||||
pub(crate) mod debug;
|
||||
pub(crate) mod default;
|
||||
pub(crate) mod do_;
|
||||
pub(crate) mod drop;
|
||||
pub(crate) mod du;
|
||||
pub(crate) mod each;
|
||||
@ -31,6 +33,7 @@ pub(crate) mod echo;
|
||||
pub(crate) mod enter;
|
||||
#[allow(unused)]
|
||||
pub(crate) mod evaluate_by;
|
||||
pub(crate) mod every;
|
||||
pub(crate) mod exit;
|
||||
pub(crate) mod first;
|
||||
pub(crate) mod format;
|
||||
@ -68,6 +71,7 @@ pub(crate) mod lines;
|
||||
pub(crate) mod ls;
|
||||
#[allow(unused)]
|
||||
pub(crate) mod map_max_by;
|
||||
pub(crate) mod math;
|
||||
pub(crate) mod merge;
|
||||
pub(crate) mod mkdir;
|
||||
pub(crate) mod mv;
|
||||
@ -80,6 +84,7 @@ pub(crate) mod plugin;
|
||||
pub(crate) mod prepend;
|
||||
pub(crate) mod prev;
|
||||
pub(crate) mod pwd;
|
||||
pub(crate) mod random;
|
||||
pub(crate) mod range;
|
||||
#[allow(unused)]
|
||||
pub(crate) mod reduce_by;
|
||||
@ -101,7 +106,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;
|
||||
@ -134,11 +138,12 @@ pub(crate) use command::{
|
||||
};
|
||||
|
||||
pub(crate) use alias::Alias;
|
||||
pub(crate) use ansi::Ansi;
|
||||
pub(crate) use append::Append;
|
||||
pub(crate) use average::Average;
|
||||
pub(crate) use build_string::BuildString;
|
||||
pub(crate) use cal::Cal;
|
||||
pub(crate) use calc::Calc;
|
||||
pub(crate) use char_::Char;
|
||||
pub(crate) use compact::Compact;
|
||||
pub(crate) use config::Config;
|
||||
pub(crate) use count::Count;
|
||||
@ -146,6 +151,7 @@ pub(crate) use cp::Cpy;
|
||||
pub(crate) use date::Date;
|
||||
pub(crate) use debug::Debug;
|
||||
pub(crate) use default::Default;
|
||||
pub(crate) use do_::Do;
|
||||
pub(crate) use drop::Drop;
|
||||
pub(crate) use du::Du;
|
||||
pub(crate) use each::Each;
|
||||
@ -160,6 +166,7 @@ pub(crate) mod touch;
|
||||
pub(crate) use enter::Enter;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use evaluate_by::EvaluateBy;
|
||||
pub(crate) use every::Every;
|
||||
pub(crate) use exit::Exit;
|
||||
pub(crate) use first::First;
|
||||
pub(crate) use format::Format;
|
||||
@ -198,6 +205,9 @@ pub(crate) use lines::Lines;
|
||||
pub(crate) use ls::Ls;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use map_max_by::MapMaxBy;
|
||||
pub(crate) use math::{
|
||||
Math, MathAverage, MathMaximum, MathMedian, MathMinimum, MathMode, MathSummation,
|
||||
};
|
||||
pub(crate) use merge::Merge;
|
||||
pub(crate) use mkdir::Mkdir;
|
||||
pub(crate) use mv::Move;
|
||||
@ -209,6 +219,7 @@ pub(crate) use pivot::Pivot;
|
||||
pub(crate) use prepend::Prepend;
|
||||
pub(crate) use prev::Previous;
|
||||
pub(crate) use pwd::Pwd;
|
||||
pub(crate) use random::{Random, RandomBool, RandomDice, RandomUUID};
|
||||
pub(crate) use range::Range;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use reduce_by::ReduceBy;
|
||||
@ -231,10 +242,9 @@ pub(crate) use split::SplitColumn;
|
||||
pub(crate) use split::SplitRow;
|
||||
pub(crate) use split_by::SplitBy;
|
||||
pub(crate) use str_::{
|
||||
Str, StrCapitalize, StrDowncase, StrFindReplace, StrSet, StrSubstring, StrToDatetime,
|
||||
StrToDecimal, StrToInteger, StrTrim, StrUpcase,
|
||||
Str, StrCapitalize, StrCollect, 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;
|
||||
|
@ -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,63 +64,78 @@ 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(®istry).await?;
|
||||
let mut processed_args: Vec<String> = vec![];
|
||||
let mut raw_input = args.raw_input.clone();
|
||||
let (
|
||||
AliasArgs {
|
||||
name,
|
||||
args: list,
|
||||
block,
|
||||
save,
|
||||
},
|
||||
_ctx,
|
||||
) = args.process(®istry).await?;
|
||||
let mut processed_args: Vec<String> = vec![];
|
||||
|
||||
if let Some(true) = save {
|
||||
let mut result = crate::data::config::read(name.clone().tag, &None)?;
|
||||
if let Some(true) = save {
|
||||
let mut result = crate::data::config::read(name.clone().tag, &None)?;
|
||||
|
||||
// process the alias to remove the --save flag
|
||||
let left_brace = raw_input.find('{').unwrap_or(0);
|
||||
let right_brace = raw_input.rfind('}').unwrap_or(raw_input.len());
|
||||
let mut left = raw_input[..left_brace].replace("--save", "").replace("-s", "");
|
||||
let mut right = raw_input[right_brace..].replace("--save", "").replace("-s", "");
|
||||
raw_input = format!("{}{}{}", left, &raw_input[left_brace..right_brace], right);
|
||||
// process the alias to remove the --save flag
|
||||
let left_brace = raw_input.find('{').unwrap_or(0);
|
||||
let right_brace = raw_input.rfind('}').unwrap_or_else(|| raw_input.len());
|
||||
let left = raw_input[..left_brace]
|
||||
.replace("--save", "")
|
||||
.replace("-s", "");
|
||||
let right = raw_input[right_brace..]
|
||||
.replace("--save", "")
|
||||
.replace("-s", "");
|
||||
raw_input = format!("{}{}{}", left, &raw_input[left_brace..right_brace], right);
|
||||
|
||||
// create a value from raw_input alias
|
||||
let alias: Value = raw_input.trim().to_string().into();
|
||||
let alias_start = raw_input.find("[").unwrap_or(0); // used to check if the same alias already exists
|
||||
// create a value from raw_input alias
|
||||
let alias: Value = raw_input.trim().to_string().into();
|
||||
let alias_start = raw_input.find('[').unwrap_or(0); // used to check if the same alias already exists
|
||||
|
||||
// add to startup if alias doesn't exist and replce if it does
|
||||
match result.get_mut("startup") {
|
||||
Some(startup) => {
|
||||
if let UntaggedValue::Table(ref mut commands) = startup.value {
|
||||
if let Some(command) = commands.iter_mut().find(|command| {
|
||||
let cmd_str = command.as_string().unwrap_or_default();
|
||||
cmd_str.starts_with(&raw_input[..alias_start])
|
||||
}) {
|
||||
*command = alias;
|
||||
} else {
|
||||
commands.push(alias);
|
||||
}
|
||||
// add to startup if alias doesn't exist and replce if it does
|
||||
match result.get_mut("startup") {
|
||||
Some(startup) => {
|
||||
if let UntaggedValue::Table(ref mut commands) = startup.value {
|
||||
if let Some(command) = commands.iter_mut().find(|command| {
|
||||
let cmd_str = command.as_string().unwrap_or_default();
|
||||
cmd_str.starts_with(&raw_input[..alias_start])
|
||||
}) {
|
||||
*command = alias;
|
||||
} else {
|
||||
commands.push(alias);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let mut table = UntaggedValue::table(&[alias]);
|
||||
result.insert("startup".to_string(), table.into_value(Tag::default()));
|
||||
}
|
||||
}
|
||||
config::write(&result, &None)?;
|
||||
}
|
||||
|
||||
for item in list.iter() {
|
||||
if let Ok(string) = item.as_string() {
|
||||
processed_args.push(format!("${}", string));
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error("Expected a string", "expected a string", item.tag()));
|
||||
None => {
|
||||
let table = UntaggedValue::table(&[alias]);
|
||||
result.insert("startup".to_string(), table.into_value(Tag::default()));
|
||||
}
|
||||
}
|
||||
yield ReturnSuccess::action(CommandAction::AddAlias(name.to_string(), processed_args, block.clone()))
|
||||
};
|
||||
config::write(&result, &None)?;
|
||||
}
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
for item in list.iter() {
|
||||
if let Ok(string) = item.as_string() {
|
||||
processed_args.push(format!("${}", string));
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a string",
|
||||
"expected a string",
|
||||
item.tag(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::AddAlias(name.to_string(), processed_args, block),
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
140
crates/nu-cli/src/commands/ansi.rs
Normal file
140
crates/nu-cli/src/commands/ansi.rs
Normal file
@ -0,0 +1,140 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct Ansi;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AnsiArgs {
|
||||
color: Value,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Ansi {
|
||||
fn name(&self) -> &str {
|
||||
"ansi"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ansi").required(
|
||||
"color",
|
||||
SyntaxShape::Any,
|
||||
"the name of the color to use or 'reset' to reset the color",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Output ANSI codes to change color"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Change color to green",
|
||||
example: r#"ansi green"#,
|
||||
result: Some(vec![Value::from("\u{1b}[32m")]),
|
||||
},
|
||||
Example {
|
||||
description: "Reset the color",
|
||||
example: r#"ansi reset"#,
|
||||
result: Some(vec![Value::from("\u{1b}[0m")]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (AnsiArgs { color }, _) = args.process(®istry).await?;
|
||||
|
||||
let color_string = color.as_string()?;
|
||||
|
||||
let ansi_code = str_to_ansi_color(color_string);
|
||||
|
||||
if let Some(output) = ansi_code {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(color.tag()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Unknown color",
|
||||
"unknown color",
|
||||
color.tag(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_ansi_color(s: String) -> Option<String> {
|
||||
match s.as_str() {
|
||||
"g" | "green" => Some(ansi_term::Color::Green.prefix().to_string()),
|
||||
"gb" | "green_bold" => Some(ansi_term::Color::Green.bold().prefix().to_string()),
|
||||
"gu" | "green_underline" => Some(ansi_term::Color::Green.underline().prefix().to_string()),
|
||||
"gi" | "green_italic" => Some(ansi_term::Color::Green.italic().prefix().to_string()),
|
||||
"gd" | "green_dimmed" => Some(ansi_term::Color::Green.dimmed().prefix().to_string()),
|
||||
"gr" | "green_reverse" => Some(ansi_term::Color::Green.reverse().prefix().to_string()),
|
||||
"r" | "red" => Some(ansi_term::Color::Red.prefix().to_string()),
|
||||
"rb" | "red_bold" => Some(ansi_term::Color::Red.bold().prefix().to_string()),
|
||||
"ru" | "red_underline" => Some(ansi_term::Color::Red.underline().prefix().to_string()),
|
||||
"ri" | "red_italic" => Some(ansi_term::Color::Red.italic().prefix().to_string()),
|
||||
"rd" | "red_dimmed" => Some(ansi_term::Color::Red.dimmed().prefix().to_string()),
|
||||
"rr" | "red_reverse" => Some(ansi_term::Color::Red.reverse().prefix().to_string()),
|
||||
"u" | "blue" => Some(ansi_term::Color::Blue.prefix().to_string()),
|
||||
"ub" | "blue_bold" => Some(ansi_term::Color::Blue.bold().prefix().to_string()),
|
||||
"uu" | "blue_underline" => Some(ansi_term::Color::Blue.underline().prefix().to_string()),
|
||||
"ui" | "blue_italic" => Some(ansi_term::Color::Blue.italic().prefix().to_string()),
|
||||
"ud" | "blue_dimmed" => Some(ansi_term::Color::Blue.dimmed().prefix().to_string()),
|
||||
"ur" | "blue_reverse" => Some(ansi_term::Color::Blue.reverse().prefix().to_string()),
|
||||
"b" | "black" => Some(ansi_term::Color::Black.prefix().to_string()),
|
||||
"bb" | "black_bold" => Some(ansi_term::Color::Black.bold().prefix().to_string()),
|
||||
"bu" | "black_underline" => Some(ansi_term::Color::Black.underline().prefix().to_string()),
|
||||
"bi" | "black_italic" => Some(ansi_term::Color::Black.italic().prefix().to_string()),
|
||||
"bd" | "black_dimmed" => Some(ansi_term::Color::Black.dimmed().prefix().to_string()),
|
||||
"br" | "black_reverse" => Some(ansi_term::Color::Black.reverse().prefix().to_string()),
|
||||
"y" | "yellow" => Some(ansi_term::Color::Yellow.prefix().to_string()),
|
||||
"yb" | "yellow_bold" => Some(ansi_term::Color::Yellow.bold().prefix().to_string()),
|
||||
"yu" | "yellow_underline" => {
|
||||
Some(ansi_term::Color::Yellow.underline().prefix().to_string())
|
||||
}
|
||||
"yi" | "yellow_italic" => Some(ansi_term::Color::Yellow.italic().prefix().to_string()),
|
||||
"yd" | "yellow_dimmed" => Some(ansi_term::Color::Yellow.dimmed().prefix().to_string()),
|
||||
"yr" | "yellow_reverse" => Some(ansi_term::Color::Yellow.reverse().prefix().to_string()),
|
||||
"p" | "purple" => Some(ansi_term::Color::Purple.prefix().to_string()),
|
||||
"pb" | "purple_bold" => Some(ansi_term::Color::Purple.bold().prefix().to_string()),
|
||||
"pu" | "purple_underline" => {
|
||||
Some(ansi_term::Color::Purple.underline().prefix().to_string())
|
||||
}
|
||||
"pi" | "purple_italic" => Some(ansi_term::Color::Purple.italic().prefix().to_string()),
|
||||
"pd" | "purple_dimmed" => Some(ansi_term::Color::Purple.dimmed().prefix().to_string()),
|
||||
"pr" | "purple_reverse" => Some(ansi_term::Color::Purple.reverse().prefix().to_string()),
|
||||
"c" | "cyan" => Some(ansi_term::Color::Cyan.prefix().to_string()),
|
||||
"cb" | "cyan_bold" => Some(ansi_term::Color::Cyan.bold().prefix().to_string()),
|
||||
"cu" | "cyan_underline" => Some(ansi_term::Color::Cyan.underline().prefix().to_string()),
|
||||
"ci" | "cyan_italic" => Some(ansi_term::Color::Cyan.italic().prefix().to_string()),
|
||||
"cd" | "cyan_dimmed" => Some(ansi_term::Color::Cyan.dimmed().prefix().to_string()),
|
||||
"cr" | "cyan_reverse" => Some(ansi_term::Color::Cyan.reverse().prefix().to_string()),
|
||||
"w" | "white" => Some(ansi_term::Color::White.prefix().to_string()),
|
||||
"wb" | "white_bold" => Some(ansi_term::Color::White.bold().prefix().to_string()),
|
||||
"wu" | "white_underline" => Some(ansi_term::Color::White.underline().prefix().to_string()),
|
||||
"wi" | "white_italic" => Some(ansi_term::Color::White.italic().prefix().to_string()),
|
||||
"wd" | "white_dimmed" => Some(ansi_term::Color::White.dimmed().prefix().to_string()),
|
||||
"wr" | "white_reverse" => Some(ansi_term::Color::White.reverse().prefix().to_string()),
|
||||
"reset" => Some("\x1b[0m".to_owned()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ansi;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Ansi {})
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
@ -110,32 +106,22 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
};
|
||||
|
||||
let (mut input_stream, context) = RunnableContextWithoutInput::convert(context);
|
||||
let term_width = context.host.lock().width();
|
||||
|
||||
if let Some(x) = input_stream.next().await {
|
||||
match input_stream.next().await {
|
||||
Some(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);
|
||||
let result = table.run(command_args, &context.registry).await;
|
||||
let result = table.run(command_args, &context.registry).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
}
|
||||
}
|
||||
@ -152,7 +138,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = text.run(command_args, &context.registry).await;
|
||||
let result = text.run(command_args, &context.registry).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{}", s);
|
||||
@ -175,7 +161,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = text.run(command_args, &context.registry).await;
|
||||
let result = text.run(command_args, &context.registry).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{}\n", s);
|
||||
@ -250,7 +236,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
stream.push_back(x);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = binary.run(command_args, &context.registry).await;
|
||||
let result = binary.run(command_args, &context.registry).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
use pretty_hex::*;
|
||||
@ -278,92 +264,30 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
.iter()
|
||||
.fold(0usize, |acc, len| acc + len.len())
|
||||
+ row.entries.iter().count() * 2)
|
||||
> textwrap::termwidth()) =>
|
||||
> term_width) =>
|
||||
{
|
||||
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, term_width);
|
||||
}
|
||||
|
||||
Value {
|
||||
@ -374,7 +298,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
stream.push_back(x);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = table.run(command_args, &context.registry).await;
|
||||
let result = table.run(command_args, &context.registry).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{:?}", item);
|
||||
|
@ -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 {})
|
||||
}
|
||||
}
|
@ -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,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -73,17 +84,15 @@ pub async fn cal(
|
||||
let mut selected_year: i32 = current_year;
|
||||
let mut current_day_option: Option<u32> = Some(current_day);
|
||||
|
||||
let month_range = if args.has("full-year") {
|
||||
if let Some(full_year_value) = args.get("full-year") {
|
||||
if let Ok(year_u64) = full_year_value.as_u64() {
|
||||
selected_year = year_u64 as i32;
|
||||
let month_range = if let Some(full_year_value) = args.get("full-year") {
|
||||
if let Ok(year_u64) = full_year_value.as_u64() {
|
||||
selected_year = year_u64 as i32;
|
||||
|
||||
if selected_year != current_year {
|
||||
current_day_option = None
|
||||
}
|
||||
} else {
|
||||
return Err(get_invalid_year_shell_error(&full_year_value.tag()));
|
||||
if selected_year != current_year {
|
||||
current_day_option = None
|
||||
}
|
||||
} else {
|
||||
return Err(get_invalid_year_shell_error(&full_year_value.tag()));
|
||||
}
|
||||
|
||||
(1, 12)
|
||||
@ -112,90 +121,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);
|
||||
}
|
||||
|
||||
Err(())
|
||||
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(),
|
||||
})
|
||||
}
|
||||
|
||||
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 +235,7 @@ fn add_month_to_table(
|
||||
},
|
||||
};
|
||||
|
||||
let day_limit = month_helper.number_of_days_in_month + month_helper.day_number_month_starts_on;
|
||||
let mut day_count: u32 = 1;
|
||||
|
||||
let days_of_the_week = [
|
||||
let mut days_of_the_week = [
|
||||
"sunday",
|
||||
"monday",
|
||||
"tuesday",
|
||||
@ -281,12 +245,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 +294,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 {
|
||||
if should_show_month_column || should_show_month_names {
|
||||
let month_value = if should_show_month_names {
|
||||
UntaggedValue::string(month_helper.get_month_name()).into_value(tag)
|
||||
UntaggedValue::string(month_helper.month_name.clone()).into_value(tag)
|
||||
} else {
|
||||
UntaggedValue::int(month_helper.selected_month).into_value(tag)
|
||||
};
|
||||
@ -315,17 +310,17 @@ fn add_month_to_table(
|
||||
|
||||
for day in &days_of_the_week {
|
||||
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 +329,7 @@ fn add_month_to_table(
|
||||
|
||||
indexmap.insert((*day).to_string(), value);
|
||||
|
||||
day_count += 1;
|
||||
day_number += 1;
|
||||
}
|
||||
|
||||
calendar_vec_deque
|
||||
|
81
crates/nu-cli/src/commands/char_.rs
Normal file
81
crates/nu-cli/src/commands/char_.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Char;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CharArgs {
|
||||
name: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Char {
|
||||
fn name(&self) -> &str {
|
||||
"char"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ansi").required(
|
||||
"character",
|
||||
SyntaxShape::Any,
|
||||
"the name of the character to output",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Output special characters (eg. 'newline')"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Output newline",
|
||||
example: r#"char newline"#,
|
||||
result: Some(vec![Value::from("\n")]),
|
||||
}]
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (CharArgs { name }, _) = args.process(®istry).await?;
|
||||
|
||||
let special_character = str_to_character(&name.item);
|
||||
|
||||
if let Some(output) = special_character {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(name.tag()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Unknown character",
|
||||
"unknown character",
|
||||
name.tag(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_character(s: &str) -> Option<String> {
|
||||
match s {
|
||||
"newline" | "enter" | "nl" => Some("\n".into()),
|
||||
"tab" => Some("\t".into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Char;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Char {})
|
||||
}
|
||||
}
|
@ -214,6 +214,9 @@ fn spawn(
|
||||
if !is_last {
|
||||
process.stdout(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdout pipe");
|
||||
|
||||
process.stderr(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stderr pipe");
|
||||
}
|
||||
|
||||
// open since we have some contents for stdin
|
||||
@ -312,6 +315,20 @@ fn spawn(
|
||||
return Err(());
|
||||
};
|
||||
|
||||
let stderr = if let Some(stderr) = child.stderr.take() {
|
||||
stderr
|
||||
} else {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"Can't redirect the stderr for external command",
|
||||
"can't redirect stderr",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
};
|
||||
|
||||
let file = futures::io::AllowStdIo::new(stdout);
|
||||
let stream = FramedRead::new(file, MaybeTextCodec);
|
||||
|
||||
@ -365,6 +382,64 @@ fn spawn(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let file = futures::io::AllowStdIo::new(stderr);
|
||||
let err_stream = FramedRead::new(file, MaybeTextCodec);
|
||||
|
||||
for err_line in block_on_stream(err_stream) {
|
||||
match err_line {
|
||||
Ok(line) => match line {
|
||||
StringOrBinary::String(s) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error(s.clone()),
|
||||
),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
StringOrBinary::Binary(_) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error(
|
||||
"Binary in stderr output",
|
||||
),
|
||||
),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// If there's an exit status, it makes sense that we may error when
|
||||
// trying to read from its stdout pipe (likely been closed). In that
|
||||
// case, don't emit an error.
|
||||
let should_error = match child.wait() {
|
||||
Ok(exit_status) => !exit_status.success(),
|
||||
Err(_) => true,
|
||||
};
|
||||
|
||||
if should_error {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!("Unable to read from stderr ({})", e),
|
||||
"unable to read from stderr",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can give an error when we see a non-zero exit code, but this is different
|
||||
|
@ -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?,
|
||||
@ -37,159 +37,217 @@ pub(crate) async fn run_internal_command(
|
||||
&scope,
|
||||
objects,
|
||||
)
|
||||
.await
|
||||
.await?
|
||||
};
|
||||
|
||||
let mut context = context.clone();
|
||||
let head = Arc::new(command.args.head.clone());
|
||||
//let context = Arc::new(context.clone());
|
||||
let context = context.clone();
|
||||
let command = Arc::new(command);
|
||||
let scope = Arc::new(scope);
|
||||
// let scope = scope.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let mut soft_errs: Vec<ShellError> = vec![];
|
||||
let mut yielded = false;
|
||||
Ok(InputStream::from_stream(
|
||||
result
|
||||
.then(move |item| {
|
||||
let head = head.clone();
|
||||
let command = command.clone();
|
||||
let mut context = context.clone();
|
||||
let scope = scope.clone();
|
||||
async move {
|
||||
match item {
|
||||
Ok(ReturnSuccess::Action(action)) => match action {
|
||||
CommandAction::ChangePath(path) => {
|
||||
context.shell_manager.set_path(path);
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::Exit => std::process::exit(0), // TODO: save history.txt
|
||||
CommandAction::Error(err) => {
|
||||
context.error(err.clone());
|
||||
InputStream::one(UntaggedValue::Error(err).into_untagged_value())
|
||||
}
|
||||
CommandAction::AutoConvert(tagged_contents, extension) => {
|
||||
let contents_tag = tagged_contents.tag.clone();
|
||||
let command_name = format!("from {}", extension);
|
||||
let command = command.clone();
|
||||
if let Some(converter) = context.registry.get_command(&command_name)
|
||||
{
|
||||
let new_args = RawCommandArgs {
|
||||
host: context.host.clone(),
|
||||
ctrl_c: context.ctrl_c.clone(),
|
||||
current_errors: context.current_errors.clone(),
|
||||
shell_manager: context.shell_manager.clone(),
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: nu_protocol::hir::Call {
|
||||
head: (&*head).clone(),
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
},
|
||||
name_tag: Tag::unknown_anchor(command.name_span),
|
||||
scope: (&*scope).clone(),
|
||||
},
|
||||
};
|
||||
let result = converter
|
||||
.run(
|
||||
new_args.with_input(vec![tagged_contents]),
|
||||
&context.registry,
|
||||
)
|
||||
.await;
|
||||
|
||||
while let Some(item) = result.next().await {
|
||||
match item {
|
||||
Ok(ReturnSuccess::Action(action)) => match action {
|
||||
CommandAction::ChangePath(path) => {
|
||||
context.shell_manager.set_path(path);
|
||||
}
|
||||
CommandAction::Exit => std::process::exit(0), // TODO: save history.txt
|
||||
CommandAction::Error(err) => {
|
||||
context.error(err);
|
||||
break;
|
||||
}
|
||||
CommandAction::AutoConvert(tagged_contents, extension) => {
|
||||
let contents_tag = tagged_contents.tag.clone();
|
||||
let command_name = format!("from {}", extension);
|
||||
let command = command.clone();
|
||||
if let Some(converter) = context.registry.get_command(&command_name) {
|
||||
let new_args = RawCommandArgs {
|
||||
host: context.host.clone(),
|
||||
ctrl_c: context.ctrl_c.clone(),
|
||||
current_errors: context.current_errors.clone(),
|
||||
shell_manager: context.shell_manager.clone(),
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: nu_protocol::hir::Call {
|
||||
head: command.args.head,
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
},
|
||||
name_tag: Tag::unknown_anchor(command.name_span),
|
||||
scope: scope.clone(),
|
||||
}
|
||||
};
|
||||
let mut result = converter.run(new_args.with_input(vec![tagged_contents]), &context.registry).await;
|
||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> = result.drain_vec().await;
|
||||
for res in result_vec {
|
||||
match res {
|
||||
Ok(ReturnSuccess::Value(Value { value: UntaggedValue::Table(list), ..})) => {
|
||||
for l in list {
|
||||
yield Ok(l);
|
||||
match result {
|
||||
Ok(mut result) => {
|
||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
||||
result.drain_vec().await;
|
||||
|
||||
let mut output = vec![];
|
||||
for res in result_vec {
|
||||
match res {
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
})) => {
|
||||
for l in list {
|
||||
output.push(Ok(l));
|
||||
}
|
||||
}
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value,
|
||||
..
|
||||
})) => {
|
||||
output
|
||||
.push(Ok(value
|
||||
.into_value(contents_tag.clone())));
|
||||
}
|
||||
Err(e) => output.push(Err(e)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
futures::stream::iter(output).to_input_stream()
|
||||
}
|
||||
Err(e) => {
|
||||
context.add_error(e);
|
||||
InputStream::empty()
|
||||
}
|
||||
}
|
||||
Ok(ReturnSuccess::Value(Value { value, .. })) => {
|
||||
yield Ok(value.into_value(contents_tag.clone()));
|
||||
}
|
||||
Err(e) => yield Err(e),
|
||||
_ => {}
|
||||
} else {
|
||||
InputStream::one(tagged_contents)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
yield Ok(tagged_contents)
|
||||
}
|
||||
}
|
||||
CommandAction::EnterHelpShell(value) => {
|
||||
match value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(cmd)),
|
||||
tag,
|
||||
} => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
HelpShell::for_command(
|
||||
UntaggedValue::string(cmd).into_value(tag),
|
||||
&context.registry(),
|
||||
)?,
|
||||
));
|
||||
CommandAction::EnterHelpShell(value) => match value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(cmd)),
|
||||
tag,
|
||||
} => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
match HelpShell::for_command(
|
||||
UntaggedValue::string(cmd).into_value(tag),
|
||||
&context.registry(),
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return InputStream::one(
|
||||
UntaggedValue::Error(err).into_untagged_value(),
|
||||
)
|
||||
}
|
||||
},
|
||||
));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
_ => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
match HelpShell::index(&context.registry()) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return InputStream::one(
|
||||
UntaggedValue::Error(err).into_untagged_value(),
|
||||
)
|
||||
}
|
||||
},
|
||||
));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
},
|
||||
CommandAction::EnterValueShell(value) => {
|
||||
context
|
||||
.shell_manager
|
||||
.insert_at_current(Box::new(ValueShell::new(value)));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
_ => {
|
||||
CommandAction::EnterShell(location) => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
HelpShell::index(&context.registry())?,
|
||||
match FilesystemShell::with_location(
|
||||
location,
|
||||
context.registry().clone(),
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return InputStream::one(
|
||||
UntaggedValue::Error(err.into())
|
||||
.into_untagged_value(),
|
||||
)
|
||||
}
|
||||
},
|
||||
));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::AddAlias(name, args, block) => {
|
||||
context.add_commands(vec![whole_stream_command(
|
||||
AliasCommand::new(name, args, block),
|
||||
)]);
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::PreviousShell => {
|
||||
context.shell_manager.prev();
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::NextShell => {
|
||||
context.shell_manager.next();
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::LeaveShell => {
|
||||
context.shell_manager.remove_at_current();
|
||||
if context.shell_manager.is_empty() {
|
||||
std::process::exit(0); // TODO: save history.txt
|
||||
}
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
},
|
||||
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Error(err),
|
||||
tag,
|
||||
})) => {
|
||||
context.error(err.clone());
|
||||
InputStream::one(UntaggedValue::Error(err).into_value(tag))
|
||||
}
|
||||
|
||||
Ok(ReturnSuccess::Value(v)) => InputStream::one(v),
|
||||
|
||||
Ok(ReturnSuccess::DebugValue(v)) => {
|
||||
let doc = PrettyDebug::pretty_doc(&v);
|
||||
let mut buffer = termcolor::Buffer::ansi();
|
||||
|
||||
let _ = doc.render_raw(
|
||||
context.with_host(|host| host.width() - 5),
|
||||
&mut nu_source::TermColored::new(&mut buffer),
|
||||
);
|
||||
|
||||
let value = String::from_utf8_lossy(buffer.as_slice());
|
||||
|
||||
InputStream::one(UntaggedValue::string(value).into_untagged_value())
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
context.error(err.clone());
|
||||
InputStream::one(UntaggedValue::Error(err).into_untagged_value())
|
||||
}
|
||||
}
|
||||
CommandAction::EnterValueShell(value) => {
|
||||
context
|
||||
.shell_manager
|
||||
.insert_at_current(Box::new(ValueShell::new(value)));
|
||||
}
|
||||
CommandAction::EnterShell(location) => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
FilesystemShell::with_location(location, context.registry().clone())?,
|
||||
));
|
||||
}
|
||||
CommandAction::AddAlias(name, args, block) => {
|
||||
context.add_commands(vec![
|
||||
whole_stream_command(AliasCommand::new(
|
||||
name,
|
||||
args,
|
||||
block,
|
||||
))
|
||||
]);
|
||||
}
|
||||
CommandAction::PreviousShell => {
|
||||
context.shell_manager.prev();
|
||||
}
|
||||
CommandAction::NextShell => {
|
||||
context.shell_manager.next();
|
||||
}
|
||||
CommandAction::LeaveShell => {
|
||||
context.shell_manager.remove_at_current();
|
||||
if context.shell_manager.is_empty() {
|
||||
std::process::exit(0); // TODO: save history.txt
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Error(err),
|
||||
..
|
||||
})) => {
|
||||
context.error(err.clone());
|
||||
yield Err(err);
|
||||
break;
|
||||
}
|
||||
|
||||
Ok(ReturnSuccess::Value(v)) => {
|
||||
yielded = true;
|
||||
yield Ok(v);
|
||||
}
|
||||
|
||||
Ok(ReturnSuccess::DebugValue(v)) => {
|
||||
yielded = true;
|
||||
|
||||
let doc = PrettyDebug::pretty_doc(&v);
|
||||
let mut buffer = termcolor::Buffer::ansi();
|
||||
|
||||
let _ = doc.render_raw(
|
||||
context.with_host(|host| host.width() - 5),
|
||||
&mut nu_source::TermColored::new(&mut buffer),
|
||||
);
|
||||
|
||||
let value = String::from_utf8_lossy(buffer.as_slice());
|
||||
|
||||
yield Ok(UntaggedValue::string(value).into_untagged_value())
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
context.error(err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_input_stream())
|
||||
})
|
||||
.flatten()
|
||||
.take_while(|x| futures::future::ready(!x.is_error())),
|
||||
))
|
||||
}
|
||||
|
@ -343,18 +343,19 @@ impl Command {
|
||||
self.0.usage()
|
||||
}
|
||||
|
||||
pub async fn run(&self, args: CommandArgs, registry: &CommandRegistry) -> OutputStream {
|
||||
pub async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
if args.call_info.switch_present("help") {
|
||||
let cl = self.0.clone();
|
||||
let registry = registry.clone();
|
||||
OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(get_help(&*cl, ®istry)).into_value(Tag::unknown()),
|
||||
)))
|
||||
))))
|
||||
} else {
|
||||
match self.0.run(args, registry).await {
|
||||
Ok(stream) => stream,
|
||||
Err(err) => OutputStream::one(Err(err)),
|
||||
}
|
||||
self.0.run(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -389,42 +390,44 @@ 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(®istry, &it).await {
|
||||
Err(err) => { yield Err(err); return; },
|
||||
Ok(args) => args,
|
||||
};
|
||||
|
||||
let args = EvaluatedFilterCommandArgs::new(
|
||||
host.clone(),
|
||||
ctrl_c.clone(),
|
||||
shell_manager.clone(),
|
||||
call_info,
|
||||
);
|
||||
|
||||
match func(args) {
|
||||
Err(err) => yield Err(err),
|
||||
Ok(mut stream) => {
|
||||
while let Some(value) = stream.values.next().await {
|
||||
yield value;
|
||||
let ctrl_c = ctrl_c.clone();
|
||||
let shell_manager = shell_manager.clone();
|
||||
let call_info = call_info.clone();
|
||||
async move {
|
||||
let call_info = match call_info.evaluate_with_new_it(&*registry, &it).await {
|
||||
Err(err) => {
|
||||
return OutputStream::one(Err(err));
|
||||
}
|
||||
Ok(args) => args,
|
||||
};
|
||||
|
||||
let args = EvaluatedFilterCommandArgs::new(
|
||||
host.clone(),
|
||||
ctrl_c.clone(),
|
||||
shell_manager.clone(),
|
||||
call_info,
|
||||
);
|
||||
|
||||
match func(args) {
|
||||
Err(err) => return OutputStream::one(Err(err)),
|
||||
Ok(stream) => stream,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ use crate::commands::WholeStreamCommand;
|
||||
use chrono::{Datelike, TimeZone, Timelike};
|
||||
use core::fmt::Display;
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::{Signature, UntaggedValue};
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
|
||||
pub struct Date;
|
||||
|
||||
@ -21,6 +21,13 @@ impl WholeStreamCommand for Date {
|
||||
Signature::build("date")
|
||||
.switch("utc", "use universal time (UTC)", Some('u'))
|
||||
.switch("local", "use the local time", Some('l'))
|
||||
.named(
|
||||
"format",
|
||||
SyntaxShape::String,
|
||||
"report datetime in supplied strftime format",
|
||||
Some('f'),
|
||||
)
|
||||
.switch("raw", "print date without tables", Some('r'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -47,46 +54,72 @@ impl WholeStreamCommand for Date {
|
||||
example: "date --utc",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current time and date and report it based on format",
|
||||
example: "date --format '%Y-%m-%d %H:%M:%S.%f %z'",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current time and date and report it without a table",
|
||||
example: "date --format '%Y-%m-%d %H:%M:%S.%f %z' --raw",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, tag: Tag) -> Value
|
||||
pub fn date_to_value_raw<T: TimeZone>(dt: DateTime<T>, dt_format: String) -> String
|
||||
where
|
||||
T::Offset: Display,
|
||||
{
|
||||
let result = dt.format(&dt_format);
|
||||
format!("{}", result)
|
||||
}
|
||||
|
||||
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, tag: Tag, dt_format: String) -> Value
|
||||
where
|
||||
T::Offset: Display,
|
||||
{
|
||||
let mut indexmap = IndexMap::new();
|
||||
|
||||
indexmap.insert(
|
||||
"year".to_string(),
|
||||
UntaggedValue::int(dt.year()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"month".to_string(),
|
||||
UntaggedValue::int(dt.month()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"day".to_string(),
|
||||
UntaggedValue::int(dt.day()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"hour".to_string(),
|
||||
UntaggedValue::int(dt.hour()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"minute".to_string(),
|
||||
UntaggedValue::int(dt.minute()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"second".to_string(),
|
||||
UntaggedValue::int(dt.second()).into_value(&tag),
|
||||
);
|
||||
if dt_format.is_empty() {
|
||||
indexmap.insert(
|
||||
"year".to_string(),
|
||||
UntaggedValue::int(dt.year()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"month".to_string(),
|
||||
UntaggedValue::int(dt.month()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"day".to_string(),
|
||||
UntaggedValue::int(dt.day()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"hour".to_string(),
|
||||
UntaggedValue::int(dt.hour()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"minute".to_string(),
|
||||
UntaggedValue::int(dt.minute()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"second".to_string(),
|
||||
UntaggedValue::int(dt.second()).into_value(&tag),
|
||||
);
|
||||
|
||||
let tz = dt.offset();
|
||||
indexmap.insert(
|
||||
"timezone".to_string(),
|
||||
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
|
||||
);
|
||||
let tz = dt.offset();
|
||||
indexmap.insert(
|
||||
"timezone".to_string(),
|
||||
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
|
||||
);
|
||||
} else {
|
||||
let result = dt.format(&dt_format);
|
||||
indexmap.insert(
|
||||
"formatted".to_string(),
|
||||
UntaggedValue::string(format!("{}", result)).into_value(&tag),
|
||||
);
|
||||
}
|
||||
|
||||
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
|
||||
}
|
||||
@ -97,15 +130,33 @@ pub async fn date(
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let raw = args.has("raw");
|
||||
|
||||
let dt_fmt = if args.has("format") {
|
||||
if let Some(dt_fmt) = args.get("format") {
|
||||
dt_fmt.convert_to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
let value = if args.has("utc") {
|
||||
let utc: DateTime<Utc> = Utc::now();
|
||||
date_to_value(utc, tag)
|
||||
if raw {
|
||||
UntaggedValue::string(date_to_value_raw(utc, dt_fmt)).into_untagged_value()
|
||||
} else {
|
||||
date_to_value(utc, tag, dt_fmt)
|
||||
}
|
||||
} else {
|
||||
let local: DateTime<Local> = Local::now();
|
||||
date_to_value(local, tag)
|
||||
if raw {
|
||||
UntaggedValue::string(date_to_value_raw(local, dt_fmt)).into_untagged_value()
|
||||
} else {
|
||||
date_to_value(local, tag, dt_fmt)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
|
111
crates/nu-cli/src/commands/do_.rs
Normal file
111
crates/nu-cli/src/commands/do_.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, ReturnSuccess, Signature, SyntaxShape, Value};
|
||||
|
||||
pub struct Do;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct DoArgs {
|
||||
block: Block,
|
||||
ignore_errors: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Do {
|
||||
fn name(&self) -> &str {
|
||||
"do"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("with-env")
|
||||
.required("block", SyntaxShape::Block, "the block to run ")
|
||||
.switch(
|
||||
"ignore_errors",
|
||||
"ignore errors as the block runs",
|
||||
Some('i'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block, optionally ignoring errors"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
do_(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Run the block",
|
||||
example: r#"do { echo hello }"#,
|
||||
result: Some(vec![Value::from("hello")]),
|
||||
},
|
||||
Example {
|
||||
description: "Run the block and ignore errors",
|
||||
example: r#"do -i { thisisnotarealcommand }"#,
|
||||
result: Some(vec![Value::nothing()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let is_last = raw_args.call_info.args.is_last;
|
||||
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let (
|
||||
DoArgs {
|
||||
ignore_errors,
|
||||
mut block,
|
||||
},
|
||||
input,
|
||||
) = raw_args.process(®istry).await?;
|
||||
block.set_is_last(is_last);
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
input,
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
|
||||
if ignore_errors {
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
let output = stream.drain_vec().await;
|
||||
context.clear_errors();
|
||||
Ok(futures::stream::iter(output).to_output_stream())
|
||||
}
|
||||
Err(_) => Ok(OutputStream::one(ReturnSuccess::value(Value::nothing()))),
|
||||
}
|
||||
} else {
|
||||
result.map(|x| x.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Do;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Do {})
|
||||
}
|
||||
}
|
@ -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(),
|
||||
|
@ -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(®istry).await?;
|
||||
let (args, _): (EchoArgs, _) = args.process(®istry).await?;
|
||||
|
||||
for i in args.rest {
|
||||
match i.as_string() {
|
||||
Ok(s) => {
|
||||
yield 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()));
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Range(range)),
|
||||
tag
|
||||
} => {
|
||||
let mut current = range.from.0.item;
|
||||
while current != range.to.0.item {
|
||||
yield Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag)));
|
||||
current = match crate::data::value::compute_values(Operator::Plus, &UntaggedValue::Primitive(current), &UntaggedValue::int(1)) {
|
||||
Ok(result) => match result {
|
||||
UntaggedValue::Primitive(p) => p,
|
||||
_ => {
|
||||
yield Err(ShellError::unimplemented("Internal error: expected a primitive result from increment"));
|
||||
return;
|
||||
}
|
||||
},
|
||||
Err((left_type, right_type)) => {
|
||||
yield Err(ShellError::coerce_error(
|
||||
left_type.spanned(tag.span),
|
||||
right_type.spanned(tag.span),
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
match range.to.1 {
|
||||
RangeInclusion::Inclusive => {
|
||||
yield Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
yield Ok(ReturnSuccess::Value(i.clone()));
|
||||
}
|
||||
},
|
||||
let stream = args.rest.into_iter().map(|i| {
|
||||
match i.as_string() {
|
||||
Ok(s) => {
|
||||
OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(s).into_value(i.tag.clone()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
};
|
||||
_ => match i {
|
||||
Value {
|
||||
value: UntaggedValue::Table(table),
|
||||
..
|
||||
} => {
|
||||
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![];
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
let mut current = range.from.0.item;
|
||||
while current != range.to.0.item {
|
||||
output_vec.push(Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag))));
|
||||
current = match crate::data::value::compute_values(Operator::Plus, &UntaggedValue::Primitive(current), &UntaggedValue::int(1)) {
|
||||
Ok(result) => match result {
|
||||
UntaggedValue::Primitive(p) => p,
|
||||
_ => {
|
||||
return OutputStream::one(Err(ShellError::unimplemented("Internal error: expected a primitive result from increment")));
|
||||
}
|
||||
},
|
||||
Err((left_type, right_type)) => {
|
||||
return OutputStream::one(Err(ShellError::coerce_error(
|
||||
left_type.spanned(tag.span),
|
||||
right_type.spanned(tag.span),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let RangeInclusion::Inclusive = range.to.1 {
|
||||
output_vec.push(Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current).into_value(&tag))));
|
||||
}
|
||||
|
||||
futures::stream::iter(output_vec.into_iter()).to_output_stream()
|
||||
}
|
||||
_ => {
|
||||
OutputStream::one(Ok(ReturnSuccess::Value(i.clone())))
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(stream).flatten().to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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(
|
||||
"location",
|
||||
SyntaxShape::Path,
|
||||
"the location to create a new shell from",
|
||||
)
|
||||
Signature::build("enter")
|
||||
.required(
|
||||
"location",
|
||||
SyntaxShape::Path,
|
||||
"the location to create a new shell from",
|
||||
)
|
||||
.named(
|
||||
"encoding",
|
||||
SyntaxShape::String,
|
||||
"encoding to use to open file",
|
||||
Some('e'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Create a new shell and begin at this path."
|
||||
r#"Create a new shell and begin at this path.
|
||||
|
||||
Multiple encodings are supported for reading text files by using
|
||||
the '--encoding <encoding>' parameter. Here is an example of a few:
|
||||
big5, euc-jp, euc-kr, gbk, iso-8859-1, utf-16, cp1252, latin5
|
||||
|
||||
For a more complete list of encodings please refer to the encoding_rs
|
||||
documentation link at https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics"#
|
||||
}
|
||||
|
||||
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,121 +69,131 @@ 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();
|
||||
let ctrl_c = raw_args.ctrl_c.clone();
|
||||
let current_errors = raw_args.current_errors.clone();
|
||||
let host = raw_args.host.clone();
|
||||
let tag = raw_args.call_info.name_tag.clone();
|
||||
let (EnterArgs { location }, _) = raw_args.process(®istry).await?;
|
||||
let location_string = location.display().to_string();
|
||||
let location_clone = location_string.clone();
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let shell_manager = raw_args.shell_manager.clone();
|
||||
let head = raw_args.call_info.args.head.clone();
|
||||
let ctrl_c = raw_args.ctrl_c.clone();
|
||||
let current_errors = raw_args.current_errors.clone();
|
||||
let host = raw_args.host.clone();
|
||||
let tag = raw_args.call_info.name_tag.clone();
|
||||
let (EnterArgs { location, encoding }, _) = raw_args.process(®istry).await?;
|
||||
let location_string = location.display().to_string();
|
||||
let location_clone = location_string.clone();
|
||||
|
||||
if location_string.starts_with("help") {
|
||||
let spec = location_string.split(':').collect::<Vec<&str>>();
|
||||
if location_string.starts_with("help") {
|
||||
let spec = location_string.split(':').collect::<Vec<&str>>();
|
||||
|
||||
if spec.len() == 2 {
|
||||
let (_, command) = (spec[0], spec[1]);
|
||||
if spec.len() == 2 {
|
||||
let (_, command) = (spec[0], spec[1]);
|
||||
|
||||
if registry.has(command) {
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
|
||||
if registry.has(command) {
|
||||
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()),
|
||||
)));
|
||||
} else if location.is_dir() {
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterShell(
|
||||
location_clone,
|
||||
)));
|
||||
} else {
|
||||
// If it's a file, attempt to open the file as a value and enter it
|
||||
let cwd = shell_manager.path();
|
||||
|
||||
let full_path = std::path::PathBuf::from(cwd);
|
||||
|
||||
let (file_extension, contents, contents_tag) =
|
||||
crate::commands::open::fetch(
|
||||
&full_path,
|
||||
&PathBuf::from(location_clone),
|
||||
tag.span,
|
||||
).await?;
|
||||
|
||||
match contents {
|
||||
UntaggedValue::Primitive(Primitive::String(_)) => {
|
||||
let tagged_contents = contents.into_value(&contents_tag);
|
||||
|
||||
if let Some(extension) = file_extension {
|
||||
let command_name = format!("from {}", extension);
|
||||
if let Some(converter) =
|
||||
registry.get_command(&command_name)
|
||||
{
|
||||
let new_args = RawCommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
current_errors,
|
||||
shell_manager,
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: nu_protocol::hir::Call {
|
||||
head,
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
},
|
||||
name_tag: tag.clone(),
|
||||
scope: scope.clone()
|
||||
},
|
||||
};
|
||||
let mut result = converter.run(
|
||||
new_args.with_input(vec![tagged_contents]),
|
||||
®istry,
|
||||
).await;
|
||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
||||
result.drain_vec().await;
|
||||
for res in result_vec {
|
||||
match res {
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value,
|
||||
..
|
||||
})) => {
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(
|
||||
Value {
|
||||
value,
|
||||
tag: contents_tag.clone(),
|
||||
})));
|
||||
}
|
||||
x => yield x,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
|
||||
}
|
||||
} else {
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let tagged_contents = contents.into_value(contents_tag);
|
||||
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
|
||||
}
|
||||
),
|
||||
)));
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterHelpShell(UntaggedValue::nothing().into_value(Tag::unknown())),
|
||||
)))
|
||||
} else if location.is_dir() {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterShell(location_clone),
|
||||
)))
|
||||
} else {
|
||||
// If it's a file, attempt to open the file as a value and enter it
|
||||
let cwd = shell_manager.path();
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
let full_path = std::path::PathBuf::from(cwd);
|
||||
|
||||
let (file_extension, contents, contents_tag) = crate::commands::open::fetch(
|
||||
&full_path,
|
||||
&PathBuf::from(location_clone),
|
||||
tag.span,
|
||||
match encoding {
|
||||
Some(e) => e.to_string(),
|
||||
_ => "".to_string(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
match contents {
|
||||
UntaggedValue::Primitive(Primitive::String(_)) => {
|
||||
let tagged_contents = contents.into_value(&contents_tag);
|
||||
|
||||
if let Some(extension) = file_extension {
|
||||
let command_name = format!("from {}", extension);
|
||||
if let Some(converter) = registry.get_command(&command_name) {
|
||||
let new_args = RawCommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
current_errors,
|
||||
shell_manager,
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: nu_protocol::hir::Call {
|
||||
head,
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
},
|
||||
name_tag: tag.clone(),
|
||||
scope: scope.clone(),
|
||||
},
|
||||
};
|
||||
let mut result = converter
|
||||
.run(new_args.with_input(vec![tagged_contents]), ®istry)
|
||||
.await?;
|
||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
||||
result.drain_vec().await;
|
||||
|
||||
Ok(futures::stream::iter(result_vec.into_iter().map(
|
||||
move |res| match res {
|
||||
Ok(ReturnSuccess::Value(Value { value, .. })) => Ok(
|
||||
ReturnSuccess::Action(CommandAction::EnterValueShell(Value {
|
||||
value,
|
||||
tag: contents_tag.clone(),
|
||||
})),
|
||||
),
|
||||
x => x,
|
||||
},
|
||||
))
|
||||
.to_output_stream())
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterValueShell(tagged_contents),
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterValueShell(tagged_contents),
|
||||
)))
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let tagged_contents = contents.into_value(contents_tag);
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterValueShell(tagged_contents),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
105
crates/nu-cli/src/commands/every.rs
Normal file
105
crates/nu-cli/src/commands/every.rs
Normal 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(®istry).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 {})
|
||||
}
|
||||
}
|
@ -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,49 +49,60 @@ 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(®istry).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(®istry).await?;
|
||||
|
||||
let format_pattern = format(&pattern);
|
||||
let commands = format_pattern;
|
||||
let format_pattern = 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 {
|
||||
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()), ®istry);
|
||||
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 result = evaluate_baseline_expr(&full_column_path.0, ®istry, &value, &scope.vars, &scope.env).await;
|
||||
let result = evaluate_baseline_expr(
|
||||
&full_column_path.0,
|
||||
®istry,
|
||||
&value,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Ok(c) = result {
|
||||
output
|
||||
.push_str(&value::format_leaf(c.borrow()).plain_string(100_000))
|
||||
} else {
|
||||
// That column doesn't match, so don't emit anything
|
||||
if let Ok(c) = result {
|
||||
output
|
||||
.push_str(&value::format_leaf(c.borrow()).plain_string(100_000))
|
||||
} else {
|
||||
// That column doesn't match, so don't emit anything
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReturnSuccess::value(UntaggedValue::string(output).into_untagged_value())
|
||||
}
|
||||
|
||||
yield ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_untagged_value())
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -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(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&From, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
));
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&From, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,35 +28,40 @@ 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(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
let input_string = input.collect_string(tag.clone()).await?.item;
|
||||
let input_bytes = input_string.as_bytes();
|
||||
let buf_reader = BufReader::new(input_bytes);
|
||||
let parser = ical::IcalParser::new(buf_reader);
|
||||
let input_string = input.collect_string(tag.clone()).await?.item;
|
||||
let input_bytes = input_string.as_bytes();
|
||||
let buf_reader = BufReader::new(input_bytes);
|
||||
let parser = ical::IcalParser::new(buf_reader);
|
||||
|
||||
for calendar in parser {
|
||||
match calendar {
|
||||
Ok(c) => yield ReturnSuccess::value(calendar_to_value(c, tag.clone())),
|
||||
Err(_) => yield Err(ShellError::labeled_error(
|
||||
"Could not parse as .ics",
|
||||
"input cannot be parsed as .ics",
|
||||
tag.clone()
|
||||
)),
|
||||
}
|
||||
// TODO: it should be possible to make this a stream, but the some of the lifetime requirements make this tricky.
|
||||
// Pre-computing for now
|
||||
let mut output = vec![];
|
||||
|
||||
for calendar in parser {
|
||||
match calendar {
|
||||
Ok(c) => output.push(ReturnSuccess::value(calendar_to_value(c, tag.clone()))),
|
||||
Err(_) => output.push(Err(ShellError::labeled_error(
|
||||
"Could not parse as .ics",
|
||||
"input cannot be parsed as .ics",
|
||||
tag.clone(),
|
||||
))),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(futures::stream::iter(output).to_output_stream())
|
||||
}
|
||||
|
||||
fn calendar_to_value(calendar: IcalCalendar, tag: Tag) -> Value {
|
||||
|
@ -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(®istry).await?;
|
||||
let concat_string = input.collect_string(name_tag.clone()).await?;
|
||||
let (FromJSONArgs { objects }, input) = args.process(®istry).await?;
|
||||
let concat_string = input.collect_string(name_tag.clone()).await?;
|
||||
|
||||
if objects {
|
||||
for json_str in concat_string.item.lines() {
|
||||
let string_clone: Vec<_> = concat_string.item.lines().map(|x| x.to_string()).collect();
|
||||
|
||||
if objects {
|
||||
Ok(
|
||||
futures::stream::iter(string_clone.into_iter().filter_map(move |json_str| {
|
||||
if json_str.is_empty() {
|
||||
continue;
|
||||
return None;
|
||||
}
|
||||
|
||||
match from_json_string_to_value(json_str.to_string(), &name_tag) {
|
||||
Ok(x) =>
|
||||
yield ReturnSuccess::value(x),
|
||||
match from_json_string_to_value(json_str, &name_tag) {
|
||||
Ok(x) => Some(ReturnSuccess::value(x)),
|
||||
Err(e) => {
|
||||
let mut message = "Could not parse as JSON (".to_string();
|
||||
message.push_str(&e.to_string());
|
||||
message.push_str(")");
|
||||
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
Some(Err(ShellError::labeled_error_with_secondary(
|
||||
message,
|
||||
"input cannot be parsed as JSON",
|
||||
&name_tag,
|
||||
name_tag.clone(),
|
||||
"value originates from here",
|
||||
concat_string.tag.clone()))
|
||||
concat_string.tag.clone(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match from_json_string_to_value(concat_string.item, name_tag.clone()) {
|
||||
Ok(x) =>
|
||||
match x {
|
||||
Value { value: UntaggedValue::Table(list), .. } => {
|
||||
for l in list {
|
||||
yield ReturnSuccess::value(l);
|
||||
}
|
||||
}
|
||||
x => yield ReturnSuccess::value(x),
|
||||
}
|
||||
Err(e) => {
|
||||
let mut message = "Could not parse as JSON (".to_string();
|
||||
message.push_str(&e.to_string());
|
||||
message.push_str(")");
|
||||
}))
|
||||
.to_output_stream(),
|
||||
)
|
||||
} else {
|
||||
match from_json_string_to_value(concat_string.item, name_tag.clone()) {
|
||||
Ok(x) => match x {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => Ok(
|
||||
futures::stream::iter(list.into_iter().map(ReturnSuccess::value))
|
||||
.to_output_stream(),
|
||||
),
|
||||
x => Ok(OutputStream::one(ReturnSuccess::value(x))),
|
||||
},
|
||||
Err(e) => {
|
||||
let mut message = "Could not parse as JSON (".to_string();
|
||||
message.push_str(&e.to_string());
|
||||
message.push_str(")");
|
||||
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
Ok(OutputStream::one(Err(
|
||||
ShellError::labeled_error_with_secondary(
|
||||
message,
|
||||
"input cannot be parsed as JSON",
|
||||
name_tag,
|
||||
"value originates from here",
|
||||
concat_string.tag))
|
||||
}
|
||||
concat_string.tag,
|
||||
),
|
||||
)))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -36,62 +36,66 @@ 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(®istry).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 (
|
||||
FromODSArgs {
|
||||
headerless: _headerless,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
let bytes = input.collect_binary(tag.clone()).await?;
|
||||
let buf: Cursor<Vec<u8>> = Cursor::new(bytes.item);
|
||||
let mut ods = Ods::<_>::new(buf).map_err(|_| {
|
||||
ShellError::labeled_error("Could not load ods file", "could not load ods file", &tag)
|
||||
})?;
|
||||
|
||||
let mut dict = TaggedDictBuilder::new(&tag);
|
||||
let mut dict = TaggedDictBuilder::new(&tag);
|
||||
|
||||
let sheet_names = ods.sheet_names().to_owned();
|
||||
let sheet_names = ods.sheet_names().to_owned();
|
||||
|
||||
for sheet_name in &sheet_names {
|
||||
let mut sheet_output = TaggedListBuilder::new(&tag);
|
||||
for sheet_name in &sheet_names {
|
||||
let mut sheet_output = TaggedListBuilder::new(&tag);
|
||||
|
||||
if let Some(Ok(current_sheet)) = ods.worksheet_range(sheet_name) {
|
||||
for row in current_sheet.rows() {
|
||||
let mut row_output = TaggedDictBuilder::new(&tag);
|
||||
for (i, cell) in row.iter().enumerate() {
|
||||
let value = match cell {
|
||||
DataType::Empty => UntaggedValue::nothing(),
|
||||
DataType::String(s) => UntaggedValue::string(s),
|
||||
DataType::Float(f) => UntaggedValue::decimal(*f),
|
||||
DataType::Int(i) => UntaggedValue::int(*i),
|
||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||
_ => UntaggedValue::nothing(),
|
||||
};
|
||||
if let Some(Ok(current_sheet)) = ods.worksheet_range(sheet_name) {
|
||||
for row in current_sheet.rows() {
|
||||
let mut row_output = TaggedDictBuilder::new(&tag);
|
||||
for (i, cell) in row.iter().enumerate() {
|
||||
let value = match cell {
|
||||
DataType::Empty => UntaggedValue::nothing(),
|
||||
DataType::String(s) => UntaggedValue::string(s),
|
||||
DataType::Float(f) => UntaggedValue::decimal(*f),
|
||||
DataType::Int(i) => UntaggedValue::int(*i),
|
||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||
_ => UntaggedValue::nothing(),
|
||||
};
|
||||
|
||||
row_output.insert_untagged(&format!("Column{}", i), value);
|
||||
}
|
||||
|
||||
sheet_output.push_untagged(row_output.into_untagged_value());
|
||||
row_output.insert_untagged(&format!("Column{}", i), value);
|
||||
}
|
||||
|
||||
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value());
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Could not load sheet",
|
||||
"could not load sheet",
|
||||
&tag));
|
||||
sheet_output.push_untagged(row_output.into_untagged_value());
|
||||
}
|
||||
|
||||
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value());
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Could not load sheet",
|
||||
"could not load sheet",
|
||||
&tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
yield ReturnSuccess::value(dict.into_value());
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(OutputStream::one(ReturnSuccess::value(dict.into_value())))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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(®istry).await?;
|
||||
let concat_string = input.collect_string(name.clone()).await?;
|
||||
let split_at = match minimum_spaces {
|
||||
Some(number) => number.item,
|
||||
None => DEFAULT_MINIMUM_SPACES
|
||||
};
|
||||
let (
|
||||
FromSSVArgs {
|
||||
headerless,
|
||||
aligned_columns,
|
||||
minimum_spaces,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
let concat_string = input.collect_string(name.clone()).await?;
|
||||
let split_at = match minimum_spaces {
|
||||
Some(number) => number.item,
|
||||
None => DEFAULT_MINIMUM_SPACES,
|
||||
};
|
||||
|
||||
match from_ssv_string_to_value(&concat_string.item, headerless, aligned_columns, split_at, name.clone()) {
|
||||
Ok(
|
||||
match from_ssv_string_to_value(
|
||||
&concat_string.item,
|
||||
headerless,
|
||||
aligned_columns,
|
||||
split_at,
|
||||
name.clone(),
|
||||
) {
|
||||
Some(x) => match x {
|
||||
Value { value: UntaggedValue::Table(list), ..} => {
|
||||
for l in list { yield ReturnSuccess::value(l) }
|
||||
}
|
||||
x => yield ReturnSuccess::value(x)
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => futures::stream::iter(list.into_iter().map(ReturnSuccess::value))
|
||||
.to_output_stream(),
|
||||
x => OutputStream::one(ReturnSuccess::value(x)),
|
||||
},
|
||||
None => {
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
return Err(ShellError::labeled_error_with_secondary(
|
||||
"Could not parse as SSV",
|
||||
"input cannot be parsed ssv",
|
||||
&name,
|
||||
"value originates from here",
|
||||
&concat_string.tag,
|
||||
))
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
));
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
let concat_string = input.collect_string(tag.clone()).await?;
|
||||
let concat_string = input.collect_string(tag.clone()).await?;
|
||||
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)]
|
||||
|
@ -36,62 +36,66 @@ 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(®istry).await?;
|
||||
let value = input.collect_binary(tag.clone()).await?;
|
||||
let (
|
||||
FromXLSXArgs {
|
||||
headerless: _headerless,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
let value = input.collect_binary(tag.clone()).await?;
|
||||
|
||||
let mut buf: Cursor<Vec<u8>> = Cursor::new(value.item);
|
||||
let mut xls = Xlsx::<_>::new(buf).map_err(|_| {
|
||||
ShellError::labeled_error("Could not load xlsx file", "could not load xlsx file", &tag)
|
||||
})?;
|
||||
let buf: Cursor<Vec<u8>> = Cursor::new(value.item);
|
||||
let mut xls = Xlsx::<_>::new(buf).map_err(|_| {
|
||||
ShellError::labeled_error("Could not load xlsx file", "could not load xlsx file", &tag)
|
||||
})?;
|
||||
|
||||
let mut dict = TaggedDictBuilder::new(&tag);
|
||||
let mut dict = TaggedDictBuilder::new(&tag);
|
||||
|
||||
let sheet_names = xls.sheet_names().to_owned();
|
||||
let sheet_names = xls.sheet_names().to_owned();
|
||||
|
||||
for sheet_name in &sheet_names {
|
||||
let mut sheet_output = TaggedListBuilder::new(&tag);
|
||||
for sheet_name in &sheet_names {
|
||||
let mut sheet_output = TaggedListBuilder::new(&tag);
|
||||
|
||||
if let Some(Ok(current_sheet)) = xls.worksheet_range(sheet_name) {
|
||||
for row in current_sheet.rows() {
|
||||
let mut row_output = TaggedDictBuilder::new(&tag);
|
||||
for (i, cell) in row.iter().enumerate() {
|
||||
let value = match cell {
|
||||
DataType::Empty => UntaggedValue::nothing(),
|
||||
DataType::String(s) => UntaggedValue::string(s),
|
||||
DataType::Float(f) => UntaggedValue::decimal(*f),
|
||||
DataType::Int(i) => UntaggedValue::int(*i),
|
||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||
_ => UntaggedValue::nothing(),
|
||||
};
|
||||
if let Some(Ok(current_sheet)) = xls.worksheet_range(sheet_name) {
|
||||
for row in current_sheet.rows() {
|
||||
let mut row_output = TaggedDictBuilder::new(&tag);
|
||||
for (i, cell) in row.iter().enumerate() {
|
||||
let value = match cell {
|
||||
DataType::Empty => UntaggedValue::nothing(),
|
||||
DataType::String(s) => UntaggedValue::string(s),
|
||||
DataType::Float(f) => UntaggedValue::decimal(*f),
|
||||
DataType::Int(i) => UntaggedValue::int(*i),
|
||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||
_ => UntaggedValue::nothing(),
|
||||
};
|
||||
|
||||
row_output.insert_untagged(&format!("Column{}", i), value);
|
||||
}
|
||||
|
||||
sheet_output.push_untagged(row_output.into_untagged_value());
|
||||
row_output.insert_untagged(&format!("Column{}", i), value);
|
||||
}
|
||||
|
||||
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value());
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Could not load sheet",
|
||||
"could not load sheet",
|
||||
&tag,
|
||||
));
|
||||
sheet_output.push_untagged(row_output.into_untagged_value());
|
||||
}
|
||||
|
||||
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value());
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Could not load sheet",
|
||||
"could not load sheet",
|
||||
&tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
yield ReturnSuccess::value(dict.into_value());
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(OutputStream::one(ReturnSuccess::value(dict.into_value())))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
let concat_string = input.collect_string(tag.clone()).await?;
|
||||
let concat_string = input.collect_string(tag.clone()).await?;
|
||||
|
||||
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)]
|
||||
|
@ -59,27 +59,19 @@ fn convert_yaml_value_to_nu_value(
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
let err_not_compatible_number = ShellError::labeled_error(
|
||||
"Expected a compatible number",
|
||||
"expected a compatible number",
|
||||
&tag,
|
||||
);
|
||||
Ok(match v {
|
||||
serde_yaml::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(tag),
|
||||
serde_yaml::Value::Number(n) if n.is_i64() => {
|
||||
UntaggedValue::int(n.as_i64().ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a compatible number",
|
||||
"expected a compatible number",
|
||||
&tag,
|
||||
)
|
||||
})?)
|
||||
.into_value(tag)
|
||||
UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag)
|
||||
}
|
||||
serde_yaml::Value::Number(n) if n.is_f64() => {
|
||||
UntaggedValue::decimal(n.as_f64().ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a compatible number",
|
||||
"expected a compatible number",
|
||||
&tag,
|
||||
)
|
||||
})?)
|
||||
.into_value(tag)
|
||||
UntaggedValue::decimal(n.as_f64().ok_or_else(|| err_not_compatible_number)?)
|
||||
.into_value(tag)
|
||||
}
|
||||
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
|
||||
serde_yaml::Value::Sequence(a) => {
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(®istry).await?;
|
||||
if fields.is_empty() {
|
||||
let mut vec = input.drain_vec().await;
|
||||
let (GetArgs { rest: mut fields }, mut input) = args.process(®istry).await?;
|
||||
if fields.is_empty() {
|
||||
let vec = input.drain_vec().await;
|
||||
|
||||
let descs = nu_protocol::merge_descriptors(&vec);
|
||||
for desc in descs {
|
||||
yield ReturnSuccess::value(desc);
|
||||
}
|
||||
} else {
|
||||
let member = fields.remove(0);
|
||||
trace!("get {:?} {:?}", member, fields);
|
||||
while let Some(item) = input.next().await {
|
||||
let descs = nu_protocol::merge_descriptors(&vec);
|
||||
|
||||
Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
|
||||
} else {
|
||||
let member = fields.remove(0);
|
||||
trace!("get {:?} {:?}", member, fields);
|
||||
|
||||
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,24 +228,26 @@ 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(),
|
||||
),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(stream.to_output_stream())
|
||||
|
||||
futures::stream::iter(output)
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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,
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
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 {
|
||||
match crate::utils::data::group(column_name, &values, None, &name) {
|
||||
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
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")}),
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
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)| {
|
||||
//the headers are the first row in the table
|
||||
let headers: Vec<String> = match &rows[0].value {
|
||||
UntaggedValue::Row(d) => {
|
||||
Ok(d.entries
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
match v.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => { //If a cell that should contain a header name is empty, we name the column Column[index]
|
||||
Err(_) => {
|
||||
//If a cell that should contain a header name is empty, we name the column Column[index]
|
||||
match d.entries.get_full(k) {
|
||||
Some((index, _, _)) => format!("Column{}", index),
|
||||
None => "unknownColumn".to_string()
|
||||
None => "unknownColumn".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}).collect())
|
||||
}
|
||||
_ => Err(ShellError::unexpected_eof("Could not get headers, is the table empty?", rows[0].tag.span))
|
||||
}?;
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
_ => Err(ShellError::unexpected_eof(
|
||||
"Could not get headers, is the table empty?",
|
||||
rows[0].tag.span,
|
||||
)),
|
||||
}?;
|
||||
|
||||
//Each row is a dictionary with the headers as keys
|
||||
for r in rows.iter().skip(1) {
|
||||
Ok(
|
||||
futures::stream::iter(rows.into_iter().skip(1).map(move |r| {
|
||||
//Each row is a dictionary with the headers as keys
|
||||
match &r.value {
|
||||
UntaggedValue::Row(d) => {
|
||||
let mut i = 0;
|
||||
let mut entries = IndexMap::new();
|
||||
for (_, v) in d.entries.iter() {
|
||||
for (i, (_, v)) in d.entries.iter().enumerate() {
|
||||
entries.insert(headers[i].clone(), v.clone());
|
||||
i += 1;
|
||||
}
|
||||
yield Ok(ReturnSuccess::Value(UntaggedValue::Row(Dictionary{entries}).into_value(r.tag.clone())))
|
||||
Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::Row(Dictionary { entries }).into_value(r.tag.clone()),
|
||||
))
|
||||
}
|
||||
_ => yield Err(ShellError::unexpected_eof("Couldn't iterate through rows, was the input a properly formatted table?", r.tag.span))
|
||||
_ => Err(ShellError::unexpected_eof(
|
||||
"Couldn't iterate through rows, was the input a properly formatted table?",
|
||||
r.tag.span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}))
|
||||
.to_output_stream(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -36,70 +36,93 @@ 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(®istry).await?;
|
||||
if let Some(document) = rest.get(0) {
|
||||
if document.item == "commands" {
|
||||
let mut sorted_names = registry.names();
|
||||
sorted_names.sort();
|
||||
for cmd in sorted_names {
|
||||
let (HelpArgs { rest }, ..) = args.process(®istry).await?;
|
||||
|
||||
if !rest.is_empty() {
|
||||
if rest[0].item == "commands" {
|
||||
let mut sorted_names = registry.names();
|
||||
sorted_names.sort();
|
||||
|
||||
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());
|
||||
}
|
||||
} 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(), ®istry)).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(), ®istry)).into_value(Tag::unknown())));
|
||||
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) {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(get_help(command.stream_command(), ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Can't find command (use 'help commands' for full list)",
|
||||
"can't find command",
|
||||
document.tag.span,
|
||||
));
|
||||
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(), ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
} else {
|
||||
let msg = r#"Welcome to Nushell.
|
||||
Err(ShellError::labeled_error(
|
||||
"Can't find command (use 'help commands' for full list)",
|
||||
"can't find command",
|
||||
rest[0].tag.span,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
let msg = r#"Welcome to Nushell.
|
||||
|
||||
Here are some tips to help you get started.
|
||||
* help commands - list all available commands
|
||||
@ -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(stream.to_output_stream())
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(msg).into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
|
@ -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,95 +70,136 @@ 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(®istry).await?;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
let Tagged { item: group_by, .. } = column_name.clone();
|
||||
let (HistogramArgs { column_name, rest }, input) = args.process(®istry).await?;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
let values = UntaggedValue::table(&values).into_value(&name);
|
||||
|
||||
let groups = group(&column_name, values, &name)?;
|
||||
let group_labels = columns_sorted(Some(group_by.clone()), &groups, &name);
|
||||
let sorted = t_sort(Some(group_by.clone()), None, &groups, &name)?;
|
||||
let evaled = evaluate(&sorted, None, &name)?;
|
||||
let reduced = reduce(&evaled, None, &name)?;
|
||||
let maxima = map_max(&reduced, None, &name)?;
|
||||
let percents = percentages(&reduced, maxima, &name)?;
|
||||
let groups = group(&Some(column_name.clone()), &values, &name)?;
|
||||
let group_labels = columns_sorted(Some(column_name.clone()), &groups, &name);
|
||||
let sorted = t_sort(Some(column_name.clone()), None, &groups, &name)?;
|
||||
let evaled = evaluate(&sorted, None, &name)?;
|
||||
let reduced = reduce(&evaled, None, &name)?;
|
||||
let maxima = map_max(&reduced, None, &name)?;
|
||||
let percents = percentages(&reduced, maxima, &name)?;
|
||||
|
||||
match percents {
|
||||
Value {
|
||||
value: UntaggedValue::Table(datasets),
|
||||
..
|
||||
} => {
|
||||
match percents {
|
||||
Value {
|
||||
value: UntaggedValue::Table(datasets),
|
||||
..
|
||||
} => {
|
||||
let mut idx = 0;
|
||||
|
||||
let mut idx = 0;
|
||||
let column_names_supplied: Vec<_> = rest.iter().map(|f| f.item.clone()).collect();
|
||||
|
||||
let column_names_supplied: Vec<_> = rest.iter().map(|f| f.item.clone()).collect();
|
||||
let frequency_column_name = if column_names_supplied.is_empty() {
|
||||
"frequency".to_string()
|
||||
} else {
|
||||
column_names_supplied[0].clone()
|
||||
};
|
||||
|
||||
let frequency_column_name = if column_names_supplied.is_empty() {
|
||||
"frequency".to_string()
|
||||
} else {
|
||||
column_names_supplied[0].clone()
|
||||
};
|
||||
let column = (*column_name).clone();
|
||||
|
||||
let column = (*column_name).clone();
|
||||
let count_column_name = "count".to_string();
|
||||
let count_shell_error = ShellError::labeled_error(
|
||||
"Unable to load group count",
|
||||
"unabled to load group count",
|
||||
&name,
|
||||
);
|
||||
let mut count_values: Vec<u64> = Vec::new();
|
||||
|
||||
let count_column_name = "count".to_string();
|
||||
let count_shell_error = ShellError::labeled_error("Unable to load group count", "unabled to load group count", &name);
|
||||
let mut count_values: Vec<u64> = Vec::new();
|
||||
|
||||
for table_entry in reduced.table_entries() {
|
||||
match table_entry {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => {
|
||||
for i in list {
|
||||
if let Ok(count) = i.value.clone().into_value(&name).as_u64() {
|
||||
count_values.push(count);
|
||||
} else {
|
||||
yield Err(count_shell_error);
|
||||
return;
|
||||
}
|
||||
for table_entry in reduced.table_entries() {
|
||||
match table_entry {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => {
|
||||
for i in list {
|
||||
if let Ok(count) = i.value.clone().into_value(&name).as_u64() {
|
||||
count_values.push(count);
|
||||
} else {
|
||||
return Err(count_shell_error);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
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(stream.to_output_stream())
|
||||
_ => Ok(OutputStream::empty()),
|
||||
}
|
||||
}
|
||||
|
||||
fn percentages(values: &Value, max: Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error("Could not open history", "history file could not be opened", tag.clone()));
|
||||
}
|
||||
};
|
||||
Ok(stream.to_output_stream())
|
||||
let history_path = HistoryFile::path();
|
||||
let file = File::open(history_path);
|
||||
if let Ok(file) = file {
|
||||
let reader = BufReader::new(file);
|
||||
let output = reader.lines().filter_map(move |line| match line {
|
||||
Ok(line) => Some(ReturnSuccess::value(
|
||||
UntaggedValue::string(line).into_value(tag.clone()),
|
||||
)),
|
||||
Err(_) => None,
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(output).to_output_stream())
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Could not open history",
|
||||
"history file could not be opened",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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(®istry).await?;
|
||||
while let Some(row) = input.next().await {
|
||||
match row {
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => match row.insert_data_at_column_path(&column, value.clone()) {
|
||||
Ok(v) => yield Ok(ReturnSuccess::Value(v)),
|
||||
Err(err) => yield Err(err),
|
||||
},
|
||||
let (InsertArgs { column, value }, input) = args.process(®istry).await?;
|
||||
|
||||
Value { tag, ..} => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
Ok(input
|
||||
.map(move |row| match row {
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => match row.insert_data_at_column_path(&column, value.clone()) {
|
||||
Ok(v) => Ok(ReturnSuccess::Value(v)),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
Ok(stream.to_output_stream())
|
||||
Value { tag, .. } => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
)),
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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(®istry).await?;
|
||||
while let Some(value) = input.next().await {
|
||||
let (IsEmptyArgs { rest }, input) = args.process(®istry).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)]
|
||||
|
@ -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(®istry).await?;
|
||||
let mut rows_desired = if let Some(quantity) = rows {
|
||||
*quantity
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
while let Some(input) = input.next().await {
|
||||
if rows_desired > 0 {
|
||||
yield ReturnSuccess::value(input);
|
||||
rows_desired -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let (KeepArgs { rows }, input) = args.process(®istry).await?;
|
||||
let rows_desired = if let Some(quantity) = rows {
|
||||
*quantity
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(input.take(rows_desired).to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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(®istry).await?;
|
||||
let registry = Arc::new(registry.clone());
|
||||
let scope = Arc::new(args.call_info.scope.clone());
|
||||
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
let call_info = args.evaluate_once(®istry).await?;
|
||||
|
||||
let condition = match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
|
||||
let condition = Arc::new(match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
while let Some(item) = call_info.input.next().await {
|
||||
let condition = condition.clone();
|
||||
trace!("ITEM = {:?}", item);
|
||||
let result =
|
||||
evaluate_baseline_expr(&*condition, ®istry, &item, &scope.vars, &scope.env)
|
||||
.await;
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
let return_value = match result {
|
||||
Ok(ref v) if v.is_true() => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
if return_value {
|
||||
yield ReturnSuccess::value(item);
|
||||
} else {
|
||||
break;
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Value { tag, .. } => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(call_info
|
||||
.input
|
||||
.take_while(move |item| {
|
||||
let condition = condition.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = scope.clone();
|
||||
let item = item.clone();
|
||||
trace!("ITEM = {:?}", item);
|
||||
|
||||
async move {
|
||||
let result = evaluate_baseline_expr(
|
||||
&*condition,
|
||||
®istry,
|
||||
&item,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
match result {
|
||||
Ok(ref v) if v.is_true() => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(®istry).await?;
|
||||
let registry = Arc::new(registry.clone());
|
||||
let scope = Arc::new(args.call_info.scope.clone());
|
||||
let call_info = args.evaluate_once(®istry).await?;
|
||||
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
|
||||
let condition = match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
let condition = Arc::new(match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
while let Some(item) = call_info.input.next().await {
|
||||
let condition = condition.clone();
|
||||
trace!("ITEM = {:?}", item);
|
||||
let result =
|
||||
evaluate_baseline_expr(&*condition, ®istry, &item, &scope.vars, &scope.env)
|
||||
.await;
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
let return_value = match result {
|
||||
Ok(ref v) if v.is_true() => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if return_value {
|
||||
yield ReturnSuccess::value(item);
|
||||
} else {
|
||||
break;
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Value { tag, .. } => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(call_info
|
||||
.input
|
||||
.take_while(move |item| {
|
||||
let condition = condition.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = scope.clone();
|
||||
let item = item.clone();
|
||||
|
||||
trace!("ITEM = {:?}", item);
|
||||
|
||||
async move {
|
||||
let result = evaluate_baseline_expr(
|
||||
&*condition,
|
||||
®istry,
|
||||
&item,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
match result {
|
||||
Ok(ref v) if v.is_true() => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,63 +62,60 @@ impl WholeStreamCommand for Kill {
|
||||
}
|
||||
}
|
||||
|
||||
fn kill(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
async fn kill(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let (KillArgs {
|
||||
let (
|
||||
KillArgs {
|
||||
pid,
|
||||
rest,
|
||||
force,
|
||||
quiet,
|
||||
}, mut input) = args.process(®istry).await?;
|
||||
let mut cmd = if cfg!(windows) {
|
||||
let mut cmd = Command::new("taskkill");
|
||||
},
|
||||
..,
|
||||
) = args.process(®istry).await?;
|
||||
let mut cmd = if cfg!(windows) {
|
||||
let mut cmd = Command::new("taskkill");
|
||||
|
||||
if *force {
|
||||
cmd.arg("/F");
|
||||
}
|
||||
if *force {
|
||||
cmd.arg("/F");
|
||||
}
|
||||
|
||||
cmd.arg("/PID");
|
||||
cmd.arg(pid.item().to_string());
|
||||
|
||||
// each pid must written as `/PID 0` otherwise
|
||||
// taskkill will act as `killall` unix command
|
||||
for id in &rest {
|
||||
cmd.arg("/PID");
|
||||
cmd.arg(pid.item().to_string());
|
||||
|
||||
// each pid must written as `/PID 0` otherwise
|
||||
// taskkill will act as `killall` unix command
|
||||
for id in &rest {
|
||||
cmd.arg("/PID");
|
||||
cmd.arg(id.item().to_string());
|
||||
}
|
||||
|
||||
cmd
|
||||
} else {
|
||||
let mut cmd = Command::new("kill");
|
||||
|
||||
if *force {
|
||||
cmd.arg("-9");
|
||||
}
|
||||
|
||||
cmd.arg(pid.item().to_string());
|
||||
|
||||
cmd.args(rest.iter().map(move |id| id.item().to_string()));
|
||||
|
||||
cmd
|
||||
};
|
||||
|
||||
// pipe everything to null
|
||||
if *quiet {
|
||||
cmd.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
cmd.arg(id.item().to_string());
|
||||
}
|
||||
|
||||
cmd.status().expect("failed to execute shell command");
|
||||
cmd
|
||||
} else {
|
||||
let mut cmd = Command::new("kill");
|
||||
|
||||
if false {
|
||||
yield ReturnSuccess::value(UntaggedValue::nothing().into_value(Tag::unknown()));
|
||||
if *force {
|
||||
cmd.arg("-9");
|
||||
}
|
||||
|
||||
cmd.arg(pid.item().to_string());
|
||||
|
||||
cmd.args(rest.iter().map(move |id| id.item().to_string()));
|
||||
|
||||
cmd
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
// pipe everything to null
|
||||
if *quiet {
|
||||
cmd.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
}
|
||||
|
||||
cmd.status().expect("failed to execute shell command");
|
||||
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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(®istry).await.unwrap();
|
||||
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;
|
||||
leftover.clear();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let name_span = tag.span;
|
||||
|
||||
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;
|
||||
leftover.clear();
|
||||
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Line(st)),
|
||||
..
|
||||
} => {
|
||||
let st = (&*leftover_string).clone() + &st;
|
||||
if let Some(leftover) = Arc::get_mut(&mut leftover) {
|
||||
leftover.clear();
|
||||
}
|
||||
let mut lines: Vec<String> = st.lines().map(|x| x.to_string()).collect();
|
||||
if !ends_with_line_ending(&st) {
|
||||
if let Some(last) = lines.pop() {
|
||||
leftover_string = last;
|
||||
} else {
|
||||
if let Some(leftover_string) = Arc::get_mut(&mut leftover_string) {
|
||||
leftover_string.clear();
|
||||
leftover_string.push_str(&last);
|
||||
}
|
||||
} else if let Some(leftover_string) = Arc::get_mut(&mut leftover_string) {
|
||||
leftover_string.clear();
|
||||
}
|
||||
} else {
|
||||
} else if let Some(leftover_string) = Arc::get_mut(&mut leftover_string) {
|
||||
leftover_string.clear();
|
||||
}
|
||||
|
||||
let success_lines: Vec<_> = lines.iter().map(|x| ReturnSuccess::value(UntaggedValue::line(x).into_untagged_value())).collect();
|
||||
yield futures::stream::iter(success_lines)
|
||||
let success_lines: Vec<_> = lines
|
||||
.iter()
|
||||
.map(|x| ReturnSuccess::value(UntaggedValue::line(x).into_untagged_value()))
|
||||
.collect();
|
||||
futures::stream::iter(success_lines)
|
||||
}
|
||||
Some( Value { tag: value_span, ..}) => {
|
||||
yield futures::stream::iter(vec![Err(ShellError::labeled_error_with_secondary(
|
||||
"Expected a string from pipeline",
|
||||
"requires string input",
|
||||
name_span,
|
||||
"value originates from here",
|
||||
value_span,
|
||||
))]);
|
||||
}
|
||||
None => {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::EndOfStream),
|
||||
..
|
||||
} => {
|
||||
if !leftover.is_empty() {
|
||||
let mut st = leftover_string.clone();
|
||||
if let Ok(extra) = String::from_utf8(leftover) {
|
||||
let mut st = (&*leftover_string).clone();
|
||||
if let Ok(extra) = String::from_utf8((&*leftover).clone()) {
|
||||
st.push_str(&extra);
|
||||
}
|
||||
yield futures::stream::iter(vec![ReturnSuccess::value(UntaggedValue::string(st).into_untagged_value())])
|
||||
// futures::stream::iter(vec![ReturnSuccess::value(
|
||||
// UntaggedValue::string(st).into_untagged_value(),
|
||||
// )])
|
||||
if !st.is_empty() {
|
||||
futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::string(&*leftover_string).into_untagged_value(),
|
||||
)])
|
||||
} else {
|
||||
futures::stream::iter(vec![])
|
||||
}
|
||||
} else {
|
||||
futures::stream::iter(vec![])
|
||||
}
|
||||
break;
|
||||
}
|
||||
Value {
|
||||
tag: value_span, ..
|
||||
} => futures::stream::iter(vec![Err(ShellError::labeled_error_with_secondary(
|
||||
"Expected a string from pipeline",
|
||||
"requires string input",
|
||||
name_span,
|
||||
"value originates from here",
|
||||
value_span,
|
||||
))]),
|
||||
}
|
||||
}
|
||||
if !leftover_string.is_empty() {
|
||||
yield futures::stream::iter(vec![ReturnSuccess::value(UntaggedValue::string(leftover_string).into_untagged_value())]);
|
||||
}
|
||||
}
|
||||
.flatten();
|
||||
Ok(stream.to_output_stream())
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
130
crates/nu-cli/src/commands/math/avg.rs
Normal file
130
crates/nu-cli/src/commands/math/avg.rs
Normal 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 {})
|
||||
}
|
||||
}
|
189
crates/nu-cli/src/commands/math/command.rs
Normal file
189
crates/nu-cli/src/commands/math/command.rs
Normal 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, ®istry.clone()))
|
||||
.into_value(Tag::unknown()),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::commands::math::{
|
||||
avg::average, max::maximum, median::median, min::minimum, mode::mode, sum::summation,
|
||||
utils::calculate, utils::MathFunction,
|
||||
};
|
||||
use nu_plugin::row;
|
||||
use nu_plugin::test_helpers::value::{decimal, int, table};
|
||||
use nu_protocol::Value;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Command {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_math_functions() {
|
||||
struct TestCase {
|
||||
description: &'static str,
|
||||
values: Vec<Value>,
|
||||
expected_err: Option<ShellError>,
|
||||
// Order is: average, minimum, maximum, median, summation
|
||||
expected_res: Vec<Result<Value, ShellError>>,
|
||||
}
|
||||
let tt: Vec<TestCase> = vec![
|
||||
TestCase {
|
||||
description: "Empty data should throw an error",
|
||||
values: Vec::new(),
|
||||
expected_err: Some(ShellError::unexpected("Expected data")),
|
||||
expected_res: Vec::new(),
|
||||
},
|
||||
TestCase {
|
||||
description: "Single value",
|
||||
values: vec![int(10)],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(decimal(10)),
|
||||
Ok(int(10)),
|
||||
Ok(int(10)),
|
||||
Ok(int(10)),
|
||||
Ok(table(&[int(10)])),
|
||||
Ok(int(10)),
|
||||
],
|
||||
},
|
||||
TestCase {
|
||||
description: "Multiple Values",
|
||||
values: vec![int(10), int(20), int(30)],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(decimal(20)),
|
||||
Ok(int(10)),
|
||||
Ok(int(30)),
|
||||
Ok(int(20)),
|
||||
Ok(table(&[int(10), int(20), int(30)])),
|
||||
Ok(int(60)),
|
||||
],
|
||||
},
|
||||
TestCase {
|
||||
description: "Mixed Values",
|
||||
values: vec![int(10), decimal(26.5), decimal(26.5)],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(decimal(21)),
|
||||
Ok(int(10)),
|
||||
Ok(decimal(26.5)),
|
||||
Ok(decimal(26.5)),
|
||||
Ok(table(&[decimal(26.5)])),
|
||||
Ok(decimal(63)),
|
||||
],
|
||||
},
|
||||
TestCase {
|
||||
description: "Negative Values",
|
||||
values: vec![int(-14), int(-11), int(10)],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(decimal(-5)),
|
||||
Ok(int(-14)),
|
||||
Ok(int(10)),
|
||||
Ok(int(-11)),
|
||||
Ok(table(&[int(-14), int(-11), int(10)])),
|
||||
Ok(int(-15)),
|
||||
],
|
||||
},
|
||||
TestCase {
|
||||
description: "Mixed Negative Values",
|
||||
values: vec![decimal(-13.5), decimal(-11.5), int(10)],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(decimal(-5)),
|
||||
Ok(decimal(-13.5)),
|
||||
Ok(int(10)),
|
||||
Ok(decimal(-11.5)),
|
||||
Ok(table(&[decimal(-13.5), decimal(-11.5), int(10)])),
|
||||
Ok(decimal(-15)),
|
||||
],
|
||||
},
|
||||
TestCase {
|
||||
description: "Tables Or Rows",
|
||||
values: vec![
|
||||
row!["col1".to_owned() => int(1), "col2".to_owned() => int(5)],
|
||||
row!["col1".to_owned() => int(2), "col2".to_owned() => int(6)],
|
||||
row!["col1".to_owned() => int(3), "col2".to_owned() => int(7)],
|
||||
row!["col1".to_owned() => int(4), "col2".to_owned() => int(8)],
|
||||
],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(row!["col1".to_owned() => decimal(2.5), "col2".to_owned() => decimal(6.5)]),
|
||||
Ok(row!["col1".to_owned() => int(1), "col2".to_owned() => int(5)]),
|
||||
Ok(row!["col1".to_owned() => int(4), "col2".to_owned() => int(8)]),
|
||||
Ok(row!["col1".to_owned() => decimal(2.5), "col2".to_owned() => decimal(6.5)]),
|
||||
Ok(row![
|
||||
"col1".to_owned() => table(&[int(1), int(2), int(3), int(4)]),
|
||||
"col2".to_owned() => table(&[int(5), int(6), int(7), int(8)])
|
||||
]),
|
||||
Ok(row!["col1".to_owned() => int(10), "col2".to_owned() => int(26)]),
|
||||
],
|
||||
},
|
||||
// TODO-Uncomment once Issue: https://github.com/nushell/nushell/issues/1883 is resolved
|
||||
// TestCase {
|
||||
// description: "Invalid Mixed Values",
|
||||
// values: vec![int(10), decimal(26.5), decimal(26.5), string("math")],
|
||||
// expected_err: Some(ShellError::unimplemented("something")),
|
||||
// expected_res: vec![],
|
||||
// },
|
||||
];
|
||||
let test_tag = Tag::unknown();
|
||||
for tc in tt.iter() {
|
||||
let tc: &TestCase = tc; // Just for type annotations
|
||||
let math_functions: Vec<MathFunction> =
|
||||
vec![average, minimum, maximum, median, mode, summation];
|
||||
let results = math_functions
|
||||
.into_iter()
|
||||
.map(|mf| calculate(&tc.values, &test_tag, mf))
|
||||
.collect_vec();
|
||||
|
||||
if tc.expected_err.is_some() {
|
||||
assert!(
|
||||
results.iter().all(|r| r.is_err()),
|
||||
"Expected all functions to error for test-case: {}",
|
||||
tc.description,
|
||||
);
|
||||
} else {
|
||||
for (i, res) in results.into_iter().enumerate() {
|
||||
assert_eq!(
|
||||
res, tc.expected_res[i],
|
||||
"math function {} failed on test-case {}",
|
||||
i, tc.description
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
crates/nu-cli/src/commands/math/max.rs
Normal file
69
crates/nu-cli/src/commands/math/max.rs
Normal 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 {})
|
||||
}
|
||||
}
|
193
crates/nu-cli/src/commands/math/median.rs
Normal file
193
crates/nu-cli/src/commands/math/median.rs
Normal 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 {})
|
||||
}
|
||||
}
|
69
crates/nu-cli/src/commands/math/min.rs
Normal file
69
crates/nu-cli/src/commands/math/min.rs
Normal 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 {})
|
||||
}
|
||||
}
|
16
crates/nu-cli/src/commands/math/mod.rs
Normal file
16
crates/nu-cli/src/commands/math/mod.rs
Normal 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;
|
94
crates/nu-cli/src/commands/math/mode.rs
Normal file
94
crates/nu-cli/src/commands/math/mode.rs
Normal 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 {})
|
||||
}
|
||||
}
|
106
crates/nu-cli/src/commands/math/sum.rs
Normal file
106
crates/nu-cli/src/commands/math/sum.rs
Normal 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 {})
|
||||
}
|
||||
}
|
66
crates/nu-cli/src/commands/math/utils.rs
Normal file
66
crates/nu-cli/src/commands/math/utils.rs
Normal 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())
|
||||
}
|
||||
}
|
@ -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, ®istry);
|
||||
let name_tag = raw_args.call_info.name_tag.clone();
|
||||
let (merge_args, mut input): (MergeArgs, _) = raw_args.process(®istry).await?;
|
||||
let block = merge_args.block;
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let name_tag = raw_args.call_info.name_tag.clone();
|
||||
let (merge_args, input): (MergeArgs, _) = raw_args.process(®istry).await?;
|
||||
let block = merge_args.block;
|
||||
|
||||
let table: Option<Vec<Value>> = match run_block(&block,
|
||||
&mut context,
|
||||
InputStream::empty(),
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env).await {
|
||||
Ok(mut stream) => Some(stream.drain_vec().await),
|
||||
Err(err) => {
|
||||
yield Err(err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let table = table.unwrap_or_else(|| vec![Value {
|
||||
value: UntaggedValue::row(IndexMap::default()),
|
||||
tag: name_tag,
|
||||
}]);
|
||||
|
||||
let mut idx = 0;
|
||||
|
||||
while let Some(value) = input.next().await {
|
||||
let other = table.get(idx);
|
||||
|
||||
match other {
|
||||
Some(replacement) => {
|
||||
match merge_values(&value.value, &replacement.value) {
|
||||
Ok(merged_value) => yield ReturnSuccess::value(merged_value.into_value(&value.tag)),
|
||||
Err(err) => {
|
||||
let message = format!("The row at {:?} types mismatch", idx);
|
||||
yield Err(ShellError::labeled_error("Could not merge", &message, &value.tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
None => yield ReturnSuccess::value(value),
|
||||
}
|
||||
|
||||
idx += 1;
|
||||
let table: Option<Vec<Value>> = match run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
InputStream::empty(),
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(mut stream) => Some(stream.drain_vec().await),
|
||||
Err(err) => {
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
let table = table.unwrap_or_else(|| {
|
||||
vec![Value {
|
||||
value: UntaggedValue::row(IndexMap::default()),
|
||||
tag: name_tag,
|
||||
}]
|
||||
});
|
||||
|
||||
Ok(input
|
||||
.enumerate()
|
||||
.map(move |(idx, value)| {
|
||||
let other = table.get(idx);
|
||||
|
||||
match other {
|
||||
Some(replacement) => match merge_values(&value.value, &replacement.value) {
|
||||
Ok(merged_value) => ReturnSuccess::value(merged_value.into_value(&value.tag)),
|
||||
Err(_) => {
|
||||
let message = format!("The row at {:?} types mismatch", idx);
|
||||
Err(ShellError::labeled_error(
|
||||
"Could not merge",
|
||||
&message,
|
||||
&value.tag,
|
||||
))
|
||||
}
|
||||
},
|
||||
None => ReturnSuccess::value(value),
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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(®istry).await?;
|
||||
let mut result = shell_manager.mv(args, name)?;
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let shell_manager = args.shell_manager.clone();
|
||||
let (args, _) = args.process(®istry).await?;
|
||||
|
||||
while let Some(item) = result.next().await {
|
||||
yield item;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
shell_manager.mv(args, name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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(®istry).await?;
|
||||
let (
|
||||
NthArgs {
|
||||
row_number,
|
||||
rest: and_rows,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).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![vec![row_number], and_rows]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<Tagged<u64>>>();
|
||||
|
||||
let row_numbers = vec![&row_number, &and_rows]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<&Tagged<u64>>>();
|
||||
|
||||
if row_numbers
|
||||
.iter()
|
||||
.any(|requested| requested.item == idx as u64)
|
||||
{
|
||||
yield ReturnSuccess::value(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(input
|
||||
.enumerate()
|
||||
.filter_map(move |(idx, item)| {
|
||||
futures::future::ready(
|
||||
if row_numbers
|
||||
.iter()
|
||||
.any(|requested| requested.item == idx as u64)
|
||||
{
|
||||
Some(ReturnSuccess::value(item))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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 {
|
||||
description: "Opens \"users.csv\" and creates a table from the data",
|
||||
example: "open users.csv",
|
||||
result: None,
|
||||
}]
|
||||
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(®istry).await?;
|
||||
let result = fetch(&full_path, &path.item, path.tag.span).await;
|
||||
let (
|
||||
OpenArgs {
|
||||
path,
|
||||
raw,
|
||||
encoding,
|
||||
},
|
||||
_,
|
||||
) = args.process(®istry).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,135 +139,270 @@ pub async fn fetch(
|
||||
cwd: &PathBuf,
|
||||
location: &PathBuf,
|
||||
span: Span,
|
||||
encoding: String,
|
||||
) -> Result<(Option<String>, UntaggedValue, Tag), ShellError> {
|
||||
let mut cwd = cwd.clone();
|
||||
let output_encoding: &Encoding = get_encoding(Some("utf-8".to_string()));
|
||||
let input_encoding: &Encoding = get_encoding(Some(encoding.clone()));
|
||||
let mut decoder = input_encoding.new_decoder();
|
||||
let mut encoder = output_encoding.new_encoder();
|
||||
let mut _file: File;
|
||||
let buf = Vec::new();
|
||||
let mut bufwriter = BufWriter::new(buf);
|
||||
|
||||
cwd.push(Path::new(location));
|
||||
if let Ok(cwd) = dunce::canonicalize(cwd) {
|
||||
match std::fs::read(&cwd) {
|
||||
Ok(bytes) => match std::str::from_utf8(&bytes) {
|
||||
Ok(s) => Ok((
|
||||
cwd.extension()
|
||||
.map(|name| name.to_string_lossy().to_string()),
|
||||
UntaggedValue::string(s),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(cwd.to_string_lossy().to_string())),
|
||||
},
|
||||
)),
|
||||
Err(_) => {
|
||||
//Non utf8 data.
|
||||
match (bytes.get(0), bytes.get(1)) {
|
||||
(Some(x), Some(y)) if *x == 0xff && *y == 0xfe => {
|
||||
// Possibly UTF-16 little endian
|
||||
let utf16 = read_le_u16(&bytes[2..]);
|
||||
|
||||
if let Some(utf16) = utf16 {
|
||||
match std::string::String::from_utf16(&utf16) {
|
||||
Ok(s) => Ok((
|
||||
cwd.extension()
|
||||
.map(|name| name.to_string_lossy().to_string()),
|
||||
UntaggedValue::string(s),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
)),
|
||||
Err(_) => Ok((
|
||||
None,
|
||||
UntaggedValue::binary(bytes),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok((
|
||||
None,
|
||||
UntaggedValue::binary(bytes),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
(Some(x), Some(y)) if *x == 0xfe && *y == 0xff => {
|
||||
// Possibly UTF-16 big endian
|
||||
let utf16 = read_be_u16(&bytes[2..]);
|
||||
|
||||
if let Some(utf16) = utf16 {
|
||||
match std::string::String::from_utf16(&utf16) {
|
||||
Ok(s) => Ok((
|
||||
cwd.extension()
|
||||
.map(|name| name.to_string_lossy().to_string()),
|
||||
UntaggedValue::string(s),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
)),
|
||||
Err(_) => Ok((
|
||||
None,
|
||||
UntaggedValue::binary(bytes),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok((
|
||||
None,
|
||||
UntaggedValue::binary(bytes),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => Ok((
|
||||
None,
|
||||
UntaggedValue::binary(bytes),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
)),
|
||||
}
|
||||
if let Ok(cwd) = dunce::canonicalize(&cwd) {
|
||||
if !encoding.is_empty() {
|
||||
// use the encoding string
|
||||
match File::open(&Path::new(&cwd)) {
|
||||
Ok(mut _file) => {
|
||||
convert_via_utf8(
|
||||
&mut decoder,
|
||||
&mut encoder,
|
||||
&mut _file,
|
||||
&mut bufwriter,
|
||||
false,
|
||||
);
|
||||
//bufwriter.flush()?;
|
||||
Ok((
|
||||
cwd.extension()
|
||||
.map(|name| name.to_string_lossy().to_string()),
|
||||
UntaggedValue::string(String::from_utf8_lossy(&bufwriter.buffer())),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(cwd.to_string_lossy().to_string())),
|
||||
},
|
||||
))
|
||||
}
|
||||
},
|
||||
Err(_) => Err(ShellError::labeled_error(
|
||||
"File could not be opened",
|
||||
"file not found",
|
||||
span,
|
||||
)),
|
||||
Err(_) => Err(ShellError::labeled_error(
|
||||
format!("Cannot open {:?} for reading.", &cwd),
|
||||
"file not found",
|
||||
span,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
// Do the old stuff
|
||||
match std::fs::read(&cwd) {
|
||||
Ok(bytes) => match std::str::from_utf8(&bytes) {
|
||||
Ok(s) => Ok((
|
||||
cwd.extension()
|
||||
.map(|name| name.to_string_lossy().to_string()),
|
||||
UntaggedValue::string(s),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(cwd.to_string_lossy().to_string())),
|
||||
},
|
||||
)),
|
||||
Err(_) => {
|
||||
//Non utf8 data.
|
||||
match (bytes.get(0), bytes.get(1)) {
|
||||
(Some(x), Some(y)) if *x == 0xff && *y == 0xfe => {
|
||||
// Possibly UTF-16 little endian
|
||||
let utf16 = read_le_u16(&bytes[2..]);
|
||||
|
||||
if let Some(utf16) = utf16 {
|
||||
match std::string::String::from_utf16(&utf16) {
|
||||
Ok(s) => Ok((
|
||||
cwd.extension()
|
||||
.map(|name| name.to_string_lossy().to_string()),
|
||||
UntaggedValue::string(s),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
)),
|
||||
Err(_) => Ok((
|
||||
None,
|
||||
UntaggedValue::binary(bytes),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok((
|
||||
None,
|
||||
UntaggedValue::binary(bytes),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
(Some(x), Some(y)) if *x == 0xfe && *y == 0xff => {
|
||||
// Possibly UTF-16 big endian
|
||||
let utf16 = read_be_u16(&bytes[2..]);
|
||||
|
||||
if let Some(utf16) = utf16 {
|
||||
match std::string::String::from_utf16(&utf16) {
|
||||
Ok(s) => Ok((
|
||||
cwd.extension()
|
||||
.map(|name| name.to_string_lossy().to_string()),
|
||||
UntaggedValue::string(s),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
)),
|
||||
Err(_) => Ok((
|
||||
None,
|
||||
UntaggedValue::binary(bytes),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok((
|
||||
None,
|
||||
UntaggedValue::binary(bytes),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => Ok((
|
||||
None,
|
||||
UntaggedValue::binary(bytes),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
)),
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(_) => Err(ShellError::labeled_error(
|
||||
format!("Cannot open {:?} for reading.", &cwd),
|
||||
"file not found",
|
||||
span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"File could not be opened",
|
||||
format!("Cannot open {:?} for reading.", &cwd),
|
||||
"file not found",
|
||||
span,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_via_utf8(
|
||||
decoder: &mut Decoder,
|
||||
encoder: &mut Encoder,
|
||||
read: &mut dyn Read,
|
||||
write: &mut dyn Write,
|
||||
last: bool,
|
||||
) {
|
||||
let mut input_buffer = [0u8; 2048];
|
||||
let mut intermediate_buffer_bytes = [0u8; 4096];
|
||||
// Is there a safe way to create a stack-allocated &mut str?
|
||||
let mut intermediate_buffer: &mut str =
|
||||
//unsafe { std::mem::transmute(&mut intermediate_buffer_bytes[..]) };
|
||||
std::str::from_utf8_mut(&mut intermediate_buffer_bytes[..]).expect("error with from_utf8_mut");
|
||||
let mut output_buffer = [0u8; 4096];
|
||||
let mut current_input_ended = false;
|
||||
while !current_input_ended {
|
||||
match read.read(&mut input_buffer) {
|
||||
Err(_) => {
|
||||
print!("Error reading input.");
|
||||
//std::process::exit(-5);
|
||||
}
|
||||
Ok(decoder_input_end) => {
|
||||
current_input_ended = decoder_input_end == 0;
|
||||
let input_ended = last && current_input_ended;
|
||||
let mut decoder_input_start = 0usize;
|
||||
loop {
|
||||
let (decoder_result, decoder_read, decoder_written, _) = decoder.decode_to_str(
|
||||
&input_buffer[decoder_input_start..decoder_input_end],
|
||||
&mut intermediate_buffer,
|
||||
input_ended,
|
||||
);
|
||||
decoder_input_start += decoder_read;
|
||||
|
||||
let last_output = if input_ended {
|
||||
match decoder_result {
|
||||
CoderResult::InputEmpty => true,
|
||||
CoderResult::OutputFull => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Regardless of whether the intermediate buffer got full
|
||||
// or the input buffer was exhausted, let's process what's
|
||||
// in the intermediate buffer.
|
||||
|
||||
if encoder.encoding() == UTF_8 {
|
||||
// If the target is UTF-8, optimize out the encoder.
|
||||
if write
|
||||
.write_all(&intermediate_buffer.as_bytes()[..decoder_written])
|
||||
.is_err()
|
||||
{
|
||||
print!("Error writing output.");
|
||||
//std::process::exit(-7);
|
||||
}
|
||||
} else {
|
||||
let mut encoder_input_start = 0usize;
|
||||
loop {
|
||||
let (encoder_result, encoder_read, encoder_written, _) = encoder
|
||||
.encode_from_utf8(
|
||||
&intermediate_buffer[encoder_input_start..decoder_written],
|
||||
&mut output_buffer,
|
||||
last_output,
|
||||
);
|
||||
encoder_input_start += encoder_read;
|
||||
if write.write_all(&output_buffer[..encoder_written]).is_err() {
|
||||
print!("Error writing output.");
|
||||
//std::process::exit(-6);
|
||||
}
|
||||
match encoder_result {
|
||||
CoderResult::InputEmpty => {
|
||||
break;
|
||||
}
|
||||
CoderResult::OutputFull => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now let's see if we should read again or process the
|
||||
// rest of the current input buffer.
|
||||
match decoder_result {
|
||||
CoderResult::InputEmpty => {
|
||||
break;
|
||||
}
|
||||
CoderResult::OutputFull => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_le_u16(input: &[u8]) -> Option<Vec<u16>> {
|
||||
if input.len() % 2 != 0 || input.len() < 2 {
|
||||
None
|
||||
|
@ -51,92 +51,105 @@ 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(®istry).await?;
|
||||
let input = input.into_vec().await;
|
||||
let (args, input): (PivotArgs, _) = args.process(®istry).await?;
|
||||
let input = input.into_vec().await;
|
||||
|
||||
let descs = merge_descriptors(&input);
|
||||
let descs = merge_descriptors(&input);
|
||||
|
||||
let mut headers: Vec<String> = vec![];
|
||||
let mut headers: Vec<String> = vec![];
|
||||
|
||||
if args.rest.len() > 0 && args.header_row {
|
||||
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 {
|
||||
for i in input.clone() {
|
||||
if let Some(desc) = descs.get(0) {
|
||||
match get_data_by_key(&i, desc[..].spanned_unknown()) {
|
||||
Some(x) => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
yield Err(ShellError::labeled_error("Header row is incomplete and can't be used", "using incomplete header row", name));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error("Header row is incomplete and can't be used", "using incomplete header row", name));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i in 0..=input.len() {
|
||||
if let Some(name) = args.rest.get(i) {
|
||||
headers.push(name.to_string())
|
||||
} else {
|
||||
headers.push(format!("Column{}", i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let descs: Vec<_> = if args.header_row {
|
||||
descs.iter().skip(1).collect()
|
||||
} else {
|
||||
descs.iter().collect()
|
||||
};
|
||||
|
||||
for desc in descs {
|
||||
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()));
|
||||
column_num += 1
|
||||
}
|
||||
|
||||
for i in input.clone() {
|
||||
if args.header_row {
|
||||
for i in input.clone() {
|
||||
if let Some(desc) = descs.get(0) {
|
||||
match get_data_by_key(&i, desc[..].spanned_unknown()) {
|
||||
Some(x) => {
|
||||
dict.insert_value(headers[column_num].clone(), x.clone());
|
||||
if let Ok(s) = x.as_string() {
|
||||
headers.push(s.to_string());
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Header row needs string headers",
|
||||
"used non-string headers",
|
||||
name,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
dict.insert_untagged(headers[column_num].clone(), UntaggedValue::nothing());
|
||||
return Err(ShellError::labeled_error(
|
||||
"Header row is incomplete and can't be used",
|
||||
"using incomplete header row",
|
||||
name,
|
||||
));
|
||||
}
|
||||
}
|
||||
column_num += 1;
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Header row is incomplete and can't be used",
|
||||
"using incomplete header row",
|
||||
name,
|
||||
));
|
||||
}
|
||||
|
||||
yield ReturnSuccess::value(dict.into_value());
|
||||
}
|
||||
} else {
|
||||
for i in 0..=input.len() {
|
||||
if let Some(name) = args.rest.get(i) {
|
||||
headers.push(name.to_string())
|
||||
} else {
|
||||
headers.push(format!("Column{}", i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let descs: Vec<_> = if args.header_row {
|
||||
descs.into_iter().skip(1).collect()
|
||||
} else {
|
||||
descs
|
||||
};
|
||||
|
||||
Ok(OutputStream::new(stream))
|
||||
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()),
|
||||
);
|
||||
column_num += 1
|
||||
}
|
||||
|
||||
for i in input.clone() {
|
||||
match get_data_by_key(&i, desc[..].spanned_unknown()) {
|
||||
Some(x) => {
|
||||
dict.insert_value(headers[column_num].clone(), x.clone());
|
||||
}
|
||||
_ => {
|
||||
dict.insert_untagged(headers[column_num].clone(), UntaggedValue::nothing());
|
||||
}
|
||||
}
|
||||
column_num += 1;
|
||||
}
|
||||
|
||||
ReturnSuccess::value(dict.into_value())
|
||||
}))
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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,207 +75,216 @@ pub fn filter_plugin(
|
||||
|
||||
let scope = args.call_info.scope.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let mut args = args.evaluate_once_with_scope(®istry, &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 mut child = std::process::Command::new(path)
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to spawn child process");
|
||||
let args = args.evaluate_once_with_scope(®istry, &scope).await?;
|
||||
|
||||
let call_info = args.call_info.clone();
|
||||
let mut child = std::process::Command::new(path)
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to spawn child process");
|
||||
|
||||
trace!("filtering :: {:?}", call_info);
|
||||
let call_info = args.call_info.clone();
|
||||
|
||||
// Beginning of the stream
|
||||
{
|
||||
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
|
||||
trace!("filtering :: {:?}", call_info);
|
||||
|
||||
let mut reader = BufReader::new(stdout);
|
||||
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");
|
||||
|
||||
let request = JsonRpc::new("begin_filter", call_info.clone());
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
let mut reader = BufReader::new(stdout);
|
||||
|
||||
match request_raw {
|
||||
Err(_) => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Could not load json from plugin",
|
||||
"could not load json from plugin",
|
||||
&call_info.name_tag,
|
||||
));
|
||||
}
|
||||
Ok(request_raw) => match stdin.write(format!("{}\n", request_raw).as_bytes()) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
yield Err(ShellError::unexpected(format!("{}", err)));
|
||||
}
|
||||
},
|
||||
}
|
||||
let request = JsonRpc::new("begin_filter", call_info.clone());
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
|
||||
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);
|
||||
match request_raw {
|
||||
Err(_) => {
|
||||
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(_) => {}
|
||||
Err(err) => {
|
||||
return OutputStream::one(Err(ShellError::unexpected(
|
||||
format!("{}", err),
|
||||
)));
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let mut input = String::new();
|
||||
match reader.read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
let response = serde_json::from_str::<NuResult>(&input);
|
||||
match response {
|
||||
Ok(NuResult::response { params }) => match params {
|
||||
Ok(params) => futures::stream::iter(params).to_output_stream(),
|
||||
Err(e) => futures::stream::iter(vec![ReturnValue::Err(e)])
|
||||
.to_output_stream(),
|
||||
},
|
||||
|
||||
Err(e) => OutputStream::one(Err(
|
||||
ShellError::untagged_runtime_error(format!(
|
||||
"Error while processing begin_filter response: {:?} {}",
|
||||
e, input
|
||||
)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
Err(e) => OutputStream::one(Err(ShellError::untagged_runtime_error(
|
||||
format!("Error while reading begin_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");
|
||||
|
||||
let mut reader = BufReader::new(stdout);
|
||||
|
||||
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("end_filter", vec![]);
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
|
||||
match request_raw {
|
||||
Err(_) => {
|
||||
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(_) => {}
|
||||
Err(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) => {
|
||||
yield Err(ShellError::untagged_runtime_error(format!(
|
||||
"Error while processing begin_filter response: {:?} {}",
|
||||
e, input
|
||||
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
|
||||
}
|
||||
Err(e) => {
|
||||
yield 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");
|
||||
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 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
|
||||
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),
|
||||
)));
|
||||
}
|
||||
}
|
||||
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!(
|
||||
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) => futures::stream::iter(params).to_output_stream(),
|
||||
Err(e) => futures::stream::iter(vec![ReturnValue::Err(e)])
|
||||
.to_output_stream(),
|
||||
},
|
||||
Err(e) => OutputStream::one(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
|
||||
)));
|
||||
Err(e) => OutputStream::one(Err(ShellError::untagged_runtime_error(
|
||||
format!("Error while reading filter response: {:?}", e),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// post stream contents
|
||||
{
|
||||
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
|
||||
|
||||
let mut reader = BufReader::new(stdout);
|
||||
|
||||
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("end_filter", vec![]);
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
|
||||
match request_raw {
|
||||
Err(_) => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Could not load json from plugin",
|
||||
"could not load json from plugin",
|
||||
&call_info.name_tag,
|
||||
));
|
||||
}
|
||||
Ok(request_raw) => match stdin.write(format!("{}\n", request_raw).as_bytes()) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
yield Err(ShellError::unexpected(format!("{}", err)));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
let mut input = String::new();
|
||||
match reader.read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
let response = serde_json::from_str::<NuResult>(&input);
|
||||
match response {
|
||||
Ok(NuResult::response { params }) => match params {
|
||||
Ok(params) => for param in params { yield param },
|
||||
Err(e) => {
|
||||
yield ReturnValue::Err(e);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
yield Err(ShellError::untagged_runtime_error(format!(
|
||||
"Error while processing end_filter response: {:?} {}",
|
||||
e, input
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
yield Err(ShellError::untagged_runtime_error(format!(
|
||||
"Error while reading end_filter response: {:?}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// End of the stream
|
||||
{
|
||||
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
|
||||
|
||||
let mut reader = BufReader::new(stdout);
|
||||
|
||||
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("quit", vec![]);
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
|
||||
match request_raw {
|
||||
Ok(request_raw) => {
|
||||
let _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error
|
||||
}
|
||||
Err(e) => {
|
||||
yield Err(ShellError::untagged_runtime_error(format!(
|
||||
"Error while processing quit response: {:?}",
|
||||
e
|
||||
)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = child.wait();
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[derive(new)]
|
||||
@ -304,49 +313,47 @@ 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(®istry).await?;
|
||||
let call_info = args.call_info.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let call_info = args.call_info.clone();
|
||||
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
|
||||
let request = JsonRpc::new("sink", (call_info.clone(), input));
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
if let Ok(request_raw) = request_raw {
|
||||
if let Ok(mut tmpfile) = tempfile::NamedTempFile::new() {
|
||||
let _ = writeln!(tmpfile, "{}", request_raw);
|
||||
let _ = tmpfile.flush();
|
||||
let request = JsonRpc::new("sink", (call_info.clone(), input));
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
if let Ok(request_raw) = request_raw {
|
||||
if let Ok(mut tmpfile) = tempfile::NamedTempFile::new() {
|
||||
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();
|
||||
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());
|
||||
}
|
||||
} else {
|
||||
yield Err(ShellError::untagged_runtime_error("Could not create process for sink command"));
|
||||
}
|
||||
Ok(OutputStream::empty())
|
||||
} else {
|
||||
yield Err(ShellError::untagged_runtime_error("Could not open file to send sink command message"));
|
||||
Err(ShellError::untagged_runtime_error(
|
||||
"Could not create process for sink command",
|
||||
))
|
||||
}
|
||||
} else {
|
||||
yield Err(ShellError::untagged_runtime_error("Could not create message to sink command"));
|
||||
Err(ShellError::untagged_runtime_error(
|
||||
"Could not open file to send sink command message",
|
||||
))
|
||||
}
|
||||
};
|
||||
Ok(OutputStream::new(stream))
|
||||
} else {
|
||||
Err(ShellError::untagged_runtime_error(
|
||||
"Could not create message to sink command",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -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(®istry).await?;
|
||||
let (PrependArgs { row }, input) = args.process(®istry).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)]
|
||||
|
97
crates/nu-cli/src/commands/random/bool.rs
Normal file
97
crates/nu-cli/src/commands/random/bool.rs
Normal file
@ -0,0 +1,97 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
use rand::prelude::{thread_rng, Rng};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct BoolArgs {
|
||||
bias: Option<Tagged<f64>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"random bool"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("random bool").named(
|
||||
"bias",
|
||||
SyntaxShape::Number,
|
||||
"Adjusts the probability of a \"true\" outcome",
|
||||
Some('b'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Generate a random boolean value"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
bool_command(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Generate a random boolean value",
|
||||
example: "random bool",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Generate a random boolean value with a 75% chance of \"true\"",
|
||||
example: "random bool --bias 0.75",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn bool_command(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (BoolArgs { bias }, _) = args.process(®istry).await?;
|
||||
|
||||
let mut probability = 0.5;
|
||||
|
||||
if let Some(prob) = bias {
|
||||
probability = *prob as f64;
|
||||
|
||||
let probability_is_valid = 0.0 <= probability && probability <= 1.0;
|
||||
|
||||
if !probability_is_valid {
|
||||
return Err(ShellError::labeled_error(
|
||||
"The probability is invalid",
|
||||
"invalid probability",
|
||||
prob.span(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut rng = thread_rng();
|
||||
let bool_result: bool = rng.gen_bool(probability);
|
||||
let bool_untagged_value = UntaggedValue::boolean(bool_result);
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(bool_untagged_value)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
32
crates/nu-cli/src/commands/random/command.rs
Normal file
32
crates/nu-cli/src/commands/random/command.rs
Normal 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, ®istry.clone()))
|
||||
.into_value(Tag::unknown()),
|
||||
))))
|
||||
}
|
||||
}
|
103
crates/nu-cli/src/commands/random/dice.rs
Normal file
103
crates/nu-cli/src/commands/random/dice.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
use rand::prelude::{thread_rng, Rng};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DiceArgs {
|
||||
dice: Option<Tagged<u32>>,
|
||||
sides: Option<Tagged<u32>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"random dice"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("random dice")
|
||||
.named(
|
||||
"dice",
|
||||
SyntaxShape::Int,
|
||||
"The amount of dice being rolled",
|
||||
Some('d'),
|
||||
)
|
||||
.named(
|
||||
"sides",
|
||||
SyntaxShape::Int,
|
||||
"The amount of sides a die has",
|
||||
Some('s'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Generate a random dice roll"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
dice(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Roll 1 dice with 6 sides each",
|
||||
example: "random dice",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Roll 10 dice with 12 sides each",
|
||||
example: "random dice -d 10 -s 12",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn dice(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (DiceArgs { dice, sides }, _) = args.process(®istry).await?;
|
||||
|
||||
let dice = if let Some(dice_tagged) = dice {
|
||||
*dice_tagged
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
let sides = if let Some(sides_tagged) = sides {
|
||||
*sides_tagged
|
||||
} else {
|
||||
6
|
||||
};
|
||||
|
||||
let iter = (0..dice).map(move |_| {
|
||||
let mut thread_rng = thread_rng();
|
||||
UntaggedValue::int(thread_rng.gen_range(1, sides + 1)).into_value(tag.clone())
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(iter).to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
11
crates/nu-cli/src/commands/random/mod.rs
Normal file
11
crates/nu-cli/src/commands/random/mod.rs
Normal file
@ -0,0 +1,11 @@
|
||||
pub mod command;
|
||||
|
||||
pub mod bool;
|
||||
pub mod dice;
|
||||
pub mod uuid;
|
||||
|
||||
pub use command::Command as Random;
|
||||
|
||||
pub use self::bool::SubCommand as RandomBool;
|
||||
pub use dice::SubCommand as RandomDice;
|
||||
pub use uuid::SubCommand as RandomUUID;
|
59
crates/nu-cli/src/commands/random/uuid.rs
Normal file
59
crates/nu-cli/src/commands/random/uuid.rs
Normal 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 {})
|
||||
}
|
||||
}
|
@ -36,28 +36,25 @@ 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(®istry).await?;
|
||||
let range = area.item;
|
||||
let (from, _) = range.from;
|
||||
let (to, _) = range.to;
|
||||
let (RangeArgs { area }, input) = args.process(®istry).await?;
|
||||
let range = area.item;
|
||||
let (from, _) = range.from;
|
||||
let (to, _) = range.to;
|
||||
|
||||
let from = *from as usize;
|
||||
let to = *to as usize;
|
||||
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)]
|
||||
|
@ -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(®istry).await?;
|
||||
if fields.is_empty() {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Reject requires fields",
|
||||
"needs parameter",
|
||||
name,
|
||||
));
|
||||
return;
|
||||
}
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (RejectArgs { rest: fields }, input) = args.process(®istry).await?;
|
||||
if fields.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Reject requires fields",
|
||||
"needs parameter",
|
||||
name,
|
||||
));
|
||||
}
|
||||
|
||||
let fields: Vec<_> = fields.iter().map(|f| f.item.clone()).collect();
|
||||
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)]
|
||||
|
@ -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(®istry).await?;
|
||||
let mut new_column_names = vec![vec![column_name]];
|
||||
new_column_names.push(rest);
|
||||
let (Arguments { column_name, rest }, input) = args.process(®istry).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<_>>();
|
||||
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)]
|
||||
|
@ -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(®istry).await?;
|
||||
let (input, _args) = args.parts();
|
||||
let args = args.evaluate_once(®istry).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())
|
||||
let input = input.collect::<Vec<_>>().await;
|
||||
Ok(futures::stream::iter(input.into_iter().rev().map(ReturnSuccess::value)).to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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,69 +38,35 @@ 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();
|
||||
let mut block = self.block.clone();
|
||||
block.set_is_last(call_info.args.is_last);
|
||||
|
||||
let alias_command = self.clone();
|
||||
let mut context = Context::from_args(&args, ®istry);
|
||||
let input = args.input;
|
||||
|
||||
let stream = async_stream! {
|
||||
let mut scope = call_info.scope.clone();
|
||||
let evaluated = call_info.evaluate(®istry).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());
|
||||
}
|
||||
let mut scope = call_info.scope.clone();
|
||||
let evaluated = call_info.evaluate(®istry).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());
|
||||
}
|
||||
}
|
||||
|
||||
let result = 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())
|
||||
// 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?
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
@ -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,69 +99,49 @@ 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(),
|
||||
args: ExternalArgs {
|
||||
list: positionals.collect(),
|
||||
span: args.call_info.args.span,
|
||||
},
|
||||
};
|
||||
|
||||
// If we're in interactive mode, we will "auto cd". That is, instead of interpreting
|
||||
// this as an external command, we will see it as a path and `cd` into it.
|
||||
if is_interactive {
|
||||
if let Some(path) = maybe_autocd_dir(&command, &mut external_context).await {
|
||||
let cd_args = CdArgs {
|
||||
path: Some(Tagged {
|
||||
item: PathBuf::from(path),
|
||||
tag: args.call_info.name_tag.clone(),
|
||||
})
|
||||
};
|
||||
|
||||
let 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;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
yield Err(e);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let scope = args.call_info.scope.clone();
|
||||
let is_last = args.call_info.args.is_last;
|
||||
let input = args.input;
|
||||
let result = external::run_external_command(
|
||||
command,
|
||||
&mut external_context,
|
||||
input,
|
||||
&scope,
|
||||
is_last,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
while let Some(value) = stream.next().await {
|
||||
yield Ok(ReturnSuccess::Value(value));
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
yield Err(e);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
let command = ExternalCommand {
|
||||
name,
|
||||
name_tag: args.call_info.name_tag.clone(),
|
||||
args: ExternalArgs {
|
||||
list: positionals.collect(),
|
||||
span: args.call_info.args.span,
|
||||
},
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
// If we're in interactive mode, we will "auto cd". That is, instead of interpreting
|
||||
// this as an external command, we will see it as a path and `cd` into it.
|
||||
if is_interactive {
|
||||
if let Some(path) = maybe_autocd_dir(&command, &mut external_context).await {
|
||||
let cd_args = CdArgs {
|
||||
path: Some(Tagged {
|
||||
item: PathBuf::from(path),
|
||||
tag: args.call_info.name_tag.clone(),
|
||||
}),
|
||||
};
|
||||
|
||||
let result = external_context
|
||||
.shell_manager
|
||||
.cd(cd_args, args.call_info.name_tag.clone());
|
||||
match result {
|
||||
Ok(stream) => return Ok(stream.to_output_stream()),
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
match result {
|
||||
Ok(stream) => Ok(stream.to_output_stream()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,101 +172,96 @@ 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(®istry).await?;
|
||||
let input: Vec<Value> = input.collect().await;
|
||||
if path.is_none() {
|
||||
// If there is no filename, check the metadata for the anchor filename
|
||||
if input.len() > 0 {
|
||||
let anchor = input[0].tag.anchor();
|
||||
match anchor {
|
||||
Some(path) => match path {
|
||||
AnchorLocation::File(file) => {
|
||||
full_path.push(Path::new(&file));
|
||||
}
|
||||
_ => {
|
||||
yield 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(),
|
||||
));
|
||||
}
|
||||
let head = raw_args.call_info.args.head.clone();
|
||||
let (
|
||||
SaveArgs {
|
||||
path,
|
||||
raw: save_raw,
|
||||
},
|
||||
input,
|
||||
) = raw_args.process(®istry).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.is_empty() {
|
||||
let anchor = input[0].tag.anchor();
|
||||
|
||||
if let Some(path) = anchor {
|
||||
if let AnchorLocation::File(file) = path {
|
||||
should_return_file_path_error = false;
|
||||
full_path.push(Path::new(&file));
|
||||
}
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Save requires a filepath",
|
||||
"needs path",
|
||||
name_tag.clone(),
|
||||
));
|
||||
}
|
||||
} 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
|
||||
let content : Result<Vec<u8>, ShellError> = 'scope: loop {
|
||||
break if !save_raw {
|
||||
if let Some(extension) = full_path.extension() {
|
||||
let command_name = format!("to {}", extension.to_string_lossy());
|
||||
if let Some(converter) = registry.get_command(&command_name) {
|
||||
let new_args = RawCommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
current_errors,
|
||||
shell_manager,
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: nu_protocol::hir::Call {
|
||||
head,
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
},
|
||||
name_tag: name_tag.clone(),
|
||||
scope,
|
||||
}
|
||||
};
|
||||
let mut result = converter.run(new_args.with_input(input), ®istry).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 {
|
||||
process_string_return_success!('scope, result_vec, name_tag)
|
||||
}
|
||||
if should_return_file_path_error {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Save requires a filepath",
|
||||
"needs path",
|
||||
name_tag.clone(),
|
||||
));
|
||||
}
|
||||
} 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() {
|
||||
let command_name = format!("to {}", extension.to_string_lossy());
|
||||
if let Some(converter) = registry.get_command(&command_name) {
|
||||
let new_args = RawCommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
current_errors,
|
||||
shell_manager,
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: nu_protocol::hir::Call {
|
||||
head,
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
},
|
||||
name_tag: name_tag.clone(),
|
||||
scope,
|
||||
},
|
||||
};
|
||||
let mut result = converter.run(new_args.with_input(input), ®istry).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 {
|
||||
process_unknown!('scope, input, name_tag)
|
||||
process_string_return_success!('scope, result_vec, name_tag)
|
||||
}
|
||||
} else {
|
||||
process_unknown!('scope, input, name_tag)
|
||||
}
|
||||
} else {
|
||||
Ok(string_from(&input).into_bytes())
|
||||
};
|
||||
process_unknown!('scope, input, name_tag)
|
||||
}
|
||||
} else {
|
||||
Ok(string_from(&input).into_bytes())
|
||||
};
|
||||
|
||||
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)),
|
||||
},
|
||||
Err(e) => yield Err(e),
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Ok(OutputStream::new(stream))
|
||||
match content {
|
||||
Ok(save_data) => match std::fs::write(full_path, save_data) {
|
||||
Ok(_) => Ok(OutputStream::empty()),
|
||||
Err(e) => Err(ShellError::labeled_error(
|
||||
e.to_string(),
|
||||
"IO error while saving",
|
||||
name,
|
||||
)),
|
||||
},
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn string_from(input: &[Value]) -> String {
|
||||
|
@ -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,123 +56,120 @@ 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(®istry).await?;
|
||||
if fields.is_empty() {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Select requires columns to select",
|
||||
"needs parameter",
|
||||
name,
|
||||
));
|
||||
return;
|
||||
}
|
||||
let (SelectArgs { rest: mut fields }, mut input) = args.process(®istry).await?;
|
||||
if fields.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Select requires columns to select",
|
||||
"needs parameter",
|
||||
name,
|
||||
));
|
||||
}
|
||||
|
||||
let member = fields.remove(0);
|
||||
let member = vec![member];
|
||||
let member = fields.remove(0);
|
||||
let member = vec![member];
|
||||
|
||||
let column_paths = vec![&member, &fields]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.cloned()
|
||||
.collect::<Vec<ColumnPath>>();
|
||||
let mut empty = true;
|
||||
let mut bring_back: indexmap::IndexMap<String, Vec<Value>> = indexmap::IndexMap::new();
|
||||
let column_paths = vec![&member, &fields]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.cloned()
|
||||
.collect::<Vec<ColumnPath>>();
|
||||
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 {
|
||||
while let Some(value) = input.next().await {
|
||||
for path in &column_paths {
|
||||
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 field = path.clone();
|
||||
let key = as_string(&UntaggedValue::Primitive(Primitive::ColumnPath(field.clone())).into_untagged_value())?;
|
||||
|
||||
match fetcher {
|
||||
Ok(results) => {
|
||||
match results.value {
|
||||
UntaggedValue::Table(records) => {
|
||||
for x in records {
|
||||
let mut out = TaggedDictBuilder::new(name.clone());
|
||||
out.insert_untagged(&key, x.value.clone());
|
||||
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());
|
||||
}
|
||||
|
||||
match fetcher {
|
||||
Ok(results) => match results.value {
|
||||
UntaggedValue::Table(records) => {
|
||||
for x in records {
|
||||
let mut out = TaggedDictBuilder::new(name.clone());
|
||||
out.insert_untagged(&key, x.value.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
|
||||
// breaks the parser.
|
||||
//
|
||||
// We allow flexibility for now and skip the error
|
||||
// if a given column isn't present.
|
||||
let strict: Option<bool> = None;
|
||||
|
||||
if strict.is_some() {
|
||||
yield Err(reason);
|
||||
return;
|
||||
}
|
||||
|
||||
bring_back.entry(key.clone()).or_insert(vec![]);
|
||||
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
|
||||
// breaks the parser.
|
||||
//
|
||||
// We allow flexibility for now and skip the error
|
||||
// if a given column isn't present.
|
||||
let strict: Option<bool> = None;
|
||||
|
||||
if strict.is_some() {
|
||||
return Err(reason);
|
||||
}
|
||||
|
||||
bring_back.entry(key.clone()).or_insert(vec![]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut max = 0;
|
||||
let mut max = 0;
|
||||
|
||||
if let Some(max_column) = bring_back.values().max() {
|
||||
max = max_column.len();
|
||||
}
|
||||
if let Some(max_column) = bring_back.values().max() {
|
||||
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 {
|
||||
let mut out = TaggedDictBuilder::new(name.clone());
|
||||
Ok(futures::stream::iter((0..max).map(move |current| {
|
||||
let mut out = TaggedDictBuilder::new(name.clone());
|
||||
|
||||
for k in &keys {
|
||||
let nothing = UntaggedValue::Primitive(Primitive::Nothing).into_untagged_value();
|
||||
let subsets = bring_back.get(k);
|
||||
for k in &keys {
|
||||
let nothing = UntaggedValue::Primitive(Primitive::Nothing).into_untagged_value();
|
||||
let subsets = bring_back.get(k);
|
||||
|
||||
match subsets {
|
||||
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()),
|
||||
}
|
||||
}
|
||||
match subsets {
|
||||
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)]
|
||||
|
@ -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;
|
||||
let mut values: Vec<Value> = input.collect().await;
|
||||
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()
|
||||
};
|
||||
values.shuffle(&mut thread_rng());
|
||||
|
||||
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)]
|
||||
|
@ -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(®istry).await?;
|
||||
let registry = Arc::new(registry.clone());
|
||||
let scope = Arc::new(args.call_info.scope.clone());
|
||||
let call_info = args.evaluate_once(®istry).await?;
|
||||
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
|
||||
let condition = match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
let condition = Arc::new(match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut skipping = true;
|
||||
while let Some(item) = call_info.input.next().await {
|
||||
let condition = condition.clone();
|
||||
trace!("ITEM = {:?}", item);
|
||||
let result =
|
||||
evaluate_baseline_expr(&*condition, ®istry, &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 block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Value { tag, .. } => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
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);
|
||||
|
||||
async move {
|
||||
let result = evaluate_baseline_expr(
|
||||
&*condition,
|
||||
®istry,
|
||||
&item,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
match result {
|
||||
Ok(ref v) if v.is_true() => false, // stop skipping
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(®istry).await?;
|
||||
let registry = Arc::new(registry.clone());
|
||||
let scope = Arc::new(args.call_info.scope.clone());
|
||||
let call_info = args.evaluate_once(®istry).await?;
|
||||
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
|
||||
let condition = match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
let condition = Arc::new(match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut skipping = true;
|
||||
while let Some(item) = call_info.input.next().await {
|
||||
let condition = condition.clone();
|
||||
trace!("ITEM = {:?}", item);
|
||||
let result =
|
||||
evaluate_baseline_expr(&*condition, ®istry, &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 block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Value { tag, .. } => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
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);
|
||||
|
||||
async move {
|
||||
let result = evaluate_baseline_expr(
|
||||
&*condition,
|
||||
®istry,
|
||||
&item,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
match result {
|
||||
Ok(ref v) if v.is_true() => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(®istry).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)]
|
||||
|
@ -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(®istry).await?;
|
||||
while let Some(v) = input.next().await {
|
||||
let (
|
||||
SplitColumnArgs {
|
||||
separator,
|
||||
rest,
|
||||
collapse_empty,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).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)]
|
||||
|
@ -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(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Command, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
));
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Command, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(®istry).await?;
|
||||
while let Some(v) = input.next().await {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (SplitRowArgs { separator }, input) = args.process(®istry).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)]
|
||||
|
@ -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 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),
|
||||
);
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::type_error(
|
||||
"a table value",
|
||||
other.spanned_type_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(),
|
||||
))
|
||||
}
|
||||
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::split(&values, &Some(block), &name)
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::type_error(
|
||||
"a table value",
|
||||
value.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)
|
||||
}
|
||||
}
|
||||
|
||||
let mut out = TaggedDictBuilder::new(&origin_tag);
|
||||
|
||||
for (k, v) in splits.into_iter() {
|
||||
out.insert_untagged(k, UntaggedValue::row(v));
|
||||
}
|
||||
|
||||
Ok(out.into_value())
|
||||
}
|
||||
|
||||
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::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(&[
|
||||
|
@ -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(®istry).await?;
|
||||
let (Arguments { rest }, input) = args.process(®istry).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> {
|
||||
|
56
crates/nu-cli/src/commands/str_/collect.rs
Normal file
56
crates/nu-cli/src/commands/str_/collect.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str collect"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str collect")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"collects a list of strings into a string"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let output = args
|
||||
.input
|
||||
.collect_string(args.call_info.name_tag.clone())
|
||||
.await?;
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output.item).into_value(output.tag),
|
||||
)))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Collect a list of string",
|
||||
example: "echo ['a' 'b' 'c'] | str collect",
|
||||
result: Some(vec![Value::from("abc")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -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(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Command, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
));
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Command, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(®istry).await?;
|
||||
let (Arguments { rest }, input) = args.process(®istry).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> {
|
||||
|
@ -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(®istry).await?;
|
||||
let options = FindReplace(find.item, replace.item);
|
||||
let (
|
||||
Arguments {
|
||||
find,
|
||||
replace,
|
||||
rest,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).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> {
|
||||
|
@ -1,4 +1,5 @@
|
||||
mod capitalize;
|
||||
mod collect;
|
||||
mod command;
|
||||
mod downcase;
|
||||
mod find_replace;
|
||||
@ -11,6 +12,7 @@ mod trim;
|
||||
mod upcase;
|
||||
|
||||
pub use capitalize::SubCommand as StrCapitalize;
|
||||
pub use collect::SubCommand as StrCollect;
|
||||
pub use command::Command as Str;
|
||||
pub use downcase::SubCommand as StrDowncase;
|
||||
pub use find_replace::SubCommand as StrFindReplace;
|
||||
|
@ -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(®istry).await?;
|
||||
let options = Replace(replace.item);
|
||||
let (Arguments { replace, rest }, input) = args.process(®istry).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> {
|
||||
|
@ -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(®istry).await?;
|
||||
let (Arguments { range, rest }, input) = args.process(®istry).await?;
|
||||
|
||||
let v: Vec<&str> = range.item.split(',').collect();
|
||||
let v: Vec<&str> = range.item.split(',').collect();
|
||||
|
||||
let start = match v[0] {
|
||||
"" => 0,
|
||||
_ => 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(|_| {
|
||||
ShellError::labeled_error(
|
||||
"could not perform substring",
|
||||
"could not perform substring",
|
||||
name.span,
|
||||
)
|
||||
})?
|
||||
};
|
||||
|
||||
if start > end {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"End must be greater than or equal to Start",
|
||||
"End must be greater than or equal to Start",
|
||||
let start = match v[0] {
|
||||
"" => 0,
|
||||
_ => v[0].trim().parse().map_err(|_| {
|
||||
ShellError::labeled_error(
|
||||
"could not perform substring",
|
||||
"could not perform substring",
|
||||
name.span,
|
||||
));
|
||||
return;
|
||||
}
|
||||
)
|
||||
})?,
|
||||
};
|
||||
|
||||
let options = Substring(start, end);
|
||||
let end = match v[1] {
|
||||
"" => usize::max_value(),
|
||||
_ => v[1].trim().parse().map_err(|_| {
|
||||
ShellError::labeled_error(
|
||||
"could not perform substring",
|
||||
"could not perform substring",
|
||||
name.span,
|
||||
)
|
||||
})?,
|
||||
};
|
||||
|
||||
let column_paths: Vec<_> = rest.iter().map(|x| x.clone()).collect();
|
||||
if start > end {
|
||||
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,
|
||||
));
|
||||
}
|
||||
|
||||
while let Some(v) = input.next().await {
|
||||
let options = Substring(start, end);
|
||||
|
||||
let column_paths: Vec<_> = rest;
|
||||
|
||||
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> {
|
||||
|
@ -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(®istry).await?;
|
||||
let (Arguments { format, rest }, input) = args.process(®istry).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 {
|
||||
DatetimeFormat(fmt)
|
||||
} else {
|
||||
DatetimeFormat(String::from("%d.%m.%Y %H:%M %P %z"))
|
||||
};
|
||||
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(
|
||||
|
@ -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(®istry).await?;
|
||||
let (Arguments { rest }, input) = args.process(®istry).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> {
|
||||
|
@ -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(®istry).await?;
|
||||
let (Arguments { rest }, input) = args.process(®istry).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> {
|
||||
|
@ -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(®istry).await?;
|
||||
let (Arguments { rest }, input) = args.process(®istry).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> {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user