Compare commits

..

1 Commits

Author SHA1 Message Date
Justin Ma
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
1278 changed files with 43034 additions and 85963 deletions

2
.gitattributes vendored
View File

@@ -1,2 +0,0 @@
# Example of a `.gitattributes` file which reclassifies `.nu` files as Nushell:
*.nu linguist-language=Nushell

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env nu
use ../toolkit.nu fmt
fmt --check --verbose

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env nu
use ../toolkit.nu [fmt, clippy]
fmt --check --verbose
clippy --verbose

View File

@@ -1 +0,0 @@
This directory is intended for templates to automatically create issues with the [create-an-issue](https://github.com/JasonEtco/create-an-issue) action.

View File

@@ -1,16 +0,0 @@
---
name: Nightly build of release binaries failed
about: Used to submit issues related to binaries release workflow
title: 'Attention: Nightly build of release binaries failed'
labels: ['build-package', 'priority']
assignees: ''
---
**Nightly build of release binaries failed**
Hi there:
If you see me here that means there is a release failure for the nightly build
Please **click the status badge** to see more details: [![Nightly Build](https://github.com/nushell/nushell/actions/workflows/nightly-build.yml/badge.svg)](https://github.com/nushell/nushell/actions/workflows/nightly-build.yml)

View File

@@ -1,6 +1,5 @@
name: Bug Report name: Bug Report
description: Create a report to help us improve description: Create a report to help us improve
labels: ["needs-triage"]
body: body:
- type: textarea - type: textarea
id: description id: description

View File

@@ -1,6 +1,6 @@
name: Feature Request name: Feature Request
description: "When you want a new feature for something that doesn't already exist" description: "When you want a new feature for something that doesn't already exist"
labels: ["needs-triage", "enhancement"] labels: "enhancement"
body: body:
- type: textarea - type: textarea
id: problem id: problem

View File

@@ -1,11 +0,0 @@
---
name: standard library bug or feature report
about: Used to submit issues related to the nu standard library
title: ''
labels: ['needs-triage', 'std-library']
assignees: ''
---
**Describe the bug or feature**
A clear and concise description of what the bug is.

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

@@ -1,40 +1,24 @@
<!--
if this PR closes one or more issues, you can automatically link the PR with
them by using one of the [*linking keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), e.g.
- this PR should close #xxxx
- fixes #xxxx
you can also mention related issues, PRs or discussions!
-->
# Description # Description
<!--
Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes.
Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience. _(Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes.)_
-->
_(Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience.)_
# User-Facing Changes # User-Facing Changes
<!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. -->
_(List of all changes that impact the user experience here. This helps us keep track of breaking changes.)_
# Tests + Formatting # Tests + Formatting
<!--
Don't forget to add tests that cover your changes. Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands: 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 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 -A clippy::result_large_err` to check that you're using the standard code style - `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 - `cargo test --workspace` to check that all tests pass
- `cargo run -- -c "use std testing; testing run-tests --path crates/nu-std"` to run the tests for the standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it automatically
> toolkit check pr
> ```
-->
# After Submitting # 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. -->
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.

View File

@@ -1,77 +1,64 @@
on: on:
pull_request: pull_request:
push: push: # Run CI on the main branch after every merge. This is important to fill the GitHub Actions cache in a way that pull requests can see it
branches: branches:
- main - main
name: continuous-integration name: continuous-integration
env:
NUSHELL_CARGO_TARGET: ci
NU_LOG_LEVEL: DEBUG
CLIPPY_OPTIONS: "-D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err"
jobs: jobs:
fmt-clippy: nu-fmt-clippy:
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 rust:
# revisiting this when 20.04 is closer to EOL (April 2025) - stable
platform: [windows-latest, macos-latest, ubuntu-20.04]
feature: [default, dataframe, extra]
include:
- feature: default
flags: ""
- feature: dataframe
flags: "--features=dataframe"
- feature: extra
flags: "--features=extra"
exclude:
- platform: windows-latest
feature: dataframe
- platform: macos-latest
feature: dataframe
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
env:
NUSHELL_CARGO_TARGET: ci
steps: steps:
- 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.5.0 uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
with:
rustflags: ""
- 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_* -- $CLIPPY_OPTIONS 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:
env:
NUSHELL_CARGO_TARGET: ci
tests:
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]
feature: [default, dataframe, extra] style: [default, dataframe]
rust:
- stable
include: include:
- feature: default - style: default
flags: "" flags: ""
- feature: dataframe - style: dataframe
flags: "--features=dataframe" flags: "--features=dataframe"
- feature: extra
flags: "--features=extra"
exclude: exclude:
# only test dataframes on Ubuntu (the fastest platform)
- platform: windows-latest - platform: windows-latest
feature: dataframe style: dataframe
- platform: macos-latest - platform: macos-latest
feature: dataframe style: dataframe
- platform: windows-latest
feature: extra
- platform: macos-latest
feature: extra
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
@@ -79,18 +66,24 @@ 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.5.0 uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
with:
rustflags: ""
- 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:
env:
NUSHELL_CARGO_TARGET: ci
std-lib-and-python-virtualenv:
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:
- stable
py: py:
- py - py
@@ -100,15 +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.5.0 uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
with:
rustflags: ""
- name: Install Nushell - name: Install Nushell
run: cargo install --path . --locked --no-default-features uses: actions-rs/cargo@v1.0.1
with:
- name: Standard library tests command: install
run: nu -c 'use std testing; testing run-tests --path crates/nu-std' args: --locked --path=. --profile ci --no-default-features
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
@@ -122,21 +113,21 @@ jobs:
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
# if we encounter problems with bleeding edge tests pin to the latest tag
# git checkout $(git describe --tags | cut -d - -f 1)
# 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
# also helps test that the plugins build without any feature unification shenanigans
plugins: plugins:
env:
NUSHELL_CARGO_TARGET: ci
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:
- stable
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
@@ -144,12 +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.5.0 uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
with:
rustflags: ""
- name: Clippy - name: Clippy
run: cargo clippy --package nu_plugin_* ${{ matrix.flags }} -- $CLIPPY_OPTIONS uses: actions-rs/cargo@v1.0.1
with:
command: clippy
args: --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
- name: Tests - name: Tests
run: cargo test --profile ci --package nu_plugin_* uses: actions-rs/cargo@v1.0.1
with:
command: test
args: --profile ci --package nu_plugin_*

41
.github/workflows/manual.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
# This is a basic workflow that is manually triggered
# Don't run it unless you know what you are doing
name: Manual Workflow for Winget Submission
# Controls when the action will run. Workflow runs when manually triggered using the UI
# or API.
on:
workflow_dispatch:
# Inputs the workflow accepts.
inputs:
ver:
# Friendly description to be shown in the UI instead of 'ver'
description: 'The nushell version to release'
# Default value if no value is explicitly provided
default: '0.66.0'
# Input has to be provided for the workflow to run
required: true
uri:
# Friendly description to be shown in the UI instead of 'uri'
description: 'The nushell windows .msi package URI to publish'
# Default value if no value is explicitly provided
default: 'https://github.com/nushell/nushell/releases/download/0.66.0/nu-0.66.0-x86_64-pc-windows-msvc.msi'
# Input has to be provided for the workflow to run
required: true
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job
rls-winget-pkg:
name: Publish winget package manually
# The type of runner that the job will run on
runs-on: windows-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Runs commands using the runners shell
- name: Submit package to Windows Package Manager Community Repository Manually
run: |
iwr https://github.com/microsoft/winget-create/releases/download/v1.0.4.0/wingetcreate.exe -OutFile wingetcreate.exe
.\wingetcreate.exe update Nushell.Nushell -s -v ${{ github.event.inputs.ver }} -u ${{ github.event.inputs.uri }} -t ${{ secrets.NUSHELL_PAT }}

View File

@@ -1,230 +0,0 @@
#
# REF:
# 1. https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrixinclude
# 2. https://github.com/JasonEtco/create-an-issue
# 3. https://docs.github.com/en/actions/learn-github-actions/variables
# 4. https://github.com/actions/github-script
#
name: Nightly Build
on:
push:
branches:
- nightly # Just for test purpose only with the nightly repo
# This schedule will run only from the default branch
schedule:
- cron: '15 1 * * *' # run at 01:15 AM UTC
defaults:
run:
shell: bash
jobs:
prepare:
name: Prepare
runs-on: ubuntu-latest
# This job is required by the release job, so we should make it run both from Nushell repo and nightly repo
# if: github.repository == 'nushell/nightly'
steps:
- name: Checkout
uses: actions/checkout@v3
if: github.repository == 'nushell/nightly'
with:
ref: main
fetch-depth: 0
# Configure PAT here: https://github.com/settings/tokens for the push operation in the following steps
token: ${{ secrets.WORKFLOW_TOKEN }}
- name: Setup Nushell
uses: hustcer/setup-nu@v3
if: github.repository == 'nushell/nightly'
with:
version: 0.81.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
- name: Prepare for Nightly Release
shell: nu {0}
if: github.repository == 'nushell/nightly'
run: |
cd $env.GITHUB_WORKSPACE
git checkout main
# We can't push if no user name and email are configured
git config user.name 'hustcer'
git config user.email 'hustcer@outlook.com'
git fetch origin main
git remote add src https://github.com/nushell/nushell.git
git fetch src main
# git pull --rebase src main
# All the changes will be overwritten by the upstream main branch
git reset --hard src/main
git push origin main -f
let sha_short = (git rev-parse --short src/main | str trim | str substring 0..7)
let tag_name = $'nightly-($sha_short)'
if (git ls-remote --tags origin $tag_name | is-empty) {
git tag -a $tag_name -m $'Nightly build from ($sha_short)'
git push origin --tags
}
release:
name: Release
needs: prepare
strategy:
fail-fast: false
matrix:
target:
- aarch64-apple-darwin
- x86_64-apple-darwin
- x86_64-pc-windows-msvc
- aarch64-pc-windows-msvc
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
- aarch64-unknown-linux-gnu
- armv7-unknown-linux-gnueabihf
- riscv64gc-unknown-linux-gnu
extra: ['bin']
include:
- target: aarch64-apple-darwin
os: macos-latest
target_rustflags: ''
- target: x86_64-apple-darwin
os: macos-latest
target_rustflags: ''
- target: x86_64-pc-windows-msvc
extra: 'bin'
os: windows-latest
target_rustflags: ''
- target: x86_64-pc-windows-msvc
extra: msi
os: windows-latest
target_rustflags: ''
- target: aarch64-pc-windows-msvc
extra: 'bin'
os: windows-latest
target_rustflags: ''
- target: aarch64-pc-windows-msvc
extra: msi
os: windows-latest
target_rustflags: ''
- target: x86_64-unknown-linux-gnu
os: ubuntu-20.04
target_rustflags: ''
- target: x86_64-unknown-linux-musl
os: ubuntu-20.04
target_rustflags: ''
- target: aarch64-unknown-linux-gnu
os: ubuntu-20.04
target_rustflags: ''
- target: armv7-unknown-linux-gnueabihf
os: ubuntu-20.04
target_rustflags: '--exclude=nu-cmd-dataframe'
- target: riscv64gc-unknown-linux-gnu
os: ubuntu-20.04
target_rustflags: '--exclude=nu-cmd-dataframe'
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v3
with:
ref: main
- name: Update Rust Toolchain Target
run: |
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
with:
rustflags: ''
- name: Setup Nushell
uses: hustcer/setup-nu@v3
with:
version: 0.81.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Release Nu Binary
id: nu
run: nu .github/workflows/release-pkg.nu
env:
OS: ${{ matrix.os }}
REF: ${{ github.ref }}
TARGET: ${{ matrix.target }}
_EXTRA_: ${{ matrix.extra }}
TARGET_RUSTFLAGS: ${{ matrix.target_rustflags }}
- name: Create an Issue for Release Failure
if: ${{ failure() }}
uses: JasonEtco/create-an-issue@v2.9.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
update_existing: true
search_existing: open
filename: .github/AUTO_ISSUE_TEMPLATE/nightly-build-fail.md
- name: Set Outputs of Short SHA
id: vars
run: |
echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
sha_short=$(git rev-parse --short HEAD)
echo "sha_short=${sha_short:0:7}" >> $GITHUB_OUTPUT
# REF: https://github.com/marketplace/actions/gh-release
# Create a release only in nushell/nightly repo
- name: Publish Archive
uses: softprops/action-gh-release@v0.1.13
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
with:
draft: false
prerelease: true
name: Nu-nightly-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.sha_short }}
tag_name: nightly-${{ steps.vars.outputs.sha_short }}
body: |
This is a NIGHTLY build of Nushell.
It is NOT recommended for production use.
files: ${{ steps.nu.outputs.archive }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
cleanup:
name: Cleanup
# Should only run in nushell/nightly repo
if: github.repository == 'nushell/nightly'
runs-on: ubuntu-latest
steps:
# Sleep for 30 minutes, waiting for the release to be published
- name: Waiting for Release
run: sleep 1800
- uses: actions/checkout@v3
with:
ref: main
- name: Setup Nushell
uses: hustcer/setup-nu@v3
with:
version: 0.81.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Keep the last a few releases
- name: Delete Older Releases
shell: nu {0}
run: |
let KEEP_COUNT = 10
let deprecated = (http get https://api.github.com/repos/nushell/nightly/releases | sort-by -r created_at | select tag_name id | range $KEEP_COUNT..)
for release in $deprecated {
print $'Deleting tag ($release.tag_name)'
git push origin --delete $release.tag_name
print $'Deleting release ($release.tag_name)'
let delete_url = $'https://api.github.com/repos/nushell/nightly/releases/($release.id)'
let version = "X-GitHub-Api-Version: 2022-11-28"
let accept = "Accept: application/vnd.github+json"
let auth = "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"
# http delete $delete_url -H $version -H $auth -H $accept
curl -L -X DELETE -H $accept -H $auth -H $version $delete_url
}

View File

@@ -6,40 +6,6 @@
# REF: # REF:
# 1. https://github.com/volks73/cargo-wix # 1. https://github.com/volks73/cargo-wix
# Instructions for manually creating an MSI for Winget Releases when they fail
# Added 2022-11-29 when Windows packaging wouldn't work
# Updated again on 2023-02-23 because msis are still failing validation
# To run this manual for windows here are the steps I take
# checkout the release you want to publish
# 1. git checkout 0.76.0
# unset CARGO_TARGET_DIR if set (I have to do this in the parent shell to get it to work)
# 2. $env:CARGO_TARGET_DIR = ""
# 2. hide-env CARGO_TARGET_DIR
# 3. $env.TARGET = 'x86_64-pc-windows-msvc'
# 4. $env.TARGET_RUSTFLAGS = ''
# 5. $env.GITHUB_WORKSPACE = 'C:\Users\dschroeder\source\repos\forks\nushell'
# 6. $env.GITHUB_OUTPUT = 'C:\Users\dschroeder\source\repos\forks\nushell\output\out.txt'
# 7. $env.OS = 'windows-latest'
# make sure 7z.exe is in your path https://www.7-zip.org/download.html
# 8. $env.Path = ($env.Path | append 'c:\apps\7-zip')
# make sure aria2c.exe is in your path https://github.com/aria2/aria2
# 9. $env.Path = ($env.Path | append 'c:\path\to\aria2c')
# make sure you have the wixtools installed https://wixtoolset.org/
# 10. $env.Path = ($env.Path | append 'C:\Users\dschroeder\AppData\Local\tauri\WixTools')
# You need to run the release-pkg twice. The first pass, with _EXTRA_ as 'bin', makes the output
# folder and builds everything. The second pass, that generates the msi file, with _EXTRA_ as 'msi'
# 11. $env._EXTRA_ = 'bin'
# 12. source .github\workflows\release-pkg.nu
# 13. cd ..
# 14. $env._EXTRA_ = 'msi'
# 15. source .github\workflows\release-pkg.nu
# After msi is generated, 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
# 16. 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
@@ -50,48 +16,38 @@ let flags = $env.TARGET_RUSTFLAGS
let dist = $'($env.GITHUB_WORKSPACE)/output' let dist = $'($env.GITHUB_WORKSPACE)/output'
let version = (open Cargo.toml | get package.version) let version = (open Cargo.toml | get package.version)
print $'Debugging info:' $'Debugging info:'
print { version: $version, bin: $bin, os: $os, target: $target, src: $src, flags: $flags, dist: $dist }; hr-line -b print { version: $version, bin: $bin, os: $os, target: $target, src: $src, flags: $flags, dist: $dist }; hr-line -b
# $env # $env
let USE_UBUNTU = 'ubuntu-20.04' let USE_UBUNTU = 'ubuntu-20.04'
print $'(char nl)Packaging ($bin) v($version) for ($target) in ($src)...'; hr-line -b $'(char nl)Packaging ($bin) v($version) for ($target) in ($src)...'; hr-line -b
if not ('Cargo.lock' | path exists) { cargo generate-lockfile } if not ('Cargo.lock' | path exists) { cargo generate-lockfile }
print $'Start building ($bin)...'; hr-line $'Start building ($bin)...'; hr-line
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# Build for Ubuntu and macOS # Build for Ubuntu and macOS
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
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
} }
match $target { if $target == 'aarch64-unknown-linux-gnu' {
'aarch64-unknown-linux-gnu' => { sudo apt-get install gcc-aarch64-linux-gnu -y
sudo apt-get install gcc-aarch64-linux-gnu -y let-env CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER = 'aarch64-linux-gnu-gcc'
$env.CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER = 'aarch64-linux-gnu-gcc' cargo-build-nu $flags
cargo-build-nu $flags } else if $target == 'armv7-unknown-linux-gnueabihf' {
} sudo apt-get install pkg-config gcc-arm-linux-gnueabihf -y
'riscv64gc-unknown-linux-gnu' => { let-env CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER = 'arm-linux-gnueabihf-gcc'
sudo apt-get install gcc-riscv64-linux-gnu -y cargo-build-nu $flags
$env.CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER = 'riscv64-linux-gnu-gcc' } else {
cargo-build-nu $flags # musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
} # Actually just for x86_64-unknown-linux-musl target
'armv7-unknown-linux-gnueabihf' => { if $os == $USE_UBUNTU { sudo apt install musl-tools -y }
sudo apt-get install pkg-config gcc-arm-linux-gnueabihf -y cargo-build-nu $flags
$env.CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER = 'arm-linux-gnueabihf-gcc'
cargo-build-nu $flags
}
_ => {
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
# Actually just for x86_64-unknown-linux-musl target
if $os == $USE_UBUNTU { sudo apt install musl-tools -y }
cargo-build-nu $flags
}
} }
} }
@@ -99,7 +55,11 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
# Build for Windows without static-link-openssl feature # Build for Windows without static-link-openssl feature
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
if $os in ['windows-latest'] { if $os in ['windows-latest'] {
cargo-build-nu $flags if ($flags | str trim | is-empty) {
cargo build --release --all --target $target
} else {
cargo build --release --all --target $target $flags
}
} }
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
@@ -108,36 +68,31 @@ if $os in ['windows-latest'] {
let suffix = if $os == 'windows-latest' { '.exe' } let suffix = if $os == 'windows-latest' { '.exe' }
# nu, nu_plugin_* were all included # nu, nu_plugin_* were all included
let executable = $'target/($target)/release/($bin)*($suffix)' let executable = $'target/($target)/release/($bin)*($suffix)'
print $'Current executable file: ($executable)' $'Current executable file: ($executable)'
cd $src; mkdir $dist; cd $src; mkdir $dist;
rm -rf $'target/($target)/release/*.d' $'target/($target)/release/nu_pretty_hex*' rm -rf $'target/($target)/release/*.d' $'target/($target)/release/nu_pretty_hex*'
print $'(char nl)All executable files:'; hr-line $'(char nl)All executable files:'; hr-line
# We have to use `print` here to make sure the command output is displayed ls -f $executable
print (ls -f $executable); sleep 1sec
print $'(char nl)Copying release files...'; hr-line $'(char nl)Copying release files...'; hr-line
"To use Nu plugins, use the register command to tell Nu where to find the plugin. For example: cp -v README.release.txt $'($dist)/README.txt'
> register ./nu_plugin_query" | save $'($dist)/README.txt'
[LICENSE $executable] | each {|it| cp -rv $it $dist } | flatten [LICENSE $executable] | each {|it| cp -rv $it $dist } | flatten
# Sleep a few seconds to make sure the cp process finished successfully
sleep 3sec
print $'(char nl)Check binary release version detail:'; hr-line $'(char nl)Check binary release version detail:'; hr-line
let ver = if $os == 'windows-latest' { let ver = if $os == 'windows-latest' {
(do -i { .\output\nu.exe -c 'version' }) | str join (do -i { ./output/nu.exe -c 'version' }) | str join
} else { } else {
(do -i { ./output/nu -c 'version' }) | str join (do -i { ./output/nu -c 'version' }) | str join
} }
if ($ver | str trim | is-empty) { if ($ver | str trim | is-empty) {
print $'(ansi r)Incompatible nu binary...(ansi reset)' $'(ansi r)Incompatible nu binary...(ansi reset)'
} else { print $ver } } else { $ver }
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# Create a release archive and send it to output for the following steps # Create a release archive and send it to output for the following steps
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
cd $dist; print $'(char nl)Creating release archive...'; hr-line cd $dist; $'(char nl)Creating release archive...'; hr-line
if $os in [$USE_UBUNTU, 'macos-latest'] { if $os in [$USE_UBUNTU, 'macos-latest'] {
let files = (ls | get name) let files = (ls | get name)
@@ -147,7 +102,7 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
mkdir $dest mkdir $dest
$files | each {|it| mv $it $dest } | ignore $files | each {|it| mv $it $dest } | ignore
print $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls $dest $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls $dest
tar -czf $archive $dest tar -czf $archive $dest
print $'archive: ---> ($archive)'; ls $archive print $'archive: ---> ($archive)'; ls $archive
@@ -158,7 +113,7 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
let releaseStem = $'($bin)-($version)-($target)' let releaseStem = $'($bin)-($version)-($target)'
print $'(char nl)Download less related stuffs...'; hr-line $'(char nl)Download less related stuffs...'; hr-line
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v608/less.exe -o less.exe aria2c https://github.com/jftuga/less-Windows/releases/download/less-v608/less.exe -o less.exe
aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
@@ -166,18 +121,18 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
if (get-env _EXTRA_) == 'msi' { if (get-env _EXTRA_) == 'msi' {
let wixRelease = $'($src)/target/wix/($releaseStem).msi' let wixRelease = $'($src)/target/wix/($releaseStem).msi'
print $'(char nl)Start creating Windows msi package...' $'(char nl)Start creating Windows msi package...'
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
} else { } else {
print $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
let archive = $'($dist)/($releaseStem).zip' let archive = $'($dist)/($releaseStem).zip'
7z a $archive * 7z a $archive *
print $'archive: ---> ($archive)'; print $'archive: ---> ($archive)';
@@ -190,17 +145,9 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
def 'cargo-build-nu' [ options: string ] { def 'cargo-build-nu' [ options: string ] {
if ($options | str trim | is-empty) { if ($options | str trim | is-empty) {
if $os == 'windows-latest' { cargo build --release --all --target $target --features=static-link-openssl
cargo build --release --all --target $target
} else {
cargo build --release --all --target $target --features=static-link-openssl
}
} else { } else {
if $os == 'windows-latest' { cargo build --release --all --target $target --features=static-link-openssl $options
cargo build --release --all --target $target $options
} else {
cargo build --release --all --target $target --features=static-link-openssl $options
}
} }
} }

View File

@@ -23,12 +23,10 @@ jobs:
- aarch64-apple-darwin - aarch64-apple-darwin
- x86_64-apple-darwin - x86_64-apple-darwin
- x86_64-pc-windows-msvc - x86_64-pc-windows-msvc
- aarch64-pc-windows-msvc
- x86_64-unknown-linux-gnu - x86_64-unknown-linux-gnu
- 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
@@ -45,14 +43,6 @@ jobs:
extra: msi extra: msi
os: windows-latest os: windows-latest
target_rustflags: '' target_rustflags: ''
- target: aarch64-pc-windows-msvc
extra: 'bin'
os: windows-latest
target_rustflags: ''
- target: aarch64-pc-windows-msvc
extra: msi
os: windows-latest
target_rustflags: ''
- target: x86_64-unknown-linux-gnu - target: x86_64-unknown-linux-gnu
os: ubuntu-20.04 os: ubuntu-20.04
target_rustflags: '' target_rustflags: ''
@@ -64,29 +54,25 @@ jobs:
target_rustflags: '' target_rustflags: ''
- target: armv7-unknown-linux-gnueabihf - target: armv7-unknown-linux-gnueabihf
os: ubuntu-20.04 os: ubuntu-20.04
target_rustflags: '--exclude=nu-cmd-dataframe' target_rustflags: ''
- target: riscv64gc-unknown-linux-gnu
os: ubuntu-20.04
target_rustflags: '--exclude=nu-cmd-dataframe'
runs-on: ${{matrix.os}} runs-on: ${{matrix.os}}
steps: steps:
- uses: actions/checkout@v3 - 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
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
with: with:
rustflags: '' override: true
profile: minimal
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.81.0 version: 0.71.0
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

28
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: 'Close stale issues and PRs'
#on: [workflow_dispatch]
on:
schedule:
- cron: '30 1 * * *'
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
with:
#debug-only: true
ascending: true
operations-per-run: 520
enable-statistics: true
repo-token: ${{ secrets.GITHUB_TOKEN }}
close-issue-message: 'This issue has been marked stale for more than 100000 days without activity. Closing this issue, but if you find that the issue is still valid, please reopen.'
close-pr-message: 'This PR has been marked stale for more than 100 days without activity. Closing this PR, but if you are still working on it, please reopen.'
days-before-issue-stale: 90
days-before-pr-stale: 45
days-before-issue-close: 100000
days-before-pr-close: 100
exempt-issue-labels: 'exempt,keep'

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@v3
- name: Check spelling
uses: crate-ci/typos@master

View File

@@ -1,14 +1,9 @@
name: Submit Nushell package to Windows Package Manager Community Repository name: Submit Nushell package to Windows Package Manager Community Repository
on: on:
release:
types: [released]
workflow_dispatch: workflow_dispatch:
inputs: release:
tag_name: types: [published]
description: 'Specific tag name'
required: true
type: string
jobs: jobs:
@@ -17,10 +12,8 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- name: Submit package to Windows Package Manager Community Repository - name: Submit package to Windows Package Manager Community Repository
uses: vedantmgoyal2009/winget-releaser@v2 run: |
with: iwr https://github.com/microsoft/winget-create/releases/download/v1.0.4.0/wingetcreate.exe -OutFile wingetcreate.exe
identifier: Nushell.Nushell $github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
version: ${{ inputs.tag_name || github.event.release.tag_name }} $installerUrl = $github.release.assets | Where-Object -Property name -match 'windows-msvc.msi' | Select -ExpandProperty browser_download_url -First 1
release-tag: ${{ inputs.tag_name || github.event.release.tag_name }} .\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}
token: ${{ secrets.NUSHELL_PAT }}
fork-user: fdncred

13
.gitignore vendored
View File

@@ -22,9 +22,6 @@ debian/nu/
# VSCode's IDE items # VSCode's IDE items
.vscode/* .vscode/*
# JetBrains' Fleet IDE
.fleet/*
# Visual Studio Extension SourceGear Rust items # Visual Studio Extension SourceGear Rust items
VSWorkspaceSettings.json VSWorkspaceSettings.json
unstable_cargo_features.txt unstable_cargo_features.txt
@@ -41,12 +38,4 @@ tarpaulin-report.html
.vs/* .vs/*
*.rsproj *.rsproj
*.rsproj.user *.rsproj.user
*.sln *.sln
*.code-workspace
# direnv
.direnv/
.envrc
# pre-commit-hooks
.pre-commit-config.yaml

View File

@@ -1,13 +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"
ratatui = "ratatui"

View File

@@ -59,12 +59,7 @@ The most comprehensive test suite we have is the `nu-test-support` crate. For te
- Run Clippy on Nushell: - Run Clippy on Nushell:
```shell ```shell
cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
```
or via the `toolkit.nu` command:
```shell
use toolkit.nu clippy
clippy
``` ```
- Run all tests: - Run all tests:
@@ -73,17 +68,6 @@ The most comprehensive test suite we have is the `nu-test-support` crate. For te
cargo test --workspace cargo test --workspace
``` ```
along with dataframe tests
```shell
cargo test --workspace --features=dataframe
```
or via the `toolkit.nu` command:
```shell
use toolkit.nu test
test
```
- Run all tests for a specific command - Run all tests for a specific command
```shell ```shell
@@ -95,30 +79,12 @@ The most comprehensive test suite we have is the `nu-test-support` crate. For te
```shell ```shell
cargo fmt --all -- --check cargo fmt --all -- --check
``` ```
or via the `toolkit.nu` command:
```shell
use toolkit.nu fmt
fmt --check
```
- Format the code in the project - Format the code in the project
```shell ```shell
cargo fmt --all cargo fmt --all
``` ```
or via the `toolkit.nu` command:
```shell
use toolkit.nu fmt
fmt
```
- Set up `git` hooks to check formatting and run `clippy` before committing and pushing:
```shell
use toolkit.nu setup-git-hooks
setup-git-hooks
```
_Unfortunately, this hook isn't available on Windows._
### Debugging Tips ### Debugging Tips
@@ -133,94 +99,3 @@ The most comprehensive test suite we have is the `nu-test-support` crate. For te
cargo run --release -- --log-level trace --log-target file cargo run --release -- --log-level trace --log-target file
open $"($nu.temp-path)/nu-($nu.pid).log" open $"($nu.temp-path)/nu-($nu.pid).log"
``` ```
## Git etiquette
As nushell thrives on its broad base of volunteer contributors and maintainers with different backgrounds we have a few guidelines for how we best utilize git and GitHub for our contributions. We strive to balance three goals with those recommendations:
1. The **volunteer maintainers and contributors** can easily follow the changes you propose, gauge the impact, and come to help you or make a decision.
2. **You as a contributor** can focus most of your time on improving the quality of the nushell project and contributing your expertise to the code or documentation.
3. Making sure we can trace back *why* decisions were made in the past.
This includes discarded approaches. Also we want to quickly identify regressions and fix when something broke.
### How we merge PRs
In general the maintainers **squash** all changes of your PR into a single commit when merging.
This keeps a clean enough linear history, while not forcing you to conform to a too strict style while iterating in your PR or fixing small problems. As an added benefit the commits on the `main` branch are tied to the discussion that happened in the PR through their `#1234` issue number.
> **Note**
> **Pro advice:** In some circumstances, we can agree on rebase-merging a particularly large but connected PR as a series of atomic commits onto the `main` branch to ensure we can more easily revert or bisect particular aspects.
### A good PR makes a change!
As a result of this PR-centric strategy and the general goal that the reviewers should easily understand your change, the **PR title and description matters** a great deal!
Make sure your description is **concise** but contains all relevant information and context.
This means demonstrating what changes, ideally through nushell code or output **examples**.
Furthermore links to technical documentation or instructions for folks that want to play with your change make the review process much easier.
> **Note**
> Try to follow the suggestions in our PR message template to make sure we can quickly focus on the technical merits and impact on the users.
#### A PR should limit itself to a single functional change or related set of same changes.
Mixing different changes in the same PR will make the review process much harder. A PR might get stuck on one aspect while we would actually like to land another change. Furthermore, if we are forced to revert a change, mixing and matching different aspects makes fixing bugs or regressions much harder.
Thus, please try to **separate out unrelated changes**!
**Don't** mix unrelated refactors with a potentially contested change.
Stylistic fixes and housekeeping can be bundled up into singular PRs.
#### Guidelines for the PR title
The PR title should be concise but contain everything for a contributor to know if they should help out in the review of this particular change.
**DON'T**
- `Update file/in/some/deeply/nested/path.rs`
- Why are you making this change?
- `Fix 2134`
- What has to be fixed?
- Hard to follow when not online on GitHub.
- ``Ignore `~` expansion``
- In what context should this change take effect?
- `[feature] refactor the whole parser and also make nushell indentation-sensitive, upgrade to using Cpython. Let me know what you think!`
- Be concise
- Maybe break up into smaller commits or PRs if the title already appears too long?
**DO**
- Mention the nushell feature or command that is affected.
- ``Fix URL parsing in `http get` (issue #1234)``
- You can mention the issue number if other context is there.
- In general, mention all related issues in the description to crosslink (e.g. `Fixes #1234`, `Closes #6789`)
- For internal changes mention the area or symbols affected if it helps to clarify
- ``Factor out `quote_string()` from parser to reuse in `explore` ``
### Review process / Merge conflicts
> **Note**
> Keep in mind that the maintainers are volunteers that need to allocate their attention to several different areas and active PRs. We will try to get back to you as soon as possible.
You can help us to make the review process a smooth experience:
- Testing:
- We generally review in detail after all the tests pass. Let us know if there is a problem you want to discuss to fix a test failure or forces us to accept a breaking change.
- If you fix a bug, it is highly recommended that you add a test that reproduces the original issue/panic in a minimal form.
- In general, added tests help us to understand which assumptions go into a particular addition/change.
- Try to also test corner cases where those assumptions might break. This can be more valuable than simply adding many similar tests.
- Commit history inside a PR during code review:
- Good **atomic commits** can help follow larger changes, but we are not pedantic.
- We don't shame fixup commits while you try to figure out a problem. They can help others see what you tried and what didn't work. (see our [squash policy](#how-we-merge-prs))
- During active review constant **force pushing** just to amend changes can be confusing!
- GitHub's UI presents reviewers with less options to compare diffs
- fetched branches for experimentation become invalid!
- the notification a maintainer receives has a low signal-to-noise ratio
- Git pros *can* use their judgement to rebase/squash to clean up the history *if it aids the understanding* of a larger change during review
- Merge conflicts:
- In general you should take care of resolving merge conflicts.
- Use your judgement whether to `git merge main` or to `git rebase main`
- Choose what simplifies having confidence in the conflict resolution and the review. **Merge commits in your branch are OK** in the squash model.
- Feel free to notify your reviewers or affected PR authors if your change might cause larger conflicts with another change.
- During the rollup of multiple PRs, we may choose to resolve merge conflicts and CI failures ourselves. (Allow maintainers to push to your branch to enable us to do this quickly.)
### License
We use the [MIT License](https://github.com/nushell/nushell/blob/main/LICENSE) in all of our Nushell projects. If you are including or referencing a crate that uses the [GPL License](https://www.gnu.org/licenses/gpl-3.0.en.html#license-text) unfortunately we will not be able to accept your PR.

3590
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,16 @@
[package] [package]
authors = ["The Nushell Project Developers"] authors = ["The Nushell Project Developers"]
build = "scripts/build.rs"
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.83.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
@@ -28,10 +27,6 @@ members = [
"crates/nu-engine", "crates/nu-engine",
"crates/nu-parser", "crates/nu-parser",
"crates/nu-system", "crates/nu-system",
"crates/nu-cmd-base",
"crates/nu-cmd-extra",
"crates/nu-cmd-lang",
"crates/nu-cmd-dataframe",
"crates/nu-command", "crates/nu-command",
"crates/nu-protocol", "crates/nu-protocol",
"crates/nu-plugin", "crates/nu-plugin",
@@ -40,105 +35,85 @@ 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-std",
"crates/nu-utils", "crates/nu-utils",
] ]
[dependencies] [dependencies]
nu-cli = { path = "./crates/nu-cli", version = "0.83.0" } chrono = { version = "0.4.23", features = ["serde"] }
nu-color-config = { path = "./crates/nu-color-config", version = "0.83.0" } crossterm = "0.24.0"
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.83.0" } ctrlc = "3.2.1"
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.83.0" }
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.83.0", optional = true }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.83.0", optional = true }
nu-command = { path = "./crates/nu-command", version = "0.83.0" }
nu-engine = { path = "./crates/nu-engine", version = "0.83.0" }
nu-explore = { path = "./crates/nu-explore", version = "0.83.0" }
nu-json = { path = "./crates/nu-json", version = "0.83.0" }
nu-parser = { path = "./crates/nu-parser", version = "0.83.0" }
nu-path = { path = "./crates/nu-path", version = "0.83.0" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.83.0" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.83.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.83.0" }
nu-system = { path = "./crates/nu-system", version = "0.83.0" }
nu-table = { path = "./crates/nu-table", version = "0.83.0" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.83.0" }
nu-std = { path = "./crates/nu-std", version = "0.83.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.83.0" }
nu-ansi-term = "0.49.0"
reedline = { version = "0.22.0", features = ["bashisms", "sqlite"]}
mimalloc = { version = "0.1.37", default-features = false, optional = true}
crossterm = "0.26"
ctrlc = "3.4"
log = "0.4" log = "0.4"
miette = { version = "5.10", features = ["fancy-no-backtrace"] } miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
serde_json = "1.0" nu-ansi-term = "0.46.0"
simplelog = "0.12" nu-cli = { path="./crates/nu-cli", version = "0.72.1" }
time = "0.3" nu-color-config = { path = "./crates/nu-color-config", version = "0.72.1" }
nu-command = { path="./crates/nu-command", version = "0.72.1" }
nu-engine = { path="./crates/nu-engine", version = "0.72.1" }
nu-json = { path="./crates/nu-json", version = "0.72.1" }
nu-parser = { path="./crates/nu-parser", version = "0.72.1" }
nu-path = { path="./crates/nu-path", version = "0.72.1" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.72.1" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.72.1" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.72.1" }
nu-system = { path = "./crates/nu-system", version = "0.72.1" }
nu-table = { path = "./crates/nu-table", version = "0.72.1" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.72.1" }
nu-utils = { path = "./crates/nu-utils", version = "0.72.1" }
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
rayon = "1.5.1"
is_executable = "1.0.1"
simplelog = "0.12.0"
time = "0.3.12"
[target.'cfg(not(target_os = "windows"))'.dependencies] [target.'cfg(not(target_os = "windows"))'.dependencies]
# Our dependencies don't use OpenSSL on Windows # Our dependencies don't use OpenSSL on Windows
openssl = { version = "0.10", features = ["vendored"], optional = true } openssl = { version = "0.10.38", features = ["vendored"], optional = true }
signal-hook = { version = "0.3", default-features = false } signal-hook = { version = "0.3.14", default-features = false }
[target.'cfg(windows)'.build-dependencies] [target.'cfg(windows)'.build-dependencies]
winresource = "0.1" winres = "0.1"
[target.'cfg(target_family = "unix")'.dependencies] [target.'cfg(target_family = "unix")'.dependencies]
nix = { version = "0.26", default-features = false, features = [ nix = { version = "0.25", default-features = false, features = ["signal", "process", "fs", "term"]}
"signal", atty = "0.2"
"process",
"fs",
"term",
] }
is-terminal = "0.4.8"
[dev-dependencies] [dev-dependencies]
nu-test-support = { path = "./crates/nu-test-support", version = "0.83.0" } nu-test-support = { path="./crates/nu-test-support", version = "0.72.1" }
tempfile = "3.7" tempfile = "3.2.0"
assert_cmd = "2.0" assert_cmd = "2.0.2"
criterion = "0.5" pretty_assertions = "1.0.0"
pretty_assertions = "1.4" serial_test = "0.8.0"
serial_test = "2.0" hamcrest2 = "0.3.0"
rstest = { version = "0.17", default-features = false } rstest = {version = "0.15.0", default-features = false}
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", # extra used to be more useful but now it's the same as default. Leaving it in for backcompat with existing build scripts
"nu-cli/plugin", extra = ["default"]
"nu-parser/plugin",
"nu-command/plugin",
"nu-protocol/plugin",
"nu-engine/plugin",
]
default = ["plugin", "which-support", "trash-support", "sqlite"] default = ["plugin", "which-support", "trash-support", "sqlite"]
stable = ["default"] stable = ["default"]
wasi = ["nu-cmd-lang/wasi"] wasi = []
# NOTE: individual features are also passed to `nu-cmd-lang` that uses them to generate the feature matrix in the `version` command
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build # Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"] static-link-openssl = ["dep:openssl"]
mimalloc = ["nu-cmd-lang/mimalloc", "dep:mimalloc"]
# Stable (Default) # Stable (Default)
which-support = ["nu-command/which-support", "nu-cmd-lang/which-support"] which-support = ["nu-command/which-support"]
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"] trash-support = ["nu-command/trash-support"]
# Extra feature for nushell # Extra
extra = ["dep:nu-cmd-extra", "nu-cmd-lang/extra"]
# Dataframe feature for nushell # Dataframe feature for nushell
dataframe = ["dep:nu-cmd-dataframe", "nu-cmd-lang/dataframe"] dataframe = ["nu-command/dataframe"]
# SQLite commands for nushell # SQLite commands for nushell
sqlite = ["nu-command/sqlite", "nu-cmd-lang/sqlite"] sqlite = ["nu-command/sqlite"]
[profile.release] [profile.release]
opt-level = "s" # Optimize for size opt-level = "s" # Optimize for size
strip = "debuginfo" strip = "debuginfo"
lto = "thin" lto = "thin"
@@ -160,17 +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" }
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.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,6 +1,6 @@
MIT License MIT License
Copyright (c) 2019 - 2023 The Nushell Project Developers Copyright (c) 2019 - 2022 The Nushell Project Developers
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,29 +1,27 @@
# 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)](https://github.com/nushell/nushell/actions) ![Build Status](https://img.shields.io/github/workflow/status/nushell/nushell/continuous-integration)
[![Nightly Build](https://github.com/nushell/nushell/actions/workflows/nightly-build.yml/badge.svg)](https://github.com/nushell/nushell/actions/workflows/nightly-build.yml)
[![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)](https://github.com/nushell/nushell/graphs/commit-activity) ![GitHub commit activity](https://img.shields.io/github/commit-activity/m/nushell/nushell)
[![GitHub contributors](https://img.shields.io/github/contributors/nushell/nushell)](https://github.com/nushell/nushell/graphs/contributors) ![GitHub contributors](https://img.shields.io/github/contributors/nushell/nushell)
[![codecov](https://codecov.io/gh/nushell/nushell/branch/main/graph/badge.svg?token=JheS8qu2II)](https://codecov.io/gh/nushell/nushell)
A new type of shell. A new type of shell.
![Example of nushell](assets/nushell-autocomplete6.gif "Example of nushell") ![Example of nushell](images/nushell-autocomplete6.gif "Example of nushell")
## Table of Contents <!-- omit in toc --> ## Table of Contents <!-- omit in toc -->
- [Status](#status) - [Status](#status)
- [Learning About Nu](#learning-about-nu) - [Learning About Nu](#learning-about-nu)
- [Installation](#installation) - [Installation](#installation)
- [Configuration](#configuration)
- [Philosophy](#philosophy) - [Philosophy](#philosophy)
- [Pipelines](#pipelines) - [Pipelines](#pipelines)
- [Opening files](#opening-files) - [Opening files](#opening-files)
- [Plugins](#plugins) - [Plugins](#plugins)
- [Goals](#goals) - [Goals](#goals)
- [Progress](#progress)
- [Officially Supported By](#officially-supported-by) - [Officially Supported By](#officially-supported-by)
- [Contributing](#contributing) - [Contributing](#contributing)
- [License](#license) - [License](#license)
@@ -34,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!
@@ -49,29 +47,13 @@ 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:
[![Packaging status](https://repology.org/badge/vertical-allrepos/nushell.svg)](https://repology.org/project/nushell/versions) [![Packaging status](https://repology.org/badge/vertical-allrepos/nushell.svg)](https://repology.org/project/nushell/versions)
## Configuration
The default configurations can be found at [sample_config](crates/nu-utils/src/sample_config)
which are the configuration files one gets when they startup Nushell for the first time.
It sets all of the default configuration to run Nushell. From here one can
then customize this file for their specific needs.
To see where *config.nu* is located on your system simply type this command.
```rust
$nu.config-path
```
Please see our [book](https://www.nushell.sh) for all of the Nushell documentation.
## Philosophy ## Philosophy
@@ -192,9 +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 while the [showcase repo](https://github.com/nushell/showcase) *shows* off informative blog posts that have been written about Nushell along with videos that highlight technical
topics that have been presented.
## 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.
@@ -209,6 +188,27 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat
- Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state. - Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
## Progress
Nu is under heavy development and will naturally change as it matures. The chart below isn't meant to be exhaustive, but it helps give an idea for some of the areas of development and their relative maturity:
| Features | Not started | Prototype | MVP | Preview | Mature | Notes |
| ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- |
| Aliases | | | | X | | Aliases allow for shortening large commands, while passing flags |
| Notebook | | X | | | | Initial jupyter support, but it loses state and lacks features |
| File ops | | | | X | | cp, mv, rm, mkdir have some support, but lacking others |
| Environment | | | | X | | Temporary environment and scoped environment variables |
| Shells | | | | X | | Basic value and file shells, but no opt-in/opt-out for commands |
| Protocol | | | | X | | Streaming protocol is serviceable |
| Plugins | | | X | | | Plugins work on one row at a time, lack batching and expression eval |
| Errors | | | | X | | Error reporting works, but could use usability polish |
| Documentation | | | X | | | Book updated to latest release, including usage examples |
| Paging | | | | X | | Textview has paging, but we'd like paging for tables |
| Functions | | | | X | | Functions and aliases are supported |
| Variables | | | | X | | Nu supports variables and environment variables |
| Completions | | | | X | | Completions for filepaths |
| Type-checking | | | | x | | Commands check basic types, and input/output types |
## Officially Supported By ## Officially Supported By
Please submit an issue or PR to be added to this list. Please submit an issue or PR to be added to this list.
@@ -218,8 +218,6 @@ Please submit an issue or PR to be added to this list.
- [oh-my-posh](https://ohmyposh.dev) - [oh-my-posh](https://ohmyposh.dev)
- [Couchbase Shell](https://couchbase.sh) - [Couchbase Shell](https://couchbase.sh)
- [virtualenv](https://github.com/pypa/virtualenv) - [virtualenv](https://github.com/pypa/virtualenv)
- [atuin](https://github.com/ellie/atuin)
- [clap](https://github.com/clap-rs/clap/tree/master/clap_complete_nushell)
## Contributing ## Contributing

3
README.release.txt Normal file
View File

@@ -0,0 +1,3 @@
To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
> register ./nu_plugin_query

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,174 +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::{engine::EngineState, PipelineData, Span, Value};
use nu_utils::{get_default_config, get_default_env};
fn load_bench_commands() -> EngineState {
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context())
}
// 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 = load_bench_commands();
// 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 = load_bench_commands();
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 = load_bench_commands();
// 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 = load_bench_commands();
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 = load_bench_commands();
// 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, 15), (10000, 5), (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, 15), (10000, 5), (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

@@ -1,10 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
DIR=$(readlink -f $(dirname "${BASH_SOURCE[0]}"))
REPO_ROOT=$(dirname $DIR)
echo "---------------------------------------------------------------" echo "---------------------------------------------------------------"
echo "Building nushell (nu) with dataframes and all the plugins" echo "Building nushell (nu) with dataframes and all the plugins"
echo "---------------------------------------------------------------" echo "---------------------------------------------------------------"
@@ -19,17 +15,11 @@ NU_PLUGINS=(
) )
echo "Building nushell" echo "Building nushell"
( cargo build --features=dataframe
cd $REPO_ROOT
cargo build --features=dataframe
)
for plugin in "${NU_PLUGINS[@]}" for plugin in "${NU_PLUGINS[@]}"
do do
echo '' && cd crates/"$plugin"
echo "Building $plugin..." echo "Building $plugin..."
echo "-----------------------------" echo "-----------------------------"
( cargo build && cd ../..
cd "$REPO_ROOT/crates/$plugin"
cargo build
)
done done

36
build-all-windows.cmd Normal file
View File

@@ -0,0 +1,36 @@
@echo off
@echo -------------------------------------------------------------------
@echo Building nushell (nu.exe) with dataframes and all the plugins
@echo -------------------------------------------------------------------
@echo.
echo Building nushell.exe
cargo build cargo build --features=dataframe
@echo.
@cd crates\nu_plugin_example
echo Building nu_plugin_example.exe
cargo build
@echo.
@cd ..\..\crates\nu_plugin_gstat
echo Building nu_plugin_gstat.exe
cargo build
@echo.
@cd ..\..\crates\nu_plugin_inc
echo Building nu_plugin_inc.exe
cargo build
@echo.
@cd ..\..\crates\nu_plugin_query
echo Building nu_plugin_query.exe
cargo build
@echo.
@cd ..\..\crates\nu_plugin_custom_values
echo Building nu_plugin_custom_values.exe
cargo build
@echo.
@cd ..\..

23
build-all.nu Normal file
View File

@@ -0,0 +1,23 @@
echo '-------------------------------------------------------------------'
echo 'Building nushell (nu) with dataframes and all the plugins'
echo '-------------------------------------------------------------------'
echo $'(char nl)Building nushell'
echo '----------------------------'
cargo build --features=dataframe
let plugins = [
nu_plugin_inc,
nu_plugin_gstat,
nu_plugin_query,
nu_plugin_example,
nu_plugin_custom_values,
]
for plugin in $plugins {
$'(char nl)Building ($plugin)'
'----------------------------'
cd $'crates/($plugin)'
cargo build
ignore
}

View File

@@ -1,6 +1,6 @@
#[cfg(windows)] #[cfg(windows)]
fn main() { fn main() {
let mut res = winresource::WindowsResource::new(); let mut res = winres::WindowsResource::new();
res.set("ProductName", "Nushell"); res.set("ProductName", "Nushell");
res.set("FileDescription", "Nushell"); res.set("FileDescription", "Nushell");
res.set("LegalCopyright", "Copyright (C) 2022"); res.set("LegalCopyright", "Copyright (C) 2022");

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: 1 # Disabled windows else: 2

View File

@@ -5,40 +5,35 @@ 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.83.0" version = "0.72.1"
[lib]
bench = false
[dev-dependencies] [dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.83.0" } nu-test-support = { path="../nu-test-support", version = "0.72.1" }
nu-test-support = { path = "../nu-test-support", version = "0.83.0" } nu-command = { path = "../nu-command", version = "0.72.1" }
rstest = { version = "0.17.0", default-features = false } rstest = {version = "0.15.0", default-features = false}
[dependencies] [dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.83.0" } nu-engine = { path = "../nu-engine", version = "0.72.1" }
nu-command = { path = "../nu-command", version = "0.83.0" } nu-path = { path = "../nu-path", version = "0.72.1" }
nu-engine = { path = "../nu-engine", version = "0.83.0" } nu-parser = { path = "../nu-parser", version = "0.72.1" }
nu-path = { path = "../nu-path", version = "0.83.0" } nu-protocol = { path = "../nu-protocol", version = "0.72.1" }
nu-parser = { path = "../nu-parser", version = "0.83.0" } nu-utils = { path = "../nu-utils", version = "0.72.1" }
nu-protocol = { path = "../nu-protocol", version = "0.83.0" } nu-ansi-term = "0.46.0"
nu-utils = { path = "../nu-utils", version = "0.83.0" } nu-color-config = { path = "../nu-color-config", version = "0.72.1" }
nu-color-config = { path = "../nu-color-config", version = "0.83.0" } reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
nu-ansi-term = "0.49.0"
reedline = { version = "0.22.0", features = ["bashisms", "sqlite"]}
chrono = { default-features = false, features = ["std"], version = "0.4" } atty = "0.2.14"
crossterm = "0.26" chrono = { default-features = false, features = ["std"], version = "0.4.23" }
fancy-regex = "0.11" crossterm = "0.24.0"
fuzzy-matcher = "0.3" fancy-regex = "0.10.0"
is_executable = "1.0" fuzzy-matcher = "0.3.7"
is-terminal = "0.4.8" is_executable = "1.0.1"
lazy_static = "1.4.0"
log = "0.4" log = "0.4"
miette = { version = "5.10", features = ["fancy-no-backtrace"] } miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
once_cell = "1.18"
percent-encoding = "2" percent-encoding = "2"
sysinfo = "0.29" sysinfo = "0.26.2"
unicode-segmentation = "1.10" thiserror = "1.0.31"
[features] [features]
plugin = [] plugin = []

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2019 - 2023 The Nushell Project Developers Copyright (c) 2019 - 2022 The Nushell Project Developers
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,9 +1,9 @@
use crate::util::report_error;
use log::info; use log::info;
use miette::Result; use miette::Result;
use nu_engine::{convert_env_values, eval_block}; use nu_engine::{convert_env_values, eval_block};
use nu_parser::parse; use nu_parser::parse;
use nu_protocol::engine::Stack; use nu_protocol::engine::Stack;
use nu_protocol::report_error;
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, StateWorkingSet}, engine::{EngineState, StateWorkingSet},
PipelineData, Spanned, Value, PipelineData, Spanned, Value,
@@ -34,9 +34,9 @@ pub fn evaluate_commands(
let mut working_set = StateWorkingSet::new(engine_state); let mut working_set = StateWorkingSet::new(engine_state);
let output = parse(&mut working_set, None, commands.item.as_bytes(), false); let (output, err) = parse(&mut working_set, None, commands.item.as_bytes(), false, &[]);
if let Some(err) = working_set.parse_errors.first() { if let Some(err) = err {
report_error(&working_set, err); report_error(&working_set, &err);
std::process::exit(1); std::process::exit(1);
} }

View File

@@ -1,129 +0,0 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category;
use nu_protocol::IntoPipelineData;
use nu_protocol::{PipelineData, ShellError, Signature, SyntaxShape, Type, Value};
use unicode_segmentation::UnicodeSegmentation;
#[derive(Clone)]
pub struct Commandline;
impl Command for Commandline {
fn name(&self) -> &str {
"commandline"
}
fn signature(&self) -> Signature {
Signature::build("commandline")
.input_output_types(vec![
(Type::Nothing, Type::Nothing),
(Type::String, Type::String),
])
.switch(
"cursor",
"Set or get the current cursor position",
Some('c'),
)
.switch(
"append",
"appends the string to the end of the buffer",
Some('a'),
)
.switch(
"insert",
"inserts the string into the buffer at the cursor position",
Some('i'),
)
.switch(
"replace",
"replaces the current contents of the buffer (default)",
Some('r'),
)
.optional(
"cmd",
SyntaxShape::String,
"the string to perform the operation with",
)
.category(Category::Core)
}
fn usage(&self) -> &str {
"View or modify the current command line input buffer."
}
fn search_terms(&self) -> Vec<&str> {
vec!["repl", "interactive"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
if call.has_flag("cursor") {
let cmd_str = cmd.as_string()?;
match cmd_str.parse::<i64>() {
Ok(n) => {
repl.cursor_pos = if n <= 0 {
0usize
} else {
repl.buffer
.grapheme_indices(true)
.map(|(i, _c)| i)
.nth(n as usize)
.unwrap_or(repl.buffer.len())
}
}
Err(_) => {
return Err(ShellError::CantConvert {
to_type: "int".to_string(),
from_type: "string".to_string(),
span: cmd.span()?,
help: Some(format!(
r#"string "{cmd_str}" does not represent a valid integer"#
)),
})
}
}
} else if call.has_flag("append") {
repl.buffer.push_str(&cmd.as_string()?);
} else if call.has_flag("insert") {
let cmd_str = cmd.as_string()?;
let cursor_pos = repl.cursor_pos;
repl.buffer.insert_str(cursor_pos, &cmd_str);
repl.cursor_pos += cmd_str.len();
} else {
repl.buffer = cmd.as_string()?;
repl.cursor_pos = repl.buffer.len();
}
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
} else {
let repl = engine_state.repl_state.lock().expect("repl state mutex");
if call.has_flag("cursor") {
let char_pos = repl
.buffer
.grapheme_indices(true)
.chain(std::iter::once((repl.buffer.len(), "")))
.position(|(i, _c)| i == repl.cursor_pos)
.expect("Cursor position isn't on a grapheme boundary");
Ok(Value::String {
val: char_pos.to_string(),
span: call.head,
}
.into_pipeline_data())
} else {
Ok(Value::String {
val: repl.buffer.to_string(),
span: call.head,
}
.into_pipeline_data())
}
}
}
}

View File

@@ -1,33 +0,0 @@
use nu_protocol::engine::{EngineState, StateWorkingSet};
use crate::commands::*;
pub fn add_cli_context(mut engine_state: EngineState) -> EngineState {
let delta = {
let mut working_set = StateWorkingSet::new(&engine_state);
macro_rules! bind_command {
( $( $command:expr ),* $(,)? ) => {
$( working_set.add_decl(Box::new($command)); )*
};
}
bind_command! {
Commandline,
History,
HistorySession,
Keybindings,
KeybindingsDefault,
KeybindingsList,
KeybindingsListen,
};
working_set.render()
};
if let Err(err) = engine_state.merge_delta(delta) {
eprintln!("Error creating CLI command context: {err:?}");
}
engine_state
}

View File

@@ -1,264 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, HistoryFileFormat, IntoInterruptiblePipelineData, PipelineData, ShellError,
Signature, Span, Type, Value,
};
use reedline::{
FileBackedHistory, History as ReedlineHistory, HistoryItem, SearchDirection, SearchQuery,
SqliteBackedHistory,
};
#[derive(Clone)]
pub struct History;
impl Command for History {
fn name(&self) -> &str {
"history"
}
fn usage(&self) -> &str {
"Get the command history."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("history")
.input_output_types(vec![
(Type::Nothing, Type::Table(vec![])),
(Type::Nothing, Type::Nothing),
])
.allow_variants_without_examples(true)
.switch("clear", "Clears out the history entries", Some('c'))
.switch(
"long",
"Show long listing of entries for sqlite history",
Some('l'),
)
.category(Category::Misc)
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
// todo for sqlite history this command should be an alias to `open ~/.config/nushell/history.sqlite3 | get history`
if let Some(config_path) = nu_path::config_dir() {
let clear = call.has_flag("clear");
let long = call.has_flag("long");
let ctrlc = engine_state.ctrlc.clone();
let mut history_path = config_path;
history_path.push("nushell");
match engine_state.config.history_file_format {
HistoryFileFormat::Sqlite => {
history_path.push("history.sqlite3");
}
HistoryFileFormat::PlainText => {
history_path.push("history.txt");
}
}
if clear {
let _ = std::fs::remove_file(history_path);
// TODO: FIXME also clear the auxiliary files when using sqlite
Ok(PipelineData::empty())
} else {
let history_reader: Option<Box<dyn ReedlineHistory>> =
match engine_state.config.history_file_format {
HistoryFileFormat::Sqlite => SqliteBackedHistory::with_file(history_path)
.map(|inner| {
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
boxed
})
.ok(),
HistoryFileFormat::PlainText => FileBackedHistory::with_file(
engine_state.config.max_history_size as usize,
history_path,
)
.map(|inner| {
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
boxed
})
.ok(),
};
match engine_state.config.history_file_format {
HistoryFileFormat::PlainText => Ok(history_reader
.and_then(|h| {
h.search(SearchQuery::everything(SearchDirection::Forward, None))
.ok()
})
.map(move |entries| {
entries
.into_iter()
.enumerate()
.map(move |(idx, entry)| Value::Record {
cols: vec!["command".to_string(), "index".to_string()],
vals: vec![
Value::String {
val: entry.command_line,
span: head,
},
Value::int(idx as i64, head),
],
span: head,
})
})
.ok_or(ShellError::FileNotFound(head))?
.into_pipeline_data(ctrlc)),
HistoryFileFormat::Sqlite => Ok(history_reader
.and_then(|h| {
h.search(SearchQuery::everything(SearchDirection::Forward, None))
.ok()
})
.map(move |entries| {
entries.into_iter().enumerate().map(move |(idx, entry)| {
create_history_record(idx, entry, long, head)
})
})
.ok_or(ShellError::FileNotFound(head))?
.into_pipeline_data(ctrlc)),
}
}
} else {
Err(ShellError::FileNotFound(head))
}
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "history | length",
description: "Get current history length",
result: None,
},
Example {
example: "history | last 5",
description: "Show last 5 commands you have ran",
result: None,
},
Example {
example: "history | wrap cmd | where cmd =~ cargo",
description: "Search all the commands from history that contains 'cargo'",
result: None,
},
]
}
}
fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span) -> Value {
//1. Format all the values
//2. Create a record of either short or long columns and values
let item_id_value = Value::Int {
val: match entry.id {
Some(id) => {
let ids = id.to_string();
match ids.parse::<i64>() {
Ok(i) => i,
_ => 0i64,
}
}
None => 0i64,
},
span: head,
};
let start_timestamp_value = Value::String {
val: match entry.start_timestamp {
Some(time) => time.to_string(),
None => "".into(),
},
span: head,
};
let command_value = Value::String {
val: entry.command_line,
span: head,
};
let session_id_value = Value::Int {
val: match entry.session_id {
Some(sid) => {
let sids = sid.to_string();
match sids.parse::<i64>() {
Ok(i) => i,
_ => 0i64,
}
}
None => 0i64,
},
span: head,
};
let hostname_value = Value::String {
val: match entry.hostname {
Some(host) => host,
None => "".into(),
},
span: head,
};
let cwd_value = Value::String {
val: match entry.cwd {
Some(cwd) => cwd,
None => "".into(),
},
span: head,
};
let duration_value = Value::Duration {
val: match entry.duration {
Some(d) => d.as_nanos().try_into().unwrap_or(0),
None => 0,
},
span: head,
};
let exit_status_value = Value::int(entry.exit_status.unwrap_or(0), head);
let index_value = Value::int(idx as i64, head);
if long {
Value::Record {
cols: vec![
"item_id".into(),
"start_timestamp".into(),
"command".to_string(),
"session_id".into(),
"hostname".into(),
"cwd".into(),
"duration".into(),
"exit_status".into(),
"idx".to_string(),
],
vals: vec![
item_id_value,
start_timestamp_value,
command_value,
session_id_value,
hostname_value,
cwd_value,
duration_value,
exit_status_value,
index_value,
],
span: head,
}
} else {
Value::Record {
cols: vec![
"start_timestamp".into(),
"command".to_string(),
"cwd".into(),
"duration".into(),
"exit_status".into(),
],
vals: vec![
start_timestamp_value,
command_value,
cwd_value,
duration_value,
exit_status_value,
],
span: head,
}
}
}

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() {
@@ -88,17 +87,35 @@ impl CommandCompletion {
let filter_predicate = |command: &[u8]| match_algorithm.matches_u8(command, partial); let filter_predicate = |command: &[u8]| match_algorithm.matches_u8(command, partial);
let mut results = working_set let results = working_set
.find_commands_by_predicate(filter_predicate, true) .find_commands_by_predicate(filter_predicate)
.into_iter() .into_iter()
.map(move |x| Suggestion { .map(move |x| Suggestion {
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,
}) });
.collect::<Vec<_>>();
let results_aliases = working_set
.find_aliases_by_predicate(filter_predicate)
.into_iter()
.map(move |x| Suggestion {
value: String::from_utf8_lossy(&x).to_string(),
description: None,
extra: None,
span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
append_whitespace: true,
});
let mut results = results.chain(results_aliases).collect::<Vec<_>>();
let partial = working_set.get_span_contents(span); let partial = working_set.get_span_contents(span);
let partial = String::from_utf8_lossy(partial).to_string(); let partial = String::from_utf8_lossy(partial).to_string();
@@ -111,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,
@@ -157,7 +174,7 @@ impl Completer for CommandCompletion {
.take_while(|x| { .take_while(|x| {
matches!( matches!(
x.1, x.1,
FlatShape::InternalCall(_) FlatShape::InternalCall
| FlatShape::External | FlatShape::External
| FlatShape::ExternalArg | FlatShape::ExternalArg
| FlatShape::Literal | FlatShape::Literal
@@ -170,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,
@@ -185,9 +205,8 @@ impl Completer for CommandCompletion {
let config = working_set.get_config(); let config = working_set.get_config();
let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External) let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External)
|| matches!(self.flat_shape, nu_parser::FlatShape::InternalCall(_)) || matches!(self.flat_shape, nu_parser::FlatShape::InternalCall)
|| ((span.end - span.start) == 0) || ((span.end - span.start) == 0)
|| is_passthrough_command(working_set.delta.get_file_contents())
{ {
// we're in a gap or at a command // we're in a gap or at a command
if working_set.get_span_contents(span).is_empty() && !self.force_completion_after_space if working_set.get_span_contents(span).is_empty() && !self.force_completion_after_space
@@ -205,114 +224,13 @@ impl Completer for CommandCompletion {
vec![] vec![]
}; };
subcommands.into_iter().chain(commands).collect::<Vec<_>>() subcommands
.into_iter()
.chain(commands.into_iter())
.collect::<Vec<_>>()
} }
fn get_sort_by(&self) -> SortBy { fn get_sort_by(&self) -> SortBy {
SortBy::LevenshteinDistance SortBy::LevenshteinDistance
} }
} }
pub fn find_non_whitespace_index(contents: &[u8], start: usize) -> usize {
match contents.get(start..) {
Some(contents) => {
contents
.iter()
.take_while(|x| x.is_ascii_whitespace())
.count()
+ start
}
None => start,
}
}
pub fn is_passthrough_command(working_set_file_contents: &[(Vec<u8>, usize, usize)]) -> bool {
for (contents, _, _) in working_set_file_contents {
let last_pipe_pos_rev = contents.iter().rev().position(|x| x == &b'|');
let last_pipe_pos = last_pipe_pos_rev.map(|x| contents.len() - x).unwrap_or(0);
let cur_pos = find_non_whitespace_index(contents, last_pipe_pos);
let result = match contents.get(cur_pos..) {
Some(contents) => contents.starts_with(b"sudo "),
None => false,
};
if result {
return true;
}
}
false
}
#[cfg(test)]
mod command_completions_tests {
use super::*;
#[test]
fn test_find_non_whitespace_index() {
let commands = vec![
(" hello", 4),
("sudo ", 0),
(" sudo ", 2),
(" sudo ", 2),
(" hello ", 1),
(" hello ", 3),
(" hello | sudo ", 4),
(" sudo|sudo", 5),
("sudo | sudo ", 0),
(" hello sud", 1),
];
for (idx, ele) in commands.iter().enumerate() {
let index = find_non_whitespace_index(&Vec::from(ele.0.as_bytes()), 0);
assert_eq!(index, ele.1, "Failed on index {}", idx);
}
}
#[test]
fn test_is_last_command_passthrough() {
let commands = vec![
(" hello", false),
(" sudo ", true),
("sudo ", true),
(" hello", false),
(" sudo", false),
(" sudo ", true),
(" sudo ", true),
(" sudo ", true),
(" hello ", false),
(" hello | sudo ", true),
(" sudo|sudo", false),
("sudo | sudo ", true),
(" hello sud", false),
(" sudo | sud ", false),
(" sudo|sudo ", true),
(" sudo | sudo ls | sudo ", true),
];
for (idx, ele) in commands.iter().enumerate() {
let input = ele.0.as_bytes();
let mut engine_state = EngineState::new();
engine_state.add_file("test.nu".into(), vec![]);
let delta = {
let mut working_set = StateWorkingSet::new(&engine_state);
let _ = working_set.add_file("child.nu".into(), input);
working_set.render()
};
let result = engine_state.merge_delta(delta);
assert!(
result.is_ok(),
"Merge delta has failed: {}",
result.err().unwrap()
);
let is_passthrough_command = is_passthrough_command(engine_state.get_file_contents());
assert_eq!(
is_passthrough_command, ele.1,
"index for '{}': {}",
ele.0, idx
);
}
}
}

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
@@ -113,13 +122,14 @@ impl NuCompleter {
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> { fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
let mut working_set = StateWorkingSet::new(&self.engine_state); let mut working_set = StateWorkingSet::new(&self.engine_state);
let offset = working_set.next_span_start(); let offset = working_set.next_span_start();
let (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set);
let initial_line = line.to_string(); let initial_line = line.to_string();
let mut line = line.to_string(); let alias_total_offset: usize = alias_offset.iter().sum();
line.insert(pos, 'a'); new_line.insert(alias_total_offset + pos, b'a');
let pos = offset + pos; let pos = offset + pos;
let config = self.engine_state.get_config(); let config = self.engine_state.get_config();
let output = parse(&mut working_set, Some("completer"), line.as_bytes(), false); let (output, _err) = parse(&mut working_set, Some("completer"), &new_line, false, &[]);
for pipeline in output.pipelines.into_iter() { for pipeline in output.pipelines.into_iter() {
for pipeline_element in pipeline.elements { for pipeline_element in pipeline.elements {
@@ -127,17 +137,12 @@ 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::SameTargetRedirection { cmd: (_, 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 mut spans: Vec<String> = vec![]; let mut spans: Vec<String> = vec![];
for (flat_idx, flat) in flattened.iter().enumerate() { for (flat_idx, flat) in flattened.iter().enumerate() {
let is_passthrough_command = spans
.first()
.filter(|content| *content == &String::from("sudo"))
.is_some();
// Read the current spam to string // Read the current spam to string
let current_span = working_set.get_span_contents(flat.0).to_vec(); let current_span = working_set.get_span_contents(flat.0).to_vec();
let current_span_str = String::from_utf8_lossy(&current_span); let current_span_str = String::from_utf8_lossy(&current_span);
@@ -153,17 +158,27 @@ impl NuCompleter {
} }
// Complete based on the last span // Complete based on the last span
if pos >= flat.0.start && pos < flat.0.end { if pos + span_offset >= flat.0.start && pos + span_offset < flat.0.end {
// Context variables // Context variables
let most_left_var = let most_left_var =
most_left_variable(flat_idx, &working_set, flattened.clone()); most_left_variable(flat_idx, &working_set, flattened.clone());
// Create a new span // Create a new span
let new_span = Span::new(flat.0.start, flat.0.end - 1); let new_span = if flat_idx == 0 {
Span {
start: flat.0.start,
end: flat.0.end - 1 - span_offset,
}
} else {
Span {
start: flat.0.start - 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.
let mut prefix = working_set.get_span_contents(flat.0).to_vec(); let mut prefix = working_set.get_span_contents(flat.0).to_vec();
let index = pos - flat.0.start; let index = pos - (flat.0.start - span_offset);
prefix.drain(index..); prefix.drain(index..);
// Variables completion // Variables completion
@@ -213,9 +228,8 @@ impl NuCompleter {
} }
// specially check if it is currently empty - always complete commands // specially check if it is currently empty - always complete commands
if (is_passthrough_command && flat_idx == 1) if flat_idx == 0
|| (flat_idx == 0 && working_set.get_span_contents(new_span).is_empty()
&& working_set.get_span_contents(new_span).is_empty())
{ {
let mut completer = CommandCompletion::new( let mut completer = CommandCompletion::new(
self.engine_state.clone(), self.engine_state.clone(),
@@ -236,7 +250,7 @@ impl NuCompleter {
} }
// Completions that depends on the previous expression (e.g: use, source-env) // Completions that depends on the previous expression (e.g: use, source-env)
if (is_passthrough_command && flat_idx > 1) || flat_idx > 0 { if flat_idx > 0 {
if let Some(previous_expr) = flattened.get(flat_idx - 1) { if let Some(previous_expr) = flattened.get(flat_idx - 1) {
// Read the content for the previous expression // Read the content for the previous expression
let prev_expr_str = let prev_expr_str =
@@ -383,6 +397,85 @@ impl ReedlineCompleter for NuCompleter {
} }
} }
type MatchedAlias = Vec<(Vec<u8>, Vec<u8>)>;
// Handler the completion when giving lines contains at least one alias. (e.g: `g checkout`)
// that `g` is an alias of `git`
fn try_find_alias(line: &[u8], working_set: &StateWorkingSet) -> (Vec<u8>, Vec<usize>) {
// An vector represents the offsets of alias
// e.g: the offset is 2 for the alias `g` of `git`
let mut alias_offset = vec![];
let mut output = vec![];
if let Some(matched_alias) = search_alias(line, working_set) {
let mut lens = matched_alias.len();
for (input_vec, line_vec) in matched_alias {
alias_offset.push(line_vec.len() - input_vec.len());
output.extend(line_vec);
if lens > 1 {
output.push(b' ');
lens -= 1;
}
}
if !line.is_empty() {
let last = line.last().expect("input is empty");
if last == &b' ' {
output.push(b' ');
}
}
} else {
output = line.to_vec();
}
(output, alias_offset)
}
fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option<MatchedAlias> {
let mut vec_names = vec![];
let mut vec_alias = vec![];
let mut pos = 0;
let mut is_alias = false;
for (index, character) in input.iter().enumerate() {
if *character == b' ' {
let range = &input[pos..index];
vec_names.push(range.to_owned());
pos = index + 1;
}
}
// Push the rest to names vector.
if pos < input.len() {
vec_names.push(input[pos..].to_owned());
}
for name in &vec_names {
if let Some(alias_id) = working_set.find_alias(&name[..]) {
let alias_span = working_set.get_alias(alias_id);
let mut span_vec = vec![];
is_alias = true;
for alias in alias_span {
let name = working_set.get_span_contents(*alias);
if !name.is_empty() {
span_vec.push(name);
}
}
// Join span of vector together for complex alias, e.g: `f` is an alias for `git remote -v`
let full_aliases = span_vec.join(&[b' '][..]);
vec_alias.push(full_aliases);
} else {
vec_alias.push(name.to_owned());
}
}
if is_alias {
// Zip names and alias vectors, the original inputs and its aliases mapping.
// e.g:(['g'], ['g','i','t'])
let output = vec_names.into_iter().zip(vec_alias).collect();
Some(output)
} else {
None
}
}
// reads the most left variable returning it's name (e.g: $myvar) // reads the most left variable returning it's name (e.g: $myvar)
// and the depth (a.b.c) // and the depth (a.b.c)
fn most_left_variable( fn most_left_variable(
@@ -403,7 +496,7 @@ fn most_left_variable(
let result = working_set.get_span_contents(item.0).to_vec(); let result = working_set.get_span_contents(item.0).to_vec();
match item.1 { match item.1 {
FlatShape::Variable(_) => { FlatShape::Variable => {
variables_found.push(result); variables_found.push(result);
found_var = true; found_var = true;
@@ -494,64 +587,3 @@ pub fn map_value_completions<'a>(
}) })
.collect() .collect()
} }
#[cfg(test)]
mod completer_tests {
use super::*;
#[test]
fn test_completion_helper() {
let mut engine_state =
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context());
// Custom additions
let delta = {
let working_set = nu_protocol::engine::StateWorkingSet::new(&engine_state);
working_set.render()
};
let result = engine_state.merge_delta(delta);
assert!(
result.is_ok(),
"Error merging delta: {:?}",
result.err().unwrap()
);
let mut completer = NuCompleter::new(engine_state.into(), Stack::new());
let dataset = vec![
("sudo", false, "", Vec::new()),
("sudo l", true, "l", vec!["ls", "let", "lines", "loop"]),
(" sudo", false, "", Vec::new()),
(" sudo le", true, "le", vec!["let", "length"]),
(
"ls | c",
true,
"c",
vec!["cd", "config", "const", "cp", "cal"],
),
("ls | sudo m", true, "m", vec!["mv", "mut", "move"]),
];
for (line, has_result, begins_with, expected_values) in dataset {
let result = completer.completion_helper(line, line.len());
// Test whether the result is empty or not
assert_eq!(!result.is_empty(), has_result, "line: {}", line);
// Test whether the result begins with the expected value
result
.iter()
.for_each(|x| assert!(x.value.starts_with(begins_with)));
// Test whether the result contains all the expected values
assert_eq!(
result
.iter()
.map(|x| expected_values.contains(&x.value.as_str()))
.filter(|x| *x)
.count(),
expected_values.len(),
"line: {}",
line
);
}
}
}

View File

@@ -6,7 +6,6 @@ use nu_protocol::{
PipelineData, Span, Type, Value, PipelineData, Span, Type, Value,
}; };
use reedline::Suggestion; use reedline::Suggestion;
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use super::completer::map_value_completions; use super::completer::map_value_completions;
@@ -53,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,
@@ -67,16 +66,15 @@ impl Completer for CustomCompletion {
], ],
redirect_stdout: true, redirect_stdout: true,
redirect_stderr: true, redirect_stderr: true,
parser_info: HashMap::new(),
}, },
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 { .. } => {
@@ -133,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

@@ -8,7 +8,7 @@ use std::fs;
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use super::{partial_from, prepend_base_dir, SortBy}; use super::{partial_from, prepend_base_dir};
const SEP: char = std::path::MAIN_SEPARATOR; const SEP: char = std::path::MAIN_SEPARATOR;
@@ -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
@@ -60,20 +67,12 @@ impl Completer for DirectoryCompletion {
// Sort items // Sort items
let mut sorted_items = items; let mut sorted_items = items;
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
match self.get_sort_by() { sorted_items.sort_by(|a, b| {
SortBy::Ascending => { let a_distance = levenshtein_distance(&prefix_str, &a.value);
sorted_items.sort_by(|a, b| a.value.cmp(&b.value)); let b_distance = levenshtein_distance(&prefix_str, &b.value);
} a_distance.cmp(&b_distance)
SortBy::LevenshteinDistance => { });
sorted_items.sort_by(|a, b| {
let a_distance = levenshtein_distance(&prefix_str, &a.value);
let b_distance = levenshtein_distance(&prefix_str, &b.value);
a_distance.cmp(&b_distance)
});
}
_ => (),
}
// Separate the results between hidden and non hidden // Separate the results between hidden and non hidden
let mut hidden: Vec<Suggestion> = vec![]; let mut hidden: Vec<Suggestion> = vec![];
@@ -127,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()
}; };
@@ -137,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

@@ -7,8 +7,6 @@ use reedline::Suggestion;
use std::path::{is_separator, Path}; use std::path::{is_separator, Path};
use std::sync::Arc; use std::sync::Arc;
use super::SortBy;
const SEP: char = std::path::MAIN_SEPARATOR; const SEP: char = std::path::MAIN_SEPARATOR;
#[derive(Clone)] #[derive(Clone)]
@@ -32,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()
@@ -57,20 +62,12 @@ impl Completer for FileCompletion {
// Sort items // Sort items
let mut sorted_items = items; let mut sorted_items = items;
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
match self.get_sort_by() { sorted_items.sort_by(|a, b| {
SortBy::Ascending => { let a_distance = levenshtein_distance(&prefix_str, &a.value);
sorted_items.sort_by(|a, b| a.value.cmp(&b.value)); let b_distance = levenshtein_distance(&prefix_str, &b.value);
} a_distance.cmp(&b_distance)
SortBy::LevenshteinDistance => { });
sorted_items.sort_by(|a, b| {
let a_distance = levenshtein_distance(&prefix_str, &a.value);
let b_distance = levenshtein_distance(&prefix_str, &b.value);
a_distance.cmp(&b_distance)
});
}
_ => (),
}
// Separate the results between hidden and non hidden // Separate the results between hidden and non hidden
let mut hidden: Vec<Suggestion> = vec![]; let mut hidden: Vec<Suggestion> = vec![];
@@ -134,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()
}; };
@@ -144,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))
@@ -190,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

@@ -1,5 +1,5 @@
use crate::completions::{Completer, CompletionOptions}; use crate::completions::{Completer, CompletionOptions};
use nu_engine::{column::get_columns, eval_variable}; use nu_engine::eval_variable;
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
Span, Value, Span, Value,
@@ -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,
@@ -179,13 +181,13 @@ impl Completer for VariableCompletion {
let mut removed_overlays = vec![]; let mut removed_overlays = vec![];
// Working set scope vars // Working set scope vars
for scope_frame in working_set.delta.scope.iter().rev() { for scope_frame in working_set.delta.scope.iter().rev() {
for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() { for overlay_frame in scope_frame
.active_overlays(&mut removed_overlays)
.iter()
.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,
@@ -200,13 +202,14 @@ impl Completer for VariableCompletion {
// Permanent state vars // Permanent state vars
// for scope in &self.engine_state.scope { // for scope in &self.engine_state.scope {
for overlay_frame in self.engine_state.active_overlays(&removed_overlays).rev() { for overlay_frame in self
.engine_state
.active_overlays(&removed_overlays)
.iter()
.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,
@@ -253,33 +256,7 @@ 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
}
Value::List { vals, span: _ } => {
for column_name in get_columns(vals.as_slice()) {
output.push(Suggestion {
value: column_name,
description: None,
extra: None,
span: current_span,
append_whitespace: false,
});
}
output
}
_ => output, _ => output,
} }
} }
@@ -294,7 +271,7 @@ fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
vals, vals,
span: _, span: _,
} => { } => {
for item in cols.into_iter().zip(vals) { for item in cols.into_iter().zip(vals.into_iter()) {
// Check if index matches with sublevel // Check if index matches with sublevel
if item.0.as_bytes().to_vec() == next_sublevel { if item.0.as_bytes().to_vec() == next_sublevel {
// If matches try to fetch recursively the next // If matches try to fetch recursively the next
@@ -304,39 +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 },
};
}
Value::LazyRecord { val, span: _ } => {
for col in val.column_names() {
if col.as_bytes().to_vec() == next_sublevel {
return recursive_value(
val.get_column_value(col).unwrap_or_default(),
sublevels.into_iter().skip(1).collect(),
);
}
}
// Current sublevel value not found
return Value::Nothing {
span: Span::unknown(),
};
}
Value::List { vals, span } => {
for col in get_columns(vals.as_slice()) {
if col.as_bytes().to_vec() == next_sublevel {
return recursive_value(
Value::List { vals, span }
.get_data_by_key(&col)
.unwrap_or_default(),
sublevels.into_iter().skip(1).collect(),
);
}
}
// Current sublevel value not found
return Value::Nothing {
span: Span::unknown(),
}; };
} }
_ => return val, _ => return val,
@@ -345,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,13 +1,14 @@
use crate::util::eval_source; use crate::util::{eval_source, report_error};
#[cfg(feature = "plugin")]
use log::info;
#[cfg(feature = "plugin")]
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};
use nu_protocol::report_error;
use nu_protocol::{HistoryFileFormat, PipelineData};
#[cfg(feature = "plugin")] #[cfg(feature = "plugin")]
use nu_protocol::{ParseError, Spanned}; use nu_protocol::Spanned;
#[cfg(feature = "plugin")] use nu_protocol::{HistoryFileFormat, PipelineData, Span};
use nu_utils::utils::perf;
use std::path::PathBuf; use std::path::PathBuf;
#[cfg(feature = "plugin")] #[cfg(feature = "plugin")]
@@ -23,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);
@@ -32,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")]
@@ -65,11 +56,12 @@ 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
@@ -93,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

@@ -1,15 +1,14 @@
use crate::util::eval_source; 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::report_error;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
Config, PipelineData, ShellError, Span, Value, Config, PipelineData, Span, Value,
}; };
use nu_utils::stdout_write_all_and_flush; use nu_utils::stdout_write_all_and_flush;
@@ -28,104 +27,31 @@ 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()),
);
stack.add_env_var(
"CURRENT_FILE".to_string(),
Value::string(file_path.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);
if working_set.find_decl(b"main").is_some() { let _ = parse(&mut working_set, Some(&path), &file, false, &[]);
if working_set.find_decl(b"main", &Type::Any).is_some() {
let args = format!("main {}", args.join(" ")); let args = format!("main {}", args.join(" "));
if !eval_source( if !eval_source(
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);
} }
@@ -134,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,
@@ -150,34 +76,43 @@ 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(), &[]) {
let command = engine_state.get_decl(decl_id); Some(decl_id) => {
if command.get_block_id().is_some() { let command = engine_state.get_decl(decl_id);
print_or_exit(pipeline_data, engine_state, config); if command.get_block_id().is_some() {
} else { print_or_exit(pipeline_data, engine_state, config);
// The final call on table command, it's ok to set redirect_output to false. } else {
let mut call = Call::new(Span::new(0, 0)); let table = command.run(
call.redirect_stdout = false; engine_state,
let table = command.run(engine_state, stack, &call, pipeline_data); stack,
&Call::new(Span::new(0, 0)),
pipeline_data,
);
match table { match table {
Ok(table) => { Ok(table) => {
print_or_exit(table, engine_state, config); print_or_exit(table, engine_state, config);
} }
Err(error) => { Err(error) => {
let working_set = StateWorkingSet::new(engine_state); let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &error);
std::process::exit(1); report_error(&working_set, &error);
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 {
@@ -198,12 +133,14 @@ fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, co
if let Value::Error { error } = item { if let Value::Error { error } = item {
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);
} }
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

@@ -1,7 +1,6 @@
mod commands; mod commands;
mod completions; mod completions;
mod config_files; mod config_files;
mod eval_cmds;
mod eval_file; mod eval_file;
mod menus; mod menus;
mod nu_highlight; mod nu_highlight;
@@ -14,19 +13,18 @@ mod syntax_highlight;
mod util; mod util;
mod validation; mod validation;
pub use commands::add_cli_context; pub use commands::evaluate_commands;
pub use completions::{FileCompletion, NuCompleter}; pub use completions::{FileCompletion, NuCompleter};
pub use config_files::eval_config_contents; pub use config_files::eval_config_contents;
pub use eval_cmds::evaluate_commands;
pub use eval_file::evaluate_file; pub use eval_file::evaluate_file;
pub use menus::{DescriptionMenu, NuHelpCompleter}; pub use menus::{DescriptionMenu, NuHelpCompleter};
pub use nu_cmd_base::util::get_init_cwd;
pub use nu_highlight::NuHighlight; pub use nu_highlight::NuHighlight;
pub use print::Print; pub use print::Print;
pub use prompt::NushellPrompt; pub use prompt::NushellPrompt;
pub use repl::evaluate_repl; pub use repl::evaluate_repl;
pub use repl::{eval_env_change_hook, eval_hook};
pub use syntax_highlight::NuHighlighter; pub use syntax_highlight::NuHighlighter;
pub use util::{eval_source, gather_parent_env_vars}; pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error, report_error_new};
pub use validation::NuValidator; pub use validation::NuValidator;
#[cfg(feature = "plugin")] #[cfg(feature = "plugin")]

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

@@ -57,9 +57,7 @@ impl NuHelpCompleter {
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature()); let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
if !sig.named.is_empty() { if !sig.named.is_empty() {
long_desc.push_str(&get_flags_section(sig, |v| { long_desc.push_str(&get_flags_section(sig))
v.into_string_parsable(", ", &self.0.config)
}))
} }
if !sig.required_positional.is_empty() if !sig.required_positional.is_empty()
@@ -71,18 +69,10 @@ impl NuHelpCompleter {
let _ = write!(long_desc, " {}: {}\r\n", positional.name, positional.desc); let _ = write!(long_desc, " {}: {}\r\n", positional.name, positional.desc);
} }
for positional in &sig.optional_positional { for positional in &sig.optional_positional {
let opt_suffix = if let Some(value) = &positional.default_value {
format!(
" (optional, default: {})",
&value.into_string_parsable(", ", &self.0.config),
)
} else {
(" (optional)").to_string()
};
let _ = write!( let _ = write!(
long_desc, long_desc,
" (optional) {}: {}{}\r\n", " (optional) {}: {}\r\n",
positional.name, positional.desc, opt_suffix positional.name, positional.desc
); );
} }

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")
@@ -94,10 +103,10 @@ fn convert_to_suggestions(
Some(span @ Value::Record { .. }) => { Some(span @ Value::Record { .. }) => {
let start = span let start = span
.get_data_by_key("start") .get_data_by_key("start")
.and_then(|val| val.as_int().ok()); .and_then(|val| val.as_integer().ok());
let end = span let end = span
.get_data_by_key("end") .get_data_by_key("end")
.and_then(|val| val.as_int().ok()); .and_then(|val| val.as_integer().ok());
match (start, end) { match (start, end) {
(Some(start), Some(end)) => { (Some(start), Some(end)) => {
let start = start.min(end); let start = start.min(end);
@@ -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 {
@@ -53,9 +51,7 @@ impl Command for NuHighlight {
span: head, span: head,
} }
} }
Err(err) => Value::Error { Err(err) => Value::Error { error: err },
error: Box::new(err),
},
}, },
ctrlc, ctrlc,
) )

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",
@@ -28,7 +26,7 @@ impl Command for Print {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Print the given values to stdout." "Print the given values to stdout"
} }
fn extra_usage(&self) -> &str { fn extra_usage(&self) -> &str {
@@ -47,23 +45,19 @@ Since this command has no output, there is no point in piping it with other comm
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
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;
// This will allow for easy printing of pipelines as well for arg in args {
if !args.is_empty() { arg.into_pipeline_data()
for arg in args { .print(engine_state, stack, no_newline, to_stderr)?;
arg.into_pipeline_data()
.print(engine_state, stack, no_newline, to_stderr)?;
}
} else if !input.is_nothing() {
input.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,14 +105,12 @@ 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();
let prompt = default default
.render_prompt_left() .render_prompt_left()
.to_string() .to_string()
.replace('\n', "\r\n") .replace('\n', "\r\n")
+ " "; .into()
prompt.into()
} }
} }
@@ -120,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()
@@ -132,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::NushellPrompt; use crate::NushellPrompt;
use log::trace; use log::info;
use nu_engine::eval_subexpression; use nu_engine::eval_subexpression;
use nu_protocol::report_error;
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,17 +90,20 @@ 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 {
// Just remove the very last newline. Some(mut x) => {
if x.ends_with('\n') { // Just remove the very last newline.
x.pop(); if x.ends_with('\n') {
} x.pop();
}
if x.ends_with('\r') { if x.ends_with('\r') {
x.pop(); x.pop();
}
Some(x)
} }
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,
@@ -97,18 +98,19 @@ pub(crate) fn add_menus(
{ {
let (block, _) = { let (block, _) = {
let mut working_set = StateWorkingSet::new(&engine_state); let mut working_set = StateWorkingSet::new(&engine_state);
let output = parse( let (output, _) = parse(
&mut working_set, &mut working_set,
Some(name), // format!("entry #{}", entry_num) Some(name), // format!("entry #{}", entry_num)
definition.as_bytes(), definition.as_bytes(),
true, true,
&[],
); );
(output, working_set.render()) (output, working_set.render())
}; };
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 {
@@ -157,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,
@@ -183,7 +188,7 @@ pub(crate) fn add_columnar_menu(
if let Value::Record { cols, vals, span } = &menu.menu_type { if let Value::Record { cols, vals, span } = &menu.menu_type {
columnar_menu = match extract_value("columns", cols, vals, span) { columnar_menu = match extract_value("columns", cols, vals, span) {
Ok(columns) => { Ok(columns) => {
let columns = columns.as_int()?; let columns = columns.as_integer()?;
columnar_menu.with_columns(columns as u16) columnar_menu.with_columns(columns as u16)
} }
Err(_) => columnar_menu, Err(_) => columnar_menu,
@@ -191,7 +196,7 @@ pub(crate) fn add_columnar_menu(
columnar_menu = match extract_value("col_width", cols, vals, span) { columnar_menu = match extract_value("col_width", cols, vals, span) {
Ok(col_width) => { Ok(col_width) => {
let col_width = col_width.as_int()?; let col_width = col_width.as_integer()?;
columnar_menu.with_column_width(Some(col_width as usize)) columnar_menu.with_column_width(Some(col_width as usize))
} }
Err(_) => columnar_menu.with_column_width(None), Err(_) => columnar_menu.with_column_width(None),
@@ -199,7 +204,7 @@ pub(crate) fn add_columnar_menu(
columnar_menu = match extract_value("col_padding", cols, vals, span) { columnar_menu = match extract_value("col_padding", cols, vals, span) {
Ok(col_padding) => { Ok(col_padding) => {
let col_padding = col_padding.as_int()?; let col_padding = col_padding.as_integer()?;
columnar_menu.with_column_padding(col_padding as usize) columnar_menu.with_column_padding(col_padding as usize)
} }
Err(_) => columnar_menu, Err(_) => columnar_menu,
@@ -285,7 +290,7 @@ pub(crate) fn add_list_menu(
if let Value::Record { cols, vals, span } = &menu.menu_type { if let Value::Record { cols, vals, span } = &menu.menu_type {
list_menu = match extract_value("page_size", cols, vals, span) { list_menu = match extract_value("page_size", cols, vals, span) {
Ok(page_size) => { Ok(page_size) => {
let page_size = page_size.as_int()?; let page_size = page_size.as_integer()?;
list_menu.with_page_size(page_size as usize) list_menu.with_page_size(page_size as usize)
} }
Err(_) => list_menu, Err(_) => list_menu,
@@ -371,7 +376,7 @@ pub(crate) fn add_description_menu(
if let Value::Record { cols, vals, span } = &menu.menu_type { if let Value::Record { cols, vals, span } = &menu.menu_type {
description_menu = match extract_value("columns", cols, vals, span) { description_menu = match extract_value("columns", cols, vals, span) {
Ok(columns) => { Ok(columns) => {
let columns = columns.as_int()?; let columns = columns.as_integer()?;
description_menu.with_columns(columns as u16) description_menu.with_columns(columns as u16)
} }
Err(_) => description_menu, Err(_) => description_menu,
@@ -379,7 +384,7 @@ pub(crate) fn add_description_menu(
description_menu = match extract_value("col_width", cols, vals, span) { description_menu = match extract_value("col_width", cols, vals, span) {
Ok(col_width) => { Ok(col_width) => {
let col_width = col_width.as_int()?; let col_width = col_width.as_integer()?;
description_menu.with_column_width(Some(col_width as usize)) description_menu.with_column_width(Some(col_width as usize))
} }
Err(_) => description_menu.with_column_width(None), Err(_) => description_menu.with_column_width(None),
@@ -387,7 +392,7 @@ pub(crate) fn add_description_menu(
description_menu = match extract_value("col_padding", cols, vals, span) { description_menu = match extract_value("col_padding", cols, vals, span) {
Ok(col_padding) => { Ok(col_padding) => {
let col_padding = col_padding.as_int()?; let col_padding = col_padding.as_integer()?;
description_menu.with_column_padding(col_padding as usize) description_menu.with_column_padding(col_padding as usize)
} }
Err(_) => description_menu, Err(_) => description_menu,
@@ -395,7 +400,7 @@ pub(crate) fn add_description_menu(
description_menu = match extract_value("selection_rows", cols, vals, span) { description_menu = match extract_value("selection_rows", cols, vals, span) {
Ok(selection_rows) => { Ok(selection_rows) => {
let selection_rows = selection_rows.as_int()?; let selection_rows = selection_rows.as_integer()?;
description_menu.with_selection_rows(selection_rows as u16) description_menu.with_selection_rows(selection_rows as u16)
} }
Err(_) => description_menu, Err(_) => description_menu,
@@ -403,7 +408,7 @@ pub(crate) fn add_description_menu(
description_menu = match extract_value("description_rows", cols, vals, span) { description_menu = match extract_value("description_rows", cols, vals, span) {
Ok(description_rows) => { Ok(description_rows) => {
let description_rows = description_rows.as_int()?; let description_rows = description_rows.as_integer()?;
description_menu.with_description_rows(description_rows as usize) description_menu.with_description_rows(description_rows as usize)
} }
Err(_) => description_menu, Err(_) => description_menu,
@@ -625,12 +630,9 @@ fn add_parsed_keybinding(
"shift" => KeyModifiers::SHIFT, "shift" => KeyModifiers::SHIFT,
"alt" => KeyModifiers::ALT, "alt" => KeyModifiers::ALT,
"none" => KeyModifiers::NONE, "none" => KeyModifiers::NONE,
"shift_alt" | "alt_shift" => KeyModifiers::SHIFT | KeyModifiers::ALT, "control | shift" => KeyModifiers::CONTROL | KeyModifiers::SHIFT,
"control_shift" | "shift_control" => KeyModifiers::CONTROL | KeyModifiers::SHIFT, "control | alt" => KeyModifiers::CONTROL | KeyModifiers::ALT,
"control_alt" | "alt_control" => KeyModifiers::CONTROL | KeyModifiers::ALT, "control | alt | shift" => KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT,
"control_alt_shift" | "control_shift_alt" => {
KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT
}
_ => { _ => {
return Err(ShellError::UnsupportedConfigValue( return Err(ShellError::UnsupportedConfigValue(
"CONTROL, SHIFT, ALT or NONE".to_string(), "CONTROL, SHIFT, ALT or NONE".to_string(),
@@ -653,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)
} }
@@ -682,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)
@@ -884,7 +885,7 @@ fn edit_from_record(
"movebigwordrightstart" => EditCommand::MoveBigWordRightStart, "movebigwordrightstart" => EditCommand::MoveBigWordRightStart,
"movetoposition" => { "movetoposition" => {
let value = extract_value("value", cols, vals, span)?; let value = extract_value("value", cols, vals, span)?;
EditCommand::MoveToPosition(value.as_int()? as usize) EditCommand::MoveToPosition(value.as_integer()? as usize)
} }
"insertchar" => { "insertchar" => {
let value = extract_value("value", cols, vals, span)?; let value = extract_value("value", cols, vals, span)?;
@@ -992,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();
@@ -1012,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();
@@ -1036,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();
@@ -1063,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 {
@@ -1075,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,
@@ -1116,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 {
@@ -1128,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,
@@ -1156,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);

File diff suppressed because it is too large Load Diff

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,
} }
@@ -18,7 +17,10 @@ impl Highlighter for NuHighlighter {
trace!("highlighting: {}", line); trace!("highlighting: {}", line);
let mut working_set = StateWorkingSet::new(&self.engine_state); let mut working_set = StateWorkingSet::new(&self.engine_state);
let block = parse(&mut working_set, None, line.as_bytes(), false); let block = {
let (block, _) = parse(&mut working_set, None, line.as_bytes(), false, &[]);
block
};
let (shapes, global_span_offset) = { let (shapes, global_span_offset) = {
let shapes = flatten_block(&working_set, &block); let shapes = flatten_block(&working_set, &block);
(shapes, self.engine_state.next_span_start()) (shapes, self.engine_state.next_span_start())
@@ -76,28 +78,29 @@ impl Highlighter for NuHighlighter {
}}; }};
} }
let mut add_colored_token = |shape: &FlatShape, text: String| { macro_rules! add_colored_token {
output.push((get_shape_color(shape.to_string(), &self.config), text)); ($shape:expr, $text:expr) => {
}; output.push((get_shape_color($shape.to_string(), &self.config), $text))
};
}
match shape.1 { match shape.1 {
FlatShape::Garbage => add_colored_token(&shape.1, next_token), FlatShape::Garbage => add_colored_token!(shape.1, next_token),
FlatShape::Nothing => add_colored_token(&shape.1, next_token), FlatShape::Nothing => add_colored_token!(shape.1, next_token),
FlatShape::Binary => add_colored_token(&shape.1, next_token), FlatShape::Binary => add_colored_token!(shape.1, next_token),
FlatShape::Bool => add_colored_token(&shape.1, next_token), FlatShape::Bool => add_colored_token!(shape.1, next_token),
FlatShape::Int => add_colored_token(&shape.1, next_token), FlatShape::Int => add_colored_token!(shape.1, next_token),
FlatShape::Float => add_colored_token(&shape.1, next_token), FlatShape::Float => add_colored_token!(shape.1, next_token),
FlatShape::Range => add_colored_token(&shape.1, next_token), FlatShape::Range => add_colored_token!(shape.1, next_token),
FlatShape::InternalCall(_) => add_colored_token(&shape.1, next_token), FlatShape::InternalCall => add_colored_token!(shape.1, next_token),
FlatShape::External => add_colored_token(&shape.1, next_token), FlatShape::External => add_colored_token!(shape.1, next_token),
FlatShape::ExternalArg => add_colored_token(&shape.1, next_token), FlatShape::ExternalArg => add_colored_token!(shape.1, next_token),
FlatShape::Keyword => add_colored_token(&shape.1, next_token), FlatShape::Literal => add_colored_token!(shape.1, next_token),
FlatShape::Literal => add_colored_token(&shape.1, next_token), FlatShape::Operator => add_colored_token!(shape.1, next_token),
FlatShape::Operator => add_colored_token(&shape.1, next_token), FlatShape::Signature => add_colored_token!(shape.1, next_token),
FlatShape::Signature => add_colored_token(&shape.1, next_token), FlatShape::String => add_colored_token!(shape.1, next_token),
FlatShape::String => add_colored_token(&shape.1, next_token), FlatShape::StringInterpolation => add_colored_token!(shape.1, next_token),
FlatShape::StringInterpolation => add_colored_token(&shape.1, next_token), FlatShape::DateTime => add_colored_token!(shape.1, next_token),
FlatShape::DateTime => add_colored_token(&shape.1, next_token),
FlatShape::List => { FlatShape::List => {
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token) add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
} }
@@ -111,23 +114,17 @@ impl Highlighter for NuHighlighter {
FlatShape::Block => { FlatShape::Block => {
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token) add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
} }
FlatShape::Closure => {
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
}
FlatShape::Filepath => add_colored_token(&shape.1, next_token), FlatShape::Filepath => add_colored_token!(shape.1, next_token),
FlatShape::Directory => add_colored_token(&shape.1, next_token), FlatShape::Directory => add_colored_token!(shape.1, next_token),
FlatShape::GlobPattern => add_colored_token(&shape.1, next_token), FlatShape::GlobPattern => add_colored_token!(shape.1, next_token),
FlatShape::Variable(_) | FlatShape::VarDecl(_) => { FlatShape::Variable => add_colored_token!(shape.1, next_token),
add_colored_token(&shape.1, next_token) FlatShape::Flag => add_colored_token!(shape.1, next_token),
} FlatShape::Pipe => add_colored_token!(shape.1, next_token),
FlatShape::Flag => add_colored_token(&shape.1, next_token), FlatShape::And => add_colored_token!(shape.1, next_token),
FlatShape::Pipe => add_colored_token(&shape.1, next_token), FlatShape::Or => add_colored_token!(shape.1, next_token),
FlatShape::And => add_colored_token(&shape.1, next_token), FlatShape::Redirection => add_colored_token!(shape.1, next_token),
FlatShape::Or => add_colored_token(&shape.1, next_token), FlatShape::Custom(..) => add_colored_token!(shape.1, next_token),
FlatShape::Redirection => add_colored_token(&shape.1, next_token),
FlatShape::Custom(..) => add_colored_token(&shape.1, next_token),
FlatShape::MatchPattern => add_colored_token(&shape.1, next_token),
} }
last_seen_span = shape.0.end; last_seen_span = shape.0.end;
} }
@@ -152,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
@@ -160,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
} }
@@ -236,9 +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::SameTargetRedirection { cmd: (_, 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,
@@ -311,8 +312,6 @@ fn find_matching_block_end_in_expr(
Expr::ImportPattern(_) => None, Expr::ImportPattern(_) => None,
Expr::Overlay(_) => None, Expr::Overlay(_) => None,
Expr::Signature(_) => None, Expr::Signature(_) => None,
Expr::MatchPattern(_) => None,
Expr::MatchBlock(_) => None,
Expr::Nothing => None, Expr::Nothing => None,
Expr::Garbage => None, Expr::Garbage => None,
@@ -359,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,16 +1,15 @@
use nu_command::hook::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::{ use nu_protocol::{
engine::{EngineState, Stack}, engine::{EngineState, Stack},
print_if_stream, PipelineData, ShellError, Span, Value, print_if_stream, PipelineData, ShellError, Span, Value,
}; };
use nu_protocol::{report_error, report_error_new};
#[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;
// 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(),
), ),
@@ -113,8 +113,7 @@ fn gather_env_vars(
span, span,
}) = parts.get(0) }) = parts.get(0)
{ {
let mut working_set = StateWorkingSet::new(engine_state); let bytes = engine_state.get_span_contents(span);
let bytes = working_set.get_span_contents(*span);
if bytes.len() < 2 { if bytes.len() < 2 {
report_capture_error( report_capture_error(
@@ -126,12 +125,9 @@ fn gather_env_vars(
continue; continue;
} }
let (bytes, err) = unescape_unquote_string(bytes, *span); let (bytes, parse_error) = unescape_unquote_string(bytes, *span);
if let Some(err) = err {
working_set.error(err);
}
if working_set.parse_errors.first().is_some() { if parse_error.is_some() {
report_capture_error( report_capture_error(
engine_state, engine_state,
&String::from_utf8_lossy(contents), &String::from_utf8_lossy(contents),
@@ -157,8 +153,7 @@ fn gather_env_vars(
span, span,
}) = parts.get(2) }) = parts.get(2)
{ {
let mut working_set = StateWorkingSet::new(engine_state); let bytes = engine_state.get_span_contents(span);
let bytes = working_set.get_span_contents(*span);
if bytes.len() < 2 { if bytes.len() < 2 {
report_capture_error( report_capture_error(
@@ -170,12 +165,9 @@ fn gather_env_vars(
continue; continue;
} }
let (bytes, err) = unescape_unquote_string(bytes, *span); let (bytes, parse_error) = unescape_unquote_string(bytes, *span);
if let Some(err) = err {
working_set.error(err);
}
if working_set.parse_errors.first().is_some() { if parse_error.is_some() {
report_capture_error( report_capture_error(
engine_state, engine_state,
&String::from_utf8_lossy(contents), &String::from_utf8_lossy(contents),
@@ -211,21 +203,19 @@ 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 = parse( let (output, err) = parse(
&mut working_set, &mut working_set,
Some(fname), // format!("entry #{}", entry_num) Some(fname), // format!("entry #{}", entry_num)
source, source,
false, false,
&[],
); );
if let Some(err) = working_set.parse_errors.first() { if let Some(err) = err {
set_last_exit_code(stack, 1); set_last_exit_code(stack, 1);
report_error(&working_set, err); report_error(&working_set, &err);
return false; return false;
} }
@@ -238,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;
@@ -266,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 {
@@ -298,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
} }
@@ -313,10 +289,58 @@ 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 },
},
); );
} }
pub fn report_error(
working_set: &StateWorkingSet,
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
) {
eprintln!("Error: {:?}", CliError(error, working_set));
// reset vt processing, aka ansi because illbehaved externals can break it
#[cfg(windows)]
{
let _ = nu_utils::enable_vt_processing();
}
}
pub fn report_error_new(
engine_state: &EngineState,
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, error);
}
pub fn get_init_cwd() -> PathBuf {
match std::env::current_dir() {
Ok(cwd) => cwd,
Err(_) => match std::env::var("PWD") {
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 {
match nu_engine::env::current_dir(engine_state, stack) {
Ok(p) => p,
Err(e) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
get_init_cwd()
}
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

View File

@@ -1,24 +1,17 @@
use nu_parser::parse; use nu_parser::{parse, ParseError};
use nu_protocol::{ use nu_protocol::engine::{EngineState, StateWorkingSet};
engine::{EngineState, StateWorkingSet},
ParseError,
};
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 {
fn validate(&self, line: &str) -> ValidationResult { fn validate(&self, line: &str) -> ValidationResult {
let mut working_set = StateWorkingSet::new(&self.engine_state); let mut working_set = StateWorkingSet::new(&self.engine_state);
parse(&mut working_set, None, line.as_bytes(), false); let (_, err) = parse(&mut working_set, None, line.as_bytes(), false, &[]);
if matches!( if matches!(err, Some(ParseError::UnexpectedEof(..))) {
working_set.parse_errors.first(),
Some(ParseError::UnexpectedEof(..))
) {
ValidationResult::Incomplete ValidationResult::Incomplete
} else { } else {
ValidationResult::Complete ValidationResult::Complete

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
@@ -143,7 +123,7 @@ fn external_completer_trailing_space() {
#[test] #[test]
fn external_completer_no_trailing_space() { fn external_completer_no_trailing_space() {
let block = "{|spans| $spans}"; let block = "let external_completer = {|spans| $spans}";
let input = "gh alias".to_string(); let input = "gh alias".to_string();
let suggestions = run_external_completion(block, &input); let suggestions = run_external_completion(block, &input);
@@ -154,7 +134,7 @@ fn external_completer_no_trailing_space() {
#[test] #[test]
fn external_completer_pass_flags() { fn external_completer_pass_flags() {
let block = "{|spans| $spans}"; let block = "let external_completer = {|spans| $spans}";
let input = "gh api --".to_string(); let input = "gh api --".to_string();
let suggestions = run_external_completion(block, &input); let suggestions = run_external_completion(block, &input);
@@ -169,20 +149,20 @@ 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
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
folder(dir.join("another")),
file(dir.join("custom_completion.nu")),
file(dir.join("nushell")), file(dir.join("nushell")),
folder(dir.join("test_a")), folder(dir.join("test_a")),
folder(dir.join("test_b")), folder(dir.join("test_b")),
folder(dir.join("another")),
file(dir.join("custom_completion.nu")),
file(dir.join(".hidden_file")), file(dir.join(".hidden_file")),
folder(dir.join(".hidden_folder")), folder(dir.join(".hidden_folder")),
]; ];
@@ -212,21 +192,21 @@ fn command_ls_with_filecompletion() {
#[cfg(windows)] #[cfg(windows)]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder\\".to_string(), ".hidden_folder\\".to_string(),
]; ];
#[cfg(not(windows))] #[cfg(not(windows))]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
@@ -244,21 +224,21 @@ fn command_open_with_filecompletion() {
#[cfg(windows)] #[cfg(windows)]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder\\".to_string(), ".hidden_folder\\".to_string(),
]; ];
#[cfg(not(windows))] #[cfg(not(windows))]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
@@ -277,21 +257,21 @@ fn command_rm_with_globcompletion() {
#[cfg(windows)] #[cfg(windows)]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder\\".to_string(), ".hidden_folder\\".to_string(),
]; ];
#[cfg(not(windows))] #[cfg(not(windows))]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
@@ -310,21 +290,21 @@ fn command_cp_with_globcompletion() {
#[cfg(windows)] #[cfg(windows)]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder\\".to_string(), ".hidden_folder\\".to_string(),
]; ];
#[cfg(not(windows))] #[cfg(not(windows))]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
@@ -343,21 +323,21 @@ fn command_save_with_filecompletion() {
#[cfg(windows)] #[cfg(windows)]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder\\".to_string(), ".hidden_folder\\".to_string(),
]; ];
#[cfg(not(windows))] #[cfg(not(windows))]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
@@ -376,21 +356,21 @@ fn command_touch_with_filecompletion() {
#[cfg(windows)] #[cfg(windows)]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder\\".to_string(), ".hidden_folder\\".to_string(),
]; ];
#[cfg(not(windows))] #[cfg(not(windows))]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
@@ -409,21 +389,21 @@ fn command_watch_with_filecompletion() {
#[cfg(windows)] #[cfg(windows)]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder\\".to_string(), ".hidden_folder\\".to_string(),
]; ];
#[cfg(not(windows))] #[cfg(not(windows))]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
@@ -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,18 +449,18 @@ 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
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
folder(dir.join("another")),
folder(dir.join("test_a")), folder(dir.join("test_a")),
folder(dir.join("test_b")), folder(dir.join("test_b")),
folder(dir.join("another")),
folder(dir.join(".hidden_folder")), folder(dir.join(".hidden_folder")),
]; ];
@@ -518,28 +477,23 @@ 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
let suggestions = completer.complete("$nu.", 4); let suggestions = completer.complete("$nu.", 4);
assert_eq!(14, suggestions.len()); assert_eq!(9, suggestions.len());
let expected: Vec<String> = vec![ let expected: Vec<String> = vec![
"config-path".into(), "config-path".into(),
"current-exe".into(),
"default-config-dir".into(),
"env-path".into(), "env-path".into(),
"history-path".into(), "history-path".into(),
"home-path".into(), "home-path".into(),
"is-interactive".into(),
"is-login".into(),
"loginshell-path".into(), "loginshell-path".into(),
"os-info".into(), "os-info".into(),
"pid".into(), "pid".into(),
"plugin-path".into(), "scope".into(),
"startup-time".into(),
"temp-path".into(), "temp-path".into(),
]; ];
@@ -556,18 +510,6 @@ fn variables_completions() {
// Match results // Match results
match_suggestions(expected, suggestions); match_suggestions(expected, suggestions);
// Test completions for $nu.os-info
let suggestions = completer.complete("$nu.os-info.", 12);
assert_eq!(4, suggestions.len());
let expected: Vec<String> = vec![
"arch".into(),
"family".into(),
"kernel_version".into(),
"name".into(),
];
// Match results
match_suggestions(expected, suggestions);
// Test completions for custom var // Test completions for custom var
let suggestions = completer.complete("$actor.", 7); let suggestions = completer.complete("$actor.", 7);
@@ -591,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);
@@ -677,8 +616,8 @@ fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
let (dir, _, mut engine_state, mut stack) = new_engine(); let (dir, _, mut engine_state, mut stack) = new_engine();
let (_, delta) = { let (_, delta) = {
let mut working_set = StateWorkingSet::new(&engine_state); let mut working_set = StateWorkingSet::new(&engine_state);
let block = parse(&mut working_set, None, block.as_bytes(), false); let (block, err) = parse(&mut working_set, None, block.as_bytes(), false, &[]);
assert!(working_set.parse_errors.is_empty()); assert!(err.is_none());
(block, working_set.render()) (block, working_set.render())
}; };
@@ -695,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())
@@ -712,21 +651,21 @@ fn unknown_command_completion() {
#[cfg(windows)] #[cfg(windows)]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder\\".to_string(), ".hidden_folder\\".to_string(),
]; ];
#[cfg(not(windows))] #[cfg(not(windows))]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
@@ -772,113 +711,24 @@ fn filecompletions_triggers_after_cursor() {
#[cfg(windows)] #[cfg(windows)]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder\\".to_string(), ".hidden_folder\\".to_string(),
]; ];
#[cfg(not(windows))] #[cfg(not(windows))]
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(),
"custom_completion.nu".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(), ".hidden_file".to_string(),
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
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

@@ -1,5 +1,6 @@
use std::path::PathBuf; use std::path::PathBuf;
use nu_command::create_default_context;
use nu_engine::eval_block; use nu_engine::eval_block;
use nu_parser::parse; use nu_parser::parse;
use nu_protocol::{ use nu_protocol::{
@@ -10,10 +11,6 @@ use nu_test_support::fs;
use reedline::Suggestion; use reedline::Suggestion;
const SEP: char = std::path::MAIN_SEPARATOR; const SEP: char = std::path::MAIN_SEPARATOR;
fn create_default_context() -> EngineState {
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context())
}
// creates a new engine with the current path into the completions fixtures folder // creates a new engine with the current path into the completions fixtures folder
pub fn new_engine() -> (PathBuf, String, EngineState, Stack) { pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
// Target folder inside assets // Target folder inside assets
@@ -36,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()),
}, },
); );
@@ -149,9 +97,9 @@ pub fn merge_input(
let (block, delta) = { let (block, delta) = {
let mut working_set = StateWorkingSet::new(engine_state); let mut working_set = StateWorkingSet::new(engine_state);
let block = parse(&mut working_set, None, input, false); let (block, err) = parse(&mut working_set, None, input, false, &[]);
assert!(working_set.parse_errors.is_empty()); assert!(err.is_none());
(block, working_set.render()) (block, working_set.render())
}; };
@@ -164,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

@@ -1,16 +0,0 @@
[package]
authors = ["The Nushell Project Developers"]
description = "The foundation tools to build Nushell commands."
edition = "2021"
license = "MIT"
name = "nu-cmd-base"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
version = "0.83.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.83.0" }
nu-path = { path = "../nu-path", version = "0.83.0" }
nu-protocol = { version = "0.83.0", path = "../nu-protocol" }
indexmap = { version = "2.0" }

View File

@@ -1 +0,0 @@
pub mod to;

View File

@@ -1,20 +0,0 @@
use indexmap::{indexset, IndexSet};
use nu_protocol::Value;
pub fn merge_descriptors(values: &[Value]) -> Vec<String> {
let mut ret: Vec<String> = vec![];
let mut seen: IndexSet<String> = indexset! {};
for value in values {
let data_descriptors = match value {
Value::Record { cols, .. } => cols.to_owned(),
_ => vec!["".to_string()],
};
for desc in data_descriptors {
if !desc.is_empty() && !seen.contains(&desc) {
seen.insert(desc.to_string());
ret.push(desc.to_string());
}
}
}
ret
}

View File

@@ -1 +0,0 @@
pub mod delimited;

View File

@@ -1,3 +0,0 @@
pub mod formats;
pub mod input_handler;
pub mod util;

View File

@@ -1,57 +0,0 @@
use nu_protocol::report_error;
use nu_protocol::{
ast::RangeInclusion,
engine::{EngineState, Stack, StateWorkingSet},
Range, ShellError, Span, Value,
};
use std::path::PathBuf;
pub fn get_init_cwd() -> PathBuf {
std::env::current_dir().unwrap_or_else(|_| {
std::env::var("PWD")
.map(Into::into)
.unwrap_or_else(|_| nu_path::home_dir().unwrap_or_default())
})
}
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf {
nu_engine::env::current_dir(engine_state, stack).unwrap_or_else(|e| {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
crate::util::get_init_cwd()
})
}
type MakeRangeError = fn(&str, Span) -> ShellError;
pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> {
let start = match &range.from {
Value::Int { val, .. } => isize::try_from(*val).unwrap_or_default(),
Value::Nothing { .. } => 0,
_ => {
return Err(|msg, span| ShellError::TypeMismatch {
err_message: msg.to_string(),
span,
})
}
};
let end = match &range.to {
Value::Int { val, .. } => {
if matches!(range.inclusion, RangeInclusion::Inclusive) {
isize::try_from(*val).unwrap_or(isize::max_value())
} else {
isize::try_from(*val).unwrap_or(isize::max_value()) - 1
}
}
Value::Nothing { .. } => isize::max_value(),
_ => {
return Err(|msg, span| ShellError::TypeMismatch {
err_message: msg.to_string(),
span,
})
}
};
Ok((start, end))
}

View File

@@ -1,64 +0,0 @@
[package]
authors = ["The Nushell Project Developers"]
description = "Nushell's dataframe commands based on polars."
edition = "2021"
license = "MIT"
name = "nu-cmd-dataframe"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-dataframe"
version = "0.83.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
bench = false
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.83.0" }
nu-parser = { path = "../nu-parser", version = "0.83.0" }
nu-protocol = { path = "../nu-protocol", version = "0.83.0" }
# Potential dependencies for extras
chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false }
fancy-regex = "0.11"
indexmap = { version = "2.0" }
num = { version = "0.4", optional = true }
serde = { version = "1.0", features = ["derive"] }
sqlparser = { version = "0.34", features = ["serde"], optional = true }
[dependencies.polars]
features = [
"arg_where",
"checked_arithmetic",
"concat_str",
"cross_join",
"csv",
"cum_agg",
"default",
"dtype-categorical",
"dtype-datetime",
"dtype-struct",
"dynamic_groupby",
"ipc",
"is_in",
"json",
"lazy",
"object",
"parquet",
"random",
"rolling_window",
"rows",
"serde",
"serde-lazy",
"strings",
"to_dummies",
]
optional = true
version = "0.30.0"
[features]
dataframe = ["default"]
default = ["num", "polars", "sqlparser"]
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.83.0" }
nu-test-support = { path = "../nu-test-support", version = "0.83.0" }

View File

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

View File

@@ -1,96 +0,0 @@
use std::{fs::File, io::BufWriter, path::PathBuf};
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
};
use polars::prelude::{JsonWriter, SerWriter};
use super::super::values::NuDataFrame;
#[derive(Clone)]
pub struct ToJsonLines;
impl Command for ToJsonLines {
fn name(&self) -> &str {
"dfr to-jsonl"
}
fn usage(&self) -> &str {
"Saves dataframe to a JSON lines file."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("file", SyntaxShape::Filepath, "file path to save dataframe")
.input_output_type(Type::Custom("dataframe".into()), Type::Any)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Saves dataframe to JSON lines file",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr to-jsonl test.jsonl",
result: None,
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let file_name: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
let file = File::create(&file_name.item).map_err(|e| {
ShellError::GenericError(
"Error with file name".into(),
e.to_string(),
Some(file_name.span),
None,
Vec::new(),
)
})?;
let buf_writer = BufWriter::new(file);
JsonWriter::new(buf_writer)
.finish(df.as_mut())
.map_err(|e| {
ShellError::GenericError(
"Error saving file".into(),
e.to_string(),
Some(file_name.span),
None,
Vec::new(),
)
})?;
let file_value = Value::String {
val: format!("saved {:?}", &file_name.item),
span: file_name.span,
};
Ok(PipelineData::Value(
Value::List {
vals: vec![file_value],
span: call.head,
},
None,
))
}

View File

@@ -1,164 +0,0 @@
use super::super::values::NuExpression;
use crate::dataframe::values::{Column, NuDataFrame};
use chrono::{DateTime, FixedOffset};
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type,
Value,
};
#[derive(Clone)]
pub struct ExprDatePart;
impl Command for ExprDatePart {
fn name(&self) -> &str {
"dfr datepart"
}
fn usage(&self) -> &str {
"Creates an expression for capturing the specified datepart in a column."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required(
"Datepart name",
SyntaxShape::String,
"Part of the date to capture. Possible values are year, quarter, month, week, weekday, day, hour, minute, second, millisecond, microsecond, nanosecond",
)
.input_output_type(
Type::Custom("expression".into()),
Type::Custom("expression".into()),
)
.category(Category::Custom("expression".into()))
}
fn examples(&self) -> Vec<Example> {
let dt = DateTime::<FixedOffset>::parse_from_str(
"2021-12-30T01:02:03.123456789 +0000",
"%Y-%m-%dT%H:%M:%S.%9f %z",
)
.expect("date calculation should not fail in test");
vec![
Example {
description: "Creates an expression to capture the year date part",
example: r#"[["2021-12-30T01:02:03.123456789"]] | dfr into-df | dfr as-datetime "%Y-%m-%dT%H:%M:%S.%9f" | dfr with-column [(dfr col datetime | dfr datepart year | dfr as datetime_year )]"#,
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new("datetime".to_string(), vec![Value::test_date(dt)]),
Column::new("datetime_year".to_string(), vec![Value::test_int(2021)]),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Creates an expression to capture multiple date parts",
example: r#"[["2021-12-30T01:02:03.123456789"]] | dfr into-df | dfr as-datetime "%Y-%m-%dT%H:%M:%S.%9f" |
dfr with-column [ (dfr col datetime | dfr datepart year | dfr as datetime_year ),
(dfr col datetime | dfr datepart month | dfr as datetime_month ),
(dfr col datetime | dfr datepart day | dfr as datetime_day ),
(dfr col datetime | dfr datepart hour | dfr as datetime_hour ),
(dfr col datetime | dfr datepart minute | dfr as datetime_minute ),
(dfr col datetime | dfr datepart second | dfr as datetime_second ),
(dfr col datetime | dfr datepart nanosecond | dfr as datetime_ns ) ]"#,
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new("datetime".to_string(), vec![Value::test_date(dt)]),
Column::new("datetime_year".to_string(), vec![Value::test_int(2021)]),
Column::new("datetime_month".to_string(), vec![Value::test_int(12)]),
Column::new("datetime_day".to_string(), vec![Value::test_int(30)]),
Column::new("datetime_hour".to_string(), vec![Value::test_int(1)]),
Column::new("datetime_minute".to_string(), vec![Value::test_int(2)]),
Column::new("datetime_second".to_string(), vec![Value::test_int(3)]),
Column::new("datetime_ns".to_string(), vec![Value::test_int(123456789)]),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
]
}
fn search_terms(&self) -> Vec<&str> {
vec![
"year",
"month",
"week",
"weekday",
"quarter",
"day",
"hour",
"minute",
"second",
"millisecond",
"microsecond",
"nanosecond",
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let part: Spanned<String> = call.req(engine_state, stack, 0)?;
let expr = NuExpression::try_from_pipeline(input, call.head)?;
let expr_dt = expr.into_polars().dt();
let expr = match part.item.as_str() {
"year" => expr_dt.year(),
"quarter" => expr_dt.quarter(),
"month" => expr_dt.month(),
"week" => expr_dt.week(),
"day" => expr_dt.day(),
"hour" => expr_dt.hour(),
"minute" => expr_dt.minute(),
"second" => expr_dt.second(),
"millisecond" => expr_dt.millisecond(),
"microsecond" => expr_dt.microsecond(),
"nanosecond" => expr_dt.nanosecond(),
_ => {
return Err(ShellError::UnsupportedInput(
format!("{} is not a valid datepart, expected one of year, month, day, hour, minute, second, millisecond, microsecond, nanosecond", part.item),
"value originates from here".to_string(),
call.head,
part.span,
));
}
}.into();
Ok(PipelineData::Value(
NuExpression::into_value(expr, call.head),
None,
))
}
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
use crate::dataframe::eager::WithColumn;
use crate::dataframe::expressions::ExprAlias;
use crate::dataframe::expressions::ExprAsNu;
use crate::dataframe::expressions::ExprCol;
use crate::dataframe::series::AsDateTime;
#[test]
fn test_examples() {
test_dataframe(vec![
Box::new(ExprDatePart {}),
Box::new(ExprCol {}),
Box::new(ExprAsNu {}),
Box::new(AsDateTime {}),
Box::new(WithColumn {}),
Box::new(ExprAlias {}),
])
}
}

View File

@@ -1,139 +0,0 @@
use crate::dataframe::values::{Column, NuDataFrame, NuExpression};
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
pub struct LazyFillNA;
impl Command for LazyFillNA {
fn name(&self) -> &str {
"dfr fill-nan"
}
fn usage(&self) -> &str {
"Replaces NaN values with the given expression."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required(
"fill",
SyntaxShape::Any,
"Expression to use to fill the NAN values",
)
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("lazyframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Fills the NaN values with 0",
example: "[1 2 NaN 3 NaN] | dfr into-df | dfr fill-nan 0",
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"0".to_string(),
vec![
Value::test_int(1),
Value::test_int(2),
Value::test_int(0),
Value::test_int(3),
Value::test_int(0),
],
)])
.expect("Df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Fills the NaN values of a whole dataframe",
example: "[[a b]; [0.2 1] [0.1 NaN]] | dfr into-df | dfr fill-nan 0",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"a".to_string(),
vec![Value::test_float(0.2), Value::test_float(0.1)],
),
Column::new(
"b".to_string(),
vec![Value::test_int(1), Value::test_int(0)],
),
])
.expect("Df for test should not fail")
.into_value(Span::test_data()),
),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let fill: Value = call.req(engine_state, stack, 0)?;
let value = input.into_value(call.head);
if NuExpression::can_downcast(&value) {
let expr = NuExpression::try_from_value(value)?;
let fill = NuExpression::try_from_value(fill)?.into_polars();
let expr: NuExpression = expr.into_polars().fill_nan(fill).into();
Ok(PipelineData::Value(
NuExpression::into_value(expr, call.head),
None,
))
} else {
let val_span = value.span()?;
let frame = NuDataFrame::try_from_value(value)?;
let columns = frame.columns(val_span)?;
let dataframe = columns
.into_iter()
.map(|column| {
let column_name = column.name().to_string();
let values = column
.into_iter()
.map(|value| match value {
Value::Float { val, .. } => {
if val.is_nan() {
fill.clone()
} else {
value
}
}
Value::List { vals, span } => {
NuDataFrame::fill_list_nan(vals, span, fill.clone())
}
_ => value,
})
.collect::<Vec<Value>>();
Column::new(column_name, values)
})
.collect::<Vec<Column>>();
Ok(PipelineData::Value(
NuDataFrame::try_from_columns(dataframe)?.into_value(call.head),
None,
))
}
}
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(LazyFillNA {})])
}
}

View File

@@ -1,34 +0,0 @@
mod eager;
mod expressions;
mod lazy;
mod series;
mod utils;
mod values;
pub use eager::add_eager_decls;
pub use expressions::add_expressions;
pub use lazy::add_lazy_decls;
pub use series::add_series_decls;
use nu_protocol::engine::{EngineState, StateWorkingSet};
pub fn add_dataframe_context(mut engine_state: EngineState) -> EngineState {
let delta = {
let mut working_set = StateWorkingSet::new(&engine_state);
add_series_decls(&mut working_set);
add_eager_decls(&mut working_set);
add_expressions(&mut working_set);
add_lazy_decls(&mut working_set);
working_set.render()
};
if let Err(err) = engine_state.merge_delta(delta) {
eprintln!("Error creating dataframe command context: {err:?}");
}
engine_state
}
#[cfg(test)]
mod test_dataframe;

View File

@@ -1,183 +0,0 @@
use super::super::super::values::{Column, NuDataFrame};
use chrono::DateTime;
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
use polars::prelude::{IntoSeries, TimeUnit, Utf8Methods};
#[derive(Clone)]
pub struct AsDateTime;
impl Command for AsDateTime {
fn name(&self) -> &str {
"dfr as-datetime"
}
fn usage(&self) -> &str {
r#"Converts string to datetime."#
}
fn extra_usage(&self) -> &str {
r#"Format example:
"%y/%m/%d %H:%M:%S" => 21/12/31 12:54:98
"%y-%m-%d %H:%M:%S" => 2021-12-31 24:58:01
"%y/%m/%d %H:%M:%S" => 21/12/31 24:58:01
"%y%m%d %H:%M:%S" => 210319 23:58:50
"%Y/%m/%d %H:%M:%S" => 2021/12/31 12:54:98
"%Y-%m-%d %H:%M:%S" => 2021-12-31 24:58:01
"%Y/%m/%d %H:%M:%S" => 2021/12/31 24:58:01
"%Y%m%d %H:%M:%S" => 20210319 23:58:50
"%FT%H:%M:%S" => 2019-04-18T02:45:55
"%FT%H:%M:%S.%6f" => microseconds
"%FT%H:%M:%S.%9f" => nanoseconds"#
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("format", SyntaxShape::String, "formatting date time string")
.switch("not-exact", "the format string may be contained in the date (e.g. foo-2021-01-01-bar could match 2021-01-01)", Some('n'))
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Converts string to datetime",
example: r#"["2021-12-30 00:00:00" "2021-12-31 00:00:00"] | dfr into-df | dfr as-datetime "%Y-%m-%d %H:%M:%S""#,
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"datetime".to_string(),
vec![
Value::Date {
val: DateTime::parse_from_str(
"2021-12-30 00:00:00 +0000",
"%Y-%m-%d %H:%M:%S %z",
)
.expect("date calculation should not fail in test"),
span: Span::test_data(),
},
Value::Date {
val: DateTime::parse_from_str(
"2021-12-31 00:00:00 +0000",
"%Y-%m-%d %H:%M:%S %z",
)
.expect("date calculation should not fail in test"),
span: Span::test_data(),
},
],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Converts string to datetime with high resolutions",
example: r#"["2021-12-30 00:00:00.123456789" "2021-12-31 00:00:00.123456789"] | dfr into-df | dfr as-datetime "%Y-%m-%d %H:%M:%S.%9f""#,
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"datetime".to_string(),
vec![
Value::Date {
val: DateTime::parse_from_str(
"2021-12-30 00:00:00.123456789 +0000",
"%Y-%m-%d %H:%M:%S.%9f %z",
)
.expect("date calculation should not fail in test"),
span: Span::test_data(),
},
Value::Date {
val: DateTime::parse_from_str(
"2021-12-31 00:00:00.123456789 +0000",
"%Y-%m-%d %H:%M:%S.%9f %z",
)
.expect("date calculation should not fail in test"),
span: Span::test_data(),
},
],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let format: String = call.req(engine_state, stack, 0)?;
let not_exact = call.has_flag("not-exact");
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let casted = series.utf8().map_err(|e| {
ShellError::GenericError(
"Error casting to string".into(),
e.to_string(),
Some(call.head),
None,
Vec::new(),
)
})?;
let res = if not_exact {
casted.as_datetime_not_exact(Some(format.as_str()), TimeUnit::Nanoseconds, None)
} else {
casted.as_datetime(
Some(format.as_str()),
TimeUnit::Nanoseconds,
false,
false,
None,
)
};
let mut res = res
.map_err(|e| {
ShellError::GenericError(
"Error creating datetime".into(),
e.to_string(),
Some(call.head),
None,
Vec::new(),
)
})?
.into_series();
res.rename("datetime");
NuDataFrame::try_from_series(vec![res], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(AsDateTime {})])
}
}

View File

@@ -1,2 +0,0 @@
pub mod dataframe;
pub use dataframe::*;

View File

@@ -1,41 +0,0 @@
[package]
authors = ["The Nushell Project Developers"]
description = "Nushell's extra commands that are not part of the 1.0 api standard."
edition = "2021"
license = "MIT"
name = "nu-cmd-extra"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
version = "0.83.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
bench = false
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.83.0" }
nu-parser = { path = "../nu-parser", version = "0.83.0" }
nu-protocol = { path = "../nu-protocol", version = "0.83.0" }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.83.0" }
nu-utils = { path = "../nu-utils", version = "0.83.0" }
# Potential dependencies for extras
num-traits = "0.2"
ahash = "0.8.3"
nu-ansi-term = "0.49.0"
fancy-regex = "0.11.0"
rust-embed = "6.7.0"
serde = "1.0.164"
nu-pretty-hex = { version = "0.83.0", path = "../nu-pretty-hex" }
nu-json = { version = "0.83.0", path = "../nu-json" }
serde_urlencoded = "0.7.1"
htmlescape = "0.3.1"
[features]
extra = ["default"]
default = []
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.83.0" }
nu-command = { path = "../nu-command", version = "0.83.0" }
nu-test-support = { path = "../nu-test-support", version = "0.83.0" }

View File

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

View File

@@ -1,30 +0,0 @@
# nu-cmd-extra
## the extra commands are not part of the Nushell binary
The commands in this crate are the *extra commands* of Nushell. They do not
get built for the release and it is the responsibility of the developer to
build these commands if they want to use them.
These commands are not going to part of the 1.0 Api; meaning that there
is no guarantee longer term that these commands will be around into the future.
Of course since they are part of the source tree one could always incorporate
them into their own custom release.
### How to build the commands in this crate
Step 1 is to
[read the installation notes](https://www.nushell.sh/book/installation.html#build-from-source)
for Nushell which is located in our Nushell book.
Once Rust is installed you can then build Nushell with the following command.
```rust
cargo build --features=extra
```
Your Nushell binary which just got built is called *nu* and will be located here.
```
nushell/target/debug/nu
```

View File

@@ -1,83 +0,0 @@
#[cfg(test)]
use nu_protocol::engine::Command;
#[cfg(test)]
pub fn test_examples(cmd: impl Command + 'static) {
test_examples::test_examples(cmd);
}
#[cfg(test)]
mod test_examples {
use nu_cmd_lang::example_support::{
check_all_signature_input_output_types_entries_have_examples,
check_example_evaluates_to_expected_output,
check_example_input_and_output_types_match_command_signature,
};
use crate::MathEuler;
use crate::MathPi;
use nu_protocol::{
engine::{Command, EngineState, StateWorkingSet},
Type,
};
use std::collections::HashSet;
pub fn test_examples(cmd: impl Command + 'static) {
let examples = cmd.examples();
let signature = cmd.signature();
let mut engine_state = make_engine_state(cmd.clone_box());
let cwd = std::env::current_dir().expect("Could not get current working directory.");
let mut witnessed_type_transformations = HashSet::<(Type, Type)>::new();
for example in examples {
if example.result.is_none() {
continue;
}
witnessed_type_transformations.extend(
check_example_input_and_output_types_match_command_signature(
&example,
&cwd,
&mut make_engine_state(cmd.clone_box()),
&signature.input_output_types,
signature.operates_on_cell_paths(),
signature.vectorizes_over_list,
),
);
check_example_evaluates_to_expected_output(&example, cwd.as_path(), &mut engine_state);
}
check_all_signature_input_output_types_entries_have_examples(
signature,
witnessed_type_transformations,
);
}
fn make_engine_state(cmd: Box<dyn Command>) -> Box<EngineState> {
let mut engine_state = Box::new(EngineState::new());
let delta = {
// Base functions that are needed for testing
// Try to keep this working set small to keep tests running as fast as possible
let mut working_set = StateWorkingSet::new(&engine_state);
working_set.add_decl(Box::new(nu_command::Enumerate));
working_set.add_decl(Box::new(nu_cmd_lang::If));
// math commands
working_set.add_decl(Box::new(MathEuler));
working_set.add_decl(Box::new(MathPi));
working_set.add_decl(Box::new(nu_command::MathRound));
// Adding the command that is being tested to the working set
working_set.add_decl(cmd);
working_set.render()
};
engine_state
.merge_delta(delta)
.expect("Error merging delta");
engine_state
}
}

View File

@@ -1,274 +0,0 @@
use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Type, Value,
};
use num_traits::ToPrimitive;
pub struct Arguments {
cell_paths: Option<Vec<CellPath>>,
}
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
#[derive(Clone)]
pub struct BitsInto;
impl Command for BitsInto {
fn name(&self) -> &str {
"into bits"
}
fn signature(&self) -> Signature {
Signature::build("into bits")
.input_output_types(vec![
(Type::Binary, Type::String),
(Type::Int, Type::String),
(Type::Filesize, Type::String),
(Type::Duration, Type::String),
(Type::String, Type::String),
(Type::Bool, Type::String),
(Type::Date, Type::String),
(Type::Table(vec![]), Type::Table(vec![])),
])
.allow_variants_without_examples(true) // TODO: supply exhaustive examples
.rest(
"rest",
SyntaxShape::CellPath,
"for a data structure input, convert data at the given cell paths",
)
.category(Category::Conversions)
}
fn usage(&self) -> &str {
"Convert value to a binary primitive."
}
fn search_terms(&self) -> Vec<&str> {
vec!["convert", "cast"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
into_bits(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "convert a binary value into a string, padded to 8 places with 0s",
example: "01b | into bits",
result: Some(Value::String {
val: "00000001".to_string(),
span: Span::test_data(),
}),
},
Example {
description: "convert an int into a string, padded to 8 places with 0s",
example: "1 | into bits",
result: Some(Value::String {
val: "00000001".to_string(),
span: Span::test_data(),
}),
},
Example {
description: "convert a filesize value into a string, padded to 8 places with 0s",
example: "1b | into bits",
result: Some(Value::String {
val: "00000001".to_string(),
span: Span::test_data(),
}),
},
Example {
description: "convert a duration value into a string, padded to 8 places with 0s",
example: "1ns | into bits",
result: Some(Value::String {
val: "00000001".to_string(),
span: Span::test_data(),
}),
},
Example {
description: "convert a boolean value into a string, padded to 8 places with 0s",
example: "true | into bits",
result: Some(Value::String {
val: "00000001".to_string(),
span: Span::test_data(),
}),
},
Example {
description: "convert a datetime value into a string, padded to 8 places with 0s",
example: "2023-04-17T01:02:03 | into bits",
result: Some(Value::String {
val: "01001101 01101111 01101110 00100000 01000001 01110000 01110010 00100000 00110001 00110111 00100000 00110000 00110001 00111010 00110000 00110010 00111010 00110000 00110011 00100000 00110010 00110000 00110010 00110011".to_string(),
span: Span::test_data(),
}),
},
Example {
description: "convert a string into a raw binary string, padded with 0s to 8 places",
example: "'nushell.sh' | into bits",
result: Some(Value::String {
val: "01101110 01110101 01110011 01101000 01100101 01101100 01101100 00101110 01110011 01101000".to_string(),
span: Span::test_data(),
}),
},
]
}
}
fn into_bits(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let cell_paths = call.rest(engine_state, stack, 0)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
match input {
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::Binary {
val: vec![],
span: head,
}
.into_pipeline_data()),
PipelineData::ExternalStream {
stdout: Some(stream),
..
} => {
// TODO: in the future, we may want this to stream out, converting each to bytes
let output = stream.into_bytes()?;
Ok(Value::Binary {
val: output.item,
span: head,
}
.into_pipeline_data())
}
_ => {
let args = Arguments { cell_paths };
operate(action, args, input, call.head, engine_state.ctrlc.clone())
}
}
}
fn convert_to_smallest_number_type(num: i64, span: Span) -> Value {
if let Some(v) = num.to_i8() {
let bytes = v.to_ne_bytes();
let mut raw_string = "".to_string();
for ch in bytes {
raw_string.push_str(&format!("{:08b} ", ch));
}
Value::String {
val: raw_string.trim().to_string(),
span,
}
} else if let Some(v) = num.to_i16() {
let bytes = v.to_ne_bytes();
let mut raw_string = "".to_string();
for ch in bytes {
raw_string.push_str(&format!("{:08b} ", ch));
}
Value::String {
val: raw_string.trim().to_string(),
span,
}
} else if let Some(v) = num.to_i32() {
let bytes = v.to_ne_bytes();
let mut raw_string = "".to_string();
for ch in bytes {
raw_string.push_str(&format!("{:08b} ", ch));
}
Value::String {
val: raw_string.trim().to_string(),
span,
}
} else {
let bytes = num.to_ne_bytes();
let mut raw_string = "".to_string();
for ch in bytes {
raw_string.push_str(&format!("{:08b} ", ch));
}
Value::String {
val: raw_string.trim().to_string(),
span,
}
}
}
pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
match input {
Value::Binary { val, .. } => {
let mut raw_string = "".to_string();
for ch in val {
raw_string.push_str(&format!("{:08b} ", ch));
}
Value::String {
val: raw_string.trim().to_string(),
span,
}
}
Value::Int { val, .. } => convert_to_smallest_number_type(*val, span),
Value::Filesize { val, .. } => convert_to_smallest_number_type(*val, span),
Value::Duration { val, .. } => convert_to_smallest_number_type(*val, span),
Value::String { val, .. } => {
let raw_bytes = val.as_bytes();
let mut raw_string = "".to_string();
for ch in raw_bytes {
raw_string.push_str(&format!("{:08b} ", ch));
}
Value::String {
val: raw_string.trim().to_string(),
span,
}
}
Value::Bool { val, .. } => {
let v = <i64 as From<bool>>::from(*val);
convert_to_smallest_number_type(v, span)
}
Value::Date { val, .. } => {
let value = val.format("%c").to_string();
let bytes = value.as_bytes();
let mut raw_string = "".to_string();
for ch in bytes {
raw_string.push_str(&format!("{:08b} ", ch));
}
Value::String {
val: raw_string.trim().to_string(),
span,
}
}
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "integer, filesize, string, date, duration, binary or bool".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.expect_span(),
}),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BitsInto {})
}
}

View File

@@ -1,217 +0,0 @@
use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_cmd_base::util;
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, Range, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
pub struct BytesAt;
struct Arguments {
indexes: Subbytes,
cell_paths: Option<Vec<CellPath>>,
}
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
impl From<(isize, isize)> for Subbytes {
fn from(input: (isize, isize)) -> Self {
Self(input.0, input.1)
}
}
#[derive(Clone, Copy)]
struct Subbytes(isize, isize);
impl Command for BytesAt {
fn name(&self) -> &str {
"bytes at"
}
fn signature(&self) -> Signature {
Signature::build("bytes at")
.input_output_types(vec![
(Type::Binary, Type::Binary),
(
Type::List(Box::new(Type::Binary)),
Type::List(Box::new(Type::Binary)),
),
(Type::Table(vec![]), Type::Table(vec![])),
])
.vectorizes_over_list(true)
.required("range", SyntaxShape::Range, "the range to get bytes")
.rest(
"rest",
SyntaxShape::CellPath,
"for a data structure input, get bytes from data at the given cell paths",
)
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Get bytes defined by a range"
}
fn search_terms(&self) -> Vec<&str> {
vec!["slice"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let range: Range = call.req(engine_state, stack, 0)?;
let indexes = match util::process_range(&range) {
Ok(idxs) => idxs.into(),
Err(processing_error) => {
return Err(processing_error("could not perform subbytes", call.head));
}
};
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let args = Arguments {
indexes,
cell_paths,
};
operate(action, args, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get a subbytes `0x[10 01]` from the bytes `0x[33 44 55 10 01 13]`",
example: " 0x[33 44 55 10 01 13] | bytes at 3..<4",
result: Some(Value::Binary {
val: vec![0x10],
span: Span::test_data(),
}),
},
Example {
description: "Get a subbytes `0x[10 01 13]` from the bytes `0x[33 44 55 10 01 13]`",
example: " 0x[33 44 55 10 01 13] | bytes at 3..6",
result: Some(Value::Binary {
val: vec![0x10, 0x01, 0x13],
span: Span::test_data(),
}),
},
Example {
description: "Get the remaining characters from a starting index",
example: " 0x[33 44 55 10 01 13] | bytes at 3..",
result: Some(Value::Binary {
val: vec![0x10, 0x01, 0x13],
span: Span::test_data(),
}),
},
Example {
description: "Get the characters from the beginning until ending index",
example: " 0x[33 44 55 10 01 13] | bytes at ..<4",
result: Some(Value::Binary {
val: vec![0x33, 0x44, 0x55, 0x10],
span: Span::test_data(),
}),
},
Example {
description:
"Or the characters from the beginning until ending index inside a table",
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes at 1.. ColB ColC"#,
result: Some(Value::List {
vals: vec![Value::Record {
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
vals: vec![
Value::Binary {
val: vec![0x11, 0x12, 0x13],
span: Span::test_data(),
},
Value::Binary {
val: vec![0x15, 0x16],
span: Span::test_data(),
},
Value::Binary {
val: vec![0x18, 0x19],
span: Span::test_data(),
},
],
span: Span::test_data(),
}],
span: Span::test_data(),
}),
},
]
}
}
fn action(input: &Value, args: &Arguments, head: Span) -> Value {
let range = &args.indexes;
match input {
Value::Binary { val, .. } => {
use std::cmp::{self, Ordering};
let len = val.len() as isize;
let start = if range.0 < 0 { range.0 + len } else { range.0 };
let end = if range.1 < 0 {
cmp::max(range.1 + len, 0)
} else {
range.1
};
if start < len && end >= 0 {
match start.cmp(&end) {
Ordering::Equal => Value::Binary {
val: vec![],
span: head,
},
Ordering::Greater => Value::Error {
error: Box::new(ShellError::TypeMismatch {
err_message: "End must be greater than or equal to Start".to_string(),
span: head,
}),
},
Ordering::Less => Value::Binary {
val: {
if end == isize::max_value() {
val.iter().skip(start as usize).copied().collect()
} else {
val.iter()
.skip(start as usize)
.take((end - start) as usize)
.copied()
.collect()
}
},
span: head,
},
}
} else {
Value::Binary {
val: vec![],
span: head,
}
}
}
Value::Error { .. } => input.clone(),
other => Value::Error {
error: Box::new(ShellError::UnsupportedInput(
"Only binary values are supported".into(),
format!("input type: {:?}", other.get_type()),
head,
// This line requires the Value::Error match above.
other.expect_span(),
)),
},
}
}

View File

@@ -1,176 +0,0 @@
use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category;
use nu_protocol::IntoPipelineData;
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
struct Arguments {
pattern: Vec<u8>,
cell_paths: Option<Vec<CellPath>>,
}
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
#[derive(Clone)]
pub struct BytesStartsWith;
impl Command for BytesStartsWith {
fn name(&self) -> &str {
"bytes starts-with"
}
fn signature(&self) -> Signature {
Signature::build("bytes starts-with")
.input_output_types(vec![
(Type::Binary, Type::Bool),
(Type::Table(vec![]), Type::Table(vec![])),
])
.allow_variants_without_examples(true)
.required("pattern", SyntaxShape::Binary, "the pattern to match")
.rest(
"rest",
SyntaxShape::CellPath,
"for a data structure input, check if bytes at the given cell paths start with the pattern",
)
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Check if bytes starts with a pattern."
}
fn search_terms(&self) -> Vec<&str> {
vec!["pattern", "match", "find", "search"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let arg = Arguments {
pattern,
cell_paths,
};
match input {
PipelineData::ExternalStream {
stdout: Some(stream),
span,
..
} => {
let mut i = 0;
for item in stream {
let byte_slice = match &item {
// String and binary data are valid byte patterns
Ok(Value::String { val, .. }) => val.as_bytes(),
Ok(Value::Binary { val, .. }) => val,
// If any Error value is output, echo it back
Ok(v @ Value::Error { .. }) => return Ok(v.clone().into_pipeline_data()),
// Unsupported data
Ok(other) => {
return Ok(Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string and binary".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.expect_span(),
}),
}
.into_pipeline_data());
}
Err(err) => return Err(err.to_owned()),
};
let max = byte_slice.len().min(arg.pattern.len() - i);
if byte_slice[..max] == arg.pattern[i..i + max] {
i += max;
if i >= arg.pattern.len() {
return Ok(Value::bool(true, span).into_pipeline_data());
}
} else {
return Ok(Value::bool(false, span).into_pipeline_data());
}
}
// We reached the end of the stream and never returned,
// the pattern wasn't exhausted so it probably doesn't match
Ok(Value::bool(false, span).into_pipeline_data())
}
_ => operate(
starts_with,
arg,
input,
call.head,
engine_state.ctrlc.clone(),
),
}
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Checks if binary 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)),
},
Example {
description: "Checks if binary starts with `0x[1F]`",
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F]",
result: Some(Value::test_bool(true)),
},
Example {
description: "Checks if binary starts with `0x[1F]`",
example: "0x[1F FF AA AA] | bytes starts-with 0x[11]",
result: Some(Value::test_bool(false)),
},
]
}
}
fn starts_with(val: &Value, args: &Arguments, span: Span) -> Value {
match val {
Value::Binary {
val,
span: val_span,
} => Value::bool(val.starts_with(&args.pattern), *val_span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "binary".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.expect_span(),
}),
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesStartsWith {})
}
}

View File

@@ -1,3 +0,0 @@
mod fmt;
pub(crate) use fmt::Fmt;

View File

@@ -1,9 +0,0 @@
mod each_while;
mod roll;
mod rotate;
mod update_cells;
pub(crate) use each_while::EachWhile;
pub(crate) use roll::*;
pub(crate) use rotate::Rotate;
pub(crate) use update_cells::UpdateCells;

View File

@@ -1 +0,0 @@
pub(crate) mod url;

View File

@@ -1,5 +0,0 @@
mod from;
mod to;
pub(crate) use from::url::FromUrl;
pub(crate) use to::html::ToHtml;

View File

@@ -1 +0,0 @@
pub(crate) mod html;

View File

@@ -1,118 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"math arccos"
}
fn signature(&self) -> Signature {
Signature::build("math arccos")
.switch("degrees", "Return degrees instead of radians", Some('d'))
.input_output_types(vec![
(Type::Number, Type::Float),
(
Type::List(Box::new(Type::Number)),
Type::List(Box::new(Type::Float)),
),
])
.allow_variants_without_examples(true)
.vectorizes_over_list(true)
.category(Category::Math)
}
fn usage(&self) -> &str {
"Returns the arccosine of the number."
}
fn search_terms(&self) -> Vec<&str> {
vec!["trigonometry", "inverse"]
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let use_degrees = call.has_flag("degrees");
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
input.map(
move |value| operate(value, head, use_degrees),
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get the arccosine of 1",
example: "1 | math arccos",
result: Some(Value::test_float(0.0f64)),
},
Example {
description: "Get the arccosine of -1 in degrees",
example: "-1 | math arccos -d",
result: Some(Value::test_float(180.0)),
},
]
}
}
fn operate(value: Value, head: Span, use_degrees: bool) -> Value {
match value {
numeric @ (Value::Int { .. } | Value::Float { .. }) => {
let (val, span) = match numeric {
Value::Int { val, span } => (val as f64, span),
Value::Float { val, span } => (val, span),
_ => unreachable!(),
};
if (-1.0..=1.0).contains(&val) {
let val = val.acos();
let val = if use_degrees { val.to_degrees() } else { val };
Value::Float { val, span }
} else {
Value::Error {
error: Box::new(ShellError::UnsupportedInput(
"'arccos' undefined for values outside the closed interval [-1, 1].".into(),
"value originates from here".into(),
head,
span,
)),
}
}
}
Value::Error { .. } => value,
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "numeric".into(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.expect_span(),
}),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@@ -1,108 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"math arccosh"
}
fn signature(&self) -> Signature {
Signature::build("math arccosh")
.input_output_types(vec![
(Type::Number, Type::Float),
(
Type::List(Box::new(Type::Number)),
Type::List(Box::new(Type::Float)),
),
])
.allow_variants_without_examples(true)
.vectorizes_over_list(true)
.category(Category::Math)
}
fn usage(&self) -> &str {
"Returns the inverse of the hyperbolic cosine function."
}
fn search_terms(&self) -> Vec<&str> {
vec!["trigonometry", "inverse", "hyperbolic"]
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
input.map(
move |value| operate(value, head),
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the arccosh of 1",
example: "1 | math arccosh",
result: Some(Value::test_float(0.0f64)),
}]
}
}
fn operate(value: Value, head: Span) -> Value {
match value {
numeric @ (Value::Int { .. } | Value::Float { .. }) => {
let (val, span) = match numeric {
Value::Int { val, span } => (val as f64, span),
Value::Float { val, span } => (val, span),
_ => unreachable!(),
};
if (1.0..).contains(&val) {
let val = val.acosh();
Value::Float { val, span }
} else {
Value::Error {
error: Box::new(ShellError::UnsupportedInput(
"'arccosh' undefined for values below 1.".into(),
"value originates from here".into(),
head,
span,
)),
}
}
}
Value::Error { .. } => value,
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "numeric".into(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.expect_span(),
}),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@@ -1,119 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"math arcsin"
}
fn signature(&self) -> Signature {
Signature::build("math arcsin")
.switch("degrees", "Return degrees instead of radians", Some('d'))
.input_output_types(vec![
(Type::Number, Type::Float),
(
Type::List(Box::new(Type::Number)),
Type::List(Box::new(Type::Float)),
),
])
.allow_variants_without_examples(true)
.vectorizes_over_list(true)
.category(Category::Math)
}
fn usage(&self) -> &str {
"Returns the arcsine of the number."
}
fn search_terms(&self) -> Vec<&str> {
vec!["trigonometry", "inverse"]
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let use_degrees = call.has_flag("degrees");
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
input.map(
move |value| operate(value, head, use_degrees),
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
let pi = std::f64::consts::PI;
vec![
Example {
description: "Get the arcsine of 1",
example: "1 | math arcsin",
result: Some(Value::test_float(pi / 2.0)),
},
Example {
description: "Get the arcsine of 1 in degrees",
example: "1 | math arcsin -d",
result: Some(Value::test_float(90.0)),
},
]
}
}
fn operate(value: Value, head: Span, use_degrees: bool) -> Value {
match value {
numeric @ (Value::Int { .. } | Value::Float { .. }) => {
let (val, span) = match numeric {
Value::Int { val, span } => (val as f64, span),
Value::Float { val, span } => (val, span),
_ => unreachable!(),
};
if (-1.0..=1.0).contains(&val) {
let val = val.asin();
let val = if use_degrees { val.to_degrees() } else { val };
Value::Float { val, span }
} else {
Value::Error {
error: Box::new(ShellError::UnsupportedInput(
"'arcsin' undefined for values outside the closed interval [-1, 1].".into(),
"value originates from here".into(),
head,
span,
)),
}
}
}
Value::Error { .. } => value,
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "numeric".into(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.expect_span(),
}),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@@ -1,97 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"math arcsinh"
}
fn signature(&self) -> Signature {
Signature::build("math arcsinh")
.input_output_types(vec![
(Type::Number, Type::Float),
(
Type::List(Box::new(Type::Number)),
Type::List(Box::new(Type::Float)),
),
])
.allow_variants_without_examples(true)
.vectorizes_over_list(true)
.category(Category::Math)
}
fn usage(&self) -> &str {
"Returns the inverse of the hyperbolic sine function."
}
fn search_terms(&self) -> Vec<&str> {
vec!["trigonometry", "inverse", "hyperbolic"]
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
input.map(
move |value| operate(value, head),
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the arcsinh of 0",
example: "0 | math arcsinh",
result: Some(Value::test_float(0.0f64)),
}]
}
}
fn operate(value: Value, head: Span) -> Value {
match value {
numeric @ (Value::Int { .. } | Value::Float { .. }) => {
let (val, span) = match numeric {
Value::Int { val, span } => (val as f64, span),
Value::Float { val, span } => (val, span),
_ => unreachable!(),
};
let val = val.asinh();
Value::Float { val, span }
}
Value::Error { .. } => value,
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "numeric".into(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.expect_span(),
}),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@@ -1,108 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"math arctan"
}
fn signature(&self) -> Signature {
Signature::build("math arctan")
.switch("degrees", "Return degrees instead of radians", Some('d'))
.input_output_types(vec![
(Type::Number, Type::Float),
(
Type::List(Box::new(Type::Number)),
Type::List(Box::new(Type::Float)),
),
])
.allow_variants_without_examples(true)
.vectorizes_over_list(true)
.category(Category::Math)
}
fn usage(&self) -> &str {
"Returns the arctangent of the number."
}
fn search_terms(&self) -> Vec<&str> {
vec!["trigonometry", "inverse"]
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let use_degrees = call.has_flag("degrees");
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
input.map(
move |value| operate(value, head, use_degrees),
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
let pi = std::f64::consts::PI;
vec![
Example {
description: "Get the arctangent of 1",
example: "1 | math arctan",
result: Some(Value::test_float(pi / 4.0f64)),
},
Example {
description: "Get the arctangent of -1 in degrees",
example: "-1 | math arctan -d",
result: Some(Value::test_float(-45.0)),
},
]
}
}
fn operate(value: Value, head: Span, use_degrees: bool) -> Value {
match value {
numeric @ (Value::Int { .. } | Value::Float { .. }) => {
let (val, span) = match numeric {
Value::Int { val, span } => (val as f64, span),
Value::Float { val, span } => (val, span),
_ => unreachable!(),
};
let val = val.atan();
let val = if use_degrees { val.to_degrees() } else { val };
Value::Float { val, span }
}
Value::Error { .. } => value,
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "numeric".into(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.expect_span(),
}),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@@ -1,108 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"math arctanh"
}
fn signature(&self) -> Signature {
Signature::build("math arctanh")
.input_output_types(vec![
(Type::Number, Type::Float),
(
Type::List(Box::new(Type::Number)),
Type::List(Box::new(Type::Float)),
),
])
.allow_variants_without_examples(true)
.vectorizes_over_list(true)
.category(Category::Math)
}
fn usage(&self) -> &str {
"Returns the inverse of the hyperbolic tangent function."
}
fn search_terms(&self) -> Vec<&str> {
vec!["trigonometry", "inverse", "hyperbolic"]
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
input.map(
move |value| operate(value, head),
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the arctanh of 1",
example: "1 | math arctanh",
result: Some(Value::test_float(f64::INFINITY)),
}]
}
}
fn operate(value: Value, head: Span) -> Value {
match value {
numeric @ (Value::Int { .. } | Value::Float { .. }) => {
let (val, span) = match numeric {
Value::Int { val, span } => (val as f64, span),
Value::Float { val, span } => (val, span),
_ => unreachable!(),
};
if (-1.0..=1.0).contains(&val) {
let val = val.atanh();
Value::Float { val, span }
} else {
Value::Error {
error: Box::new(ShellError::UnsupportedInput(
"'arctanh' undefined for values outside the open interval (-1, 1).".into(),
"value originates from here".into(),
head,
span,
)),
}
}
}
Value::Error { .. } => value,
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "numeric".into(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.expect_span(),
}),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@@ -1,117 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"math cos"
}
fn signature(&self) -> Signature {
Signature::build("math cos")
.switch("degrees", "Use degrees instead of radians", Some('d'))
.input_output_types(vec![
(Type::Number, Type::Float),
(
Type::List(Box::new(Type::Number)),
Type::List(Box::new(Type::Float)),
),
])
.vectorizes_over_list(true)
.category(Category::Math)
}
fn usage(&self) -> &str {
"Returns the cosine of the number."
}
fn search_terms(&self) -> Vec<&str> {
vec!["trigonometry"]
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let use_degrees = call.has_flag("degrees");
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
input.map(
move |value| operate(value, head, use_degrees),
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Apply the cosine to π",
example: "math pi | math cos",
result: Some(Value::test_float(-1f64)),
},
Example {
description: "Apply the cosine to a list of angles in degrees",
example: "[0 90 180 270 360] | math cos -d",
result: Some(Value::List {
vals: vec![
Value::test_float(1f64),
Value::test_float(0f64),
Value::test_float(-1f64),
Value::test_float(0f64),
Value::test_float(1f64),
],
span: Span::test_data(),
}),
},
]
}
}
fn operate(value: Value, head: Span, use_degrees: bool) -> Value {
match value {
numeric @ (Value::Int { .. } | Value::Float { .. }) => {
let (val, span) = match numeric {
Value::Int { val, span } => (val as f64, span),
Value::Float { val, span } => (val, span),
_ => unreachable!(),
};
let val = if use_degrees { val.to_radians() } else { val };
Value::Float {
val: val.cos(),
span,
}
}
Value::Error { .. } => value,
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "numeric".into(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.expect_span(),
}),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@@ -1,99 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"math cosh"
}
fn signature(&self) -> Signature {
Signature::build("math cosh")
.input_output_types(vec![
(Type::Number, Type::Float),
(
Type::List(Box::new(Type::Number)),
Type::List(Box::new(Type::Float)),
),
])
.allow_variants_without_examples(true)
.vectorizes_over_list(true)
.category(Category::Math)
}
fn usage(&self) -> &str {
"Returns the hyperbolic cosine of the number."
}
fn search_terms(&self) -> Vec<&str> {
vec!["trigonometry", "hyperbolic"]
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
input.map(
move |value| operate(value, head),
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
let e = std::f64::consts::E;
vec![Example {
description: "Apply the hyperbolic cosine to 1",
example: "1 | math cosh",
result: Some(Value::test_float(((e * e) + 1.0) / (2.0 * e))),
}]
}
}
fn operate(value: Value, head: Span) -> Value {
match value {
numeric @ (Value::Int { .. } | Value::Float { .. }) => {
let (val, span) = match numeric {
Value::Int { val, span } => (val as f64, span),
Value::Float { val, span } => (val, span),
_ => unreachable!(),
};
Value::Float {
val: val.cosh(),
span,
}
}
Value::Error { .. } => value,
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "numeric".into(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.expect_span(),
}),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@@ -1,64 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
};
#[allow(clippy::excessive_precision)]
/// The Euler-Mascheroni constant (γ)
pub const EGAMMA: f64 = 0.577215664901532860606512090082402431_f64;
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"math egamma"
}
fn signature(&self) -> Signature {
Signature::build("math egamma")
.input_output_types(vec![(Type::Any, Type::Float)])
.category(Category::Math)
}
fn usage(&self) -> &str {
"Returns the EulerMascheroni constant γ. ( 1 | math egamma)."
}
fn search_terms(&self) -> Vec<&str> {
vec!["euler", "constant", "gamma"]
}
#[allow(clippy::approx_constant)]
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "math egamma | math round --precision 3",
description: "Get the first three decimal digits of γ",
result: Some(Value::test_float(0.577)),
}]
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
// TODO: replace with std::f64::consts::EGAMMA when available https://github.com/rust-lang/rust/issues/103883
Ok(Value::float(EGAMMA, call.head).into_pipeline_data())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@@ -1,59 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"math e"
}
fn signature(&self) -> Signature {
Signature::build("math e")
.input_output_types(vec![(Type::Any, Type::Float)])
.category(Category::Math)
}
fn usage(&self) -> &str {
"Returns the mathematical constant e (exp(1)/'1 | math exp')."
}
fn search_terms(&self) -> Vec<&str> {
vec!["euler", "number", "constant"]
}
#[allow(clippy::approx_constant)]
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "math e | math round --precision 3",
description: "Get the first three decimal digits of e",
result: Some(Value::test_float(2.718)),
}]
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
Ok(Value::float(std::f64::consts::E, call.head).into_pipeline_data())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

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