Compare commits

..

1 Commits

Author SHA1 Message Date
777f746127 Update release script to nu v0.71 and use ubuntu-20.04 to build nu binary (#7290)
# Description

1. Update nu to v0.71 for release script
2. Remove the usage of `set-output` see:
https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
3. Use `ubuntu-20.04` instead of `ubuntu-latest` to fix #7282 

To check the workflow running result see:
https://github.com/hustcer/nu-release/actions/runs/3588720720/jobs/6040412953

# User-Facing Changes

_(List of all changes that impact the user experience here. This helps
us keep track of breaking changes.)_

# Tests + Formatting

Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass

# After Submitting

If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-12-06 06:17:06 +13:00
941 changed files with 20265 additions and 42425 deletions

View File

@ -1,20 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# docs
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-patch"]
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@ -11,26 +11,9 @@ jobs:
strategy: strategy:
fail-fast: true fail-fast: true
matrix: matrix:
# Pinning to Ubuntu 20.04 because building on newer Ubuntu versions causes linux-gnu platform: [windows-latest, macos-latest, ubuntu-latest]
# builds to link against a too-new-for-many-Linux-installs glibc version. Consider
# revisiting this when 20.04 is closer to EOL (April 2025)
platform: [windows-latest, macos-latest, ubuntu-20.04]
style: [default, dataframe]
rust: rust:
- stable - stable
include:
- style: default
flags: ""
- style: dataframe
flags: "--features=dataframe "
exclude:
# only test dataframes on Ubuntu (the fastest platform)
- platform: windows-latest
style: dataframe
- platform: macos-latest
style: dataframe
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
env: env:
@ -40,13 +23,19 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.4.2 uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
- name: cargo fmt - name: Rustfmt
run: cargo fmt --all -- --check uses: actions-rs/cargo@v1.0.1
with:
command: fmt
args: --all -- --check
- name: Clippy - name: Clippy
run: cargo clippy --workspace ${{ matrix.flags }}--exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect uses: actions-rs/cargo@v1.0.1
with:
command: clippy
args: --workspace --exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
nu-tests: nu-tests:
env: env:
@ -55,7 +44,7 @@ jobs:
strategy: strategy:
fail-fast: true fail-fast: true
matrix: matrix:
platform: [windows-latest, macos-latest, ubuntu-20.04] platform: [windows-latest, macos-latest, ubuntu-latest]
style: [default, dataframe] style: [default, dataframe]
rust: rust:
- stable - stable
@ -77,10 +66,13 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.4.2 uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
- name: Tests - name: Tests
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }} uses: actions-rs/cargo@v1.0.1
with:
command: test
args: --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
python-virtualenv: python-virtualenv:
env: env:
@ -89,7 +81,7 @@ jobs:
strategy: strategy:
fail-fast: true fail-fast: true
matrix: matrix:
platform: [ubuntu-20.04, macos-latest, windows-latest] platform: [ubuntu-latest, macos-latest, windows-latest]
rust: rust:
- stable - stable
py: py:
@ -101,10 +93,13 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.4.2 uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
- name: Install Nushell - name: Install Nushell
run: cargo install --locked --path=. --profile ci --no-default-features uses: actions-rs/cargo@v1.0.1
with:
command: install
args: --locked --path=. --profile ci --no-default-features
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
@ -113,18 +108,12 @@ jobs:
- run: python -m pip install tox - run: python -m pip install tox
# Get only the latest tagged version for stability reasons
- name: Install virtualenv - name: Install virtualenv
run: git clone https://github.com/pypa/virtualenv.git && cd virtualenv && git checkout $(git describe --tags | cut -d - -f 1) run: git clone https://github.com/pypa/virtualenv.git
shell: bash shell: bash
- name: Test Nushell in virtualenv - name: Test Nushell in virtualenv
run: | run: cd virtualenv && tox -e ${{ matrix.py }} -- -k nushell
cd virtualenv
# We need to disable failing on coverage levels.
nu -c "open pyproject.toml | upsert tool.coverage.report.fail_under 1 | save patchproject.toml"
mv patchproject.toml pyproject.toml
tox -e ${{ matrix.py }} -- -k nushell
shell: bash shell: bash
# Build+test plugins on their own, without the rest of Nu. This helps with CI parallelization and # Build+test plugins on their own, without the rest of Nu. This helps with CI parallelization and
@ -136,7 +125,7 @@ jobs:
strategy: strategy:
fail-fast: true fail-fast: true
matrix: matrix:
platform: [windows-latest, macos-latest, ubuntu-20.04] platform: [windows-latest, macos-latest, ubuntu-latest]
rust: rust:
- stable - stable
@ -146,47 +135,16 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.4.2 uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
- name: Clippy - name: Clippy
run: cargo clippy --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect uses: actions-rs/cargo@v1.0.1
- name: Tests
run: cargo test --profile ci --package nu_plugin_*
nu-coverage:
needs: nu-tests
env:
NUSHELL_CARGO_TARGET: ci
strategy:
fail-fast: true
matrix:
platform: [windows-latest, ubuntu-20.04]
rust:
- stable
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.4.2
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Tests
shell: bash
run: |
source <(cargo llvm-cov show-env --export-prefix) # Set the environment variables needed to get coverage.
cargo llvm-cov clean --workspace # Remove artifacts that may affect the coverage results.
cargo build --lib --bins --examples --workspace --profile ci
cargo test --lib --bins --examples --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
cargo llvm-cov report --profile ci --lcov --output-path lcov.info
- name: Upload coverage reports to Codecov with GitHub Action
uses: codecov/codecov-action@v3
with: with:
files: lcov.info command: clippy
args: --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
- name: Tests
uses: actions-rs/cargo@v1.0.1
with:
command: test
args: --profile ci --package nu_plugin_*

View File

