Merge branch 'master' of github.com:nushell/nushell

This commit is contained in:
Yehuda Katz 2019-10-30 18:40:34 -07:00
commit 4935129c5a
245 changed files with 20471 additions and 6691 deletions

View file

@ -5,10 +5,25 @@ strategy:
matrix:
linux-nightly:
image: ubuntu-16.04
style: 'unflagged'
macos-nightly:
image: macos-10.14
style: 'unflagged'
windows-nightly:
image: vs2017-win2016
style: 'unflagged'
linux-nightly-canary:
image: ubuntu-16.04
style: 'canary'
macos-nightly-canary:
image: macos-10.14
style: 'canary'
windows-nightly-canary:
image: vs2017-win2016
style: 'canary'
fmt:
image: ubuntu-16.04
style: 'fmt'
pool:
vmImage: $(image)
@ -16,10 +31,22 @@ pool:
steps:
- bash: |
set -e
if [ -e /etc/debian_version ]
then
sudo apt-get -y install libxcb-composite0-dev libx11-dev
fi
curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain `cat rust-toolchain`
export PATH=$HOME/.cargo/bin:$PATH
rustc -Vv
echo "##vso[task.prependpath]$HOME/.cargo/bin"
rustup component add rustfmt --toolchain `cat rust-toolchain`
displayName: Install Rust
- bash: RUSTFLAGS="-D warnings" cargo test
- bash: RUSTFLAGS="-D warnings" cargo test --all-features
condition: eq(variables['style'], 'unflagged')
displayName: Run tests
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo test --all-features
condition: eq(variables['style'], 'canary')
displayName: Run tests
- bash: cargo fmt --all -- --check
condition: eq(variables['style'], 'fmt')
displayName: Lint

View file

@ -9,21 +9,16 @@ version: 2.1
commands:
check_token:
description: Check that QUAY_TOKEN is provided in environment
steps:
- run:
if [[ -z "${QUAY_TOKEN}" ]]; then
echo "QUAY_TOKEN is undefined. Add to CircleCI environment to continue."
exit 1;
fi
pull_cache:
description: Pulls Quay.io docker images usable for our cache
description: Pulls Quay.io docker images (latest) for our cache
parameters:
tag:
type: string
default: "devel"
steps:
- run: docker pull quay.io/nushell/nu:latest
- run: docker pull quay.io/nushell/nu-base:latest
- run: echo "Tag is << parameters.tag >>"
- run: docker pull quay.io/nushell/nu:<< parameters.tag >>
- run: docker pull quay.io/nushell/nu-base:<< parameters.tag >>
orbs:
# https://circleci.com/orbs/registry/orb/circleci/docker
@ -40,13 +35,12 @@ workflows:
image: nushell/nu-base
tag: latest
dockerfile: docker/Dockerfile.nu-base
extra_build_args: --cache-from=quay.io/nushell/nu-base:latest,quay.io/nushell/nu:latest
extra_build_args: --cache-from=quay.io/nushell/nu-base:devel
filters:
branches:
ignore:
- master
before_build:
- check_token
- pull_cache
after_build:
- run:
@ -58,31 +52,41 @@ workflows:
command: |
DOCKER_TAG=$(docker run quay.io/nushell/nu --version | cut -d' ' -f2)
echo "Version that would be used for Docker tag is v${DOCKER_TAG}"
- run:
name: Test Executable
command: |
docker run --rm quay.io/nushell/nu-base --help
docker run --rm quay.io/nushell/nu --help
# workflow publishes to Docker Hub, with each job having different triggers
build_with_deploy:
jobs:
# Deploy versioned and latest images on tags (releases) only.
# Deploy versioned and latest images on tags (releases) only - builds --release.
- docker/publish:
image: nushell/nu-base
registry: quay.io
tag: latest
dockerfile: docker/Dockerfile.nu-base
extra_build_args: --cache-from=quay.io/nushell/nu-base:latest,quay.io/nushell/nu:latest
extra_build_args: --cache-from=quay.io/nushell/nu-base:latest,quay.io/nushell/nu:latest --build-arg RELEASE=true
filters:
branches:
ignore: /.*/
tags:
only: /^v.*/
before_build:
- check_token
- pull_cache
- run: docker pull quay.io/nushell/nu:latest
- run: docker pull quay.io/nushell/nu-base:latest
after_build:
- run:
name: Build Multistage (smaller) container
command: |
docker build -f docker/Dockerfile -t quay.io/nushell/nu .
- run:
name: Test Executable
command: |
docker run --rm quay.io/nushell/nu --help
docker run --rm quay.io/nushell/nu-base --help
- run:
name: Publish Docker Tag with Nushell Version
command: |
@ -90,12 +94,11 @@ workflows:
echo "Version for Docker tag is ${DOCKER_TAG}"
docker tag quay.io/nushell/nu-base:latest quay.io/nushell/nu-base:${DOCKER_TAG}
docker tag quay.io/nushell/nu:latest quay.io/nushell/nu:${DOCKER_TAG}
docker login -u="nushell+circleci" -p="${QUAY_TOKEN}" quay.io
docker push quay.io/nushell/nu-base
docker push quay.io/nushell/nu
# publish devel to Docker Hub on merge to master
# publish devel to Docker Hub on merge to master (doesn't build --release)
build_with_deploy_devel:
jobs:
@ -105,9 +108,8 @@ workflows:
registry: quay.io
tag: devel
dockerfile: docker/Dockerfile.nu-base
extra_build_args: --cache-from=quay.io/nushell/nu-base:latest,quay.io/nushell/nu:latest
extra_build_args: --cache-from=quay.io/nushell/nu-base:devel
before_build:
- check_token
- pull_cache
filters:
branches:
@ -117,9 +119,47 @@ workflows:
name: Build Multistage (smaller) container
command: |
docker build --build-arg FROMTAG=devel -f docker/Dockerfile -t quay.io/nushell/nu:devel .
- run:
name: Test Executable
command: |
docker run --rm quay.io/nushell/nu:devel --help
docker run --rm quay.io/nushell/nu-base:devel --help
- run:
name: Publish Development Docker Tags
command: |
docker login -u="nushell+circleci" -p="${QUAY_TOKEN}" quay.io
docker push quay.io/nushell/nu-base:devel
docker push quay.io/nushell/nu:devel
nightly:
triggers:
- schedule:
cron: "0 0 * * *"
filters:
branches:
only:
- master
jobs:
- docker/publish:
image: nushell/nu-base
registry: quay.io
tag: nightly
dockerfile: docker/Dockerfile.nu-base
extra_build_args: --cache-from=quay.io/nushell/nu-base:nightly --build-arg RELEASE=true
before_build:
- run: docker pull quay.io/nushell/nu:nightly
- run: docker pull quay.io/nushell/nu-base:nightly
after_build:
- run:
name: Build Multistage (smaller) container
command: |
docker build -f docker/Dockerfile -t quay.io/nushell/nu:nightly .
- run:
name: Test Executable
command: |
docker run --rm quay.io/nushell/nu:nightly --help
docker run --rm quay.io/nushell/nu-base:nightly --help
- run:
name: Publish Nightly Nushell Containers
command: |
docker push quay.io/nushell/nu-base:nightly
docker push quay.io/nushell/nu:nightly

View file

@ -6,4 +6,9 @@ indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = false
end_of_line = lf
end_of_line = lf
[*.{yml,yaml}]
indent_size = 2
charset = utf-8
insert_final_newline = true

98
.github/workflows/docker-publish.yml vendored Normal file
View file

