Merge branch 'main' into alacritty-terminal
This commit is contained in:
commit
ca781ef29d
8
.github/dependabot.yml
vendored
8
.github/dependabot.yml
vendored
|
@ -18,6 +18,14 @@ updates:
|
||||||
ignore:
|
ignore:
|
||||||
- dependency-name: "*"
|
- dependency-name: "*"
|
||||||
update-types: ["version-update:semver-patch"]
|
update-types: ["version-update:semver-patch"]
|
||||||
|
groups:
|
||||||
|
# Only update polars as a whole as there are many subcrates that need to
|
||||||
|
# be updated at once. We explicitly depend on some of them, so batch their
|
||||||
|
# updates to not take up dependabot PR slots with dysfunctional PRs
|
||||||
|
polars:
|
||||||
|
patterns:
|
||||||
|
- "polars"
|
||||||
|
- "polars-*"
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
|
|
2
.github/workflows/audit.yml
vendored
2
.github/workflows/audit.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
||||||
# Prevent sudden announcement of a new advisory from failing ci:
|
# Prevent sudden announcement of a new advisory from failing ci:
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
- uses: rustsec/audit-check@v1.4.1
|
- uses: rustsec/audit-check@v1.4.1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
|
@ -33,10 +33,10 @@ jobs:
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||||
|
|
||||||
- name: cargo fmt
|
- name: cargo fmt
|
||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
|
@ -66,10 +66,10 @@ jobs:
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }}
|
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }}
|
||||||
|
@ -95,10 +95,10 @@ jobs:
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||||
|
|
||||||
- name: Install Nushell
|
- name: Install Nushell
|
||||||
run: cargo install --path . --locked --no-default-features
|
run: cargo install --path . --locked --no-default-features
|
||||||
|
@ -146,10 +146,10 @@ jobs:
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS
|
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS
|
||||||
|
|
22
.github/workflows/nightly-build.yml
vendored
22
.github/workflows/nightly-build.yml
vendored
|
@ -27,7 +27,7 @@ jobs:
|
||||||
# if: github.repository == 'nushell/nightly'
|
# if: github.repository == 'nushell/nightly'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
if: github.repository == 'nushell/nightly'
|
if: github.repository == 'nushell/nightly'
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
|
@ -36,10 +36,10 @@ jobs:
|
||||||
token: ${{ secrets.WORKFLOW_TOKEN }}
|
token: ${{ secrets.WORKFLOW_TOKEN }}
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.10
|
uses: hustcer/setup-nu@v3.12
|
||||||
if: github.repository == 'nushell/nightly'
|
if: github.repository == 'nushell/nightly'
|
||||||
with:
|
with:
|
||||||
version: 0.93.0
|
version: 0.95.0
|
||||||
|
|
||||||
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
||||||
- name: Prepare for Nightly Release
|
- name: Prepare for Nightly Release
|
||||||
|
@ -112,7 +112,7 @@ jobs:
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
@ -122,15 +122,15 @@ jobs:
|
||||||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||||
with:
|
with:
|
||||||
rustflags: ''
|
rustflags: ''
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.10
|
uses: hustcer/setup-nu@v3.12
|
||||||
with:
|
with:
|
||||||
version: 0.93.0
|
version: 0.95.0
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
|
@ -161,7 +161,7 @@ jobs:
|
||||||
# REF: https://github.com/marketplace/actions/gh-release
|
# REF: https://github.com/marketplace/actions/gh-release
|
||||||
# Create a release only in nushell/nightly repo
|
# Create a release only in nushell/nightly repo
|
||||||
- name: Publish Archive
|
- name: Publish Archive
|
||||||
uses: softprops/action-gh-release@v2.0.5
|
uses: softprops/action-gh-release@v2.0.6
|
||||||
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
|
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
|
||||||
with:
|
with:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
|
@ -181,14 +181,14 @@ jobs:
|
||||||
- name: Waiting for Release
|
- name: Waiting for Release
|
||||||
run: sleep 1800
|
run: sleep 1800
|
||||||
|
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.10
|
uses: hustcer/setup-nu@v3.12
|
||||||
with:
|
with:
|
||||||
version: 0.93.0
|
version: 0.95.0
|
||||||
|
|
||||||
# Keep the last a few releases
|
# Keep the last a few releases
|
||||||
- name: Delete Older Releases
|
- name: Delete Older Releases
|
||||||
|
|
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
|
@ -62,23 +62,23 @@ jobs:
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Update Rust Toolchain Target
|
- name: Update Rust Toolchain Target
|
||||||
run: |
|
run: |
|
||||||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||||
with:
|
with:
|
||||||
cache: false
|
cache: false
|
||||||
rustflags: ''
|
rustflags: ''
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.10
|
uses: hustcer/setup-nu@v3.12
|
||||||
with:
|
with:
|
||||||
version: 0.93.0
|
version: 0.95.0
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
|
@ -91,7 +91,7 @@ jobs:
|
||||||
|
|
||||||
# REF: https://github.com/marketplace/actions/gh-release
|
# REF: https://github.com/marketplace/actions/gh-release
|
||||||
- name: Publish Archive
|
- name: Publish Archive
|
||||||
uses: softprops/action-gh-release@v2.0.5
|
uses: softprops/action-gh-release@v2.0.6
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
|
|
4
.github/workflows/typos.yml
vendored
4
.github/workflows/typos.yml
vendored
|
@ -7,7 +7,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Actions Repository
|
- name: Checkout Actions Repository
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Check spelling
|
- name: Check spelling
|
||||||
uses: crate-ci/typos@v1.21.0
|
uses: crate-ci/typos@v1.22.9
|
||||||
|
|
26
CITATION.cff
Normal file
26
CITATION.cff
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
cff-version: 1.2.0
|
||||||
|
title: 'Nushell'
|
||||||
|
message: >-
|
||||||
|
If you use this software and wish to cite it,
|
||||||
|
you can use the metadata from this file.
|
||||||
|
type: software
|
||||||
|
authors:
|
||||||
|
- name: "The Nushell Project Team"
|
||||||
|
identifiers:
|
||||||
|
- type: url
|
||||||
|
value: 'https://github.com/nushell/nushell'
|
||||||
|
description: Repository
|
||||||
|
repository-code: 'https://github.com/nushell/nushell'
|
||||||
|
url: 'https://www.nushell.sh/'
|
||||||
|
abstract: >-
|
||||||
|
The goal of the Nushell project is to take the Unix
|
||||||
|
philosophy of shells, where pipes connect simple commands
|
||||||
|
together, and bring it to the modern style of development.
|
||||||
|
Thus, rather than being either a shell, or a programming
|
||||||
|
language, Nushell connects both by bringing a rich
|
||||||
|
programming language and a full-featured shell together
|
||||||
|
into one package.
|
||||||
|
keywords:
|
||||||
|
- nushell
|
||||||
|
- shell
|
||||||
|
license: MIT
|
555
Cargo.lock
generated
555
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
75
Cargo.toml
75
Cargo.toml
|
@ -11,7 +11,7 @@ license = "MIT"
|
||||||
name = "nu"
|
name = "nu"
|
||||||
repository = "https://github.com/nushell/nushell"
|
repository = "https://github.com/nushell/nushell"
|
||||||
rust-version = "1.77.2"
|
rust-version = "1.77.2"
|
||||||
version = "0.94.2"
|
version = "0.95.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
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ members = [
|
||||||
"crates/nu-lsp",
|
"crates/nu-lsp",
|
||||||
"crates/nu-pretty-hex",
|
"crates/nu-pretty-hex",
|
||||||
"crates/nu-protocol",
|
"crates/nu-protocol",
|
||||||
|
"crates/nu-derive-value",
|
||||||
"crates/nu-plugin",
|
"crates/nu-plugin",
|
||||||
"crates/nu-plugin-core",
|
"crates/nu-plugin-core",
|
||||||
"crates/nu-plugin-engine",
|
"crates/nu-plugin-engine",
|
||||||
|
@ -75,10 +76,12 @@ chardetng = "0.1.17"
|
||||||
chrono = { default-features = false, version = "0.4.34" }
|
chrono = { default-features = false, version = "0.4.34" }
|
||||||
chrono-humanize = "0.2.3"
|
chrono-humanize = "0.2.3"
|
||||||
chrono-tz = "0.8"
|
chrono-tz = "0.8"
|
||||||
|
convert_case = "0.6"
|
||||||
crossbeam-channel = "0.5.8"
|
crossbeam-channel = "0.5.8"
|
||||||
crossterm = "0.27"
|
crossterm = "0.27"
|
||||||
csv = "1.3"
|
csv = "1.3"
|
||||||
ctrlc = "3.4"
|
ctrlc = "3.4"
|
||||||
|
deunicode = "1.6.0"
|
||||||
dialoguer = { default-features = false, version = "0.11" }
|
dialoguer = { default-features = false, version = "0.11" }
|
||||||
digest = { default-features = false, version = "0.10" }
|
digest = { default-features = false, version = "0.10" }
|
||||||
dirs-next = "2.0"
|
dirs-next = "2.0"
|
||||||
|
@ -94,7 +97,7 @@ heck = "0.5.0"
|
||||||
human-date-parser = "0.1.1"
|
human-date-parser = "0.1.1"
|
||||||
indexmap = "2.2"
|
indexmap = "2.2"
|
||||||
indicatif = "0.17"
|
indicatif = "0.17"
|
||||||
interprocess = "2.1.0"
|
interprocess = "2.2.0"
|
||||||
is_executable = "1.0"
|
is_executable = "1.0"
|
||||||
itertools = "0.12"
|
itertools = "0.12"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
@ -118,17 +121,20 @@ num-format = "0.4"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
omnipath = "0.1"
|
omnipath = "0.1"
|
||||||
once_cell = "1.18"
|
once_cell = "1.18"
|
||||||
open = "5.1"
|
open = "5.2"
|
||||||
os_pipe = { version = "1.1", features = ["io_safety"] }
|
os_pipe = { version = "1.2", features = ["io_safety"] }
|
||||||
pathdiff = "0.2"
|
pathdiff = "0.2"
|
||||||
percent-encoding = "2"
|
percent-encoding = "2"
|
||||||
pretty_assertions = "1.4"
|
pretty_assertions = "1.4"
|
||||||
print-positions = "0.6"
|
print-positions = "0.6"
|
||||||
|
proc-macro-error = { version = "1.0", default-features = false }
|
||||||
|
proc-macro2 = "1.0"
|
||||||
procfs = "0.16.0"
|
procfs = "0.16.0"
|
||||||
pwd = "1.3"
|
pwd = "1.3"
|
||||||
quick-xml = "0.31.0"
|
quick-xml = "0.31.0"
|
||||||
quickcheck = "1.0"
|
quickcheck = "1.0"
|
||||||
quickcheck_macros = "1.0"
|
quickcheck_macros = "1.0"
|
||||||
|
quote = "1.0"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
ratatui = "0.26"
|
ratatui = "0.26"
|
||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
|
@ -148,6 +154,7 @@ serde_urlencoded = "0.7.1"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
strip-ansi-escapes = "0.2.0"
|
strip-ansi-escapes = "0.2.0"
|
||||||
|
syn = "2.0"
|
||||||
sysinfo = "0.30"
|
sysinfo = "0.30"
|
||||||
tabled = { version = "0.14.0", default-features = false }
|
tabled = { version = "0.14.0", default-features = false }
|
||||||
tempfile = "3.10"
|
tempfile = "3.10"
|
||||||
|
@ -160,14 +167,14 @@ unicode-segmentation = "1.11"
|
||||||
unicode-width = "0.1"
|
unicode-width = "0.1"
|
||||||
ureq = { version = "2.9", default-features = false }
|
ureq = { version = "2.9", default-features = false }
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
uu_cp = "0.0.25"
|
uu_cp = "0.0.27"
|
||||||
uu_mkdir = "0.0.25"
|
uu_mkdir = "0.0.27"
|
||||||
uu_mktemp = "0.0.25"
|
uu_mktemp = "0.0.27"
|
||||||
uu_mv = "0.0.25"
|
uu_mv = "0.0.27"
|
||||||
uu_whoami = "0.0.25"
|
uu_whoami = "0.0.27"
|
||||||
uu_uname = "0.0.25"
|
uu_uname = "0.0.27"
|
||||||
uucore = "0.0.25"
|
uucore = "0.0.27"
|
||||||
uuid = "1.8.0"
|
uuid = "1.9.1"
|
||||||
v_htmlescape = "0.15.0"
|
v_htmlescape = "0.15.0"
|
||||||
wax = "0.6"
|
wax = "0.6"
|
||||||
which = "6.0.0"
|
which = "6.0.0"
|
||||||
|
@ -175,27 +182,27 @@ windows = "0.54"
|
||||||
winreg = "0.52"
|
winreg = "0.52"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cli = { path = "./crates/nu-cli", version = "0.94.2" }
|
nu-cli = { path = "./crates/nu-cli", version = "0.95.1" }
|
||||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.94.2" }
|
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.95.1" }
|
||||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.94.2" }
|
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.95.1" }
|
||||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.94.2", optional = true }
|
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.95.1", optional = true }
|
||||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.94.2" }
|
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.95.1" }
|
||||||
nu-command = { path = "./crates/nu-command", version = "0.94.2" }
|
nu-command = { path = "./crates/nu-command", version = "0.95.1" }
|
||||||
nu-engine = { path = "./crates/nu-engine", version = "0.94.2" }
|
nu-engine = { path = "./crates/nu-engine", version = "0.95.1" }
|
||||||
nu-explore = { path = "./crates/nu-explore", version = "0.94.2" }
|
nu-explore = { path = "./crates/nu-explore", version = "0.95.1" }
|
||||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.94.2" }
|
nu-lsp = { path = "./crates/nu-lsp/", version = "0.95.1" }
|
||||||
nu-parser = { path = "./crates/nu-parser", version = "0.94.2" }
|
nu-parser = { path = "./crates/nu-parser", version = "0.95.1" }
|
||||||
nu-path = { path = "./crates/nu-path", version = "0.94.2" }
|
nu-path = { path = "./crates/nu-path", version = "0.95.1" }
|
||||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.94.2" }
|
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.95.1" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.94.2" }
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.95.1" }
|
||||||
nu-std = { path = "./crates/nu-std", version = "0.94.2" }
|
nu-std = { path = "./crates/nu-std", version = "0.95.1" }
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.94.2" }
|
nu-system = { path = "./crates/nu-system", version = "0.95.1" }
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.94.2" }
|
nu-utils = { path = "./crates/nu-utils", version = "0.95.1" }
|
||||||
|
|
||||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
ctrlc = { workspace = true }
|
ctrlc = { workspace = true }
|
||||||
|
dirs-next = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] }
|
miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] }
|
||||||
mimalloc = { version = "0.1.42", default-features = false, optional = true }
|
mimalloc = { version = "0.1.42", default-features = false, optional = true }
|
||||||
|
@ -219,9 +226,9 @@ nix = { workspace = true, default-features = false, features = [
|
||||||
] }
|
] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.94.2" }
|
nu-test-support = { path = "./crates/nu-test-support", version = "0.95.1" }
|
||||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.94.2" }
|
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.95.1" }
|
||||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.94.2" }
|
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.95.1" }
|
||||||
alacritty_terminal = { workspace = true }
|
alacritty_terminal = { workspace = true }
|
||||||
assert_cmd = "2.0"
|
assert_cmd = "2.0"
|
||||||
dirs-next = { workspace = true }
|
dirs-next = { workspace = true }
|
||||||
|
@ -246,7 +253,6 @@ default = ["default-no-clipboard", "system-clipboard"]
|
||||||
# See https://github.com/nushell/nushell/pull/11535
|
# See https://github.com/nushell/nushell/pull/11535
|
||||||
default-no-clipboard = [
|
default-no-clipboard = [
|
||||||
"plugin",
|
"plugin",
|
||||||
"which-support",
|
|
||||||
"trash-support",
|
"trash-support",
|
||||||
"sqlite",
|
"sqlite",
|
||||||
"mimalloc",
|
"mimalloc",
|
||||||
|
@ -266,7 +272,6 @@ system-clipboard = [
|
||||||
]
|
]
|
||||||
|
|
||||||
# Stable (Default)
|
# Stable (Default)
|
||||||
which-support = ["nu-command/which-support", "nu-cmd-lang/which-support"]
|
|
||||||
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"]
|
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"]
|
||||||
|
|
||||||
# SQLite commands for nushell
|
# SQLite commands for nushell
|
||||||
|
@ -300,7 +305,7 @@ 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", branch = "main" }
|
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||||
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
||||||
|
|
||||||
# Run all benchmarks with `cargo bench`
|
# Run all benchmarks with `cargo bench`
|
||||||
|
|
|
@ -52,7 +52,7 @@ To use `Nu` in GitHub Action, check [setup-nu](https://github.com/marketplace/ac
|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
[](https://repology.org/project/nushell/versions)
|
[](https://repology.org/project/nushell/versions)
|
||||||
|
|
||||||
For details about which platforms the Nushell team actively supports, see [our platform support policy](devdocs/PLATFORM_SUPPORT.md).
|
For details about which platforms the Nushell team actively supports, see [our platform support policy](devdocs/PLATFORM_SUPPORT.md).
|
||||||
|
|
||||||
|
@ -222,6 +222,7 @@ Please submit an issue or PR to be added to this list.
|
||||||
- [clap](https://github.com/clap-rs/clap/tree/master/clap_complete_nushell)
|
- [clap](https://github.com/clap-rs/clap/tree/master/clap_complete_nushell)
|
||||||
- [Dorothy](http://github.com/bevry/dorothy)
|
- [Dorothy](http://github.com/bevry/dorothy)
|
||||||
- [Direnv](https://github.com/direnv/direnv/blob/master/docs/hook.md#nushell)
|
- [Direnv](https://github.com/direnv/direnv/blob/master/docs/hook.md#nushell)
|
||||||
|
- [x-cmd](https://x-cmd.com/mod/nu)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
@ -47,8 +47,7 @@ fn setup_stack_and_engine_from_command(command: &str) -> (Stack, EngineState) {
|
||||||
&mut engine,
|
&mut engine,
|
||||||
&mut stack,
|
&mut stack,
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
None,
|
Default::default(),
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -90,8 +89,7 @@ fn bench_command(
|
||||||
&mut engine,
|
&mut engine,
|
||||||
&mut stack,
|
&mut stack,
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
None,
|
Default::default(),
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,27 +5,27 @@ 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.94.2"
|
version = "0.95.1"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.2" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
|
||||||
nu-command = { path = "../nu-command", version = "0.94.2" }
|
nu-command = { path = "../nu-command", version = "0.95.1" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.94.2" }
|
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
|
||||||
rstest = { workspace = true, default-features = false }
|
rstest = { workspace = true, default-features = false }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.2" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.94.2" }
|
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||||
nu-path = { path = "../nu-path", version = "0.94.2" }
|
nu-path = { path = "../nu-path", version = "0.95.1" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.94.2" }
|
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.2", optional = true }
|
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1", optional = true }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.94.2" }
|
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.94.2" }
|
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.94.2" }
|
nu-color-config = { path = "../nu-color-config", version = "0.95.1" }
|
||||||
nu-ansi-term = { workspace = true }
|
nu-ansi-term = { workspace = true }
|
||||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use crate::completions::{CompletionOptions, SortBy};
|
use crate::completions::CompletionOptions;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{Stack, StateWorkingSet},
|
engine::{Stack, StateWorkingSet},
|
||||||
levenshtein_distance, Span,
|
Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
|
|
||||||
// Completer trait represents the three stages of the completion
|
|
||||||
// fetch, filter and sort
|
|
||||||
pub trait Completer {
|
pub trait Completer {
|
||||||
|
/// Fetch, filter, and sort completions
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn fetch(
|
fn fetch(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -19,32 +18,6 @@ pub trait Completer {
|
||||||
pos: usize,
|
pos: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<SemanticSuggestion>;
|
) -> Vec<SemanticSuggestion>;
|
||||||
|
|
||||||
fn get_sort_by(&self) -> SortBy {
|
|
||||||
SortBy::Ascending
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sort(&self, items: Vec<SemanticSuggestion>, prefix: Vec<u8>) -> Vec<SemanticSuggestion> {
|
|
||||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
|
||||||
let mut filtered_items = items;
|
|
||||||
|
|
||||||
// Sort items
|
|
||||||
match self.get_sort_by() {
|
|
||||||
SortBy::LevenshteinDistance => {
|
|
||||||
filtered_items.sort_by(|a, b| {
|
|
||||||
let a_distance = levenshtein_distance(&prefix_str, &a.suggestion.value);
|
|
||||||
let b_distance = levenshtein_distance(&prefix_str, &b.suggestion.value);
|
|
||||||
a_distance.cmp(&b_distance)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
SortBy::Ascending => {
|
|
||||||
filtered_items.sort_by(|a, b| a.suggestion.value.cmp(&b.suggestion.value));
|
|
||||||
}
|
|
||||||
SortBy::None => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
filtered_items
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
|
|
|
@ -9,7 +9,7 @@ use nu_protocol::{
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
|
|
||||||
use super::SemanticSuggestion;
|
use super::{completion_common::sort_suggestions, SemanticSuggestion};
|
||||||
|
|
||||||
pub struct CommandCompletion {
|
pub struct CommandCompletion {
|
||||||
flattened: Vec<(Span, FlatShape)>,
|
flattened: Vec<(Span, FlatShape)>,
|
||||||
|
@ -161,7 +161,7 @@ impl Completer for CommandCompletion {
|
||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
_stack: &Stack,
|
_stack: &Stack,
|
||||||
_prefix: Vec<u8>,
|
prefix: Vec<u8>,
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
|
@ -198,7 +198,11 @@ impl Completer for CommandCompletion {
|
||||||
};
|
};
|
||||||
|
|
||||||
if !subcommands.is_empty() {
|
if !subcommands.is_empty() {
|
||||||
return subcommands;
|
return sort_suggestions(
|
||||||
|
&String::from_utf8_lossy(&prefix),
|
||||||
|
subcommands,
|
||||||
|
SortBy::LevenshteinDistance,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = working_set.get_config();
|
let config = working_set.get_config();
|
||||||
|
@ -223,11 +227,11 @@ impl Completer for CommandCompletion {
|
||||||
vec![]
|
vec![]
|
||||||
};
|
};
|
||||||
|
|
||||||
subcommands.into_iter().chain(commands).collect::<Vec<_>>()
|
sort_suggestions(
|
||||||
}
|
&String::from_utf8_lossy(&prefix),
|
||||||
|
commands,
|
||||||
fn get_sort_by(&self) -> SortBy {
|
SortBy::LevenshteinDistance,
|
||||||
SortBy::LevenshteinDistance
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,8 +51,7 @@ impl NuCompleter {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fetch
|
completer.fetch(
|
||||||
let mut suggestions = completer.fetch(
|
|
||||||
working_set,
|
working_set,
|
||||||
&self.stack,
|
&self.stack,
|
||||||
prefix.clone(),
|
prefix.clone(),
|
||||||
|
@ -60,12 +59,7 @@ impl NuCompleter {
|
||||||
offset,
|
offset,
|
||||||
pos,
|
pos,
|
||||||
&options,
|
&options,
|
||||||
);
|
)
|
||||||
|
|
||||||
// Sort
|
|
||||||
suggestions = completer.sort(suggestions, prefix);
|
|
||||||
|
|
||||||
suggestions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn external_completion(
|
fn external_completion(
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
use crate::completions::{matches, CompletionOptions};
|
use crate::{
|
||||||
|
completions::{matches, CompletionOptions},
|
||||||
|
SemanticSuggestion,
|
||||||
|
};
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use nu_engine::env_to_string;
|
use nu_engine::env_to_string;
|
||||||
use nu_path::home_dir;
|
use nu_path::{expand_to_real_path, home_dir};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Span,
|
levenshtein_distance, Span,
|
||||||
};
|
};
|
||||||
use nu_utils::get_ls_colors;
|
use nu_utils::get_ls_colors;
|
||||||
use std::path::{
|
use std::path::{
|
||||||
is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR,
|
is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::SortBy;
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct PathBuiltFromString {
|
pub struct PathBuiltFromString {
|
||||||
parts: Vec<String>,
|
parts: Vec<String>,
|
||||||
|
@ -45,6 +50,7 @@ fn complete_rec(
|
||||||
return completions;
|
return completions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut entries = Vec::new();
|
||||||
for entry in result.filter_map(|e| e.ok()) {
|
for entry in result.filter_map(|e| e.ok()) {
|
||||||
let entry_name = entry.file_name().to_string_lossy().into_owned();
|
let entry_name = entry.file_name().to_string_lossy().into_owned();
|
||||||
let entry_isdir = entry.path().is_dir();
|
let entry_isdir = entry.path().is_dir();
|
||||||
|
@ -53,20 +59,26 @@ fn complete_rec(
|
||||||
built.isdir = entry_isdir;
|
built.isdir = entry_isdir;
|
||||||
|
|
||||||
if !dir || entry_isdir {
|
if !dir || entry_isdir {
|
||||||
match partial.split_first() {
|
entries.push((entry_name, built));
|
||||||
Some((base, rest)) => {
|
}
|
||||||
if matches(base, &entry_name, options) {
|
}
|
||||||
if !rest.is_empty() || isdir {
|
|
||||||
completions
|
let prefix = partial.first().unwrap_or(&"");
|
||||||
.extend(complete_rec(rest, &built, cwd, options, dir, isdir));
|
let sorted_entries = sort_completions(prefix, entries, SortBy::Ascending, |(entry, _)| entry);
|
||||||
} else {
|
|
||||||
completions.push(built);
|
for (entry_name, built) in sorted_entries {
|
||||||
}
|
match partial.split_first() {
|
||||||
|
Some((base, rest)) => {
|
||||||
|
if matches(base, &entry_name, options) {
|
||||||
|
if !rest.is_empty() || isdir {
|
||||||
|
completions.extend(complete_rec(rest, &built, cwd, options, dir, isdir));
|
||||||
|
} else {
|
||||||
|
completions.push(built);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
}
|
||||||
completions.push(built);
|
None => {
|
||||||
}
|
completions.push(built);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,9 +197,14 @@ pub fn complete_item(
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
let path = original_cwd.apply(p);
|
let path = original_cwd.apply(p);
|
||||||
let style = ls_colors.as_ref().map(|lsc| {
|
let style = ls_colors.as_ref().map(|lsc| {
|
||||||
lsc.style_for_path_with_metadata(&path, std::fs::symlink_metadata(&path).ok().as_ref())
|
lsc.style_for_path_with_metadata(
|
||||||
.map(lscolors::Style::to_nu_ansi_term_style)
|
&path,
|
||||||
.unwrap_or_default()
|
std::fs::symlink_metadata(expand_to_real_path(&path))
|
||||||
|
.ok()
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.map(lscolors::Style::to_nu_ansi_term_style)
|
||||||
|
.unwrap_or_default()
|
||||||
});
|
});
|
||||||
(span, escape_path(path, want_directory), style)
|
(span, escape_path(path, want_directory), style)
|
||||||
})
|
})
|
||||||
|
@ -251,3 +268,38 @@ pub fn adjust_if_intermediate(
|
||||||
readjusted,
|
readjusted,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience function to sort suggestions using [`sort_completions`]
|
||||||
|
pub fn sort_suggestions(
|
||||||
|
prefix: &str,
|
||||||
|
items: Vec<SemanticSuggestion>,
|
||||||
|
sort_by: SortBy,
|
||||||
|
) -> Vec<SemanticSuggestion> {
|
||||||
|
sort_completions(prefix, items, sort_by, |it| &it.suggestion.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Arguments
|
||||||
|
/// * `prefix` - What the user's typed, for sorting by Levenshtein distance
|
||||||
|
pub fn sort_completions<T>(
|
||||||
|
prefix: &str,
|
||||||
|
mut items: Vec<T>,
|
||||||
|
sort_by: SortBy,
|
||||||
|
get_value: fn(&T) -> &str,
|
||||||
|
) -> Vec<T> {
|
||||||
|
// Sort items
|
||||||
|
match sort_by {
|
||||||
|
SortBy::LevenshteinDistance => {
|
||||||
|
items.sort_by(|a, b| {
|
||||||
|
let a_distance = levenshtein_distance(prefix, get_value(a));
|
||||||
|
let b_distance = levenshtein_distance(prefix, get_value(b));
|
||||||
|
a_distance.cmp(&b_distance)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
SortBy::Ascending => {
|
||||||
|
items.sort_by(|a, b| get_value(a).cmp(get_value(b)));
|
||||||
|
}
|
||||||
|
SortBy::None => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
items
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@ use nu_protocol::{
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use super::completion_common::sort_suggestions;
|
||||||
|
|
||||||
pub struct CustomCompletion {
|
pub struct CustomCompletion {
|
||||||
stack: Stack,
|
stack: Stack,
|
||||||
decl_id: usize,
|
decl_id: usize,
|
||||||
|
@ -52,18 +54,16 @@ impl Completer for CustomCompletion {
|
||||||
decl_id: self.decl_id,
|
decl_id: self.decl_id,
|
||||||
head: span,
|
head: span,
|
||||||
arguments: vec![
|
arguments: vec![
|
||||||
Argument::Positional(Expression {
|
Argument::Positional(Expression::new_unknown(
|
||||||
span: Span::unknown(),
|
Expr::String(self.line.clone()),
|
||||||
ty: Type::String,
|
Span::unknown(),
|
||||||
expr: Expr::String(self.line.clone()),
|
Type::String,
|
||||||
custom_completion: None,
|
)),
|
||||||
}),
|
Argument::Positional(Expression::new_unknown(
|
||||||
Argument::Positional(Expression {
|
Expr::Int(line_pos as i64),
|
||||||
span: Span::unknown(),
|
Span::unknown(),
|
||||||
ty: Type::Int,
|
Type::Int,
|
||||||
expr: Expr::Int(line_pos as i64),
|
)),
|
||||||
custom_completion: None,
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
parser_info: HashMap::new(),
|
parser_info: HashMap::new(),
|
||||||
},
|
},
|
||||||
|
@ -124,15 +124,12 @@ impl Completer for CustomCompletion {
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if let Some(custom_completion_options) = custom_completion_options {
|
let suggestions = if let Some(custom_completion_options) = custom_completion_options {
|
||||||
filter(&prefix, suggestions, &custom_completion_options)
|
filter(&prefix, suggestions, &custom_completion_options)
|
||||||
} else {
|
} else {
|
||||||
filter(&prefix, suggestions, completion_options)
|
filter(&prefix, suggestions, completion_options)
|
||||||
}
|
};
|
||||||
}
|
sort_suggestions(&String::from_utf8_lossy(&prefix), suggestions, self.sort_by)
|
||||||
|
|
||||||
fn get_sort_by(&self) -> SortBy {
|
|
||||||
self.sort_by
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::completions::{
|
use crate::completions::{
|
||||||
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
||||||
Completer, CompletionOptions, SortBy,
|
Completer, CompletionOptions,
|
||||||
};
|
};
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
levenshtein_distance, Span,
|
Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::path::{Path, MAIN_SEPARATOR as SEP};
|
use std::path::Path;
|
||||||
|
|
||||||
use super::SemanticSuggestion;
|
use super::SemanticSuggestion;
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ impl Completer for DirectoryCompletion {
|
||||||
|
|
||||||
// Filter only the folders
|
// Filter only the folders
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
let output: Vec<_> = directory_completion(
|
let items: Vec<_> = directory_completion(
|
||||||
span,
|
span,
|
||||||
&prefix,
|
&prefix,
|
||||||
&working_set.permanent_state.current_work_dir(),
|
&working_set.permanent_state.current_work_dir(),
|
||||||
|
@ -62,41 +62,11 @@ impl Completer for DirectoryCompletion {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort results prioritizing the non hidden folders
|
|
||||||
fn sort(&self, items: Vec<SemanticSuggestion>, prefix: Vec<u8>) -> Vec<SemanticSuggestion> {
|
|
||||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
|
||||||
|
|
||||||
// Sort items
|
|
||||||
let mut sorted_items = items;
|
|
||||||
|
|
||||||
match self.get_sort_by() {
|
|
||||||
SortBy::Ascending => {
|
|
||||||
sorted_items.sort_by(|a, b| {
|
|
||||||
// Ignore trailing slashes in folder names when sorting
|
|
||||||
a.suggestion
|
|
||||||
.value
|
|
||||||
.trim_end_matches(SEP)
|
|
||||||
.cmp(b.suggestion.value.trim_end_matches(SEP))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
SortBy::LevenshteinDistance => {
|
|
||||||
sorted_items.sort_by(|a, b| {
|
|
||||||
let a_distance = levenshtein_distance(&prefix_str, &a.suggestion.value);
|
|
||||||
let b_distance = levenshtein_distance(&prefix_str, &b.suggestion.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<SemanticSuggestion> = vec![];
|
let mut hidden: Vec<SemanticSuggestion> = vec![];
|
||||||
let mut non_hidden: Vec<SemanticSuggestion> = vec![];
|
let mut non_hidden: Vec<SemanticSuggestion> = vec![];
|
||||||
|
|
||||||
for item in sorted_items.into_iter() {
|
for item in items.into_iter() {
|
||||||
let item_path = Path::new(&item.suggestion.value);
|
let item_path = Path::new(&item.suggestion.value);
|
||||||
|
|
||||||
if let Some(value) = item_path.file_name() {
|
if let Some(value) = item_path.file_name() {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::completions::{file_path_completion, Completer, CompletionOptions, SortBy};
|
use crate::completions::{file_path_completion, Completer, CompletionOptions};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{Stack, StateWorkingSet},
|
engine::{Stack, StateWorkingSet},
|
||||||
Span,
|
Span,
|
||||||
|
@ -6,7 +6,7 @@ use nu_protocol::{
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR};
|
use std::path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR};
|
||||||
|
|
||||||
use super::SemanticSuggestion;
|
use super::{completion_common::sort_suggestions, SemanticSuggestion, SortBy};
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct DotNuCompletion {}
|
pub struct DotNuCompletion {}
|
||||||
|
@ -131,10 +131,6 @@ impl Completer for DotNuCompletion {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
output
|
sort_suggestions(&prefix_str, output, SortBy::Ascending)
|
||||||
}
|
|
||||||
|
|
||||||
fn get_sort_by(&self) -> SortBy {
|
|
||||||
SortBy::LevenshteinDistance
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
use crate::completions::{
|
use crate::completions::{
|
||||||
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
||||||
Completer, CompletionOptions, SortBy,
|
Completer, CompletionOptions,
|
||||||
};
|
};
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
levenshtein_distance, Span,
|
Span,
|
||||||
};
|
};
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::path::{Path, MAIN_SEPARATOR as SEP};
|
use std::path::Path;
|
||||||
|
|
||||||
use super::SemanticSuggestion;
|
use super::SemanticSuggestion;
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ impl Completer for FileCompletion {
|
||||||
} = adjust_if_intermediate(&prefix, working_set, span);
|
} = adjust_if_intermediate(&prefix, working_set, span);
|
||||||
|
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
let output: Vec<_> = complete_item(
|
let items: Vec<_> = complete_item(
|
||||||
readjusted,
|
readjusted,
|
||||||
span,
|
span,
|
||||||
&prefix,
|
&prefix,
|
||||||
|
@ -67,41 +67,13 @@ impl Completer for FileCompletion {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
output
|
// Sort results prioritizing the non hidden folders
|
||||||
}
|
|
||||||
|
|
||||||
// Sort results prioritizing the non hidden folders
|
|
||||||
fn sort(&self, items: Vec<SemanticSuggestion>, prefix: Vec<u8>) -> Vec<SemanticSuggestion> {
|
|
||||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
|
||||||
|
|
||||||
// Sort items
|
|
||||||
let mut sorted_items = items;
|
|
||||||
|
|
||||||
match self.get_sort_by() {
|
|
||||||
SortBy::Ascending => {
|
|
||||||
sorted_items.sort_by(|a, b| {
|
|
||||||
// Ignore trailing slashes in folder names when sorting
|
|
||||||
a.suggestion
|
|
||||||
.value
|
|
||||||
.trim_end_matches(SEP)
|
|
||||||
.cmp(b.suggestion.value.trim_end_matches(SEP))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
SortBy::LevenshteinDistance => {
|
|
||||||
sorted_items.sort_by(|a, b| {
|
|
||||||
let a_distance = levenshtein_distance(&prefix_str, &a.suggestion.value);
|
|
||||||
let b_distance = levenshtein_distance(&prefix_str, &b.suggestion.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<SemanticSuggestion> = vec![];
|
let mut hidden: Vec<SemanticSuggestion> = vec![];
|
||||||
let mut non_hidden: Vec<SemanticSuggestion> = vec![];
|
let mut non_hidden: Vec<SemanticSuggestion> = vec![];
|
||||||
|
|
||||||
for item in sorted_items.into_iter() {
|
for item in items.into_iter() {
|
||||||
let item_path = Path::new(&item.suggestion.value);
|
let item_path = Path::new(&item.suggestion.value);
|
||||||
|
|
||||||
if let Some(value) = item_path.file_name() {
|
if let Some(value) = item_path.file_name() {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use crate::completions::{Completer, CompletionOptions};
|
use crate::completions::{
|
||||||
|
completion_common::sort_suggestions, Completer, CompletionOptions, SortBy,
|
||||||
|
};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Expr, Expression},
|
ast::{Expr, Expression},
|
||||||
engine::{Stack, StateWorkingSet},
|
engine::{Stack, StateWorkingSet},
|
||||||
|
@ -90,7 +92,7 @@ impl Completer for FlagCompletion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return sort_suggestions(&String::from_utf8_lossy(&prefix), output, SortBy::Ascending);
|
||||||
}
|
}
|
||||||
|
|
||||||
vec![]
|
vec![]
|
||||||
|
|
|
@ -9,6 +9,8 @@ use nu_protocol::{
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
|
use super::{completion_common::sort_suggestions, SortBy};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct VariableCompletion {
|
pub struct VariableCompletion {
|
||||||
var_context: (Vec<u8>, Vec<Vec<u8>>), // tuple with $var and the sublevels (.b.c.d)
|
var_context: (Vec<u8>, Vec<Vec<u8>>), // tuple with $var and the sublevels (.b.c.d)
|
||||||
|
@ -40,6 +42,7 @@ impl Completer for VariableCompletion {
|
||||||
end: span.end - offset,
|
end: span.end - offset,
|
||||||
};
|
};
|
||||||
let sublevels_count = self.var_context.1.len();
|
let sublevels_count = self.var_context.1.len();
|
||||||
|
let prefix_str = String::from_utf8_lossy(&prefix);
|
||||||
|
|
||||||
// Completions for the given variable
|
// Completions for the given variable
|
||||||
if !var_str.is_empty() {
|
if !var_str.is_empty() {
|
||||||
|
@ -69,7 +72,7 @@ impl Completer for VariableCompletion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No nesting provided, return all env vars
|
// No nesting provided, return all env vars
|
||||||
|
@ -93,7 +96,7 @@ impl Completer for VariableCompletion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +120,7 @@ impl Completer for VariableCompletion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +142,7 @@ impl Completer for VariableCompletion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,6 +229,8 @@ impl Completer for VariableCompletion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output = sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
||||||
|
|
||||||
output.dedup(); // TODO: Removes only consecutive duplicates, is it intended?
|
output.dedup(); // TODO: Removes only consecutive duplicates, is it intended?
|
||||||
|
|
||||||
output
|
output
|
||||||
|
|
|
@ -8,7 +8,7 @@ use nu_protocol::{
|
||||||
report_error_new, HistoryFileFormat, PipelineData,
|
report_error_new, HistoryFileFormat, PipelineData,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use nu_utils::utils::perf;
|
use nu_utils::perf;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
|
@ -53,13 +53,10 @@ pub fn read_plugin_file(
|
||||||
// Reading signatures from plugin registry file
|
// Reading signatures from plugin registry file
|
||||||
// The plugin.msgpackz file stores the parsed signature collected from each registered plugin
|
// The plugin.msgpackz file stores the parsed signature collected from each registered plugin
|
||||||
add_plugin_file(engine_state, plugin_file.clone(), storage_path);
|
add_plugin_file(engine_state, plugin_file.clone(), storage_path);
|
||||||
perf(
|
perf!(
|
||||||
"add plugin file to engine_state",
|
"add plugin file to engine_state",
|
||||||
start_time,
|
start_time,
|
||||||
file!(),
|
engine_state.get_config().use_ansi_coloring
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
engine_state.get_config().use_ansi_coloring,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
|
@ -137,13 +134,10 @@ pub fn read_plugin_file(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
perf(
|
perf!(
|
||||||
&format!("read plugin file {}", plugin_path.display()),
|
&format!("read plugin file {}", plugin_path.display()),
|
||||||
start_time,
|
start_time,
|
||||||
file!(),
|
engine_state.get_config().use_ansi_coloring
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
engine_state.get_config().use_ansi_coloring,
|
|
||||||
);
|
);
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
|
|
||||||
|
@ -156,13 +150,10 @@ pub fn read_plugin_file(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
perf(
|
perf!(
|
||||||
&format!("load plugin file {}", plugin_path.display()),
|
&format!("load plugin file {}", plugin_path.display()),
|
||||||
start_time,
|
start_time,
|
||||||
file!(),
|
engine_state.get_config().use_ansi_coloring
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
engine_state.get_config().use_ansi_coloring,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -344,7 +335,10 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -
|
||||||
name: identity.name().to_owned(),
|
name: identity.name().to_owned(),
|
||||||
filename: identity.filename().to_owned(),
|
filename: identity.filename().to_owned(),
|
||||||
shell: identity.shell().map(|p| p.to_owned()),
|
shell: identity.shell().map(|p| p.to_owned()),
|
||||||
data: PluginRegistryItemData::Valid { commands },
|
data: PluginRegistryItemData::Valid {
|
||||||
|
metadata: Default::default(),
|
||||||
|
commands,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,13 +372,10 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
perf(
|
perf!(
|
||||||
"migrate old plugin file",
|
"migrate old plugin file",
|
||||||
start_time,
|
start_time,
|
||||||
file!(),
|
engine_state.get_config().use_ansi_coloring
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
engine_state.get_config().use_ansi_coloring,
|
|
||||||
);
|
);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,15 +8,45 @@ use nu_protocol::{
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct EvaluateCommandsOpts {
|
||||||
|
pub table_mode: Option<Value>,
|
||||||
|
pub error_style: Option<Value>,
|
||||||
|
pub no_newline: bool,
|
||||||
|
}
|
||||||
|
|
||||||
/// Run a command (or commands) given to us by the user
|
/// Run a command (or commands) given to us by the user
|
||||||
pub fn evaluate_commands(
|
pub fn evaluate_commands(
|
||||||
commands: &Spanned<String>,
|
commands: &Spanned<String>,
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
table_mode: Option<Value>,
|
opts: EvaluateCommandsOpts,
|
||||||
no_newline: bool,
|
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
|
let EvaluateCommandsOpts {
|
||||||
|
table_mode,
|
||||||
|
error_style,
|
||||||
|
no_newline,
|
||||||
|
} = opts;
|
||||||
|
|
||||||
|
// Handle the configured error style early
|
||||||
|
if let Some(e_style) = error_style {
|
||||||
|
match e_style.coerce_str()?.parse() {
|
||||||
|
Ok(e_style) => {
|
||||||
|
Arc::make_mut(&mut engine_state.config).error_style = e_style;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(ShellError::GenericError {
|
||||||
|
error: "Invalid value for `--error-style`".into(),
|
||||||
|
msg: err.into(),
|
||||||
|
span: Some(e_style.span()),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Translate environment variables from Strings to Values
|
// Translate environment variables from Strings to Values
|
||||||
convert_env_values(engine_state, stack)?;
|
convert_env_values(engine_state, stack)?;
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ mod validation;
|
||||||
pub use commands::add_cli_context;
|
pub use commands::add_cli_context;
|
||||||
pub use completions::{FileCompletion, NuCompleter, SemanticSuggestion, SuggestionKind};
|
pub use completions::{FileCompletion, NuCompleter, SemanticSuggestion, SuggestionKind};
|
||||||
pub use config_files::eval_config_contents;
|
pub use config_files::eval_config_contents;
|
||||||
pub use eval_cmds::evaluate_commands;
|
pub use eval_cmds::{evaluate_commands, EvaluateCommandsOpts};
|
||||||
pub use eval_file::evaluate_file;
|
pub use eval_file::evaluate_file;
|
||||||
pub use menus::NuHelpCompleter;
|
pub use menus::NuHelpCompleter;
|
||||||
pub use nu_cmd_base::util::get_init_cwd;
|
pub use nu_cmd_base::util::get_init_cwd;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::{menus::NuMenuCompleter, NuHelpCompleter};
|
use crate::{menus::NuMenuCompleter, NuHelpCompleter};
|
||||||
use crossterm::event::{KeyCode, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyModifiers};
|
||||||
use log::trace;
|
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
|
@ -76,15 +75,15 @@ const DEFAULT_HELP_MENU: &str = r#"
|
||||||
// Adds all menus to line editor
|
// Adds all menus to line editor
|
||||||
pub(crate) fn add_menus(
|
pub(crate) fn add_menus(
|
||||||
mut line_editor: Reedline,
|
mut line_editor: Reedline,
|
||||||
engine_state: Arc<EngineState>,
|
engine_state_ref: Arc<EngineState>,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
trace!("add_menus: config: {:#?}", &config);
|
//log::trace!("add_menus: config: {:#?}", &config);
|
||||||
line_editor = line_editor.clear_menus();
|
line_editor = line_editor.clear_menus();
|
||||||
|
|
||||||
for menu in &config.menus {
|
for menu in &config.menus {
|
||||||
line_editor = add_menu(line_editor, menu, engine_state.clone(), stack, config)?
|
line_editor = add_menu(line_editor, menu, engine_state_ref.clone(), stack, config)?
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checking if the default menus have been added from the config file
|
// Checking if the default menus have been added from the config file
|
||||||
|
@ -94,13 +93,16 @@ pub(crate) fn add_menus(
|
||||||
("help_menu", DEFAULT_HELP_MENU),
|
("help_menu", DEFAULT_HELP_MENU),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let mut engine_state = (*engine_state_ref).clone();
|
||||||
|
let mut menu_eval_results = vec![];
|
||||||
|
|
||||||
for (name, definition) in default_menus {
|
for (name, definition) in default_menus {
|
||||||
if !config
|
if !config
|
||||||
.menus
|
.menus
|
||||||
.iter()
|
.iter()
|
||||||
.any(|menu| menu.name.to_expanded_string("", config) == name)
|
.any(|menu| menu.name.to_expanded_string("", config) == name)
|
||||||
{
|
{
|
||||||
let (block, _) = {
|
let (block, delta) = {
|
||||||
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,
|
||||||
|
@ -112,15 +114,31 @@ pub(crate) fn add_menus(
|
||||||
(output, working_set.render())
|
(output, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
engine_state.merge_delta(delta)?;
|
||||||
|
|
||||||
let mut temp_stack = Stack::new().capture();
|
let mut temp_stack = Stack::new().capture();
|
||||||
let input = PipelineData::Empty;
|
let input = PipelineData::Empty;
|
||||||
let res = eval_block::<WithoutDebug>(&engine_state, &mut temp_stack, &block, input)?;
|
menu_eval_results.push(eval_block::<WithoutDebug>(
|
||||||
|
&engine_state,
|
||||||
|
&mut temp_stack,
|
||||||
|
&block,
|
||||||
|
input,
|
||||||
|
)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let PipelineData::Value(value, None) = res {
|
let new_engine_state_ref = Arc::new(engine_state);
|
||||||
for menu in create_menus(&value)? {
|
|
||||||
line_editor =
|
for res in menu_eval_results.into_iter() {
|
||||||
add_menu(line_editor, &menu, engine_state.clone(), stack, config)?;
|
if let PipelineData::Value(value, None) = res {
|
||||||
}
|
for menu in create_menus(&value)? {
|
||||||
|
line_editor = add_menu(
|
||||||
|
line_editor,
|
||||||
|
&menu,
|
||||||
|
new_engine_state_ref.clone(),
|
||||||
|
stack,
|
||||||
|
config,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ use nu_protocol::{
|
||||||
};
|
};
|
||||||
use nu_utils::{
|
use nu_utils::{
|
||||||
filesystem::{have_permission, PermissionResult},
|
filesystem::{have_permission, PermissionResult},
|
||||||
utils::perf,
|
perf,
|
||||||
};
|
};
|
||||||
use reedline::{
|
use reedline::{
|
||||||
CursorConfig, CwdAwareHinter, DefaultCompleter, EditCommand, Emacs, FileBackedHistory,
|
CursorConfig, CwdAwareHinter, DefaultCompleter, EditCommand, Emacs, FileBackedHistory,
|
||||||
|
@ -89,14 +89,7 @@ pub fn evaluate_repl(
|
||||||
if let Err(e) = convert_env_values(engine_state, &unique_stack) {
|
if let Err(e) = convert_env_values(engine_state, &unique_stack) {
|
||||||
report_error_new(engine_state, &e);
|
report_error_new(engine_state, &e);
|
||||||
}
|
}
|
||||||
perf(
|
perf!("translate env vars", start_time, use_color);
|
||||||
"translate env vars",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
// seed env vars
|
// seed env vars
|
||||||
unique_stack.add_env_var(
|
unique_stack.add_env_var(
|
||||||
|
@ -225,28 +218,14 @@ fn get_line_editor(
|
||||||
|
|
||||||
// Now that reedline is created, get the history session id and store it in engine_state
|
// Now that reedline is created, get the history session id and store it in engine_state
|
||||||
store_history_id_in_engine(engine_state, &line_editor);
|
store_history_id_in_engine(engine_state, &line_editor);
|
||||||
perf(
|
perf!("setup reedline", start_time, use_color);
|
||||||
"setup reedline",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(history) = engine_state.history_config() {
|
if let Some(history) = engine_state.history_config() {
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
|
|
||||||
line_editor = setup_history(nushell_path, engine_state, line_editor, history)?;
|
line_editor = setup_history(nushell_path, engine_state, line_editor, history)?;
|
||||||
|
|
||||||
perf(
|
perf!("setup history", start_time, use_color);
|
||||||
"setup history",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Ok(line_editor)
|
Ok(line_editor)
|
||||||
}
|
}
|
||||||
|
@ -289,28 +268,14 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||||
if let Err(err) = engine_state.merge_env(&mut stack, cwd) {
|
if let Err(err) = engine_state.merge_env(&mut stack, cwd) {
|
||||||
report_error_new(engine_state, &err);
|
report_error_new(engine_state, &err);
|
||||||
}
|
}
|
||||||
perf(
|
perf!("merge env", start_time, use_color);
|
||||||
"merge env",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
// Reset the ctrl-c handler
|
// Reset the ctrl-c handler
|
||||||
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
||||||
ctrlc.store(false, Ordering::SeqCst);
|
ctrlc.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
perf(
|
perf!("reset ctrlc", start_time, use_color);
|
||||||
"reset ctrlc",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
// Right before we start our prompt and take input from the user,
|
// Right before we start our prompt and take input from the user,
|
||||||
|
@ -320,14 +285,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||||
report_error_new(engine_state, &err);
|
report_error_new(engine_state, &err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
perf(
|
perf!("pre-prompt hook", start_time, use_color);
|
||||||
"pre-prompt hook",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
// Next, check all the environment variables they ask for
|
// Next, check all the environment variables they ask for
|
||||||
|
@ -336,14 +294,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||||
if let Err(error) = hook::eval_env_change_hook(env_change, engine_state, &mut stack) {
|
if let Err(error) = hook::eval_env_change_hook(env_change, engine_state, &mut stack) {
|
||||||
report_error_new(engine_state, &error)
|
report_error_new(engine_state, &error)
|
||||||
}
|
}
|
||||||
perf(
|
perf!("env-change hook", start_time, use_color);
|
||||||
"env-change hook",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
let engine_reference = Arc::new(engine_state.clone());
|
let engine_reference = Arc::new(engine_state.clone());
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
|
@ -355,14 +306,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||||
vi_normal: map_nucursorshape_to_cursorshape(config.cursor_shape_vi_normal),
|
vi_normal: map_nucursorshape_to_cursorshape(config.cursor_shape_vi_normal),
|
||||||
emacs: map_nucursorshape_to_cursorshape(config.cursor_shape_emacs),
|
emacs: map_nucursorshape_to_cursorshape(config.cursor_shape_emacs),
|
||||||
};
|
};
|
||||||
perf(
|
perf!("get config/cursor config", start_time, use_color);
|
||||||
"get config/cursor config",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
// at this line we have cloned the state for the completer and the transient prompt
|
// at this line we have cloned the state for the completer and the transient prompt
|
||||||
|
@ -394,14 +338,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||||
.with_ansi_colors(config.use_ansi_coloring)
|
.with_ansi_colors(config.use_ansi_coloring)
|
||||||
.with_cursor_config(cursor_config);
|
.with_cursor_config(cursor_config);
|
||||||
|
|
||||||
perf(
|
perf!("reedline builder", start_time, use_color);
|
||||||
"reedline builder",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
let style_computer = StyleComputer::from_config(engine_state, &stack_arc);
|
let style_computer = StyleComputer::from_config(engine_state, &stack_arc);
|
||||||
|
|
||||||
|
@ -416,14 +353,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||||
line_editor.disable_hints()
|
line_editor.disable_hints()
|
||||||
};
|
};
|
||||||
|
|
||||||
perf(
|
perf!("reedline coloring/style_computer", start_time, use_color);
|
||||||
"reedline coloring/style_computer",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
trace!("adding menus");
|
trace!("adding menus");
|
||||||
|
@ -433,14 +363,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||||
Reedline::create()
|
Reedline::create()
|
||||||
});
|
});
|
||||||
|
|
||||||
perf(
|
perf!("reedline adding menus", start_time, use_color);
|
||||||
"reedline adding menus",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
let buffer_editor = get_editor(engine_state, &stack_arc, Span::unknown());
|
let buffer_editor = get_editor(engine_state, &stack_arc, Span::unknown());
|
||||||
|
@ -457,14 +380,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||||
line_editor
|
line_editor
|
||||||
};
|
};
|
||||||
|
|
||||||
perf(
|
perf!("reedline buffer_editor", start_time, use_color);
|
||||||
"reedline buffer_editor",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(history) = engine_state.history_config() {
|
if let Some(history) = engine_state.history_config() {
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
|
@ -474,28 +390,14 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
perf(
|
perf!("sync_history", start_time, use_color);
|
||||||
"sync_history",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
// Changing the line editor based on the found keybindings
|
// Changing the line editor based on the found keybindings
|
||||||
line_editor = setup_keybindings(engine_state, line_editor);
|
line_editor = setup_keybindings(engine_state, line_editor);
|
||||||
|
|
||||||
perf(
|
perf!("keybindings", start_time, use_color);
|
||||||
"keybindings",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
let config = &engine_state.get_config().clone();
|
let config = &engine_state.get_config().clone();
|
||||||
|
@ -512,14 +414,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||||
nu_prompt,
|
nu_prompt,
|
||||||
);
|
);
|
||||||
|
|
||||||
perf(
|
perf!("update_prompt", start_time, use_color);
|
||||||
"update_prompt",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
*entry_num += 1;
|
*entry_num += 1;
|
||||||
|
|
||||||
|
@ -546,14 +441,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||||
// so we should avoid it or making stack cheaper to clone.
|
// so we should avoid it or making stack cheaper to clone.
|
||||||
let mut stack = Arc::unwrap_or_clone(stack_arc);
|
let mut stack = Arc::unwrap_or_clone(stack_arc);
|
||||||
|
|
||||||
perf(
|
perf!("line_editor setup", start_time, use_color);
|
||||||
"line_editor setup",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
let line_editor_input_time = std::time::Instant::now();
|
let line_editor_input_time = std::time::Instant::now();
|
||||||
match input {
|
match input {
|
||||||
|
@ -590,14 +478,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
perf(
|
perf!("pre_execution_hook", start_time, use_color);
|
||||||
"pre_execution_hook",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||||
repl.cursor_pos = line_editor.current_insertion_point();
|
repl.cursor_pos = line_editor.current_insertion_point();
|
||||||
|
@ -612,26 +493,20 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||||
|
|
||||||
run_ansi_sequence(VSCODE_PRE_EXECUTION_MARKER);
|
run_ansi_sequence(VSCODE_PRE_EXECUTION_MARKER);
|
||||||
|
|
||||||
perf(
|
perf!(
|
||||||
"pre_execute_marker (633;C) ansi escape sequence",
|
"pre_execute_marker (633;C) ansi escape sequence",
|
||||||
start_time,
|
start_time,
|
||||||
file!(),
|
use_color
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
);
|
||||||
} else if shell_integration_osc133 {
|
} else if shell_integration_osc133 {
|
||||||
start_time = Instant::now();
|
start_time = Instant::now();
|
||||||
|
|
||||||
run_ansi_sequence(PRE_EXECUTION_MARKER);
|
run_ansi_sequence(PRE_EXECUTION_MARKER);
|
||||||
|
|
||||||
perf(
|
perf!(
|
||||||
"pre_execute_marker (133;C) ansi escape sequence",
|
"pre_execute_marker (133;C) ansi escape sequence",
|
||||||
start_time,
|
start_time,
|
||||||
file!(),
|
use_color
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if shell_integration_osc133 {
|
} else if shell_integration_osc133 {
|
||||||
|
@ -639,13 +514,10 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||||
|
|
||||||
run_ansi_sequence(PRE_EXECUTION_MARKER);
|
run_ansi_sequence(PRE_EXECUTION_MARKER);
|
||||||
|
|
||||||
perf(
|
perf!(
|
||||||
"pre_execute_marker (133;C) ansi escape sequence",
|
"pre_execute_marker (133;C) ansi escape sequence",
|
||||||
start_time,
|
start_time,
|
||||||
file!(),
|
use_color
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -769,22 +641,16 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
perf(
|
perf!(
|
||||||
"processing line editor input",
|
"processing line editor input",
|
||||||
line_editor_input_time,
|
line_editor_input_time,
|
||||||
file!(),
|
use_color
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
perf(
|
perf!(
|
||||||
"time between prompts in line editor loop",
|
"time between prompts in line editor loop",
|
||||||
loop_start_time,
|
loop_start_time,
|
||||||
file!(),
|
use_color
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
(true, stack, line_editor)
|
(true, stack, line_editor)
|
||||||
|
@ -1061,14 +927,7 @@ fn run_shell_integration_osc2(
|
||||||
// ESC]2;stringBEL -- Set window title to string
|
// ESC]2;stringBEL -- Set window title to string
|
||||||
run_ansi_sequence(&format!("\x1b]2;{title}\x07"));
|
run_ansi_sequence(&format!("\x1b]2;{title}\x07"));
|
||||||
|
|
||||||
perf(
|
perf!("set title with command osc2", start_time, use_color);
|
||||||
"set title with command osc2",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1093,13 +952,10 @@ fn run_shell_integration_osc7(
|
||||||
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
|
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
|
||||||
));
|
));
|
||||||
|
|
||||||
perf(
|
perf!(
|
||||||
"communicate path to terminal with osc7",
|
"communicate path to terminal with osc7",
|
||||||
start_time,
|
start_time,
|
||||||
file!(),
|
use_color
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1116,13 +972,10 @@ fn run_shell_integration_osc9_9(engine_state: &EngineState, stack: &mut Stack, u
|
||||||
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
|
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
|
||||||
));
|
));
|
||||||
|
|
||||||
perf(
|
perf!(
|
||||||
"communicate path to terminal with osc9;9",
|
"communicate path to terminal with osc9;9",
|
||||||
start_time,
|
start_time,
|
||||||
file!(),
|
use_color
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1142,13 +995,10 @@ fn run_shell_integration_osc633(engine_state: &EngineState, stack: &mut Stack, u
|
||||||
VSCODE_CWD_PROPERTY_MARKER_PREFIX, path, VSCODE_CWD_PROPERTY_MARKER_SUFFIX
|
VSCODE_CWD_PROPERTY_MARKER_PREFIX, path, VSCODE_CWD_PROPERTY_MARKER_SUFFIX
|
||||||
));
|
));
|
||||||
|
|
||||||
perf(
|
perf!(
|
||||||
"communicate path to terminal with osc633;P",
|
"communicate path to terminal with osc633;P",
|
||||||
start_time,
|
start_time,
|
||||||
file!(),
|
use_color
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1371,13 +1221,10 @@ fn run_finaliziation_ansi_sequence(
|
||||||
shell_integration_osc133,
|
shell_integration_osc133,
|
||||||
));
|
));
|
||||||
|
|
||||||
perf(
|
perf!(
|
||||||
"post_execute_marker (633;D) ansi escape sequences",
|
"post_execute_marker (633;D) ansi escape sequences",
|
||||||
start_time,
|
start_time,
|
||||||
file!(),
|
use_color
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
);
|
||||||
} else if shell_integration_osc133 {
|
} else if shell_integration_osc133 {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
@ -1389,13 +1236,10 @@ fn run_finaliziation_ansi_sequence(
|
||||||
shell_integration_osc133,
|
shell_integration_osc133,
|
||||||
));
|
));
|
||||||
|
|
||||||
perf(
|
perf!(
|
||||||
"post_execute_marker (133;D) ansi escape sequences",
|
"post_execute_marker (133;D) ansi escape sequences",
|
||||||
start_time,
|
start_time,
|
||||||
file!(),
|
use_color
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if shell_integration_osc133 {
|
} else if shell_integration_osc133 {
|
||||||
|
@ -1408,13 +1252,10 @@ fn run_finaliziation_ansi_sequence(
|
||||||
shell_integration_osc133,
|
shell_integration_osc133,
|
||||||
));
|
));
|
||||||
|
|
||||||
perf(
|
perf!(
|
||||||
"post_execute_marker (133;D) ansi escape sequences",
|
"post_execute_marker (133;D) ansi escape sequences",
|
||||||
start_time,
|
start_time,
|
||||||
file!(),
|
use_color
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,6 +138,7 @@ impl Highlighter for NuHighlighter {
|
||||||
|
|
||||||
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::GlobInterpolation => 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(_) | FlatShape::VarDecl(_) => {
|
||||||
add_colored_token(&shape.1, next_token)
|
add_colored_token(&shape.1, next_token)
|
||||||
|
@ -452,15 +453,17 @@ fn find_matching_block_end_in_expr(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::StringInterpolation(exprs) => exprs.iter().find_map(|expr| {
|
Expr::StringInterpolation(exprs) | Expr::GlobInterpolation(exprs, _) => {
|
||||||
find_matching_block_end_in_expr(
|
exprs.iter().find_map(|expr| {
|
||||||
line,
|
find_matching_block_end_in_expr(
|
||||||
working_set,
|
line,
|
||||||
expr,
|
working_set,
|
||||||
global_span_offset,
|
expr,
|
||||||
global_cursor_offset,
|
global_span_offset,
|
||||||
)
|
global_cursor_offset,
|
||||||
}),
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
Expr::List(list) => {
|
Expr::List(list) => {
|
||||||
if expr_last == global_cursor_offset {
|
if expr_last == global_cursor_offset {
|
||||||
|
|
|
@ -8,7 +8,7 @@ use nu_protocol::{
|
||||||
};
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use nu_utils::enable_vt_processing;
|
use nu_utils::enable_vt_processing;
|
||||||
use nu_utils::utils::perf;
|
use nu_utils::perf;
|
||||||
use std::path::Path;
|
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.
|
||||||
|
@ -228,13 +228,10 @@ pub fn eval_source(
|
||||||
let _ = enable_vt_processing();
|
let _ = enable_vt_processing();
|
||||||
}
|
}
|
||||||
|
|
||||||
perf(
|
perf!(
|
||||||
&format!("eval_source {}", &fname),
|
&format!("eval_source {}", &fname),
|
||||||
start_time,
|
start_time,
|
||||||
file!(),
|
engine_state.get_config().use_ansi_coloring
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
engine_state.get_config().use_ansi_coloring,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
exit_code
|
exit_code
|
||||||
|
|
|
@ -11,7 +11,7 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use support::{
|
use support::{
|
||||||
completions_helpers::{new_partial_engine, new_quote_engine},
|
completions_helpers::{new_dotnu_engine, new_partial_engine, new_quote_engine},
|
||||||
file, folder, match_suggestions, new_engine,
|
file, folder, match_suggestions, new_engine,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -85,8 +85,29 @@ fn custom_completer() -> NuCompleter {
|
||||||
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[fixture]
|
||||||
|
fn subcommand_completer() -> NuCompleter {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Use fuzzy matching, because subcommands are sorted by Levenshtein distance,
|
||||||
|
// and that's not very useful with prefix matching
|
||||||
|
let commands = r#"
|
||||||
|
$env.config.completions.algorithm = "fuzzy"
|
||||||
|
def foo [] {}
|
||||||
|
def "foo bar" [] {}
|
||||||
|
def "foo abaz" [] {}
|
||||||
|
def "foo aabrr" [] {}
|
||||||
|
def food [] {}
|
||||||
|
"#;
|
||||||
|
assert!(support::merge_input(commands.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
// Instantiate a new completer
|
||||||
|
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn variables_dollar_sign_with_varialblecompletion() {
|
fn variables_dollar_sign_with_variablecompletion() {
|
||||||
let (_, _, engine, stack) = new_engine();
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||||
|
@ -138,43 +159,42 @@ fn variables_customcompletion_subcommands_with_customcompletion_2(
|
||||||
#[test]
|
#[test]
|
||||||
fn dotnu_completions() {
|
fn dotnu_completions() {
|
||||||
// Create a new engine
|
// Create a new engine
|
||||||
let (_, _, engine, stack) = new_engine();
|
let (_, _, engine, stack) = new_dotnu_engine();
|
||||||
|
|
||||||
// Instantiate a new completer
|
// Instantiate a new completer
|
||||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||||
|
|
||||||
|
let expected = vec![
|
||||||
|
"asdf.nu".into(),
|
||||||
|
"bar.nu".into(),
|
||||||
|
"bat.nu".into(),
|
||||||
|
"baz.nu".into(),
|
||||||
|
#[cfg(windows)]
|
||||||
|
"dir_module\\".into(),
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
"dir_module/".into(),
|
||||||
|
"foo.nu".into(),
|
||||||
|
"spam.nu".into(),
|
||||||
|
"xyzzy.nu".into(),
|
||||||
|
];
|
||||||
|
|
||||||
// Test source completion
|
// Test source completion
|
||||||
let completion_str = "source-env ".to_string();
|
let completion_str = "source-env ".to_string();
|
||||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||||
|
|
||||||
assert_eq!(2, suggestions.len());
|
match_suggestions(expected.clone(), suggestions);
|
||||||
assert_eq!("custom_completion.nu", suggestions.first().unwrap().value);
|
|
||||||
#[cfg(windows)]
|
|
||||||
assert_eq!("directory_completion\\", suggestions.get(1).unwrap().value);
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
assert_eq!("directory_completion/", suggestions.get(1).unwrap().value);
|
|
||||||
|
|
||||||
// Test use completion
|
// Test use completion
|
||||||
let completion_str = "use ".to_string();
|
let completion_str = "use ".to_string();
|
||||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||||
|
|
||||||
assert_eq!(2, suggestions.len());
|
match_suggestions(expected.clone(), suggestions);
|
||||||
assert_eq!("custom_completion.nu", suggestions.first().unwrap().value);
|
|
||||||
#[cfg(windows)]
|
|
||||||
assert_eq!("directory_completion\\", suggestions.get(1).unwrap().value);
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
assert_eq!("directory_completion/", suggestions.get(1).unwrap().value);
|
|
||||||
|
|
||||||
// Test overlay use completion
|
// Test overlay use completion
|
||||||
let completion_str = "overlay use ".to_string();
|
let completion_str = "overlay use ".to_string();
|
||||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||||
|
|
||||||
assert_eq!(2, suggestions.len());
|
match_suggestions(expected, suggestions);
|
||||||
assert_eq!("custom_completion.nu", suggestions.first().unwrap().value);
|
|
||||||
#[cfg(windows)]
|
|
||||||
assert_eq!("directory_completion\\", suggestions.get(1).unwrap().value);
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
assert_eq!("directory_completion/", suggestions.get(1).unwrap().value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -276,9 +296,10 @@ fn partial_completions() {
|
||||||
|
|
||||||
// Create the expected values
|
// Create the expected values
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
folder(dir.join("partial_a")),
|
folder(dir.join("partial")),
|
||||||
folder(dir.join("partial_b")),
|
folder(dir.join("partial-a")),
|
||||||
folder(dir.join("partial_c")),
|
folder(dir.join("partial-b")),
|
||||||
|
folder(dir.join("partial-c")),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Match the results
|
// Match the results
|
||||||
|
@ -292,13 +313,14 @@ fn partial_completions() {
|
||||||
|
|
||||||
// Create the expected values
|
// Create the expected values
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
file(dir.join("partial_a").join("have_ext.exe")),
|
file(dir.join("partial").join("hello.txt")),
|
||||||
file(dir.join("partial_a").join("have_ext.txt")),
|
file(dir.join("partial-a").join("have_ext.exe")),
|
||||||
file(dir.join("partial_a").join("hello")),
|
file(dir.join("partial-a").join("have_ext.txt")),
|
||||||
file(dir.join("partial_a").join("hola")),
|
file(dir.join("partial-a").join("hello")),
|
||||||
file(dir.join("partial_b").join("hello_b")),
|
file(dir.join("partial-a").join("hola")),
|
||||||
file(dir.join("partial_b").join("hi_b")),
|
file(dir.join("partial-b").join("hello_b")),
|
||||||
file(dir.join("partial_c").join("hello_c")),
|
file(dir.join("partial-b").join("hi_b")),
|
||||||
|
file(dir.join("partial-c").join("hello_c")),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Match the results
|
// Match the results
|
||||||
|
@ -311,14 +333,15 @@ fn partial_completions() {
|
||||||
|
|
||||||
// Create the expected values
|
// Create the expected values
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
file(dir.join("partial_a").join("anotherfile")),
|
file(dir.join("partial").join("hello.txt")),
|
||||||
file(dir.join("partial_a").join("have_ext.exe")),
|
file(dir.join("partial-a").join("anotherfile")),
|
||||||
file(dir.join("partial_a").join("have_ext.txt")),
|
file(dir.join("partial-a").join("have_ext.exe")),
|
||||||
file(dir.join("partial_a").join("hello")),
|
file(dir.join("partial-a").join("have_ext.txt")),
|
||||||
file(dir.join("partial_a").join("hola")),
|
file(dir.join("partial-a").join("hello")),
|
||||||
file(dir.join("partial_b").join("hello_b")),
|
file(dir.join("partial-a").join("hola")),
|
||||||
file(dir.join("partial_b").join("hi_b")),
|
file(dir.join("partial-b").join("hello_b")),
|
||||||
file(dir.join("partial_c").join("hello_c")),
|
file(dir.join("partial-b").join("hi_b")),
|
||||||
|
file(dir.join("partial-c").join("hello_c")),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Match the results
|
// Match the results
|
||||||
|
@ -343,19 +366,25 @@ fn partial_completions() {
|
||||||
// Create the expected values
|
// Create the expected values
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
file(
|
file(
|
||||||
dir.join("partial_a")
|
dir.join("partial")
|
||||||
.join("..")
|
.join("..")
|
||||||
.join("final_partial")
|
.join("final_partial")
|
||||||
.join("somefile"),
|
.join("somefile"),
|
||||||
),
|
),
|
||||||
file(
|
file(
|
||||||
dir.join("partial_b")
|
dir.join("partial-a")
|
||||||
.join("..")
|
.join("..")
|
||||||
.join("final_partial")
|
.join("final_partial")
|
||||||
.join("somefile"),
|
.join("somefile"),
|
||||||
),
|
),
|
||||||
file(
|
file(
|
||||||
dir.join("partial_c")
|
dir.join("partial-b")
|
||||||
|
.join("..")
|
||||||
|
.join("final_partial")
|
||||||
|
.join("somefile"),
|
||||||
|
),
|
||||||
|
file(
|
||||||
|
dir.join("partial-c")
|
||||||
.join("..")
|
.join("..")
|
||||||
.join("final_partial")
|
.join("final_partial")
|
||||||
.join("somefile"),
|
.join("somefile"),
|
||||||
|
@ -366,28 +395,28 @@ fn partial_completions() {
|
||||||
match_suggestions(expected_paths, suggestions);
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
|
||||||
// Test completion for all files under directories whose names begin with "pa"
|
// Test completion for all files under directories whose names begin with "pa"
|
||||||
let file_str = file(dir.join("partial_a").join("have"));
|
let file_str = file(dir.join("partial-a").join("have"));
|
||||||
let target_file = format!("rm {file_str}");
|
let target_file = format!("rm {file_str}");
|
||||||
let suggestions = completer.complete(&target_file, target_file.len());
|
let suggestions = completer.complete(&target_file, target_file.len());
|
||||||
|
|
||||||
// Create the expected values
|
// Create the expected values
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
file(dir.join("partial_a").join("have_ext.exe")),
|
file(dir.join("partial-a").join("have_ext.exe")),
|
||||||
file(dir.join("partial_a").join("have_ext.txt")),
|
file(dir.join("partial-a").join("have_ext.txt")),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Match the results
|
// Match the results
|
||||||
match_suggestions(expected_paths, suggestions);
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
|
||||||
// Test completion for all files under directories whose names begin with "pa"
|
// Test completion for all files under directories whose names begin with "pa"
|
||||||
let file_str = file(dir.join("partial_a").join("have_ext."));
|
let file_str = file(dir.join("partial-a").join("have_ext."));
|
||||||
let file_dir = format!("rm {file_str}");
|
let file_dir = format!("rm {file_str}");
|
||||||
let suggestions = completer.complete(&file_dir, file_dir.len());
|
let suggestions = completer.complete(&file_dir, file_dir.len());
|
||||||
|
|
||||||
// Create the expected values
|
// Create the expected values
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
file(dir.join("partial_a").join("have_ext.exe")),
|
file(dir.join("partial-a").join("have_ext.exe")),
|
||||||
file(dir.join("partial_a").join("have_ext.txt")),
|
file(dir.join("partial-a").join("have_ext.txt")),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Match the results
|
// Match the results
|
||||||
|
@ -652,6 +681,27 @@ fn command_watch_with_filecompletion() {
|
||||||
match_suggestions(expected_paths, suggestions)
|
match_suggestions(expected_paths, suggestions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn subcommand_completions(mut subcommand_completer: NuCompleter) {
|
||||||
|
let prefix = "foo br";
|
||||||
|
let suggestions = subcommand_completer.complete(prefix, prefix.len());
|
||||||
|
match_suggestions(
|
||||||
|
vec!["foo bar".to_string(), "foo aabrr".to_string()],
|
||||||
|
suggestions,
|
||||||
|
);
|
||||||
|
|
||||||
|
let prefix = "foo b";
|
||||||
|
let suggestions = subcommand_completer.complete(prefix, prefix.len());
|
||||||
|
match_suggestions(
|
||||||
|
vec![
|
||||||
|
"foo bar".to_string(),
|
||||||
|
"foo abaz".to_string(),
|
||||||
|
"foo aabrr".to_string(),
|
||||||
|
],
|
||||||
|
suggestions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn file_completion_quoted() {
|
fn file_completion_quoted() {
|
||||||
let (_, _, engine, stack) = new_quote_engine();
|
let (_, _, engine, stack) = new_quote_engine();
|
||||||
|
@ -662,11 +712,11 @@ fn file_completion_quoted() {
|
||||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"\'[a] bc.txt\'".to_string(),
|
|
||||||
"`--help`".to_string(),
|
"`--help`".to_string(),
|
||||||
"`-42`".to_string(),
|
"`-42`".to_string(),
|
||||||
"`-inf`".to_string(),
|
"`-inf`".to_string(),
|
||||||
"`4.2`".to_string(),
|
"`4.2`".to_string(),
|
||||||
|
"\'[a] bc.txt\'".to_string(),
|
||||||
"`te st.txt`".to_string(),
|
"`te st.txt`".to_string(),
|
||||||
"`te#st.txt`".to_string(),
|
"`te#st.txt`".to_string(),
|
||||||
"`te'st.txt`".to_string(),
|
"`te'st.txt`".to_string(),
|
||||||
|
@ -763,11 +813,13 @@ fn variables_completions() {
|
||||||
// Test completions for $nu
|
// Test completions for $nu
|
||||||
let suggestions = completer.complete("$nu.", 4);
|
let suggestions = completer.complete("$nu.", 4);
|
||||||
|
|
||||||
assert_eq!(15, suggestions.len());
|
assert_eq!(18, suggestions.len());
|
||||||
|
|
||||||
let expected: Vec<String> = vec![
|
let expected: Vec<String> = vec![
|
||||||
|
"cache-dir".into(),
|
||||||
"config-path".into(),
|
"config-path".into(),
|
||||||
"current-exe".into(),
|
"current-exe".into(),
|
||||||
|
"data-dir".into(),
|
||||||
"default-config-dir".into(),
|
"default-config-dir".into(),
|
||||||
"env-path".into(),
|
"env-path".into(),
|
||||||
"history-enabled".into(),
|
"history-enabled".into(),
|
||||||
|
@ -781,6 +833,7 @@ fn variables_completions() {
|
||||||
"plugin-path".into(),
|
"plugin-path".into(),
|
||||||
"startup-time".into(),
|
"startup-time".into(),
|
||||||
"temp-path".into(),
|
"temp-path".into(),
|
||||||
|
"vendor-autoload-dir".into(),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Match results
|
// Match results
|
||||||
|
@ -854,6 +907,11 @@ fn variables_completions() {
|
||||||
|
|
||||||
// Match results
|
// Match results
|
||||||
match_suggestions(expected, suggestions);
|
match_suggestions(expected, suggestions);
|
||||||
|
|
||||||
|
let suggestions = completer.complete("$", 1);
|
||||||
|
let expected: Vec<String> = vec!["$actor".into(), "$env".into(), "$in".into(), "$nu".into()];
|
||||||
|
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -68,6 +68,52 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||||
(dir, dir_str, engine_state, stack)
|
(dir, dir_str, engine_state, stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// creates a new engine with the current path into the completions fixtures folder
|
||||||
|
pub fn new_dotnu_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||||
|
// Target folder inside assets
|
||||||
|
let dir = fs::fixtures().join("dotnu_completions");
|
||||||
|
let dir_str = dir
|
||||||
|
.clone()
|
||||||
|
.into_os_string()
|
||||||
|
.into_string()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let dir_span = nu_protocol::Span::new(0, dir_str.len());
|
||||||
|
|
||||||
|
// Create a new engine with default context
|
||||||
|
let mut engine_state = create_default_context();
|
||||||
|
|
||||||
|
// Add $nu
|
||||||
|
engine_state.generate_nu_constant();
|
||||||
|
|
||||||
|
// New stack
|
||||||
|
let mut stack = Stack::new();
|
||||||
|
|
||||||
|
// Add pwd as env var
|
||||||
|
stack.add_env_var("PWD".to_string(), Value::string(dir_str.clone(), dir_span));
|
||||||
|
stack.add_env_var(
|
||||||
|
"TEST".to_string(),
|
||||||
|
Value::string("NUSHELL".to_string(), dir_span),
|
||||||
|
);
|
||||||
|
|
||||||
|
stack.add_env_var(
|
||||||
|
"NU_LIB_DIRS".to_string(),
|
||||||
|
Value::List {
|
||||||
|
vals: vec![
|
||||||
|
Value::string(file(dir.join("lib-dir1")), dir_span),
|
||||||
|
Value::string(file(dir.join("lib-dir2")), dir_span),
|
||||||
|
Value::string(file(dir.join("lib-dir3")), dir_span),
|
||||||
|
],
|
||||||
|
internal_span: dir_span,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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) {
|
pub fn new_quote_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||||
// Target folder inside assets
|
// Target folder inside assets
|
||||||
let dir = fs::fixtures().join("quoted_completions");
|
let dir = fs::fixtures().join("quoted_completions");
|
||||||
|
@ -149,9 +195,13 @@ pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
||||||
Expected: {expected:#?}\n"
|
Expected: {expected:#?}\n"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
expected.iter().zip(suggestions).for_each(|it| {
|
assert_eq!(
|
||||||
assert_eq!(it.0, &it.1.value);
|
expected,
|
||||||
});
|
suggestions
|
||||||
|
.into_iter()
|
||||||
|
.map(|it| it.value)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// append the separator to the converted path
|
// append the separator to the converted path
|
||||||
|
|
|
@ -5,15 +5,15 @@ edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-base"
|
name = "nu-cmd-base"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
||||||
version = "0.94.2"
|
version = "0.95.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
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.94.2" }
|
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.94.2" }
|
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||||
nu-path = { path = "../nu-path", version = "0.94.2" }
|
nu-path = { path = "../nu-path", version = "0.95.1" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.94.2" }
|
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||||
|
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
miette = { workspace = true }
|
miette = { workspace = true }
|
||||||
|
|
|
@ -194,7 +194,7 @@ pub fn eval_hook(
|
||||||
let Some(follow) = val.get("code") else {
|
let Some(follow) = val.get("code") else {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: "code".into(),
|
col_name: "code".into(),
|
||||||
span,
|
span: Some(span),
|
||||||
src_span: span,
|
src_span: span,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,13 +20,14 @@ pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf
|
||||||
|
|
||||||
type MakeRangeError = fn(&str, Span) -> ShellError;
|
type MakeRangeError = fn(&str, Span) -> ShellError;
|
||||||
|
|
||||||
|
/// Returns a inclusive pair of boundary in given `range`.
|
||||||
pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> {
|
pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> {
|
||||||
match range {
|
match range {
|
||||||
Range::IntRange(range) => {
|
Range::IntRange(range) => {
|
||||||
let start = range.start().try_into().unwrap_or(0);
|
let start = range.start().try_into().unwrap_or(0);
|
||||||
let end = match range.end() {
|
let end = match range.end() {
|
||||||
Bound::Included(v) => (v + 1) as isize,
|
Bound::Included(v) => v as isize,
|
||||||
Bound::Excluded(v) => v as isize,
|
Bound::Excluded(v) => (v - 1) as isize,
|
||||||
Bound::Unbounded => isize::MAX,
|
Bound::Unbounded => isize::MAX,
|
||||||
};
|
};
|
||||||
Ok((start, end))
|
Ok((start, end))
|
||||||
|
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-extra"
|
name = "nu-cmd-extra"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
||||||
version = "0.94.2"
|
version = "0.95.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
|
||||||
|
|
||||||
|
@ -13,13 +13,13 @@ version = "0.94.2"
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.2" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.94.2" }
|
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||||
nu-json = { version = "0.94.2", path = "../nu-json" }
|
nu-json = { version = "0.95.1", path = "../nu-json" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.94.2" }
|
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||||
nu-pretty-hex = { version = "0.94.2", path = "../nu-pretty-hex" }
|
nu-pretty-hex = { version = "0.95.1", path = "../nu-pretty-hex" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.94.2" }
|
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.94.2" }
|
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
heck = { workspace = true }
|
heck = { workspace = true }
|
||||||
|
@ -32,11 +32,7 @@ serde_urlencoded = { workspace = true }
|
||||||
v_htmlescape = { workspace = true }
|
v_htmlescape = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
|
|
||||||
[features]
|
|
||||||
extra = ["default"]
|
|
||||||
default = []
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.2" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
|
||||||
nu-command = { path = "../nu-command", version = "0.94.2" }
|
nu-command = { path = "../nu-command", version = "0.95.1" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.94.2" }
|
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
|
||||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
@ -118,15 +120,41 @@ fn into_bits(
|
||||||
let cell_paths = call.rest(engine_state, stack, 0)?;
|
let cell_paths = call.rest(engine_state, stack, 0)?;
|
||||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
|
||||||
if let PipelineData::ByteStream(stream, ..) = input {
|
if let PipelineData::ByteStream(stream, metadata) = input {
|
||||||
// TODO: in the future, we may want this to stream out, converting each to bytes
|
Ok(PipelineData::ByteStream(
|
||||||
Ok(Value::binary(stream.into_bytes()?, head).into_pipeline_data())
|
byte_stream_to_bits(stream, head),
|
||||||
|
metadata,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
let args = Arguments { cell_paths };
|
let args = Arguments { cell_paths };
|
||||||
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn byte_stream_to_bits(stream: ByteStream, head: Span) -> ByteStream {
|
||||||
|
if let Some(mut reader) = stream.reader() {
|
||||||
|
let mut is_first = true;
|
||||||
|
ByteStream::from_fn(head, None, ByteStreamType::String, move |buffer| {
|
||||||
|
let mut byte = [0];
|
||||||
|
if reader.read(&mut byte[..]).err_span(head)? > 0 {
|
||||||
|
// Format the byte as bits
|
||||||
|
if is_first {
|
||||||
|
is_first = false;
|
||||||
|
} else {
|
||||||
|
buffer.push(b' ');
|
||||||
|
}
|
||||||
|
write!(buffer, "{:08b}", byte[0]).expect("format failed");
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
// EOF
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ByteStream::read(io::empty(), head, None, ByteStreamType::String)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn convert_to_smallest_number_type(num: i64, span: Span) -> Value {
|
fn convert_to_smallest_number_type(num: i64, span: Span) -> Value {
|
||||||
if let Some(v) = num.to_i8() {
|
if let Some(v) = num.to_i8() {
|
||||||
let bytes = v.to_ne_bytes();
|
let bytes = v.to_ne_bytes();
|
||||||
|
|
|
@ -368,6 +368,7 @@ fn theme_demo(span: Span) -> PipelineData {
|
||||||
.collect();
|
.collect();
|
||||||
Value::list(result, span).into_pipeline_data_with_metadata(PipelineMetadata {
|
Value::list(result, span).into_pipeline_data_with_metadata(PipelineMetadata {
|
||||||
data_source: DataSource::HtmlThemes,
|
data_source: DataSource::HtmlThemes,
|
||||||
|
content_type: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use nu_engine::{command_prelude::*, get_eval_expression};
|
use nu_engine::command_prelude::*;
|
||||||
use nu_parser::parse_expression;
|
|
||||||
use nu_protocol::{ast::PathMember, engine::StateWorkingSet, ListStream};
|
use nu_protocol::{ast::PathMember, engine::StateWorkingSet, ListStream};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -57,14 +56,7 @@ impl Command for FormatPattern {
|
||||||
string_span.start + 1,
|
string_span.start + 1,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
format(
|
format(input_val, &ops, engine_state, call.head)
|
||||||
input_val,
|
|
||||||
&ops,
|
|
||||||
engine_state,
|
|
||||||
&mut working_set,
|
|
||||||
stack,
|
|
||||||
call.head,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,8 +92,6 @@ enum FormatOperation {
|
||||||
FixedText(String),
|
FixedText(String),
|
||||||
// raw input is something like {column1.column2}
|
// raw input is something like {column1.column2}
|
||||||
ValueFromColumn(String, Span),
|
ValueFromColumn(String, Span),
|
||||||
// raw input is something like {$it.column1.column2} or {$var}.
|
|
||||||
ValueNeedEval(String, Span),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given a pattern that is fed into the Format command, we can process it and subdivide it
|
/// Given a pattern that is fed into the Format command, we can process it and subdivide it
|
||||||
|
@ -110,7 +100,6 @@ enum FormatOperation {
|
||||||
/// there without any further processing.
|
/// there without any further processing.
|
||||||
/// FormatOperation::ValueFromColumn contains the name of a column whose values will be
|
/// FormatOperation::ValueFromColumn contains the name of a column whose values will be
|
||||||
/// formatted according to the input pattern.
|
/// formatted according to the input pattern.
|
||||||
/// FormatOperation::ValueNeedEval contains expression which need to eval, it has the following form:
|
|
||||||
/// "$it.column1.column2" or "$variable"
|
/// "$it.column1.column2" or "$variable"
|
||||||
fn extract_formatting_operations(
|
fn extract_formatting_operations(
|
||||||
input: String,
|
input: String,
|
||||||
|
@ -161,10 +150,17 @@ fn extract_formatting_operations(
|
||||||
|
|
||||||
if !column_name.is_empty() {
|
if !column_name.is_empty() {
|
||||||
if column_need_eval {
|
if column_need_eval {
|
||||||
output.push(FormatOperation::ValueNeedEval(
|
return Err(ShellError::GenericError {
|
||||||
column_name.clone(),
|
error: "Removed functionality".into(),
|
||||||
Span::new(span_start + column_span_start, span_start + column_span_end),
|
msg: "The ability to use variables ($it) in `format pattern` has been removed."
|
||||||
));
|
.into(),
|
||||||
|
span: Some(error_span),
|
||||||
|
help: Some(
|
||||||
|
"You can use other formatting options, such as string interpolation."
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
inner: vec![],
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
output.push(FormatOperation::ValueFromColumn(
|
output.push(FormatOperation::ValueFromColumn(
|
||||||
column_name.clone(),
|
column_name.clone(),
|
||||||
|
@ -185,8 +181,6 @@ fn format(
|
||||||
input_data: Value,
|
input_data: Value,
|
||||||
format_operations: &[FormatOperation],
|
format_operations: &[FormatOperation],
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
working_set: &mut StateWorkingSet,
|
|
||||||
stack: &mut Stack,
|
|
||||||
head_span: Span,
|
head_span: Span,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let data_as_value = input_data;
|
let data_as_value = input_data;
|
||||||
|
@ -194,13 +188,7 @@ fn format(
|
||||||
// We can only handle a Record or a List of Records
|
// We can only handle a Record or a List of Records
|
||||||
match data_as_value {
|
match data_as_value {
|
||||||
Value::Record { .. } => {
|
Value::Record { .. } => {
|
||||||
match format_record(
|
match format_record(format_operations, &data_as_value, engine_state) {
|
||||||
format_operations,
|
|
||||||
&data_as_value,
|
|
||||||
engine_state,
|
|
||||||
working_set,
|
|
||||||
stack,
|
|
||||||
) {
|
|
||||||
Ok(value) => Ok(PipelineData::Value(Value::string(value, head_span), None)),
|
Ok(value) => Ok(PipelineData::Value(Value::string(value, head_span), None)),
|
||||||
Err(value) => Err(value),
|
Err(value) => Err(value),
|
||||||
}
|
}
|
||||||
|
@ -211,13 +199,7 @@ fn format(
|
||||||
for val in vals.iter() {
|
for val in vals.iter() {
|
||||||
match val {
|
match val {
|
||||||
Value::Record { .. } => {
|
Value::Record { .. } => {
|
||||||
match format_record(
|
match format_record(format_operations, val, engine_state) {
|
||||||
format_operations,
|
|
||||||
val,
|
|
||||||
engine_state,
|
|
||||||
working_set,
|
|
||||||
stack,
|
|
||||||
) {
|
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
list.push(Value::string(value, head_span));
|
list.push(Value::string(value, head_span));
|
||||||
}
|
}
|
||||||
|
@ -256,12 +238,9 @@ fn format_record(
|
||||||
format_operations: &[FormatOperation],
|
format_operations: &[FormatOperation],
|
||||||
data_as_value: &Value,
|
data_as_value: &Value,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
working_set: &mut StateWorkingSet,
|
|
||||||
stack: &mut Stack,
|
|
||||||
) -> Result<String, ShellError> {
|
) -> Result<String, ShellError> {
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
let eval_expression = get_eval_expression(engine_state);
|
|
||||||
|
|
||||||
for op in format_operations {
|
for op in format_operations {
|
||||||
match op {
|
match op {
|
||||||
|
@ -283,23 +262,6 @@ fn format_record(
|
||||||
Err(se) => return Err(se),
|
Err(se) => return Err(se),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FormatOperation::ValueNeedEval(_col_name, span) => {
|
|
||||||
let exp = parse_expression(working_set, &[*span]);
|
|
||||||
match working_set.parse_errors.first() {
|
|
||||||
None => {
|
|
||||||
let parsed_result = eval_expression(engine_state, stack, &exp);
|
|
||||||
if let Ok(val) = parsed_result {
|
|
||||||
output.push_str(&val.to_abbreviated_string(config))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(err) => {
|
|
||||||
return Err(ShellError::TypeMismatch {
|
|
||||||
err_message: format!("expression is invalid, detail message: {err:?}"),
|
|
||||||
span: *span,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(output)
|
Ok(output)
|
||||||
|
|
13
crates/nu-cmd-extra/tests/commands/bits/into.rs
Normal file
13
crates/nu-cmd-extra/tests/commands/bits/into.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
use nu_test_support::nu;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn byte_stream_into_bits() {
|
||||||
|
let result = nu!("[0x[01] 0x[02 03]] | bytes collect | into bits");
|
||||||
|
assert_eq!("00000001 00000010 00000011", result.out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn byte_stream_into_bits_is_stream() {
|
||||||
|
let result = nu!("[0x[01] 0x[02 03]] | bytes collect | into bits | describe");
|
||||||
|
assert_eq!("string (stream)", result.out);
|
||||||
|
}
|
1
crates/nu-cmd-extra/tests/commands/bits/mod.rs
Normal file
1
crates/nu-cmd-extra/tests/commands/bits/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
mod into;
|
|
@ -1 +1,2 @@
|
||||||
|
mod bits;
|
||||||
mod bytes;
|
mod bytes;
|
||||||
|
|
|
@ -6,26 +6,25 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-lang"
|
name = "nu-cmd-lang"
|
||||||
version = "0.94.2"
|
version = "0.95.1"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.94.2" }
|
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.94.2" }
|
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.94.2" }
|
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.94.2" }
|
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||||
|
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
shadow-rs = { version = "0.28", default-features = false }
|
shadow-rs = { version = "0.29", default-features = false }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = { version = "0.28", default-features = false }
|
shadow-rs = { version = "0.29", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
mimalloc = []
|
mimalloc = []
|
||||||
which-support = []
|
|
||||||
trash-support = []
|
trash-support = []
|
||||||
sqlite = []
|
sqlite = []
|
||||||
static-link-openssl = []
|
static-link-openssl = []
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::engine::CommandType;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Break;
|
pub struct Break;
|
||||||
|
@ -18,6 +19,15 @@ impl Command for Break {
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"This command is a parser keyword. For details, check:
|
||||||
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_type(&self) -> CommandType {
|
||||||
|
CommandType::Keyword
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
_engine_state: &EngineState,
|
_engine_state: &EngineState,
|
||||||
|
|
|
@ -50,6 +50,7 @@ is particularly large, this can cause high memory usage."#
|
||||||
// check where some input came from.
|
// check where some input came from.
|
||||||
Some(PipelineMetadata {
|
Some(PipelineMetadata {
|
||||||
data_source: DataSource::FilePath(_),
|
data_source: DataSource::FilePath(_),
|
||||||
|
content_type: None,
|
||||||
}) => None,
|
}) => None,
|
||||||
other => other,
|
other => other,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::engine::CommandType;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Continue;
|
pub struct Continue;
|
||||||
|
@ -18,6 +19,14 @@ impl Command for Continue {
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"This command is a parser keyword. For details, check:
|
||||||
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_type(&self) -> CommandType {
|
||||||
|
CommandType::Keyword
|
||||||
|
}
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
_engine_state: &EngineState,
|
_engine_state: &EngineState,
|
||||||
|
|
|
@ -60,10 +60,15 @@ impl Command for Def {
|
||||||
example: r#"def --env foo [] { $env.BAR = "BAZ" }; foo; $env.BAR"#,
|
example: r#"def --env foo [] { $env.BAR = "BAZ" }; foo; $env.BAR"#,
|
||||||
result: Some(Value::test_string("BAZ")),
|
result: Some(Value::test_string("BAZ")),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "cd affects the environment, so '--env' is required to change directory from within a command",
|
||||||
|
example: r#"def --env gohome [] { cd ~ }; gohome; $env.PWD == ('~' | path expand)"#,
|
||||||
|
result: Some(Value::test_string("true")),
|
||||||
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Define a custom wrapper for an external command",
|
description: "Define a custom wrapper for an external command",
|
||||||
example: r#"def --wrapped my-echo [...rest] { echo $rest }; my-echo spam"#,
|
example: r#"def --wrapped my-echo [...rest] { ^echo ...$rest }; my-echo -e 'spam\tspam'"#,
|
||||||
result: Some(Value::test_list(vec![Value::test_string("spam")])),
|
result: Some(Value::test_string("spam\tspam")),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,7 @@ impl Command for Do {
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("do")
|
Signature::build("do")
|
||||||
.required(
|
.required("closure", SyntaxShape::Closure(None), "The closure to run.")
|
||||||
"closure",
|
|
||||||
SyntaxShape::OneOf(vec![SyntaxShape::Closure(None), SyntaxShape::Any]),
|
|
||||||
"The closure to run.",
|
|
||||||
)
|
|
||||||
.input_output_types(vec![(Type::Any, Type::Any)])
|
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||||
.switch(
|
.switch(
|
||||||
"ignore-errors",
|
"ignore-errors",
|
||||||
|
@ -229,14 +225,24 @@ impl Command for Do {
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Run the closure, with a positional parameter",
|
description: "Run the closure with a positional, type-checked parameter",
|
||||||
example: r#"do {|x| 100 + $x } 77"#,
|
example: r#"do {|x:int| 100 + $x } 77"#,
|
||||||
result: Some(Value::test_int(177)),
|
result: Some(Value::test_int(177)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Run the closure, with input",
|
description: "Run the closure with pipeline input",
|
||||||
example: r#"77 | do {|x| 100 + $in }"#,
|
example: r#"77 | do { 100 + $in }"#,
|
||||||
result: None, // TODO: returns 177
|
result: Some(Value::test_int(177)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Run the closure with a default parameter value",
|
||||||
|
example: r#"77 | do {|x=100| $x + $in }"#,
|
||||||
|
result: Some(Value::test_int(177)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Run the closure with two positional parameters",
|
||||||
|
example: r#"do {|x,y| $x + $y } 77 100"#,
|
||||||
|
result: Some(Value::test_int(177)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Run the closure and keep changes to the environment",
|
description: "Run the closure and keep changes to the environment",
|
||||||
|
|
|
@ -56,16 +56,7 @@ impl Command for ErrorMake {
|
||||||
Example {
|
Example {
|
||||||
description: "Create a simple custom error",
|
description: "Create a simple custom error",
|
||||||
example: r#"error make {msg: "my custom error message"}"#,
|
example: r#"error make {msg: "my custom error message"}"#,
|
||||||
result: Some(Value::error(
|
result: None,
|
||||||
ShellError::GenericError {
|
|
||||||
error: "my custom error message".into(),
|
|
||||||
msg: "".into(),
|
|
||||||
span: None,
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
},
|
|
||||||
Span::unknown(),
|
|
||||||
)),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Create a more complex custom error",
|
description: "Create a more complex custom error",
|
||||||
|
@ -82,16 +73,7 @@ impl Command for ErrorMake {
|
||||||
}
|
}
|
||||||
help: "A help string, suggesting a fix to the user" # optional
|
help: "A help string, suggesting a fix to the user" # optional
|
||||||
}"#,
|
}"#,
|
||||||
result: Some(Value::error(
|
result: None,
|
||||||
ShellError::GenericError {
|
|
||||||
error: "my custom error message".into(),
|
|
||||||
msg: "my custom label text".into(),
|
|
||||||
span: Some(Span::new(123, 456)),
|
|
||||||
help: Some("A help string, suggesting a fix to the user".into()),
|
|
||||||
inner: vec![],
|
|
||||||
},
|
|
||||||
Span::unknown(),
|
|
||||||
)),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -28,11 +28,6 @@ impl Command for For {
|
||||||
"Range of the loop.",
|
"Range of the loop.",
|
||||||
)
|
)
|
||||||
.required("block", SyntaxShape::Block, "The block to run.")
|
.required("block", SyntaxShape::Block, "The block to run.")
|
||||||
.switch(
|
|
||||||
"numbered",
|
|
||||||
"return a numbered item ($it.index and $it.item)",
|
|
||||||
Some('n'),
|
|
||||||
)
|
|
||||||
.creates_scope()
|
.creates_scope()
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
@ -77,8 +72,6 @@ impl Command for For {
|
||||||
|
|
||||||
let value = eval_expression(engine_state, stack, keyword_expr)?;
|
let value = eval_expression(engine_state, stack, keyword_expr)?;
|
||||||
|
|
||||||
let numbered = call.has_flag(engine_state, stack, "numbered")?;
|
|
||||||
|
|
||||||
let ctrlc = engine_state.ctrlc.clone();
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
let engine_state = engine_state.clone();
|
let engine_state = engine_state.clone();
|
||||||
let block = engine_state.get_block(block_id);
|
let block = engine_state.get_block(block_id);
|
||||||
|
@ -88,7 +81,7 @@ impl Command for For {
|
||||||
let span = value.span();
|
let span = value.span();
|
||||||
match value {
|
match value {
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
for (idx, x) in vals.into_iter().enumerate() {
|
for x in vals.into_iter() {
|
||||||
if nu_utils::ctrl_c::was_pressed(&ctrlc) {
|
if nu_utils::ctrl_c::was_pressed(&ctrlc) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -97,20 +90,7 @@ impl Command for For {
|
||||||
// a different set of environment variables.
|
// a different set of environment variables.
|
||||||
// Hence, a 'cd' in the first loop won't affect the next loop.
|
// Hence, a 'cd' in the first loop won't affect the next loop.
|
||||||
|
|
||||||
stack.add_var(
|
stack.add_var(var_id, x);
|
||||||
var_id,
|
|
||||||
if numbered {
|
|
||||||
Value::record(
|
|
||||||
record! {
|
|
||||||
"index" => Value::int(idx as i64, head),
|
|
||||||
"item" => x,
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
x
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
match eval_block(&engine_state, stack, block, PipelineData::empty()) {
|
match eval_block(&engine_state, stack, block, PipelineData::empty()) {
|
||||||
Err(ShellError::Break { .. }) => {
|
Err(ShellError::Break { .. }) => {
|
||||||
|
@ -136,21 +116,8 @@ impl Command for For {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Range { val, .. } => {
|
Value::Range { val, .. } => {
|
||||||
for (idx, x) in val.into_range_iter(span, ctrlc).enumerate() {
|
for x in val.into_range_iter(span, ctrlc) {
|
||||||
stack.add_var(
|
stack.add_var(var_id, x);
|
||||||
var_id,
|
|
||||||
if numbered {
|
|
||||||
Value::record(
|
|
||||||
record! {
|
|
||||||
"index" => Value::int(idx as i64, head),
|
|
||||||
"item" => x,
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
x
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
match eval_block(&engine_state, stack, block, PipelineData::empty()) {
|
match eval_block(&engine_state, stack, block, PipelineData::empty()) {
|
||||||
Err(ShellError::Break { .. }) => {
|
Err(ShellError::Break { .. }) => {
|
||||||
|
@ -198,8 +165,7 @@ impl Command for For {
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Number each item and print a message",
|
description: "Number each item and print a message",
|
||||||
example:
|
example: r#"for $it in (['bob' 'fred'] | enumerate) { print $"($it.index) is ($it.item)" }"#,
|
||||||
"for $it in ['bob' 'fred'] --numbered { print $\"($it.index) is ($it.item)\" }",
|
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,7 +2,7 @@ use nu_engine::{
|
||||||
command_prelude::*, get_eval_block, get_eval_expression, get_eval_expression_with_input,
|
command_prelude::*, get_eval_block, get_eval_expression, get_eval_expression_with_input,
|
||||||
};
|
};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::StateWorkingSet,
|
engine::{CommandType, StateWorkingSet},
|
||||||
eval_const::{eval_const_subexpression, eval_constant, eval_constant_with_input},
|
eval_const::{eval_const_subexpression, eval_constant, eval_constant_with_input},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -41,6 +41,15 @@ impl Command for If {
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"This command is a parser keyword. For details, check:
|
||||||
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_type(&self) -> CommandType {
|
||||||
|
CommandType::Keyword
|
||||||
|
}
|
||||||
|
|
||||||
fn is_const(&self) -> bool {
|
fn is_const(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -122,6 +131,10 @@ impl Command for If {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["else", "conditional"]
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use nu_engine::{command_prelude::*, get_eval_block};
|
use nu_engine::{command_prelude::*, get_eval_block};
|
||||||
|
use nu_protocol::engine::CommandType;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Loop;
|
pub struct Loop;
|
||||||
|
@ -20,6 +21,15 @@ impl Command for Loop {
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"This command is a parser keyword. For details, check:
|
||||||
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_type(&self) -> CommandType {
|
||||||
|
CommandType::Keyword
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use nu_engine::{
|
use nu_engine::{
|
||||||
command_prelude::*, get_eval_block, get_eval_expression, get_eval_expression_with_input,
|
command_prelude::*, get_eval_block, get_eval_expression, get_eval_expression_with_input,
|
||||||
};
|
};
|
||||||
use nu_protocol::engine::Matcher;
|
use nu_protocol::engine::{CommandType, Matcher};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Match;
|
pub struct Match;
|
||||||
|
@ -27,6 +27,15 @@ impl Command for Match {
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"This command is a parser keyword. For details, check:
|
||||||
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_type(&self) -> CommandType {
|
||||||
|
CommandType::Keyword
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use nu_engine::{command_prelude::*, get_full_help};
|
use nu_engine::{command_prelude::*, get_full_help};
|
||||||
use nu_protocol::engine::CommandType;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Scope;
|
pub struct Scope;
|
||||||
|
@ -20,10 +19,6 @@ impl Command for Scope {
|
||||||
"Commands for getting info about what is in scope."
|
"Commands for getting info about what is in scope."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn command_type(&self) -> CommandType {
|
|
||||||
CommandType::Keyword
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use nu_engine::{command_prelude::*, get_eval_block, EvalBlockFn};
|
use nu_engine::{command_prelude::*, get_eval_block, EvalBlockFn};
|
||||||
use nu_protocol::engine::Closure;
|
use nu_protocol::engine::{Closure, CommandType};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Try;
|
pub struct Try;
|
||||||
|
@ -10,7 +10,7 @@ impl Command for Try {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Try to run a block, if it fails optionally run a catch block."
|
"Try to run a block, if it fails optionally run a catch closure."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
@ -18,7 +18,7 @@ impl Command for Try {
|
||||||
.input_output_types(vec![(Type::Any, Type::Any)])
|
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||||
.required("try_block", SyntaxShape::Block, "Block to run.")
|
.required("try_block", SyntaxShape::Block, "Block to run.")
|
||||||
.optional(
|
.optional(
|
||||||
"catch_block",
|
"catch_closure",
|
||||||
SyntaxShape::Keyword(
|
SyntaxShape::Keyword(
|
||||||
b"catch".to_vec(),
|
b"catch".to_vec(),
|
||||||
Box::new(SyntaxShape::OneOf(vec![
|
Box::new(SyntaxShape::OneOf(vec![
|
||||||
|
@ -26,11 +26,20 @@ impl Command for Try {
|
||||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||||
])),
|
])),
|
||||||
),
|
),
|
||||||
"Block to run if try block fails.",
|
"Closure to run if try block fails.",
|
||||||
)
|
)
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"This command is a parser keyword. For details, check:
|
||||||
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_type(&self) -> CommandType {
|
||||||
|
CommandType::Keyword
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
|
@ -85,9 +94,14 @@ impl Command for Try {
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Try to run a missing command",
|
description: "Try to run a missing command",
|
||||||
example: "try { asdfasdf } catch { 'missing' } ",
|
example: "try { asdfasdf } catch { 'missing' }",
|
||||||
result: Some(Value::test_string("missing")),
|
result: Some(Value::test_string("missing")),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Try to run a missing command and report the message",
|
||||||
|
example: "try { asdfasdf } catch { |err| $err.msg }",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,11 +116,18 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, S
|
||||||
Value::string(features_enabled().join(", "), span),
|
Value::string(features_enabled().join(", "), span),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get a list of plugin names
|
// Get a list of plugin names and versions if present
|
||||||
let installed_plugins = engine_state
|
let installed_plugins = engine_state
|
||||||
.plugins()
|
.plugins()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.identity().name())
|
.map(|x| {
|
||||||
|
let name = x.identity().name();
|
||||||
|
if let Some(version) = x.metadata().and_then(|m| m.version) {
|
||||||
|
format!("{name} {version}")
|
||||||
|
} else {
|
||||||
|
name.into()
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
record.push(
|
record.push(
|
||||||
|
@ -160,11 +167,6 @@ fn features_enabled() -> Vec<String> {
|
||||||
|
|
||||||
// NOTE: There should be another way to know features on.
|
// NOTE: There should be another way to know features on.
|
||||||
|
|
||||||
#[cfg(feature = "which-support")]
|
|
||||||
{
|
|
||||||
names.push("which".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "trash-support")]
|
#[cfg(feature = "trash-support")]
|
||||||
{
|
{
|
||||||
names.push("trash".to_string());
|
names.push("trash".to_string());
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression};
|
use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression};
|
||||||
|
use nu_protocol::engine::CommandType;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct While;
|
pub struct While;
|
||||||
|
@ -29,6 +30,15 @@ impl Command for While {
|
||||||
vec!["loop"]
|
vec!["loop"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"This command is a parser keyword. For details, check:
|
||||||
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_type(&self) -> CommandType {
|
||||||
|
CommandType::Keyword
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
|
|
|
@ -5,15 +5,15 @@ edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-plugin"
|
name = "nu-cmd-plugin"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin"
|
||||||
version = "0.94.2"
|
version = "0.95.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
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.94.2" }
|
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||||
nu-path = { path = "../nu-path", version = "0.94.2" }
|
nu-path = { path = "../nu-path", version = "0.95.1" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.94.2", features = ["plugin"] }
|
nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] }
|
||||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.2" }
|
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1" }
|
||||||
|
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
mod plugin;
|
mod plugin;
|
||||||
mod register;
|
|
||||||
|
|
||||||
pub use plugin::*;
|
pub use plugin::*;
|
||||||
pub use register::Register;
|
|
||||||
|
|
|
@ -118,11 +118,12 @@ apparent the next time `nu` is next launched with that plugin registry file.
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
let interface = plugin.clone().get_plugin(Some((engine_state, stack)))?;
|
let interface = plugin.clone().get_plugin(Some((engine_state, stack)))?;
|
||||||
|
let metadata = interface.get_metadata()?;
|
||||||
let commands = interface.get_signature()?;
|
let commands = interface.get_signature()?;
|
||||||
|
|
||||||
modify_plugin_file(engine_state, stack, call.head, custom_path, |contents| {
|
modify_plugin_file(engine_state, stack, call.head, custom_path, |contents| {
|
||||||
// Update the file with the received signatures
|
// Update the file with the received metadata and signatures
|
||||||
let item = PluginRegistryItem::new(plugin.identity(), commands);
|
let item = PluginRegistryItem::new(plugin.identity(), metadata, commands);
|
||||||
contents.upsert_plugin(item);
|
contents.upsert_plugin(item);
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
|
@ -16,6 +16,7 @@ impl Command for PluginList {
|
||||||
Type::Table(
|
Type::Table(
|
||||||
[
|
[
|
||||||
("name".into(), Type::String),
|
("name".into(), Type::String),
|
||||||
|
("version".into(), Type::String),
|
||||||
("is_running".into(), Type::Bool),
|
("is_running".into(), Type::Bool),
|
||||||
("pid".into(), Type::Int),
|
("pid".into(), Type::Int),
|
||||||
("filename".into(), Type::String),
|
("filename".into(), Type::String),
|
||||||
|
@ -43,6 +44,7 @@ impl Command for PluginList {
|
||||||
description: "List installed plugins.",
|
description: "List installed plugins.",
|
||||||
result: Some(Value::test_list(vec![Value::test_record(record! {
|
result: Some(Value::test_list(vec![Value::test_record(record! {
|
||||||
"name" => Value::test_string("inc"),
|
"name" => Value::test_string("inc"),
|
||||||
|
"version" => Value::test_string(env!("CARGO_PKG_VERSION")),
|
||||||
"is_running" => Value::test_bool(true),
|
"is_running" => Value::test_bool(true),
|
||||||
"pid" => Value::test_int(106480),
|
"pid" => Value::test_int(106480),
|
||||||
"filename" => if cfg!(windows) {
|
"filename" => if cfg!(windows) {
|
||||||
|
@ -98,8 +100,15 @@ impl Command for PluginList {
|
||||||
.map(|s| Value::string(s.to_string_lossy(), head))
|
.map(|s| Value::string(s.to_string_lossy(), head))
|
||||||
.unwrap_or(Value::nothing(head));
|
.unwrap_or(Value::nothing(head));
|
||||||
|
|
||||||
|
let metadata = plugin.metadata();
|
||||||
|
let version = metadata
|
||||||
|
.and_then(|m| m.version)
|
||||||
|
.map(|s| Value::string(s, head))
|
||||||
|
.unwrap_or(Value::nothing(head));
|
||||||
|
|
||||||
let record = record! {
|
let record = record! {
|
||||||
"name" => Value::string(plugin.identity().name(), head),
|
"name" => Value::string(plugin.identity().name(), head),
|
||||||
|
"version" => version,
|
||||||
"is_running" => Value::bool(plugin.is_running(), head),
|
"is_running" => Value::bool(plugin.is_running(), head),
|
||||||
"pid" => pid,
|
"pid" => pid,
|
||||||
"filename" => Value::string(plugin.identity().filename().to_string_lossy(), head),
|
"filename" => Value::string(plugin.identity().filename().to_string_lossy(), head),
|
||||||
|
|
|
@ -72,7 +72,7 @@ impl Command for PluginStop {
|
||||||
error: format!("Failed to stop the `{}` plugin", name.item),
|
error: format!("Failed to stop the `{}` plugin", name.item),
|
||||||
msg: "couldn't find a plugin with this name".into(),
|
msg: "couldn't find a plugin with this name".into(),
|
||||||
span: Some(name.span),
|
span: Some(name.span),
|
||||||
help: Some("you may need to `register` the plugin first".into()),
|
help: Some("you may need to `plugin add` the plugin first".into()),
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
use nu_engine::command_prelude::*;
|
|
||||||
use nu_protocol::engine::CommandType;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Register;
|
|
||||||
|
|
||||||
impl Command for Register {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"register"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Register a plugin."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
|
||||||
Signature::build("register")
|
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
|
||||||
.required(
|
|
||||||
"plugin",
|
|
||||||
SyntaxShape::Filepath,
|
|
||||||
"Path of executable for plugin.",
|
|
||||||
)
|
|
||||||
.optional(
|
|
||||||
"signature",
|
|
||||||
SyntaxShape::Any,
|
|
||||||
"Block with signature description as json object.",
|
|
||||||
)
|
|
||||||
.named(
|
|
||||||
"shell",
|
|
||||||
SyntaxShape::Filepath,
|
|
||||||
"path of shell used to run plugin (cmd, sh, python, etc)",
|
|
||||||
Some('s'),
|
|
||||||
)
|
|
||||||
.category(Category::Plugin)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
|
||||||
r#"
|
|
||||||
Deprecated in favor of `plugin add` and `plugin use`.
|
|
||||||
|
|
||||||
This command is a parser keyword. For details, check:
|
|
||||||
https://www.nushell.sh/book/thinking_in_nu.html
|
|
||||||
"#
|
|
||||||
.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["add"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn command_type(&self) -> CommandType {
|
|
||||||
CommandType::Keyword
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
_engine_state: &EngineState,
|
|
||||||
_stack: &mut Stack,
|
|
||||||
_call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
Ok(PipelineData::empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
description: "Register `nu_plugin_query` plugin from ~/.cargo/bin/ dir",
|
|
||||||
example: r#"register ~/.cargo/bin/nu_plugin_query"#,
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Register `nu_plugin_query` plugin from `nu -c` (writes/updates $nu.plugin-path)",
|
|
||||||
example: r#"let plugin = ((which nu).path.0 | path dirname | path join 'nu_plugin_query'); nu -c $'register ($plugin); version'"#,
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,7 +18,6 @@ pub fn add_plugin_command_context(mut engine_state: EngineState) -> EngineState
|
||||||
PluginRm,
|
PluginRm,
|
||||||
PluginStop,
|
PluginStop,
|
||||||
PluginUse,
|
PluginUse,
|
||||||
Register,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
working_set.render()
|
working_set.render()
|
||||||
|
|
|
@ -31,11 +31,20 @@ pub(crate) fn modify_plugin_file(
|
||||||
})?
|
})?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let file_span = custom_path.as_ref().map(|p| p.span).unwrap_or(span);
|
||||||
|
|
||||||
// Try to read the plugin file if it exists
|
// Try to read the plugin file if it exists
|
||||||
let mut contents = if fs::metadata(&plugin_registry_file_path).is_ok_and(|m| m.len() > 0) {
|
let mut contents = if fs::metadata(&plugin_registry_file_path).is_ok_and(|m| m.len() > 0) {
|
||||||
PluginRegistryFile::read_from(
|
PluginRegistryFile::read_from(
|
||||||
File::open(&plugin_registry_file_path).err_span(span)?,
|
File::open(&plugin_registry_file_path).map_err(|err| ShellError::IOErrorSpanned {
|
||||||
Some(span),
|
msg: format!(
|
||||||
|
"failed to read `{}`: {}",
|
||||||
|
plugin_registry_file_path.display(),
|
||||||
|
err
|
||||||
|
),
|
||||||
|
span: file_span,
|
||||||
|
})?,
|
||||||
|
Some(file_span),
|
||||||
)?
|
)?
|
||||||
} else {
|
} else {
|
||||||
PluginRegistryFile::default()
|
PluginRegistryFile::default()
|
||||||
|
@ -46,7 +55,14 @@ pub(crate) fn modify_plugin_file(
|
||||||
|
|
||||||
// Save the modified file on success
|
// Save the modified file on success
|
||||||
contents.write_to(
|
contents.write_to(
|
||||||
File::create(&plugin_registry_file_path).err_span(span)?,
|
File::create(&plugin_registry_file_path).map_err(|err| ShellError::IOErrorSpanned {
|
||||||
|
msg: format!(
|
||||||
|
"failed to create `{}`: {}",
|
||||||
|
plugin_registry_file_path.display(),
|
||||||
|
err
|
||||||
|
),
|
||||||
|
span: file_span,
|
||||||
|
})?,
|
||||||
Some(span),
|
Some(span),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
@ -5,18 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-color-config"
|
name = "nu-color-config"
|
||||||
version = "0.94.2"
|
version = "0.95.1"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.94.2" }
|
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.94.2" }
|
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||||
nu-json = { path = "../nu-json", version = "0.94.2" }
|
nu-json = { path = "../nu-json", version = "0.95.1" }
|
||||||
nu-ansi-term = { workspace = true }
|
nu-ansi-term = { workspace = true }
|
||||||
|
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.94.2" }
|
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
|
|
@ -20,6 +20,7 @@ pub fn default_shape_color(shape: &str) -> Style {
|
||||||
"shape_flag" => Style::new().fg(Color::Blue).bold(),
|
"shape_flag" => Style::new().fg(Color::Blue).bold(),
|
||||||
"shape_float" => Style::new().fg(Color::Purple).bold(),
|
"shape_float" => Style::new().fg(Color::Purple).bold(),
|
||||||
"shape_garbage" => Style::new().fg(Color::White).on(Color::Red).bold(),
|
"shape_garbage" => Style::new().fg(Color::White).on(Color::Red).bold(),
|
||||||
|
"shape_glob_interpolation" => Style::new().fg(Color::Cyan).bold(),
|
||||||
"shape_globpattern" => Style::new().fg(Color::Cyan).bold(),
|
"shape_globpattern" => Style::new().fg(Color::Cyan).bold(),
|
||||||
"shape_int" => Style::new().fg(Color::Purple).bold(),
|
"shape_int" => Style::new().fg(Color::Purple).bold(),
|
||||||
"shape_internalcall" => Style::new().fg(Color::Cyan).bold(),
|
"shape_internalcall" => Style::new().fg(Color::Cyan).bold(),
|
||||||
|
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-command"
|
name = "nu-command"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||||
version = "0.94.2"
|
version = "0.95.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
|
||||||
|
|
||||||
|
@ -13,21 +13,21 @@ version = "0.94.2"
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.2" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.94.2" }
|
nu-color-config = { path = "../nu-color-config", version = "0.95.1" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.94.2" }
|
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.94.2" }
|
nu-glob = { path = "../nu-glob", version = "0.95.1" }
|
||||||
nu-json = { path = "../nu-json", version = "0.94.2" }
|
nu-json = { path = "../nu-json", version = "0.95.1" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.94.2" }
|
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||||
nu-path = { path = "../nu-path", version = "0.94.2" }
|
nu-path = { path = "../nu-path", version = "0.95.1" }
|
||||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.94.2" }
|
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.1" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.94.2" }
|
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||||
nu-system = { path = "../nu-system", version = "0.94.2" }
|
nu-system = { path = "../nu-system", version = "0.95.1" }
|
||||||
nu-table = { path = "../nu-table", version = "0.94.2" }
|
nu-table = { path = "../nu-table", version = "0.95.1" }
|
||||||
nu-term-grid = { path = "../nu-term-grid", version = "0.94.2" }
|
nu-term-grid = { path = "../nu-term-grid", version = "0.95.1" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.94.2" }
|
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||||
nu-ansi-term = { workspace = true }
|
nu-ansi-term = { workspace = true }
|
||||||
nuon = { path = "../nuon", version = "0.94.2" }
|
nuon = { path = "../nuon", version = "0.95.1" }
|
||||||
|
|
||||||
alphanumeric-sort = { workspace = true }
|
alphanumeric-sort = { workspace = true }
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
|
@ -42,6 +42,7 @@ chrono-humanize = { workspace = true }
|
||||||
chrono-tz = { workspace = true }
|
chrono-tz = { workspace = true }
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
csv = { workspace = true }
|
csv = { workspace = true }
|
||||||
|
deunicode = { workspace = true }
|
||||||
dialoguer = { workspace = true, default-features = false, features = ["fuzzy-select"] }
|
dialoguer = { workspace = true, default-features = false, features = ["fuzzy-select"] }
|
||||||
digest = { workspace = true, default-features = false }
|
digest = { workspace = true, default-features = false }
|
||||||
dtparse = { workspace = true }
|
dtparse = { workspace = true }
|
||||||
|
@ -86,7 +87,7 @@ sysinfo = { workspace = true }
|
||||||
tabled = { workspace = true, features = ["color"], default-features = false }
|
tabled = { workspace = true, features = ["color"], default-features = false }
|
||||||
terminal_size = { workspace = true }
|
terminal_size = { workspace = true }
|
||||||
titlecase = { workspace = true }
|
titlecase = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true, features = ["preserve_order"]}
|
||||||
unicode-segmentation = { workspace = true }
|
unicode-segmentation = { workspace = true }
|
||||||
ureq = { workspace = true, default-features = false, features = ["charset", "gzip", "json", "native-tls"] }
|
ureq = { workspace = true, default-features = false, features = ["charset", "gzip", "json", "native-tls"] }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
|
@ -134,11 +135,10 @@ workspace = true
|
||||||
plugin = ["nu-parser/plugin"]
|
plugin = ["nu-parser/plugin"]
|
||||||
sqlite = ["rusqlite"]
|
sqlite = ["rusqlite"]
|
||||||
trash-support = ["trash"]
|
trash-support = ["trash"]
|
||||||
which-support = []
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.2" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.94.2" }
|
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
|
||||||
|
|
||||||
dirs-next = { workspace = true }
|
dirs-next = { workspace = true }
|
||||||
mockito = { workspace = true, default-features = false }
|
mockito = { workspace = true, default-features = false }
|
||||||
|
|
|
@ -128,48 +128,24 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||||
let range = &args.indexes;
|
let range = &args.indexes;
|
||||||
match input {
|
match input {
|
||||||
Value::Binary { val, .. } => {
|
Value::Binary { val, .. } => {
|
||||||
use std::cmp::{self, Ordering};
|
|
||||||
let len = val.len() as isize;
|
let len = val.len() as isize;
|
||||||
|
|
||||||
let start = if range.0 < 0 { range.0 + len } else { range.0 };
|
let start = if range.0 < 0 { range.0 + len } else { range.0 };
|
||||||
|
let end = if range.1 < 0 { range.1 + len } else { range.1 };
|
||||||
|
|
||||||
let end = if range.1 < 0 {
|
if start > end {
|
||||||
cmp::max(range.1 + len, 0)
|
|
||||||
} else {
|
|
||||||
range.1
|
|
||||||
};
|
|
||||||
|
|
||||||
if start < len && end >= 0 {
|
|
||||||
match start.cmp(&end) {
|
|
||||||
Ordering::Equal => Value::binary(vec![], head),
|
|
||||||
Ordering::Greater => Value::error(
|
|
||||||
ShellError::TypeMismatch {
|
|
||||||
err_message: "End must be greater than or equal to Start".to_string(),
|
|
||||||
span: head,
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
),
|
|
||||||
Ordering::Less => Value::binary(
|
|
||||||
if end == isize::MAX {
|
|
||||||
val.iter()
|
|
||||||
.skip(start as usize)
|
|
||||||
.copied()
|
|
||||||
.collect::<Vec<u8>>()
|
|
||||||
} else {
|
|
||||||
val.iter()
|
|
||||||
.skip(start as usize)
|
|
||||||
.take((end - start) as usize)
|
|
||||||
.copied()
|
|
||||||
.collect()
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Value::binary(vec![], head)
|
Value::binary(vec![], head)
|
||||||
|
} else {
|
||||||
|
let val_iter = val.iter().skip(start as usize);
|
||||||
|
Value::binary(
|
||||||
|
if end == isize::MAX {
|
||||||
|
val_iter.copied().collect::<Vec<u8>>()
|
||||||
|
} else {
|
||||||
|
val_iter.take((end - start + 1) as usize).copied().collect()
|
||||||
|
},
|
||||||
|
head,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Value::Error { .. } => input.clone(),
|
Value::Error { .. } => input.clone(),
|
||||||
|
|
||||||
other => Value::error(
|
other => Value::error(
|
||||||
|
|
|
@ -194,7 +194,7 @@ fn run_histogram(
|
||||||
if inputs.is_empty() {
|
if inputs.is_empty() {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: head_span,
|
span: Some(head_span),
|
||||||
src_span: list_span,
|
src_span: list_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,7 +154,7 @@ fn record_to_path_member(
|
||||||
let Some(value) = record.get("value") else {
|
let Some(value) = record.get("value") else {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: "value".into(),
|
col_name: "value".into(),
|
||||||
span: val_span,
|
span: Some(val_span),
|
||||||
src_span: span,
|
src_span: span,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{generate_strftime_list, parse_date_from_string};
|
use crate::{generate_strftime_list, parse_date_from_string};
|
||||||
use chrono::{DateTime, FixedOffset, Local, NaiveTime, TimeZone, Utc};
|
use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, NaiveTime, TimeZone, Utc};
|
||||||
use human_date_parser::{from_human_time, ParseResult};
|
use human_date_parser::{from_human_time, ParseResult};
|
||||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
@ -162,24 +162,37 @@ impl Command for SubCommand {
|
||||||
};
|
};
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Convert any standard timestamp string to datetime",
|
description: "Convert timestamp string to datetime with timezone offset",
|
||||||
example: "'27.02.2021 1:55 pm +0000' | into datetime",
|
example: "'27.02.2021 1:55 pm +0000' | into datetime",
|
||||||
#[allow(clippy::inconsistent_digit_grouping)]
|
#[allow(clippy::inconsistent_digit_grouping)]
|
||||||
result: example_result_1(1614434100_000000000),
|
result: example_result_1(1614434100_000000000),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert any standard timestamp string to datetime",
|
description: "Convert standard timestamp string to datetime with timezone offset",
|
||||||
example: "'2021-02-27T13:55:40.2246+00:00' | into datetime",
|
example: "'2021-02-27T13:55:40.2246+00:00' | into datetime",
|
||||||
#[allow(clippy::inconsistent_digit_grouping)]
|
#[allow(clippy::inconsistent_digit_grouping)]
|
||||||
result: example_result_1(1614434140_224600000),
|
result: example_result_1(1614434140_224600000),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description:
|
description:
|
||||||
"Convert non-standard timestamp string to datetime using a custom format",
|
"Convert non-standard timestamp string, with timezone offset, to datetime using a custom format",
|
||||||
example: "'20210227_135540+0000' | into datetime --format '%Y%m%d_%H%M%S%z'",
|
example: "'20210227_135540+0000' | into datetime --format '%Y%m%d_%H%M%S%z'",
|
||||||
#[allow(clippy::inconsistent_digit_grouping)]
|
#[allow(clippy::inconsistent_digit_grouping)]
|
||||||
result: example_result_1(1614434140_000000000),
|
result: example_result_1(1614434140_000000000),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert non-standard timestamp string, without timezone offset, to datetime with custom formatting",
|
||||||
|
example: "'16.11.1984 8:00 am' | into datetime --format '%d.%m.%Y %H:%M %P'",
|
||||||
|
#[allow(clippy::inconsistent_digit_grouping)]
|
||||||
|
result: Some(Value::date(
|
||||||
|
DateTime::from_naive_utc_and_offset(
|
||||||
|
NaiveDateTime::parse_from_str("16.11.1984 8:00 am", "%d.%m.%Y %H:%M %P")
|
||||||
|
.expect("date calculation should not fail in test"),
|
||||||
|
*Local::now().offset(),
|
||||||
|
),
|
||||||
|
Span::test_data(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
Example {
|
Example {
|
||||||
description:
|
description:
|
||||||
"Convert nanosecond-precision unix timestamp to a datetime with offset from UTC",
|
"Convert nanosecond-precision unix timestamp to a datetime with offset from UTC",
|
||||||
|
@ -372,10 +385,21 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||||
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
|
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
|
||||||
Ok(d) => Value::date ( d, head ),
|
Ok(d) => Value::date ( d, head ),
|
||||||
Err(reason) => {
|
Err(reason) => {
|
||||||
Value::error (
|
match NaiveDateTime::parse_from_str(val, &dt.0) {
|
||||||
ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) },
|
Ok(d) => Value::date (
|
||||||
head,
|
DateTime::from_naive_utc_and_offset(
|
||||||
)
|
d,
|
||||||
|
*Local::now().offset(),
|
||||||
|
),
|
||||||
|
head,
|
||||||
|
),
|
||||||
|
Err(_) => {
|
||||||
|
Value::error (
|
||||||
|
ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) },
|
||||||
|
head,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -457,7 +481,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn takes_a_date_format() {
|
fn takes_a_date_format_with_timezone() {
|
||||||
let date_str = Value::test_string("16.11.1984 8:00 am +0000");
|
let date_str = Value::test_string("16.11.1984 8:00 am +0000");
|
||||||
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
||||||
let args = Arguments {
|
let args = Arguments {
|
||||||
|
@ -473,6 +497,26 @@ mod tests {
|
||||||
assert_eq!(actual, expected)
|
assert_eq!(actual, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn takes_a_date_format_without_timezone() {
|
||||||
|
let date_str = Value::test_string("16.11.1984 8:00 am");
|
||||||
|
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P".to_string()));
|
||||||
|
let args = Arguments {
|
||||||
|
zone_options: None,
|
||||||
|
format_options: fmt_options,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
|
let expected = Value::date(
|
||||||
|
DateTime::from_naive_utc_and_offset(
|
||||||
|
NaiveDateTime::parse_from_str("16.11.1984 8:00 am", "%d.%m.%Y %H:%M %P").unwrap(),
|
||||||
|
*Local::now().offset(),
|
||||||
|
),
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
|
assert_eq!(actual, expected)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn takes_iso8601_date_format() {
|
fn takes_iso8601_date_format() {
|
||||||
let date_str = Value::test_string("2020-08-04T16:39:18+00:00");
|
let date_str = Value::test_string("2020-08-04T16:39:18+00:00");
|
||||||
|
|
|
@ -122,7 +122,7 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
Value::Filesize { .. } => input.clone(),
|
Value::Filesize { .. } => input.clone(),
|
||||||
Value::Int { val, .. } => Value::filesize(*val, value_span),
|
Value::Int { val, .. } => Value::filesize(*val, value_span),
|
||||||
Value::Float { val, .. } => Value::filesize(*val as i64, value_span),
|
Value::Float { val, .. } => Value::filesize(*val as i64, value_span),
|
||||||
Value::String { val, .. } => match int_from_string(val, value_span) {
|
Value::String { val, .. } => match i64_from_string(val, value_span) {
|
||||||
Ok(val) => Value::filesize(val, value_span),
|
Ok(val) => Value::filesize(val, value_span),
|
||||||
Err(error) => Value::error(error, value_span),
|
Err(error) => Value::error(error, value_span),
|
||||||
},
|
},
|
||||||
|
@ -138,7 +138,8 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
|
||||||
|
fn i64_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
||||||
// Get the Locale so we know what the thousands separator is
|
// Get the Locale so we know what the thousands separator is
|
||||||
let locale = get_system_locale();
|
let locale = get_system_locale();
|
||||||
|
|
||||||
|
@ -148,29 +149,46 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
||||||
let clean_string = no_comma_string.trim();
|
let clean_string = no_comma_string.trim();
|
||||||
|
|
||||||
// Hadle negative file size
|
// Hadle negative file size
|
||||||
if let Some(stripped_string) = clean_string.strip_prefix('-') {
|
if let Some(stripped_negative_string) = clean_string.strip_prefix('-') {
|
||||||
match stripped_string.parse::<bytesize::ByteSize>() {
|
match stripped_negative_string.parse::<bytesize::ByteSize>() {
|
||||||
Ok(n) => Ok(-(n.as_u64() as i64)),
|
Ok(n) => i64_from_byte_size(n, true, span),
|
||||||
Err(_) => Err(ShellError::CantConvert {
|
Err(_) => Err(string_convert_error(span)),
|
||||||
to_type: "int".into(),
|
}
|
||||||
from_type: "string".into(),
|
} else if let Some(stripped_positive_string) = clean_string.strip_prefix('+') {
|
||||||
span,
|
match stripped_positive_string.parse::<bytesize::ByteSize>() {
|
||||||
help: None,
|
Ok(n) if stripped_positive_string.starts_with(|c: char| c.is_ascii_digit()) => {
|
||||||
}),
|
i64_from_byte_size(n, false, span)
|
||||||
|
}
|
||||||
|
_ => Err(string_convert_error(span)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match clean_string.parse::<bytesize::ByteSize>() {
|
match clean_string.parse::<bytesize::ByteSize>() {
|
||||||
Ok(n) => Ok(n.0 as i64),
|
Ok(n) => i64_from_byte_size(n, false, span),
|
||||||
Err(_) => Err(ShellError::CantConvert {
|
Err(_) => Err(string_convert_error(span)),
|
||||||
to_type: "int".into(),
|
|
||||||
from_type: "string".into(),
|
|
||||||
span,
|
|
||||||
help: None,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn i64_from_byte_size(
|
||||||
|
byte_size: bytesize::ByteSize,
|
||||||
|
is_negative: bool,
|
||||||
|
span: Span,
|
||||||
|
) -> Result<i64, ShellError> {
|
||||||
|
match i64::try_from(byte_size.as_u64()) {
|
||||||
|
Ok(n) => Ok(if is_negative { -n } else { n }),
|
||||||
|
Err(_) => Err(string_convert_error(span)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_convert_error(span: Span) -> ShellError {
|
||||||
|
ShellError::CantConvert {
|
||||||
|
to_type: "filesize".into(),
|
||||||
|
from_type: "string".into(),
|
||||||
|
span,
|
||||||
|
help: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -80,16 +80,23 @@ impl Command for Metadata {
|
||||||
match x {
|
match x {
|
||||||
PipelineMetadata {
|
PipelineMetadata {
|
||||||
data_source: DataSource::Ls,
|
data_source: DataSource::Ls,
|
||||||
|
..
|
||||||
} => record.push("source", Value::string("ls", head)),
|
} => record.push("source", Value::string("ls", head)),
|
||||||
PipelineMetadata {
|
PipelineMetadata {
|
||||||
data_source: DataSource::HtmlThemes,
|
data_source: DataSource::HtmlThemes,
|
||||||
|
..
|
||||||
} => record.push("source", Value::string("into html --list", head)),
|
} => record.push("source", Value::string("into html --list", head)),
|
||||||
PipelineMetadata {
|
PipelineMetadata {
|
||||||
data_source: DataSource::FilePath(path),
|
data_source: DataSource::FilePath(path),
|
||||||
|
..
|
||||||
} => record.push(
|
} => record.push(
|
||||||
"source",
|
"source",
|
||||||
Value::string(path.to_string_lossy().to_string(), head),
|
Value::string(path.to_string_lossy().to_string(), head),
|
||||||
),
|
),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
if let Some(ref content_type) = x.content_type {
|
||||||
|
record.push("content_type", Value::string(content_type, head));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,16 +140,23 @@ fn build_metadata_record(arg: &Value, metadata: Option<&PipelineMetadata>, head:
|
||||||
match x {
|
match x {
|
||||||
PipelineMetadata {
|
PipelineMetadata {
|
||||||
data_source: DataSource::Ls,
|
data_source: DataSource::Ls,
|
||||||
|
..
|
||||||
} => record.push("source", Value::string("ls", head)),
|
} => record.push("source", Value::string("ls", head)),
|
||||||
PipelineMetadata {
|
PipelineMetadata {
|
||||||
data_source: DataSource::HtmlThemes,
|
data_source: DataSource::HtmlThemes,
|
||||||
|
..
|
||||||
} => record.push("source", Value::string("into html --list", head)),
|
} => record.push("source", Value::string("into html --list", head)),
|
||||||
PipelineMetadata {
|
PipelineMetadata {
|
||||||
data_source: DataSource::FilePath(path),
|
data_source: DataSource::FilePath(path),
|
||||||
|
..
|
||||||
} => record.push(
|
} => record.push(
|
||||||
"source",
|
"source",
|
||||||
Value::string(path.to_string_lossy().to_string(), head),
|
Value::string(path.to_string_lossy().to_string(), head),
|
||||||
),
|
),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
if let Some(ref content_type) = x.content_type {
|
||||||
|
record.push("content_type", Value::string(content_type, head));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{DataSource, PipelineMetadata};
|
use nu_protocol::DataSource;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MetadataSet;
|
pub struct MetadataSet;
|
||||||
|
@ -27,6 +27,12 @@ impl Command for MetadataSet {
|
||||||
"Assign the DataSource::FilePath metadata to the input",
|
"Assign the DataSource::FilePath metadata to the input",
|
||||||
Some('f'),
|
Some('f'),
|
||||||
)
|
)
|
||||||
|
.named(
|
||||||
|
"content-type",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"Assign content type metadata to the input",
|
||||||
|
Some('c'),
|
||||||
|
)
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.category(Category::Debug)
|
.category(Category::Debug)
|
||||||
}
|
}
|
||||||
|
@ -41,33 +47,30 @@ impl Command for MetadataSet {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let ds_fp: Option<String> = call.get_flag(engine_state, stack, "datasource-filepath")?;
|
let ds_fp: Option<String> = call.get_flag(engine_state, stack, "datasource-filepath")?;
|
||||||
let ds_ls = call.has_flag(engine_state, stack, "datasource-ls")?;
|
let ds_ls = call.has_flag(engine_state, stack, "datasource-ls")?;
|
||||||
|
let content_type: Option<String> = call.get_flag(engine_state, stack, "content-type")?;
|
||||||
|
|
||||||
|
let metadata = input
|
||||||
|
.metadata()
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.with_content_type(content_type);
|
||||||
|
|
||||||
match (ds_fp, ds_ls) {
|
match (ds_fp, ds_ls) {
|
||||||
(Some(path), false) => {
|
(Some(path), false) => Ok(input.into_pipeline_data_with_metadata(
|
||||||
let metadata = PipelineMetadata {
|
head,
|
||||||
data_source: DataSource::FilePath(path.into()),
|
engine_state.ctrlc.clone(),
|
||||||
};
|
metadata.with_data_source(DataSource::FilePath(path.into())),
|
||||||
Ok(input.into_pipeline_data_with_metadata(
|
)),
|
||||||
head,
|
(None, true) => Ok(input.into_pipeline_data_with_metadata(
|
||||||
engine_state.ctrlc.clone(),
|
head,
|
||||||
metadata,
|
engine_state.ctrlc.clone(),
|
||||||
))
|
metadata.with_data_source(DataSource::Ls),
|
||||||
}
|
)),
|
||||||
(None, true) => {
|
_ => Ok(input.into_pipeline_data_with_metadata(
|
||||||
let metadata = PipelineMetadata {
|
head,
|
||||||
data_source: DataSource::Ls,
|
engine_state.ctrlc.clone(),
|
||||||
};
|
metadata,
|
||||||
Ok(input.into_pipeline_data_with_metadata(
|
)),
|
||||||
head,
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
metadata,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
_ => Err(ShellError::IncorrectValue {
|
|
||||||
msg: "Expected either --datasource-ls(-l) or --datasource-filepath(-f)".to_string(),
|
|
||||||
val_span: head,
|
|
||||||
call_span: head,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,18 +86,23 @@ impl Command for MetadataSet {
|
||||||
example: "'crates' | metadata set --datasource-filepath $'(pwd)/crates' | metadata",
|
example: "'crates' | metadata set --datasource-filepath $'(pwd)/crates' | metadata",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Set the metadata of a file path",
|
||||||
|
example: "'crates' | metadata set --content-type text/plain | metadata",
|
||||||
|
result: Some(Value::record(record!("content_type" => Value::string("text/plain", Span::test_data())), Span::test_data())),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use crate::{test_examples_with_commands, Metadata};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
test_examples_with_commands(MetadataSet {}, &[&Metadata {}])
|
||||||
|
|
||||||
test_examples(MetadataSet {})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,7 +127,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||||
SysTemp,
|
SysTemp,
|
||||||
SysUsers,
|
SysUsers,
|
||||||
UName,
|
UName,
|
||||||
|
Which,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Help
|
// Help
|
||||||
|
@ -172,9 +172,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||||
))]
|
))]
|
||||||
bind_command! { Ps };
|
bind_command! { Ps };
|
||||||
|
|
||||||
#[cfg(feature = "which-support")]
|
|
||||||
bind_command! { Which };
|
|
||||||
|
|
||||||
// Strings
|
// Strings
|
||||||
bind_command! {
|
bind_command! {
|
||||||
Char,
|
Char,
|
||||||
|
@ -192,6 +189,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||||
Str,
|
Str,
|
||||||
StrCapitalize,
|
StrCapitalize,
|
||||||
StrContains,
|
StrContains,
|
||||||
|
StrDeunicode,
|
||||||
StrDistance,
|
StrDistance,
|
||||||
StrDowncase,
|
StrDowncase,
|
||||||
StrEndswith,
|
StrEndswith,
|
||||||
|
|
10
crates/nu-command/src/env/export_env.rs
vendored
10
crates/nu-command/src/env/export_env.rs
vendored
|
@ -1,4 +1,5 @@
|
||||||
use nu_engine::{command_prelude::*, get_eval_block, redirect_env};
|
use nu_engine::{command_prelude::*, get_eval_block, redirect_env};
|
||||||
|
use nu_protocol::engine::CommandType;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ExportEnv;
|
pub struct ExportEnv;
|
||||||
|
@ -23,6 +24,15 @@ impl Command for ExportEnv {
|
||||||
"Run a block and preserve its environment in a current scope."
|
"Run a block and preserve its environment in a current scope."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"This command is a parser keyword. For details, check:
|
||||||
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_type(&self) -> CommandType {
|
||||||
|
CommandType::Keyword
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
|
|
10
crates/nu-command/src/env/source_env.rs
vendored
10
crates/nu-command/src/env/source_env.rs
vendored
|
@ -2,6 +2,7 @@ use nu_engine::{
|
||||||
command_prelude::*, find_in_dirs_env, get_dirs_var_from_call, get_eval_block_with_early_return,
|
command_prelude::*, find_in_dirs_env, get_dirs_var_from_call, get_eval_block_with_early_return,
|
||||||
redirect_env,
|
redirect_env,
|
||||||
};
|
};
|
||||||
|
use nu_protocol::engine::CommandType;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// Source a file for environment variables.
|
/// Source a file for environment variables.
|
||||||
|
@ -28,6 +29,15 @@ impl Command for SourceEnv {
|
||||||
"Source the environment from a source file into the current environment."
|
"Source the environment from a source file into the current environment."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"This command is a parser keyword. For details, check:
|
||||||
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_type(&self) -> CommandType {
|
||||||
|
CommandType::Keyword
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
|
|
|
@ -135,6 +135,11 @@ impl Command for Cd {
|
||||||
example: r#"cd -"#,
|
example: r#"cd -"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Changing directory with a custom command requires 'def --env'",
|
||||||
|
example: r#"def --env gohome [] { cd ~ }"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,6 +122,7 @@ impl Command for Ls {
|
||||||
ctrl_c,
|
ctrl_c,
|
||||||
PipelineMetadata {
|
PipelineMetadata {
|
||||||
data_source: DataSource::Ls,
|
data_source: DataSource::Ls,
|
||||||
|
content_type: None,
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
Some(pattern) => {
|
Some(pattern) => {
|
||||||
|
@ -145,6 +146,7 @@ impl Command for Ls {
|
||||||
ctrl_c,
|
ctrl_c,
|
||||||
PipelineMetadata {
|
PipelineMetadata {
|
||||||
data_source: DataSource::Ls,
|
data_source: DataSource::Ls,
|
||||||
|
content_type: None,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -175,20 +177,32 @@ impl Command for Ls {
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "List files and directories whose name do not contain 'bar'",
|
description: "List files and directories whose name do not contain 'bar'",
|
||||||
example: "ls -s | where name !~ bar",
|
example: "ls | where name !~ bar",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "List all dirs in your home directory",
|
description: "List the full path of all dirs in your home directory",
|
||||||
example: "ls -a ~ | where type == dir",
|
example: "ls -a ~ | where type == dir",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description:
|
description:
|
||||||
"List all dirs in your home directory which have not been modified in 7 days",
|
"List only the names (not paths) of all dirs in your home directory which have not been modified in 7 days",
|
||||||
example: "ls -as ~ | where type == dir and modified < ((date now) - 7day)",
|
example: "ls -as ~ | where type == dir and modified < ((date now) - 7day)",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Recursively list all files and subdirectories under the current directory using a glob pattern",
|
||||||
|
example: "ls -a **/*",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Recursively list *.rs and *.toml files using the glob command",
|
||||||
|
example: "ls ...(glob **/*.{rs,toml})",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "List given paths and show directories themselves",
|
description: "List given paths and show directories themselves",
|
||||||
example: "['/path/to/directory' '/path/to/file'] | each {|| ls -D $in } | flatten",
|
example: "['/path/to/directory' '/path/to/file'] | each {|| ls -D $in } | flatten",
|
||||||
|
|
|
@ -147,6 +147,7 @@ impl Command for Open {
|
||||||
ByteStream::file(file, call_span, ctrlc.clone()),
|
ByteStream::file(file, call_span, ctrlc.clone()),
|
||||||
Some(PipelineMetadata {
|
Some(PipelineMetadata {
|
||||||
data_source: DataSource::FilePath(path.to_path_buf()),
|
data_source: DataSource::FilePath(path.to_path_buf()),
|
||||||
|
content_type: None,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -461,7 +461,7 @@ fn open_file(path: &Path, span: Span, append: bool) -> Result<File, ShellError>
|
||||||
};
|
};
|
||||||
|
|
||||||
file.map_err(|e| ShellError::GenericError {
|
file.map_err(|e| ShellError::GenericError {
|
||||||
error: "Permission denied".into(),
|
error: format!("Problem with [{}], Permission denied", path.display()),
|
||||||
msg: e.to_string(),
|
msg: e.to_string(),
|
||||||
span: Some(span),
|
span: Some(span),
|
||||||
help: None,
|
help: None,
|
||||||
|
|
|
@ -35,12 +35,12 @@ impl Command for Touch {
|
||||||
)
|
)
|
||||||
.switch(
|
.switch(
|
||||||
"modified",
|
"modified",
|
||||||
"change the modification time of the file or directory. If no timestamp, date or reference file/directory is given, the current time is used",
|
"change the modification time of the file or directory. If no reference file/directory is given, the current time is used",
|
||||||
Some('m'),
|
Some('m'),
|
||||||
)
|
)
|
||||||
.switch(
|
.switch(
|
||||||
"access",
|
"access",
|
||||||
"change the access time of the file or directory. If no timestamp, date or reference file/directory is given, the current time is used",
|
"change the access time of the file or directory. If no reference file/directory is given, the current time is used",
|
||||||
Some('a'),
|
Some('a'),
|
||||||
)
|
)
|
||||||
.switch(
|
.switch(
|
||||||
|
@ -189,11 +189,6 @@ impl Command for Touch {
|
||||||
example: r#"touch -m -r fixture.json d e"#,
|
example: r#"touch -m -r fixture.json d e"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
|
||||||
description: r#"Changes the last accessed time of "fixture.json" to a date"#,
|
|
||||||
example: r#"touch -a -d "August 24, 2019; 12:30:30" fixture.json"#,
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,9 +69,9 @@ impl Command for Find {
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Search and highlight text for a term in a string",
|
description: "Search and highlight text for a term in a string. Note that regular search is case insensitive",
|
||||||
example: r#"'Cargo.toml' | find toml"#,
|
example: r#"'Cargo.toml' | find cargo"#,
|
||||||
result: Some(Value::test_string("\u{1b}[37mCargo.\u{1b}[0m\u{1b}[41;37mtoml\u{1b}[0m\u{1b}[37m\u{1b}[0m".to_owned())),
|
result: Some(Value::test_string("\u{1b}[37m\u{1b}[0m\u{1b}[41;37mCargo\u{1b}[0m\u{1b}[37m.toml\u{1b}[0m".to_owned())),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Search a number or a file size in a list of numbers",
|
description: "Search a number or a file size in a list of numbers",
|
||||||
|
@ -457,9 +457,10 @@ fn find_with_rest_and_highlight(
|
||||||
|
|
||||||
let mut output: Vec<Value> = vec![];
|
let mut output: Vec<Value> = vec![];
|
||||||
for line in lines {
|
for line in lines {
|
||||||
let line = line?.to_lowercase();
|
let line = line?;
|
||||||
|
let lower_val = line.to_lowercase();
|
||||||
for term in &terms {
|
for term in &terms {
|
||||||
if line.contains(term) {
|
if lower_val.contains(term) {
|
||||||
output.push(Value::string(
|
output.push(Value::string(
|
||||||
highlight_search_string(
|
highlight_search_string(
|
||||||
&line,
|
&line,
|
||||||
|
|
|
@ -130,7 +130,7 @@ pub fn split(
|
||||||
Some(group_key) => Ok(group_key.coerce_string()?),
|
Some(group_key) => Ok(group_key.coerce_string()?),
|
||||||
None => Err(ShellError::CantFindColumn {
|
None => Err(ShellError::CantFindColumn {
|
||||||
col_name: column_name.item.to_string(),
|
col_name: column_name.item.to_string(),
|
||||||
span: column_name.span,
|
span: Some(column_name.span),
|
||||||
src_span: row.span(),
|
src_span: row.span(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,7 +123,7 @@ fn validate(vec: &[Value], columns: &[String], span: Span) -> Result<(), ShellEr
|
||||||
if let Some(nonexistent) = nonexistent_column(columns, record.columns()) {
|
if let Some(nonexistent) = nonexistent_column(columns, record.columns()) {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: nonexistent,
|
col_name: nonexistent,
|
||||||
span,
|
span: Some(span),
|
||||||
src_span: val_span,
|
src_span: val_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use nu_engine::{command_prelude::*, ClosureEval};
|
use nu_engine::{command_prelude::*, ClosureEval};
|
||||||
use nu_protocol::engine::Closure;
|
use nu_protocol::engine::{Closure, CommandType};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Where;
|
pub struct Where;
|
||||||
|
@ -19,6 +19,10 @@ tables, known as "row conditions". On the other hand, reading the condition from
|
||||||
not supported."#
|
not supported."#
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn command_type(&self) -> CommandType {
|
||||||
|
CommandType::Keyword
|
||||||
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("where")
|
Signature::build("where")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
|
|
|
@ -39,7 +39,7 @@ fn from_delimited_stream(
|
||||||
.from_reader(input_reader);
|
.from_reader(input_reader);
|
||||||
|
|
||||||
let headers = if noheaders {
|
let headers = if noheaders {
|
||||||
(1..=reader
|
(0..reader
|
||||||
.headers()
|
.headers()
|
||||||
.map_err(|err| from_csv_error(err, span))?
|
.map_err(|err| from_csv_error(err, span))?
|
||||||
.len())
|
.len())
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::ListStream;
|
use nu_protocol::{ListStream, PipelineMetadata};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FromJson;
|
pub struct FromJson;
|
||||||
|
@ -81,7 +81,7 @@ impl Command for FromJson {
|
||||||
PipelineData::Value(Value::String { val, .. }, metadata) => {
|
PipelineData::Value(Value::String { val, .. }, metadata) => {
|
||||||
Ok(PipelineData::ListStream(
|
Ok(PipelineData::ListStream(
|
||||||
read_json_lines(Cursor::new(val), span, strict, engine_state.ctrlc.clone()),
|
read_json_lines(Cursor::new(val), span, strict, engine_state.ctrlc.clone()),
|
||||||
metadata,
|
update_metadata(metadata),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
PipelineData::ByteStream(stream, metadata)
|
PipelineData::ByteStream(stream, metadata)
|
||||||
|
@ -90,7 +90,7 @@ impl Command for FromJson {
|
||||||
if let Some(reader) = stream.reader() {
|
if let Some(reader) = stream.reader() {
|
||||||
Ok(PipelineData::ListStream(
|
Ok(PipelineData::ListStream(
|
||||||
read_json_lines(reader, span, strict, None),
|
read_json_lines(reader, span, strict, None),
|
||||||
metadata,
|
update_metadata(metadata),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Ok(PipelineData::Empty)
|
Ok(PipelineData::Empty)
|
||||||
|
@ -113,10 +113,10 @@ impl Command for FromJson {
|
||||||
|
|
||||||
if strict {
|
if strict {
|
||||||
Ok(convert_string_to_value_strict(&string_input, span)?
|
Ok(convert_string_to_value_strict(&string_input, span)?
|
||||||
.into_pipeline_data_with_metadata(metadata))
|
.into_pipeline_data_with_metadata(update_metadata(metadata)))
|
||||||
} else {
|
} else {
|
||||||
Ok(convert_string_to_value(&string_input, span)?
|
Ok(convert_string_to_value(&string_input, span)?
|
||||||
.into_pipeline_data_with_metadata(metadata))
|
.into_pipeline_data_with_metadata(update_metadata(metadata)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -263,6 +263,14 @@ fn convert_string_to_value_strict(string_input: &str, span: Span) -> Result<Valu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_metadata(metadata: Option<PipelineMetadata>) -> Option<PipelineMetadata> {
|
||||||
|
metadata
|
||||||
|
.map(|md| md.with_content_type(Some("application/json".into())))
|
||||||
|
.or_else(|| {
|
||||||
|
Some(PipelineMetadata::default().with_content_type(Some("application/json".into())))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -52,12 +52,12 @@ impl Command for FromSsv {
|
||||||
Value::test_list(
|
Value::test_list(
|
||||||
vec![
|
vec![
|
||||||
Value::test_record(record! {
|
Value::test_record(record! {
|
||||||
"column1" => Value::test_string("FOO"),
|
"column0" => Value::test_string("FOO"),
|
||||||
"column2" => Value::test_string("BAR"),
|
"column1" => Value::test_string("BAR"),
|
||||||
}),
|
}),
|
||||||
Value::test_record(record! {
|
Value::test_record(record! {
|
||||||
"column1" => Value::test_string("1"),
|
"column0" => Value::test_string("1"),
|
||||||
"column2" => Value::test_string("2"),
|
"column1" => Value::test_string("2"),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -170,7 +170,7 @@ fn parse_aligned_columns<'a>(
|
||||||
let headers: Vec<(String, usize)> = indices
|
let headers: Vec<(String, usize)> = indices
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, position)| (format!("column{}", i + 1), *position))
|
.map(|(i, position)| (format!("column{}", i), *position))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
construct(ls.iter().map(|s| s.to_owned()), headers)
|
construct(ls.iter().map(|s| s.to_owned()), headers)
|
||||||
|
@ -215,7 +215,7 @@ fn parse_separated_columns<'a>(
|
||||||
let parse_without_headers = |ls: Vec<&str>| {
|
let parse_without_headers = |ls: Vec<&str>| {
|
||||||
let num_columns = ls.iter().map(|r| r.len()).max().unwrap_or(0);
|
let num_columns = ls.iter().map(|r| r.len()).max().unwrap_or(0);
|
||||||
|
|
||||||
let headers = (1..=num_columns)
|
let headers = (0..=num_columns)
|
||||||
.map(|i| format!("column{i}"))
|
.map(|i| format!("column{i}"))
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
collect(headers, ls.into_iter(), separator)
|
collect(headers, ls.into_iter(), separator)
|
||||||
|
@ -370,9 +370,9 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result,
|
result,
|
||||||
vec![
|
vec![
|
||||||
vec![owned("column1", "a"), owned("column2", "b")],
|
vec![owned("column0", "a"), owned("column1", "b")],
|
||||||
vec![owned("column1", "1"), owned("column2", "2")],
|
vec![owned("column0", "1"), owned("column1", "2")],
|
||||||
vec![owned("column1", "3"), owned("column2", "4")]
|
vec![owned("column0", "3"), owned("column1", "4")]
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -484,25 +484,25 @@ mod tests {
|
||||||
result,
|
result,
|
||||||
vec![
|
vec![
|
||||||
vec![
|
vec![
|
||||||
owned("column1", "a multi-word value"),
|
owned("column0", "a multi-word value"),
|
||||||
owned("column2", "b"),
|
owned("column1", "b"),
|
||||||
owned("column3", ""),
|
|
||||||
owned("column4", "d"),
|
|
||||||
owned("column5", "")
|
|
||||||
],
|
|
||||||
vec![
|
|
||||||
owned("column1", "1"),
|
|
||||||
owned("column2", ""),
|
owned("column2", ""),
|
||||||
owned("column3", "3-3"),
|
owned("column3", "d"),
|
||||||
owned("column4", "4"),
|
owned("column4", "")
|
||||||
owned("column5", "")
|
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
|
owned("column0", "1"),
|
||||||
|
owned("column1", ""),
|
||||||
|
owned("column2", "3-3"),
|
||||||
|
owned("column3", "4"),
|
||||||
|
owned("column4", "")
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
owned("column0", ""),
|
||||||
owned("column1", ""),
|
owned("column1", ""),
|
||||||
owned("column2", ""),
|
owned("column2", ""),
|
||||||
owned("column3", ""),
|
owned("column3", ""),
|
||||||
owned("column4", ""),
|
owned("column4", "last")
|
||||||
owned("column5", "last")
|
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use std::str::FromStr;
|
use toml::value::{Datetime, Offset};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FromToml;
|
pub struct FromToml;
|
||||||
|
@ -56,6 +56,54 @@ b = [1, 2]' | from toml",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn convert_toml_datetime_to_value(dt: &Datetime, span: Span) -> Value {
|
||||||
|
match &dt.clone() {
|
||||||
|
toml::value::Datetime {
|
||||||
|
date: Some(_),
|
||||||
|
time: _,
|
||||||
|
offset: _,
|
||||||
|
} => (),
|
||||||
|
_ => return Value::string(dt.to_string(), span),
|
||||||
|
}
|
||||||
|
|
||||||
|
let date = match dt.date {
|
||||||
|
Some(date) => {
|
||||||
|
chrono::NaiveDate::from_ymd_opt(date.year.into(), date.month.into(), date.day.into())
|
||||||
|
}
|
||||||
|
None => Some(chrono::NaiveDate::default()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let time = match dt.time {
|
||||||
|
Some(time) => chrono::NaiveTime::from_hms_nano_opt(
|
||||||
|
time.hour.into(),
|
||||||
|
time.minute.into(),
|
||||||
|
time.second.into(),
|
||||||
|
time.nanosecond,
|
||||||
|
),
|
||||||
|
None => Some(chrono::NaiveTime::default()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tz = match dt.offset {
|
||||||
|
Some(offset) => match offset {
|
||||||
|
Offset::Z => chrono::FixedOffset::east_opt(0),
|
||||||
|
Offset::Custom { minutes: min } => chrono::FixedOffset::east_opt(min as i32 * 60),
|
||||||
|
},
|
||||||
|
None => chrono::FixedOffset::east_opt(0),
|
||||||
|
};
|
||||||
|
|
||||||
|
let datetime = match (date, time, tz) {
|
||||||
|
(Some(date), Some(time), Some(tz)) => chrono::NaiveDateTime::new(date, time)
|
||||||
|
.and_local_timezone(tz)
|
||||||
|
.earliest(),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match datetime {
|
||||||
|
Some(datetime) => Value::date(datetime, span),
|
||||||
|
None => Value::string(dt.to_string(), span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn convert_toml_to_value(value: &toml::Value, span: Span) -> Value {
|
fn convert_toml_to_value(value: &toml::Value, span: Span) -> Value {
|
||||||
match value {
|
match value {
|
||||||
toml::Value::Array(array) => {
|
toml::Value::Array(array) => {
|
||||||
|
@ -76,13 +124,7 @@ fn convert_toml_to_value(value: &toml::Value, span: Span) -> Value {
|
||||||
span,
|
span,
|
||||||
),
|
),
|
||||||
toml::Value::String(s) => Value::string(s.clone(), span),
|
toml::Value::String(s) => Value::string(s.clone(), span),
|
||||||
toml::Value::Datetime(d) => match chrono::DateTime::from_str(&d.to_string()) {
|
toml::Value::Datetime(dt) => convert_toml_datetime_to_value(dt, span),
|
||||||
Ok(nushell_date) => Value::date(nushell_date, span),
|
|
||||||
// in the unlikely event that parsing goes wrong, this function still returns a valid
|
|
||||||
// nushell date (however the default one). This decision was made to make the output of
|
|
||||||
// this function uniform amongst all eventualities
|
|
||||||
Err(_) => Value::date(chrono::DateTime::default(), span),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,32 +155,6 @@ mod tests {
|
||||||
test_examples(FromToml {})
|
test_examples(FromToml {})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn from_toml_creates_nushell_date() {
|
|
||||||
let toml_date = toml::Value::Datetime(Datetime {
|
|
||||||
date: Option::from(toml::value::Date {
|
|
||||||
year: 1980,
|
|
||||||
month: 10,
|
|
||||||
day: 12,
|
|
||||||
}),
|
|
||||||
time: Option::from(toml::value::Time {
|
|
||||||
hour: 10,
|
|
||||||
minute: 12,
|
|
||||||
second: 44,
|
|
||||||
nanosecond: 0,
|
|
||||||
}),
|
|
||||||
offset: Option::from(toml::value::Offset::Custom { minutes: 120 }),
|
|
||||||
});
|
|
||||||
|
|
||||||
let span = Span::test_data();
|
|
||||||
let reference_date = Value::date(Default::default(), Span::test_data());
|
|
||||||
|
|
||||||
let result = convert_toml_to_value(&toml_date, span);
|
|
||||||
|
|
||||||
//positive test (from toml returns a nushell date)
|
|
||||||
assert_eq!(result.get_type(), reference_date.get_type());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn from_toml_creates_correct_date() {
|
fn from_toml_creates_correct_date() {
|
||||||
let toml_date = toml::Value::Datetime(Datetime {
|
let toml_date = toml::Value::Datetime(Datetime {
|
||||||
|
@ -206,4 +222,113 @@ mod tests {
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_toml_datetime_to_value_date_time_offset() {
|
||||||
|
let toml_date = Datetime {
|
||||||
|
date: Option::from(toml::value::Date {
|
||||||
|
year: 2000,
|
||||||
|
month: 1,
|
||||||
|
day: 1,
|
||||||
|
}),
|
||||||
|
time: Option::from(toml::value::Time {
|
||||||
|
hour: 12,
|
||||||
|
minute: 12,
|
||||||
|
second: 12,
|
||||||
|
nanosecond: 0,
|
||||||
|
}),
|
||||||
|
offset: Option::from(toml::value::Offset::Custom { minutes: 120 }),
|
||||||
|
};
|
||||||
|
|
||||||
|
let span = Span::test_data();
|
||||||
|
let reference_date = Value::date(
|
||||||
|
chrono::FixedOffset::east_opt(60 * 120)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(2000, 1, 1, 12, 12, 12)
|
||||||
|
.unwrap(),
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = convert_toml_datetime_to_value(&toml_date, span);
|
||||||
|
|
||||||
|
assert_eq!(result, reference_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_toml_datetime_to_value_date_time() {
|
||||||
|
let toml_date = Datetime {
|
||||||
|
date: Option::from(toml::value::Date {
|
||||||
|
year: 2000,
|
||||||
|
month: 1,
|
||||||
|
day: 1,
|
||||||
|
}),
|
||||||
|
time: Option::from(toml::value::Time {
|
||||||
|
hour: 12,
|
||||||
|
minute: 12,
|
||||||
|
second: 12,
|
||||||
|
nanosecond: 0,
|
||||||
|
}),
|
||||||
|
offset: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let span = Span::test_data();
|
||||||
|
let reference_date = Value::date(
|
||||||
|
chrono::FixedOffset::east_opt(0)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(2000, 1, 1, 12, 12, 12)
|
||||||
|
.unwrap(),
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = convert_toml_datetime_to_value(&toml_date, span);
|
||||||
|
|
||||||
|
assert_eq!(result, reference_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_toml_datetime_to_value_date() {
|
||||||
|
let toml_date = Datetime {
|
||||||
|
date: Option::from(toml::value::Date {
|
||||||
|
year: 2000,
|
||||||
|
month: 1,
|
||||||
|
day: 1,
|
||||||
|
}),
|
||||||
|
time: None,
|
||||||
|
offset: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let span = Span::test_data();
|
||||||
|
let reference_date = Value::date(
|
||||||
|
chrono::FixedOffset::east_opt(0)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(2000, 1, 1, 0, 0, 0)
|
||||||
|
.unwrap(),
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = convert_toml_datetime_to_value(&toml_date, span);
|
||||||
|
|
||||||
|
assert_eq!(result, reference_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_toml_datetime_to_value_only_time() {
|
||||||
|
let toml_date = Datetime {
|
||||||
|
date: None,
|
||||||
|
time: Option::from(toml::value::Time {
|
||||||
|
hour: 12,
|
||||||
|
minute: 12,
|
||||||
|
second: 12,
|
||||||
|
nanosecond: 0,
|
||||||
|
}),
|
||||||
|
offset: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let span = Span::test_data();
|
||||||
|
let reference_date = Value::string(toml_date.to_string(), span);
|
||||||
|
|
||||||
|
let result = convert_toml_datetime_to_value(&toml_date, span);
|
||||||
|
|
||||||
|
assert_eq!(result, reference_date);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::ast::PathMember;
|
use nu_protocol::{ast::PathMember, PipelineMetadata};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ToJson;
|
pub struct ToJson;
|
||||||
|
@ -61,7 +61,12 @@ impl Command for ToJson {
|
||||||
|
|
||||||
match json_result {
|
match json_result {
|
||||||
Ok(serde_json_string) => {
|
Ok(serde_json_string) => {
|
||||||
Ok(Value::string(serde_json_string, span).into_pipeline_data())
|
let res = Value::string(serde_json_string, span);
|
||||||
|
let metadata = PipelineMetadata {
|
||||||
|
data_source: nu_protocol::DataSource::None,
|
||||||
|
content_type: Some("application/json".to_string()),
|
||||||
|
};
|
||||||
|
Ok(PipelineData::Value(res, Some(metadata)))
|
||||||
}
|
}
|
||||||
_ => Ok(Value::error(
|
_ => Ok(Value::error(
|
||||||
ShellError::CantConvert {
|
ShellError::CantConvert {
|
||||||
|
|
|
@ -5,7 +5,7 @@ use nu_engine::command_prelude::*;
|
||||||
use super::msgpack::write_value;
|
use super::msgpack::write_value;
|
||||||
|
|
||||||
const BUFFER_SIZE: usize = 65536;
|
const BUFFER_SIZE: usize = 65536;
|
||||||
const DEFAULT_QUALITY: u32 = 1;
|
const DEFAULT_QUALITY: u32 = 3; // 1 can be very bad
|
||||||
const DEFAULT_WINDOW_SIZE: u32 = 20;
|
const DEFAULT_WINDOW_SIZE: u32 = 20;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -22,7 +22,7 @@ impl Command for ToMsgpackz {
|
||||||
.named(
|
.named(
|
||||||
"quality",
|
"quality",
|
||||||
SyntaxShape::Int,
|
SyntaxShape::Int,
|
||||||
"Quality of brotli compression (default 1)",
|
"Quality of brotli compression (default 3)",
|
||||||
Some('q'),
|
Some('q'),
|
||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use chrono_humanize::HumanTime;
|
use chrono_humanize::HumanTime;
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{format_duration, format_filesize_from_conf, ByteStream, Config};
|
use nu_protocol::{
|
||||||
|
format_duration, format_filesize_from_conf, ByteStream, Config, PipelineMetadata,
|
||||||
|
};
|
||||||
|
|
||||||
const LINE_ENDING: &str = if cfg!(target_os = "windows") {
|
const LINE_ENDING: &str = if cfg!(target_os = "windows") {
|
||||||
"\r\n"
|
"\r\n"
|
||||||
|
@ -37,10 +39,14 @@ impl Command for ToText {
|
||||||
let input = input.try_expand_range()?;
|
let input = input.try_expand_range()?;
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
PipelineData::Empty => Ok(Value::string(String::new(), span).into_pipeline_data()),
|
PipelineData::Empty => Ok(Value::string(String::new(), span)
|
||||||
|
.into_pipeline_data_with_metadata(update_metadata(None))),
|
||||||
PipelineData::Value(value, ..) => {
|
PipelineData::Value(value, ..) => {
|
||||||
let str = local_into_string(value, LINE_ENDING, engine_state.get_config());
|
let str = local_into_string(value, LINE_ENDING, engine_state.get_config());
|
||||||
Ok(Value::string(str, span).into_pipeline_data())
|
Ok(
|
||||||
|
Value::string(str, span)
|
||||||
|
.into_pipeline_data_with_metadata(update_metadata(None)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
PipelineData::ListStream(stream, meta) => {
|
PipelineData::ListStream(stream, meta) => {
|
||||||
let span = stream.span();
|
let span = stream.span();
|
||||||
|
@ -57,10 +63,12 @@ impl Command for ToText {
|
||||||
engine_state.ctrlc.clone(),
|
engine_state.ctrlc.clone(),
|
||||||
ByteStreamType::String,
|
ByteStreamType::String,
|
||||||
),
|
),
|
||||||
meta,
|
update_metadata(meta),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
PipelineData::ByteStream(stream, meta) => Ok(PipelineData::ByteStream(stream, meta)),
|
PipelineData::ByteStream(stream, meta) => {
|
||||||
|
Ok(PipelineData::ByteStream(stream, update_metadata(meta)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +132,14 @@ fn local_into_string(value: Value, separator: &str, config: &Config) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_metadata(metadata: Option<PipelineMetadata>) -> Option<PipelineMetadata> {
|
||||||
|
metadata
|
||||||
|
.map(|md| md.with_content_type(Some("text/plain".to_string())))
|
||||||
|
.or_else(|| {
|
||||||
|
Some(PipelineMetadata::default().with_content_type(Some("text/plain".to_string())))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use chrono::SecondsFormat;
|
use chrono::{DateTime, Datelike, FixedOffset, Timelike};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::ast::PathMember;
|
use nu_protocol::ast::PathMember;
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ impl Command for ToToml {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Outputs an TOML string representing the contents of this record",
|
description: "Outputs an TOML string representing the contents of this record",
|
||||||
example: r#"{foo: 1 bar: 'qwe'} | to toml"#,
|
example: r#"{foo: 1 bar: 'qwe'} | to toml"#,
|
||||||
result: Some(Value::test_string("bar = \"qwe\"\nfoo = 1\n")),
|
result: Some(Value::test_string("foo = 1\nbar = \"qwe\"\n")),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,9 +49,7 @@ fn helper(engine_state: &EngineState, v: &Value) -> Result<toml::Value, ShellErr
|
||||||
Value::Int { val, .. } => toml::Value::Integer(*val),
|
Value::Int { val, .. } => toml::Value::Integer(*val),
|
||||||
Value::Filesize { val, .. } => toml::Value::Integer(*val),
|
Value::Filesize { val, .. } => toml::Value::Integer(*val),
|
||||||
Value::Duration { val, .. } => toml::Value::String(val.to_string()),
|
Value::Duration { val, .. } => toml::Value::String(val.to_string()),
|
||||||
Value::Date { val, .. } => {
|
Value::Date { val, .. } => toml::Value::Datetime(to_toml_datetime(val)),
|
||||||
toml::Value::String(val.to_rfc3339_opts(SecondsFormat::AutoSi, false))
|
|
||||||
}
|
|
||||||
Value::Range { .. } => toml::Value::String("<Range>".to_string()),
|
Value::Range { .. } => toml::Value::String("<Range>".to_string()),
|
||||||
Value::Float { val, .. } => toml::Value::Float(*val),
|
Value::Float { val, .. } => toml::Value::Float(*val),
|
||||||
Value::String { val, .. } | Value::Glob { val, .. } => toml::Value::String(val.clone()),
|
Value::String { val, .. } | Value::Glob { val, .. } => toml::Value::String(val.clone()),
|
||||||
|
@ -103,7 +101,7 @@ fn toml_into_pipeline_data(
|
||||||
value_type: Type,
|
value_type: Type,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
match toml::to_string(&toml_value) {
|
match toml::to_string_pretty(&toml_value) {
|
||||||
Ok(serde_toml_string) => Ok(Value::string(serde_toml_string, span).into_pipeline_data()),
|
Ok(serde_toml_string) => Ok(Value::string(serde_toml_string, span).into_pipeline_data()),
|
||||||
_ => Ok(Value::error(
|
_ => Ok(Value::error(
|
||||||
ShellError::CantConvert {
|
ShellError::CantConvert {
|
||||||
|
@ -157,6 +155,43 @@ fn to_toml(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert chrono datetime into a toml::Value datetime. The latter uses its
|
||||||
|
/// own ad-hoc datetime types, which makes this somewhat convoluted.
|
||||||
|
fn to_toml_datetime(datetime: &DateTime<FixedOffset>) -> toml::value::Datetime {
|
||||||
|
let date = toml::value::Date {
|
||||||
|
// TODO: figure out what to do with BC dates, because the toml
|
||||||
|
// crate doesn't support them. Same for large years, which
|
||||||
|
// don't fit in u16.
|
||||||
|
year: datetime.year_ce().1 as u16,
|
||||||
|
// Panic: this is safe, because chrono guarantees that the month
|
||||||
|
// value will be between 1 and 12 and the day will be between 1
|
||||||
|
// and 31
|
||||||
|
month: datetime.month() as u8,
|
||||||
|
day: datetime.day() as u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
let time = toml::value::Time {
|
||||||
|
// Panic: same as before, chorono guarantees that all of the following 3
|
||||||
|
// methods return values less than 65'000
|
||||||
|
hour: datetime.hour() as u8,
|
||||||
|
minute: datetime.minute() as u8,
|
||||||
|
second: datetime.second() as u8,
|
||||||
|
nanosecond: datetime.nanosecond(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let offset = toml::value::Offset::Custom {
|
||||||
|
// Panic: minute timezone offset fits into i16 (that's more than
|
||||||
|
// 1000 hours)
|
||||||
|
minutes: (-datetime.timezone().utc_minus_local() / 60) as i16,
|
||||||
|
};
|
||||||
|
|
||||||
|
toml::value::Datetime {
|
||||||
|
date: Some(date),
|
||||||
|
time: Some(time),
|
||||||
|
offset: Some(offset),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -181,7 +216,20 @@ mod tests {
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let reference_date = toml::Value::String(String::from("1980-10-12T10:12:44+02:00"));
|
let reference_date = toml::Value::Datetime(toml::value::Datetime {
|
||||||
|
date: Some(toml::value::Date {
|
||||||
|
year: 1980,
|
||||||
|
month: 10,
|
||||||
|
day: 12,
|
||||||
|
}),
|
||||||
|
time: Some(toml::value::Time {
|
||||||
|
hour: 10,
|
||||||
|
minute: 12,
|
||||||
|
second: 44,
|
||||||
|
nanosecond: 0,
|
||||||
|
}),
|
||||||
|
offset: Some(toml::value::Offset::Custom { minutes: 120 }),
|
||||||
|
});
|
||||||
|
|
||||||
let result = helper(&engine_state, &test_date);
|
let result = helper(&engine_state, &test_date);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use chrono::{Datelike, Local, NaiveDate};
|
use chrono::{Datelike, Local, NaiveDate};
|
||||||
use nu_color_config::StyleComputer;
|
use nu_color_config::StyleComputer;
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::ast::{Expr, Expression};
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ struct Arguments {
|
||||||
month_names: bool,
|
month_names: bool,
|
||||||
full_year: Option<Spanned<i64>>,
|
full_year: Option<Spanned<i64>>,
|
||||||
week_start: Option<Spanned<String>>,
|
week_start: Option<Spanned<String>>,
|
||||||
|
as_table: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command for Cal {
|
impl Command for Cal {
|
||||||
|
@ -26,6 +28,7 @@ impl Command for Cal {
|
||||||
.switch("year", "Display the year column", Some('y'))
|
.switch("year", "Display the year column", Some('y'))
|
||||||
.switch("quarter", "Display the quarter column", Some('q'))
|
.switch("quarter", "Display the quarter column", Some('q'))
|
||||||
.switch("month", "Display the month column", Some('m'))
|
.switch("month", "Display the month column", Some('m'))
|
||||||
|
.switch("as-table", "output as a table", Some('t'))
|
||||||
.named(
|
.named(
|
||||||
"full-year",
|
"full-year",
|
||||||
SyntaxShape::Int,
|
SyntaxShape::Int,
|
||||||
|
@ -43,7 +46,10 @@ impl Command for Cal {
|
||||||
"Display the month names instead of integers",
|
"Display the month names instead of integers",
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.input_output_types(vec![(Type::Nothing, Type::table())])
|
.input_output_types(vec![
|
||||||
|
(Type::Nothing, Type::table()),
|
||||||
|
(Type::Nothing, Type::String),
|
||||||
|
])
|
||||||
.allow_variants_without_examples(true) // TODO: supply exhaustive examples
|
.allow_variants_without_examples(true) // TODO: supply exhaustive examples
|
||||||
.category(Category::Generators)
|
.category(Category::Generators)
|
||||||
}
|
}
|
||||||
|
@ -75,10 +81,15 @@ impl Command for Cal {
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "This month's calendar with the week starting on monday",
|
description: "This month's calendar with the week starting on Monday",
|
||||||
example: "cal --week-start mo",
|
example: "cal --week-start mo",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "How many 'Friday the Thirteenths' occurred in 2015?",
|
||||||
|
example: "cal --as-table --full-year 2015 | where fr == 13 | length",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,6 +112,7 @@ pub fn cal(
|
||||||
quarter: call.has_flag(engine_state, stack, "quarter")?,
|
quarter: call.has_flag(engine_state, stack, "quarter")?,
|
||||||
full_year: call.get_flag(engine_state, stack, "full-year")?,
|
full_year: call.get_flag(engine_state, stack, "full-year")?,
|
||||||
week_start: call.get_flag(engine_state, stack, "week-start")?,
|
week_start: call.get_flag(engine_state, stack, "week-start")?,
|
||||||
|
as_table: call.has_flag(engine_state, stack, "as-table")?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let style_computer = &StyleComputer::from_config(engine_state, stack);
|
let style_computer = &StyleComputer::from_config(engine_state, stack);
|
||||||
|
@ -131,7 +143,27 @@ pub fn cal(
|
||||||
style_computer,
|
style_computer,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(Value::list(calendar_vec_deque.into_iter().collect(), tag).into_pipeline_data())
|
let mut table_no_index = Call::new(Span::unknown());
|
||||||
|
table_no_index.add_named((
|
||||||
|
Spanned {
|
||||||
|
item: "index".to_string(),
|
||||||
|
span: Span::unknown(),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
Some(Expression::new_unknown(
|
||||||
|
Expr::Bool(false),
|
||||||
|
Span::unknown(),
|
||||||
|
Type::Bool,
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
|
||||||
|
let cal_table_output =
|
||||||
|
Value::list(calendar_vec_deque.into_iter().collect(), tag).into_pipeline_data();
|
||||||
|
if !arguments.as_table {
|
||||||
|
crate::Table.run(engine_state, stack, &table_no_index, cal_table_output)
|
||||||
|
} else {
|
||||||
|
Ok(cal_table_output)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_invalid_year_shell_error(head: Span) -> ShellError {
|
fn get_invalid_year_shell_error(head: Span) -> ShellError {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use itertools::unfold;
|
|
||||||
use nu_engine::{command_prelude::*, ClosureEval};
|
use nu_engine::{command_prelude::*, ClosureEval};
|
||||||
use nu_protocol::engine::Closure;
|
use nu_protocol::engine::Closure;
|
||||||
|
|
||||||
|
@ -12,13 +11,7 @@ impl Command for Generate {
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("generate")
|
Signature::build("generate")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::Any)))])
|
||||||
(Type::Nothing, Type::List(Box::new(Type::Any))),
|
|
||||||
(
|
|
||||||
Type::List(Box::new(Type::Any)),
|
|
||||||
Type::List(Box::new(Type::Any)),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
.required("initial", SyntaxShape::Any, "Initial value.")
|
.required("initial", SyntaxShape::Any, "Initial value.")
|
||||||
.required(
|
.required(
|
||||||
"closure",
|
"closure",
|
||||||
|
@ -63,23 +56,10 @@ used as the next argument to the closure, otherwise generation stops.
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
example: "generate [0, 1] {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} } | first 10",
|
example:
|
||||||
description: "Generate a stream of fibonacci numbers",
|
"generate [0, 1] {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} }",
|
||||||
result: Some(Value::list(
|
description: "Generate a continuous stream of Fibonacci numbers",
|
||||||
vec![
|
result: None,
|
||||||
Value::test_int(0),
|
|
||||||
Value::test_int(1),
|
|
||||||
Value::test_int(1),
|
|
||||||
Value::test_int(2),
|
|
||||||
Value::test_int(3),
|
|
||||||
Value::test_int(5),
|
|
||||||
Value::test_int(8),
|
|
||||||
Value::test_int(13),
|
|
||||||
Value::test_int(21),
|
|
||||||
Value::test_int(34),
|
|
||||||
],
|
|
||||||
Span::test_data(),
|
|
||||||
)),
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -100,7 +80,8 @@ used as the next argument to the closure, otherwise generation stops.
|
||||||
// A type of Option<S> is used to represent state. Invocation
|
// A type of Option<S> is used to represent state. Invocation
|
||||||
// will stop on None. Using Option<S> allows functions to output
|
// will stop on None. Using Option<S> allows functions to output
|
||||||
// one final value before stopping.
|
// one final value before stopping.
|
||||||
let iter = unfold(Some(initial), move |state| {
|
let mut state = Some(initial);
|
||||||
|
let iter = std::iter::from_fn(move || {
|
||||||
let arg = state.take()?;
|
let arg = state.take()?;
|
||||||
|
|
||||||
let (output, next_input) = match closure.run_with_value(arg) {
|
let (output, next_input) = match closure.run_with_value(arg) {
|
||||||
|
@ -179,7 +160,7 @@ used as the next argument to the closure, otherwise generation stops.
|
||||||
// We use `state` to control when to stop, not `output`. By wrapping
|
// We use `state` to control when to stop, not `output`. By wrapping
|
||||||
// it in a `Some`, we allow the generator to output `None` as a valid output
|
// it in a `Some`, we allow the generator to output `None` as a valid output
|
||||||
// value.
|
// value.
|
||||||
*state = next_input;
|
state = next_input;
|
||||||
Some(output)
|
Some(output)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -122,7 +122,7 @@ fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
|
||||||
let usage = sig.usage;
|
let usage = sig.usage;
|
||||||
let search_terms = sig.search_terms;
|
let search_terms = sig.search_terms;
|
||||||
|
|
||||||
let command_type = format!("{:?}", decl.command_type()).to_ascii_lowercase();
|
let command_type = decl.command_type().to_string();
|
||||||
|
|
||||||
// Build table of parameters
|
// Build table of parameters
|
||||||
let param_table = {
|
let param_table = {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::ast::{Assignment, Bits, Boolean, Comparison, Math, Operator};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct HelpOperators;
|
pub struct HelpOperators;
|
||||||
|
@ -27,282 +28,148 @@ impl Command for HelpOperators {
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let op_info = generate_operator_info();
|
let mut operators = [
|
||||||
let mut recs = vec![];
|
Operator::Assignment(Assignment::Assign),
|
||||||
|
Operator::Assignment(Assignment::PlusAssign),
|
||||||
for op in op_info {
|
Operator::Assignment(Assignment::AppendAssign),
|
||||||
recs.push(Value::record(
|
Operator::Assignment(Assignment::MinusAssign),
|
||||||
|
Operator::Assignment(Assignment::MultiplyAssign),
|
||||||
|
Operator::Assignment(Assignment::DivideAssign),
|
||||||
|
Operator::Comparison(Comparison::Equal),
|
||||||
|
Operator::Comparison(Comparison::NotEqual),
|
||||||
|
Operator::Comparison(Comparison::LessThan),
|
||||||
|
Operator::Comparison(Comparison::GreaterThan),
|
||||||
|
Operator::Comparison(Comparison::LessThanOrEqual),
|
||||||
|
Operator::Comparison(Comparison::GreaterThanOrEqual),
|
||||||
|
Operator::Comparison(Comparison::RegexMatch),
|
||||||
|
Operator::Comparison(Comparison::NotRegexMatch),
|
||||||
|
Operator::Comparison(Comparison::In),
|
||||||
|
Operator::Comparison(Comparison::NotIn),
|
||||||
|
Operator::Comparison(Comparison::StartsWith),
|
||||||
|
Operator::Comparison(Comparison::EndsWith),
|
||||||
|
Operator::Math(Math::Plus),
|
||||||
|
Operator::Math(Math::Append),
|
||||||
|
Operator::Math(Math::Minus),
|
||||||
|
Operator::Math(Math::Multiply),
|
||||||
|
Operator::Math(Math::Divide),
|
||||||
|
Operator::Math(Math::Modulo),
|
||||||
|
Operator::Math(Math::FloorDivision),
|
||||||
|
Operator::Math(Math::Pow),
|
||||||
|
Operator::Bits(Bits::BitOr),
|
||||||
|
Operator::Bits(Bits::BitXor),
|
||||||
|
Operator::Bits(Bits::BitAnd),
|
||||||
|
Operator::Bits(Bits::ShiftLeft),
|
||||||
|
Operator::Bits(Bits::ShiftRight),
|
||||||
|
Operator::Boolean(Boolean::And),
|
||||||
|
Operator::Boolean(Boolean::Or),
|
||||||
|
Operator::Boolean(Boolean::Xor),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(|op| {
|
||||||
|
Value::record(
|
||||||
record! {
|
record! {
|
||||||
"type" => Value::string(op.op_type, head),
|
"type" => Value::string(op_type(&op), head),
|
||||||
"operator" => Value::string(op.operator, head),
|
"operator" => Value::string(op.to_string(), head),
|
||||||
"name" => Value::string(op.name, head),
|
"name" => Value::string(name(&op), head),
|
||||||
"description" => Value::string(op.description, head),
|
"description" => Value::string(description(&op), head),
|
||||||
"precedence" => Value::int(op.precedence, head),
|
"precedence" => Value::int(op.precedence().into(), head),
|
||||||
},
|
},
|
||||||
head,
|
head,
|
||||||
));
|
)
|
||||||
}
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Ok(Value::list(recs, head).into_pipeline_data())
|
operators.push(Value::record(
|
||||||
|
record! {
|
||||||
|
"type" => Value::string("Boolean", head),
|
||||||
|
"operator" => Value::string("not", head),
|
||||||
|
"name" => Value::string("Not", head),
|
||||||
|
"description" => Value::string("Negates a value or expression.", head),
|
||||||
|
"precedence" => Value::int(0, head),
|
||||||
|
},
|
||||||
|
head,
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(Value::list(operators, head).into_pipeline_data())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct OperatorInfo {
|
fn op_type(operator: &Operator) -> &'static str {
|
||||||
op_type: String,
|
match operator {
|
||||||
operator: String,
|
Operator::Comparison(_) => "Comparison",
|
||||||
name: String,
|
Operator::Math(_) => "Math",
|
||||||
description: String,
|
Operator::Boolean(_) => "Boolean",
|
||||||
precedence: i64,
|
Operator::Bits(_) => "Bitwise",
|
||||||
|
Operator::Assignment(_) => "Assignment",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_operator_info() -> Vec<OperatorInfo> {
|
fn name(operator: &Operator) -> String {
|
||||||
vec![
|
match operator {
|
||||||
OperatorInfo {
|
Operator::Comparison(op) => format!("{op:?}"),
|
||||||
op_type: "Assignment".into(),
|
Operator::Math(op) => format!("{op:?}"),
|
||||||
operator: "=".into(),
|
Operator::Boolean(op) => format!("{op:?}"),
|
||||||
name: "Assign".into(),
|
Operator::Bits(op) => format!("{op:?}"),
|
||||||
description: "Assigns a value to a variable.".into(),
|
Operator::Assignment(op) => format!("{op:?}"),
|
||||||
precedence: 10,
|
}
|
||||||
},
|
}
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Assignment".into(),
|
fn description(operator: &Operator) -> &'static str {
|
||||||
operator: "+=".into(),
|
// NOTE: if you have to add an operator here, also add it to the operator list above.
|
||||||
name: "PlusAssign".into(),
|
match operator {
|
||||||
description: "Adds a value to a variable.".into(),
|
Operator::Comparison(Comparison::Equal) => "Checks if two values are equal.",
|
||||||
precedence: 10,
|
Operator::Comparison(Comparison::NotEqual) => "Checks if two values are not equal.",
|
||||||
},
|
Operator::Comparison(Comparison::LessThan) => "Checks if a value is less than another.",
|
||||||
OperatorInfo {
|
Operator::Comparison(Comparison::GreaterThan) => {
|
||||||
op_type: "Assignment".into(),
|
"Checks if a value is greater than another."
|
||||||
operator: "++=".into(),
|
}
|
||||||
name: "AppendAssign".into(),
|
Operator::Comparison(Comparison::LessThanOrEqual) => {
|
||||||
description: "Appends a list or a value to a variable.".into(),
|
"Checks if a value is less than or equal to another."
|
||||||
precedence: 10,
|
}
|
||||||
},
|
Operator::Comparison(Comparison::GreaterThanOrEqual) => {
|
||||||
OperatorInfo {
|
"Checks if a value is greater than or equal to another."
|
||||||
op_type: "Assignment".into(),
|
}
|
||||||
operator: "-=".into(),
|
Operator::Comparison(Comparison::RegexMatch) => {
|
||||||
name: "MinusAssign".into(),
|
"Checks if a value matches a regular expression."
|
||||||
description: "Subtracts a value from a variable.".into(),
|
}
|
||||||
precedence: 10,
|
Operator::Comparison(Comparison::NotRegexMatch) => {
|
||||||
},
|
"Checks if a value does not match a regular expression."
|
||||||
OperatorInfo {
|
}
|
||||||
op_type: "Assignment".into(),
|
Operator::Comparison(Comparison::In) => {
|
||||||
operator: "*=".into(),
|
"Checks if a value is in a list, is part of a string, or is a key in a record."
|
||||||
name: "MultiplyAssign".into(),
|
}
|
||||||
description: "Multiplies a variable by a value.".into(),
|
Operator::Comparison(Comparison::NotIn) => {
|
||||||
precedence: 10,
|
"Checks if a value is not in a list, is not part of a string, or is not a key in a record."
|
||||||
},
|
}
|
||||||
OperatorInfo {
|
Operator::Comparison(Comparison::StartsWith) => "Checks if a string starts with another.",
|
||||||
op_type: "Assignment".into(),
|
Operator::Comparison(Comparison::EndsWith) => "Checks if a string ends with another.",
|
||||||
operator: "/=".into(),
|
Operator::Math(Math::Plus) => "Adds two values.",
|
||||||
name: "DivideAssign".into(),
|
Operator::Math(Math::Append) => {
|
||||||
description: "Divides a variable by a value.".into(),
|
"Appends two lists, a list and a value, two strings, or two binary values."
|
||||||
precedence: 10,
|
}
|
||||||
},
|
Operator::Math(Math::Minus) => "Subtracts two values.",
|
||||||
OperatorInfo {
|
Operator::Math(Math::Multiply) => "Multiplies two values.",
|
||||||
op_type: "Comparison".into(),
|
Operator::Math(Math::Divide) => "Divides two values.",
|
||||||
operator: "==".into(),
|
Operator::Math(Math::Modulo) => "Divides two values and returns the remainder.",
|
||||||
name: "Equal".into(),
|
Operator::Math(Math::FloorDivision) => "Divides two values and floors the result.",
|
||||||
description: "Checks if two values are equal.".into(),
|
Operator::Math(Math::Pow) => "Raises one value to the power of another.",
|
||||||
precedence: 80,
|
Operator::Boolean(Boolean::And) => "Checks if both values are true.",
|
||||||
},
|
Operator::Boolean(Boolean::Or) => "Checks if either value is true.",
|
||||||
OperatorInfo {
|
Operator::Boolean(Boolean::Xor) => "Checks if one value is true and the other is false.",
|
||||||
op_type: "Comparison".into(),
|
Operator::Bits(Bits::BitOr) => "Performs a bitwise OR on two values.",
|
||||||
operator: "!=".into(),
|
Operator::Bits(Bits::BitXor) => "Performs a bitwise XOR on two values.",
|
||||||
name: "NotEqual".into(),
|
Operator::Bits(Bits::BitAnd) => "Performs a bitwise AND on two values.",
|
||||||
description: "Checks if two values are not equal.".into(),
|
Operator::Bits(Bits::ShiftLeft) => "Bitwise shifts a value left by another.",
|
||||||
precedence: 80,
|
Operator::Bits(Bits::ShiftRight) => "Bitwise shifts a value right by another.",
|
||||||
},
|
Operator::Assignment(Assignment::Assign) => "Assigns a value to a variable.",
|
||||||
OperatorInfo {
|
Operator::Assignment(Assignment::PlusAssign) => "Adds a value to a variable.",
|
||||||
op_type: "Comparison".into(),
|
Operator::Assignment(Assignment::AppendAssign) => {
|
||||||
operator: "<".into(),
|
"Appends a list, a value, a string, or a binary value to a variable."
|
||||||
name: "LessThan".into(),
|
}
|
||||||
description: "Checks if a value is less than another.".into(),
|
Operator::Assignment(Assignment::MinusAssign) => "Subtracts a value from a variable.",
|
||||||
precedence: 80,
|
Operator::Assignment(Assignment::MultiplyAssign) => "Multiplies a variable by a value.",
|
||||||
},
|
Operator::Assignment(Assignment::DivideAssign) => "Divides a variable by a value.",
|
||||||
OperatorInfo {
|
}
|
||||||
op_type: "Comparison".into(),
|
|
||||||
operator: "<=".into(),
|
|
||||||
name: "LessThanOrEqual".into(),
|
|
||||||
description: "Checks if a value is less than or equal to another.".into(),
|
|
||||||
precedence: 80,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Comparison".into(),
|
|
||||||
operator: ">".into(),
|
|
||||||
name: "GreaterThan".into(),
|
|
||||||
description: "Checks if a value is greater than another.".into(),
|
|
||||||
precedence: 80,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Comparison".into(),
|
|
||||||
operator: ">=".into(),
|
|
||||||
name: "GreaterThanOrEqual".into(),
|
|
||||||
description: "Checks if a value is greater than or equal to another.".into(),
|
|
||||||
precedence: 80,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Comparison".into(),
|
|
||||||
operator: "=~".into(),
|
|
||||||
name: "RegexMatch".into(),
|
|
||||||
description: "Checks if a value matches a regular expression.".into(),
|
|
||||||
precedence: 80,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Comparison".into(),
|
|
||||||
operator: "!~".into(),
|
|
||||||
name: "NotRegexMatch".into(),
|
|
||||||
description: "Checks if a value does not match a regular expression.".into(),
|
|
||||||
precedence: 80,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Comparison".into(),
|
|
||||||
operator: "in".into(),
|
|
||||||
name: "In".into(),
|
|
||||||
description: "Checks if a value is in a list or string.".into(),
|
|
||||||
precedence: 80,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Comparison".into(),
|
|
||||||
operator: "not-in".into(),
|
|
||||||
name: "NotIn".into(),
|
|
||||||
description: "Checks if a value is not in a list or string.".into(),
|
|
||||||
precedence: 80,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Comparison".into(),
|
|
||||||
operator: "starts-with".into(),
|
|
||||||
name: "StartsWith".into(),
|
|
||||||
description: "Checks if a string starts with another.".into(),
|
|
||||||
precedence: 80,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Comparison".into(),
|
|
||||||
operator: "ends-with".into(),
|
|
||||||
name: "EndsWith".into(),
|
|
||||||
description: "Checks if a string ends with another.".into(),
|
|
||||||
precedence: 80,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Comparison".into(),
|
|
||||||
operator: "not".into(),
|
|
||||||
name: "UnaryNot".into(),
|
|
||||||
description: "Negates a value or expression.".into(),
|
|
||||||
precedence: 0,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Math".into(),
|
|
||||||
operator: "+".into(),
|
|
||||||
name: "Plus".into(),
|
|
||||||
description: "Adds two values.".into(),
|
|
||||||
precedence: 90,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Math".into(),
|
|
||||||
operator: "++".into(),
|
|
||||||
name: "Append".into(),
|
|
||||||
description: "Appends two lists or a list and a value.".into(),
|
|
||||||
precedence: 80,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Math".into(),
|
|
||||||
operator: "-".into(),
|
|
||||||
name: "Minus".into(),
|
|
||||||
description: "Subtracts two values.".into(),
|
|
||||||
precedence: 90,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Math".into(),
|
|
||||||
operator: "*".into(),
|
|
||||||
name: "Multiply".into(),
|
|
||||||
description: "Multiplies two values.".into(),
|
|
||||||
precedence: 95,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Math".into(),
|
|
||||||
operator: "/".into(),
|
|
||||||
name: "Divide".into(),
|
|
||||||
description: "Divides two values.".into(),
|
|
||||||
precedence: 95,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Math".into(),
|
|
||||||
operator: "//".into(),
|
|
||||||
name: "FloorDivision".into(),
|
|
||||||
description: "Divides two values and floors the result.".into(),
|
|
||||||
precedence: 95,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Math".into(),
|
|
||||||
operator: "mod".into(),
|
|
||||||
name: "Modulo".into(),
|
|
||||||
description: "Divides two values and returns the remainder.".into(),
|
|
||||||
precedence: 95,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Math".into(),
|
|
||||||
operator: "**".into(),
|
|
||||||
name: "Pow ".into(),
|
|
||||||
description: "Raises one value to the power of another.".into(),
|
|
||||||
precedence: 100,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Bitwise".into(),
|
|
||||||
operator: "bit-or".into(),
|
|
||||||
name: "BitOr".into(),
|
|
||||||
description: "Performs a bitwise OR on two values.".into(),
|
|
||||||
precedence: 60,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Bitwise".into(),
|
|
||||||
operator: "bit-xor".into(),
|
|
||||||
name: "BitXor".into(),
|
|
||||||
description: "Performs a bitwise XOR on two values.".into(),
|
|
||||||
precedence: 70,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Bitwise".into(),
|
|
||||||
operator: "bit-and".into(),
|
|
||||||
name: "BitAnd".into(),
|
|
||||||
description: "Performs a bitwise AND on two values.".into(),
|
|
||||||
precedence: 75,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Bitwise".into(),
|
|
||||||
operator: "bit-shl".into(),
|
|
||||||
name: "ShiftLeft".into(),
|
|
||||||
description: "Shifts a value left by another.".into(),
|
|
||||||
precedence: 85,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Bitwise".into(),
|
|
||||||
operator: "bit-shr".into(),
|
|
||||||
name: "ShiftRight".into(),
|
|
||||||
description: "Shifts a value right by another.".into(),
|
|
||||||
precedence: 85,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Boolean".into(),
|
|
||||||
operator: "and".into(),
|
|
||||||
name: "And".into(),
|
|
||||||
description: "Checks if two values are true.".into(),
|
|
||||||
precedence: 50,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Boolean".into(),
|
|
||||||
operator: "or".into(),
|
|
||||||
name: "Or".into(),
|
|
||||||
description: "Checks if either value is true.".into(),
|
|
||||||
precedence: 40,
|
|
||||||
},
|
|
||||||
OperatorInfo {
|
|
||||||
op_type: "Boolean".into(),
|
|
||||||
operator: "xor".into(),
|
|
||||||
name: "Xor".into(),
|
|
||||||
description: "Checks if one value is true and the other is false.".into(),
|
|
||||||
precedence: 45,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -180,88 +180,113 @@ impl From<ShellError> for ShellErrorOrRequestError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum HttpBody {
|
||||||
|
Value(Value),
|
||||||
|
ByteStream(ByteStream),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove once all commands have been migrated
|
||||||
pub fn send_request(
|
pub fn send_request(
|
||||||
request: Request,
|
request: Request,
|
||||||
body: Option<Value>,
|
http_body: HttpBody,
|
||||||
content_type: Option<String>,
|
content_type: Option<String>,
|
||||||
ctrl_c: Option<Arc<AtomicBool>>,
|
ctrl_c: Option<Arc<AtomicBool>>,
|
||||||
) -> Result<Response, ShellErrorOrRequestError> {
|
) -> Result<Response, ShellErrorOrRequestError> {
|
||||||
let request_url = request.url().to_string();
|
let request_url = request.url().to_string();
|
||||||
if body.is_none() {
|
|
||||||
return send_cancellable_request(&request_url, Box::new(|| request.call()), ctrl_c);
|
|
||||||
}
|
|
||||||
let body = body.expect("Should never be none.");
|
|
||||||
|
|
||||||
let body_type = match content_type {
|
match http_body {
|
||||||
Some(it) if it == "application/json" => BodyType::Json,
|
HttpBody::None => {
|
||||||
Some(it) if it == "application/x-www-form-urlencoded" => BodyType::Form,
|
send_cancellable_request(&request_url, Box::new(|| request.call()), ctrl_c)
|
||||||
_ => BodyType::Unknown,
|
|
||||||
};
|
|
||||||
match body {
|
|
||||||
Value::Binary { val, .. } => send_cancellable_request(
|
|
||||||
&request_url,
|
|
||||||
Box::new(move || request.send_bytes(&val)),
|
|
||||||
ctrl_c,
|
|
||||||
),
|
|
||||||
Value::String { .. } if body_type == BodyType::Json => {
|
|
||||||
let data = value_to_json_value(&body)?;
|
|
||||||
send_cancellable_request(&request_url, Box::new(|| request.send_json(data)), ctrl_c)
|
|
||||||
}
|
}
|
||||||
Value::String { val, .. } => send_cancellable_request(
|
HttpBody::ByteStream(byte_stream) => {
|
||||||
&request_url,
|
let req = if let Some(content_type) = content_type {
|
||||||
Box::new(move || request.send_string(&val)),
|
request.set("Content-Type", &content_type)
|
||||||
ctrl_c,
|
} else {
|
||||||
),
|
request
|
||||||
Value::Record { .. } if body_type == BodyType::Json => {
|
|
||||||
let data = value_to_json_value(&body)?;
|
|
||||||
send_cancellable_request(&request_url, Box::new(|| request.send_json(data)), ctrl_c)
|
|
||||||
}
|
|
||||||
Value::Record { val, .. } if body_type == BodyType::Form => {
|
|
||||||
let mut data: Vec<(String, String)> = Vec::with_capacity(val.len());
|
|
||||||
|
|
||||||
for (col, val) in val.into_owned() {
|
|
||||||
data.push((col, val.coerce_into_string()?))
|
|
||||||
}
|
|
||||||
|
|
||||||
let request_fn = move || {
|
|
||||||
// coerce `data` into a shape that send_form() is happy with
|
|
||||||
let data = data
|
|
||||||
.iter()
|
|
||||||
.map(|(a, b)| (a.as_str(), b.as_str()))
|
|
||||||
.collect::<Vec<(&str, &str)>>();
|
|
||||||
request.send_form(&data)
|
|
||||||
};
|
};
|
||||||
send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c)
|
|
||||||
|
send_cancellable_request_bytes(&request_url, req, byte_stream, ctrl_c)
|
||||||
}
|
}
|
||||||
Value::List { vals, .. } if body_type == BodyType::Form => {
|
HttpBody::Value(body) => {
|
||||||
if vals.len() % 2 != 0 {
|
let (body_type, req) = match content_type {
|
||||||
return Err(ShellErrorOrRequestError::ShellError(ShellError::IOError {
|
Some(it) if it == "application/json" => (BodyType::Json, request),
|
||||||
|
Some(it) if it == "application/x-www-form-urlencoded" => (BodyType::Form, request),
|
||||||
|
Some(it) => {
|
||||||
|
let r = request.clone().set("Content-Type", &it);
|
||||||
|
(BodyType::Unknown, r)
|
||||||
|
}
|
||||||
|
_ => (BodyType::Unknown, request),
|
||||||
|
};
|
||||||
|
|
||||||
|
match body {
|
||||||
|
Value::Binary { val, .. } => send_cancellable_request(
|
||||||
|
&request_url,
|
||||||
|
Box::new(move || req.send_bytes(&val)),
|
||||||
|
ctrl_c,
|
||||||
|
),
|
||||||
|
Value::String { .. } if body_type == BodyType::Json => {
|
||||||
|
let data = value_to_json_value(&body)?;
|
||||||
|
send_cancellable_request(&request_url, Box::new(|| req.send_json(data)), ctrl_c)
|
||||||
|
}
|
||||||
|
Value::String { val, .. } => send_cancellable_request(
|
||||||
|
&request_url,
|
||||||
|
Box::new(move || req.send_string(&val)),
|
||||||
|
ctrl_c,
|
||||||
|
),
|
||||||
|
Value::Record { .. } if body_type == BodyType::Json => {
|
||||||
|
let data = value_to_json_value(&body)?;
|
||||||
|
send_cancellable_request(&request_url, Box::new(|| req.send_json(data)), ctrl_c)
|
||||||
|
}
|
||||||
|
Value::Record { val, .. } if body_type == BodyType::Form => {
|
||||||
|
let mut data: Vec<(String, String)> = Vec::with_capacity(val.len());
|
||||||
|
|
||||||
|
for (col, val) in val.into_owned() {
|
||||||
|
data.push((col, val.coerce_into_string()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
let request_fn = move || {
|
||||||
|
// coerce `data` into a shape that send_form() is happy with
|
||||||
|
let data = data
|
||||||
|
.iter()
|
||||||
|
.map(|(a, b)| (a.as_str(), b.as_str()))
|
||||||
|
.collect::<Vec<(&str, &str)>>();
|
||||||
|
req.send_form(&data)
|
||||||
|
};
|
||||||
|
send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c)
|
||||||
|
}
|
||||||
|
Value::List { vals, .. } if body_type == BodyType::Form => {
|
||||||
|
if vals.len() % 2 != 0 {
|
||||||
|
return Err(ShellErrorOrRequestError::ShellError(ShellError::IOError {
|
||||||
|
msg: "unsupported body input".into(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = vals
|
||||||
|
.chunks(2)
|
||||||
|
.map(|it| Ok((it[0].coerce_string()?, it[1].coerce_string()?)))
|
||||||
|
.collect::<Result<Vec<(String, String)>, ShellErrorOrRequestError>>()?;
|
||||||
|
|
||||||
|
let request_fn = move || {
|
||||||
|
// coerce `data` into a shape that send_form() is happy with
|
||||||
|
let data = data
|
||||||
|
.iter()
|
||||||
|
.map(|(a, b)| (a.as_str(), b.as_str()))
|
||||||
|
.collect::<Vec<(&str, &str)>>();
|
||||||
|
req.send_form(&data)
|
||||||
|
};
|
||||||
|
send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c)
|
||||||
|
}
|
||||||
|
Value::List { .. } if body_type == BodyType::Json => {
|
||||||
|
let data = value_to_json_value(&body)?;
|
||||||
|
send_cancellable_request(&request_url, Box::new(|| req.send_json(data)), ctrl_c)
|
||||||
|
}
|
||||||
|
_ => Err(ShellErrorOrRequestError::ShellError(ShellError::IOError {
|
||||||
msg: "unsupported body input".into(),
|
msg: "unsupported body input".into(),
|
||||||
}));
|
})),
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = vals
|
|
||||||
.chunks(2)
|
|
||||||
.map(|it| Ok((it[0].coerce_string()?, it[1].coerce_string()?)))
|
|
||||||
.collect::<Result<Vec<(String, String)>, ShellErrorOrRequestError>>()?;
|
|
||||||
|
|
||||||
let request_fn = move || {
|
|
||||||
// coerce `data` into a shape that send_form() is happy with
|
|
||||||
let data = data
|
|
||||||
.iter()
|
|
||||||
.map(|(a, b)| (a.as_str(), b.as_str()))
|
|
||||||
.collect::<Vec<(&str, &str)>>();
|
|
||||||
request.send_form(&data)
|
|
||||||
};
|
|
||||||
send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c)
|
|
||||||
}
|
}
|
||||||
Value::List { .. } if body_type == BodyType::Json => {
|
|
||||||
let data = value_to_json_value(&body)?;
|
|
||||||
send_cancellable_request(&request_url, Box::new(|| request.send_json(data)), ctrl_c)
|
|
||||||
}
|
|
||||||
_ => Err(ShellErrorOrRequestError::ShellError(ShellError::IOError {
|
|
||||||
msg: "unsupported body input".into(),
|
|
||||||
})),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,6 +330,61 @@ fn send_cancellable_request(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper method used to make blocking HTTP request calls cancellable with ctrl+c
|
||||||
|
// ureq functions can block for a long time (default 30s?) while attempting to make an HTTP connection
|
||||||
|
fn send_cancellable_request_bytes(
|
||||||
|
request_url: &str,
|
||||||
|
request: Request,
|
||||||
|
byte_stream: ByteStream,
|
||||||
|
ctrl_c: Option<Arc<AtomicBool>>,
|
||||||
|
) -> Result<Response, ShellErrorOrRequestError> {
|
||||||
|
let (tx, rx) = mpsc::channel::<Result<Response, ShellErrorOrRequestError>>();
|
||||||
|
let request_url_string = request_url.to_string();
|
||||||
|
|
||||||
|
// Make the blocking request on a background thread...
|
||||||
|
std::thread::Builder::new()
|
||||||
|
.name("HTTP requester".to_string())
|
||||||
|
.spawn(move || {
|
||||||
|
let ret = byte_stream
|
||||||
|
.reader()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ShellErrorOrRequestError::ShellError(ShellError::GenericError {
|
||||||
|
error: "Could not read byte stream".to_string(),
|
||||||
|
msg: "".into(),
|
||||||
|
span: None,
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.and_then(|reader| {
|
||||||
|
request.send(reader).map_err(|e| {
|
||||||
|
ShellErrorOrRequestError::RequestError(request_url_string, Box::new(e))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// may fail if the user has cancelled the operation
|
||||||
|
let _ = tx.send(ret);
|
||||||
|
})
|
||||||
|
.map_err(ShellError::from)?;
|
||||||
|
|
||||||
|
// ...and poll the channel for responses
|
||||||
|
loop {
|
||||||
|
if nu_utils::ctrl_c::was_pressed(&ctrl_c) {
|
||||||
|
// Return early and give up on the background thread. The connection will either time out or be disconnected
|
||||||
|
return Err(ShellErrorOrRequestError::ShellError(
|
||||||
|
ShellError::InterruptedByUser { span: None },
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 100ms wait time chosen arbitrarily
|
||||||
|
match rx.recv_timeout(Duration::from_millis(100)) {
|
||||||
|
Ok(result) => return result,
|
||||||
|
Err(RecvTimeoutError::Timeout) => continue,
|
||||||
|
Err(RecvTimeoutError::Disconnected) => panic!("http response channel disconnected"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn request_set_timeout(
|
pub fn request_set_timeout(
|
||||||
timeout: Option<Value>,
|
timeout: Option<Value>,
|
||||||
mut request: Request,
|
mut request: Request,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::network::http::client::{
|
use crate::network::http::client::{
|
||||||
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
|
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
|
||||||
request_add_authorization_header, request_add_custom_headers, request_handle_response,
|
request_add_authorization_header, request_add_custom_headers, request_handle_response,
|
||||||
request_set_timeout, send_request, RequestFlags,
|
request_set_timeout, send_request, HttpBody, RequestFlags,
|
||||||
};
|
};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ impl Command for SubCommand {
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("http delete")
|
Signature::build("http delete")
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Any)])
|
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.required(
|
.required(
|
||||||
"URL",
|
"URL",
|
||||||
|
@ -132,6 +132,11 @@ impl Command for SubCommand {
|
||||||
"http delete --content-type application/json --data { field: value } https://www.example.com",
|
"http delete --content-type application/json --data { field: value } https://www.example.com",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Perform an HTTP delete with JSON content from a pipeline to example.com",
|
||||||
|
example: "open foo.json | http delete https://www.example.com",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,7 +144,7 @@ impl Command for SubCommand {
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
url: Value,
|
url: Value,
|
||||||
headers: Option<Value>,
|
headers: Option<Value>,
|
||||||
data: Option<Value>,
|
data: HttpBody,
|
||||||
content_type: Option<String>,
|
content_type: Option<String>,
|
||||||
raw: bool,
|
raw: bool,
|
||||||
insecure: bool,
|
insecure: bool,
|
||||||
|
@ -155,13 +160,27 @@ fn run_delete(
|
||||||
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 (data, maybe_metadata) = call
|
||||||
|
.get_flag::<Value>(engine_state, stack, "data")?
|
||||||
|
.map(|v| (HttpBody::Value(v), None))
|
||||||
|
.unwrap_or_else(|| match input {
|
||||||
|
PipelineData::Value(v, metadata) => (HttpBody::Value(v), metadata),
|
||||||
|
PipelineData::ByteStream(byte_stream, metadata) => {
|
||||||
|
(HttpBody::ByteStream(byte_stream), metadata)
|
||||||
|
}
|
||||||
|
_ => (HttpBody::None, None),
|
||||||
|
});
|
||||||
|
let content_type = call
|
||||||
|
.get_flag(engine_state, stack, "content-type")?
|
||||||
|
.or_else(|| maybe_metadata.and_then(|m| m.content_type));
|
||||||
|
|
||||||
let args = Arguments {
|
let args = Arguments {
|
||||||
url: call.req(engine_state, stack, 0)?,
|
url: call.req(engine_state, stack, 0)?,
|
||||||
headers: call.get_flag(engine_state, stack, "headers")?,
|
headers: call.get_flag(engine_state, stack, "headers")?,
|
||||||
data: call.get_flag(engine_state, stack, "data")?,
|
data,
|
||||||
content_type: call.get_flag(engine_state, stack, "content-type")?,
|
content_type,
|
||||||
raw: call.has_flag(engine_state, stack, "raw")?,
|
raw: call.has_flag(engine_state, stack, "raw")?,
|
||||||
insecure: call.has_flag(engine_state, stack, "insecure")?,
|
insecure: call.has_flag(engine_state, stack, "insecure")?,
|
||||||
user: call.get_flag(engine_state, stack, "user")?,
|
user: call.get_flag(engine_state, stack, "user")?,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user