@ -6,32 +6,6 @@
# REF: # REF:
# 1. https://github.com/volks73/cargo-wix # 1. https://github.com/volks73/cargo-wix
# Added 2022-11-29 when Windows packaging wouldn't work
# To run this manual for windows
# unset CARGO_TARGET_DIR if set
# hide-env CARGO_TARGET_DIR
# let-env TARGET = 'x86_64-pc-windows-msvc'
# let-env TARGET_RUSTFLAGS = ''
# let-env GITHUB_WORKSPACE = 'C:\Users\dschroeder\source\repos\forks\nushell'
# let-env GITHUB_OUTPUT = 'C:\Users\dschroeder\source\repos\forks\nushell\output\out.txt'
# let-env OS = 'windows-latest'
# You need to run this twice. The first pass makes the output folder and builds everything
# The second pass generates the msi file
# Pass 1 let-env _EXTRA_ = 'bin'
# Pass 2 let-env _EXTRA_ = 'msi'
# make sure 7z.exe is in your path https://www.7-zip.org/download.html
# let-env Path = ($env.Path | append 'c:\apps\7-zip')
# make sure aria2c.exe is in your path https://github.com/aria2/aria2
# let-env Path = ($env.Path | append 'c:\path\to\aria2c')
# make sure you have the wixtools installed https://wixtoolset.org/
# let-env Path = ($env.Path | append 'C:\Users\dschroeder\AppData\Local\tauri\WixTools')
# After msi is generated, if you have to update winget-pkgs repo, you'll need to patch the release
# by deleting the existing msi and uploading this new msi. Then you'll need to update the hash
# on the winget-pkgs PR. To generate the hash, run this command
# open target\wix\nu-0.74.0-x86_64-pc-windows-msvc.msi | hash sha256
# Then, just take the output and put it in the winget-pkgs PR for the hash on the msi
# The main binary file to be released # The main binary file to be released
let bin = 'nu' let bin = 'nu'
let os = $env.OS let os = $env.OS
@ -59,7 +33,6 @@ $'Start building ($bin)...'; hr-line
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
if $os in [$USE_UBUNTU, 'macos-latest'] { if $os in [$USE_UBUNTU, 'macos-latest'] {
if $os == $USE_UBUNTU { if $os == $USE_UBUNTU {
sudo apt update
sudo apt-get install libxcb-composite0-dev -y sudo apt-get install libxcb-composite0-dev -y
} }
if $target == 'aarch64-unknown-linux-gnu' { if $target == 'aarch64-unknown-linux-gnu' {
@ -70,10 +43,6 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
sudo apt-get install pkg-config gcc-arm-linux-gnueabihf -y sudo apt-get install pkg-config gcc-arm-linux-gnueabihf -y
let-env CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER = 'arm-linux-gnueabihf-gcc' let-env CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER = 'arm-linux-gnueabihf-gcc'
cargo-build-nu $flags cargo-build-nu $flags
} else if $target == 'riscv64gc-unknown-linux-gnu' {
sudo apt-get install gcc-riscv64-linux-gnu -y
let-env CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER = 'riscv64-linux-gnu-gcc'
cargo-build-nu $flags
} else { } else {
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?' # musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
# Actually just for x86_64-unknown-linux-musl target # Actually just for x86_64-unknown-linux-musl target
@ -156,7 +125,7 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
cd $src; hr-line cd $src; hr-line
# Wix need the binaries be stored in target/release/ # Wix need the binaries be stored in target/release/
cp -r $'($dist)/*' target/release/ cp -r $'($dist)/*' target/release/
cargo install cargo-wix --version 0.3.4 cargo install cargo-wix --version 0.3.3
cargo wix --no-build --nocapture --package nu --output $wixRelease cargo wix --no-build --nocapture --package nu --output $wixRelease
print $'archive: ---> ($wixRelease)'; print $'archive: ---> ($wixRelease)';
echo $"archive=($wixRelease)" | save --append $env.GITHUB_OUTPUT echo $"archive=($wixRelease)" | save --append $env.GITHUB_OUTPUT

View File

@ -27,7 +27,6 @@ jobs:
- x86_64-unknown-linux-musl - x86_64-unknown-linux-musl
- aarch64-unknown-linux-gnu - aarch64-unknown-linux-gnu
- armv7-unknown-linux-gnueabihf - armv7-unknown-linux-gnueabihf
- riscv64gc-unknown-linux-gnu
extra: ['bin'] extra: ['bin']
include: include:
- target: aarch64-apple-darwin - target: aarch64-apple-darwin
@ -56,26 +55,24 @@ jobs:
- target: armv7-unknown-linux-gnueabihf - target: armv7-unknown-linux-gnueabihf
os: ubuntu-20.04 os: ubuntu-20.04
target_rustflags: '' target_rustflags: ''
- target: riscv64gc-unknown-linux-gnu
os: ubuntu-20.04
target_rustflags: ''
runs-on: ${{matrix.os}} runs-on: ${{matrix.os}}
steps: steps:
- uses: actions/checkout@v3.1.0 - uses: actions/checkout@v3.1.0
- name: Update Rust Toolchain Target - name: Install Rust Toolchain Components
run: | uses: actions-rs/toolchain@v1.0.6
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml with:
override: true
- name: Setup Rust toolchain and cache profile: minimal
uses: actions-rust-lang/setup-rust-toolchain@v1.4.2 toolchain: stable
target: ${{ matrix.target }}
- name: Setup Nushell - name: Setup Nushell
uses: hustcer/setup-nu@v3 uses: hustcer/setup-nu@v3
with: with:
version: 0.72.1 version: 0.71.0
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -12,7 +12,7 @@ jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v6 - uses: actions/stale@v3
with: with:
#debug-only: true #debug-only: true
ascending: true ascending: true

View File

@ -1,13 +0,0 @@
name: Typos
on: [pull_request]
jobs:
run:
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v2
- name: Check spelling
uses: crate-ci/typos@master

View File

@ -1,12 +0,0 @@
[files]
extend-exclude = ["crates/nu-command/tests/commands/table.rs", "*.tsv", "*.json", "*.txt"]
[default.extend-words]
# Ignore false-positives
nd = "nd"
fo = "fo"
ons = "ons"
ba = "ba"
Plasticos = "Plasticos"
IIF = "IIF"
numer = "numer"

1025
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -3,14 +3,14 @@ authors = ["The Nushell Project Developers"]
default-run = "nu" default-run = "nu"
description = "A new type of shell" description = "A new type of shell"
documentation = "https://www.nushell.sh/book/" documentation = "https://www.nushell.sh/book/"
edition = "2021" edition = "2018"
exclude = ["images"] exclude = ["images"]
homepage = "https://www.nushell.sh" homepage = "https://www.nushell.sh"
license = "MIT" license = "MIT"
name = "nu" name = "nu"
repository = "https://github.com/nushell/nushell" repository = "https://github.com/nushell/nushell"
rust-version = "1.60" rust-version = "1.60"
version = "0.76.0" version = "0.72.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -35,7 +35,6 @@ members = [
"crates/nu_plugin_example", "crates/nu_plugin_example",
"crates/nu_plugin_query", "crates/nu_plugin_query",
"crates/nu_plugin_custom_values", "crates/nu_plugin_custom_values",
"crates/nu_plugin_formats",
"crates/nu-utils", "crates/nu-utils",
] ]
@ -44,26 +43,25 @@ chrono = { version = "0.4.23", features = ["serde"] }
crossterm = "0.24.0" crossterm = "0.24.0"
ctrlc = "3.2.1" ctrlc = "3.2.1"
log = "0.4" log = "0.4"
miette = { version = "5.5.0", features = ["fancy-no-backtrace"] } miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
nu-ansi-term = "0.46.0" nu-ansi-term = "0.46.0"
nu-cli = { path = "./crates/nu-cli", version = "0.76.0" } nu-cli = { path="./crates/nu-cli", version = "0.72.1" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.76.0" } nu-color-config = { path = "./crates/nu-color-config", version = "0.72.1" }
nu-command = { path = "./crates/nu-command", version = "0.76.0" } nu-command = { path="./crates/nu-command", version = "0.72.1" }
nu-engine = { path = "./crates/nu-engine", version = "0.76.0" } nu-engine = { path="./crates/nu-engine", version = "0.72.1" }
nu-json = { path = "./crates/nu-json", version = "0.76.0" } nu-json = { path="./crates/nu-json", version = "0.72.1" }
nu-parser = { path = "./crates/nu-parser", version = "0.76.0" } nu-parser = { path="./crates/nu-parser", version = "0.72.1" }
nu-path = { path = "./crates/nu-path", version = "0.76.0" } nu-path = { path="./crates/nu-path", version = "0.72.1" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.76.0" } nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.72.1" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.76.0" } nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.72.1" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.76.0" } nu-protocol = { path = "./crates/nu-protocol", version = "0.72.1" }
nu-system = { path = "./crates/nu-system", version = "0.76.0" } nu-system = { path = "./crates/nu-system", version = "0.72.1" }
nu-table = { path = "./crates/nu-table", version = "0.76.0" } nu-table = { path = "./crates/nu-table", version = "0.72.1" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.76.0" } nu-term-grid = { path = "./crates/nu-term-grid", version = "0.72.1" }
nu-utils = { path = "./crates/nu-utils", version = "0.76.0" } nu-utils = { path = "./crates/nu-utils", version = "0.72.1" }
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
reedline = { version = "0.16.0", features = ["bashisms", "sqlite"] } rayon = "1.5.1"
rayon = "1.6.1"
is_executable = "1.0.1" is_executable = "1.0.1"
simplelog = "0.12.0" simplelog = "0.12.0"
time = "0.3.12" time = "0.3.12"
@ -78,34 +76,21 @@ signal-hook = { version = "0.3.14", default-features = false }
winres = "0.1" winres = "0.1"
[target.'cfg(target_family = "unix")'.dependencies] [target.'cfg(target_family = "unix")'.dependencies]
nix = { version = "0.25", default-features = false, features = [ nix = { version = "0.25", default-features = false, features = ["signal", "process", "fs", "term"]}
"signal",
"process",
"fs",
"term",
] }
atty = "0.2" atty = "0.2"
[dev-dependencies] [dev-dependencies]
nu-test-support = { path = "./crates/nu-test-support", version = "0.76.0" } nu-test-support = { path="./crates/nu-test-support", version = "0.72.1" }
tempfile = "3.2.0" tempfile = "3.2.0"
assert_cmd = "2.0.2" assert_cmd = "2.0.2"
criterion = "0.4"
pretty_assertions = "1.0.0" pretty_assertions = "1.0.0"
serial_test = "1.0.0" serial_test = "0.8.0"
hamcrest2 = "0.3.0" hamcrest2 = "0.3.0"
rstest = { version = "0.16.0", default-features = false } rstest = {version = "0.15.0", default-features = false}
itertools = "0.10.3" itertools = "0.10.3"
[features] [features]
plugin = [ plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
"nu-plugin",
"nu-cli/plugin",
"nu-parser/plugin",
"nu-command/plugin",
"nu-protocol/plugin",
"nu-engine/plugin",
]
# extra used to be more useful but now it's the same as default. Leaving it in for backcompat with existing build scripts # extra used to be more useful but now it's the same as default. Leaving it in for backcompat with existing build scripts
extra = ["default"] extra = ["default"]
default = ["plugin", "which-support", "trash-support", "sqlite"] default = ["plugin", "which-support", "trash-support", "sqlite"]
@ -150,16 +135,8 @@ debug = false
[[bin]] [[bin]]
name = "nu" name = "nu"
path = "src/main.rs" path = "src/main.rs"
bench = false
# To use a development version of a dependency please use a global override here # To use a development version of a dependency please use a global override here
# changing versions in each sub-crate of the workspace is tedious # changing versions in each sub-crate of the workspace is tedious
[patch.crates-io] [patch.crates-io]
# reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" } # reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" }
# Criterion benchmarking setup
# Run all benchmarks with `cargo bench`
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
[[bench]]
name = "benchmarks"
harness = false

View File

@ -1,9 +0,0 @@
# Configuration for cross-rs: https://github.com/cross-rs/cross
# Run cross-rs like this:
# cross build --target aarch64-unknown-linux-musl --release
[target.aarch64-unknown-linux-gnu]
dockerfile = "./docker/cross-rs/aarch64-unknown-linux-gnu.dockerfile"
[target.aarch64-unknown-linux-musl]
dockerfile = "./docker/cross-rs/aarch64-unknown-linux-musl.dockerfile"

View File

@ -1,12 +1,11 @@
# Nushell <!-- omit in toc --> # Nushell <!-- omit in toc -->
[![Crates.io](https://img.shields.io/crates/v/nu.svg)](https://crates.io/crates/nu) [![Crates.io](https://img.shields.io/crates/v/nu.svg)](https://crates.io/crates/nu)
![Build Status](https://img.shields.io/github/actions/workflow/status/nushell/nushell/ci.yml?branch=main) ![Build Status](https://img.shields.io/github/workflow/status/nushell/nushell/continuous-integration)
[![Discord](https://img.shields.io/discord/601130461678272522.svg?logo=discord)](https://discord.gg/NtAbbGn) [![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) [![The Changelog #363](https://img.shields.io/badge/The%20Changelog-%23363-61c192.svg)](https://changelog.com/podcast/363)
[![@nu_shell](https://img.shields.io/badge/twitter-@nu_shell-1DA1F3?style=flat-square)](https://twitter.com/nu_shell) [![@nu_shell](https://img.shields.io/badge/twitter-@nu_shell-1DA1F3?style=flat-square)](https://twitter.com/nu_shell)
![GitHub commit activity](https://img.shields.io/github/commit-activity/m/nushell/nushell) ![GitHub commit activity](https://img.shields.io/github/commit-activity/m/nushell/nushell)
![GitHub contributors](https://img.shields.io/github/contributors/nushell/nushell) ![GitHub contributors](https://img.shields.io/github/contributors/nushell/nushell)
[![codecov](https://codecov.io/github/nushell/nushell/branch/main/graph/badge.svg?token=JheS8qu2II)](https://codecov.io/github/nushell/nushell)
A new type of shell. A new type of shell.
@ -33,7 +32,7 @@ This project has reached a minimum-viable-product level of quality. Many people
## Learning About Nu ## Learning About Nu
The [Nushell book](https://www.nushell.sh/book/) is the primary source of Nushell documentation. You can find [a full list of Nu commands in the book](https://www.nushell.sh/commands/), and we have many examples of using Nu in our [cookbook](https://www.nushell.sh/cookbook/). The [Nushell book](https://www.nushell.sh/book/) is the primary source of Nushell documentation. You can find [a full list of Nu commands in the book](https://www.nushell.sh/book/command_reference.html), and we have many examples of using Nu in our [cookbook](https://www.nushell.sh/cookbook/).
We're also active on [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell); come and chat with us! We're also active on [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell); come and chat with us!
@ -48,7 +47,7 @@ brew install nushell
winget install nushell winget install nushell
``` ```
To use `Nu` in GitHub Action, check [setup-nu](https://github.com/marketplace/actions/setup-nu) for more detail. To use `Nu` in Github Action, check [setup-nu](https://github.com/marketplace/actions/setup-nu) for more detail.
Detailed installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). Nu is available via many package managers: Detailed installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). Nu is available via many package managers:
@ -175,8 +174,6 @@ These binaries interact with nu via a simple JSON-RPC protocol where the command
If the plugin is a filter, data streams to it one element at a time, and it can stream data back in return via stdin/stdout. If the plugin is a 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. 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.
The [awesome-nu repo](https://github.com/nushell/awesome-nu#plugins) lists a variety of nu-plugins.
## Goals ## Goals
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals. Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.

View File

@ -1,7 +0,0 @@
# Criterion benchmarks
These are benchmarks using [Criterion](https://github.com/bheisler/criterion.rs), a microbenchmarking tool for Rust.
Run all benchmarks with `cargo bench`
Or run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`

View File

@ -1,191 +0,0 @@
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use nu_cli::eval_source;
use nu_parser::parse;
use nu_plugin::{EncodingType, PluginResponse};
use nu_protocol::{PipelineData, Span, Value};
use nu_utils::{get_default_config, get_default_env};
// FIXME: All benchmarks live in this 1 file to speed up build times when benchmarking.
// When the *_benchmarks functions were in different files, `cargo bench` would build
// an executable for every single one - incredibly slowly. Would be nice to figure out
// a way to split things up again.
fn parser_benchmarks(c: &mut Criterion) {
let mut engine_state = nu_command::create_default_context();
// parsing config.nu breaks without PWD set
engine_state.add_env_var(
"PWD".into(),
Value::string("/some/dir".to_string(), Span::test_data()),
);
let default_env = get_default_env().as_bytes();
c.bench_function("parse_default_env_file", |b| {
b.iter_batched(
|| nu_protocol::engine::StateWorkingSet::new(&engine_state),
|mut working_set| parse(&mut working_set, None, default_env, false, &[]),
BatchSize::SmallInput,
)
});
let default_config = get_default_config().as_bytes();
c.bench_function("parse_default_config_file", |b| {
b.iter_batched(
|| nu_protocol::engine::StateWorkingSet::new(&engine_state),
|mut working_set| parse(&mut working_set, None, default_config, false, &[]),
BatchSize::SmallInput,
)
});
c.bench_function("eval default_env.nu", |b| {
b.iter(|| {
let mut engine_state = nu_command::create_default_context();
let mut stack = nu_protocol::engine::Stack::new();
eval_source(
&mut engine_state,
&mut stack,
get_default_env().as_bytes(),
"default_env.nu",
PipelineData::empty(),
false,
)
})
});
c.bench_function("eval default_config.nu", |b| {
b.iter(|| {
let mut engine_state = nu_command::create_default_context();
// parsing config.nu breaks without PWD set
engine_state.add_env_var(
"PWD".into(),
Value::string("/some/dir".to_string(), Span::test_data()),
);
let mut stack = nu_protocol::engine::Stack::new();
eval_source(
&mut engine_state,
&mut stack,
get_default_config().as_bytes(),
"default_config.nu",
PipelineData::empty(),
false,
)
})
});
}
fn eval_benchmarks(c: &mut Criterion) {
c.bench_function("eval default_env.nu", |b| {
b.iter(|| {
let mut engine_state = nu_command::create_default_context();
let mut stack = nu_protocol::engine::Stack::new();
eval_source(
&mut engine_state,
&mut stack,
get_default_env().as_bytes(),
"default_env.nu",
PipelineData::empty(),
false,
)
})
});
c.bench_function("eval default_config.nu", |b| {
b.iter(|| {
let mut engine_state = nu_command::create_default_context();
// parsing config.nu breaks without PWD set
engine_state.add_env_var(
"PWD".into(),
Value::string("/some/dir".to_string(), Span::test_data()),
);
let mut stack = nu_protocol::engine::Stack::new();
eval_source(
&mut engine_state,
&mut stack,
get_default_config().as_bytes(),
"default_config.nu",
PipelineData::empty(),
false,
)
})
});
}
// generate a new table data with `row_cnt` rows, `col_cnt` columns.
fn encoding_test_data(row_cnt: usize, col_cnt: usize) -> Value {
let columns: Vec<String> = (0..col_cnt).map(|x| format!("col_{x}")).collect();
let vals: Vec<Value> = (0..col_cnt as i64).map(Value::test_int).collect();
Value::List {
vals: (0..row_cnt)
.map(|_| Value::test_record(columns.clone(), vals.clone()))
.collect(),
span: Span::test_data(),
}
}
fn encoding_benchmarks(c: &mut Criterion) {
let mut group = c.benchmark_group("Encoding");
let test_cnt_pairs = [
(100, 5),
(100, 10),
(100, 15),
(1000, 5),
(1000, 10),
(1000, 15),
(10000, 5),
(10000, 10),
(10000, 15),
];
for (row_cnt, col_cnt) in test_cnt_pairs.into_iter() {
for fmt in ["json", "msgpack"] {
group.bench_function(&format!("{fmt} encode {row_cnt} * {col_cnt}"), |b| {
let mut res = vec![];
let test_data =
PluginResponse::Value(Box::new(encoding_test_data(row_cnt, col_cnt)));
let encoder = EncodingType::try_from_bytes(fmt.as_bytes()).unwrap();
b.iter(|| encoder.encode_response(&test_data, &mut res))
});
}
}
group.finish();
}
fn decoding_benchmarks(c: &mut Criterion) {
let mut group = c.benchmark_group("Decoding");
let test_cnt_pairs = [
(100, 5),
(100, 10),
(100, 15),
(1000, 5),
(1000, 10),
(1000, 15),
(10000, 5),
(10000, 10),
(10000, 15),
];
for (row_cnt, col_cnt) in test_cnt_pairs.into_iter() {
for fmt in ["json", "msgpack"] {
group.bench_function(&format!("{fmt} decode for {row_cnt} * {col_cnt}"), |b| {
let mut res = vec![];
let test_data =
PluginResponse::Value(Box::new(encoding_test_data(row_cnt, col_cnt)));
let encoder = EncodingType::try_from_bytes(fmt.as_bytes()).unwrap();
encoder.encode_response(&test_data, &mut res).unwrap();
let mut binary_data = std::io::Cursor::new(res);
b.iter(|| {
binary_data.set_position(0);
encoder.decode_response(&mut binary_data)
})
});
}
}
group.finish();
}
criterion_group!(
benches,
parser_benchmarks,
eval_benchmarks,
encoding_benchmarks,
decoding_benchmarks
);
criterion_main!(benches);

View File

@ -5,7 +5,7 @@
@echo. @echo.
echo Building nushell.exe echo Building nushell.exe
cargo build --features=dataframe cargo build cargo build --features=dataframe
@echo. @echo.
@cd crates\nu_plugin_example @cd crates\nu_plugin_example

View File

@ -12,7 +12,6 @@ let plugins = [
nu_plugin_query, nu_plugin_query,
nu_plugin_example, nu_plugin_example,
nu_plugin_custom_values, nu_plugin_custom_values,
nu_plugin_formats,
] ]
for plugin in $plugins { for plugin in $plugins {

View File

@ -1,17 +0,0 @@
coverage:
status:
project:
default:
target: 55%
threshold: 2%
patch:
default:
informational: true
comment:
layout: reach, diff, files
behavior: default
require_base: yes
require_head: yes
after_n_builds: 2

View File

@ -1,54 +0,0 @@
#!/usr/bin/env nu
let start = (date now)
# Script to generate coverage locally
#
# Output: `lcov.info` file
#
# Relies on `cargo-llvm-cov`. Install via `cargo install cargo-llvm-cov`
# https://github.com/taiki-e/cargo-llvm-cov
# You probably have to run `cargo llvm-cov clean` once manually,
# as you have to confirm to install additional tooling for your rustup toolchain.
# Else the script might stall waiting for your `y<ENTER>`
# Some of the internal tests rely on the exact cargo profile
# (This is somewhat criminal itself)
# but we have to signal to the tests that we use the `ci` `--profile`
let-env NUSHELL_CARGO_TARGET = "ci"
# Manual gathering of coverage to catch invocation of the `nu` binary.
# This is relevant for tests using the `nu!` macro from `nu-test-support`
# see: https://github.com/taiki-e/cargo-llvm-cov#get-coverage-of-external-tests
print "Setting up environment variables for coverage"
# Enable LLVM coverage tracking through environment variables
# show env outputs .ini/.toml style description of the variables
# In order to use from toml, we need to make sure our string literals are single quoted
# This is especially important when running on Windows since "C:\blah" is treated as an escape
cargo llvm-cov show-env | str replace (char dq) (char sq) -a | from toml | load-env
print "Cleaning up coverage data"
cargo llvm-cov clean --workspace
print "Building with workspace and profile=ci"
# Apparently we need to explicitly build the necessary parts
# using the `--profile=ci` is basically `debug` build with unnecessary symbols stripped
# leads to smaller binaries and potential savings when compiling and running
cargo build --workspace --profile=ci
print "Running tests with --workspace and profile=ci"
cargo test --workspace --profile=ci
# You need to provide the used profile to find the raw data
print "Generating coverage report as lcov.info"
cargo llvm-cov report --lcov --output-path lcov.info --profile=ci
let end = (date now)
$"Coverage generation took ($end - $start)."
# To display the coverage in your editor see:
#
# - https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters
# - https://github.com/umaumax/vim-lcov
# - https://github.com/andythigpen/nvim-coverage (probably needs some additional config)

View File

@ -1,38 +0,0 @@
#!/usr/bin/env bash
# Script to generate coverage locally
#
# Output: `lcov.info` file
#
# Relies on `cargo-llvm-cov`. Install via `cargo install cargo-llvm-cov`
# https://github.com/taiki-e/cargo-llvm-cov
# You probably have to run `cargo llvm-cov clean` once manually,
# as you have to confirm to install additional tooling for your rustup toolchain.
# Else the script might stall waiting for your `y<ENTER>`
# Some of the internal tests rely on the exact cargo profile
# (This is somewhat criminal itself)
# but we have to signal to the tests that we use the `ci` `--profile`
export NUSHELL_CARGO_TARGET=ci
# Manual gathering of coverage to catch invocation of the `nu` binary.
# This is relevant for tests using the `nu!` macro from `nu-test-support`
# see: https://github.com/taiki-e/cargo-llvm-cov#get-coverage-of-external-tests
# Enable LLVM coverage tracking through environment variables
source <(cargo llvm-cov show-env --export-prefix)
cargo llvm-cov clean --workspace
# Apparently we need to explicitly build the necessary parts
# using the `--profile=ci` is basically `debug` build with unnecessary symbols stripped
# leads to smaller binaries and potential savings when compiling and running
cargo build --workspace --profile=ci
cargo test --workspace --profile=ci
# You need to provide the used profile to find the raw data
cargo llvm-cov report --lcov --output-path lcov.info --profile=ci
# To display the coverage in your editor see:
#
# - https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters
# - https://github.com/umaumax/vim-lcov
# - https://github.com/andythigpen/nvim-coverage (probably needs some additional config)

View File

@ -5,38 +5,34 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cli" name = "nu-cli"
version = "0.76.0" version = "0.72.1"
[lib]
bench = false
[dev-dependencies] [dev-dependencies]
nu-test-support = { path = "../nu-test-support", version = "0.76.0" } nu-test-support = { path="../nu-test-support", version = "0.72.1" }
nu-command = { path = "../nu-command", version = "0.76.0" } nu-command = { path = "../nu-command", version = "0.72.1" }
rstest = { version = "0.16.0", default-features = false } rstest = {version = "0.15.0", default-features = false}
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.76.0" } nu-engine = { path = "../nu-engine", version = "0.72.1" }
nu-path = { path = "../nu-path", version = "0.76.0" } nu-path = { path = "../nu-path", version = "0.72.1" }
nu-parser = { path = "../nu-parser", version = "0.76.0" } nu-parser = { path = "../nu-parser", version = "0.72.1" }
nu-protocol = { path = "../nu-protocol", version = "0.76.0" } nu-protocol = { path = "../nu-protocol", version = "0.72.1" }
nu-utils = { path = "../nu-utils", version = "0.76.0" } nu-utils = { path = "../nu-utils", version = "0.72.1" }
nu-ansi-term = "0.46.0" nu-ansi-term = "0.46.0"
nu-color-config = { path = "../nu-color-config", version = "0.76.0" } nu-color-config = { path = "../nu-color-config", version = "0.72.1" }
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
reedline = { version = "0.16.0", features = ["bashisms", "sqlite"] }
atty = "0.2.14" atty = "0.2.14"
chrono = { default-features = false, features = ["std"], version = "0.4.23" } chrono = { default-features = false, features = ["std"], version = "0.4.23" }
crossterm = "0.24.0" crossterm = "0.24.0"
fancy-regex = "0.11.0" fancy-regex = "0.10.0"
fuzzy-matcher = "0.3.7" fuzzy-matcher = "0.3.7"
is_executable = "1.0.1" is_executable = "1.0.1"
once_cell = "1.17.0" lazy_static = "1.4.0"
log = "0.4" log = "0.4"
miette = { version = "5.5.0", features = ["fancy-no-backtrace"] } miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
percent-encoding = "2" percent-encoding = "2"
sysinfo = "0.28.0" sysinfo = "0.26.2"
thiserror = "1.0.31" thiserror = "1.0.31"
[features] [features]

View File

@ -37,8 +37,7 @@ impl CommandCompletion {
) -> Vec<String> { ) -> Vec<String> {
let mut executables = vec![]; let mut executables = vec![];
// os agnostic way to get the PATH env var let paths = self.engine_state.get_env_var("PATH");
let paths = self.engine_state.get_path_env_var();
if let Some(paths) = paths { if let Some(paths) = paths {
if let Ok(paths) = paths.as_list() { if let Ok(paths) = paths.as_list() {
@ -95,7 +94,10 @@ impl CommandCompletion {
value: String::from_utf8_lossy(&x.0).to_string(), value: String::from_utf8_lossy(&x.0).to_string(),
description: x.1, description: x.1,
extra: None, extra: None,
span: reedline::Span::new(span.start - offset, span.end - offset), span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
append_whitespace: true, append_whitespace: true,
}); });
@ -106,7 +108,10 @@ impl CommandCompletion {
value: String::from_utf8_lossy(&x).to_string(), value: String::from_utf8_lossy(&x).to_string(),
description: None, description: None,
extra: None, extra: None,
span: reedline::Span::new(span.start - offset, span.end - offset), span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
append_whitespace: true, append_whitespace: true,
}); });
@ -123,15 +128,15 @@ impl CommandCompletion {
value: x, value: x,
description: None, description: None,
extra: None, extra: None,
span: reedline::Span::new(span.start - offset, span.end - offset), span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
append_whitespace: true, append_whitespace: true,
}); });
let results_strings: Vec<String> =
results.clone().into_iter().map(|x| x.value).collect();
for external in results_external { for external in results_external {
if results_strings.contains(&external.value) { if results.contains(&external) {
results.push(Suggestion { results.push(Suggestion {
value: format!("^{}", external.value), value: format!("^{}", external.value),
description: None, description: None,
@ -182,7 +187,10 @@ impl Completer for CommandCompletion {
let subcommands = if let Some(last) = last { let subcommands = if let Some(last) = last {
self.complete_commands( self.complete_commands(
working_set, working_set,
Span::new(last.0.start, pos), Span {
start: last.0.start,
end: pos,
},
offset, offset,
false, false,
options.match_algorithm, options.match_algorithm,

View File

@ -77,7 +77,10 @@ impl NuCompleter {
Value::List { Value::List {
vals: spans vals: spans
.iter() .iter()
.map(|it| Value::string(it, Span::unknown())) .map(|it| Value::String {
val: it.to_string(),
span: Span::unknown(),
})
.collect(), .collect(),
span: Span::unknown(), span: Span::unknown(),
}, },
@ -89,7 +92,7 @@ impl NuCompleter {
&self.engine_state, &self.engine_state,
&mut callee_stack, &mut callee_stack,
block, block,
PipelineData::empty(), PipelineData::new(span),
true, true,
true, true,
); );
@ -98,13 +101,19 @@ impl NuCompleter {
Ok(pd) => { Ok(pd) => {
let value = pd.into_value(span); let value = pd.into_value(span);
if let Value::List { vals, span: _ } = value { if let Value::List { vals, span: _ } = value {
let result = let result = map_value_completions(
map_value_completions(vals.iter(), Span::new(span.start, span.end), offset); vals.iter(),
Span {
start: span.start,
end: span.end,
},
offset,
);
return Some(result); return Some(result);
} }
} }
Err(err) => println!("failed to eval completer block: {err}"), Err(err) => println!("failed to eval completer block: {}", err),
} }
None None
@ -128,8 +137,7 @@ impl NuCompleter {
PipelineElement::Expression(_, expr) PipelineElement::Expression(_, expr)
| PipelineElement::Redirection(_, _, expr) | PipelineElement::Redirection(_, _, expr)
| PipelineElement::And(_, expr) | PipelineElement::And(_, expr)
| PipelineElement::Or(_, expr) | PipelineElement::Or(_, expr) => {
| PipelineElement::SeparateRedirection { out: (_, expr), .. } => {
let flattened: Vec<_> = flatten_expression(&working_set, &expr); let flattened: Vec<_> = flatten_expression(&working_set, &expr);
let span_offset: usize = alias_offset.iter().sum(); let span_offset: usize = alias_offset.iter().sum();
let mut spans: Vec<String> = vec![]; let mut spans: Vec<String> = vec![];
@ -157,12 +165,15 @@ impl NuCompleter {
// Create a new span // Create a new span
let new_span = if flat_idx == 0 { let new_span = if flat_idx == 0 {
Span::new(flat.0.start, flat.0.end - 1 - span_offset) Span {
start: flat.0.start,
end: flat.0.end - 1 - span_offset,
}
} else { } else {
Span::new( Span {
flat.0.start - span_offset, start: flat.0.start - span_offset,
flat.0.end - 1 - span_offset, end: flat.0.end - 1 - span_offset,
) }
}; };
// Parses the prefix. Completion should look up to the cursor position, not after. // Parses the prefix. Completion should look up to the cursor position, not after.

View File

@ -52,13 +52,13 @@ impl Completer for CustomCompletion {
head: span, head: span,
arguments: vec![ arguments: vec![
Argument::Positional(Expression { Argument::Positional(Expression {
span: Span::unknown(), span: Span { start: 0, end: 0 },
ty: Type::String, ty: Type::String,
expr: Expr::String(self.line.clone()), expr: Expr::String(self.line.clone()),
custom_completion: None, custom_completion: None,
}), }),
Argument::Positional(Expression { Argument::Positional(Expression {
span: Span::unknown(), span: Span { start: 0, end: 0 },
ty: Type::Int, ty: Type::Int,
expr: Expr::Int(line_pos as i64), expr: Expr::Int(line_pos as i64),
custom_completion: None, custom_completion: None,
@ -66,16 +66,15 @@ impl Completer for CustomCompletion {
], ],
redirect_stdout: true, redirect_stdout: true,
redirect_stderr: true, redirect_stderr: true,
parser_info: vec![],
}, },
PipelineData::empty(), PipelineData::new(span),
); );
let mut custom_completion_options = None; let mut custom_completion_options = None;
// Parse result // Parse result
let suggestions = result let suggestions = match result {
.map(|pd| { Ok(pd) => {
let value = pd.into_value(span); let value = pd.into_value(span);
match &value { match &value {
Value::Record { .. } => { Value::Record { .. } => {
@ -132,8 +131,9 @@ impl Completer for CustomCompletion {
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset), Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
_ => vec![], _ => vec![],
} }
}) }
.unwrap_or_default(); _ => vec![],
};
if let Some(custom_completion_options) = custom_completion_options { if let Some(custom_completion_options) = custom_completion_options {
filter(&prefix, suggestions, &custom_completion_options) filter(&prefix, suggestions, &custom_completion_options)

View File

@ -33,7 +33,14 @@ impl Completer for DirectoryCompletion {
_: usize, _: usize,
options: &CompletionOptions, options: &CompletionOptions,
) -> Vec<Suggestion> { ) -> Vec<Suggestion> {
let cwd = self.engine_state.current_work_dir(); let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") {
match d.as_string() {
Ok(s) => s,
Err(_) => "".to_string(),
}
} else {
"".to_string()
};
let partial = String::from_utf8_lossy(&prefix).to_string(); let partial = String::from_utf8_lossy(&prefix).to_string();
// Filter only the folders // Filter only the folders
@ -119,7 +126,7 @@ pub fn directory_completion(
let mut file_name = entry.file_name().to_string_lossy().into_owned(); let mut file_name = entry.file_name().to_string_lossy().into_owned();
if matches(&partial, &file_name, options) { if matches(&partial, &file_name, options) {
let mut path = if prepend_base_dir(original_input, &base_dir_name) { let mut path = if prepend_base_dir(original_input, &base_dir_name) {
format!("{base_dir_name}{file_name}") format!("{}{}", base_dir_name, file_name)
} else { } else {
file_name.to_string() file_name.to_string()
}; };
@ -129,13 +136,9 @@ pub fn directory_completion(
file_name.push(SEP); file_name.push(SEP);
} }
// Fix files or folders with quotes or hash // Fix files or folders with quotes
if path.contains('\'') if path.contains('\'') || path.contains('"') || path.contains(' ') {
|| path.contains('"') path = format!("`{}`", path);
|| path.contains(' ')
|| path.contains('#')
{
path = format!("`{path}`");
} }
Some((span, path)) Some((span, path))

View File

@ -58,7 +58,7 @@ impl Completer for DotNuCompletion {
}; };
// Check if the base_dir is a folder // Check if the base_dir is a folder
if base_dir != format!(".{SEP}") { if base_dir != format!(".{}", SEP) {
// Add the base dir into the directories to be searched // Add the base dir into the directories to be searched
search_dirs.push(base_dir.clone()); search_dirs.push(base_dir.clone());
@ -70,7 +70,14 @@ impl Completer for DotNuCompletion {
partial = base_dir_partial; partial = base_dir_partial;
} else { } else {
// Fetch the current folder // Fetch the current folder
let current_folder = self.engine_state.current_work_dir(); let current_folder = if let Some(d) = self.engine_state.get_env_var("PWD") {
match d.as_string() {
Ok(s) => s,
Err(_) => "".to_string(),
}
} else {
"".to_string()
};
is_current_folder = true; is_current_folder = true;
// Add the current folder and the lib dirs into the // Add the current folder and the lib dirs into the

View File

@ -30,7 +30,14 @@ impl Completer for FileCompletion {
_: usize, _: usize,
options: &CompletionOptions, options: &CompletionOptions,
) -> Vec<Suggestion> { ) -> Vec<Suggestion> {
let cwd = self.engine_state.current_work_dir(); let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") {
match d.as_string() {
Ok(s) => s,
Err(_) => "".to_string(),
}
} else {
"".to_string()
};
let prefix = String::from_utf8_lossy(&prefix).to_string(); let prefix = String::from_utf8_lossy(&prefix).to_string();
let output: Vec<_> = file_path_completion(span, &prefix, &cwd, options) let output: Vec<_> = file_path_completion(span, &prefix, &cwd, options)
.into_iter() .into_iter()
@ -124,7 +131,7 @@ pub fn file_path_completion(
let mut file_name = entry.file_name().to_string_lossy().into_owned(); let mut file_name = entry.file_name().to_string_lossy().into_owned();
if matches(&partial, &file_name, options) { if matches(&partial, &file_name, options) {
let mut path = if prepend_base_dir(original_input, &base_dir_name) { let mut path = if prepend_base_dir(original_input, &base_dir_name) {
format!("{base_dir_name}{file_name}") format!("{}{}", base_dir_name, file_name)
} else { } else {
file_name.to_string() file_name.to_string()
}; };
@ -134,25 +141,9 @@ pub fn file_path_completion(
file_name.push(SEP); file_name.push(SEP);
} }
// Fix files or folders with quotes or hashes // Fix files or folders with quotes
if path.contains('\'') if path.contains('\'') || path.contains('"') || path.contains(' ') {
|| path.contains('"') path = format!("`{}`", path);
|| path.contains(' ')
|| path.contains('#')
|| path.contains('(')
|| path.contains(')')
|| path.starts_with('0')
|| path.starts_with('1')
|| path.starts_with('2')
|| path.starts_with('3')
|| path.starts_with('4')
|| path.starts_with('5')
|| path.starts_with('6')
|| path.starts_with('7')
|| path.starts_with('8')
|| path.starts_with('9')
{
path = format!("`{path}`");
} }
Some((span, path)) Some((span, path))
@ -180,7 +171,7 @@ pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
/// Returns whether the base_dir should be prepended to the file path /// Returns whether the base_dir should be prepended to the file path
pub fn prepend_base_dir(input: &str, base_dir: &str) -> bool { pub fn prepend_base_dir(input: &str, base_dir: &str) -> bool {
if base_dir == format!(".{SEP}") { if base_dir == format!(".{}", SEP) {
// if the current base_dir path is the local folder we only add a "./" prefix if the user // if the current base_dir path is the local folder we only add a "./" prefix if the user
// input already includes a local folder prefix. // input already includes a local folder prefix.
let manually_entered = { let manually_entered = {

View File

@ -9,8 +9,6 @@ use reedline::Suggestion;
use std::str; use std::str;
use std::sync::Arc; use std::sync::Arc;
use super::MatchAlgorithm;
#[derive(Clone)] #[derive(Clone)]
pub struct VariableCompletion { pub struct VariableCompletion {
engine_state: Arc<EngineState>, // TODO: Is engine state necessary? It's already a part of working set in fetch() engine_state: Arc<EngineState>, // TODO: Is engine state necessary? It's already a part of working set in fetch()
@ -75,11 +73,10 @@ impl Completer for VariableCompletion {
for suggestion in for suggestion in
nested_suggestions(val.clone(), nested_levels, current_span) nested_suggestions(val.clone(), nested_levels, current_span)
{ {
if options.match_algorithm.matches_u8_insensitive( if options
options.case_sensitive, .match_algorithm
suggestion.value.as_bytes(), .matches_u8(suggestion.value.as_bytes(), &prefix)
&prefix, {
) {
output.push(suggestion); output.push(suggestion);
} }
} }
@ -89,11 +86,10 @@ impl Completer for VariableCompletion {
} else { } else {
// No nesting provided, return all env vars // No nesting provided, return all env vars
for env_var in env_vars { for env_var in env_vars {
if options.match_algorithm.matches_u8_insensitive( if options
options.case_sensitive, .match_algorithm
env_var.0.as_bytes(), .matches_u8(env_var.0.as_bytes(), &prefix)
&prefix, {
) {
output.push(Suggestion { output.push(Suggestion {
value: env_var.0, value: env_var.0,
description: None, description: None,
@ -115,16 +111,18 @@ impl Completer for VariableCompletion {
&self.engine_state, &self.engine_state,
&self.stack, &self.stack,
nu_protocol::NU_VARIABLE_ID, nu_protocol::NU_VARIABLE_ID,
nu_protocol::Span::new(current_span.start, current_span.end), nu_protocol::Span {
start: current_span.start,
end: current_span.end,
},
) { ) {
for suggestion in for suggestion in
nested_suggestions(nuval, self.var_context.1.clone(), current_span) nested_suggestions(nuval, self.var_context.1.clone(), current_span)
{ {
if options.match_algorithm.matches_u8_insensitive( if options
options.case_sensitive, .match_algorithm
suggestion.value.as_bytes(), .matches_u8(suggestion.value.as_bytes(), &prefix)
&prefix, {
) {
output.push(suggestion); output.push(suggestion);
} }
} }
@ -136,18 +134,23 @@ impl Completer for VariableCompletion {
// Completion other variable types // Completion other variable types
if let Some(var_id) = var_id { if let Some(var_id) = var_id {
// Extract the variable value from the stack // Extract the variable value from the stack
let var = self.stack.get_var(var_id, Span::new(span.start, span.end)); let var = self.stack.get_var(
var_id,
Span {
start: span.start,
end: span.end,
},
);
// If the value exists and it's of type Record // If the value exists and it's of type Record
if let Ok(value) = var { if let Ok(value) = var {
for suggestion in for suggestion in
nested_suggestions(value, self.var_context.1.clone(), current_span) nested_suggestions(value, self.var_context.1.clone(), current_span)
{ {
if options.match_algorithm.matches_u8_insensitive( if options
options.case_sensitive, .match_algorithm
suggestion.value.as_bytes(), .matches_u8(suggestion.value.as_bytes(), &prefix)
&prefix, {
) {
output.push(suggestion); output.push(suggestion);
} }
} }
@ -159,11 +162,10 @@ impl Completer for VariableCompletion {
// Variable completion (e.g: $en<tab> to complete $env) // Variable completion (e.g: $en<tab> to complete $env)
for builtin in builtins { for builtin in builtins {
if options.match_algorithm.matches_u8_insensitive( if options
options.case_sensitive, .match_algorithm
builtin.as_bytes(), .matches_u8(builtin.as_bytes(), &prefix)
&prefix, {
) {
output.push(Suggestion { output.push(Suggestion {
value: builtin.to_string(), value: builtin.to_string(),
description: None, description: None,
@ -185,11 +187,7 @@ impl Completer for VariableCompletion {
.rev() .rev()
{ {
for v in &overlay_frame.vars { for v in &overlay_frame.vars {
if options.match_algorithm.matches_u8_insensitive( if options.match_algorithm.matches_u8(v.0, &prefix) {
options.case_sensitive,
v.0,
&prefix,
) {
output.push(Suggestion { output.push(Suggestion {
value: String::from_utf8_lossy(v.0).to_string(), value: String::from_utf8_lossy(v.0).to_string(),
description: None, description: None,
@ -211,11 +209,7 @@ impl Completer for VariableCompletion {
.rev() .rev()
{ {
for v in &overlay_frame.vars { for v in &overlay_frame.vars {
if options.match_algorithm.matches_u8_insensitive( if options.match_algorithm.matches_u8(v.0, &prefix) {
options.case_sensitive,
v.0,
&prefix,
) {
output.push(Suggestion { output.push(Suggestion {
value: String::from_utf8_lossy(v.0).to_string(), value: String::from_utf8_lossy(v.0).to_string(),
description: None, description: None,
@ -262,20 +256,6 @@ fn nested_suggestions(
output output
} }
Value::LazyRecord { val, .. } => {
// Add all the columns as completion
for column_name in val.column_names() {
output.push(Suggestion {
value: column_name.to_string(),
description: None,
extra: None,
span: current_span,
append_whitespace: false,
});
}
output
}
_ => output, _ => output,
} }
@ -301,7 +281,7 @@ fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
// Current sublevel value not found // Current sublevel value not found
return Value::Nothing { return Value::Nothing {
span: Span::unknown(), span: Span { start: 0, end: 0 },
}; };
} }
_ => return val, _ => return val,
@ -310,13 +290,3 @@ fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
val val
} }
impl MatchAlgorithm {
pub fn matches_u8_insensitive(&self, sensitive: bool, haystack: &[u8], needle: &[u8]) -> bool {
if sensitive {
self.matches_u8(haystack, needle)
} else {
self.matches_u8(&haystack.to_ascii_lowercase(), &needle.to_ascii_lowercase())
}
}
}

View File

@ -1,14 +1,14 @@
use crate::util::{eval_source, report_error}; use crate::util::{eval_source, report_error};
#[cfg(feature = "plugin")] #[cfg(feature = "plugin")]
use log::info;
#[cfg(feature = "plugin")]
use nu_parser::ParseError; use nu_parser::ParseError;
#[cfg(feature = "plugin")] #[cfg(feature = "plugin")]
use nu_path::canonicalize_with; use nu_path::canonicalize_with;
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet}; use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
#[cfg(feature = "plugin")] #[cfg(feature = "plugin")]
use nu_protocol::Spanned; use nu_protocol::Spanned;
use nu_protocol::{HistoryFileFormat, PipelineData}; use nu_protocol::{HistoryFileFormat, PipelineData, Span};
#[cfg(feature = "plugin")]
use nu_utils::utils::perf;
use std::path::PathBuf; use std::path::PathBuf;
#[cfg(feature = "plugin")] #[cfg(feature = "plugin")]
@ -24,8 +24,6 @@ pub fn read_plugin_file(
plugin_file: Option<Spanned<String>>, plugin_file: Option<Spanned<String>>,
storage_path: &str, storage_path: &str,
) { ) {
let start_time = std::time::Instant::now();
let mut plug_path = String::new();
// Reading signatures from signature file // Reading signatures from signature file
// The plugin.nu file stores the parsed signature collected from each registered plugin // The plugin.nu file stores the parsed signature collected from each registered plugin
add_plugin_file(engine_state, plugin_file, storage_path); add_plugin_file(engine_state, plugin_file, storage_path);
@ -33,27 +31,19 @@ pub fn read_plugin_file(
let plugin_path = engine_state.plugin_signatures.clone(); let plugin_path = engine_state.plugin_signatures.clone();
if let Some(plugin_path) = plugin_path { if let Some(plugin_path) = plugin_path {
let plugin_filename = plugin_path.to_string_lossy(); let plugin_filename = plugin_path.to_string_lossy();
plug_path = plugin_filename.to_string();
if let Ok(contents) = std::fs::read(&plugin_path) { if let Ok(contents) = std::fs::read(&plugin_path) {
eval_source( eval_source(
engine_state, engine_state,
stack, stack,
&contents, &contents,
&plugin_filename, &plugin_filename,
PipelineData::empty(), PipelineData::new(Span::new(0, 0)),
false,
); );
} }
} }
perf( info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
&format!("read_plugin_file {}", &plug_path),
start_time,
file!(),
line!(),
column!(),
engine_state.get_config().use_ansi_coloring,
);
} }
#[cfg(feature = "plugin")] #[cfg(feature = "plugin")]
@ -66,12 +56,13 @@ pub fn add_plugin_file(
let working_set = StateWorkingSet::new(engine_state); let working_set = StateWorkingSet::new(engine_state);
let cwd = working_set.get_cwd(); let cwd = working_set.get_cwd();
if let Ok(path) = canonicalize_with(&plugin_file.item, cwd) { match canonicalize_with(&plugin_file.item, cwd) {
engine_state.plugin_signatures = Some(path) Ok(path) => engine_state.plugin_signatures = Some(path),
} else { Err(_) => {
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span); let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
report_error(&working_set, &e); report_error(&working_set, &e);
} }
}
} else if let Some(mut plugin_path) = nu_path::config_dir() { } else if let Some(mut plugin_path) = nu_path::config_dir() {
// Path to store plugins signatures // Path to store plugins signatures
plugin_path.push(storage_path); plugin_path.push(storage_path);
@ -94,8 +85,7 @@ pub fn eval_config_contents(
stack, stack,
&contents, &contents,
&config_filename, &config_filename,
PipelineData::empty(), PipelineData::new(Span::new(0, 0)),
false,
); );
// Merge the environment in case env vars changed in the config // Merge the environment in case env vars changed in the config

View File

@ -2,13 +2,13 @@ use crate::util::{eval_source, report_error};
use log::info; use log::info;
use log::trace; use log::trace;
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use nu_engine::{convert_env_values, current_dir}; use nu_engine::convert_env_values;
use nu_parser::parse; use nu_parser::parse;
use nu_path::canonicalize_with; use nu_protocol::Type;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
Config, PipelineData, ShellError, Span, Type, Value, Config, PipelineData, Span, Value,
}; };
use nu_utils::stdout_write_all_and_flush; use nu_utils::stdout_write_all_and_flush;
@ -27,75 +27,14 @@ pub fn evaluate_file(
std::process::exit(1); std::process::exit(1);
} }
let cwd = current_dir(engine_state, stack)?; let file = std::fs::read(&path).into_diagnostic()?;
let file_path = canonicalize_with(&path, cwd).unwrap_or_else(|e| { engine_state.start_in_file(Some(&path));
let working_set = StateWorkingSet::new(engine_state);
report_error(
&working_set,
&ShellError::FileNotFoundCustom(
format!("Could not access file '{}': {:?}", path, e.to_string()),
Span::unknown(),
),
);
std::process::exit(1);
});
let file_path_str = file_path.to_str().unwrap_or_else(|| {
let working_set = StateWorkingSet::new(engine_state);
report_error(
&working_set,
&ShellError::NonUtf8Custom(
format!(
"Input file name '{}' is not valid UTF8",
file_path.to_string_lossy()
),
Span::unknown(),
),
);
std::process::exit(1);
});
let file = std::fs::read(&file_path)
.into_diagnostic()
.unwrap_or_else(|e| {
let working_set = StateWorkingSet::new(engine_state);
report_error(
&working_set,
&ShellError::FileNotFoundCustom(
format!(
"Could not read file '{}': {:?}",
file_path_str,
e.to_string()
),
Span::unknown(),
),
);
std::process::exit(1);
});
engine_state.start_in_file(Some(file_path_str));
let parent = file_path.parent().unwrap_or_else(|| {
let working_set = StateWorkingSet::new(engine_state);
report_error(
&working_set,
&ShellError::FileNotFoundCustom(
format!("The file path '{file_path_str}' does not have a parent"),
Span::unknown(),
),
);
std::process::exit(1);
});
stack.add_env_var(
"FILE_PWD".to_string(),
Value::string(parent.to_string_lossy(), Span::unknown()),
);
let mut working_set = StateWorkingSet::new(engine_state); let mut working_set = StateWorkingSet::new(engine_state);
trace!("parsing file: {}", file_path_str); trace!("parsing file: {}", path);
let _ = parse(&mut working_set, Some(file_path_str), &file, false, &[]);
let _ = parse(&mut working_set, Some(&path), &file, false, &[]);
if working_set.find_decl(b"main", &Type::Any).is_some() { if working_set.find_decl(b"main", &Type::Any).is_some() {
let args = format!("main {}", args.join(" ")); let args = format!("main {}", args.join(" "));
@ -104,23 +43,15 @@ pub fn evaluate_file(
engine_state, engine_state,
stack, stack,
&file, &file,
file_path_str, &path,
PipelineData::empty(), PipelineData::new(Span::new(0, 0)),
true,
) { ) {
std::process::exit(1); std::process::exit(1);
} }
if !eval_source( if !eval_source(engine_state, stack, args.as_bytes(), "<commandline>", input) {
engine_state,
stack,
args.as_bytes(),
"<commandline>",
input,
true,
) {
std::process::exit(1); std::process::exit(1);
} }
} else if !eval_source(engine_state, stack, &file, file_path_str, input, true) { } else if !eval_source(engine_state, stack, &file, &path, input) {
std::process::exit(1); std::process::exit(1);
} }
@ -129,7 +60,7 @@ pub fn evaluate_file(
Ok(()) Ok(())
} }
pub(crate) fn print_table_or_error( pub fn print_table_or_error(
engine_state: &mut EngineState, engine_state: &mut EngineState,
stack: &mut Stack, stack: &mut Stack,
mut pipeline_data: PipelineData, mut pipeline_data: PipelineData,
@ -145,11 +76,14 @@ pub(crate) fn print_table_or_error(
if let PipelineData::Value(Value::Error { error }, ..) = &pipeline_data { if let PipelineData::Value(Value::Error { error }, ..) = &pipeline_data {
let working_set = StateWorkingSet::new(engine_state); let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, error); report_error(&working_set, error);
std::process::exit(1); std::process::exit(1);
} }
if let Some(decl_id) = engine_state.find_decl("table".as_bytes(), &[]) { match engine_state.find_decl("table".as_bytes(), &[]) {
Some(decl_id) => {
let command = engine_state.get_decl(decl_id); let command = engine_state.get_decl(decl_id);
if command.get_block_id().is_some() { if command.get_block_id().is_some() {
print_or_exit(pipeline_data, engine_state, config); print_or_exit(pipeline_data, engine_state, config);
@ -167,14 +101,18 @@ pub(crate) fn print_table_or_error(
} }
Err(error) => { Err(error) => {
let working_set = StateWorkingSet::new(engine_state); let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &error); report_error(&working_set, &error);
std::process::exit(1); std::process::exit(1);
} }
} }
} }
} else { }
None => {
print_or_exit(pipeline_data, engine_state, config); print_or_exit(pipeline_data, engine_state, config);
} }
};
// Make sure everything has finished // Make sure everything has finished
if let Some(exit_code) = exit_code { if let Some(exit_code) = exit_code {
@ -200,7 +138,9 @@ fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, co
std::process::exit(1); std::process::exit(1);
} }
let out = item.into_string("\n", config) + "\n"; let mut out = item.into_string("\n", config);
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{err}")); out.push('\n');
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
} }
} }

View File

@ -411,10 +411,10 @@ impl DescriptionMenu {
RESET RESET
) )
} else { } else {
format!(" {example}\r\n") format!(" {}\r\n", example)
} }
} else { } else {
format!(" {example}\r\n") format!(" {}\r\n", example)
} }
}) })
.collect(); .collect();
@ -429,7 +429,7 @@ impl DescriptionMenu {
examples, examples,
) )
} else { } else {
format!("\r\n\r\nExamples:\r\n{examples}",) format!("\r\n\r\nExamples:\r\n{}", examples,)
} }
} }
} }

View File

@ -42,14 +42,20 @@ impl Completer for NuMenuCompleter {
if let Some(buffer) = block.signature.get_positional(0) { if let Some(buffer) = block.signature.get_positional(0) {
if let Some(buffer_id) = &buffer.var_id { if let Some(buffer_id) = &buffer.var_id {
let line_buffer = Value::string(parsed.remainder, self.span); let line_buffer = Value::String {
val: parsed.remainder.to_string(),
span: self.span,
};
self.stack.add_var(*buffer_id, line_buffer); self.stack.add_var(*buffer_id, line_buffer);
} }
} }
if let Some(position) = block.signature.get_positional(1) { if let Some(position) = block.signature.get_positional(1) {
if let Some(position_id) = &position.var_id { if let Some(position_id) = &position.var_id {
let line_buffer = Value::int(pos as i64, self.span); let line_buffer = Value::Int {
val: pos as i64,
span: self.span,
};
self.stack.add_var(*position_id, line_buffer); self.stack.add_var(*position_id, line_buffer);
} }
} }
@ -81,10 +87,13 @@ fn convert_to_suggestions(
) -> Vec<Suggestion> { ) -> Vec<Suggestion> {
match value { match value {
Value::Record { .. } => { Value::Record { .. } => {
let text = value let text = match value
.get_data_by_key("value") .get_data_by_key("value")
.and_then(|val| val.as_string().ok()) .and_then(|val| val.as_string().ok())
.unwrap_or_else(|| "No value key".to_string()); {
Some(val) => val,
None => "No value key".to_string(),
};
let description = value let description = value
.get_data_by_key("description") .get_data_by_key("description")
@ -154,7 +163,7 @@ fn convert_to_suggestions(
.flat_map(|val| convert_to_suggestions(val, line, pos, only_buffer_difference)) .flat_map(|val| convert_to_suggestions(val, line, pos, only_buffer_difference))
.collect(), .collect(),
_ => vec![Suggestion { _ => vec![Suggestion {
value: format!("Not a record: {value:?}"), value: format!("Not a record: {:?}", value),
description: None, description: None,
extra: None, extra: None,
span: reedline::Span { span: reedline::Span {

View File

@ -1,6 +1,6 @@
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Type, Value}; use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Value};
use reedline::Highlighter; use reedline::Highlighter;
#[derive(Clone)] #[derive(Clone)]
@ -12,9 +12,7 @@ impl Command for NuHighlight {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("nu-highlight") Signature::build("nu-highlight").category(Category::Strings)
.category(Category::Strings)
.input_output_types(vec![(Type::String, Type::String)])
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -35,7 +33,7 @@ impl Command for NuHighlight {
let head = call.head; let head = call.head;
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
let engine_state = std::sync::Arc::new(engine_state.clone()); let engine_state = engine_state.clone();
let config = engine_state.get_config().clone(); let config = engine_state.get_config().clone();
let highlighter = crate::NuHighlighter { let highlighter = crate::NuHighlighter {

View File

@ -2,8 +2,7 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -16,7 +15,6 @@ impl Command for Print {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("print") Signature::build("print")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.rest("rest", SyntaxShape::Any, "the values to print") .rest("rest", SyntaxShape::Any, "the values to print")
.switch( .switch(
"no-newline", "no-newline",
@ -52,13 +50,14 @@ Since this command has no output, there is no point in piping it with other comm
let args: Vec<Value> = call.rest(engine_state, stack, 0)?; let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
let no_newline = call.has_flag("no-newline"); let no_newline = call.has_flag("no-newline");
let to_stderr = call.has_flag("stderr"); let to_stderr = call.has_flag("stderr");
let head = call.head;
for arg in args { for arg in args {
arg.into_pipeline_data() arg.into_pipeline_data()
.print(engine_state, stack, no_newline, to_stderr)?; .print(engine_state, stack, no_newline, to_stderr)?;
} }
Ok(PipelineData::empty()) Ok(PipelineData::new(head))
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {

View File

@ -91,7 +91,7 @@ impl NushellPrompt {
} }
fn default_wrapped_custom_string(&self, str: String) -> String { fn default_wrapped_custom_string(&self, str: String) -> String {
format!("({str})") format!("({})", str)
} }
} }
@ -105,7 +105,7 @@ impl Prompt for NushellPrompt {
if let Some(prompt_string) = &self.left_prompt_string { if let Some(prompt_string) = &self.left_prompt_string {
prompt_string.replace('\n', "\r\n").into() prompt_string.replace('\n', "\r\n").into()
} else { } else {
let default = DefaultPrompt::default(); let default = DefaultPrompt::new();
default default
.render_prompt_left() .render_prompt_left()
.to_string() .to_string()
@ -118,7 +118,7 @@ impl Prompt for NushellPrompt {
if let Some(prompt_string) = &self.right_prompt_string { if let Some(prompt_string) = &self.right_prompt_string {
prompt_string.replace('\n', "\r\n").into() prompt_string.replace('\n', "\r\n").into()
} else { } else {
let default = DefaultPrompt::default(); let default = DefaultPrompt::new();
default default
.render_prompt_right() .render_prompt_right()
.to_string() .to_string()
@ -130,36 +130,32 @@ impl Prompt for NushellPrompt {
fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow<str> { fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow<str> {
match edit_mode { match edit_mode {
PromptEditMode::Default => match &self.default_prompt_indicator { PromptEditMode::Default => match &self.default_prompt_indicator {
Some(indicator) => indicator, Some(indicator) => indicator.as_str().into(),
None => "", None => "".into(),
} },
.into(),
PromptEditMode::Emacs => match &self.default_prompt_indicator { PromptEditMode::Emacs => match &self.default_prompt_indicator {
Some(indicator) => indicator, Some(indicator) => indicator.as_str().into(),
None => "", None => "".into(),
} },
.into(),
PromptEditMode::Vi(vi_mode) => match vi_mode { PromptEditMode::Vi(vi_mode) => match vi_mode {
PromptViMode::Normal => match &self.default_vi_normal_prompt_indicator { PromptViMode::Normal => match &self.default_vi_normal_prompt_indicator {
Some(indicator) => indicator, Some(indicator) => indicator.as_str().into(),
None => ": ", None => ": ".into(),
}, },
PromptViMode::Insert => match &self.default_vi_insert_prompt_indicator { PromptViMode::Insert => match &self.default_vi_insert_prompt_indicator {
Some(indicator) => indicator, Some(indicator) => indicator.as_str().into(),
None => "", None => "".into(),
},
}, },
}
.into(),
PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(), PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(),
} }
} }
fn render_prompt_multiline_indicator(&self) -> Cow<str> { fn render_prompt_multiline_indicator(&self) -> Cow<str> {
match &self.default_multiline_indicator { match &self.default_multiline_indicator {
Some(indicator) => indicator, Some(indicator) => indicator.as_str().into(),
None => "::: ", None => "::: ".into(),
} }
.into()
} }
fn render_prompt_history_search_indicator( fn render_prompt_history_search_indicator(

View File

@ -1,10 +1,10 @@
use crate::util::report_error; use crate::util::report_error;
use crate::NushellPrompt; use crate::NushellPrompt;
use log::trace; use log::info;
use nu_engine::eval_subexpression; use nu_engine::eval_subexpression;
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
Config, PipelineData, Value, Config, PipelineData, Span, Value,
}; };
use reedline::Prompt; use reedline::Prompt;
@ -37,39 +37,52 @@ fn get_prompt_string(
let block = engine_state.get_block(block_id); let block = engine_state.get_block(block_id);
let mut stack = stack.captures_to_stack(&captures); let mut stack = stack.captures_to_stack(&captures);
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt // Use eval_subexpression to force a redirection of output, so we can use everything in prompt
let ret_val = let ret_val = eval_subexpression(
eval_subexpression(engine_state, &mut stack, block, PipelineData::empty()); engine_state,
trace!( &mut stack,
block,
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
);
info!(
"get_prompt_string (block) {}:{}:{}", "get_prompt_string (block) {}:{}:{}",
file!(), file!(),
line!(), line!(),
column!() column!()
); );
ret_val match ret_val {
.map_err(|err| { Ok(ret_val) => Some(ret_val),
Err(err) => {
let working_set = StateWorkingSet::new(engine_state); let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err); report_error(&working_set, &err);
}) None
.ok() }
}
} }
Value::Block { val: block_id, .. } => { Value::Block { val: block_id, .. } => {
let block = engine_state.get_block(block_id); let block = engine_state.get_block(block_id);
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt // Use eval_subexpression to force a redirection of output, so we can use everything in prompt
let ret_val = eval_subexpression(engine_state, stack, block, PipelineData::empty()); let ret_val = eval_subexpression(
trace!( engine_state,
stack,
block,
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
);
info!(
"get_prompt_string (block) {}:{}:{}", "get_prompt_string (block) {}:{}:{}",
file!(), file!(),
line!(), line!(),
column!() column!()
); );
ret_val match ret_val {
.map_err(|err| { Ok(ret_val) => Some(ret_val),
Err(err) => {
let working_set = StateWorkingSet::new(engine_state); let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err); report_error(&working_set, &err);
}) None
.ok() }
}
} }
Value::String { .. } => Some(PipelineData::Value(v.clone(), None)), Value::String { .. } => Some(PipelineData::Value(v.clone(), None)),
_ => None, _ => None,
@ -77,7 +90,8 @@ fn get_prompt_string(
.and_then(|pipeline_data| { .and_then(|pipeline_data| {
let output = pipeline_data.collect_string("", config).ok(); let output = pipeline_data.collect_string("", config).ok();
output.map(|mut x| { match output {
Some(mut x) => {
// Just remove the very last newline. // Just remove the very last newline.
if x.ends_with('\n') { if x.ends_with('\n') {
x.pop(); x.pop();
@ -86,8 +100,10 @@ fn get_prompt_string(
if x.ends_with('\r') { if x.ends_with('\r') {
x.pop(); x.pop();
} }
x Some(x)
}) }
None => None,
}
}) })
} }
@ -104,12 +120,12 @@ pub(crate) fn update_prompt<'prompt>(
// Now that we have the prompt string lets ansify it. // Now that we have the prompt string lets ansify it.
// <133 A><prompt><133 B><command><133 C><command output> // <133 A><prompt><133 B><command><133 C><command output>
let left_prompt_string = if config.shell_integration { let left_prompt_string = if config.shell_integration {
if let Some(prompt_string) = left_prompt_string { match left_prompt_string {
Some(format!( Some(prompt_string) => Some(format!(
"{PRE_PROMPT_MARKER}{prompt_string}{POST_PROMPT_MARKER}" "{}{}{}",
)) PRE_PROMPT_MARKER, prompt_string, POST_PROMPT_MARKER
} else { )),
left_prompt_string None => left_prompt_string,
} }
} else { } else {
left_prompt_string left_prompt_string
@ -141,7 +157,7 @@ pub(crate) fn update_prompt<'prompt>(
); );
let ret_val = nu_prompt as &dyn Prompt; let ret_val = nu_prompt as &dyn Prompt;
trace!("update_prompt {}:{}:{}", file!(), line!(), column!()); info!("update_prompt {}:{}:{}", file!(), line!(), column!());
ret_val ret_val
} }

View File

@ -1,13 +1,14 @@
use super::DescriptionMenu; use super::DescriptionMenu;
use crate::{menus::NuMenuCompleter, NuHelpCompleter}; use crate::{menus::NuMenuCompleter, NuHelpCompleter};
use crossterm::event::{KeyCode, KeyModifiers}; use crossterm::event::{KeyCode, KeyModifiers};
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style}; use nu_color_config::lookup_ansi_color_style;
use nu_engine::eval_block; use nu_engine::eval_block;
use nu_parser::parse; use nu_parser::parse;
use nu_protocol::{ use nu_protocol::{
create_menus, color_value_string, create_menus,
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
extract_value, Config, ParsedKeybinding, ParsedMenu, PipelineData, ShellError, Span, Value, extract_value, Config, IntoPipelineData, ParsedKeybinding, ParsedMenu, PipelineData,
ShellError, Span, Value,
}; };
use reedline::{ use reedline::{
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
@ -109,7 +110,7 @@ pub(crate) fn add_menus(
}; };
let mut temp_stack = Stack::new(); let mut temp_stack = Stack::new();
let input = PipelineData::Empty; let input = Value::nothing(Span::test_data()).into_pipeline_data();
let res = eval_block(&engine_state, &mut temp_stack, &block, input, false, false)?; let res = eval_block(&engine_state, &mut temp_stack, &block, input, false, false)?;
if let PipelineData::Value(value, None) = res { if let PipelineData::Value(value, None) = res {
@ -158,11 +159,14 @@ macro_rules! add_style {
($name:expr, $cols: expr, $vals:expr, $span:expr, $config: expr, $menu:expr, $f:expr) => { ($name:expr, $cols: expr, $vals:expr, $span:expr, $config: expr, $menu:expr, $f:expr) => {
$menu = match extract_value($name, $cols, $vals, $span) { $menu = match extract_value($name, $cols, $vals, $span) {
Ok(text) => { Ok(text) => {
let style = match text { let text = match text {
Value::String { val, .. } => lookup_ansi_color_style(&val), Value::String { val, .. } => val.clone(),
Value::Record { .. } => color_record_to_nustyle(&text), Value::Record { cols, vals, span } => {
_ => lookup_ansi_color_style("green"), color_value_string(span, cols, vals, $config).into_string("", $config)
}
_ => "green".to_string(),
}; };
let style = lookup_ansi_color_style(&text);
$f($menu, style) $f($menu, style)
} }
Err(_) => $menu, Err(_) => $menu,
@ -651,15 +655,14 @@ fn add_parsed_keybinding(
let pos1 = char_iter.next(); let pos1 = char_iter.next();
let pos2 = char_iter.next(); let pos2 = char_iter.next();
let char = if let (Some(char), None) = (pos1, pos2) { let char = match (pos1, pos2) {
char (Some(char), None) => Ok(char),
} else { _ => Err(ShellError::UnsupportedConfigValue(
return Err(ShellError::UnsupportedConfigValue(
"char_<CHAR: unicode codepoint>".to_string(), "char_<CHAR: unicode codepoint>".to_string(),
c.to_string(), c.to_string(),
keybinding.keycode.span()?, keybinding.keycode.span()?,
)); )),
}; }?;
KeyCode::Char(char) KeyCode::Char(char)
} }
@ -680,10 +683,10 @@ fn add_parsed_keybinding(
let fn_num: u8 = c[1..] let fn_num: u8 = c[1..]
.parse() .parse()
.ok() .ok()
.filter(|num| matches!(num, 1..=20)) .filter(|num| matches!(num, 1..=12))
.ok_or(ShellError::UnsupportedConfigValue( .ok_or(ShellError::UnsupportedConfigValue(
"(f1|f2|...|f20)".to_string(), "(f1|f2|...|f12)".to_string(),
format!("unknown function key: {c}"), format!("unknown function key: {}", c),
keybinding.keycode.span()?, keybinding.keycode.span()?,
))?; ))?;
KeyCode::F(fn_num) KeyCode::F(fn_num)
@ -990,7 +993,10 @@ mod test {
#[test] #[test]
fn test_send_event() { fn test_send_event() {
let cols = vec!["send".to_string()]; let cols = vec!["send".to_string()];
let vals = vec![Value::test_string("Enter")]; let vals = vec![Value::String {
val: "Enter".to_string(),
span: Span::test_data(),
}];
let span = Span::test_data(); let span = Span::test_data();
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap(); let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
@ -1010,7 +1016,10 @@ mod test {
#[test] #[test]
fn test_edit_event() { fn test_edit_event() {
let cols = vec!["edit".to_string()]; let cols = vec!["edit".to_string()];
let vals = vec![Value::test_string("Clear")]; let vals = vec![Value::String {
val: "Clear".to_string(),
span: Span::test_data(),
}];
let span = Span::test_data(); let span = Span::test_data();
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap(); let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
@ -1034,8 +1043,14 @@ mod test {
fn test_send_menu() { fn test_send_menu() {
let cols = vec!["send".to_string(), "name".to_string()]; let cols = vec!["send".to_string(), "name".to_string()];
let vals = vec![ let vals = vec![
Value::test_string("Menu"), Value::String {
Value::test_string("history_menu"), val: "Menu".to_string(),
span: Span::test_data(),
},
Value::String {
val: "history_menu".to_string(),
span: Span::test_data(),
},
]; ];
let span = Span::test_data(); let span = Span::test_data();
@ -1061,8 +1076,14 @@ mod test {
// Menu event // Menu event
let cols = vec!["send".to_string(), "name".to_string()]; let cols = vec!["send".to_string(), "name".to_string()];
let vals = vec![ let vals = vec![
Value::test_string("Menu"), Value::String {
Value::test_string("history_menu"), val: "Menu".to_string(),
span: Span::test_data(),
},
Value::String {
val: "history_menu".to_string(),
span: Span::test_data(),
},
]; ];
let menu_event = Value::Record { let menu_event = Value::Record {
@ -1073,7 +1094,10 @@ mod test {
// Enter event // Enter event
let cols = vec!["send".to_string()]; let cols = vec!["send".to_string()];
let vals = vec![Value::test_string("Enter")]; let vals = vec![Value::String {
val: "Enter".to_string(),
span: Span::test_data(),
}];
let enter_event = Value::Record { let enter_event = Value::Record {
cols, cols,
@ -1114,8 +1138,14 @@ mod test {
// Menu event // Menu event
let cols = vec!["send".to_string(), "name".to_string()]; let cols = vec!["send".to_string(), "name".to_string()];
let vals = vec![ let vals = vec![
Value::test_string("Menu"), Value::String {
Value::test_string("history_menu"), val: "Menu".to_string(),
span: Span::test_data(),
},
Value::String {
val: "history_menu".to_string(),
span: Span::test_data(),
},
]; ];
let menu_event = Value::Record { let menu_event = Value::Record {
@ -1126,7 +1156,10 @@ mod test {
// Enter event // Enter event
let cols = vec!["send".to_string()]; let cols = vec!["send".to_string()];
let vals = vec![Value::test_string("Enter")]; let vals = vec![Value::String {
val: "Enter".to_string(),
span: Span::test_data(),
}];
let enter_event = Value::Record { let enter_event = Value::Record {
cols, cols,
@ -1154,7 +1187,10 @@ mod test {
#[test] #[test]
fn test_error() { fn test_error() {
let cols = vec!["not_exist".to_string()]; let cols = vec!["not_exist".to_string()];
let vals = vec![Value::test_string("Enter")]; let vals = vec![Value::String {
val: "Enter".to_string(),
span: Span::test_data(),
}];
let span = Span::test_data(); let span = Span::test_data();
let b = EventType::try_from_columns(&cols, &vals, &span); let b = EventType::try_from_columns(&cols, &vals, &span);

View File

@ -5,21 +5,20 @@ use crate::{
util::{eval_source, get_guaranteed_cwd, report_error, report_error_new}, util::{eval_source, get_guaranteed_cwd, report_error, report_error_new},
NuHighlighter, NuValidator, NushellPrompt, NuHighlighter, NuValidator, NushellPrompt,
}; };
use crossterm::cursor::CursorShape; use fancy_regex::Regex;
use log::{trace, warn}; use lazy_static::lazy_static;
use log::{info, trace, warn};
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use nu_color_config::StyleComputer; use nu_color_config::get_color_config;
use nu_engine::{convert_env_values, eval_block, eval_block_with_early_return}; use nu_engine::{convert_env_values, eval_block, eval_block_with_early_return};
use nu_parser::{lex, parse, trim_quotes_str}; use nu_parser::{lex, parse, trim_quotes_str};
use nu_protocol::{ use nu_protocol::{
ast::PathMember, ast::PathMember,
config::NuCursorShape,
engine::{EngineState, ReplOperation, Stack, StateWorkingSet}, engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span, format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
Spanned, Type, Value, VarId, Spanned, Type, Value, VarId,
}; };
use nu_utils::utils::perf; use reedline::{DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
use reedline::{CursorConfig, DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
use std::{ use std::{
io::{self, Write}, io::{self, Write},
sync::atomic::Ordering, sync::atomic::Ordering,
@ -42,10 +41,8 @@ pub fn evaluate_repl(
stack: &mut Stack, stack: &mut Stack,
nushell_path: &str, nushell_path: &str,
prerun_command: Option<Spanned<String>>, prerun_command: Option<Spanned<String>>,
entire_start_time: Instant,
) -> Result<()> { ) -> Result<()> {
use reedline::{FileBackedHistory, Reedline, Signal}; use reedline::{FileBackedHistory, Reedline, Signal};
let use_color = engine_state.get_config().use_ansi_coloring;
// Guard against invocation without a connected terminal. // Guard against invocation without a connected terminal.
// reedline / crossterm event polling will fail without a connected tty // reedline / crossterm event polling will fail without a connected tty
@ -61,55 +58,63 @@ pub fn evaluate_repl(
let mut nu_prompt = NushellPrompt::new(); let mut nu_prompt = NushellPrompt::new();
let start_time = std::time::Instant::now(); info!(
"translate environment vars {}:{}:{}",
file!(),
line!(),
column!()
);
// Translate environment variables from Strings to Values // Translate environment variables from Strings to Values
if let Some(e) = convert_env_values(engine_state, stack) { if let Some(e) = convert_env_values(engine_state, stack) {
let working_set = StateWorkingSet::new(engine_state); let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e); report_error(&working_set, &e);
} }
perf(
"translate env vars",
start_time,
file!(),
line!(),
column!(),
use_color,
);
// seed env vars // seed env vars
stack.add_env_var( stack.add_env_var(
"CMD_DURATION_MS".into(), "CMD_DURATION_MS".into(),
Value::string("0823", Span::unknown()), Value::String {
val: "0823".to_string(),
span: Span { start: 0, end: 0 },
},
); );
stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown())); stack.add_env_var(
"LAST_EXIT_CODE".into(),
Value::Int {
val: 0,
span: Span { start: 0, end: 0 },
},
);
info!(
"load config initially {}:{}:{}",
file!(),
line!(),
column!()
);
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
let mut start_time = std::time::Instant::now();
let mut line_editor = Reedline::create(); let mut line_editor = Reedline::create();
// Now that reedline is created, get the history session id and store it in engine_state // Now that reedline is created, get the history session id and store it in engine_state
let hist_sesh = line_editor let hist_sesh = match line_editor.get_history_session_id() {
.get_history_session_id() Some(id) => i64::from(id),
.map(i64::from) None => 0,
.unwrap_or(0); };
engine_state.history_session_id = hist_sesh; engine_state.history_session_id = hist_sesh;
perf(
"setup reedline",
start_time,
file!(),
line!(),
column!(),
use_color,
);
let config = engine_state.get_config(); let config = engine_state.get_config();
start_time = std::time::Instant::now();
let history_path = crate::config_files::get_history_path( let history_path = crate::config_files::get_history_path(
nushell_path, nushell_path,
engine_state.config.history_file_format, engine_state.config.history_file_format,
); );
if let Some(history_path) = history_path.as_deref() { if let Some(history_path) = history_path.as_deref() {
info!("setup history {}:{}:{}", file!(), line!(), column!());
let history: Box<dyn reedline::History> = match engine_state.config.history_file_format { let history: Box<dyn reedline::History> = match engine_state.config.history_file_format {
HistoryFileFormat::PlainText => Box::new( HistoryFileFormat::PlainText => Box::new(
FileBackedHistory::with_file( FileBackedHistory::with_file(
@ -124,16 +129,7 @@ pub fn evaluate_repl(
}; };
line_editor = line_editor.with_history(history); line_editor = line_editor.with_history(history);
}; };
perf(
"setup history",
start_time,
file!(),
line!(),
column!(),
use_color,
);
start_time = std::time::Instant::now();
let sys = sysinfo::System::new(); let sys = sysinfo::System::new();
let show_banner = config.show_banner; let show_banner = config.show_banner;
@ -141,113 +137,63 @@ pub fn evaluate_repl(
if show_banner { if show_banner {
let banner = get_banner(engine_state, stack); let banner = get_banner(engine_state, stack);
if use_ansi { if use_ansi {
println!("{banner}"); println!("{}", banner);
} else { } else {
println!("{}", nu_utils::strip_ansi_string_likely(banner)); println!("{}", nu_utils::strip_ansi_string_likely(banner));
} }
} }
perf(
"get sysinfo/show banner",
start_time,
file!(),
line!(),
column!(),
use_color,
);
if let Some(s) = prerun_command { if let Some(s) = prerun_command {
eval_source( eval_source(
engine_state, engine_state,
stack, stack,
s.item.as_bytes(), s.item.as_bytes(),
&format!("entry #{entry_num}"), &format!("entry #{}", entry_num),
PipelineData::empty(), PipelineData::new(Span::new(0, 0)),
false,
); );
engine_state.merge_env(stack, get_guaranteed_cwd(engine_state, stack))?; engine_state.merge_env(stack, get_guaranteed_cwd(engine_state, stack))?;
} }
loop { loop {
let loop_start_time = std::time::Instant::now(); info!(
"load config each loop {}:{}:{}",
file!(),
line!(),
column!()
);
let cwd = get_guaranteed_cwd(engine_state, stack); let cwd = get_guaranteed_cwd(engine_state, stack);
start_time = std::time::Instant::now();
// Before doing anything, merge the environment from the previous REPL iteration into the // Before doing anything, merge the environment from the previous REPL iteration into the
// permanent state. // permanent state.
if let Err(err) = engine_state.merge_env(stack, cwd) { if let Err(err) = engine_state.merge_env(stack, cwd) {
report_error_new(engine_state, &err); report_error_new(engine_state, &err);
} }
perf(
"merge env",
start_time,
file!(),
line!(),
column!(),
use_color,
);
start_time = std::time::Instant::now();
//Reset the ctrl-c handler //Reset the ctrl-c handler
if let Some(ctrlc) = &mut engine_state.ctrlc { if let Some(ctrlc) = &mut engine_state.ctrlc {
ctrlc.store(false, Ordering::SeqCst); ctrlc.store(false, Ordering::SeqCst);
} }
perf(
"reset ctrlc",
start_time,
file!(),
line!(),
column!(),
use_color,
);
start_time = std::time::Instant::now();
// Reset the SIGQUIT handler // Reset the SIGQUIT handler
if let Some(sig_quit) = engine_state.get_sig_quit() { if let Some(sig_quit) = engine_state.get_sig_quit() {
sig_quit.store(false, Ordering::SeqCst); sig_quit.store(false, Ordering::SeqCst);
} }
perf(
"reset sig_quit",
start_time,
file!(),
line!(),
column!(),
use_color,
);
start_time = std::time::Instant::now();
let config = engine_state.get_config(); let config = engine_state.get_config();
info!("setup colors {}:{}:{}", file!(), line!(), column!());
let color_hm = get_color_config(config);
info!("update reedline {}:{}:{}", file!(), line!(), column!());
let engine_reference = std::sync::Arc::new(engine_state.clone()); let engine_reference = std::sync::Arc::new(engine_state.clone());
// Find the configured cursor shapes for each mode
let cursor_config = CursorConfig {
vi_insert: Some(map_nucursorshape_to_cursorshape(
config.cursor_shape_vi_insert,
)),
vi_normal: Some(map_nucursorshape_to_cursorshape(
config.cursor_shape_vi_normal,
)),
emacs: Some(map_nucursorshape_to_cursorshape(config.cursor_shape_emacs)),
};
perf(
"get config/cursor config",
start_time,
file!(),
line!(),
column!(),
use_color,
);
start_time = std::time::Instant::now();
line_editor = line_editor line_editor = line_editor
.with_highlighter(Box::new(NuHighlighter { .with_highlighter(Box::new(NuHighlighter {
engine_state: engine_reference.clone(), engine_state: engine_state.clone(),
config: config.clone(), config: config.clone(),
})) }))
.with_validator(Box::new(NuValidator { .with_validator(Box::new(NuValidator {
engine_state: engine_reference.clone(), engine_state: engine_state.clone(),
})) }))
.with_completer(Box::new(NuCompleter::new( .with_completer(Box::new(NuCompleter::new(
engine_reference.clone(), engine_reference.clone(),
@ -255,54 +201,25 @@ pub fn evaluate_repl(
))) )))
.with_quick_completions(config.quick_completions) .with_quick_completions(config.quick_completions)
.with_partial_completions(config.partial_completions) .with_partial_completions(config.partial_completions)
.with_ansi_colors(config.use_ansi_coloring) .with_ansi_colors(config.use_ansi_coloring);
.with_cursor_config(cursor_config);
perf(
"reedline builder",
start_time,
file!(),
line!(),
column!(),
use_color,
);
let style_computer = StyleComputer::from_config(engine_state, stack);
start_time = std::time::Instant::now();
line_editor = if config.use_ansi_coloring { line_editor = if config.use_ansi_coloring {
line_editor.with_hinter(Box::new({ line_editor.with_hinter(Box::new(
// As of Nov 2022, "hints" color_config closures only get `null` passed in. DefaultHinter::default().with_style(color_hm["hints"]),
let style = style_computer.compute("hints", &Value::nothing(Span::unknown())); ))
DefaultHinter::default().with_style(style)
}))
} else { } else {
line_editor.disable_hints() line_editor.disable_hints()
}; };
perf(
"reedline coloring/style_computer",
start_time,
file!(),
line!(),
column!(),
use_color,
);
start_time = std::time::Instant::now(); line_editor = match add_menus(line_editor, engine_reference, stack, config) {
line_editor = add_menus(line_editor, engine_reference, stack, config).unwrap_or_else(|e| { Ok(line_editor) => line_editor,
Err(e) => {
let working_set = StateWorkingSet::new(engine_state); let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e); report_error(&working_set, &e);
Reedline::create() Reedline::create()
}); }
perf( };
"reedline menus",
start_time,
file!(),
line!(),
column!(),
use_color,
);
start_time = std::time::Instant::now();
let buffer_editor = if !config.buffer_editor.is_empty() { let buffer_editor = if !config.buffer_editor.is_empty() {
Some(config.buffer_editor.clone()) Some(config.buffer_editor.clone())
} else { } else {
@ -323,31 +240,17 @@ pub fn evaluate_repl(
} else { } else {
line_editor line_editor
}; };
perf(
"reedline buffer_editor",
start_time,
file!(),
line!(),
column!(),
use_color,
);
start_time = std::time::Instant::now();
if config.sync_history_on_enter { if config.sync_history_on_enter {
info!("sync history {}:{}:{}", file!(), line!(), column!());
if let Err(e) = line_editor.sync_history() { if let Err(e) = line_editor.sync_history() {
warn!("Failed to sync history: {}", e); warn!("Failed to sync history: {}", e);
} }
} }
perf(
"sync_history",
start_time,
file!(),
line!(),
column!(),
use_color,
);
start_time = std::time::Instant::now(); info!("setup keybindings {}:{}:{}", file!(), line!(), column!());
// Changing the line editor based on the found keybindings // Changing the line editor based on the found keybindings
line_editor = match create_keybindings(config) { line_editor = match create_keybindings(config) {
Ok(keybindings) => match keybindings { Ok(keybindings) => match keybindings {
@ -369,16 +272,9 @@ pub fn evaluate_repl(
line_editor line_editor
} }
}; };
perf(
"keybindings",
start_time,
file!(),
line!(),
column!(),
use_color,
);
start_time = std::time::Instant::now(); info!("prompt_update {}:{}:{}", file!(), line!(), column!());
// Right before we start our prompt and take input from the user, // Right before we start our prompt and take input from the user,
// fire the "pre_prompt" hook // fire the "pre_prompt" hook
if let Some(hook) = config.hooks.pre_prompt.clone() { if let Some(hook) = config.hooks.pre_prompt.clone() {
@ -386,16 +282,7 @@ pub fn evaluate_repl(
report_error_new(engine_state, &err); report_error_new(engine_state, &err);
} }
} }
perf(
"pre-prompt hook",
start_time,
file!(),
line!(),
column!(),
use_color,
);
start_time = std::time::Instant::now();
// Next, check all the environment variables they ask for // Next, check all the environment variables they ask for
// fire the "env_change" hook // fire the "env_change" hook
let config = engine_state.get_config(); let config = engine_state.get_config();
@ -404,37 +291,19 @@ pub fn evaluate_repl(
{ {
report_error_new(engine_state, &error) report_error_new(engine_state, &error)
} }
perf(
"env-change hook",
start_time,
file!(),
line!(),
column!(),
use_color,
);
start_time = std::time::Instant::now();
let config = engine_state.get_config(); let config = engine_state.get_config();
let prompt = prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt); let prompt = prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt);
perf(
"update_prompt",
start_time,
file!(),
line!(),
column!(),
use_color,
);
entry_num += 1; entry_num += 1;
if entry_num == 1 && show_banner { info!(
println!( "finished setup, starting repl {}:{}:{}",
"Startup Time: {}", file!(),
format_duration(entire_start_time.elapsed().as_nanos() as i64) line!(),
column!()
); );
}
start_time = std::time::Instant::now();
let input = line_editor.read_line(prompt); let input = line_editor.read_line(prompt);
let shell_integration = config.shell_integration; let shell_integration = config.shell_integration;
@ -506,7 +375,7 @@ pub fn evaluate_repl(
"OLDPWD".into(), "OLDPWD".into(),
Value::String { Value::String {
val: cwd.clone(), val: cwd.clone(),
span: Span::unknown(), span: Span { start: 0, end: 0 },
}, },
); );
@ -516,7 +385,7 @@ pub fn evaluate_repl(
"PWD".into(), "PWD".into(),
Value::String { Value::String {
val: path.clone(), val: path.clone(),
span: Span::unknown(), span: Span { start: 0, end: 0 },
}, },
); );
let cwd = Value::String { val: cwd, span }; let cwd = Value::String { val: cwd, span };
@ -561,9 +430,8 @@ pub fn evaluate_repl(
engine_state, engine_state,
stack, stack,
s.as_bytes(), s.as_bytes(),
&format!("entry #{entry_num}"), &format!("entry #{}", entry_num),
PipelineData::empty(), PipelineData::new(Span::new(0, 0)),
false,
); );
} }
let cmd_duration = start_time.elapsed(); let cmd_duration = start_time.elapsed();
@ -572,7 +440,7 @@ pub fn evaluate_repl(
"CMD_DURATION_MS".into(), "CMD_DURATION_MS".into(),
Value::String { Value::String {
val: format!("{}", cmd_duration.as_millis()), val: format!("{}", cmd_duration.as_millis()),
span: Span::unknown(), span: Span { start: 0, end: 0 },
}, },
); );
@ -620,7 +488,7 @@ pub fn evaluate_repl(
// ESC]0;stringBEL -- Set icon name and window title to string // ESC]0;stringBEL -- Set icon name and window title to string
// ESC]1;stringBEL -- Set icon name to string // ESC]1;stringBEL -- Set icon name to string
// ESC]2;stringBEL -- Set window title to string // ESC]2;stringBEL -- Set window title to string
run_ansi_sequence(&format!("\x1b]2;{maybe_abbrev_path}\x07"))?; run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
} }
run_ansi_sequence(RESET_APPLICATION_MODE)?; run_ansi_sequence(RESET_APPLICATION_MODE)?;
} }
@ -660,7 +528,7 @@ pub fn evaluate_repl(
Err(err) => { Err(err) => {
let message = err.to_string(); let message = err.to_string();
if !message.contains("duration") { if !message.contains("duration") {
eprintln!("Error: {err:?}"); eprintln!("Error: {:?}", err);
// TODO: Identify possible error cases where a hard failure is preferable // TODO: Identify possible error cases where a hard failure is preferable
// Ignoring and reporting could hide bigger problems // Ignoring and reporting could hide bigger problems
// e.g. https://github.com/nushell/nushell/issues/6452 // e.g. https://github.com/nushell/nushell/issues/6452
@ -671,36 +539,11 @@ pub fn evaluate_repl(
} }
} }
} }
perf(
"processing line editor input",
start_time,
file!(),
line!(),
column!(),
use_color,
);
perf(
"finished repl loop",
loop_start_time,
file!(),
line!(),
column!(),
use_color,
);
} }
Ok(()) Ok(())
} }
fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> CursorShape {
match shape {
NuCursorShape::Block => CursorShape::Block,
NuCursorShape::UnderScore => CursorShape::UnderScore,
NuCursorShape::Line => CursorShape::Line,
}
}
fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String { fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String {
let age = match eval_string_with_input( let age = match eval_string_with_input(
engine_state, engine_state,
@ -724,7 +567,15 @@ Our {}Documentation{} is located at {}http://nushell.sh{}
{}Tweet{} us at {}@nu_shell{} {}Tweet{} us at {}@nu_shell{}
It's been this long since {}Nushell{}'s first commit: It's been this long since {}Nushell{}'s first commit:
{}{} {}
{}You can disable this banner using the {}config nu{}{} command
to modify the config.nu file and setting show_banner to false.
let-env config = {{
show_banner: false
...
}}{}
"#, "#,
"\x1b[32m", //start line 1 green "\x1b[32m", //start line 1 green
"\x1b[32m", //start line 2 "\x1b[32m", //start line 2
@ -756,6 +607,10 @@ It's been this long since {}Nushell{}'s first commit:
"\x1b[32m", //before Nushell "\x1b[32m", //before Nushell
"\x1b[0m", //after Nushell "\x1b[0m", //after Nushell
age, age,
"\x1b[2;37m", //before banner disable dim white
"\x1b[2;36m", //before config nu dim cyan
"\x1b[0m", //after config nu
"\x1b[2;37m", //after config nu dim white
"\x1b[0m", //after banner disable "\x1b[0m", //after banner disable
); );
@ -782,7 +637,7 @@ pub fn eval_string_with_input(
let input_as_pipeline_data = match input { let input_as_pipeline_data = match input {
Some(input) => PipelineData::Value(input, None), Some(input) => PipelineData::Value(input, None),
None => PipelineData::empty(), None => PipelineData::new(Span::test_data()),
}; };
eval_block( eval_block(
@ -793,7 +648,7 @@ pub fn eval_string_with_input(
false, false,
true, true,
) )
.map(|x| x.into_value(Span::unknown())) .map(|x| x.into_value(Span::test_data()))
} }
pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String { pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String {
@ -863,18 +718,11 @@ pub fn eval_hook(
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let value_span = value.span()?; let value_span = value.span()?;
// Hooks can optionally be a record in this form:
// {
// condition: {|before, after| ... } # block that evaluates to true/false
// code: # block or a string
// }
// The condition block will be run to check whether the main hook (in `code`) should be run.
// If it returns true (the default if a condition block is not specified), the hook should be run.
let condition_path = PathMember::String { let condition_path = PathMember::String {
val: "condition".to_string(), val: "condition".to_string(),
span: value_span, span: value_span,
}; };
let mut output = PipelineData::empty(); let mut output = PipelineData::new(Span::new(0, 0));
let code_path = PathMember::String { let code_path = PathMember::String {
val: "code".to_string(), val: "code".to_string(),
@ -888,11 +736,8 @@ pub fn eval_hook(
} }
} }
Value::Record { .. } => { Value::Record { .. } => {
let do_run_hook = if let Ok(condition) = let do_run_hook =
value if let Ok(condition) = value.clone().follow_cell_path(&[condition_path], false) {
.clone()
.follow_cell_path(&[condition_path], false, false)
{
match condition { match condition {
Value::Block { Value::Block {
val: block_id, val: block_id,
@ -912,19 +757,16 @@ pub fn eval_hook(
arguments.clone(), arguments.clone(),
block_span, block_span,
) { ) {
Ok(pipeline_data) => { Ok(value) => match value {
if let PipelineData::Value(Value::Bool { val, .. }, ..) = Value::Bool { val, .. } => val,
pipeline_data other => {
{
val
} else {
return Err(ShellError::UnsupportedConfigValue( return Err(ShellError::UnsupportedConfigValue(
"boolean output".to_string(), "boolean output".to_string(),
"other PipelineData variant".to_string(), format!("{}", other.get_type()),
block_span, other.span()?,
)); ));
} }
} },
Err(err) => { Err(err) => {
return Err(err); return Err(err);
} }
@ -944,7 +786,7 @@ pub fn eval_hook(
}; };
if do_run_hook { if do_run_hook {
match value.clone().follow_cell_path(&[code_path], false, false)? { match value.clone().follow_cell_path(&[code_path], false)? {
Value::String { Value::String {
val, val,
span: source_span, span: source_span,
@ -981,7 +823,7 @@ pub fn eval_hook(
}; };
engine_state.merge_delta(delta)?; engine_state.merge_delta(delta)?;
let input = PipelineData::empty(); let input = PipelineData::new(value_span);
let var_ids: Vec<VarId> = vars let var_ids: Vec<VarId> = vars
.into_iter() .into_iter()
@ -1047,28 +889,34 @@ pub fn eval_hook(
span: block_span, span: block_span,
.. ..
} => { } => {
output = run_hook_block( output = PipelineData::Value(
run_hook_block(
engine_state, engine_state,
stack, stack,
*block_id, *block_id,
input, input,
arguments, arguments,
*block_span, *block_span,
)?; )?,
None,
);
} }
Value::Closure { Value::Closure {
val: block_id, val: block_id,
span: block_span, span: block_span,
.. ..
} => { } => {
output = run_hook_block( output = PipelineData::Value(
run_hook_block(
engine_state, engine_state,
stack, stack,
*block_id, *block_id,
input, input,
arguments, arguments,
*block_span, *block_span,
)?; )?,
None,
);
} }
other => { other => {
return Err(ShellError::UnsupportedConfigValue( return Err(ShellError::UnsupportedConfigValue(
@ -1085,17 +933,17 @@ pub fn eval_hook(
Ok(output) Ok(output)
} }
fn run_hook_block( pub fn run_hook_block(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
block_id: BlockId, block_id: BlockId,
optional_input: Option<PipelineData>, optional_input: Option<PipelineData>,
arguments: Vec<(String, Value)>, arguments: Vec<(String, Value)>,
span: Span, span: Span,
) -> Result<PipelineData, ShellError> { ) -> Result<Value, ShellError> {
let block = engine_state.get_block(block_id); let block = engine_state.get_block(block_id);
let input = optional_input.unwrap_or_else(PipelineData::empty); let input = optional_input.unwrap_or_else(|| PipelineData::new(span));
let mut callee_stack = stack.gather_captures(&block.captures); let mut callee_stack = stack.gather_captures(&block.captures);
@ -1114,13 +962,11 @@ fn run_hook_block(
} }
} }
let pipeline_data = match eval_block_with_early_return(engine_state, &mut callee_stack, block, input, false, false)
eval_block_with_early_return(engine_state, &mut callee_stack, block, input, false, false)?; {
Ok(pipeline_data) => match pipeline_data.into_value(span) {
if let PipelineData::Value(Value::Error { error }, _) = pipeline_data { Value::Error { error } => Err(error),
return Err(error); val => {
}
// If all went fine, preserve the environment of the called block // If all went fine, preserve the environment of the called block
let caller_env_vars = stack.get_env_var_names(engine_state); let caller_env_vars = stack.get_env_var_names(engine_state);
@ -1136,36 +982,43 @@ fn run_hook_block(
for (var, value) in callee_stack.get_stack_env_vars() { for (var, value) in callee_stack.get_stack_env_vars() {
stack.add_env_var(var, value); stack.add_env_var(var, value);
} }
Ok(pipeline_data)
Ok(val)
}
},
Err(err) => Err(err),
}
} }
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> { fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
io::stdout().write_all(seq.as_bytes()).map_err(|e| { match io::stdout().write_all(seq.as_bytes()) {
ShellError::GenericError( Ok(it) => it,
Err(err) => {
return Err(ShellError::GenericError(
"Error writing ansi sequence".into(), "Error writing ansi sequence".into(),
e.to_string(), err.to_string(),
Some(Span::unknown()), Some(Span { start: 0, end: 0 }),
None, None,
Vec::new(), Vec::new(),
) ));
})?; }
};
io::stdout().flush().map_err(|e| { io::stdout().flush().map_err(|e| {
ShellError::GenericError( ShellError::GenericError(
"Error flushing stdio".into(), "Error flushing stdio".into(),
e.to_string(), e.to_string(),
Some(Span::unknown()), Some(Span { start: 0, end: 0 }),
None, None,
Vec::new(), Vec::new(),
) )
}) })
} }
lazy_static! {
// Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo' // Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo'
#[cfg(windows)] static ref DRIVE_PATH_REGEX: Regex =
static DRIVE_PATH_REGEX: once_cell::sync::Lazy<fancy_regex::Regex> = Regex::new(r"^[a-zA-Z]:[/\\]?").expect("Internal error: regex creation");
once_cell::sync::Lazy::new(|| { }
fancy_regex::Regex::new(r"^[a-zA-Z]:[/\\]?").expect("Internal error: regex creation")
});
// A best-effort "does this string look kinda like a path?" function to determine whether to auto-cd // A best-effort "does this string look kinda like a path?" function to determine whether to auto-cd
fn looks_like_path(orig: &str) -> bool { fn looks_like_path(orig: &str) -> bool {

View File

@ -6,10 +6,9 @@ use nu_protocol::ast::{Argument, Block, Expr, Expression, PipelineElement};
use nu_protocol::engine::{EngineState, StateWorkingSet}; use nu_protocol::engine::{EngineState, StateWorkingSet};
use nu_protocol::{Config, Span}; use nu_protocol::{Config, Span};
use reedline::{Highlighter, StyledText}; use reedline::{Highlighter, StyledText};
use std::sync::Arc;
pub struct NuHighlighter { pub struct NuHighlighter {
pub engine_state: Arc<EngineState>, pub engine_state: EngineState,
pub config: Config, pub config: Config,
} }
@ -150,7 +149,7 @@ fn split_span_by_highlight_positions(
for pos in highlight_positions { for pos in highlight_positions {
if start <= *pos && pos < &span.end { if start <= *pos && pos < &span.end {
if start < *pos { if start < *pos {
result.push((Span::new(start, *pos), false)); result.push((Span { start, end: *pos }, false));
} }
let span_str = &line[pos - global_span_offset..span.end - global_span_offset]; let span_str = &line[pos - global_span_offset..span.end - global_span_offset];
let end = span_str let end = span_str
@ -158,12 +157,18 @@ fn split_span_by_highlight_positions(
.next() .next()
.map(|c| pos + get_char_length(c)) .map(|c| pos + get_char_length(c))
.unwrap_or(pos + 1); .unwrap_or(pos + 1);
result.push((Span::new(*pos, end), true)); result.push((Span { start: *pos, end }, true));
start = end; start = end;
} }
} }
if start < span.end { if start < span.end {
result.push((Span::new(start, span.end), false)); result.push((
Span {
start,
end: span.end,
},
false,
));
} }
result result
} }
@ -234,8 +239,7 @@ fn find_matching_block_end_in_block(
PipelineElement::Expression(_, e) PipelineElement::Expression(_, e)
| PipelineElement::Redirection(_, _, e) | PipelineElement::Redirection(_, _, e)
| PipelineElement::And(_, e) | PipelineElement::And(_, e)
| PipelineElement::Or(_, e) | PipelineElement::Or(_, e) => {
| PipelineElement::SeparateRedirection { out: (_, e), .. } => {
if e.span.contains(global_cursor_offset) { if e.span.contains(global_cursor_offset) {
if let Some(pos) = find_matching_block_end_in_expr( if let Some(pos) = find_matching_block_end_in_expr(
line, line,
@ -354,7 +358,6 @@ fn find_matching_block_end_in_expr(
let opt_expr = match arg { let opt_expr = match arg {
Argument::Named((_, _, opt_expr)) => opt_expr.as_ref(), Argument::Named((_, _, opt_expr)) => opt_expr.as_ref(),
Argument::Positional(inner_expr) => Some(inner_expr), Argument::Positional(inner_expr) => Some(inner_expr),
Argument::Unknown(inner_expr) => Some(inner_expr),
}; };
if let Some(inner_expr) = opt_expr { if let Some(inner_expr) = opt_expr {

View File

@ -1,5 +1,5 @@
use crate::repl::eval_hook; use crate::repl::eval_hook;
use nu_engine::{eval_block, eval_block_with_early_return}; use nu_engine::eval_block;
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents}; use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
use nu_protocol::engine::StateWorkingSet; use nu_protocol::engine::StateWorkingSet;
use nu_protocol::CliError; use nu_protocol::CliError;
@ -9,7 +9,6 @@ use nu_protocol::{
}; };
#[cfg(windows)] #[cfg(windows)]
use nu_utils::enable_vt_processing; use nu_utils::enable_vt_processing;
use nu_utils::utils::perf;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
// This will collect environment variables from std::env and adds them to a stack. // This will collect environment variables from std::env and adds them to a stack.
@ -44,7 +43,7 @@ fn gather_env_vars(
report_error( report_error(
&working_set, &working_set,
&ShellError::GenericError( &ShellError::GenericError(
format!("Environment variable was not captured: {env_str}"), format!("Environment variable was not captured: {}", env_str),
"".to_string(), "".to_string(),
None, None,
Some(msg.into()), Some(msg.into()),
@ -80,7 +79,8 @@ fn gather_env_vars(
"".to_string(), "".to_string(),
None, None,
Some(format!( Some(format!(
"Retrieving current directory failed: {init_cwd:?} not a valid utf-8 path" "Retrieving current directory failed: {:?} not a valid utf-8 path",
init_cwd
)), )),
Vec::new(), Vec::new(),
), ),
@ -203,10 +203,7 @@ pub fn eval_source(
source: &[u8], source: &[u8],
fname: &str, fname: &str,
input: PipelineData, input: PipelineData,
allow_return: bool,
) -> bool { ) -> bool {
let start_time = std::time::Instant::now();
let (block, delta) = { let (block, delta) = {
let mut working_set = StateWorkingSet::new(engine_state); let mut working_set = StateWorkingSet::new(engine_state);
let (output, err) = parse( let (output, err) = parse(
@ -231,13 +228,7 @@ pub fn eval_source(
return false; return false;
} }
let b = if allow_return { match eval_block(engine_state, stack, &block, input, false, false) {
eval_block_with_early_return(engine_state, stack, &block, input, false, false)
} else {
eval_block(engine_state, stack, &block, input, false, false)
};
match b {
Ok(pipeline_data) => { Ok(pipeline_data) => {
let config = engine_state.get_config(); let config = engine_state.get_config();
let result; let result;
@ -259,7 +250,7 @@ pub fn eval_source(
} }
} }
} else { } else {
result = pipeline_data.print(engine_state, stack, true, false); result = pipeline_data.print(engine_state, stack, false, false);
} }
match result { match result {
@ -291,14 +282,6 @@ pub fn eval_source(
return false; return false;
} }
} }
perf(
&format!("eval_source {}", &fname),
start_time,
file!(),
line!(),
column!(),
engine_state.get_config().use_ansi_coloring,
);
true true
} }
@ -306,7 +289,10 @@ pub fn eval_source(
fn set_last_exit_code(stack: &mut Stack, exit_code: i64) { fn set_last_exit_code(stack: &mut Stack, exit_code: i64) {
stack.add_env_var( stack.add_env_var(
"LAST_EXIT_CODE".to_string(), "LAST_EXIT_CODE".to_string(),
Value::int(exit_code, Span::unknown()), Value::Int {
val: exit_code,
span: Span { start: 0, end: 0 },
},
); );
} }
@ -332,19 +318,27 @@ pub fn report_error_new(
} }
pub fn get_init_cwd() -> PathBuf { pub fn get_init_cwd() -> PathBuf {
std::env::current_dir().unwrap_or_else(|_| { match std::env::current_dir() {
std::env::var("PWD") Ok(cwd) => cwd,
.map(Into::into) Err(_) => match std::env::var("PWD") {
.unwrap_or_else(|_| nu_path::home_dir().unwrap_or_default()) Ok(cwd) => PathBuf::from(cwd),
}) Err(_) => match nu_path::home_dir() {
Some(cwd) => cwd,
None => PathBuf::new(),
},
},
}
} }
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf { pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf {
nu_engine::env::current_dir(engine_state, stack).unwrap_or_else(|e| { match nu_engine::env::current_dir(engine_state, stack) {
Ok(p) => p,
Err(e) => {
let working_set = StateWorkingSet::new(engine_state); let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e); report_error(&working_set, &e);
get_init_cwd() get_init_cwd()
}) }
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -1,10 +1,9 @@
use nu_parser::{parse, ParseError}; use nu_parser::{parse, ParseError};
use nu_protocol::engine::{EngineState, StateWorkingSet}; use nu_protocol::engine::{EngineState, StateWorkingSet};
use reedline::{ValidationResult, Validator}; use reedline::{ValidationResult, Validator};
use std::sync::Arc;
pub struct NuValidator { pub struct NuValidator {
pub engine_state: Arc<EngineState>, pub engine_state: EngineState,
} }
impl Validator for NuValidator { impl Validator for NuValidator {

View File

@ -5,7 +5,7 @@ use nu_parser::parse;
use nu_protocol::engine::StateWorkingSet; use nu_protocol::engine::StateWorkingSet;
use reedline::{Completer, Suggestion}; use reedline::{Completer, Suggestion};
use rstest::{fixture, rstest}; use rstest::{fixture, rstest};
use support::{completions_helpers::new_quote_engine, file, folder, match_suggestions, new_engine}; use support::{file, folder, match_suggestions, new_engine};
#[fixture] #[fixture]
fn completer() -> NuCompleter { fn completer() -> NuCompleter {
@ -34,26 +34,6 @@ fn completer_strings() -> NuCompleter {
NuCompleter::new(std::sync::Arc::new(engine), stack) NuCompleter::new(std::sync::Arc::new(engine), stack)
} }
#[fixture]
fn extern_completer() -> NuCompleter {
// Create a new engine
let (dir, _, mut engine, mut stack) = new_engine();
// Add record value as example
let record = r#"
def animals [] { [ "cat", "dog", "eel" ] }
extern spam [
animal: string@animals
--foo (-f): string@animals
-b: string@animals
]
"#;
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
// Instantiate a new completer
NuCompleter::new(std::sync::Arc::new(engine), stack)
}
#[test] #[test]
fn variables_dollar_sign_with_varialblecompletion() { fn variables_dollar_sign_with_varialblecompletion() {
let (_, _, engine, stack) = new_engine(); let (_, _, engine, stack) = new_engine();
@ -109,7 +89,7 @@ fn dotnu_completions() {
// Create a new engine // Create a new engine
let (_, _, engine, stack) = new_engine(); let (_, _, engine, stack) = new_engine();
// Instantiate a new completer // Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack); let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test source completion // Test source completion
@ -169,11 +149,11 @@ fn file_completions() {
// Create a new engine // Create a new engine
let (dir, dir_str, engine, stack) = new_engine(); let (dir, dir_str, engine, stack) = new_engine();
// Instantiate a new completer // Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack); let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the current folder // Test completions for the current folder
let target_dir = format!("cp {dir_str}"); let target_dir = format!("cp {}", dir_str);
let suggestions = completer.complete(&target_dir, target_dir.len()); let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values // Create the expected values
@ -431,36 +411,17 @@ fn command_watch_with_filecompletion() {
match_suggestions(expected_paths, suggestions) match_suggestions(expected_paths, suggestions)
} }
#[test]
fn file_completion_quoted() {
let (_, _, engine, stack) = new_quote_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "open ";
let suggestions = completer.complete(target_dir, target_dir.len());
let expected_paths: Vec<String> = vec![
"`te st.txt`".to_string(),
"`te#st.txt`".to_string(),
"`te'st.txt`".to_string(),
"`te(st).txt`".to_string(),
];
match_suggestions(expected_paths, suggestions)
}
#[test] #[test]
fn flag_completions() { fn flag_completions() {
// Create a new engine // Create a new engine
let (_, _, engine, stack) = new_engine(); let (_, _, engine, stack) = new_engine();
// Instantiate a new completer // Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack); let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the 'ls' flags // Test completions for the 'ls' flags
let suggestions = completer.complete("ls -", 4); let suggestions = completer.complete("ls -", 4);
assert_eq!(16, suggestions.len()); assert_eq!(14, suggestions.len());
let expected: Vec<String> = vec![ let expected: Vec<String> = vec![
"--all".into(), "--all".into(),
@ -469,7 +430,6 @@ fn flag_completions() {
"--full-paths".into(), "--full-paths".into(),
"--help".into(), "--help".into(),
"--long".into(), "--long".into(),
"--mime-type".into(),
"--short-names".into(), "--short-names".into(),
"-D".into(), "-D".into(),
"-a".into(), "-a".into(),
@ -477,7 +437,6 @@ fn flag_completions() {
"-f".into(), "-f".into(),
"-h".into(), "-h".into(),
"-l".into(), "-l".into(),
"-m".into(),
"-s".into(), "-s".into(),
]; ];
@ -490,11 +449,11 @@ fn folder_with_directorycompletions() {
// Create a new engine // Create a new engine
let (dir, dir_str, engine, stack) = new_engine(); let (dir, dir_str, engine, stack) = new_engine();
// Instantiate a new completer // Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack); let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the current folder // Test completions for the current folder
let target_dir = format!("cd {dir_str}"); let target_dir = format!("cd {}", dir_str);
let suggestions = completer.complete(&target_dir, target_dir.len()); let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values // Create the expected values
@ -518,7 +477,7 @@ fn variables_completions() {
let record = "let actor = { name: 'Tom Hardy', age: 44 }"; let record = "let actor = { name: 'Tom Hardy', age: 44 }";
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok()); assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
// Instantiate a new completer // Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack); let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for $nu // Test completions for $nu
@ -574,12 +533,9 @@ fn variables_completions() {
// Test completions for $env // Test completions for $env
let suggestions = completer.complete("$env.", 5); let suggestions = completer.complete("$env.", 5);
assert_eq!(3, suggestions.len()); assert_eq!(2, suggestions.len());
#[cfg(windows)] let expected: Vec<String> = vec!["PWD".into(), "TEST".into()];
let expected: Vec<String> = vec!["PWD".into(), "Path".into(), "TEST".into()];
#[cfg(not(windows))]
let expected: Vec<String> = vec!["PATH".into(), "PWD".into(), "TEST".into()];
// Match results // Match results
match_suggestions(expected, suggestions); match_suggestions(expected, suggestions);
@ -678,7 +634,7 @@ fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
config.external_completer = Some(latest_block_id); config.external_completer = Some(latest_block_id);
engine_state.set_config(&config); engine_state.set_config(&config);
// Instantiate a new completer // Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack); let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);
completer.complete(input, input.len()) completer.complete(input, input.len())
@ -776,92 +732,3 @@ fn filecompletions_triggers_after_cursor() {
match_suggestions(expected_paths, suggestions); match_suggestions(expected_paths, suggestions);
} }
#[rstest]
fn extern_custom_completion_positional(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam ", 5);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn extern_custom_completion_long_flag_1(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam --foo=", 11);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn extern_custom_completion_long_flag_2(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam --foo ", 11);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn extern_custom_completion_long_flag_short(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam -f ", 8);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn extern_custom_completion_short_flag(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam -b ", 8);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn extern_complete_flags(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam -", 6);
let expected: Vec<String> = vec!["--foo".into(), "-b".into(), "-f".into()];
match_suggestions(expected, suggestions);
}
#[ignore = "was reverted, still needs fixing"]
#[rstest]
fn alias_offset_bug_7648() {
let (dir, _, mut engine, mut stack) = new_engine();
// Create an alias
let alias = r#"alias ea = ^$env.EDITOR /tmp/test.s"#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Issue #7648
// Nushell crashes when an alias name is shorter than the alias command
// and the alias command is a external command
// This happens because of offset is not correct.
// This crashes before PR #7779
let _suggestions = completer.complete("e", 1);
}
#[ignore = "was reverted, still needs fixing"]
#[rstest]
fn alias_offset_bug_7754() {
let (dir, _, mut engine, mut stack) = new_engine();
// Create an alias
let alias = r#"alias ll = ls -l"#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Issue #7754
// Nushell crashes when an alias name is shorter than the alias command
// and the alias command contains pipes.
// This crashes before PR #7756
let _suggestions = completer.complete("ll -a | c", 9);
}
#[test]
fn get_path_env_var_8003() {
// Create a new engine
let (_, _, engine, _) = new_engine();
// Get the path env var in a platform agnostic way
let the_path = engine.get_path_env_var();
// Make sure it's not empty
assert!(the_path.is_some());
}

View File

@ -33,69 +33,20 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
"PWD".to_string(), "PWD".to_string(),
Value::String { Value::String {
val: dir_str.clone(), val: dir_str.clone(),
span: nu_protocol::Span::new(0, dir_str.len()), span: nu_protocol::Span {
start: 0,
end: dir_str.len(),
},
}, },
); );
stack.add_env_var( stack.add_env_var(
"TEST".to_string(), "TEST".to_string(),
Value::String { Value::String {
val: "NUSHELL".to_string(), val: "NUSHELL".to_string(),
span: nu_protocol::Span::new(0, dir_str.len()), span: nu_protocol::Span {
start: 0,
end: dir_str.len(),
}, },
);
#[cfg(windows)]
stack.add_env_var(
"Path".to_string(),
Value::String {
val: "c:\\some\\path;c:\\some\\other\\path".to_string(),
span: nu_protocol::Span::new(0, dir_str.len()),
},
);
#[cfg(not(windows))]
stack.add_env_var(
"PATH".to_string(),
Value::String {
val: "/some/path:/some/other/path".to_string(),
span: nu_protocol::Span::new(0, dir_str.len()),
},
);
// Merge environment into the permanent state
let merge_result = engine_state.merge_env(&mut stack, &dir);
assert!(merge_result.is_ok());
(dir, dir_str, engine_state, stack)
}
pub fn new_quote_engine() -> (PathBuf, String, EngineState, Stack) {
// Target folder inside assets
let dir = fs::fixtures().join("quoted_completions");
let mut dir_str = dir
.clone()
.into_os_string()
.into_string()
.unwrap_or_default();
dir_str.push(SEP);
// Create a new engine with default context
let mut engine_state = create_default_context();
// New stack
let mut stack = Stack::new();
// Add pwd as env var
stack.add_env_var(
"PWD".to_string(),
Value::String {
val: dir_str.clone(),
span: nu_protocol::Span::new(0, dir_str.len()),
},
);
stack.add_env_var(
"TEST".to_string(),
Value::String {
val: "NUSHELL".to_string(),
span: nu_protocol::Span::new(0, dir_str.len()),
}, },
); );
@ -161,7 +112,7 @@ pub fn merge_input(
&block, &block,
PipelineData::Value( PipelineData::Value(
Value::Nothing { Value::Nothing {
span: Span::unknown(), span: Span { start: 0, end: 0 },
}, },
None None
), ),

View File

@ -5,21 +5,11 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-color-config" name = "nu-color-config"
version = "0.76.0" version = "0.72.1"
[lib]
bench = false
[dependencies] [dependencies]
serde = { version="1.0.123", features=["derive"] } nu-protocol = { path = "../nu-protocol", version = "0.72.1" }
# used only for text_style Alignments
tabled = { version = "0.10.0", features = ["color"], default-features = false }
nu-protocol = { path = "../nu-protocol", version = "0.76.0" }
nu-ansi-term = "0.46.0" nu-ansi-term = "0.46.0"
nu-utils = { path = "../nu-utils", version = "0.76.0" } nu-json = { path = "../nu-json", version = "0.72.1" }
nu-engine = { path = "../nu-engine", version = "0.76.0" } nu-table = { path = "../nu-table", version = "0.72.1" }
nu-json = { path="../nu-json", version = "0.76.0" } serde = { version="1.0.123", features=["derive"] }
[dev-dependencies]
nu-test-support = { path="../nu-test-support", version = "0.76.0" }

View File

@ -1,89 +1,413 @@
use crate::{ use crate::nu_style::{color_from_hex, color_string_to_nustyle};
nu_style::{color_from_hex, lookup_style}, use nu_ansi_term::{Color, Style};
parse_nustyle, NuStyle, use nu_protocol::Config;
}; use nu_table::{Alignment, TextStyle};
use nu_ansi_term::Style;
use nu_protocol::Value;
use std::collections::HashMap; use std::collections::HashMap;
pub fn lookup_ansi_color_style(s: &str) -> Style { pub fn lookup_ansi_color_style(s: &str) -> Style {
if s.starts_with('#') { if s.starts_with('#') {
color_from_hex(s) match color_from_hex(s) {
.ok() Ok(c) => match c {
.and_then(|c| c.map(|c| c.normal())) Some(c) => c.normal(),
.unwrap_or_default() None => Style::default(),
},
Err(_) => Style::default(),
}
} else if s.starts_with('{') { } else if s.starts_with('{') {
color_string_to_nustyle(s.to_string()) color_string_to_nustyle(s.to_string())
} else { } else {
lookup_style(s) match s {
"g" | "green" => Color::Green.normal(),
"gb" | "green_bold" => Color::Green.bold(),
"gu" | "green_underline" => Color::Green.underline(),
"gi" | "green_italic" => Color::Green.italic(),
"gd" | "green_dimmed" => Color::Green.dimmed(),
"gr" | "green_reverse" => Color::Green.reverse(),
"gbl" | "green_blink" => Color::Green.blink(),
"gst" | "green_strike" => Color::Green.strikethrough(),
"lg" | "light_green" => Color::LightGreen.normal(),
"lgb" | "light_green_bold" => Color::LightGreen.bold(),
"lgu" | "light_green_underline" => Color::LightGreen.underline(),
"lgi" | "light_green_italic" => Color::LightGreen.italic(),
"lgd" | "light_green_dimmed" => Color::LightGreen.dimmed(),
"lgr" | "light_green_reverse" => Color::LightGreen.reverse(),
"lgbl" | "light_green_blink" => Color::LightGreen.blink(),
"lgst" | "light_green_strike" => Color::LightGreen.strikethrough(),
"r" | "red" => Color::Red.normal(),
"rb" | "red_bold" => Color::Red.bold(),
"ru" | "red_underline" => Color::Red.underline(),
"ri" | "red_italic" => Color::Red.italic(),
"rd" | "red_dimmed" => Color::Red.dimmed(),
"rr" | "red_reverse" => Color::Red.reverse(),
"rbl" | "red_blink" => Color::Red.blink(),
"rst" | "red_strike" => Color::Red.strikethrough(),
"lr" | "light_red" => Color::LightRed.normal(),
"lrb" | "light_red_bold" => Color::LightRed.bold(),
"lru" | "light_red_underline" => Color::LightRed.underline(),
"lri" | "light_red_italic" => Color::LightRed.italic(),
"lrd" | "light_red_dimmed" => Color::LightRed.dimmed(),
"lrr" | "light_red_reverse" => Color::LightRed.reverse(),
"lrbl" | "light_red_blink" => Color::LightRed.blink(),
"lrst" | "light_red_strike" => Color::LightRed.strikethrough(),
"u" | "blue" => Color::Blue.normal(),
"ub" | "blue_bold" => Color::Blue.bold(),
"uu" | "blue_underline" => Color::Blue.underline(),
"ui" | "blue_italic" => Color::Blue.italic(),
"ud" | "blue_dimmed" => Color::Blue.dimmed(),
"ur" | "blue_reverse" => Color::Blue.reverse(),
"ubl" | "blue_blink" => Color::Blue.blink(),
"ust" | "blue_strike" => Color::Blue.strikethrough(),
"lu" | "light_blue" => Color::LightBlue.normal(),
"lub" | "light_blue_bold" => Color::LightBlue.bold(),
"luu" | "light_blue_underline" => Color::LightBlue.underline(),
"lui" | "light_blue_italic" => Color::LightBlue.italic(),
"lud" | "light_blue_dimmed" => Color::LightBlue.dimmed(),
"lur" | "light_blue_reverse" => Color::LightBlue.reverse(),
"lubl" | "light_blue_blink" => Color::LightBlue.blink(),
"lust" | "light_blue_strike" => Color::LightBlue.strikethrough(),
"b" | "black" => Color::Black.normal(),
"bb" | "black_bold" => Color::Black.bold(),
"bu" | "black_underline" => Color::Black.underline(),
"bi" | "black_italic" => Color::Black.italic(),
"bd" | "black_dimmed" => Color::Black.dimmed(),
"br" | "black_reverse" => Color::Black.reverse(),
"bbl" | "black_blink" => Color::Black.blink(),
"bst" | "black_strike" => Color::Black.strikethrough(),
"ligr" | "light_gray" => Color::LightGray.normal(),
"ligrb" | "light_gray_bold" => Color::LightGray.bold(),
"ligru" | "light_gray_underline" => Color::LightGray.underline(),
"ligri" | "light_gray_italic" => Color::LightGray.italic(),
"ligrd" | "light_gray_dimmed" => Color::LightGray.dimmed(),
"ligrr" | "light_gray_reverse" => Color::LightGray.reverse(),
"ligrbl" | "light_gray_blink" => Color::LightGray.blink(),
"ligrst" | "light_gray_strike" => Color::LightGray.strikethrough(),
"y" | "yellow" => Color::Yellow.normal(),
"yb" | "yellow_bold" => Color::Yellow.bold(),
"yu" | "yellow_underline" => Color::Yellow.underline(),
"yi" | "yellow_italic" => Color::Yellow.italic(),
"yd" | "yellow_dimmed" => Color::Yellow.dimmed(),
"yr" | "yellow_reverse" => Color::Yellow.reverse(),
"ybl" | "yellow_blink" => Color::Yellow.blink(),
"yst" | "yellow_strike" => Color::Yellow.strikethrough(),
"ly" | "light_yellow" => Color::LightYellow.normal(),
"lyb" | "light_yellow_bold" => Color::LightYellow.bold(),
"lyu" | "light_yellow_underline" => Color::LightYellow.underline(),
"lyi" | "light_yellow_italic" => Color::LightYellow.italic(),
"lyd" | "light_yellow_dimmed" => Color::LightYellow.dimmed(),
"lyr" | "light_yellow_reverse" => Color::LightYellow.reverse(),
"lybl" | "light_yellow_blink" => Color::LightYellow.blink(),
"lyst" | "light_yellow_strike" => Color::LightYellow.strikethrough(),
"p" | "purple" => Color::Purple.normal(),
"pb" | "purple_bold" => Color::Purple.bold(),
"pu" | "purple_underline" => Color::Purple.underline(),
"pi" | "purple_italic" => Color::Purple.italic(),
"pd" | "purple_dimmed" => Color::Purple.dimmed(),
"pr" | "purple_reverse" => Color::Purple.reverse(),
"pbl" | "purple_blink" => Color::Purple.blink(),
"pst" | "purple_strike" => Color::Purple.strikethrough(),
"lp" | "light_purple" => Color::LightPurple.normal(),
"lpb" | "light_purple_bold" => Color::LightPurple.bold(),
"lpu" | "light_purple_underline" => Color::LightPurple.underline(),
"lpi" | "light_purple_italic" => Color::LightPurple.italic(),
"lpd" | "light_purple_dimmed" => Color::LightPurple.dimmed(),
"lpr" | "light_purple_reverse" => Color::LightPurple.reverse(),
"lpbl" | "light_purple_blink" => Color::LightPurple.blink(),
"lpst" | "light_purple_strike" => Color::LightPurple.strikethrough(),
"c" | "cyan" => Color::Cyan.normal(),
"cb" | "cyan_bold" => Color::Cyan.bold(),
"cu" | "cyan_underline" => Color::Cyan.underline(),
"ci" | "cyan_italic" => Color::Cyan.italic(),
"cd" | "cyan_dimmed" => Color::Cyan.dimmed(),
"cr" | "cyan_reverse" => Color::Cyan.reverse(),
"cbl" | "cyan_blink" => Color::Cyan.blink(),
"cst" | "cyan_strike" => Color::Cyan.strikethrough(),
"lc" | "light_cyan" => Color::LightCyan.normal(),
"lcb" | "light_cyan_bold" => Color::LightCyan.bold(),
"lcu" | "light_cyan_underline" => Color::LightCyan.underline(),
"lci" | "light_cyan_italic" => Color::LightCyan.italic(),
"lcd" | "light_cyan_dimmed" => Color::LightCyan.dimmed(),
"lcr" | "light_cyan_reverse" => Color::LightCyan.reverse(),
"lcbl" | "light_cyan_blink" => Color::LightCyan.blink(),
"lcst" | "light_cyan_strike" => Color::LightCyan.strikethrough(),
"w" | "white" => Color::White.normal(),
"wb" | "white_bold" => Color::White.bold(),
"wu" | "white_underline" => Color::White.underline(),
"wi" | "white_italic" => Color::White.italic(),
"wd" | "white_dimmed" => Color::White.dimmed(),
"wr" | "white_reverse" => Color::White.reverse(),
"wbl" | "white_blink" => Color::White.blink(),
"wst" | "white_strike" => Color::White.strikethrough(),
"dgr" | "dark_gray" => Color::DarkGray.normal(),
"dgrb" | "dark_gray_bold" => Color::DarkGray.bold(),
"dgru" | "dark_gray_underline" => Color::DarkGray.underline(),
"dgri" | "dark_gray_italic" => Color::DarkGray.italic(),
"dgrd" | "dark_gray_dimmed" => Color::DarkGray.dimmed(),
"dgrr" | "dark_gray_reverse" => Color::DarkGray.reverse(),
"dgrbl" | "dark_gray_blink" => Color::DarkGray.blink(),
"dgrst" | "dark_gray_strike" => Color::DarkGray.strikethrough(),
"def" | "default" => Color::Default.normal(),
"defb" | "default_bold" => Color::Default.bold(),
"defu" | "default_underline" => Color::Default.underline(),
"defi" | "default_italic" => Color::Default.italic(),
"defd" | "default_dimmed" => Color::Default.dimmed(),
"defr" | "default_reverse" => Color::Default.reverse(),
_ => Color::White.normal(),
}
} }
} }
pub fn get_color_map(colors: &HashMap<String, Value>) -> HashMap<String, Style> { fn update_hashmap(key: &str, val: &str, hm: &mut HashMap<String, Style>) {
// eprintln!("key: {}, val: {}", &key, &val);
let color = lookup_ansi_color_style(val);
if let Some(v) = hm.get_mut(key) {
*v = color;
} else {
hm.insert(key.to_string(), color);
}
}
pub fn get_color_config(config: &Config) -> HashMap<String, Style> {
let config = config;
// create the hashmap
let mut hm: HashMap<String, Style> = HashMap::new(); let mut hm: HashMap<String, Style> = HashMap::new();
// set some defaults
// hm.insert("primitive_line".to_string(), Color::White.normal());
// hm.insert("primitive_pattern".to_string(), Color::White.normal());
// hm.insert("primitive_path".to_string(), Color::White.normal());
hm.insert("separator".to_string(), Color::White.normal());
hm.insert(
"leading_trailing_space_bg".to_string(),
Style::default().on(Color::Rgb(128, 128, 128)),
);
hm.insert("header".to_string(), Color::Green.bold());
hm.insert("empty".to_string(), Color::Blue.normal());
hm.insert("bool".to_string(), Color::White.normal());
hm.insert("int".to_string(), Color::White.normal());
hm.insert("filesize".to_string(), Color::White.normal());
hm.insert("duration".to_string(), Color::White.normal());
hm.insert("date".to_string(), Color::White.normal());
hm.insert("range".to_string(), Color::White.normal());
hm.insert("float".to_string(), Color::White.normal());
hm.insert("string".to_string(), Color::White.normal());
hm.insert("nothing".to_string(), Color::White.normal());
hm.insert("binary".to_string(), Color::White.normal());
hm.insert("cellpath".to_string(), Color::White.normal());
hm.insert("row_index".to_string(), Color::Green.bold());
hm.insert("record".to_string(), Color::White.normal());
hm.insert("list".to_string(), Color::White.normal());
hm.insert("block".to_string(), Color::White.normal());
hm.insert("hints".to_string(), Color::DarkGray.normal());
for (key, value) in colors { for (key, value) in &config.color_config {
parse_map_entry(&mut hm, key, value); match value.as_string() {
Ok(value) => update_hashmap(key, &value, &mut hm),
Err(_) => continue,
}
} }
hm hm
} }
fn parse_map_entry(hm: &mut HashMap<String, Style>, key: &str, value: &Value) { // This function will assign a text style to a primitive, or really any string that's
let value = match value { // in the hashmap. The hashmap actually contains the style to be applied.
Value::String { val, .. } => Some(lookup_ansi_color_style(val)), pub fn style_primitive(primitive: &str, color_hm: &HashMap<String, Style>) -> TextStyle {
Value::Record { cols, vals, .. } => get_style_from_value(cols, vals).map(parse_nustyle), match primitive {
_ => None, "bool" => {
}; let style = color_hm.get(primitive);
if let Some(value) = value { match style {
hm.entry(key.to_owned()).or_insert(value); Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
} }
} }
fn get_style_from_value(cols: &[String], vals: &[Value]) -> Option<NuStyle> { "int" => {
let mut was_set = false; let style = color_hm.get(primitive);
let mut style = NuStyle::from(Style::default()); match style {
for (col, val) in cols.iter().zip(vals) { Some(s) => TextStyle::with_style(Alignment::Right, *s),
match col.as_str() { None => TextStyle::basic_right(),
"bg" => {
if let Value::String { val, .. } = val {
style.bg = Some(val.clone());
was_set = true;
}
}
"fg" => {
if let Value::String { val, .. } = val {
style.fg = Some(val.clone());
was_set = true;
}
}
"attr" => {
if let Value::String { val, .. } = val {
style.attr = Some(val.clone());
was_set = true;
}
}
_ => (),
} }
} }
if was_set { "filesize" => {
Some(style) let style = color_hm.get(primitive);
} else { match style {
None Some(s) => TextStyle::with_style(Alignment::Right, *s),
None => TextStyle::basic_right(),
} }
} }
fn color_string_to_nustyle(color_string: String) -> Style { "duration" => {
// eprintln!("color_string: {}", &color_string); let style = color_hm.get(primitive);
if color_string.is_empty() { match style {
return Style::default(); Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
}
} }
let nu_style = match nu_json::from_str::<NuStyle>(&color_string) { "date" => {
Ok(s) => s, let style = color_hm.get(primitive);
Err(_) => return Style::default(), match style {
}; Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
parse_nustyle(nu_style) }
}
"range" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
}
}
"float" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Right, *s),
None => TextStyle::basic_right(),
}
}
"string" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
}
}
"nothing" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
}
}
// not sure what to do with error
// "error" => {}
"binary" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
}
}
"cellpath" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
}
}
"row_index" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Right, *s),
None => TextStyle::new()
.alignment(Alignment::Right)
.fg(Color::Green)
.bold(Some(true)),
}
}
"record" | "list" | "block" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
}
}
// types in nushell but not in engine-q
// "Line" => {
// let style = color_hm.get("Primitive::Line");
// match style {
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
// None => TextStyle::basic_left(),
// }
// }
// "GlobPattern" => {
// let style = color_hm.get("Primitive::GlobPattern");
// match style {
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
// None => TextStyle::basic_left(),
// }
// }
// "FilePath" => {
// let style = color_hm.get("Primitive::FilePath");
// match style {
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
// None => TextStyle::basic_left(),
// }
// }
// "BeginningOfStream" => {
// let style = color_hm.get("Primitive::BeginningOfStream");
// match style {
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
// None => TextStyle::basic_left(),
// }
// }
// "EndOfStream" => {
// let style = color_hm.get("Primitive::EndOfStream");
// match style {
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
// None => TextStyle::basic_left(),
// }
// }
_ => TextStyle::basic_left(),
}
}
#[test]
fn test_hm() {
use nu_ansi_term::{Color, Style};
let mut hm: HashMap<String, Style> = HashMap::new();
hm.insert("primitive_int".to_string(), Color::White.normal());
hm.insert("primitive_decimal".to_string(), Color::White.normal());
hm.insert("primitive_filesize".to_string(), Color::White.normal());
hm.insert("primitive_string".to_string(), Color::White.normal());
hm.insert("primitive_line".to_string(), Color::White.normal());
hm.insert("primitive_columnpath".to_string(), Color::White.normal());
hm.insert("primitive_pattern".to_string(), Color::White.normal());
hm.insert("primitive_boolean".to_string(), Color::White.normal());
hm.insert("primitive_date".to_string(), Color::White.normal());
hm.insert("primitive_duration".to_string(), Color::White.normal());
hm.insert("primitive_range".to_string(), Color::White.normal());
hm.insert("primitive_path".to_string(), Color::White.normal());
hm.insert("primitive_binary".to_string(), Color::White.normal());
hm.insert("separator".to_string(), Color::White.normal());
hm.insert("header_align".to_string(), Color::Green.bold());
hm.insert("header".to_string(), Color::Green.bold());
hm.insert("header_style".to_string(), Style::default());
hm.insert("row_index".to_string(), Color::Green.bold());
hm.insert(
"leading_trailing_space_bg".to_string(),
Style::default().on(Color::Rgb(128, 128, 128)),
);
update_hashmap("primitive_int", "green", &mut hm);
assert_eq!(hm["primitive_int"], Color::Green.normal());
} }

View File

@ -2,12 +2,8 @@ mod color_config;
mod matching_brackets_style; mod matching_brackets_style;
mod nu_style; mod nu_style;
mod shape_color; mod shape_color;
mod style_computer;
mod text_style;
pub use color_config::*; pub use color_config::*;
pub use matching_brackets_style::*; pub use matching_brackets_style::*;
pub use nu_style::*; pub use nu_style::*;
pub use shape_color::*; pub use shape_color::*;
pub use style_computer::*;
pub use text_style::*;

View File

@ -1,113 +1,88 @@
use nu_ansi_term::{Color, Style}; use nu_ansi_term::{Color, Style};
use nu_protocol::Value; use serde::Deserialize;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, PartialEq, Eq, Debug)] #[derive(Deserialize, PartialEq, Eq, Debug)]
pub struct NuStyle { pub struct NuStyle {
pub fg: Option<String>, pub fg: Option<String>,
pub bg: Option<String>, pub bg: Option<String>,
pub attr: Option<String>, pub attr: Option<String>,
} }
impl From<Style> for NuStyle {
fn from(s: Style) -> Self {
Self {
bg: s.background.and_then(color_to_string),
fg: s.foreground.and_then(color_to_string),
attr: style_get_attr(s),
}
}
}
fn style_get_attr(s: Style) -> Option<String> {
macro_rules! check {
($attrs:expr, $b:expr, $c:expr) => {
if $b {
$attrs.push($c);
}
};
}
let mut attrs = String::new();
check!(attrs, s.is_blink, 'l');
check!(attrs, s.is_bold, 'b');
check!(attrs, s.is_dimmed, 'd');
check!(attrs, s.is_reverse, 'r');
check!(attrs, s.is_strikethrough, 's');
check!(attrs, s.is_underline, 'u');
if attrs.is_empty() {
None
} else {
Some(attrs)
}
}
fn color_to_string(color: Color) -> Option<String> {
match color {
Color::Black => Some(String::from("black")),
Color::DarkGray => Some(String::from("dark_gray")),
Color::Red => Some(String::from("red")),
Color::LightRed => Some(String::from("light_red")),
Color::Green => Some(String::from("green")),
Color::LightGreen => Some(String::from("light_green")),
Color::Yellow => Some(String::from("yellow")),
Color::LightYellow => Some(String::from("light_yellow")),
Color::Blue => Some(String::from("blue")),
Color::LightBlue => Some(String::from("light_blue")),
Color::Purple => Some(String::from("purple")),
Color::LightPurple => Some(String::from("light_purple")),
Color::Magenta => Some(String::from("magenta")),
Color::LightMagenta => Some(String::from("light_magenta")),
Color::Cyan => Some(String::from("cyan")),
Color::LightCyan => Some(String::from("light_cyan")),
Color::White => Some(String::from("white")),
Color::LightGray => Some(String::from("light_gray")),
Color::Default => Some(String::from("default")),
Color::Rgb(r, g, b) => Some(format!("#{r:X}{g:X}{b:X}")),
Color::Fixed(_) => None,
}
}
pub fn parse_nustyle(nu_style: NuStyle) -> Style { pub fn parse_nustyle(nu_style: NuStyle) -> Style {
let mut style = Style { // get the nu_ansi_term::Color foreground color
foreground: nu_style.fg.and_then(|fg| lookup_color_str(&fg)), let fg_color = match nu_style.fg {
background: nu_style.bg.and_then(|bg| lookup_color_str(&bg)), Some(fg) => color_from_hex(&fg).unwrap_or_default(),
..Default::default() _ => None,
};
// get the nu_ansi_term::Color background color
let bg_color = match nu_style.bg {
Some(bg) => color_from_hex(&bg).unwrap_or_default(),
_ => None,
};
// get the attributes
let color_attr = match nu_style.attr {
Some(attr) => attr,
_ => "".to_string(),
}; };
if let Some(attrs) = nu_style.attr { // setup the attributes available in nu_ansi_term::Style
fill_modifiers(&attrs, &mut style) let mut bold = false;
} let mut dimmed = false;
let mut italic = false;
let mut underline = false;
let mut blink = false;
let mut reverse = false;
let mut hidden = false;
let mut strikethrough = false;
style // since we can combine styles like bold-italic, iterate through the chars
} // and set the bools for later use in the nu_ansi_term::Style application
for ch in color_attr.to_lowercase().chars() {
// Converts the color_config records, { fg, bg, attr }, into a Style. match ch {
pub fn color_record_to_nustyle(value: &Value) -> Style { 'l' => blink = true,
let mut fg = None; 'b' => bold = true,
let mut bg = None; 'd' => dimmed = true,
let mut attr = None; 'h' => hidden = true,
let v = value.as_record(); 'i' => italic = true,
if let Ok((cols, inner_vals)) = v { 'r' => reverse = true,
for (k, v) in cols.iter().zip(inner_vals) { 's' => strikethrough = true,
// Because config already type-checked the color_config records, this doesn't bother giving errors 'u' => underline = true,
// if there are unrecognised keys or bad values. 'n' => (),
if let Ok(v) = v.as_string() {
match k.as_str() {
"fg" => fg = Some(v),
"bg" => bg = Some(v),
"attr" => attr = Some(v),
_ => (), _ => (),
} }
} }
// here's where we build the nu_ansi_term::Style
Style {
foreground: fg_color,
background: bg_color,
is_blink: blink,
is_bold: bold,
is_dimmed: dimmed,
is_hidden: hidden,
is_italic: italic,
is_reverse: reverse,
is_strikethrough: strikethrough,
is_underline: underline,
} }
} }
parse_nustyle(NuStyle { fg, bg, attr }) pub fn color_string_to_nustyle(color_string: String) -> Style {
// eprintln!("color_string: {}", &color_string);
if color_string.chars().count() < 1 {
Style::default()
} else {
let nu_style = match nu_json::from_str::<NuStyle>(&color_string) {
Ok(s) => s,
Err(_) => NuStyle {
fg: None,
bg: None,
attr: None,
},
};
parse_nustyle(nu_style)
}
} }
pub fn color_from_hex( pub fn color_from_hex(
@ -126,471 +101,3 @@ pub fn color_from_hex(
))) )))
} }
} }
pub fn lookup_style(s: &str) -> Style {
match s {
"g" | "green" => Color::Green.normal(),
"gb" | "green_bold" => Color::Green.bold(),
"gu" | "green_underline" => Color::Green.underline(),
"gi" | "green_italic" => Color::Green.italic(),
"gd" | "green_dimmed" => Color::Green.dimmed(),
"gr" | "green_reverse" => Color::Green.reverse(),
"gbl" | "green_blink" => Color::Green.blink(),
"gst" | "green_strike" => Color::Green.strikethrough(),
"lg" | "light_green" => Color::LightGreen.normal(),
"lgb" | "light_green_bold" => Color::LightGreen.bold(),
"lgu" | "light_green_underline" => Color::LightGreen.underline(),
"lgi" | "light_green_italic" => Color::LightGreen.italic(),
"lgd" | "light_green_dimmed" => Color::LightGreen.dimmed(),
"lgr" | "light_green_reverse" => Color::LightGreen.reverse(),
"lgbl" | "light_green_blink" => Color::LightGreen.blink(),
"lgst" | "light_green_strike" => Color::LightGreen.strikethrough(),
"r" | "red" => Color::Red.normal(),
"rb" | "red_bold" => Color::Red.bold(),
"ru" | "red_underline" => Color::Red.underline(),
"ri" | "red_italic" => Color::Red.italic(),
"rd" | "red_dimmed" => Color::Red.dimmed(),
"rr" | "red_reverse" => Color::Red.reverse(),
"rbl" | "red_blink" => Color::Red.blink(),
"rst" | "red_strike" => Color::Red.strikethrough(),
"lr" | "light_red" => Color::LightRed.normal(),
"lrb" | "light_red_bold" => Color::LightRed.bold(),
"lru" | "light_red_underline" => Color::LightRed.underline(),
"lri" | "light_red_italic" => Color::LightRed.italic(),
"lrd" | "light_red_dimmed" => Color::LightRed.dimmed(),
"lrr" | "light_red_reverse" => Color::LightRed.reverse(),
"lrbl" | "light_red_blink" => Color::LightRed.blink(),
"lrst" | "light_red_strike" => Color::LightRed.strikethrough(),
"u" | "blue" => Color::Blue.normal(),
"ub" | "blue_bold" => Color::Blue.bold(),
"uu" | "blue_underline" => Color::Blue.underline(),
"ui" | "blue_italic" => Color::Blue.italic(),
"ud" | "blue_dimmed" => Color::Blue.dimmed(),
"ur" | "blue_reverse" => Color::Blue.reverse(),
"ubl" | "blue_blink" => Color::Blue.blink(),
"ust" | "blue_strike" => Color::Blue.strikethrough(),
"lu" | "light_blue" => Color::LightBlue.normal(),
"lub" | "light_blue_bold" => Color::LightBlue.bold(),
"luu" | "light_blue_underline" => Color::LightBlue.underline(),
"lui" | "light_blue_italic" => Color::LightBlue.italic(),
"lud" | "light_blue_dimmed" => Color::LightBlue.dimmed(),
"lur" | "light_blue_reverse" => Color::LightBlue.reverse(),
"lubl" | "light_blue_blink" => Color::LightBlue.blink(),
"lust" | "light_blue_strike" => Color::LightBlue.strikethrough(),
"b" | "black" => Color::Black.normal(),
"bb" | "black_bold" => Color::Black.bold(),
"bu" | "black_underline" => Color::Black.underline(),
"bi" | "black_italic" => Color::Black.italic(),
"bd" | "black_dimmed" => Color::Black.dimmed(),
"br" | "black_reverse" => Color::Black.reverse(),
"bbl" | "black_blink" => Color::Black.blink(),
"bst" | "black_strike" => Color::Black.strikethrough(),
"ligr" | "light_gray" => Color::LightGray.normal(),
"ligrb" | "light_gray_bold" => Color::LightGray.bold(),
"ligru" | "light_gray_underline" => Color::LightGray.underline(),
"ligri" | "light_gray_italic" => Color::LightGray.italic(),
"ligrd" | "light_gray_dimmed" => Color::LightGray.dimmed(),
"ligrr" | "light_gray_reverse" => Color::LightGray.reverse(),
"ligrbl" | "light_gray_blink" => Color::LightGray.blink(),
"ligrst" | "light_gray_strike" => Color::LightGray.strikethrough(),
"y" | "yellow" => Color::Yellow.normal(),
"yb" | "yellow_bold" => Color::Yellow.bold(),
"yu" | "yellow_underline" => Color::Yellow.underline(),
"yi" | "yellow_italic" => Color::Yellow.italic(),
"yd" | "yellow_dimmed" => Color::Yellow.dimmed(),
"yr" | "yellow_reverse" => Color::Yellow.reverse(),
"ybl" | "yellow_blink" => Color::Yellow.blink(),
"yst" | "yellow_strike" => Color::Yellow.strikethrough(),
"ly" | "light_yellow" => Color::LightYellow.normal(),
"lyb" | "light_yellow_bold" => Color::LightYellow.bold(),
"lyu" | "light_yellow_underline" => Color::LightYellow.underline(),
"lyi" | "light_yellow_italic" => Color::LightYellow.italic(),
"lyd" | "light_yellow_dimmed" => Color::LightYellow.dimmed(),
"lyr" | "light_yellow_reverse" => Color::LightYellow.reverse(),
"lybl" | "light_yellow_blink" => Color::LightYellow.blink(),
"lyst" | "light_yellow_strike" => Color::LightYellow.strikethrough(),
"p" | "purple" => Color::Purple.normal(),
"pb" | "purple_bold" => Color::Purple.bold(),
"pu" | "purple_underline" => Color::Purple.underline(),
"pi" | "purple_italic" => Color::Purple.italic(),
"pd" | "purple_dimmed" => Color::Purple.dimmed(),
"pr" | "purple_reverse" => Color::Purple.reverse(),
"pbl" | "purple_blink" => Color::Purple.blink(),
"pst" | "purple_strike" => Color::Purple.strikethrough(),
"lp" | "light_purple" => Color::LightPurple.normal(),
"lpb" | "light_purple_bold" => Color::LightPurple.bold(),
"lpu" | "light_purple_underline" => Color::LightPurple.underline(),
"lpi" | "light_purple_italic" => Color::LightPurple.italic(),
"lpd" | "light_purple_dimmed" => Color::LightPurple.dimmed(),
"lpr" | "light_purple_reverse" => Color::LightPurple.reverse(),
"lpbl" | "light_purple_blink" => Color::LightPurple.blink(),
"lpst" | "light_purple_strike" => Color::LightPurple.strikethrough(),
"c" | "cyan" => Color::Cyan.normal(),
"cb" | "cyan_bold" => Color::Cyan.bold(),
"cu" | "cyan_underline" => Color::Cyan.underline(),
"ci" | "cyan_italic" => Color::Cyan.italic(),
"cd" | "cyan_dimmed" => Color::Cyan.dimmed(),
"cr" | "cyan_reverse" => Color::Cyan.reverse(),
"cbl" | "cyan_blink" => Color::Cyan.blink(),
"cst" | "cyan_strike" => Color::Cyan.strikethrough(),
"lc" | "light_cyan" => Color::LightCyan.normal(),
"lcb" | "light_cyan_bold" => Color::LightCyan.bold(),
"lcu" | "light_cyan_underline" => Color::LightCyan.underline(),
"lci" | "light_cyan_italic" => Color::LightCyan.italic(),
"lcd" | "light_cyan_dimmed" => Color::LightCyan.dimmed(),
"lcr" | "light_cyan_reverse" => Color::LightCyan.reverse(),
"lcbl" | "light_cyan_blink" => Color::LightCyan.blink(),
"lcst" | "light_cyan_strike" => Color::LightCyan.strikethrough(),
"w" | "white" => Color::White.normal(),
"wb" | "white_bold" => Color::White.bold(),
"wu" | "white_underline" => Color::White.underline(),
"wi" | "white_italic" => Color::White.italic(),
"wd" | "white_dimmed" => Color::White.dimmed(),
"wr" | "white_reverse" => Color::White.reverse(),
"wbl" | "white_blink" => Color::White.blink(),
"wst" | "white_strike" => Color::White.strikethrough(),
"dgr" | "dark_gray" => Color::DarkGray.normal(),
"dgrb" | "dark_gray_bold" => Color::DarkGray.bold(),
"dgru" | "dark_gray_underline" => Color::DarkGray.underline(),
"dgri" | "dark_gray_italic" => Color::DarkGray.italic(),
"dgrd" | "dark_gray_dimmed" => Color::DarkGray.dimmed(),
"dgrr" | "dark_gray_reverse" => Color::DarkGray.reverse(),
"dgrbl" | "dark_gray_blink" => Color::DarkGray.blink(),
"dgrst" | "dark_gray_strike" => Color::DarkGray.strikethrough(),
"def" | "default" => Color::Default.normal(),
"defb" | "default_bold" => Color::Default.bold(),
"defu" | "default_underline" => Color::Default.underline(),
"defi" | "default_italic" => Color::Default.italic(),
"defd" | "default_dimmed" => Color::Default.dimmed(),
"defr" | "default_reverse" => Color::Default.reverse(),
// Add xterm 256 colors adding an x prefix where the name conflicts
"xblack" | "xterm_black" => Color::Fixed(0).normal(),
"maroon" | "xterm_maroon" => Color::Fixed(1).normal(),
"xgreen" | "xterm_green" => Color::Fixed(2).normal(),
"olive" | "xterm_olive" => Color::Fixed(3).normal(),
"navy" | "xterm_navy" => Color::Fixed(4).normal(),
"xpurplea" | "xterm_purplea" => Color::Fixed(5).normal(),
"teal" | "xterm_teal" => Color::Fixed(6).normal(),
"silver" | "xterm_silver" => Color::Fixed(7).normal(),
"grey" | "xterm_grey" => Color::Fixed(8).normal(),
"xred" | "xterm_red" => Color::Fixed(9).normal(),
"lime" | "xterm_lime" => Color::Fixed(10).normal(),
"xyellow" | "xterm_yellow" => Color::Fixed(11).normal(),
"xblue" | "xterm_blue" => Color::Fixed(12).normal(),
"fuchsia" | "xterm_fuchsia" => Color::Fixed(13).normal(),
"aqua" | "xterm_aqua" => Color::Fixed(14).normal(),
"xwhite" | "xterm_white" => Color::Fixed(15).normal(),
"grey0" | "xterm_grey0" => Color::Fixed(16).normal(),
"navyblue" | "xterm_navyblue" => Color::Fixed(17).normal(),
"darkblue" | "xterm_darkblue" => Color::Fixed(18).normal(),
"blue3a" | "xterm_blue3a" => Color::Fixed(19).normal(),
"blue3b" | "xterm_blue3b" => Color::Fixed(20).normal(),
"blue1" | "xterm_blue1" => Color::Fixed(21).normal(),
"darkgreen" | "xterm_darkgreen" => Color::Fixed(22).normal(),
"deepskyblue4a" | "xterm_deepskyblue4a" => Color::Fixed(23).normal(),
"deepskyblue4b" | "xterm_deepskyblue4b" => Color::Fixed(24).normal(),
"deepskyblue4c" | "xterm_deepskyblue4c" => Color::Fixed(25).normal(),
"dodgerblue3" | "xterm_dodgerblue3" => Color::Fixed(26).normal(),
"dodgerblue2" | "xterm_dodgerblue2" => Color::Fixed(27).normal(),
"green4" | "xterm_green4" => Color::Fixed(28).normal(),
"springgreen4" | "xterm_springgreen4" => Color::Fixed(29).normal(),
"turquoise4" | "xterm_turquoise4" => Color::Fixed(30).normal(),
"deepskyblue3a" | "xterm_deepskyblue3a" => Color::Fixed(31).normal(),
"deepskyblue3b" | "xterm_deepskyblue3b" => Color::Fixed(32).normal(),
"dodgerblue1" | "xterm_dodgerblue1" => Color::Fixed(33).normal(),
"green3a" | "xterm_green3a" => Color::Fixed(34).normal(),
"springgreen3a" | "xterm_springgreen3a" => Color::Fixed(35).normal(),
"darkcyan" | "xterm_darkcyan" => Color::Fixed(36).normal(),
"lightseagreen" | "xterm_lightseagreen" => Color::Fixed(37).normal(),
"deepskyblue2" | "xterm_deepskyblue2" => Color::Fixed(38).normal(),
"deepskyblue1" | "xterm_deepskyblue1" => Color::Fixed(39).normal(),
"green3b" | "xterm_green3b" => Color::Fixed(40).normal(),
"springgreen3b" | "xterm_springgreen3b" => Color::Fixed(41).normal(),
"springgreen2a" | "xterm_springgreen2a" => Color::Fixed(42).normal(),
"cyan3" | "xterm_cyan3" => Color::Fixed(43).normal(),
"darkturquoise" | "xterm_darkturquoise" => Color::Fixed(44).normal(),
"turquoise2" | "xterm_turquoise2" => Color::Fixed(45).normal(),
"green1" | "xterm_green1" => Color::Fixed(46).normal(),
"springgreen2b" | "xterm_springgreen2b" => Color::Fixed(47).normal(),
"springgreen1" | "xterm_springgreen1" => Color::Fixed(48).normal(),
"mediumspringgreen" | "xterm_mediumspringgreen" => Color::Fixed(49).normal(),
"cyan2" | "xterm_cyan2" => Color::Fixed(50).normal(),
"cyan1" | "xterm_cyan1" => Color::Fixed(51).normal(),
"darkreda" | "xterm_darkreda" => Color::Fixed(52).normal(),
"deeppink4a" | "xterm_deeppink4a" => Color::Fixed(53).normal(),
"purple4a" | "xterm_purple4a" => Color::Fixed(54).normal(),
"purple4b" | "xterm_purple4b" => Color::Fixed(55).normal(),
"purple3" | "xterm_purple3" => Color::Fixed(56).normal(),
"blueviolet" | "xterm_blueviolet" => Color::Fixed(57).normal(),
"orange4a" | "xterm_orange4a" => Color::Fixed(58).normal(),
"grey37" | "xterm_grey37" => Color::Fixed(59).normal(),
"mediumpurple4" | "xterm_mediumpurple4" => Color::Fixed(60).normal(),
"slateblue3a" | "xterm_slateblue3a" => Color::Fixed(61).normal(),
"slateblue3b" | "xterm_slateblue3b" => Color::Fixed(62).normal(),
"royalblue1" | "xterm_royalblue1" => Color::Fixed(63).normal(),
"chartreuse4" | "xterm_chartreuse4" => Color::Fixed(64).normal(),
"darkseagreen4a" | "xterm_darkseagreen4a" => Color::Fixed(65).normal(),
"paleturquoise4" | "xterm_paleturquoise4" => Color::Fixed(66).normal(),
"steelblue" | "xterm_steelblue" => Color::Fixed(67).normal(),
"steelblue3" | "xterm_steelblue3" => Color::Fixed(68).normal(),
"cornflowerblue" | "xterm_cornflowerblue" => Color::Fixed(69).normal(),
"chartreuse3a" | "xterm_chartreuse3a" => Color::Fixed(70).normal(),
"darkseagreen4b" | "xterm_darkseagreen4b" => Color::Fixed(71).normal(),
"cadetbluea" | "xterm_cadetbluea" => Color::Fixed(72).normal(),
"cadetblueb" | "xterm_cadetblueb" => Color::Fixed(73).normal(),
"skyblue3" | "xterm_skyblue3" => Color::Fixed(74).normal(),
"steelblue1a" | "xterm_steelblue1a" => Color::Fixed(75).normal(),
"chartreuse3b" | "xterm_chartreuse3b" => Color::Fixed(76).normal(),
"palegreen3a" | "xterm_palegreen3a" => Color::Fixed(77).normal(),
"seagreen3" | "xterm_seagreen3" => Color::Fixed(78).normal(),
"aquamarine3" | "xterm_aquamarine3" => Color::Fixed(79).normal(),
"mediumturquoise" | "xterm_mediumturquoise" => Color::Fixed(80).normal(),
"steelblue1b" | "xterm_steelblue1b" => Color::Fixed(81).normal(),
"chartreuse2a" | "xterm_chartreuse2a" => Color::Fixed(82).normal(),
"seagreen2" | "xterm_seagreen2" => Color::Fixed(83).normal(),
"seagreen1a" | "xterm_seagreen1a" => Color::Fixed(84).normal(),
"seagreen1b" | "xterm_seagreen1b" => Color::Fixed(85).normal(),
"aquamarine1a" | "xterm_aquamarine1a" => Color::Fixed(86).normal(),
"darkslategray2" | "xterm_darkslategray2" => Color::Fixed(87).normal(),
"darkredb" | "xterm_darkredb" => Color::Fixed(88).normal(),
"deeppink4b" | "xterm_deeppink4b" => Color::Fixed(89).normal(),
"darkmagentaa" | "xterm_darkmagentaa" => Color::Fixed(90).normal(),
"darkmagentab" | "xterm_darkmagentab" => Color::Fixed(91).normal(),
"darkvioleta" | "xterm_darkvioleta" => Color::Fixed(92).normal(),
"xpurpleb" | "xterm_purpleb" => Color::Fixed(93).normal(),
"orange4b" | "xterm_orange4b" => Color::Fixed(94).normal(),
"lightpink4" | "xterm_lightpink4" => Color::Fixed(95).normal(),
"plum4" | "xterm_plum4" => Color::Fixed(96).normal(),
"mediumpurple3a" | "xterm_mediumpurple3a" => Color::Fixed(97).normal(),
"mediumpurple3b" | "xterm_mediumpurple3b" => Color::Fixed(98).normal(),
"slateblue1" | "xterm_slateblue1" => Color::Fixed(99).normal(),
"yellow4a" | "xterm_yellow4a" => Color::Fixed(100).normal(),
"wheat4" | "xterm_wheat4" => Color::Fixed(101).normal(),
"grey53" | "xterm_grey53" => Color::Fixed(102).normal(),
"lightslategrey" | "xterm_lightslategrey" => Color::Fixed(103).normal(),
"mediumpurple" | "xterm_mediumpurple" => Color::Fixed(104).normal(),
"lightslateblue" | "xterm_lightslateblue" => Color::Fixed(105).normal(),
"yellow4b" | "xterm_yellow4b" => Color::Fixed(106).normal(),
"darkolivegreen3a" | "xterm_darkolivegreen3a" => Color::Fixed(107).normal(),
"darkseagreen" | "xterm_darkseagreen" => Color::Fixed(108).normal(),
"lightskyblue3a" | "xterm_lightskyblue3a" => Color::Fixed(109).normal(),
"lightskyblue3b" | "xterm_lightskyblue3b" => Color::Fixed(110).normal(),
"skyblue2" | "xterm_skyblue2" => Color::Fixed(111).normal(),
"chartreuse2b" | "xterm_chartreuse2b" => Color::Fixed(112).normal(),
"darkolivegreen3b" | "xterm_darkolivegreen3b" => Color::Fixed(113).normal(),
"palegreen3b" | "xterm_palegreen3b" => Color::Fixed(114).normal(),
"darkseagreen3a" | "xterm_darkseagreen3a" => Color::Fixed(115).normal(),
"darkslategray3" | "xterm_darkslategray3" => Color::Fixed(116).normal(),
"skyblue1" | "xterm_skyblue1" => Color::Fixed(117).normal(),
"chartreuse1" | "xterm_chartreuse1" => Color::Fixed(118).normal(),
"lightgreena" | "xterm_lightgreena" => Color::Fixed(119).normal(),
"lightgreenb" | "xterm_lightgreenb" => Color::Fixed(120).normal(),
"palegreen1a" | "xterm_palegreen1a" => Color::Fixed(121).normal(),
"aquamarine1b" | "xterm_aquamarine1b" => Color::Fixed(122).normal(),
"darkslategray1" | "xterm_darkslategray1" => Color::Fixed(123).normal(),
"red3a" | "xterm_red3a" => Color::Fixed(124).normal(),
"deeppink4c" | "xterm_deeppink4c" => Color::Fixed(125).normal(),
"mediumvioletred" | "xterm_mediumvioletred" => Color::Fixed(126).normal(),
"magenta3" | "xterm_magenta3" => Color::Fixed(127).normal(),
"darkvioletb" | "xterm_darkvioletb" => Color::Fixed(128).normal(),
"purplec" | "xterm_purplec" => Color::Fixed(129).normal(),
"darkorange3a" | "xterm_darkorange3a" => Color::Fixed(130).normal(),
"indianreda" | "xterm_indianreda" => Color::Fixed(131).normal(),
"hotpink3a" | "xterm_hotpink3a" => Color::Fixed(132).normal(),
"mediumorchid3" | "xterm_mediumorchid3" => Color::Fixed(133).normal(),
"mediumorchid" | "xterm_mediumorchid" => Color::Fixed(134).normal(),
"mediumpurple2a" | "xterm_mediumpurple2a" => Color::Fixed(135).normal(),
"darkgoldenrod" | "xterm_darkgoldenrod" => Color::Fixed(136).normal(),
"lightsalmon3a" | "xterm_lightsalmon3a" => Color::Fixed(137).normal(),
"rosybrown" | "xterm_rosybrown" => Color::Fixed(138).normal(),
"grey63" | "xterm_grey63" => Color::Fixed(139).normal(),
"mediumpurple2b" | "xterm_mediumpurple2b" => Color::Fixed(140).normal(),
"mediumpurple1" | "xterm_mediumpurple1" => Color::Fixed(141).normal(),
"gold3a" | "xterm_gold3a" => Color::Fixed(142).normal(),
"darkkhaki" | "xterm_darkkhaki" => Color::Fixed(143).normal(),
"navajowhite3" | "xterm_navajowhite3" => Color::Fixed(144).normal(),
"grey69" | "xterm_grey69" => Color::Fixed(145).normal(),
"lightsteelblue3" | "xterm_lightsteelblue3" => Color::Fixed(146).normal(),
"lightsteelblue" | "xterm_lightsteelblue" => Color::Fixed(147).normal(),
"yellow3a" | "xterm_yellow3a" => Color::Fixed(148).normal(),
"darkolivegreen3c" | "xterm_darkolivegreen3c" => Color::Fixed(149).normal(),
"darkseagreen3b" | "xterm_darkseagreen3b" => Color::Fixed(150).normal(),
"darkseagreen2a" | "xterm_darkseagreen2a" => Color::Fixed(151).normal(),
"lightcyan3" | "xterm_lightcyan3" => Color::Fixed(152).normal(),
"lightskyblue1" | "xterm_lightskyblue1" => Color::Fixed(153).normal(),
"greenyellow" | "xterm_greenyellow" => Color::Fixed(154).normal(),
"darkolivegreen2" | "xterm_darkolivegreen2" => Color::Fixed(155).normal(),
"palegreen1b" | "xterm_palegreen1b" => Color::Fixed(156).normal(),
"darkseagreen2b" | "xterm_darkseagreen2b" => Color::Fixed(157).normal(),
"darkseagreen1a" | "xterm_darkseagreen1a" => Color::Fixed(158).normal(),
"paleturquoise1" | "xterm_paleturquoise1" => Color::Fixed(159).normal(),
"red3b" | "xterm_red3b" => Color::Fixed(160).normal(),
"deeppink3a" | "xterm_deeppink3a" => Color::Fixed(161).normal(),
"deeppink3b" | "xterm_deeppink3b" => Color::Fixed(162).normal(),
"magenta3a" | "xterm_magenta3a" => Color::Fixed(163).normal(),
"magenta3b" | "xterm_magenta3b" => Color::Fixed(164).normal(),
"magenta2a" | "xterm_magenta2a" => Color::Fixed(165).normal(),
"darkorange3b" | "xterm_darkorange3b" => Color::Fixed(166).normal(),
"indianredb" | "xterm_indianredb" => Color::Fixed(167).normal(),
"hotpink3b" | "xterm_hotpink3b" => Color::Fixed(168).normal(),
"hotpink2" | "xterm_hotpink2" => Color::Fixed(169).normal(),
"orchid" | "xterm_orchid" => Color::Fixed(170).normal(),
"mediumorchid1a" | "xterm_mediumorchid1a" => Color::Fixed(171).normal(),
"orange3" | "xterm_orange3" => Color::Fixed(172).normal(),
"lightsalmon3b" | "xterm_lightsalmon3b" => Color::Fixed(173).normal(),
"lightpink3" | "xterm_lightpink3" => Color::Fixed(174).normal(),
"pink3" | "xterm_pink3" => Color::Fixed(175).normal(),
"plum3" | "xterm_plum3" => Color::Fixed(176).normal(),
"violet" | "xterm_violet" => Color::Fixed(177).normal(),
"gold3b" | "xterm_gold3b" => Color::Fixed(178).normal(),
"lightgoldenrod3" | "xterm_lightgoldenrod3" => Color::Fixed(179).normal(),
"tan" | "xterm_tan" => Color::Fixed(180).normal(),
"mistyrose3" | "xterm_mistyrose3" => Color::Fixed(181).normal(),
"thistle3" | "xterm_thistle3" => Color::Fixed(182).normal(),
"plum2" | "xterm_plum2" => Color::Fixed(183).normal(),
"yellow3b" | "xterm_yellow3b" => Color::Fixed(184).normal(),
"khaki3" | "xterm_khaki3" => Color::Fixed(185).normal(),
"lightgoldenrod2" | "xterm_lightgoldenrod2" => Color::Fixed(186).normal(),
"lightyellow3" | "xterm_lightyellow3" => Color::Fixed(187).normal(),
"grey84" | "xterm_grey84" => Color::Fixed(188).normal(),
"lightsteelblue1" | "xterm_lightsteelblue1" => Color::Fixed(189).normal(),
"yellow2" | "xterm_yellow2" => Color::Fixed(190).normal(),
"darkolivegreen1a" | "xterm_darkolivegreen1a" => Color::Fixed(191).normal(),
"darkolivegreen1b" | "xterm_darkolivegreen1b" => Color::Fixed(192).normal(),
"darkseagreen1b" | "xterm_darkseagreen1b" => Color::Fixed(193).normal(),
"honeydew2" | "xterm_honeydew2" => Color::Fixed(194).normal(),
"lightcyan1" | "xterm_lightcyan1" => Color::Fixed(195).normal(),
"red1" | "xterm_red1" => Color::Fixed(196).normal(),
"deeppink2" | "xterm_deeppink2" => Color::Fixed(197).normal(),
"deeppink1a" | "xterm_deeppink1a" => Color::Fixed(198).normal(),
"deeppink1b" | "xterm_deeppink1b" => Color::Fixed(199).normal(),
"magenta2b" | "xterm_magenta2b" => Color::Fixed(200).normal(),
"magenta1" | "xterm_magenta1" => Color::Fixed(201).normal(),
"orangered1" | "xterm_orangered1" => Color::Fixed(202).normal(),
"indianred1a" | "xterm_indianred1a" => Color::Fixed(203).normal(),
"indianred1b" | "xterm_indianred1b" => Color::Fixed(204).normal(),
"hotpinka" | "xterm_hotpinka" => Color::Fixed(205).normal(),
"hotpinkb" | "xterm_hotpinkb" => Color::Fixed(206).normal(),
"mediumorchid1b" | "xterm_mediumorchid1b" => Color::Fixed(207).normal(),
"darkorange" | "xterm_darkorange" => Color::Fixed(208).normal(),
"salmon1" | "xterm_salmon1" => Color::Fixed(209).normal(),
"lightcoral" | "xterm_lightcoral" => Color::Fixed(210).normal(),
"palevioletred1" | "xterm_palevioletred1" => Color::Fixed(211).normal(),
"orchid2" | "xterm_orchid2" => Color::Fixed(212).normal(),
"orchid1" | "xterm_orchid1" => Color::Fixed(213).normal(),
"orange1" | "xterm_orange1" => Color::Fixed(214).normal(),
"sandybrown" | "xterm_sandybrown" => Color::Fixed(215).normal(),
"lightsalmon1" | "xterm_lightsalmon1" => Color::Fixed(216).normal(),
"lightpink1" | "xterm_lightpink1" => Color::Fixed(217).normal(),
"pink1" | "xterm_pink1" => Color::Fixed(218).normal(),
"plum1" | "xterm_plum1" => Color::Fixed(219).normal(),
"gold1" | "xterm_gold1" => Color::Fixed(220).normal(),
"lightgoldenrod2a" | "xterm_lightgoldenrod2a" => Color::Fixed(221).normal(),
"lightgoldenrod2b" | "xterm_lightgoldenrod2b" => Color::Fixed(222).normal(),
"navajowhite1" | "xterm_navajowhite1" => Color::Fixed(223).normal(),
"mistyrose1" | "xterm_mistyrose1" => Color::Fixed(224).normal(),
"thistle1" | "xterm_thistle1" => Color::Fixed(225).normal(),
"yellow1" | "xterm_yellow1" => Color::Fixed(226).normal(),
"lightgoldenrod1" | "xterm_lightgoldenrod1" => Color::Fixed(227).normal(),
"khaki1" | "xterm_khaki1" => Color::Fixed(228).normal(),
"wheat1" | "xterm_wheat1" => Color::Fixed(229).normal(),
"cornsilk1" | "xterm_cornsilk1" => Color::Fixed(230).normal(),
"grey100" | "xterm_grey100" => Color::Fixed(231).normal(),
"grey3" | "xterm_grey3" => Color::Fixed(232).normal(),
"grey7" | "xterm_grey7" => Color::Fixed(233).normal(),
"grey11" | "xterm_grey11" => Color::Fixed(234).normal(),
"grey15" | "xterm_grey15" => Color::Fixed(235).normal(),
"grey19" | "xterm_grey19" => Color::Fixed(236).normal(),
"grey23" | "xterm_grey23" => Color::Fixed(237).normal(),
"grey27" | "xterm_grey27" => Color::Fixed(238).normal(),
"grey30" | "xterm_grey30" => Color::Fixed(239).normal(),
"grey35" | "xterm_grey35" => Color::Fixed(240).normal(),
"grey39" | "xterm_grey39" => Color::Fixed(241).normal(),
"grey42" | "xterm_grey42" => Color::Fixed(242).normal(),
"grey46" | "xterm_grey46" => Color::Fixed(243).normal(),
"grey50" | "xterm_grey50" => Color::Fixed(244).normal(),
"grey54" | "xterm_grey54" => Color::Fixed(245).normal(),
"grey58" | "xterm_grey58" => Color::Fixed(246).normal(),
"grey62" | "xterm_grey62" => Color::Fixed(247).normal(),
"grey66" | "xterm_grey66" => Color::Fixed(248).normal(),
"grey70" | "xterm_grey70" => Color::Fixed(249).normal(),
"grey74" | "xterm_grey74" => Color::Fixed(250).normal(),
"grey78" | "xterm_grey78" => Color::Fixed(251).normal(),
"grey82" | "xterm_grey82" => Color::Fixed(252).normal(),
"grey85" | "xterm_grey85" => Color::Fixed(253).normal(),
"grey89" | "xterm_grey89" => Color::Fixed(254).normal(),
"grey93" | "xterm_grey93" => Color::Fixed(255).normal(),
_ => Color::White.normal(),
}
}
pub fn lookup_color(s: &str) -> Option<Color> {
let color = match s {
"g" | "green" => Color::Green,
"lg" | "light_green" => Color::LightGreen,
"r" | "red" => Color::Red,
"lr" | "light_red" => Color::LightRed,
"u" | "blue" => Color::Blue,
"lu" | "light_blue" => Color::LightBlue,
"b" | "black" => Color::Black,
"ligr" | "light_gray" => Color::LightGray,
"y" | "yellow" => Color::Yellow,
"ly" | "light_yellow" => Color::LightYellow,
"p" | "purple" => Color::Purple,
"lp" | "light_purple" => Color::LightPurple,
"c" | "cyan" => Color::Cyan,
"lc" | "light_cyan" => Color::LightCyan,
"w" | "white" => Color::White,
"dgr" | "dark_gray" => Color::DarkGray,
"def" | "default" => Color::Default,
_ => return None,
};
Some(color)
}
fn fill_modifiers(attrs: &str, style: &mut Style) {
// setup the attributes available in nu_ansi_term::Style
//
// since we can combine styles like bold-italic, iterate through the chars
// and set the bools for later use in the nu_ansi_term::Style application
for ch in attrs.to_lowercase().chars() {
match ch {
'l' => style.is_blink = true,
'b' => style.is_bold = true,
'd' => style.is_dimmed = true,
'h' => style.is_hidden = true,
'i' => style.is_italic = true,
'r' => style.is_reverse = true,
's' => style.is_strikethrough = true,
'u' => style.is_underline = true,
'n' => (),
_ => (),
}
}
}
fn lookup_color_str(s: &str) -> Option<Color> {
if s.starts_with('#') {
color_from_hex(s).ok().and_then(|c| c)
} else {
lookup_color(s)
}
}