@ -0,0 +1,98 @@
name: Publish consumable Docker images
on:
push:
tags: ['*.*.*']
jobs:
compile:
runs-on: ubuntu-latest
strategy:
matrix:
arch:
- x86_64-unknown-linux-musl
- x86_64-unknown-linux-gnu
steps:
- uses: actions/checkout@v1
- run: cargo install cross
- name: compile for specific target
env: { arch: '${{ matrix.arch }}' }
run: |
cross build --target ${{ matrix.arch }} --release
# leave only the executable file
rm -rd target/${{ matrix.arch }}/release/{*/*,*.d,*.rlib,.fingerprint}
find . -empty -delete
- uses: actions/upload-artifact@master
with:
name: ${{ matrix.arch }}
path: target/${{ matrix.arch }}/release
docker:
name: Build and publish docker images
needs: compile
runs-on: ubuntu-latest
strategy:
matrix:
tag:
- alpine
- slim
- debian
- glibc-busybox
- musl-busybox
- musl-distroless
- glibc-distroless
- glibc
- musl
include:
- { tag: alpine, base-image: alpine, arch: x86_64-unknown-linux-musl, plugin: true }
- { tag: slim, base-image: 'debian:stable-slim', arch: x86_64-unknown-linux-gnu, plugin: true }
- { tag: debian, base-image: debian, arch: x86_64-unknown-linux-gnu, plugin: true }
- { tag: glibc-busybox, base-image: 'busybox:glibc', arch: x86_64-unknown-linux-gnu, use-patch: true }
- { tag: musl-busybox, base-image: 'busybox:musl', arch: x86_64-unknown-linux-musl, }
- { tag: musl-distroless, base-image: 'gcr.io/distroless/static', arch: x86_64-unknown-linux-musl, }
- { tag: glibc-distroless, base-image: 'gcr.io/distroless/cc', arch: x86_64-unknown-linux-gnu, use-patch: true }
- { tag: glibc, base-image: scratch, arch: x86_64-unknown-linux-gnu, }
- { tag: musl, base-image: scratch, arch: x86_64-unknown-linux-musl, }
steps:
- uses: actions/checkout@v1
- uses: actions/download-artifact@master
with: { name: '${{ matrix.arch }}', path: target/release }
- name: Build and publish exact version
run: |
REGISTRY=${REGISTRY,,}; export TAG=${GITHUB_REF##*/}-${{ matrix.tag }};
export NU_BINS=target/release/$( [ ${{ matrix.plugin }} = true ] && echo nu* || echo nu )
export PATCH=$([ ${{ matrix.use-patch }} = true ] && echo .${{ matrix.tag }} || echo '')
chmod +x $NU_BINS
echo ${{ secrets.DOCKER_REGISTRY }} | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin
docker-compose --file docker/docker-compose.package.yml build
docker-compose --file docker/docker-compose.package.yml push # exact version
env:
BASE_IMAGE: ${{ matrix.base-image }}
REGISTRY: docker.pkg.github.com/${{ github.repository }}
#region semantics tagging
- name: Retag and push without suffixing version
run: |
VERSION=${GITHUB_REF##*/}
docker tag ${REGISTRY,,}/nu:${VERSION}-${{ matrix.tag }} ${REGISTRY,,}/nu:${{ matrix.tag }}
docker tag ${REGISTRY,,}/nu:${VERSION}-${{ matrix.tag }} ${REGISTRY,,}/nu:${VERSION%%.*}-${{ matrix.tag }}
docker tag ${REGISTRY,,}/nu:${VERSION}-${{ matrix.tag }} ${REGISTRY,,}/nu:${VERSION%.*}-${{ matrix.tag }}
docker push ${REGISTRY,,}/nu:${VERSION%.*}-${{ matrix.tag }} # latest patch
docker push ${REGISTRY,,}/nu:${VERSION%%.*}-${{ matrix.tag }} # latest features
docker push ${REGISTRY,,}/nu:${{ matrix.tag }} # latest version
env: { REGISTRY: 'docker.pkg.github.com/${{ github.repository }}' }
- name: Retag and push debian as latest
if: matrix.tag == 'debian'
run: |
VERSION=${GITHUB_REF##*/}
docker tag ${REGISTRY,,}/nu:${{ matrix.tag }} ${REGISTRY,,}/nu:latest
docker tag ${REGISTRY,,}/nu:${VERSION}-${{ matrix.tag }} ${REGISTRY,,}/nu:${VERSION%.*}
docker tag ${REGISTRY,,}/nu:${VERSION}-${{ matrix.tag }} ${REGISTRY,,}/nu:${VERSION%%.*}
docker tag ${REGISTRY,,}/nu:${VERSION}-${{ matrix.tag }} ${REGISTRY,,}/nu:${VERSION}
docker push ${REGISTRY,,}/nu:${VERSION} # exact version
docker push ${REGISTRY,,}/nu:${VERSION%%.*} # latest features
docker push ${REGISTRY,,}/nu:${VERSION%.*} # latest patch
docker push ${REGISTRY,,}/nu:latest # latest version
env: { REGISTRY: 'docker.pkg.github.com/${{ github.repository }}' }
#endregion semantics tagging

7
.gitignore vendored
View file

@ -3,3 +3,10 @@
**/*.rs.bk
history.txt
tests/fixtures/nuplayground
# Debian/Ubuntu
debian/.debhelper/
debian/debhelper-build-stamp
debian/files
debian/nu.substvars
debian/nu/

7
.gitpod.Dockerfile vendored Normal file
View file

@ -0,0 +1,7 @@
FROM gitpod/workspace-full
USER root
RUN apt-get update && apt-get install -y libssl-dev \
libxcb-composite0-dev \
pkg-config \
curl \
rustc

21
.gitpod.yml Normal file
View file

@ -0,0 +1,21 @@
image:
file: .gitpod.Dockerfile
tasks:
- init: cargo build
command: cargo run
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

1588
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package]
name = "nu"
version = "0.2.0"
version = "0.4.1"
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
description = "A shell for the GitHub era"
license = "MIT"
@ -8,83 +8,94 @@ edition = "2018"
readme = "README.md"
default-run = "nu"
repository = "https://github.com/nushell/nushell"
homepage = "http://nushell.sh"
homepage = "https://www.nushell.sh"
documentation = "https://book.nushell.sh"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rustyline = "5.0.2"
chrono = { version = "0.4.7", features = ["serde"] }
rustyline = "5.0.4"
chrono = { version = "0.4.9", features = ["serde"] }
derive-new = "0.5.8"
prettytable-rs = "0.8.0"
itertools = "0.8.0"
ansi_term = "0.12.0"
ansi_term = "0.12.1"
nom = "5.0.0"
dunce = "1.0.0"
indexmap = { version = "1.1.0", features = ["serde-1"] }
indexmap = { version = "1.2.0", features = ["serde-1"] }
chrono-humanize = "0.0.11"
byte-unit = "3.0.1"
base64 = "0.10.1"
futures-preview = { version = "=0.3.0-alpha.18", features = ["compat", "io-compat"] }
futures-async-stream = "=0.1.0-alpha.5"
async-stream = "0.1.1"
futures_codec = "0.2.5"
num-traits = "0.2.8"
term = "0.5.2"
bytes = "0.4.12"
log = "0.4.8"
pretty_env_logger = "0.3.1"
serde = { version = "1.0.99", features = ["derive"] }
serde = { version = "1.0.100", features = ["derive"] }
bson = { version = "0.14.0", features = ["decimal128"] }
serde_json = "1.0.40"
serde-hjson = "0.9.1"
serde_yaml = "0.8"
serde_bytes = "0.11.2"
getset = "0.0.8"
language-reporting = "0.3.1"
language-reporting = "0.4.0"
app_dirs = "1.2.1"
csv = "1.1"
toml = "0.5.3"
clap = "2.33.0"
git2 = { version = "0.10.0", default_features = false }
git2 = { version = "0.10.1", default_features = false }
dirs = "2.0.2"
glob = "0.3.0"
ctrlc = "3.1.3"
ptree = "0.2"
surf = "1.0.2"
url = "2.1.0"
roxmltree = "0.7.0"
nom5_locate = "0.1.1"
enum-utils = "0.1.1"
nom_locate = "1.0.0"
nom-tracable = "0.4.0"
unicode-xid = "0.2.0"
serde_ini = "0.2.0"
subprocess = "0.1.18"
mime = "0.3.13"
regex = "1.2.1"
mime = "0.3.14"
pretty-hex = "0.1.0"
neso = { version = "0.5.0", optional = true }
hex = "0.3.2"
crossterm = "0.10.2"
tempfile = "3.1.0"
image = { version = "0.22.1", default_features = false, features = ["png_codec", "jpeg"] }
semver = "0.9.0"
uuid = {version = "0.7.4", features = [ "v4", "serde" ]}
syntect = "3.2.0"
onig_sys = "=69.1.0"
heim = "0.0.7"
which = "2.0.1"
battery = "0.7.4"
textwrap = {version = "0.11.0", features = ["term_size"]}
shellexpand = "1.0.0"
futures-timer = "0.4.0"
pin-utils = "0.1.0-alpha.4"
num-bigint = { version = "0.2.3", features = ["serde"] }
bigdecimal = { version = "0.1.0", features = ["serde"] }
natural = "0.3.0"
serde_urlencoded = "0.6.1"
sublime_fuzzy = "0.5"
trash = "1.0.0"
regex = "1"
neso = { version = "0.5.0", optional = true }
crossterm = { version = "0.10.2", optional = true }
syntect = {version = "3.2.0", optional = true }
onig_sys = {version = "=69.1.0", optional = true }
heim = {version = "0.0.8", optional = true }
battery = {version = "0.7.4", optional = true }
rawkey = {version = "0.1.2", optional = true }
clipboard = {version = "0.5", optional = true }
shellexpand = "1.0.0"
futures-timer = "0.3.0"
pin-utils = "0.1.0-alpha.4"
num-bigint = { version = "0.2.2", features = ["serde"] }
bigdecimal = { version = "0.1.0", features = ["serde"] }
ptree = {version = "0.2" }
image = { version = "0.22.2", default_features = false, features = ["png_codec", "jpeg"], optional = true }
[features]
default = ["textview", "sys", "ps"]
raw-key = ["rawkey", "neso"]
textview = ["syntect", "onig_sys", "crossterm"]
binaryview = ["image", "crossterm"]
sys = ["heim", "battery"]
ps = ["heim"]
# trace = ["nom-tracable/trace"]
all = ["raw-key", "textview", "binaryview", "sys", "ps", "clipboard"]
[dependencies.rusqlite]
version = "0.20.0"
@ -93,6 +104,10 @@ features = ["bundled", "blob"]
[dev-dependencies]
pretty_assertions = "0.6.1"
[build-dependencies]
toml = "0.5.3"
serde = { version = "1.0.101", features = ["derive"] }
[lib]
name = "nu"
path = "src/lib.rs"
@ -105,6 +120,10 @@ path = "src/plugins/inc.rs"
name = "nu_plugin_sum"
path = "src/plugins/sum.rs"
[[bin]]
name = "nu_plugin_average"
path = "src/plugins/average.rs"
[[bin]]
name = "nu_plugin_embed"
path = "src/plugins/embed.rs"
@ -117,6 +136,10 @@ path = "src/plugins/add.rs"
name = "nu_plugin_edit"
path = "src/plugins/edit.rs"
[[bin]]
name = "nu_plugin_read"
path = "src/plugins/read.rs"
[[bin]]
name = "nu_plugin_str"
path = "src/plugins/str.rs"
@ -125,21 +148,40 @@ path = "src/plugins/str.rs"
name = "nu_plugin_skip"
path = "src/plugins/skip.rs"
[[bin]]
name = "nu_plugin_match"
path = "src/plugins/match.rs"
required-features = ["regex"]
[[bin]]
name = "nu_plugin_sys"
path = "src/plugins/sys.rs"
required-features = ["sys"]
[[bin]]
name = "nu_plugin_ps"
path = "src/plugins/ps.rs"
required-features = ["ps"]
[[bin]]
name = "nu_plugin_tree"
path = "src/plugins/tree.rs"
required-features = ["tree"]
[[bin]]
name = "nu_plugin_binaryview"
path = "src/plugins/binaryview.rs"
required-features = ["binaryview"]
[[bin]]
name = "nu_plugin_textview"
path = "src/plugins/textview.rs"
required-features = ["textview"]
[[bin]]
name = "nu_plugin_docker"
path = "src/plugins/docker.rs"
required-features = ["docker"]
[[bin]]
name = "nu"

209
README.md
View file

@ -1,25 +1,36 @@
[![Crates.io](https://img.shields.io/crates/v/nu.svg)](https://crates.io/crates/nu)
[![Build Status](https://dev.azure.com/nushell/nushell/_apis/build/status/nushell.nushell?branchName=master)](https://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=master)
[![Build Status](https://dev.azure.com/nushell/nushell/_apis/build/status/nushell.nushell?branchName=master)](https://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=master)
[![Discord](https://img.shields.io/discord/601130461678272522.svg?logo=discord)](https://discord.gg/NtAbbGn)
[![The Changelog #363](https://img.shields.io/badge/The%20Changelog-%23363-61c192.svg)](https://changelog.com/podcast/363)
# Nu Shell
A modern shell for the GitHub era
A modern shell for the GitHub era.
![Example of nushell](images/nushell-autocomplete4.gif "Example of nushell")
![Example of nushell](images/nushell-autocomplete.gif "Example of nushell")
# Status
This project has reached a minimum-viable product level of quality. While contributors dogfood it as their daily driver, it may be instable for some commands. Future releases will work fill out missing features and improve stability. Its design is also subject to change as it matures.
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. Future releases will work to fill out missing features and improve stability. 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.
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.
There is also a [book](https://book.nushell.sh) about Nu, currently in progress.
# Learning more
There are a few good resources to learn about Nu. There is a [book](https://book.nushell.sh) about Nu that is currently in progress. The book focuses on using Nu and its core concepts.
If you're a developer who would like to contribute to Nu, we're also working on a [book for developers](https://github.com/nushell/contributor-book/tree/master/en) to help you get started. There are also [good first issues](https://github.com/nushell/nushell/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) to help you dive in.
We also have an active [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell) if you'd like to come and chat with us.
Try it in Gitpod.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/nushell/nushell)
# Installation
## Local
## Local
Up-to-date installation instructions can be found in the [installation chapter of the book](https://book.nushell.sh/en/installation).
@ -27,31 +38,26 @@ To build Nu, you will need to use the **nightly** version of the compiler.
Required dependencies:
* libssl (only needed on Linux)
* on Debian/Ubuntu: `apt install libssl-dev`
* pkg-config and libssl (only needed on Linux)
* on Debian/Ubuntu: `apt install pkg-config libssl-dev`
Optional dependencies:
* To use Nu with all possible optional features enabled, you'll also need the following:
* on Linux (on Debian/Ubuntu): `apt install libxcb-composite0-dev libx11-dev`
To install Nu via cargo:
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the beta compiler via `rustup install beta`):
```
cargo +nightly install nu
cargo +beta install nu
```
You can also install Nu with all the bells and whistles:
You can also install Nu with all the bells and whistles (be sure to have installed the [dependencies](https://book.nushell.sh/en/installation#dependencies) for your platform):
```
cargo +nightly install nu --features raw-key,clipboard
cargo +beta install nu --all-features
```
The following optional features are currently supported:
* **raw-key** - direct keyboard input, which creates a smoother experience in viewing text and binaries
* **clipboard** - integration with the native clipboard via the `clip` command
## Docker
If you want to pull a pre-built container, you can browse tags for the [nushell organization](https://quay.io/organization/nushell)
@ -70,13 +76,13 @@ To build the base image:
```bash
$ 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 .
```
```
Either way, you can run either container as follows:
@ -86,35 +92,44 @@ $ docker run -it nushell/nu
/> exit
```
The second container is a bit smaller, if size is important to you.
The second container is a bit smaller if the size is important to you.
## Packaging status
[![Packaging status](https://repology.org/badge/vertical-allrepos/nushell.svg)](https://repology.org/project/nushell/versions)
### Fedora
[COPR repo](https://copr.fedorainfracloud.org/coprs/atim/nushell/): `sudo dnf copr enable atim/nushell -y && sudo dnf install nushell -y`
# 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 list of objects, where each object represents an item in that directory. These values can be piped through a series of steps, in a series of commands called a 'pipeline'.
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
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_. Just as the Unix philosophy, Nu allows commands to output from stdout and read from stdin. Additionally, commands can output structured data (you can think of this as a third kind of stream). Commands that work in the pipeline fit into one of three categories
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_. Just as the Unix philosophy, Nu allows commands to output from stdout and read from stdin. Additionally, commands can output structured data (you can think of this as a third kind of stream). 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 consumes the output of the pipeline (eg, `autoview`)
* 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
--------+-----------+----------+--------+--------------+----------------
target | Directory | | 4.1 KB | 19 hours ago | 19 hours ago
images | Directory | | 4.1 KB | 2 weeks ago | a week ago
tests | Directory | | 4.1 KB | 2 weeks ago | 18 minutes ago
docs | Directory | | 4.1 KB | a week ago | a week ago
.git | Directory | | 4.1 KB | 2 weeks ago | 25 minutes ago
src | Directory | | 4.1 KB | 2 weeks ago | 25 minutes ago
.cargo | Directory | | 4.1 KB | 2 weeks ago | 2 weeks ago
--------+-----------+----------+--------+--------------+----------------
━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━
# │ 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
━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━
```
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:
@ -126,15 +141,16 @@ Because most of the time you'll want to see the output of a pipeline, `autoview`
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
C:\Code\nushell(master)> ps | where cpu > 0
------------------ +-----+-------+-------+----------
name | cmd | cpu | pid | status
------------------ +-----+-------+-------+----------
msedge.exe | - | 0.77 | 26472 | Runnable
nu.exe | - | 7.83 | 15473 | Runnable
SearchIndexer.exe | - | 82.17 | 23476 | Runnable
BlueJeans.exe | - | 4.54 | 10000 | Runnable
-------------------+-----+-------+-------+----------
/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
━━━┷━━━━━━━┷━━━━━━━━━━━━━━━━━┷━━━━━━━━━━┷━━━━━━━━━━
```
## Opening files
@ -143,36 +159,36 @@ Nu can load file and URL contents as raw text or as structured data (if it recog
```
/home/jonathan/Source/nushell(master)> open Cargo.toml
-----------------+------------------+-----------------
dependencies | dev-dependencies | package
-----------------+------------------+-----------------
[object Object] | [object Object] | [object Object]
-----------------+------------------+-----------------
━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━
bin │ dependencies │ dev-dependencies
──────────────────┼────────────────┼──────────────────
[table: 12 rows] │ [table: 1 row] │ [table: 1 row]
━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━
```
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
-------------+----------------------------+---------+---------+------+---------
[list List] | A shell for the GitHub era | 2018 | MIT | nu | 0.2.0
-------------+----------------------------+---------+---------+------+---------
━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━┯━━━━━━━━━┯━━━━━━┯━━━━━━━━━
authors │ description │ edition │ license │ name │ version
─────────────────┼────────────────────────────┼─────────┼─────────┼──────┼─────────
[table: 3 rows] │ A shell for the GitHub era │ 2018 │ MIT │ nu │ 0.4.0
━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━┷━━━━━━━━━┷━━━━━━┷━━━━━━━━━
```
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.2.0
0.4.0
```
Here we use the variable `$it` to refer to the value being piped to the external command.
## Shells
By default, Nu will work inside of a single directory and allow you to navigate around your filesystem. Sometimes, you'll want to work in multiple directories at the same time. For this, Nu offers a way of adding additional working directories that you can jump between.
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.
To do so, use the `enter` command, which will allow you create a new "shell" and enter it at the specified path. You can toggle between this new shell and the original shell with the `p` (for previous) and `n` (for next), allowing you to navigate around a ring buffer of shells. Once you're done with a shell, you can `exit` it and remove it from the ring buffer.
@ -180,11 +196,11 @@ Finally, to get a list of all the current shells, you can use the `shells` comma
## Plugins
Nu supports plugins that offer additional functionality to the shell and follow the same object model that built-in commands use. This allows you to extend nu for your needs.
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.
There are a few examples in the `plugins` directory.
Plugins are binaries that are available in your path and follow a "nu_plugin_*" naming convention. These binaries interact with nu via a simple JSON-RPC protocol where the command identifies itself and passes along its configuration, which then makes it available for use. 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.
Plugins are binaries that are available in your path and follow a `nu_plugin_*` naming convention. These binaries interact with nu via a simple JSON-RPC protocol where the command identifies itself and passes along its configuration, which then makes it available for use. 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
@ -196,7 +212,7 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat
* Nu's workflow and tools should have the usability in day-to-day experience of using a shell in 2019 (and beyond).
* Nu views data as both structured and unstructured. It is an object shell like PowerShell.
* Nu views data as both structured and unstructured. It is a structured shell like PowerShell.
* Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
@ -206,56 +222,63 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat
| ------------- | ------------- |
| cd path | Change to a new path |
| cp source path | Copy files |
| date (--utc) | Get the current datetime |
| fetch url | Fetch contents from a url and retrieve data as a table if possible |
| help | Display help information about commands |
| ls (path) | View the contents of the current or given path |
| mkdir path | Make directories, creates intermediary directories as required. |
| mv source target | Move files or directories. |
| date (--utc) | Get the current datetime |
| open filename | Load a file into a cell, convert to table if possible (avoid by appending '--raw') |
| post url body (--user <user>) (--password <password>) | Post content to a url and retrieve data as a table if possible |
| ps | View current processes |
| sys | View information about the current system |
| which filename | Finds a program file. |
| open {filename or url} | Load a file into a cell, convert to table if possible (avoid by appending '--raw') |
| post url body (--user <user>) (--password <password>) | Post content to a url and retrieve data as a table if possible |
| rm {file or directory} | Remove a file, (for removing directory append '--recursive') |
| version | Display Nu version |
## Shell commands
| command | description |
| ------- | ----------- |
| exit (--now) | Exit the current shell (or all shells) |
| enter (path) | Create a new shell and begin at this path |
| p | Go to previous shell |
| n | Go to next shell |
| shells | Display the list of current shells |
| help | Display help information about commands |
| version | Display Nu version |
## Filters on tables (structured data)
| command | description |
| ------------- | ------------- |
| pick ...columns | Down-select table to only these columns |
| reject ...columns | Remove the given columns from the table |
| add column-or-column-path value | Add a new column to the table |
| append row-data | Append a row to the end of the table |
| count | Show the total number of rows |
| edit column-or-column-path value | Edit an existing column to have a new value |
| embed column | Creates a new table of one column with the given name, and places the current table inside of it |
| first amount | Show only the first number of rows |
| get column-or-column-path | Open column and get data from the corresponding cells |
| sort-by ...columns | Sort by the given columns |
| where condition | Filter table to match the condition |
| inc (field) | Increment a value or version. Optional use the field of a table |
| add field value | Add a new field to the table |
| embed field | Embeds a new field to the table |
| sum | Sum a column of values |
| edit field value | Edit an existing field to have a new value |
| group-by column | Creates a new table with the data from the table rows grouped by the column given |
| inc (column-or-column-path) | Increment a value or version. Optionally use the column of a table |
| last amount | Show only the last number of rows |
| nth row-number | Return only the selected row |
| pick ...columns | Down-select table to only these columns |
| pivot --header-row <headers> | Pivot the tables, making columns into rows and vice versa |
| prepend row-data | Prepend a row to the beginning of the table |
| reject ...columns | Remove the given columns from the table |
| reverse | Reverses the table. |
| skip amount | Skip a number of rows |
| skip-while condition | Skips rows while the condition matches. |
| first amount | Show only the first number of rows |
| last amount | Show only the last number of rows |
| nth row-number | Return only the selected row |
| str (field) | Apply string function. Optional use the field of a table |
| sort-by ...columns | Sort by the given columns |
| str (column) | Apply string function. Optionally use the column of a table |
| sum | Sum a column of values |
| tags | Read the tags (metadata) for values |
| from-array | Expand an array/list into rows |
| to-array | Collapse rows into a single list |
| to-json | Convert table into .json text |
| to-toml | Convert table into .toml text |
| to-yaml | Convert table into .yaml text |
| to-bson | Convert table into .bson text |
| to-csv | Convert table into .csv text |
| to-bson | Convert table into .bson binary data |
| to-tsv | Convert table into .tsv text |
| to-csv | Convert table into .csv text |
| to-json | Convert table into .json text |
| to-sqlite | Convert table to sqlite .db binary data |
| reverse | Reverse the rows of a table |
| to-toml | Convert table into .toml text |
| to-tsv | Convert table into .tsv text |
| to-url | Convert table to a urlencoded string |
| to-yaml | Convert table into .yaml text |
| where condition | Filter table to match the condition |
## Filters on text (unstructured data)
| command | description |
@ -265,13 +288,16 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat
| from-ini | Parse text as .ini and create table |
| from-json | Parse text as .json and create table |
| from-sqlite | Parse binary data as sqlite .db and create table |
| from-ssv --minimum-spaces <minimum number of spaces to count as a separator> | Parse text as space-separated values and create table |
| from-toml | Parse text as .toml and create table |
| from-tsv | Parse text as .tsv and create table |
| from-url | Parse urlencoded string and create a table |
| from-xml | Parse text as .xml and create a table |
| from-yaml | Parse text as a .yaml/.yml and create a table |
| lines | Split single string into rows, one per line |
| read pattern | Convert text to a table by matching the given pattern |
| size | Gather word count statistics on the text |
| split-column sep ...fields | Split row contents across multiple columns via the separator |
| split-column sep ...column-names | Split row contents across multiple columns via the separator, optionally give the columns names |
| split-row sep | Split row contents over multiple rows via the separator |
| trim | Trim leading and following whitespace from text data |
| {external-command} $it | Run external command with given arguments, replacing $it with each row text |
@ -280,13 +306,12 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat
| command | description |
| ------------- | ------------- |
| autoview | View the contents of the pipeline as a table or list |
| binaryview | Autoview of binary data |
| clip | Copy the contents of the pipeline to the copy/paste buffer |
| binaryview | Autoview of binary data (optional feature) |
| clip | Copy the contents of the pipeline to the copy/paste buffer (optional feature) |
| save filename | Save the contents of the pipeline to a file |
| table | View the contents of the pipeline as a table |
| textview | Autoview of text data |
| tree | View the contents of the pipeline as a tree |
| vtable | View the contents of the pipeline as a vertical (rotated) table |
| tree | View the contents of the pipeline as a tree (optional feature) |
# License

39
build.rs Normal file
View file

@ -0,0 +1,39 @@
use serde::Deserialize;
use std::collections::HashMap;
use std::collections::HashSet;
use std::env;
use std::path::Path;
#[derive(Deserialize)]
struct Feature {
#[allow(unused)]
description: String,
enabled: bool,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = env::var("CARGO_MANIFEST_DIR").unwrap();
let all_on = env::var("NUSHELL_ENABLE_ALL_FLAGS").is_ok();
let flags: HashSet<String> = env::var("NUSHELL_ENABLE_FLAGS")
.map(|s| s.split(",").map(|s| s.to_string()).collect())
.unwrap_or_else(|_| HashSet::new());
if all_on && !flags.is_empty() {
println!(
"cargo:warning={}",
"Both NUSHELL_ENABLE_ALL_FLAGS and NUSHELL_ENABLE_FLAGS were set. You don't need both."
);
}
let path = Path::new(&input).join("features.toml");
let toml: HashMap<String, Feature> = toml::from_str(&std::fs::read_to_string(path)?)?;
for (key, value) in toml.iter() {
if value.enabled == true || all_on || flags.contains(key) {
println!("cargo:rustc-cfg={}", key);
}
}
Ok(())
}

5
debian/changelog vendored Normal file
View file

@ -0,0 +1,5 @@
nu (0.2.0-1) unstable; urgency=low
* Initial release
-- Jan Koprowski <jan.koprowski@gmail.com> Wed, 04 Sep 2019 21:38:44 +0200

1
debian/compat vendored Normal file
View file

@ -0,0 +1 @@
10

18
debian/control vendored Normal file
View file

@ -0,0 +1,18 @@
Source: nu
Section: shells
Priority: optional
Maintainer: Jan Koprowski <jan.koprowski@gmail.com>
Build-Depends: debhelper (>= 10)
Standards-Version: 4.1.2
Homepage: https://github.com/nushell/nushell
Vcs-Git: https://github.com/nushell/nushell.git
Vcs-Browser: https://github.com/nushell/nushell
Package: nu
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: A modern shell for the GitHub era
The goal of this project is to take the Unix
philosophy of shells, where pipes connect simple
commands together, and bring it to the modern
style of development.

32
debian/copyright vendored Normal file
View file

@ -0,0 +1,32 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: nu
Source: https://github.com/nushell/nushell
Files: *
Copyright: 2019 Yehuda Katz
2019 Jonathan Turner
License: MIT
Files: debian/*
Copyright: 2019 Yehuda Katz
2019 Jonathan Turner
License: MIT
License: MIT
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
.
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

11
debian/install vendored Normal file
View file

@ -0,0 +1,11 @@
target/release/nu usr/bin
target/release/nu_plugin_binaryview usr/bin
target/release/nu_plugin_edit usr/bin
target/release/nu_plugin_inc usr/bin
target/release/nu_plugin_skip usr/bin
target/release/nu_plugin_str usr/bin
target/release/nu_plugin_sum usr/bin
target/release/nu_plugin_sys usr/bin
target/release/nu_plugin_textview usr/bin
target/release/nu_plugin_tree usr/bin
target/release/nu_plugin_docker usr/bin

8
debian/postinst vendored Normal file
View file

@ -0,0 +1,8 @@
#! /bin/bash
if [ "$1" = configure ] && which add-shell >/dev/null
then
add-shell /usr/bin/nu
fi
exit 0

17
debian/postrm vendored Normal file
View file

@ -0,0 +1,17 @@
#!/bin/sh
set -e
case "$1" in
upgrade|failed-upgrade|abort-install|abort-upgrade)
;;
remove|purge|disappear)
if which remove-shell >/dev/null && [ -f /etc/shells ]; then
remove-shell /usr/bin/nu
fi
;;
*)
echo "postrm called with unknown argument \`$1'" >&2
exit 1
;;
esac

25
debian/rules vendored Executable file
View file

@ -0,0 +1,25 @@
#!/usr/bin/make -f
# See debhelper(7) (uncomment to enable)
# output every command that modifies files on the build system.
#export DH_VERBOSE = 1
# see FEATURE AREAS in dpkg-buildflags(1)
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
# see ENVIRONMENT in dpkg-buildflags(1)
# package maintainers to append CFLAGS
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
# package maintainers to append LDFLAGS
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
%:
dh $@
# dh_make generated override targets
# This is example for Cmake (See https://bugs.debian.org/641051 )
#override_dh_auto_configure:
# dh_auto_configure -- # -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH)

1
debian/source/format vendored Normal file
View file

@ -0,0 +1 @@
3.0 (quilt)

View file

@ -1,5 +1,9 @@
ARG FROMTAG=latest
ARG FROMTAG=latest
FROM quay.io/nushell/nu-base:${FROMTAG} as base
FROM rust:1.37-slim
FROM ubuntu:18.04
COPY --from=base /usr/local/bin/nu /usr/local/bin/nu
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && apt-get install -y libssl-dev \
pkg-config
ENTRYPOINT ["nu"]
CMD ["-l", "info"]

View file

@ -1,4 +1,4 @@
FROM rust:1.37-slim
FROM ubuntu:18.04
# docker build -f docker/Dockerfile.nu-base -t nushell/nu-base .
# docker run -it nushell/nu-base
@ -6,13 +6,20 @@ FROM rust:1.37-slim
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && apt-get install -y libssl-dev \
libxcb-composite0-dev \
libx11-dev \
pkg-config
RUN USER=root cargo new --bin /code
pkg-config \
curl
ARG RELEASE=false
WORKDIR /code
ADD . /code
RUN cargo build --release && cargo run --release
RUN cp target/release/nu /usr/local/bin
COPY ./rust-toolchain ./rust-toolchain
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain `cat rust-toolchain`
ENV PATH=/root/.cargo/bin:$PATH
COPY . /code
RUN echo "##vso[task.prependpath]/root/.cargo/bin" && \
rustc -Vv && \
if $RELEASE; then cargo build --release; \
cp target/release/nu /usr/local/bin; \
else cargo build; \
cp target/debug/nu /usr/local/bin; fi;
ENTRYPOINT ["nu"]
CMD ["-l", "info"]

View file

@ -0,0 +1,7 @@
ARG base
FROM ${base}
ARG artifact
COPY ${artifact} /bin/
ENTRYPOINT ["/bin/nu"]

View file

@ -0,0 +1,15 @@
ARG base
FROM debian:stable-slim AS patch
FROM ${base}
ARG artifact
COPY ${artifact} /bin/
COPY --from=patch \
/lib/x86_64-linux-gnu/libz.so.1 \
/lib/x86_64-linux-gnu/libdl.so.2 \
/lib/x86_64-linux-gnu/librt.so.1 \
/lib/x86_64-linux-gnu/libgcc_s.so.1 \
/lib/x86_64-linux-gnu/
ENTRYPOINT ["/bin/nu"]

View file

@ -0,0 +1,12 @@
ARG base
FROM debian:stable-slim AS patch
FROM ${base}
ARG artifact
COPY ${artifact} /bin/
COPY --from=patch \
/lib/x86_64-linux-gnu/libz.so.1 \
/lib/x86_64-linux-gnu/
ENTRYPOINT ["/bin/nu"]

View file

@ -0,0 +1,11 @@
version: '3'
services:
nushell:
image: ${REGISTRY}/nu:${TAG}
build:
context: ..
dockerfile: docker/Package${PATCH}.Dockerfile
args:
base: ${BASE_IMAGE}
artifact: ${NU_BINS}

View file

@ -0,0 +1,17 @@
# docker build -f docker/packaging/Dockerfile.ubuntu-bionic .
ARG FROMTAG=latest
FROM quay.io/nushell/nu-base:${FROMTAG}
RUN apt-get update && apt-get install -y \
devscripts \
debhelper
COPY debian /code/debian
RUN rustc -Vv && cargo build --release && \
cp README.md debian/README.Debian && \
debuild -b -us -uc -i && \
dpkg -i ../nu_0.2.0-1_amd64.deb && \
chsh -s /usr/bin/nu && \
echo 'ls | get name | echo $it' | /usr/bin/nu

View file

@ -0,0 +1,55 @@
# Packaging
This directory contains docker images used for creating packages for different distribution.
## How to use this docker files?
Start with:
```bash
$ docker build -f docker/packaging/Dockerfile.ubuntu-bionic -t nushell/package:ubuntu-bionic .
```
after building the image please run container:
```bash
$ docker run -td --rm --name nushell_package_ubuntu_bionic nushell/package:ubuntu-bionic
```
and copy deb package from inside:
```bash
$ docker cp nushell_package_ubuntu_bionic:/nu_0.2.0-1_amd64.deb .
```
or shell inside, and test install:
```bash
$ docker exec -it nushell_package_ubuntu_bionic bash
$ dpkg -i /nu_0.2.0-1_amd64.deb
(Reading database ... 25656 files and directories currently installed.)
Preparing to unpack /nu_0.2.0-1_amd64.deb ...
Unpacking nu (0.2.0-1) over (0.2.0-1) ...
Setting up nu (0.2.0-1) ...
```
When you are finished, exit and stop the container. It will be removed since we
used `--rm`.
```bash
$ docker stop nushell_package_ubuntu_bionic
```
## What should be done
* We should run sbuild command to create chroot and then install dpkg.
For two reasons. First: we want to use the same tools as Ubuntu package builders
to handle the cornercases. Second: we want to test dpkg requirements.
https://github.com/nushell/nushell/issues/681
* File debian/changelog file should be generated based on git history.
https://github.com/nushell/nushell/issues/682
* Building package and nu version should be parametrized.
https://github.com/nushell/nushell/issues/683

25
docs/commands/README.md Normal file
View file

@ -0,0 +1,25 @@
# How do I get started?
Pick any command from the checklist and write a comment acknowledging you started work.
# Instructions for documenting a Nu command of your choosing
Name the file after the command, like so:
`command.md`
Example: If you want to add documentation for the Nu command `enter`, create a file named `enter.md`, write documentation, save it at `/docs/commands/[your_command_picked].md` as and create your pull request.
# What kind of documentation should I write?
Anything you want that you believe it *best* documents the command and the way you would like to see it. Here are some of our ideas of documentation we would *love* to see (feel free to add yours):
* Examples of using the command (max creativity welcomed!)
* Description of the command.
* Command usage.
# Anything else?
Of course! (These are drafts) so feel free to leave feedback and suggestions in the same file.
Happy Documenting.

28
docs/commands/add.md Normal file
View file

@ -0,0 +1,28 @@
# add
This command adds a column to any table output. The first parameter takes the heading, the second parameter takes the value for all the rows.
## Examples
```shell
> ls | add is_on_a_computer yes_obviously
━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━┯━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━
# │ name │ type │ readonly │ size │ accessed │ modified │ is_on_a_computer
───┼────────────────────────────┼──────┼──────────┼────────┼───────────┼───────────┼──────────────────
0 │ zeusiscrazy.txt │ File │ │ 556 B │ a day ago │ a day ago │ yes_obviously
1 │ coww.txt │ File │ │ 24 B │ a day ago │ a day ago │ yes_obviously
2 │ randomweirdstuff.txt │ File │ │ 197 B │ a day ago │ a day ago │ yes_obviously
3 │ abaracadabra.txt │ File │ │ 401 B │ a day ago │ a day ago │ yes_obviously
4 │ youshouldeatmorecereal.txt │ File │ │ 768 B │ a day ago │ a day ago │ yes_obviously
━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━┷━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━
```
```shell
> shells | add os linux_on_this_machine
━━━┯━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━
# │ │ name │ path │ os
───┼───┼────────────┼────────────────────────────────┼───────────────────────
0 │ X │ filesystem │ /home/shaurya/stuff/expr/stuff │ linux_on_this_machine
1 │ │ filesystem │ / │ linux_on_this_machine
━━━┷━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━
```

45
docs/commands/average.md Normal file
View file

@ -0,0 +1,45 @@
# average
This command allows you to calculate the average of values in a column.
## Examples
To get the average of the file sizes in a directory, simply pipe the size column from the ls command to the average command.
```shell
> ls | get size | average
━━━━━━━━━
<value>
━━━━━━━━━
2282.727272727273
━━━━━━━━━
```
```shell
> pwd | split-row / | size | get chars | average
━━━━━━━━━
<value>
━━━━━━━━━
5.250000000000000
━━━━━━━━━
```
Note that average only works for integer and byte values. If the shell doesn't recognize the values in a column as one of those types, it will return an error.
One way to solve this is to convert each row to an integer when possible and then pipe the result to `average`
```shell
> open tests/fixtures/formats/caco3_plastics.csv | get tariff_item | average
error: Unrecognized type in stream: Primitive(String("2509000000"))
- shell:1:0
1 | open tests/fixtures/formats/caco3_plastics.csv | get tariff_item | average
| ^^^^ source
```
```shell
> open tests/fixtures/formats/caco3_plastics.csv | get tariff_item | str --to-int | average
━━━━━━━━━━━━━━━━━━━
<value>
───────────────────
3239404444.000000
━━━━━━━━━━━━━━━━━━━
```

33
docs/commands/cd.md Normal file
View file

@ -0,0 +1,33 @@
# cd
If you didn't already know, the `cd` command is very simple. It stands for 'change directory' and it does exactly that. It changes the current directory to the one specified. If no directory is specified, it takes you to the home directory. Additionally, using `cd ..` takes you to the parent directory.
## Examples
```shell
/home/username> cd Desktop
/home/username/Desktop> now your current directory has been changed
```
```shell
/home/username/Desktop/nested/folders> cd ..
/home/username/Desktop/nested> cd ..
/home/username/Desktop> cd ../Documents/school_related
/home/username/Documents/school_related> cd ../../..
/home/>
```
```shell
/home/username/Desktop/super/duper/crazy/nested/folders> cd
/home/username> cd ../../usr
/usr> cd
/home/username>
```
Using `cd -` will take you to the previous directory:
```shell
/home/username/Desktop/super/duper/crazy/nested/folders> cd
/home/username> cd -
/home/username/Desktop/super/duper/crazy/nested/folders> cd
```

34
docs/commands/date.md Normal file
View file

@ -0,0 +1,34 @@
# date
Use `date` to get the current date and time. Defaults to local timezone but you can get it in UTC too.
## Flags
--utc
Returns the current date and time in UTC
--local
Returns the current date and time in your local timezone
## Examples
```shell
> date
━━━━━━┯━━━━━━━┯━━━━━┯━━━━━━┯━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━
year │ month │ day │ hour │ minute │ second │ timezone
──────┼───────┼─────┼──────┼────────┼────────┼──────────
2019 │ 9 │ 30 │ 21 │ 52 │ 30 │ -03:00
━━━━━━┷━━━━━━━┷━━━━━┷━━━━━━┷━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━
> date --utc
━━━━━━┯━━━━━━━┯━━━━━┯━━━━━━┯━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━
year │ month │ day │ hour │ minute │ second │ timezone
──────┼───────┼─────┼──────┼────────┼────────┼──────────
2019 │ 10 │ 1 │ 0 │ 52 │ 32 │ UTC
━━━━━━┷━━━━━━━┷━━━━━┷━━━━━━┷━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━
> date --local
━━━━━━┯━━━━━━━┯━━━━━┯━━━━━━┯━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━
year │ month │ day │ hour │ minute │ second │ timezone
──────┼───────┼─────┼──────┼────────┼────────┼──────────
2019 │ 9 │ 30 │ 21 │ 52 │ 34 │ -03:00
━━━━━━┷━━━━━━━┷━━━━━┷━━━━━━┷━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━
```

12
docs/commands/echo.md Normal file
View file

@ -0,0 +1,12 @@
# echo
Use `echo` to repeat arguments back to the user
## Examples
```shell
> echo Hello world
Hello world
> echo "Hello, world!"
Hello, world!
```

45
docs/commands/edit.md Normal file
View file

@ -0,0 +1,45 @@
# edit
Edits an existing column on a table. First parameter is the column to edit and the second parameter is the value to put.
## Examples
```shell
> ls
━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━┯━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━
# │ name │ type │ readonly │ size │ accessed │ modified
───┼────────────────────────────┼──────┼──────────┼────────┼───────────┼───────────
0 │ zeusiscrazy.txt │ File │ │ 556 B │ a day ago │ a day ago
1 │ coww.txt │ File │ │ 24 B │ a day ago │ a day ago
2 │ randomweirdstuff.txt │ File │ │ 197 B │ a day ago │ a day ago
3 │ abaracadabra.txt │ File │ │ 401 B │ a day ago │ a day ago
4 │ youshouldeatmorecereal.txt │ File │ │ 768 B │ a day ago │ a day ago
━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━┷━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━
> ls | edit modified neverrrr
━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━┯━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━
# │ name │ type │ readonly │ size │ accessed │ modified
───┼────────────────────────────┼──────┼──────────┼────────┼───────────┼──────────
0 │ zeusiscrazy.txt │ File │ │ 556 B │ a day ago │ neverrrr
1 │ coww.txt │ File │ │ 24 B │ a day ago │ neverrrr
2 │ randomweirdstuff.txt │ File │ │ 197 B │ a day ago │ neverrrr
3 │ abaracadabra.txt │ File │ │ 401 B │ a day ago │ neverrrr
4 │ youshouldeatmorecereal.txt │ File │ │ 768 B │ a day ago │ neverrrr
━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━┷━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━
```
```shell
> shells
━━━┯━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# │ │ name │ path
───┼───┼────────────┼────────────────────────────────
0 │ X │ filesystem │ /home/username/stuff/expr/stuff
1 │ │ filesystem │ /
━━━┷━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
> shells | edit " " X | edit path /
━━━┯━━━┯━━━━━━━━━━━━┯━━━━━━
# │ │ name │ path
───┼───┼────────────┼──────
0 │ X │ filesystem │ /
1 │ X │ filesystem │ /
━━━┷━━━┷━━━━━━━━━━━━┷━━━━━━
```

39
docs/commands/enter.md Normal file
View file

@ -0,0 +1,39 @@
# enter
This command creates a new shell and begin at this path.
## Examples
```shell
/home/foobar> cat user.json
{
"Name": "Peter",
"Age": 30,
"Telephone": 88204828,
"Country": "Singapore"
}
/home/foobar> enter user.json
/> ls
━━━━━━━┯━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━
Name │ Age │ Telephone │ Country
───────┼─────┼───────────┼───────────
Peter │ 30 │ 88204828 │ Singapore
━━━━━━━┷━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━
/> exit
/home/foobar>
```
It also provides the ability to work with multiple directories at the same time. This command will allow you to create a new "shell" and enter it at the specified path. You can toggle between this new shell and the original shell with the `p` (for previous) and `n` (for next), allowing you to navigate around a ring buffer of shells. Once you're done with a shell, you can `exit` it and remove it from the ring buffer.
```shell
/> enter /tmp
/tmp> enter /usr
/usr> enter /bin
/bin> enter /opt
/opt> p
/bin> p
/usr> p
/tmp> p
/> n
/tmp>
```

27
docs/commands/env.md Normal file
View file

@ -0,0 +1,27 @@
# env
The `env` command prints to terminal the environment of nushell
This includes
- cwd : the path to the current working the directory (`cwd`),
- home : the path to the home directory
- config : the path to the config file for nushell
- history : the path to the nushell command history
- temp : the path to the temp file
- vars : descriptor variable for the table
`env` does not take any arguments, and ignores any arguments given.
## Examples -
```shell
/home/username/mynushell/docs/commands(master)> env
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━┯━━━━━━━━━━━━━━━━
cwd │ home │ config │ history │ temp │ vars
────────────────────────────────────────┼────────────────┼───────────────────────────────────────┼────────────────────────────────────────────┼──────┼────────────────
/home/username/mynushell/docs/commands │ /home/username │ /home/username/.config/nu/config.toml │ /home/username/.local/share/nu/history.txt │ /tmp │ [table: 1 row]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━┷━━━━━━━━━━━━━━━━
```

30
docs/commands/exit.md Normal file
View file

@ -0,0 +1,30 @@
# exit
Exits the nu shell. If you have multiple nu shells, use `exit --now` to exit all of them.
## Examples
```shell
> exit
```
```
/home/username/stuff/books> shells
---+---+------------+----------------------------
# | | name | path
---+---+------------+----------------------------
0 | | filesystem | /home/username/stuff/notes
1 | | filesystem | /home/username/stuff/videos
2 | X | filesystem | /home/username/stuff/books
---+---+------------+----------------------------
/home/username/stuff/books> exit
/home/username/stuff/videos> shells
---+---+------------+----------------------------
# | | name | path
---+---+------------+----------------------------
0 | | filesystem | /home/username/stuff/notes
1 | X | filesystem | /home/username/stuff/videos
---+---+------------+----------------------------
/home/username/stuff/videos> exit --now
exits both the shells
```

32
docs/commands/fetch.md Normal file
View file

@ -0,0 +1,32 @@
# fetch
This command loads from a URL into a cell, convert it to table if possible (avoid by appending `--raw` flag)
## Examples
```shell
> fetch http://headers.jsontest.com
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━
X-Cloud-Trace-Context │ Accept │ Host │ Content-Length │ user-agent
───────────────────────────────────────────────────────┼────────┼──────────────────────┼────────────────┼─────────────────────────
aeee1a8abf08820f6fe19d114dc3bb87/16772233176633589121 │ */* │ headers.jsontest.com │ 0 │ curl/7.54.0 isahc/0.7.1
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━
> fetch http://headers.jsontest.com --raw
{
"X-Cloud-Trace-Context": "aeee1a8abf08820f6fe19d114dc3bb87/16772233176633589121",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36",
"Host": "headers.jsontest.com",
"Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8"
}
```
```shell
> fetch https://www.jonathanturner.org/feed.xml
━━━━━━━━━━━━━━━━
rss
────────────────
[table: 1 row]
━━━━━━━━━━━━━━━━
```

28
docs/commands/first.md Normal file
View file

@ -0,0 +1,28 @@
# first
Use `first` to retrieve the first "n" rows of a table. `first` has a required amount parameter that indicates how many rows you would like returned. If more than one row is returned, an index column will be included showing the row number.
## Examples
```shell
> ps | first 1
━━━━━━━┯━━━━━━━━━━━━━━┯━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━
pid │ name │ status │ cpu
───────┼──────────────┼─────────┼───────────────────
60358 │ nu_plugin_ps │ Running │ 5.399802999999999
━━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━
```
```shell
> ps | first 5
━━━┯━━━━━━━┯━━━━━━━━━━━━━━┯━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━
# │ pid │ name │ status │ cpu
───┼───────┼──────────────┼─────────┼───────────────────
0 │ 60754 │ nu_plugin_ps │ Running │ 4.024156000000000
1 │ 60107 │ quicklookd │ Running │ 0.000000000000000
2 │ 59356 │ nu │ Running │ 0.000000000000000
3 │ 59216 │ zsh │ Running │ 0.000000000000000
4 │ 59162 │ vim │ Running │ 0.000000000000000
━━━┷━━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━
```

47
docs/commands/from-csv.md Normal file
View file

@ -0,0 +1,47 @@
# from-csv
Converts csv data into table. Use this when nushell cannot dertermine the input file extension.
## Example
Let's say we have the following file :
```shell
> cat pets.txt
animal, name, age
cat, Tom, 7
dog, Alfred, 10
chameleon, Linda, 1
```
`pets.txt` is actually a .csv file but it has the .txt extension, `open` is not able to convert it into a table :
```shell
> open pets.txt
animal, name, age
cat, Tom, 7
dog, Alfred, 10
chameleon, Linda, 1
```
To get a table from `pets.txt` we need to use the `from-csv` command :
```shell
> open pets.txt | from-csv
━━━┯━━━━━━━━━━━┯━━━━━━━━━┯━━━━━━
# │ animal │ name │ age
───┼───────────┼─────────┼──────
0 │ cat │ Tom │ 7
1 │ dog │ Alfred │ 10
2 │ chameleon │ Linda │ 1
━━━┷━━━━━━━━━━━┷━━━━━━━━━┷━━━━━━
```
To ignore the csv headers use `--headerless` :
```shell
━━━┯━━━━━━━━━━━┯━━━━━━━━━┯━━━━━━━━━
# │ Column1 │ Column2 │ Column3
───┼───────────┼─────────┼─────────
0 │ dog │ Alfred │ 10
1 │ chameleon │ Linda │ 1
━━━┷━━━━━━━━━━━┷━━━━━━━━━┷━━━━━━━━━
```

View file

@ -0,0 +1,23 @@
# from-toml
Converts toml data into table. Use this when nushell cannot dertermine the input file extension.
## Example
Let's say we have the following Rust .lock file :
```shell
> open Cargo.lock
# This file is automatically @generated by Cargo.
# It is not intended for manual editing. [[package]] name = "adler32" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index"
...
```
The "Cargo.lock" file is actually a .toml file, but the file extension isn't .toml. That's okay, we can use the `from-toml` command :
```shell
> open Cargo.lock | from-toml
━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━
metadata │ package
────────────────┼───────────────────
[table: 1 row] │ [table: 154 rows]
━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━
```

47
docs/commands/help.md Normal file
View file

@ -0,0 +1,47 @@
# help
Use `help` for more information on a command.
Use `help commands` to list all availble commands.
Use `help <command name>` to display help about a particular command.
## Examples
```shell
> help
Welcome to Nushell.
Here are some tips to help you get started.
* help commands - list all available commands
* help <command name> - display help about a particular command
You can also learn more at https://book.nushell.sh
```
```shell
> help commands
━━━━┯━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# │ name │ description
────┼──────────────┼────────────────────────────────────────────────────────────────────────────────────────
0 │ add │ Add a new field to the table.
1 │ autoview │ View the contents of the pipeline as a table or list.
2 │ cd │ Change to a new path.
3 │ config │ Configuration management.
4 │ cp │ Copy files.
5 │ date │ Get the current datetime.
...
70 │ trim │ Trim leading and following whitespace from text data.
71 │ version │ Display Nu version
72 │ where │ Filter table to match the condition.
73 │ which │ Finds a program file.
━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
```shell
> help cd
Change to a new path.
Usage:
> cd (directory)
```

31
docs/commands/inc.md Normal file
View file

@ -0,0 +1,31 @@
# inc
This command increments the value of variable by one.
## Examples
```shell
> open rustfmt.toml
---------
edition
---------
2018
---------
> open rustfmt.toml | inc edition
---------
edition
---------
2019
---------
```
```shell
> open Cargo.toml | get package.version
0.1.3
> open Cargo.toml | inc package.version --major | get package.version
1.0.0
> open Cargo.toml | inc package.version --minor | get package.version
0.2.0
> open Cargo.toml | inc package.version --patch | get package.version
0.1.4
```

29
docs/commands/last.md Normal file
View file

@ -0,0 +1,29 @@
# last
Use `last` to retrieve the last "n" rows of a table. `last` has a required amount parameter that indicates how many rows you would like returned. If more than one row is returned, an index column will be included showing the row number. `last` does not alter the order of the rows of the table.
## Examples
```shell
> ps | last 1
━━━━━┯━━━━━━━━━━━━━┯━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━
pid │ name │ status │ cpu
─────┼─────────────┼─────────┼───────────────────
121 │ loginwindow │ Running │ 0.000000000000000
━━━━━┷━━━━━━━━━━━━━┷━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━
```
```shell
> ps | last 5
━━━┯━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━
# │ pid │ name │ status │ cpu
───┼─────┼────────────────┼─────────┼───────────────────
0 │ 360 │ CommCenter │ Running │ 0.000000000000000
1 │ 358 │ distnoted │ Running │ 0.000000000000000
2 │ 356 │ UserEventAgent │ Running │ 0.000000000000000
3 │ 354 │ cfprefsd │ Running │ 0.000000000000000
4 │ 121 │ loginwindow │ Running │ 0.000000000000000
━━━┷━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━
```

28
docs/commands/lines.md Normal file
View file

@ -0,0 +1,28 @@
# lines
This command takes a string from a pipeline as input, and returns a table where each line of the input string is a row in the table. Empty lines are ignored. This command is capable of feeding other commands, such as `nth`, with its output.
## Usage
```shell
> [input-command] | lines
```
## Examples
Basic usage:
```shell
> printf "Hello\nWorld!\nLove, nushell." | lines
━━━┯━━━━━━━━━━━━━━━━
# │ value
───┼────────────────
0 │ Hello
1 │ World!
2 │ Love, nushell.
━━━┷━━━━━━━━━━━━━━━━
```
One useful application is piping the contents of file into `lines`. This example extracts a certain line from a given file.
```shell
> cat lines.md | lines | nth 6
## Examples
```
Similarly to this example, `lines` can be used to extract certain portions of or apply transformations to data returned by any program which returns a string.

31
docs/commands/nth.md Normal file
View file

@ -0,0 +1,31 @@
# nth
This command returns the nth row of a table, starting from 0.
If the number given is less than 0 or more than the number of rows, nothing is returned.
## Usage
```shell
> [input-command] | nth [row-number]
```
## Examples
```shell
> ls
━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━
# │ name │ type │ readonly │ size │ accessed │ modified
───┼────────────┼───────────┼──────────┼────────┼───────────────┼───────────────
0 │ Cargo.toml │ File │ │ 239 B │ 2 minutes ago │ 2 minutes ago
1 │ .git │ Directory │ │ 4.1 KB │ 2 minutes ago │ 2 minutes ago
2 │ .gitignore │ File │ │ 19 B │ 2 minutes ago │ 2 minutes ago
3 │ src │ Directory │ │ 4.1 KB │ 2 minutes ago │ 2 minutes ago
━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━
> ls | nth 0
━━━━━━━━━━━━┯━━━━━━┯━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━
name │ type │ readonly │ size │ accessed │ modified
────────────┼──────┼──────────┼────────┼───────────────┼───────────────
Cargo.toml │ File │ │ 239 B │ 2 minutes ago │ 2 minutes ago
━━━━━━━━━━━━┷━━━━━━┷━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━
> ls | nth 5
```

95
docs/commands/open.md Normal file
View file

@ -0,0 +1,95 @@
# open
Loads a file into a cell, convert it to table if possible (avoid by appending `--raw` flag)
## Example
```shell
> cat user.yaml
- Name: Peter
Age: 30
Telephone: 88204828
Country: Singapore
- Name: Michael
Age: 42
Telephone: 44002010
Country: Spain
- Name: Will
Age: 50
Telephone: 99521080
Country: Germany
> open user.yaml
━━━┯━━━━━━━━━┯━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━
# │ Name │ Age │ Telephone │ Country
───┼─────────┼─────┼───────────┼───────────
0 │ Peter │ 30 │ 88204828 │ Singapore
1 │ Michael │ 42 │ 44002010 │ Spain
2 │ Will │ 50 │ 99521080 │ Germany
━━━┷━━━━━━━━━┷━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━
> open user.yaml --raw
- Name: Peter
Age: 30
Telephone: 88204828
Country: Singapore
- Name: Michael
Age: 42
Telephone: 44002010
Country: Spain
- Name: Will
Age: 50
Telephone: 99521080
Country: Germany
```
```shell
> cat user.json
[
{
"Name": "Peter",
"Age": 30,
"Telephone": 88204828,
"Country": "Singapore"
},
{
"Name": "Michael",
"Age": 42,
"Telephone": 44002010,
"Country": "Spain"
},
{
"Name": "Will",
"Age": 50,
"Telephone": 99521080,
"Country": "Germany"
}
]
> open user.json
━━━┯━━━━━━━━━┯━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━
# │ Name │ Age │ Telephone │ Country
───┼─────────┼─────┼───────────┼───────────
0 │ Peter │ 30 │ 88204828 │ Singapore
1 │ Michael │ 42 │ 44002010 │ Spain
2 │ Will │ 50 │ 99521080 │ Germany
━━━┷━━━━━━━━━┷━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━
> open user.json --raw
[
{
"Name": "Peter",
"Age": 30,
"Telephone": 88204828,
"Country": "Singapore"
},
{
"Name": "Michael",
"Age": 42,
"Telephone": 44002010,
"Country": "Spain"
},
{
"Name": "Will",
"Age": 50,
"Telephone": 99521080,
"Country": "Germany"
}
]
```

51
docs/commands/reverse.md Normal file
View file

@ -0,0 +1,51 @@
# reverse
This command reverses the order of the elements in a sorted table.
## Examples
```shell
> ls | sort-by name
━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━┯━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━
# │ name │ type │ readonly │ size │ accessed │ modified
───┼────────────────────────────┼──────┼──────────┼────────┼────────────────┼────────────────
0 │ abaracadabra.txt │ File │ │ 401 B │ 23 minutes ago │ 16 minutes ago
1 │ coww.txt │ File │ │ 24 B │ 22 minutes ago │ 17 minutes ago
2 │ randomweirdstuff.txt │ File │ │ 197 B │ 21 minutes ago │ 18 minutes ago
3 │ youshouldeatmorecereal.txt │ File │ │ 768 B │ 30 seconds ago │ now
4 │ zeusiscrazy.txt │ File │ │ 556 B │ 22 minutes ago │ 18 minutes ago
━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━┷━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━
> ls | sort-by name | reverse
━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━┯━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━
# │ name │ type │ readonly │ size │ accessed │ modified
───┼────────────────────────────┼──────┼──────────┼────────┼────────────────┼────────────────
0 │ zeusiscrazy.txt │ File │ │ 556 B │ 22 minutes ago │ 19 minutes ago
1 │ youshouldeatmorecereal.txt │ File │ │ 768 B │ 39 seconds ago │ 18 seconds ago
2 │ randomweirdstuff.txt │ File │ │ 197 B │ 21 minutes ago │ 18 minutes ago
3 │ coww.txt │ File │ │ 24 B │ 22 minutes ago │ 18 minutes ago
4 │ abaracadabra.txt │ File │ │ 401 B │ 23 minutes ago │ 16 minutes ago
━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━┷━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━
```
```shell
> ls | sort-by size
━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━┯━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━
# │ name │ type │ readonly │ size │ accessed │ modified
───┼────────────────────────────┼──────┼──────────┼────────┼────────────────┼────────────────
0 │ coww.txt │ File │ │ 24 B │ 22 minutes ago │ 18 minutes ago
1 │ randomweirdstuff.txt │ File │ │ 197 B │ 21 minutes ago │ 18 minutes ago
2 │ abaracadabra.txt │ File │ │ 401 B │ 23 minutes ago │ 16 minutes ago
3 │ zeusiscrazy.txt │ File │ │ 556 B │ 22 minutes ago │ 19 minutes ago
4 │ youshouldeatmorecereal.txt │ File │ │ 768 B │ a minute ago │ 26 seconds ago
━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━┷━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━
> ls | sort-by size | reverse
━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━┯━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━
# │ name │ type │ readonly │ size │ accessed │ modified
───┼────────────────────────────┼──────┼──────────┼────────┼────────────────┼────────────────
0 │ youshouldeatmorecereal.txt │ File │ │ 768 B │ a minute ago │ 32 seconds ago
1 │ zeusiscrazy.txt │ File │ │ 556 B │ 22 minutes ago │ 19 minutes ago
2 │ abaracadabra.txt │ File │ │ 401 B │ 23 minutes ago │ 16 minutes ago
3 │ randomweirdstuff.txt │ File │ │ 197 B │ 21 minutes ago │ 18 minutes ago
4 │ coww.txt │ File │ │ 24 B │ 22 minutes ago │ 18 minutes ago
━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━┷━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━
```

26
docs/commands/shells.md Normal file
View file

@ -0,0 +1,26 @@
# shells
Lists all the active nu shells with a number/index, a name and the path. Also marks the current nu shell.
## Examples
```
> shells
---+---+------------+---------------
# | | name | path
---+---+------------+---------------
0 | | filesystem | /usr
1 | | filesystem | /home
2 | X | filesystem | /home/username
---+---+------------+---------------
```
```
/> shells
---+---+-------------------------------------------------+------------------------------------
# | | name | path
---+---+-------------------------------------------------+------------------------------------
0 | | filesystem | /Users/username/Code/nushell
1 | X | {/Users/username/Code/nushell/Cargo.toml} | /
---+---+-------------------------------------------------+------------------------------------
```

56
docs/commands/sort-by.md Normal file
View file

@ -0,0 +1,56 @@
# env
The `sort-by` command sorts the table being displayed in the terminal by a chosen column(s).
`sort-by` takes multiple arguments (being the names of columns) sorting by each argument in order.
## Examples -
```shell
/home/example> ls | sort-by size
━━━┯━━━━━━┯━━━━━━┯━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━
# │ name │ type │ readonly │ size │ accessed │ modified
───┼──────┼──────┼──────────┼────────┼────────────────┼────────────────
0 │ az │ File │ │ 18 B │ 4 minutes ago │ 4 minutes ago
1 │ a │ File │ │ 18 B │ 4 minutes ago │ 38 minutes ago
2 │ ad │ File │ │ 18 B │ 4 minutes ago │ 4 minutes ago
3 │ ac │ File │ │ 18 B │ 4 minutes ago │ 4 minutes ago
4 │ ab │ File │ │ 18 B │ 4 minutes ago │ 4 minutes ago
5 │ c │ File │ │ 102 B │ 35 minutes ago │ 35 minutes ago
6 │ d │ File │ │ 189 B │ 35 minutes ago │ 34 minutes ago
7 │ b │ File │ │ 349 B │ 35 minutes ago │ 35 minutes ago
━━━┷━━━━━━┷━━━━━━┷━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━
```
```shell
/home/example> ls | sort-by size name
━━━┯━━━━━━┯━━━━━━┯━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━
# │ name │ type │ readonly │ size │ accessed │ modified
───┼──────┼──────┼──────────┼────────┼────────────────┼────────────────
0 │ a │ File │ │ 18 B │ 4 minutes ago │ 39 minutes ago
1 │ ab │ File │ │ 18 B │ 4 minutes ago │ 4 minutes ago
2 │ ac │ File │ │ 18 B │ 4 minutes ago │ 4 minutes ago
3 │ ad │ File │ │ 18 B │ 4 minutes ago │ 4 minutes ago
4 │ az │ File │ │ 18 B │ 4 minutes ago │ 4 minutes ago
5 │ c │ File │ │ 102 B │ 36 minutes ago │ 35 minutes ago
6 │ d │ File │ │ 189 B │ 35 minutes ago │ 35 minutes ago
7 │ b │ File │ │ 349 B │ 36 minutes ago │ 36 minutes ago
```
```
/home/example> ls | sort-by accessed
━━━┯━━━━━━┯━━━━━━┯━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━
# │ name │ type │ readonly │ size │ accessed │ modified
───┼──────┼──────┼──────────┼────────┼────────────────┼────────────────
0 │ b │ File │ │ 349 B │ 37 minutes ago │ 37 minutes ago
1 │ c │ File │ │ 102 B │ 37 minutes ago │ 37 minutes ago
2 │ d │ File │ │ 189 B │ 37 minutes ago │ 36 minutes ago
3 │ a │ File │ │ 18 B │ 6 minutes ago │ 40 minutes ago
4 │ ab │ File │ │ 18 B │ 6 minutes ago │ 6 minutes ago
5 │ ac │ File │ │ 18 B │ 6 minutes ago │ 6 minutes ago
6 │ ad │ File │ │ 18 B │ 5 minutes ago │ 5 minutes ago
7 │ az │ File │ │ 18 B │ 5 minutes ago │ 5 minutes ago
━━━┷━━━━━━┷━━━━━━┷━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━
```

44
docs/commands/sum.md Normal file
View file

@ -0,0 +1,44 @@
# sum
This command allows you to calculate the sum of values in a column.
## Examples
To get the sum of the file sizes in a directory, simply pipe the size column from the ls command to the sum command.
```shell
> ls | get size | sum
━━━━━━━━━
value
━━━━━━━━━
51.0 MB
━━━━━━━━━
```
To get the sum of the characters that make up your present working directory.
```shell
> pwd | split-row / | size | get chars | sum
━━━━━━━━━
<value>
━━━━━━━━━
21
━━━━━━━━━
```
Note that sum only works for integer and byte values. If the shell doesn't recognize the values in a column as one of those types, it will return an error.
One way to solve this is to convert each row to an integer when possible and then pipe the result to `sum`
```shell
> open tests/fixtures/formats/caco3_plastics.csv | get tariff_item | sum
error: Unrecognized type in stream: Primitive(String("2509000000"))
- shell:1:0
1 | open tests/fixtures/formats/caco3_plastics.csv | get tariff_item | sum
| ^^^^ source
```
```shell
> open tests/fixtures/formats/caco3_plastics.csv | get tariff_item | str --to-int | sum
━━━━━━━━━━━━━
<value>
─────────────
29154639996
━━━━━━━━━━━━━
```

32
docs/commands/sys.md Normal file
View file

@ -0,0 +1,32 @@
# sys
This command gives information about the system where nu is running on.
## Examples
```shell
> sys
━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━
host │ cpu │ disks │ mem │ net │ battery
────────────────┼────────────────┼─────────────────┼────────────────┼──────────────────┼────────────────
[table: 1 row] │ [table: 1 row] │ [table: 3 rows] │ [table: 1 row] │ [table: 18 rows] │ [table: 1 row]
━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━
> sys | get host
━━━━━━━━┯━━━━━━━━━┯━━━━━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━
name │ release │ hostname │ arch │ uptime │ users
────────┼─────────┼──────────────┼────────┼────────────────┼──────────────────
Darwin │ 18.7.0 │ C02Y437GJGH6 │ x86_64 │ [table: 1 row] │ [table: 17 rows]
━━━━━━━━┷━━━━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━
> sys | get cpu
━━━━━━━┯━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━
cores │ current ghz │ min ghz │ max ghz
───────┼───────────────────┼───────────────────┼───────────────────
12 │ 2.600000000000000 │ 2.600000000000000 │ 2.600000000000000
━━━━━━━┷━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━
> sys | get mem
━━━━━━━━━┯━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━
total │ free │ swap total │ swap free
─────────┼──────────┼────────────┼───────────
34.4 GB │ 545.0 MB │ 2.1 GB │ 723.0 MB
━━━━━━━━━┷━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━
```

80
docs/commands/to-csv.md Normal file
View file

@ -0,0 +1,80 @@
# to-csv
Converts table data into csv text.
## Example
```shell
> shells
━━━┯━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━
# │ │ name │ path
───┼───┼────────────┼────────────────────────
0 │ X │ filesystem │ /home/shaurya
1 │ │ filesystem │ /home/shaurya/Pictures
2 │ │ filesystem │ /home/shaurya/Desktop
━━━┷━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━
> shells | to-csv
,name,path
X,filesystem,/home/shaurya
,filesystem,/home/shaurya/Pictures
,filesystem,/home/shaurya/Desktop
```
```shell
> open caco3_plastics.csv
━━━┯━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┯━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┯━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━━━━
# │ importer │ shipper │ tariff_item │ name │ origin │ shipped_at │ arrived_at │ net_weight │ fob_price │ cif_price │ cif_per_net_
│ │ │ │ │ │ │ │ │ │ │ weight
───┼──────────────┼──────────────┼─────────────┼──────────────┼──────────┼────────────┼────────────┼────────────┼───────────┼───────────┼──────────────
0 │ PLASTICOS │ S A REVERTE │ 2509000000 │ CARBONATO DE │ SPAIN │ 18/03/2016 │ 17/04/2016 │ 81,000.00 │ 14,417.58 │ 18,252.34 │ 0.23
│ RIVAL CIA │ │ │ CALCIO TIPO │ │ │ │ │ │ │
│ LTDA │ │ │ CALCIPORE │ │ │ │ │ │ │
│ │ │ │ 160 T AL │ │ │ │ │ │ │
1 │ MEXICHEM │ OMYA ANDINA │ 2836500000 │ CARBONATO │ COLOMBIA │ 07/07/2016 │ 10/07/2016 │ 26,000.00 │ 7,072.00 │ 8,127.18 │ 0.31
│ ECUADOR S.A. │ S A │ │ │ │ │ │ │ │ │
2 │ PLASTIAZUAY │ SA REVERTE │ 2836500000 │ CARBONATO DE │ SPAIN │ 27/07/2016 │ 09/08/2016 │ 81,000.00 │ 8,100.00 │ 11,474.55 │ 0.14
│ SA │ │ │ CALCIO │ │ │ │ │ │ │
3 │ PLASTICOS │ AND │ 2836500000 │ CALCIUM │ TURKEY │ 04/10/2016 │ 11/11/2016 │ 100,000.00 │ 17,500.00 │ 22,533.75 │ 0.23
│ RIVAL CIA │ ENDUSTRIYEL │ │ CARBONATE │ │ │ │ │ │ │
│ LTDA │ HAMMADDELER │ │ ANADOLU │ │ │ │ │ │ │
│ │ DIS TCARET │ │ ANDCARB CT-1 │ │ │ │ │ │ │
│ │ LTD.STI. │ │ │ │ │ │ │ │ │
4 │ QUIMICA │ SA REVERTE │ 2836500000 │ CARBONATO DE │ SPAIN │ 24/06/2016 │ 12/07/2016 │ 27,000.00 │ 3,258.90 │ 5,585.00 │ 0.21
│ COMERCIAL │ │ │ CALCIO │ │ │ │ │ │ │
│ QUIMICIAL │ │ │ │ │ │ │ │ │ │
│ CIA. LTDA. │ │ │ │ │ │ │ │ │ │
5 │ PICA │ OMYA ANDINA │ 3824909999 │ CARBONATO DE │ COLOMBIA │ 01/01/1900 │ 18/01/2016 │ 66,500.00 │ 12,635.00 │ 18,670.52 │ 0.28
│ PLASTICOS │ S.A │ │ CALCIO │ │ │ │ │ │ │
│ INDUSTRIALES │ │ │ │ │ │ │ │ │ │
│ C.A. │ │ │ │ │ │ │ │ │ │
6 │ PLASTIQUIM │ OMYA ANDINA │ 3824909999 │ CARBONATO DE │ COLOMBIA │ 01/01/1900 │ 25/10/2016 │ 33,000.00 │ 6,270.00 │ 9,999.00 │ 0.30
│ S.A. │ S.A NIT │ │ CALCIO │ │ │ │ │ │ │
│ │ 830.027.386- │ │ RECUBIERTO │ │ │ │ │ │ │
│ │ 6 │ │ CON ACIDO │ │ │ │ │ │ │
│ │ │ │ ESTEARICO │ │ │ │ │ │ │
│ │ │ │ OMYA CARB 1T │ │ │ │ │ │ │
│ │ │ │ CG BBS 1000 │ │ │ │ │ │ │
7 │ QUIMICOS │ SIBELCO │ 3824909999 │ CARBONATO DE │ COLOMBIA │ 01/11/2016 │ 03/11/2016 │ 52,000.00 │ 8,944.00 │ 13,039.05 │ 0.25
│ ANDINOS │ COLOMBIA SAS │ │ CALCIO │ │ │ │ │ │ │
│ QUIMANDI │ │ │ RECUBIERTO │ │ │ │ │ │ │
│ S.A. │ │ │ │ │ │ │ │ │ │
8 │ TIGRE │ OMYA ANDINA │ 3824909999 │ CARBONATO DE │ COLOMBIA │ 01/01/1900 │ 28/10/2016 │ 66,000.00 │ 11,748.00 │ 18,216.00 │ 0.28
│ ECUADOR S.A. │ S.A NIT │ │ CALCIO │ │ │ │ │ │ │
│ ECUATIGRE │ 830.027.386- │ │ RECUBIERTO │ │ │ │ │ │ │
│ │ 6 │ │ CON ACIDO │ │ │ │ │ │ │
│ │ │ │ ESTEARICO │ │ │ │ │ │ │
│ │ │ │ OMYACARB 1T │ │ │ │ │ │ │
│ │ │ │ CG BPA 25 NO │ │ │ │ │ │ │
━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━━━━
> open caco3_plastics.csv | to-csv
importer,shipper,tariff_item,name,origin,shipped_at,arrived_at,net_weight,fob_price,cif_price,cif_per_net_weight
PLASTICOS RIVAL CIA LTDA,S A REVERTE,2509000000,CARBONATO DE CALCIO TIPO CALCIPORE 160 T AL,SPAIN,18/03/2016,17/04/2016,"81,000.00","14,417.58","18,252.34",0.23
MEXICHEM ECUADOR S.A.,OMYA ANDINA S A,2836500000,CARBONATO,COLOMBIA,07/07/2016,10/07/2016,"26,000.00","7,072.00","8,127.18",0.31
PLASTIAZUAY SA,SA REVERTE,2836500000,CARBONATO DE CALCIO,SPAIN,27/07/2016,09/08/2016,"81,000.00","8,100.00","11,474.55",0.14
PLASTICOS RIVAL CIA LTDA,AND ENDUSTRIYEL HAMMADDELER DIS TCARET LTD.STI.,2836500000,CALCIUM CARBONATE ANADOLU ANDCARB CT-1,TURKEY,04/10/2016,11/11/2016,"100,000.00","17,500.00","22,533.75",0.23
QUIMICA COMERCIAL QUIMICIAL CIA. LTDA.,SA REVERTE,2836500000,CARBONATO DE CALCIO,SPAIN,24/06/2016,12/07/2016,"27,000.00","3,258.90","5,585.00",0.21
PICA PLASTICOS INDUSTRIALES C.A.,OMYA ANDINA S.A,3824909999,CARBONATO DE CALCIO,COLOMBIA,01/01/1900,18/01/2016,"66,500.00","12,635.00","18,670.52",0.28
PLASTIQUIM S.A.,OMYA ANDINA S.A NIT 830.027.386-6,3824909999,CARBONATO DE CALCIO RECUBIERTO CON ACIDO ESTEARICO OMYA CARB 1T CG BBS 1000,COLOMBIA,01/01/1900,25/10/2016,"33,000.00","6,270.00","9,999.00",0.30
QUIMICOS ANDINOS QUIMANDI S.A.,SIBELCO COLOMBIA SAS,3824909999,CARBONATO DE CALCIO RECUBIERTO,COLOMBIA,01/11/2016,03/11/2016,"52,000.00","8,944.00","13,039.05",0.25
TIGRE ECUADOR S.A. ECUATIGRE,OMYA ANDINA S.A NIT 830.027.386-6,3824909999,CARBONATO DE CALCIO RECUBIERTO CON ACIDO ESTEARICO OMYACARB 1T CG BPA 25 NO,COLOMBIA,01/01/1900,28/10/2016,"66,000.00","11,748.00","18,216.00",0.28
```

40
docs/commands/to-json.md Normal file
View file

@ -0,0 +1,40 @@
# to-json
Converts table data into json text.
## Example
```shell
> shells
━━━┯━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━
# │ │ name │ path
───┼───┼────────────┼────────────────────────
0 │ X │ filesystem │ /home/shaurya
1 │ │ filesystem │ /home/shaurya/Pictures
2 │ │ filesystem │ /home/shaurya/Desktop
━━━┷━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━
> shells | to-json
[{" ":"X","name":"filesystem","path":"/home/shaurya"},{" ":" ","name":"filesystem","path":"/home/shaurya/Pictures"},{" ":" ","name":"filesystem","path":"/home/shaurya/Desktop"}]
```
```shell
> open sgml_description.json
━━━━━━━━━━━━━━━━
glossary
────────────────
[table: 1 row]
━━━━━━━━━━━━━━━━
> open sgml_description.json | to-json
{"glossary":{"title":"example glossary","GlossDiv":{"title":"S","GlossList":{"GlossEntry":{"ID":"SGML","SortAs":"SGML","GlossTerm":"Standard Generalized Markup Language","Acronym":"SGML","Abbrev":"ISO 8879:1986","Height":10,"GlossDef":{"para":"A meta-markup language, used to create markup languages such as DocBook.","GlossSeeAlso":["GML","XML"]},"Sections":[101,102],"GlossSee":"markup"}}}}}
```
We can also convert formats !
```shell
> open jonathan.xml
━━━━━━━━━━━━━━━━
rss
────────────────
[table: 1 row]
━━━━━━━━━━━━━━━━
> open jonathan.xml | to-json
{"rss":[{"channel":[{"title":["Jonathan Turner"]},{"link":["http://www.jonathanturner.org"]},{"link":[]},{"item":[{"title":["Creating crossplatform Rust terminal apps"]},{"description":["<p><img src=\"/images/pikachu.jpg\" alt=\"Pikachu animation in Windows\" /></p>\n\n<p><em>Look Mom, Pikachu running in Windows CMD!</em></p>\n\n<p>Part of the adventure is not seeing the way ahead and going anyway.</p>\n"]},{"pubDate":["Mon, 05 Oct 2015 00:00:00 +0000"]},{"link":["http://www.jonathanturner.org/2015/10/off-to-new-adventures.html"]},{"guid":["http://www.jonathanturner.org/2015/10/off-to-new-adventures.html"]}]}]}]}
```

112
docs/commands/to-toml.md Normal file
View file

@ -0,0 +1,112 @@
# to-toml
Converts table data into toml text.
## Example
```shell
> shells
━━━┯━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━
# │ │ name │ path
───┼───┼────────────┼────────────────────────
0 │ X │ filesystem │ /home/shaurya
1 │ │ filesystem │ /home/shaurya/Pictures
2 │ │ filesystem │ /home/shaurya/Desktop
━━━┷━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━
> shells | to-toml
[[]]
" " = "X"
name = "filesystem"
path = "/home/shaurya"
[[]]
" " = " "
name = "filesystem"
path = "/home/shaurya/Pictures"
[[]]
" " = " "
name = "filesystem"
path = "/home/shaurya/Desktop"
```
```shell
> open cargo_sample.toml
━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━
dependencies │ dev-dependencies │ package
────────────────┼──────────────────┼────────────────
[table: 1 row] │ [table: 1 row] │ [table: 1 row]
━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━
> open cargo_sample.toml | to-toml
[dependencies]
ansi_term = "0.11.0"
app_dirs = "1.2.1"
byte-unit = "2.1.0"
bytes = "0.4.12"
chrono-humanize = "0.0.11"
chrono-tz = "0.5.1"
clap = "2.33.0"
conch-parser = "0.1.1"
derive-new = "0.5.6"
dunce = "1.0.0"
futures-sink-preview = "0.3.0-alpha.16"
futures_codec = "0.2.2"
getset = "0.0.7"
git2 = "0.8.0"
itertools = "0.8.0"
lalrpop-util = "0.17.0"
language-reporting = "0.3.0"
log = "0.4.6"
logos = "0.10.0-rc2"
logos-derive = "0.10.0-rc2"
nom = "5.0.0-beta1"
ordered-float = "1.0.2"
pretty_env_logger = "0.3.0"
prettyprint = "0.6.0"
prettytable-rs = "0.8.0"
regex = "1.1.6"
rustyline = "4.1.0"
serde = "1.0.91"
serde_derive = "1.0.91"
serde_json = "1.0.39"
subprocess = "0.1.18"
sysinfo = "0.8.4"
term = "0.5.2"
tokio-fs = "0.1.6"
toml = "0.5.1"
toml-query = "0.9.0"
[dependencies.chrono]
features = ["serde"]
version = "0.4.6"
[dependencies.cursive]
default-features = false
features = ["pancurses-backend"]
version = "0.12.0"
[dependencies.futures-preview]
features = ["compat", "io-compat"]
version = "0.3.0-alpha.16"
[dependencies.indexmap]
features = ["serde-1"]
version = "1.0.2"
[dependencies.pancurses]
features = ["win32a"]
version = "0.16"
[dev-dependencies]
pretty_assertions = "0.6.1"
[package]
authors = ["Yehuda Katz <wycats@gmail.com>"]
description = "A shell for the GitHub era"
edition = "2018"
license = "ISC"
name = "nu"
version = "0.1.1"
```

80
docs/commands/to-tsv.md Normal file
View file

@ -0,0 +1,80 @@
# to-tsv
Converts table data into tsv text.
## Example
```shell
> shells
━━━┯━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━
# │ │ name │ path
───┼───┼────────────┼────────────────────────
0 │ X │ filesystem │ /home/shaurya
1 │ │ filesystem │ /home/shaurya/Pictures
2 │ │ filesystem │ /home/shaurya/Desktop
━━━┷━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━
> shells |to-tsv
name path
X filesystem /home/shaurya
```
```shell
> open caco3_plastics.tsv
━━━┯━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┯━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┯━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━━━━
# │ importer │ shipper │ tariff_item │ name │ origin │ shipped_at │ arrived_at │ net_weight │ fob_price │ cif_price │ cif_per_net_
│ │ │ │ │ │ │ │ │ │ │ weight
───┼──────────────┼──────────────┼─────────────┼──────────────┼──────────┼────────────┼────────────┼────────────┼───────────┼───────────┼──────────────
0 │ PLASTICOS │ S A REVERTE │ 2509000000 │ CARBONATO DE │ SPAIN │ 18/03/2016 │ 17/04/2016 │ 81,000.00 │ 14,417.58 │ 18,252.34 │ 0.23
│ RIVAL CIA │ │ │ CALCIO TIPO │ │ │ │ │ │ │
│ LTDA │ │ │ CALCIPORE │ │ │ │ │ │ │
│ │ │ │ 160 T AL │ │ │ │ │ │ │
1 │ MEXICHEM │ OMYA ANDINA │ 2836500000 │ CARBONATO │ COLOMBIA │ 07/07/2016 │ 10/07/2016 │ 26,000.00 │ 7,072.00 │ 8,127.18 │ 0.31
│ ECUADOR S.A. │ S A │ │ │ │ │ │ │ │ │
2 │ PLASTIAZUAY │ SA REVERTE │ 2836500000 │ CARBONATO DE │ SPAIN │ 27/07/2016 │ 09/08/2016 │ 81,000.00 │ 8,100.00 │ 11,474.55 │ 0.14
│ SA │ │ │ CALCIO │ │ │ │ │ │ │
3 │ PLASTICOS │ AND │ 2836500000 │ CALCIUM │ TURKEY │ 04/10/2016 │ 11/11/2016 │ 100,000.00 │ 17,500.00 │ 22,533.75 │ 0.23
│ RIVAL CIA │ ENDUSTRIYEL │ │ CARBONATE │ │ │ │ │ │ │
│ LTDA │ HAMMADDELER │ │ ANADOLU │ │ │ │ │ │ │
│ │ DIS TCARET │ │ ANDCARB CT-1 │ │ │ │ │ │ │
│ │ LTD.STI. │ │ │ │ │ │ │ │ │
4 │ QUIMICA │ SA REVERTE │ 2836500000 │ CARBONATO DE │ SPAIN │ 24/06/2016 │ 12/07/2016 │ 27,000.00 │ 3,258.90 │ 5,585.00 │ 0.21
│ COMERCIAL │ │ │ CALCIO │ │ │ │ │ │ │
│ QUIMICIAL │ │ │ │ │ │ │ │ │ │
│ CIA. LTDA. │ │ │ │ │ │ │ │ │ │
5 │ PICA │ OMYA ANDINA │ 3824909999 │ CARBONATO DE │ COLOMBIA │ 01/01/1900 │ 18/01/2016 │ 66,500.00 │ 12,635.00 │ 18,670.52 │ 0.28
│ PLASTICOS │ S.A │ │ CALCIO │ │ │ │ │ │ │
│ INDUSTRIALES │ │ │ │ │ │ │ │ │ │
│ C.A. │ │ │ │ │ │ │ │ │ │
6 │ PLASTIQUIM │ OMYA ANDINA │ 3824909999 │ CARBONATO DE │ COLOMBIA │ 01/01/1900 │ 25/10/2016 │ 33,000.00 │ 6,270.00 │ 9,999.00 │ 0.30
│ S.A. │ S.A NIT │ │ CALCIO │ │ │ │ │ │ │
│ │ 830.027.386- │ │ RECUBIERTO │ │ │ │ │ │ │
│ │ 6 │ │ CON ACIDO │ │ │ │ │ │ │
│ │ │ │ ESTEARICO │ │ │ │ │ │ │
│ │ │ │ OMYA CARB 1T │ │ │ │ │ │ │
│ │ │ │ CG BBS 1000 │ │ │ │ │ │ │
7 │ QUIMICOS │ SIBELCO │ 3824909999 │ CARBONATO DE │ COLOMBIA │ 01/11/2016 │ 03/11/2016 │ 52,000.00 │ 8,944.00 │ 13,039.05 │ 0.25
│ ANDINOS │ COLOMBIA SAS │ │ CALCIO │ │ │ │ │ │ │
│ QUIMANDI │ │ │ RECUBIERTO │ │ │ │ │ │ │
│ S.A. │ │ │ │ │ │ │ │ │ │
8 │ TIGRE │ OMYA ANDINA │ 3824909999 │ CARBONATO DE │ COLOMBIA │ 01/01/1900 │ 28/10/2016 │ 66,000.00 │ 11,748.00 │ 18,216.00 │ 0.28
│ ECUADOR S.A. │ S.A NIT │ │ CALCIO │ │ │ │ │ │ │
│ ECUATIGRE │ 830.027.386- │ │ RECUBIERTO │ │ │ │ │ │ │
│ │ 6 │ │ CON ACIDO │ │ │ │ │ │ │
│ │ │ │ ESTEARICO │ │ │ │ │ │ │
│ │ │ │ OMYACARB 1T │ │ │ │ │ │ │
│ │ │ │ CG BPA 25 NO │ │ │ │ │ │ │
━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━━━━
> open caco3_plastics.tsv | to-tsv
importer shipper tariff_item name origin shipped_at arrived_at net_weight fob_price cif_price cif_per_net_weight
PLASTICOS RIVAL CIA LTDA S A REVERTE 2509000000 CARBONATO DE CALCIO TIPO CALCIPORE 160 T AL SPAIN 18/03/2016 17/04/2016 81,000.00 14,417.58 18,252.34 0.23
MEXICHEM ECUADOR S.A. OMYA ANDINA S A 2836500000 CARBONATO COLOMBIA 07/07/2016 10/07/2016 26,000.00 7,072.00 8,127.18 0.31
PLASTIAZUAY SA SA REVERTE 2836500000 CARBONATO DE CALCIO SPAIN 27/07/2016 09/08/2016 81,000.00 8,100.00 11,474.55 0.14
PLASTICOS RIVAL CIA LTDA AND ENDUSTRIYEL HAMMADDELER DIS TCARET LTD.STI. 2836500000 CALCIUM CARBONATE ANADOLU ANDCARB CT-1 TURKEY 04/10/2016 11/11/2016 100,000.00 17,500.00 22,533.75 0.23
QUIMICA COMERCIAL QUIMICIAL CIA. LTDA. SA REVERTE 2836500000 CARBONATO DE CALCIO SPAIN 24/06/2016 12/07/2016 27,000.00 3,258.90 5,585.00 0.21
PICA PLASTICOS INDUSTRIALES C.A. OMYA ANDINA S.A 3824909999 CARBONATO DE CALCIO COLOMBIA 01/01/1900 18/01/2016 66,500.00 12,635.00 18,670.52 0.28
PLASTIQUIM S.A. OMYA ANDINA S.A NIT 830.027.386-6 3824909999 CARBONATO DE CALCIO RECUBIERTO CON ACIDO ESTEARICO OMYA CARB 1T CG BBS 1000 COLOMBIA 01/01/1900 25/10/2016 33,000.00 6,270.00 9,999.00 0.30
QUIMICOS ANDINOS QUIMANDI S.A. SIBELCO COLOMBIA SAS 3824909999 CARBONATO DE CALCIO RECUBIERTO COLOMBIA 01/11/2016 03/11/2016 52,000.00 8,944.00 13,039.05 0.25
TIGRE ECUADOR S.A. ECUATIGRE OMYA ANDINA S.A NIT 830.027.386-6 3824909999 CARBONATO DE CALCIO RECUBIERTO CON ACIDO ESTEARICO OMYACARB 1T CG BPA 25 NO COLOMBIA 01/01/1900 28/10/2016 66,000.00 11,748.00 18,216.00 0.28
```

35
docs/commands/to-url.md Normal file
View file

@ -0,0 +1,35 @@
# to-url
Converts table data into url-formatted text.
## Example
```shell
> shells
━━━┯━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━
# │ │ name │ path
───┼───┼────────────┼────────────────────────
0 │ X │ filesystem │ /home/shaurya
1 │ │ filesystem │ /home/shaurya/Pictures
2 │ │ filesystem │ /home/shaurya/Desktop
━━━┷━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━
> shells | to-url
━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# │ value
───┼───────────────────────────────────────────────────────
0 │ +=X&name=filesystem&path=%2Fhome%2Fshaurya
1 │ +=+&name=filesystem&path=%2Fhome%2Fshaurya%2FPictures
2 │ +=+&name=filesystem&path=%2Fhome%2Fshaurya%2FDesktop
━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
```shell
> open sample.url
━━━━━━━━━━┯━━━━━━━━┯━━━━━━┯━━━━━━━━
bread │ cheese │ meat │ fat
──────────┼────────┼──────┼────────
baguette │ comté │ ham │ butter
━━━━━━━━━━┷━━━━━━━━┷━━━━━━┷━━━━━━━━
> open sample.url | to-url
bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter
```

60
docs/commands/to-yaml.md Normal file
View file

@ -0,0 +1,60 @@
# to-yaml
Converts table data into yaml text.
## Example
```shell
> shells
━━━┯━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━
# │ │ name │ path
───┼───┼────────────┼────────────────────────
0 │ X │ filesystem │ /home/shaurya
1 │ │ filesystem │ /home/shaurya/Pictures
2 │ │ filesystem │ /home/shaurya/Desktop
━━━┷━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━
> shells | to-yaml
---
- " ": X
name: filesystem
path: /home/shaurya
- " ": " "
name: filesystem
path: /home/shaurya/Pictures
- " ": " "
name: filesystem
path: /home/shaurya/Desktop
```
```shell
> open appveyor.yml
━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━┯━━━━━━━┯━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━
image │ environment │ install │ build │ test_script │ cache
────────────────────┼────────────────┼─────────────────┼───────┼─────────────────┼─────────────────
Visual Studio 2017 │ [table: 1 row] │ [table: 5 rows] │ │ [table: 2 rows] │ [table: 2 rows]
━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━┷━━━━━━━┷━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━
> open appveyor.yml | to-yaml
---
image: Visual Studio 2017
environment:
global:
PROJECT_NAME: nushell
RUST_BACKTRACE: 1
matrix:
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly
BITS: 64
install:
- "set PATH=C:\\msys64\\mingw%BITS%\\bin;C:\\msys64\\usr\\bin;%PATH%"
- "curl -sSf -o rustup-init.exe https://win.rustup.rs"
- rustup-init.exe -y --default-host %TARGET% --default-toolchain %CHANNEL%-%TARGET%
- "set PATH=%PATH%;C:\\Users\\appveyor\\.cargo\\bin"
- "call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat\""
build: false
test_script:
- cargo build --verbose
- cargo test --all --verbose
cache:
- target -> Cargo.lock
- "C:\\Users\\appveyor\\.cargo\\registry -> Cargo.lock"
```

12
docs/commands/trim.md Normal file
View file

@ -0,0 +1,12 @@
# trim
Trim leading and following whitespace from text data
## Example
```shell
> echo " Hello world"
Hello world
> echo " Hello world" | trim
Hello world
```

14
docs/commands/version.md Normal file
View file

@ -0,0 +1,14 @@
# version
Outputs the nushell version.
## Examples
```shell
> version
━━━━━━━━━
version
─────────
0.3.0
━━━━━━━━━
```

34
docs/commands/where.md Normal file
View file

@ -0,0 +1,34 @@
# where
This command filters the content of a table based on a condition passed as a parameter, which must be a boolean expression making use of any of the table columns. Other commands such as `ls` are capable of feeding `where` with their output through pipelines.
## Usage
```shell
> [input-command] | where [condition]
```
## Examples
```shell
> ls | where size > 4kb
----+----------------+------+----------+----------+----------------+----------------
# | name | type | readonly | size | accessed | modified
----+----------------+------+----------+----------+----------------+----------------
0 | IMG_1291.jpg | File | | 115.5 KB | a month ago | 4 months ago
1 | README.md | File | | 11.1 KB | 2 days ago | 2 days ago
2 | IMG_1291.png | File | | 589.0 KB | a month ago | a month ago
3 | IMG_1381.jpg | File | | 81.0 KB | a month ago | 4 months ago
4 | butterfly.jpeg | File | | 4.2 KB | a month ago | a month ago
5 | Cargo.lock | File | | 199.6 KB | 22 minutes ago | 22 minutes ago
```
```shell
> ps | where cpu > 10
---+-------+----------+-------+-----------------------------
# | pid | status | cpu | name
---+-------+----------+-------+-----------------------------
0 | 1992 | Sleeping | 44.52 | /usr/bin/gnome-shell
1 | 1069 | Sleeping | 16.15 |
2 | 24116 | Sleeping | 13.70 | /opt/google/chrome/chrome
3 | 21976 | Sleeping | 12.67 | /usr/share/discord/Discord
```

124
docs/docker.md Normal file
View file

@ -0,0 +1,124 @@
# Docker Guide
| tag | base image | plugins | package manager | libs & bins | size |
| ------------------ | -------------------- | ------- | --------------- | ---------------------------------------------------------------- | ----------- |
| `latest`, `debian` | `debian:latest` | yes | apt | **a lot**, including _glibc_ | ~(48+62) MB |
| `slim` | `debian:stable-slim` | yes | apt | all `nu:debian` image but exclude [this list][.slimify-excludes] | ~(26+62) MB |
| `alpine` | `alpine:latest` | yes | apk | all `nu:musl-busybox` image + libcrypto, libssl, libtls, libz | ~(3+61) MB |
| `musl-busybox` | `busybox:musl` | no | — | GNU utils + _musl_ | ~(1+16) MB |
| `glibc-busybox` | `busybox:glibc` | no | — | GNU utils + _glibc_ | ~(3+17) MB |
| `musl-distroless` | `distroless/static` | no | — | see [here][distroless/base] | ~(2+16) MB |
| `glibc-distroless` | `distroless/cc` | no | — | `distroless/static` with _glibc_ | ~(17+17) MB |
| `glibc` | `scratch` | no | — | **only `nu` binary-executable** which depend on glibc runtime | ~17 MB |
| `musl` | `scratch` | no | — | **only `nu` binary-executable** statically linked to musl | ~16 MB |
[.slimify-excludes]: https://github.com/debuerreotype/debuerreotype/blob/master/scripts/.slimify-excludes
[distroless/base]: https://github.com/GoogleContainerTools/distroless/blob/master/base/README.md
## Image Variants
### `nu:<version>`
This is the defacto image. If you are unsure about what your needs are, you probably want to use this one. It is designed to be used both as a throw away container (mount your source code and start the container to start your app), as well as the base to build other images off of.
<details><summary>example</summary>
Let say you create a plugin in Rust.
- create a Dockerfile in your root project
```dockerfile
FROM nu:0.2
COPY /target/debug/nu_plugin_cowsay /bin/
ENTRYPOINT ["nu"]
```
- build your project first then run it via docker
```console
cargo build
docker run -it .
```
</details>
### `nu:<version>-slim`
This image does not contain the common packages contained in the default tag and only contains the minimal packages needed to run `nu`. Unless you are working in an environment where only the `nu` image will be deployed and you have space constraints, it's highly recommended to use the alpine image if you aim for small image size. Only use this image if you really need **both** `glibc` and small image size.
### `nu:<version>-alpine`
This image is based on the popular [Alpine Linux project](https://alpinelinux.org/), available in [the alpine official image][alpine]. Alpine Linux is much smaller than most distribution base images (~5MB), and thus leads to much slimmer images in general.
This variant is highly recommended when final image size being as small as possible is desired. The main caveat to note is that it does use `musl` libc instead of `glibc` and friends, so certain software might run into issues depending on the depth of their libc requirements. However, most software doesn't have an issue with this, so this variant is usually a very safe choice. See [this Hacker News comment thread](https://news.ycombinator.com/item?id=10782897) for more discussion of the issues that might arise and some pro/con comparisons of using Alpine-based images.
To minimize image size, it's uncommon for additional related tools (such as `git` or `bash`) to be included in Alpine-based images. Using this image as a base, add the things you need in your own Dockerfile (see the [alpine image description][alpine] for examples of how to install packages if you are unfamiliar).
### `nu:<version>-<libc-variant>`
This image is based on [`scratch`](https://hub.docker.com/_/scratch) which doesn't create an extra layer. This variants can be handy in a project that uses multiple programming language as you need a lot of tools. By using this in [multi-stage build][], you can slim down the docker image that need to be pulled.
[multi-stage build]: https://docs.docker.com/develop/develop-images/multistage-build/
<details><summary>example</summary>
- using `glibc` variant
```dockerfile
FROM nu:0.2-glibc as shell
FROM node:slim
# Build your plugins
COPY --from=shell /bin/nu /bin/
# Something else
ENTRYPOINT ["nu"]
```
- using `musl` variant
```dockerfile
FROM nu:musl as shell
FROM go:alpine
# Build your plugins
COPY --from=shell /bin/nu /bin/
# Something else
ENTRYPOINT ["nu"]
```
</details>
### `nu:<version>-<libc-variant>-distroless`
This image is base on [Distroless](https://github.com/GoogleContainerTools/distroless) which usually to contain only your application and its runtime dependencies. This image do not contain package managers, shells or any other programs you would expect to find in a standard Linux distribution except for nushell itself. All distroless variant always contains:
- ca-certificates
- A /etc/passwd entry for a root user
- A /tmp directory
- tzdata
As for `glibc-distroless` variant, it **adds**:
- glibc
- libssl
- openssl
> Most likely you want to use this in CI/CD environment for plugins that can be statically compiled.
<details><summary>example</summary>
```dockerfile
FROM nu:musl-distroless
COPY target/x86_64-unknown-linux-musl/release/nu_plugin_* /bin/
ENTRYPOINT ["nu"]
```
</details>
### `nu:<version>-<libc-variant>-busybox`
This image is based on [Busybox](https://www.busybox.net/) which is a very good ingredient to craft space-efficient distributions. It combines tiny versions of many common UNIX utilities into a single small executable. It also provides replacements for most of the utilities you usually find in GNU fileutils, shellutils, etc. The utilities in BusyBox generally have fewer options than their full-featured GNU cousins; however, the options that are included provide the expected functionality and behave very much like their GNU counterparts. Basically, this image provides a fairly complete environment for any small or embedded system.
> Use this only if you need common utilities like `tar`, `awk`, and many more but don't want extra blob like nushell plugins and others.
<details><summary>example</summary>
```dockerfile
FROM nu:0.2-glibc-busybox
ADD https://github.com/user/repo/releases/download/latest/nu_plugin_cowsay.tar.gz /tmp/
RUN tar xzfv nu_plugin_cowsay.tar.gz -C /bin --strip=1 nu_plugin_cowsay
ENTRYPOINT ["nu"]
```
</details>
[musl]: https://www.musl-libc.org/
[alpine]: https://hub.docker.com/_/alpine/

13
features.toml Normal file
View file

@ -0,0 +1,13 @@
[hintsv1]
description = "Adding hints based upon error states in the syntax highlighter"
enabled = false
[coloring_in_tokens]
description = "Move coloring into the TokensIterator so they can be atomic with the rest of the iterator"
reason = """
This is laying the groundwork for merging coloring and parsing. It also makes token_nodes.atomic() naturally
work with coloring, which is pretty useful on its own.
"""
enabled = false

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View file

@ -1 +1 @@
nightly-2019-08-30
beta-2019-09-25

View file

@ -1,4 +1,3 @@
use crate::commands::autoview;
use crate::commands::classified::{
ClassifiedCommand, ClassifiedInputStream, ClassifiedPipeline, ExternalCommand, InternalCommand,
StreamNext,
@ -7,22 +6,29 @@ use crate::commands::plugin::JsonRpc;
use crate::commands::plugin::{PluginCommand, PluginSink};
use crate::commands::whole_stream_command;
use crate::context::Context;
use crate::data::config;
use crate::data::Value;
pub(crate) use crate::errors::ShellError;
use crate::fuzzysearch::{interactive_fuzzy_search, SelectionResult};
use crate::git::current_branch;
use crate::object::Value;
use crate::parser::registry::Signature;
use crate::parser::{hir, CallNode, Pipeline, PipelineElement, TokenNode};
use crate::parser::{
hir,
hir::syntax_shape::{expand_syntax, PipelineShape},
hir::{expand_external_tokens::expand_external_tokens, tokens_iterator::TokensIterator},
TokenNode,
};
use crate::prelude::*;
use log::{debug, trace};
use regex::Regex;
use rustyline::error::ReadlineError;
use rustyline::{self, config::Configurer, config::EditMode, ColorMode, Config, Editor};
use std::env;
use std::error::Error;
use std::io::{BufRead, BufReader, Write};
use std::iter::Iterator;
use std::sync::atomic::{AtomicBool, Ordering};
use std::path::PathBuf;
use std::sync::atomic::Ordering;
#[derive(Debug)]
pub enum MaybeOwned<'a, T> {
@ -60,6 +66,7 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
let result = match reader.read_line(&mut input) {
Ok(count) => {
trace!("processing response ({} bytes)", count);
trace!("response: {}", input);
let response = serde_json::from_str::<JsonRpc<Result<Signature, ShellError>>>(&input);
match response {
@ -69,28 +76,39 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
trace!("processing {:?}", params);
if params.is_filter {
let fname = fname.to_string();
let name = params.name.clone();
context.add_commands(vec![whole_stream_command(PluginCommand::new(
name, fname, params,
))]);
Ok(())
let name = params.name.clone();
let fname = fname.to_string();
if let Some(_) = context.get_command(&name) {
trace!("plugin {:?} already loaded.", &name);
} else {
let fname = fname.to_string();
let name = params.name.clone();
context.add_commands(vec![whole_stream_command(PluginSink::new(
name, fname, params,
))]);
Ok(())
if params.is_filter {
context.add_commands(vec![whole_stream_command(
PluginCommand::new(name, fname, params),
)]);
} else {
context.add_commands(vec![whole_stream_command(PluginSink::new(
name, fname, params,
))]);
};
}
Ok(())
}
Err(e) => Err(e),
},
Err(e) => Err(ShellError::string(format!("Error: {:?}", e))),
Err(e) => {
trace!("incompatible plugin {:?}", input);
Err(ShellError::untagged_runtime_error(format!(
"Error: {:?}",
e
)))
}
}
}
Err(e) => Err(ShellError::string(format!("Error: {:?}", e))),
Err(e) => Err(ShellError::untagged_runtime_error(format!(
"Error: {:?}",
e
))),
};
let _ = child.wait();
@ -98,38 +116,12 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
result
}
fn load_plugins_in_dir(path: &std::path::PathBuf, context: &mut Context) -> Result<(), ShellError> {
let re_bin = Regex::new(r"^nu_plugin_[A-Za-z_]+$")?;
let re_exe = Regex::new(r"^nu_plugin_[A-Za-z_]+\.(exe|bat)$")?;
fn search_paths() -> Vec<std::path::PathBuf> {
let mut search_paths = Vec::new();
trace!("Looking for plugins in {:?}", path);
match std::fs::read_dir(path) {
Ok(p) => {
for entry in p {
let entry = entry?;
let filename = entry.file_name();
let f_name = filename.to_string_lossy();
if re_bin.is_match(&f_name) || re_exe.is_match(&f_name) {
let mut load_path = path.clone();
trace!("Found {:?}", f_name);
load_path.push(f_name.to_string());
load_plugin(&load_path, context)?;
}
}
}
_ => {}
}
Ok(())
}
fn load_plugins(context: &mut Context) -> Result<(), ShellError> {
match env::var_os("PATH") {
Some(paths) => {
for path in env::split_paths(&paths) {
let _ = load_plugins_in_dir(&path, context);
}
search_paths = env::split_paths(&paths).collect::<Vec<_>>();
}
None => println!("PATH is not defined in the environment."),
}
@ -140,7 +132,10 @@ fn load_plugins(context: &mut Context) -> Result<(), ShellError> {
let mut path = std::path::PathBuf::from(".");
path.push("target");
path.push("debug");
let _ = load_plugins_in_dir(&path, context);
if path.exists() {
search_paths.push(path);
}
}
#[cfg(not(debug_assertions))]
@ -150,12 +145,104 @@ fn load_plugins(context: &mut Context) -> Result<(), ShellError> {
path.push("target");
path.push("release");
let _ = load_plugins_in_dir(&path, context);
if path.exists() {
search_paths.push(path);
}
}
// permit Nu finding and picking up development plugins
// if there are any first.
search_paths.reverse();
search_paths
}
fn load_plugins(context: &mut Context) -> Result<(), ShellError> {
let opts = glob::MatchOptions {
case_sensitive: false,
require_literal_separator: false,
require_literal_leading_dot: false,
};
for path in search_paths() {
let mut pattern = path.to_path_buf();
pattern.push(std::path::Path::new("nu_plugin_[a-z]*"));
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 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_alphabetic() || c == '_' || c == '.')
}
#[cfg(not(windows))]
{
bin_name
.chars()
.all(|c| c.is_ascii_alphabetic() || 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);
}
}
}
}
}
Ok(())
}
pub struct History;
impl History {
pub fn path() -> PathBuf {
const FNAME: &str = "history.txt";
config::user_data()
.map(|mut p| {
p.push(FNAME);
p
})
.unwrap_or(PathBuf::from(FNAME))
}
}
pub async fn cli() -> Result<(), Box<dyn Error>> {
let mut context = Context::basic()?;
@ -163,7 +250,7 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
use crate::commands::*;
context.add_commands(vec![
whole_stream_command(PS),
whole_stream_command(PWD),
whole_stream_command(LS),
whole_stream_command(CD),
whole_stream_command(Size),
@ -171,15 +258,15 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
whole_stream_command(Next),
whole_stream_command(Previous),
whole_stream_command(Debug),
whole_stream_command(Lines),
whole_stream_command(Shells),
whole_stream_command(SplitColumn),
whole_stream_command(SplitRow),
whole_stream_command(Lines),
whole_stream_command(Reject),
whole_stream_command(Reverse),
whole_stream_command(Append),
whole_stream_command(Prepend),
whole_stream_command(Trim),
whole_stream_command(ToArray),
whole_stream_command(ToBSON),
whole_stream_command(ToCSV),
whole_stream_command(ToJSON),
@ -187,43 +274,50 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
whole_stream_command(ToDB),
whole_stream_command(ToTOML),
whole_stream_command(ToTSV),
whole_stream_command(ToURL),
whole_stream_command(ToYAML),
whole_stream_command(SortBy),
whole_stream_command(GroupBy),
whole_stream_command(Tags),
whole_stream_command(Count),
whole_stream_command(First),
whole_stream_command(Last),
whole_stream_command(FromArray),
whole_stream_command(FromArray),
whole_stream_command(Env),
whole_stream_command(FromCSV),
whole_stream_command(FromTSV),
whole_stream_command(FromSSV),
whole_stream_command(FromINI),
whole_stream_command(FromBSON),
whole_stream_command(FromJSON),
whole_stream_command(FromDB),
whole_stream_command(FromSQLite),
whole_stream_command(FromTOML),
whole_stream_command(FromURL),
whole_stream_command(FromXML),
whole_stream_command(FromYAML),
whole_stream_command(FromYML),
whole_stream_command(Pick),
whole_stream_command(Get),
per_item_command(Remove),
per_item_command(Fetch),
per_item_command(Open),
per_item_command(Post),
per_item_command(Where),
per_item_command(Echo),
whole_stream_command(Config),
whole_stream_command(SkipWhile),
per_item_command(Enter),
per_item_command(Help),
per_item_command(History),
whole_stream_command(Exit),
whole_stream_command(Autoview),
whole_stream_command(Pivot),
per_item_command(Cpy),
whole_stream_command(Date),
per_item_command(Mkdir),
per_item_command(Move),
whole_stream_command(Save),
whole_stream_command(Table),
whole_stream_command(VTable),
whole_stream_command(Version),
whole_stream_command(Which),
]);
@ -235,6 +329,7 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
)]);
}
}
let _ = load_plugins(&mut context);
let config = Config::builder().color_mode(ColorMode::Forced).build();
@ -246,28 +341,25 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
}
// we are ok if history does not exist
let _ = rl.load_history("history.txt");
let _ = rl.load_history(&History::path());
let ctrl_c = Arc::new(AtomicBool::new(false));
let cc = ctrl_c.clone();
let cc = context.ctrl_c.clone();
ctrlc::set_handler(move || {
cc.store(true, Ordering::SeqCst);
})
.expect("Error setting Ctrl-C handler");
let mut ctrlcbreak = false;
loop {
if ctrl_c.load(Ordering::SeqCst) {
ctrl_c.store(false, Ordering::SeqCst);
if context.ctrl_c.load(Ordering::SeqCst) {
context.ctrl_c.store(false, Ordering::SeqCst);
continue;
}
let cwd = context.shell_manager.path();
rl.set_helper(Some(crate::shell::Helper::new(
context.shell_manager.clone(),
)));
rl.set_helper(Some(crate::shell::Helper::new(context.clone())));
let edit_mode = crate::object::config::config(Span::unknown())?
let edit_mode = config::config(Tag::unknown())?
.get("edit_mode")
.map(|s| match s.as_string().unwrap().as_ref() {
"vi" => EditMode::Vi,
@ -278,22 +370,70 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
rl.set_edit_mode(edit_mode);
let readline = rl.readline(&format!(
// Register Ctrl-r for history fuzzy search
// rustyline doesn't support custom commands, so we override Ctrl-D (EOF)
// https://github.com/nushell/nushell/issues/689
#[cfg(all(not(windows), feature = "crossterm"))]
rl.bind_sequence(rustyline::KeyPress::Ctrl('R'), rustyline::Cmd::EndOfFile);
// Redefine Ctrl-D to same command as Ctrl-C
rl.bind_sequence(rustyline::KeyPress::Ctrl('D'), rustyline::Cmd::Interrupt);
let prompt = &format!(
"{}{}> ",
cwd,
match current_branch() {
Some(s) => format!("({})", s),
None => "".to_string(),
}
));
);
let mut initial_command = Some(String::new());
let mut readline = Err(ReadlineError::Eof);
while let Some(ref cmd) = initial_command {
readline = rl.readline_with_initial(prompt, (&cmd, ""));
if let Err(ReadlineError::Eof) = &readline {
// Fuzzy search in history
let lines = rl.history().iter().rev().map(|s| s.as_str()).collect();
let selection = interactive_fuzzy_search(&lines, 5); // Clears last line with prompt
match selection {
SelectionResult::Selected(line) => {
println!("{}{}", &prompt, &line); // TODO: colorize prompt
readline = Ok(line.clone());
initial_command = None;
}
SelectionResult::Edit(line) => {
initial_command = Some(line);
}
SelectionResult::NoSelection => {
readline = Ok("".to_string());
initial_command = None;
}
}
} else {
initial_command = None;
}
}
match process_line(readline, &mut context).await {
LineResult::Success(line) => {
rl.add_history_entry(line.clone());
let _ = rl.save_history(&History::path());
}
LineResult::CtrlC => {
let config_ctrlc_exit = config::config(Tag::unknown())?
.get("ctrlc_exit")
.map(|s| match s.as_string().unwrap().as_ref() {
"true" => true,
_ => false,
})
.unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells
if !config_ctrlc_exit {
continue;
}
if ctrlcbreak {
let _ = rl.save_history(&History::path());
std::process::exit(0);
} else {
context.with_host(|host| host.stdout("CTRL-C pressed (again to quit)"));
@ -302,21 +442,12 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
}
}
LineResult::Error(mut line, err) => {
LineResult::Error(line, err) => {
rl.add_history_entry(line.clone());
let diag = err.to_diagnostic();
let _ = rl.save_history(&History::path());
context.with_host(|host| {
let writer = host.err_termcolor();
line.push_str(" ");
let files = crate::parser::Files::new(line);
let _ = std::panic::catch_unwind(move || {
let _ = language_reporting::emit(
&mut writer.lock(),
&files,
&diag,
&language_reporting::DefaultConfig,
);
});
print_err(err, host, &Text::from(line));
})
}
@ -328,11 +459,19 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
}
// we are ok if we can not save history
let _ = rl.save_history("history.txt");
let _ = rl.save_history(&History::path());
Ok(())
}
fn chomp_newline(s: &str) -> &str {
if s.ends_with('\n') {
&s[..s.len() - 1]
} else {
s
}
}
enum LineResult {
Success(String),
Error(String, ShellError),
@ -345,9 +484,11 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
Ok(line) if line.trim() == "" => LineResult::Success(line.clone()),
Ok(line) => {
let line = chomp_newline(line);
let result = match crate::parser::parse(&line) {
Err(err) => {
return LineResult::Error(line.clone(), err);
return LineResult::Error(line.to_string(), err);
}
Ok(val) => val,
@ -358,7 +499,7 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
let mut pipeline = match classify_pipeline(&result, ctx, &Text::from(line)) {
Ok(pipeline) => pipeline,
Err(err) => return LineResult::Error(line.clone(), err),
Err(err) => return LineResult::Error(line.to_string(), err),
};
match pipeline.commands.last() {
@ -366,8 +507,8 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
_ => pipeline
.commands
.push(ClassifiedCommand::Internal(InternalCommand {
command: whole_stream_command(autoview::Autoview),
name_span: Span::unknown(),
name: "autoview".to_string(),
name_tag: Tag::unknown(),
args: hir::Call::new(
Box::new(hir::Expression::synthetic_string("autoview")),
None,
@ -379,6 +520,44 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
let mut input = ClassifiedInputStream::new();
let mut iter = pipeline.commands.into_iter().peekable();
let mut is_first_command = true;
// Check the config to see if we need to update the path
// TODO: make sure config is cached so we don't path this load every call
let config = crate::data::config::read(Tag::unknown(), &None).unwrap();
if config.contains_key("path") {
// Override the path with what they give us from config
let value = config.get("path");
match value {
Some(value) => match value {
Tagged {
item: Value::Table(table),
..
} => {
let mut paths = vec![];
for val in table {
let path_str = val.as_string();
match path_str {
Err(_) => {}
Ok(path_str) => {
paths.push(PathBuf::from(path_str));
}
}
}
let path_os_string = std::env::join_paths(&paths);
match path_os_string {
Ok(path_os_string) => {
std::env::set_var("PATH", path_os_string);
}
Err(_) => {}
}
}
_ => {}
},
None => {}
}
}
loop {
let item: Option<ClassifiedCommand> = iter.next();
@ -387,16 +566,24 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
input = match (item, next) {
(None, _) => break,
(Some(ClassifiedCommand::Dynamic(_)), _)
| (_, Some(ClassifiedCommand::Dynamic(_))) => {
return LineResult::Error(
line.to_string(),
ShellError::unimplemented("Dynamic commands"),
)
}
(Some(ClassifiedCommand::Expr(_)), _) => {
return LineResult::Error(
line.clone(),
line.to_string(),
ShellError::unimplemented("Expression-only commands"),
)
}
(_, Some(ClassifiedCommand::Expr(_))) => {
return LineResult::Error(
line.clone(),
line.to_string(),
ShellError::unimplemented("Expression-only commands"),
)
}
@ -404,22 +591,46 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
(
Some(ClassifiedCommand::Internal(left)),
Some(ClassifiedCommand::External(_)),
) => match left.run(ctx, input, Text::from(line)).await {
) => match left.run(ctx, input, Text::from(line), is_first_command) {
Ok(val) => ClassifiedInputStream::from_input_stream(val),
Err(err) => return LineResult::Error(line.clone(), err),
Err(err) => return LineResult::Error(line.to_string(), err),
},
(Some(ClassifiedCommand::Internal(left)), Some(_)) => {
match left.run(ctx, input, Text::from(line)).await {
match left.run(ctx, input, Text::from(line), is_first_command) {
Ok(val) => ClassifiedInputStream::from_input_stream(val),
Err(err) => return LineResult::Error(line.clone(), err),
Err(err) => return LineResult::Error(line.to_string(), err),
}
}
(Some(ClassifiedCommand::Internal(left)), None) => {
match left.run(ctx, input, Text::from(line)).await {
Ok(val) => ClassifiedInputStream::from_input_stream(val),
Err(err) => return LineResult::Error(line.clone(), err),
match left.run(ctx, input, Text::from(line), is_first_command) {
Ok(val) => {
use futures::stream::TryStreamExt;
let mut output_stream: OutputStream = val.into();
loop {
match output_stream.try_next().await {
Ok(Some(ReturnSuccess::Value(Tagged {
item: Value::Error(e),
..
}))) => {
return LineResult::Error(line.to_string(), e);
}
Ok(Some(_item)) => {
if ctx.ctrl_c.load(Ordering::SeqCst) {
break;
}
}
_ => {
break;
}
}
}
return LineResult::Success(line.to_string());
}
Err(err) => return LineResult::Error(line.to_string(), err),
}
}
@ -428,32 +639,31 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
Some(ClassifiedCommand::External(_)),
) => match left.run(ctx, input, StreamNext::External).await {
Ok(val) => val,
Err(err) => return LineResult::Error(line.clone(), err),
Err(err) => return LineResult::Error(line.to_string(), err),
},
(Some(ClassifiedCommand::External(left)), Some(_)) => {
match left.run(ctx, input, StreamNext::Internal).await {
Ok(val) => val,
Err(err) => return LineResult::Error(line.clone(), err),
Err(err) => return LineResult::Error(line.to_string(), err),
}
}
(Some(ClassifiedCommand::External(left)), None) => {
match left.run(ctx, input, StreamNext::Last).await {
Ok(val) => val,
Err(err) => return LineResult::Error(line.clone(), err),
Err(err) => return LineResult::Error(line.to_string(), err),
}
}
}
};
is_first_command = false;
}
LineResult::Success(line.clone())
LineResult::Success(line.to_string())
}
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
Err(ReadlineError::Eof) => {
println!("CTRL-D");
LineResult::Break
}
Err(ReadlineError::Eof) => LineResult::Break,
Err(err) => {
println!("Error: {:?}", err);
LineResult::Break
@ -466,98 +676,52 @@ fn classify_pipeline(
context: &Context,
source: &Text,
) -> Result<ClassifiedPipeline, ShellError> {
let pipeline = pipeline.as_pipeline()?;
let mut pipeline_list = vec![pipeline.clone()];
let mut iterator = TokensIterator::all(&mut pipeline_list, pipeline.span());
let Pipeline { parts, .. } = pipeline;
let commands: Result<Vec<_>, ShellError> = parts
.iter()
.map(|item| classify_command(&item, context, &source))
.collect();
Ok(ClassifiedPipeline {
commands: commands?,
})
}
fn classify_command(
command: &PipelineElement,
context: &Context,
source: &Text,
) -> Result<ClassifiedCommand, ShellError> {
let call = command.call();
match call {
// If the command starts with `^`, treat it as an external command no matter what
call if call.head().is_external() => {
let name_span = call.head().expect_external();
let name = name_span.slice(source);
Ok(external_command(call, source, name.tagged(name_span)))
}
// Otherwise, if the command is a bare word, we'll need to triage it
call if call.head().is_bare() => {
let head = call.head();
let name = head.source(source);
match context.has_command(name) {
// if the command is in the registry, it's an internal command
true => {
let command = context.get_command(name);
let config = command.signature();
trace!(target: "nu::build_pipeline", "classifying {:?}", config);
let args: hir::Call = config.parse_args(call, &context, source)?;
trace!(target: "nu::build_pipeline", "args :: {}", args.debug(source));
Ok(ClassifiedCommand::Internal(InternalCommand {
command,
name_span: head.span().clone(),
args,
}))
}
// otherwise, it's an external command
false => Ok(external_command(call, source, name.tagged(head.span()))),
}
}
// If the command is something else (like a number or a variable), that is currently unsupported.
// We might support `$somevar` as a curried command in the future.
call => Err(ShellError::invalid_command(call.head().span())),
}
expand_syntax(
&PipelineShape,
&mut iterator,
&context.expand_context(source, pipeline.span()),
)
}
// Classify this command as an external command, which doesn't give special meaning
// to nu syntactic constructs, and passes all arguments to the external command as
// strings.
fn external_command(
call: &Tagged<CallNode>,
pub(crate) fn external_command(
tokens: &mut TokensIterator,
source: &Text,
name: Tagged<&str>,
) -> ClassifiedCommand {
let arg_list_strings: Vec<Tagged<String>> = match call.children() {
Some(args) => args
) -> Result<ClassifiedCommand, ShellError> {
let arg_list_strings = expand_external_tokens(tokens, source)?;
Ok(ClassifiedCommand::External(ExternalCommand {
name: name.to_string(),
name_tag: name.tag(),
args: arg_list_strings
.iter()
.filter_map(|i| match i {
TokenNode::Whitespace(_) => None,
other => Some(Tagged::from_simple_spanned_item(
other.as_external_arg(source),
other.span(),
)),
.map(|x| Tagged {
tag: x.span.into(),
item: x.item.clone(),
})
.collect(),
None => vec![],
};
let (name, tag) = name.into_parts();
ClassifiedCommand::External(ExternalCommand {
name: name.to_string(),
name_span: tag.span,
args: arg_list_strings,
})
}))
}
pub fn print_err(err: ShellError, host: &dyn Host, source: &Text) {
let diag = err.to_diagnostic();
let writer = host.err_termcolor();
let mut source = source.to_string();
source.push_str(" ");
let files = crate::parser::Files::new(source);
let _ = std::panic::catch_unwind(move || {
let _ = language_reporting::emit(
&mut writer.lock(),
&files,
&diag,
&language_reporting::DefaultConfig,
);
});
}

View file

@ -1,6 +1,7 @@
#[macro_use]
pub(crate) mod macros;
pub(crate) mod append;
pub(crate) mod args;
pub(crate) mod autoview;
pub(crate) mod cd;
@ -8,24 +9,31 @@ pub(crate) mod classified;
pub(crate) mod clip;
pub(crate) mod command;
pub(crate) mod config;
pub(crate) mod count;
pub(crate) mod cp;
pub(crate) mod date;
pub(crate) mod debug;
pub(crate) mod echo;
pub(crate) mod enter;
pub(crate) mod env;
pub(crate) mod exit;
pub(crate) mod fetch;
pub(crate) mod first;
pub(crate) mod from_array;
pub(crate) mod from_bson;
pub(crate) mod from_csv;
pub(crate) mod from_ini;
pub(crate) mod from_json;
pub(crate) mod from_sqlite;
pub(crate) mod from_ssv;
pub(crate) mod from_toml;
pub(crate) mod from_tsv;
pub(crate) mod from_url;
pub(crate) mod from_xml;
pub(crate) mod from_yaml;
pub(crate) mod get;
pub(crate) mod group_by;
pub(crate) mod help;
pub(crate) mod history;
pub(crate) mod last;
pub(crate) mod lines;
pub(crate) mod ls;
@ -35,10 +43,12 @@ pub(crate) mod next;
pub(crate) mod nth;
pub(crate) mod open;
pub(crate) mod pick;
pub(crate) mod pivot;
pub(crate) mod plugin;
pub(crate) mod post;
pub(crate) mod prepend;
pub(crate) mod prev;
pub(crate) mod ps;
pub(crate) mod pwd;
pub(crate) mod reject;
pub(crate) mod reverse;
pub(crate) mod rm;
@ -51,17 +61,16 @@ pub(crate) mod split_column;
pub(crate) mod split_row;
pub(crate) mod table;
pub(crate) mod tags;
pub(crate) mod to_array;
pub(crate) mod to_bson;
pub(crate) mod to_csv;
pub(crate) mod to_json;
pub(crate) mod to_sqlite;
pub(crate) mod to_toml;
pub(crate) mod to_tsv;
pub(crate) mod to_url;
pub(crate) mod to_yaml;
pub(crate) mod trim;
pub(crate) mod version;
pub(crate) mod vtable;
pub(crate) mod where_;
pub(crate) mod which_;
@ -72,27 +81,36 @@ pub(crate) use command::{
UnevaluatedCallInfo, WholeStreamCommand,
};
pub(crate) use append::Append;
pub(crate) use classified::ClassifiedCommand;
pub(crate) use config::Config;
pub(crate) use count::Count;
pub(crate) use cp::Cpy;
pub(crate) use date::Date;
pub(crate) use debug::Debug;
pub(crate) use echo::Echo;
pub(crate) use enter::Enter;
pub(crate) use env::Env;
pub(crate) use exit::Exit;
pub(crate) use fetch::Fetch;
pub(crate) use first::First;
pub(crate) use from_array::FromArray;
pub(crate) use from_bson::FromBSON;
pub(crate) use from_csv::FromCSV;
pub(crate) use from_ini::FromINI;
pub(crate) use from_json::FromJSON;
pub(crate) use from_sqlite::FromDB;
pub(crate) use from_sqlite::FromSQLite;
pub(crate) use from_ssv::FromSSV;
pub(crate) use from_toml::FromTOML;
pub(crate) use from_tsv::FromTSV;
pub(crate) use from_url::FromURL;
pub(crate) use from_xml::FromXML;
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 help::Help;
pub(crate) use history::History;
pub(crate) use last::Last;
pub(crate) use lines::Lines;
pub(crate) use ls::LS;
@ -102,9 +120,11 @@ pub(crate) use next::Next;
pub(crate) use nth::Nth;
pub(crate) use open::Open;
pub(crate) use pick::Pick;
pub(crate) use pivot::Pivot;
pub(crate) use post::Post;
pub(crate) use prepend::Prepend;
pub(crate) use prev::Previous;
pub(crate) use ps::PS;
pub(crate) use pwd::PWD;
pub(crate) use reject::Reject;
pub(crate) use reverse::Reverse;
pub(crate) use rm::Remove;
@ -117,7 +137,6 @@ pub(crate) use split_column::SplitColumn;
pub(crate) use split_row::SplitRow;
pub(crate) use table::Table;
pub(crate) use tags::Tags;
pub(crate) use to_array::ToArray;
pub(crate) use to_bson::ToBSON;
pub(crate) use to_csv::ToCSV;
pub(crate) use to_json::ToJSON;
@ -125,9 +144,9 @@ pub(crate) use to_sqlite::ToDB;
pub(crate) use to_sqlite::ToSQLite;
pub(crate) use to_toml::ToTOML;
pub(crate) use to_tsv::ToTSV;
pub(crate) use to_url::ToURL;
pub(crate) use to_yaml::ToYAML;
pub(crate) use trim::Trim;
pub(crate) use version::Version;
pub(crate) use vtable::VTable;
pub(crate) use where_::Where;
pub(crate) use which_::Which;

47
src/commands/append.rs Normal file
View file

@ -0,0 +1,47 @@
use crate::commands::WholeStreamCommand;
use crate::errors::ShellError;
use crate::parser::CommandRegistry;
use crate::prelude::*;
#[derive(Deserialize)]
struct AppendArgs {
row: Tagged<Value>,
}
pub struct Append;
impl WholeStreamCommand for Append {
fn name(&self) -> &str {
"append"
}
fn signature(&self) -> Signature {
Signature::build("append").required(
"row value",
SyntaxShape::Any,
"the value of the row to append to the table",
)
}
fn usage(&self) -> &str {
"Append the given row to the table"
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, append)?.run()
}
}
fn append(
AppendArgs { row }: AppendArgs,
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let mut after: VecDeque<Tagged<Value>> = VecDeque::new();
after.push_back(row);
Ok(OutputStream::from_input(input.values.chain(after)))
}

View file

@ -1,4 +1,4 @@
use crate::object::Value;
use crate::data::Value;
#[derive(Debug)]
pub enum LogLevel {}

View file

@ -1,9 +1,14 @@
use crate::commands::{RawCommandArgs, WholeStreamCommand};
use crate::errors::ShellError;
use crate::parser::hir::{Expression, NamedArguments};
use crate::prelude::*;
use futures::stream::TryStreamExt;
use std::sync::atomic::Ordering;
pub struct Autoview;
const STREAM_PAGE_SIZE: u64 = 50;
#[derive(Deserialize)]
pub struct AutoviewArgs {}
@ -31,68 +36,138 @@ impl WholeStreamCommand for Autoview {
pub fn autoview(
AutoviewArgs {}: AutoviewArgs,
mut context: RunnableContext,
context: RunnableContext,
raw: RawCommandArgs,
) -> Result<OutputStream, ShellError> {
Ok(OutputStream::new(async_stream_block! {
let input = context.input.drain_vec().await;
let binary = context.get_command("binaryview");
let text = context.get_command("textview");
let table = context.get_command("table");
if input.len() > 0 {
if let Tagged {
item: Value::Binary(_),
..
} = input[0usize]
{
let binary = context.expect_command("binaryview");
let result = binary.run(raw.with_input(input), &context.commands);
result.collect::<Vec<_>>().await;
} else if is_single_text_value(&input) {
let text = context.expect_command("textview");
let result = text.run(raw.with_input(input), &context.commands);
result.collect::<Vec<_>>().await;
} else if equal_shapes(&input) {
let table = context.expect_command("table");
let result = table.run(raw.with_input(input), &context.commands);
result.collect::<Vec<_>>().await;
} else {
let table = context.expect_command("table");
let result = table.run(raw.with_input(input), &context.commands);
result.collect::<Vec<_>>().await;
Ok(OutputStream::new(async_stream! {
let mut output_stream: OutputStream = context.input.into();
match output_stream.try_next().await {
Ok(Some(x)) => {
match output_stream.try_next().await {
Ok(Some(y)) => {
let ctrl_c = context.ctrl_c.clone();
let stream = async_stream! {
yield Ok(x);
yield Ok(y);
loop {
match output_stream.try_next().await {
Ok(Some(z)) => {
if ctrl_c.load(Ordering::SeqCst) {
break;
}
yield Ok(z);
}
_ => break,
}
}
};
if let Some(table) = table {
let mut new_output_stream: OutputStream = stream.to_output_stream();
let mut finished = false;
let mut current_idx = 0;
loop {
let mut new_input = VecDeque::new();
for _ in 0..STREAM_PAGE_SIZE {
match new_output_stream.try_next().await {
Ok(Some(a)) => {
if let ReturnSuccess::Value(v) = a {
new_input.push_back(v);
}
}
_ => {
finished = true;
break;
}
}
}
let raw = raw.clone();
let mut command_args = raw.with_input(new_input.into());
let mut named_args = NamedArguments::new();
named_args.insert_optional("start_number", Some(Expression::number(current_idx, Tag::unknown())));
command_args.call_info.args.named = Some(named_args);
let result = table.run(command_args, &context.commands, false);
result.collect::<Vec<_>>().await;
if finished {
break;
} else {
current_idx += STREAM_PAGE_SIZE;
}
}
}
}
_ => {
if let ReturnSuccess::Value(x) = x {
match x {
Tagged {
item: Value::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(Value::string(s).tagged(Tag { anchor, span }));
let result = text.run(raw.with_input(stream.into()), &context.commands, false);
result.collect::<Vec<_>>().await;
} else {
println!("{}", s);
}
}
Tagged {
item: Value::Primitive(Primitive::String(s)),
..
} => {
println!("{}", s);
}
Tagged { item: Value::Primitive(Primitive::Binary(ref b)), .. } => {
if let Some(binary) = binary {
let mut stream = VecDeque::new();
stream.push_back(x.clone());
let result = binary.run(raw.with_input(stream.into()), &context.commands, false);
result.collect::<Vec<_>>().await;
} else {
use pretty_hex::*;
println!("{:?}", b.hex_dump());
}
}
Tagged { item: Value::Error(e), .. } => {
yield Err(e);
}
Tagged { item: ref item, .. } => {
if let Some(table) = table {
let mut stream = VecDeque::new();
stream.push_back(x.clone());
let result = table.run(raw.with_input(stream.into()), &context.commands, false);
result.collect::<Vec<_>>().await;
} else {
println!("{:?}", item);
}
}
}
}
}
}
}
_ => {
//println!("<no results>");
}
}
// Needed for async_stream to type check
if false {
yield ReturnSuccess::value(Value::nothing().tagged_unknown());
}
}))
}
fn equal_shapes(input: &Vec<Tagged<Value>>) -> bool {
let mut items = input.iter();
let item = match items.next() {
Some(item) => item,
None => return false,
};
let desc = item.data_descriptors();
for item in items {
if desc != item.data_descriptors() {
return false;
}
}
true
}
fn is_single_text_value(input: &Vec<Tagged<Value>>) -> bool {
if input.len() != 1 {
return false;
}
if let Tagged {
item: Value::Primitive(Primitive::String(_)),
..
} = input[0]
{
true
} else {
false
}
}

View file

@ -10,8 +10,11 @@ impl WholeStreamCommand for CD {
}
fn signature(&self) -> Signature {
Signature::build("cd")
.optional("directory", SyntaxType::Path)
Signature::build("cd").optional(
"directory",
SyntaxShape::Path,
"the directory to change to",
)
}
fn usage(&self) -> &str {

View file

@ -1,12 +1,11 @@
use crate::commands::Command;
use crate::parser::{hir, TokenNode};
use crate::prelude::*;
use bytes::{BufMut, BytesMut};
use derive_new::new;
use futures::stream::StreamExt;
use futures_codec::{Decoder, Encoder, Framed};
use log::{log_enabled, trace};
use std::io::{Error, ErrorKind};
use std::sync::Arc;
use subprocess::Exec;
/// A simple `Codec` implementation that splits up data into lines.
@ -73,126 +72,144 @@ impl ClassifiedInputStream {
}
}
#[derive(Debug)]
pub(crate) struct ClassifiedPipeline {
pub(crate) commands: Vec<ClassifiedCommand>,
}
#[derive(Debug, Eq, PartialEq)]
pub(crate) enum ClassifiedCommand {
#[allow(unused)]
Expr(TokenNode),
Internal(InternalCommand),
#[allow(unused)]
Dynamic(hir::Call),
External(ExternalCommand),
}
#[derive(new, Debug, Eq, PartialEq)]
pub(crate) struct InternalCommand {
pub(crate) command: Arc<Command>,
pub(crate) name_span: Span,
pub(crate) name: String,
pub(crate) name_tag: Tag,
pub(crate) args: hir::Call,
}
#[derive(new, Debug, Eq, PartialEq)]
pub(crate) struct DynamicCommand {
pub(crate) args: hir::Call,
}
impl InternalCommand {
pub(crate) async fn run(
pub(crate) fn run(
self,
context: &mut Context,
input: ClassifiedInputStream,
source: Text,
is_first_command: bool,
) -> Result<InputStream, ShellError> {
if log_enabled!(log::Level::Trace) {
trace!(target: "nu::run::internal", "->");
trace!(target: "nu::run::internal", "{}", self.command.name());
trace!(target: "nu::run::internal", "{}", self.name);
trace!(target: "nu::run::internal", "{}", self.args.debug(&source));
}
let objects: InputStream =
trace_stream!(target: "nu::trace_stream::internal", "input" = input.objects);
let result = context.run_command(
self.command,
self.name_span.clone(),
context.source_map.clone(),
self.args,
&source,
objects,
);
let command = context.expect_command(&self.name);
let result = {
context.run_command(
command,
self.name_tag.clone(),
self.args,
&source,
objects,
is_first_command,
)
};
let result = trace_out_stream!(target: "nu::trace_stream::internal", source: &source, "output" = result);
let mut result = result.values;
let mut context = context.clone();
let mut stream = VecDeque::new();
while let Some(item) = result.next().await {
match item? {
ReturnSuccess::Action(action) => match action {
CommandAction::ChangePath(path) => {
context.shell_manager.set_path(path);
}
CommandAction::AddSpanSource(uuid, span_source) => {
context.add_span_source(uuid, span_source);
}
CommandAction::Exit => std::process::exit(0),
CommandAction::EnterHelpShell(value) => {
match value {
Tagged {
item: Value::Primitive(Primitive::String(cmd)),
..
} => {
context.shell_manager.insert_at_current(Box::new(
HelpShell::for_command(
Tagged::from_simple_spanned_item(
Value::string(cmd),
Span::unknown(),
),
&context.registry().clone(),
)?,
));
}
_ => {
context.shell_manager.insert_at_current(Box::new(
HelpShell::index(&context.registry().clone())?,
));
let stream = async_stream! {
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::EnterHelpShell(value) => {
match value {
Tagged {
item: Value::Primitive(Primitive::String(cmd)),
tag,
} => {
context.shell_manager.insert_at_current(Box::new(
HelpShell::for_command(
Value::string(cmd).tagged(tag),
&context.registry(),
).unwrap(),
));
}
_ => {
context.shell_manager.insert_at_current(Box::new(
HelpShell::index(&context.registry()).unwrap(),
));
}
}
}
}
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::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);
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()).unwrap(),
));
}
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
}
}
},
ReturnSuccess::Value(v) => {
stream.push_back(v);
Ok(ReturnSuccess::Value(v)) => {
yield Ok(v);
}
Err(x) => {
yield Ok(Value::Error(x).tagged_unknown());
break;
}
}
}
}
};
Ok(stream.into())
Ok(stream.to_input_stream())
}
}
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct ExternalCommand {
pub(crate) name: String,
pub(crate) name_span: Span,
pub(crate) name_tag: Tag,
pub(crate) args: Vec<Tagged<String>>,
}
#[derive(Debug)]
pub(crate) enum StreamNext {
Last,
External,
@ -208,7 +225,6 @@ impl ExternalCommand {
) -> Result<ClassifiedInputStream, ShellError> {
let stdin = input.stdin;
let inputs: Vec<Tagged<Value>> = input.objects.into_vec().await;
let name_span = self.name_span.clone();
trace!(target: "nu::run::external", "-> {}", self.name);
trace!(target: "nu::run::external", "inputs = {:?}", inputs);
@ -218,115 +234,66 @@ impl ExternalCommand {
arg_string.push_str(&arg);
}
trace!(target: "nu::run::external", "command = {:?}", self.name);
let mut process;
#[cfg(windows)]
{
process = Exec::shell(&self.name);
if arg_string.contains("$it") {
let mut first = true;
for i in &inputs {
if i.as_string().is_err() {
let mut span = None;
for arg in &self.args {
if arg.item.contains("$it") {
span = Some(arg.span());
}
}
if let Some(span) = span {
return Err(ShellError::labeled_error(
if arg_string.contains("$it") {
let input_strings = inputs
.iter()
.map(|i| {
i.as_string().map_err(|_| {
let arg = self.args.iter().find(|arg| arg.item.contains("$it"));
if let Some(arg) = arg {
ShellError::labeled_error(
"External $it needs string data",
"given object instead of string data",
span,
));
"given row instead of string data",
arg.tag(),
)
} else {
return Err(ShellError::string("Error: $it needs string data"));
ShellError::labeled_error(
"$it needs string data",
"given something else",
self.name_tag.clone(),
)
}
}
if !first {
process = process.arg("&&");
process = process.arg(&self.name);
} else {
first = false;
}
})
})
.collect::<Result<Vec<String>, ShellError>>()?;
for arg in &self.args {
if arg.chars().all(|c| c.is_whitespace()) {
continue;
}
process = process.arg(&arg.replace("$it", &i.as_string()?));
}
}
} else {
for arg in &self.args {
let arg_chars: Vec<_> = arg.chars().collect();
if arg_chars.len() > 1
&& arg_chars[0] == '"'
&& arg_chars[arg_chars.len() - 1] == '"'
{
// quoted string
let new_arg: String = arg_chars[1..arg_chars.len() - 1].iter().collect();
process = process.arg(new_arg);
let commands = input_strings.iter().map(|i| {
let args = self.args.iter().filter_map(|arg| {
if arg.chars().all(|c| c.is_whitespace()) {
None
} else {
process = process.arg(arg.item.clone());
Some(arg.replace("$it", &i))
}
});
format!("{} {}", self.name, itertools::join(args, " "))
});
process = Exec::shell(itertools::join(commands, " && "))
} else {
process = Exec::cmd(&self.name);
for arg in &self.args {
let arg_chars: Vec<_> = arg.chars().collect();
if arg_chars.len() > 1
&& arg_chars[0] == '"'
&& arg_chars[arg_chars.len() - 1] == '"'
{
// quoted string
let new_arg: String = arg_chars[1..arg_chars.len() - 1].iter().collect();
process = process.arg(new_arg);
} else {
process = process.arg(arg.item.clone());
}
}
}
#[cfg(not(windows))]
{
let mut new_arg_string = self.name.to_string();
if arg_string.contains("$it") {
let mut first = true;
for i in &inputs {
let i = match i.as_string() {
Err(_err) => {
let mut span = name_span;
for arg in &self.args {
if arg.item.contains("$it") {
span = arg.span();
}
}
return Err(ShellError::labeled_error(
"External $it needs string data",
"given object instead of string data",
span,
));
}
Ok(val) => val,
};
if !first {
new_arg_string.push_str("&&");
new_arg_string.push_str(&self.name);
} else {
first = false;
}
for arg in &self.args {
if arg.chars().all(|c| c.is_whitespace()) {
continue;
}
new_arg_string.push_str(" ");
new_arg_string.push_str(&arg.replace("$it", &i));
}
}
} else {
for arg in &self.args {
new_arg_string.push_str(" ");
new_arg_string.push_str(&arg);
}
}
process = Exec::shell(new_arg_string);
}
process = process.cwd(context.shell_manager.path());
trace!(target: "nu::run::external", "cwd = {:?}", context.shell_manager.path());
let mut process = match stream_next {
StreamNext::Last => process,
StreamNext::External | StreamNext::Internal => {
@ -334,32 +301,60 @@ impl ExternalCommand {
}
};
trace!(target: "nu::run::external", "set up stdout pipe");
if let Some(stdin) = stdin {
process = process.stdin(stdin);
}
let mut popen = process.popen()?;
trace!(target: "nu::run::external", "set up stdin pipe");
trace!(target: "nu::run::external", "built process {:?}", process);
match stream_next {
StreamNext::Last => {
popen.wait()?;
Ok(ClassifiedInputStream::new())
}
StreamNext::External => {
let stdout = popen.stdout.take().unwrap();
Ok(ClassifiedInputStream::from_stdout(stdout))
}
StreamNext::Internal => {
let stdout = popen.stdout.take().unwrap();
let file = futures::io::AllowStdIo::new(stdout);
let stream = Framed::new(file, LinesCodec {});
let stream = stream.map(move |line| {
Tagged::from_simple_spanned_item(Value::string(line.unwrap()), name_span)
});
Ok(ClassifiedInputStream::from_input_stream(
stream.boxed() as BoxStream<'static, Tagged<Value>>
))
let popen = process.popen();
trace!(target: "nu::run::external", "next = {:?}", stream_next);
let name_tag = self.name_tag.clone();
if let Ok(mut popen) = popen {
match stream_next {
StreamNext::Last => {
let _ = popen.detach();
loop {
match popen.poll() {
None => {
let _ = std::thread::sleep(std::time::Duration::new(0, 100000000));
}
_ => {
let _ = popen.terminate();
break;
}
}
}
Ok(ClassifiedInputStream::new())
}
StreamNext::External => {
let _ = popen.detach();
let stdout = popen.stdout.take().unwrap();
Ok(ClassifiedInputStream::from_stdout(stdout))
}
StreamNext::Internal => {
let _ = popen.detach();
let stdout = popen.stdout.take().unwrap();
let file = futures::io::AllowStdIo::new(stdout);
let stream = Framed::new(file, LinesCodec {});
let stream =
stream.map(move |line| Value::string(line.unwrap()).tagged(&name_tag));
Ok(ClassifiedInputStream::from_input_stream(
stream.boxed() as BoxStream<'static, Tagged<Value>>
))
}
}
} else {
return Err(ShellError::labeled_error(
"Command not found",
"command not found",
name_tag,
));
}
}
}

View file

@ -5,7 +5,6 @@ pub mod clipboard {
use crate::errors::ShellError;
use crate::prelude::*;
use futures::stream::StreamExt;
use futures_async_stream::async_stream_block;
use clipboard::{ClipboardContext, ClipboardProvider};
@ -40,10 +39,13 @@ pub mod clipboard {
ClipArgs {}: ClipArgs,
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream_block! {
let stream = async_stream! {
let values: Vec<Tagged<Value>> = input.values.collect().await;
inner_clip(values, name).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();
@ -51,7 +53,7 @@ pub mod clipboard {
Ok(OutputStream::from(stream))
}
async fn inner_clip(input: Vec<Tagged<Value>>, name: Span) -> OutputStream {
async fn inner_clip(input: Vec<Tagged<Value>>, name: Tag) -> OutputStream {
let mut clip_context: ClipboardContext = ClipboardProvider::new().unwrap();
let mut new_copy_data = String::new();

View file

@ -1,7 +1,6 @@
use crate::context::{SourceMap, SpanSource};
use crate::data::Value;
use crate::errors::ShellError;
use crate::evaluate::Scope;
use crate::object::Value;
use crate::parser::hir;
use crate::parser::{registry, ConfigDeserializer};
use crate::prelude::*;
@ -11,14 +10,13 @@ use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::Deref;
use std::path::PathBuf;
use uuid::Uuid;
use std::sync::atomic::AtomicBool;
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct UnevaluatedCallInfo {
pub args: hir::Call,
pub source: Text,
pub source_map: SourceMap,
pub name_span: Span,
pub name_tag: Tag,
}
impl ToDebug for UnevaluatedCallInfo {
@ -37,44 +35,15 @@ impl UnevaluatedCallInfo {
Ok(CallInfo {
args,
source_map: self.source_map,
name_span: self.name_span,
name_tag: self.name_tag,
})
}
pub fn has_it_or_block(&self) -> bool {
use hir::RawExpression;
use hir::Variable;
if let Some(positional) = &self.args.positional() {
for pos in positional {
match pos {
Tagged {
item: RawExpression::Variable(Variable::It(_)),
..
} => {
return true;
}
Tagged {
item: RawExpression::Block(_),
..
} => {
return true;
}
_ => {}
}
}
}
false
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct CallInfo {
pub args: registry::EvaluatedArgs,
pub source_map: SourceMap,
pub name_span: Span,
pub name_tag: Tag,
}
impl CallInfo {
@ -89,7 +58,7 @@ impl CallInfo {
args: T::deserialize(&mut deserializer)?,
context: RunnablePerItemContext {
shell_manager: shell_manager.clone(),
name: self.name_span,
name: self.name_tag.clone(),
},
callback,
})
@ -100,6 +69,7 @@ impl CallInfo {
#[get = "pub(crate)"]
pub struct CommandArgs {
pub host: Arc<Mutex<dyn Host>>,
pub ctrl_c: Arc<AtomicBool>,
pub shell_manager: ShellManager,
pub call_info: UnevaluatedCallInfo,
pub input: InputStream,
@ -109,6 +79,7 @@ pub struct CommandArgs {
#[get = "pub(crate)"]
pub struct RawCommandArgs {
pub host: Arc<Mutex<dyn Host>>,
pub ctrl_c: Arc<AtomicBool>,
pub shell_manager: ShellManager,
pub call_info: UnevaluatedCallInfo,
}
@ -117,6 +88,7 @@ impl RawCommandArgs {
pub fn with_input(self, input: Vec<Tagged<Value>>) -> CommandArgs {
CommandArgs {
host: self.host,
ctrl_c: self.ctrl_c,
shell_manager: self.shell_manager,
call_info: self.call_info,
input: input.into(),
@ -136,12 +108,14 @@ impl CommandArgs {
registry: &registry::CommandRegistry,
) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
let host = self.host.clone();
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, &Scope::empty())?;
Ok(EvaluatedWholeStreamCommandArgs::new(
host,
ctrl_c,
shell_manager,
call_info,
input,
@ -154,12 +128,13 @@ impl CommandArgs {
callback: fn(T, RunnableContext) -> Result<OutputStream, ShellError>,
) -> Result<RunnableArgs<T>, ShellError> {
let shell_manager = self.shell_manager.clone();
let source_map = self.call_info.source_map.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_span = args.call_info.name_span;
let mut deserializer = ConfigDeserializer::from_call_info(args.call_info);
let name_tag = args.call_info.name_tag;
let mut deserializer = ConfigDeserializer::from_call_info(call_info);
Ok(RunnableArgs {
args: T::deserialize(&mut deserializer)?,
@ -167,9 +142,9 @@ impl CommandArgs {
input,
commands: registry.clone(),
shell_manager,
name: name_span,
source_map,
name: name_tag,
host,
ctrl_c,
},
callback,
})
@ -182,17 +157,20 @@ impl CommandArgs {
) -> 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 source_map = self.call_info.source_map.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_span = args.call_info.name_span;
let mut deserializer = ConfigDeserializer::from_call_info(args.call_info);
let name_tag = args.call_info.name_tag;
let mut deserializer = ConfigDeserializer::from_call_info(call_info.clone());
Ok(RunnableRawArgs {
args: T::deserialize(&mut deserializer)?,
@ -200,9 +178,9 @@ impl CommandArgs {
input,
commands: registry.clone(),
shell_manager,
name: name_span,
source_map,
name: name_tag,
host,
ctrl_c,
},
raw_args,
callback,
@ -212,7 +190,7 @@ impl CommandArgs {
pub struct RunnablePerItemContext {
pub shell_manager: ShellManager,
pub name: Span,
pub name: Tag,
}
impl RunnablePerItemContext {
@ -225,16 +203,14 @@ pub struct RunnableContext {
pub input: InputStream,
pub shell_manager: ShellManager,
pub host: Arc<Mutex<dyn Host>>,
pub ctrl_c: Arc<AtomicBool>,
pub commands: CommandRegistry,
pub source_map: SourceMap,
pub name: Span,
pub name: Tag,
}
impl RunnableContext {
pub fn expect_command(&self, name: &str) -> Arc<Command> {
self.commands
.get_command(name)
.expect(&format!("Expected command {}", name))
pub fn get_command(&self, name: &str) -> Option<Arc<Command>> {
self.commands.get_command(name)
}
}
@ -293,6 +269,7 @@ impl Deref for EvaluatedWholeStreamCommandArgs {
impl EvaluatedWholeStreamCommandArgs {
pub fn new(
host: Arc<Mutex<dyn Host>>,
ctrl_c: Arc<AtomicBool>,
shell_manager: ShellManager,
call_info: CallInfo,
input: impl Into<InputStream>,
@ -300,6 +277,7 @@ impl EvaluatedWholeStreamCommandArgs {
EvaluatedWholeStreamCommandArgs {
args: EvaluatedCommandArgs {
host,
ctrl_c,
shell_manager,
call_info,
},
@ -307,8 +285,8 @@ impl EvaluatedWholeStreamCommandArgs {
}
}
pub fn name_span(&self) -> Span {
self.args.call_info.name_span
pub fn name_tag(&self) -> Tag {
self.args.call_info.name_tag.clone()
}
pub fn parts(self) -> (InputStream, registry::EvaluatedArgs) {
@ -340,12 +318,14 @@ impl Deref for EvaluatedFilterCommandArgs {
impl EvaluatedFilterCommandArgs {
pub fn new(
host: Arc<Mutex<dyn Host>>,
ctrl_c: Arc<AtomicBool>,
shell_manager: ShellManager,
call_info: CallInfo,
) -> EvaluatedFilterCommandArgs {
EvaluatedFilterCommandArgs {
args: EvaluatedCommandArgs {
host,
ctrl_c,
shell_manager,
call_info,
},
@ -357,6 +337,7 @@ impl EvaluatedFilterCommandArgs {
#[get = "pub(crate)"]
pub struct EvaluatedCommandArgs {
pub host: Arc<Mutex<dyn Host>>,
pub ctrl_c: Arc<AtomicBool>,
pub shell_manager: ShellManager,
pub call_info: CallInfo,
}
@ -399,7 +380,6 @@ impl EvaluatedCommandArgs {
#[derive(Debug, Serialize, Deserialize)]
pub enum CommandAction {
ChangePath(String),
AddSpanSource(Uuid, SpanSource),
Exit,
EnterShell(String),
EnterValueShell(Tagged<Value>),
@ -413,9 +393,6 @@ impl ToDebug for CommandAction {
fn fmt_debug(&self, f: &mut fmt::Formatter, _source: &str) -> fmt::Result {
match self {
CommandAction::ChangePath(s) => write!(f, "action:change-path={}", s),
CommandAction::AddSpanSource(u, source) => {
write!(f, "action:add-span-source={}@{:?}", u, source)
}
CommandAction::Exit => write!(f, "action:exit"),
CommandAction::EnterShell(s) => write!(f, "action:enter-shell={}", s),
CommandAction::EnterValueShell(t) => {
@ -467,12 +444,6 @@ impl ReturnSuccess {
pub fn action(input: CommandAction) -> ReturnValue {
Ok(ReturnSuccess::Action(input))
}
pub fn spanned_value(input: Value, span: Span) -> ReturnValue {
Ok(ReturnSuccess::Value(Tagged::from_simple_spanned_item(
input, span,
)))
}
}
pub trait WholeStreamCommand: Send + Sync {
@ -496,6 +467,10 @@ pub trait WholeStreamCommand: Send + Sync {
args: CommandArgs,
registry: &registry::CommandRegistry,
) -> Result<OutputStream, ShellError>;
fn is_binary(&self) -> bool {
false
}
}
pub trait PerItemCommand: Send + Sync {
@ -521,6 +496,10 @@ pub trait PerItemCommand: Send + Sync {
raw_args: &RawCommandArgs,
input: Tagged<Value>,
) -> Result<OutputStream, ShellError>;
fn is_binary(&self) -> bool {
false
}
}
pub enum Command {
@ -528,6 +507,15 @@ pub enum Command {
PerItem(Arc<dyn PerItemCommand>),
}
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()),
}
}
}
impl Command {
pub fn name(&self) -> &str {
match self {
@ -550,13 +538,20 @@ impl Command {
}
}
pub fn run(&self, args: CommandArgs, registry: &registry::CommandRegistry) -> OutputStream {
pub fn run(
&self,
args: CommandArgs,
registry: &registry::CommandRegistry,
is_first_command: bool,
) -> OutputStream {
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()),
Command::PerItem(command) => {
self.run_helper(command.clone(), args, registry.clone(), is_first_command)
}
}
}
@ -565,14 +560,16 @@ impl Command {
command: Arc<dyn PerItemCommand>,
args: CommandArgs,
registry: CommandRegistry,
is_first_command: bool,
) -> OutputStream {
let raw_args = RawCommandArgs {
host: args.host,
ctrl_c: args.ctrl_c,
shell_manager: args.shell_manager,
call_info: args.call_info,
};
if raw_args.call_info.has_it_or_block() {
if !is_first_command {
let out = args
.input
.values
@ -592,22 +589,33 @@ impl Command {
out.to_output_stream()
} else {
let nothing = Value::nothing().tagged(Tag::unknown());
let call_info = raw_args
.clone()
.call_info
.evaluate(&registry, &Scope::it_value(nothing.clone()))
.unwrap();
// We don't have an $it or block, so just execute what we have
.evaluate(&registry, &Scope::it_value(nothing.clone()));
match command
.run(&call_info, &registry, &raw_args, nothing)
.into()
{
Ok(o) => o,
match call_info {
Ok(call_info) => {
match command
.run(&call_info, &registry, &raw_args, nothing)
.into()
{
Ok(o) => o,
Err(e) => OutputStream::one(Err(e)),
}
}
Err(e) => OutputStream::one(Err(e)),
}
}
}
pub fn is_binary(&self) -> bool {
match self {
Command::WholeStream(command) => command.is_binary(),
Command::PerItem(command) => command.is_binary(),
}
}
}
pub struct FnFilterCommand {
@ -631,6 +639,7 @@ impl WholeStreamCommand for FnFilterCommand {
) -> Result<OutputStream, ShellError> {
let CommandArgs {
host,
ctrl_c,
shell_manager,
call_info,
input,
@ -648,8 +657,12 @@ impl WholeStreamCommand for FnFilterCommand {
Ok(args) => args,
};
let args =
EvaluatedFilterCommandArgs::new(host.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) => return OutputStream::from(vec![Err(err)]).values,

View file

@ -1,16 +1,17 @@
use crate::prelude::*;
use crate::commands::WholeStreamCommand;
use crate::data::{config, Value};
use crate::errors::ShellError;
use crate::object::{config, Value};
use crate::parser::hir::SyntaxType;
use crate::parser::hir::SyntaxShape;
use crate::parser::registry::{self};
use crate::prelude::*;
use std::iter::FromIterator;
use std::path::PathBuf;
pub struct Config;
#[derive(Deserialize)]
pub struct ConfigArgs {
load: Option<Tagged<PathBuf>>,
set: Option<(Tagged<String>, Tagged<Value>)>,
get: Option<Tagged<String>>,
clear: Tagged<bool>,
@ -25,11 +26,16 @@ impl WholeStreamCommand for Config {
fn signature(&self) -> Signature {
Signature::build("config")
.named("set", SyntaxType::Any)
.named("get", SyntaxType::Any)
.named("remove", SyntaxType::Any)
.switch("clear")
.switch("path")
.named(
"load",
SyntaxShape::Path,
"load the config from the path give",
)
.named("set", SyntaxShape::Any, "set a value in the config")
.named("get", SyntaxShape::Any, "get a value from the config")
.named("remove", SyntaxShape::Any, "remove a value from the config")
.switch("clear", "clear the config")
.switch("path", "return the path to the config file")
}
fn usage(&self) -> &str {
@ -47,6 +53,7 @@ impl WholeStreamCommand for Config {
pub fn config(
ConfigArgs {
load,
set,
get,
clear,
@ -55,77 +62,78 @@ pub fn config(
}: ConfigArgs,
RunnableContext { name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let mut result = crate::object::config::config(name)?;
let name_span = name.clone();
let configuration = if let Some(supplied) = load {
Some(supplied.item().clone())
} else {
None
};
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::string(&format!("Missing key {} in config", key)))?;
.ok_or_else(|| ShellError::labeled_error("Missing key in config", "key", v.tag()))?;
return Ok(
stream![value.clone()].into(), // futures::stream::once(futures::future::ready(ReturnSuccess::Value(value.clone()))).into(),
);
let mut results = VecDeque::new();
match value {
Tagged {
item: Value::Table(list),
..
} => {
for l in list {
results.push_back(ReturnSuccess::value(l.clone()));
}
}
x => results.push_back(ReturnSuccess::value(x.clone())),
}
return Ok(results.to_output_stream());
}
if let Some((key, value)) = set {
result.insert(key.to_string(), value.clone());
config::write_config(&result)?;
config::write(&result, &configuration)?;
return Ok(stream![Tagged::from_simple_spanned_item(
Value::Object(result.into()),
value.span()
)]
.from_input_stream());
return Ok(stream![Value::Row(result.into()).tagged(value.tag())].from_input_stream());
}
if let Tagged {
item: true,
tag: Tag { span, .. },
} = clear
{
if let Tagged { item: true, tag } = clear {
result.clear();
config::write_config(&result)?;
config::write(&result, &configuration)?;
return Ok(stream![Tagged::from_simple_spanned_item(
Value::Object(result.into()),
span
)]
.from_input_stream());
return Ok(stream![Value::Row(result.into()).tagged(tag)].from_input_stream());
}
if let Tagged {
item: true,
tag: Tag { span, .. },
} = path
{
let path = config::config_path()?;
if let Tagged { item: true, tag } = path {
let path = config::default_path_for(&configuration)?;
return Ok(stream![Tagged::from_simple_spanned_item(
Value::Primitive(Primitive::Path(path)),
span
)]
.from_input_stream());
return Ok(stream![Value::Primitive(Primitive::Path(path)).tagged(tag)].from_input_stream());
}
if let Some(v) = remove {
let key = v.to_string();
if result.contains_key(&key) {
result.remove(&key);
config::write_config(&result)?;
result.swap_remove(&key);
config::write(&result, &configuration)?;
} else {
return Err(ShellError::string(&format!(
"{} does not exist in config",
key
)));
return Err(ShellError::labeled_error(
"Key does not exist in config",
"key",
v.tag(),
));
}
let obj = VecDeque::from_iter(vec![Value::Object(result.into()).simple_spanned(v.span())]);
let obj = VecDeque::from_iter(vec![Value::Row(result.into()).tagged(v.tag())]);
return Ok(obj.from_input_stream());
}
return Ok(vec![Value::Object(result.into()).simple_spanned(name)].into());
return Ok(vec![Value::Row(result.into()).tagged(name)].into());
}

46
src/commands/count.rs Normal file
View file

@ -0,0 +1,46 @@
use crate::commands::WholeStreamCommand;
use crate::data::Value;
use crate::errors::ShellError;
use crate::parser::CommandRegistry;
use crate::prelude::*;
use futures::stream::StreamExt;
pub struct Count;
#[derive(Deserialize)]
pub struct CountArgs {}
impl WholeStreamCommand for Count {
fn name(&self) -> &str {
"count"
}
fn signature(&self) -> Signature {
Signature::build("count")
}
fn usage(&self) -> &str {
"Show the total number of rows."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, count)?.run()
}
}
pub fn count(
CountArgs {}: CountArgs,
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let rows: Vec<Tagged<Value>> = input.values.collect().await;
yield ReturnSuccess::value(Value::int(rows.len()).tagged(name))
};
Ok(stream.to_output_stream())
}

View file

@ -1,6 +1,6 @@
use crate::commands::command::RunnablePerItemContext;
use crate::errors::ShellError;
use crate::parser::hir::SyntaxType;
use crate::parser::hir::SyntaxShape;
use crate::parser::registry::{CommandRegistry, Signature};
use crate::prelude::*;
use std::path::PathBuf;
@ -21,10 +21,9 @@ impl PerItemCommand for Cpy {
fn signature(&self) -> Signature {
Signature::build("cp")
.required("src", SyntaxType::Path)
.required("dst", SyntaxType::Path)
.named("file", SyntaxType::Any)
.switch("recursive")
.required("src", SyntaxShape::Pattern, "the place to copy from")
.required("dst", SyntaxShape::Path, "the place to copy to")
.switch("recursive", "copy recursively through subdirectories")
}
fn usage(&self) -> &str {

View file

@ -1,5 +1,5 @@
use crate::data::{Dictionary, Value};
use crate::errors::ShellError;
use crate::object::{Dictionary, Value};
use crate::prelude::*;
use chrono::{DateTime, Local, Utc};
@ -18,8 +18,8 @@ impl WholeStreamCommand for Date {
fn signature(&self) -> Signature {
Signature::build("date")
.switch("utc")
.switch("local")
.switch("utc", "use universal time (UTC)")
.switch("local", "use the local time")
}
fn usage(&self) -> &str {
@ -35,58 +35,40 @@ impl WholeStreamCommand for Date {
}
}
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, span: Span) -> Tagged<Value>
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, tag: Tag) -> Tagged<Value>
where
T::Offset: Display,
{
let mut indexmap = IndexMap::new();
indexmap.insert(
"year".to_string(),
Tagged::from_simple_spanned_item(Value::int(dt.year()), span),
);
indexmap.insert(
"month".to_string(),
Tagged::from_simple_spanned_item(Value::int(dt.month()), span),
);
indexmap.insert(
"day".to_string(),
Tagged::from_simple_spanned_item(Value::int(dt.day()), span),
);
indexmap.insert(
"hour".to_string(),
Tagged::from_simple_spanned_item(Value::int(dt.hour()), span),
);
indexmap.insert(
"minute".to_string(),
Tagged::from_simple_spanned_item(Value::int(dt.minute()), span),
);
indexmap.insert(
"second".to_string(),
Tagged::from_simple_spanned_item(Value::int(dt.second()), span),
);
indexmap.insert("year".to_string(), Value::int(dt.year()).tagged(&tag));
indexmap.insert("month".to_string(), Value::int(dt.month()).tagged(&tag));
indexmap.insert("day".to_string(), Value::int(dt.day()).tagged(&tag));
indexmap.insert("hour".to_string(), Value::int(dt.hour()).tagged(&tag));
indexmap.insert("minute".to_string(), Value::int(dt.minute()).tagged(&tag));
indexmap.insert("second".to_string(), Value::int(dt.second()).tagged(&tag));
let tz = dt.offset();
indexmap.insert(
"timezone".to_string(),
Tagged::from_simple_spanned_item(Value::string(format!("{}", tz)), span),
Value::string(format!("{}", tz)).tagged(&tag),
);
Tagged::from_simple_spanned_item(Value::Object(Dictionary::from(indexmap)), span)
Value::Row(Dictionary::from(indexmap)).tagged(&tag)
}
pub fn date(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
let mut date_out = VecDeque::new();
let span = args.call_info.name_span;
let tag = args.call_info.name_tag.clone();
let value = if args.has("utc") {
let utc: DateTime<Utc> = Utc::now();
date_to_value(utc, span)
date_to_value(utc, tag)
} else {
let local: DateTime<Local> = Local::now();
date_to_value(local, span)
date_to_value(local, tag)
};
date_out.push_back(value);

71
src/commands/echo.rs Normal file
View file

@ -0,0 +1,71 @@
use crate::data::Value;
use crate::errors::ShellError;
use crate::prelude::*;
use crate::parser::registry::Signature;
pub struct Echo;
impl PerItemCommand for Echo {
fn name(&self) -> &str {
"echo"
}
fn signature(&self) -> Signature {
Signature::build("echo").rest(SyntaxShape::Any, "the values to echo")
}
fn usage(&self) -> &str {
"Echo the arguments back to the user."
}
fn run(
&self,
call_info: &CallInfo,
registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Tagged<Value>,
) -> Result<OutputStream, ShellError> {
run(call_info, registry, raw_args)
}
}
fn run(
call_info: &CallInfo,
_registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
) -> Result<OutputStream, ShellError> {
let name = call_info.name_tag.clone();
let mut output = String::new();
let mut first = true;
if let Some(ref positional) = call_info.args.positional {
for i in positional {
match i.as_string() {
Ok(s) => {
if !first {
output.push_str(" ");
} else {
first = false;
}
output.push_str(&s);
}
_ => {
return Err(ShellError::type_error(
"a string-compatible value",
i.tagged_type_name(),
))
}
}
}
}
let stream = VecDeque::from(vec![Ok(ReturnSuccess::Value(
Value::string(output).tagged(name),
))]);
Ok(stream.to_output_stream())
}

View file

@ -14,7 +14,11 @@ impl PerItemCommand for Enter {
}
fn signature(&self) -> registry::Signature {
Signature::build("enter").required("location", SyntaxType::Block)
Signature::build("enter").required(
"location",
SyntaxShape::Path,
"the location to create a new shell from",
)
}
fn usage(&self) -> &str {
@ -32,49 +36,52 @@ impl PerItemCommand for Enter {
let raw_args = raw_args.clone();
match call_info.args.expect_nth(0)? {
Tagged {
item: Value::Primitive(Primitive::String(location)),
item: Value::Primitive(Primitive::Path(location)),
tag,
..
} => {
let location = location.to_string();
let location_clone = location.to_string();
let location_string = location.display().to_string();
let location_clone = location_string.clone();
let tag_clone = tag.clone();
if registry.has(&location) {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
Value::string(location_clone).tagged(Tag::unknown()),
)))]
.into())
if location.starts_with("help") {
let spec = location_string.split(":").collect::<Vec<&str>>();
let (_, command) = (spec[0], spec[1]);
if registry.has(command) {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
Value::string(command).tagged(Tag::unknown()),
)))]
.into())
} else {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
Value::nothing().tagged(Tag::unknown()),
)))]
.into())
}
} else if PathBuf::from(location).is_dir() {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterShell(
location_clone,
)))]
.into())
} else {
let stream = async_stream_block! {
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, span_source) =
let (file_extension, contents, contents_tag) =
crate::commands::open::fetch(
&full_path,
&location_clone,
Span::unknown(),
)
.await.unwrap();
if let Some(uuid) = contents_tag.origin {
// If we have loaded something, track its source
yield ReturnSuccess::action(CommandAction::AddSpanSource(
uuid,
span_source,
));
}
tag_clone.span,
).await?;
match contents {
Value::Primitive(Primitive::String(_)) => {
let tagged_contents = contents.tagged(contents_tag);
let tagged_contents = contents.tagged(&contents_tag);
if let Some(extension) = file_extension {
let command_name = format!("from-{}", extension);
@ -83,6 +90,7 @@ impl PerItemCommand for Enter {
{
let new_args = RawCommandArgs {
host: raw_args.host,
ctrl_c: raw_args.ctrl_c,
shell_manager: raw_args.shell_manager,
call_info: UnevaluatedCallInfo {
args: crate::parser::hir::Call {
@ -91,13 +99,13 @@ impl PerItemCommand for Enter {
named: None,
},
source: raw_args.call_info.source,
source_map: raw_args.call_info.source_map,
name_span: raw_args.call_info.name_span,
name_tag: raw_args.call_info.name_tag,
},
};
let mut result = converter.run(
new_args.with_input(vec![tagged_contents]),
&registry,
false
);
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
result.drain_vec().await;
@ -110,7 +118,7 @@ impl PerItemCommand for Enter {
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(
Tagged {
item,
tag: contents_tag,
tag: contents_tag.clone(),
})));
}
x => yield x,

76
src/commands/env.rs Normal file
View file

@ -0,0 +1,76 @@
use crate::cli::History;
use crate::data::config;
use crate::data::{Dictionary, Value};
use crate::errors::ShellError;
use crate::prelude::*;
use crate::TaggedDictBuilder;
use crate::commands::WholeStreamCommand;
use crate::parser::registry::Signature;
use indexmap::IndexMap;
pub struct Env;
impl WholeStreamCommand for Env {
fn name(&self) -> &str {
"env"
}
fn signature(&self) -> Signature {
Signature::build("env")
}
fn usage(&self) -> &str {
"Get the current environment."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
env(args, registry)
}
}
pub fn get_environment(tag: Tag) -> Result<Tagged<Value>, Box<dyn std::error::Error>> {
let mut indexmap = IndexMap::new();
let path = std::env::current_dir()?;
indexmap.insert("cwd".to_string(), Value::path(path).tagged(&tag));
if let Some(home) = dirs::home_dir() {
indexmap.insert("home".to_string(), Value::path(home).tagged(&tag));
}
let config = config::default_path()?;
indexmap.insert("config".to_string(), Value::path(config).tagged(&tag));
let history = History::path();
indexmap.insert("history".to_string(), Value::path(history).tagged(&tag));
let temp = std::env::temp_dir();
indexmap.insert("temp".to_string(), Value::path(temp).tagged(&tag));
let mut dict = TaggedDictBuilder::new(&tag);
for v in std::env::vars() {
dict.insert(v.0, Value::string(v.1));
}
if !dict.is_empty() {
indexmap.insert("vars".to_string(), dict.into_tagged_value());
}
Ok(Value::Row(Dictionary::from(indexmap)).tagged(&tag))
}
pub fn env(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
let mut env_out = VecDeque::new();
let tag = args.call_info.name_tag.clone();
let value = get_environment(tag)?;
env_out.push_back(value);
Ok(env_out.to_output_stream())
}

View file

@ -11,8 +11,7 @@ impl WholeStreamCommand for Exit {
}
fn signature(&self) -> Signature {
Signature::build("exit")
.switch("now")
Signature::build("exit").switch("now", "exit out of the shell immediately")
}
fn usage(&self) -> &str {

288
src/commands/fetch.rs Normal file
View file

@ -0,0 +1,288 @@
use crate::commands::UnevaluatedCallInfo;
use crate::context::AnchorLocation;
use crate::data::meta::Span;
use crate::data::Value;
use crate::errors::ShellError;
use crate::parser::hir::SyntaxShape;
use crate::parser::registry::Signature;
use crate::prelude::*;
use mime::Mime;
use std::path::PathBuf;
use std::str::FromStr;
use surf::mime;
pub struct Fetch;
impl PerItemCommand for Fetch {
fn name(&self) -> &str {
"fetch"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required(
"path",
SyntaxShape::Path,
"the URL to fetch the contents from",
)
.switch("raw", "fetch contents as text rather than a table")
}
fn usage(&self) -> &str {
"Load from a URL into a cell, convert to table if possible (avoid by appending '--raw')"
}
fn run(
&self,
call_info: &CallInfo,
registry: &CommandRegistry,
raw_args: &RawCommandArgs,
_input: Tagged<Value>,
) -> Result<OutputStream, ShellError> {
run(call_info, registry, raw_args)
}
}
fn run(
call_info: &CallInfo,
registry: &CommandRegistry,
raw_args: &RawCommandArgs,
) -> Result<OutputStream, ShellError> {
let path = match call_info.args.nth(0).ok_or_else(|| {
ShellError::labeled_error(
"No file or directory specified",
"for command",
&call_info.name_tag,
)
})? {
file => file,
};
let path_buf = path.as_path()?;
let path_str = path_buf.display().to_string();
let path_span = path.tag.span;
let has_raw = call_info.args.has("raw");
let registry = registry.clone();
let raw_args = raw_args.clone();
let stream = async_stream! {
let result = fetch(&path_str, path_span).await;
if let Err(e) = result {
yield Err(e);
return;
}
let (file_extension, contents, contents_tag) = result.unwrap();
let file_extension = if has_raw {
None
} else {
// If the extension could not be determined via mimetype, try to use the path
// extension. Some file types do not declare their mimetypes (such as bson files).
file_extension.or(path_str.split('.').last().map(String::from))
};
let tagged_contents = contents.tagged(&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: crate::parser::hir::Call {
head: raw_args.call_info.args.head,
positional: None,
named: None
},
source: raw_args.call_info.source,
name_tag: raw_args.call_info.name_tag,
}
};
let mut result = converter.run(new_args.with_input(vec![tagged_contents]), &registry, false);
let result_vec: Vec<Result<ReturnSuccess, ShellError>> = result.drain_vec().await;
for res in result_vec {
match res {
Ok(ReturnSuccess::Value(Tagged { item: Value::Table(list), ..})) => {
for l in list {
yield Ok(ReturnSuccess::Value(l));
}
}
Ok(ReturnSuccess::Value(Tagged { item, .. })) => {
yield Ok(ReturnSuccess::Value(Tagged { item, tag: contents_tag.clone() }));
}
x => yield x,
}
}
} else {
yield ReturnSuccess::value(tagged_contents);
}
} else {
yield ReturnSuccess::value(tagged_contents);
}
};
Ok(stream.to_output_stream())
}
pub async fn fetch(location: &str, span: Span) -> Result<(Option<String>, Value, Tag), ShellError> {
if let Err(_) = url::Url::parse(location) {
return Err(ShellError::labeled_error(
"Incomplete or incorrect url",
"expected a full url",
span,
));
}
let response = surf::get(location).await;
match response {
Ok(mut r) => match r.headers().get("content-type") {
Some(content_type) => {
let content_type = Mime::from_str(content_type).unwrap();
match (content_type.type_(), content_type.subtype()) {
(mime::APPLICATION, mime::XML) => Ok((
Some("xml".to_string()),
Value::string(r.body_string().await.map_err(|_| {
ShellError::labeled_error(
"Could not load text from remote url",
"could not load",
span,
)
})?),
Tag {
span,
anchor: Some(AnchorLocation::Url(location.to_string())),
},
)),
(mime::APPLICATION, mime::JSON) => Ok((
Some("json".to_string()),
Value::string(r.body_string().await.map_err(|_| {
ShellError::labeled_error(
"Could not load text from remote url",
"could not load",
span,
)
})?),
Tag {
span,
anchor: Some(AnchorLocation::Url(location.to_string())),
},
)),
(mime::APPLICATION, mime::OCTET_STREAM) => {
let buf: Vec<u8> = r.body_bytes().await.map_err(|_| {
ShellError::labeled_error(
"Could not load binary file",
"could not load",
span,
)
})?;
Ok((
None,
Value::binary(buf),
Tag {
span,
anchor: Some(AnchorLocation::Url(location.to_string())),
},
))
}
(mime::IMAGE, mime::SVG) => Ok((
Some("svg".to_string()),
Value::string(r.body_string().await.map_err(|_| {
ShellError::labeled_error(
"Could not load svg from remote url",
"could not load",
span,
)
})?),
Tag {
span,
anchor: Some(AnchorLocation::Url(location.to_string())),
},
)),
(mime::IMAGE, image_ty) => {
let buf: Vec<u8> = r.body_bytes().await.map_err(|_| {
ShellError::labeled_error(
"Could not load image file",
"could not load",
span,
)
})?;
Ok((
Some(image_ty.to_string()),
Value::binary(buf),
Tag {
span,
anchor: Some(AnchorLocation::Url(location.to_string())),
},
))
}
(mime::TEXT, mime::HTML) => Ok((
Some("html".to_string()),
Value::string(r.body_string().await.map_err(|_| {
ShellError::labeled_error(
"Could not load text from remote url",
"could not load",
span,
)
})?),
Tag {
span,
anchor: Some(AnchorLocation::Url(location.to_string())),
},
)),
(mime::TEXT, mime::PLAIN) => {
let path_extension = url::Url::parse(location)
.unwrap()
.path_segments()
.and_then(|segments| segments.last())
.and_then(|name| if name.is_empty() { None } else { Some(name) })
.and_then(|name| {
PathBuf::from(name)
.extension()
.map(|name| name.to_string_lossy().to_string())
});
Ok((
path_extension,
Value::string(r.body_string().await.map_err(|_| {
ShellError::labeled_error(
"Could not load text from remote url",
"could not load",
span,
)
})?),
Tag {
span,
anchor: Some(AnchorLocation::Url(location.to_string())),
},
))
}
(ty, sub_ty) => Ok((
None,
Value::string(format!("Not yet supported MIME type: {} {}", ty, sub_ty)),
Tag {
span,
anchor: Some(AnchorLocation::Url(location.to_string())),
},
)),
}
}
None => Ok((
None,
Value::string(format!("No content type found")),
Tag {
span,
anchor: Some(AnchorLocation::Url(location.to_string())),
},
)),
},
Err(_) => {
return Err(ShellError::labeled_error(
"URL could not be opened",
"url not found",
span,
));
}
}
}

View file

@ -7,7 +7,7 @@ pub struct First;
#[derive(Deserialize)]
pub struct FirstArgs {
amount: Tagged<u64>,
rows: Option<Tagged<u64>>,
}
impl WholeStreamCommand for First {
@ -16,8 +16,11 @@ impl WholeStreamCommand for First {
}
fn signature(&self) -> Signature {
Signature::build("first")
.required("amount", SyntaxType::Literal)
Signature::build("first").optional(
"rows",
SyntaxShape::Int,
"starting from the front, the number of rows to return",
)
}
fn usage(&self) -> &str {
@ -34,8 +37,16 @@ impl WholeStreamCommand for First {
}
fn first(
FirstArgs { amount }: FirstArgs,
FirstArgs { rows }: FirstArgs,
context: RunnableContext,
) -> Result<OutputStream, ShellError> {
Ok(OutputStream::from_input(context.input.values.take(*amount)))
let rows_desired = if let Some(quantity) = rows {
*quantity
} else {
1
};
Ok(OutputStream::from_input(
context.input.values.take(rows_desired),
))
}

View file

@ -1,43 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::object::Value;
use crate::prelude::*;
pub struct FromArray;
impl WholeStreamCommand for FromArray {
fn name(&self) -> &str {
"from-array"
}
fn signature(&self) -> Signature {
Signature::build("from-array")
}
fn usage(&self) -> &str {
"Expand an array/list into rows"
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_array(args, registry)
}
}
fn from_array(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let stream = args
.input
.values
.map(|item| match item {
Tagged {
item: Value::List(vec),
..
} => VecDeque::from(vec),
x => VecDeque::from(vec![x]),
})
.flatten();
Ok(stream.to_output_stream())
}

View file

@ -1,6 +1,6 @@
use crate::commands::WholeStreamCommand;
use crate::data::{Primitive, TaggedDictBuilder, Value};
use crate::errors::ExpectedRange;
use crate::object::{Primitive, TaggedDictBuilder, Value};
use crate::prelude::*;
use bson::{decode_document, spec::BinarySubtype, Bson};
use std::str::FromStr;
@ -33,7 +33,7 @@ fn bson_array(input: &Vec<Bson>, tag: Tag) -> Result<Vec<Tagged<Value>>, ShellEr
let mut out = vec![];
for value in input {
out.push(convert_bson_value_to_nu_value(value, tag)?);
out.push(convert_bson_value_to_nu_value(value, &tag)?);
}
Ok(out)
@ -46,100 +46,100 @@ fn convert_bson_value_to_nu_value(
let tag = tag.into();
Ok(match v {
Bson::FloatingPoint(n) => Value::Primitive(Primitive::from(*n)).tagged(tag),
Bson::String(s) => Value::Primitive(Primitive::String(String::from(s))).tagged(tag),
Bson::Array(a) => Value::List(bson_array(a, tag)?).tagged(tag),
Bson::FloatingPoint(n) => Value::Primitive(Primitive::from(*n)).tagged(&tag),
Bson::String(s) => Value::Primitive(Primitive::String(String::from(s))).tagged(&tag),
Bson::Array(a) => Value::Table(bson_array(a, tag.clone())?).tagged(&tag),
Bson::Document(doc) => {
let mut collected = TaggedDictBuilder::new(tag);
let mut collected = TaggedDictBuilder::new(tag.clone());
for (k, v) in doc.iter() {
collected.insert_tagged(k.clone(), convert_bson_value_to_nu_value(v, tag)?);
collected.insert_tagged(k.clone(), convert_bson_value_to_nu_value(v, &tag)?);
}
collected.into_tagged_value()
}
Bson::Boolean(b) => Value::Primitive(Primitive::Boolean(*b)).tagged(tag),
Bson::Null => Value::Primitive(Primitive::Nothing).tagged(tag),
Bson::Boolean(b) => Value::Primitive(Primitive::Boolean(*b)).tagged(&tag),
Bson::Null => Value::Primitive(Primitive::Nothing).tagged(&tag),
Bson::RegExp(r, opts) => {
let mut collected = TaggedDictBuilder::new(tag);
let mut collected = TaggedDictBuilder::new(tag.clone());
collected.insert_tagged(
"$regex".to_string(),
Value::Primitive(Primitive::String(String::from(r))).tagged(tag),
Value::Primitive(Primitive::String(String::from(r))).tagged(&tag),
);
collected.insert_tagged(
"$options".to_string(),
Value::Primitive(Primitive::String(String::from(opts))).tagged(tag),
Value::Primitive(Primitive::String(String::from(opts))).tagged(&tag),
);
collected.into_tagged_value()
}
Bson::I32(n) => Value::number(n).tagged(tag),
Bson::I64(n) => Value::number(n).tagged(tag),
Bson::I32(n) => Value::number(n).tagged(&tag),
Bson::I64(n) => Value::number(n).tagged(&tag),
Bson::Decimal128(n) => {
// TODO: this really isn't great, and we should update this to do a higher
// fidelity translation
let decimal = BigDecimal::from_str(&format!("{}", n)).map_err(|_| {
ShellError::range_error(
ExpectedRange::BigDecimal,
&n.tagged(tag),
&n.tagged(&tag),
format!("converting BSON Decimal128 to BigDecimal"),
)
})?;
Value::Primitive(Primitive::Decimal(decimal)).tagged(tag)
Value::Primitive(Primitive::Decimal(decimal)).tagged(&tag)
}
Bson::JavaScriptCode(js) => {
let mut collected = TaggedDictBuilder::new(tag);
let mut collected = TaggedDictBuilder::new(tag.clone());
collected.insert_tagged(
"$javascript".to_string(),
Value::Primitive(Primitive::String(String::from(js))).tagged(tag),
Value::Primitive(Primitive::String(String::from(js))).tagged(&tag),
);
collected.into_tagged_value()
}
Bson::JavaScriptCodeWithScope(js, doc) => {
let mut collected = TaggedDictBuilder::new(tag);
let mut collected = TaggedDictBuilder::new(tag.clone());
collected.insert_tagged(
"$javascript".to_string(),
Value::Primitive(Primitive::String(String::from(js))).tagged(tag),
Value::Primitive(Primitive::String(String::from(js))).tagged(&tag),
);
collected.insert_tagged(
"$scope".to_string(),
convert_bson_value_to_nu_value(&Bson::Document(doc.to_owned()), tag)?,
convert_bson_value_to_nu_value(&Bson::Document(doc.to_owned()), tag.clone())?,
);
collected.into_tagged_value()
}
Bson::TimeStamp(ts) => {
let mut collected = TaggedDictBuilder::new(tag);
collected.insert_tagged("$timestamp".to_string(), Value::number(ts).tagged(tag));
let mut collected = TaggedDictBuilder::new(tag.clone());
collected.insert_tagged("$timestamp".to_string(), Value::number(ts).tagged(&tag));
collected.into_tagged_value()
}
Bson::Binary(bst, bytes) => {
let mut collected = TaggedDictBuilder::new(tag);
let mut collected = TaggedDictBuilder::new(tag.clone());
collected.insert_tagged(
"$binary_subtype".to_string(),
match bst {
BinarySubtype::UserDefined(u) => Value::number(u),
_ => Value::Primitive(Primitive::String(binary_subtype_to_string(*bst))),
}
.tagged(tag),
.tagged(&tag),
);
collected.insert_tagged(
"$binary".to_string(),
Value::Binary(bytes.to_owned()).tagged(tag),
Value::Primitive(Primitive::Binary(bytes.to_owned())).tagged(&tag),
);
collected.into_tagged_value()
}
Bson::ObjectId(obj_id) => {
let mut collected = TaggedDictBuilder::new(tag);
let mut collected = TaggedDictBuilder::new(tag.clone());
collected.insert_tagged(
"$object_id".to_string(),
Value::Primitive(Primitive::String(obj_id.to_hex())).tagged(tag),
Value::Primitive(Primitive::String(obj_id.to_hex())).tagged(&tag),
);
collected.into_tagged_value()
}
Bson::UtcDatetime(dt) => Value::Primitive(Primitive::Date(*dt)).tagged(tag),
Bson::UtcDatetime(dt) => Value::Primitive(Primitive::Date(*dt)).tagged(&tag),
Bson::Symbol(s) => {
let mut collected = TaggedDictBuilder::new(tag);
let mut collected = TaggedDictBuilder::new(tag.clone());
collected.insert_tagged(
"$symbol".to_string(),
Value::Primitive(Primitive::String(String::from(s))).tagged(tag),
Value::Primitive(Primitive::String(String::from(s))).tagged(&tag),
);
collected.into_tagged_value()
}
@ -198,34 +198,34 @@ pub fn from_bson_bytes_to_value(
fn from_bson(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
let span = args.name_span();
let tag = args.name_tag();
let input = args.input;
let stream = async_stream_block! {
let stream = async_stream! {
let values: Vec<Tagged<Value>> = input.values.collect().await;
for value in values {
let value_tag = value.tag();
match value.item {
Value::Binary(vb) =>
match from_bson_bytes_to_value(vb, span) {
Value::Primitive(Primitive::Binary(vb)) =>
match from_bson_bytes_to_value(vb, 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",
span,
tag.clone(),
"value originates from here",
value_tag.span,
value_tag,
))
}
}
_ => yield Err(ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
span,
tag.clone(),
"value originates from here",
value_tag.span,
value_tag,
)),
}

View file

@ -1,5 +1,5 @@
use crate::commands::WholeStreamCommand;
use crate::object::{Primitive, TaggedDictBuilder, Value};
use crate::data::{Primitive, TaggedDictBuilder, Value};
use crate::prelude::*;
use csv::ReaderBuilder;
@ -17,7 +17,7 @@ impl WholeStreamCommand for FromCSV {
fn signature(&self) -> Signature {
Signature::build("from-csv")
.switch("headerless")
.switch("headerless", "don't treat the first row as column names")
}
fn usage(&self) -> &str {
@ -63,12 +63,12 @@ pub fn from_csv_string_to_value(
if let Some(row_values) = iter.next() {
let row_values = row_values?;
let mut row = TaggedDictBuilder::new(tag);
let mut row = TaggedDictBuilder::new(tag.clone());
for (idx, entry) in row_values.iter().enumerate() {
row.insert_tagged(
fields.get(idx).unwrap(),
Value::Primitive(Primitive::String(String::from(entry))).tagged(tag),
Value::Primitive(Primitive::String(String::from(entry))).tagged(&tag),
);
}
@ -78,7 +78,7 @@ pub fn from_csv_string_to_value(
}
}
Ok(Tagged::from_item(Value::List(rows), tag))
Ok(Value::Table(rows).tagged(&tag))
}
fn from_csv(
@ -87,9 +87,9 @@ fn from_csv(
}: FromCSVArgs,
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let name_span = name;
let name_tag = name;
let stream = async_stream_block! {
let stream = async_stream! {
let values: Vec<Tagged<Value>> = input.values.collect().await;
let mut concat_string = String::new();
@ -97,7 +97,7 @@ fn from_csv(
for value in values {
let value_tag = value.tag();
latest_tag = Some(value_tag);
latest_tag = Some(value_tag.clone());
match value.item {
Value::Primitive(Primitive::String(s)) => {
concat_string.push_str(&s);
@ -106,17 +106,17 @@ fn from_csv(
_ => yield Err(ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
name_span,
name_tag.clone(),
"value originates from here",
value_tag.span,
value_tag.clone(),
)),
}
}
match from_csv_string_to_value(concat_string, skip_headers, name_span) {
match from_csv_string_to_value(concat_string, skip_headers, name_tag.clone()) {
Ok(x) => match x {
Tagged { item: Value::List(list), .. } => {
Tagged { item: Value::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
@ -127,9 +127,9 @@ fn from_csv(
yield Err(ShellError::labeled_error_with_secondary(
"Could not parse as CSV",
"input cannot be parsed as CSV",
name_span,
name_tag.clone(),
"value originates from here",
last_tag.span,
last_tag.clone(),
))
} ,
}

View file

@ -1,5 +1,5 @@
use crate::commands::WholeStreamCommand;
use crate::object::{Primitive, TaggedDictBuilder, Value};
use crate::data::{Primitive, TaggedDictBuilder, Value};
use crate::prelude::*;
use std::collections::HashMap;
@ -45,10 +45,13 @@ fn convert_ini_top_to_nu_value(
tag: impl Into<Tag>,
) -> Tagged<Value> {
let tag = tag.into();
let mut top_level = TaggedDictBuilder::new(tag);
let mut top_level = TaggedDictBuilder::new(tag.clone());
for (key, value) in v.iter() {
top_level.insert_tagged(key.clone(), convert_ini_second_to_nu_value(value, tag));
top_level.insert_tagged(
key.clone(),
convert_ini_second_to_nu_value(value, tag.clone()),
);
}
top_level.into_tagged_value()
@ -64,10 +67,10 @@ pub fn from_ini_string_to_value(
fn from_ini(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
let span = args.name_span();
let tag = args.name_tag();
let input = args.input;
let stream = async_stream_block! {
let stream = async_stream! {
let values: Vec<Tagged<Value>> = input.values.collect().await;
let mut concat_string = String::new();
@ -75,7 +78,7 @@ fn from_ini(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
for value in values {
let value_tag = value.tag();
latest_tag = Some(value_tag);
latest_tag = Some(value_tag.clone());
match value.item {
Value::Primitive(Primitive::String(s)) => {
concat_string.push_str(&s);
@ -84,17 +87,17 @@ fn from_ini(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
_ => yield Err(ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
span,
&tag,
"value originates from here",
value_tag.span,
&value_tag,
)),
}
}
match from_ini_string_to_value(concat_string, span) {
match from_ini_string_to_value(concat_string, tag.clone()) {
Ok(x) => match x {
Tagged { item: Value::List(list), .. } => {
Tagged { item: Value::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
@ -105,9 +108,9 @@ fn from_ini(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
yield Err(ShellError::labeled_error_with_secondary(
"Could not parse as INI",
"input cannot be parsed as INI",
span,
&tag,
"value originates from here",
last_tag.span,
last_tag,
))
} ,
}

View file

@ -1,5 +1,5 @@
use crate::commands::WholeStreamCommand;
use crate::object::{Primitive, TaggedDictBuilder, Value};
use crate::data::{Primitive, TaggedDictBuilder, Value};
use crate::prelude::*;
pub struct FromJSON;
@ -15,8 +15,7 @@ impl WholeStreamCommand for FromJSON {
}
fn signature(&self) -> Signature {
Signature::build("from-json")
.switch("objects")
Signature::build("from-json").switch("objects", "treat each line as a separate value")
}
fn usage(&self) -> &str {
@ -36,24 +35,24 @@ fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -
let tag = tag.into();
match v {
serde_hjson::Value::Null => Value::Primitive(Primitive::Nothing).tagged(tag),
serde_hjson::Value::Bool(b) => Value::boolean(*b).tagged(tag),
serde_hjson::Value::F64(n) => Value::number(n).tagged(tag),
serde_hjson::Value::U64(n) => Value::number(n).tagged(tag),
serde_hjson::Value::I64(n) => Value::number(n).tagged(tag),
serde_hjson::Value::Null => Value::Primitive(Primitive::Nothing).tagged(&tag),
serde_hjson::Value::Bool(b) => Value::boolean(*b).tagged(&tag),
serde_hjson::Value::F64(n) => Value::number(n).tagged(&tag),
serde_hjson::Value::U64(n) => Value::number(n).tagged(&tag),
serde_hjson::Value::I64(n) => Value::number(n).tagged(&tag),
serde_hjson::Value::String(s) => {
Value::Primitive(Primitive::String(String::from(s))).tagged(tag)
Value::Primitive(Primitive::String(String::from(s))).tagged(&tag)
}
serde_hjson::Value::Array(a) => Value::List(
serde_hjson::Value::Array(a) => Value::Table(
a.iter()
.map(|x| convert_json_value_to_nu_value(x, tag))
.map(|x| convert_json_value_to_nu_value(x, &tag))
.collect(),
)
.tagged(tag),
serde_hjson::Value::Object(o) => {
let mut collected = TaggedDictBuilder::new(tag);
let mut collected = TaggedDictBuilder::new(&tag);
for (k, v) in o.iter() {
collected.insert_tagged(k.clone(), convert_json_value_to_nu_value(v, tag));
collected.insert_tagged(k.clone(), convert_json_value_to_nu_value(v, &tag));
}
collected.into_tagged_value()
@ -73,9 +72,9 @@ fn from_json(
FromJSONArgs { objects }: FromJSONArgs,
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let name_span = name;
let name_tag = name;
let stream = async_stream_block! {
let stream = async_stream! {
let values: Vec<Tagged<Value>> = input.values.collect().await;
let mut concat_string = String::new();
@ -83,7 +82,7 @@ fn from_json(
for value in values {
let value_tag = value.tag();
latest_tag = Some(value_tag);
latest_tag = Some(value_tag.clone());
match value.item {
Value::Primitive(Primitive::String(s)) => {
concat_string.push_str(&s);
@ -92,9 +91,9 @@ fn from_json(
_ => yield Err(ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
name_span,
&name_tag,
"value originates from here",
value_tag.span,
&value_tag,
)),
}
@ -107,26 +106,26 @@ fn from_json(
continue;
}
match from_json_string_to_value(json_str.to_string(), name_span) {
match from_json_string_to_value(json_str.to_string(), &name_tag) {
Ok(x) =>
yield ReturnSuccess::value(x),
Err(_) => {
if let Some(last_tag) = latest_tag {
if let Some(ref last_tag) = latest_tag {
yield Err(ShellError::labeled_error_with_secondary(
"Could nnot parse as JSON",
"input cannot be parsed as JSON",
name_span,
&name_tag,
"value originates from here",
last_tag.span))
last_tag))
}
}
}
}
} else {
match from_json_string_to_value(concat_string, name_span) {
match from_json_string_to_value(concat_string, name_tag.clone()) {
Ok(x) =>
match x {
Tagged { item: Value::List(list), .. } => {
Tagged { item: Value::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
@ -138,9 +137,9 @@ fn from_json(
yield Err(ShellError::labeled_error_with_secondary(
"Could not parse as JSON",
"input cannot be parsed as JSON",
name_span,
name_tag,
"value originates from here",
last_tag.span))
last_tag))
}
}
}

View file

@ -1,6 +1,6 @@
use crate::commands::WholeStreamCommand;
use crate::data::{Primitive, TaggedDictBuilder, Value};
use crate::errors::ShellError;
use crate::object::{Primitive, TaggedDictBuilder, Value};
use crate::prelude::*;
use rusqlite::{types::ValueRef, Connection, Row, NO_PARAMS};
use std::io::Write;
@ -76,11 +76,11 @@ pub fn convert_sqlite_file_to_nu_value(
"table_name".to_string(),
Value::Primitive(Primitive::String(table_name)).tagged(tag.clone()),
);
meta_dict.insert_tagged("table_values", Value::List(out).tagged(tag.clone()));
meta_dict.insert_tagged("table_values", Value::Table(out).tagged(tag.clone()));
meta_out.push(meta_dict.into_tagged_value());
}
let tag = tag.into();
Ok(Value::List(meta_out).tagged(tag))
Ok(Value::Table(meta_out).tagged(tag))
}
fn convert_sqlite_row_to_nu_value(
@ -106,7 +106,7 @@ fn convert_sqlite_value_to_nu_value(value: ValueRef, tag: impl Into<Tag> + Clone
// this unwrap is safe because we know the ValueRef is Text.
Value::Primitive(Primitive::String(t.as_str().unwrap().to_string())).tagged(tag)
}
ValueRef::Blob(u) => Value::Binary(u.to_owned()).tagged(tag),
ValueRef::Blob(u) => Value::binary(u.to_owned()).tagged(tag),
}
}
@ -128,19 +128,19 @@ pub fn from_sqlite_bytes_to_value(
fn from_sqlite(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
let span = args.name_span();
let tag = args.name_tag();
let input = args.input;
let stream = async_stream_block! {
let stream = async_stream! {
let values: Vec<Tagged<Value>> = input.values.collect().await;
for value in values {
let value_tag = value.tag();
match value.item {
Value::Binary(vb) =>
match from_sqlite_bytes_to_value(vb, span) {
Value::Primitive(Primitive::Binary(vb)) =>
match from_sqlite_bytes_to_value(vb, tag.clone()) {
Ok(x) => match x {
Tagged { item: Value::List(list), .. } => {
Tagged { item: Value::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
@ -151,18 +151,18 @@ fn from_sqlite(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
yield Err(ShellError::labeled_error_with_secondary(
"Could not parse as SQLite",
"input cannot be parsed as SQLite",
span,
&tag,
"value originates from here",
value_tag.span,
value_tag,
))
}
}
_ => yield Err(ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
span,
&tag,
"value originates from here",
value_tag.span,
value_tag,
)),
}

320
src/commands/from_ssv.rs Normal file
View file

@ -0,0 +1,320 @@
use crate::commands::WholeStreamCommand;
use crate::data::{Primitive, TaggedDictBuilder, Value};
use crate::prelude::*;
pub struct FromSSV;
#[derive(Deserialize)]
pub struct FromSSVArgs {
headerless: bool,
#[serde(rename(deserialize = "minimum-spaces"))]
minimum_spaces: Option<Tagged<usize>>,
}
const STRING_REPRESENTATION: &str = "from-ssv";
const DEFAULT_MINIMUM_SPACES: usize = 2;
impl WholeStreamCommand for FromSSV {
fn name(&self) -> &str {
STRING_REPRESENTATION
}
fn signature(&self) -> Signature {
Signature::build(STRING_REPRESENTATION)
.switch("headerless", "don't treat the first row as column names")
.named(
"minimum-spaces",
SyntaxShape::Int,
"the mininum spaces to separate columns",
)
}
fn usage(&self) -> &str {
"Parse text as space-separated values and create a table. The default minimum number of spaces counted as a separator is 2."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, from_ssv)?.run()
}
}
fn string_to_table(
s: &str,
headerless: bool,
split_at: usize,
) -> Option<Vec<Vec<(String, String)>>> {
let mut lines = s.lines().filter(|l| !l.trim().is_empty());
let separator = " ".repeat(std::cmp::max(split_at, 1));
let headers_raw = lines.next()?;
let headers = headers_raw
.trim()
.split(&separator)
.map(str::trim)
.filter(|s| !s.is_empty())
.map(|s| (headers_raw.find(s).unwrap(), s.to_owned()));
let columns = if headerless {
headers
.enumerate()
.map(|(header_no, (string_index, _))| {
(string_index, format!("Column{}", header_no + 1))
})
.collect::<Vec<(usize, String)>>()
} else {
headers.collect::<Vec<(usize, String)>>()
};
Some(
lines
.map(|l| {
columns
.iter()
.enumerate()
.filter_map(|(i, (start, col))| {
(match columns.get(i + 1) {
Some((end, _)) => l.get(*start..*end),
None => l.get(*start..),
})
.and_then(|s| Some((col.clone(), String::from(s.trim()))))
})
.collect()
})
.collect(),
)
}
fn from_ssv_string_to_value(
s: &str,
headerless: bool,
split_at: usize,
tag: impl Into<Tag>,
) -> Option<Tagged<Value>> {
let tag = tag.into();
let rows = string_to_table(s, headerless, split_at)?
.iter()
.map(|row| {
let mut tagged_dict = TaggedDictBuilder::new(&tag);
for (col, entry) in row {
tagged_dict.insert_tagged(
col,
Value::Primitive(Primitive::String(String::from(entry))).tagged(&tag),
)
}
tagged_dict.into_tagged_value()
})
.collect();
Some(Value::Table(rows).tagged(&tag))
}
fn from_ssv(
FromSSVArgs {
headerless,
minimum_spaces,
}: FromSSVArgs,
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let values: Vec<Tagged<Value>> = input.values.collect().await;
let mut concat_string = String::new();
let mut latest_tag: Option<Tag> = None;
let split_at = match minimum_spaces {
Some(number) => number.item,
None => DEFAULT_MINIMUM_SPACES
};
for value in values {
let value_tag = value.tag();
latest_tag = Some(value_tag.clone());
match value.item {
Value::Primitive(Primitive::String(s)) => {
concat_string.push_str(&s);
}
_ => yield Err(ShellError::labeled_error_with_secondary (
"Expected a string from pipeline",
"requires string input",
&name,
"value originates from here",
&value_tag
)),
}
}
match from_ssv_string_to_value(&concat_string, headerless, split_at, name.clone()) {
Some(x) => match x {
Tagged { item: Value::Table(list), ..} => {
for l in list { yield ReturnSuccess::value(l) }
}
x => yield ReturnSuccess::value(x)
},
None => if let Some(tag) = latest_tag {
yield Err(ShellError::labeled_error_with_secondary(
"Could not parse as SSV",
"input cannot be parsed ssv",
&name,
"value originates from here",
&tag,
))
},
}
};
Ok(stream.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::*;
fn owned(x: &str, y: &str) -> (String, String) {
(String::from(x), String::from(y))
}
#[test]
fn it_trims_empty_and_whitespace_only_lines() {
let input = r#"
a b
1 2
3 4
"#;
let result = string_to_table(input, false, 1);
assert_eq!(
result,
Some(vec![
vec![owned("a", "1"), owned("b", "2")],
vec![owned("a", "3"), owned("b", "4")]
])
);
}
#[test]
fn it_deals_with_single_column_input() {
let input = r#"
a
1
2
"#;
let result = string_to_table(input, false, 1);
assert_eq!(
result,
Some(vec![vec![owned("a", "1")], vec![owned("a", "2")]])
);
}
#[test]
fn it_ignores_headers_when_headerless() {
let input = r#"
a b
1 2
3 4
"#;
let result = string_to_table(input, true, 1);
assert_eq!(
result,
Some(vec![
vec![owned("Column1", "1"), owned("Column2", "2")],
vec![owned("Column1", "3"), owned("Column2", "4")]
])
);
}
#[test]
fn it_returns_none_given_an_empty_string() {
let input = "";
let result = string_to_table(input, true, 1);
assert!(result.is_none());
}
#[test]
fn it_allows_a_predefined_number_of_spaces() {
let input = r#"
column a column b
entry 1 entry number 2
3 four
"#;
let result = string_to_table(input, false, 3);
assert_eq!(
result,
Some(vec![
vec![
owned("column a", "entry 1"),
owned("column b", "entry number 2")
],
vec![owned("column a", "3"), owned("column b", "four")]
])
);
}
#[test]
fn it_trims_remaining_separator_space() {
let input = r#"
colA colB colC
val1 val2 val3
"#;
let trimmed = |s: &str| s.trim() == s;
let result = string_to_table(input, false, 2).unwrap();
assert!(result
.iter()
.all(|row| row.iter().all(|(a, b)| trimmed(a) && trimmed(b))))
}
#[test]
fn it_keeps_empty_columns() {
let input = r#"
colA col B col C
val2 val3
val4 val 5 val 6
val7 val8
"#;
let result = string_to_table(input, false, 2).unwrap();
assert_eq!(
result,
vec![
vec![
owned("colA", ""),
owned("col B", "val2"),
owned("col C", "val3")
],
vec![
owned("colA", "val4"),
owned("col B", "val 5"),
owned("col C", "val 6")
],
vec![
owned("colA", "val7"),
owned("col B", ""),
owned("col C", "val8")
],
]
)
}
#[test]
fn it_uses_the_full_final_column() {
let input = r#"
colA col B
val1 val2 trailing value that should be included
"#;
let result = string_to_table(input, false, 2).unwrap();
assert_eq!(
result,
vec![vec![
owned("colA", "val1"),
owned("col B", "val2 trailing value that should be included"),
],]
)
}
}

View file

@ -1,5 +1,5 @@
use crate::commands::WholeStreamCommand;
use crate::object::{Primitive, TaggedDictBuilder, Value};
use crate::data::{Primitive, TaggedDictBuilder, Value};
use crate::prelude::*;
pub struct FromTOML;
@ -34,9 +34,9 @@ pub fn convert_toml_value_to_nu_value(v: &toml::Value, tag: impl Into<Tag>) -> T
toml::Value::Integer(n) => Value::number(n).tagged(tag),
toml::Value::Float(n) => Value::number(n).tagged(tag),
toml::Value::String(s) => Value::Primitive(Primitive::String(String::from(s))).tagged(tag),
toml::Value::Array(a) => Value::List(
toml::Value::Array(a) => Value::Table(
a.iter()
.map(|x| convert_toml_value_to_nu_value(x, tag))
.map(|x| convert_toml_value_to_nu_value(x, &tag))
.collect(),
)
.tagged(tag),
@ -44,10 +44,10 @@ pub fn convert_toml_value_to_nu_value(v: &toml::Value, tag: impl Into<Tag>) -> T
Value::Primitive(Primitive::String(dt.to_string())).tagged(tag)
}
toml::Value::Table(t) => {
let mut collected = TaggedDictBuilder::new(tag);
let mut collected = TaggedDictBuilder::new(&tag);
for (k, v) in t.iter() {
collected.insert_tagged(k.clone(), convert_toml_value_to_nu_value(v, tag));
collected.insert_tagged(k.clone(), convert_toml_value_to_nu_value(v, &tag));
}
collected.into_tagged_value()
@ -68,10 +68,10 @@ pub fn from_toml(
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
let span = args.name_span();
let tag = args.name_tag();
let input = args.input;
let stream = async_stream_block! {
let stream = async_stream! {
let values: Vec<Tagged<Value>> = input.values.collect().await;
let mut concat_string = String::new();
@ -79,7 +79,7 @@ pub fn from_toml(
for value in values {
let value_tag = value.tag();
latest_tag = Some(value_tag);
latest_tag = Some(value_tag.clone());
match value.item {
Value::Primitive(Primitive::String(s)) => {
concat_string.push_str(&s);
@ -88,17 +88,17 @@ pub fn from_toml(
_ => yield Err(ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
span,
&tag,
"value originates from here",
value_tag.span,
&value_tag,
)),
}
}
match from_toml_string_to_value(concat_string, span) {
match from_toml_string_to_value(concat_string, tag.clone()) {
Ok(x) => match x {
Tagged { item: Value::List(list), .. } => {
Tagged { item: Value::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
@ -109,9 +109,9 @@ pub fn from_toml(
yield Err(ShellError::labeled_error_with_secondary(
"Could not parse as TOML",
"input cannot be parsed as TOML",
span,
&tag,
"value originates from here",
last_tag.span,
last_tag,
))
} ,
}

View file

@ -1,5 +1,5 @@
use crate::commands::WholeStreamCommand;
use crate::object::{Primitive, TaggedDictBuilder, Value};
use crate::data::{Primitive, TaggedDictBuilder, Value};
use crate::prelude::*;
use csv::ReaderBuilder;
@ -16,7 +16,8 @@ impl WholeStreamCommand for FromTSV {
}
fn signature(&self) -> Signature {
Signature::build("from-tsv").switch("headerless")
Signature::build("from-tsv")
.switch("headerless", "don't treat the first row as column names")
}
fn usage(&self) -> &str {
@ -63,12 +64,12 @@ pub fn from_tsv_string_to_value(
if let Some(row_values) = iter.next() {
let row_values = row_values?;
let mut row = TaggedDictBuilder::new(tag);
let mut row = TaggedDictBuilder::new(&tag);
for (idx, entry) in row_values.iter().enumerate() {
row.insert_tagged(
fields.get(idx).unwrap(),
Value::Primitive(Primitive::String(String::from(entry))).tagged(tag),
Value::Primitive(Primitive::String(String::from(entry))).tagged(&tag),
);
}
@ -78,7 +79,7 @@ pub fn from_tsv_string_to_value(
}
}
Ok(Tagged::from_item(Value::List(rows), tag))
Ok(Value::Table(rows).tagged(&tag))
}
fn from_tsv(
@ -87,9 +88,9 @@ fn from_tsv(
}: FromTSVArgs,
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let name_span = name;
let name_tag = name;
let stream = async_stream_block! {
let stream = async_stream! {
let values: Vec<Tagged<Value>> = input.values.collect().await;
let mut concat_string = String::new();
@ -97,7 +98,7 @@ fn from_tsv(
for value in values {
let value_tag = value.tag();
latest_tag = Some(value_tag);
latest_tag = Some(value_tag.clone());
match value.item {
Value::Primitive(Primitive::String(s)) => {
concat_string.push_str(&s);
@ -106,17 +107,17 @@ fn from_tsv(
_ => yield Err(ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
name_span,
&name_tag,
"value originates from here",
value_tag.span,
&value_tag,
)),
}
}
match from_tsv_string_to_value(concat_string, skip_headers, name_span) {
match from_tsv_string_to_value(concat_string, skip_headers, name_tag.clone()) {
Ok(x) => match x {
Tagged { item: Value::List(list), .. } => {
Tagged { item: Value::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
@ -127,9 +128,9 @@ fn from_tsv(
yield Err(ShellError::labeled_error_with_secondary(
"Could not parse as TSV",
"input cannot be parsed as TSV",
name_span,
&name_tag,
"value originates from here",
last_tag.span,
&last_tag,
))
} ,
}

85
src/commands/from_url.rs Normal file
View file

@ -0,0 +1,85 @@
use crate::commands::WholeStreamCommand;
use crate::data::{Primitive, TaggedDictBuilder, Value};
use crate::prelude::*;
pub struct FromURL;
impl WholeStreamCommand for FromURL {
fn name(&self) -> &str {
"from-url"
}
fn signature(&self) -> Signature {
Signature::build("from-url")
}
fn usage(&self) -> &str {
"Parse url-encoded string as a table."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_url(args, registry)
}
}
fn from_url(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
let tag = args.name_tag();
let input = args.input;
let stream = async_stream! {
let values: Vec<Tagged<Value>> = input.values.collect().await;
let mut concat_string = String::new();
let mut latest_tag: Option<Tag> = None;
for value in values {
let value_tag = value.tag();
latest_tag = Some(value_tag.clone());
match value.item {
Value::Primitive(Primitive::String(s)) => {
concat_string.push_str(&s);
}
_ => yield Err(ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
&tag,
"value originates from here",
&value_tag,
)),
}
}
let result = serde_urlencoded::from_str::<Vec<(String, String)>>(&concat_string);
match result {
Ok(result) => {
let mut row = TaggedDictBuilder::new(tag);
for (k,v) in result {
row.insert(k, Value::string(v));
}
yield ReturnSuccess::value(row.into_tagged_value());
}
_ => {
if let Some(last_tag) = latest_tag {
yield Err(ShellError::labeled_error_with_secondary(
"String not compatible with url-encoding",
"input not url-encoded",
tag,
"value originates from here",
last_tag,
));
}
}
}
};
Ok(stream.to_output_stream())
}

View file

@ -1,5 +1,5 @@
use crate::commands::WholeStreamCommand;
use crate::object::{Primitive, TaggedDictBuilder, Value};
use crate::data::{Primitive, TaggedDictBuilder, Value};
use crate::prelude::*;
pub struct FromXML;
@ -34,7 +34,7 @@ fn from_node_to_value<'a, 'd>(n: &roxmltree::Node<'a, 'd>, tag: impl Into<Tag>)
let mut children_values = vec![];
for c in n.children() {
children_values.push(from_node_to_value(&c, tag));
children_values.push(from_node_to_value(&c, &tag));
}
let children_values: Vec<Tagged<Value>> = children_values
@ -55,7 +55,7 @@ fn from_node_to_value<'a, 'd>(n: &roxmltree::Node<'a, 'd>, tag: impl Into<Tag>)
.collect();
let mut collected = TaggedDictBuilder::new(tag);
collected.insert(name.clone(), Value::List(children_values));
collected.insert(name.clone(), Value::Table(children_values));
collected.into_tagged_value()
} else if n.is_comment() {
@ -83,10 +83,10 @@ pub fn from_xml_string_to_value(
fn from_xml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
let span = args.name_span();
let tag = args.name_tag();
let input = args.input;
let stream = async_stream_block! {
let stream = async_stream! {
let values: Vec<Tagged<Value>> = input.values.collect().await;
let mut concat_string = String::new();
@ -94,7 +94,7 @@ fn from_xml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
for value in values {
let value_tag = value.tag();
latest_tag = Some(value_tag);
latest_tag = Some(value_tag.clone());
match value.item {
Value::Primitive(Primitive::String(s)) => {
concat_string.push_str(&s);
@ -103,17 +103,17 @@ fn from_xml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
_ => yield Err(ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
span,
&tag,
"value originates from here",
value_tag.span,
&value_tag,
)),
}
}
match from_xml_string_to_value(concat_string, span) {
match from_xml_string_to_value(concat_string, tag.clone()) {
Ok(x) => match x {
Tagged { item: Value::List(list), .. } => {
Tagged { item: Value::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
@ -124,9 +124,9 @@ fn from_xml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
yield Err(ShellError::labeled_error_with_secondary(
"Could not parse as XML",
"input cannot be parsed as XML",
span,
&tag,
"value originates from here",
last_tag.span,
&last_tag,
))
} ,
}
@ -134,3 +134,73 @@ fn from_xml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
Ok(stream.to_output_stream())
}
#[cfg(test)]
mod tests {
use crate::commands::from_xml;
use crate::data::meta::*;
use crate::Value;
use indexmap::IndexMap;
fn string(input: impl Into<String>) -> Tagged<Value> {
Value::string(input.into()).tagged_unknown()
}
fn row(entries: IndexMap<String, Tagged<Value>>) -> Tagged<Value> {
Value::row(entries).tagged_unknown()
}
fn table(list: &Vec<Tagged<Value>>) -> Tagged<Value> {
Value::table(list).tagged_unknown()
}
fn parse(xml: &str) -> Tagged<Value> {
from_xml::from_xml_string_to_value(xml.to_string(), Tag::unknown()).unwrap()
}
#[test]
fn parses_empty_element() {
let source = "<nu></nu>";
assert_eq!(
parse(source),
row(indexmap! {
"nu".into() => table(&vec![])
})
);
}
#[test]
fn parses_element_with_text() {
let source = "<nu>La era de los tres caballeros</nu>";
assert_eq!(
parse(source),
row(indexmap! {
"nu".into() => table(&vec![string("La era de los tres caballeros")])
})
);
}
#[test]
fn parses_element_with_elements() {
let source = "\
<nu>
<dev>Andrés</dev>
<dev>Jonathan</dev>
<dev>Yehuda</dev>
</nu>";
assert_eq!(
parse(source),
row(indexmap! {
"nu".into() => table(&vec![
row(indexmap! {"dev".into() => table(&vec![string("Andrés")])}),
row(indexmap! {"dev".into() => table(&vec![string("Jonathan")])}),
row(indexmap! {"dev".into() => table(&vec![string("Yehuda")])})
])
})
);
}
}

View file

@ -1,5 +1,5 @@
use crate::commands::WholeStreamCommand;
use crate::object::{Primitive, TaggedDictBuilder, Value};
use crate::data::{Primitive, TaggedDictBuilder, Value};
use crate::prelude::*;
pub struct FromYAML;
@ -62,19 +62,19 @@ fn convert_yaml_value_to_nu_value(v: &serde_yaml::Value, tag: impl Into<Tag>) ->
Value::Primitive(Primitive::from(n.as_f64().unwrap())).tagged(tag)
}
serde_yaml::Value::String(s) => Value::string(s).tagged(tag),
serde_yaml::Value::Sequence(a) => Value::List(
serde_yaml::Value::Sequence(a) => Value::Table(
a.iter()
.map(|x| convert_yaml_value_to_nu_value(x, tag))
.map(|x| convert_yaml_value_to_nu_value(x, &tag))
.collect(),
)
.tagged(tag),
serde_yaml::Value::Mapping(t) => {
let mut collected = TaggedDictBuilder::new(tag);
let mut collected = TaggedDictBuilder::new(&tag);
for (k, v) in t.iter() {
match k {
serde_yaml::Value::String(k) => {
collected.insert_tagged(k.clone(), convert_yaml_value_to_nu_value(v, tag));
collected.insert_tagged(k.clone(), convert_yaml_value_to_nu_value(v, &tag));
}
_ => unimplemented!("Unknown key type"),
}
@ -97,10 +97,10 @@ pub fn from_yaml_string_to_value(
fn from_yaml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
let span = args.name_span();
let tag = args.name_tag();
let input = args.input;
let stream = async_stream_block! {
let stream = async_stream! {
let values: Vec<Tagged<Value>> = input.values.collect().await;
let mut concat_string = String::new();
@ -108,7 +108,7 @@ fn from_yaml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStre
for value in values {
let value_tag = value.tag();
latest_tag = Some(value_tag);
latest_tag = Some(value_tag.clone());
match value.item {
Value::Primitive(Primitive::String(s)) => {
concat_string.push_str(&s);
@ -117,17 +117,17 @@ fn from_yaml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStre
_ => yield Err(ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
span,
&tag,
"value originates from here",
value_tag.span,
&value_tag,
)),
}
}
match from_yaml_string_to_value(concat_string, span) {
match from_yaml_string_to_value(concat_string, tag.clone()) {
Ok(x) => match x {
Tagged { item: Value::List(list), .. } => {
Tagged { item: Value::Table(list), .. } => {
for l in list {
yield ReturnSuccess::value(l);
}
@ -138,9 +138,9 @@ fn from_yaml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStre
yield Err(ShellError::labeled_error_with_secondary(
"Could not parse as YAML",
"input cannot be parsed as YAML",
span,
&tag,
"value originates from here",
last_tag.span,
&last_tag,
))
} ,
}

View file

@ -1,13 +1,16 @@
use crate::commands::WholeStreamCommand;
use crate::data::meta::tag_for_tagged_list;
use crate::data::Value;
use crate::errors::ShellError;
use crate::object::Value;
use crate::prelude::*;
use log::trace;
pub struct Get;
#[derive(Deserialize)]
pub struct GetArgs {
rest: Vec<Tagged<String>>,
member: ColumnPath,
rest: Vec<ColumnPath>,
}
impl WholeStreamCommand for Get {
@ -16,7 +19,16 @@ impl WholeStreamCommand for Get {
}
fn signature(&self) -> Signature {
Signature::build("get").rest(SyntaxType::Member)
Signature::build("get")
.required(
"member",
SyntaxShape::ColumnPath,
"the path to the data to get",
)
.rest(
SyntaxShape::ColumnPath,
"optionally return additional data by path",
)
}
fn usage(&self) -> &str {
@ -32,24 +44,41 @@ impl WholeStreamCommand for Get {
}
}
fn get_member(path: &Tagged<String>, obj: &Tagged<Value>) -> Result<Tagged<Value>, ShellError> {
pub type ColumnPath = Vec<Tagged<String>>;
pub fn get_column_path(
path: &ColumnPath,
obj: &Tagged<Value>,
) -> Result<Tagged<Value>, ShellError> {
let mut current = Some(obj);
for p in path.split(".") {
for p in path.iter() {
if let Some(obj) = current {
current = match obj.get_data_by_key(p) {
current = match obj.get_data_by_key(&p) {
Some(v) => Some(v),
None =>
// Before we give up, see if they gave us a path that matches a field name by itself
{
match obj.get_data_by_key(&path.item) {
Some(v) => return Ok(v.clone()),
None => {
return Err(ShellError::labeled_error(
"Unknown column",
"table missing column",
path.span(),
));
}
let possibilities = obj.data_descriptors();
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|x| (natural::distance::levenshtein_distance(x, &p), x))
.collect();
possible_matches.sort();
if possible_matches.len() > 0 {
return Err(ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", possible_matches[0].1),
tag_for_tagged_list(path.iter().map(|p| p.tag())),
));
} else {
return Err(ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
tag_for_tagged_list(path.iter().map(|p| p.tag())),
));
}
}
}
@ -58,22 +87,46 @@ fn get_member(path: &Tagged<String>, obj: &Tagged<Value>) -> Result<Tagged<Value
match current {
Some(v) => Ok(v.clone()),
None => Ok(Value::nothing().tagged(obj.tag)),
None => match obj {
// If its None check for certain values.
Tagged {
item: Value::Primitive(Primitive::String(_)),
..
} => Ok(obj.clone()),
Tagged {
item: Value::Primitive(Primitive::Path(_)),
..
} => Ok(obj.clone()),
_ => Ok(Value::nothing().tagged(&obj.tag)),
},
}
}
pub fn get(
GetArgs { rest: fields }: GetArgs,
GetArgs {
member,
rest: fields,
}: GetArgs,
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
trace!("get {:?} {:?}", member, fields);
let stream = input
.values
.map(move |item| {
let mut result = VecDeque::new();
for field in &fields {
match get_member(field, &item) {
let member = vec![member.clone()];
let fields = vec![&member, &fields]
.into_iter()
.flatten()
.collect::<Vec<&ColumnPath>>();
for column_path in &fields {
match get_column_path(column_path, &item) {
Ok(Tagged {
item: Value::List(l),
item: Value::Table(l),
..
}) => {
for item in l {

94
src/commands/group_by.rs Normal file
View file

@ -0,0 +1,94 @@
use crate::commands::WholeStreamCommand;
use crate::data::TaggedDictBuilder;
use crate::errors::ShellError;
use crate::prelude::*;
pub struct GroupBy;
#[derive(Deserialize)]
pub struct GroupByArgs {
column_name: Tagged<String>,
}
impl WholeStreamCommand for GroupBy {
fn name(&self) -> &str {
"group-by"
}
fn signature(&self) -> Signature {
Signature::build("group-by").required(
"column_name",
SyntaxShape::String,
"the name of the column to group by",
)
}
fn usage(&self) -> &str {
"Creates a new table with the data from the table rows grouped by the column given."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, group_by)?.run()
}
}
fn group_by(
GroupByArgs { column_name }: GroupByArgs,
RunnableContext { input, name, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let values: Vec<Tagged<Value>> = input.values.collect().await;
let mut groups = indexmap::IndexMap::new();
for value in values {
let group_key = value.get_data_by_key(&column_name.item);
if group_key.is_none() {
let possibilities = value.data_descriptors();
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|x| (natural::distance::levenshtein_distance(x, &column_name.item), x))
.collect();
possible_matches.sort();
let err = {
if possible_matches.len() > 0 {
ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", possible_matches[0].1),
&column_name.tag,)
} else {
ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
&column_name.tag,
)
}
};
yield Err(err)
} else {
let group_key = group_key.unwrap().as_string()?;
let mut group = groups.entry(group_key).or_insert(vec![]);
group.push(value);
}
}
let mut out = TaggedDictBuilder::new(name.clone());
for (k,v) in groups.iter() {
out.insert(k, Value::table(v));
}
yield ReturnSuccess::value(out)
};
Ok(stream.to_output_stream())
}

View file

@ -1,7 +1,7 @@
use crate::commands::command::CommandAction;
use crate::commands::PerItemCommand;
use crate::data::{command_dict, TaggedDictBuilder};
use crate::errors::ShellError;
use crate::parser::registry;
use crate::parser::registry::{self, NamedType, PositionalType};
use crate::prelude::*;
pub struct Help;
@ -12,7 +12,7 @@ impl PerItemCommand for Help {
}
fn signature(&self) -> registry::Signature {
Signature::build("help").rest(SyntaxType::Any)
Signature::build("help").rest(SyntaxShape::Any, "the name of command(s) to get help on")
}
fn usage(&self) -> &str {
@ -22,34 +22,149 @@ impl PerItemCommand for Help {
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
_input: Tagged<Value>,
) -> Result<OutputStream, ShellError> {
let span = call_info.name_span;
let tag = &call_info.name_tag;
if call_info.args.len() == 0 {
return Ok(
vec![
Ok(ReturnSuccess::Action(
CommandAction::EnterHelpShell(
Tagged::from_simple_spanned_item(Value::nothing(), span)
)))].into()
)
}
match call_info.args.expect_nth(0)? {
Tagged {
match call_info.args.nth(0) {
Some(Tagged {
item: Value::Primitive(Primitive::String(document)),
..
} => Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
Tagged::from_simple_spanned_item(Value::string(document), span)
)))]
.into()),
x => Ok(
vec![Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(x.clone())))]
.into(),
),
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).unwrap(), tag.clone());
short_desc.insert("name", cmd);
short_desc.insert(
"description",
value.get_data_by_key("usage").unwrap().as_string().unwrap(),
);
help.push_back(ReturnSuccess::value(short_desc.into_tagged_value()));
}
} else {
if let Some(command) = registry.get_command(document) {
let mut long_desc = String::new();
long_desc.push_str(&command.usage());
long_desc.push_str("\n");
let signature = command.signature();
let mut one_liner = String::new();
one_liner.push_str(&signature.name);
one_liner.push_str(" ");
if signature.named.len() > 0 {
one_liner.push_str("{flags} ");
}
for positional in &signature.positional {
match &positional.0 {
PositionalType::Mandatory(name, _m) => {
one_liner.push_str(&format!("<{}> ", name));
}
PositionalType::Optional(name, _o) => {
one_liner.push_str(&format!("({}) ", name));
}
}
}
if signature.rest_positional.is_some() {
one_liner.push_str(&format!(" ...args",));
}
long_desc.push_str(&format!("\nUsage:\n > {}\n", one_liner));
if signature.positional.len() > 0 || signature.rest_positional.is_some() {
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));
}
PositionalType::Optional(name, _o) => {
long_desc
.push_str(&format!(" ({}) {}\n", name, positional.1));
}
}
}
if signature.rest_positional.is_some() {
long_desc.push_str(&format!(
" ...args{} {}\n",
if signature.rest_positional.is_some() {
":"
} else {
""
},
signature.rest_positional.unwrap().1
));
}
}
if signature.named.len() > 0 {
long_desc.push_str("\nflags:\n");
for (flag, ty) in signature.named {
match ty.0 {
NamedType::Switch => {
long_desc.push_str(&format!(
" --{}{} {}\n",
flag,
if ty.1.len() > 0 { ":" } else { "" },
ty.1
));
}
NamedType::Mandatory(m) => {
long_desc.push_str(&format!(
" --{} <{}> (required parameter){} {}\n",
flag,
m,
if ty.1.len() > 0 { ":" } else { "" },
ty.1
));
}
NamedType::Optional(o) => {
long_desc.push_str(&format!(
" --{} <{}>{} {}\n",
flag,
o,
if ty.1.len() > 0 { ":" } else { "" },
ty.1
));
}
}
}
}
help.push_back(ReturnSuccess::value(
Value::string(long_desc).tagged(tag.clone()),
));
}
}
Ok(help.to_output_stream())
}
_ => {
let msg = r#"Welcome to Nushell.
Here are some tips to help you get started.
* help commands - list all available commands
* help <command name> - display help about a particular command
You can also learn more at https://book.nushell.sh"#;
let mut output_stream = VecDeque::new();
output_stream.push_back(ReturnSuccess::value(Value::string(msg).tagged(tag)));
Ok(output_stream.to_output_stream())
}
}
}
}

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