forked from extern/nushell
Compare commits
275 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 | |||
a3e1a3f266 | |||
e5a18eb3c2 | |||
16ba274170 | |||
3bb2c9beed | |||
2fa83b0bbe | |||
bf459e09cb | |||
ec7ff5960d | |||
545f70705e | |||
48672f8e30 | |||
160191e9f4 | |||
bef9669b85 | |||
15e66ae065 | |||
ba6370621f | |||
2a8ea88413 | |||
05959d6a61 | |||
012c99839c | |||
5dd346094e | |||
b6f9d0ca58 | |||
ae72593831 | |||
ac22319a5d | |||
7e8c84e394 | |||
ef4eefa96a | |||
2dc43775e3 | |||
4bdf27b173 | |||
741d7b9f10 | |||
ecb67fee40 | |||
ad43ef08e5 | |||
092ee127ee | |||
b84ff99e7f | |||
3a6a3d7409 | |||
48ee20782f | |||
360e8340d1 | |||
fbc0dd57e9 | |||
3f9871f60d | |||
fe01a223a4 | |||
0a6692ac44 | |||
98a3d9fff6 | |||
e2dabecc0b | |||
49b0592e3c | |||
fa812849b8 | |||
9567c1f564 | |||
a915471b38 | |||
bf212a5a3a | |||
f0fc9e1038 | |||
cb6ccc3c5a | |||
07996ea93d | |||
c71510c65c | |||
9c9941cf97 | |||
005d76cf57 | |||
8a99d112fc | |||
fb09d7d1a1 | |||
9c14fb6c02 | |||
d488fddfe1 | |||
e1b598d478 | |||
edbecda14d | |||
74c2daf665 | |||
aadbcf5ce8 | |||
460daf029b | |||
9e6ab33fd7 | |||
5de30d0ae5 | |||
97b9c078b1 | |||
8dc5c34932 | |||
3239e5055c | |||
b22db39775 | |||
7c61f427c6 | |||
ae8c864934 | |||
ed80933806 | |||
ae87582cb6 | |||
b89976daef | |||
76b170cea0 | |||
3302586379 | |||
3144dc7f93 | |||
6efabef8d3 | |||
0743b69ad5 | |||
5f1136dcb0 | |||
acf13a6fcf | |||
3fc4a9f142 | |||
1d781e8797 | |||
b6cdfb1b19 | |||
334685af23 | |||
c475be2df8 | |||
6ec6eb5199 | |||
f18424a6f6 | |||
d1b1438ce5 | |||
af6aff8ca3 | |||
d4dd8284a6 | |||
e728cecb4d | |||
41e1aef369 | |||
e50db1a4de | |||
41412bc577 | |||
e12aa87abe | |||
0abc94f0c6 | |||
48d06f40b1 | |||
f43ed23ed7 | |||
40ec8c41a0 | |||
076fde16dd | |||
822440d5ff | |||
fc8ee8e4b9 | |||
5fbe5cf785 | |||
f78536333a | |||
e7f08cb21d | |||
f5a1d2f4fb | |||
8abbfd3ec9 | |||
6826a9aeac | |||
e3b7e47515 | |||
196991ae1e | |||
34b5e5c34e | |||
cb24a9c7ea | |||
9700b74407 | |||
803c6539eb | |||
75edcbc0d0 | |||
b2eecfb110 | |||
b0aa142542 | |||
247d8b00f8 | |||
0b520eeaf0 | |||
c3535b5c67 | |||
8b9a8daa1d | |||
c5ea4a31bd | |||
2275575575 | |||
c3a066eeb4 | |||
42eb658c37 | |||
a2e9bbd358 | |||
8951a01e58 | |||
f702aae72f | |||
f5e03aaf1c | |||
0f0847e45b | |||
ccd5d69fd1 | |||
55374ee54f | |||
f93ff9ec33 | |||
9a94b3c656 | |||
e04b89f747 | |||
180c1204f3 | |||
96e5fc05a3 | |||
c3efdf2689 | |||
27fdef5479 | |||
7ce8026916 | |||
8a9fc6a721 | |||
c06a692709 | |||
b37e420c7c | |||
22e70478a4 | |||
8ab2b92405 | |||
3201c90647 | |||
454f560eaa | |||
d2ac506de3 | |||
a9968046ed | |||
453087248a | |||
81ff598d6c | |||
d7d487de73 | |||
8d69c77989 | |||
0779a46179 | |||
ada92f41b4 | |||
ef3049f5a1 | |||
1dab82ffa1 | |||
e9e3fac59d | |||
7d403a6cc7 | |||
cf53264438 | |||
d834708be8 | |||
f8b4784891 | |||
789b28ac8a | |||
db8219e798 | |||
73d5310c9c | |||
8d197e1b2f | |||
c704157bc8 | |||
6abb9181d5 | |||
006171d669 | |||
8bd3cedce1 | |||
6f2ef05195 | |||
80025ea684 | |||
a62745eefb | |||
2ac501f88e | |||
df90d9e4b6 | |||
ad7a3fd908 | |||
ad8ab5b04d | |||
e7767ab7b3 | |||
846a779516 | |||
e7a4f31b38 | |||
10768b6ecf | |||
716c4def03 | |||
0e510ad42b | |||
3c222916c6 | |||
6887554888 | |||
d7bd77829f | |||
9e8434326d | |||
27bff35c79 | |||
e2fae63a42 | |||
701711eada | |||
9ec2aca86f | |||
818171cc2c | |||
b3c623396f |
@ -37,6 +37,8 @@ steps:
|
||||
fi
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain "stable"
|
||||
echo "Installing clippy"
|
||||
rustup component add clippy --toolchain stable-x86_64-apple-darwin
|
||||
export PATH=$HOME/.cargo/bin:$PATH
|
||||
fi
|
||||
rustup update
|
||||
@ -44,13 +46,13 @@ steps:
|
||||
echo "##vso[task.prependpath]$HOME/.cargo/bin"
|
||||
rustup component add rustfmt
|
||||
displayName: Install Rust
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features stable,test-bins
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features stable
|
||||
condition: eq(variables['style'], 'unflagged')
|
||||
displayName: Run tests
|
||||
- bash: RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
|
||||
condition: eq(variables['style'], 'unflagged')
|
||||
displayName: Check clippy lints
|
||||
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo test --all --features stable,test-bins
|
||||
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo test --all --features stable
|
||||
condition: eq(variables['style'], 'canary')
|
||||
displayName: Run tests
|
||||
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
|
||||
|
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -23,8 +23,8 @@ A clear and concise description of what you expected to happen.
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Configuration (please complete the following information):**
|
||||
- OS: [e.g. Windows]
|
||||
- Version [e.g. 0.4.0]
|
||||
- Optional features (if any)
|
||||
- OS (e.g. Windows):
|
||||
- Nu version (you can use the `version` command to find out):
|
||||
- Optional features (if any):
|
||||
|
||||
Add any other context about the problem here.
|
||||
|
4
.github/workflows/docker-publish.yml
vendored
4
.github/workflows/docker-publish.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
- x86_64-unknown-linux-musl
|
||||
- x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install rust-embedded/cross
|
||||
env: { VERSION: v0.1.16 }
|
||||
run: >-
|
||||
@ -62,7 +62,7 @@ jobs:
|
||||
- { tag: glibc, base-image: scratch, arch: x86_64-unknown-linux-gnu, plugin: false, use-patch: false}
|
||||
- { tag: musl, base-image: scratch, arch: x86_64-unknown-linux-musl, plugin: false, use-patch: false}
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/download-artifact@master
|
||||
with: { name: '${{ matrix.arch }}', path: target/release }
|
||||
- name: Build and publish exact version
|
||||
|
240
.github/workflows/release.yml
vendored
Normal file
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
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -11,3 +11,12 @@ debian/debhelper-build-stamp
|
||||
debian/files
|
||||
debian/nu.substvars
|
||||
debian/nu/
|
||||
|
||||
# macOS junk
|
||||
.DS_Store
|
||||
|
||||
# JetBrains' IDE items
|
||||
.idea/*
|
||||
|
||||
# VSCode's IDE items
|
||||
.vscode/*
|
||||
|
7
.gitpod.Dockerfile
vendored
7
.gitpod.Dockerfile
vendored
@ -6,4 +6,9 @@ RUN sudo apt-get update && \
|
||||
sudo apt-get install -y \
|
||||
libssl-dev \
|
||||
libxcb-composite0-dev \
|
||||
pkg-config
|
||||
pkg-config \
|
||||
libpython3.6 \
|
||||
rust-lldb \
|
||||
&& sudo rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV RUST_LLDB=/usr/bin/lldb-8
|
||||
|
22
.gitpod.yml
22
.gitpod.yml
@ -1,23 +1,19 @@
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
tasks:
|
||||
- init: cargo install --path . --force --features=stable
|
||||
- name: Clippy
|
||||
init: cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
|
||||
- name: Testing
|
||||
init: cargo test --all --features=stable,test-bins
|
||||
- name: Build
|
||||
init: cargo build --features=stable
|
||||
- name: Nu
|
||||
init: cargo install --path . --features=stable
|
||||
command: nu
|
||||
github:
|
||||
prebuilds:
|
||||
# enable for the master/default branch (defaults to true)
|
||||
master: true
|
||||
# enable for all branches in this repo (defaults to false)
|
||||
branches: true
|
||||
# enable for pull requests coming from this repo (defaults to true)
|
||||
pullRequests: true
|
||||
# enable for pull requests coming from forks (defaults to false)
|
||||
pullRequestsFromForks: true
|
||||
# add a "Review in Gitpod" button as a comment to pull requests (defaults to true)
|
||||
addComment: true
|
||||
# add a "Review in Gitpod" button to pull requests (defaults to false)
|
||||
addBadge: false
|
||||
# add a label once the prebuild is ready to pull requests (defaults to false)
|
||||
addLabel: prebuilt-in-gitpod
|
||||
vscode:
|
||||
extensions:
|
||||
@ -25,5 +21,5 @@ vscode:
|
||||
- Swellaby.vscode-rust-test-adapter@0.11.0:Xg+YeZZQiVpVUsIkH+uiiw==
|
||||
- serayuzgur.crates@0.4.7:HMkoguLcXp9M3ud7ac3eIw==
|
||||
- belfz.search-crates-io@1.2.1:kSLnyrOhXtYPjQpKnMr4eQ==
|
||||
- vadimcn.vscode-lldb@1.4.5:lwHCNwtm0kmOBXeQUIPGMQ==
|
||||
- bungcip.better-toml@0.3.2:3QfgGxxYtGHfJKQU7H0nEw==
|
||||
- webfreak.debug@0.24.0:1zVcRsAhewYEX3/A9xjMNw==
|
||||
|
14
.theia/launch.json
Normal file
14
.theia/launch.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "gdb",
|
||||
"request": "launch",
|
||||
"name": "Debug Rust Code",
|
||||
"preLaunchTask": "cargo",
|
||||
"target": "${workspaceFolder}/target/debug/nu",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"valuesFormatting": "parseText"
|
||||
}
|
||||
]
|
||||
}
|
12
.theia/tasks.json
Normal file
12
.theia/tasks.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"build"
|
||||
],
|
||||
"type": "process",
|
||||
"label": "cargo",
|
||||
}
|
||||
],
|
||||
}
|
@ -55,7 +55,7 @@ a project may be further defined and clarified by project maintainers.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at wycats@gmail.com. All
|
||||
reported by contacting the project team at wycats@gmail.com via email or by reaching out to @jturner, @gedge, or @andras_io on discord. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
@ -68,9 +68,9 @@ members of the project's leadership.
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
available at <https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
<https://www.contributor-covenant.org/faq>
|
||||
|
@ -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,3 +11,52 @@ For speedy contributions open it in Gitpod, nu will be pre-installed with the la
|
||||
To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)!
|
||||
<!--WIP-->
|
||||
|
||||
## Developing
|
||||
|
||||
### Set up
|
||||
|
||||
This is no different than other Rust projects.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/nushell/nushell
|
||||
cd nushell
|
||||
cargo build
|
||||
```
|
||||
|
||||
### Useful Commands
|
||||
|
||||
Build and run Nushell:
|
||||
|
||||
```shell
|
||||
cargo build --release && cargo run --release
|
||||
```
|
||||
|
||||
Run Clippy on Nushell:
|
||||
|
||||
```shell
|
||||
cargo clippy --all --features=stable
|
||||
```
|
||||
|
||||
Run all tests:
|
||||
|
||||
```shell
|
||||
cargo test --all --features=stable
|
||||
```
|
||||
|
||||
Run all tests for a specific command
|
||||
|
||||
```shell
|
||||
cargo test --package nu-cli --test main -- commands::<command_name_here>
|
||||
```
|
||||
|
||||
Check to see if there are code formatting issues
|
||||
|
||||
```shell
|
||||
cargo fmt --all -- --check
|
||||
```
|
||||
|
||||
Format the code in the project
|
||||
|
||||
```shell
|
||||
cargo fmt --all
|
||||
```
|
||||
|
2025
Cargo.lock
generated
2025
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
106
Cargo.toml
106
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nu"
|
||||
version = "0.13.0"
|
||||
version = "0.16.0"
|
||||
authors = ["The Nu Project Contributors"]
|
||||
description = "A new type of shell"
|
||||
license = "MIT"
|
||||
@ -18,97 +18,68 @@ members = ["crates/*/"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { version = "0.13.0", path = "./crates/nu-cli" }
|
||||
nu-source = { version = "0.13.0", path = "./crates/nu-source" }
|
||||
nu-plugin = { version = "0.13.0", path = "./crates/nu-plugin" }
|
||||
nu-protocol = { version = "0.13.0", path = "./crates/nu-protocol" }
|
||||
nu-errors = { version = "0.13.0", path = "./crates/nu-errors" }
|
||||
nu-parser = { version = "0.13.0", path = "./crates/nu-parser" }
|
||||
nu-value-ext = { version = "0.13.0", path = "./crates/nu-value-ext" }
|
||||
nu_plugin_average = { version = "0.13.0", path = "./crates/nu_plugin_average", optional=true }
|
||||
nu_plugin_binaryview = { version = "0.13.0", path = "./crates/nu_plugin_binaryview", optional=true }
|
||||
nu_plugin_fetch = { version = "0.13.0", path = "./crates/nu_plugin_fetch", optional=true }
|
||||
nu_plugin_inc = { version = "0.13.0", path = "./crates/nu_plugin_inc", optional=true }
|
||||
nu_plugin_match = { version = "0.13.0", path = "./crates/nu_plugin_match", optional=true }
|
||||
nu_plugin_post = { version = "0.13.0", path = "./crates/nu_plugin_post", optional=true }
|
||||
nu_plugin_ps = { version = "0.13.0", path = "./crates/nu_plugin_ps", optional=true }
|
||||
nu_plugin_str = { version = "0.13.0", path = "./crates/nu_plugin_str", optional=true }
|
||||
nu_plugin_sys = { version = "0.13.0", path = "./crates/nu_plugin_sys", optional=true }
|
||||
nu_plugin_textview = { version = "0.13.0", path = "./crates/nu_plugin_textview", optional=true }
|
||||
nu_plugin_tree = { version = "0.13.0", path = "./crates/nu_plugin_tree", optional=true }
|
||||
nu-cli = { version = "0.16.0", path = "./crates/nu-cli" }
|
||||
nu-source = { version = "0.16.0", path = "./crates/nu-source" }
|
||||
nu-plugin = { version = "0.16.0", path = "./crates/nu-plugin" }
|
||||
nu-protocol = { version = "0.16.0", path = "./crates/nu-protocol" }
|
||||
nu-errors = { version = "0.16.0", path = "./crates/nu-errors" }
|
||||
nu-parser = { version = "0.16.0", path = "./crates/nu-parser" }
|
||||
nu-value-ext = { version = "0.16.0", path = "./crates/nu-value-ext" }
|
||||
nu_plugin_binaryview = { version = "0.16.0", path = "./crates/nu_plugin_binaryview", optional=true }
|
||||
nu_plugin_fetch = { version = "0.16.0", path = "./crates/nu_plugin_fetch", optional=true }
|
||||
nu_plugin_inc = { version = "0.16.0", path = "./crates/nu_plugin_inc", optional=true }
|
||||
nu_plugin_match = { version = "0.16.0", path = "./crates/nu_plugin_match", optional=true }
|
||||
nu_plugin_post = { version = "0.16.0", path = "./crates/nu_plugin_post", optional=true }
|
||||
nu_plugin_ps = { version = "0.16.0", path = "./crates/nu_plugin_ps", optional=true }
|
||||
nu_plugin_start = { version = "0.16.0", path = "./crates/nu_plugin_start", optional=true }
|
||||
nu_plugin_sys = { version = "0.16.0", path = "./crates/nu_plugin_sys", optional=true }
|
||||
nu_plugin_textview = { version = "0.16.0", path = "./crates/nu_plugin_textview", optional=true }
|
||||
nu_plugin_tree = { version = "0.16.0", path = "./crates/nu_plugin_tree", optional=true }
|
||||
|
||||
crossterm = { version = "0.17.2", optional = true }
|
||||
semver = { version = "0.9.0", optional = true }
|
||||
syntect = { version = "4.1", default-features = false, features = ["default-fancy"], optional = true}
|
||||
crossterm = { version = "0.17.5", optional = true }
|
||||
semver = { version = "0.10.0", optional = true }
|
||||
syntect = { version = "4.2", default-features = false, features = ["default-fancy"], optional = true}
|
||||
url = { version = "2.1.1", optional = true }
|
||||
|
||||
clap = "2.33.0"
|
||||
clap = "2.33.1"
|
||||
ctrlc = "3.1.4"
|
||||
dunce = "1.0.0"
|
||||
dunce = "1.0.1"
|
||||
futures = { version = "0.3", features = ["compat", "io-compat"] }
|
||||
log = "0.4.8"
|
||||
pretty_env_logger = "0.4.0"
|
||||
starship = "0.43.0"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { version = "0.13.0", path = "./crates/nu-test-support" }
|
||||
nu-test-support = { version = "0.16.0", path = "./crates/nu-test-support" }
|
||||
|
||||
[build-dependencies]
|
||||
toml = "0.5.6"
|
||||
serde = { version = "1.0.106", features = ["derive"] }
|
||||
nu-build = { version = "0.13.0", path = "./crates/nu-build" }
|
||||
serde = { version = "1.0.110", features = ["derive"] }
|
||||
nu-build = { version = "0.16.0", path = "./crates/nu-build" }
|
||||
|
||||
[features]
|
||||
# Test executables
|
||||
test-bins = []
|
||||
|
||||
default = ["sys", "ps", "textview", "inc", "str"]
|
||||
stable = ["default", "starship-prompt", "binaryview", "match", "tree", "average", "post", "fetch", "clipboard-cli", "trash-support"]
|
||||
default = ["sys", "ps", "textview", "inc"]
|
||||
stable = ["default", "binaryview", "match", "tree", "post", "fetch", "clipboard-cli", "trash-support", "start"]
|
||||
|
||||
# Default
|
||||
textview = ["crossterm", "syntect", "url", "nu_plugin_textview"]
|
||||
sys = ["nu_plugin_sys"]
|
||||
ps = ["nu_plugin_ps"]
|
||||
inc = ["semver", "nu_plugin_inc"]
|
||||
str = ["nu_plugin_str"]
|
||||
|
||||
# Stable
|
||||
average = ["nu_plugin_average"]
|
||||
binaryview = ["nu_plugin_binaryview"]
|
||||
fetch = ["nu_plugin_fetch"]
|
||||
match = ["nu_plugin_match"]
|
||||
post = ["nu_plugin_post"]
|
||||
trace = ["nu-parser/trace"]
|
||||
tree = ["nu_plugin_tree"]
|
||||
start = ["nu_plugin_start"]
|
||||
|
||||
clipboard-cli = ["nu-cli/clipboard-cli"]
|
||||
starship-prompt = ["nu-cli/starship-prompt"]
|
||||
# starship-prompt = ["nu-cli/starship-prompt"]
|
||||
trash-support = ["nu-cli/trash-support"]
|
||||
|
||||
[[bin]]
|
||||
name = "fail"
|
||||
path = "crates/nu-test-support/src/bins/fail.rs"
|
||||
required-features = ["test-bins"]
|
||||
|
||||
[[bin]]
|
||||
name = "chop"
|
||||
path = "crates/nu-test-support/src/bins/chop.rs"
|
||||
required-features = ["test-bins"]
|
||||
|
||||
[[bin]]
|
||||
name = "cococo"
|
||||
path = "crates/nu-test-support/src/bins/cococo.rs"
|
||||
required-features = ["test-bins"]
|
||||
|
||||
[[bin]]
|
||||
name = "nonu"
|
||||
path = "crates/nu-test-support/src/bins/nonu.rs"
|
||||
required-features = ["test-bins"]
|
||||
|
||||
[[bin]]
|
||||
name = "iecho"
|
||||
path = "crates/nu-test-support/src/bins/iecho.rs"
|
||||
required-features = ["test-bins"]
|
||||
|
||||
# Core plugins that ship with `cargo install nu` by default
|
||||
# Currently, Cargo limits us to installing only one binary
|
||||
# unless we use [[bin]], so we use this as a workaround
|
||||
@ -127,22 +98,12 @@ name = "nu_plugin_core_ps"
|
||||
path = "src/plugins/nu_plugin_core_ps.rs"
|
||||
required-features = ["ps"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_core_str"
|
||||
path = "src/plugins/nu_plugin_core_str.rs"
|
||||
required-features = ["str"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_core_sys"
|
||||
path = "src/plugins/nu_plugin_core_sys.rs"
|
||||
required-features = ["sys"]
|
||||
|
||||
# Stable plugins
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_average"
|
||||
path = "src/plugins/nu_plugin_stable_average.rs"
|
||||
required-features = ["average"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_fetch"
|
||||
path = "src/plugins/nu_plugin_stable_fetch.rs"
|
||||
@ -168,6 +129,11 @@ name = "nu_plugin_stable_tree"
|
||||
path = "src/plugins/nu_plugin_stable_tree.rs"
|
||||
required-features = ["tree"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_start"
|
||||
path = "src/plugins/nu_plugin_stable_start.rs"
|
||||
required-features = ["start"]
|
||||
|
||||
# Main nu binary
|
||||
[[bin]]
|
||||
name = "nu"
|
||||
|
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.
|
205
README.md
205
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,13 +39,13 @@ 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.
|
||||
|
||||
To build Nu, you will need to use the **latest stable (1.39 or later)** version of the compiler.
|
||||
To build Nu, you will need to use the **latest stable (1.41 or later)** version of the compiler.
|
||||
|
||||
Required dependencies:
|
||||
|
||||
@ -58,19 +59,19 @@ Optional dependencies:
|
||||
|
||||
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`):
|
||||
|
||||
```
|
||||
```bash
|
||||
cargo install nu
|
||||
```
|
||||
|
||||
You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/en/installation.html#dependencies) for your platform), once you have checked out this repo with git:
|
||||
|
||||
```
|
||||
```bash
|
||||
cargo build --workspace --features=stable
|
||||
```
|
||||
|
||||
## Docker
|
||||
### Docker
|
||||
|
||||
### Quickstart
|
||||
#### Quickstart
|
||||
|
||||
Want to try Nu right away? Execute the following to get started.
|
||||
|
||||
@ -78,14 +79,14 @@ Want to try Nu right away? Execute the following to get started.
|
||||
docker run -it quay.io/nushell/nu:latest
|
||||
```
|
||||
|
||||
### Guide
|
||||
#### Guide
|
||||
|
||||
If you want to pull a pre-built container, you can browse tags for the [nushell organization](https://quay.io/organization/nushell)
|
||||
on Quay.io. Pulling a container would come down to:
|
||||
|
||||
```bash
|
||||
$ docker pull quay.io/nushell/nu
|
||||
$ docker pull quay.io/nushell/nu-base
|
||||
docker pull quay.io/nushell/nu
|
||||
docker pull quay.io/nushell/nu-base
|
||||
```
|
||||
|
||||
Both "nu-base" and "nu" provide the nu binary, however nu-base also includes the source code at `/code`
|
||||
@ -95,41 +96,41 @@ Optionally, you can also build the containers locally using the [dockerfiles pro
|
||||
To build the base image:
|
||||
|
||||
```bash
|
||||
$ docker build -f docker/Dockerfile.nu-base -t nushell/nu-base .
|
||||
docker build -f docker/Dockerfile.nu-base -t nushell/nu-base .
|
||||
```
|
||||
|
||||
And then to build the smaller container (using a Multistage build):
|
||||
|
||||
```bash
|
||||
$ docker build -f docker/Dockerfile -t nushell/nu .
|
||||
docker build -f docker/Dockerfile -t nushell/nu .
|
||||
```
|
||||
|
||||
Either way, you can run either container as follows:
|
||||
|
||||
```bash
|
||||
$ docker run -it nushell/nu-base
|
||||
$ docker run -it nushell/nu
|
||||
docker run -it nushell/nu-base
|
||||
docker run -it nushell/nu
|
||||
/> exit
|
||||
```
|
||||
|
||||
The second container is a bit smaller if the size is important to you.
|
||||
|
||||
## Packaging status
|
||||
### Packaging status
|
||||
|
||||
[](https://repology.org/project/nushell/versions)
|
||||
|
||||
### Fedora
|
||||
#### Fedora
|
||||
|
||||
[COPR repo](https://copr.fedorainfracloud.org/coprs/atim/nushell/): `sudo dnf copr enable atim/nushell -y && sudo dnf install nushell -y`
|
||||
|
||||
# Philosophy
|
||||
## Philosophy
|
||||
|
||||
Nu draws inspiration from projects like PowerShell, functional programming languages, and modern CLI tools.
|
||||
Rather than thinking of files and services as raw streams of text, Nu looks at each input as something with structure.
|
||||
For example, when you list the contents of a directory, what you get back is a table of rows, where each row represents an item in that directory.
|
||||
These values can be piped through a series of steps, in a series of commands called a 'pipeline'.
|
||||
|
||||
## Pipelines
|
||||
### Pipelines
|
||||
|
||||
In Unix, it's common to pipe between commands to split up a sophisticated command over multiple steps.
|
||||
Nu takes this a step further and builds heavily on the idea of _pipelines_.
|
||||
@ -138,104 +139,110 @@ Additionally, commands can output structured data (you can think of this as a th
|
||||
Commands that work in the pipeline fit into one of three categories:
|
||||
|
||||
* Commands that produce a stream (eg, `ls`)
|
||||
* Commands that filter a stream (eg, `where type == "Directory"`)
|
||||
* Commands that filter a stream (eg, `where type == "Dir"`)
|
||||
* Commands that consume the output of the pipeline (eg, `autoview`)
|
||||
|
||||
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
||||
|
||||
```
|
||||
/home/jonathan/Source/nushell(master)> ls | where type == "Directory" | autoview
|
||||
────┬───────────┬───────────┬──────────┬────────┬──────────────┬────────────────
|
||||
# │ name │ type │ readonly │ size │ accessed │ modified
|
||||
────┼───────────┼───────────┼──────────┼────────┼──────────────┼────────────────
|
||||
0 │ .azure │ Directory │ │ 4.1 KB │ 2 months ago │ a day ago
|
||||
1 │ target │ Directory │ │ 4.1 KB │ 3 days ago │ 3 days ago
|
||||
2 │ images │ Directory │ │ 4.1 KB │ 2 months ago │ 2 weeks ago
|
||||
3 │ tests │ Directory │ │ 4.1 KB │ 2 months ago │ 37 minutes ago
|
||||
4 │ tmp │ Directory │ │ 4.1 KB │ 2 weeks ago │ 2 weeks ago
|
||||
5 │ src │ Directory │ │ 4.1 KB │ 2 months ago │ 37 minutes ago
|
||||
6 │ assets │ Directory │ │ 4.1 KB │ a month ago │ a month ago
|
||||
7 │ docs │ Directory │ │ 4.1 KB │ 2 months ago │ 2 months ago
|
||||
────┴───────────┴───────────┴──────────┴────────┴──────────────┴────────────────
|
||||
```shell
|
||||
> ls | where type == "Dir" | autoview
|
||||
───┬────────┬──────┬───────┬──────────────
|
||||
# │ name │ type │ size │ modified
|
||||
───┼────────┼──────┼───────┼──────────────
|
||||
0 │ assets │ Dir │ 128 B │ 5 months ago
|
||||
1 │ crates │ Dir │ 704 B │ 50 mins ago
|
||||
2 │ debian │ 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
|
||||
───┬───────┬─────────────────┬──────────┬──────────
|
||||
# │ pid │ name │ status │ cpu
|
||||
───┼───────┼─────────────────┼──────────┼──────────
|
||||
0 │ 992 │ chrome │ Sleeping │ 6.988768
|
||||
1 │ 4240 │ chrome │ Sleeping │ 5.645982
|
||||
2 │ 13973 │ qemu-system-x86 │ Sleeping │ 4.996551
|
||||
3 │ 15746 │ nu │ Sleeping │ 84.59905
|
||||
───┴───────┴─────────────────┴──────────┴──────────
|
||||
```shell
|
||||
> ps | where cpu > 0
|
||||
───┬────────┬───────────────────┬──────────┬─────────┬──────────┬──────────
|
||||
# │ pid │ name │ status │ cpu │ mem │ virtual
|
||||
───┼────────┼───────────────────┼──────────┼─────────┼──────────┼──────────
|
||||
0 │ 435 │ irq/142-SYNA327 │ Sleeping │ 7.5699 │ 0 B │ 0 B
|
||||
1 │ 1609 │ pulseaudio │ Sleeping │ 6.5605 │ 10.6 MB │ 2.3 GB
|
||||
2 │ 1625 │ gnome-shell │ Sleeping │ 6.5684 │ 639.6 MB │ 7.3 GB
|
||||
3 │ 2202 │ Web Content │ Sleeping │ 6.8157 │ 320.8 MB │ 3.0 GB
|
||||
4 │ 328788 │ nu_plugin_core_ps │ Sleeping │ 92.5750 │ 5.9 MB │ 633.2 MB
|
||||
───┴────────┴───────────────────┴──────────┴─────────┴──────────┴──────────
|
||||
```
|
||||
|
||||
## Opening files
|
||||
### Opening files
|
||||
|
||||
Nu can load file and URL contents as raw text or as structured data (if it recognizes the format).
|
||||
For example, you can load a .toml file as structured data and explore it:
|
||||
|
||||
```
|
||||
/home/jonathan/Source/nushell(master)> open Cargo.toml
|
||||
──────────────────┬────────────────┬──────────────────
|
||||
bin │ dependencies │ dev-dependencies
|
||||
──────────────────┼────────────────┼──────────────────
|
||||
[table: 12 rows] │ [table: 1 row] │ [table: 1 row]
|
||||
──────────────────┴────────────────┴──────────────────
|
||||
```shell
|
||||
> open Cargo.toml
|
||||
────────────────────┬───────────────────────────
|
||||
bin │ [table 18 rows]
|
||||
build-dependencies │ [row nu-build serde toml]
|
||||
dependencies │ [row 29 columns]
|
||||
dev-dependencies │ [row nu-test-support]
|
||||
features │ [row 19 columns]
|
||||
package │ [row 12 columns]
|
||||
workspace │ [row members]
|
||||
────────────────────┴───────────────────────────
|
||||
```
|
||||
|
||||
We can pipeline this into a command that gets the contents of one of the columns:
|
||||
|
||||
```
|
||||
/home/jonathan/Source/nushell(master)> open Cargo.toml | get package
|
||||
─────────────────┬────────────────────────────┬─────────┬─────────┬──────┬─────────
|
||||
authors │ description │ edition │ license │ name │ version
|
||||
─────────────────┼────────────────────────────┼─────────┼─────────┼──────┼─────────
|
||||
[table: 3 rows] │ A shell for the GitHub era │ 2018 │ MIT │ nu │ 0.9.0
|
||||
─────────────────┴────────────────────────────┴─────────┴─────────┴──────┴─────────
|
||||
```shell
|
||||
> open Cargo.toml | get package
|
||||
───────────────┬────────────────────────────────────
|
||||
authors │ [table 1 rows]
|
||||
default-run │ nu
|
||||
description │ A new type of shell
|
||||
documentation │ https://www.nushell.sh/book/
|
||||
edition │ 2018
|
||||
exclude │ [table 1 rows]
|
||||
homepage │ https://www.nushell.sh
|
||||
license │ MIT
|
||||
name │ nu
|
||||
readme │ README.md
|
||||
repository │ https://github.com/nushell/nushell
|
||||
version │ 0.15.1
|
||||
───────────────┴────────────────────────────────────
|
||||
```
|
||||
|
||||
Finally, we can use commands outside of Nu once we have the data we want:
|
||||
|
||||
```
|
||||
/home/jonathan/Source/nushell(master)> open Cargo.toml | get package.version | echo $it
|
||||
0.9.0
|
||||
```shell
|
||||
> open Cargo.toml | get package.version | echo $it
|
||||
0.15.1
|
||||
```
|
||||
|
||||
Here we use the variable `$it` to refer to the value being piped to the external command.
|
||||
|
||||
## Configuration
|
||||
### Configuration
|
||||
|
||||
Nu has early support for configuring the shell. It currently supports the following settings:
|
||||
|
||||
| Variable | Type | Description |
|
||||
| --------------- | -------------------- | -------------------------------------------------------------- |
|
||||
| path | table of strings | PATH to use to find binaries |
|
||||
| env | row | the environment variables to pass to external commands |
|
||||
| ctrlc_exit | boolean | whether or not to exit Nu after multiple ctrl-c presses |
|
||||
| table_mode | "light" or other | enable lightweight or normal tables |
|
||||
| edit_mode | "vi" or "emacs" | changes line editing to "vi" or "emacs" mode |
|
||||
| completion_mode | "circular" or "list" | changes completion type to "circular" (default) or "list" mode |
|
||||
Nu has early support for configuring the shell. You can refer to the book for a list of [all supported variables](https://www.nushell.sh/book/en/configuration.html).
|
||||
|
||||
To set one of these variables, you can use `config --set`. For example:
|
||||
|
||||
```
|
||||
```shell
|
||||
> config --set [edit_mode "vi"]
|
||||
> config --set [path $nu.path]
|
||||
```
|
||||
|
||||
## Shells
|
||||
### Shells
|
||||
|
||||
Nu will work inside of a single directory and allow you to navigate around your filesystem by default.
|
||||
Nu also offers a way of adding additional working directories that you can jump between, allowing you to work in multiple directories at the same time.
|
||||
@ -246,7 +253,7 @@ Once you're done with a shell, you can `exit` it and remove it from the ring buf
|
||||
|
||||
Finally, to get a list of all the current shells, you can use the `shells` command.
|
||||
|
||||
## Plugins
|
||||
### Plugins
|
||||
|
||||
Nu supports plugins that offer additional functionality to the shell and follow the same structured data model that built-in commands use.
|
||||
This allows you to extend nu for your needs.
|
||||
@ -258,7 +265,7 @@ These binaries interact with nu via a simple JSON-RPC protocol where the command
|
||||
If the plugin is a filter, data streams to it one element at a time, and it can stream data back in return via stdin/stdout.
|
||||
If the plugin is a sink, it is given the full vector of final data and is given free reign over stdin/stdout to use as it pleases.
|
||||
|
||||
# Goals
|
||||
## Goals
|
||||
|
||||
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
|
||||
|
||||
@ -272,11 +279,39 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat
|
||||
|
||||
* Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
|
||||
|
||||
# Commands
|
||||
## Commands
|
||||
|
||||
You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/documentation.html#quick-command-references).
|
||||
|
||||
# License
|
||||
## Progress
|
||||
|
||||
Nu is in heavy development, and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
|
||||
|
||||
| Features | Not started | Prototype | MVP | Preview | Mature | Notes
|
||||
| -------- |:-----------:|:---------:|:---:|:-------:|:------:| -----
|
||||
| Aliases | | X | | | | Initial implementation but lacks necessary features
|
||||
| Notebook | | X | | | | Initial jupyter support, but it loses state and lacks features
|
||||
| File ops | | | X | | | cp, mv, rm, mkdir have some support, but lacking others
|
||||
| Environment | | X | | | | Temporary environment, but no session-wide env variables
|
||||
| Shells | | X | | | | Basic value and file shells, but no opt-in/opt-out for commands
|
||||
| Protocol | | | X | | | Streaming protocol is serviceable
|
||||
| Plugins | | X | | | | Plugins work on one row at a time, lack batching and expression eval
|
||||
| Errors | | | X | | | Error reporting works, but could use usability polish
|
||||
| Documentation | | X | | | | Book and related are barebones and lack task-based lessons
|
||||
| Paging | | X | | | | Textview has paging, but we'd like paging for tables
|
||||
| Functions| X | | | | | No functions, yet, only aliases
|
||||
| Variables| X | | | | | Nu doesn't yet support variables
|
||||
| Completions | | X | | | | Completions are currently barebones, at best
|
||||
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked
|
||||
|
||||
## Current Roadmap
|
||||
|
||||
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in NuShell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
|
||||
|
||||
## Contributing
|
||||
|
||||
See [Contributing](CONTRIBUTING.md) for details.
|
||||
|
||||
## License
|
||||
|
||||
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.13.0"
|
||||
version = "0.16.0"
|
||||
authors = ["The Nu Project Contributors"]
|
||||
edition = "2018"
|
||||
description = "Core build system for nushell"
|
||||
@ -10,7 +10,7 @@ license = "MIT"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.106", features = ["derive"] }
|
||||
serde = { version = "1.0.114", features = ["derive"] }
|
||||
lazy_static = "1.4.0"
|
||||
serde_json = "1.0.51"
|
||||
serde_json = "1.0.55"
|
||||
toml = "0.5.6"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nu-cli"
|
||||
version = "0.13.0"
|
||||
version = "0.16.0"
|
||||
authors = ["The Nu Project Contributors"]
|
||||
description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
@ -10,66 +10,69 @@ license = "MIT"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-source = { version = "0.13.0", path = "../nu-source" }
|
||||
nu-plugin = { version = "0.13.0", path = "../nu-plugin" }
|
||||
nu-protocol = { version = "0.13.0", path = "../nu-protocol" }
|
||||
nu-errors = { version = "0.13.0", path = "../nu-errors" }
|
||||
nu-parser = { version = "0.13.0", path = "../nu-parser" }
|
||||
nu-value-ext = { version = "0.13.0", path = "../nu-value-ext" }
|
||||
nu-test-support = { version = "0.13.0", path = "../nu-test-support" }
|
||||
nu-source = { version = "0.16.0", path = "../nu-source" }
|
||||
nu-plugin = { version = "0.16.0", path = "../nu-plugin" }
|
||||
nu-protocol = { version = "0.16.0", path = "../nu-protocol" }
|
||||
nu-errors = { version = "0.16.0", path = "../nu-errors" }
|
||||
nu-parser = { version = "0.16.0", path = "../nu-parser" }
|
||||
nu-value-ext = { version = "0.16.0", path = "../nu-value-ext" }
|
||||
nu-test-support = { version = "0.16.0", path = "../nu-test-support" }
|
||||
nu-table = {version = "0.16.0", path = "../nu-table"}
|
||||
|
||||
ansi_term = "0.12.1"
|
||||
app_dirs = "1.2.1"
|
||||
async-stream = "0.2"
|
||||
base64 = "0.12.0"
|
||||
bigdecimal = { version = "0.1.0", features = ["serde"] }
|
||||
async-recursion = "0.3.1"
|
||||
async-trait = "0.1.36"
|
||||
directories = "2.0.2"
|
||||
base64 = "0.12.3"
|
||||
bigdecimal = { version = "0.1.2", features = ["serde"] }
|
||||
bson = { version = "0.14.1", features = ["decimal128"] }
|
||||
byte-unit = "3.0.3"
|
||||
bytes = "0.5.4"
|
||||
byte-unit = "3.1.3"
|
||||
bytes = "0.5.5"
|
||||
calamine = "0.16"
|
||||
cfg-if = "0.1"
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
clap = "2.33.0"
|
||||
clap = "2.33.1"
|
||||
csv = "1.1"
|
||||
ctrlc = "3.1.4"
|
||||
derive-new = "0.5.8"
|
||||
dirs = "2.0.2"
|
||||
dunce = "1.0.0"
|
||||
dunce = "1.0.1"
|
||||
eml-parser = "0.1.0"
|
||||
filesize = "0.2.0"
|
||||
futures = { version = "0.3", features = ["compat", "io-compat"] }
|
||||
futures-util = "0.3.4"
|
||||
futures-util = "0.3.5"
|
||||
futures_codec = "0.4"
|
||||
getset = "0.1.0"
|
||||
git2 = { version = "0.13.1", default_features = false }
|
||||
getset = "0.1.1"
|
||||
git2 = { version = "0.13.6", default_features = false }
|
||||
glob = "0.3.0"
|
||||
hex = "0.4"
|
||||
htmlescape = "0.3.1"
|
||||
ical = "0.6.*"
|
||||
ichwh = "0.3.4"
|
||||
indexmap = { version = "1.3.2", features = ["serde-1"] }
|
||||
indexmap = { version = "1.4.0", features = ["serde-1"] }
|
||||
itertools = "0.9.0"
|
||||
language-reporting = "0.4.0"
|
||||
codespan-reporting = "0.9.5"
|
||||
log = "0.4.8"
|
||||
meval = "0.2"
|
||||
natural = "0.5.0"
|
||||
num-bigint = { version = "0.2.6", features = ["serde"] }
|
||||
num-traits = "0.2.11"
|
||||
parking_lot = "0.10.0"
|
||||
pin-utils = "0.1.0-alpha.4"
|
||||
parking_lot = "0.11.0"
|
||||
pin-utils = "0.1.0"
|
||||
pretty-hex = "0.1.1"
|
||||
pretty_env_logger = "0.4.0"
|
||||
prettytable-rs = "0.8.0"
|
||||
ptree = {version = "0.2" }
|
||||
query_interface = "0.3.5"
|
||||
rand = "0.7"
|
||||
regex = "1"
|
||||
roxmltree = "0.10.1"
|
||||
rustyline = "6.1.1"
|
||||
serde = { version = "1.0.106", features = ["derive"] }
|
||||
roxmltree = "0.13.0"
|
||||
rustyline = "6.2.0"
|
||||
serde = { version = "1.0.114", features = ["derive"] }
|
||||
serde-hjson = "0.9.1"
|
||||
serde_bytes = "0.11.3"
|
||||
serde_bytes = "0.11.5"
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.51"
|
||||
serde_json = "1.0.55"
|
||||
serde_urlencoded = "0.6.1"
|
||||
serde_yaml = "0.8"
|
||||
shellexpand = "2.0.0"
|
||||
@ -77,25 +80,29 @@ strip-ansi-escapes = "0.1.0"
|
||||
tempfile = "3.1.0"
|
||||
term = "0.5.2"
|
||||
termcolor = "1.1.0"
|
||||
textwrap = {version = "0.11.0", features = ["term_size"]}
|
||||
term_size = "0.3.2"
|
||||
toml = "0.5.6"
|
||||
typetag = "0.1.4"
|
||||
umask = "0.1"
|
||||
unicode-xid = "0.2.0"
|
||||
typetag = "0.1.5"
|
||||
umask = "1.0.0"
|
||||
unicode-xid = "0.2.1"
|
||||
uuid_crate = { package = "uuid", version = "0.8.1", features = ["v4"] }
|
||||
which = "4.0.1"
|
||||
|
||||
trash = { version = "1.0.0", optional = true }
|
||||
trash = { version = "1.0.1", optional = true }
|
||||
clipboard = { version = "0.5", optional = true }
|
||||
starship = { version = "0.39.0", optional = true }
|
||||
starship = "0.43.0"
|
||||
rayon = "1.3.1"
|
||||
encoding_rs = "0.8.23"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
users = "0.10.0"
|
||||
|
||||
[dependencies.rusqlite]
|
||||
version = "0.22.0"
|
||||
version = "0.23.1"
|
||||
features = ["bundled", "blob"]
|
||||
|
||||
[build-dependencies]
|
||||
nu-build = { version = "0.13.0", path = "../nu-build" }
|
||||
nu-build = { version = "0.16.0", path = "../nu-build" }
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.9"
|
||||
@ -103,6 +110,6 @@ quickcheck_macros = "0.9"
|
||||
|
||||
[features]
|
||||
stable = []
|
||||
starship-prompt = ["starship"]
|
||||
# starship-prompt = ["starship"]
|
||||
clipboard-cli = ["clipboard"]
|
||||
trash-support = ["trash"]
|
||||
|
@ -4,14 +4,14 @@ use crate::commands::plugin::JsonRpc;
|
||||
use crate::commands::plugin::{PluginCommand, PluginSink};
|
||||
use crate::commands::whole_stream_command;
|
||||
use crate::context::Context;
|
||||
#[cfg(not(feature = "starship-prompt"))]
|
||||
use crate::git::current_branch;
|
||||
use crate::path::canonicalize;
|
||||
use crate::prelude::*;
|
||||
use crate::EnvironmentSyncer;
|
||||
use futures_codec::FramedRead;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_errors::{ProximateShellError, ShellDiagnostic, ShellError};
|
||||
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
use log::{debug, trace};
|
||||
use rustyline::error::ReadlineError;
|
||||
@ -22,9 +22,11 @@ use rustyline::{
|
||||
use std::error::Error;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::iter::Iterator;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use rayon::prelude::*;
|
||||
|
||||
fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), ShellError> {
|
||||
let mut child = std::process::Command::new(path)
|
||||
.stdin(std::process::Stdio::piped())
|
||||
@ -45,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 {
|
||||
@ -54,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,
|
||||
@ -75,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
|
||||
@ -106,13 +109,19 @@ fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
match env::var_os("PATH") {
|
||||
Some(paths) => {
|
||||
search_paths.extend(env::split_paths(&paths).collect::<Vec<_>>());
|
||||
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
|
||||
if let Some(plugin_dirs) = config.get("plugin_dirs") {
|
||||
if let Value {
|
||||
value: UntaggedValue::Table(pipelines),
|
||||
..
|
||||
} = plugin_dirs
|
||||
{
|
||||
for pipeline in pipelines {
|
||||
if let Ok(plugin_dir) = pipeline.as_string() {
|
||||
search_paths.push(PathBuf::from(plugin_dir));
|
||||
}
|
||||
}
|
||||
}
|
||||
None => println!("PATH is not defined in the environment."),
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,62 +140,60 @@ pub fn load_plugins(context: &mut Context) -> Result<(), ShellError> {
|
||||
|
||||
pattern.push(std::path::Path::new("nu_plugin_[a-z0-9][a-z0-9]*"));
|
||||
|
||||
match glob::glob_with(&pattern.to_string_lossy(), opts) {
|
||||
Err(_) => {}
|
||||
Ok(binaries) => {
|
||||
for bin in binaries.filter_map(Result::ok) {
|
||||
if !bin.is_file() {
|
||||
continue;
|
||||
let plugs: Vec<_> = glob::glob_with(&pattern.to_string_lossy(), opts)?
|
||||
.filter_map(|x| x.ok())
|
||||
.collect();
|
||||
|
||||
let _failures: Vec<_> = plugs
|
||||
.par_iter()
|
||||
.map(|path| {
|
||||
let bin_name = {
|
||||
if let Some(name) = path.file_name() {
|
||||
match name.to_str() {
|
||||
Some(raw) => raw,
|
||||
None => "",
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
};
|
||||
|
||||
let is_valid_name = {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
bin_name
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
|
||||
}
|
||||
|
||||
let bin_name = {
|
||||
if let Some(name) = bin.file_name() {
|
||||
match name.to_str() {
|
||||
Some(raw) => raw,
|
||||
None => continue,
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let is_valid_name = {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
bin_name
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
bin_name
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '_')
|
||||
}
|
||||
};
|
||||
|
||||
let is_executable = {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
bin_name.ends_with(".exe") || bin_name.ends_with(".bat")
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
if is_valid_name && is_executable {
|
||||
trace!("Trying {:?}", bin.display());
|
||||
|
||||
// we are ok if this plugin load fails
|
||||
let _ = load_plugin(&bin, context);
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
bin_name
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '_')
|
||||
}
|
||||
};
|
||||
|
||||
let is_executable = {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
bin_name.ends_with(".exe") || bin_name.ends_with(".bat")
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
if is_valid_name && is_executable {
|
||||
trace!(target: "nu::load", "plugin infrastructure -> Trying {:?}", path.display());
|
||||
|
||||
// we are ok if this plugin load fails
|
||||
let _ = load_plugin(&path, &mut context.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -223,7 +230,8 @@ fn create_default_starship_config() -> Option<toml::Value> {
|
||||
}
|
||||
|
||||
pub fn create_default_context(
|
||||
syncer: &mut crate::env::environment_syncer::EnvironmentSyncer,
|
||||
syncer: &mut crate::EnvironmentSyncer,
|
||||
interactive: bool,
|
||||
) -> Result<Context, Box<dyn Error>> {
|
||||
syncer.load_environment();
|
||||
|
||||
@ -237,28 +245,31 @@ pub fn create_default_context(
|
||||
context.add_commands(vec![
|
||||
// System/file operations
|
||||
whole_stream_command(Pwd),
|
||||
per_item_command(Ls),
|
||||
per_item_command(Du),
|
||||
whole_stream_command(Ls),
|
||||
whole_stream_command(Du),
|
||||
whole_stream_command(Cd),
|
||||
per_item_command(Remove),
|
||||
per_item_command(Open),
|
||||
whole_stream_command(Remove),
|
||||
whole_stream_command(Open),
|
||||
whole_stream_command(Config),
|
||||
per_item_command(Help),
|
||||
per_item_command(History),
|
||||
whole_stream_command(Help),
|
||||
whole_stream_command(History),
|
||||
whole_stream_command(Save),
|
||||
per_item_command(Touch),
|
||||
per_item_command(Cpy),
|
||||
whole_stream_command(Touch),
|
||||
whole_stream_command(Cpy),
|
||||
whole_stream_command(Date),
|
||||
per_item_command(Calc),
|
||||
per_item_command(Mkdir),
|
||||
per_item_command(Move),
|
||||
per_item_command(Kill),
|
||||
whole_stream_command(Cal),
|
||||
whole_stream_command(Calc),
|
||||
whole_stream_command(Mkdir),
|
||||
whole_stream_command(Move),
|
||||
whole_stream_command(Kill),
|
||||
whole_stream_command(Version),
|
||||
whole_stream_command(Clear),
|
||||
whole_stream_command(What),
|
||||
whole_stream_command(Which),
|
||||
whole_stream_command(Debug),
|
||||
per_item_command(Alias),
|
||||
whole_stream_command(Alias),
|
||||
whole_stream_command(WithEnv),
|
||||
whole_stream_command(Do),
|
||||
// Statistics
|
||||
whole_stream_command(Size),
|
||||
whole_stream_command(Count),
|
||||
@ -268,24 +279,40 @@ pub fn create_default_context(
|
||||
whole_stream_command(Next),
|
||||
whole_stream_command(Previous),
|
||||
whole_stream_command(Shells),
|
||||
per_item_command(Enter),
|
||||
whole_stream_command(Enter),
|
||||
whole_stream_command(Exit),
|
||||
// Viewers
|
||||
whole_stream_command(Autoview),
|
||||
whole_stream_command(Table),
|
||||
// Text manipulation
|
||||
whole_stream_command(Split),
|
||||
whole_stream_command(SplitColumn),
|
||||
whole_stream_command(SplitRow),
|
||||
whole_stream_command(Lines),
|
||||
whole_stream_command(Trim),
|
||||
per_item_command(Echo),
|
||||
per_item_command(Parse),
|
||||
whole_stream_command(Echo),
|
||||
whole_stream_command(Parse),
|
||||
whole_stream_command(Str),
|
||||
whole_stream_command(StrToDecimal),
|
||||
whole_stream_command(StrToInteger),
|
||||
whole_stream_command(StrDowncase),
|
||||
whole_stream_command(StrUpcase),
|
||||
whole_stream_command(StrCapitalize),
|
||||
whole_stream_command(StrFindReplace),
|
||||
whole_stream_command(StrSubstring),
|
||||
whole_stream_command(StrSet),
|
||||
whole_stream_command(StrToDatetime),
|
||||
whole_stream_command(StrTrim),
|
||||
whole_stream_command(StrCollect),
|
||||
whole_stream_command(BuildString),
|
||||
whole_stream_command(Ansi),
|
||||
whole_stream_command(Char),
|
||||
// Column manipulation
|
||||
whole_stream_command(Reject),
|
||||
whole_stream_command(Pick),
|
||||
whole_stream_command(Select),
|
||||
whole_stream_command(Get),
|
||||
per_item_command(Edit),
|
||||
per_item_command(Insert),
|
||||
whole_stream_command(Update),
|
||||
whole_stream_command(Insert),
|
||||
whole_stream_command(SplitBy),
|
||||
// Row manipulation
|
||||
whole_stream_command(Reverse),
|
||||
@ -293,28 +320,44 @@ pub fn create_default_context(
|
||||
whole_stream_command(Prepend),
|
||||
whole_stream_command(SortBy),
|
||||
whole_stream_command(GroupBy),
|
||||
whole_stream_command(GroupByDate),
|
||||
whole_stream_command(First),
|
||||
whole_stream_command(Last),
|
||||
whole_stream_command(Skip),
|
||||
whole_stream_command(Every),
|
||||
whole_stream_command(Nth),
|
||||
per_item_command(Format),
|
||||
per_item_command(Where),
|
||||
whole_stream_command(Drop),
|
||||
whole_stream_command(Format),
|
||||
whole_stream_command(Where),
|
||||
whole_stream_command(Compact),
|
||||
whole_stream_command(Default),
|
||||
whole_stream_command(Skip),
|
||||
whole_stream_command(SkipUntil),
|
||||
whole_stream_command(SkipWhile),
|
||||
whole_stream_command(Keep),
|
||||
whole_stream_command(KeepUntil),
|
||||
whole_stream_command(KeepWhile),
|
||||
whole_stream_command(Range),
|
||||
whole_stream_command(Rename),
|
||||
whole_stream_command(Uniq),
|
||||
per_item_command(Each),
|
||||
whole_stream_command(Each),
|
||||
whole_stream_command(IsEmpty),
|
||||
// Table manipulation
|
||||
whole_stream_command(Merge),
|
||||
whole_stream_command(Shuffle),
|
||||
whole_stream_command(Wrap),
|
||||
whole_stream_command(Pivot),
|
||||
whole_stream_command(Headers),
|
||||
// Data processing
|
||||
whole_stream_command(Histogram),
|
||||
whole_stream_command(Sum),
|
||||
whole_stream_command(Math),
|
||||
whole_stream_command(MathAverage),
|
||||
whole_stream_command(MathMedian),
|
||||
whole_stream_command(MathMinimum),
|
||||
whole_stream_command(MathMode),
|
||||
whole_stream_command(MathMaximum),
|
||||
whole_stream_command(MathSummation),
|
||||
// File format output
|
||||
whole_stream_command(To),
|
||||
whole_stream_command(ToBSON),
|
||||
whole_stream_command(ToCSV),
|
||||
whole_stream_command(ToHTML),
|
||||
@ -327,7 +370,9 @@ pub fn create_default_context(
|
||||
whole_stream_command(ToURL),
|
||||
whole_stream_command(ToYAML),
|
||||
// File format input
|
||||
whole_stream_command(From),
|
||||
whole_stream_command(FromCSV),
|
||||
whole_stream_command(FromEML),
|
||||
whole_stream_command(FromTSV),
|
||||
whole_stream_command(FromSSV),
|
||||
whole_stream_command(FromINI),
|
||||
@ -345,7 +390,12 @@ pub fn create_default_context(
|
||||
whole_stream_command(FromIcs),
|
||||
whole_stream_command(FromVcf),
|
||||
// "Private" commands (not intended to be accessed directly)
|
||||
whole_stream_command(RunExternalCommand),
|
||||
whole_stream_command(RunExternalCommand { interactive }),
|
||||
// Random value generation
|
||||
whole_stream_command(Random),
|
||||
whole_stream_command(RandomBool),
|
||||
whole_stream_command(RandomDice),
|
||||
whole_stream_command(RandomUUID),
|
||||
]);
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
@ -361,9 +411,7 @@ pub fn create_default_context(
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
{
|
||||
context.add_commands(vec![whole_stream_command(
|
||||
crate::commands::clip::clipboard::Clip,
|
||||
)]);
|
||||
context.add_commands(vec![whole_stream_command(crate::commands::clip::Clip)]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -375,7 +423,7 @@ pub async fn run_vec_of_pipelines(
|
||||
redirect_stdin: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut syncer = crate::EnvironmentSyncer::new();
|
||||
let mut context = crate::create_default_context(&mut syncer)?;
|
||||
let mut context = create_default_context(&mut syncer, false)?;
|
||||
|
||||
let _ = crate::load_plugins(&mut context);
|
||||
|
||||
@ -445,14 +493,14 @@ pub async fn run_pipeline_standalone(
|
||||
};
|
||||
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
if error_code != 0 {
|
||||
if error_code != 0 && exit_on_error {
|
||||
std::process::exit(error_code);
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
context.with_host(|host| {
|
||||
print_err(err, host, &Text::from(line.clone()));
|
||||
context.with_host(|_host| {
|
||||
print_err(err, &Text::from(line.clone()));
|
||||
});
|
||||
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
@ -468,9 +516,14 @@ pub async fn run_pipeline_standalone(
|
||||
}
|
||||
|
||||
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input.
|
||||
pub async fn cli() -> Result<(), Box<dyn Error>> {
|
||||
let mut syncer = crate::env::environment_syncer::EnvironmentSyncer::new();
|
||||
let mut context = create_default_context(&mut syncer)?;
|
||||
pub async fn cli(
|
||||
mut syncer: EnvironmentSyncer,
|
||||
mut context: Context,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
let _ = load_plugins(&mut context);
|
||||
|
||||
@ -539,7 +592,30 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
rl.set_helper(Some(crate::shell::Helper::new(context.clone())));
|
||||
|
||||
let edit_mode = config::config(Tag::unknown())?
|
||||
let config = match config::config(Tag::unknown()) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
eprintln!("Config could not be loaded.");
|
||||
if let ShellError {
|
||||
error: ProximateShellError::Diagnostic(ShellDiagnostic { diagnostic }),
|
||||
..
|
||||
} = e
|
||||
{
|
||||
eprintln!("{}", diagnostic.message);
|
||||
}
|
||||
IndexMap::new()
|
||||
}
|
||||
};
|
||||
|
||||
let use_starship = match config.get("use_starship") {
|
||||
Some(b) => match b.as_bool() {
|
||||
Ok(b) => b,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let edit_mode = config
|
||||
.get("edit_mode")
|
||||
.map(|s| match s.value.expect_string() {
|
||||
"vi" => EditMode::Vi,
|
||||
@ -550,21 +626,35 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
rl.set_edit_mode(edit_mode);
|
||||
|
||||
let completion_mode = config::config(Tag::unknown())?
|
||||
let max_history_size = config
|
||||
.get("history_size")
|
||||
.map(|i| i.value.expect_int())
|
||||
.unwrap_or(100_000);
|
||||
|
||||
rl.set_max_history_size(max_history_size as usize);
|
||||
|
||||
let key_timeout = config
|
||||
.get("key_timeout")
|
||||
.map(|s| s.value.expect_int())
|
||||
.unwrap_or(1);
|
||||
|
||||
rl.set_keyseq_timeout(key_timeout as i32);
|
||||
|
||||
let completion_mode = config
|
||||
.get("completion_mode")
|
||||
.map(|s| match s.value.expect_string() {
|
||||
"list" => CompletionType::List,
|
||||
"circular" => CompletionType::Circular,
|
||||
_ => CompletionType::Circular,
|
||||
_ => DEFAULT_COMPLETION_MODE,
|
||||
})
|
||||
.unwrap_or(CompletionType::Circular);
|
||||
.unwrap_or(DEFAULT_COMPLETION_MODE);
|
||||
|
||||
rl.set_completion_type(completion_mode);
|
||||
|
||||
let colored_prompt = {
|
||||
#[cfg(feature = "starship-prompt")]
|
||||
{
|
||||
if use_starship {
|
||||
std::env::set_var("STARSHIP_SHELL", "");
|
||||
std::env::set_var("PWD", &cwd);
|
||||
let mut starship_context =
|
||||
starship::context::Context::new_with_dir(clap::ArgMatches::default(), cwd);
|
||||
|
||||
@ -578,9 +668,63 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
|
||||
_ => {}
|
||||
};
|
||||
starship::print::get_prompt(starship_context)
|
||||
}
|
||||
#[cfg(not(feature = "starship-prompt"))]
|
||||
{
|
||||
} else if let Some(prompt) = config.get("prompt") {
|
||||
let prompt_line = prompt.as_string()?;
|
||||
|
||||
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
|
||||
Ok(result) => {
|
||||
let mut prompt_block =
|
||||
nu_parser::classify_block(&result, context.registry());
|
||||
|
||||
let env = context.get_env();
|
||||
|
||||
prompt_block.block.expand_it_usage();
|
||||
|
||||
match run_block(
|
||||
&prompt_block.block,
|
||||
&mut context,
|
||||
InputStream::empty(),
|
||||
&Value::nothing(),
|
||||
&IndexMap::new(),
|
||||
&env,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(result) => match result.collect_string(Tag::unknown()).await {
|
||||
Ok(string_result) => {
|
||||
let errors = context.get_errors();
|
||||
context.maybe_print_errors(Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
if !errors.is_empty() {
|
||||
"> ".to_string()
|
||||
} else {
|
||||
string_result.item
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
format!(
|
||||
"\x1b[32m{}{}\x1b[m> ",
|
||||
cwd,
|
||||
@ -619,17 +763,17 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
rl.add_history_entry(line.clone());
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.save_history(&History::path());
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
rl.add_history_entry(line.clone());
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.save_history(&History::path());
|
||||
|
||||
context.with_host(|host| {
|
||||
print_err(err, host, &Text::from(line.clone()));
|
||||
context.with_host(|_host| {
|
||||
print_err(err, &Text::from(line.clone()));
|
||||
});
|
||||
|
||||
context.maybe_print_errors(Text::from(line.clone()));
|
||||
@ -679,7 +823,7 @@ fn chomp_newline(s: &str) -> &str {
|
||||
}
|
||||
}
|
||||
|
||||
enum LineResult {
|
||||
pub enum LineResult {
|
||||
Success(String),
|
||||
Error(String, ShellError),
|
||||
CtrlC,
|
||||
@ -687,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,
|
||||
@ -698,6 +842,7 @@ async fn process_line(
|
||||
|
||||
Ok(line) => {
|
||||
let line = chomp_newline(line);
|
||||
ctx.raw_input = line.to_string();
|
||||
|
||||
let result = match nu_parser::lite_parse(&line, 0) {
|
||||
Err(err) => {
|
||||
@ -710,7 +855,7 @@ async fn process_line(
|
||||
debug!("=== Parsed ===");
|
||||
debug!("{:#?}", result);
|
||||
|
||||
let classified_block = nu_parser::classify_block(&result, ctx.registry());
|
||||
let mut classified_block = nu_parser::classify_block(&result, ctx.registry());
|
||||
|
||||
debug!("{:#?}", classified_block);
|
||||
//println!("{:#?}", pipeline);
|
||||
@ -759,9 +904,9 @@ async fn process_line(
|
||||
.as_ref()
|
||||
.map(NamedArguments::is_empty)
|
||||
.unwrap_or(true)
|
||||
&& dunce::canonicalize(&name).is_ok()
|
||||
&& PathBuf::from(&name).is_dir()
|
||||
&& ichwh::which(&name).await.unwrap_or(None).is_none()
|
||||
&& canonicalize(ctx.shell_manager.path(), name).is_ok()
|
||||
&& Path::new(&name).is_dir()
|
||||
&& which::which(&name).is_err()
|
||||
{
|
||||
// Here we work differently if we're in Windows because of the expected Windows behavior
|
||||
#[cfg(windows)]
|
||||
@ -787,7 +932,8 @@ async fn process_line(
|
||||
ctx.shell_manager.set_path(val.to_string());
|
||||
return LineResult::Success(line.to_string());
|
||||
} else {
|
||||
ctx.shell_manager.set_path(name.to_string());
|
||||
ctx.shell_manager
|
||||
.set_path(format!("{}\\", name.to_string()));
|
||||
return LineResult::Success(line.to_string());
|
||||
}
|
||||
} else {
|
||||
@ -829,7 +975,20 @@ async fn process_line(
|
||||
InputStream::empty()
|
||||
};
|
||||
|
||||
match run_block(&classified_block.block, ctx, input_stream, &Scope::empty()).await {
|
||||
classified_block.block.expand_it_usage();
|
||||
|
||||
trace!("{:#?}", classified_block);
|
||||
let env = ctx.get_env();
|
||||
match run_block(
|
||||
&classified_block.block,
|
||||
ctx,
|
||||
input_stream,
|
||||
&Value::nothing(),
|
||||
&IndexMap::new(),
|
||||
&env,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(input) => {
|
||||
// Running a pipeline gives us back a stream that we can then
|
||||
// work through. At the top level, we just want to pull on the
|
||||
@ -841,11 +1000,15 @@ async fn process_line(
|
||||
shell_manager: ctx.shell_manager.clone(),
|
||||
host: ctx.host.clone(),
|
||||
ctrl_c: ctx.ctrl_c.clone(),
|
||||
current_errors: ctx.current_errors.clone(),
|
||||
registry: ctx.registry.clone(),
|
||||
name: Tag::unknown(),
|
||||
raw_input: line.to_string(),
|
||||
};
|
||||
|
||||
if let Ok(mut output_stream) = crate::commands::autoview::autoview(context) {
|
||||
if let Ok(mut output_stream) =
|
||||
crate::commands::autoview::autoview(context).await
|
||||
{
|
||||
loop {
|
||||
match output_stream.try_next().await {
|
||||
Ok(Some(ReturnSuccess::Value(Value {
|
||||
@ -877,26 +1040,25 @@ async fn process_line(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_err(err: ShellError, host: &dyn Host, source: &Text) {
|
||||
pub fn print_err(err: ShellError, source: &Text) {
|
||||
if let Some(diag) = err.into_diagnostic() {
|
||||
let writer = host.err_termcolor();
|
||||
let mut source = source.to_string();
|
||||
source.push_str(" ");
|
||||
let files = nu_parser::Files::new(source);
|
||||
let source = source.to_string();
|
||||
let mut files = codespan_reporting::files::SimpleFiles::new();
|
||||
files.add("shell", source);
|
||||
|
||||
let writer = codespan_reporting::term::termcolor::StandardStream::stderr(
|
||||
codespan_reporting::term::termcolor::ColorChoice::Always,
|
||||
);
|
||||
let config = codespan_reporting::term::Config::default();
|
||||
|
||||
let _ = std::panic::catch_unwind(move || {
|
||||
let _ = language_reporting::emit(
|
||||
&mut writer.lock(),
|
||||
&files,
|
||||
&diag,
|
||||
&language_reporting::DefaultConfig,
|
||||
);
|
||||
let _ = codespan_reporting::term::emit(&mut writer.lock(), &config, &files, &diag);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
#[quickcheck]
|
||||
fn quickcheck_parse(data: String) -> bool {
|
||||
if let Ok(lite_block) = nu_parser::lite_parse(&data, 0) {
|
||||
|
@ -5,12 +5,17 @@ mod from_delimited_data;
|
||||
mod to_delimited_data;
|
||||
|
||||
pub(crate) mod alias;
|
||||
pub(crate) mod ansi;
|
||||
pub(crate) mod append;
|
||||
pub(crate) mod args;
|
||||
pub(crate) mod autoview;
|
||||
pub(crate) mod build_string;
|
||||
pub(crate) mod cal;
|
||||
pub(crate) mod calc;
|
||||
pub(crate) mod cd;
|
||||
pub(crate) mod char_;
|
||||
pub(crate) mod classified;
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub(crate) mod clip;
|
||||
pub(crate) mod command;
|
||||
pub(crate) mod compact;
|
||||
@ -20,18 +25,22 @@ 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;
|
||||
pub(crate) mod echo;
|
||||
pub(crate) mod edit;
|
||||
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;
|
||||
pub(crate) mod from;
|
||||
pub(crate) mod from_bson;
|
||||
pub(crate) mod from_csv;
|
||||
pub(crate) mod from_eml;
|
||||
pub(crate) mod from_ics;
|
||||
pub(crate) mod from_ini;
|
||||
pub(crate) mod from_json;
|
||||
@ -47,28 +56,35 @@ pub(crate) mod from_xml;
|
||||
pub(crate) mod from_yaml;
|
||||
pub(crate) mod get;
|
||||
pub(crate) mod group_by;
|
||||
pub(crate) mod group_by_date;
|
||||
pub(crate) mod headers;
|
||||
pub(crate) mod help;
|
||||
pub(crate) mod histogram;
|
||||
pub(crate) mod history;
|
||||
pub(crate) mod insert;
|
||||
pub(crate) mod is_empty;
|
||||
pub(crate) mod keep;
|
||||
pub(crate) mod keep_until;
|
||||
pub(crate) mod keep_while;
|
||||
pub(crate) mod last;
|
||||
pub(crate) mod lines;
|
||||
pub(crate) mod ls;
|
||||
#[allow(unused)]
|
||||
pub(crate) mod map_max_by;
|
||||
pub(crate) mod math;
|
||||
pub(crate) mod merge;
|
||||
pub(crate) mod mkdir;
|
||||
pub(crate) mod mv;
|
||||
pub(crate) mod next;
|
||||
pub(crate) mod nth;
|
||||
pub(crate) mod open;
|
||||
pub(crate) mod parse;
|
||||
pub(crate) mod pick;
|
||||
pub(crate) mod pivot;
|
||||
pub(crate) mod plugin;
|
||||
pub(crate) mod prepend;
|
||||
pub(crate) mod prev;
|
||||
pub(crate) mod pwd;
|
||||
pub(crate) mod random;
|
||||
pub(crate) mod range;
|
||||
#[allow(unused)]
|
||||
pub(crate) mod reduce_by;
|
||||
@ -79,20 +95,22 @@ pub(crate) mod rm;
|
||||
pub(crate) mod run_alias;
|
||||
pub(crate) mod run_external;
|
||||
pub(crate) mod save;
|
||||
pub(crate) mod select;
|
||||
pub(crate) mod shells;
|
||||
pub(crate) mod shuffle;
|
||||
pub(crate) mod size;
|
||||
pub(crate) mod skip;
|
||||
pub(crate) mod skip_until;
|
||||
pub(crate) mod skip_while;
|
||||
pub(crate) mod sort_by;
|
||||
pub(crate) mod split;
|
||||
pub(crate) mod split_by;
|
||||
pub(crate) mod split_column;
|
||||
pub(crate) mod split_row;
|
||||
pub(crate) mod sum;
|
||||
pub(crate) mod str_;
|
||||
#[allow(unused)]
|
||||
pub(crate) mod t_sort_by;
|
||||
pub(crate) mod table;
|
||||
pub(crate) mod tags;
|
||||
pub(crate) mod to;
|
||||
pub(crate) mod to_bson;
|
||||
pub(crate) mod to_csv;
|
||||
pub(crate) mod to_html;
|
||||
@ -105,22 +123,27 @@ pub(crate) mod to_url;
|
||||
pub(crate) mod to_yaml;
|
||||
pub(crate) mod trim;
|
||||
pub(crate) mod uniq;
|
||||
pub(crate) mod update;
|
||||
pub(crate) mod version;
|
||||
pub(crate) mod what;
|
||||
pub(crate) mod where_;
|
||||
pub(crate) mod which_;
|
||||
pub(crate) mod with_env;
|
||||
pub(crate) mod wrap;
|
||||
|
||||
pub(crate) use autoview::Autoview;
|
||||
pub(crate) use cd::Cd;
|
||||
pub(crate) use command::{
|
||||
per_item_command, whole_stream_command, Command, PerItemCommand, UnevaluatedCallInfo,
|
||||
WholeStreamCommand,
|
||||
whole_stream_command, Command, Example, UnevaluatedCallInfo, WholeStreamCommand,
|
||||
};
|
||||
|
||||
pub(crate) use alias::Alias;
|
||||
pub(crate) use ansi::Ansi;
|
||||
pub(crate) use append::Append;
|
||||
pub(crate) use build_string::BuildString;
|
||||
pub(crate) use cal::Cal;
|
||||
pub(crate) use calc::Calc;
|
||||
pub(crate) use char_::Char;
|
||||
pub(crate) use compact::Compact;
|
||||
pub(crate) use config::Config;
|
||||
pub(crate) use count::Count;
|
||||
@ -128,10 +151,13 @@ 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;
|
||||
pub(crate) use echo::Echo;
|
||||
pub(crate) use edit::Edit;
|
||||
pub(crate) use is_empty::IsEmpty;
|
||||
pub(crate) use update::Update;
|
||||
pub(crate) mod kill;
|
||||
pub(crate) use kill::Kill;
|
||||
pub(crate) mod clear;
|
||||
@ -140,11 +166,14 @@ 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;
|
||||
pub(crate) use from::From;
|
||||
pub(crate) use from_bson::FromBSON;
|
||||
pub(crate) use from_csv::FromCSV;
|
||||
pub(crate) use from_eml::FromEML;
|
||||
pub(crate) use from_ics::FromIcs;
|
||||
pub(crate) use from_ini::FromINI;
|
||||
pub(crate) use from_json::FromJSON;
|
||||
@ -162,27 +191,35 @@ pub(crate) use from_yaml::FromYAML;
|
||||
pub(crate) use from_yaml::FromYML;
|
||||
pub(crate) use get::Get;
|
||||
pub(crate) use group_by::GroupBy;
|
||||
pub(crate) use group_by_date::GroupByDate;
|
||||
pub(crate) use headers::Headers;
|
||||
pub(crate) use help::Help;
|
||||
pub(crate) use histogram::Histogram;
|
||||
pub(crate) use history::History;
|
||||
pub(crate) use insert::Insert;
|
||||
pub(crate) use keep::Keep;
|
||||
pub(crate) use keep_until::KeepUntil;
|
||||
pub(crate) use keep_while::KeepWhile;
|
||||
pub(crate) use last::Last;
|
||||
pub(crate) use lines::Lines;
|
||||
pub(crate) use ls::Ls;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use map_max_by::MapMaxBy;
|
||||
pub(crate) use math::{
|
||||
Math, MathAverage, MathMaximum, MathMedian, MathMinimum, MathMode, MathSummation,
|
||||
};
|
||||
pub(crate) use merge::Merge;
|
||||
pub(crate) use mkdir::Mkdir;
|
||||
pub(crate) use mv::Move;
|
||||
pub(crate) use next::Next;
|
||||
pub(crate) use nth::Nth;
|
||||
pub(crate) use open::Open;
|
||||
pub(crate) use parse::Parse;
|
||||
pub(crate) use pick::Pick;
|
||||
pub(crate) use pivot::Pivot;
|
||||
pub(crate) use prepend::Prepend;
|
||||
pub(crate) use prev::Previous;
|
||||
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;
|
||||
@ -192,20 +229,27 @@ pub(crate) use reverse::Reverse;
|
||||
pub(crate) use rm::Remove;
|
||||
pub(crate) use run_external::RunExternalCommand;
|
||||
pub(crate) use save::Save;
|
||||
pub(crate) use select::Select;
|
||||
pub(crate) use shells::Shells;
|
||||
pub(crate) use shuffle::Shuffle;
|
||||
pub(crate) use size::Size;
|
||||
pub(crate) use skip::Skip;
|
||||
pub(crate) use skip_until::SkipUntil;
|
||||
pub(crate) use skip_while::SkipWhile;
|
||||
pub(crate) use sort_by::SortBy;
|
||||
pub(crate) use split::Split;
|
||||
pub(crate) use split::SplitColumn;
|
||||
pub(crate) use split::SplitRow;
|
||||
pub(crate) use split_by::SplitBy;
|
||||
pub(crate) use split_column::SplitColumn;
|
||||
pub(crate) use split_row::SplitRow;
|
||||
pub(crate) use sum::Sum;
|
||||
pub(crate) use str_::{
|
||||
Str, StrCapitalize, StrCollect, StrDowncase, StrFindReplace, StrSet, StrSubstring,
|
||||
StrToDatetime, StrToDecimal, StrToInteger, StrTrim, StrUpcase,
|
||||
};
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use t_sort_by::TSortBy;
|
||||
pub(crate) use table::Table;
|
||||
pub(crate) use tags::Tags;
|
||||
pub(crate) use to::To;
|
||||
pub(crate) use to_bson::ToBSON;
|
||||
pub(crate) use to_csv::ToCSV;
|
||||
pub(crate) use to_html::ToHTML;
|
||||
@ -224,4 +268,5 @@ pub(crate) use version::Version;
|
||||
pub(crate) use what::What;
|
||||
pub(crate) use where_::Where;
|
||||
pub(crate) use which_::Which;
|
||||
pub(crate) use with_env::WithEnv;
|
||||
pub(crate) use wrap::Wrap;
|
||||
|
@ -1,14 +1,25 @@
|
||||
use crate::commands::PerItemCommand;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::data::config;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
CallInfo, CommandAction, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Alias;
|
||||
|
||||
impl PerItemCommand for Alias {
|
||||
#[derive(Deserialize)]
|
||||
pub struct AliasArgs {
|
||||
pub name: Tagged<String>,
|
||||
pub args: Vec<Value>,
|
||||
pub block: Block,
|
||||
pub save: Option<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Alias {
|
||||
fn name(&self) -> &str {
|
||||
"alias"
|
||||
}
|
||||
@ -17,49 +28,124 @@ impl PerItemCommand for Alias {
|
||||
Signature::build("alias")
|
||||
.required("name", SyntaxShape::String, "the name of the alias")
|
||||
.required("args", SyntaxShape::Table, "the arguments to the alias")
|
||||
.required("block", SyntaxShape::Block, "the block to run on each row")
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run as the body of the alias",
|
||||
)
|
||||
.switch("save", "save the alias to your config", Some('s'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Run a block on each row of the table."
|
||||
"Define a shortcut for another command."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
_registry: &CommandRegistry,
|
||||
_raw_args: &RawCommandArgs,
|
||||
_input: Value,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let call_info = call_info.clone();
|
||||
let stream = async_stream! {
|
||||
match (call_info.args.expect_nth(0)?, call_info.args.expect_nth(1)?, call_info.args.expect_nth(2)?) {
|
||||
(Value {value: UntaggedValue::Primitive(Primitive::String(name)), .. },
|
||||
Value { value: UntaggedValue::Table(list), .. },
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag
|
||||
}) => {
|
||||
let mut args: Vec<String> = vec![];
|
||||
for item in list.iter() {
|
||||
if let Ok(string) = item.as_string() {
|
||||
args.push(format!("${}", string));
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error("Expected a string", "expected a string", item.tag()));
|
||||
}
|
||||
}
|
||||
yield ReturnSuccess::action(CommandAction::AddAlias(name.to_string(), args, block.clone()))
|
||||
}
|
||||
_ => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected `name [args] {block}",
|
||||
"needs a name, args, and a block",
|
||||
call_info.name_tag,
|
||||
))
|
||||
}
|
||||
};
|
||||
};
|
||||
alias(args, registry).await
|
||||
}
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "An alias without parameters",
|
||||
example: "alias say-hi [] { echo 'Hello!' }",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "An alias with a single parameter",
|
||||
example: "alias l [x] { ls $x }",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn alias(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
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)?;
|
||||
|
||||
// process the alias to remove the --save flag
|
||||
let left_brace = raw_input.find('{').unwrap_or(0);
|
||||
let right_brace = raw_input.rfind('}').unwrap_or_else(|| raw_input.len());
|
||||
let left = raw_input[..left_brace]
|
||||
.replace("--save", "")
|
||||
.replace("-s", "");
|
||||
let right = raw_input[right_brace..]
|
||||
.replace("--save", "")
|
||||
.replace("-s", "");
|
||||
raw_input = format!("{}{}{}", left, &raw_input[left_brace..right_brace], right);
|
||||
|
||||
// create a value from raw_input alias
|
||||
let alias: Value = raw_input.trim().to_string().into();
|
||||
let alias_start = raw_input.find('[').unwrap_or(0); // used to check if the same alias already exists
|
||||
|
||||
// add to startup if alias doesn't exist and replce if it does
|
||||
match result.get_mut("startup") {
|
||||
Some(startup) => {
|
||||
if let UntaggedValue::Table(ref mut commands) = startup.value {
|
||||
if let Some(command) = commands.iter_mut().find(|command| {
|
||||
let cmd_str = command.as_string().unwrap_or_default();
|
||||
cmd_str.starts_with(&raw_input[..alias_start])
|
||||
}) {
|
||||
*command = alias;
|
||||
} else {
|
||||
commands.push(alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let table = UntaggedValue::table(&[alias]);
|
||||
result.insert("startup".to_string(), table.into_value(Tag::default()));
|
||||
}
|
||||
}
|
||||
config::write(&result, &None)?;
|
||||
}
|
||||
|
||||
for item in list.iter() {
|
||||
if let Ok(string) = item.as_string() {
|
||||
processed_args.push(format!("${}", string));
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a string",
|
||||
"expected a string",
|
||||
item.tag(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::AddAlias(name.to_string(), processed_args, block),
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Alias;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Alias {})
|
||||
}
|
||||
}
|
||||
|
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 {})
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, Value};
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AppendArgs {
|
||||
@ -11,6 +11,7 @@ struct AppendArgs {
|
||||
|
||||
pub struct Append;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Append {
|
||||
fn name(&self) -> &str {
|
||||
"append"
|
||||
@ -28,22 +29,40 @@ impl WholeStreamCommand for Append {
|
||||
"Append the given row to the table"
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, append)?.run()
|
||||
let (AppendArgs { row }, input) = args.process(registry).await?;
|
||||
|
||||
let eos = futures::stream::iter(vec![row]);
|
||||
|
||||
Ok(input.chain(eos).to_output_stream())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Add something to the end of a list or table",
|
||||
example: "echo [1 2 3] | append 4",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(4).into(),
|
||||
]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn append(
|
||||
AppendArgs { row }: AppendArgs,
|
||||
RunnableContext { input, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let mut after: VecDeque<Value> = VecDeque::new();
|
||||
after.push_back(row);
|
||||
let after = futures::stream::iter(after);
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Append;
|
||||
|
||||
Ok(OutputStream::from_input(input.chain(after)))
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Append {})
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,16 @@
|
||||
use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::format_leaf;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
|
||||
use nu_protocol::{Primitive, Scope, Signature, UntaggedValue, Value};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
pub struct Autoview;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Autoview {
|
||||
fn name(&self) -> &str {
|
||||
"autoview"
|
||||
@ -22,7 +24,7 @@ impl WholeStreamCommand for Autoview {
|
||||
"View the contents of the pipeline as a table or list."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
@ -33,14 +35,33 @@ impl WholeStreamCommand for Autoview {
|
||||
shell_manager: args.shell_manager,
|
||||
host: args.host,
|
||||
ctrl_c: args.ctrl_c,
|
||||
current_errors: args.current_errors,
|
||||
name: args.call_info.name_tag,
|
||||
raw_input: args.raw_input,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Automatically view the results",
|
||||
example: "ls | autoview",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Autoview is also implied. The above can be written as",
|
||||
example: "ls",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RunnableContextWithoutInput {
|
||||
pub shell_manager: ShellManager,
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub registry: CommandRegistry,
|
||||
pub name: Tag,
|
||||
@ -52,6 +73,7 @@ impl RunnableContextWithoutInput {
|
||||
shell_manager: context.shell_manager,
|
||||
host: context.host,
|
||||
ctrl_c: context.ctrl_c,
|
||||
current_errors: context.current_errors,
|
||||
registry: context.registry,
|
||||
name: context.name,
|
||||
};
|
||||
@ -59,221 +81,235 @@ impl RunnableContextWithoutInput {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||
pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||
let binary = context.get_command("binaryview");
|
||||
let text = context.get_command("textview");
|
||||
let table = context.get_command("table");
|
||||
|
||||
Ok(OutputStream::new(async_stream! {
|
||||
let (mut input_stream, context) = RunnableContextWithoutInput::convert(context);
|
||||
#[derive(PartialEq)]
|
||||
enum AutoPivotMode {
|
||||
Auto,
|
||||
Always,
|
||||
Never,
|
||||
}
|
||||
|
||||
let pivot_mode = crate::data::config::config(Tag::unknown());
|
||||
let pivot_mode = if let Some(v) = pivot_mode?.get("pivot_mode") {
|
||||
match v.as_string() {
|
||||
Ok(m) if m.to_lowercase() == "auto" => AutoPivotMode::Auto,
|
||||
Ok(m) if m.to_lowercase() == "always" => AutoPivotMode::Always,
|
||||
Ok(m) if m.to_lowercase() == "never" => AutoPivotMode::Never,
|
||||
_ => AutoPivotMode::Always,
|
||||
}
|
||||
} else {
|
||||
AutoPivotMode::Always
|
||||
};
|
||||
|
||||
let (mut input_stream, context) = RunnableContextWithoutInput::convert(context);
|
||||
let term_width = context.host.lock().width();
|
||||
|
||||
if let Some(x) = input_stream.next().await {
|
||||
match input_stream.next().await {
|
||||
Some(x) => {
|
||||
match input_stream.next().await {
|
||||
Some(y) => {
|
||||
let ctrl_c = context.ctrl_c.clone();
|
||||
let stream = async_stream! {
|
||||
yield Ok(x);
|
||||
yield Ok(y);
|
||||
Some(y) => {
|
||||
let ctrl_c = context.ctrl_c.clone();
|
||||
let xy = vec![x, y];
|
||||
let xy_stream = futures::stream::iter(xy)
|
||||
.chain(input_stream)
|
||||
.interruptible(ctrl_c);
|
||||
|
||||
loop {
|
||||
match input_stream.next().await {
|
||||
Some(z) => {
|
||||
if ctrl_c.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
yield Ok(z);
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
};
|
||||
let stream = stream.to_input_stream();
|
||||
let stream = InputStream::from_stream(xy_stream);
|
||||
|
||||
if let Some(table) = table {
|
||||
let command_args = create_default_command_args(&context).with_input(stream);
|
||||
let result = table.run(command_args, &context.registry);
|
||||
if let Some(table) = table {
|
||||
let command_args = create_default_command_args(&context).with_input(stream);
|
||||
let result = table.run(command_args, &context.registry).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
match x {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(ref s)),
|
||||
tag: Tag { anchor, span },
|
||||
} if anchor.is_some() => {
|
||||
if let Some(text) = text {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(
|
||||
UntaggedValue::string(s).into_value(Tag { anchor, span }),
|
||||
);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = text.run(command_args, &context.registry).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{}", s);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
match x {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(ref s)),
|
||||
tag: Tag { anchor, span },
|
||||
} if anchor.is_some() => {
|
||||
if let Some(text) = text {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(UntaggedValue::string(s).into_value(Tag { anchor, span }));
|
||||
let command_args = create_default_command_args(&context).with_input(stream);
|
||||
let result = text.run(command_args, &context.registry);
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{}", s);
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(s)),
|
||||
..
|
||||
} => {
|
||||
out!("{}", s);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Line(ref s)),
|
||||
tag: Tag { anchor, span },
|
||||
} if anchor.is_some() => {
|
||||
if let Some(text) = text {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(UntaggedValue::string(s).into_value(Tag { anchor, span }));
|
||||
let command_args = create_default_command_args(&context).with_input(stream);
|
||||
let result = text.run(command_args, &context.registry);
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{}\n", s);
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Line(s)),
|
||||
..
|
||||
} => {
|
||||
out!("{}\n", s);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Path(s)),
|
||||
..
|
||||
} => {
|
||||
out!("{}", s.display());
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Int(n)),
|
||||
..
|
||||
} => {
|
||||
out!("{}", n);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Decimal(n)),
|
||||
..
|
||||
} => {
|
||||
out!("{}", n);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Boolean(b)),
|
||||
..
|
||||
} => {
|
||||
out!("{}", b);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(s)),
|
||||
..
|
||||
} => {
|
||||
out!("{}", s);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Line(ref s)),
|
||||
tag: Tag { anchor, span },
|
||||
} if anchor.is_some() => {
|
||||
if let Some(text) = text {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(
|
||||
UntaggedValue::string(s).into_value(Tag { anchor, span }),
|
||||
);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = text.run(command_args, &context.registry).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{}\n", s);
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Line(s)),
|
||||
..
|
||||
} => {
|
||||
out!("{}\n", s);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Path(s)),
|
||||
..
|
||||
} => {
|
||||
out!("{}", s.display());
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Int(n)),
|
||||
..
|
||||
} => {
|
||||
out!("{}", n);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Decimal(n)),
|
||||
..
|
||||
} => {
|
||||
// TODO: normalize decimal to remove trailing zeros.
|
||||
// normalization will be available in next release of bigdecimal crate
|
||||
let mut output = n.to_string();
|
||||
if output.contains('.') {
|
||||
output = output.trim_end_matches('0').to_owned();
|
||||
}
|
||||
if output.ends_with('.') {
|
||||
output.push('0');
|
||||
}
|
||||
out!("{}", output);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Boolean(b)),
|
||||
..
|
||||
} => {
|
||||
out!("{}", b);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Duration(_)),
|
||||
..
|
||||
} => {
|
||||
let output = format_leaf(&x).plain_string(100_000);
|
||||
out!("{}", output);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Date(d)),
|
||||
..
|
||||
} => {
|
||||
out!("{}", d);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Range(_)),
|
||||
..
|
||||
} => {
|
||||
let output = format_leaf(&x).plain_string(100_000);
|
||||
out!("{}", output);
|
||||
}
|
||||
|
||||
Value { value: UntaggedValue::Primitive(Primitive::Binary(ref b)), .. } => {
|
||||
if let Some(binary) = binary {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(x);
|
||||
let command_args = create_default_command_args(&context).with_input(stream);
|
||||
let result = binary.run(command_args, &context.registry);
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
use pretty_hex::*;
|
||||
out!("{:?}", b.hex_dump());
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Binary(ref b)),
|
||||
..
|
||||
} => {
|
||||
if let Some(binary) = binary {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(x);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = binary.run(command_args, &context.registry).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
use pretty_hex::*;
|
||||
out!("{:?}", b.hex_dump());
|
||||
}
|
||||
}
|
||||
|
||||
Value { value: UntaggedValue::Error(e), .. } => {
|
||||
yield Err(e);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Error(e),
|
||||
..
|
||||
} => {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
Value { value: UntaggedValue::Row(row), ..} => {
|
||||
use prettytable::format::{FormatBuilder, LinePosition, LineSeparator};
|
||||
use prettytable::{color, Attr, Cell, Row, Table};
|
||||
use crate::data::value::{format_leaf, style_leaf};
|
||||
use textwrap::fill;
|
||||
Value {
|
||||
value: UntaggedValue::Row(row),
|
||||
..
|
||||
} if pivot_mode == AutoPivotMode::Always
|
||||
|| (pivot_mode == AutoPivotMode::Auto
|
||||
&& (row
|
||||
.entries
|
||||
.iter()
|
||||
.map(|(_, v)| v.convert_to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.iter()
|
||||
.fold(0usize, |acc, len| acc + len.len())
|
||||
+ row.entries.iter().count() * 2)
|
||||
> term_width) =>
|
||||
{
|
||||
let mut entries = vec![];
|
||||
for (key, value) in row.entries.iter() {
|
||||
entries.push(vec![
|
||||
nu_table::StyledString::new(
|
||||
key.to_string(),
|
||||
nu_table::TextStyle {
|
||||
alignment: nu_table::Alignment::Left,
|
||||
color: Some(ansi_term::Color::Green),
|
||||
is_bold: true,
|
||||
},
|
||||
),
|
||||
nu_table::StyledString::new(
|
||||
format_leaf(value).plain_string(100_000),
|
||||
nu_table::TextStyle::basic(),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
let termwidth = std::cmp::max(textwrap::termwidth(), 20);
|
||||
let table =
|
||||
nu_table::Table::new(vec![], entries, nu_table::Theme::compact());
|
||||
|
||||
enum TableMode {
|
||||
Light,
|
||||
Normal,
|
||||
}
|
||||
nu_table::draw_table(&table, term_width);
|
||||
}
|
||||
|
||||
let mut table = Table::new();
|
||||
let table_mode = crate::data::config::config(Tag::unknown());
|
||||
|
||||
let table_mode = if let Some(s) = table_mode?.get("table_mode") {
|
||||
match s.as_string() {
|
||||
Ok(typ) if typ == "light" => TableMode::Light,
|
||||
_ => TableMode::Normal,
|
||||
}
|
||||
} else {
|
||||
TableMode::Normal
|
||||
};
|
||||
|
||||
match table_mode {
|
||||
TableMode::Light => {
|
||||
table.set_format(
|
||||
FormatBuilder::new()
|
||||
.separator(LinePosition::Title, LineSeparator::new('─', '─', ' ', ' '))
|
||||
.padding(1, 1)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
table.set_format(
|
||||
FormatBuilder::new()
|
||||
.column_separator('│')
|
||||
.separator(LinePosition::Top, LineSeparator::new('─', '┬', ' ', ' '))
|
||||
.separator(LinePosition::Title, LineSeparator::new('─', '┼', ' ', ' '))
|
||||
.separator(LinePosition::Bottom, LineSeparator::new('─', '┴', ' ', ' '))
|
||||
.padding(1, 1)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut max_key_len = 0;
|
||||
for (key, _) in row.entries.iter() {
|
||||
max_key_len = std::cmp::max(max_key_len, key.chars().count());
|
||||
}
|
||||
|
||||
if max_key_len > (termwidth/2 - 1) {
|
||||
max_key_len = termwidth/2 - 1;
|
||||
}
|
||||
|
||||
let max_val_len = termwidth - max_key_len - 5;
|
||||
|
||||
for (key, value) in row.entries.iter() {
|
||||
table.add_row(Row::new(vec![Cell::new(&fill(&key, max_key_len)).with_style(Attr::ForegroundColor(color::GREEN)).with_style(Attr::Bold),
|
||||
Cell::new(&fill(&format_leaf(value).plain_string(100_000), max_val_len))]));
|
||||
}
|
||||
|
||||
table.printstd();
|
||||
|
||||
// table.print_term(&mut *context.host.lock().out_terminal().ok_or_else(|| ShellError::untagged_runtime_error("Could not open terminal for output"))?)
|
||||
// .map_err(|_| ShellError::untagged_runtime_error("Internal error: could not print to terminal (for unix systems check to make sure TERM is set)"))?;
|
||||
}
|
||||
|
||||
Value { value: ref item, .. } => {
|
||||
if let Some(table) = table {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(x);
|
||||
let command_args = create_default_command_args(&context).with_input(stream);
|
||||
let result = table.run(command_args, &context.registry);
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{:?}", item);
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: ref item, ..
|
||||
} => {
|
||||
if let Some(table) = table {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(x);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = table.run(command_args, &context.registry).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{:?}", item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
//out!("<no results>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Needed for async_stream to type check
|
||||
if false {
|
||||
yield ReturnSuccess::value(UntaggedValue::nothing().into_untagged_value());
|
||||
}
|
||||
}))
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawCommandArgs {
|
||||
@ -281,6 +317,7 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
|
||||
RawCommandArgs {
|
||||
host: context.host.clone(),
|
||||
ctrl_c: context.ctrl_c.clone(),
|
||||
current_errors: context.current_errors.clone(),
|
||||
shell_manager: context.shell_manager.clone(),
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: hir::Call {
|
||||
@ -294,7 +331,19 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
|
||||
is_last: true,
|
||||
},
|
||||
name_tag: context.name.clone(),
|
||||
scope: Scope::empty(),
|
||||
scope: Scope::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Autoview;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Autoview {})
|
||||
}
|
||||
}
|
||||
|
56
crates/nu-cli/src/commands/build_string.rs
Normal file
56
crates/nu-cli/src/commands/build_string.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::format_leaf;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct BuildStringArgs {
|
||||
rest: Vec<Value>,
|
||||
}
|
||||
|
||||
pub struct BuildString;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for BuildString {
|
||||
fn name(&self) -> &str {
|
||||
"build-string"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("build-string")
|
||||
.rest(SyntaxShape::Any, "all values to form into the string")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Builds a string from the arguments"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (BuildStringArgs { rest }, _) = args.process(®istry).await?;
|
||||
|
||||
let mut output_string = String::new();
|
||||
|
||||
for r in rest {
|
||||
output_string.push_str(&format_leaf(&r).plain_string(100_000))
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output_string).into_value(tag),
|
||||
)))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Builds a string from a string and a number, without spaces between them",
|
||||
example: "build-string 'foo' 3",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
352
crates/nu-cli/src/commands/cal.rs
Normal file
352
crates/nu-cli/src/commands/cal.rs
Normal file
@ -0,0 +1,352 @@
|
||||
use crate::commands::{command::EvaluatedWholeStreamCommandArgs, WholeStreamCommand};
|
||||
use crate::prelude::*;
|
||||
use chrono::{Datelike, Local, NaiveDate};
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct Cal;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Cal {
|
||||
fn name(&self) -> &str {
|
||||
"cal"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("cal")
|
||||
.switch("year", "Display the year column", Some('y'))
|
||||
.switch("quarter", "Display the quarter column", Some('q'))
|
||||
.switch("month", "Display the month column", Some('m'))
|
||||
.named(
|
||||
"full-year",
|
||||
SyntaxShape::Int,
|
||||
"Display a year-long calendar for the specified year",
|
||||
None,
|
||||
)
|
||||
.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",
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Display a calendar."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
cal(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "This month's calendar",
|
||||
example: "cal",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "The calendar for all of 2012",
|
||||
example: "cal --full-year 2012",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "This month's calendar with the week starting on monday",
|
||||
example: "cal --week-start monday",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn cal(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let mut calendar_vec_deque = VecDeque::new();
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let (current_year, current_month, current_day) = get_current_date();
|
||||
|
||||
let mut selected_year: i32 = current_year;
|
||||
let mut current_day_option: Option<u32> = Some(current_day);
|
||||
|
||||
let month_range = if let Some(full_year_value) = args.get("full-year") {
|
||||
if let Ok(year_u64) = full_year_value.as_u64() {
|
||||
selected_year = year_u64 as i32;
|
||||
|
||||
if selected_year != current_year {
|
||||
current_day_option = None
|
||||
}
|
||||
} else {
|
||||
return Err(get_invalid_year_shell_error(&full_year_value.tag()));
|
||||
}
|
||||
|
||||
(1, 12)
|
||||
} else {
|
||||
(current_month, current_month)
|
||||
};
|
||||
|
||||
let add_months_of_year_to_table_result = add_months_of_year_to_table(
|
||||
&args,
|
||||
&mut calendar_vec_deque,
|
||||
&tag,
|
||||
selected_year,
|
||||
month_range,
|
||||
current_month,
|
||||
current_day_option,
|
||||
);
|
||||
|
||||
match add_months_of_year_to_table_result {
|
||||
Ok(()) => Ok(futures::stream::iter(calendar_vec_deque).to_output_stream()),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_invalid_year_shell_error(year_tag: &Tag) -> ShellError {
|
||||
ShellError::labeled_error("The year is invalid", "invalid year", year_tag)
|
||||
}
|
||||
|
||||
struct MonthHelper {
|
||||
selected_year: i32,
|
||||
selected_month: u32,
|
||||
day_number_of_week_month_starts_on: u32,
|
||||
number_of_days_in_month: u32,
|
||||
quarter_number: u32,
|
||||
month_name: String,
|
||||
}
|
||||
|
||||
impl MonthHelper {
|
||||
pub fn new(selected_year: i32, selected_month: u32) -> Result<MonthHelper, ()> {
|
||||
let naive_date = NaiveDate::from_ymd_opt(selected_year, selected_month, 1).ok_or(())?;
|
||||
let number_of_days_in_month =
|
||||
MonthHelper::calculate_number_of_days_in_month(selected_year, selected_month)?;
|
||||
|
||||
Ok(MonthHelper {
|
||||
selected_year,
|
||||
selected_month,
|
||||
day_number_of_week_month_starts_on: naive_date.weekday().num_days_from_sunday(),
|
||||
number_of_days_in_month,
|
||||
quarter_number: ((selected_month - 1) / 3) + 1,
|
||||
month_name: naive_date.format("%B").to_string().to_ascii_lowercase(),
|
||||
})
|
||||
}
|
||||
|
||||
fn calculate_number_of_days_in_month(
|
||||
mut selected_year: i32,
|
||||
mut selected_month: u32,
|
||||
) -> Result<u32, ()> {
|
||||
// Chrono does not provide a method to output the amount of days in a month
|
||||
// This is a workaround taken from the example code from the Chrono docs here:
|
||||
// https://docs.rs/chrono/0.3.0/chrono/naive/date/struct.NaiveDate.html#example-30
|
||||
if selected_month == 12 {
|
||||
selected_year += 1;
|
||||
selected_month = 1;
|
||||
} else {
|
||||
selected_month += 1;
|
||||
};
|
||||
|
||||
let next_month_naive_date =
|
||||
NaiveDate::from_ymd_opt(selected_year, selected_month, 1).ok_or(())?;
|
||||
|
||||
Ok(next_month_naive_date.pred().day())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_date() -> (i32, u32, u32) {
|
||||
let local_now_date = Local::now().date();
|
||||
|
||||
let current_year: i32 = local_now_date.year();
|
||||
let current_month: u32 = local_now_date.month();
|
||||
let current_day: u32 = local_now_date.day();
|
||||
|
||||
(current_year, current_month, current_day)
|
||||
}
|
||||
|
||||
fn add_months_of_year_to_table(
|
||||
args: &EvaluatedWholeStreamCommandArgs,
|
||||
mut calendar_vec_deque: &mut VecDeque<Value>,
|
||||
tag: &Tag,
|
||||
selected_year: i32,
|
||||
(start_month, end_month): (u32, u32),
|
||||
current_month: u32,
|
||||
current_day_option: Option<u32>,
|
||||
) -> Result<(), ShellError> {
|
||||
for month_number in start_month..=end_month {
|
||||
let mut new_current_day_option: Option<u32> = None;
|
||||
|
||||
if let Some(current_day) = current_day_option {
|
||||
if month_number == current_month {
|
||||
new_current_day_option = Some(current_day)
|
||||
}
|
||||
}
|
||||
|
||||
let add_month_to_table_result = add_month_to_table(
|
||||
&args,
|
||||
&mut calendar_vec_deque,
|
||||
&tag,
|
||||
selected_year,
|
||||
month_number,
|
||||
new_current_day_option,
|
||||
);
|
||||
|
||||
add_month_to_table_result?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_month_to_table(
|
||||
args: &EvaluatedWholeStreamCommandArgs,
|
||||
calendar_vec_deque: &mut VecDeque<Value>,
|
||||
tag: &Tag,
|
||||
selected_year: i32,
|
||||
current_month: u32,
|
||||
current_day_option: Option<u32>,
|
||||
) -> Result<(), ShellError> {
|
||||
let month_helper_result = MonthHelper::new(selected_year, current_month);
|
||||
|
||||
let month_helper = match month_helper_result {
|
||||
Ok(month_helper) => month_helper,
|
||||
Err(()) => match args.get("full-year") {
|
||||
Some(full_year_value) => {
|
||||
return Err(get_invalid_year_shell_error(&full_year_value.tag()))
|
||||
}
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Issue parsing command",
|
||||
"invalid command",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let mut days_of_the_week = [
|
||||
"sunday",
|
||||
"monday",
|
||||
"tuesday",
|
||||
"wednesday",
|
||||
"thursday",
|
||||
"friday",
|
||||
"saturday",
|
||||
];
|
||||
|
||||
let mut week_start_day = days_of_the_week[0].to_string();
|
||||
|
||||
if let Some(week_start_value) = args.get("week-start") {
|
||||
if let Ok(day) = week_start_value.as_string() {
|
||||
if days_of_the_week.contains(&day.as_str()) {
|
||||
week_start_day = day;
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"The specified week start day is invalid",
|
||||
"invalid week start day",
|
||||
week_start_value.tag(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let week_start_day_offset = days_of_the_week.len()
|
||||
- days_of_the_week
|
||||
.iter()
|
||||
.position(|day| *day == week_start_day)
|
||||
.unwrap_or(0);
|
||||
|
||||
days_of_the_week.rotate_right(week_start_day_offset);
|
||||
|
||||
let mut total_start_offset: u32 =
|
||||
month_helper.day_number_of_week_month_starts_on + week_start_day_offset as u32;
|
||||
total_start_offset %= days_of_the_week.len() as u32;
|
||||
|
||||
let mut day_number: u32 = 1;
|
||||
let day_limit: u32 = total_start_offset + month_helper.number_of_days_in_month;
|
||||
|
||||
let should_show_year_column = args.has("year");
|
||||
let should_show_quarter_column = args.has("quarter");
|
||||
let should_show_month_column = args.has("month");
|
||||
let should_show_month_names = args.has("month-names");
|
||||
|
||||
while day_number <= day_limit {
|
||||
let mut indexmap = IndexMap::new();
|
||||
|
||||
if should_show_year_column {
|
||||
indexmap.insert(
|
||||
"year".to_string(),
|
||||
UntaggedValue::int(month_helper.selected_year).into_value(tag),
|
||||
);
|
||||
}
|
||||
|
||||
if should_show_quarter_column {
|
||||
indexmap.insert(
|
||||
"quarter".to_string(),
|
||||
UntaggedValue::int(month_helper.quarter_number).into_value(tag),
|
||||
);
|
||||
}
|
||||
|
||||
if should_show_month_column || should_show_month_names {
|
||||
let month_value = if should_show_month_names {
|
||||
UntaggedValue::string(month_helper.month_name.clone()).into_value(tag)
|
||||
} else {
|
||||
UntaggedValue::int(month_helper.selected_month).into_value(tag)
|
||||
};
|
||||
|
||||
indexmap.insert("month".to_string(), month_value);
|
||||
}
|
||||
|
||||
for day in &days_of_the_week {
|
||||
let should_add_day_number_to_table =
|
||||
(day_number > total_start_offset) && (day_number <= day_limit);
|
||||
|
||||
let mut value = UntaggedValue::nothing().into_value(tag);
|
||||
|
||||
if should_add_day_number_to_table {
|
||||
let adjusted_day_number = day_number - total_start_offset;
|
||||
|
||||
value = UntaggedValue::int(adjusted_day_number).into_value(tag);
|
||||
|
||||
if let Some(current_day) = current_day_option {
|
||||
if current_day == adjusted_day_number {
|
||||
// TODO: Update the value here with a color when color support is added
|
||||
// This colors the current day
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
indexmap.insert((*day).to_string(), value);
|
||||
|
||||
day_number += 1;
|
||||
}
|
||||
|
||||
calendar_vec_deque
|
||||
.push_back(UntaggedValue::Row(Dictionary::from(indexmap)).into_value(tag));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Cal;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Cal {})
|
||||
}
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
use crate::commands::PerItemCommand;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CallInfo, Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||
|
||||
pub struct Calc;
|
||||
|
||||
impl PerItemCommand for Calc {
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Calc {
|
||||
fn name(&self) -> &str {
|
||||
"calc"
|
||||
}
|
||||
@ -14,38 +15,50 @@ impl PerItemCommand for Calc {
|
||||
"Parse a math expression into a number"
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
_call_info: &CallInfo,
|
||||
_registry: &CommandRegistry,
|
||||
raw_args: &RawCommandArgs,
|
||||
input: Value,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
calc(input, raw_args)
|
||||
calc(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Calculate math in the pipeline",
|
||||
example: "echo '10 / 4' | calc",
|
||||
result: Some(vec![UntaggedValue::decimal(2.5).into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn calc(input: Value, args: &RawCommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_span = &args.call_info.name_tag.span;
|
||||
pub async fn calc(
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let input = args.input;
|
||||
let name = args.call_info.name_tag.span;
|
||||
|
||||
let output = if let Ok(string) = input.as_string() {
|
||||
match parse(&string, &input.tag) {
|
||||
Ok(value) => ReturnSuccess::value(value),
|
||||
Err(err) => Err(ShellError::labeled_error(
|
||||
"Calculation error",
|
||||
err,
|
||||
&input.tag.span,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Expected a string from pipeline",
|
||||
"requires string input",
|
||||
name_span,
|
||||
))
|
||||
};
|
||||
|
||||
Ok(vec![output].into())
|
||||
Ok(input
|
||||
.map(move |input| {
|
||||
if let Ok(string) = input.as_string() {
|
||||
match parse(&string, &input.tag) {
|
||||
Ok(value) => ReturnSuccess::value(value),
|
||||
Err(err) => Err(ShellError::labeled_error(
|
||||
"Calculation error",
|
||||
err,
|
||||
&input.tag.span,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Expected a string from pipeline",
|
||||
"requires string input",
|
||||
name,
|
||||
))
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
pub fn parse(math_expression: &str, tag: impl Into<Tag>) -> Result<Value, String> {
|
||||
@ -61,3 +74,15 @@ pub fn parse(math_expression: &str, tag: impl Into<Tag>) -> Result<Value, String
|
||||
Err(error) => Err(error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Calc;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Calc {})
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,20 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CdArgs {
|
||||
pub(crate) path: Option<Tagged<PathBuf>>,
|
||||
}
|
||||
|
||||
pub struct Cd;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Cd {
|
||||
fn name(&self) -> &str {
|
||||
"cd"
|
||||
@ -22,17 +32,51 @@ impl WholeStreamCommand for Cd {
|
||||
"Change to a new path."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
cd(args, registry)
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let shell_manager = args.shell_manager.clone();
|
||||
let (args, _): (CdArgs, _) = args.process(®istry).await?;
|
||||
shell_manager.cd(args, name)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Change to a new directory called 'dirname'",
|
||||
example: "cd dirname",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Change to your home directory",
|
||||
example: "cd",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Change to your home directory (alternate version)",
|
||||
example: "cd ~",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Change to the previous directory",
|
||||
example: "cd -",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn cd(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let shell_manager = args.shell_manager.clone();
|
||||
let args = args.evaluate_once(registry)?;
|
||||
shell_manager.cd(args)
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Cd;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Cd {})
|
||||
}
|
||||
}
|
||||
|
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 {})
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
use crate::commands::classified::expr::run_expression_block;
|
||||
//use crate::commands::classified::external::run_external_command;
|
||||
use crate::commands::classified::internal::run_internal_command;
|
||||
use crate::context::Context;
|
||||
use crate::prelude::*;
|
||||
@ -7,14 +6,16 @@ use crate::stream::InputStream;
|
||||
use futures::stream::TryStreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{Block, ClassifiedCommand, Commands};
|
||||
use nu_protocol::{ReturnSuccess, Scope, UntaggedValue, Value};
|
||||
use nu_protocol::{ReturnSuccess, UntaggedValue, Value};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
pub(crate) async fn run_block(
|
||||
block: &Block,
|
||||
ctx: &mut Context,
|
||||
mut input: InputStream,
|
||||
scope: &Scope,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let mut output: Result<InputStream, ShellError> = Ok(InputStream::empty());
|
||||
for pipeline in &block.block {
|
||||
@ -53,7 +54,7 @@ pub(crate) async fn run_block(
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
output = run_pipeline(pipeline, ctx, input, scope).await;
|
||||
output = run_pipeline(pipeline, ctx, input, it, vars, env).await;
|
||||
|
||||
input = InputStream::empty();
|
||||
}
|
||||
@ -65,10 +66,11 @@ async fn run_pipeline(
|
||||
commands: &Commands,
|
||||
ctx: &mut Context,
|
||||
mut input: InputStream,
|
||||
scope: &Scope,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let mut iter = commands.list.clone().into_iter().peekable();
|
||||
|
||||
loop {
|
||||
let item: Option<ClassifiedCommand> = iter.next();
|
||||
let next: Option<&ClassifiedCommand> = iter.peek();
|
||||
@ -79,13 +81,13 @@ async fn run_pipeline(
|
||||
}
|
||||
|
||||
(Some(ClassifiedCommand::Expr(expr)), _) => {
|
||||
run_expression_block(*expr, ctx, input, scope)?
|
||||
run_expression_block(*expr, ctx, it, vars, env).await?
|
||||
}
|
||||
(Some(ClassifiedCommand::Error(err)), _) => return Err(err.into()),
|
||||
(_, Some(ClassifiedCommand::Error(err))) => return Err(err.clone().into()),
|
||||
|
||||
(Some(ClassifiedCommand::Internal(left)), _) => {
|
||||
run_internal_command(left, ctx, input, scope)?
|
||||
run_internal_command(left, ctx, input, it, vars, env).await?
|
||||
}
|
||||
|
||||
(None, _) => break,
|
||||
|
@ -3,27 +3,25 @@ use crate::prelude::*;
|
||||
|
||||
use log::{log_enabled, trace};
|
||||
|
||||
use futures::stream::once;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::SpannedExpression;
|
||||
use nu_protocol::Scope;
|
||||
use nu_protocol::Value;
|
||||
|
||||
pub(crate) fn run_expression_block(
|
||||
pub(crate) async fn run_expression_block(
|
||||
expr: SpannedExpression,
|
||||
context: &mut Context,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
if log_enabled!(log::Level::Trace) {
|
||||
trace!(target: "nu::run::expr", "->");
|
||||
trace!(target: "nu::run::expr", "{:?}", expr);
|
||||
}
|
||||
|
||||
let scope = scope.clone();
|
||||
let registry = context.registry().clone();
|
||||
let stream = input.map(move |row| {
|
||||
let scope = scope.clone().set_it(row);
|
||||
evaluate_baseline_expr(&expr, ®istry, &scope)
|
||||
});
|
||||
let output = evaluate_baseline_expr(&expr, ®istry, it, vars, env).await?;
|
||||
|
||||
Ok(stream.to_input_stream())
|
||||
Ok(once(async { Ok(output) }).to_input_stream())
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use std::sync::mpsc;
|
||||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use futures::executor::block_on_stream;
|
||||
use futures::stream::StreamExt;
|
||||
// use futures::stream::StreamExt;
|
||||
use futures_codec::FramedRead;
|
||||
use log::trace;
|
||||
|
||||
@ -82,20 +82,6 @@ impl futures_codec::Decoder for MaybeTextCodec {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nu_value_to_string(name_tag: &Tag, from: &Value) -> Result<String, ShellError> {
|
||||
match &from.value {
|
||||
UntaggedValue::Primitive(Primitive::Int(i)) => Ok(i.to_string()),
|
||||
UntaggedValue::Primitive(Primitive::String(s))
|
||||
| UntaggedValue::Primitive(Primitive::Line(s)) => Ok(s.clone()),
|
||||
UntaggedValue::Primitive(Primitive::Path(p)) => Ok(p.to_string_lossy().to_string()),
|
||||
unsupported => Err(ShellError::labeled_error(
|
||||
format!("needs string data (given: {})", unsupported.type_name()),
|
||||
"expected a string",
|
||||
name_tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run_external_command(
|
||||
command: ExternalCommand,
|
||||
context: &mut Context,
|
||||
@ -113,88 +99,10 @@ pub(crate) async fn run_external_command(
|
||||
));
|
||||
}
|
||||
|
||||
if command.has_it_argument() {
|
||||
run_with_iterator_arg(command, context, input, scope, is_last)
|
||||
} else {
|
||||
run_with_stdin(command, context, input, scope, is_last)
|
||||
}
|
||||
run_with_stdin(command, context, input, scope, is_last).await
|
||||
}
|
||||
|
||||
fn run_with_iterator_arg(
|
||||
command: ExternalCommand,
|
||||
context: &mut Context,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
is_last: bool,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let path = context.shell_manager.path();
|
||||
|
||||
let mut inputs: InputStream =
|
||||
trace_stream!(target: "nu::trace_stream::external::it", "input" = input);
|
||||
|
||||
let name_tag = command.name_tag.clone();
|
||||
let scope = scope.clone();
|
||||
let context = context.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
while let Some(value) = inputs.next().await {
|
||||
// Evaluate the expressions into values, and from values into strings for each iteration
|
||||
let mut command_args = vec![];
|
||||
let scope = scope.clone().set_it(value);
|
||||
for arg in command.args.iter() {
|
||||
let value = evaluate_baseline_expr(arg, &context.registry, &scope)?;
|
||||
command_args.push(nu_value_to_string(&name_tag, &value)?);
|
||||
}
|
||||
|
||||
let process_args = command_args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
let arg = expand_tilde(arg.deref(), dirs::home_dir);
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
if argument_contains_whitespace(&arg) && argument_is_quoted(&arg) {
|
||||
if let Some(unquoted) = remove_quotes(&arg) {
|
||||
format!(r#""{}""#, unquoted)
|
||||
} else {
|
||||
arg.as_ref().to_string()
|
||||
}
|
||||
} else {
|
||||
arg.as_ref().to_string()
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Some(unquoted) = remove_quotes(&arg) {
|
||||
unquoted.to_string()
|
||||
} else {
|
||||
arg.as_ref().to_string()
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
match spawn(&command, &path, &process_args[..], InputStream::empty(), is_last) {
|
||||
Ok(mut res) => {
|
||||
while let Some(item) = res.next().await {
|
||||
yield Ok(item)
|
||||
}
|
||||
}
|
||||
Err(reason) => {
|
||||
yield Ok(Value {
|
||||
value: UntaggedValue::Error(reason),
|
||||
tag: name_tag
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_input_stream())
|
||||
}
|
||||
|
||||
fn run_with_stdin(
|
||||
async fn run_with_stdin(
|
||||
command: ExternalCommand,
|
||||
context: &mut Context,
|
||||
input: InputStream,
|
||||
@ -207,8 +115,32 @@ fn run_with_stdin(
|
||||
|
||||
let mut command_args = vec![];
|
||||
for arg in command.args.iter() {
|
||||
let value = evaluate_baseline_expr(arg, &context.registry, scope)?;
|
||||
command_args.push(value.as_string()?);
|
||||
let value =
|
||||
evaluate_baseline_expr(arg, &context.registry, &scope.it, &scope.vars, &scope.env)
|
||||
.await?;
|
||||
// Skip any arguments that don't really exist, treating them as optional
|
||||
// FIXME: we may want to preserve the gap in the future, though it's hard to say
|
||||
// what value we would put in its place.
|
||||
if value.value.is_none() {
|
||||
continue;
|
||||
}
|
||||
// Do the cleanup that we need to do on any argument going out:
|
||||
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
|
||||
|
||||
let value_string;
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
value_string = trimmed_value_string
|
||||
.replace('$', "\\$")
|
||||
.replace('"', "\\\"")
|
||||
.to_string()
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
value_string = trimmed_value_string
|
||||
}
|
||||
|
||||
command_args.push(value_string);
|
||||
}
|
||||
|
||||
let process_args = command_args
|
||||
@ -235,7 +167,7 @@ fn run_with_stdin(
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
spawn(&command, &path, &process_args[..], input, is_last)
|
||||
spawn(&command, &path, &process_args[..], input, is_last, scope)
|
||||
}
|
||||
|
||||
fn spawn(
|
||||
@ -244,6 +176,7 @@ fn spawn(
|
||||
args: &[String],
|
||||
input: InputStream,
|
||||
is_last: bool,
|
||||
scope: &Scope,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let command = command.clone();
|
||||
|
||||
@ -273,11 +206,17 @@ fn spawn(
|
||||
process.current_dir(path);
|
||||
trace!(target: "nu::run::external", "cwd = {:?}", &path);
|
||||
|
||||
process.env_clear();
|
||||
process.envs(scope.env.iter());
|
||||
|
||||
// We want stdout regardless of what
|
||||
// we are doing ($it case or pipe stdin)
|
||||
if !is_last {
|
||||
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
|
||||
@ -376,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);
|
||||
|
||||
@ -405,16 +358,85 @@ fn spawn(
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"Unable to read from stdout.",
|
||||
"unable to read from stdout",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
break;
|
||||
Err(e) => {
|
||||
// If there's an exit status, it makes sense that we may error when
|
||||
// trying to read from its stdout pipe (likely been closed). In that
|
||||
// case, don't emit an error.
|
||||
let should_error = match child.wait() {
|
||||
Ok(exit_status) => !exit_status.success(),
|
||||
Err(_) => true,
|
||||
};
|
||||
|
||||
if should_error {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!("Unable to read from stdout ({})", e),
|
||||
"unable to read from stdout",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -424,10 +446,7 @@ fn spawn(
|
||||
// than what other shells will do.
|
||||
let external_failed = match child.wait() {
|
||||
Err(_) => true,
|
||||
Ok(exit_status) => match exit_status.code() {
|
||||
Some(e) if e != 0 => true,
|
||||
_ => false,
|
||||
},
|
||||
Ok(exit_status) => !exit_status.success(),
|
||||
};
|
||||
|
||||
if external_failed {
|
||||
@ -467,12 +486,12 @@ fn spawn(
|
||||
async fn did_find_command(name: &str) -> bool {
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
ichwh::which(name).await.unwrap_or(None).is_some()
|
||||
which::which(name).is_ok()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if ichwh::which(name).await.unwrap_or(None).is_some() {
|
||||
if which::which(name).is_ok() {
|
||||
true
|
||||
} else {
|
||||
let cmd_builtins = [
|
||||
@ -514,6 +533,7 @@ fn add_quotes(argument: &str) -> String {
|
||||
format!("\"{}\"", argument)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn remove_quotes(argument: &str) -> Option<&str> {
|
||||
if !argument_is_quoted(argument) {
|
||||
return None;
|
||||
@ -566,7 +586,7 @@ mod tests {
|
||||
let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
|
||||
|
||||
assert!(
|
||||
run_external_command(cmd, &mut ctx, input, &Scope::empty(), false)
|
||||
run_external_command(cmd, &mut ctx, input, &Scope::new(), false)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::commands::command::per_item_command;
|
||||
use crate::commands::command::whole_stream_command;
|
||||
use crate::commands::run_alias::AliasCommand;
|
||||
use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::prelude::*;
|
||||
@ -7,179 +7,247 @@ use nu_errors::ShellError;
|
||||
use nu_protocol::hir::InternalCommand;
|
||||
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue, Value};
|
||||
|
||||
pub(crate) fn run_internal_command(
|
||||
pub(crate) async fn run_internal_command(
|
||||
command: InternalCommand,
|
||||
context: &mut Context,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
if log_enabled!(log::Level::Trace) {
|
||||
trace!(target: "nu::run::internal", "->");
|
||||
trace!(target: "nu::run::internal", "{}", command.name);
|
||||
}
|
||||
|
||||
let scope = Scope {
|
||||
it: it.clone(),
|
||||
vars: vars.clone(),
|
||||
env: env.clone(),
|
||||
};
|
||||
let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
|
||||
let internal_command = context.expect_command(&command.name);
|
||||
|
||||
let result = {
|
||||
context.run_command(
|
||||
internal_command?,
|
||||
Tag::unknown_anchor(command.name_span),
|
||||
command.args.clone(),
|
||||
scope,
|
||||
objects,
|
||||
)
|
||||
context
|
||||
.run_command(
|
||||
internal_command?,
|
||||
Tag::unknown_anchor(command.name_span),
|
||||
command.args.clone(),
|
||||
&scope,
|
||||
objects,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
let mut result = trace_out_stream!(target: "nu::trace_stream::internal", "output" = result);
|
||||
let mut context = context.clone();
|
||||
let head = Arc::new(command.args.head.clone());
|
||||
//let context = Arc::new(context.clone());
|
||||
let context = context.clone();
|
||||
let command = Arc::new(command);
|
||||
let scope = Arc::new(scope);
|
||||
// let scope = scope.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let mut soft_errs: Vec<ShellError> = vec![];
|
||||
let mut yielded = false;
|
||||
Ok(InputStream::from_stream(
|
||||
result
|
||||
.then(move |item| {
|
||||
let head = head.clone();
|
||||
let command = command.clone();
|
||||
let mut context = context.clone();
|
||||
let scope = scope.clone();
|
||||
async move {
|
||||
match item {
|
||||
Ok(ReturnSuccess::Action(action)) => match action {
|
||||
CommandAction::ChangePath(path) => {
|
||||
context.shell_manager.set_path(path);
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::Exit => std::process::exit(0), // TODO: save history.txt
|
||||
CommandAction::Error(err) => {
|
||||
context.error(err.clone());
|
||||
InputStream::one(UntaggedValue::Error(err).into_untagged_value())
|
||||
}
|
||||
CommandAction::AutoConvert(tagged_contents, extension) => {
|
||||
let contents_tag = tagged_contents.tag.clone();
|
||||
let command_name = format!("from {}", extension);
|
||||
let command = command.clone();
|
||||
if let Some(converter) = context.registry.get_command(&command_name)
|
||||
{
|
||||
let new_args = RawCommandArgs {
|
||||
host: context.host.clone(),
|
||||
ctrl_c: context.ctrl_c.clone(),
|
||||
current_errors: context.current_errors.clone(),
|
||||
shell_manager: context.shell_manager.clone(),
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: nu_protocol::hir::Call {
|
||||
head: (&*head).clone(),
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
},
|
||||
name_tag: Tag::unknown_anchor(command.name_span),
|
||||
scope: (&*scope).clone(),
|
||||
},
|
||||
};
|
||||
let result = converter
|
||||
.run(
|
||||
new_args.with_input(vec![tagged_contents]),
|
||||
&context.registry,
|
||||
)
|
||||
.await;
|
||||
|
||||
while let Some(item) = result.next().await {
|
||||
match item {
|
||||
Ok(ReturnSuccess::Action(action)) => match action {
|
||||
CommandAction::ChangePath(path) => {
|
||||
context.shell_manager.set_path(path);
|
||||
}
|
||||
CommandAction::Exit => std::process::exit(0), // TODO: save history.txt
|
||||
CommandAction::Error(err) => {
|
||||
context.error(err);
|
||||
break;
|
||||
}
|
||||
CommandAction::AutoConvert(tagged_contents, extension) => {
|
||||
let contents_tag = tagged_contents.tag.clone();
|
||||
let command_name = format!("from-{}", extension);
|
||||
let command = command.clone();
|
||||
if let Some(converter) = context.registry.get_command(&command_name) {
|
||||
let new_args = RawCommandArgs {
|
||||
host: context.host.clone(),
|
||||
ctrl_c: context.ctrl_c.clone(),
|
||||
shell_manager: context.shell_manager.clone(),
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: nu_protocol::hir::Call {
|
||||
head: command.args.head,
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
},
|
||||
name_tag: Tag::unknown_anchor(command.name_span),
|
||||
scope: Scope::empty(),
|
||||
}
|
||||
};
|
||||
let mut result = converter.run(new_args.with_input(vec![tagged_contents]), &context.registry);
|
||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> = result.drain_vec().await;
|
||||
for res in result_vec {
|
||||
match res {
|
||||
Ok(ReturnSuccess::Value(Value { value: UntaggedValue::Table(list), ..})) => {
|
||||
for l in list {
|
||||
yield Ok(l);
|
||||
match result {
|
||||
Ok(mut result) => {
|
||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
||||
result.drain_vec().await;
|
||||
|
||||
let mut output = vec![];
|
||||
for res in result_vec {
|
||||
match res {
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
})) => {
|
||||
for l in list {
|
||||
output.push(Ok(l));
|
||||
}
|
||||
}
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value,
|
||||
..
|
||||
})) => {
|
||||
output
|
||||
.push(Ok(value
|
||||
.into_value(contents_tag.clone())));
|
||||
}
|
||||
Err(e) => output.push(Err(e)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
futures::stream::iter(output).to_input_stream()
|
||||
}
|
||||
Err(e) => {
|
||||
context.add_error(e);
|
||||
InputStream::empty()
|
||||
}
|
||||
}
|
||||
Ok(ReturnSuccess::Value(Value { value, .. })) => {
|
||||
yield Ok(value.into_value(contents_tag.clone()));
|
||||
}
|
||||
Err(e) => yield Err(e),
|
||||
_ => {}
|
||||
} else {
|
||||
InputStream::one(tagged_contents)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
yield Ok(tagged_contents)
|
||||
}
|
||||
}
|
||||
CommandAction::EnterHelpShell(value) => {
|
||||
match value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(cmd)),
|
||||
tag,
|
||||
} => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
HelpShell::for_command(
|
||||
UntaggedValue::string(cmd).into_value(tag),
|
||||
&context.registry(),
|
||||
)?,
|
||||
));
|
||||
CommandAction::EnterHelpShell(value) => match value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(cmd)),
|
||||
tag,
|
||||
} => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
match HelpShell::for_command(
|
||||
UntaggedValue::string(cmd).into_value(tag),
|
||||
&context.registry(),
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return InputStream::one(
|
||||
UntaggedValue::Error(err).into_untagged_value(),
|
||||
)
|
||||
}
|
||||
},
|
||||
));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
_ => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
match HelpShell::index(&context.registry()) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return InputStream::one(
|
||||
UntaggedValue::Error(err).into_untagged_value(),
|
||||
)
|
||||
}
|
||||
},
|
||||
));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
},
|
||||
CommandAction::EnterValueShell(value) => {
|
||||
context
|
||||
.shell_manager
|
||||
.insert_at_current(Box::new(ValueShell::new(value)));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
_ => {
|
||||
CommandAction::EnterShell(location) => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
HelpShell::index(&context.registry())?,
|
||||
match FilesystemShell::with_location(
|
||||
location,
|
||||
context.registry().clone(),
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return InputStream::one(
|
||||
UntaggedValue::Error(err.into())
|
||||
.into_untagged_value(),
|
||||
)
|
||||
}
|
||||
},
|
||||
));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::AddAlias(name, args, block) => {
|
||||
context.add_commands(vec![whole_stream_command(
|
||||
AliasCommand::new(name, args, block),
|
||||
)]);
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::PreviousShell => {
|
||||
context.shell_manager.prev();
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::NextShell => {
|
||||
context.shell_manager.next();
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::LeaveShell => {
|
||||
context.shell_manager.remove_at_current();
|
||||
if context.shell_manager.is_empty() {
|
||||
std::process::exit(0); // TODO: save history.txt
|
||||
}
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
},
|
||||
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Error(err),
|
||||
tag,
|
||||
})) => {
|
||||
context.error(err.clone());
|
||||
InputStream::one(UntaggedValue::Error(err).into_value(tag))
|
||||
}
|
||||
|
||||
Ok(ReturnSuccess::Value(v)) => InputStream::one(v),
|
||||
|
||||
Ok(ReturnSuccess::DebugValue(v)) => {
|
||||
let doc = PrettyDebug::pretty_doc(&v);
|
||||
let mut buffer = termcolor::Buffer::ansi();
|
||||
|
||||
let _ = doc.render_raw(
|
||||
context.with_host(|host| host.width() - 5),
|
||||
&mut nu_source::TermColored::new(&mut buffer),
|
||||
);
|
||||
|
||||
let value = String::from_utf8_lossy(buffer.as_slice());
|
||||
|
||||
InputStream::one(UntaggedValue::string(value).into_untagged_value())
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
context.error(err.clone());
|
||||
InputStream::one(UntaggedValue::Error(err).into_untagged_value())
|
||||
}
|
||||
}
|
||||
CommandAction::EnterValueShell(value) => {
|
||||
context
|
||||
.shell_manager
|
||||
.insert_at_current(Box::new(ValueShell::new(value)));
|
||||
}
|
||||
CommandAction::EnterShell(location) => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
FilesystemShell::with_location(location, context.registry().clone()),
|
||||
));
|
||||
}
|
||||
CommandAction::AddAlias(name, args, block) => {
|
||||
context.add_commands(vec![
|
||||
per_item_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())),
|
||||
))
|
||||
}
|
||||
|
@ -6,35 +6,52 @@ use std::process::Command;
|
||||
|
||||
pub struct Clear;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Clear {
|
||||
fn name(&self) -> &str {
|
||||
"clear"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("clear")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"clears the terminal"
|
||||
}
|
||||
fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
clear(args, registry)
|
||||
|
||||
async fn run(&self, _: CommandArgs, _: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
if cfg!(windows) {
|
||||
Command::new("cmd")
|
||||
.args(&["/C", "cls"])
|
||||
.status()
|
||||
.expect("failed to execute process");
|
||||
} else if cfg!(unix) {
|
||||
Command::new("/bin/sh")
|
||||
.args(&["-c", "clear"])
|
||||
.status()
|
||||
.expect("failed to execute process");
|
||||
}
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Clear the screen",
|
||||
example: "clear",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
fn clear(_args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
if cfg!(windows) {
|
||||
Command::new("cmd")
|
||||
.args(&["/C", "cls"])
|
||||
.status()
|
||||
.expect("failed to execute process");
|
||||
} else if cfg!(unix) {
|
||||
Command::new("/bin/sh")
|
||||
.args(&["-c", "clear"])
|
||||
.status()
|
||||
.expect("failed to execute process");
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Clear;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Clear {})
|
||||
}
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
@ -1,106 +1,109 @@
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub mod clipboard {
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnValue, Signature, Value};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, Value};
|
||||
|
||||
use clipboard::{ClipboardContext, ClipboardProvider};
|
||||
use clipboard::{ClipboardContext, ClipboardProvider};
|
||||
|
||||
pub struct Clip;
|
||||
pub struct Clip;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ClipArgs {}
|
||||
|
||||
impl WholeStreamCommand for Clip {
|
||||
fn name(&self) -> &str {
|
||||
"clip"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("clip")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Copy the contents of the pipeline to the copy/paste buffer"
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, clip)?.run()
|
||||
}
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Clip {
|
||||
fn name(&self) -> &str {
|
||||
"clip"
|
||||
}
|
||||
|
||||
pub fn clip(
|
||||
ClipArgs {}: ClipArgs,
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("clip")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Copy the contents of the pipeline to the copy/paste buffer"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
let mut clip_stream = inner_clip(values, name).await;
|
||||
while let Some(value) = clip_stream.next().await {
|
||||
yield value;
|
||||
}
|
||||
};
|
||||
|
||||
let stream: BoxStream<'static, ReturnValue> = stream.boxed();
|
||||
|
||||
Ok(OutputStream::from(stream))
|
||||
clip(args, registry).await
|
||||
}
|
||||
|
||||
async fn inner_clip(input: Vec<Value>, name: Tag) -> OutputStream {
|
||||
if let Ok(clip_context) = ClipboardProvider::new() {
|
||||
let mut clip_context: ClipboardContext = clip_context;
|
||||
let mut new_copy_data = String::new();
|
||||
|
||||
if !input.is_empty() {
|
||||
let mut first = true;
|
||||
for i in input.iter() {
|
||||
if !first {
|
||||
new_copy_data.push_str("\n");
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
||||
let string: String = match i.as_string() {
|
||||
Ok(string) => string.to_string(),
|
||||
Err(_) => {
|
||||
return OutputStream::one(Err(ShellError::labeled_error(
|
||||
"Given non-string data",
|
||||
"expected strings from pipeline",
|
||||
name,
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
new_copy_data.push_str(&string);
|
||||
}
|
||||
}
|
||||
|
||||
match clip_context.set_contents(new_copy_data) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
return OutputStream::one(Err(ShellError::labeled_error(
|
||||
"Could not set contents of clipboard",
|
||||
"could not set contents of clipboard",
|
||||
name,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
OutputStream::empty()
|
||||
} else {
|
||||
OutputStream::one(Err(ShellError::labeled_error(
|
||||
"Could not open clipboard",
|
||||
"could not open clipboard",
|
||||
name,
|
||||
)))
|
||||
}
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Save text to the clipboard",
|
||||
example: "echo 'secret value' | clip",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn clip(
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let input = args.input;
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
if let Ok(clip_context) = ClipboardProvider::new() {
|
||||
let mut clip_context: ClipboardContext = clip_context;
|
||||
let mut new_copy_data = String::new();
|
||||
|
||||
if !values.is_empty() {
|
||||
let mut first = true;
|
||||
for i in values.iter() {
|
||||
if !first {
|
||||
new_copy_data.push_str("\n");
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
||||
let string: String = match i.as_string() {
|
||||
Ok(string) => string.to_string(),
|
||||
Err(_) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Given non-string data",
|
||||
"expected strings from pipeline",
|
||||
name,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
new_copy_data.push_str(&string);
|
||||
}
|
||||
}
|
||||
|
||||
match clip_context.set_contents(new_copy_data) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Could not set contents of clipboard",
|
||||
"could not set contents of clipboard",
|
||||
name,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Could not open clipboard",
|
||||
"could not open clipboard",
|
||||
name,
|
||||
));
|
||||
}
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Clip;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Clip {})
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,8 @@ use derive_new::new;
|
||||
use getset::Getters;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir;
|
||||
use nu_protocol::{CallInfo, EvaluatedArgs, ReturnValue, Scope, Signature, Value};
|
||||
use nu_protocol::{CallInfo, EvaluatedArgs, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
|
||||
use parking_lot::Mutex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
@ -20,8 +21,8 @@ pub struct UnevaluatedCallInfo {
|
||||
}
|
||||
|
||||
impl UnevaluatedCallInfo {
|
||||
pub fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
|
||||
let args = evaluate_args(&self.args, registry, &self.scope)?;
|
||||
pub async fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
|
||||
let args = evaluate_args(&self.args, registry, &self.scope).await?;
|
||||
|
||||
Ok(CallInfo {
|
||||
args,
|
||||
@ -29,14 +30,14 @@ impl UnevaluatedCallInfo {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn evaluate_with_new_it(
|
||||
pub async fn evaluate_with_new_it(
|
||||
self,
|
||||
registry: &CommandRegistry,
|
||||
it: &Value,
|
||||
) -> Result<CallInfo, ShellError> {
|
||||
let mut scope = self.scope.clone();
|
||||
scope = scope.set_it(it.clone());
|
||||
let args = evaluate_args(&self.args, registry, &scope)?;
|
||||
scope.it = it.clone();
|
||||
let args = evaluate_args(&self.args, registry, &scope).await?;
|
||||
|
||||
Ok(CallInfo {
|
||||
args,
|
||||
@ -49,44 +50,16 @@ impl UnevaluatedCallInfo {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CallInfoExt {
|
||||
fn process<'de, T: Deserialize<'de>>(
|
||||
&self,
|
||||
shell_manager: &ShellManager,
|
||||
ctrl_c: Arc<AtomicBool>,
|
||||
callback: fn(T, &RunnablePerItemContext) -> Result<OutputStream, ShellError>,
|
||||
) -> Result<RunnablePerItemArgs<T>, ShellError>;
|
||||
}
|
||||
|
||||
impl CallInfoExt for CallInfo {
|
||||
fn process<'de, T: Deserialize<'de>>(
|
||||
&self,
|
||||
shell_manager: &ShellManager,
|
||||
ctrl_c: Arc<AtomicBool>,
|
||||
callback: fn(T, &RunnablePerItemContext) -> Result<OutputStream, ShellError>,
|
||||
) -> Result<RunnablePerItemArgs<T>, ShellError> {
|
||||
let mut deserializer = ConfigDeserializer::from_call_info(self.clone());
|
||||
|
||||
Ok(RunnablePerItemArgs {
|
||||
args: T::deserialize(&mut deserializer)?,
|
||||
context: RunnablePerItemContext {
|
||||
shell_manager: shell_manager.clone(),
|
||||
name: self.name_tag.clone(),
|
||||
ctrl_c,
|
||||
},
|
||||
callback,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Getters)]
|
||||
#[get = "pub(crate)"]
|
||||
pub struct CommandArgs {
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub shell_manager: ShellManager,
|
||||
pub call_info: UnevaluatedCallInfo,
|
||||
pub input: InputStream,
|
||||
pub raw_input: String,
|
||||
}
|
||||
|
||||
#[derive(Getters, Clone)]
|
||||
@ -94,6 +67,7 @@ pub struct CommandArgs {
|
||||
pub struct RawCommandArgs {
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub shell_manager: ShellManager,
|
||||
pub call_info: UnevaluatedCallInfo,
|
||||
}
|
||||
@ -103,9 +77,11 @@ impl RawCommandArgs {
|
||||
CommandArgs {
|
||||
host: self.host,
|
||||
ctrl_c: self.ctrl_c,
|
||||
current_errors: self.current_errors,
|
||||
shell_manager: self.shell_manager,
|
||||
call_info: self.call_info,
|
||||
input: input.into(),
|
||||
raw_input: String::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -117,7 +93,7 @@ impl std::fmt::Debug for CommandArgs {
|
||||
}
|
||||
|
||||
impl CommandArgs {
|
||||
pub fn evaluate_once(
|
||||
pub async fn evaluate_once(
|
||||
self,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
|
||||
@ -125,7 +101,7 @@ impl CommandArgs {
|
||||
let ctrl_c = self.ctrl_c.clone();
|
||||
let shell_manager = self.shell_manager.clone();
|
||||
let input = self.input;
|
||||
let call_info = self.call_info.evaluate(registry)?;
|
||||
let call_info = self.call_info.evaluate(registry).await?;
|
||||
|
||||
Ok(EvaluatedWholeStreamCommandArgs::new(
|
||||
host,
|
||||
@ -136,7 +112,7 @@ impl CommandArgs {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn evaluate_once_with_scope(
|
||||
pub async fn evaluate_once_with_scope(
|
||||
self,
|
||||
registry: &CommandRegistry,
|
||||
scope: &Scope,
|
||||
@ -150,7 +126,7 @@ impl CommandArgs {
|
||||
args: self.call_info.args,
|
||||
scope: scope.clone(),
|
||||
};
|
||||
let call_info = call_info.evaluate(registry)?;
|
||||
let call_info = call_info.evaluate(registry).await?;
|
||||
|
||||
Ok(EvaluatedWholeStreamCommandArgs::new(
|
||||
host,
|
||||
@ -161,133 +137,36 @@ impl CommandArgs {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn process<'de, T: Deserialize<'de>, O: ToOutputStream>(
|
||||
pub async fn process<'de, T: Deserialize<'de>>(
|
||||
self,
|
||||
registry: &CommandRegistry,
|
||||
callback: fn(T, RunnableContext) -> Result<O, ShellError>,
|
||||
) -> Result<RunnableArgs<T, O>, ShellError> {
|
||||
let shell_manager = self.shell_manager.clone();
|
||||
let host = self.host.clone();
|
||||
let ctrl_c = self.ctrl_c.clone();
|
||||
let args = self.evaluate_once(registry)?;
|
||||
let call_info = args.call_info.clone();
|
||||
let (input, args) = args.split();
|
||||
let name_tag = args.call_info.name_tag;
|
||||
let mut deserializer = ConfigDeserializer::from_call_info(call_info);
|
||||
|
||||
Ok(RunnableArgs {
|
||||
args: T::deserialize(&mut deserializer)?,
|
||||
context: RunnableContext {
|
||||
input,
|
||||
registry: registry.clone(),
|
||||
shell_manager,
|
||||
name: name_tag,
|
||||
host,
|
||||
ctrl_c,
|
||||
},
|
||||
callback,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn process_raw<'de, T: Deserialize<'de>>(
|
||||
self,
|
||||
registry: &CommandRegistry,
|
||||
callback: fn(T, RunnableContext, RawCommandArgs) -> Result<OutputStream, ShellError>,
|
||||
) -> Result<RunnableRawArgs<T>, ShellError> {
|
||||
let raw_args = RawCommandArgs {
|
||||
host: self.host.clone(),
|
||||
ctrl_c: self.ctrl_c.clone(),
|
||||
shell_manager: self.shell_manager.clone(),
|
||||
call_info: self.call_info.clone(),
|
||||
};
|
||||
|
||||
let shell_manager = self.shell_manager.clone();
|
||||
let host = self.host.clone();
|
||||
let ctrl_c = self.ctrl_c.clone();
|
||||
let args = self.evaluate_once(registry)?;
|
||||
) -> Result<(T, InputStream), ShellError> {
|
||||
let args = self.evaluate_once(registry).await?;
|
||||
let call_info = args.call_info.clone();
|
||||
|
||||
let (input, args) = args.split();
|
||||
let name_tag = args.call_info.name_tag;
|
||||
let mut deserializer = ConfigDeserializer::from_call_info(call_info);
|
||||
|
||||
Ok(RunnableRawArgs {
|
||||
args: T::deserialize(&mut deserializer)?,
|
||||
context: RunnableContext {
|
||||
input,
|
||||
registry: registry.clone(),
|
||||
shell_manager,
|
||||
name: name_tag,
|
||||
host,
|
||||
ctrl_c,
|
||||
},
|
||||
raw_args,
|
||||
callback,
|
||||
})
|
||||
Ok((T::deserialize(&mut deserializer)?, args.input))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RunnablePerItemContext {
|
||||
pub shell_manager: ShellManager,
|
||||
pub name: Tag,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
pub struct RunnableContext {
|
||||
pub input: InputStream,
|
||||
pub shell_manager: ShellManager,
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub registry: CommandRegistry,
|
||||
pub name: Tag,
|
||||
pub raw_input: String,
|
||||
}
|
||||
|
||||
impl RunnableContext {
|
||||
pub fn get_command(&self, name: &str) -> Option<Arc<Command>> {
|
||||
pub fn get_command(&self, name: &str) -> Option<Command> {
|
||||
self.registry.get_command(name)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RunnablePerItemArgs<T> {
|
||||
args: T,
|
||||
context: RunnablePerItemContext,
|
||||
callback: fn(T, &RunnablePerItemContext) -> Result<OutputStream, ShellError>,
|
||||
}
|
||||
|
||||
impl<T> RunnablePerItemArgs<T> {
|
||||
pub fn run(self) -> Result<OutputStream, ShellError> {
|
||||
(self.callback)(self.args, &self.context)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RunnableArgs<T, O: ToOutputStream> {
|
||||
args: T,
|
||||
context: RunnableContext,
|
||||
callback: fn(T, RunnableContext) -> Result<O, ShellError>,
|
||||
}
|
||||
|
||||
impl<T, O: ToOutputStream> RunnableArgs<T, O> {
|
||||
pub fn run(self) -> Result<OutputStream, ShellError> {
|
||||
(self.callback)(self.args, self.context).map(|v| v.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RunnableRawArgs<T> {
|
||||
args: T,
|
||||
raw_args: RawCommandArgs,
|
||||
context: RunnableContext,
|
||||
callback: fn(T, RunnableContext, RawCommandArgs) -> Result<OutputStream, ShellError>,
|
||||
}
|
||||
|
||||
impl<T> RunnableRawArgs<T> {
|
||||
pub fn run(self) -> OutputStream {
|
||||
match (self.callback)(self.args, self.context, self.raw_args) {
|
||||
Ok(stream) => stream,
|
||||
Err(err) => OutputStream::one(Err(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EvaluatedWholeStreamCommandArgs {
|
||||
pub args: EvaluatedCommandArgs,
|
||||
pub input: InputStream,
|
||||
@ -398,6 +277,13 @@ impl EvaluatedCommandArgs {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Example {
|
||||
pub example: &'static str,
|
||||
pub description: &'static str,
|
||||
pub result: Option<Vec<Value>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait WholeStreamCommand: Send + Sync {
|
||||
fn name(&self) -> &str;
|
||||
|
||||
@ -407,7 +293,7 @@ pub trait WholeStreamCommand: Send + Sync {
|
||||
|
||||
fn usage(&self) -> &str;
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
@ -416,152 +302,69 @@ pub trait WholeStreamCommand: Send + Sync {
|
||||
fn is_binary(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PerItemCommand: Send + Sync {
|
||||
fn name(&self) -> &str;
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::new(self.name()).desc(self.usage()).filter()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str;
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
registry: &CommandRegistry,
|
||||
raw_args: &RawCommandArgs,
|
||||
input: Value,
|
||||
) -> Result<OutputStream, ShellError>;
|
||||
|
||||
fn is_binary(&self) -> bool {
|
||||
false
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Command {
|
||||
WholeStream(Arc<dyn WholeStreamCommand>),
|
||||
PerItem(Arc<dyn PerItemCommand>),
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct Command(Arc<dyn WholeStreamCommand>);
|
||||
|
||||
impl PrettyDebugWithSource for Command {
|
||||
fn pretty_debug(&self, source: &str) -> DebugDocBuilder {
|
||||
match self {
|
||||
Command::WholeStream(command) => b::typed(
|
||||
"whole stream command",
|
||||
b::description(command.name())
|
||||
+ b::space()
|
||||
+ b::equals()
|
||||
+ b::space()
|
||||
+ command.signature().pretty_debug(source),
|
||||
),
|
||||
Command::PerItem(command) => b::typed(
|
||||
"per item command",
|
||||
b::description(command.name())
|
||||
+ b::space()
|
||||
+ b::equals()
|
||||
+ b::space()
|
||||
+ command.signature().pretty_debug(source),
|
||||
),
|
||||
}
|
||||
b::typed(
|
||||
"whole stream command",
|
||||
b::description(self.name())
|
||||
+ b::space()
|
||||
+ b::equals()
|
||||
+ b::space()
|
||||
+ self.signature().pretty_debug(source),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Command {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Command::WholeStream(command) => write!(f, "WholeStream({})", command.name()),
|
||||
Command::PerItem(command) => write!(f, "PerItem({})", command.name()),
|
||||
}
|
||||
write!(f, "Command({})", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn name(&self) -> &str {
|
||||
match self {
|
||||
Command::WholeStream(command) => command.name(),
|
||||
Command::PerItem(command) => command.name(),
|
||||
}
|
||||
self.0.name()
|
||||
}
|
||||
|
||||
pub fn signature(&self) -> Signature {
|
||||
match self {
|
||||
Command::WholeStream(command) => command.signature(),
|
||||
Command::PerItem(command) => command.signature(),
|
||||
}
|
||||
self.0.signature()
|
||||
}
|
||||
|
||||
pub fn usage(&self) -> &str {
|
||||
match self {
|
||||
Command::WholeStream(command) => command.usage(),
|
||||
Command::PerItem(command) => command.usage(),
|
||||
}
|
||||
self.0.usage()
|
||||
}
|
||||
|
||||
pub fn run(&self, args: CommandArgs, registry: &CommandRegistry) -> OutputStream {
|
||||
if args.call_info.switch_present("help") {
|
||||
get_help(self.name(), self.usage(), self.signature()).into()
|
||||
} else {
|
||||
match self {
|
||||
Command::WholeStream(command) => match command.run(args, registry) {
|
||||
Ok(stream) => stream,
|
||||
Err(err) => OutputStream::one(Err(err)),
|
||||
},
|
||||
Command::PerItem(command) => {
|
||||
self.run_helper(command.clone(), args, registry.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_helper(
|
||||
pub async fn run(
|
||||
&self,
|
||||
command: Arc<dyn PerItemCommand>,
|
||||
args: CommandArgs,
|
||||
registry: CommandRegistry,
|
||||
) -> OutputStream {
|
||||
let raw_args = RawCommandArgs {
|
||||
host: args.host,
|
||||
ctrl_c: args.ctrl_c,
|
||||
shell_manager: args.shell_manager,
|
||||
call_info: args.call_info,
|
||||
};
|
||||
|
||||
let out = args
|
||||
.input
|
||||
.map(move |x| {
|
||||
let call_info = UnevaluatedCallInfo {
|
||||
args: raw_args.call_info.args.clone(),
|
||||
name_tag: raw_args.call_info.name_tag.clone(),
|
||||
scope: raw_args.call_info.scope.clone().set_it(x.clone()),
|
||||
}
|
||||
.evaluate(®istry);
|
||||
// let call_info = raw_args
|
||||
// .clone()
|
||||
// .call_info
|
||||
// .evaluate(®istry, &Scope::it_value(x.clone()));
|
||||
|
||||
match call_info {
|
||||
Ok(call_info) => match command.run(&call_info, ®istry, &raw_args, x) {
|
||||
Ok(o) => o,
|
||||
Err(e) => {
|
||||
futures::stream::iter(vec![ReturnValue::Err(e)]).to_output_stream()
|
||||
}
|
||||
},
|
||||
Err(e) => futures::stream::iter(vec![ReturnValue::Err(e)]).to_output_stream(),
|
||||
}
|
||||
})
|
||||
.flatten();
|
||||
|
||||
out.to_output_stream()
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
if args.call_info.switch_present("help") {
|
||||
let cl = self.0.clone();
|
||||
let registry = registry.clone();
|
||||
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(get_help(&*cl, ®istry)).into_value(Tag::unknown()),
|
||||
))))
|
||||
} else {
|
||||
self.0.run(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_binary(&self) -> bool {
|
||||
match self {
|
||||
Command::WholeStream(command) => command.is_binary(),
|
||||
Command::PerItem(command) => command.is_binary(),
|
||||
}
|
||||
self.0.is_binary()
|
||||
}
|
||||
|
||||
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
|
||||
@ -570,6 +373,7 @@ pub struct FnFilterCommand {
|
||||
func: fn(EvaluatedFilterCommandArgs) -> Result<OutputStream, ShellError>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FnFilterCommand {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
@ -579,54 +383,54 @@ impl WholeStreamCommand for FnFilterCommand {
|
||||
"usage"
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let CommandArgs {
|
||||
CommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
shell_manager,
|
||||
call_info,
|
||||
input,
|
||||
} = args;
|
||||
|
||||
let host: Arc<parking_lot::Mutex<dyn Host>> = host.clone();
|
||||
let registry: CommandRegistry = registry.clone();
|
||||
..
|
||||
}: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = Arc::new(registry.clone());
|
||||
let func = self.func;
|
||||
|
||||
let result = input.map(move |it| {
|
||||
let registry = registry.clone();
|
||||
let call_info = match call_info.clone().evaluate_with_new_it(®istry, &it) {
|
||||
Err(err) => return OutputStream::from(vec![Err(err)]).values,
|
||||
Ok(args) => args,
|
||||
};
|
||||
Ok(input
|
||||
.then(move |it| {
|
||||
let host = host.clone();
|
||||
let registry = registry.clone();
|
||||
let ctrl_c = ctrl_c.clone();
|
||||
let shell_manager = shell_manager.clone();
|
||||
let call_info = call_info.clone();
|
||||
async move {
|
||||
let call_info = match call_info.evaluate_with_new_it(&*registry, &it).await {
|
||||
Err(err) => {
|
||||
return OutputStream::one(Err(err));
|
||||
}
|
||||
Ok(args) => args,
|
||||
};
|
||||
|
||||
let args = EvaluatedFilterCommandArgs::new(
|
||||
host.clone(),
|
||||
ctrl_c.clone(),
|
||||
shell_manager.clone(),
|
||||
call_info,
|
||||
);
|
||||
let args = EvaluatedFilterCommandArgs::new(
|
||||
host.clone(),
|
||||
ctrl_c.clone(),
|
||||
shell_manager.clone(),
|
||||
call_info,
|
||||
);
|
||||
|
||||
match func(args) {
|
||||
Err(err) => OutputStream::from(vec![Err(err)]).values,
|
||||
Ok(stream) => stream.values,
|
||||
}
|
||||
});
|
||||
|
||||
let result = result.flatten();
|
||||
let result: BoxStream<ReturnValue> = result.boxed();
|
||||
|
||||
Ok(result.into())
|
||||
match func(args) {
|
||||
Err(err) => return OutputStream::one(Err(err)),
|
||||
Ok(stream) => stream,
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Arc<Command> {
|
||||
Arc::new(Command::WholeStream(Arc::new(command)))
|
||||
}
|
||||
|
||||
pub fn per_item_command(command: impl PerItemCommand + 'static) -> Arc<Command> {
|
||||
Arc::new(Command::PerItem(Arc::new(command)))
|
||||
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command {
|
||||
Command(Arc::new(command))
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::future;
|
||||
use futures::stream::StreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Compact;
|
||||
@ -13,6 +14,7 @@ pub struct CompactArgs {
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Compact {
|
||||
fn name(&self) -> &str {
|
||||
"compact"
|
||||
@ -26,36 +28,78 @@ impl WholeStreamCommand for Compact {
|
||||
"Creates a table with non-empty rows"
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, compact)?.run()
|
||||
compact(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Filter out all null entries in a list",
|
||||
example: "echo [1 2 $null 3 $null $null] | compact target",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Filter out all directory entries having no 'target'",
|
||||
example: "ls -af | compact target",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compact(
|
||||
CompactArgs { rest: columns }: CompactArgs,
|
||||
RunnableContext { input, .. }: RunnableContext,
|
||||
pub async fn compact(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let objects = input.filter(move |item| {
|
||||
let keep = if columns.is_empty() {
|
||||
item.is_some()
|
||||
} else {
|
||||
match item {
|
||||
Value {
|
||||
value: UntaggedValue::Row(ref r),
|
||||
..
|
||||
} => columns
|
||||
.iter()
|
||||
.all(|field| r.get_data(field).borrow().is_some()),
|
||||
_ => false,
|
||||
}
|
||||
};
|
||||
|
||||
futures::future::ready(keep)
|
||||
});
|
||||
|
||||
Ok(objects.from_input_stream())
|
||||
let registry = registry.clone();
|
||||
let (CompactArgs { rest: columns }, input) = args.process(®istry).await?;
|
||||
Ok(input
|
||||
.filter_map(move |item| {
|
||||
future::ready(if columns.is_empty() {
|
||||
if !item.is_empty() {
|
||||
Some(ReturnSuccess::value(item))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
match item {
|
||||
Value {
|
||||
value: UntaggedValue::Row(ref r),
|
||||
..
|
||||
} => {
|
||||
if columns
|
||||
.iter()
|
||||
.all(|field| r.get_data(field).borrow().is_some())
|
||||
{
|
||||
Some(ReturnSuccess::value(item))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Compact;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Compact {})
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ pub struct ConfigArgs {
|
||||
path: Tagged<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Config {
|
||||
fn name(&self) -> &str {
|
||||
"config"
|
||||
@ -30,7 +31,7 @@ impl WholeStreamCommand for Config {
|
||||
.named(
|
||||
"load",
|
||||
SyntaxShape::Path,
|
||||
"load the config from the path give",
|
||||
"load the config from the path given",
|
||||
Some('l'),
|
||||
)
|
||||
.named(
|
||||
@ -65,124 +66,195 @@ impl WholeStreamCommand for Config {
|
||||
"Configuration management."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, config)?.run()
|
||||
config(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "See all config values",
|
||||
example: "config",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Set completion_mode to circular",
|
||||
example: "config --set [completion_mode circular]",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Store the contents of the pipeline as a path",
|
||||
example: "echo ['/usr/bin' '/bin'] | config --set_into path",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current startup commands",
|
||||
example: "config --get startup",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Remove the startup commands",
|
||||
example: "config --remove startup",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Clear the config (be careful!)",
|
||||
example: "config --clear",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the path to the current config file",
|
||||
example: "config --path",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config(
|
||||
ConfigArgs {
|
||||
load,
|
||||
set,
|
||||
set_into,
|
||||
get,
|
||||
clear,
|
||||
remove,
|
||||
path,
|
||||
}: ConfigArgs,
|
||||
RunnableContext { name, input, .. }: RunnableContext,
|
||||
pub async fn config(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_span = name.clone();
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let configuration = if let Some(supplied) = load {
|
||||
Some(supplied.item().clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (
|
||||
ConfigArgs {
|
||||
load,
|
||||
set,
|
||||
set_into,
|
||||
get,
|
||||
clear,
|
||||
remove,
|
||||
path,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
|
||||
let mut result = crate::data::config::read(name_span, &configuration)?;
|
||||
|
||||
if let Some(v) = get {
|
||||
let key = v.to_string();
|
||||
let value = result
|
||||
.get(&key)
|
||||
.ok_or_else(|| ShellError::labeled_error("Missing key in config", "key", v.tag()))?;
|
||||
|
||||
match value {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => {
|
||||
for l in list {
|
||||
let value = l.clone();
|
||||
yield ReturnSuccess::value(l.clone());
|
||||
}
|
||||
}
|
||||
x => yield ReturnSuccess::value(x.clone()),
|
||||
}
|
||||
}
|
||||
else if let Some((key, value)) = set {
|
||||
result.insert(key.to_string(), value.clone());
|
||||
|
||||
config::write(&result, &configuration)?;
|
||||
|
||||
yield ReturnSuccess::value(UntaggedValue::Row(result.into()).into_value(&value.tag));
|
||||
}
|
||||
else if let Some(v) = set_into {
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
let key = v.to_string();
|
||||
|
||||
if rows.len() == 0 {
|
||||
yield Err(ShellError::labeled_error("No values given for set_into", "needs value(s) from pipeline", v.tag()));
|
||||
} else if rows.len() == 1 {
|
||||
// A single value
|
||||
let value = &rows[0];
|
||||
|
||||
result.insert(key.to_string(), value.clone());
|
||||
|
||||
config::write(&result, &configuration)?;
|
||||
|
||||
yield ReturnSuccess::value(UntaggedValue::Row(result.into()).into_value(name));
|
||||
} else {
|
||||
// Take in the pipeline as a table
|
||||
let value = UntaggedValue::Table(rows).into_value(name.clone());
|
||||
|
||||
result.insert(key.to_string(), value.clone());
|
||||
|
||||
config::write(&result, &configuration)?;
|
||||
|
||||
yield ReturnSuccess::value(UntaggedValue::Row(result.into()).into_value(name));
|
||||
}
|
||||
}
|
||||
else if let Tagged { item: true, tag } = clear {
|
||||
result.clear();
|
||||
|
||||
config::write(&result, &configuration)?;
|
||||
|
||||
yield ReturnSuccess::value(UntaggedValue::Row(result.into()).into_value(tag));
|
||||
|
||||
return;
|
||||
}
|
||||
else if let Tagged { item: true, tag } = path {
|
||||
let path = config::default_path_for(&configuration)?;
|
||||
|
||||
yield ReturnSuccess::value(UntaggedValue::Primitive(Primitive::Path(path)).into_value(tag));
|
||||
}
|
||||
else if let Some(v) = remove {
|
||||
let key = v.to_string();
|
||||
|
||||
if result.contains_key(&key) {
|
||||
result.swap_remove(&key);
|
||||
config::write(&result, &configuration)?
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Key does not exist in config",
|
||||
"key",
|
||||
v.tag(),
|
||||
));
|
||||
}
|
||||
|
||||
yield ReturnSuccess::value(UntaggedValue::Row(result.into()).into_value(v.tag()));
|
||||
}
|
||||
else {
|
||||
yield ReturnSuccess::value(UntaggedValue::Row(result.into()).into_value(name));
|
||||
}
|
||||
let configuration = if let Some(supplied) = load {
|
||||
Some(supplied.item().clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
let mut result = crate::data::config::read(name_span, &configuration)?;
|
||||
|
||||
Ok(if let Some(v) = get {
|
||||
let key = v.to_string();
|
||||
let value = result
|
||||
.get(&key)
|
||||
.ok_or_else(|| ShellError::labeled_error("Missing key in config", "key", v.tag()))?;
|
||||
|
||||
match value {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => {
|
||||
let list: Vec<_> = list
|
||||
.iter()
|
||||
.map(|x| ReturnSuccess::value(x.clone()))
|
||||
.collect();
|
||||
|
||||
futures::stream::iter(list).to_output_stream()
|
||||
}
|
||||
x => {
|
||||
let x = x.clone();
|
||||
OutputStream::one(ReturnSuccess::value(x))
|
||||
}
|
||||
}
|
||||
} else if let Some((key, value)) = set {
|
||||
result.insert(key.to_string(), value.clone());
|
||||
|
||||
config::write(&result, &configuration)?;
|
||||
|
||||
OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(&value.tag),
|
||||
))
|
||||
} else if let Some(v) = set_into {
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
let key = v.to_string();
|
||||
|
||||
if rows.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"No values given for set_into",
|
||||
"needs value(s) from pipeline",
|
||||
v.tag(),
|
||||
));
|
||||
} else if rows.len() == 1 {
|
||||
// A single value
|
||||
let value = &rows[0];
|
||||
|
||||
result.insert(key, value.clone());
|
||||
|
||||
config::write(&result, &configuration)?;
|
||||
|
||||
OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
))
|
||||
} else {
|
||||
// Take in the pipeline as a table
|
||||
let value = UntaggedValue::Table(rows).into_value(name.clone());
|
||||
|
||||
result.insert(key, value);
|
||||
|
||||
config::write(&result, &configuration)?;
|
||||
|
||||
OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
))
|
||||
}
|
||||
} else if let Tagged { item: true, tag } = clear {
|
||||
result.clear();
|
||||
|
||||
config::write(&result, &configuration)?;
|
||||
|
||||
OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(tag),
|
||||
))
|
||||
} else if let Tagged { item: true, tag } = path {
|
||||
let path = config::default_path_for(&configuration)?;
|
||||
|
||||
OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Primitive(Primitive::Path(path)).into_value(tag),
|
||||
))
|
||||
} else if let Some(v) = remove {
|
||||
let key = v.to_string();
|
||||
|
||||
if result.contains_key(&key) {
|
||||
result.swap_remove(&key);
|
||||
config::write(&result, &configuration)?;
|
||||
futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(v.tag()),
|
||||
)])
|
||||
.to_output_stream()
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Key does not exist in config",
|
||||
"key",
|
||||
v.tag(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
)])
|
||||
.to_output_stream()
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Config;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Config {})
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,11 @@ use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
use nu_protocol::{Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct Count;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CountArgs {}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Count {
|
||||
fn name(&self) -> &str {
|
||||
"count"
|
||||
@ -20,27 +18,39 @@ impl WholeStreamCommand for Count {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show the total number of rows."
|
||||
"Show the total number of rows or items."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, count)?.run()
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let rows: Vec<Value> = args.input.collect().await;
|
||||
|
||||
Ok(OutputStream::one(
|
||||
UntaggedValue::int(rows.len()).into_value(name),
|
||||
))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Count the number of entries in a list",
|
||||
example: "echo [1 2 3 4 5] | count",
|
||||
result: Some(vec![UntaggedValue::int(5).into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn count(
|
||||
CountArgs {}: CountArgs,
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Count;
|
||||
|
||||
yield ReturnSuccess::value(UntaggedValue::int(rows.len()).into_value(name))
|
||||
};
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
test_examples(Count {})
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::commands::command::RunnablePerItemContext;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CallInfo, Signature, SyntaxShape, Value};
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@ -15,7 +15,8 @@ pub struct CopyArgs {
|
||||
pub recursive: Tagged<bool>,
|
||||
}
|
||||
|
||||
impl PerItemCommand for Cpy {
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Cpy {
|
||||
fn name(&self) -> &str {
|
||||
"cp"
|
||||
}
|
||||
@ -35,20 +36,41 @@ impl PerItemCommand for Cpy {
|
||||
"Copy files."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
_registry: &CommandRegistry,
|
||||
raw_args: &RawCommandArgs,
|
||||
_input: Value,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
call_info
|
||||
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), cp)?
|
||||
.run()
|
||||
let shell_manager = args.shell_manager.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (args, _) = args.process(®istry).await?;
|
||||
shell_manager.cp(args, name)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Copy myfile to dir_b",
|
||||
example: "cp myfile dir_b",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Recursively copy dir_a to dir_b",
|
||||
example: "cp -r dir_a dir_b",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn cp(args: CopyArgs, context: &RunnablePerItemContext) -> Result<OutputStream, ShellError> {
|
||||
let shell_manager = context.shell_manager.clone();
|
||||
shell_manager.cp(args, context)
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Cpy;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Cpy {})
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,11 @@ use crate::commands::WholeStreamCommand;
|
||||
use chrono::{Datelike, TimeZone, Timelike};
|
||||
use core::fmt::Display;
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::{Signature, UntaggedValue};
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date"
|
||||
@ -20,76 +21,155 @@ impl WholeStreamCommand for Date {
|
||||
Signature::build("date")
|
||||
.switch("utc", "use universal time (UTC)", Some('u'))
|
||||
.switch("local", "use the local time", Some('l'))
|
||||
.named(
|
||||
"format",
|
||||
SyntaxShape::String,
|
||||
"report datetime in supplied strftime format",
|
||||
Some('f'),
|
||||
)
|
||||
.switch("raw", "print date without tables", Some('r'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Get the current datetime."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
date(args, registry)
|
||||
date(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get the current local time and date",
|
||||
example: "date",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current UTC time and date",
|
||||
example: "date --utc",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current time and date and report it based on format",
|
||||
example: "date --format '%Y-%m-%d %H:%M:%S.%f %z'",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current time and date and report it without a table",
|
||||
example: "date --format '%Y-%m-%d %H:%M:%S.%f %z' --raw",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, tag: Tag) -> Value
|
||||
pub fn date_to_value_raw<T: TimeZone>(dt: DateTime<T>, dt_format: String) -> String
|
||||
where
|
||||
T::Offset: Display,
|
||||
{
|
||||
let result = dt.format(&dt_format);
|
||||
format!("{}", result)
|
||||
}
|
||||
|
||||
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, tag: Tag, dt_format: String) -> Value
|
||||
where
|
||||
T::Offset: Display,
|
||||
{
|
||||
let mut indexmap = IndexMap::new();
|
||||
|
||||
indexmap.insert(
|
||||
"year".to_string(),
|
||||
UntaggedValue::int(dt.year()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"month".to_string(),
|
||||
UntaggedValue::int(dt.month()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"day".to_string(),
|
||||
UntaggedValue::int(dt.day()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"hour".to_string(),
|
||||
UntaggedValue::int(dt.hour()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"minute".to_string(),
|
||||
UntaggedValue::int(dt.minute()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"second".to_string(),
|
||||
UntaggedValue::int(dt.second()).into_value(&tag),
|
||||
);
|
||||
if dt_format.is_empty() {
|
||||
indexmap.insert(
|
||||
"year".to_string(),
|
||||
UntaggedValue::int(dt.year()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"month".to_string(),
|
||||
UntaggedValue::int(dt.month()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"day".to_string(),
|
||||
UntaggedValue::int(dt.day()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"hour".to_string(),
|
||||
UntaggedValue::int(dt.hour()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"minute".to_string(),
|
||||
UntaggedValue::int(dt.minute()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"second".to_string(),
|
||||
UntaggedValue::int(dt.second()).into_value(&tag),
|
||||
);
|
||||
|
||||
let tz = dt.offset();
|
||||
indexmap.insert(
|
||||
"timezone".to_string(),
|
||||
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
|
||||
);
|
||||
let tz = dt.offset();
|
||||
indexmap.insert(
|
||||
"timezone".to_string(),
|
||||
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
|
||||
);
|
||||
} else {
|
||||
let result = dt.format(&dt_format);
|
||||
indexmap.insert(
|
||||
"formatted".to_string(),
|
||||
UntaggedValue::string(format!("{}", result)).into_value(&tag),
|
||||
);
|
||||
}
|
||||
|
||||
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
|
||||
}
|
||||
|
||||
pub fn date(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once(registry)?;
|
||||
|
||||
let mut date_out = VecDeque::new();
|
||||
pub async fn date(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®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)
|
||||
}
|
||||
};
|
||||
|
||||
date_out.push_back(value);
|
||||
|
||||
Ok(futures::stream::iter(date_out).to_output_stream())
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Date {})
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ pub struct DebugArgs {
|
||||
raw: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Debug {
|
||||
fn name(&self) -> &str {
|
||||
"debug"
|
||||
@ -23,19 +24,21 @@ impl WholeStreamCommand for Debug {
|
||||
"Print the Rust debug representation of the values"
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, debug_value)?.run()
|
||||
debug_value(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_value(
|
||||
DebugArgs { raw }: DebugArgs,
|
||||
RunnableContext { input, .. }: RunnableContext,
|
||||
) -> Result<impl ToOutputStream, ShellError> {
|
||||
async fn debug_value(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (DebugArgs { raw }, input) = args.process(®istry).await?;
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if raw {
|
||||
@ -48,3 +51,15 @@ fn debug_value(
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Debug;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Debug {})
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ struct DefaultArgs {
|
||||
|
||||
pub struct Default;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Default {
|
||||
fn name(&self) -> &str {
|
||||
"default"
|
||||
@ -33,23 +34,32 @@ impl WholeStreamCommand for Default {
|
||||
"Sets a default row's column if missing."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, default)?.run()
|
||||
default(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Give a default 'target' to all file entries",
|
||||
example: "ls -af | default target 'nothing'",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn default(
|
||||
DefaultArgs { column, value }: DefaultArgs,
|
||||
RunnableContext { input, .. }: RunnableContext,
|
||||
async fn default(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = input
|
||||
.map(move |item| {
|
||||
let mut result = VecDeque::new();
|
||||
let registry = registry.clone();
|
||||
let (DefaultArgs { column, value }, input) = args.process(®istry).await?;
|
||||
|
||||
Ok(input
|
||||
.map(move |item| {
|
||||
let should_add = match item {
|
||||
Value {
|
||||
value: UntaggedValue::Row(ref r),
|
||||
@ -60,16 +70,24 @@ fn default(
|
||||
|
||||
if should_add {
|
||||
match item.insert_data_at_path(&column.item, value.clone()) {
|
||||
Some(new_value) => result.push_back(ReturnSuccess::value(new_value)),
|
||||
None => result.push_back(ReturnSuccess::value(item)),
|
||||
Some(new_value) => ReturnSuccess::value(new_value),
|
||||
None => ReturnSuccess::value(item),
|
||||
}
|
||||
} else {
|
||||
result.push_back(ReturnSuccess::value(item));
|
||||
ReturnSuccess::value(item)
|
||||
}
|
||||
|
||||
futures::stream::iter(result)
|
||||
})
|
||||
.flatten();
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Default;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Default {})
|
||||
}
|
||||
}
|
||||
|
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 {})
|
||||
}
|
||||
}
|
83
crates/nu-cli/src/commands/drop.rs
Normal file
83
crates/nu-cli/src/commands/drop.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Drop;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DropArgs {
|
||||
rows: Option<Tagged<u64>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Drop {
|
||||
fn name(&self) -> &str {
|
||||
"drop"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("drop").optional(
|
||||
"rows",
|
||||
SyntaxShape::Number,
|
||||
"starting from the back, the number of rows to drop",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Drop the last number of rows."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (DropArgs { rows }, input) = args.process(®istry).await?;
|
||||
let mut v: Vec<_> = input.into_vec().await;
|
||||
|
||||
let rows_to_drop = if let Some(quantity) = rows {
|
||||
*quantity as usize
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
for _ in 0..rows_to_drop {
|
||||
v.pop();
|
||||
}
|
||||
|
||||
Ok(futures::stream::iter(v).to_output_stream())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Remove the last item of a list/table",
|
||||
example: "echo [1 2 3] | drop",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(2).into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Remove the last 2 items of a list/table",
|
||||
example: "echo [1 2 3] | drop 2",
|
||||
result: Some(vec![UntaggedValue::int(1).into()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Drop;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Drop {})
|
||||
}
|
||||
}
|
@ -1,14 +1,13 @@
|
||||
extern crate filesize;
|
||||
|
||||
use crate::commands::command::RunnablePerItemContext;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use filesize::file_real_size_fast;
|
||||
use glob::*;
|
||||
use indexmap::map::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CallInfo, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
const NAME: &str = "du";
|
||||
const GLOB_PARAMS: MatchOptions = MatchOptions {
|
||||
@ -31,7 +30,8 @@ pub struct DuArgs {
|
||||
min_size: Option<Tagged<u64>>,
|
||||
}
|
||||
|
||||
impl PerItemCommand for Du {
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Du {
|
||||
fn name(&self) -> &str {
|
||||
NAME
|
||||
}
|
||||
@ -73,22 +73,30 @@ impl PerItemCommand for Du {
|
||||
"Find disk usage sizes of specified items"
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
_registry: &CommandRegistry,
|
||||
raw_args: &RawCommandArgs,
|
||||
_input: Value,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
call_info
|
||||
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), du)?
|
||||
.run()
|
||||
du(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Disk usage of the current directory",
|
||||
example: "du",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn du(args: DuArgs, ctx: &RunnablePerItemContext) -> Result<OutputStream, ShellError> {
|
||||
let tag = ctx.name.clone();
|
||||
async fn du(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let ctrl_c = args.ctrl_c.clone();
|
||||
let ctrl_c_copy = ctrl_c.clone();
|
||||
|
||||
let (args, _): (DuArgs, _) = args.process(®istry).await?;
|
||||
let exclude = args.exclude.map_or(Ok(None), move |x| {
|
||||
Pattern::new(&x.item)
|
||||
.map(Option::Some)
|
||||
@ -117,7 +125,6 @@ fn du(args: DuArgs, ctx: &RunnablePerItemContext) -> Result<OutputStream, ShellE
|
||||
})
|
||||
.map(|v| v.map_err(glob_err_into));
|
||||
|
||||
let ctrl_c = ctx.ctrl_c.clone();
|
||||
let all = args.all;
|
||||
let deref = args.deref;
|
||||
let max_depth = args.max_depth.map(|f| f.item);
|
||||
@ -131,25 +138,30 @@ fn du(args: DuArgs, ctx: &RunnablePerItemContext) -> Result<OutputStream, ShellE
|
||||
all,
|
||||
};
|
||||
|
||||
let stream = futures::stream::iter(paths)
|
||||
.interruptible(ctrl_c)
|
||||
.map(move |path| match path {
|
||||
Ok(p) => {
|
||||
if p.is_dir() {
|
||||
Ok(ReturnSuccess::Value(
|
||||
DirInfo::new(p, ¶ms, max_depth).into(),
|
||||
))
|
||||
} else {
|
||||
FileInfo::new(p, deref, tag.clone()).map(|v| ReturnSuccess::Value(v.into()))
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
});
|
||||
let inp = futures::stream::iter(paths);
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(inp
|
||||
.flat_map(move |path| match path {
|
||||
Ok(p) => {
|
||||
let mut output = vec![];
|
||||
if p.is_dir() {
|
||||
output.push(Ok(ReturnSuccess::Value(
|
||||
DirInfo::new(p, ¶ms, max_depth, ctrl_c.clone()).into(),
|
||||
)));
|
||||
} else {
|
||||
for v in FileInfo::new(p, deref, tag.clone()).into_iter() {
|
||||
output.push(Ok(ReturnSuccess::Value(v.into())));
|
||||
}
|
||||
}
|
||||
futures::stream::iter(output)
|
||||
}
|
||||
Err(e) => futures::stream::iter(vec![Err(e)]),
|
||||
})
|
||||
.interruptible(ctrl_c_copy)
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
struct DirBuilder {
|
||||
pub struct DirBuilder {
|
||||
tag: Tag,
|
||||
min: Option<u64>,
|
||||
deref: bool,
|
||||
@ -157,7 +169,25 @@ struct DirBuilder {
|
||||
all: bool,
|
||||
}
|
||||
|
||||
struct DirInfo {
|
||||
impl DirBuilder {
|
||||
pub fn new(
|
||||
tag: Tag,
|
||||
min: Option<u64>,
|
||||
deref: bool,
|
||||
exclude: Option<Pattern>,
|
||||
all: bool,
|
||||
) -> DirBuilder {
|
||||
DirBuilder {
|
||||
tag,
|
||||
min,
|
||||
deref,
|
||||
exclude,
|
||||
all,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirInfo {
|
||||
dirs: Vec<DirInfo>,
|
||||
files: Vec<FileInfo>,
|
||||
errors: Vec<ShellError>,
|
||||
@ -200,7 +230,12 @@ impl FileInfo {
|
||||
}
|
||||
|
||||
impl DirInfo {
|
||||
fn new(path: impl Into<PathBuf>, params: &DirBuilder, depth: Option<u64>) -> Self {
|
||||
pub fn new(
|
||||
path: impl Into<PathBuf>,
|
||||
params: &DirBuilder,
|
||||
depth: Option<u64>,
|
||||
ctrl_c: Arc<AtomicBool>,
|
||||
) -> Self {
|
||||
let path = path.into();
|
||||
|
||||
let mut s = Self {
|
||||
@ -216,9 +251,15 @@ impl DirInfo {
|
||||
match std::fs::read_dir(&s.path) {
|
||||
Ok(d) => {
|
||||
for f in d {
|
||||
if ctrl_c.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
|
||||
match f {
|
||||
Ok(i) => match i.file_type() {
|
||||
Ok(t) if t.is_dir() => s = s.add_dir(i.path(), depth, ¶ms),
|
||||
Ok(t) if t.is_dir() => {
|
||||
s = s.add_dir(i.path(), depth, ¶ms, ctrl_c.clone())
|
||||
}
|
||||
Ok(_t) => s = s.add_file(i.path(), ¶ms),
|
||||
Err(e) => s = s.add_error(e.into()),
|
||||
},
|
||||
@ -236,6 +277,7 @@ impl DirInfo {
|
||||
path: impl Into<PathBuf>,
|
||||
mut depth: Option<u64>,
|
||||
params: &DirBuilder,
|
||||
ctrl_c: Arc<AtomicBool>,
|
||||
) -> Self {
|
||||
if let Some(current) = depth {
|
||||
if let Some(new) = current.checked_sub(1) {
|
||||
@ -245,7 +287,7 @@ impl DirInfo {
|
||||
}
|
||||
}
|
||||
|
||||
let d = DirInfo::new(path, ¶ms, depth);
|
||||
let d = DirInfo::new(path, ¶ms, depth, ctrl_c);
|
||||
self.size += d.size;
|
||||
self.blocks += d.blocks;
|
||||
self.dirs.push(d);
|
||||
@ -280,6 +322,10 @@ impl DirInfo {
|
||||
self.errors.push(e);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
fn glob_err_into(e: GlobError) -> ShellError {
|
||||
@ -374,3 +420,15 @@ impl From<FileInfo> for Value {
|
||||
UntaggedValue::row(r).retag(&f.tag)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Du;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Du {})
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,24 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::PerItemCommand;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
|
||||
use futures::stream::once;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{
|
||||
hir::Block, hir::Expression, hir::SpannedExpression, hir::Synthetic, Scope, Signature,
|
||||
SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
pub struct Each;
|
||||
|
||||
impl PerItemCommand for Each {
|
||||
#[derive(Deserialize)]
|
||||
pub struct EachArgs {
|
||||
block: Block,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Each {
|
||||
fn name(&self) -> &str {
|
||||
"each"
|
||||
}
|
||||
@ -27,60 +35,107 @@ impl PerItemCommand for Each {
|
||||
"Run a block on each row of the table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
raw_args: &RawCommandArgs,
|
||||
input: Value,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let call_info = call_info.clone();
|
||||
let registry = registry.clone();
|
||||
let raw_args = raw_args.clone();
|
||||
let stream = async_stream! {
|
||||
match call_info.args.expect_nth(0)? {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag
|
||||
} => {
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let input_clone = input.clone();
|
||||
let input_stream = once(async { Ok(input) }).to_input_stream();
|
||||
each(args, registry).await
|
||||
}
|
||||
|
||||
let result = run_block(
|
||||
block,
|
||||
&mut context,
|
||||
input_stream,
|
||||
&Scope::new(input_clone),
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
let errors = context.get_errors();
|
||||
if let Some(error) = errors.first() {
|
||||
yield Err(error.clone());
|
||||
return;
|
||||
}
|
||||
|
||||
while let Some(result) = stream.next().await {
|
||||
yield Ok(ReturnSuccess::Value(result));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
yield Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a block",
|
||||
"each needs a block",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Echo the square of each integer",
|
||||
example: "echo [1 2 3] | each { echo $(= $it * $it) }",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(4).into(),
|
||||
UntaggedValue::int(9).into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Echo the sum of each row",
|
||||
example: "echo [[1 2] [3 4]] | each { echo $it | math sum }",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(7).into(),
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn is_expanded_it_usage(head: &SpannedExpression) -> bool {
|
||||
match &*head {
|
||||
SpannedExpression {
|
||||
expr: Expression::Synthetic(Synthetic::String(s)),
|
||||
..
|
||||
} if s == "expanded-each" => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_row(
|
||||
block: Arc<Block>,
|
||||
scope: Arc<Scope>,
|
||||
head: Arc<Box<SpannedExpression>>,
|
||||
mut context: Arc<Context>,
|
||||
input: Value,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let input_clone = input.clone();
|
||||
let input_stream = if is_expanded_it_usage(&head) {
|
||||
InputStream::empty()
|
||||
} else {
|
||||
once(async { Ok(input_clone) }).to_input_stream()
|
||||
};
|
||||
Ok(run_block(
|
||||
&block,
|
||||
Arc::make_mut(&mut context),
|
||||
input_stream,
|
||||
&input,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await?
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
async fn each(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let context = Arc::new(Context::from_raw(&raw_args, ®istry));
|
||||
let (each_args, input): (EachArgs, _) = raw_args.process(®istry).await?;
|
||||
let block = Arc::new(each_args.block);
|
||||
Ok(input
|
||||
.then(move |input| {
|
||||
let block = block.clone();
|
||||
let scope = scope.clone();
|
||||
let head = head.clone();
|
||||
let context = context.clone();
|
||||
async {
|
||||
match process_row(block, scope, head, context, input).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Each;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Each {})
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,20 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CallInfo, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::hir::Operator;
|
||||
use nu_protocol::{
|
||||
Primitive, RangeInclusion, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
pub struct Echo;
|
||||
|
||||
impl PerItemCommand for Echo {
|
||||
#[derive(Deserialize)]
|
||||
pub struct EchoArgs {
|
||||
pub rest: Vec<Value>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Echo {
|
||||
fn name(&self) -> &str {
|
||||
"echo"
|
||||
}
|
||||
@ -17,51 +27,96 @@ impl PerItemCommand for Echo {
|
||||
"Echo the arguments back to the user."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
raw_args: &RawCommandArgs,
|
||||
_input: Value,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
run(call_info, registry, raw_args)
|
||||
echo(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Put a hello message in the pipeline",
|
||||
example: "echo 'hello'",
|
||||
result: Some(vec![Value::from("hello")]),
|
||||
},
|
||||
Example {
|
||||
description: "Print the value of the special '$nu' variable",
|
||||
example: "echo $nu",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn run(
|
||||
call_info: &CallInfo,
|
||||
_registry: &CommandRegistry,
|
||||
_raw_args: &RawCommandArgs,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let mut output = vec![];
|
||||
async fn echo(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (args, _): (EchoArgs, _) = args.process(®istry).await?;
|
||||
|
||||
if let Some(ref positional) = call_info.args.positional {
|
||||
for i in positional {
|
||||
match i.as_string() {
|
||||
Ok(s) => {
|
||||
output.push(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(s).into_value(i.tag.clone()),
|
||||
)));
|
||||
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()
|
||||
}
|
||||
_ => match i {
|
||||
Value {
|
||||
value: UntaggedValue::Table(table),
|
||||
..
|
||||
} => {
|
||||
for value in table {
|
||||
output.push(Ok(ReturnSuccess::Value(value.clone())));
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Range(range)),
|
||||
tag
|
||||
} => {
|
||||
let mut output_vec = vec![];
|
||||
|
||||
let mut current = range.from.0.item;
|
||||
while current != range.to.0.item {
|
||||
output_vec.push(Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag))));
|
||||
current = match crate::data::value::compute_values(Operator::Plus, &UntaggedValue::Primitive(current), &UntaggedValue::int(1)) {
|
||||
Ok(result) => match result {
|
||||
UntaggedValue::Primitive(p) => p,
|
||||
_ => {
|
||||
return OutputStream::one(Err(ShellError::unimplemented("Internal error: expected a primitive result from increment")));
|
||||
}
|
||||
},
|
||||
Err((left_type, right_type)) => {
|
||||
return OutputStream::one(Err(ShellError::coerce_error(
|
||||
left_type.spanned(tag.span),
|
||||
right_type.spanned(tag.span),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
output.push(Ok(ReturnSuccess::Value(i.clone())));
|
||||
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())))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: This whole block can probably be replaced with `.map()`
|
||||
let stream = futures::stream::iter(output);
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(futures::stream::iter(stream).flatten().to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Echo;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Echo {})
|
||||
}
|
||||
}
|
||||
|
@ -1,72 +0,0 @@
|
||||
use crate::commands::PerItemCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CallInfo, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
pub struct Edit;
|
||||
|
||||
impl PerItemCommand for Edit {
|
||||
fn name(&self) -> &str {
|
||||
"edit"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("edit")
|
||||
.required(
|
||||
"Field",
|
||||
SyntaxShape::ColumnPath,
|
||||
"the name of the column to edit",
|
||||
)
|
||||
.required(
|
||||
"Value",
|
||||
SyntaxShape::String,
|
||||
"the new value to give the cell(s)",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Edit an existing column to have a new value."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
_registry: &CommandRegistry,
|
||||
_raw_args: &RawCommandArgs,
|
||||
value: Value,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let value_tag = value.tag();
|
||||
let field = call_info.args.expect_nth(0)?.as_column_path()?;
|
||||
let replacement = call_info.args.expect_nth(1)?.tagged_unknown();
|
||||
|
||||
let stream = match value {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => match obj.replace_data_at_column_path(&field, replacement.item.clone()) {
|
||||
Some(v) => futures::stream::iter(vec![Ok(ReturnSuccess::Value(v))]),
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"edit could not find place to insert column",
|
||||
"column name",
|
||||
&field.tag,
|
||||
))
|
||||
}
|
||||
},
|
||||
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
value_tag,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
@ -1,157 +1,209 @@
|
||||
use crate::commands::PerItemCommand;
|
||||
use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
CallInfo, CommandAction, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
CommandAction, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct Enter;
|
||||
|
||||
impl PerItemCommand for Enter {
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnterArgs {
|
||||
location: Tagged<PathBuf>,
|
||||
encoding: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Enter {
|
||||
fn name(&self) -> &str {
|
||||
"enter"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("enter").required(
|
||||
"location",
|
||||
SyntaxShape::Path,
|
||||
"the location to create a new shell from",
|
||||
)
|
||||
Signature::build("enter")
|
||||
.required(
|
||||
"location",
|
||||
SyntaxShape::Path,
|
||||
"the location to create a new shell from",
|
||||
)
|
||||
.named(
|
||||
"encoding",
|
||||
SyntaxShape::String,
|
||||
"encoding to use to open file",
|
||||
Some('e'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Create a new shell and begin at this path."
|
||||
r#"Create a new shell and begin at this path.
|
||||
|
||||
Multiple encodings are supported for reading text files by using
|
||||
the '--encoding <encoding>' parameter. Here is an example of a few:
|
||||
big5, euc-jp, euc-kr, gbk, iso-8859-1, utf-16, cp1252, latin5
|
||||
|
||||
For a more complete list of encodings please refer to the encoding_rs
|
||||
documentation link at https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics"#
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
raw_args: &RawCommandArgs,
|
||||
_input: Value,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let raw_args = raw_args.clone();
|
||||
match call_info.args.expect_nth(0)? {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Path(location)),
|
||||
tag,
|
||||
..
|
||||
} => {
|
||||
let location_string = location.display().to_string();
|
||||
let location_clone = location_string.clone();
|
||||
let tag_clone = tag.clone();
|
||||
enter(args, registry).await
|
||||
}
|
||||
|
||||
if location_string.starts_with("help") {
|
||||
let spec = location_string.split(':').collect::<Vec<&str>>();
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Enter a path as a new shell",
|
||||
example: "enter ../projectB",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Enter a file as a new shell",
|
||||
example: "enter package.json",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Enters file with iso-8859-1 encoding",
|
||||
example: "enter file.csv --encoding iso-8859-1",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if spec.len() == 2 {
|
||||
let (_, command) = (spec[0], spec[1]);
|
||||
async fn enter(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let shell_manager = raw_args.shell_manager.clone();
|
||||
let head = raw_args.call_info.args.head.clone();
|
||||
let ctrl_c = raw_args.ctrl_c.clone();
|
||||
let current_errors = raw_args.current_errors.clone();
|
||||
let host = raw_args.host.clone();
|
||||
let tag = raw_args.call_info.name_tag.clone();
|
||||
let (EnterArgs { location, encoding }, _) = raw_args.process(®istry).await?;
|
||||
let location_string = location.display().to_string();
|
||||
let location_clone = location_string.clone();
|
||||
|
||||
if registry.has(command) {
|
||||
return Ok(vec![Ok(ReturnSuccess::Action(
|
||||
CommandAction::EnterHelpShell(
|
||||
UntaggedValue::string(command).into_value(Tag::unknown()),
|
||||
if location_string.starts_with("help") {
|
||||
let spec = location_string.split(':').collect::<Vec<&str>>();
|
||||
|
||||
if spec.len() == 2 {
|
||||
let (_, command) = (spec[0], spec[1]);
|
||||
|
||||
if registry.has(command) {
|
||||
return Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterHelpShell(
|
||||
UntaggedValue::string(command).into_value(Tag::unknown()),
|
||||
),
|
||||
)));
|
||||
}
|
||||
}
|
||||
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();
|
||||
|
||||
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(),
|
||||
})),
|
||||
),
|
||||
))]
|
||||
.into());
|
||||
}
|
||||
x => x,
|
||||
},
|
||||
))
|
||||
.to_output_stream())
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterValueShell(tagged_contents),
|
||||
)))
|
||||
}
|
||||
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
|
||||
UntaggedValue::nothing().into_value(Tag::unknown()),
|
||||
)))]
|
||||
.into())
|
||||
} else if location.is_dir() {
|
||||
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterShell(
|
||||
location_clone,
|
||||
)))]
|
||||
.into())
|
||||
} else {
|
||||
let stream = async_stream! {
|
||||
// If it's a file, attempt to open the file as a value and enter it
|
||||
let cwd = raw_args.shell_manager.path();
|
||||
|
||||
let full_path = std::path::PathBuf::from(cwd);
|
||||
|
||||
let (file_extension, contents, contents_tag) =
|
||||
crate::commands::open::fetch(
|
||||
&full_path,
|
||||
&location_clone,
|
||||
tag_clone.span,
|
||||
).await?;
|
||||
|
||||
match contents {
|
||||
UntaggedValue::Primitive(Primitive::String(_)) => {
|
||||
let tagged_contents = contents.into_value(&contents_tag);
|
||||
|
||||
if let Some(extension) = file_extension {
|
||||
let command_name = format!("from-{}", extension);
|
||||
if let Some(converter) =
|
||||
registry.get_command(&command_name)
|
||||
{
|
||||
let new_args = RawCommandArgs {
|
||||
host: raw_args.host,
|
||||
ctrl_c: raw_args.ctrl_c,
|
||||
shell_manager: raw_args.shell_manager,
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: nu_protocol::hir::Call {
|
||||
head: raw_args.call_info.args.head,
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
},
|
||||
name_tag: raw_args.call_info.name_tag,
|
||||
scope: raw_args.call_info.scope.clone()
|
||||
},
|
||||
};
|
||||
let mut result = converter.run(
|
||||
new_args.with_input(vec![tagged_contents]),
|
||||
®istry,
|
||||
);
|
||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
||||
result.drain_vec().await;
|
||||
for res in result_vec {
|
||||
match res {
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value,
|
||||
..
|
||||
})) => {
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(
|
||||
Value {
|
||||
value,
|
||||
tag: contents_tag.clone(),
|
||||
})));
|
||||
}
|
||||
x => yield x,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
|
||||
}
|
||||
} else {
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let tagged_contents = contents.into_value(contents_tag);
|
||||
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterValueShell(tagged_contents),
|
||||
)))
|
||||
}
|
||||
}
|
||||
x => Ok(
|
||||
vec![Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(
|
||||
x.clone(),
|
||||
)))]
|
||||
.into(),
|
||||
),
|
||||
_ => {
|
||||
let tagged_contents = contents.into_value(contents_tag);
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterValueShell(tagged_contents),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Enter;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Enter {})
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ pub struct EvaluateByArgs {
|
||||
evaluate_with: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for EvaluateBy {
|
||||
fn name(&self) -> &str {
|
||||
"evaluate-by"
|
||||
@ -31,42 +32,52 @@ impl WholeStreamCommand for EvaluateBy {
|
||||
"Creates a new table with the data from the tables rows evaluated by the column given."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, evaluate_by)?.run()
|
||||
evaluate_by(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evaluate_by(
|
||||
EvaluateByArgs { evaluate_with }: EvaluateByArgs,
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
pub async fn evaluate_by(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (EvaluateByArgs { evaluate_with }, mut input) = args.process(®istry).await?;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
if values.is_empty() {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected table from pipeline",
|
||||
"requires a table input",
|
||||
name
|
||||
))
|
||||
if values.is_empty() {
|
||||
Err(ShellError::labeled_error(
|
||||
"Expected table from pipeline",
|
||||
"requires a table input",
|
||||
name,
|
||||
))
|
||||
} else {
|
||||
let evaluate_with = if let Some(evaluator) = evaluate_with {
|
||||
Some(evaluator.item().clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let evaluate_with = if let Some(evaluator) = evaluate_with {
|
||||
Some(evaluator.item().clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match evaluate(&values[0], evaluate_with, name) {
|
||||
Ok(evaluated) => yield ReturnSuccess::value(evaluated),
|
||||
Err(err) => yield Err(err)
|
||||
}
|
||||
match evaluate(&values[0], evaluate_with, name) {
|
||||
Ok(evaluated) => Ok(OutputStream::one(ReturnSuccess::value(evaluated))),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::EvaluateBy;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(EvaluateBy {})
|
||||
}
|
||||
}
|
||||
|
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 {})
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ use nu_protocol::{CommandAction, ReturnSuccess, Signature};
|
||||
|
||||
pub struct Exit;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Exit {
|
||||
fn name(&self) -> &str {
|
||||
"exit"
|
||||
@ -19,21 +20,54 @@ impl WholeStreamCommand for Exit {
|
||||
"Exit the current shell (or all shells)"
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
exit(args, registry)
|
||||
exit(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Exit the current shell",
|
||||
example: "exit",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Exit all shells (exiting Nu)",
|
||||
example: "exit --now",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exit(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once(registry)?;
|
||||
pub async fn exit(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
|
||||
if args.call_info.args.has("now") {
|
||||
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::Exit))].into())
|
||||
let command_action = if args.call_info.args.has("now") {
|
||||
CommandAction::Exit
|
||||
} else {
|
||||
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::LeaveShell))].into())
|
||||
CommandAction::LeaveShell
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::action(command_action)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Exit;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Exit {})
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct First;
|
||||
@ -12,6 +12,7 @@ pub struct FirstArgs {
|
||||
rows: Option<Tagged<usize>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for First {
|
||||
fn name(&self) -> &str {
|
||||
"first"
|
||||
@ -29,24 +30,53 @@ impl WholeStreamCommand for First {
|
||||
"Show only the first number of rows."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, first)?.run()
|
||||
first(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Return the first item of a list/table",
|
||||
example: "echo [1 2 3] | first",
|
||||
result: Some(vec![UntaggedValue::int(1).into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Return the first 2 items of a list/table",
|
||||
example: "echo [1 2 3] | first 2",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(2).into(),
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn first(
|
||||
FirstArgs { rows }: FirstArgs,
|
||||
context: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
async fn first(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (FirstArgs { rows }, input) = args.process(®istry).await?;
|
||||
let rows_desired = if let Some(quantity) = rows {
|
||||
*quantity
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
Ok(OutputStream::from_input(context.input.take(rows_desired)))
|
||||
Ok(input.take(rows_desired).to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::First;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(First {})
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,21 @@
|
||||
use crate::commands::PerItemCommand;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
CallInfo, ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
use nu_value_ext::{as_column_path, get_data_by_column_path};
|
||||
use std::borrow::Borrow;
|
||||
|
||||
pub struct Format;
|
||||
|
||||
impl PerItemCommand for Format {
|
||||
#[derive(Deserialize)]
|
||||
pub struct FormatArgs {
|
||||
pattern: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Format {
|
||||
fn name(&self) -> &str {
|
||||
"format"
|
||||
}
|
||||
@ -19,7 +23,7 @@ impl PerItemCommand for Format {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("format").required(
|
||||
"pattern",
|
||||
SyntaxShape::Any,
|
||||
SyntaxShape::String,
|
||||
"the pattern to output. Eg) \"{foo}: {bar}\"",
|
||||
)
|
||||
}
|
||||
@ -28,63 +32,77 @@ impl PerItemCommand for Format {
|
||||
"Format columns into a string using a simple pattern."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
_registry: &CommandRegistry,
|
||||
_raw_args: &RawCommandArgs,
|
||||
value: Value,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
//let value_tag = value.tag();
|
||||
let pattern = call_info.args.expect_nth(0)?;
|
||||
let pattern_tag = pattern.tag.clone();
|
||||
let pattern = pattern.as_string()?;
|
||||
format_command(args, registry).await
|
||||
}
|
||||
|
||||
let format_pattern = format(&pattern);
|
||||
let commands = format_pattern;
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Print filenames with their sizes",
|
||||
example: "ls | format '{name}: {size}'",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
let output = match value {
|
||||
value
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
let mut output = String::new();
|
||||
async fn format_command(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = Arc::new(registry.clone());
|
||||
let scope = Arc::new(args.call_info.scope.clone());
|
||||
let (FormatArgs { pattern }, input) = args.process(®istry).await?;
|
||||
|
||||
for command in &commands {
|
||||
let format_pattern = format(&pattern);
|
||||
let commands = Arc::new(format_pattern);
|
||||
|
||||
Ok(input
|
||||
.then(move |value| {
|
||||
let mut output = String::new();
|
||||
let commands = commands.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = scope.clone();
|
||||
|
||||
async move {
|
||||
for command in &*commands {
|
||||
match command {
|
||||
FormatCommand::Text(s) => {
|
||||
output.push_str(&s);
|
||||
}
|
||||
FormatCommand::Column(c) => {
|
||||
let key = to_column_path(&c, &pattern_tag)?;
|
||||
|
||||
let fetcher = get_data_by_column_path(
|
||||
&value,
|
||||
&key,
|
||||
Box::new(move |(_, _, error)| error),
|
||||
// FIXME: use the correct spans
|
||||
let full_column_path = nu_parser::parse_full_column_path(
|
||||
&(c.to_string()).spanned(Span::unknown()),
|
||||
&*registry,
|
||||
);
|
||||
|
||||
if let Ok(c) = fetcher {
|
||||
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
|
||||
}
|
||||
// That column doesn't match, so don't emit anything
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
ReturnSuccess::value(UntaggedValue::string(output).into_untagged_value())
|
||||
}
|
||||
_ => String::new(),
|
||||
};
|
||||
|
||||
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_untagged_value(),
|
||||
)])
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -132,26 +150,14 @@ fn format(input: &str) -> Vec<FormatCommand> {
|
||||
output
|
||||
}
|
||||
|
||||
fn to_column_path(
|
||||
path_members: &str,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Tagged<ColumnPath>, ShellError> {
|
||||
let tag = tag.into();
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Format;
|
||||
|
||||
as_column_path(
|
||||
&UntaggedValue::Table(
|
||||
path_members
|
||||
.split('.')
|
||||
.map(|x| {
|
||||
let member = match x.parse::<u64>() {
|
||||
Ok(v) => UntaggedValue::int(v),
|
||||
Err(_) => UntaggedValue::string(x),
|
||||
};
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
member.into_value(&tag)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.into_value(&tag),
|
||||
)
|
||||
test_examples(Format {})
|
||||
}
|
||||
}
|
||||
|
45
crates/nu-cli/src/commands/from.rs
Normal file
45
crates/nu-cli/src/commands/from.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct From;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for From {
|
||||
fn name(&self) -> &str {
|
||||
"from"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse content (string or binary) as a table (input format based on subcommand, like csv, ini, json, toml)"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&From, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::From;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(From {})
|
||||
}
|
||||
}
|
@ -8,25 +8,34 @@ use std::str::FromStr;
|
||||
|
||||
pub struct FromBSON;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromBSON {
|
||||
fn name(&self) -> &str {
|
||||
"from-bson"
|
||||
"from bson"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from-bson")
|
||||
Signature::build("from bson")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse text as .bson and create table."
|
||||
"Parse binary as .bson and create table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_bson(args, registry)
|
||||
from_bson(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Convert bson data to a table",
|
||||
example: "open file.bin | from bson",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,27 +208,37 @@ pub fn from_bson_bytes_to_value(bytes: Vec<u8>, tag: impl Into<Tag>) -> Result<V
|
||||
convert_bson_value_to_nu_value(&Bson::Array(docs), tag)
|
||||
}
|
||||
|
||||
fn from_bson(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once(registry)?;
|
||||
async fn from_bson(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
let stream = async_stream! {
|
||||
let bytes = input.collect_binary(tag.clone()).await?;
|
||||
let bytes = input.collect_binary(tag.clone()).await?;
|
||||
|
||||
match from_bson_bytes_to_value(bytes.item, tag.clone()) {
|
||||
Ok(x) => yield ReturnSuccess::value(x),
|
||||
Err(_) => {
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
"Could not parse as BSON",
|
||||
"input cannot be parsed as BSON",
|
||||
tag.clone(),
|
||||
"value originates from here",
|
||||
bytes.tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
match from_bson_bytes_to_value(bytes.item, tag.clone()) {
|
||||
Ok(x) => Ok(OutputStream::one(ReturnSuccess::value(x))),
|
||||
Err(_) => Err(ShellError::labeled_error_with_secondary(
|
||||
"Could not parse as BSON",
|
||||
"input cannot be parsed as BSON",
|
||||
tag.clone(),
|
||||
"value originates from here",
|
||||
bytes.tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromBSON;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromBSON {})
|
||||
}
|
||||
}
|
||||
|
@ -12,13 +12,14 @@ pub struct FromCSVArgs {
|
||||
separator: Option<Value>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromCSV {
|
||||
fn name(&self) -> &str {
|
||||
"from-csv"
|
||||
"from csv"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from-csv")
|
||||
Signature::build("from csv")
|
||||
.named(
|
||||
"separator",
|
||||
SyntaxShape::String,
|
||||
@ -36,22 +37,49 @@ impl WholeStreamCommand for FromCSV {
|
||||
"Parse text as .csv and create table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, from_csv)?.run()
|
||||
from_csv(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Convert comma-separated data to a table",
|
||||
example: "open data.txt | from csv",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Convert comma-separated data to a table, ignoring headers",
|
||||
example: "open data.txt | from csv --headerless",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Convert semicolon-separated data to a table",
|
||||
example: "open data.txt | from csv --separator ';'",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn from_csv(
|
||||
FromCSVArgs {
|
||||
headerless,
|
||||
separator,
|
||||
}: FromCSVArgs,
|
||||
runnable_context: RunnableContext,
|
||||
async fn from_csv(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
|
||||
let (
|
||||
FromCSVArgs {
|
||||
headerless,
|
||||
separator,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
let sep = match separator {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(s)),
|
||||
@ -75,5 +103,17 @@ fn from_csv(
|
||||
_ => ',',
|
||||
};
|
||||
|
||||
from_delimited_data(headerless, sep, "CSV", runnable_context)
|
||||
from_delimited_data(headerless, sep, "CSV", input, name).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromCSV;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromCSV {})
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::prelude::*;
|
||||
use csv::{ErrorKind, ReaderBuilder};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
|
||||
|
||||
fn from_delimited_string_to_value(
|
||||
s: String,
|
||||
@ -41,44 +41,40 @@ fn from_delimited_string_to_value(
|
||||
Ok(UntaggedValue::Table(rows).into_value(&tag))
|
||||
}
|
||||
|
||||
pub fn from_delimited_data(
|
||||
pub async fn from_delimited_data(
|
||||
headerless: bool,
|
||||
sep: char,
|
||||
format_name: &'static str,
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
input: InputStream,
|
||||
name: Tag,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = name;
|
||||
let concat_string = input.collect_string(name_tag.clone()).await?;
|
||||
|
||||
let stream = async_stream! {
|
||||
let concat_string = input.collect_string(name_tag.clone()).await?;
|
||||
match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) {
|
||||
Ok(x) => match x {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => Ok(futures::stream::iter(list).to_output_stream()),
|
||||
x => Ok(OutputStream::one(x)),
|
||||
},
|
||||
Err(err) => {
|
||||
let line_one = match pretty_csv_error(err) {
|
||||
Some(pretty) => format!("Could not parse as {} ({})", format_name, pretty),
|
||||
None => format!("Could not parse as {}", format_name),
|
||||
};
|
||||
let line_two = format!("input cannot be parsed as {}", format_name);
|
||||
|
||||
match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) {
|
||||
Ok(x) => match x {
|
||||
Value { value: UntaggedValue::Table(list), .. } => {
|
||||
for l in list {
|
||||
yield ReturnSuccess::value(l);
|
||||
}
|
||||
}
|
||||
x => yield ReturnSuccess::value(x),
|
||||
},
|
||||
Err(err) => {
|
||||
let line_one = match pretty_csv_error(err) {
|
||||
Some(pretty) => format!("Could not parse as {} ({})", format_name,pretty),
|
||||
None => format!("Could not parse as {}", format_name),
|
||||
};
|
||||
let line_two = format!("input cannot be parsed as {}", format_name);
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
line_one,
|
||||
line_two,
|
||||
name_tag.clone(),
|
||||
"value originates from here",
|
||||
concat_string.tag,
|
||||
))
|
||||
} ,
|
||||
Err(ShellError::labeled_error_with_secondary(
|
||||
line_one,
|
||||
line_two,
|
||||
name_tag.clone(),
|
||||
"value originates from here",
|
||||
concat_string.tag,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
fn pretty_csv_error(err: csv::Error) -> Option<String> {
|
||||
|
140
crates/nu-cli/src/commands/from_eml.rs
Normal file
140
crates/nu-cli/src/commands/from_eml.rs
Normal file
@ -0,0 +1,140 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use ::eml_parser::eml::*;
|
||||
use ::eml_parser::EmlParser;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct FromEML;
|
||||
|
||||
const DEFAULT_BODY_PREVIEW: usize = 50;
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct FromEMLArgs {
|
||||
#[serde(rename(deserialize = "preview-body"))]
|
||||
preview_body: Option<Tagged<usize>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromEML {
|
||||
fn name(&self) -> &str {
|
||||
"from eml"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from eml").named(
|
||||
"preview-body",
|
||||
SyntaxShape::Int,
|
||||
"How many bytes of the body to preview",
|
||||
Some('b'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse text as .eml and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_eml(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
fn emailaddress_to_value(tag: &Tag, email_address: &EmailAddress) -> TaggedDictBuilder {
|
||||
let mut dict = TaggedDictBuilder::with_capacity(tag, 2);
|
||||
let (n, a) = match email_address {
|
||||
EmailAddress::AddressOnly { address } => {
|
||||
(UntaggedValue::nothing(), UntaggedValue::string(address))
|
||||
}
|
||||
EmailAddress::NameAndEmailAddress { name, address } => {
|
||||
(UntaggedValue::string(name), UntaggedValue::string(address))
|
||||
}
|
||||
};
|
||||
|
||||
dict.insert_untagged("Name", n);
|
||||
dict.insert_untagged("Address", a);
|
||||
|
||||
dict
|
||||
}
|
||||
|
||||
fn headerfieldvalue_to_value(tag: &Tag, value: &HeaderFieldValue) -> UntaggedValue {
|
||||
use HeaderFieldValue::*;
|
||||
|
||||
match value {
|
||||
SingleEmailAddress(address) => emailaddress_to_value(tag, address).into_untagged_value(),
|
||||
MultipleEmailAddresses(addresses) => UntaggedValue::Table(
|
||||
addresses
|
||||
.iter()
|
||||
.map(|a| emailaddress_to_value(tag, a).into_value())
|
||||
.collect(),
|
||||
),
|
||||
Unstructured(s) => UntaggedValue::string(s),
|
||||
Empty => UntaggedValue::nothing(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_eml(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
let (eml_args, input): (FromEMLArgs, _) = args.process(®istry).await?;
|
||||
let value = input.collect_string(tag.clone()).await?;
|
||||
|
||||
let body_preview = eml_args
|
||||
.preview_body
|
||||
.map(|b| b.item)
|
||||
.unwrap_or(DEFAULT_BODY_PREVIEW);
|
||||
|
||||
let eml = EmlParser::from_string(value.item)
|
||||
.with_body_preview(body_preview)
|
||||
.parse()
|
||||
.map_err(|_| {
|
||||
ShellError::labeled_error(
|
||||
"Could not parse .eml file",
|
||||
"could not parse .eml file",
|
||||
&tag,
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut dict = TaggedDictBuilder::new(&tag);
|
||||
|
||||
if let Some(subj) = eml.subject {
|
||||
dict.insert_untagged("Subject", UntaggedValue::string(subj));
|
||||
}
|
||||
|
||||
if let Some(from) = eml.from {
|
||||
dict.insert_untagged("From", headerfieldvalue_to_value(&tag, &from));
|
||||
}
|
||||
|
||||
if let Some(to) = eml.to {
|
||||
dict.insert_untagged("To", headerfieldvalue_to_value(&tag, &to));
|
||||
}
|
||||
|
||||
for HeaderField { name, value } in eml.headers.iter() {
|
||||
dict.insert_untagged(name, headerfieldvalue_to_value(&tag, &value));
|
||||
}
|
||||
|
||||
if let Some(body) = eml.body {
|
||||
dict.insert_untagged("Body", UntaggedValue::string(body));
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(dict.into_value())))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromEML;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromEML {})
|
||||
}
|
||||
}
|
@ -9,52 +9,59 @@ use std::io::BufReader;
|
||||
|
||||
pub struct FromIcs;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromIcs {
|
||||
fn name(&self) -> &str {
|
||||
"from-ics"
|
||||
"from ics"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from-ics")
|
||||
Signature::build("from ics")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse text as .ics and create table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_ics(args, registry)
|
||||
from_ics(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
fn from_ics(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once(registry)?;
|
||||
async fn from_ics(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
let stream = async_stream! {
|
||||
let input_string = input.collect_string(tag.clone()).await?.item;
|
||||
let input_bytes = input_string.as_bytes();
|
||||
let buf_reader = BufReader::new(input_bytes);
|
||||
let parser = ical::IcalParser::new(buf_reader);
|
||||
let input_string = input.collect_string(tag.clone()).await?.item;
|
||||
let input_bytes = input_string.as_bytes();
|
||||
let buf_reader = BufReader::new(input_bytes);
|
||||
let parser = ical::IcalParser::new(buf_reader);
|
||||
|
||||
for calendar in parser {
|
||||
match calendar {
|
||||
Ok(c) => yield ReturnSuccess::value(calendar_to_value(c, tag.clone())),
|
||||
Err(_) => yield Err(ShellError::labeled_error(
|
||||
"Could not parse as .ics",
|
||||
"input cannot be parsed as .ics",
|
||||
tag.clone()
|
||||
)),
|
||||
}
|
||||
// TODO: it should be possible to make this a stream, but the some of the lifetime requirements make this tricky.
|
||||
// Pre-computing for now
|
||||
let mut output = vec![];
|
||||
|
||||
for calendar in parser {
|
||||
match calendar {
|
||||
Ok(c) => output.push(ReturnSuccess::value(calendar_to_value(c, tag.clone()))),
|
||||
Err(_) => output.push(Err(ShellError::labeled_error(
|
||||
"Could not parse as .ics",
|
||||
"input cannot be parsed as .ics",
|
||||
tag.clone(),
|
||||
))),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(futures::stream::iter(output).to_output_stream())
|
||||
}
|
||||
|
||||
fn calendar_to_value(calendar: IcalCalendar, tag: Tag) -> Value {
|
||||
@ -238,3 +245,15 @@ fn params_to_value(params: Vec<(String, Vec<String>)>, tag: Tag) -> Value {
|
||||
|
||||
row.into_value()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromIcs;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromIcs {})
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,31 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct FromINI;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromINI {
|
||||
fn name(&self) -> &str {
|
||||
"from-ini"
|
||||
"from ini"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from-ini")
|
||||
Signature::build("from ini")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse text as .ini and create table"
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_ini(args, registry)
|
||||
from_ini(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,34 +64,42 @@ pub fn from_ini_string_to_value(
|
||||
Ok(convert_ini_top_to_nu_value(&v, tag))
|
||||
}
|
||||
|
||||
fn from_ini(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once(registry)?;
|
||||
async fn from_ini(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
let concat_string = input.collect_string(tag.clone()).await?;
|
||||
|
||||
let stream = async_stream! {
|
||||
let concat_string = input.collect_string(tag.clone()).await?;
|
||||
|
||||
match from_ini_string_to_value(concat_string.item, tag.clone()) {
|
||||
Ok(x) => match x {
|
||||
Value { value: UntaggedValue::Table(list), .. } => {
|
||||
for l in list {
|
||||
yield ReturnSuccess::value(l);
|
||||
}
|
||||
}
|
||||
x => yield ReturnSuccess::value(x),
|
||||
},
|
||||
Err(_) => {
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
"Could not parse as INI",
|
||||
"input cannot be parsed as INI",
|
||||
&tag,
|
||||
"value originates from here",
|
||||
concat_string.tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
match from_ini_string_to_value(concat_string.item, tag.clone()) {
|
||||
Ok(x) => match x {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => Ok(futures::stream::iter(list).to_output_stream()),
|
||||
x => Ok(OutputStream::one(x)),
|
||||
},
|
||||
Err(_) => Err(ShellError::labeled_error_with_secondary(
|
||||
"Could not parse as INI",
|
||||
"input cannot be parsed as INI",
|
||||
&tag,
|
||||
"value originates from here",
|
||||
concat_string.tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromINI;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromINI {})
|
||||
}
|
||||
}
|
||||
|
@ -10,13 +10,14 @@ pub struct FromJSONArgs {
|
||||
objects: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromJSON {
|
||||
fn name(&self) -> &str {
|
||||
"from-json"
|
||||
"from json"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from-json").switch(
|
||||
Signature::build("from json").switch(
|
||||
"objects",
|
||||
"treat each line as a separate value",
|
||||
Some('o'),
|
||||
@ -27,12 +28,12 @@ impl WholeStreamCommand for FromJSON {
|
||||
"Parse text as .json and create table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, from_json)?.run()
|
||||
from_json(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,64 +71,83 @@ pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> serde_hjson:
|
||||
Ok(convert_json_value_to_nu_value(&v, tag))
|
||||
}
|
||||
|
||||
fn from_json(
|
||||
FromJSONArgs { objects }: FromJSONArgs,
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
async fn from_json(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = name;
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let concat_string = input.collect_string(name_tag.clone()).await?;
|
||||
let (FromJSONArgs { objects }, input) = args.process(®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)]
|
||||
mod tests {
|
||||
use super::FromJSON;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromJSON {})
|
||||
}
|
||||
}
|
||||
|
@ -13,13 +13,14 @@ pub struct FromODSArgs {
|
||||
headerless: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromODS {
|
||||
fn name(&self) -> &str {
|
||||
"from-ods"
|
||||
"from ods"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from-ods").switch(
|
||||
Signature::build("from ods").switch(
|
||||
"headerless",
|
||||
"don't treat the first row as column names",
|
||||
None,
|
||||
@ -30,69 +31,81 @@ impl WholeStreamCommand for FromODS {
|
||||
"Parse OpenDocument Spreadsheet(.ods) data and create table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, from_ods)?.run()
|
||||
from_ods(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
fn from_ods(
|
||||
FromODSArgs {
|
||||
headerless: _headerless,
|
||||
}: FromODSArgs,
|
||||
runnable_context: RunnableContext,
|
||||
async fn from_ods(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let input = runnable_context.input;
|
||||
let tag = runnable_context.name;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let bytes = input.collect_binary(tag.clone()).await?;
|
||||
let mut buf: Cursor<Vec<u8>> = Cursor::new(bytes.item);
|
||||
let mut ods = Ods::<_>::new(buf).map_err(|_| ShellError::labeled_error(
|
||||
"Could not load ods file",
|
||||
"could not load ods file",
|
||||
&tag))?;
|
||||
let (
|
||||
FromODSArgs {
|
||||
headerless: _headerless,
|
||||
},
|
||||
input,
|
||||
) = args.process(®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)]
|
||||
mod tests {
|
||||
use super::FromODS;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromODS {})
|
||||
}
|
||||
}
|
||||
|
@ -1,56 +1,58 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use rusqlite::{types::ValueRef, Connection, Row, NO_PARAMS};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct FromSQLite;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromSQLite {
|
||||
fn name(&self) -> &str {
|
||||
"from-sqlite"
|
||||
"from sqlite"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from-sqlite")
|
||||
Signature::build("from sqlite")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse binary data as sqlite .db and create table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_sqlite(args, registry)
|
||||
from_sqlite(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FromDB;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromDB {
|
||||
fn name(&self) -> &str {
|
||||
"from-db"
|
||||
"from db"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from-db")
|
||||
Signature::build("from db")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse binary data as db and create table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_sqlite(args, registry)
|
||||
from_sqlite(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,6 +65,7 @@ pub fn convert_sqlite_file_to_nu_value(
|
||||
let mut meta_out = Vec::new();
|
||||
let mut meta_stmt = conn.prepare("select name from sqlite_master where type='table'")?;
|
||||
let mut meta_rows = meta_stmt.query(NO_PARAMS)?;
|
||||
|
||||
while let Some(meta_row) = meta_rows.next()? {
|
||||
let table_name: String = meta_row.get(0)?;
|
||||
let mut meta_dict = TaggedDictBuilder::new(tag.clone());
|
||||
@ -132,34 +135,47 @@ pub fn from_sqlite_bytes_to_value(
|
||||
}
|
||||
}
|
||||
|
||||
fn from_sqlite(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once(registry)?;
|
||||
async fn from_sqlite(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
let stream = async_stream! {
|
||||
let bytes = input.collect_binary(tag.clone()).await?;
|
||||
match from_sqlite_bytes_to_value(bytes.item, tag.clone()) {
|
||||
Ok(x) => match x {
|
||||
Value { value: UntaggedValue::Table(list), .. } => {
|
||||
for l in list {
|
||||
yield ReturnSuccess::value(l);
|
||||
}
|
||||
}
|
||||
_ => yield ReturnSuccess::value(x),
|
||||
}
|
||||
Err(err) => {
|
||||
println!("{:?}", err);
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
"Could not parse as SQLite",
|
||||
"input cannot be parsed as SQLite",
|
||||
&tag,
|
||||
"value originates from here",
|
||||
bytes.tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
let bytes = input.collect_binary(tag.clone()).await?;
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
match from_sqlite_bytes_to_value(bytes.item, tag.clone()) {
|
||||
Ok(x) => match x {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => Ok(futures::stream::iter(list).to_output_stream()),
|
||||
_ => Ok(OutputStream::one(x)),
|
||||
},
|
||||
Err(err) => {
|
||||
println!("{:?}", err);
|
||||
|
||||
Err(ShellError::labeled_error_with_secondary(
|
||||
"Could not parse as SQLite",
|
||||
"input cannot be parsed as SQLite",
|
||||
&tag,
|
||||
"value originates from here",
|
||||
bytes.tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromSQLite;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromSQLite {})
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,10 @@ pub struct FromSSVArgs {
|
||||
minimum_spaces: Option<Tagged<usize>>,
|
||||
}
|
||||
|
||||
const STRING_REPRESENTATION: &str = "from-ssv";
|
||||
const STRING_REPRESENTATION: &str = "from ssv";
|
||||
const DEFAULT_MINIMUM_SPACES: usize = 2;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromSSV {
|
||||
fn name(&self) -> &str {
|
||||
STRING_REPRESENTATION
|
||||
@ -45,12 +46,12 @@ impl WholeStreamCommand for FromSSV {
|
||||
"Parse text as space-separated values and create a table. The default minimum number of spaces counted as a separator is 2."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, from_ssv)?.run()
|
||||
from_ssv(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,41 +251,53 @@ fn from_ssv_string_to_value(
|
||||
Some(UntaggedValue::Table(rows).into_value(&tag))
|
||||
}
|
||||
|
||||
fn from_ssv(
|
||||
FromSSVArgs {
|
||||
headerless,
|
||||
aligned_columns,
|
||||
minimum_spaces,
|
||||
}: FromSSVArgs,
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
async fn from_ssv(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let concat_string = input.collect_string(name.clone()).await?;
|
||||
let split_at = match minimum_spaces {
|
||||
Some(number) => number.item,
|
||||
None => DEFAULT_MINIMUM_SPACES
|
||||
};
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
let (
|
||||
FromSSVArgs {
|
||||
headerless,
|
||||
aligned_columns,
|
||||
minimum_spaces,
|
||||
},
|
||||
input,
|
||||
) = args.process(®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)]
|
||||
@ -489,4 +502,12 @@ mod tests {
|
||||
assert_eq!(aligned_columns_headerless, separator_headerless);
|
||||
assert_eq!(aligned_columns_with_headers, separator_with_headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use super::FromSSV;
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromSSV {})
|
||||
}
|
||||
}
|
||||
|
@ -5,25 +5,26 @@ use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, Untagg
|
||||
|
||||
pub struct FromTOML;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromTOML {
|
||||
fn name(&self) -> &str {
|
||||
"from-toml"
|
||||
"from toml"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from-toml")
|
||||
Signature::build("from toml")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse text as .toml and create table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_toml(args, registry)
|
||||
from_toml(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,27 +64,28 @@ pub fn from_toml_string_to_value(s: String, tag: impl Into<Tag>) -> Result<Value
|
||||
Ok(convert_toml_value_to_nu_value(&v, tag))
|
||||
}
|
||||
|
||||
pub fn from_toml(
|
||||
pub async fn from_toml(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once(registry)?;
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
let stream = async_stream! {
|
||||
let concat_string = input.collect_string(tag.clone()).await?;
|
||||
let concat_string = input.collect_string(tag.clone()).await?;
|
||||
Ok(
|
||||
match from_toml_string_to_value(concat_string.item, tag.clone()) {
|
||||
Ok(x) => match x {
|
||||
Value { value: UntaggedValue::Table(list), .. } => {
|
||||
for l in list {
|
||||
yield ReturnSuccess::value(l);
|
||||
}
|
||||
}
|
||||
x => yield ReturnSuccess::value(x),
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => futures::stream::iter(list.into_iter().map(ReturnSuccess::value))
|
||||
.to_output_stream(),
|
||||
x => OutputStream::one(ReturnSuccess::value(x)),
|
||||
},
|
||||
Err(_) => {
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
return Err(ShellError::labeled_error_with_secondary(
|
||||
"Could not parse as TOML",
|
||||
"input cannot be parsed as TOML",
|
||||
&tag,
|
||||
@ -91,8 +93,18 @@ pub fn from_toml(
|
||||
concat_string.tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromTOML;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromTOML {})
|
||||
}
|
||||
}
|
||||
|
@ -11,13 +11,14 @@ pub struct FromTSVArgs {
|
||||
headerless: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromTSV {
|
||||
fn name(&self) -> &str {
|
||||
"from-tsv"
|
||||
"from tsv"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from-tsv").switch(
|
||||
Signature::build("from tsv").switch(
|
||||
"headerless",
|
||||
"don't treat the first row as column names",
|
||||
None,
|
||||
@ -28,18 +29,34 @@ impl WholeStreamCommand for FromTSV {
|
||||
"Parse text as .tsv and create table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, from_tsv)?.run()
|
||||
from_tsv(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
fn from_tsv(
|
||||
FromTSVArgs { headerless }: FromTSVArgs,
|
||||
runnable_context: RunnableContext,
|
||||
async fn from_tsv(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_delimited_data(headerless, '\t', "TSV", runnable_context)
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (FromTSVArgs { headerless }, input) = args.process(®istry).await?;
|
||||
|
||||
from_delimited_data(headerless, '\t', "TSV", input, name).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromTSV;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromTSV {})
|
||||
}
|
||||
}
|
||||
|
@ -5,59 +5,70 @@ use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
|
||||
|
||||
pub struct FromURL;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromURL {
|
||||
fn name(&self) -> &str {
|
||||
"from-url"
|
||||
"from url"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from-url")
|
||||
Signature::build("from url")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse url-encoded string as a table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_url(args, registry)
|
||||
from_url(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
fn from_url(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once(registry)?;
|
||||
async fn from_url(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
let stream = async_stream! {
|
||||
let concat_string = input.collect_string(tag.clone()).await?;
|
||||
let concat_string = input.collect_string(tag.clone()).await?;
|
||||
|
||||
let result = serde_urlencoded::from_str::<Vec<(String, String)>>(&concat_string.item);
|
||||
let result = serde_urlencoded::from_str::<Vec<(String, String)>>(&concat_string.item);
|
||||
|
||||
match result {
|
||||
Ok(result) => {
|
||||
let mut row = TaggedDictBuilder::new(tag);
|
||||
match result {
|
||||
Ok(result) => {
|
||||
let mut row = TaggedDictBuilder::new(tag);
|
||||
|
||||
for (k,v) in result {
|
||||
row.insert_untagged(k, UntaggedValue::string(v));
|
||||
}
|
||||
|
||||
yield ReturnSuccess::value(row.into_value());
|
||||
}
|
||||
_ => {
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
"String not compatible with url-encoding",
|
||||
"input not url-encoded",
|
||||
tag,
|
||||
"value originates from here",
|
||||
concat_string.tag,
|
||||
));
|
||||
for (k, v) in result {
|
||||
row.insert_untagged(k, UntaggedValue::string(v));
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(row.into_value())))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
_ => Err(ShellError::labeled_error_with_secondary(
|
||||
"String not compatible with url-encoding",
|
||||
"input not url-encoded",
|
||||
tag,
|
||||
"value originates from here",
|
||||
concat_string.tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromURL;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromURL {})
|
||||
}
|
||||
}
|
||||
|
@ -9,52 +9,61 @@ use std::io::BufReader;
|
||||
|
||||
pub struct FromVcf;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromVcf {
|
||||
fn name(&self) -> &str {
|
||||
"from-vcf"
|
||||
"from vcf"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from-vcf")
|
||||
Signature::build("from vcf")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse text as .vcf and create table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_vcf(args, registry)
|
||||
from_vcf(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
fn from_vcf(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once(registry)?;
|
||||
async fn from_vcf(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
let stream = async_stream! {
|
||||
let input_string = input.collect_string(tag.clone()).await?.item;
|
||||
let input_bytes = input_string.as_bytes();
|
||||
let buf_reader = BufReader::new(input_bytes);
|
||||
let parser = ical::VcardParser::new(buf_reader);
|
||||
let input_string = input.collect_string(tag.clone()).await?.item;
|
||||
let input_bytes = input_string.as_bytes();
|
||||
let buf_reader = BufReader::new(input_bytes);
|
||||
let parser = ical::VcardParser::new(buf_reader);
|
||||
|
||||
for contact in parser {
|
||||
match contact {
|
||||
Ok(c) => yield ReturnSuccess::value(contact_to_value(c, tag.clone())),
|
||||
Err(_) => yield Err(ShellError::labeled_error(
|
||||
let mut values_vec_deque = VecDeque::new();
|
||||
|
||||
for contact in parser {
|
||||
match contact {
|
||||
Ok(c) => {
|
||||
values_vec_deque.push_back(ReturnSuccess::value(contact_to_value(c, tag.clone())))
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Could not parse as .vcf",
|
||||
"input cannot be parsed as .vcf",
|
||||
tag.clone()
|
||||
)),
|
||||
tag.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(futures::stream::iter(values_vec_deque).to_output_stream())
|
||||
}
|
||||
|
||||
fn contact_to_value(contact: VcardContact, tag: Tag) -> Value {
|
||||
@ -100,3 +109,15 @@ fn params_to_value(params: Vec<(String, Vec<String>)>, tag: Tag) -> Value {
|
||||
|
||||
row.into_value()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromVcf;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromVcf {})
|
||||
}
|
||||
}
|
||||
|
@ -13,13 +13,14 @@ pub struct FromXLSXArgs {
|
||||
headerless: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromXLSX {
|
||||
fn name(&self) -> &str {
|
||||
"from-xlsx"
|
||||
"from xlsx"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from-xlsx").switch(
|
||||
Signature::build("from xlsx").switch(
|
||||
"headerless",
|
||||
"don't treat the first row as column names",
|
||||
None,
|
||||
@ -30,70 +31,81 @@ impl WholeStreamCommand for FromXLSX {
|
||||
"Parse binary Excel(.xlsx) data and create table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, from_xlsx)?.run()
|
||||
from_xlsx(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
fn from_xlsx(
|
||||
FromXLSXArgs {
|
||||
headerless: _headerless,
|
||||
}: FromXLSXArgs,
|
||||
runnable_context: RunnableContext,
|
||||
async fn from_xlsx(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let input = runnable_context.input;
|
||||
let tag = runnable_context.name;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
let (
|
||||
FromXLSXArgs {
|
||||
headerless: _headerless,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
let value = input.collect_binary(tag.clone()).await?;
|
||||
|
||||
let stream = async_stream! {
|
||||
let value = input.collect_binary(tag.clone()).await?;
|
||||
let buf: Cursor<Vec<u8>> = Cursor::new(value.item);
|
||||
let mut xls = Xlsx::<_>::new(buf).map_err(|_| {
|
||||
ShellError::labeled_error("Could not load xlsx file", "could not load xlsx file", &tag)
|
||||
})?;
|
||||
|
||||
let mut buf: Cursor<Vec<u8>> = Cursor::new(value.item);
|
||||
let mut xls = Xlsx::<_>::new(buf).map_err(|_| {
|
||||
ShellError::labeled_error("Could not load xlsx file", "could not load xlsx file", &tag)
|
||||
})?;
|
||||
let mut dict = TaggedDictBuilder::new(&tag);
|
||||
|
||||
let mut dict = TaggedDictBuilder::new(&tag);
|
||||
let sheet_names = xls.sheet_names().to_owned();
|
||||
|
||||
let sheet_names = xls.sheet_names().to_owned();
|
||||
for sheet_name in &sheet_names {
|
||||
let mut sheet_output = TaggedListBuilder::new(&tag);
|
||||
|
||||
for sheet_name in &sheet_names {
|
||||
let mut sheet_output = TaggedListBuilder::new(&tag);
|
||||
if let Some(Ok(current_sheet)) = xls.worksheet_range(sheet_name) {
|
||||
for row in current_sheet.rows() {
|
||||
let mut row_output = TaggedDictBuilder::new(&tag);
|
||||
for (i, cell) in row.iter().enumerate() {
|
||||
let value = match cell {
|
||||
DataType::Empty => UntaggedValue::nothing(),
|
||||
DataType::String(s) => UntaggedValue::string(s),
|
||||
DataType::Float(f) => UntaggedValue::decimal(*f),
|
||||
DataType::Int(i) => UntaggedValue::int(*i),
|
||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||
_ => UntaggedValue::nothing(),
|
||||
};
|
||||
|
||||
if let Some(Ok(current_sheet)) = xls.worksheet_range(sheet_name) {
|
||||
for row in current_sheet.rows() {
|
||||
let mut row_output = TaggedDictBuilder::new(&tag);
|
||||
for (i, cell) in row.iter().enumerate() {
|
||||
let value = match cell {
|
||||
DataType::Empty => UntaggedValue::nothing(),
|
||||
DataType::String(s) => UntaggedValue::string(s),
|
||||
DataType::Float(f) => UntaggedValue::decimal(*f),
|
||||
DataType::Int(i) => UntaggedValue::int(*i),
|
||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||
_ => UntaggedValue::nothing(),
|
||||
};
|
||||
|
||||
row_output.insert_untagged(&format!("Column{}", i), value);
|
||||
}
|
||||
|
||||
sheet_output.push_untagged(row_output.into_untagged_value());
|
||||
row_output.insert_untagged(&format!("Column{}", i), value);
|
||||
}
|
||||
|
||||
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value());
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Could not load sheet",
|
||||
"could not load sheet",
|
||||
&tag,
|
||||
));
|
||||
sheet_output.push_untagged(row_output.into_untagged_value());
|
||||
}
|
||||
|
||||
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value());
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Could not load sheet",
|
||||
"could not load sheet",
|
||||
&tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
yield ReturnSuccess::value(dict.into_value());
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(OutputStream::one(ReturnSuccess::value(dict.into_value())))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromXLSX;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromXLSX {})
|
||||
}
|
||||
}
|
||||
|
@ -5,25 +5,26 @@ use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, Untagg
|
||||
|
||||
pub struct FromXML;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromXML {
|
||||
fn name(&self) -> &str {
|
||||
"from-xml"
|
||||
"from xml"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from-xml")
|
||||
Signature::build("from xml")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse text as .xml and create table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_xml(args, registry)
|
||||
from_xml(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,36 +99,38 @@ pub fn from_xml_string_to_value(s: String, tag: impl Into<Tag>) -> Result<Value,
|
||||
Ok(from_document_to_value(&parsed, tag))
|
||||
}
|
||||
|
||||
fn from_xml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once(registry)?;
|
||||
async fn from_xml(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
let stream = async_stream! {
|
||||
let concat_string = input.collect_string(tag.clone()).await?;
|
||||
let concat_string = input.collect_string(tag.clone()).await?;
|
||||
|
||||
Ok(
|
||||
match from_xml_string_to_value(concat_string.item, tag.clone()) {
|
||||
Ok(x) => match x {
|
||||
Value { value: UntaggedValue::Table(list), .. } => {
|
||||
for l in list {
|
||||
yield ReturnSuccess::value(l);
|
||||
}
|
||||
}
|
||||
x => yield ReturnSuccess::value(x),
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => futures::stream::iter(list.into_iter().map(ReturnSuccess::value))
|
||||
.to_output_stream(),
|
||||
x => OutputStream::one(ReturnSuccess::value(x)),
|
||||
},
|
||||
Err(_) => {
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
return Err(ShellError::labeled_error_with_secondary(
|
||||
"Could not parse as XML",
|
||||
"input cannot be parsed as XML",
|
||||
&tag,
|
||||
"value originates from here",
|
||||
&concat_string.tag,
|
||||
))
|
||||
} ,
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -300,4 +303,12 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use super::FromXML;
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromXML {})
|
||||
}
|
||||
}
|
||||
|
@ -1,53 +1,55 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value};
|
||||
|
||||
pub struct FromYAML;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromYAML {
|
||||
fn name(&self) -> &str {
|
||||
"from-yaml"
|
||||
"from yaml"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from-yaml")
|
||||
Signature::build("from yaml")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse text as .yaml/.yml and create table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_yaml(args, registry)
|
||||
from_yaml(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FromYML;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromYML {
|
||||
fn name(&self) -> &str {
|
||||
"from-yml"
|
||||
"from yml"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from-yml")
|
||||
Signature::build("from yml")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse text as .yaml/.yml and create table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_yaml(args, registry)
|
||||
from_yaml(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,27 +59,19 @@ fn convert_yaml_value_to_nu_value(
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
let err_not_compatible_number = ShellError::labeled_error(
|
||||
"Expected a compatible number",
|
||||
"expected a compatible number",
|
||||
&tag,
|
||||
);
|
||||
Ok(match v {
|
||||
serde_yaml::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(tag),
|
||||
serde_yaml::Value::Number(n) if n.is_i64() => {
|
||||
UntaggedValue::int(n.as_i64().ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a compatible number",
|
||||
"expected a compatible number",
|
||||
&tag,
|
||||
)
|
||||
})?)
|
||||
.into_value(tag)
|
||||
UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag)
|
||||
}
|
||||
serde_yaml::Value::Number(n) if n.is_f64() => {
|
||||
UntaggedValue::decimal(n.as_f64().ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a compatible number",
|
||||
"expected a compatible number",
|
||||
&tag,
|
||||
)
|
||||
})?)
|
||||
.into_value(tag)
|
||||
UntaggedValue::decimal(n.as_f64().ok_or_else(|| err_not_compatible_number)?)
|
||||
.into_value(tag)
|
||||
}
|
||||
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
|
||||
serde_yaml::Value::Sequence(a) => {
|
||||
@ -91,11 +85,39 @@ fn convert_yaml_value_to_nu_value(
|
||||
let mut collected = TaggedDictBuilder::new(&tag);
|
||||
|
||||
for (k, v) in t.iter() {
|
||||
match k {
|
||||
serde_yaml::Value::String(k) => {
|
||||
// A ShellError that we re-use multiple times in the Mapping scenario
|
||||
let err_unexpected_map = ShellError::labeled_error(
|
||||
format!("Unexpected YAML:\nKey: {:?}\nValue: {:?}", k, v),
|
||||
"unexpected",
|
||||
tag.clone(),
|
||||
);
|
||||
match (k, v) {
|
||||
(serde_yaml::Value::String(k), _) => {
|
||||
collected.insert_value(k.clone(), convert_yaml_value_to_nu_value(v, &tag)?);
|
||||
}
|
||||
_ => unimplemented!("Unknown key type"),
|
||||
// Hard-code fix for cases where "v" is a string without quotations with double curly braces
|
||||
// e.g. k = value
|
||||
// value: {{ something }}
|
||||
// Strangely, serde_yaml returns
|
||||
// "value" -> Mapping(Mapping { map: {Mapping(Mapping { map: {String("something"): Null} }): Null} })
|
||||
(serde_yaml::Value::Mapping(m), serde_yaml::Value::Null) => {
|
||||
return m
|
||||
.iter()
|
||||
.take(1)
|
||||
.collect_vec()
|
||||
.first()
|
||||
.and_then(|e| match e {
|
||||
(serde_yaml::Value::String(s), serde_yaml::Value::Null) => Some(
|
||||
UntaggedValue::string("{{ ".to_owned() + &s + " }}")
|
||||
.into_value(tag),
|
||||
),
|
||||
_ => None,
|
||||
})
|
||||
.ok_or(err_unexpected_map);
|
||||
}
|
||||
(_, _) => {
|
||||
return Err(err_unexpected_map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,34 +140,79 @@ pub fn from_yaml_string_to_value(s: String, tag: impl Into<Tag>) -> Result<Value
|
||||
Ok(convert_yaml_value_to_nu_value(&v, tag)?)
|
||||
}
|
||||
|
||||
fn from_yaml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once(registry)?;
|
||||
async fn from_yaml(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
let stream = async_stream! {
|
||||
let concat_string = input.collect_string(tag.clone()).await?;
|
||||
let concat_string = input.collect_string(tag.clone()).await?;
|
||||
|
||||
match from_yaml_string_to_value(concat_string.item, tag.clone()) {
|
||||
Ok(x) => match x {
|
||||
Value { value: UntaggedValue::Table(list), .. } => {
|
||||
for l in list {
|
||||
yield ReturnSuccess::value(l);
|
||||
}
|
||||
}
|
||||
x => yield ReturnSuccess::value(x),
|
||||
match from_yaml_string_to_value(concat_string.item, tag.clone()) {
|
||||
Ok(x) => match x {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => Ok(futures::stream::iter(list).to_output_stream()),
|
||||
x => Ok(OutputStream::one(x)),
|
||||
},
|
||||
Err(_) => Err(ShellError::labeled_error_with_secondary(
|
||||
"Could not parse as YAML",
|
||||
"input cannot be parsed as YAML",
|
||||
&tag,
|
||||
"value originates from here",
|
||||
&concat_string.tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nu_plugin::row;
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromYAML {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_problematic_yaml() {
|
||||
struct TestCase {
|
||||
description: &'static str,
|
||||
input: &'static str,
|
||||
expected: Result<Value, ShellError>,
|
||||
}
|
||||
let tt: Vec<TestCase> = vec![
|
||||
TestCase {
|
||||
description: "Double Curly Braces With Quotes",
|
||||
input: r#"value: "{{ something }}""#,
|
||||
expected: Ok(row!["value".to_owned() => string("{{ something }}")]),
|
||||
},
|
||||
Err(_) => {
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
"Could not parse as YAML",
|
||||
"input cannot be parsed as YAML",
|
||||
&tag,
|
||||
"value originates from here",
|
||||
&concat_string.tag,
|
||||
))
|
||||
TestCase {
|
||||
description: "Double Curly Braces Without Quotes",
|
||||
input: r#"value: {{ something }}"#,
|
||||
expected: Ok(row!["value".to_owned() => string("{{ something }}")]),
|
||||
},
|
||||
];
|
||||
for tc in tt.into_iter() {
|
||||
let actual = from_yaml_string_to_value(tc.input.to_owned(), Tag::default());
|
||||
if actual.is_err() {
|
||||
assert!(
|
||||
tc.expected.is_err(),
|
||||
"actual is Err for test:\nTest Description {}\nErr: {:?}",
|
||||
tc.description,
|
||||
actual
|
||||
);
|
||||
} else {
|
||||
assert_eq!(actual, tc.expected, "{}", tc.description);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ use indexmap::set::IndexSet;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, ReturnValue, Signature,
|
||||
SyntaxShape, UnspannedPathMember, UntaggedValue, Value,
|
||||
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape,
|
||||
UnspannedPathMember, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::span_for_spanned_list;
|
||||
use nu_value_ext::get_data_by_column_path;
|
||||
@ -17,6 +17,7 @@ pub struct GetArgs {
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Get {
|
||||
fn name(&self) -> &str {
|
||||
"get"
|
||||
@ -33,12 +34,27 @@ impl WholeStreamCommand for Get {
|
||||
"Open given cells as text."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, get)?.run()
|
||||
get(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Extract the name of files as a list",
|
||||
example: "ls | get name",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Extract the cpu list from the sys information",
|
||||
example: "sys | get cpu",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,30 +192,24 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
GetArgs { rest: mut fields }: GetArgs,
|
||||
RunnableContext { mut input, .. }: RunnableContext,
|
||||
pub async fn get(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (GetArgs { rest: mut fields }, mut input) = args.process(®istry).await?;
|
||||
if fields.is_empty() {
|
||||
let stream = async_stream! {
|
||||
let mut vec = input.drain_vec().await;
|
||||
let vec = input.drain_vec().await;
|
||||
|
||||
let descs = nu_protocol::merge_descriptors(&vec);
|
||||
for desc in descs {
|
||||
yield ReturnSuccess::value(desc);
|
||||
}
|
||||
};
|
||||
let descs = nu_protocol::merge_descriptors(&vec);
|
||||
|
||||
let stream: BoxStream<'static, ReturnValue> = stream.boxed();
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
|
||||
} else {
|
||||
let member = fields.remove(0);
|
||||
trace!("get {:?} {:?}", member, fields);
|
||||
let stream = input
|
||||
.map(move |item| {
|
||||
let mut result = VecDeque::new();
|
||||
|
||||
Ok(input
|
||||
.map(move |item| {
|
||||
let member = vec![member.clone()];
|
||||
|
||||
let column_paths = vec![&member, &fields]
|
||||
@ -207,6 +217,7 @@ pub fn get(
|
||||
.flatten()
|
||||
.collect::<Vec<&ColumnPath>>();
|
||||
|
||||
let mut output = vec![];
|
||||
for path in column_paths {
|
||||
let res = get_column_path(&path, &item);
|
||||
|
||||
@ -217,25 +228,36 @@ pub fn get(
|
||||
..
|
||||
} => {
|
||||
for item in rows {
|
||||
result.push_back(ReturnSuccess::value(item.clone()));
|
||||
output.push(ReturnSuccess::value(item.clone()));
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
} => {}
|
||||
other => result.push_back(ReturnSuccess::value(other.clone())),
|
||||
other => output.push(ReturnSuccess::value(other.clone())),
|
||||
},
|
||||
Err(reason) => result.push_back(ReturnSuccess::value(
|
||||
Err(reason) => output.push(ReturnSuccess::value(
|
||||
UntaggedValue::Error(reason).into_untagged_value(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
futures::stream::iter(result)
|
||||
futures::stream::iter(output)
|
||||
})
|
||||
.flatten();
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Get;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Get {})
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,26 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use indexmap::indexmap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use nu_value_ext::get_data_by_key;
|
||||
use nu_value_ext::as_string;
|
||||
|
||||
pub struct GroupBy;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GroupByArgs {
|
||||
column_name: Tagged<String>,
|
||||
column_name: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for GroupBy {
|
||||
fn name(&self) -> &str {
|
||||
"group-by"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("group-by").required(
|
||||
Signature::build("group-by").optional(
|
||||
"column_name",
|
||||
SyntaxShape::String,
|
||||
"the name of the column to group by",
|
||||
@ -29,93 +31,139 @@ impl WholeStreamCommand for GroupBy {
|
||||
"Creates a new table with the data from the table rows grouped by the column given."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, group_by)?.run()
|
||||
group_by(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Group items by type",
|
||||
example: r#"ls | group-by type"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Group items by their value",
|
||||
example: "echo [1 3 1 3 2 1 1] | group-by",
|
||||
result: Some(vec![UntaggedValue::row(indexmap! {
|
||||
"1".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(1).into(),
|
||||
]).into(),
|
||||
|
||||
"3".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
]).into(),
|
||||
|
||||
"2".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::int(2).into(),
|
||||
]).into(),
|
||||
})
|
||||
.into()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn group_by(
|
||||
GroupByArgs { column_name }: GroupByArgs,
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
enum Grouper {
|
||||
ByColumn(Option<Tagged<String>>),
|
||||
}
|
||||
|
||||
pub async fn group_by(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (GroupByArgs { column_name }, input) = args.process(®istry).await?;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
if values.is_empty() {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected table from pipeline",
|
||||
"requires a table input",
|
||||
column_name.span()
|
||||
))
|
||||
} else {
|
||||
match group(&column_name, values, name) {
|
||||
Ok(grouped) => yield ReturnSuccess::value(grouped),
|
||||
Err(err) => yield Err(err)
|
||||
}
|
||||
}
|
||||
};
|
||||
if values.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected table from pipeline",
|
||||
"requires a table input",
|
||||
name,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
let values = UntaggedValue::table(&values).into_value(&name);
|
||||
|
||||
match group(&column_name, &values, name) {
|
||||
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
|
||||
Err(reason) => Err(reason),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
|
||||
let possibilities = for_value.data_descriptors();
|
||||
|
||||
let mut possible_matches: Vec<_> = possibilities
|
||||
.iter()
|
||||
.map(|x| (natural::distance::levenshtein_distance(x, &tried), x))
|
||||
.collect();
|
||||
|
||||
possible_matches.sort();
|
||||
|
||||
if !possible_matches.is_empty() {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", possible_matches[0].1),
|
||||
tried.tag(),
|
||||
)
|
||||
} else {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
"row does not contain this column",
|
||||
tried.tag(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn group(
|
||||
column_name: &Tagged<String>,
|
||||
values: Vec<Value>,
|
||||
column_name: &Option<Tagged<String>>,
|
||||
values: &Value,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
let name = tag.into();
|
||||
|
||||
let mut groups: indexmap::IndexMap<String, Vec<Value>> = indexmap::IndexMap::new();
|
||||
let grouper = if let Some(column_name) = column_name {
|
||||
Grouper::ByColumn(Some(column_name.clone()))
|
||||
} else {
|
||||
Grouper::ByColumn(None)
|
||||
};
|
||||
|
||||
for value in values {
|
||||
let group_key = get_data_by_key(&value, column_name.borrow_spanned());
|
||||
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)),
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(group_key) = group_key {
|
||||
let group_key = group_key.as_string()?.to_string();
|
||||
let group = groups.entry(group_key).or_insert(vec![]);
|
||||
group.push(value);
|
||||
} else {
|
||||
let possibilities = value.data_descriptors();
|
||||
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),
|
||||
});
|
||||
|
||||
let mut possible_matches: Vec<_> = possibilities
|
||||
.iter()
|
||||
.map(|x| (natural::distance::levenshtein_distance(x, column_name), x))
|
||||
.collect();
|
||||
|
||||
possible_matches.sort();
|
||||
|
||||
if !possible_matches.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", possible_matches[0].1),
|
||||
column_name.tag(),
|
||||
));
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
"row does not contain this column",
|
||||
column_name.tag(),
|
||||
));
|
||||
}
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
}
|
||||
}
|
||||
|
||||
let mut out = TaggedDictBuilder::new(&tag);
|
||||
|
||||
for (k, v) in groups.iter() {
|
||||
out.insert_untagged(k, UntaggedValue::table(v));
|
||||
}
|
||||
|
||||
Ok(out.into_value())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::commands::group_by::group;
|
||||
use super::group;
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
@ -133,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")},
|
||||
@ -167,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")}),
|
||||
@ -195,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")}),
|
||||
@ -220,4 +270,12 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use super::GroupBy;
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(GroupBy {})
|
||||
}
|
||||
}
|
||||
|
187
crates/nu-cli/src/commands/group_by_date.rs
Normal file
187
crates/nu-cli/src/commands/group_by_date.rs
Normal file
@ -0,0 +1,187 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct GroupByDate;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GroupByDateArgs {
|
||||
column_name: Option<Tagged<String>>,
|
||||
format: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for GroupByDate {
|
||||
fn name(&self) -> &str {
|
||||
"group-by date"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("group-by date")
|
||||
.optional(
|
||||
"column_name",
|
||||
SyntaxShape::String,
|
||||
"the name of the column to group by",
|
||||
)
|
||||
.named(
|
||||
"format",
|
||||
SyntaxShape::String,
|
||||
"Specify date and time formatting",
|
||||
Some('f'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates a new table with the data from the table rows grouped by the column given."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
group_by_date(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Group files by type",
|
||||
example: "ls | group-by date --format '%d/%m/%Y'",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
enum Grouper {
|
||||
ByDate(Option<Tagged<String>>),
|
||||
}
|
||||
|
||||
enum GroupByColumn {
|
||||
Name(Option<Tagged<String>>),
|
||||
}
|
||||
|
||||
pub async fn group_by_date(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (
|
||||
GroupByDateArgs {
|
||||
column_name,
|
||||
format,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
if values.is_empty() {
|
||||
Err(ShellError::labeled_error(
|
||||
"Expected table from pipeline",
|
||||
"requires a table input",
|
||||
name,
|
||||
))
|
||||
} else {
|
||||
let values = UntaggedValue::table(&values).into_value(&name);
|
||||
|
||||
let grouper_column = if let Some(column_name) = column_name {
|
||||
GroupByColumn::Name(Some(column_name))
|
||||
} else {
|
||||
GroupByColumn::Name(None)
|
||||
};
|
||||
|
||||
let grouper_date = if let Some(date_format) = format {
|
||||
Grouper::ByDate(Some(date_format))
|
||||
} else {
|
||||
Grouper::ByDate(None)
|
||||
};
|
||||
|
||||
match (grouper_date, grouper_column) {
|
||||
(Grouper::ByDate(None), GroupByColumn::Name(None)) => {
|
||||
let block = Box::new(move |row: &Value| row.format("%Y-%b-%d"));
|
||||
|
||||
match crate::utils::data::group(&values, &Some(block), &name) {
|
||||
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
(Grouper::ByDate(None), GroupByColumn::Name(Some(column_name))) => {
|
||||
let block = Box::new(move |row: &Value| {
|
||||
let group_key = match row.get_data_by_key(column_name.borrow_spanned()) {
|
||||
Some(group_key) => Ok(group_key),
|
||||
None => Err(suggestions(column_name.borrow_tagged(), &row)),
|
||||
};
|
||||
|
||||
group_key?.format("%Y-%b-%d")
|
||||
});
|
||||
|
||||
match crate::utils::data::group(&values, &Some(block), &name) {
|
||||
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
(Grouper::ByDate(Some(fmt)), GroupByColumn::Name(None)) => {
|
||||
let block = Box::new(move |row: &Value| row.format(&fmt));
|
||||
|
||||
match crate::utils::data::group(&values, &Some(block), &name) {
|
||||
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
(Grouper::ByDate(Some(fmt)), GroupByColumn::Name(Some(column_name))) => {
|
||||
let block = Box::new(move |row: &Value| {
|
||||
let group_key = match row.get_data_by_key(column_name.borrow_spanned()) {
|
||||
Some(group_key) => Ok(group_key),
|
||||
None => Err(suggestions(column_name.borrow_tagged(), &row)),
|
||||
};
|
||||
|
||||
group_key?.format(&fmt)
|
||||
});
|
||||
|
||||
match crate::utils::data::group(&values, &Some(block), &name) {
|
||||
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
|
||||
let possibilities = for_value.data_descriptors();
|
||||
|
||||
let mut possible_matches: Vec<_> = possibilities
|
||||
.iter()
|
||||
.map(|x| (natural::distance::levenshtein_distance(x, &tried), x))
|
||||
.collect();
|
||||
|
||||
possible_matches.sort();
|
||||
|
||||
if !possible_matches.is_empty() {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", possible_matches[0].1),
|
||||
tried.tag(),
|
||||
)
|
||||
} else {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
"row does not contain this column",
|
||||
tried.tag(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::GroupByDate;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(GroupByDate {})
|
||||
}
|
||||
}
|
@ -8,73 +8,107 @@ use nu_protocol::Dictionary;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct Headers;
|
||||
#[derive(Deserialize)]
|
||||
pub struct HeadersArgs {}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Headers {
|
||||
fn name(&self) -> &str {
|
||||
"headers"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("headers")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Use the first row of the table as column names"
|
||||
}
|
||||
fn run(
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, headers)?.run()
|
||||
headers(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Create headers for a raw string",
|
||||
example: r#"echo "a b c|1 2 3" | split row "|" | split column " " | headers"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn headers(
|
||||
HeadersArgs {}: HeadersArgs,
|
||||
RunnableContext { input, .. }: RunnableContext,
|
||||
pub async fn headers(
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
let input = args.input;
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
|
||||
if rows.len() < 1 {
|
||||
yield Err(ShellError::untagged_runtime_error("Couldn't find headers, was the input a properly formatted, non-empty table?"));
|
||||
}
|
||||
if rows.is_empty() {
|
||||
return Err(ShellError::untagged_runtime_error(
|
||||
"Couldn't find headers, was the input a properly formatted, non-empty table?",
|
||||
));
|
||||
}
|
||||
|
||||
//the headers are the first row in the table
|
||||
let headers: Vec<String> = match &rows[0].value {
|
||||
UntaggedValue::Row(d) => {
|
||||
Ok(d.entries.iter().map(|(k, v)| {
|
||||
//the headers are the first row in the table
|
||||
let headers: Vec<String> = match &rows[0].value {
|
||||
UntaggedValue::Row(d) => {
|
||||
Ok(d.entries
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
match v.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => { //If a cell that should contain a header name is empty, we name the column Column[index]
|
||||
Err(_) => {
|
||||
//If a cell that should contain a header name is empty, we name the column Column[index]
|
||||
match d.entries.get_full(k) {
|
||||
Some((index, _, _)) => format!("Column{}", index),
|
||||
None => "unknownColumn".to_string()
|
||||
None => "unknownColumn".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}).collect())
|
||||
}
|
||||
_ => Err(ShellError::unexpected_eof("Could not get headers, is the table empty?", rows[0].tag.span))
|
||||
}?;
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
_ => Err(ShellError::unexpected_eof(
|
||||
"Could not get headers, is the table empty?",
|
||||
rows[0].tag.span,
|
||||
)),
|
||||
}?;
|
||||
|
||||
//Each row is a dictionary with the headers as keys
|
||||
for r in rows.iter().skip(1) {
|
||||
Ok(
|
||||
futures::stream::iter(rows.into_iter().skip(1).map(move |r| {
|
||||
//Each row is a dictionary with the headers as keys
|
||||
match &r.value {
|
||||
UntaggedValue::Row(d) => {
|
||||
let mut i = 0;
|
||||
let mut entries = IndexMap::new();
|
||||
for (_, v) in d.entries.iter() {
|
||||
for (i, (_, v)) in d.entries.iter().enumerate() {
|
||||
entries.insert(headers[i].clone(), v.clone());
|
||||
i += 1;
|
||||
}
|
||||
yield Ok(ReturnSuccess::Value(UntaggedValue::Row(Dictionary{entries}).into_value(r.tag.clone())))
|
||||
Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::Row(Dictionary { entries }).into_value(r.tag.clone()),
|
||||
))
|
||||
}
|
||||
_ => yield Err(ShellError::unexpected_eof("Couldn't iterate through rows, was the input a properly formatted table?", r.tag.span))
|
||||
_ => Err(ShellError::unexpected_eof(
|
||||
"Couldn't iterate through rows, was the input a properly formatted table?",
|
||||
r.tag.span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}))
|
||||
.to_output_stream(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Headers;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Headers {})
|
||||
}
|
||||
}
|
||||
|
@ -1,93 +1,128 @@
|
||||
use crate::commands::PerItemCommand;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::command_dict;
|
||||
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
CallInfo, NamedType, PositionalType, Primitive, ReturnSuccess, Signature, SyntaxShape,
|
||||
TaggedDictBuilder, UntaggedValue, Value,
|
||||
NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder,
|
||||
UntaggedValue,
|
||||
};
|
||||
use nu_source::SpannedItem;
|
||||
use nu_source::{SpannedItem, Tagged};
|
||||
use nu_value_ext::get_data_by_key;
|
||||
|
||||
pub struct Help;
|
||||
|
||||
impl PerItemCommand for Help {
|
||||
#[derive(Deserialize)]
|
||||
pub struct HelpArgs {
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Help {
|
||||
fn name(&self) -> &str {
|
||||
"help"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help").rest(SyntaxShape::Any, "the name of command(s) to get help on")
|
||||
Signature::build("help").rest(SyntaxShape::String, "the name of command to get help on")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Display help information about commands."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
_raw_args: &RawCommandArgs,
|
||||
_input: Value,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = &call_info.name_tag;
|
||||
help(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
match call_info.args.nth(0) {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(document)),
|
||||
tag,
|
||||
}) => {
|
||||
let mut help = VecDeque::new();
|
||||
if document == "commands" {
|
||||
let mut sorted_names = registry.names();
|
||||
sorted_names.sort();
|
||||
for cmd in sorted_names {
|
||||
let mut short_desc = TaggedDictBuilder::new(tag.clone());
|
||||
let value = command_dict(
|
||||
registry.get_command(&cmd).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
format!("Could not load {}", cmd),
|
||||
"could not load command",
|
||||
tag,
|
||||
)
|
||||
})?,
|
||||
tag.clone(),
|
||||
);
|
||||
async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (HelpArgs { rest }, ..) = args.process(®istry).await?;
|
||||
|
||||
short_desc.insert_untagged("name", cmd);
|
||||
short_desc.insert_untagged(
|
||||
"description",
|
||||
get_data_by_key(&value, "usage".spanned_unknown())
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a usage key",
|
||||
"expected a 'usage' key",
|
||||
&value.tag,
|
||||
)
|
||||
})?
|
||||
.as_string()?,
|
||||
);
|
||||
if !rest.is_empty() {
|
||||
if rest[0].item == "commands" {
|
||||
let mut sorted_names = registry.names();
|
||||
sorted_names.sort();
|
||||
|
||||
help.push_back(ReturnSuccess::value(short_desc.into_value()));
|
||||
Ok(
|
||||
futures::stream::iter(sorted_names.into_iter().filter_map(move |cmd| {
|
||||
// If it's a subcommand, don't list it during the commands list
|
||||
if cmd.contains(' ') {
|
||||
return None;
|
||||
}
|
||||
} else if let Some(command) = registry.get_command(document) {
|
||||
return Ok(
|
||||
get_help(&command.name(), &command.usage(), command.signature()).into(),
|
||||
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
||||
let document_tag = rest[0].tag.clone();
|
||||
let value = command_dict(
|
||||
match registry.get_command(&cmd).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
format!("Could not load {}", cmd),
|
||||
"could not load command",
|
||||
document_tag,
|
||||
)
|
||||
}) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Some(Err(err)),
|
||||
},
|
||||
name.clone(),
|
||||
);
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Can't find command (use 'help commands' for full list)",
|
||||
"can't find command",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
let help = futures::stream::iter(help);
|
||||
Ok(help.to_output_stream())
|
||||
|
||||
short_desc.insert_untagged("name", cmd);
|
||||
short_desc.insert_untagged(
|
||||
"description",
|
||||
match match get_data_by_key(&value, "usage".spanned_unknown()).ok_or_else(
|
||||
|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a usage key",
|
||||
"expected a 'usage' key",
|
||||
&value.tag,
|
||||
)
|
||||
},
|
||||
) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Some(Err(err)),
|
||||
}
|
||||
.as_string()
|
||||
{
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Some(Err(err)),
|
||||
},
|
||||
);
|
||||
|
||||
Some(ReturnSuccess::value(short_desc.into_value()))
|
||||
}))
|
||||
.to_output_stream(),
|
||||
)
|
||||
} else if rest.len() == 2 {
|
||||
// Check for a subcommand
|
||||
let command_name = format!("{} {}", rest[0].item, rest[1].item);
|
||||
if let Some(command) = registry.get_command(&command_name) {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(get_help(command.stream_command(), ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
} else {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
_ => {
|
||||
let msg = r#"Welcome to Nushell.
|
||||
} 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 {
|
||||
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
|
||||
@ -109,28 +144,29 @@ Get the processes on your system actively using CPU:
|
||||
|
||||
You can also learn more at https://www.nushell.sh/book/"#;
|
||||
|
||||
let output_stream = futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::string(msg).into_value(tag),
|
||||
)]);
|
||||
|
||||
Ok(output_stream.to_output_stream())
|
||||
}
|
||||
}
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(msg).into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_help(
|
||||
cmd_name: &str,
|
||||
cmd_usage: &str,
|
||||
cmd_sig: Signature,
|
||||
) -> impl Into<OutputStream> {
|
||||
let mut help = VecDeque::new();
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn get_help(cmd: &dyn WholeStreamCommand, registry: &CommandRegistry) -> String {
|
||||
let cmd_name = cmd.name();
|
||||
let signature = cmd.signature();
|
||||
let mut long_desc = String::new();
|
||||
|
||||
long_desc.push_str(&cmd_usage);
|
||||
long_desc.push_str(&cmd.usage());
|
||||
long_desc.push_str("\n");
|
||||
|
||||
let signature = cmd_sig;
|
||||
let mut subcommands = String::new();
|
||||
for name in registry.names() {
|
||||
if name.starts_with(&format!("{} ", cmd_name)) {
|
||||
let subcommand = registry.get_command(&name).expect("This shouldn't happen");
|
||||
|
||||
subcommands.push_str(&format!(" {} - {}\n", name, subcommand.usage()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut one_liner = String::new();
|
||||
one_liner.push_str(&signature.name);
|
||||
@ -151,16 +187,25 @@ pub(crate) fn get_help(
|
||||
one_liner.push_str(" ...args");
|
||||
}
|
||||
|
||||
if !subcommands.is_empty() {
|
||||
one_liner.push_str("<subcommand> ");
|
||||
}
|
||||
|
||||
if !signature.named.is_empty() {
|
||||
one_liner.push_str("{flags} ");
|
||||
}
|
||||
|
||||
long_desc.push_str(&format!("\nUsage:\n > {}\n", one_liner));
|
||||
|
||||
if !subcommands.is_empty() {
|
||||
long_desc.push_str("\nSubcommands:\n");
|
||||
long_desc.push_str(&subcommands);
|
||||
}
|
||||
|
||||
if !signature.positional.is_empty() || signature.rest_positional.is_some() {
|
||||
long_desc.push_str("\nparameters:\n");
|
||||
for positional in signature.positional {
|
||||
match positional.0 {
|
||||
long_desc.push_str("\nParameters:\n");
|
||||
for positional in &signature.positional {
|
||||
match &positional.0 {
|
||||
PositionalType::Mandatory(name, _m) => {
|
||||
long_desc.push_str(&format!(" <{}> {}\n", name, positional.1));
|
||||
}
|
||||
@ -170,79 +215,110 @@ pub(crate) fn get_help(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(rest_positional) = signature.rest_positional {
|
||||
if let Some(rest_positional) = &signature.rest_positional {
|
||||
long_desc.push_str(&format!(" ...args: {}\n", rest_positional.1));
|
||||
}
|
||||
}
|
||||
if !signature.named.is_empty() {
|
||||
long_desc.push_str("\nflags:\n");
|
||||
for (flag, ty) in signature.named {
|
||||
let msg = match ty.0 {
|
||||
NamedType::Switch(s) => {
|
||||
if let Some(c) = s {
|
||||
format!(
|
||||
" -{}, --{}{} {}\n",
|
||||
c,
|
||||
flag,
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
" --{}{} {}\n",
|
||||
flag,
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
}
|
||||
}
|
||||
NamedType::Mandatory(s, m) => {
|
||||
if let Some(c) = s {
|
||||
format!(
|
||||
" -{}, --{} <{}> (required parameter){} {}\n",
|
||||
c,
|
||||
flag,
|
||||
m.display(),
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
" --{} <{}> (required parameter){} {}\n",
|
||||
flag,
|
||||
m.display(),
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
}
|
||||
}
|
||||
NamedType::Optional(s, o) => {
|
||||
if let Some(c) = s {
|
||||
format!(
|
||||
" -{}, --{} <{}>{} {}\n",
|
||||
c,
|
||||
flag,
|
||||
o.display(),
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
" --{} <{}>{} {}\n",
|
||||
flag,
|
||||
o.display(),
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
long_desc.push_str(&msg);
|
||||
}
|
||||
long_desc.push_str(&get_flags_section(&signature))
|
||||
}
|
||||
|
||||
help.push_back(ReturnSuccess::value(
|
||||
UntaggedValue::string(long_desc).into_value(Tag::from((0, cmd_name.len(), None))),
|
||||
));
|
||||
help
|
||||
let palette = crate::shell::palette::DefaultPalette {};
|
||||
let examples = cmd.examples();
|
||||
if !examples.is_empty() {
|
||||
long_desc.push_str("\nExamples:");
|
||||
}
|
||||
for example in examples {
|
||||
long_desc.push_str("\n");
|
||||
long_desc.push_str(" ");
|
||||
long_desc.push_str(example.description);
|
||||
let colored_example =
|
||||
crate::shell::helper::Painter::paint_string(example.example, registry, &palette);
|
||||
long_desc.push_str(&format!("\n > {}\n", colored_example));
|
||||
}
|
||||
|
||||
long_desc.push_str("\n");
|
||||
|
||||
long_desc
|
||||
}
|
||||
|
||||
fn get_flags_section(signature: &Signature) -> String {
|
||||
let mut long_desc = String::new();
|
||||
long_desc.push_str("\nFlags:\n");
|
||||
for (flag, ty) in &signature.named {
|
||||
let msg = match ty.0 {
|
||||
NamedType::Switch(s) => {
|
||||
if let Some(c) = s {
|
||||
format!(
|
||||
" -{}, --{}{} {}\n",
|
||||
c,
|
||||
flag,
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
" --{}{} {}\n",
|
||||
flag,
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
}
|
||||
}
|
||||
NamedType::Mandatory(s, m) => {
|
||||
if let Some(c) = s {
|
||||
format!(
|
||||
" -{}, --{} <{}> (required parameter){} {}\n",
|
||||
c,
|
||||
flag,
|
||||
m.display(),
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
" --{} <{}> (required parameter){} {}\n",
|
||||
flag,
|
||||
m.display(),
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
}
|
||||
}
|
||||
NamedType::Optional(s, o) => {
|
||||
if let Some(c) = s {
|
||||
format!(
|
||||
" -{}, --{} <{}>{} {}\n",
|
||||
c,
|
||||
flag,
|
||||
o.display(),
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
" --{} <{}>{} {}\n",
|
||||
flag,
|
||||
o.display(),
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
long_desc.push_str(&msg);
|
||||
}
|
||||
long_desc
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Help;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Help {})
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ pub struct HistogramArgs {
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Histogram {
|
||||
fn name(&self) -> &str {
|
||||
"histogram"
|
||||
@ -39,73 +40,166 @@ impl WholeStreamCommand for Histogram {
|
||||
"Creates a new table with a histogram based on the column name passed in."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, histogram)?.run()
|
||||
histogram(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get a histogram for the types of files",
|
||||
example: "ls | histogram type",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Get a histogram for the types of files, with frequency column named count",
|
||||
example: "ls | histogram type count",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get a histogram for a list of numbers",
|
||||
example: "echo [1 2 3 1 1 1 2 2 1 1] | histogram",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn histogram(
|
||||
HistogramArgs { column_name, rest }: HistogramArgs,
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
pub async fn histogram(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
|
||||
let Tagged { item: group_by, .. } = column_name.clone();
|
||||
let (HistogramArgs { column_name, rest }, input) = args.process(®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 column_names_supplied: Vec<_> = rest.iter().map(|f| f.item.clone()).collect();
|
||||
|
||||
let frequency_column_name = if column_names_supplied.is_empty() {
|
||||
"frequency".to_string()
|
||||
} else {
|
||||
column_names_supplied[0].clone()
|
||||
};
|
||||
|
||||
let column = (*column_name).clone();
|
||||
|
||||
let count_column_name = "count".to_string();
|
||||
let count_shell_error = ShellError::labeled_error(
|
||||
"Unable to load group count",
|
||||
"unabled to load group count",
|
||||
&name,
|
||||
);
|
||||
let mut count_values: Vec<u64> = Vec::new();
|
||||
|
||||
for table_entry in reduced.table_entries() {
|
||||
match table_entry {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => {
|
||||
for i in list {
|
||||
if let Ok(count) = i.value.clone().into_value(&name).as_u64() {
|
||||
count_values.push(count);
|
||||
} else {
|
||||
return Err(count_shell_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(count_shell_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Value {
|
||||
value: UntaggedValue::Table(start),
|
||||
..
|
||||
} => {
|
||||
|
||||
let mut idx = 0;
|
||||
|
||||
let column_names_supplied: Vec<_> = rest.iter().map(|f| f.item.clone()).collect();
|
||||
|
||||
let frequency_column_name = if column_names_supplied.is_empty() {
|
||||
"frequency".to_string()
|
||||
} else {
|
||||
column_names_supplied[0].clone()
|
||||
};
|
||||
|
||||
let column = (*column_name).clone();
|
||||
|
||||
if let Value { value: UntaggedValue::Table(start), .. } = datasets.get(0).ok_or_else(|| ShellError::labeled_error("Unable to load dataset", "unabled to load dataset", &name))? {
|
||||
for percentage in start.iter() {
|
||||
|
||||
} = datasets.get(0).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Unable to load dataset",
|
||||
"unabled to load dataset",
|
||||
&name,
|
||||
)
|
||||
})? {
|
||||
let start = start.clone();
|
||||
Ok(
|
||||
futures::stream::iter(start.into_iter().map(move |percentage| {
|
||||
let mut fact = TaggedDictBuilder::new(&name);
|
||||
let value: Tagged<String> = group_labels.get(idx).ok_or_else(|| ShellError::labeled_error("Unable to load group labels", "unabled to load group labels", &name))?.clone();
|
||||
fact.insert_value(&column, UntaggedValue::string(value.item).into_value(value.tag));
|
||||
let value: Tagged<String> = group_labels
|
||||
.get(idx)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Unable to load group labels",
|
||||
"unabled to load group labels",
|
||||
&name,
|
||||
)
|
||||
})?
|
||||
.clone();
|
||||
fact.insert_value(
|
||||
&column,
|
||||
UntaggedValue::string(value.item).into_value(value.tag),
|
||||
);
|
||||
|
||||
if let Value { value: UntaggedValue::Primitive(Primitive::Int(ref num)), ref tag } = percentage.clone() {
|
||||
let string = std::iter::repeat("*").take(num.to_i32().ok_or_else(|| ShellError::labeled_error("Expected a number", "expected a number", tag))? as usize).collect::<String>();
|
||||
fact.insert_untagged(&frequency_column_name, UntaggedValue::string(string));
|
||||
fact.insert_untagged(
|
||||
&count_column_name,
|
||||
UntaggedValue::int(count_values[idx]),
|
||||
);
|
||||
|
||||
if let Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Int(ref num)),
|
||||
ref tag,
|
||||
} = percentage
|
||||
{
|
||||
let string = std::iter::repeat("*")
|
||||
.take(num.to_i32().ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a number",
|
||||
"expected a number",
|
||||
tag,
|
||||
)
|
||||
})? as usize)
|
||||
.collect::<String>();
|
||||
fact.insert_untagged(
|
||||
&frequency_column_name,
|
||||
UntaggedValue::string(string),
|
||||
);
|
||||
}
|
||||
|
||||
idx += 1;
|
||||
|
||||
yield ReturnSuccess::value(fact.into_value());
|
||||
}
|
||||
}
|
||||
ReturnSuccess::value(fact.into_value())
|
||||
}))
|
||||
.to_output_stream(),
|
||||
)
|
||||
} else {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
_ => Ok(OutputStream::empty()),
|
||||
}
|
||||
}
|
||||
|
||||
fn percentages(values: &Value, max: Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
@ -158,3 +252,15 @@ fn percentages(values: &Value, max: Value, tag: impl Into<Tag>) -> Result<Value,
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Histogram;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Histogram {})
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
use crate::cli::History as HistoryFile;
|
||||
use crate::commands::PerItemCommand;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CallInfo, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
|
||||
pub struct History;
|
||||
|
||||
impl PerItemCommand for History {
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for History {
|
||||
fn name(&self) -> &str {
|
||||
"history"
|
||||
}
|
||||
@ -21,29 +22,46 @@ impl PerItemCommand for History {
|
||||
"Display command history."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
_registry: &CommandRegistry,
|
||||
_raw_args: &RawCommandArgs,
|
||||
_input: Value,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = call_info.name_tag.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let history_path = HistoryFile::path();
|
||||
let file = File::open(history_path);
|
||||
if let Ok(file) = file {
|
||||
let reader = BufReader::new(file);
|
||||
for line in reader.lines() {
|
||||
if let Ok(line) = line {
|
||||
yield ReturnSuccess::value(UntaggedValue::string(line).into_value(tag.clone()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error("Could not open history", "history file could not be opened", tag.clone()));
|
||||
}
|
||||
};
|
||||
Ok(stream.to_output_stream())
|
||||
history(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag;
|
||||
let history_path = HistoryFile::path();
|
||||
let file = File::open(history_path);
|
||||
if let Ok(file) = file {
|
||||
let reader = BufReader::new(file);
|
||||
let output = reader.lines().filter_map(move |line| match line {
|
||||
Ok(line) => Some(ReturnSuccess::value(
|
||||
UntaggedValue::string(line).into_value(tag.clone()),
|
||||
)),
|
||||
Err(_) => None,
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(output).to_output_stream())
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Could not open history",
|
||||
"history file could not be opened",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::History;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(History {})
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,20 @@
|
||||
use crate::commands::PerItemCommand;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CallInfo, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
pub struct Insert;
|
||||
|
||||
impl PerItemCommand for Insert {
|
||||
#[derive(Deserialize)]
|
||||
pub struct InsertArgs {
|
||||
column: ColumnPath,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Insert {
|
||||
fn name(&self) -> &str {
|
||||
"insert"
|
||||
}
|
||||
@ -27,40 +34,50 @@ impl PerItemCommand for Insert {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Edit an existing column to have a new value."
|
||||
"Insert a new column with a given value."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
_registry: &CommandRegistry,
|
||||
_raw_args: &RawCommandArgs,
|
||||
value: Value,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let value_tag = value.tag();
|
||||
let field = call_info.args.expect_nth(0)?.as_column_path()?;
|
||||
let replacement = call_info.args.expect_nth(1)?.tagged_unknown();
|
||||
insert(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
let stream = match value {
|
||||
obj
|
||||
@
|
||||
async fn insert(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let (InsertArgs { column, value }, input) = args.process(®istry).await?;
|
||||
|
||||
Ok(input
|
||||
.map(move |row| match row {
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => match obj.insert_data_at_column_path(&field, replacement.item.clone()) {
|
||||
Ok(v) => futures::stream::iter(vec![Ok(ReturnSuccess::Value(v))]),
|
||||
Err(err) => return Err(err),
|
||||
} => match row.insert_data_at_column_path(&column, value.clone()) {
|
||||
Ok(v) => Ok(ReturnSuccess::Value(v)),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
value_tag,
|
||||
))
|
||||
}
|
||||
};
|
||||
Value { tag, .. } => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
)),
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Insert;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Insert {})
|
||||
}
|
||||
}
|
||||
|
211
crates/nu-cli/src/commands/is_empty.rs
Normal file
211
crates/nu-cli/src/commands/is_empty.rs
Normal file
@ -0,0 +1,211 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
enum IsEmptyFor {
|
||||
Value,
|
||||
RowWithFieldsAndFallback(Vec<Tagged<ColumnPath>>, Value),
|
||||
RowWithField(Tagged<ColumnPath>),
|
||||
RowWithFieldAndFallback(Box<Tagged<ColumnPath>>, Value),
|
||||
}
|
||||
|
||||
pub struct IsEmpty;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct IsEmptyArgs {
|
||||
rest: Vec<Value>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for IsEmpty {
|
||||
fn name(&self) -> &str {
|
||||
"empty?"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("empty?").rest(
|
||||
SyntaxShape::Any,
|
||||
"the names of the columns to check emptiness followed by the replacement value.",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Checks emptiness. The last value is the replacement value for any empty column(s) given to check against the table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
is_empty(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn is_empty(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (IsEmptyArgs { rest }, input) = args.process(®istry).await?;
|
||||
|
||||
Ok(input
|
||||
.map(move |value| {
|
||||
let value_tag = value.tag();
|
||||
|
||||
let action = if rest.len() <= 2 {
|
||||
let field = rest.get(0);
|
||||
let replacement_if_true = rest.get(1);
|
||||
|
||||
match (field, replacement_if_true) {
|
||||
(Some(field), Some(replacement_if_true)) => {
|
||||
IsEmptyFor::RowWithFieldAndFallback(
|
||||
Box::new(field.as_column_path()?),
|
||||
replacement_if_true.clone(),
|
||||
)
|
||||
}
|
||||
(Some(field), None) => IsEmptyFor::RowWithField(field.as_column_path()?),
|
||||
(_, _) => IsEmptyFor::Value,
|
||||
}
|
||||
} else {
|
||||
// let no_args = vec![];
|
||||
let mut arguments = rest.iter().rev();
|
||||
let replacement_if_true = match arguments.next() {
|
||||
Some(arg) => arg.clone(),
|
||||
None => UntaggedValue::boolean(value.is_empty()).into_value(&value_tag),
|
||||
};
|
||||
|
||||
IsEmptyFor::RowWithFieldsAndFallback(
|
||||
arguments
|
||||
.map(|a| a.as_column_path())
|
||||
.filter_map(Result::ok)
|
||||
.collect(),
|
||||
replacement_if_true,
|
||||
)
|
||||
};
|
||||
|
||||
match action {
|
||||
IsEmptyFor::Value => Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::boolean(value.is_empty()).into_value(value_tag),
|
||||
)),
|
||||
IsEmptyFor::RowWithFieldsAndFallback(fields, default) => {
|
||||
let mut out = value;
|
||||
|
||||
for field in fields.iter() {
|
||||
let val = crate::commands::get::get_column_path(&field, &out)?;
|
||||
|
||||
let emptiness_value = match out {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
match obj.replace_data_at_column_path(&field, default.clone()) {
|
||||
Some(v) => Ok(v),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok(obj)
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
)),
|
||||
};
|
||||
|
||||
out = emptiness_value?;
|
||||
}
|
||||
|
||||
Ok(ReturnSuccess::Value(out))
|
||||
}
|
||||
IsEmptyFor::RowWithField(field) => {
|
||||
let val = crate::commands::get::get_column_path(&field, &value)?;
|
||||
|
||||
match &value {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
match obj.replace_data_at_column_path(
|
||||
&field,
|
||||
UntaggedValue::boolean(true).into_value(&value_tag),
|
||||
) {
|
||||
Some(v) => Ok(ReturnSuccess::Value(v)),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok(ReturnSuccess::Value(value))
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
IsEmptyFor::RowWithFieldAndFallback(field, default) => {
|
||||
let val = crate::commands::get::get_column_path(&field, &value)?;
|
||||
|
||||
match &value {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
match obj.replace_data_at_column_path(&field, default) {
|
||||
Some(v) => Ok(ReturnSuccess::Value(v)),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok(ReturnSuccess::Value(value))
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::IsEmpty;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(IsEmpty {})
|
||||
}
|
||||
}
|
84
crates/nu-cli/src/commands/keep.rs
Normal file
84
crates/nu-cli/src/commands/keep.rs
Normal file
@ -0,0 +1,84 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Keep;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct KeepArgs {
|
||||
rows: Option<Tagged<usize>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Keep {
|
||||
fn name(&self) -> &str {
|
||||
"keep"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("keep").optional(
|
||||
"rows",
|
||||
SyntaxShape::Int,
|
||||
"starting from the front, the number of rows to keep",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Keep the number of rows only"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
keep(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Keep the first row",
|
||||
example: "echo [1 2 3] | keep",
|
||||
result: Some(vec![UntaggedValue::int(1).into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Keep the first four rows",
|
||||
example: "echo [1 2 3 4 5] | keep 4",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(4).into(),
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn keep(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (KeepArgs { rows }, input) = args.process(®istry).await?;
|
||||
let rows_desired = if let Some(quantity) = rows {
|
||||
*quantity
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
Ok(input.take(rows_desired).to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Keep;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Keep {})
|
||||
}
|
||||
}
|
123
crates/nu-cli/src/commands/keep_until.rs
Normal file
123
crates/nu-cli/src/commands/keep_until.rs
Normal file
@ -0,0 +1,123 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct KeepUntil;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for KeepUntil {
|
||||
fn name(&self) -> &str {
|
||||
"keep-until"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("keep-until")
|
||||
.required(
|
||||
"condition",
|
||||
SyntaxShape::Math,
|
||||
"the condition that must be met to stop keeping rows",
|
||||
)
|
||||
.filter()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Keeps rows until the condition matches."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = 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 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,
|
||||
));
|
||||
}
|
||||
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(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())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::KeepUntil;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(KeepUntil {})
|
||||
}
|
||||
}
|
123
crates/nu-cli/src/commands/keep_while.rs
Normal file
123
crates/nu-cli/src/commands/keep_while.rs
Normal file
@ -0,0 +1,123 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct KeepWhile;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for KeepWhile {
|
||||
fn name(&self) -> &str {
|
||||
"keep-while"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("keep-while")
|
||||
.required(
|
||||
"condition",
|
||||
SyntaxShape::Math,
|
||||
"the condition that must be met to keep rows",
|
||||
)
|
||||
.filter()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Keeps rows while the condition matches."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = 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 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,
|
||||
));
|
||||
}
|
||||
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(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())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::KeepWhile;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(KeepWhile {})
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
use crate::commands::command::RunnablePerItemContext;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CallInfo, Signature, SyntaxShape, Value};
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
@ -16,7 +16,8 @@ pub struct KillArgs {
|
||||
pub quiet: Tagged<bool>,
|
||||
}
|
||||
|
||||
impl PerItemCommand for Kill {
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Kill {
|
||||
fn name(&self) -> &str {
|
||||
"kill"
|
||||
}
|
||||
@ -37,28 +38,42 @@ impl PerItemCommand for Kill {
|
||||
"Kill a process using the process id."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
_registry: &CommandRegistry,
|
||||
raw_args: &RawCommandArgs,
|
||||
_input: Value,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
call_info
|
||||
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), kill)?
|
||||
.run()
|
||||
kill(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Kill the pid using the most memory",
|
||||
example: "ps | sort-by mem | last | kill $it.pid",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Force kill a given pid",
|
||||
example: "kill --force 12345",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn kill(
|
||||
KillArgs {
|
||||
pid,
|
||||
rest,
|
||||
force,
|
||||
quiet,
|
||||
}: KillArgs,
|
||||
_context: &RunnablePerItemContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
async fn kill(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let (
|
||||
KillArgs {
|
||||
pid,
|
||||
rest,
|
||||
force,
|
||||
quiet,
|
||||
},
|
||||
..,
|
||||
) = args.process(®istry).await?;
|
||||
let mut cmd = if cfg!(windows) {
|
||||
let mut cmd = Command::new("taskkill");
|
||||
|
||||
@ -102,3 +117,15 @@ fn kill(
|
||||
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Kill;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Kill {})
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Last;
|
||||
@ -12,6 +12,7 @@ pub struct LastArgs {
|
||||
rows: Option<Tagged<u64>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Last {
|
||||
fn name(&self) -> &str {
|
||||
"last"
|
||||
@ -29,33 +30,68 @@ impl WholeStreamCommand for Last {
|
||||
"Show only the last number of rows."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, last)?.run()
|
||||
last(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get the last row",
|
||||
example: "echo [1 2 3] | last",
|
||||
result: Some(vec![Value::from(UntaggedValue::from(BigInt::from(3)))]),
|
||||
},
|
||||
Example {
|
||||
description: "Get the last three rows",
|
||||
example: "echo [1 2 3 4 5] | last 3",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(4).into(),
|
||||
UntaggedValue::int(5).into(),
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn last(LastArgs { rows }: LastArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let v: Vec<_> = context.input.into_vec().await;
|
||||
async fn last(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (LastArgs { rows }, input) = args.process(®istry).await?;
|
||||
let v: Vec<_> = input.into_vec().await;
|
||||
|
||||
let rows_desired = if let Some(quantity) = rows {
|
||||
*quantity
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
let count = (rows_desired as usize);
|
||||
if count < v.len() {
|
||||
let k = v.len() - count;
|
||||
for x in v[k..].iter() {
|
||||
let y: Value = x.clone();
|
||||
yield ReturnSuccess::value(y)
|
||||
}
|
||||
}
|
||||
let rows_desired = if let Some(quantity) = rows {
|
||||
*quantity
|
||||
} else {
|
||||
1
|
||||
};
|
||||
Ok(stream.to_output_stream())
|
||||
|
||||
let mut values_vec_deque = VecDeque::new();
|
||||
|
||||
let count = rows_desired as usize;
|
||||
|
||||
if count < v.len() {
|
||||
let k = v.len() - count;
|
||||
|
||||
for x in v[k..].iter() {
|
||||
values_vec_deque.push_back(ReturnSuccess::value(x.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(futures::stream::iter(values_vec_deque).to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Last;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Last {})
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct Lines;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Lines {
|
||||
fn name(&self) -> &str {
|
||||
"lines"
|
||||
@ -18,12 +19,20 @@ impl WholeStreamCommand for Lines {
|
||||
"Split single string into rows, one per line."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
lines(args, registry)
|
||||
lines(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Split multi-line string into lines",
|
||||
example: r#"^echo "two\nlines" | lines"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,80 +46,131 @@ fn ends_with_line_ending(st: &str) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once(registry)?;
|
||||
async fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let leftover = Arc::new(vec![]);
|
||||
let leftover_string = Arc::new(String::new());
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let name_span = tag.span;
|
||||
let mut input = args.input;
|
||||
|
||||
let mut leftover = vec![];
|
||||
let mut leftover_string = String::new();
|
||||
let stream = async_stream! {
|
||||
loop {
|
||||
match input.next().await {
|
||||
Some(Value { value: UntaggedValue::Primitive(Primitive::String(st)), ..}) => {
|
||||
let mut st = leftover_string.clone() + &st;
|
||||
leftover.clear();
|
||||
let eos = futures::stream::iter(vec![
|
||||
UntaggedValue::Primitive(Primitive::EndOfStream).into_untagged_value()
|
||||
]);
|
||||
|
||||
Ok(args
|
||||
.input
|
||||
.chain(eos)
|
||||
.map(move |item| {
|
||||
let mut leftover = leftover.clone();
|
||||
let mut leftover_string = leftover_string.clone();
|
||||
|
||||
match item {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(st)),
|
||||
..
|
||||
} => {
|
||||
let st = (&*leftover_string).clone() + &st;
|
||||
if let Some(leftover) = Arc::get_mut(&mut leftover) {
|
||||
leftover.clear();
|
||||
}
|
||||
|
||||
let mut lines: Vec<String> = st.lines().map(|x| x.to_string()).collect();
|
||||
|
||||
if !ends_with_line_ending(&st) {
|
||||
if let Some(last) = lines.pop() {
|
||||
leftover_string = last;
|
||||
} else {
|
||||
if let Some(leftover_string) = Arc::get_mut(&mut leftover_string) {
|
||||
leftover_string.clear();
|
||||
leftover_string.push_str(&last);
|
||||
}
|
||||
} else if let Some(leftover_string) = Arc::get_mut(&mut leftover_string) {
|
||||
leftover_string.clear();
|
||||
}
|
||||
} else {
|
||||
} else if let Some(leftover_string) = Arc::get_mut(&mut leftover_string) {
|
||||
leftover_string.clear();
|
||||
}
|
||||
|
||||
let success_lines: Vec<_> = lines.iter().map(|x| ReturnSuccess::value(UntaggedValue::line(x).into_untagged_value())).collect();
|
||||
yield futures::stream::iter(success_lines)
|
||||
}
|
||||
Some(Value { value: UntaggedValue::Primitive(Primitive::Line(st)), ..}) => {
|
||||
let mut st = leftover_string.clone() + &st;
|
||||
leftover.clear();
|
||||
let success_lines: Vec<_> = lines
|
||||
.iter()
|
||||
.map(|x| ReturnSuccess::value(UntaggedValue::line(x).into_untagged_value()))
|
||||
.collect();
|
||||
|
||||
futures::stream::iter(success_lines)
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Line(st)),
|
||||
..
|
||||
} => {
|
||||
let st = (&*leftover_string).clone() + &st;
|
||||
if let Some(leftover) = Arc::get_mut(&mut leftover) {
|
||||
leftover.clear();
|
||||
}
|
||||
let mut lines: Vec<String> = st.lines().map(|x| x.to_string()).collect();
|
||||
if !ends_with_line_ending(&st) {
|
||||
if let Some(last) = lines.pop() {
|
||||
leftover_string = last;
|
||||
} else {
|
||||
if let Some(leftover_string) = Arc::get_mut(&mut leftover_string) {
|
||||
leftover_string.clear();
|
||||
leftover_string.push_str(&last);
|
||||
}
|
||||
} else if let Some(leftover_string) = Arc::get_mut(&mut leftover_string) {
|
||||
leftover_string.clear();
|
||||
}
|
||||
} else {
|
||||
} else if let Some(leftover_string) = Arc::get_mut(&mut leftover_string) {
|
||||
leftover_string.clear();
|
||||
}
|
||||
|
||||
let success_lines: Vec<_> = lines.iter().map(|x| ReturnSuccess::value(UntaggedValue::line(x).into_untagged_value())).collect();
|
||||
yield futures::stream::iter(success_lines)
|
||||
let success_lines: Vec<_> = lines
|
||||
.iter()
|
||||
.map(|x| ReturnSuccess::value(UntaggedValue::line(x).into_untagged_value()))
|
||||
.collect();
|
||||
futures::stream::iter(success_lines)
|
||||
}
|
||||
Some( Value { tag: value_span, ..}) => {
|
||||
yield futures::stream::iter(vec![Err(ShellError::labeled_error_with_secondary(
|
||||
"Expected a string from pipeline",
|
||||
"requires string input",
|
||||
name_span,
|
||||
"value originates from here",
|
||||
value_span,
|
||||
))]);
|
||||
}
|
||||
None => {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::EndOfStream),
|
||||
..
|
||||
} => {
|
||||
if !leftover.is_empty() {
|
||||
let mut st = leftover_string.clone();
|
||||
if let Ok(extra) = String::from_utf8(leftover) {
|
||||
let mut st = (&*leftover_string).clone();
|
||||
if let Ok(extra) = String::from_utf8((&*leftover).clone()) {
|
||||
st.push_str(&extra);
|
||||
}
|
||||
yield futures::stream::iter(vec![ReturnSuccess::value(UntaggedValue::string(st).into_untagged_value())])
|
||||
// futures::stream::iter(vec![ReturnSuccess::value(
|
||||
// UntaggedValue::string(st).into_untagged_value(),
|
||||
// )])
|
||||
if !st.is_empty() {
|
||||
futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::string(&*leftover_string).into_untagged_value(),
|
||||
)])
|
||||
} else {
|
||||
futures::stream::iter(vec![])
|
||||
}
|
||||
} else {
|
||||
futures::stream::iter(vec![])
|
||||
}
|
||||
break;
|
||||
}
|
||||
Value {
|
||||
tag: value_span, ..
|
||||
} => futures::stream::iter(vec![Err(ShellError::labeled_error_with_secondary(
|
||||
"Expected a string from pipeline",
|
||||
"requires string input",
|
||||
name_span,
|
||||
"value originates from here",
|
||||
value_span,
|
||||
))]),
|
||||
}
|
||||
}
|
||||
if !leftover_string.is_empty() {
|
||||
yield futures::stream::iter(vec![ReturnSuccess::value(UntaggedValue::string(leftover_string).into_untagged_value())]);
|
||||
}
|
||||
}
|
||||
.flatten();
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Lines;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Lines {})
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::command::RunnablePerItemContext;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CallInfo, Signature, SyntaxShape, Value};
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@ -16,9 +16,12 @@ pub struct LsArgs {
|
||||
pub short_names: bool,
|
||||
#[serde(rename = "with-symlink-targets")]
|
||||
pub with_symlink_targets: bool,
|
||||
#[serde(rename = "du")]
|
||||
pub du: bool,
|
||||
}
|
||||
|
||||
impl PerItemCommand for Ls {
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Ls {
|
||||
fn name(&self) -> &str {
|
||||
"ls"
|
||||
}
|
||||
@ -46,25 +49,58 @@ impl PerItemCommand for Ls {
|
||||
"display the paths to the target files that symlinks point to",
|
||||
Some('w'),
|
||||
)
|
||||
.switch(
|
||||
"du",
|
||||
"display the apparent directory size in place of the directory metadata size",
|
||||
Some('d'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"View the contents of the current or given path."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
_registry: &CommandRegistry,
|
||||
raw_args: &RawCommandArgs,
|
||||
_input: Value,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
call_info
|
||||
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), ls)?
|
||||
.run()
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let ctrl_c = args.ctrl_c.clone();
|
||||
let shell_manager = args.shell_manager.clone();
|
||||
let (args, _) = args.process(®istry).await?;
|
||||
shell_manager.ls(args, name, ctrl_c)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "List all files in the current directory",
|
||||
example: "ls",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "List all files in a subdirectory",
|
||||
example: "ls subdir",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "List all rust files",
|
||||
example: "ls *.rs",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn ls(args: LsArgs, context: &RunnablePerItemContext) -> Result<OutputStream, ShellError> {
|
||||
context.shell_manager.ls(args, context)
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ls;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Ls {})
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ pub struct MapMaxByArgs {
|
||||
column_name: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for MapMaxBy {
|
||||
fn name(&self) -> &str {
|
||||
"map-max-by"
|
||||
@ -32,43 +33,52 @@ impl WholeStreamCommand for MapMaxBy {
|
||||
"Creates a new table with the data from the tables rows maxed by the column given."
|
||||
}
|
||||
|
||||
fn run(
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, map_max_by)?.run()
|
||||
map_max_by(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_max_by(
|
||||
MapMaxByArgs { column_name }: MapMaxByArgs,
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
pub async fn map_max_by(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (MapMaxByArgs { column_name }, mut input) = args.process(®istry).await?;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
|
||||
if values.is_empty() {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected table from pipeline",
|
||||
"requires a table input",
|
||||
name
|
||||
))
|
||||
if values.is_empty() {
|
||||
Err(ShellError::labeled_error(
|
||||
"Expected table from pipeline",
|
||||
"requires a table input",
|
||||
name,
|
||||
))
|
||||
} else {
|
||||
let map_by_column = if let Some(column_to_map) = column_name {
|
||||
Some(column_to_map.item().clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let map_by_column = if let Some(column_to_map) = column_name {
|
||||
Some(column_to_map.item().clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match map_max(&values[0], map_by_column, name) {
|
||||
Ok(table_maxed) => yield ReturnSuccess::value(table_maxed),
|
||||
Err(err) => yield Err(err)
|
||||
}
|
||||
match map_max(&values[0], map_by_column, name) {
|
||||
Ok(table_maxed) => Ok(OutputStream::one(ReturnSuccess::value(table_maxed))),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::MapMaxBy;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(MapMaxBy {})
|
||||
}
|
||||
}
|
||||
|
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())
|
||||
}
|
||||
}
|
119
crates/nu-cli/src/commands/merge.rs
Normal file
119
crates/nu-cli/src/commands/merge.rs
Normal file
@ -0,0 +1,119 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::data::value::merge_values;
|
||||
use crate::prelude::*;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
pub struct Merge;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MergeArgs {
|
||||
block: Block,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Merge {
|
||||
fn name(&self) -> &str {
|
||||
"merge"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("merge").required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run and merge into the table",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Merge a table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
merge(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Merge a 1-based index column with some ls output",
|
||||
example: "ls | select name | keep 3 | merge { echo [1 2 3] | wrap index }",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async fn merge(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
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) => {
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
|
||||
let table = table.unwrap_or_else(|| {
|
||||
vec![Value {
|
||||
value: UntaggedValue::row(IndexMap::default()),
|
||||
tag: name_tag,
|
||||
}]
|
||||
});
|
||||
|
||||
Ok(input
|
||||
.enumerate()
|
||||
.map(move |(idx, value)| {
|
||||
let other = table.get(idx);
|
||||
|
||||
match other {
|
||||
Some(replacement) => match merge_values(&value.value, &replacement.value) {
|
||||
Ok(merged_value) => ReturnSuccess::value(merged_value.into_value(&value.tag)),
|
||||
Err(_) => {
|
||||
let message = format!("The row at {:?} types mismatch", idx);
|
||||
Err(ShellError::labeled_error(
|
||||
"Could not merge",
|
||||
&message,
|
||||
&value.tag,
|
||||
))
|
||||
}
|
||||
},
|
||||
None => ReturnSuccess::value(value),
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Merge;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Merge {})
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user