View File

@ -1,56 +1,45 @@
use crate::{color_config::lookup_ansi_color_style, color_record_to_nustyle}; use crate::color_config::lookup_ansi_color_style;
use nu_ansi_term::{Color, Style}; use nu_ansi_term::{Color, Style};
use nu_protocol::{Config, Value}; use nu_protocol::Config;
// The default colors for shapes, used when there is no config for them.
pub fn default_shape_color(shape: String) -> Style {
match shape.as_ref() {
"shape_and" => Style::new().fg(Color::Purple).bold(),
"shape_binary" => Style::new().fg(Color::Purple).bold(),
"shape_block" => Style::new().fg(Color::Blue).bold(),
"shape_bool" => Style::new().fg(Color::LightCyan),
"shape_custom" => Style::new().fg(Color::Green),
"shape_datetime" => Style::new().fg(Color::Cyan).bold(),
"shape_directory" => Style::new().fg(Color::Cyan),
"shape_external" => Style::new().fg(Color::Cyan),
"shape_externalarg" => Style::new().fg(Color::Green).bold(),
"shape_filepath" => Style::new().fg(Color::Cyan),
"shape_flag" => Style::new().fg(Color::Blue).bold(),
"shape_float" => Style::new().fg(Color::Purple).bold(),
"shape_garbage" => Style::new().fg(Color::White).on(Color::Red).bold(),
"shape_globpattern" => Style::new().fg(Color::Cyan).bold(),
"shape_int" => Style::new().fg(Color::Purple).bold(),
"shape_internalcall" => Style::new().fg(Color::Cyan).bold(),
"shape_list" => Style::new().fg(Color::Cyan).bold(),
"shape_literal" => Style::new().fg(Color::Blue),
"shape_nothing" => Style::new().fg(Color::LightCyan),
"shape_operator" => Style::new().fg(Color::Yellow),
"shape_or" => Style::new().fg(Color::Purple).bold(),
"shape_pipe" => Style::new().fg(Color::Purple).bold(),
"shape_range" => Style::new().fg(Color::Yellow).bold(),
"shape_record" => Style::new().fg(Color::Cyan).bold(),
"shape_redirection" => Style::new().fg(Color::Purple).bold(),
"shape_signature" => Style::new().fg(Color::Green).bold(),
"shape_string" => Style::new().fg(Color::Green),
"shape_string_interpolation" => Style::new().fg(Color::Cyan).bold(),
"shape_table" => Style::new().fg(Color::Blue).bold(),
"shape_variable" => Style::new().fg(Color::Purple),
_ => Style::default(),
}
}
pub fn get_shape_color(shape: String, conf: &Config) -> Style { pub fn get_shape_color(shape: String, conf: &Config) -> Style {
match conf.color_config.get(shape.as_str()) { match conf.color_config.get(shape.as_str()) {
Some(int_color) => { Some(int_color) => match int_color.as_string() {
// Shapes do not use color_config closures, currently. Ok(int_color) => lookup_ansi_color_style(&int_color),
match int_color { Err(_) => Style::default(),
Value::Record { .. } => color_record_to_nustyle(int_color), },
Value::String { val, .. } => lookup_ansi_color_style(val), None => match shape.as_ref() {
// Defer to the default in the event of incorrect types being given "shape_garbage" => Style::new().fg(Color::White).on(Color::Red).bold(),
// (i.e. treat null, etc. as the value being unset) "shape_binary" => Style::new().fg(Color::Purple).bold(),
_ => default_shape_color(shape), "shape_bool" => Style::new().fg(Color::LightCyan),
} "shape_int" => Style::new().fg(Color::Purple).bold(),
} "shape_float" => Style::new().fg(Color::Purple).bold(),
None => default_shape_color(shape), "shape_range" => Style::new().fg(Color::Yellow).bold(),
"shape_internalcall" => Style::new().fg(Color::Cyan).bold(),
"shape_external" => Style::new().fg(Color::Cyan),
"shape_externalarg" => Style::new().fg(Color::Green).bold(),
"shape_literal" => Style::new().fg(Color::Blue),
"shape_operator" => Style::new().fg(Color::Yellow),
"shape_signature" => Style::new().fg(Color::Green).bold(),
"shape_string" => Style::new().fg(Color::Green),
"shape_string_interpolation" => Style::new().fg(Color::Cyan).bold(),
"shape_datetime" => Style::new().fg(Color::Cyan).bold(),
"shape_list" => Style::new().fg(Color::Cyan).bold(),
"shape_table" => Style::new().fg(Color::Blue).bold(),
"shape_record" => Style::new().fg(Color::Cyan).bold(),
"shape_block" => Style::new().fg(Color::Blue).bold(),
"shape_filepath" => Style::new().fg(Color::Cyan),
"shape_directory" => Style::new().fg(Color::Cyan),
"shape_globpattern" => Style::new().fg(Color::Cyan).bold(),
"shape_variable" => Style::new().fg(Color::Purple),
"shape_flag" => Style::new().fg(Color::Blue).bold(),
"shape_custom" => Style::new().fg(Color::Green),
"shape_pipe" => Style::new().fg(Color::Purple).bold(),
"shape_redirection" => Style::new().fg(Color::Purple).bold(),
"shape_and" => Style::new().fg(Color::Purple).bold(),
"shape_or" => Style::new().fg(Color::Purple).bold(),
"shape_nothing" => Style::new().fg(Color::LightCyan),
_ => Style::default(),
},
} }
} }

View File

@ -1,285 +0,0 @@
use crate::{color_record_to_nustyle, lookup_ansi_color_style, TextStyle};
use nu_ansi_term::{Color, Style};
use nu_engine::eval_block;
use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet},
CliError, IntoPipelineData, Value,
};
use tabled::alignment::AlignmentHorizontal;
use std::{
collections::HashMap,
fmt::{Debug, Formatter, Result},
};
// ComputableStyle represents the valid user style types: a single color value, or a closure which
// takes an input value and produces a color value. The latter represents a value which
// is computed at use-time.
#[derive(Debug, Clone)]
pub enum ComputableStyle {
Static(Style),
Closure(Value),
}
// macro used for adding initial values to the style hashmap
macro_rules! initial {
($a:expr, $b:expr) => {
($a.to_string(), ComputableStyle::Static($b))
};
}
// An alias for the mapping used internally by StyleComputer.
pub type StyleMapping = HashMap<String, ComputableStyle>;
//
// A StyleComputer is an all-in-one way to compute styles. A nu command can
// simply create it with from_config(), and then use it with compute().
// It stores the engine state and stack needed to run closures that
// may be defined as a user style.
//
pub struct StyleComputer<'a> {
engine_state: &'a EngineState,
stack: &'a Stack,
map: StyleMapping,
}
impl<'a> StyleComputer<'a> {
// This is NOT meant to be used in most cases - please use from_config() instead.
// This only exists for testing purposes.
pub fn new(
engine_state: &'a EngineState,
stack: &'a Stack,
map: StyleMapping,
) -> StyleComputer<'a> {
StyleComputer {
engine_state,
stack,
map,
}
}
// The main method. Takes a string name which maps to a color_config style name,
// and a Nu value to pipe into any closures that may have been defined there.
pub fn compute(&self, style_name: &str, value: &Value) -> Style {
match self.map.get(style_name) {
// Static values require no computation.
Some(ComputableStyle::Static(s)) => *s,
// Closures are run here.
Some(ComputableStyle::Closure(Value::Closure {
val: block_id,
captures,
span,
})) => {
let block = self.engine_state.get_block(*block_id).clone();
// Because captures_to_stack() clones, we don't need to use with_env() here
// (contrast with_env() usage in `each` or `do`).
let mut stack = self.stack.captures_to_stack(captures);
// Support 1-argument blocks as well as 0-argument blocks.
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, value.clone());
}
}
// Run the block.
match eval_block(
self.engine_state,
&mut stack,
&block,
value.clone().into_pipeline_data(),
false,
false,
) {
Ok(v) => {
let value = v.into_value(*span);
// These should be the same color data forms supported by color_config.
match value {
Value::Record { .. } => color_record_to_nustyle(&value),
Value::String { val, .. } => lookup_ansi_color_style(&val),
_ => Style::default(),
}
}
// This is basically a copy of nu_cli::report_error(), but that isn't usable due to
// dependencies. While crudely spitting out a bunch of errors like this is not ideal,
// currently hook closure errors behave roughly the same.
Err(e) => {
eprintln!(
"Error: {:?}",
CliError(&e, &StateWorkingSet::new(self.engine_state))
);
Style::default()
}
}
}
// There should be no other kinds of values (due to create_map() in config.rs filtering them out)
// so this is just a fallback.
_ => Style::default(),
}
}
// Used only by the `table` command.
pub fn style_primitive(&self, value: &Value) -> TextStyle {
let s = self.compute(&value.get_type().to_string(), value);
match *value {
Value::Bool { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
Value::Int { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s),
Value::Filesize { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s),
Value::Duration { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s),
Value::Date { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
Value::Range { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
Value::Float { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s),
Value::String { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
Value::Nothing { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
Value::Binary { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
Value::CellPath { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
Value::Record { .. } | Value::List { .. } | Value::Block { .. } => {
TextStyle::with_style(AlignmentHorizontal::Left, s)
}
_ => TextStyle::basic_left(),
}
}
// The main constructor.
pub fn from_config(engine_state: &'a EngineState, stack: &'a Stack) -> StyleComputer<'a> {
let config = engine_state.get_config();
// Create the hashmap
let mut map: StyleMapping = HashMap::from([
initial!("separator", Color::White.normal()),
initial!(
"leading_trailing_space_bg",
Style::default().on(Color::Rgb(128, 128, 128))
),
initial!("header", Color::White.normal()),
initial!("empty", Color::White.normal()),
initial!("bool", Color::White.normal()),
initial!("int", Color::White.normal()),
initial!("filesize", Color::White.normal()),
initial!("duration", Color::White.normal()),
initial!("date", Color::White.normal()),
initial!("range", Color::White.normal()),
initial!("float", Color::White.normal()),
initial!("string", Color::White.normal()),
initial!("nothing", Color::White.normal()),
initial!("binary", Color::White.normal()),
initial!("cellpath", Color::White.normal()),
initial!("row_index", Color::Green.bold()),
initial!("record", Color::White.normal()),
initial!("list", Color::White.normal()),
initial!("block", Color::White.normal()),
initial!("hints", Color::DarkGray.normal()),
]);
for (key, value) in &config.color_config {
match value {
Value::Closure { .. } => {
map.insert(key.to_string(), ComputableStyle::Closure(value.clone()));
}
Value::Record { .. } => {
map.insert(
key.to_string(),
ComputableStyle::Static(color_record_to_nustyle(value)),
);
}
Value::String { val, .. } => {
// update the stylemap with the found key
let color = lookup_ansi_color_style(val.as_str());
if let Some(v) = map.get_mut(key) {
*v = ComputableStyle::Static(color);
} else {
map.insert(key.to_string(), ComputableStyle::Static(color));
}
}
// This should never occur.
_ => (),
}
}
StyleComputer::new(engine_state, stack, map)
}
}
// Because EngineState doesn't have Debug (Dec 2022),
// this incomplete representation must be used.
impl<'a> Debug for StyleComputer<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
f.debug_struct("StyleComputer")
.field("map", &self.map)
.finish()
}
}
#[test]
fn test_computable_style_static() {
use nu_protocol::Span;
let style1 = Style::default().italic();
let style2 = Style::default().underline();
// Create a "dummy" style_computer for this test.
let dummy_engine_state = EngineState::new();
let dummy_stack = Stack::new();
let style_computer = StyleComputer::new(
&dummy_engine_state,
&dummy_stack,
HashMap::from([
("string".into(), ComputableStyle::Static(style1)),
("row_index".into(), ComputableStyle::Static(style2)),
]),
);
assert_eq!(
style_computer.compute("string", &Value::nothing(Span::unknown())),
style1
);
assert_eq!(
style_computer.compute("row_index", &Value::nothing(Span::unknown())),
style2
);
}
// Because each closure currently runs in a separate environment, checks that the closures have run
// must use the filesystem.
#[test]
fn test_computable_style_closure_basic() {
use nu_test_support::{nu, nu_repl_code, playground::Playground};
Playground::setup("computable_style_closure_basic", |dirs, _| {
let inp = [
r#"let-env config = {
color_config: {
string: {|e| touch ($e + '.obj'); 'red' }
}
};"#,
"[bell book candle] | table | ignore",
"ls | get name | to nuon",
];
let actual_repl = nu!(cwd: dirs.test(), nu_repl_code(&inp));
assert_eq!(actual_repl.err, "");
assert_eq!(actual_repl.out, "[bell.obj, book.obj, candle.obj]");
});
}
#[test]
fn test_computable_style_closure_errors() {
use nu_test_support::{nu, nu_repl_code};
let inp = [
r#"let-env config = {
color_config: {
string: {|e| $e + 2 }
}
};"#,
"[bell] | table",
];
let actual_repl = nu!(cwd: ".", nu_repl_code(&inp));
// Check that the error was printed
assert!(actual_repl.err.contains("type mismatch for operator"));
// Check that the value was printed
assert!(actual_repl.out.contains("bell"));
}

View File

@ -1,118 +1,112 @@
[package] [package]
authors = ["The Nushell Project Developers"] authors = ["The Nushell Project Developers"]
build = "build.rs"
description = "Nushell's built-in commands" description = "Nushell's built-in commands"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-command" name = "nu-command"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command" version = "0.72.1"
version = "0.76.0" build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
bench = false
[dependencies] [dependencies]
nu-color-config = { path = "../nu-color-config", version = "0.72.1" }
nu-engine = { path = "../nu-engine", version = "0.72.1" }
nu-glob = { path = "../nu-glob", version = "0.72.1" }
nu-json = { path = "../nu-json", version = "0.72.1" }
nu-parser = { path = "../nu-parser", version = "0.72.1" }
nu-path = { path = "../nu-path", version = "0.72.1" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.72.1" }
nu-protocol = { path = "../nu-protocol", version = "0.72.1" }
nu-system = { path = "../nu-system", version = "0.72.1" }
nu-table = { path = "../nu-table", version = "0.72.1" }
nu-term-grid = { path = "../nu-term-grid", version = "0.72.1" }
nu-utils = { path = "../nu-utils", version = "0.72.1" }
nu-ansi-term = "0.46.0" nu-ansi-term = "0.46.0"
nu-color-config = { path = "../nu-color-config", version = "0.76.0" }
nu-engine = { path = "../nu-engine", version = "0.76.0" }
nu-explore = { path = "../nu-explore", version = "0.76.0" }
nu-glob = { path = "../nu-glob", version = "0.76.0" }
nu-json = { path = "../nu-json", version = "0.76.0" }
nu-parser = { path = "../nu-parser", version = "0.76.0" }
nu-path = { path = "../nu-path", version = "0.76.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.76.0" }
nu-protocol = { path = "../nu-protocol", version = "0.76.0" }
nu-system = { path = "../nu-system", version = "0.76.0" }
nu-table = { path = "../nu-table", version = "0.76.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.76.0" }
nu-utils = { path = "../nu-utils", version = "0.76.0" }
num-format = { version = "0.4.3" } num-format = { version = "0.4.3" }
# Potential dependencies for extras # Potential dependencies for extras
Inflector = "0.11"
alphanumeric-sort = "1.4.4" alphanumeric-sort = "1.4.4"
atty = "0.2.14" base64 = "0.13.0"
base64 = "0.21.0"
byteorder = "1.4.3" byteorder = "1.4.3"
bytesize = "1.1.0" bytesize = "1.1.0"
calamine = "0.19.1" calamine = "0.18.0"
chrono = { version = "0.4.23", features = ["std", "unstable-locales"], default-features = false } chrono = { version = "0.4.23", features = ["unstable-locales", "std"], default-features = false }
chrono-humanize = "0.2.1" chrono-humanize = "0.2.1"
chrono-tz = "0.8.1" chrono-tz = "0.6.3"
crossterm = "0.24.0" crossterm = "0.24.0"
csv = "1.1.6" csv = "1.1.6"
dialoguer = { default-features = false, version = "0.10.3" } dialoguer = { default-features = false, version = "0.9.0" }
digest = { default-features = false, version = "0.10.0" } digest = { default-features = false, version = "0.10.0" }
dtparse = "1.2.0" dtparse = "1.2.0"
eml-parser = "0.1.0"
encoding_rs = "0.8.30" encoding_rs = "0.8.30"
fancy-regex = "0.11.0" fancy-regex = "0.10.0"
filesize = "0.2.0" filesize = "0.2.0"
filetime = "0.2.15" filetime = "0.2.15"
fs_extra = "1.3.0" fs_extra = "1.2.0"
htmlescape = "0.3.1" htmlescape = "0.3.1"
ical = "0.7.0"
indexmap = { version="1.7", features=["serde-1"] } indexmap = { version="1.7", features=["serde-1"] }
indicatif = "0.17.2" Inflector = "0.11"
is-root = "0.1.2" is-root = "0.1.2"
itertools = "0.10.0" itertools = "0.10.0"
lazy_static = "1.4.0"
log = "0.4.14" log = "0.4.14"
lscolors = { version = "0.12.0", features = ["crossterm"], default-features = false } lscolors = { version = "0.12.0", features = ["crossterm"], default-features = false }
md5 = { package = "md-5", version = "0.10.0" } md5 = { package = "md-5", version = "0.10.0" }
meval = "0.2.0"
mime = "0.3.16" mime = "0.3.16"
mime_guess = "2.0.4"
notify = "4.0.17" notify = "4.0.17"
num = { version = "0.4.0", optional = true } num = { version = "0.4.0", optional = true }
num-traits = "0.2.14" num-traits = "0.2.14"
once_cell = "1.17" once_cell = "1.0"
open = "3.2.0"
pathdiff = "0.2.1" pathdiff = "0.2.1"
powierza-coefficient = "1.0.2" powierza-coefficient = "1.0.1"
quick-xml = "0.27" quick-xml = "0.23.0"
rand = "0.8" rand = "0.8"
rayon = "1.6.1" rayon = "1.5.1"
regex = "1.7.1"
reqwest = {version = "0.11", features = ["blocking", "json"] } reqwest = {version = "0.11", features = ["blocking", "json"] }
roxmltree = "0.18.0" roxmltree = "0.14.0"
rust-embed = "6.3.0" rust-embed = "6.3.0"
same-file = "1.0.6" same-file = "1.0.6"
serde = { version="1.0.123", features=["derive"] } serde = { version="1.0.123", features=["derive"] }
serde_ini = "0.2.0"
serde_urlencoded = "0.7.0" serde_urlencoded = "0.7.0"
serde_yaml = "0.9.4" serde_yaml = "0.9.4"
sha2 = "0.10.0" sha2 = "0.10.0"
# Disable default features b/c the default features build Git (very slow to compile) # Disable default features b/c the default features build Git (very slow to compile)
percent-encoding = "2.2.0" shadow-rs = { version = "0.16.1", default-features = false }
reedline = { version = "0.16.0", features = ["bashisms", "sqlite"] } sysinfo = "0.26.2"
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
shadow-rs = { version = "0.20.0", default-features = false }
sqlparser = { version = "0.30.0", features = ["serde"], optional = true }
sysinfo = "0.28.0"
tabled = "0.10.0"
terminal_size = "0.2.1" terminal_size = "0.2.1"
thiserror = "1.0.31" thiserror = "1.0.31"
titlecase = "2.0.0" titlecase = "2.0.0"
toml = "0.7.1" toml = "0.5.8"
unicode-segmentation = "1.10.0" unicode-segmentation = "1.8.0"
unicode-width = "0.1.10"
url = "2.2.1" url = "2.2.1"
uuid = { version = "1.2.2", features = ["v4"] } uuid = { version = "1.1.2", features = ["v4"] }
which = { version = "4.3.0", optional = true }
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
wax = { version = "0.5.0" } wax = { version = "0.5.0" }
which = { version = "4.4.0", optional = true } rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
print-positions = "0.6.1" sqlparser = { version = "0.23.0", features = ["serde"], optional = true }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winreg = "0.11.0" winreg = "0.10.1"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2"
umask = "2.0.0" umask = "2.0.0"
users = "0.11.0" users = "0.11.0"
libc = "0.2"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
version = "3.0.0"
optional = true optional = true
version = "3.0.1"
[dependencies.polars] [dependencies.polars]
version = "0.25.0"
optional = true
features = [ features = [
"arg_where", "arg_where",
"checked_arithmetic", "checked_arithmetic",
@ -121,9 +115,9 @@ features = [
"csv-file", "csv-file",
"cum_agg", "cum_agg",
"default", "default",
"dtype-categorical",
"dtype-datetime", "dtype-datetime",
"dtype-struct", "dtype-struct",
"dtype-categorical",
"dynamic_groupby", "dynamic_groupby",
"ipc", "ipc",
"is_in", "is_in",
@ -140,29 +134,31 @@ features = [
"strings", "strings",
"to_dummies", "to_dummies",
] ]
optional = true
version = "0.26.1"
[target.'cfg(windows)'.dependencies.windows] [target.'cfg(windows)'.dependencies.windows]
features = ["Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_SystemServices"] version = "0.43.0"
version = "0.44.0" features = [
"Win32_Foundation",
"Win32_Storage_FileSystem",
"Win32_System_SystemServices",
]
[features] [features]
dataframe = ["num", "polars", "sqlparser"]
plugin = ["nu-parser/plugin"]
sqlite = ["rusqlite"] # TODO: given that rusqlite is included in reedline, should we just always include it?
trash-support = ["trash"] trash-support = ["trash"]
which-support = ["which"] which-support = ["which"]
plugin = ["nu-parser/plugin"]
dataframe = ["polars", "num", "sqlparser"]
sqlite = ["rusqlite"] # TODO: given that rusqlite is included in reedline, should we just always include it?
[build-dependencies] [build-dependencies]
shadow-rs = { version = "0.20.0", default-features = false } shadow-rs = { version = "0.16.1", default-features = false }
[dev-dependencies] [dev-dependencies]
nu-test-support = { path = "../nu-test-support", version = "0.76.0" } nu-test-support = { path = "../nu-test-support", version = "0.72.1" }
dirs-next = "2.0.0"
hamcrest2 = "0.3.0" hamcrest2 = "0.3.0"
proptest = "1.1.0" dirs-next = "2.0.0"
proptest = "1.0.0"
quickcheck = "1.0.3" quickcheck = "1.0.3"
quickcheck_macros = "1.0.0" quickcheck_macros = "1.0.0"
rstest = { version = "0.16.0", default-features = false } rstest = {version = "0.15.0", default-features = false}

View File

@ -4,7 +4,7 @@ fn main() -> shadow_rs::SdResult<()> {
// Look up the current Git commit ourselves instead of relying on shadow_rs, // Look up the current Git commit ourselves instead of relying on shadow_rs,
// because shadow_rs does it in a really slow-to-compile way (it builds libgit2) // because shadow_rs does it in a really slow-to-compile way (it builds libgit2)
let hash = get_git_hash().unwrap_or_default(); let hash = get_git_hash().unwrap_or_default();
println!("cargo:rustc-env=NU_COMMIT_HASH={hash}"); println!("cargo:rustc-env=NU_COMMIT_HASH={}", hash);
shadow_rs::new() shadow_rs::new()
} }

View File

@ -39,14 +39,10 @@ impl Command for SubCommand {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
let target: i64 = call.req(engine_state, stack, 0)?; let target: i64 = call.req(engine_state, stack, 0)?;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, target, head), move |value| operate(value, target, head),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -58,7 +54,10 @@ impl Command for SubCommand {
Example { Example {
description: "Apply bits and to two numbers", description: "Apply bits and to two numbers",
example: "2 | bits and 2", example: "2 | bits and 2",
result: Some(Value::test_int(2)), result: Some(Value::Int {
val: 2,
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Apply logical and to a list of numbers", description: "Apply logical and to a list of numbers",
@ -78,15 +77,13 @@ fn operate(value: Value, target: i64, head: Span) -> Value {
val: val & target, val: val & target,
span, span,
}, },
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"integer".into(), format!(
other.get_type().to_string(), "Only integer values are supported, input type: {:?}",
head, other.get_type()
// This line requires the Value::Error match above. ),
other.expect_span(), other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -2,7 +2,7 @@ use nu_engine::get_full_help;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value, Category, IntoPipelineData, PipelineData, Signature, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -14,26 +14,20 @@ impl Command for Bits {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("bits") Signature::build("bits").category(Category::Bits)
.category(Category::Bits)
.input_output_types(vec![(Type::Nothing, Type::String)])
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Various commands for working with bits" "Various commands for working with bits"
} }
fn extra_usage(&self) -> &str {
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(Value::String { Ok(Value::String {
val: get_full_help( val: get_full_help(
&Bits.signature(), &Bits.signature(),
@ -47,3 +41,15 @@ impl Command for Bits {
.into_pipeline_data()) .into_pipeline_data())
} }
} }
#[cfg(test)]
mod test {
use crate::Bits;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Bits {})
}
}

View File

@ -46,7 +46,7 @@ impl Command for SubCommand {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
let signed = call.has_flag("signed"); let signed = call.has_flag("signed");
let number_bytes: Option<Spanned<String>> = let number_bytes: Option<Spanned<String>> =
@ -56,17 +56,11 @@ impl Command for SubCommand {
if let Some(val) = number_bytes { if let Some(val) = number_bytes {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(), "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
"value originates from here".to_string(),
head,
val.span, val.span,
)); ));
} }
} }
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, head, signed, bytes_len), move |value| operate(value, head, signed, bytes_len),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -146,17 +140,14 @@ fn operate(value: Value, head: Span, signed: bool, number_size: NumberBytes) ->
Value::Int { val: out_val, span } Value::Int { val: out_val, span }
} }
} }
other => match other { other => Value::Error {
// Propagate errors inside the value error: ShellError::UnsupportedInput(
Value::Error { .. } => other, format!(
_ => Value::Error { "Only numerical values are supported, input type: {:?}",
error: ShellError::OnlySupportsThisInputType( other.get_type()
"numeric".into(), ),
other.get_type().to_string(), other.span().unwrap_or(head),
head,
other.expect_span(),
), ),
},
}, },
} }
} }

View File

@ -39,14 +39,10 @@ impl Command for SubCommand {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
let target: i64 = call.req(engine_state, stack, 0)?; let target: i64 = call.req(engine_state, stack, 0)?;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, target, head), move |value| operate(value, target, head),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -58,7 +54,10 @@ impl Command for SubCommand {
Example { Example {
description: "Apply bits or to two numbers", description: "Apply bits or to two numbers",
example: "2 | bits or 6", example: "2 | bits or 6",
result: Some(Value::test_int(6)), result: Some(Value::Int {
val: 6,
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Apply logical or to a list of numbers", description: "Apply logical or to a list of numbers",
@ -78,15 +77,13 @@ fn operate(value: Value, target: i64, head: Span) -> Value {
val: val | target, val: val | target,
span, span,
}, },
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"integer".into(), format!(
other.get_type().to_string(), "Only integer values are supported, input type: {:?}",
head, other.get_type()
// This line requires the Value::Error match above. ),
other.expect_span(), other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -49,7 +49,7 @@ impl Command for SubCommand {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
let bits: usize = call.req(engine_state, stack, 0)?; let bits: usize = call.req(engine_state, stack, 0)?;
let signed = call.has_flag("signed"); let signed = call.has_flag("signed");
@ -60,16 +60,11 @@ impl Command for SubCommand {
if let Some(val) = number_bytes { if let Some(val) = number_bytes {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(), "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
"value originates from here".to_string(),
head,
val.span, val.span,
)); ));
} }
} }
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, bits, head, signed, bytes_len), move |value| operate(value, bits, head, signed, bytes_len),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -81,7 +76,10 @@ impl Command for SubCommand {
Example { Example {
description: "Rotate left a number with 2 bits", description: "Rotate left a number with 2 bits",
example: "17 | bits rol 2", example: "17 | bits rol 2",
result: Some(Value::test_int(68)), result: Some(Value::Int {
val: 68,
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Rotate left a list of numbers with 2 bits", description: "Rotate left a list of numbers with 2 bits",
@ -106,7 +104,8 @@ where
error: ShellError::GenericError( error: ShellError::GenericError(
"Rotate left result beyond the range of 64 bit signed number".to_string(), "Rotate left result beyond the range of 64 bit signed number".to_string(),
format!( format!(
"{val} of the specified number of bytes rotate left {bits} bits exceed limit" "{} of the specified number of bytes rotate left {} bits exceed limit",
val, bits
), ),
Some(span), Some(span),
None, None,
@ -131,18 +130,16 @@ fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: Num
SignedOne => get_rotate_left(val as i8, bits, span), SignedOne => get_rotate_left(val as i8, bits, span),
SignedTwo => get_rotate_left(val as i16, bits, span), SignedTwo => get_rotate_left(val as i16, bits, span),
SignedFour => get_rotate_left(val as i32, bits, span), SignedFour => get_rotate_left(val as i32, bits, span),
SignedEight => get_rotate_left(val, bits, span), SignedEight => get_rotate_left(val as i64, bits, span),
} }
} }
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"integer".into(), format!(
other.get_type().to_string(), "Only integer values are supported, input type: {:?}",
head, other.get_type()
// This line requires the Value::Error match above. ),
other.expect_span(), other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -49,7 +49,7 @@ impl Command for SubCommand {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
let bits: usize = call.req(engine_state, stack, 0)?; let bits: usize = call.req(engine_state, stack, 0)?;
let signed = call.has_flag("signed"); let signed = call.has_flag("signed");
@ -60,16 +60,11 @@ impl Command for SubCommand {
if let Some(val) = number_bytes { if let Some(val) = number_bytes {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(), "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
"value originates from here".to_string(),
head,
val.span, val.span,
)); ));
} }
} }
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, bits, head, signed, bytes_len), move |value| operate(value, bits, head, signed, bytes_len),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -81,7 +76,10 @@ impl Command for SubCommand {
Example { Example {
description: "Rotate right a number with 60 bits", description: "Rotate right a number with 60 bits",
example: "17 | bits ror 60", example: "17 | bits ror 60",
result: Some(Value::test_int(272)), result: Some(Value::Int {
val: 272,
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Rotate right a list of numbers of one byte", description: "Rotate right a list of numbers of one byte",
@ -110,7 +108,8 @@ where
error: ShellError::GenericError( error: ShellError::GenericError(
"Rotate right result beyond the range of 64 bit signed number".to_string(), "Rotate right result beyond the range of 64 bit signed number".to_string(),
format!( format!(
"{val} of the specified number of bytes rotate right {bits} bits exceed limit" "{} of the specified number of bytes rotate right {} bits exceed limit",
val, bits
), ),
Some(span), Some(span),
None, None,
@ -135,18 +134,16 @@ fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: Num
SignedOne => get_rotate_right(val as i8, bits, span), SignedOne => get_rotate_right(val as i8, bits, span),
SignedTwo => get_rotate_right(val as i16, bits, span), SignedTwo => get_rotate_right(val as i16, bits, span),
SignedFour => get_rotate_right(val as i32, bits, span), SignedFour => get_rotate_right(val as i32, bits, span),
SignedEight => get_rotate_right(val, bits, span), SignedEight => get_rotate_right(val as i64, bits, span),
} }
} }
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"integer".into(), format!(
other.get_type().to_string(), "Only integer values are supported, input type: {:?}",
head, other.get_type()
// This line requires the Value::Error match above. ),
other.expect_span(), other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -49,7 +49,7 @@ impl Command for SubCommand {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
let bits: usize = call.req(engine_state, stack, 0)?; let bits: usize = call.req(engine_state, stack, 0)?;
let signed = call.has_flag("signed"); let signed = call.has_flag("signed");
@ -60,16 +60,11 @@ impl Command for SubCommand {
if let Some(val) = number_bytes { if let Some(val) = number_bytes {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(), "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
"value originates from here".to_string(),
head,
val.span, val.span,
)); ));
} }
} }
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, bits, head, signed, bytes_len), move |value| operate(value, bits, head, signed, bytes_len),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -81,17 +76,26 @@ impl Command for SubCommand {
Example { Example {
description: "Shift left a number by 7 bits", description: "Shift left a number by 7 bits",
example: "2 | bits shl 7", example: "2 | bits shl 7",
result: Some(Value::test_int(256)), result: Some(Value::Int {
val: 256,
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Shift left a number with 1 byte by 7 bits", description: "Shift left a number with 1 byte by 7 bits",
example: "2 | bits shl 7 -n 1", example: "2 | bits shl 7 -n 1",
result: Some(Value::test_int(0)), result: Some(Value::Int {
val: 0,
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Shift left a signed number by 1 bit", description: "Shift left a signed number by 1 bit",
example: "0x7F | bits shl 1 -s", example: "0x7F | bits shl 1 -s",
result: Some(Value::test_int(254)), result: Some(Value::Int {
val: 254,
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Shift left a list of numbers", description: "Shift left a list of numbers",
@ -118,7 +122,8 @@ where
error: ShellError::GenericError( error: ShellError::GenericError(
"Shift left result beyond the range of 64 bit signed number".to_string(), "Shift left result beyond the range of 64 bit signed number".to_string(),
format!( format!(
"{val} of the specified number of bytes shift left {bits} bits exceed limit" "{} of the specified number of bytes shift left {} bits exceed limit",
val, bits
), ),
Some(span), Some(span),
None, None,
@ -130,7 +135,10 @@ where
None => Value::Error { None => Value::Error {
error: ShellError::GenericError( error: ShellError::GenericError(
"Shift left failed".to_string(), "Shift left failed".to_string(),
format!("{val} shift left {bits} bits failed, you may shift too many bits"), format!(
"{} shift left {} bits failed, you may shift too many bits",
val, bits
),
Some(span), Some(span),
None, None,
Vec::new(), Vec::new(),
@ -154,18 +162,16 @@ fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: Num
SignedOne => get_shift_left(val as i8, bits, span), SignedOne => get_shift_left(val as i8, bits, span),
SignedTwo => get_shift_left(val as i16, bits, span), SignedTwo => get_shift_left(val as i16, bits, span),
SignedFour => get_shift_left(val as i32, bits, span), SignedFour => get_shift_left(val as i32, bits, span),
SignedEight => get_shift_left(val, bits, span), SignedEight => get_shift_left(val as i64, bits, span),
} }
} }
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"integer".into(), format!(
other.get_type().to_string(), "Only integer values are supported, input type: {:?}",
head, other.get_type()
// This line requires the Value::Error match above. ),
other.expect_span(), other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -49,7 +49,7 @@ impl Command for SubCommand {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
let bits: usize = call.req(engine_state, stack, 0)?; let bits: usize = call.req(engine_state, stack, 0)?;
let signed = call.has_flag("signed"); let signed = call.has_flag("signed");
@ -60,16 +60,11 @@ impl Command for SubCommand {
if let Some(val) = number_bytes { if let Some(val) = number_bytes {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(), "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
"value originates from here".to_string(),
head,
val.span, val.span,
)); ));
} }
} }
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, bits, head, signed, bytes_len), move |value| operate(value, bits, head, signed, bytes_len),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -81,7 +76,10 @@ impl Command for SubCommand {
Example { Example {
description: "Shift right a number with 2 bits", description: "Shift right a number with 2 bits",
example: "8 | bits shr 2", example: "8 | bits shr 2",
result: Some(Value::test_int(2)), result: Some(Value::Int {
val: 2,
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Shift right a list of numbers", description: "Shift right a list of numbers",
@ -108,7 +106,8 @@ where
error: ShellError::GenericError( error: ShellError::GenericError(
"Shift right result beyond the range of 64 bit signed number".to_string(), "Shift right result beyond the range of 64 bit signed number".to_string(),
format!( format!(
"{val} of the specified number of bytes shift right {bits} bits exceed limit" "{} of the specified number of bytes shift right {} bits exceed limit",
val, bits
), ),
Some(span), Some(span),
None, None,
@ -120,7 +119,10 @@ where
None => Value::Error { None => Value::Error {
error: ShellError::GenericError( error: ShellError::GenericError(
"Shift right failed".to_string(), "Shift right failed".to_string(),
format!("{val} shift right {bits} bits failed, you may shift too many bits"), format!(
"{} shift right {} bits failed, you may shift too many bits",
val, bits
),
Some(span), Some(span),
None, None,
Vec::new(), Vec::new(),
@ -144,18 +146,16 @@ fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: Num
SignedOne => get_shift_right(val as i8, bits, span), SignedOne => get_shift_right(val as i8, bits, span),
SignedTwo => get_shift_right(val as i16, bits, span), SignedTwo => get_shift_right(val as i16, bits, span),
SignedFour => get_shift_right(val as i32, bits, span), SignedFour => get_shift_right(val as i32, bits, span),
SignedEight => get_shift_right(val, bits, span), SignedEight => get_shift_right(val as i64, bits, span),
} }
} }
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"integer".into(), format!(
other.get_type().to_string(), "Only integer values are supported, input type: {:?}",
head, other.get_type()
// This line requires the Value::Error match above. ),
other.expect_span(), other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -39,13 +39,10 @@ impl Command for SubCommand {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
let target: i64 = call.req(engine_state, stack, 0)?; let target: i64 = call.req(engine_state, stack, 0)?;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, target, head), move |value| operate(value, target, head),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -57,7 +54,10 @@ impl Command for SubCommand {
Example { Example {
description: "Apply bits xor to two numbers", description: "Apply bits xor to two numbers",
example: "2 | bits xor 2", example: "2 | bits xor 2",
result: Some(Value::test_int(0)), result: Some(Value::Int {
val: 0,
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Apply logical xor to a list of numbers", description: "Apply logical xor to a list of numbers",
@ -77,15 +77,13 @@ fn operate(value: Value, target: i64, head: Span) -> Value {
val: val ^ target, val: val ^ target,
span, span,
}, },
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"integer".into(), format!(
other.get_type().to_string(), "Only integer values are supported, input type: {:?}",
head, other.get_type()
// This line requires the Value::Error match above. ),
other.expect_span(), other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -122,15 +122,13 @@ fn add(val: &Value, args: &Arguments, span: Span) -> Value {
val, val,
span: val_span, span: val_span,
} => add_impl(val, args, *val_span), } => add_impl(val, args, *val_span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"integer".into(), format!(
other.get_type().to_string(), "Input's type is {}. This command only works with bytes.",
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -30,9 +30,7 @@ fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellEr
Value::List { mut vals, span } => { Value::List { mut vals, span } => {
if vals.len() != 2 { if vals.len() != 2 {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"More than two indices in range".to_string(), "More than two indices given".to_string(),
"value originates from here".to_string(),
head,
span, span,
)); ));
} else { } else {
@ -40,14 +38,10 @@ fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellEr
let end = match end { let end = match end {
Value::Int { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(),
Value::String { val, .. } => val, Value::String { val, .. } => val,
// Explicitly propagate errors instead of dropping them.
Value::Error { error } => return Err(error),
other => { other => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Only string or list<int> ranges are supported".into(), "could not perform subbytes. Expecting a string or int".to_string(),
format!("input type: {:?}", other.get_type()), other.span().unwrap_or(head),
head,
other.expect_span(),
)) ))
} }
}; };
@ -55,14 +49,10 @@ fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellEr
let start = match start { let start = match start {
Value::Int { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(),
Value::String { val, .. } => val, Value::String { val, .. } => val,
// Explicitly propagate errors instead of dropping them.
Value::Error { error } => return Err(error),
other => { other => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Only string or list<int> ranges are supported".into(), "could not perform subbytes. Expecting a string or int".to_string(),
format!("input type: {:?}", other.get_type()), other.span().unwrap_or(head),
head,
other.expect_span(),
)) ))
} }
}; };
@ -70,27 +60,21 @@ fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellEr
} }
} }
Value::String { val, span } => { Value::String { val, span } => {
let split_result = val.split_once(','); let splitted_result = val.split_once(',');
match split_result { match splitted_result {
Some((start, end)) => (start.to_string(), end.to_string(), span), Some((start, end)) => (start.to_string(), end.to_string(), span),
None => { None => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(), "could not perform subbytes".to_string(),
"with this range".to_string(),
head,
span, span,
)) ))
} }
} }
} }
// Explicitly propagate errors instead of dropping them.
Value::Error { error } => return Err(error),
other => { other => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(), "could not perform subbytes".to_string(),
"with this range".to_string(), other.span().unwrap_or(head),
head,
other.expect_span(),
)) ))
} }
}; };
@ -98,26 +82,28 @@ fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellEr
let start: isize = if start.is_empty() || start == "_" { let start: isize = if start.is_empty() || start == "_" {
0 0
} else { } else {
start.trim().parse().map_err(|_| { match start.trim().parse() {
ShellError::UnsupportedInput( Ok(s) => s,
Err(_) => {
return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(), "could not perform subbytes".to_string(),
"with this range".to_string(),
head,
span, span,
) ))
})? }
}
}; };
let end: isize = if end.is_empty() || end == "_" { let end: isize = if end.is_empty() || end == "_" {
isize::max_value() isize::max_value()
} else { } else {
end.trim().parse().map_err(|_| { match end.trim().parse() {
ShellError::UnsupportedInput( Ok(s) => s,
Err(_) => {
return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(), "could not perform subbytes".to_string(),
"with this range".to_string(),
head,
span, span,
) ))
})? }
}
}; };
Ok((start, end, span)) Ok((start, end, span))
} }
@ -246,15 +232,13 @@ fn at(val: &Value, args: &Arguments, span: Span) -> Value {
val, val,
span: val_span, span: val_span,
} => at_impl(val, args, *val_span), } => at_impl(val, args, *val_span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"integer".into(), format!(
other.get_type().to_string(), "Input's type is {}. This command only works with bytes.",
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }
@ -278,7 +262,7 @@ fn at_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
match start.cmp(&end) { match start.cmp(&end) {
Ordering::Equal => Value::Binary { val: vec![], span }, Ordering::Equal => Value::Binary { val: vec![], span },
Ordering::Greater => Value::Error { Ordering::Greater => Value::Error {
error: ShellError::TypeMismatch( error: ShellError::UnsupportedInput(
"End must be greater than or equal to Start".to_string(), "End must be greater than or equal to Start".to_string(),
arg.arg_span, arg.arg_span,
), ),

View File

@ -46,18 +46,16 @@ impl Command for BytesBuild {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let mut output = vec![]; let mut output = vec![];
for expr in call.positional_iter() { for expr in call.positional_iter() {
let val = eval_expression(engine_state, stack, expr)?; let val = eval_expression(engine_state, stack, expr)?;
match val { match val {
Value::Binary { mut val, .. } => output.append(&mut val), Value::Binary { mut val, .. } => output.append(&mut val),
// Explicitly propagate errors instead of dropping them.
Value::Error { error } => return Err(error),
other => { other => {
return Err(ShellError::TypeMismatch( return Err(ShellError::UnsupportedInput(
"only binary data arguments are supported".to_string(), "only support expression which yields to binary data".to_string(),
other.expect_span(), other.span().unwrap_or(call.head),
)) ))
} }
} }

View File

@ -2,7 +2,7 @@ use nu_engine::get_full_help;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value, Category, IntoPipelineData, PipelineData, Signature, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -14,26 +14,20 @@ impl Command for Bytes {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("bytes") Signature::build("bytes").category(Category::Bytes)
.category(Category::Bytes)
.input_output_types(vec![(Type::Nothing, Type::String)])
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Various commands for working with byte data" "Various commands for working with byte data"
} }
fn extra_usage(&self) -> &str {
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(Value::String { Ok(Value::String {
val: get_full_help( val: get_full_help(
&Bytes.signature(), &Bytes.signature(),
@ -47,3 +41,15 @@ impl Command for Bytes {
.into_pipeline_data()) .into_pipeline_data())
} }
} }
#[cfg(test)]
mod test {
use crate::Bytes;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Bytes {})
}
}

View File

@ -54,16 +54,14 @@ impl Command for BytesCollect {
output_binary.append(&mut work_sep) output_binary.append(&mut work_sep)
} }
} }
// Explicitly propagate errors instead of dropping them.
Value::Error { error } => return Err(error),
other => { other => {
return Err(ShellError::OnlySupportsThisInputType( return Err(ShellError::UnsupportedInput(
"integer".into(), format!(
other.get_type().to_string(), "The element type is {}, this command only works with bytes.",
call.head, other.get_type()
// This line requires the Value::Error match above. ),
other.expect_span(), other.span().unwrap_or(call.head),
)); ))
} }
} }
} }

View File

@ -68,17 +68,26 @@ impl Command for BytesEndsWith {
Example { Example {
description: "Checks if binary ends with `0x[AA]`", description: "Checks if binary ends with `0x[AA]`",
example: "0x[1F FF AA AA] | bytes ends-with 0x[AA]", example: "0x[1F FF AA AA] | bytes ends-with 0x[AA]",
result: Some(Value::test_bool(true)), result: Some(Value::Bool {
val: true,
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Checks if binary ends with `0x[FF AA AA]`", description: "Checks if binary ends with `0x[FF AA AA]`",
example: "0x[1F FF AA AA] | bytes ends-with 0x[FF AA AA]", example: "0x[1F FF AA AA] | bytes ends-with 0x[FF AA AA]",
result: Some(Value::test_bool(true)), result: Some(Value::Bool {
val: true,
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Checks if binary ends with `0x[11]`", description: "Checks if binary ends with `0x[11]`",
example: "0x[1F FF AA AA] | bytes ends-with 0x[11]", example: "0x[1F FF AA AA] | bytes ends-with 0x[11]",
result: Some(Value::test_bool(false)), result: Some(Value::Bool {
val: false,
span: Span::test_data(),
}),
}, },
] ]
} }
@ -89,16 +98,17 @@ fn ends_with(val: &Value, args: &Arguments, span: Span) -> Value {
Value::Binary { Value::Binary {
val, val,
span: val_span, span: val_span,
} => Value::boolean(val.ends_with(&args.pattern), *val_span), } => Value::Bool {
// Propagate errors by explicitly matching them before the final case. val: val.ends_with(&args.pattern),
Value::Error { .. } => val.clone(), span: *val_span,
},
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"binary".into(), format!(
other.get_type().to_string(), "Input's type is {}. This command only works with bytes.",
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -132,15 +132,13 @@ fn index_of(val: &Value, args: &Arguments, span: Span) -> Value {
val, val,
span: val_span, span: val_span,
} => index_of_impl(val, args, *val_span), } => index_of_impl(val, args, *val_span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"binary".into(), format!(
other.get_type().to_string(), "Input's type is {}. This command only works with bytes.",
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -70,16 +70,17 @@ fn length(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
Value::Binary { Value::Binary {
val, val,
span: val_span, span: val_span,
} => Value::int(val.len() as i64, *val_span), } => Value::Int {
// Propagate errors by explicitly matching them before the final case. val: val.len() as i64,
Value::Error { .. } => val.clone(), span: *val_span,
},
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"binary".into(), format!(
other.get_type().to_string(), "Input's type is {}. This command only works with bytes.",
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -61,7 +61,7 @@ impl Command for BytesRemove {
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?; let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
if pattern_to_remove.item.is_empty() { if pattern_to_remove.item.is_empty() {
return Err(ShellError::TypeMismatch( return Err(ShellError::UnsupportedInput(
"the pattern to remove cannot be empty".to_string(), "the pattern to remove cannot be empty".to_string(),
pattern_to_remove.span, pattern_to_remove.span,
)); ));
@ -139,15 +139,13 @@ fn remove(val: &Value, args: &Arguments, span: Span) -> Value {
val, val,
span: val_span, span: val_span,
} => remove_impl(val, args, *val_span), } => remove_impl(val, args, *val_span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"binary".into(), format!(
other.get_type().to_string(), "Input's type is {}. This command only works with bytes.",
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }
@ -161,7 +159,7 @@ fn remove_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
// Note: // Note:
// remove_all from start and end will generate the same result. // remove_all from start and end will generate the same result.
// so we'll put `remove_all` relative logic into else clause. // so we'll put `remove_all` relative logic into else clouse.
if arg.end && !remove_all { if arg.end && !remove_all {
let (mut left, mut right) = ( let (mut left, mut right) = (
input.len() as isize - arg.pattern.len() as isize, input.len() as isize - arg.pattern.len() as isize,
@ -172,7 +170,7 @@ fn remove_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
left -= 1; left -= 1;
right -= 1; right -= 1;
} }
// append the remaining thing to result, this can be happening when // append the remaining thing to result, this can be happeneed when
// we have something to remove and remove_all is False. // we have something to remove and remove_all is False.
let mut remain = input[..left as usize].iter().copied().rev().collect(); let mut remain = input[..left as usize].iter().copied().rev().collect();
result.append(&mut remain); result.append(&mut remain);
@ -193,7 +191,7 @@ fn remove_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
right += 1; right += 1;
} }
} }
// append the remaining thing to result, this can happened when // append the remaing thing to result, this can happened when
// we have something to remove and remove_all is False. // we have something to remove and remove_all is False.
let mut remain = input[left..].to_vec(); let mut remain = input[left..].to_vec();
result.append(&mut remain); result.append(&mut remain);

View File

@ -61,7 +61,7 @@ impl Command for BytesReplace {
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?; let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
if find.item.is_empty() { if find.item.is_empty() {
return Err(ShellError::TypeMismatch( return Err(ShellError::UnsupportedInput(
"the pattern to find cannot be empty".to_string(), "the pattern to find cannot be empty".to_string(),
find.span, find.span,
)); ));
@ -130,15 +130,13 @@ fn replace(val: &Value, args: &Arguments, span: Span) -> Value {
val, val,
span: val_span, span: val_span,
} => replace_impl(val, args, *val_span), } => replace_impl(val, args, *val_span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"binary".into(), format!(
other.get_type().to_string(), "Input's type is {}. This command only works with bytes.",
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -81,15 +81,13 @@ fn reverse(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
span: *val_span, span: *val_span,
} }
} }
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"binary".into(), format!(
other.get_type().to_string(), "Input's type is {}. This command only works with bytes.",
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -74,17 +74,26 @@ impl Command for BytesStartsWith {
Example { Example {
description: "Checks if binary starts with `0x[1F FF AA]`", description: "Checks if binary starts with `0x[1F FF AA]`",
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F FF AA]", example: "0x[1F FF AA AA] | bytes starts-with 0x[1F FF AA]",
result: Some(Value::test_bool(true)), result: Some(Value::Bool {
val: true,
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Checks if binary starts with `0x[1F]`", description: "Checks if binary starts with `0x[1F]`",
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F]", example: "0x[1F FF AA AA] | bytes starts-with 0x[1F]",
result: Some(Value::test_bool(true)), result: Some(Value::Bool {
val: true,
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Checks if binary starts with `0x[1F]`", description: "Checks if binary starts with `0x[1F]`",
example: "0x[1F FF AA AA] | bytes starts-with 0x[11]", example: "0x[1F FF AA AA] | bytes starts-with 0x[11]",
result: Some(Value::test_bool(false)), result: Some(Value::Bool {
val: false,
span: Span::test_data(),
}),
}, },
] ]
} }
@ -95,16 +104,17 @@ fn starts_with(val: &Value, args: &Arguments, span: Span) -> Value {
Value::Binary { Value::Binary {
val, val,
span: val_span, span: val_span,
} => Value::boolean(val.starts_with(&args.pattern), *val_span), } => Value::Bool {
// Propagate errors by explicitly matching them before the final case. val: val.starts_with(&args.pattern),
Value::Error { .. } => val.clone(), span: *val_span,
},
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"binary".into(), format!(
other.get_type().to_string(), "Input's type is {}. This command only works with bytes.",
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -53,7 +53,7 @@ impl Default for HashableValue {
fn default() -> Self { fn default() -> Self {
HashableValue::Bool { HashableValue::Bool {
val: false, val: false,
span: Span::unknown(), span: Span { start: 0, end: 0 },
} }
} }
} }
@ -78,14 +78,13 @@ impl HashableValue {
Value::String { val, span } => Ok(HashableValue::String { val, span }), Value::String { val, span } => Ok(HashableValue::String { val, span }),
Value::Binary { val, span } => Ok(HashableValue::Binary { val, span }), Value::Binary { val, span } => Ok(HashableValue::Binary { val, span }),
// Explicitly propagate errors instead of dropping them. _ => {
Value::Error { error } => Err(error), let input_span = value.span().unwrap_or(span);
_ => Err(ShellError::UnsupportedInput( Err(ShellError::UnsupportedInput(
"input value is not hashable".into(), format!("input value {value:?} is not hashable"),
format!("input type: {:?}", value.get_type()), input_span,
span, ))
value.expect_span(), }
)),
} }
} }
@ -215,7 +214,7 @@ mod test {
]; ];
for (val, expect_hashable_val) in values.into_iter() { for (val, expect_hashable_val) in values.into_iter() {
assert_eq!( assert_eq!(
HashableValue::from_value(val, Span::unknown()).unwrap(), HashableValue::from_value(val, Span { start: 0, end: 0 }).unwrap(),
expect_hashable_val expect_hashable_val
); );
} }
@ -246,7 +245,7 @@ mod test {
}, },
]; ];
for v in values { for v in values {
assert!(HashableValue::from_value(v, Span::unknown()).is_err()) assert!(HashableValue::from_value(v, Span { start: 0, end: 0 }).is_err())
} }
} }
@ -267,7 +266,7 @@ mod test {
for val in values.into_iter() { for val in values.into_iter() {
let expected_val = val.clone(); let expected_val = val.clone();
assert_eq!( assert_eq!(
HashableValue::from_value(val, Span::unknown()) HashableValue::from_value(val, Span { start: 0, end: 0 })
.unwrap() .unwrap()
.into_value(), .into_value(),
expected_val expected_val
@ -280,11 +279,14 @@ mod test {
assert_eq!( assert_eq!(
HashableValue::Bool { HashableValue::Bool {
val: true, val: true,
span: Span::new(0, 1) span: Span { start: 0, end: 1 }
}, },
HashableValue::Bool { HashableValue::Bool {
val: true, val: true,
span: Span::new(90, 1000) span: Span {
start: 90,
end: 1000
}
} }
) )
} }
@ -297,7 +299,7 @@ mod test {
assert!(set.contains(&HashableValue::Bool { val: true, span })); assert!(set.contains(&HashableValue::Bool { val: true, span }));
// hashable value doesn't care about span. // hashable value doesn't care about span.
let diff_span = Span::new(1, 2); let diff_span = Span { start: 1, end: 2 };
set.insert(HashableValue::Bool { set.insert(HashableValue::Bool {
val: true, val: true,
span: diff_span, span: diff_span,

View File

@ -50,7 +50,7 @@ impl Command for Histogram {
}, },
Example { Example {
description: "Compute a histogram for a list of numbers", description: "Compute a histogram for a list of numbers",
example: "[1 2 1] | histogram", example: "echo [1 2 1] | histogram",
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::Record { vals: vec![Value::Record {
cols: vec!["value".to_string(), "count".to_string(), "quantile".to_string(), "percentage".to_string(), "frequency".to_string()], cols: vec!["value".to_string(), "count".to_string(), "quantile".to_string(), "percentage".to_string(), "frequency".to_string()],
@ -80,7 +80,7 @@ impl Command for Histogram {
}, },
Example { Example {
description: "Compute a histogram for a list of numbers, and percentage is based on the maximum value", description: "Compute a histogram for a list of numbers, and percentage is based on the maximum value",
example: "[1 2 3 1 1 1 2 2 1 1] | histogram --percentage-type relative", example: "echo [1 2 3 1 1 1 2 2 1 1] | histogram --percentage-type relative",
result: None, result: None,
} }
] ]
@ -98,11 +98,12 @@ impl Command for Histogram {
let frequency_name_arg = call.opt::<Spanned<String>>(engine_state, stack, 1)?; let frequency_name_arg = call.opt::<Spanned<String>>(engine_state, stack, 1)?;
let frequency_column_name = match frequency_name_arg { let frequency_column_name = match frequency_name_arg {
Some(inner) => { Some(inner) => {
let span = inner.span;
if ["value", "count", "quantile", "percentage"].contains(&inner.item.as_str()) { if ["value", "count", "quantile", "percentage"].contains(&inner.item.as_str()) {
return Err(ShellError::TypeMismatch( return Err(ShellError::UnsupportedInput(
"frequency-column-name can't be 'value', 'count' or 'percentage'" "frequency-column-name can't be 'value', 'count' or 'percentage'"
.to_string(), .to_string(),
inner.span, span,
)); ));
} }
inner.item inner.item
@ -118,7 +119,7 @@ impl Command for Histogram {
"normalize" => PercentageCalcMethod::Normalize, "normalize" => PercentageCalcMethod::Normalize,
"relative" => PercentageCalcMethod::Relative, "relative" => PercentageCalcMethod::Relative,
_ => { _ => {
return Err(ShellError::TypeMismatch( return Err(ShellError::UnsupportedInput(
"calc method can only be 'normalize' or 'relative'".to_string(), "calc method can only be 'normalize' or 'relative'".to_string(),
inner.span, inner.span,
)) ))
@ -129,15 +130,16 @@ impl Command for Histogram {
let span = call.head; let span = call.head;
let data_as_value = input.into_value(span); let data_as_value = input.into_value(span);
// `input` is not a list, here we can return an error. // `input` is not a list, here we can return an error.
run_histogram( match data_as_value.as_list() {
data_as_value.as_list()?.to_vec(), Ok(list_value) => run_histogram(
list_value.to_vec(),
column_name, column_name,
frequency_column_name, frequency_column_name,
calc_method, calc_method,
span, span,
// Note that as_list() filters out Value::Error here. ),
data_as_value.expect_span(), Err(e) => Err(e),
) }
} }
} }
@ -147,7 +149,6 @@ fn run_histogram(
freq_column: String, freq_column: String,
calc_method: PercentageCalcMethod, calc_method: PercentageCalcMethod,
head_span: Span, head_span: Span,
list_span: Span,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let mut inputs = vec![]; let mut inputs = vec![];
// convert from inputs to hashable values. // convert from inputs to hashable values.
@ -156,24 +157,14 @@ fn run_histogram(
// some invalid input scenario needs to handle: // some invalid input scenario needs to handle:
// Expect input is a list of hashable value, if one value is not hashable, throw out error. // Expect input is a list of hashable value, if one value is not hashable, throw out error.
for v in values { for v in values {
match v { let current_span = v.span().unwrap_or(head_span);
// Propagate existing errors.
Value::Error { error } => return Err(error),
_ => {
let t = v.get_type();
let span = v.expect_span();
inputs.push(HashableValue::from_value(v, head_span).map_err(|_| { inputs.push(HashableValue::from_value(v, head_span).map_err(|_| {
ShellError::UnsupportedInput( ShellError::UnsupportedInput(
"Since --column-name was not provided, only lists of hashable values are supported.".to_string(), "--column-name is not provided, can only support a list of simple value."
format!( .to_string(),
"input type: {t:?}" current_span,
),
head_span,
span,
) )
})?) })?);
}
}
} }
} }
Some(ref col) => { Some(ref col) => {
@ -195,17 +186,14 @@ fn run_histogram(
} }
} }
} }
// Propagate existing errors.
Value::Error { error } => return Err(error),
_ => continue, _ => continue,
} }
} }
if inputs.is_empty() { if inputs.is_empty() {
return Err(ShellError::CantFindColumn( return Err(ShellError::UnsupportedInput(
col_name.clone(), format!("expect input is table, and inputs doesn't contain any value which has {col_name} column"),
head_span, head_span,
list_span,
)); ));
} }
} }
@ -261,9 +249,7 @@ fn histogram_impl(
let percentage = format!("{:.2}%", quantile * 100_f64); let percentage = format!("{:.2}%", quantile * 100_f64);
let freq = "*".repeat((MAX_FREQ_COUNT * quantile).floor() as usize); let freq = "*".repeat((MAX_FREQ_COUNT * quantile).floor() as usize);
result.push(( result.push(Value::Record {
count, // attach count first for easily sorting.
Value::Record {
cols: result_cols.clone(), cols: result_cols.clone(),
vals: vec![ vals: vec![
val.into_value(), val.into_value(),
@ -279,15 +265,9 @@ fn histogram_impl(
Value::String { val: freq, span }, Value::String { val: freq, span },
], ],
span, span,
}, });
));
} }
result.sort_by(|a, b| b.0.cmp(&a.0)); Value::List { vals: result, span }.into_pipeline_data()
Value::List {
vals: result.into_iter().map(|x| x.1).collect(),
span,
}
.into_pipeline_data()
} }
#[cfg(test)] #[cfg(test)]

View File

@ -1,269 +0,0 @@
use crate::input_handler::{operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
use print_positions::print_positions;
#[derive(Clone)]
pub struct Fill;
struct Arguments {
width: usize,
alignment: FillAlignment,
character: String,
cell_paths: Option<Vec<CellPath>>,
}
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
#[derive(Clone, Copy)]
enum FillAlignment {
Left,
Right,
Middle,
MiddleRight,
}
impl Command for Fill {
fn name(&self) -> &str {
"fill"
}
fn usage(&self) -> &str {
"Fill and Align"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("fill")
.input_output_types(vec![
(Type::Int, Type::String),
(Type::Float, Type::String),
(Type::String, Type::String),
(Type::Filesize, Type::String),
])
.vectorizes_over_list(true)
.named(
"width",
SyntaxShape::Int,
"The width of the output. Defaults to 1",
Some('w'),
)
.named(
"alignment",
SyntaxShape::String,
"The alignment of the output. Defaults to Left (Left(l), Right(r), Center(c/m), MiddleRight(cr/mr))",
Some('a'),
)
.named(
"character",
SyntaxShape::String,
"The character to fill with. Defaults to ' ' (space)",
Some('c'),
)
.category(Category::Conversions)
}
fn search_terms(&self) -> Vec<&str> {
vec!["display", "render", "format", "pad", "align"]
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description:
"Fill a string on the left side to a width of 15 with the character '─'",
example: "'nushell' | fill -a l -c '─' -w 15",
result: Some(Value::String {
val: "nushell────────".into(),
span: Span::test_data(),
}),
},
Example {
description:
"Fill a string on the right side to a width of 15 with the character '─'",
example: "'nushell' | fill -a r -c '─' -w 15",
result: Some(Value::String {
val: "────────nushell".into(),
span: Span::test_data(),
}),
},
Example {
description: "Fill a string on both sides to a width of 15 with the character '─'",
example: "'nushell' | fill -a m -c '─' -w 15",
result: Some(Value::String {
val: "────nushell────".into(),
span: Span::test_data(),
}),
},
Example {
description:
"Fill a number on the left side to a width of 5 with the character '0'",
example: "1 | fill --alignment right --character 0 --width 5",
result: Some(Value::String {
val: "00001".into(),
span: Span::test_data(),
}),
},
Example {
description: "Fill a number on both sides to a width of 5 with the character '0'",
example: "1.1 | fill --alignment center --character 0 --width 5",
result: Some(Value::String {
val: "01.10".into(),
span: Span::test_data(),
}),
},
Example {
description:
"Fill a filesize on the left side to a width of 5 with the character '0'",
example: "1kib | fill --alignment middle --character 0 --width 10",
result: Some(Value::String {
val: "0001024000".into(),
span: Span::test_data(),
}),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
fill(engine_state, stack, call, input)
}
}
fn fill(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let width_arg: Option<usize> = call.get_flag(engine_state, stack, "width")?;
let alignment_arg: Option<String> = call.get_flag(engine_state, stack, "alignment")?;
let character_arg: Option<String> = call.get_flag(engine_state, stack, "character")?;
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let alignment = if let Some(arg) = alignment_arg {
match arg.to_lowercase().as_str() {
"l" | "left" => FillAlignment::Left,
"r" | "right" => FillAlignment::Right,
"c" | "center" | "m" | "middle" => FillAlignment::Middle,
"cr" | "centerright" | "mr" | "middleright" => FillAlignment::MiddleRight,
_ => FillAlignment::Left,
}
} else {
FillAlignment::Left
};
let width = if let Some(arg) = width_arg { arg } else { 1 };
let character = if let Some(arg) = character_arg {
arg
} else {
" ".to_string()
};
let arg = Arguments {
width,
alignment,
character,
cell_paths,
};
operate(action, arg, input, call.head, engine_state.ctrlc.clone())
}
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
match input {
Value::Int { val, .. } => fill_int(*val, args, span),
Value::Filesize { val, .. } => fill_int(*val, args, span),
Value::Float { val, .. } => fill_float(*val, args, span),
Value::String { val, .. } => fill_string(val, args, span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: ShellError::OnlySupportsThisInputType(
"int, filesize, float, string".into(),
other.get_type().to_string(),
span,
// This line requires the Value::Error match above.
other.expect_span(),
),
},
}
}
fn fill_float(num: f64, args: &Arguments, span: Span) -> Value {
let s = num.to_string();
let out_str = pad(&s, args.width, &args.character, args.alignment, false);
Value::String { val: out_str, span }
}
fn fill_int(num: i64, args: &Arguments, span: Span) -> Value {
let s = num.to_string();
let out_str = pad(&s, args.width, &args.character, args.alignment, false);
Value::String { val: out_str, span }
}
fn fill_string(s: &str, args: &Arguments, span: Span) -> Value {
let out_str = pad(s, args.width, &args.character, args.alignment, false);
Value::String { val: out_str, span }
}
fn pad(s: &str, width: usize, pad_char: &str, alignment: FillAlignment, truncate: bool) -> String {
// Attribution: Most of this function was taken from https://github.com/ogham/rust-pad and tweaked. Thank you!
// Use width instead of len for graphical display
let cols = print_positions(s).count();
if cols >= width {
if truncate {
return s[..width].to_string();
} else {
return s.to_string();
}
}
let diff = width - cols;
let (left_pad, right_pad) = match alignment {
FillAlignment::Left => (0, diff),
FillAlignment::Right => (diff, 0),
FillAlignment::Middle => (diff / 2, diff - diff / 2),
FillAlignment::MiddleRight => (diff - diff / 2, diff / 2),
};
let mut new_str = String::new();
for _ in 0..left_pad {
new_str.push_str(pad_char)
}
new_str.push_str(s);
for _ in 0..right_pad {
new_str.push_str(pad_char)
}
new_str
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Fill {})
}
}

View File

@ -44,14 +44,38 @@ impl Command for Fmt {
"upperhex".into(), "upperhex".into(),
], ],
vals: vec![ vals: vec![
Value::test_string("0b101010"), Value::String {
Value::test_string("42"), val: "0b101010".to_string(),
Value::test_string("42"), span: Span::test_data(),
Value::test_string("4.2e1"), },
Value::test_string("0x2a"), Value::String {
Value::test_string("0o52"), val: "42".to_string(),
Value::test_string("4.2E1"), span: Span::test_data(),
Value::test_string("0x2A"), },
Value::String {
val: "42".to_string(),
span: Span::test_data(),
},
Value::String {
val: "4.2e1".to_string(),
span: Span::test_data(),
},
Value::String {
val: "0x2a".to_string(),
span: Span::test_data(),
},
Value::String {
val: "0o52".to_string(),
span: Span::test_data(),
},
Value::String {
val: "4.2E1".to_string(),
span: Span::test_data(),
},
Value::String {
val: "0x2A".to_string(),
span: Span::test_data(),
},
], ],
span: Span::test_data(), span: Span::test_data(),
}), }),
@ -64,7 +88,7 @@ impl Command for Fmt {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
fmt(engine_state, stack, call, input) fmt(engine_state, stack, call, input)
} }
} }
@ -74,7 +98,7 @@ fn fmt(
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let args = CellPathOnlyArgs::from(cell_paths); let args = CellPathOnlyArgs::from(cell_paths);
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
@ -84,15 +108,10 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
match input { match input {
Value::Int { val, .. } => fmt_it(*val, span), Value::Int { val, .. } => fmt_it(*val, span),
Value::Filesize { val, .. } => fmt_it(*val, span), Value::Filesize { val, .. } => fmt_it(*val, span),
// Propagate errors by explicitly matching them before the final case. _ => Value::Error {
Value::Error { .. } => input.clone(), error: ShellError::UnsupportedInput(
other => Value::Error { format!("unsupported input type: {:?}", input.get_type()),
error: ShellError::OnlySupportsThisInputType(
"integer or filesize".into(),
other.get_type().to_string(),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }
@ -103,31 +122,31 @@ fn fmt_it(num: i64, span: Span) -> Value {
let mut vals = vec![]; let mut vals = vec![];
cols.push("binary".into()); cols.push("binary".into());
vals.push(Value::string(format!("{num:#b}"), span)); vals.push(Value::string(format!("{:#b}", num), span));
cols.push("debug".into()); cols.push("debug".into());
vals.push(Value::string(format!("{num:#?}"), span)); vals.push(Value::string(format!("{:#?}", num), span));
cols.push("display".into()); cols.push("display".into());
vals.push(Value::string(format!("{num}"), span)); vals.push(Value::string(format!("{}", num), span));
cols.push("lowerexp".into()); cols.push("lowerexp".into());
vals.push(Value::string(format!("{num:#e}"), span)); vals.push(Value::string(format!("{:#e}", num), span));
cols.push("lowerhex".into()); cols.push("lowerhex".into());
vals.push(Value::string(format!("{num:#x}"), span)); vals.push(Value::string(format!("{:#x}", num), span));
cols.push("octal".into()); cols.push("octal".into());
vals.push(Value::string(format!("{num:#o}"), span)); vals.push(Value::string(format!("{:#o}", num), span));
// cols.push("pointer".into()); // cols.push("pointer".into());
// vals.push(Value::string(format!("{:#p}", &num), span)); // vals.push(Value::string(format!("{:#p}", &num), span));
cols.push("upperexp".into()); cols.push("upperexp".into());
vals.push(Value::string(format!("{num:#E}"), span)); vals.push(Value::string(format!("{:#E}", num), span));
cols.push("upperhex".into()); cols.push("upperhex".into());
vals.push(Value::string(format!("{num:#X}"), span)); vals.push(Value::string(format!("{:#X}", num), span));
Value::Record { cols, vals, span } Value::Record { cols, vals, span }
} }

View File

@ -49,7 +49,7 @@ impl Command for SubCommand {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
into_binary(engine_state, stack, call, input) into_binary(engine_state, stack, call, input)
} }
@ -109,7 +109,7 @@ fn into_binary(
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
@ -177,24 +177,13 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
val: int_to_endian(i64::from(*val)), val: int_to_endian(i64::from(*val)),
span, span,
}, },
Value::Duration { val, .. } => Value::Binary {
val: int_to_endian(*val),
span,
},
Value::Date { val, .. } => Value::Binary { Value::Date { val, .. } => Value::Binary {
val: val.format("%c").to_string().as_bytes().to_vec(), val: val.format("%c").to_string().as_bytes().to_vec(),
span, span,
}, },
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(), _ => Value::Error {
other => Value::Error { error: ShellError::UnsupportedInput("'into binary' for unsupported type".into(), span),
error: ShellError::OnlySupportsThisInputType(
"integer, float, filesize, string, date, duration, binary or bool".into(),
other.get_type().to_string(),
span,
// This line requires the Value::Error match above.
other.expect_span(),
),
}, },
} }
} }

View File

@ -45,7 +45,7 @@ impl Command for SubCommand {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
into_bool(engine_state, stack, call, input) into_bool(engine_state, stack, call, input)
} }
@ -54,7 +54,7 @@ impl Command for SubCommand {
vec![ vec![
Example { Example {
description: "Convert value to boolean in table", description: "Convert value to boolean in table",
example: "[[value]; ['false'] ['1'] [0] [1.0] [true]] | into bool value", example: "echo [[value]; ['false'] ['1'] [0] [1.0] [true]] | into bool value",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::Record {
@ -163,15 +163,10 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
Ok(val) => Value::Bool { val, span }, Ok(val) => Value::Bool { val, span },
Err(error) => Value::Error { error }, Err(error) => Value::Error { error },
}, },
// Propagate errors by explicitly matching them before the final case. _ => Value::Error {
Value::Error { .. } => input.clone(), error: ShellError::UnsupportedInput(
other => Value::Error { "'into bool' does not support this input".into(),
error: ShellError::OnlySupportsThisInputType(
"bool, integer, float or string".into(),
other.get_type().to_string(),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -2,7 +2,7 @@ use nu_engine::get_full_help;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value, Category, IntoPipelineData, PipelineData, Signature, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -14,26 +14,20 @@ impl Command for Into {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("into") Signature::build("into").category(Category::Conversions)
.category(Category::Conversions)
.input_output_types(vec![(Type::Nothing, Type::String)])
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Commands to convert data from one type to another." "Commands to convert data from one type to another."
} }
fn extra_usage(&self) -> &str {
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(Value::String { Ok(Value::String {
val: get_full_help( val: get_full_help(
&Into.signature(), &Into.signature(),
@ -47,3 +41,15 @@ impl Command for Into {
.into_pipeline_data()) .into_pipeline_data())
} }
} }
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Into {})
}
}

View File

@ -147,19 +147,41 @@ impl Command for SubCommand {
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
let example_result_1 = |secs: i64, nsecs: u32| match Utc.timestamp_opt(secs, nsecs) { let example_result_1 = |secs: i64, nsecs: u32| {
LocalResult::Single(dt) => Some(Value::Date { let dt = match Utc.timestamp_opt(secs, nsecs) {
val: dt.into(), LocalResult::Single(dt) => Some(dt),
span: Span::test_data(), _ => None,
}),
_ => panic!("datetime: help example is invalid"),
}; };
let example_result_2 = |millis: i64| match Utc.timestamp_millis_opt(millis) { match dt {
LocalResult::Single(dt) => Some(Value::Date { Some(dt) => Some(Value::Date {
val: dt.into(), val: dt.into(),
span: Span::test_data(), span: Span::test_data(),
}), }),
_ => panic!("datetime: help example is invalid"), None => Some(Value::Error {
error: ShellError::UnsupportedInput(
"The given datetime representation is unsupported.".to_string(),
Span::test_data(),
),
}),
}
};
let example_result_2 = |millis: i64| {
let dt = match Utc.timestamp_millis_opt(millis) {
LocalResult::Single(dt) => Some(dt),
_ => None,
};
match dt {
Some(dt) => Some(Value::Date {
val: dt.into(),
span: Span::test_data(),
}),
None => Some(Value::Error {
error: ShellError::UnsupportedInput(
"The given datetime representation is unsupported.".to_string(),
Span::test_data(),
),
}),
}
}; };
vec![ vec![
Example { Example {
@ -191,7 +213,7 @@ impl Command for SubCommand {
}, },
Example { Example {
description: description:
"Convert a millisecond-precise timestamp", "Convert timestamps like the sqlite history t",
example: "1656165681720 | into datetime", example: "1656165681720 | into datetime",
result: example_result_2(1656165681720) result: example_result_2(1656165681720)
}, },
@ -209,16 +231,11 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
let timestamp = match input { let timestamp = match input {
Value::Int { val, .. } => Ok(*val), Value::Int { val, .. } => Ok(*val),
Value::String { val, .. } => val.parse::<i64>(), Value::String { val, .. } => val.parse::<i64>(),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => return input.clone(),
other => { other => {
return Value::Error { return Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"string and integer".into(), format!("Expected string or int, got {} instead", other.get_type()),
other.get_type().to_string(),
head, head,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}; };
} }
@ -231,68 +248,113 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
if ts.abs() > TIMESTAMP_BOUND { if ts.abs() > TIMESTAMP_BOUND {
return Value::Error { return Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::UnsupportedInput(
"timestamp is out of range; it should between -8e+12 and 8e+12".to_string(), "Given timestamp is out of range, it should between -8e+12 and 8e+12"
format!("timestamp is {ts:?}"), .to_string(),
head,
// Again, can safely unwrap this from here on
input.expect_span(),
),
};
}
macro_rules! match_datetime {
($expr:expr) => {
match $expr {
LocalResult::Single(dt) => Value::Date {
val: dt.into(),
span: head,
},
_ => {
return Value::Error {
error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid.".into(),
format!("timestamp is {:?}", ts),
head,
head, head,
), ),
}; };
} }
}
};
}
return match timezone { return match timezone {
// default to UTC // default to UTC
None => { None => {
// be able to convert chrono::Utc::now() // be able to convert chrono::Utc::now()
match ts.to_string().len() { let dt = match ts.to_string().len() {
x if x > 13 => Value::Date { x if x > 13 => Utc.timestamp_nanos(ts).into(),
val: Utc.timestamp_nanos(ts).into(), x if x > 10 => match Utc.timestamp_millis_opt(ts) {
span: head, LocalResult::Single(dt) => dt.into(),
_ => {
return Value::Error {
// This error message is from chrono
error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid."
.to_string(),
head,
),
};
}
}, },
x if x > 10 => match_datetime!(Utc.timestamp_millis_opt(ts)), _ => match Utc.timestamp_opt(ts, 0) {
_ => match_datetime!(Utc.timestamp_opt(ts, 0)), LocalResult::Single(dt) => dt.into(),
_ => {
return Value::Error {
error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid."
.to_string(),
head,
),
}
}
},
};
Value::Date {
val: dt,
span: head,
} }
} }
Some(Spanned { item, span }) => match item { Some(Spanned { item, span }) => match item {
Zone::Utc => match_datetime!(Utc.timestamp_opt(ts, 0)), Zone::Utc => match Utc.timestamp_opt(ts, 0) {
Zone::Local => match_datetime!(Local.timestamp_opt(ts, 0)), LocalResult::Single(val) => Value::Date {
val: val.into(),
span: head,
},
_ => Value::Error {
error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid.".to_string(),
*span,
),
},
},
Zone::Local => match Local.timestamp_opt(ts, 0) {
LocalResult::Single(val) => Value::Date {
val: val.into(),
span: head,
},
_ => Value::Error {
error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid.".to_string(),
*span,
),
},
},
Zone::East(i) => match FixedOffset::east_opt((*i as i32) * HOUR) { Zone::East(i) => match FixedOffset::east_opt((*i as i32) * HOUR) {
Some(eastoffset) => match_datetime!(eastoffset.timestamp_opt(ts, 0)), Some(eastoffset) => match eastoffset.timestamp_opt(ts, 0) {
LocalResult::Single(val) => Value::Date { val, span: head },
_ => Value::Error {
error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid.".to_string(),
*span,
),
},
},
None => Value::Error { None => Value::Error {
error: ShellError::DatetimeParseError(input.debug_value(), *span), error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid.".to_string(),
*span,
),
}, },
}, },
Zone::West(i) => match FixedOffset::west_opt((*i as i32) * HOUR) { Zone::West(i) => match FixedOffset::west_opt((*i as i32) * HOUR) {
Some(westoffset) => match_datetime!(westoffset.timestamp_opt(ts, 0)), Some(westoffset) => match westoffset.timestamp_opt(ts, 0) {
LocalResult::Single(val) => Value::Date { val, span: head },
_ => Value::Error {
error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid.".to_string(),
*span,
),
},
},
None => Value::Error { None => Value::Error {
error: ShellError::DatetimeParseError(input.debug_value(), *span), error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid.".to_string(),
*span,
),
}, },
}, },
Zone::Error => Value::Error { Zone::Error => Value::Error {
// This is an argument error, not an input error error: ShellError::UnsupportedInput(
error: ShellError::TypeMismatch( "Cannot convert given timezone or offset to timestamp".to_string(),
"Invalid timezone or offset".to_string(),
*span, *span,
), ),
}, },
@ -329,15 +391,10 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
}, },
} }
} }
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"string".into(), format!("Expected string, got {} instead", other.get_type()),
other.get_type().to_string(),
head, head,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -41,7 +41,7 @@ impl Command for SubCommand {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let args = CellPathOnlyArgs::from(cell_paths); let args = CellPathOnlyArgs::from(cell_paths);
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
@ -86,7 +86,7 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
let other = s.trim(); let other = s.trim();
match other.parse::<f64>() { match other.parse::<f64>() {
Ok(x) => Value::float(x, head), Ok(x) => Value::Float { val: x, span: head },
Err(reason) => Value::Error { Err(reason) => Value::Error {
error: ShellError::CantConvert( error: ShellError::CantConvert(
"float".to_string(), "float".to_string(),
@ -97,7 +97,10 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
}, },
} }
} }
Value::Int { val: v, span } => Value::float(*v as f64, *span), Value::Int { val: v, span } => Value::Float {
val: *v as f64,
span: *span,
},
Value::Bool { val: b, span } => Value::Float { Value::Bool { val: b, span } => Value::Float {
val: match b { val: match b {
true => 1.0, true => 1.0,
@ -105,17 +108,18 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
}, },
span: *span, span: *span,
}, },
// Propagate errors by explicitly matching them before the final case. other => {
Value::Error { .. } => input.clone(), let span = other.span();
other => Value::Error { match span {
error: ShellError::OnlySupportsThisInputType( Ok(s) => {
"string, integer or bool".into(), let got = format!("Expected a string, got {} instead", other.get_type());
other.get_type().to_string(), Value::Error {
head, error: ShellError::UnsupportedInput(got, s),
// This line requires the Value::Error match above. }
other.expect_span(), }
), Err(e) => Value::Error { error: e },
}, }
}
} }
} }

View File

@ -56,7 +56,7 @@ impl Command for SubCommand {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
into_duration(engine_state, stack, call, input) into_duration(engine_state, stack, call, input)
} }
@ -65,8 +65,7 @@ impl Command for SubCommand {
vec![ vec![
Example { Example {
description: "Convert string to duration in table", description: "Convert string to duration in table",
example: example: "echo [[value]; ['1sec'] ['2min'] ['3hr'] ['4day'] ['5wk']] | into duration value",
"[[value]; ['1sec'] ['2min'] ['3hr'] ['4day'] ['5wk']] | into duration value",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::Record {
@ -468,11 +467,9 @@ fn action(
} }
} else { } else {
Value::Error { Value::Error {
error: ShellError::CantConvert( error: ShellError::UnsupportedInput(
"string".into(), "'into duration' does not support this string input".into(),
"duration".into(),
span, span,
None,
), ),
} }
} }
@ -483,15 +480,10 @@ fn action(
} }
} }
} }
// Propagate errors by explicitly matching them before the final case. _ => Value::Error {
Value::Error { .. } => input.clone(), error: ShellError::UnsupportedInput(
other => Value::Error { "'into duration' does not support this input".into(),
error: ShellError::OnlySupportsThisInputType(
"string or duration".into(),
other.get_type().to_string(),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }
@ -510,7 +502,7 @@ mod test {
#[test] #[test]
fn turns_ns_to_duration() { fn turns_ns_to_duration() {
let span = Span::new(0, 2); let span = Span::test_data();
let word = Value::test_string("3ns"); let word = Value::test_string("3ns");
let expected = Value::Duration { val: 3, span }; let expected = Value::Duration { val: 3, span };
let convert_duration = None; let convert_duration = None;
@ -521,7 +513,7 @@ mod test {
#[test] #[test]
fn turns_us_to_duration() { fn turns_us_to_duration() {
let span = Span::new(0, 2); let span = Span::test_data();
let word = Value::test_string("4us"); let word = Value::test_string("4us");
let expected = Value::Duration { let expected = Value::Duration {
val: 4 * 1000, val: 4 * 1000,
@ -535,7 +527,7 @@ mod test {
#[test] #[test]
fn turns_ms_to_duration() { fn turns_ms_to_duration() {
let span = Span::new(0, 2); let span = Span::test_data();
let word = Value::test_string("5ms"); let word = Value::test_string("5ms");
let expected = Value::Duration { let expected = Value::Duration {
val: 5 * 1000 * 1000, val: 5 * 1000 * 1000,
@ -549,7 +541,7 @@ mod test {
#[test] #[test]
fn turns_sec_to_duration() { fn turns_sec_to_duration() {
let span = Span::new(0, 3); let span = Span::test_data();
let word = Value::test_string("1sec"); let word = Value::test_string("1sec");
let expected = Value::Duration { let expected = Value::Duration {
val: 1000 * 1000 * 1000, val: 1000 * 1000 * 1000,
@ -563,7 +555,7 @@ mod test {
#[test] #[test]
fn turns_min_to_duration() { fn turns_min_to_duration() {
let span = Span::new(0, 3); let span = Span::test_data();
let word = Value::test_string("7min"); let word = Value::test_string("7min");
let expected = Value::Duration { let expected = Value::Duration {
val: 7 * 60 * 1000 * 1000 * 1000, val: 7 * 60 * 1000 * 1000 * 1000,
@ -577,7 +569,7 @@ mod test {
#[test] #[test]
fn turns_hr_to_duration() { fn turns_hr_to_duration() {
let span = Span::new(0, 3); let span = Span::test_data();
let word = Value::test_string("42hr"); let word = Value::test_string("42hr");
let expected = Value::Duration { let expected = Value::Duration {
val: 42 * 60 * 60 * 1000 * 1000 * 1000, val: 42 * 60 * 60 * 1000 * 1000 * 1000,
@ -591,7 +583,7 @@ mod test {
#[test] #[test]
fn turns_day_to_duration() { fn turns_day_to_duration() {
let span = Span::new(0, 5); let span = Span::test_data();
let word = Value::test_string("123day"); let word = Value::test_string("123day");
let expected = Value::Duration { let expected = Value::Duration {
val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000, val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000,
@ -605,7 +597,7 @@ mod test {
#[test] #[test]
fn turns_wk_to_duration() { fn turns_wk_to_duration() {
let span = Span::new(0, 2); let span = Span::test_data();
let word = Value::test_string("3wk"); let word = Value::test_string("3wk");
let expected = Value::Duration { let expected = Value::Duration {
val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000, val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000,

View File

@ -44,7 +44,7 @@ impl Command for SubCommand {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let args = CellPathOnlyArgs::from(cell_paths); let args = CellPathOnlyArgs::from(cell_paths);
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
@ -116,18 +116,20 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
val: 0, val: 0,
span: value_span, span: value_span,
}, },
other => Value::Error { _ => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"string and integer".into(), "'into filesize' for unsupported type".into(),
other.get_type().to_string(),
span,
value_span, value_span,
), ),
}, },
} }
} else { } else {
// Propagate existing errors Value::Error {
input.clone() error: ShellError::UnsupportedInput(
"'into filesize' for unsupported type".into(),
span,
),
}
} }
} }
fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> { fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {

View File

@ -62,7 +62,7 @@ impl Command for SubCommand {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let cell_paths = call.rest(engine_state, stack, 0)?; let cell_paths = call.rest(engine_state, stack, 0)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
@ -70,7 +70,7 @@ impl Command for SubCommand {
let radix: u32 = match radix { let radix: u32 = match radix {
Some(Value::Int { val, span }) => { Some(Value::Int { val, span }) => {
if !(2..=36).contains(&val) { if !(2..=36).contains(&val) {
return Err(ShellError::TypeMismatch( return Err(ShellError::UnsupportedInput(
"Radix must lie in the range [2, 36]".to_string(), "Radix must lie in the range [2, 36]".to_string(),
span, span,
)); ));
@ -92,7 +92,7 @@ impl Command for SubCommand {
vec![ vec![
Example { Example {
description: "Convert string to integer in table", description: "Convert string to integer in table",
example: "[[num]; ['-5'] [4] [1.5]] | into int num", example: "echo [[num]; ['-5'] [4] [1.5]] | into int num",
result: None, result: None,
}, },
Example { Example {
@ -113,7 +113,10 @@ impl Command for SubCommand {
Example { Example {
description: "Convert file size to integer", description: "Convert file size to integer",
example: "4KB | into int", example: "4KB | into int",
result: Some(Value::test_int(4000)), result: Some(Value::Int {
val: 4000,
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Convert bool to integer", description: "Convert bool to integer",
@ -187,11 +190,9 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
Ok(v) => v, Ok(v) => v,
_ => { _ => {
return Value::Error { return Value::Error {
error: ShellError::CantConvert( error: ShellError::UnsupportedInput(
"float".to_string(), "Could not convert float to integer".to_string(),
"integer".to_string(),
span, span,
None,
), ),
} }
} }
@ -221,7 +222,6 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
val: val.timestamp(), val: val.timestamp(),
span, span,
}, },
Value::Duration { val, .. } => Value::Int { val: *val, span },
Value::Binary { val, span } => { Value::Binary { val, span } => {
use byteorder::{BigEndian, ByteOrder, LittleEndian}; use byteorder::{BigEndian, ByteOrder, LittleEndian};
@ -233,25 +233,26 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
} }
val.resize(8, 0); val.resize(8, 0);
Value::int(LittleEndian::read_i64(&val), *span) Value::Int {
val: LittleEndian::read_i64(&val),
span: *span,
}
} else { } else {
while val.len() < 8 { while val.len() < 8 {
val.insert(0, 0); val.insert(0, 0);
} }
val.resize(8, 0); val.resize(8, 0);
Value::int(BigEndian::read_i64(&val), *span) Value::Int {
val: BigEndian::read_i64(&val),
span: *span,
} }
} }
// Propagate errors by explicitly matching them before the final case. }
Value::Error { .. } => input.clone(), _ => Value::Error {
other => Value::Error { error: ShellError::UnsupportedInput(
error: ShellError::OnlySupportsThisInputType( format!("'into int' for unsupported type '{}'", input.get_type()),
"integer, float, filesize, date, string, binary, duration or bool".into(),
other.get_type().to_string(),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }
@ -268,13 +269,13 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
// octal // octal
{ {
match int_from_string(val, head) { match int_from_string(val, head) {
Ok(x) => return Value::int(x, head), Ok(x) => return Value::Int { val: x, span: head },
Err(e) => return Value::Error { error: e }, Err(e) => return Value::Error { error: e },
} }
} else if val.starts_with("00") { } else if val.starts_with("00") {
// It's a padded string // It's a padded string
match i64::from_str_radix(val, radix) { match i64::from_str_radix(val, radix) {
Ok(n) => return Value::int(n, head), Ok(n) => return Value::Int { val: n, span: head },
Err(e) => { Err(e) => {
return Value::Error { return Value::Error {
error: ShellError::CantConvert( error: ShellError::CantConvert(
@ -289,22 +290,17 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
} }
val.to_string() val.to_string()
} }
// Propagate errors by explicitly matching them before the final case. _ => {
Value::Error { .. } => return input.clone(),
other => {
return Value::Error { return Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"string and integer".into(), "only strings or integers are supported".to_string(),
other.get_type().to_string(),
head, head,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}; }
} }
}; };
match i64::from_str_radix(i.trim(), radix) { match i64::from_str_radix(i.trim(), radix) {
Ok(n) => Value::int(n, head), Ok(n) => Value::Int { val: n, span: head },
Err(_reason) => Value::Error { Err(_reason) => Value::Error {
error: ShellError::CantConvert("string".to_string(), "int".to_string(), head, None), error: ShellError::CantConvert("string".to_string(), "int".to_string(), head, None),
}, },
@ -367,7 +363,8 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
"string".to_string(), "string".to_string(),
span, span,
Some(format!( Some(format!(
r#"string "{trimmed}" does not represent a valid integer"# r#"string "{}" does not represent a valid integer"#,
trimmed
)), )),
)), )),
}, },

View File

@ -40,7 +40,7 @@ impl Command for SubCommand {
_stack: &mut Stack, _stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
into_record(engine_state, call, input) into_record(engine_state, call, input)
} }
@ -49,7 +49,7 @@ impl Command for SubCommand {
vec![ vec![
Example { Example {
description: "Convert from one row table to record", description: "Convert from one row table to record",
example: "[[value]; [false]] | into record", example: "echo [[value]; [false]] | into record",
result: Some(Value::Record { result: Some(Value::Record {
cols: vec!["value".to_string()], cols: vec!["value".to_string()],
vals: vec![Value::boolean(false, span)], vals: vec![Value::boolean(false, span)],
@ -183,14 +183,10 @@ fn into_record(
Value::Record { cols, vals, span } Value::Record { cols, vals, span }
} }
Value::Record { cols, vals, span } => Value::Record { cols, vals, span }, Value::Record { cols, vals, span } => Value::Record { cols, vals, span },
Value::Error { .. } => input,
other => Value::Error { other => Value::Error {
error: ShellError::OnlySupportsThisInputType( error: ShellError::UnsupportedInput(
"string".into(), "'into record' does not support this input".into(),
other.get_type().to_string(), other.span().unwrap_or(call.head),
call.head,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
}; };

View File

@ -70,7 +70,7 @@ impl Command for SubCommand {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
string_helper(engine_state, stack, call, input) string_helper(engine_state, stack, call, input)
} }
@ -79,22 +79,34 @@ impl Command for SubCommand {
Example { Example {
description: "convert integer to string and append three decimal places", description: "convert integer to string and append three decimal places",
example: "5 | into string -d 3", example: "5 | into string -d 3",
result: Some(Value::test_string("5.000")), result: Some(Value::String {
val: "5.000".to_string(),
span: Span::test_data(),
}),
}, },
Example { Example {
description: "convert decimal to string and round to nearest integer", description: "convert decimal to string and round to nearest integer",
example: "1.7 | into string -d 0", example: "1.7 | into string -d 0",
result: Some(Value::test_string("2")), result: Some(Value::String {
val: "2".to_string(),
span: Span::test_data(),
}),
}, },
Example { Example {
description: "convert decimal to string", description: "convert decimal to string",
example: "1.7 | into string -d 1", example: "1.7 | into string -d 1",
result: Some(Value::test_string("1.7")), result: Some(Value::String {
val: "1.7".to_string(),
span: Span::test_data(),
}),
}, },
Example { Example {
description: "convert decimal to string and limit to 2 decimals", description: "convert decimal to string and limit to 2 decimals",
example: "1.734 | into string -d 2", example: "1.734 | into string -d 2",
result: Some(Value::test_string("1.73")), result: Some(Value::String {
val: "1.73".to_string(),
span: Span::test_data(),
}),
}, },
Example { Example {
description: "try to convert decimal to string and provide negative decimal points", description: "try to convert decimal to string and provide negative decimal points",
@ -111,17 +123,26 @@ impl Command for SubCommand {
Example { Example {
description: "convert decimal to string", description: "convert decimal to string",
example: "4.3 | into string", example: "4.3 | into string",
result: Some(Value::test_string("4.3")), result: Some(Value::String {
val: "4.3".to_string(),
span: Span::test_data(),
}),
}, },
Example { Example {
description: "convert string to string", description: "convert string to string",
example: "'1234' | into string", example: "'1234' | into string",
result: Some(Value::test_string("1234")), result: Some(Value::String {
val: "1234".to_string(),
span: Span::test_data(),
}),
}, },
Example { Example {
description: "convert boolean to string", description: "convert boolean to string",
example: "true | into string", example: "true | into string",
result: Some(Value::test_string("true")), result: Some(Value::String {
val: "true".to_string(),
span: Span::test_data(),
}),
}, },
// TODO: This should work but does not; see https://github.com/nushell/nushell/issues/7032 // TODO: This should work but does not; see https://github.com/nushell/nushell/issues/7032
// Example { // Example {
@ -148,13 +169,13 @@ fn string_helper(
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, ShellError> {
let decimals = call.has_flag("decimals"); let decimals = call.has_flag("decimals");
let head = call.head; let head = call.head;
let decimals_value: Option<i64> = call.get_flag(engine_state, stack, "decimals")?; let decimals_value: Option<i64> = call.get_flag(engine_state, stack, "decimals")?;
if let Some(decimal_val) = decimals_value { if let Some(decimal_val) = decimals_value {
if decimals && decimal_val.is_negative() { if decimals && decimal_val.is_negative() {
return Err(ShellError::TypeMismatch( return Err(ShellError::UnsupportedInput(
"Cannot accept negative integers for decimals arguments".to_string(), "Cannot accept negative integers for decimals arguments".to_string(),
head, head,
)); ));
@ -206,7 +227,7 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
if decimals { if decimals {
let decimal_value = digits.unwrap_or(2) as usize; let decimal_value = digits.unwrap_or(2) as usize;
Value::String { Value::String {
val: format!("{val:.decimal_value$}"), val: format!("{:.*}", decimal_value, val),
span, span,
} }
} else { } else {
@ -234,7 +255,12 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
span, span,
}, },
Value::Error { error } => Value::String { Value::Error { error } => Value::String {
val: into_code(error).unwrap_or_default(), val: {
match into_code(error) {
Some(code) => code,
None => "".to_string(),
}
},
span, span,
}, },
Value::Nothing { .. } => Value::String { Value::Nothing { .. } => Value::String {
@ -246,11 +272,9 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
vals: _, vals: _,
span: _, span: _,
} => Value::Error { } => Value::Error {
error: ShellError::CantConvert( error: ShellError::UnsupportedInput(
"record".into(), "Cannot convert Record into string".to_string(),
"string".into(),
span, span,
Some("try using the `to nuon` command".into()),
), ),
}, },
Value::Binary { .. } => Value::Error { Value::Binary { .. } => Value::Error {

View File

@ -1,7 +1,5 @@
mod fill;
mod fmt; mod fmt;
pub(crate) mod into; pub(crate) mod into;
pub use fill::Fill;
pub use fmt::Fmt; pub use fmt::Fmt;
pub use into::*; pub use into::*;

View File

@ -1,8 +1,6 @@
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Type, Value};
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)] #[derive(Clone)]
pub struct Alias; pub struct Alias;
@ -45,10 +43,10 @@ impl Command for Alias {
&self, &self,
_engine_state: &EngineState, _engine_state: &EngineState,
_stack: &mut Stack, _stack: &mut Stack,
_call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(PipelineData::empty()) Ok(PipelineData::new(call.head))
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {

View File

@ -3,8 +3,8 @@ use nu_parser::parse;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack, StateWorkingSet}, engine::{Command, EngineState, Stack, StateWorkingSet},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type,
Type, Value, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -21,14 +21,13 @@ impl Command for Ast {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("ast") Signature::build("ast")
.input_output_types(vec![(Type::String, Type::Record(vec![]))]) .input_output_types(vec![(Type::Nothing, Type::Nothing)])
.required( .required(
"pipeline", "pipeline",
SyntaxShape::String, SyntaxShape::String,
"the pipeline to print the ast for", "the pipeline to print the ast for",
) )
.allow_variants_without_examples(true) .category(Category::Core)
.category(Category::Debug)
} }
fn run( fn run(
@ -38,25 +37,14 @@ impl Command for Ast {
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head;
let pipeline: Spanned<String> = call.req(engine_state, stack, 0)?; let pipeline: Spanned<String> = call.req(engine_state, stack, 0)?;
let mut working_set = StateWorkingSet::new(engine_state); let mut working_set = StateWorkingSet::new(engine_state);
let (block_output, error_output) = let (output, err) = parse(&mut working_set, None, pipeline.item.as_bytes(), false, &[]);
parse(&mut working_set, None, pipeline.item.as_bytes(), false, &[]); eprintln!("output: {:#?}\nerror: {:#?}", output, err);
let block_value = Value::String {
val: format!("{block_output:#?}"), Ok(PipelineData::new(head))
span: pipeline.span,
};
let error_value = Value::String {
val: format!("{error_output:#?}"),
span: pipeline.span,
};
let output_record = Value::Record {
cols: vec!["block".to_string(), "error".to_string()],
vals: vec![block_value, error_value],
span: pipeline.span,
};
Ok(output_record.into_pipeline_data())
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -64,17 +52,17 @@ impl Command for Ast {
Example { Example {
description: "Print the ast of a string", description: "Print the ast of a string",
example: "ast 'hello'", example: "ast 'hello'",
result: None, result: Some(Value::nothing(Span::test_data())),
}, },
Example { Example {
description: "Print the ast of a pipeline", description: "Print the ast of a pipeline",
example: "ast 'ls | where name =~ README'", example: "ast 'ls | where name =~ README'",
result: None, result: Some(Value::nothing(Span::test_data())),
}, },
Example { Example {
description: "Print the ast of a pipeline with an error", description: "Print the ast of a pipeline with an error",
example: "ast 'for x in 1..10 { echo $x '", example: "ast 'for x in 1..10 { echo $x '",
result: None, result: Some(Value::nothing(Span::test_data())),
}, },
] ]
} }

View File

@ -35,7 +35,7 @@ impl Command for Break {
_stack: &mut Stack, _stack: &mut Stack,
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Err(ShellError::Break(call.head)) Err(ShellError::Break(call.head))
} }

View File

@ -1,104 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
#[derive(Clone)]
pub struct Const;
impl Command for Const {
fn name(&self) -> &str {
"const"
}
fn usage(&self) -> &str {
"Create a parse-time constant."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("const")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.allow_variants_without_examples(true)
.required("const_name", SyntaxShape::VarWithOptType, "constant name")
.required(
"initial_value",
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
"equals sign followed by constant value",
)
.category(Category::Core)
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn search_terms(&self) -> Vec<&str> {
vec!["set", "let"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let var_id = call
.positional_nth(0)
.expect("checked through parser")
.as_var()
.expect("internal error: missing variable");
if let Some(constval) = engine_state.find_constant(var_id, &[]) {
// Instead of creating a second copy of the value in the stack, we could change
// stack.get_var() to check engine_state.find_constant().
stack.add_var(var_id, constval.clone());
Ok(PipelineData::empty())
} else {
Err(ShellError::NushellFailedSpanned(
"Missing Constant".to_string(),
"constant not added by the parser".to_string(),
call.head,
))
}
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Create a new parse-time constant.",
example: "const x = 10",
result: None,
},
Example {
description: "Create a composite constant value",
example: "const x = { a: 10, b: 20 }",
result: None,
},
]
}
}
#[cfg(test)]
mod test {
use nu_protocol::engine::CommandType;
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Const {})
}
#[test]
fn test_command_type() {
assert!(matches!(Const.command_type(), CommandType::Keyword));
}
}

View File

@ -35,7 +35,7 @@ impl Command for Continue {
_stack: &mut Stack, _stack: &mut Stack,
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Err(ShellError::Continue(call.head)) Err(ShellError::Continue(call.head))
} }

View File

@ -24,7 +24,7 @@ impl Command for Debug {
(Type::Table(vec![]), Type::List(Box::new(Type::String))), (Type::Table(vec![]), Type::List(Box::new(Type::String))),
(Type::Any, Type::String), (Type::Any, Type::String),
]) ])
.category(Category::Debug) .category(Category::Core)
.switch("raw", "Prints the raw value representation", Some('r')) .switch("raw", "Prints the raw value representation", Some('r'))
} }
@ -39,8 +39,6 @@ impl Command for Debug {
let config = engine_state.get_config().clone(); let config = engine_state.get_config().clone();
let raw = call.has_flag("raw"); let raw = call.has_flag("raw");
// Should PipelineData::Empty result in an error here?
input.map( input.map(
move |x| { move |x| {
if raw { if raw {
@ -76,7 +74,7 @@ impl Command for Debug {
}, },
Example { Example {
description: "Debug print a table", description: "Debug print a table",
example: "[[version patch]; [0.1.0 false] [0.1.1 true] [0.2.0 false]] | debug", example: "echo [[version patch]; [0.1.0 false] [0.1.1 true] [0.2.0 false]] | debug",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::test_string("{version: 0.1.0, patch: false}"), Value::test_string("{version: 0.1.0, patch: false}"),

View File

@ -1,8 +1,6 @@
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Type, Value};
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
};
#[derive(Clone)] #[derive(Clone)]
pub struct Def; pub struct Def;
@ -38,10 +36,10 @@ impl Command for Def {
&self, &self,
_engine_state: &EngineState, _engine_state: &EngineState,
_stack: &mut Stack, _stack: &mut Stack,
_call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(PipelineData::empty()) Ok(PipelineData::new(call.head))
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {

View File

@ -1,8 +1,6 @@
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Type, Value};
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
};
#[derive(Clone)] #[derive(Clone)]
pub struct DefEnv; pub struct DefEnv;
@ -28,7 +26,32 @@ impl Command for DefEnv {
fn extra_usage(&self) -> &str { fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check: r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html https://www.nushell.sh/book/thinking_in_nu.html
"#
=== EXTRA NOTE ===
All blocks are scoped, including variable definition and environment variable changes.
Because of this, the following doesn't work:
def-env cd_with_fallback [arg = ""] {
let fall_back_path = "/tmp"
if $arg != "" {
cd $arg
} else {
cd $fall_back_path
}
}
Instead, you have to use cd in the top level scope:
def-env cd_with_fallback [arg = ""] {
let fall_back_path = "/tmp"
let path = if $arg != "" {
$arg
} else {
$fall_back_path
}
cd $path
}"#
} }
fn is_parser_keyword(&self) -> bool { fn is_parser_keyword(&self) -> bool {
@ -39,17 +62,20 @@ impl Command for DefEnv {
&self, &self,
_engine_state: &EngineState, _engine_state: &EngineState,
_stack: &mut Stack, _stack: &mut Stack,
_call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(PipelineData::empty()) Ok(PipelineData::new(call.head))
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "Set environment variable by call a custom command", description: "Set environment variable by call a custom command",
example: r#"def-env foo [] { let-env BAR = "BAZ" }; foo; $env.BAR"#, example: r#"def-env foo [] { let-env BAR = "BAZ" }; foo; $env.BAR"#,
result: Some(Value::test_string("BAZ")), result: Some(Value::String {
val: "BAZ".to_string(),
span: Span::test_data(),
}),
}] }]
} }
} }

View File

@ -19,11 +19,6 @@ impl Command for Describe {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("describe") Signature::build("describe")
.input_output_types(vec![(Type::Any, Type::String)]) .input_output_types(vec![(Type::Any, Type::String)])
.switch(
"no-collect",
"do not collect streams of structured data",
Some('n'),
)
.category(Category::Core) .category(Category::Core)
} }
@ -35,58 +30,32 @@ impl Command for Describe {
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
if matches!(input, PipelineData::ExternalStream { .. }) {
let no_collect: bool = call.has_flag("no-collect"); Ok(PipelineData::Value(
Value::string("raw input", call.head),
let description = match input { None,
PipelineData::ExternalStream { .. } => "raw input".into(), ))
PipelineData::ListStream(_, _) => {
if no_collect {
"stream".into()
} else { } else {
let value = input.into_value(head); let value = input.into_value(call.head);
let base_description = match value { let description = match value {
Value::CustomValue { val, .. } => val.value_string(), Value::CustomValue { val, .. } => val.value_string(),
_ => value.get_type().to_string(), _ => value.get_type().to_string(),
}; };
format!("{base_description} (stream)")
}
}
_ => {
let value = input.into_value(head);
match value {
Value::CustomValue { val, .. } => val.value_string(),
_ => value.get_type().to_string(),
}
}
};
Ok(Value::String { Ok(Value::String {
val: description, val: description,
span: head, span: head,
} }
.into_pipeline_data()) .into_pipeline_data())
} }
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![Example {
Example {
description: "Describe the type of a string", description: "Describe the type of a string",
example: "'hello' | describe", example: "'hello' | describe",
result: Some(Value::test_string("string")), result: Some(Value::test_string("string")),
}, }]
Example {
description: "Describe a stream of data, collecting it first",
example: "[1 2 3] | each {|i| $i} | describe",
result: Some(Value::test_string("list<int> (stream)")),
},
Example {
description: "Describe the input but do not collect streams",
example: "[1 2 3] | each {|i| $i} | describe --no-collect",
result: Some(Value::test_string("stream")),
},
]
} }
fn search_terms(&self) -> Vec<&str> { fn search_terms(&self) -> Vec<&str> {

View File

@ -1,11 +1,8 @@
use std::thread;
use nu_engine::{eval_block_with_early_return, CallExt}; use nu_engine::{eval_block_with_early_return, CallExt};
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Closure, Command, EngineState, Stack}; use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, SyntaxShape, Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -17,7 +14,7 @@ impl Command for Do {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Run a closure, providing it with the pipeline input" "Run a block"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
@ -26,25 +23,25 @@ impl Command for Do {
.input_output_types(vec![(Type::Any, Type::Any)]) .input_output_types(vec![(Type::Any, Type::Any)])
.switch( .switch(
"ignore-errors", "ignore-errors",
"ignore errors as the closure runs", "ignore errors as the block runs",
Some('i'), Some('i'),
) )
.switch( .switch(
"ignore-shell-errors", "ignore-shell-errors",
"ignore shell errors as the closure runs", "ignore shell errors as the block runs",
Some('s'), Some('s'),
) )
.switch( .switch(
"ignore-program-errors", "ignore-program-errors",
"ignore external program errors as the closure runs", "ignore program errors as the block runs",
Some('p'), Some('p'),
) )
.switch( .switch(
"capture-errors", "capture-errors",
"catch errors as the closure runs, and return them", "capture errors as the block runs and return it",
Some('c'), Some('c'),
) )
.rest("rest", SyntaxShape::Any, "the parameter(s) for the closure") .rest("rest", SyntaxShape::Any, "the parameter(s) for the block")
.category(Category::Core) .category(Category::Core)
} }
@ -109,7 +106,7 @@ impl Command for Do {
block, block,
input, input,
call.redirect_stdout, call.redirect_stdout,
call.redirect_stdout, capture_errors || ignore_shell_errors || ignore_program_errors,
); );
match result { match result {
@ -121,58 +118,6 @@ impl Command for Do {
metadata, metadata,
trim_end_newline, trim_end_newline,
}) if capture_errors => { }) if capture_errors => {
// Use a thread to receive stdout message.
// Or we may get a deadlock if child process sends out too much bytes to stderr.
//
// For example: in normal linux system, stderr pipe's limit is 65535 bytes.
// if child process sends out 65536 bytes, the process will be hanged because no consumer
// consumes the first 65535 bytes
// So we need a thread to receive stdout message, then the current thread can continue to consume
// stderr messages.
let stdout_handler = stdout.map(|stdout_stream| {
thread::Builder::new()
.name("stderr redirector".to_string())
.spawn(move || {
let ctrlc = stdout_stream.ctrlc.clone();
let span = stdout_stream.span;
RawStream::new(
Box::new(
vec![stdout_stream.into_bytes().map(|s| s.item)].into_iter(),
),
ctrlc,
span,
None,
)
})
.expect("Failed to create thread")
});
// Intercept stderr so we can return it in the error if the exit code is non-zero.
// The threading issues mentioned above dictate why we also need to intercept stdout.
let mut stderr_ctrlc = None;
let stderr_msg = match stderr {
None => "".to_string(),
Some(stderr_stream) => {
stderr_ctrlc = stderr_stream.ctrlc.clone();
stderr_stream.into_string().map(|s| s.item)?
}
};
let stdout = if let Some(handle) = stdout_handler {
match handle.join() {
Err(err) => {
return Err(ShellError::ExternalCommand(
"Fail to receive external commands stdout message".to_string(),
format!("{err:?}"),
span,
));
}
Ok(res) => Some(res),
}
} else {
None
};
let mut exit_code_ctrlc = None; let mut exit_code_ctrlc = None;
let exit_code: Vec<Value> = match exit_code { let exit_code: Vec<Value> = match exit_code {
None => vec![], None => vec![],
@ -183,6 +128,11 @@ impl Command for Do {
}; };
if let Some(Value::Int { val: code, .. }) = exit_code.last() { if let Some(Value::Int { val: code, .. }) = exit_code.last() {
if *code != 0 { if *code != 0 {
let stderr_msg = match stderr {
None => "".to_string(),
Some(stderr_stream) => stderr_stream.into_string().map(|s| s.item)?,
};
return Err(ShellError::ExternalCommand( return Err(ShellError::ExternalCommand(
"External command failed".to_string(), "External command failed".to_string(),
stderr_msg, stderr_msg,
@ -193,12 +143,7 @@ impl Command for Do {
Ok(PipelineData::ExternalStream { Ok(PipelineData::ExternalStream {
stdout, stdout,
stderr: Some(RawStream::new( stderr,
Box::new(vec![Ok(stderr_msg.into_bytes())].into_iter()),
stderr_ctrlc,
span,
None,
)),
exit_code: Some(ListStream::from_stream( exit_code: Some(ListStream::from_stream(
exit_code.into_iter(), exit_code.into_iter(),
exit_code_ctrlc, exit_code_ctrlc,
@ -215,35 +160,18 @@ impl Command for Do {
span, span,
metadata, metadata,
trim_end_newline, trim_end_newline,
}) if ignore_program_errors && !call.redirect_stdout => { }) if ignore_program_errors => Ok(PipelineData::ExternalStream {
Ok(PipelineData::ExternalStream {
stdout, stdout,
stderr, stderr,
exit_code: None, exit_code: None,
span, span,
metadata, metadata,
trim_end_newline, trim_end_newline,
}) }),
} Ok(PipelineData::Value(Value::Error { .. }, ..)) if ignore_shell_errors => {
Ok(PipelineData::Value(Value::Error { .. }, ..)) | Err(_) if ignore_shell_errors => { Ok(PipelineData::new(call.head))
Ok(PipelineData::empty())
}
Ok(PipelineData::ListStream(ls, metadata)) if ignore_shell_errors => {
// check if there is a `Value::Error` in given list stream first.
let mut values = vec![];
let ctrlc = ls.ctrlc.clone();
for v in ls {
if let Value::Error { .. } = v {
values.push(Value::nothing(call.head));
} else {
values.push(v)
}
}
Ok(PipelineData::ListStream(
ListStream::from_stream(values.into_iter(), ctrlc),
metadata,
))
} }
Err(_) if ignore_shell_errors => Ok(PipelineData::new(call.head)),
r => r, r => r,
} }
} }
@ -251,27 +179,22 @@ impl Command for Do {
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
description: "Run the closure", description: "Run the block",
example: r#"do { echo hello }"#, example: r#"do { echo hello }"#,
result: Some(Value::test_string("hello")), result: Some(Value::test_string("hello")),
}, },
Example { Example {
description: "Run a stored first-class closure", description: "Run the block and ignore both shell and program errors",
example: r#"let text = "I am enclosed"; let hello = {|| echo $text}; do $hello"#,
result: Some(Value::test_string("I am enclosed")),
},
Example {
description: "Run the closure and ignore both shell and external program errors",
example: r#"do -i { thisisnotarealcommand }"#, example: r#"do -i { thisisnotarealcommand }"#,
result: None, result: None,
}, },
Example { Example {
description: "Run the closure and ignore shell errors", description: "Run the block and ignore shell errors",
example: r#"do -s { thisisnotarealcommand }"#, example: r#"do -s { thisisnotarealcommand }"#,
result: None, result: None,
}, },
Example { Example {
description: "Run the closure and ignore external program errors", description: "Run the block and ignore program errors",
example: r#"do -p { nu -c 'exit 1' }; echo "I'll still run""#, example: r#"do -p { nu -c 'exit 1' }; echo "I'll still run""#,
result: None, result: None,
}, },
@ -281,12 +204,12 @@ impl Command for Do {
result: None, result: None,
}, },
Example { Example {
description: "Run the closure, with a positional parameter", description: "Run the block, with a positional parameter",
example: r#"do {|x| 100 + $x } 77"#, example: r#"do {|x| 100 + $x } 77"#,
result: Some(Value::test_int(177)), result: Some(Value::test_int(177)),
}, },
Example { Example {
description: "Run the closure, with input", description: "Run the block, with input",
example: r#"77 | do {|x| 100 + $in }"#, example: r#"77 | do {|x| 100 + $in }"#,
result: None, // TODO: returns 177 result: None, // TODO: returns 177
}, },

View File

@ -51,7 +51,13 @@ little reason to use this over just writing the values as-is."#
std::cmp::Ordering::Equal => PipelineData::Value(to_be_echoed[0].clone(), None), std::cmp::Ordering::Equal => PipelineData::Value(to_be_echoed[0].clone(), None),
// When there are no elements, we echo the empty string // When there are no elements, we echo the empty string
std::cmp::Ordering::Less => PipelineData::Value(Value::string("", call.head), None), std::cmp::Ordering::Less => PipelineData::Value(
Value::String {
val: "".to_string(),
span: call.head,
},
None,
),
} }
}) })
} }

View File

@ -2,7 +2,7 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -15,7 +15,6 @@ impl Command for ErrorMake {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("error make") Signature::build("error make")
.input_output_types(vec![(Type::Nothing, Type::Error)])
.required("error_struct", SyntaxShape::Record, "the error to create") .required("error_struct", SyntaxShape::Record, "the error to create")
.switch( .switch(
"unspanned", "unspanned",
@ -39,7 +38,7 @@ impl Command for ErrorMake {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let span = call.head; let span = call.head;
let arg: Value = call.req(engine_state, stack, 0)?; let arg: Value = call.req(engine_state, stack, 0)?;
let unspanned = call.has_flag("unspanned"); let unspanned = call.has_flag("unspanned");
@ -109,7 +108,10 @@ fn make_error(value: &Value, throw_span: Option<Span>) -> Option<ShellError> {
) => Some(ShellError::GenericError( ) => Some(ShellError::GenericError(
message, message,
label_text, label_text,
Some(Span::new(start as usize, end as usize)), Some(Span {
start: start as usize,
end: end as usize,
}),
None, None,
Vec::new(), Vec::new(),
)), )),

View File

@ -2,7 +2,7 @@ use nu_engine::get_full_help;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value, Category, Example, IntoPipelineData, PipelineData, Signature, Span, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -38,7 +38,7 @@ impl Command for ExportCommand {
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(Value::String { Ok(Value::String {
val: get_full_help( val: get_full_help(
&ExportCommand.signature(), &ExportCommand.signature(),
@ -56,7 +56,10 @@ impl Command for ExportCommand {
vec![Example { vec![Example {
description: "Export a definition from a module", description: "Export a definition from a module",
example: r#"module utils { export def my-command [] { "hello" } }; use utils my-command; my-command"#, example: r#"module utils { export def my-command [] { "hello" } }; use utils my-command; my-command"#,
result: Some(Value::test_string("hello")), result: Some(Value::String {
val: "hello".to_string(),
span: Span::test_data(),
}),
}] }]
} }

View File

@ -1,6 +1,6 @@
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type}; use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Type};
#[derive(Clone)] #[derive(Clone)]
pub struct ExportAlias; pub struct ExportAlias;
@ -39,10 +39,10 @@ impl Command for ExportAlias {
&self, &self,
_engine_state: &EngineState, _engine_state: &EngineState,
_stack: &mut Stack, _stack: &mut Stack,
_call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(PipelineData::empty()) Ok(PipelineData::new(call.head))
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {

View File

@ -1,8 +1,6 @@
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Type, Value};
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
};
#[derive(Clone)] #[derive(Clone)]
pub struct ExportDef; pub struct ExportDef;
@ -38,17 +36,20 @@ impl Command for ExportDef {
&self, &self,
_engine_state: &EngineState, _engine_state: &EngineState,
_stack: &mut Stack, _stack: &mut Stack,
_call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(PipelineData::empty()) Ok(PipelineData::new(call.head))
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "Define a custom command in a module and call it", description: "Define a custom command in a module and call it",
example: r#"module spam { export def foo [] { "foo" } }; use spam foo; foo"#, example: r#"module spam { export def foo [] { "foo" } }; use spam foo; foo"#,
result: Some(Value::test_string("foo")), result: Some(Value::String {
val: "foo".to_string(),
span: Span::test_data(),
}),
}] }]
} }

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