mirror of
https://github.com/nushell/nushell.git
synced 2025-05-06 07:52:57 +00:00
Compare commits
No commits in common. "main" and "0.103.0" have entirely different histories.
40
.github/labeler.yml
vendored
40
.github/labeler.yml
vendored
@ -1,40 +0,0 @@
|
|||||||
# A bot for automatically labelling pull requests
|
|
||||||
# See https://github.com/actions/labeler
|
|
||||||
|
|
||||||
dataframe:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- crates/nu_plugin_polars/**
|
|
||||||
|
|
||||||
std-library:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- crates/nu-std/**
|
|
||||||
|
|
||||||
ci:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- .github/workflows/**
|
|
||||||
|
|
||||||
|
|
||||||
LSP:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- crates/nu-lsp/**
|
|
||||||
|
|
||||||
parser:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- crates/nu-parser/**
|
|
||||||
|
|
||||||
pr:plugins:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
# plugins API
|
|
||||||
- crates/nu-plugin/**
|
|
||||||
- crates/nu-plugin-core/**
|
|
||||||
- crates/nu-plugin-engine/**
|
|
||||||
- crates/nu-plugin-protocol/**
|
|
||||||
- crates/nu-plugin-test-support/**
|
|
||||||
# specific plugins (like polars)
|
|
||||||
- crates/nu_plugin_*/**
|
|
19
.github/workflows/labels.yml
vendored
19
.github/workflows/labels.yml
vendored
@ -1,19 +0,0 @@
|
|||||||
# Automatically labels PRs based on the configuration file
|
|
||||||
# you are probably looking for 👉 `.github/labeler.yml`
|
|
||||||
name: Label PRs
|
|
||||||
|
|
||||||
on:
|
|
||||||
- pull_request_target
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
triage:
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository_owner == 'nushell'
|
|
||||||
steps:
|
|
||||||
- uses: actions/labeler@v5
|
|
||||||
with:
|
|
||||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
|
||||||
sync-labels: true
|
|
7
.github/workflows/nightly-build.yml
vendored
7
.github/workflows/nightly-build.yml
vendored
@ -8,7 +8,6 @@
|
|||||||
name: Nightly Build
|
name: Nightly Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- nightly # Just for test purpose only with the nightly repo
|
- nightly # Just for test purpose only with the nightly repo
|
||||||
@ -40,7 +39,7 @@ jobs:
|
|||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3
|
||||||
if: github.repository == 'nushell/nightly'
|
if: github.repository == 'nushell/nightly'
|
||||||
with:
|
with:
|
||||||
version: 0.103.0
|
version: 0.101.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
|
||||||
@ -140,7 +139,7 @@ jobs:
|
|||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3
|
||||||
with:
|
with:
|
||||||
version: 0.103.0
|
version: 0.101.0
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
@ -198,7 +197,7 @@ jobs:
|
|||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3
|
||||||
with:
|
with:
|
||||||
version: 0.103.0
|
version: 0.101.0
|
||||||
|
|
||||||
# Keep the last a few releases
|
# Keep the last a few releases
|
||||||
- name: Delete Older Releases
|
- name: Delete Older Releases
|
||||||
|
14
.github/workflows/release-pkg.nu
vendored
14
.github/workflows/release-pkg.nu
vendored
@ -117,14 +117,14 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
|
|||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
# Build for Windows without static-link-openssl feature
|
# Build for Windows without static-link-openssl feature
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
if $os =~ 'windows' {
|
if $os in ['windows-latest'] {
|
||||||
cargo-build-nu
|
cargo-build-nu
|
||||||
}
|
}
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
# Prepare for the release archive
|
# Prepare for the release archive
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
let suffix = if $os =~ 'windows' { '.exe' }
|
let suffix = if $os == 'windows-latest' { '.exe' }
|
||||||
# nu, nu_plugin_* were all included
|
# nu, nu_plugin_* were all included
|
||||||
let executable = $'target/($target)/release/($bin)*($suffix)'
|
let executable = $'target/($target)/release/($bin)*($suffix)'
|
||||||
print $'Current executable file: ($executable)'
|
print $'Current executable file: ($executable)'
|
||||||
@ -148,10 +148,10 @@ For more information, refer to https://www.nushell.sh/book/plugins.html
|
|||||||
[LICENSE ...(glob $executable)] | each {|it| cp -rv $it $dist } | flatten
|
[LICENSE ...(glob $executable)] | each {|it| cp -rv $it $dist } | flatten
|
||||||
|
|
||||||
print $'(char nl)Check binary release version detail:'; hr-line
|
print $'(char nl)Check binary release version detail:'; hr-line
|
||||||
let ver = if $os =~ 'windows' {
|
let ver = if $os == 'windows-latest' {
|
||||||
(do -i { .\output\nu.exe -c 'version' }) | default '' | str join
|
(do -i { .\output\nu.exe -c 'version' }) | str join
|
||||||
} else {
|
} else {
|
||||||
(do -i { ./output/nu -c 'version' }) | default '' | str join
|
(do -i { ./output/nu -c 'version' }) | str join
|
||||||
}
|
}
|
||||||
if ($ver | str trim | is-empty) {
|
if ($ver | str trim | is-empty) {
|
||||||
print $'(ansi r)Incompatible Nu binary: The binary cross compiled is not runnable on current arch...(ansi reset)'
|
print $'(ansi r)Incompatible Nu binary: The binary cross compiled is not runnable on current arch...(ansi reset)'
|
||||||
@ -177,7 +177,7 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
|
|||||||
# REF: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
|
# REF: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
|
||||||
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
|
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
|
||||||
|
|
||||||
} else if $os =~ 'windows' {
|
} else if $os == 'windows-latest' {
|
||||||
|
|
||||||
let releaseStem = $'($bin)-($version)-($target)'
|
let releaseStem = $'($bin)-($version)-($target)'
|
||||||
|
|
||||||
@ -221,7 +221,7 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def 'cargo-build-nu' [] {
|
def 'cargo-build-nu' [] {
|
||||||
if $os =~ 'windows' {
|
if $os == 'windows-latest' {
|
||||||
cargo build --release --all --target $target
|
cargo build --release --all --target $target
|
||||||
} else {
|
} else {
|
||||||
cargo build --release --all --target $target --features=static-link-openssl
|
cargo build --release --all --target $target --features=static-link-openssl
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -89,7 +89,7 @@ jobs:
|
|||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3
|
||||||
with:
|
with:
|
||||||
version: 0.103.0
|
version: 0.101.0
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@ -10,4 +10,4 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Check spelling
|
- name: Check spelling
|
||||||
uses: crate-ci/typos@v1.31.1
|
uses: crate-ci/typos@v1.29.10
|
||||||
|
507
Cargo.lock
generated
507
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
86
Cargo.toml
86
Cargo.toml
@ -10,8 +10,8 @@ homepage = "https://www.nushell.sh"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu"
|
name = "nu"
|
||||||
repository = "https://github.com/nushell/nushell"
|
repository = "https://github.com/nushell/nushell"
|
||||||
rust-version = "1.84.1"
|
rust-version = "1.83.0"
|
||||||
version = "0.104.1"
|
version = "0.103.0"
|
||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
@ -66,12 +66,12 @@ alphanumeric-sort = "1.5"
|
|||||||
ansi-str = "0.8"
|
ansi-str = "0.8"
|
||||||
anyhow = "1.0.82"
|
anyhow = "1.0.82"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
bracoxide = "0.1.6"
|
bracoxide = "0.1.5"
|
||||||
brotli = "7.0"
|
brotli = "7.0"
|
||||||
byteorder = "1.5"
|
byteorder = "1.5"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytesize = "1.3.3"
|
bytesize = "1.3.1"
|
||||||
calamine = "0.27"
|
calamine = "0.26.1"
|
||||||
chardetng = "0.1.17"
|
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"
|
||||||
@ -91,8 +91,8 @@ fancy-regex = "0.14"
|
|||||||
filesize = "0.2"
|
filesize = "0.2"
|
||||||
filetime = "0.2"
|
filetime = "0.2"
|
||||||
heck = "0.5.0"
|
heck = "0.5.0"
|
||||||
human-date-parser = "0.3.0"
|
human-date-parser = "0.2.0"
|
||||||
indexmap = "2.9"
|
indexmap = "2.7"
|
||||||
indicatif = "0.17"
|
indicatif = "0.17"
|
||||||
interprocess = "2.2.0"
|
interprocess = "2.2.0"
|
||||||
is_executable = "1.0"
|
is_executable = "1.0"
|
||||||
@ -110,7 +110,7 @@ md5 = { version = "0.10", package = "md-5" }
|
|||||||
miette = "7.5"
|
miette = "7.5"
|
||||||
mime = "0.3.17"
|
mime = "0.3.17"
|
||||||
mime_guess = "2.0"
|
mime_guess = "2.0"
|
||||||
mockito = { version = "1.7", default-features = false }
|
mockito = { version = "1.6", default-features = false }
|
||||||
multipart-rs = "0.1.13"
|
multipart-rs = "0.1.13"
|
||||||
native-tls = "0.2"
|
native-tls = "0.2"
|
||||||
nix = { version = "0.29", default-features = false }
|
nix = { version = "0.29", default-features = false }
|
||||||
@ -135,22 +135,22 @@ quick-xml = "0.37.0"
|
|||||||
quickcheck = "1.0"
|
quickcheck = "1.0"
|
||||||
quickcheck_macros = "1.0"
|
quickcheck_macros = "1.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
rand = "0.9"
|
rand = "0.8"
|
||||||
getrandom = "0.2" # pick same version that rand requires
|
getrandom = "0.2" # pick same version that rand requires
|
||||||
rand_chacha = "0.9"
|
rand_chacha = "0.3.1"
|
||||||
ratatui = "0.29"
|
ratatui = "0.29"
|
||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
reedline = "0.40.0"
|
reedline = "0.39.0"
|
||||||
rmp = "0.8"
|
rmp = "0.8"
|
||||||
rmp-serde = "1.3"
|
rmp-serde = "1.3"
|
||||||
roxmltree = "0.20"
|
roxmltree = "0.20"
|
||||||
rstest = { version = "0.23", default-features = false }
|
rstest = { version = "0.23", default-features = false }
|
||||||
rstest_reuse = "0.7"
|
rstest_reuse = "0.7"
|
||||||
rusqlite = "0.31"
|
rusqlite = "0.31"
|
||||||
rust-embed = "8.7.0"
|
rust-embed = "8.6.0"
|
||||||
scopeguard = { version = "1.2.0" }
|
scopeguard = { version = "1.2.0" }
|
||||||
serde = { version = "1.0" }
|
serde = { version = "1.0" }
|
||||||
serde_json = "1.0.97"
|
serde_json = "1.0"
|
||||||
serde_urlencoded = "0.7.1"
|
serde_urlencoded = "0.7.1"
|
||||||
serde_yaml = "0.9.33"
|
serde_yaml = "0.9.33"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
@ -161,24 +161,24 @@ syn = "2.0"
|
|||||||
sysinfo = "0.33"
|
sysinfo = "0.33"
|
||||||
tabled = { version = "0.17.0", default-features = false }
|
tabled = { version = "0.17.0", default-features = false }
|
||||||
tempfile = "3.15"
|
tempfile = "3.15"
|
||||||
titlecase = "3.5"
|
titlecase = "3.4"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
trash = "5.2"
|
trash = "5.2"
|
||||||
update-informer = { version = "1.2.0", default-features = false, features = ["github", "native-tls", "ureq"] }
|
update-informer = { version = "1.2.0", default-features = false, features = ["github", "native-tls", "ureq"] }
|
||||||
umask = "2.1"
|
umask = "2.1"
|
||||||
unicode-segmentation = "1.12"
|
unicode-segmentation = "1.12"
|
||||||
unicode-width = "0.2"
|
unicode-width = "0.2"
|
||||||
ureq = { version = "2.12", default-features = false, features = ["socks-proxy"] }
|
ureq = { version = "2.12", default-features = false }
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
uu_cp = "0.0.30"
|
uu_cp = "0.0.29"
|
||||||
uu_mkdir = "0.0.30"
|
uu_mkdir = "0.0.29"
|
||||||
uu_mktemp = "0.0.30"
|
uu_mktemp = "0.0.29"
|
||||||
uu_mv = "0.0.30"
|
uu_mv = "0.0.29"
|
||||||
uu_touch = "0.0.30"
|
uu_touch = "0.0.29"
|
||||||
uu_whoami = "0.0.30"
|
uu_whoami = "0.0.29"
|
||||||
uu_uname = "0.0.30"
|
uu_uname = "0.0.29"
|
||||||
uucore = "0.0.30"
|
uucore = "0.0.29"
|
||||||
uuid = "1.16.0"
|
uuid = "1.12.0"
|
||||||
v_htmlescape = "0.15.0"
|
v_htmlescape = "0.15.0"
|
||||||
wax = "0.6"
|
wax = "0.6"
|
||||||
web-time = "1.1.0"
|
web-time = "1.1.0"
|
||||||
@ -197,22 +197,22 @@ unchecked_duration_subtraction = "warn"
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cli = { path = "./crates/nu-cli", version = "0.104.1" }
|
nu-cli = { path = "./crates/nu-cli", version = "0.103.0" }
|
||||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.104.1" }
|
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.103.0" }
|
||||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.104.1" }
|
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.103.0" }
|
||||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.104.1", optional = true }
|
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.103.0", optional = true }
|
||||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.104.1" }
|
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.103.0" }
|
||||||
nu-command = { path = "./crates/nu-command", version = "0.104.1" }
|
nu-command = { path = "./crates/nu-command", version = "0.103.0" }
|
||||||
nu-engine = { path = "./crates/nu-engine", version = "0.104.1" }
|
nu-engine = { path = "./crates/nu-engine", version = "0.103.0" }
|
||||||
nu-explore = { path = "./crates/nu-explore", version = "0.104.1" }
|
nu-explore = { path = "./crates/nu-explore", version = "0.103.0" }
|
||||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.104.1" }
|
nu-lsp = { path = "./crates/nu-lsp/", version = "0.103.0" }
|
||||||
nu-parser = { path = "./crates/nu-parser", version = "0.104.1" }
|
nu-parser = { path = "./crates/nu-parser", version = "0.103.0" }
|
||||||
nu-path = { path = "./crates/nu-path", version = "0.104.1" }
|
nu-path = { path = "./crates/nu-path", version = "0.103.0" }
|
||||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.104.1" }
|
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.103.0" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.104.1" }
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.103.0" }
|
||||||
nu-std = { path = "./crates/nu-std", version = "0.104.1" }
|
nu-std = { path = "./crates/nu-std", version = "0.103.0" }
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.104.1" }
|
nu-system = { path = "./crates/nu-system", version = "0.103.0" }
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.104.1" }
|
nu-utils = { path = "./crates/nu-utils", version = "0.103.0" }
|
||||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
@ -241,9 +241,9 @@ nix = { workspace = true, default-features = false, features = [
|
|||||||
] }
|
] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.104.1" }
|
nu-test-support = { path = "./crates/nu-test-support", version = "0.103.0" }
|
||||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.104.1" }
|
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.103.0" }
|
||||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.104.1" }
|
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.103.0" }
|
||||||
assert_cmd = "2.0"
|
assert_cmd = "2.0"
|
||||||
dirs = { workspace = true }
|
dirs = { workspace = true }
|
||||||
tango-bench = "0.6"
|
tango-bench = "0.6"
|
||||||
|
@ -222,7 +222,6 @@ Please submit an issue or PR to be added to this list.
|
|||||||
- [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)
|
- [x-cmd](https://x-cmd.com/mod/nu)
|
||||||
- [vfox](https://github.com/version-fox/vfox)
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
@ -5,29 +5,28 @@ 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.104.1"
|
version = "0.103.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.104.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.103.0" }
|
||||||
nu-command = { path = "../nu-command", version = "0.104.1" }
|
nu-command = { path = "../nu-command", version = "0.103.0" }
|
||||||
nu-std = { path = "../nu-std", version = "0.104.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.103.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.104.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.104.1" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.103.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.104.1", features = ["os"] }
|
nu-engine = { path = "../nu-engine", version = "0.103.0", features = ["os"] }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.104.1" }
|
nu-glob = { path = "../nu-glob", version = "0.103.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.104.1" }
|
nu-path = { path = "../nu-path", version = "0.103.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.104.1" }
|
nu-parser = { path = "../nu-parser", version = "0.103.0" }
|
||||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.104.1", optional = true }
|
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.103.0", optional = true }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.104.1", features = ["os"] }
|
nu-protocol = { path = "../nu-protocol", version = "0.103.0", features = ["os"] }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.104.1" }
|
nu-utils = { path = "../nu-utils", version = "0.103.0" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.104.1" }
|
nu-color-config = { path = "../nu-color-config", version = "0.103.0" }
|
||||||
nu-ansi-term = { workspace = true }
|
nu-ansi-term = { workspace = true }
|
||||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
|
@ -105,9 +105,10 @@ impl Command for History {
|
|||||||
.ok()
|
.ok()
|
||||||
})
|
})
|
||||||
.map(move |entries| {
|
.map(move |entries| {
|
||||||
entries.into_iter().enumerate().map(move |(idx, entry)| {
|
entries
|
||||||
create_sqlite_history_record(idx, entry, long, head)
|
.into_iter()
|
||||||
})
|
.enumerate()
|
||||||
|
.map(move |(idx, entry)| create_history_record(idx, entry, long, head))
|
||||||
})
|
})
|
||||||
.ok_or(IoError::new(
|
.ok_or(IoError::new(
|
||||||
std::io::ErrorKind::NotFound,
|
std::io::ErrorKind::NotFound,
|
||||||
@ -139,7 +140,7 @@ impl Command for History {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_sqlite_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span) -> Value {
|
fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span) -> Value {
|
||||||
//1. Format all the values
|
//1. Format all the values
|
||||||
//2. Create a record of either short or long columns and values
|
//2. Create a record of either short or long columns and values
|
||||||
|
|
||||||
@ -150,8 +151,11 @@ fn create_sqlite_history_record(idx: usize, entry: HistoryItem, long: bool, head
|
|||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
head,
|
head,
|
||||||
);
|
);
|
||||||
let start_timestamp_value = Value::date(
|
let start_timestamp_value = Value::string(
|
||||||
entry.start_timestamp.unwrap_or_default().fixed_offset(),
|
entry
|
||||||
|
.start_timestamp
|
||||||
|
.map(|time| time.to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
head,
|
head,
|
||||||
);
|
);
|
||||||
let command_value = Value::string(entry.command_line, head);
|
let command_value = Value::string(entry.command_line, head);
|
||||||
|
@ -26,7 +26,7 @@ impl Command for HistoryImport {
|
|||||||
|
|
||||||
fn extra_description(&self) -> &str {
|
fn extra_description(&self) -> &str {
|
||||||
r#"Can import history from input, either successive command lines or more detailed records. If providing records, available fields are:
|
r#"Can import history from input, either successive command lines or more detailed records. If providing records, available fields are:
|
||||||
command, start_timestamp, hostname, cwd, duration, exit_status.
|
command_line, id, start_timestamp, hostname, cwd, duration, exit_status.
|
||||||
|
|
||||||
If no input is provided, will import all history items from existing history in the other format: if current history is stored in sqlite, it will store it in plain text and vice versa.
|
If no input is provided, will import all history items from existing history in the other format: if current history is stored in sqlite, it will store it in plain text and vice versa.
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ impl Completer for AttributeCompletion {
|
|||||||
let attr_commands =
|
let attr_commands =
|
||||||
working_set.find_commands_by_predicate(|s| s.starts_with(b"attr "), true);
|
working_set.find_commands_by_predicate(|s| s.starts_with(b"attr "), true);
|
||||||
|
|
||||||
for (decl_id, name, desc, ty) in attr_commands {
|
for (name, desc, ty) in attr_commands {
|
||||||
let name = name.strip_prefix(b"attr ").unwrap_or(&name);
|
let name = name.strip_prefix(b"attr ").unwrap_or(&name);
|
||||||
matcher.add_semantic_suggestion(SemanticSuggestion {
|
matcher.add_semantic_suggestion(SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
@ -41,7 +41,7 @@ impl Completer for AttributeCompletion {
|
|||||||
},
|
},
|
||||||
append_whitespace: false,
|
append_whitespace: false,
|
||||||
},
|
},
|
||||||
kind: Some(SuggestionKind::Command(ty, Some(decl_id))),
|
kind: Some(SuggestionKind::Command(ty)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ impl Completer for AttributableCompletion {
|
|||||||
},
|
},
|
||||||
append_whitespace: false,
|
append_whitespace: false,
|
||||||
},
|
},
|
||||||
kind: Some(SuggestionKind::Command(cmd.command_type(), None)),
|
kind: Some(SuggestionKind::Command(cmd.command_type())),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::completions::CompletionOptions;
|
use crate::completions::CompletionOptions;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{Stack, StateWorkingSet},
|
engine::{Stack, StateWorkingSet},
|
||||||
DeclId, Span,
|
Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ pub struct SemanticSuggestion {
|
|||||||
// TODO: think about name: maybe suggestion context?
|
// TODO: think about name: maybe suggestion context?
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum SuggestionKind {
|
pub enum SuggestionKind {
|
||||||
Command(nu_protocol::engine::CommandType, Option<DeclId>),
|
Command(nu_protocol::engine::CommandType),
|
||||||
Value(nu_protocol::Type),
|
Value(nu_protocol::Type),
|
||||||
CellPath,
|
CellPath,
|
||||||
Directory,
|
Directory,
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind};
|
use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind};
|
||||||
use nu_engine::{column::get_columns, eval_variable};
|
use nu_engine::{column::get_columns, eval_variable};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
@ -19,14 +17,14 @@ pub struct CellPathCompletion<'a> {
|
|||||||
|
|
||||||
fn prefix_from_path_member(member: &PathMember, pos: usize) -> (String, Span) {
|
fn prefix_from_path_member(member: &PathMember, pos: usize) -> (String, Span) {
|
||||||
let (prefix_str, start) = match member {
|
let (prefix_str, start) = match member {
|
||||||
PathMember::String { val, span, .. } => (val, span.start),
|
PathMember::String { val, span, .. } => (val.clone(), span.start),
|
||||||
PathMember::Int { val, span, .. } => (&val.to_string(), span.start),
|
PathMember::Int { val, span, .. } => (val.to_string(), span.start),
|
||||||
};
|
};
|
||||||
let prefix_str = prefix_str.get(..pos + 1 - start).unwrap_or(prefix_str);
|
let prefix_str = prefix_str
|
||||||
// strip wrapping quotes
|
.get(..pos + 1 - start)
|
||||||
let quotations = ['"', '\'', '`'];
|
.map(str::to_string)
|
||||||
let prefix_str = prefix_str.strip_prefix(quotations).unwrap_or(prefix_str);
|
.unwrap_or(prefix_str);
|
||||||
(prefix_str.to_string(), Span::new(start, pos + 1))
|
(prefix_str, Span::new(start, pos + 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completer for CellPathCompletion<'_> {
|
impl Completer for CellPathCompletion<'_> {
|
||||||
@ -103,35 +101,21 @@ pub(crate) fn eval_cell_path(
|
|||||||
} else {
|
} else {
|
||||||
eval_constant(working_set, head)
|
eval_constant(working_set, head)
|
||||||
}?;
|
}?;
|
||||||
head_value
|
head_value.follow_cell_path(path_members, false)
|
||||||
.follow_cell_path(path_members, false)
|
|
||||||
.map(Cow::into_owned)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_suggestions_by_value(
|
fn get_suggestions_by_value(
|
||||||
value: &Value,
|
value: &Value,
|
||||||
current_span: reedline::Span,
|
current_span: reedline::Span,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let to_suggestion = |s: String, v: Option<&Value>| {
|
let to_suggestion = |s: String, v: Option<&Value>| SemanticSuggestion {
|
||||||
// Check if the string needs quoting
|
|
||||||
let value = if s.is_empty()
|
|
||||||
|| s.chars()
|
|
||||||
.any(|c: char| !(c.is_ascii_alphabetic() || ['_', '-'].contains(&c)))
|
|
||||||
{
|
|
||||||
format!("{:?}", s)
|
|
||||||
} else {
|
|
||||||
s
|
|
||||||
};
|
|
||||||
|
|
||||||
SemanticSuggestion {
|
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value,
|
value: s,
|
||||||
span: current_span,
|
span: current_span,
|
||||||
description: v.map(|v| v.get_type().to_string()),
|
description: v.map(|v| v.get_type().to_string()),
|
||||||
..Suggestion::default()
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
kind: Some(SuggestionKind::CellPath),
|
kind: Some(SuggestionKind::CellPath),
|
||||||
}
|
|
||||||
};
|
};
|
||||||
match value {
|
match value {
|
||||||
Value::Record { val, .. } => val
|
Value::Record { val, .. } => val
|
||||||
|
@ -75,10 +75,7 @@ impl CommandCompletion {
|
|||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
kind: Some(SuggestionKind::Command(
|
kind: Some(SuggestionKind::Command(CommandType::External)),
|
||||||
CommandType::External,
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -115,7 +112,7 @@ impl Completer for CommandCompletion {
|
|||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
for (decl_id, name, description, typ) in filtered_commands {
|
for (name, description, typ) in filtered_commands {
|
||||||
let name = String::from_utf8_lossy(&name);
|
let name = String::from_utf8_lossy(&name);
|
||||||
internal_suggs.insert(
|
internal_suggs.insert(
|
||||||
name.to_string(),
|
name.to_string(),
|
||||||
@ -127,7 +124,7 @@ impl Completer for CommandCompletion {
|
|||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
..Suggestion::default()
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
kind: Some(SuggestionKind::Command(typ, Some(decl_id))),
|
kind: Some(SuggestionKind::Command(typ)),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
use crate::completions::{
|
use crate::completions::{
|
||||||
base::{SemanticSuggestion, SuggestionKind},
|
|
||||||
AttributableCompletion, AttributeCompletion, CellPathCompletion, CommandCompletion, Completer,
|
AttributableCompletion, AttributeCompletion, CellPathCompletion, CommandCompletion, Completer,
|
||||||
CompletionOptions, CustomCompletion, DirectoryCompletion, DotNuCompletion,
|
CompletionOptions, CustomCompletion, DirectoryCompletion, DotNuCompletion, FileCompletion,
|
||||||
ExportableCompletion, FileCompletion, FlagCompletion, OperatorCompletion, VariableCompletion,
|
FlagCompletion, OperatorCompletion, VariableCompletion,
|
||||||
};
|
};
|
||||||
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;
|
||||||
use nu_parser::{flatten_expression, parse, parse_module_file_or_dir};
|
use nu_parser::{flatten_expression, parse};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Argument, Block, Expr, Expression, FindMapResult, ListItem, Traverse},
|
ast::{Argument, Block, Expr, Expression, FindMapResult, Traverse},
|
||||||
debugger::WithoutDebug,
|
debugger::WithoutDebug,
|
||||||
engine::{Closure, EngineState, Stack, StateWorkingSet},
|
engine::{Closure, EngineState, Stack, StateWorkingSet},
|
||||||
PipelineData, Span, Type, Value,
|
PipelineData, Span, Type, Value,
|
||||||
};
|
};
|
||||||
use reedline::{Completer as ReedlineCompleter, Suggestion};
|
use reedline::{Completer as ReedlineCompleter, Suggestion};
|
||||||
use std::sync::Arc;
|
use std::{str, sync::Arc};
|
||||||
|
|
||||||
|
use super::base::{SemanticSuggestion, SuggestionKind};
|
||||||
|
|
||||||
/// Used as the function `f` in find_map Traverse
|
/// Used as the function `f` in find_map Traverse
|
||||||
///
|
///
|
||||||
@ -56,13 +57,8 @@ fn find_pipeline_element_by_position<'a>(
|
|||||||
Expr::FullCellPath(fcp) => fcp
|
Expr::FullCellPath(fcp) => fcp
|
||||||
.head
|
.head
|
||||||
.find_map(working_set, &closure)
|
.find_map(working_set, &closure)
|
||||||
|
.or(Some(expr))
|
||||||
.map(FindMapResult::Found)
|
.map(FindMapResult::Found)
|
||||||
// e.g. use std/util [<tab>
|
|
||||||
.or_else(|| {
|
|
||||||
(fcp.head.span.contains(pos) && matches!(fcp.head.expr, Expr::List(_)))
|
|
||||||
.then_some(FindMapResult::Continue)
|
|
||||||
})
|
|
||||||
.or(Some(FindMapResult::Found(expr)))
|
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
Expr::Var(_) => FindMapResult::Found(expr),
|
Expr::Var(_) => FindMapResult::Found(expr),
|
||||||
Expr::AttributeBlock(ab) => ab
|
Expr::AttributeBlock(ab) => ab
|
||||||
@ -131,18 +127,6 @@ struct Context<'a> {
|
|||||||
offset: usize,
|
offset: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For argument completion
|
|
||||||
struct PositionalArguments<'a> {
|
|
||||||
/// command name
|
|
||||||
command_head: &'a str,
|
|
||||||
/// indices of positional arguments
|
|
||||||
positional_arg_indices: Vec<usize>,
|
|
||||||
/// argument list
|
|
||||||
arguments: &'a [Argument],
|
|
||||||
/// expression of current argument
|
|
||||||
expr: &'a Expression,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Context<'_> {
|
impl Context<'_> {
|
||||||
fn new<'a>(
|
fn new<'a>(
|
||||||
working_set: &'a StateWorkingSet,
|
working_set: &'a StateWorkingSet,
|
||||||
@ -344,8 +328,7 @@ impl NuCompleter {
|
|||||||
// NOTE: the argument to complete is not necessarily the last one
|
// NOTE: the argument to complete is not necessarily the last one
|
||||||
// for lsp completion, we don't trim the text,
|
// for lsp completion, we don't trim the text,
|
||||||
// so that `def`s after pos can be completed
|
// so that `def`s after pos can be completed
|
||||||
let mut positional_arg_indices = Vec::new();
|
for arg in call.arguments.iter() {
|
||||||
for (arg_idx, arg) in call.arguments.iter().enumerate() {
|
|
||||||
let span = arg.span();
|
let span = arg.span();
|
||||||
if span.contains(pos) {
|
if span.contains(pos) {
|
||||||
// if customized completion specified, it has highest priority
|
// if customized completion specified, it has highest priority
|
||||||
@ -395,16 +378,10 @@ impl NuCompleter {
|
|||||||
Argument::Positional(_) if prefix == b"-" => flag_completion_helper(),
|
Argument::Positional(_) if prefix == b"-" => flag_completion_helper(),
|
||||||
// complete according to expression type and command head
|
// complete according to expression type and command head
|
||||||
Argument::Positional(expr) => {
|
Argument::Positional(expr) => {
|
||||||
let command_head = working_set.get_decl(call.decl_id).name();
|
let command_head = working_set.get_span_contents(call.head);
|
||||||
positional_arg_indices.push(arg_idx);
|
|
||||||
self.argument_completion_helper(
|
self.argument_completion_helper(
|
||||||
PositionalArguments {
|
|
||||||
command_head,
|
command_head,
|
||||||
positional_arg_indices,
|
|
||||||
arguments: &call.arguments,
|
|
||||||
expr,
|
expr,
|
||||||
},
|
|
||||||
pos,
|
|
||||||
&ctx,
|
&ctx,
|
||||||
suggestions.is_empty(),
|
suggestions.is_empty(),
|
||||||
)
|
)
|
||||||
@ -412,8 +389,6 @@ impl NuCompleter {
|
|||||||
_ => vec![],
|
_ => vec![],
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
} else if !matches!(arg, Argument::Named(_)) {
|
|
||||||
positional_arg_indices.push(arg_idx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -511,10 +486,9 @@ impl NuCompleter {
|
|||||||
externals: bool,
|
externals: bool,
|
||||||
strip: bool,
|
strip: bool,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let config = self.engine_state.get_config();
|
|
||||||
let mut command_completions = CommandCompletion {
|
let mut command_completions = CommandCompletion {
|
||||||
internals,
|
internals,
|
||||||
externals: !internals || (externals && config.completions.external.enable),
|
externals,
|
||||||
};
|
};
|
||||||
let (new_span, prefix) = strip_placeholder_if_any(working_set, &span, strip);
|
let (new_span, prefix) = strip_placeholder_if_any(working_set, &span, strip);
|
||||||
let ctx = Context::new(working_set, new_span, prefix, offset);
|
let ctx = Context::new(working_set, new_span, prefix, offset);
|
||||||
@ -523,97 +497,20 @@ impl NuCompleter {
|
|||||||
|
|
||||||
fn argument_completion_helper(
|
fn argument_completion_helper(
|
||||||
&self,
|
&self,
|
||||||
argument_info: PositionalArguments,
|
command_head: &[u8],
|
||||||
pos: usize,
|
expr: &Expression,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
need_fallback: bool,
|
need_fallback: bool,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let PositionalArguments {
|
|
||||||
command_head,
|
|
||||||
positional_arg_indices,
|
|
||||||
arguments,
|
|
||||||
expr,
|
|
||||||
} = argument_info;
|
|
||||||
// special commands
|
// special commands
|
||||||
match command_head {
|
match command_head {
|
||||||
// complete module file/directory
|
// complete module file/directory
|
||||||
"use" | "export use" | "overlay use" | "source-env"
|
// TODO: if module file already specified,
|
||||||
if positional_arg_indices.len() == 1 =>
|
|
||||||
{
|
|
||||||
return self.process_completion(
|
|
||||||
&mut DotNuCompletion {
|
|
||||||
std_virtual_path: command_head != "source-env",
|
|
||||||
},
|
|
||||||
ctx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// NOTE: if module file already specified,
|
|
||||||
// should parse it to get modules/commands/consts to complete
|
// should parse it to get modules/commands/consts to complete
|
||||||
"use" | "export use" => {
|
b"use" | b"export use" | b"overlay use" | b"source-env" => {
|
||||||
let Some(Argument::Positional(Expression {
|
return self.process_completion(&mut DotNuCompletion, ctx);
|
||||||
expr: Expr::String(module_name),
|
|
||||||
span,
|
|
||||||
..
|
|
||||||
})) = positional_arg_indices
|
|
||||||
.first()
|
|
||||||
.and_then(|i| arguments.get(*i))
|
|
||||||
else {
|
|
||||||
return vec![];
|
|
||||||
};
|
|
||||||
let module_name = module_name.as_bytes();
|
|
||||||
let (module_id, temp_working_set) = match ctx.working_set.find_module(module_name) {
|
|
||||||
Some(module_id) => (module_id, None),
|
|
||||||
None => {
|
|
||||||
let mut temp_working_set =
|
|
||||||
StateWorkingSet::new(ctx.working_set.permanent_state);
|
|
||||||
let Some(module_id) = parse_module_file_or_dir(
|
|
||||||
&mut temp_working_set,
|
|
||||||
module_name,
|
|
||||||
*span,
|
|
||||||
None,
|
|
||||||
) else {
|
|
||||||
return vec![];
|
|
||||||
};
|
|
||||||
(module_id, Some(temp_working_set))
|
|
||||||
}
|
}
|
||||||
};
|
b"which" => {
|
||||||
let mut exportable_completion = ExportableCompletion {
|
|
||||||
module_id,
|
|
||||||
temp_working_set,
|
|
||||||
};
|
|
||||||
let mut complete_on_list_items = |items: &[ListItem]| -> Vec<SemanticSuggestion> {
|
|
||||||
for item in items {
|
|
||||||
let span = item.expr().span;
|
|
||||||
if span.contains(pos) {
|
|
||||||
let offset = span.start.saturating_sub(ctx.span.start);
|
|
||||||
let end_offset =
|
|
||||||
ctx.prefix.len().min(pos.min(span.end) - ctx.span.start + 1);
|
|
||||||
let new_ctx = Context::new(
|
|
||||||
ctx.working_set,
|
|
||||||
Span::new(span.start, ctx.span.end.min(span.end)),
|
|
||||||
ctx.prefix.get(offset..end_offset).unwrap_or_default(),
|
|
||||||
ctx.offset,
|
|
||||||
);
|
|
||||||
return self.process_completion(&mut exportable_completion, &new_ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
|
|
||||||
match &expr.expr {
|
|
||||||
Expr::String(_) => {
|
|
||||||
return self.process_completion(&mut exportable_completion, ctx);
|
|
||||||
}
|
|
||||||
Expr::FullCellPath(fcp) => match &fcp.head.expr {
|
|
||||||
Expr::List(items) => {
|
|
||||||
return complete_on_list_items(items);
|
|
||||||
}
|
|
||||||
_ => return vec![],
|
|
||||||
},
|
|
||||||
_ => return vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"which" => {
|
|
||||||
let mut completer = CommandCompletion {
|
let mut completer = CommandCompletion {
|
||||||
internals: true,
|
internals: true,
|
||||||
externals: true,
|
externals: true,
|
||||||
@ -646,6 +543,7 @@ impl NuCompleter {
|
|||||||
case_sensitive: config.completions.case_sensitive,
|
case_sensitive: config.completions.case_sensitive,
|
||||||
match_algorithm: config.completions.algorithm.into(),
|
match_algorithm: config.completions.algorithm.into(),
|
||||||
sort: config.completions.sort,
|
sort: config.completions.sort,
|
||||||
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
completer.fetch(
|
completer.fetch(
|
||||||
|
@ -22,22 +22,18 @@ pub struct PathBuiltFromString {
|
|||||||
/// Recursively goes through paths that match a given `partial`.
|
/// Recursively goes through paths that match a given `partial`.
|
||||||
/// built: State struct for a valid matching path built so far.
|
/// built: State struct for a valid matching path built so far.
|
||||||
///
|
///
|
||||||
/// `want_directory`: Whether we want only directories as completion matches.
|
|
||||||
/// Some commands like `cd` can only be run on directories whereas others
|
|
||||||
/// like `ls` can be run on regular files as well.
|
|
||||||
///
|
|
||||||
/// `isdir`: whether the current partial path has a trailing slash.
|
/// `isdir`: whether the current partial path has a trailing slash.
|
||||||
/// Parsing a path string into a pathbuf loses that bit of information.
|
/// Parsing a path string into a pathbuf loses that bit of information.
|
||||||
///
|
///
|
||||||
/// `enable_exact_match`: Whether match algorithm is Prefix and all previous components
|
/// want_directory: Whether we want only directories as completion matches.
|
||||||
/// of the path matched a directory exactly.
|
/// Some commands like `cd` can only be run on directories whereas others
|
||||||
|
/// like `ls` can be run on regular files as well.
|
||||||
fn complete_rec(
|
fn complete_rec(
|
||||||
partial: &[&str],
|
partial: &[&str],
|
||||||
built_paths: &[PathBuiltFromString],
|
built_paths: &[PathBuiltFromString],
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
want_directory: bool,
|
want_directory: bool,
|
||||||
isdir: bool,
|
isdir: bool,
|
||||||
enable_exact_match: bool,
|
|
||||||
) -> Vec<PathBuiltFromString> {
|
) -> Vec<PathBuiltFromString> {
|
||||||
if let Some((&base, rest)) = partial.split_first() {
|
if let Some((&base, rest)) = partial.split_first() {
|
||||||
if base.chars().all(|c| c == '.') && (isdir || !rest.is_empty()) {
|
if base.chars().all(|c| c == '.') && (isdir || !rest.is_empty()) {
|
||||||
@ -50,14 +46,7 @@ fn complete_rec(
|
|||||||
built
|
built
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
return complete_rec(
|
return complete_rec(rest, &built_paths, options, want_directory, isdir);
|
||||||
rest,
|
|
||||||
&built_paths,
|
|
||||||
options,
|
|
||||||
want_directory,
|
|
||||||
isdir,
|
|
||||||
enable_exact_match,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,26 +86,27 @@ fn complete_rec(
|
|||||||
// Serves as confirmation to ignore longer completions for
|
// Serves as confirmation to ignore longer completions for
|
||||||
// components in between.
|
// components in between.
|
||||||
if !rest.is_empty() || isdir {
|
if !rest.is_empty() || isdir {
|
||||||
// Don't show longer completions if we have an exact match (#13204, #14794)
|
|
||||||
let exact_match = enable_exact_match
|
|
||||||
&& (if options.case_sensitive {
|
|
||||||
entry_name.eq(base)
|
|
||||||
} else {
|
|
||||||
entry_name.eq_ignore_case(base)
|
|
||||||
});
|
|
||||||
completions.extend(complete_rec(
|
completions.extend(complete_rec(
|
||||||
rest,
|
rest,
|
||||||
&[built],
|
&[built],
|
||||||
options,
|
options,
|
||||||
want_directory,
|
want_directory,
|
||||||
isdir,
|
isdir,
|
||||||
exact_match,
|
|
||||||
));
|
));
|
||||||
|
} else {
|
||||||
|
completions.push(built);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For https://github.com/nushell/nushell/issues/13204
|
||||||
|
if isdir && options.match_algorithm == MatchAlgorithm::Prefix {
|
||||||
|
let exact_match = if options.case_sensitive {
|
||||||
|
entry_name.eq(base)
|
||||||
|
} else {
|
||||||
|
entry_name.to_folded_case().eq(&base.to_folded_case())
|
||||||
|
};
|
||||||
if exact_match {
|
if exact_match {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
completions.push(built);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@ -150,7 +140,7 @@ impl OriginalCwd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn surround_remove(partial: &str) -> String {
|
fn surround_remove(partial: &str) -> String {
|
||||||
for c in ['`', '"', '\''] {
|
for c in ['`', '"', '\''] {
|
||||||
if partial.starts_with(c) {
|
if partial.starts_with(c) {
|
||||||
let ret = partial.strip_prefix(c).unwrap_or(partial);
|
let ret = partial.strip_prefix(c).unwrap_or(partial);
|
||||||
@ -209,9 +199,10 @@ pub fn complete_item(
|
|||||||
let ls_colors = (engine_state.config.completions.use_ls_colors
|
let ls_colors = (engine_state.config.completions.use_ls_colors
|
||||||
&& engine_state.config.use_ansi_coloring.get(engine_state))
|
&& engine_state.config.use_ansi_coloring.get(engine_state))
|
||||||
.then(|| {
|
.then(|| {
|
||||||
let ls_colors_env_str = stack
|
let ls_colors_env_str = match stack.get_env_var(engine_state, "LS_COLORS") {
|
||||||
.get_env_var(engine_state, "LS_COLORS")
|
Some(v) => env_to_string("LS_COLORS", v, engine_state, stack).ok(),
|
||||||
.and_then(|v| env_to_string("LS_COLORS", v, engine_state, stack).ok());
|
None => None,
|
||||||
|
};
|
||||||
get_ls_colors(ls_colors_env_str)
|
get_ls_colors(ls_colors_env_str)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -265,7 +256,6 @@ pub fn complete_item(
|
|||||||
options,
|
options,
|
||||||
want_directory,
|
want_directory,
|
||||||
isdir,
|
isdir,
|
||||||
options.match_algorithm == MatchAlgorithm::Prefix,
|
|
||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut p| {
|
.map(|mut p| {
|
||||||
@ -274,10 +264,13 @@ pub fn complete_item(
|
|||||||
}
|
}
|
||||||
let is_dir = p.isdir;
|
let is_dir = p.isdir;
|
||||||
let path = original_cwd.apply(p, path_separator);
|
let path = original_cwd.apply(p, path_separator);
|
||||||
let real_path = expand_to_real_path(&path);
|
|
||||||
let metadata = std::fs::symlink_metadata(&real_path).ok();
|
|
||||||
let style = ls_colors.as_ref().map(|lsc| {
|
let style = ls_colors.as_ref().map(|lsc| {
|
||||||
lsc.style_for_path_with_metadata(&real_path, metadata.as_ref())
|
lsc.style_for_path_with_metadata(
|
||||||
|
&path,
|
||||||
|
std::fs::symlink_metadata(expand_to_real_path(&path))
|
||||||
|
.ok()
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
.map(lscolors::Style::to_nu_ansi_term_style)
|
.map(lscolors::Style::to_nu_ansi_term_style)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
});
|
});
|
||||||
|
@ -18,12 +18,6 @@ pub enum MatchAlgorithm {
|
|||||||
/// "git switch" is matched by "git sw"
|
/// "git switch" is matched by "git sw"
|
||||||
Prefix,
|
Prefix,
|
||||||
|
|
||||||
/// Only show suggestions which have a substring matching with the given input
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// "git checkout" is matched by "checkout"
|
|
||||||
Substring,
|
|
||||||
|
|
||||||
/// Only show suggestions which contain the input chars at any place
|
/// Only show suggestions which contain the input chars at any place
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
@ -42,10 +36,6 @@ enum State<T> {
|
|||||||
/// Holds (haystack, item)
|
/// Holds (haystack, item)
|
||||||
items: Vec<(String, T)>,
|
items: Vec<(String, T)>,
|
||||||
},
|
},
|
||||||
Substring {
|
|
||||||
/// Holds (haystack, item)
|
|
||||||
items: Vec<(String, T)>,
|
|
||||||
},
|
|
||||||
Fuzzy {
|
Fuzzy {
|
||||||
matcher: Matcher,
|
matcher: Matcher,
|
||||||
atom: Atom,
|
atom: Atom,
|
||||||
@ -74,18 +64,6 @@ impl<T> NuMatcher<'_, T> {
|
|||||||
state: State::Prefix { items: Vec::new() },
|
state: State::Prefix { items: Vec::new() },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MatchAlgorithm::Substring => {
|
|
||||||
let lowercase_needle = if options.case_sensitive {
|
|
||||||
needle.to_owned()
|
|
||||||
} else {
|
|
||||||
needle.to_folded_case()
|
|
||||||
};
|
|
||||||
NuMatcher {
|
|
||||||
options,
|
|
||||||
needle: lowercase_needle,
|
|
||||||
state: State::Substring { items: Vec::new() },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MatchAlgorithm::Fuzzy => {
|
MatchAlgorithm::Fuzzy => {
|
||||||
let atom = Atom::new(
|
let atom = Atom::new(
|
||||||
needle,
|
needle,
|
||||||
@ -124,21 +102,11 @@ impl<T> NuMatcher<'_, T> {
|
|||||||
} else {
|
} else {
|
||||||
Cow::Owned(haystack.to_folded_case())
|
Cow::Owned(haystack.to_folded_case())
|
||||||
};
|
};
|
||||||
let matches = haystack_folded.starts_with(self.needle.as_str());
|
let matches = if self.options.positional {
|
||||||
if matches {
|
haystack_folded.starts_with(self.needle.as_str())
|
||||||
if let Some(item) = item {
|
|
||||||
items.push((haystack.to_string(), item));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
matches
|
|
||||||
}
|
|
||||||
State::Substring { items } => {
|
|
||||||
let haystack_folded = if self.options.case_sensitive {
|
|
||||||
Cow::Borrowed(haystack)
|
|
||||||
} else {
|
} else {
|
||||||
Cow::Owned(haystack.to_folded_case())
|
haystack_folded.contains(self.needle.as_str())
|
||||||
};
|
};
|
||||||
let matches = haystack_folded.contains(self.needle.as_str());
|
|
||||||
if matches {
|
if matches {
|
||||||
if let Some(item) = item {
|
if let Some(item) = item {
|
||||||
items.push((haystack.to_string(), item));
|
items.push((haystack.to_string(), item));
|
||||||
@ -180,7 +148,7 @@ impl<T> NuMatcher<'_, T> {
|
|||||||
/// Get all the items that matched (sorted)
|
/// Get all the items that matched (sorted)
|
||||||
pub fn results(self) -> Vec<T> {
|
pub fn results(self) -> Vec<T> {
|
||||||
match self.state {
|
match self.state {
|
||||||
State::Prefix { mut items, .. } | State::Substring { mut items, .. } => {
|
State::Prefix { mut items, .. } => {
|
||||||
items.sort_by(|(haystack1, _), (haystack2, _)| {
|
items.sort_by(|(haystack1, _), (haystack2, _)| {
|
||||||
let cmp_sensitive = haystack1.cmp(haystack2);
|
let cmp_sensitive = haystack1.cmp(haystack2);
|
||||||
if self.options.case_sensitive {
|
if self.options.case_sensitive {
|
||||||
@ -227,7 +195,6 @@ impl From<CompletionAlgorithm> for MatchAlgorithm {
|
|||||||
fn from(value: CompletionAlgorithm) -> Self {
|
fn from(value: CompletionAlgorithm) -> Self {
|
||||||
match value {
|
match value {
|
||||||
CompletionAlgorithm::Prefix => MatchAlgorithm::Prefix,
|
CompletionAlgorithm::Prefix => MatchAlgorithm::Prefix,
|
||||||
CompletionAlgorithm::Substring => MatchAlgorithm::Substring,
|
|
||||||
CompletionAlgorithm::Fuzzy => MatchAlgorithm::Fuzzy,
|
CompletionAlgorithm::Fuzzy => MatchAlgorithm::Fuzzy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,7 +206,6 @@ impl TryFrom<String> for MatchAlgorithm {
|
|||||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
match value.as_str() {
|
match value.as_str() {
|
||||||
"prefix" => Ok(Self::Prefix),
|
"prefix" => Ok(Self::Prefix),
|
||||||
"substring" => Ok(Self::Substring),
|
|
||||||
"fuzzy" => Ok(Self::Fuzzy),
|
"fuzzy" => Ok(Self::Fuzzy),
|
||||||
_ => Err(InvalidMatchAlgorithm::Unknown),
|
_ => Err(InvalidMatchAlgorithm::Unknown),
|
||||||
}
|
}
|
||||||
@ -264,6 +230,7 @@ impl std::error::Error for InvalidMatchAlgorithm {}
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CompletionOptions {
|
pub struct CompletionOptions {
|
||||||
pub case_sensitive: bool,
|
pub case_sensitive: bool,
|
||||||
|
pub positional: bool,
|
||||||
pub match_algorithm: MatchAlgorithm,
|
pub match_algorithm: MatchAlgorithm,
|
||||||
pub sort: CompletionSort,
|
pub sort: CompletionSort,
|
||||||
}
|
}
|
||||||
@ -272,6 +239,7 @@ impl Default for CompletionOptions {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
case_sensitive: true,
|
case_sensitive: true,
|
||||||
|
positional: true,
|
||||||
match_algorithm: MatchAlgorithm::Prefix,
|
match_algorithm: MatchAlgorithm::Prefix,
|
||||||
sort: Default::default(),
|
sort: Default::default(),
|
||||||
}
|
}
|
||||||
@ -288,9 +256,6 @@ mod test {
|
|||||||
#[case(MatchAlgorithm::Prefix, "example text", "", true)]
|
#[case(MatchAlgorithm::Prefix, "example text", "", true)]
|
||||||
#[case(MatchAlgorithm::Prefix, "example text", "examp", true)]
|
#[case(MatchAlgorithm::Prefix, "example text", "examp", true)]
|
||||||
#[case(MatchAlgorithm::Prefix, "example text", "text", false)]
|
#[case(MatchAlgorithm::Prefix, "example text", "text", false)]
|
||||||
#[case(MatchAlgorithm::Substring, "example text", "", true)]
|
|
||||||
#[case(MatchAlgorithm::Substring, "example text", "text", true)]
|
|
||||||
#[case(MatchAlgorithm::Substring, "example text", "mplxt", false)]
|
|
||||||
#[case(MatchAlgorithm::Fuzzy, "example text", "", true)]
|
#[case(MatchAlgorithm::Fuzzy, "example text", "", true)]
|
||||||
#[case(MatchAlgorithm::Fuzzy, "example text", "examp", true)]
|
#[case(MatchAlgorithm::Fuzzy, "example text", "examp", true)]
|
||||||
#[case(MatchAlgorithm::Fuzzy, "example text", "ext", true)]
|
#[case(MatchAlgorithm::Fuzzy, "example text", "ext", true)]
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
use crate::completions::{
|
use crate::completions::{
|
||||||
completer::map_value_completions, Completer, CompletionOptions, MatchAlgorithm,
|
completer::map_value_completions, Completer, CompletionOptions, SemanticSuggestion,
|
||||||
SemanticSuggestion,
|
|
||||||
};
|
};
|
||||||
use nu_engine::eval_call;
|
use nu_engine::eval_call;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Argument, Call, Expr, Expression},
|
ast::{Argument, Call, Expr, Expression},
|
||||||
debugger::WithoutDebug,
|
debugger::WithoutDebug,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{Stack, StateWorkingSet},
|
||||||
DeclId, PipelineData, Span, Type, Value,
|
DeclId, PipelineData, Span, Type, Value,
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -43,9 +42,8 @@ impl<T: Completer> Completer for CustomCompletion<T> {
|
|||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
// Call custom declaration
|
// Call custom declaration
|
||||||
let mut stack_mut = stack.clone();
|
let mut stack_mut = stack.clone();
|
||||||
let mut eval = |engine_state: &EngineState| {
|
let result = eval_call::<WithoutDebug>(
|
||||||
eval_call::<WithoutDebug>(
|
working_set.permanent_state,
|
||||||
engine_state,
|
|
||||||
&mut stack_mut,
|
&mut stack_mut,
|
||||||
&Call {
|
&Call {
|
||||||
decl_id: self.decl_id,
|
decl_id: self.decl_id,
|
||||||
@ -65,15 +63,7 @@ impl<T: Completer> Completer for CustomCompletion<T> {
|
|||||||
parser_info: HashMap::new(),
|
parser_info: HashMap::new(),
|
||||||
},
|
},
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
)
|
);
|
||||||
};
|
|
||||||
let result = if self.decl_id.get() < working_set.permanent_state.num_decls() {
|
|
||||||
eval(working_set.permanent_state)
|
|
||||||
} else {
|
|
||||||
let mut engine_state = working_set.permanent_state.clone();
|
|
||||||
let _ = engine_state.merge_delta(working_set.delta.clone());
|
|
||||||
eval(&engine_state)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut completion_options = orig_options.clone();
|
let mut completion_options = orig_options.clone();
|
||||||
let mut should_sort = true;
|
let mut should_sort = true;
|
||||||
@ -103,10 +93,10 @@ impl<T: Completer> Completer for CustomCompletion<T> {
|
|||||||
{
|
{
|
||||||
completion_options.case_sensitive = case_sensitive;
|
completion_options.case_sensitive = case_sensitive;
|
||||||
}
|
}
|
||||||
let positional =
|
if let Some(positional) =
|
||||||
options.get("positional").and_then(|val| val.as_bool().ok());
|
options.get("positional").and_then(|val| val.as_bool().ok())
|
||||||
if positional.is_some() {
|
{
|
||||||
log::warn!("Use of the positional option is deprecated. Use the substring match algorithm instead.");
|
completion_options.positional = positional;
|
||||||
}
|
}
|
||||||
if let Some(algorithm) = options
|
if let Some(algorithm) = options
|
||||||
.get("completion_algorithm")
|
.get("completion_algorithm")
|
||||||
@ -114,11 +104,6 @@ impl<T: Completer> Completer for CustomCompletion<T> {
|
|||||||
.and_then(|option| option.try_into().ok())
|
.and_then(|option| option.try_into().ok())
|
||||||
{
|
{
|
||||||
completion_options.match_algorithm = algorithm;
|
completion_options.match_algorithm = algorithm;
|
||||||
if let Some(false) = positional {
|
|
||||||
if completion_options.match_algorithm == MatchAlgorithm::Prefix {
|
|
||||||
completion_options.match_algorithm = MatchAlgorithm::Substring
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,23 +1,18 @@
|
|||||||
use crate::completions::{
|
use crate::completions::{file_path_completion, Completer, CompletionOptions};
|
||||||
completion_common::{surround_remove, FileSuggestion},
|
|
||||||
completion_options::NuMatcher,
|
|
||||||
file_path_completion, Completer, CompletionOptions, SemanticSuggestion, SuggestionKind,
|
|
||||||
};
|
|
||||||
use nu_path::expand_tilde;
|
use nu_path::expand_tilde;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{Stack, StateWorkingSet, VirtualPath},
|
engine::{Stack, StateWorkingSet},
|
||||||
Span,
|
Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
path::{is_separator, PathBuf, MAIN_SEPARATOR_STR},
|
path::{is_separator, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct DotNuCompletion {
|
use super::{SemanticSuggestion, SuggestionKind};
|
||||||
/// e.g. use std/a<tab>
|
|
||||||
pub std_virtual_path: bool,
|
pub struct DotNuCompletion;
|
||||||
}
|
|
||||||
|
|
||||||
impl Completer for DotNuCompletion {
|
impl Completer for DotNuCompletion {
|
||||||
fn fetch(
|
fn fetch(
|
||||||
@ -107,7 +102,7 @@ impl Completer for DotNuCompletion {
|
|||||||
|
|
||||||
// Fetch the files filtering the ones that ends with .nu
|
// Fetch the files filtering the ones that ends with .nu
|
||||||
// and transform them into suggestions
|
// and transform them into suggestions
|
||||||
let mut completions = file_path_completion(
|
let completions = file_path_completion(
|
||||||
span,
|
span,
|
||||||
partial,
|
partial,
|
||||||
&search_dirs
|
&search_dirs
|
||||||
@ -118,60 +113,17 @@ impl Completer for DotNuCompletion {
|
|||||||
working_set.permanent_state,
|
working_set.permanent_state,
|
||||||
stack,
|
stack,
|
||||||
);
|
);
|
||||||
|
|
||||||
if self.std_virtual_path {
|
|
||||||
let mut matcher = NuMatcher::new(partial, options);
|
|
||||||
let base_dir = surround_remove(&base_dir);
|
|
||||||
if base_dir == "." {
|
|
||||||
let surround_prefix = partial
|
|
||||||
.chars()
|
|
||||||
.take_while(|c| "`'\"".contains(*c))
|
|
||||||
.collect::<String>();
|
|
||||||
for path in ["std", "std-rfc"] {
|
|
||||||
let path = format!("{}{}", surround_prefix, path);
|
|
||||||
matcher.add(
|
|
||||||
path.clone(),
|
|
||||||
FileSuggestion {
|
|
||||||
span,
|
|
||||||
path,
|
|
||||||
style: None,
|
|
||||||
is_dir: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if let Some(VirtualPath::Dir(sub_paths)) =
|
|
||||||
working_set.find_virtual_path(&base_dir)
|
|
||||||
{
|
|
||||||
for sub_vp_id in sub_paths {
|
|
||||||
let (path, sub_vp) = working_set.get_virtual_path(*sub_vp_id);
|
|
||||||
let path = path
|
|
||||||
.strip_prefix(&format!("{}/", base_dir))
|
|
||||||
.unwrap_or(path)
|
|
||||||
.to_string();
|
|
||||||
matcher.add(
|
|
||||||
path.clone(),
|
|
||||||
FileSuggestion {
|
|
||||||
path,
|
|
||||||
span,
|
|
||||||
style: None,
|
|
||||||
is_dir: matches!(sub_vp, VirtualPath::Dir(_)),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
completions.extend(matcher.results());
|
|
||||||
}
|
|
||||||
|
|
||||||
completions
|
completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
// Different base dir, so we list the .nu files or folders
|
// Different base dir, so we list the .nu files or folders
|
||||||
.filter(|it| {
|
.filter(|it| {
|
||||||
// for paths with spaces in them
|
// for paths with spaces in them
|
||||||
let path = it.path.trim_end_matches('`');
|
let path = it.path.trim_end_matches('`');
|
||||||
path.ends_with(".nu") || it.is_dir
|
path.ends_with(".nu") || path.ends_with(SEP)
|
||||||
})
|
})
|
||||||
.map(|x| {
|
.map(|x| {
|
||||||
let append_whitespace = !x.is_dir && (!start_with_backquote || end_with_backquote);
|
let append_whitespace =
|
||||||
|
x.path.ends_with(".nu") && (!start_with_backquote || end_with_backquote);
|
||||||
// Re-calculate the span to replace
|
// Re-calculate the span to replace
|
||||||
let mut span_offset = 0;
|
let mut span_offset = 0;
|
||||||
let mut value = x.path.to_string();
|
let mut value = x.path.to_string();
|
||||||
|
@ -1,112 +0,0 @@
|
|||||||
use crate::completions::{
|
|
||||||
completion_common::surround_remove, completion_options::NuMatcher, Completer,
|
|
||||||
CompletionOptions, SemanticSuggestion, SuggestionKind,
|
|
||||||
};
|
|
||||||
use nu_protocol::{
|
|
||||||
engine::{Stack, StateWorkingSet},
|
|
||||||
ModuleId, Span,
|
|
||||||
};
|
|
||||||
use reedline::Suggestion;
|
|
||||||
|
|
||||||
pub struct ExportableCompletion<'a> {
|
|
||||||
pub module_id: ModuleId,
|
|
||||||
pub temp_working_set: Option<StateWorkingSet<'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If name contains space, wrap it in quotes
|
|
||||||
fn wrapped_name(name: String) -> String {
|
|
||||||
if !name.contains(' ') {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
if name.contains('\'') {
|
|
||||||
format!("\"{}\"", name.replace('"', r#"\""#))
|
|
||||||
} else {
|
|
||||||
format!("'{name}'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Completer for ExportableCompletion<'_> {
|
|
||||||
fn fetch(
|
|
||||||
&mut self,
|
|
||||||
working_set: &StateWorkingSet,
|
|
||||||
_stack: &Stack,
|
|
||||||
prefix: impl AsRef<str>,
|
|
||||||
span: Span,
|
|
||||||
offset: usize,
|
|
||||||
options: &CompletionOptions,
|
|
||||||
) -> Vec<SemanticSuggestion> {
|
|
||||||
let mut matcher = NuMatcher::<()>::new(surround_remove(prefix.as_ref()), options);
|
|
||||||
let mut results = Vec::new();
|
|
||||||
let span = reedline::Span {
|
|
||||||
start: span.start - offset,
|
|
||||||
end: span.end - offset,
|
|
||||||
};
|
|
||||||
// TODO: use matcher.add_lazy to lazy evaluate an item if it matches the prefix
|
|
||||||
let mut add_suggestion = |value: String,
|
|
||||||
description: Option<String>,
|
|
||||||
extra: Option<Vec<String>>,
|
|
||||||
kind: SuggestionKind| {
|
|
||||||
results.push(SemanticSuggestion {
|
|
||||||
suggestion: Suggestion {
|
|
||||||
value,
|
|
||||||
span,
|
|
||||||
description,
|
|
||||||
extra,
|
|
||||||
..Suggestion::default()
|
|
||||||
},
|
|
||||||
kind: Some(kind),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
let working_set = self.temp_working_set.as_ref().unwrap_or(working_set);
|
|
||||||
let module = working_set.get_module(self.module_id);
|
|
||||||
|
|
||||||
for (name, decl_id) in &module.decls {
|
|
||||||
let name = String::from_utf8_lossy(name).to_string();
|
|
||||||
if matcher.matches(&name) {
|
|
||||||
let cmd = working_set.get_decl(*decl_id);
|
|
||||||
add_suggestion(
|
|
||||||
wrapped_name(name),
|
|
||||||
Some(cmd.description().to_string()),
|
|
||||||
None,
|
|
||||||
// `None` here avoids arguments being expanded by snippet edit style for lsp
|
|
||||||
SuggestionKind::Command(cmd.command_type(), None),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (name, module_id) in &module.submodules {
|
|
||||||
let name = String::from_utf8_lossy(name).to_string();
|
|
||||||
if matcher.matches(&name) {
|
|
||||||
let comments = working_set.get_module_comments(*module_id).map(|spans| {
|
|
||||||
spans
|
|
||||||
.iter()
|
|
||||||
.map(|sp| {
|
|
||||||
String::from_utf8_lossy(working_set.get_span_contents(*sp)).into()
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
});
|
|
||||||
add_suggestion(
|
|
||||||
wrapped_name(name),
|
|
||||||
Some("Submodule".into()),
|
|
||||||
comments,
|
|
||||||
SuggestionKind::Module,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (name, var_id) in &module.constants {
|
|
||||||
let name = String::from_utf8_lossy(name).to_string();
|
|
||||||
if matcher.matches(&name) {
|
|
||||||
let var = working_set.get_variable(*var_id);
|
|
||||||
add_suggestion(
|
|
||||||
wrapped_name(name),
|
|
||||||
var.const_val
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|v| v.clone().coerce_into_string().ok()),
|
|
||||||
None,
|
|
||||||
SuggestionKind::Variable,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
results
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +1,12 @@
|
|||||||
use crate::completions::{
|
use crate::completions::{completion_options::NuMatcher, Completer, CompletionOptions};
|
||||||
completion_options::NuMatcher, Completer, CompletionOptions, SemanticSuggestion, SuggestionKind,
|
|
||||||
};
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{Stack, StateWorkingSet},
|
engine::{Stack, StateWorkingSet},
|
||||||
DeclId, Span,
|
DeclId, Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
|
|
||||||
|
use super::{SemanticSuggestion, SuggestionKind};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FlagCompletion {
|
pub struct FlagCompletion {
|
||||||
pub decl_id: DeclId,
|
pub decl_id: DeclId,
|
||||||
|
@ -8,7 +8,6 @@ mod completion_options;
|
|||||||
mod custom_completions;
|
mod custom_completions;
|
||||||
mod directory_completions;
|
mod directory_completions;
|
||||||
mod dotnu_completions;
|
mod dotnu_completions;
|
||||||
mod exportable_completions;
|
|
||||||
mod file_completions;
|
mod file_completions;
|
||||||
mod flag_completions;
|
mod flag_completions;
|
||||||
mod operator_completions;
|
mod operator_completions;
|
||||||
@ -23,7 +22,6 @@ pub use completion_options::{CompletionOptions, MatchAlgorithm};
|
|||||||
pub use custom_completions::CustomCompletion;
|
pub use custom_completions::CustomCompletion;
|
||||||
pub use directory_completions::DirectoryCompletion;
|
pub use directory_completions::DirectoryCompletion;
|
||||||
pub use dotnu_completions::DotNuCompletion;
|
pub use dotnu_completions::DotNuCompletion;
|
||||||
pub use exportable_completions::ExportableCompletion;
|
|
||||||
pub use file_completions::{file_path_completion, FileCompletion};
|
pub use file_completions::{file_path_completion, FileCompletion};
|
||||||
pub use flag_completions::FlagCompletion;
|
pub use flag_completions::FlagCompletion;
|
||||||
pub use operator_completions::OperatorCompletion;
|
pub use operator_completions::OperatorCompletion;
|
||||||
|
@ -864,7 +864,7 @@ fn do_auto_cd(
|
|||||||
path.to_string_lossy().to_string()
|
path.to_string_lossy().to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
if let PermissionResult::PermissionDenied = have_permission(path.clone()) {
|
if let PermissionResult::PermissionDenied(_) = have_permission(path.clone()) {
|
||||||
report_shell_error(
|
report_shell_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&ShellError::Io(IoError::new_with_additional_context(
|
&ShellError::Io(IoError::new_with_additional_context(
|
||||||
|
@ -10,8 +10,7 @@ use nu_cli::NuCompleter;
|
|||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_path::expand_tilde;
|
use nu_path::expand_tilde;
|
||||||
use nu_protocol::{debugger::WithoutDebug, engine::StateWorkingSet, Config, PipelineData};
|
use nu_protocol::{debugger::WithoutDebug, engine::StateWorkingSet, PipelineData};
|
||||||
use nu_std::load_standard_library;
|
|
||||||
use reedline::{Completer, Suggestion};
|
use reedline::{Completer, Suggestion};
|
||||||
use rstest::{fixture, rstest};
|
use rstest::{fixture, rstest};
|
||||||
use support::{
|
use support::{
|
||||||
@ -228,12 +227,14 @@ fn customcompletions_override_options() {
|
|||||||
let mut completer = custom_completer_with_options(
|
let mut completer = custom_completer_with_options(
|
||||||
r#"$env.config.completions.algorithm = "fuzzy"
|
r#"$env.config.completions.algorithm = "fuzzy"
|
||||||
$env.config.completions.case_sensitive = false"#,
|
$env.config.completions.case_sensitive = false"#,
|
||||||
r#"completion_algorithm: "substring",
|
r#"completion_algorithm: "prefix",
|
||||||
|
positional: false,
|
||||||
case_sensitive: true,
|
case_sensitive: true,
|
||||||
sort: true"#,
|
sort: true"#,
|
||||||
&["Foo Abcdef", "Abcdef", "Acd Bar"],
|
&["Foo Abcdef", "Abcdef", "Acd Bar"],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// positional: false should make it do substring matching
|
||||||
// sort: true should force sorting
|
// sort: true should force sorting
|
||||||
let expected: Vec<_> = vec!["Abcdef", "Foo Abcdef"];
|
let expected: Vec<_> = vec!["Abcdef", "Foo Abcdef"];
|
||||||
let suggestions = completer.complete("my-command Abcd", 15);
|
let suggestions = completer.complete("my-command Abcd", 15);
|
||||||
@ -349,24 +350,9 @@ fn custom_arguments_vs_subcommands() {
|
|||||||
match_suggestions(&expected, &suggestions);
|
match_suggestions(&expected, &suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn custom_completions_defined_inline() {
|
|
||||||
let (_, _, engine, stack) = new_engine();
|
|
||||||
|
|
||||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
|
||||||
let completion_str = "def animals [] { [cat dog] }
|
|
||||||
export def say [
|
|
||||||
animal: string@animals
|
|
||||||
] { }; say ";
|
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
|
||||||
// including only subcommand completions
|
|
||||||
let expected: Vec<_> = vec!["cat", "dog"];
|
|
||||||
match_suggestions(&expected, &suggestions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// External command only if starts with `^`
|
/// External command only if starts with `^`
|
||||||
#[test]
|
#[test]
|
||||||
fn external_commands() {
|
fn external_commands_only() {
|
||||||
let engine = new_external_engine();
|
let engine = new_external_engine();
|
||||||
let mut completer = NuCompleter::new(
|
let mut completer = NuCompleter::new(
|
||||||
Arc::new(engine),
|
Arc::new(engine),
|
||||||
@ -389,31 +375,6 @@ fn external_commands() {
|
|||||||
match_suggestions(&expected, &suggestions);
|
match_suggestions(&expected, &suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disable external commands except for those start with `^`
|
|
||||||
#[test]
|
|
||||||
fn external_commands_disabled() {
|
|
||||||
let mut engine = new_external_engine();
|
|
||||||
|
|
||||||
let mut config = Config::default();
|
|
||||||
config.completions.external.enable = false;
|
|
||||||
engine.set_config(config);
|
|
||||||
|
|
||||||
let stack = nu_protocol::engine::Stack::new();
|
|
||||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
|
||||||
let completion_str = "ls; ^sleep";
|
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
|
||||||
#[cfg(windows)]
|
|
||||||
let expected: Vec<_> = vec!["sleep.exe"];
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
let expected: Vec<_> = vec!["sleep"];
|
|
||||||
match_suggestions(&expected, &suggestions);
|
|
||||||
|
|
||||||
let completion_str = "sleep";
|
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
|
||||||
let expected: Vec<_> = vec!["sleep"];
|
|
||||||
match_suggestions(&expected, &suggestions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Which completes both internals and externals
|
/// Which completes both internals and externals
|
||||||
#[test]
|
#[test]
|
||||||
fn which_command_completions() {
|
fn which_command_completions() {
|
||||||
@ -512,7 +473,7 @@ fn dotnu_completions() {
|
|||||||
|
|
||||||
match_suggestions(&vec!["sub.nu`"], &suggestions);
|
match_suggestions(&vec!["sub.nu`"], &suggestions);
|
||||||
|
|
||||||
let mut expected = vec![
|
let expected = vec![
|
||||||
"asdf.nu",
|
"asdf.nu",
|
||||||
"bar.nu",
|
"bar.nu",
|
||||||
"bat.nu",
|
"bat.nu",
|
||||||
@ -545,8 +506,6 @@ fn dotnu_completions() {
|
|||||||
match_suggestions(&expected, &suggestions);
|
match_suggestions(&expected, &suggestions);
|
||||||
|
|
||||||
// Test use completion
|
// Test use completion
|
||||||
expected.push("std");
|
|
||||||
expected.push("std-rfc");
|
|
||||||
let completion_str = "use ";
|
let completion_str = "use ";
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
let suggestions = completer.complete(completion_str, completion_str.len());
|
||||||
|
|
||||||
@ -578,66 +537,6 @@ fn dotnu_completions() {
|
|||||||
match_dir_content_for_dotnu(dir_content, &suggestions);
|
match_dir_content_for_dotnu(dir_content, &suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn dotnu_stdlib_completions() {
|
|
||||||
let (_, _, mut engine, stack) = new_dotnu_engine();
|
|
||||||
assert!(load_standard_library(&mut engine).is_ok());
|
|
||||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
|
||||||
|
|
||||||
// `export use` should be recognized as command `export use`
|
|
||||||
let completion_str = "export use std/ass";
|
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
|
||||||
match_suggestions(&vec!["assert"], &suggestions);
|
|
||||||
|
|
||||||
let completion_str = "use `std-rfc/cli";
|
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
|
||||||
match_suggestions(&vec!["clip"], &suggestions);
|
|
||||||
|
|
||||||
let completion_str = "use \"std";
|
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
|
||||||
match_suggestions(&vec!["\"std", "\"std-rfc"], &suggestions);
|
|
||||||
|
|
||||||
let completion_str = "overlay use \'std-rfc/cli";
|
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
|
||||||
match_suggestions(&vec!["clip"], &suggestions);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn exportable_completions() {
|
|
||||||
let (_, _, mut engine, mut stack) = new_dotnu_engine();
|
|
||||||
let code = r#"export module "🤔🐘" {
|
|
||||||
export const foo = "🤔🐘";
|
|
||||||
}"#;
|
|
||||||
assert!(support::merge_input(code.as_bytes(), &mut engine, &mut stack).is_ok());
|
|
||||||
assert!(load_standard_library(&mut engine).is_ok());
|
|
||||||
|
|
||||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
|
||||||
|
|
||||||
let completion_str = "use std null";
|
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
|
||||||
match_suggestions(&vec!["null-device", "null_device"], &suggestions);
|
|
||||||
|
|
||||||
let completion_str = "export use std/assert eq";
|
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
|
||||||
match_suggestions(&vec!["equal"], &suggestions);
|
|
||||||
|
|
||||||
let completion_str = "use std/assert \"not eq";
|
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
|
||||||
match_suggestions(&vec!["'not equal'"], &suggestions);
|
|
||||||
|
|
||||||
let completion_str = "use std-rfc/clip ['prefi";
|
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
|
||||||
match_suggestions(&vec!["prefix"], &suggestions);
|
|
||||||
|
|
||||||
let completion_str = "use std/math [E, `TAU";
|
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
|
||||||
match_suggestions(&vec!["TAU"], &suggestions);
|
|
||||||
|
|
||||||
let completion_str = "use 🤔🐘 'foo";
|
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
|
||||||
match_suggestions(&vec!["foo"], &suggestions);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dotnu_completions_const_nu_lib_dirs() {
|
fn dotnu_completions_const_nu_lib_dirs() {
|
||||||
let (_, _, engine, stack) = new_dotnu_engine();
|
let (_, _, engine, stack) = new_dotnu_engine();
|
||||||
@ -1012,11 +911,10 @@ fn partial_completions() {
|
|||||||
// Create the expected values
|
// Create the expected values
|
||||||
let expected_paths = [
|
let expected_paths = [
|
||||||
file(dir.join("partial").join("hello.txt")),
|
file(dir.join("partial").join("hello.txt")),
|
||||||
folder(dir.join("partial").join("hol")),
|
|
||||||
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")),
|
||||||
file(dir.join("partial-a").join("hello")),
|
file(dir.join("partial-a").join("hello")),
|
||||||
folder(dir.join("partial-a").join("hola")),
|
file(dir.join("partial-a").join("hola")),
|
||||||
file(dir.join("partial-b").join("hello_b")),
|
file(dir.join("partial-b").join("hello_b")),
|
||||||
file(dir.join("partial-b").join("hi_b")),
|
file(dir.join("partial-b").join("hi_b")),
|
||||||
file(dir.join("partial-c").join("hello_c")),
|
file(dir.join("partial-c").join("hello_c")),
|
||||||
@ -1033,12 +931,11 @@ fn partial_completions() {
|
|||||||
// Create the expected values
|
// Create the expected values
|
||||||
let expected_paths = [
|
let expected_paths = [
|
||||||
file(dir.join("partial").join("hello.txt")),
|
file(dir.join("partial").join("hello.txt")),
|
||||||
folder(dir.join("partial").join("hol")),
|
|
||||||
file(dir.join("partial-a").join("anotherfile")),
|
file(dir.join("partial-a").join("anotherfile")),
|
||||||
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")),
|
||||||
file(dir.join("partial-a").join("hello")),
|
file(dir.join("partial-a").join("hello")),
|
||||||
folder(dir.join("partial-a").join("hola")),
|
file(dir.join("partial-a").join("hola")),
|
||||||
file(dir.join("partial-b").join("hello_b")),
|
file(dir.join("partial-b").join("hello_b")),
|
||||||
file(dir.join("partial-b").join("hi_b")),
|
file(dir.join("partial-b").join("hi_b")),
|
||||||
file(dir.join("partial-c").join("hello_c")),
|
file(dir.join("partial-c").join("hello_c")),
|
||||||
@ -2005,35 +1902,6 @@ fn table_cell_path_completions() {
|
|||||||
match_suggestions(&expected, &suggestions);
|
match_suggestions(&expected, &suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn quoted_cell_path_completions() {
|
|
||||||
let (_, _, mut engine, mut stack) = new_engine();
|
|
||||||
let command = r#"let foo = {'foo bar':1 'foo\\"bar"': 1 '.': 1 '|': 1 1: 1 "": 1}"#;
|
|
||||||
assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok());
|
|
||||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
|
||||||
|
|
||||||
let expected: Vec<_> = vec![
|
|
||||||
"\"\"",
|
|
||||||
"\".\"",
|
|
||||||
"\"1\"",
|
|
||||||
"\"foo bar\"",
|
|
||||||
"\"foo\\\\\\\\\\\"bar\\\"\"",
|
|
||||||
"\"|\"",
|
|
||||||
];
|
|
||||||
let completion_str = "$foo.";
|
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
|
||||||
match_suggestions(&expected, &suggestions);
|
|
||||||
|
|
||||||
let expected: Vec<_> = vec!["\"foo bar\"", "\"foo\\\\\\\\\\\"bar\\\"\""];
|
|
||||||
let completion_str = "$foo.`foo";
|
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
|
||||||
match_suggestions(&expected, &suggestions);
|
|
||||||
|
|
||||||
let completion_str = "$foo.foo";
|
|
||||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
|
||||||
match_suggestions(&expected, &suggestions);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn alias_of_command_and_flags() {
|
fn alias_of_command_and_flags() {
|
||||||
let (_, _, mut engine, mut stack) = new_engine();
|
let (_, _, mut engine, mut stack) = new_engine();
|
||||||
@ -2307,43 +2175,15 @@ fn exact_match() {
|
|||||||
|
|
||||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||||
|
|
||||||
// Troll case to test if exact match logic works case insensitively
|
|
||||||
let target_dir = format!("open {}", folder(dir.join("pArTiAL")));
|
let target_dir = format!("open {}", folder(dir.join("pArTiAL")));
|
||||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
|
// Since it's an exact match, only 'partial' should be suggested, not
|
||||||
|
// 'partial-a' and stuff. Implemented in #13302
|
||||||
match_suggestions(
|
match_suggestions(
|
||||||
&vec![
|
&vec![file(dir.join("partial").join("hello.txt")).as_str()],
|
||||||
file(dir.join("partial").join("hello.txt")).as_str(),
|
|
||||||
folder(dir.join("partial").join("hol")).as_str(),
|
|
||||||
],
|
|
||||||
&suggestions,
|
&suggestions,
|
||||||
);
|
);
|
||||||
|
|
||||||
let target_dir = format!("open {}", file(dir.join("partial").join("h")));
|
|
||||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
|
||||||
match_suggestions(
|
|
||||||
&vec![
|
|
||||||
file(dir.join("partial").join("hello.txt")).as_str(),
|
|
||||||
folder(dir.join("partial").join("hol")).as_str(),
|
|
||||||
],
|
|
||||||
&suggestions,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Even though "hol" is an exact match, the first component ("part") wasn't an
|
|
||||||
// exact match, so we include partial-a/hola
|
|
||||||
let target_dir = format!("open {}", file(dir.join("part").join("hol")));
|
|
||||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
|
||||||
match_suggestions(
|
|
||||||
&vec![
|
|
||||||
folder(dir.join("partial").join("hol")).as_str(),
|
|
||||||
folder(dir.join("partial-a").join("hola")).as_str(),
|
|
||||||
],
|
|
||||||
&suggestions,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Exact match behavior shouldn't be enabled if the path has no slashes
|
|
||||||
let target_dir = format!("open {}", file(dir.join("partial")));
|
|
||||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
|
||||||
assert!(suggestions.len() > 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ignore = "was reverted, still needs fixing"]
|
#[ignore = "was reverted, still needs fixing"]
|
||||||
|
@ -5,7 +5,7 @@ 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.104.1"
|
version = "0.103.0"
|
||||||
|
|
||||||
# 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,10 +13,10 @@ version = "0.104.1"
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.103.0", default-features = false }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.104.1" }
|
nu-parser = { path = "../nu-parser", version = "0.103.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.104.1" }
|
nu-path = { path = "../nu-path", version = "0.103.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.103.0", default-features = false }
|
||||||
|
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
miette = { workspace = true }
|
miette = { workspace = true }
|
||||||
|
@ -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.104.1"
|
version = "0.103.0"
|
||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
@ -16,13 +16,13 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.104.1" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.103.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.103.0", default-features = false }
|
||||||
nu-json = { version = "0.104.1", path = "../nu-json" }
|
nu-json = { version = "0.103.0", path = "../nu-json" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.104.1" }
|
nu-parser = { path = "../nu-parser", version = "0.103.0" }
|
||||||
nu-pretty-hex = { version = "0.104.1", path = "../nu-pretty-hex" }
|
nu-pretty-hex = { version = "0.103.0", path = "../nu-pretty-hex" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.103.0", default-features = false }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
|
nu-utils = { path = "../nu-utils", version = "0.103.0", default-features = false }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
heck = { workspace = true }
|
heck = { workspace = true }
|
||||||
@ -37,6 +37,6 @@ itertools = { workspace = true }
|
|||||||
mime = { workspace = true }
|
mime = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.104.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.103.0" }
|
||||||
nu-command = { path = "../nu-command", version = "0.104.1" }
|
nu-command = { path = "../nu-command", version = "0.103.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.104.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.103.0" }
|
||||||
|
@ -135,7 +135,7 @@ where
|
|||||||
(min, max) => (rhs, lhs, max, min),
|
(min, max) => (rhs, lhs, max, min),
|
||||||
};
|
};
|
||||||
|
|
||||||
let pad = iter::repeat_n(0, max_len - min_len);
|
let pad = iter::repeat(0).take(max_len - min_len);
|
||||||
|
|
||||||
let mut a;
|
let mut a;
|
||||||
let mut b;
|
let mut b;
|
||||||
@ -159,10 +159,9 @@ where
|
|||||||
}
|
}
|
||||||
(Value::Binary { .. }, Value::Int { .. }) | (Value::Int { .. }, Value::Binary { .. }) => {
|
(Value::Binary { .. }, Value::Int { .. }) | (Value::Int { .. }, Value::Binary { .. }) => {
|
||||||
Value::error(
|
Value::error(
|
||||||
ShellError::OnlySupportsThisInputType {
|
ShellError::PipelineMismatch {
|
||||||
exp_input_type: "input, and argument, to be both int or both binary"
|
exp_input_type: "input, and argument, to be both int or both binary"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
wrong_type: "int and binary".to_string(),
|
|
||||||
dst_span: rhs.span(),
|
dst_span: rhs.span(),
|
||||||
src_span: span,
|
src_span: span,
|
||||||
},
|
},
|
||||||
|
@ -249,7 +249,7 @@ fn shift_bytes_and_bits_left(data: &[u8], byte_shift: usize, bit_shift: usize) -
|
|||||||
Last | Only => lhs << bit_shift,
|
Last | Only => lhs << bit_shift,
|
||||||
_ => (lhs << bit_shift) | (rhs >> (8 - bit_shift)),
|
_ => (lhs << bit_shift) | (rhs >> (8 - bit_shift)),
|
||||||
})
|
})
|
||||||
.chain(iter::repeat_n(0, byte_shift))
|
.chain(iter::repeat(0).take(byte_shift))
|
||||||
.collect::<Vec<u8>>()
|
.collect::<Vec<u8>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,11 +253,12 @@ fn format_record(
|
|||||||
optional: false,
|
optional: false,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
match data_as_value.clone().follow_cell_path(&path_members, false) {
|
||||||
let expanded_string = data_as_value
|
Ok(value_at_column) => {
|
||||||
.follow_cell_path(&path_members, false)?
|
output.push_str(value_at_column.to_expanded_string(", ", config).as_str())
|
||||||
.to_expanded_string(", ", config);
|
}
|
||||||
output.push_str(expanded_string.as_str())
|
Err(se) => return Err(se),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ 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.104.1"
|
version = "0.103.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
@ -15,16 +15,16 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.103.0", default-features = false }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.104.1" }
|
nu-parser = { path = "../nu-parser", version = "0.103.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.103.0", default-features = false }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
|
nu-utils = { path = "../nu-utils", version = "0.103.0", default-features = false }
|
||||||
|
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
shadow-rs = { version = "1.1", default-features = false }
|
shadow-rs = { version = "0.38", default-features = false }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = { version = "1.1", default-features = false, features = ["build"] }
|
shadow-rs = { version = "0.38", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
quickcheck = { workspace = true }
|
quickcheck = { workspace = true }
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{
|
use nu_protocol::{engine::StateWorkingSet, ByteStreamSource, PipelineMetadata};
|
||||||
engine::{Closure, StateWorkingSet},
|
|
||||||
BlockId, ByteStreamSource, Category, PipelineMetadata, Signature,
|
|
||||||
};
|
|
||||||
use std::any::type_name;
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Describe;
|
pub struct Describe;
|
||||||
|
|
||||||
@ -76,116 +73,39 @@ impl Command for Describe {
|
|||||||
"{shell:'true', uwu:true, features: {bugs:false, multiplatform:true, speed: 10}, fib: [1 1 2 3 5 8], on_save: {|x| $'Saving ($x)'}, first_commit: 2019-05-10, my_duration: (4min + 20sec)} | describe -d",
|
"{shell:'true', uwu:true, features: {bugs:false, multiplatform:true, speed: 10}, fib: [1 1 2 3 5 8], on_save: {|x| $'Saving ($x)'}, first_commit: 2019-05-10, my_duration: (4min + 20sec)} | describe -d",
|
||||||
result: Some(Value::test_record(record!(
|
result: Some(Value::test_record(record!(
|
||||||
"type" => Value::test_string("record"),
|
"type" => Value::test_string("record"),
|
||||||
"detailed_type" => Value::test_string("record<shell: string, uwu: bool, features: record<bugs: bool, multiplatform: bool, speed: int>, fib: list<int>, on_save: closure, first_commit: datetime, my_duration: duration>"),
|
|
||||||
"columns" => Value::test_record(record!(
|
"columns" => Value::test_record(record!(
|
||||||
"shell" => Value::test_record(record!(
|
"shell" => Value::test_string("string"),
|
||||||
"type" => Value::test_string("string"),
|
"uwu" => Value::test_string("bool"),
|
||||||
"detailed_type" => Value::test_string("string"),
|
|
||||||
"rust_type" => Value::test_string("&alloc::string::String"),
|
|
||||||
"value" => Value::test_string("true"),
|
|
||||||
)),
|
|
||||||
"uwu" => Value::test_record(record!(
|
|
||||||
"type" => Value::test_string("bool"),
|
|
||||||
"detailed_type" => Value::test_string("bool"),
|
|
||||||
"rust_type" => Value::test_string("bool"),
|
|
||||||
"value" => Value::test_bool(true),
|
|
||||||
)),
|
|
||||||
"features" => Value::test_record(record!(
|
"features" => Value::test_record(record!(
|
||||||
"type" => Value::test_string("record"),
|
"type" => Value::test_string("record"),
|
||||||
"detailed_type" => Value::test_string("record<bugs: bool, multiplatform: bool, speed: int>"),
|
|
||||||
"columns" => Value::test_record(record!(
|
"columns" => Value::test_record(record!(
|
||||||
"bugs" => Value::test_record(record!(
|
"bugs" => Value::test_string("bool"),
|
||||||
"type" => Value::test_string("bool"),
|
"multiplatform" => Value::test_string("bool"),
|
||||||
"detailed_type" => Value::test_string("bool"),
|
"speed" => Value::test_string("int"),
|
||||||
"rust_type" => Value::test_string("bool"),
|
|
||||||
"value" => Value::test_bool(false),
|
|
||||||
)),
|
)),
|
||||||
"multiplatform" => Value::test_record(record!(
|
|
||||||
"type" => Value::test_string("bool"),
|
|
||||||
"detailed_type" => Value::test_string("bool"),
|
|
||||||
"rust_type" => Value::test_string("bool"),
|
|
||||||
"value" => Value::test_bool(true),
|
|
||||||
)),
|
|
||||||
"speed" => Value::test_record(record!(
|
|
||||||
"type" => Value::test_string("int"),
|
|
||||||
"detailed_type" => Value::test_string("int"),
|
|
||||||
"rust_type" => Value::test_string("i64"),
|
|
||||||
"value" => Value::test_int(10),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
"rust_type" => Value::test_string("&nu_utils::shared_cow::SharedCow<nu_protocol::value::record::Record>"),
|
|
||||||
)),
|
)),
|
||||||
"fib" => Value::test_record(record!(
|
"fib" => Value::test_record(record!(
|
||||||
"type" => Value::test_string("list"),
|
"type" => Value::test_string("list"),
|
||||||
"detailed_type" => Value::test_string("list<int>"),
|
|
||||||
"length" => Value::test_int(6),
|
"length" => Value::test_int(6),
|
||||||
"rust_type" => Value::test_string("&mut alloc::vec::Vec<nu_protocol::value::Value>"),
|
"values" => Value::test_list(vec![
|
||||||
"value" => Value::test_list(vec![
|
Value::test_string("int"),
|
||||||
Value::test_record(record!(
|
Value::test_string("int"),
|
||||||
"type" => Value::test_string("int"),
|
Value::test_string("int"),
|
||||||
"detailed_type" => Value::test_string("int"),
|
Value::test_string("int"),
|
||||||
"rust_type" => Value::test_string("i64"),
|
Value::test_string("int"),
|
||||||
"value" => Value::test_int(1),
|
Value::test_string("int"),
|
||||||
)),
|
]),
|
||||||
Value::test_record(record!(
|
|
||||||
"type" => Value::test_string("int"),
|
|
||||||
"detailed_type" => Value::test_string("int"),
|
|
||||||
"rust_type" => Value::test_string("i64"),
|
|
||||||
"value" => Value::test_int(1),
|
|
||||||
)),
|
|
||||||
Value::test_record(record!(
|
|
||||||
"type" => Value::test_string("int"),
|
|
||||||
"detailed_type" => Value::test_string("int"),
|
|
||||||
"rust_type" => Value::test_string("i64"),
|
|
||||||
"value" => Value::test_int(2),
|
|
||||||
)),
|
|
||||||
Value::test_record(record!(
|
|
||||||
"type" => Value::test_string("int"),
|
|
||||||
"detailed_type" => Value::test_string("int"),
|
|
||||||
"rust_type" => Value::test_string("i64"),
|
|
||||||
"value" => Value::test_int(3),
|
|
||||||
)),
|
|
||||||
Value::test_record(record!(
|
|
||||||
"type" => Value::test_string("int"),
|
|
||||||
"detailed_type" => Value::test_string("int"),
|
|
||||||
"rust_type" => Value::test_string("i64"),
|
|
||||||
"value" => Value::test_int(5),
|
|
||||||
)),
|
|
||||||
Value::test_record(record!(
|
|
||||||
"type" => Value::test_string("int"),
|
|
||||||
"detailed_type" => Value::test_string("int"),
|
|
||||||
"rust_type" => Value::test_string("i64"),
|
|
||||||
"value" => Value::test_int(8),
|
|
||||||
))]
|
|
||||||
),
|
|
||||||
)),
|
)),
|
||||||
"on_save" => Value::test_record(record!(
|
"on_save" => Value::test_record(record!(
|
||||||
"type" => Value::test_string("closure"),
|
"type" => Value::test_string("closure"),
|
||||||
"detailed_type" => Value::test_string("closure"),
|
|
||||||
"rust_type" => Value::test_string("&alloc::boxed::Box<nu_protocol::engine::closure::Closure>"),
|
|
||||||
"value" => Value::test_closure(Closure {
|
|
||||||
block_id: BlockId::new(1),
|
|
||||||
captures: vec![],
|
|
||||||
}),
|
|
||||||
"signature" => Value::test_record(record!(
|
"signature" => Value::test_record(record!(
|
||||||
"name" => Value::test_string(""),
|
"name" => Value::test_string(""),
|
||||||
"category" => Value::test_string("default"),
|
"category" => Value::test_string("default"),
|
||||||
)),
|
)),
|
||||||
)),
|
)),
|
||||||
"first_commit" => Value::test_record(record!(
|
"first_commit" => Value::test_string("date"),
|
||||||
"type" => Value::test_string("datetime"),
|
"my_duration" => Value::test_string("duration"),
|
||||||
"detailed_type" => Value::test_string("datetime"),
|
|
||||||
"rust_type" => Value::test_string("chrono::datetime::DateTime<chrono::offset::fixed::FixedOffset>"),
|
|
||||||
"value" => Value::test_date("2019-05-10 00:00:00Z".parse().unwrap_or_default()),
|
|
||||||
)),
|
)),
|
||||||
"my_duration" => Value::test_record(record!(
|
|
||||||
"type" => Value::test_string("duration"),
|
|
||||||
"detailed_type" => Value::test_string("duration"),
|
|
||||||
"rust_type" => Value::test_string("i64"),
|
|
||||||
"value" => Value::test_duration(260_000_000_000),
|
|
||||||
))
|
|
||||||
)),
|
|
||||||
"rust_type" => Value::test_string("&nu_utils::shared_cow::SharedCow<nu_protocol::value::record::Record>"),
|
|
||||||
))),
|
))),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
@ -255,9 +175,7 @@ fn run(
|
|||||||
|
|
||||||
Value::record(
|
Value::record(
|
||||||
record! {
|
record! {
|
||||||
"type" => Value::string("bytestream", head),
|
"type" => Value::string(type_, head),
|
||||||
"detailed_type" => Value::string(type_, head),
|
|
||||||
"rust_type" => Value::string(type_of(&stream), head),
|
|
||||||
"origin" => Value::string(origin, head),
|
"origin" => Value::string(origin, head),
|
||||||
"metadata" => metadata_to_value(metadata, head),
|
"metadata" => metadata_to_value(metadata, head),
|
||||||
},
|
},
|
||||||
@ -274,7 +192,6 @@ fn run(
|
|||||||
description
|
description
|
||||||
}
|
}
|
||||||
PipelineData::ListStream(stream, ..) => {
|
PipelineData::ListStream(stream, ..) => {
|
||||||
let type_ = type_of(&stream);
|
|
||||||
if options.detailed {
|
if options.detailed {
|
||||||
let subtype = if options.no_collect {
|
let subtype = if options.no_collect {
|
||||||
Value::string("any", head)
|
Value::string("any", head)
|
||||||
@ -284,8 +201,6 @@ fn run(
|
|||||||
Value::record(
|
Value::record(
|
||||||
record! {
|
record! {
|
||||||
"type" => Value::string("stream", head),
|
"type" => Value::string("stream", head),
|
||||||
"detailed_type" => Value::string("list stream", head),
|
|
||||||
"rust_type" => Value::string(type_, head),
|
|
||||||
"origin" => Value::string("nushell", head),
|
"origin" => Value::string("nushell", head),
|
||||||
"subtype" => subtype,
|
"subtype" => subtype,
|
||||||
"metadata" => metadata_to_value(metadata, head),
|
"metadata" => metadata_to_value(metadata, head),
|
||||||
@ -314,95 +229,45 @@ fn run(
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum Description {
|
enum Description {
|
||||||
|
String(String),
|
||||||
Record(Record),
|
Record(Record),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Description {
|
impl Description {
|
||||||
fn into_value(self, span: Span) -> Value {
|
fn into_value(self, span: Span) -> Value {
|
||||||
match self {
|
match self {
|
||||||
|
Description::String(ty) => Value::string(ty, span),
|
||||||
Description::Record(record) => Value::record(record, span),
|
Description::Record(record) => Value::record(record, span),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn describe_value(value: Value, head: Span, engine_state: Option<&EngineState>) -> Value {
|
fn describe_value(value: Value, head: Span, engine_state: Option<&EngineState>) -> Value {
|
||||||
let Description::Record(record) = describe_value_inner(value, head, engine_state);
|
let record = match describe_value_inner(value, head, engine_state) {
|
||||||
|
Description::String(ty) => record! { "type" => Value::string(ty, head) },
|
||||||
|
Description::Record(record) => record,
|
||||||
|
};
|
||||||
Value::record(record, head)
|
Value::record(record, head)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type_of<T>(_: &T) -> String {
|
|
||||||
type_name::<T>().to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn describe_value_inner(
|
fn describe_value_inner(
|
||||||
mut value: Value,
|
value: Value,
|
||||||
head: Span,
|
head: Span,
|
||||||
engine_state: Option<&EngineState>,
|
engine_state: Option<&EngineState>,
|
||||||
) -> Description {
|
) -> Description {
|
||||||
let value_type = value.get_type().to_string();
|
|
||||||
match value {
|
match value {
|
||||||
Value::Bool { val, .. } => Description::Record(record! {
|
Value::Bool { .. }
|
||||||
"type" => Value::string("bool", head),
|
| Value::Int { .. }
|
||||||
"detailed_type" => Value::string(value_type, head),
|
| Value::Float { .. }
|
||||||
"rust_type" => Value::string(type_of(&val), head),
|
| Value::Filesize { .. }
|
||||||
"value" => value,
|
| Value::Duration { .. }
|
||||||
}),
|
| Value::Date { .. }
|
||||||
Value::Int { val, .. } => Description::Record(record! {
|
| Value::Range { .. }
|
||||||
"type" => Value::string("int", head),
|
| Value::String { .. }
|
||||||
"detailed_type" => Value::string(value_type, head),
|
| Value::Glob { .. }
|
||||||
"rust_type" => Value::string(type_of(&val), head),
|
| Value::Nothing { .. } => Description::String(value.get_type().to_string()),
|
||||||
"value" => value,
|
Value::Record { val, .. } => {
|
||||||
}),
|
let mut columns = val.into_owned();
|
||||||
Value::Float { val, .. } => Description::Record(record! {
|
|
||||||
"type" => Value::string("float", head),
|
|
||||||
"detailed_type" => Value::string(value_type, head),
|
|
||||||
"rust_type" => Value::string(type_of(&val), head),
|
|
||||||
"value" => value,
|
|
||||||
}),
|
|
||||||
Value::Filesize { val, .. } => Description::Record(record! {
|
|
||||||
"type" => Value::string("filesize", head),
|
|
||||||
"detailed_type" => Value::string(value_type, head),
|
|
||||||
"rust_type" => Value::string(type_of(&val), head),
|
|
||||||
"value" => value,
|
|
||||||
}),
|
|
||||||
Value::Duration { val, .. } => Description::Record(record! {
|
|
||||||
"type" => Value::string("duration", head),
|
|
||||||
"detailed_type" => Value::string(value_type, head),
|
|
||||||
"rust_type" => Value::string(type_of(&val), head),
|
|
||||||
"value" => value,
|
|
||||||
}),
|
|
||||||
Value::Date { val, .. } => Description::Record(record! {
|
|
||||||
"type" => Value::string("datetime", head),
|
|
||||||
"detailed_type" => Value::string(value_type, head),
|
|
||||||
"rust_type" => Value::string(type_of(&val), head),
|
|
||||||
"value" => value,
|
|
||||||
}),
|
|
||||||
Value::Range { ref val, .. } => Description::Record(record! {
|
|
||||||
"type" => Value::string("range", head),
|
|
||||||
"detailed_type" => Value::string(value_type, head),
|
|
||||||
"rust_type" => Value::string(type_of(&val), head),
|
|
||||||
"value" => value,
|
|
||||||
}),
|
|
||||||
Value::String { ref val, .. } => Description::Record(record! {
|
|
||||||
"type" => Value::string("string", head),
|
|
||||||
"detailed_type" => Value::string(value_type, head),
|
|
||||||
"rust_type" => Value::string(type_of(&val), head),
|
|
||||||
"value" => value,
|
|
||||||
}),
|
|
||||||
Value::Glob { ref val, .. } => Description::Record(record! {
|
|
||||||
"type" => Value::string("glob", head),
|
|
||||||
"detailed_type" => Value::string(value_type, head),
|
|
||||||
"rust_type" => Value::string(type_of(&val), head),
|
|
||||||
"value" => value,
|
|
||||||
}),
|
|
||||||
Value::Nothing { .. } => Description::Record(record! {
|
|
||||||
"type" => Value::string("nothing", head),
|
|
||||||
"detailed_type" => Value::string(value_type, head),
|
|
||||||
"rust_type" => Value::string("", head),
|
|
||||||
"value" => value,
|
|
||||||
}),
|
|
||||||
Value::Record { ref val, .. } => {
|
|
||||||
let mut columns = val.clone().into_owned();
|
|
||||||
for (_, val) in &mut columns {
|
for (_, val) in &mut columns {
|
||||||
*val =
|
*val =
|
||||||
describe_value_inner(std::mem::take(val), head, engine_state).into_value(head);
|
describe_value_inner(std::mem::take(val), head, engine_state).into_value(head);
|
||||||
@ -410,34 +275,25 @@ fn describe_value_inner(
|
|||||||
|
|
||||||
Description::Record(record! {
|
Description::Record(record! {
|
||||||
"type" => Value::string("record", head),
|
"type" => Value::string("record", head),
|
||||||
"detailed_type" => Value::string(value_type, head),
|
"columns" => Value::record(columns, head),
|
||||||
"columns" => Value::record(columns.clone(), head),
|
|
||||||
"rust_type" => Value::string(type_of(&val), head),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Value::List { ref mut vals, .. } => {
|
Value::List { mut vals, .. } => {
|
||||||
for val in &mut *vals {
|
for val in &mut vals {
|
||||||
*val =
|
*val =
|
||||||
describe_value_inner(std::mem::take(val), head, engine_state).into_value(head);
|
describe_value_inner(std::mem::take(val), head, engine_state).into_value(head);
|
||||||
}
|
}
|
||||||
|
|
||||||
Description::Record(record! {
|
Description::Record(record! {
|
||||||
"type" => Value::string("list", head),
|
"type" => Value::string("list", head),
|
||||||
"detailed_type" => Value::string(value_type, head),
|
|
||||||
"length" => Value::int(vals.len() as i64, head),
|
"length" => Value::int(vals.len() as i64, head),
|
||||||
"rust_type" => Value::string(type_of(&vals), head),
|
"values" => Value::list(vals, head),
|
||||||
"value" => value,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Value::Closure { ref val, .. } => {
|
Value::Closure { val, .. } => {
|
||||||
let block = engine_state.map(|engine_state| engine_state.get_block(val.block_id));
|
let block = engine_state.map(|engine_state| engine_state.get_block(val.block_id));
|
||||||
|
|
||||||
let mut record = record! {
|
let mut record = record! { "type" => Value::string("closure", head) };
|
||||||
"type" => Value::string("closure", head),
|
|
||||||
"detailed_type" => Value::string(value_type, head),
|
|
||||||
"rust_type" => Value::string(type_of(&val), head),
|
|
||||||
"value" => value,
|
|
||||||
};
|
|
||||||
if let Some(block) = block {
|
if let Some(block) = block {
|
||||||
record.push(
|
record.push(
|
||||||
"signature",
|
"signature",
|
||||||
@ -452,37 +308,21 @@ fn describe_value_inner(
|
|||||||
}
|
}
|
||||||
Description::Record(record)
|
Description::Record(record)
|
||||||
}
|
}
|
||||||
Value::Error { ref error, .. } => Description::Record(record! {
|
Value::Error { error, .. } => Description::Record(record! {
|
||||||
"type" => Value::string("error", head),
|
"type" => Value::string("error", head),
|
||||||
"detailed_type" => Value::string(value_type, head),
|
|
||||||
"subtype" => Value::string(error.to_string(), head),
|
"subtype" => Value::string(error.to_string(), head),
|
||||||
"rust_type" => Value::string(type_of(&error), head),
|
|
||||||
"value" => value,
|
|
||||||
}),
|
}),
|
||||||
Value::Binary { ref val, .. } => Description::Record(record! {
|
Value::Binary { val, .. } => Description::Record(record! {
|
||||||
"type" => Value::string("binary", head),
|
"type" => Value::string("binary", head),
|
||||||
"detailed_type" => Value::string(value_type, head),
|
|
||||||
"length" => Value::int(val.len() as i64, head),
|
"length" => Value::int(val.len() as i64, head),
|
||||||
"rust_type" => Value::string(type_of(&val), head),
|
|
||||||
"value" => value,
|
|
||||||
}),
|
}),
|
||||||
Value::CellPath { ref val, .. } => Description::Record(record! {
|
Value::CellPath { val, .. } => Description::Record(record! {
|
||||||
"type" => Value::string("cell-path", head),
|
"type" => Value::string("cell-path", head),
|
||||||
"detailed_type" => Value::string(value_type, head),
|
|
||||||
"length" => Value::int(val.members.len() as i64, head),
|
"length" => Value::int(val.members.len() as i64, head),
|
||||||
"rust_type" => Value::string(type_of(&val), head),
|
|
||||||
"value" => value
|
|
||||||
}),
|
}),
|
||||||
Value::Custom { ref val, .. } => Description::Record(record! {
|
Value::Custom { val, .. } => Description::Record(record! {
|
||||||
"type" => Value::string("custom", head),
|
"type" => Value::string("custom", head),
|
||||||
"detailed_type" => Value::string(value_type, head),
|
|
||||||
"subtype" => Value::string(val.type_name(), head),
|
"subtype" => Value::string(val.type_name(), head),
|
||||||
"rust_type" => Value::string(type_of(&val), head),
|
|
||||||
"value" =>
|
|
||||||
match val.to_base_value(head) {
|
|
||||||
Ok(base_value) => base_value,
|
|
||||||
Err(err) => Value::error(err, head),
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,16 @@ impl Command for Do {
|
|||||||
"ignore errors as the closure runs",
|
"ignore errors as the closure runs",
|
||||||
Some('i'),
|
Some('i'),
|
||||||
)
|
)
|
||||||
|
.switch(
|
||||||
|
"ignore-shell-errors",
|
||||||
|
"ignore shell errors as the closure runs",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"ignore-program-errors",
|
||||||
|
"ignore external program errors as the closure runs",
|
||||||
|
Some('p'),
|
||||||
|
)
|
||||||
.switch(
|
.switch(
|
||||||
"capture-errors",
|
"capture-errors",
|
||||||
"catch errors as the closure runs, and return them",
|
"catch errors as the closure runs, and return them",
|
||||||
@ -61,6 +71,36 @@ impl Command for Do {
|
|||||||
let rest: Vec<Value> = call.rest(engine_state, caller_stack, 1)?;
|
let rest: Vec<Value> = call.rest(engine_state, caller_stack, 1)?;
|
||||||
let ignore_all_errors = call.has_flag(engine_state, caller_stack, "ignore-errors")?;
|
let ignore_all_errors = call.has_flag(engine_state, caller_stack, "ignore-errors")?;
|
||||||
|
|
||||||
|
if call.has_flag(engine_state, caller_stack, "ignore-shell-errors")? {
|
||||||
|
nu_protocol::report_shell_warning(
|
||||||
|
engine_state,
|
||||||
|
&ShellError::GenericError {
|
||||||
|
error: "Deprecated option".into(),
|
||||||
|
msg: "`--ignore-shell-errors` is deprecated and will be removed in 0.102.0."
|
||||||
|
.into(),
|
||||||
|
span: Some(call.head),
|
||||||
|
help: Some("Please use the `--ignore-errors(-i)`".into()),
|
||||||
|
inner: vec![],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if call.has_flag(engine_state, caller_stack, "ignore-program-errors")? {
|
||||||
|
nu_protocol::report_shell_warning(
|
||||||
|
engine_state,
|
||||||
|
&ShellError::GenericError {
|
||||||
|
error: "Deprecated option".into(),
|
||||||
|
msg: "`--ignore-program-errors` is deprecated and will be removed in 0.102.0."
|
||||||
|
.into(),
|
||||||
|
span: Some(call.head),
|
||||||
|
help: Some("Please use the `--ignore-errors(-i)`".into()),
|
||||||
|
inner: vec![],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let ignore_shell_errors = ignore_all_errors
|
||||||
|
|| call.has_flag(engine_state, caller_stack, "ignore-shell-errors")?;
|
||||||
|
let ignore_program_errors = ignore_all_errors
|
||||||
|
|| call.has_flag(engine_state, caller_stack, "ignore-program-errors")?;
|
||||||
let capture_errors = call.has_flag(engine_state, caller_stack, "capture-errors")?;
|
let capture_errors = call.has_flag(engine_state, caller_stack, "capture-errors")?;
|
||||||
let has_env = call.has_flag(engine_state, caller_stack, "env")?;
|
let has_env = call.has_flag(engine_state, caller_stack, "env")?;
|
||||||
|
|
||||||
@ -166,7 +206,7 @@ impl Command for Do {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(PipelineData::ByteStream(mut stream, metadata))
|
Ok(PipelineData::ByteStream(mut stream, metadata))
|
||||||
if ignore_all_errors
|
if ignore_program_errors
|
||||||
&& !matches!(
|
&& !matches!(
|
||||||
caller_stack.stdout(),
|
caller_stack.stdout(),
|
||||||
OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value
|
OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value
|
||||||
@ -178,10 +218,10 @@ impl Command for Do {
|
|||||||
}
|
}
|
||||||
Ok(PipelineData::ByteStream(stream, metadata))
|
Ok(PipelineData::ByteStream(stream, metadata))
|
||||||
}
|
}
|
||||||
Ok(PipelineData::Value(Value::Error { .. }, ..)) | Err(_) if ignore_all_errors => {
|
Ok(PipelineData::Value(Value::Error { .. }, ..)) | Err(_) if ignore_shell_errors => {
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
Ok(PipelineData::ListStream(stream, metadata)) if ignore_all_errors => {
|
Ok(PipelineData::ListStream(stream, metadata)) if ignore_shell_errors => {
|
||||||
let stream = stream.map(move |value| {
|
let stream = stream.map(move |value| {
|
||||||
if let Value::Error { .. } = value {
|
if let Value::Error { .. } = value {
|
||||||
Value::nothing(head)
|
Value::nothing(head)
|
||||||
|
@ -116,8 +116,6 @@ impl Command for OverlayUse {
|
|||||||
// b) refreshing an active overlay (the origin module changed)
|
// b) refreshing an active overlay (the origin module changed)
|
||||||
|
|
||||||
let module = engine_state.get_module(module_id);
|
let module = engine_state.get_module(module_id);
|
||||||
// in such case, should also make sure that PWD is not restored in old overlays.
|
|
||||||
let cwd = caller_stack.get_env_var(engine_state, "PWD").cloned();
|
|
||||||
|
|
||||||
// Evaluate the export-env block (if any) and keep its environment
|
// Evaluate the export-env block (if any) and keep its environment
|
||||||
if let Some(block_id) = module.env_block {
|
if let Some(block_id) = module.env_block {
|
||||||
@ -162,19 +160,11 @@ impl Command for OverlayUse {
|
|||||||
|
|
||||||
// The export-env block should see the env vars *before* activating this overlay
|
// The export-env block should see the env vars *before* activating this overlay
|
||||||
caller_stack.add_overlay(overlay_name);
|
caller_stack.add_overlay(overlay_name);
|
||||||
// make sure that PWD is not restored in old overlays.
|
|
||||||
if let Some(cwd) = cwd {
|
|
||||||
caller_stack.add_env_var("PWD".to_string(), cwd);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge the block's environment to the current stack
|
// Merge the block's environment to the current stack
|
||||||
redirect_env(engine_state, caller_stack, &callee_stack);
|
redirect_env(engine_state, caller_stack, &callee_stack);
|
||||||
} else {
|
} else {
|
||||||
caller_stack.add_overlay(overlay_name);
|
caller_stack.add_overlay(overlay_name);
|
||||||
// make sure that PWD is not restored in old overlays.
|
|
||||||
if let Some(cwd) = cwd {
|
|
||||||
caller_stack.add_env_var("PWD".to_string(), cwd);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
caller_stack.add_overlay(overlay_name);
|
caller_stack.add_overlay(overlay_name);
|
||||||
|
@ -5,7 +5,7 @@ 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.104.1"
|
version = "0.103.0"
|
||||||
|
|
||||||
# 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,10 +13,10 @@ version = "0.104.1"
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.104.1" }
|
nu-engine = { path = "../nu-engine", version = "0.103.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.104.1" }
|
nu-path = { path = "../nu-path", version = "0.103.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.104.1", features = ["plugin"] }
|
nu-protocol = { path = "../nu-protocol", version = "0.103.0", features = ["plugin"] }
|
||||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.104.1" }
|
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.103.0" }
|
||||||
|
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ 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.104.1"
|
version = "0.103.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
@ -14,12 +14,12 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.103.0", default-features = false }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.103.0", default-features = false }
|
||||||
nu-json = { path = "../nu-json", version = "0.104.1" }
|
nu-json = { path = "../nu-json", version = "0.103.0" }
|
||||||
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.104.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.103.0" }
|
@ -120,7 +120,7 @@ impl<'a> StyleComputer<'a> {
|
|||||||
("int".to_string(), ComputableStyle::Static(Color::White.normal())),
|
("int".to_string(), ComputableStyle::Static(Color::White.normal())),
|
||||||
("filesize".to_string(), ComputableStyle::Static(Color::Cyan.normal())),
|
("filesize".to_string(), ComputableStyle::Static(Color::Cyan.normal())),
|
||||||
("duration".to_string(), ComputableStyle::Static(Color::White.normal())),
|
("duration".to_string(), ComputableStyle::Static(Color::White.normal())),
|
||||||
("datetime".to_string(), ComputableStyle::Static(Color::Purple.normal())),
|
("date".to_string(), ComputableStyle::Static(Color::Purple.normal())),
|
||||||
("range".to_string(), ComputableStyle::Static(Color::White.normal())),
|
("range".to_string(), ComputableStyle::Static(Color::White.normal())),
|
||||||
("float".to_string(), ComputableStyle::Static(Color::White.normal())),
|
("float".to_string(), ComputableStyle::Static(Color::White.normal())),
|
||||||
("string".to_string(), ComputableStyle::Static(Color::White.normal())),
|
("string".to_string(), ComputableStyle::Static(Color::White.normal())),
|
||||||
|
@ -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.104.1"
|
version = "0.103.0"
|
||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
@ -16,21 +16,21 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.104.1" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.103.0" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.104.1" }
|
nu-color-config = { path = "../nu-color-config", version = "0.103.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.103.0", default-features = false }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.104.1" }
|
nu-glob = { path = "../nu-glob", version = "0.103.0" }
|
||||||
nu-json = { path = "../nu-json", version = "0.104.1" }
|
nu-json = { path = "../nu-json", version = "0.103.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.104.1" }
|
nu-parser = { path = "../nu-parser", version = "0.103.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.104.1" }
|
nu-path = { path = "../nu-path", version = "0.103.0" }
|
||||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.104.1" }
|
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.103.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.103.0", default-features = false }
|
||||||
nu-system = { path = "../nu-system", version = "0.104.1" }
|
nu-system = { path = "../nu-system", version = "0.103.0" }
|
||||||
nu-table = { path = "../nu-table", version = "0.104.1" }
|
nu-table = { path = "../nu-table", version = "0.103.0" }
|
||||||
nu-term-grid = { path = "../nu-term-grid", version = "0.104.1" }
|
nu-term-grid = { path = "../nu-term-grid", version = "0.103.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
|
nu-utils = { path = "../nu-utils", version = "0.103.0", default-features = false }
|
||||||
nu-ansi-term = { workspace = true }
|
nu-ansi-term = { workspace = true }
|
||||||
nuon = { path = "../nuon", version = "0.104.1" }
|
nuon = { path = "../nuon", version = "0.103.0" }
|
||||||
|
|
||||||
alphanumeric-sort = { workspace = true }
|
alphanumeric-sort = { workspace = true }
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
@ -129,7 +129,7 @@ v_htmlescape = { workspace = true }
|
|||||||
wax = { workspace = true }
|
wax = { workspace = true }
|
||||||
which = { workspace = true, optional = true }
|
which = { workspace = true, optional = true }
|
||||||
unicode-width = { workspace = true }
|
unicode-width = { workspace = true }
|
||||||
data-encoding = { version = "2.9.0", features = ["alloc"] }
|
data-encoding = { version = "2.8.0", features = ["alloc"] }
|
||||||
web-time = { workspace = true }
|
web-time = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
@ -209,8 +209,8 @@ sqlite = ["rusqlite"]
|
|||||||
trash-support = ["trash"]
|
trash-support = ["trash"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.104.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.103.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.104.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.103.0" }
|
||||||
|
|
||||||
dirs = { workspace = true }
|
dirs = { workspace = true }
|
||||||
mockito = { workspace = true, default-features = false }
|
mockito = { workspace = true, default-features = false }
|
||||||
|
@ -1,29 +1,12 @@
|
|||||||
use crate::{generate_strftime_list, parse_date_from_string};
|
use crate::{generate_strftime_list, parse_date_from_string};
|
||||||
use chrono::{
|
use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, TimeZone, Utc};
|
||||||
DateTime, Datelike, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone,
|
use human_date_parser::{from_human_time, ParseResult};
|
||||||
Timelike, Utc,
|
|
||||||
};
|
|
||||||
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::*;
|
||||||
|
|
||||||
const HOUR: i32 = 60 * 60;
|
|
||||||
const ALLOWED_COLUMNS: [&str; 10] = [
|
|
||||||
"year",
|
|
||||||
"month",
|
|
||||||
"day",
|
|
||||||
"hour",
|
|
||||||
"minute",
|
|
||||||
"second",
|
|
||||||
"millisecond",
|
|
||||||
"microsecond",
|
|
||||||
"nanosecond",
|
|
||||||
"timezone",
|
|
||||||
];
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
zone_options: Option<Spanned<Zone>>,
|
zone_options: Option<Spanned<Zone>>,
|
||||||
format_options: Option<Spanned<DatetimeFormat>>,
|
format_options: Option<DatetimeFormat>,
|
||||||
cell_paths: Option<Vec<CellPath>>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,12 +64,8 @@ impl Command for IntoDatetime {
|
|||||||
(Type::String, Type::Date),
|
(Type::String, Type::Date),
|
||||||
(Type::List(Box::new(Type::String)), Type::List(Box::new(Type::Date))),
|
(Type::List(Box::new(Type::String)), Type::List(Box::new(Type::Date))),
|
||||||
(Type::table(), Type::table()),
|
(Type::table(), Type::table()),
|
||||||
(Type::Nothing, Type::table()),
|
|
||||||
// FIXME: https://github.com/nushell/nushell/issues/15485
|
|
||||||
// 'record -> any' was added as a temporary workaround to avoid type inference issues. The Any arm needs to be appear first.
|
|
||||||
(Type::record(), Type::Any),
|
|
||||||
(Type::record(), Type::record()),
|
(Type::record(), Type::record()),
|
||||||
(Type::record(), Type::Date),
|
(Type::Nothing, Type::table()),
|
||||||
// FIXME Type::Any input added to disable pipeline input type checking, as run-time checks can raise undesirable type errors
|
// FIXME Type::Any input added to disable pipeline input type checking, as run-time checks can raise undesirable type errors
|
||||||
// which aren't caught by the parser. see https://github.com/nushell/nushell/pull/14922 for more details
|
// which aren't caught by the parser. see https://github.com/nushell/nushell/pull/14922 for more details
|
||||||
// only applicable for --list flag
|
// only applicable for --list flag
|
||||||
@ -116,6 +95,11 @@ impl Command for IntoDatetime {
|
|||||||
"Show all possible variables for use in --format flag",
|
"Show all possible variables for use in --format flag",
|
||||||
Some('l'),
|
Some('l'),
|
||||||
)
|
)
|
||||||
|
.switch(
|
||||||
|
"list-human",
|
||||||
|
"Show human-readable datetime parsing examples",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
@ -133,6 +117,8 @@ impl Command for IntoDatetime {
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
if call.has_flag(engine_state, stack, "list")? {
|
if call.has_flag(engine_state, stack, "list")? {
|
||||||
Ok(generate_strftime_list(call.head, true).into_pipeline_data())
|
Ok(generate_strftime_list(call.head, true).into_pipeline_data())
|
||||||
|
} else if call.has_flag(engine_state, stack, "list-human")? {
|
||||||
|
Ok(list_human_readable_examples(call.head).into_pipeline_data())
|
||||||
} else {
|
} else {
|
||||||
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);
|
||||||
@ -152,16 +138,13 @@ impl Command for IntoDatetime {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let format_options = call
|
let format_options = call
|
||||||
.get_flag::<Spanned<String>>(engine_state, stack, "format")?
|
.get_flag::<String>(engine_state, stack, "format")?
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|fmt| Spanned {
|
.map(|fmt| DatetimeFormat(fmt.to_string()));
|
||||||
item: DatetimeFormat(fmt.item.to_string()),
|
|
||||||
span: fmt.span,
|
|
||||||
});
|
|
||||||
|
|
||||||
let args = Arguments {
|
let args = Arguments {
|
||||||
zone_options,
|
|
||||||
format_options,
|
format_options,
|
||||||
|
zone_options,
|
||||||
cell_paths,
|
cell_paths,
|
||||||
};
|
};
|
||||||
operate(action, args, input, call.head, engine_state.signals())
|
operate(action, args, input, call.head, engine_state.signals())
|
||||||
@ -237,12 +220,6 @@ impl Command for IntoDatetime {
|
|||||||
#[allow(clippy::inconsistent_digit_grouping)]
|
#[allow(clippy::inconsistent_digit_grouping)]
|
||||||
result: example_result_1(1614434140_000000000),
|
result: example_result_1(1614434140_000000000),
|
||||||
},
|
},
|
||||||
Example {
|
|
||||||
description: "Using a record as input",
|
|
||||||
example: "{year: 2025, month: 3, day: 30, hour: 12, minute: 15, second: 59, timezone: '+02:00'} | into datetime",
|
|
||||||
#[allow(clippy::inconsistent_digit_grouping)]
|
|
||||||
result: example_result_1(1743329759_000000000),
|
|
||||||
},
|
|
||||||
Example {
|
Example {
|
||||||
description: "Convert list of timestamps to datetimes",
|
description: "Convert list of timestamps to datetimes",
|
||||||
example: r#"["2023-03-30 10:10:07 -05:00", "2023-05-05 13:43:49 -05:00", "2023-06-05 01:37:42 -05:00"] | into datetime"#,
|
example: r#"["2023-03-30 10:10:07 -05:00", "2023-05-05 13:43:49 -05:00", "2023-06-05 01:37:42 -05:00"] | into datetime"#,
|
||||||
@ -276,11 +253,26 @@ impl Command for IntoDatetime {
|
|||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Parsing human readable datetimes",
|
||||||
|
example: "'Today at 18:30' | into datetime",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Parsing human readable datetimes",
|
||||||
|
example: "'Last Friday at 19:45' | into datetime",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Parsing human readable datetimes",
|
||||||
|
example: "'In 5 minutes and 30 seconds' | into datetime",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
struct DatetimeFormat(String);
|
struct DatetimeFormat(String);
|
||||||
|
|
||||||
fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||||
@ -292,44 +284,45 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
return input.clone();
|
return input.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Value::Record { val: record, .. } = input {
|
|
||||||
if let Some(tz) = timezone {
|
|
||||||
return Value::error(
|
|
||||||
ShellError::IncompatibleParameters {
|
|
||||||
left_message: "got a record as input".into(),
|
|
||||||
left_span: head,
|
|
||||||
right_message: "the timezone should be included in the record".into(),
|
|
||||||
right_span: tz.span,
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(dt) = dateformat {
|
|
||||||
return Value::error(
|
|
||||||
ShellError::IncompatibleParameters {
|
|
||||||
left_message: "got a record as input".into(),
|
|
||||||
left_span: head,
|
|
||||||
right_message: "cannot be used with records".into(),
|
|
||||||
right_span: dt.span,
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let span = input.span();
|
|
||||||
return merge_record(record, head, span).unwrap_or_else(|err| Value::error(err, span));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's try dtparse first
|
// Let's try dtparse first
|
||||||
if matches!(input, Value::String { .. }) && dateformat.is_none() {
|
if matches!(input, Value::String { .. }) && dateformat.is_none() {
|
||||||
let span = input.span();
|
let span = input.span();
|
||||||
if let Ok(input_val) = input.coerce_str() {
|
if let Ok(input_val) = input.coerce_str() {
|
||||||
if let Ok(date) = parse_date_from_string(&input_val, span) {
|
match parse_date_from_string(&input_val, span) {
|
||||||
return Value::date(date, span);
|
Ok(date) => return Value::date(date, span),
|
||||||
|
Err(_) => {
|
||||||
|
if let Ok(date) = from_human_time(&input_val) {
|
||||||
|
match date {
|
||||||
|
ParseResult::Date(date) => {
|
||||||
|
let time = Local::now().time();
|
||||||
|
let combined = date.and_time(time);
|
||||||
|
let local_offset = *Local::now().offset();
|
||||||
|
let dt_fixed =
|
||||||
|
TimeZone::from_local_datetime(&local_offset, &combined)
|
||||||
|
.single()
|
||||||
|
.unwrap_or_default();
|
||||||
|
return Value::date(dt_fixed, span);
|
||||||
|
}
|
||||||
|
ParseResult::DateTime(date) => {
|
||||||
|
return Value::date(date.fixed_offset(), span)
|
||||||
|
}
|
||||||
|
ParseResult::Time(time) => {
|
||||||
|
let date = Local::now().date_naive();
|
||||||
|
let combined = date.and_time(time);
|
||||||
|
let local_offset = *Local::now().offset();
|
||||||
|
let dt_fixed =
|
||||||
|
TimeZone::from_local_datetime(&local_offset, &combined)
|
||||||
|
.single()
|
||||||
|
.unwrap_or_default();
|
||||||
|
return Value::date(dt_fixed, span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const HOUR: i32 = 60 * 60;
|
||||||
|
|
||||||
// Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?)
|
// Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?)
|
||||||
let timestamp = match input {
|
let timestamp = match input {
|
||||||
@ -410,59 +403,23 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
|
|
||||||
let parse_as_string = |val: &str| {
|
let parse_as_string = |val: &str| {
|
||||||
match dateformat {
|
match dateformat {
|
||||||
Some(dt_format) => match DateTime::parse_from_str(val, &dt_format.item.0) {
|
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
|
||||||
Ok(dt) => {
|
Ok(d) => Value::date ( d, head ),
|
||||||
match timezone {
|
|
||||||
None => {
|
|
||||||
Value::date ( dt, head )
|
|
||||||
},
|
|
||||||
Some(Spanned { item, span }) => match item {
|
|
||||||
Zone::Utc => {
|
|
||||||
Value::date ( dt, head )
|
|
||||||
}
|
|
||||||
Zone::Local => {
|
|
||||||
Value::date(dt.with_timezone(&Local).into(), *span)
|
|
||||||
}
|
|
||||||
Zone::East(i) => match FixedOffset::east_opt((*i as i32) * HOUR) {
|
|
||||||
Some(eastoffset) => {
|
|
||||||
Value::date(dt.with_timezone(&eastoffset), *span)
|
|
||||||
}
|
|
||||||
None => Value::error(
|
|
||||||
ShellError::DatetimeParseError {
|
|
||||||
msg: input.to_abbreviated_string(&nu_protocol::Config::default()),
|
|
||||||
span: *span,
|
|
||||||
},
|
|
||||||
*span,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
Zone::West(i) => match FixedOffset::west_opt((*i as i32) * HOUR) {
|
|
||||||
Some(westoffset) => {
|
|
||||||
Value::date(dt.with_timezone(&westoffset), *span)
|
|
||||||
}
|
|
||||||
None => Value::error(
|
|
||||||
ShellError::DatetimeParseError {
|
|
||||||
msg: input.to_abbreviated_string(&nu_protocol::Config::default()),
|
|
||||||
span: *span,
|
|
||||||
},
|
|
||||||
*span,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
Zone::Error => Value::error(
|
|
||||||
// This is an argument error, not an input error
|
|
||||||
ShellError::TypeMismatch {
|
|
||||||
err_message: "Invalid timezone or offset".to_string(),
|
|
||||||
span: *span,
|
|
||||||
},
|
|
||||||
*span,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(reason) => {
|
Err(reason) => {
|
||||||
parse_with_format(val, &dt_format.item.0, head).unwrap_or_else(|_| Value::error (
|
match NaiveDateTime::parse_from_str(val, &dt.0) {
|
||||||
ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt_format.item.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) => {
|
||||||
|
let dt_fixed =
|
||||||
|
Local.from_local_datetime(&d).single().unwrap_or_default();
|
||||||
|
|
||||||
|
Value::date(dt_fixed.into(),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,
|
head,
|
||||||
))
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -497,260 +454,42 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge_record(record: &Record, head: Span, span: Span) -> Result<Value, ShellError> {
|
fn list_human_readable_examples(span: Span) -> Value {
|
||||||
if let Some(invalid_col) = record
|
let examples: Vec<String> = vec![
|
||||||
.columns()
|
"Today 18:30".into(),
|
||||||
.find(|key| !ALLOWED_COLUMNS.contains(&key.as_str()))
|
"2022-11-07 13:25:30".into(),
|
||||||
{
|
"15:20 Friday".into(),
|
||||||
let allowed_cols = ALLOWED_COLUMNS.join(", ");
|
"This Friday 17:00".into(),
|
||||||
return Err(ShellError::UnsupportedInput {
|
"13:25, Next Tuesday".into(),
|
||||||
msg: format!(
|
"Last Friday at 19:45".into(),
|
||||||
"Column '{invalid_col}' is not valid for a structured datetime. Allowed columns are: {allowed_cols}"
|
"In 3 days".into(),
|
||||||
),
|
"In 2 hours".into(),
|
||||||
input: "value originates from here".into(),
|
"10 hours and 5 minutes ago".into(),
|
||||||
msg_span: head,
|
"1 years ago".into(),
|
||||||
input_span: span
|
"A year ago".into(),
|
||||||
}
|
"A month ago".into(),
|
||||||
);
|
"A week ago".into(),
|
||||||
};
|
"A day ago".into(),
|
||||||
|
"An hour ago".into(),
|
||||||
|
"A minute ago".into(),
|
||||||
|
"A second ago".into(),
|
||||||
|
"Now".into(),
|
||||||
|
];
|
||||||
|
|
||||||
// Empty fields are filled in a specific way: the time units bigger than the biggest provided fields are assumed to be current and smaller ones are zeroed.
|
let records = examples
|
||||||
// And local timezone is used if not provided.
|
.iter()
|
||||||
#[derive(Debug)]
|
.map(|s| {
|
||||||
enum RecordColumnDefault {
|
Value::record(
|
||||||
Now,
|
record! {
|
||||||
Zero,
|
"parseable human datetime examples" => Value::test_string(s.to_string()),
|
||||||
}
|
"result" => action(&Value::test_string(s.to_string()), &Arguments { zone_options: None, format_options: None, cell_paths: None }, span)
|
||||||
let mut record_column_default = RecordColumnDefault::Now;
|
|
||||||
|
|
||||||
let now = Local::now();
|
|
||||||
let mut now_nanosecond = now.nanosecond();
|
|
||||||
let now_millisecond = now_nanosecond / 1_000_000;
|
|
||||||
now_nanosecond %= 1_000_000;
|
|
||||||
let now_microsecond = now_nanosecond / 1_000;
|
|
||||||
now_nanosecond %= 1_000;
|
|
||||||
|
|
||||||
let year: i32 = match record.get("year") {
|
|
||||||
Some(val) => {
|
|
||||||
record_column_default = RecordColumnDefault::Zero;
|
|
||||||
match val {
|
|
||||||
Value::Int { val, .. } => *val as i32,
|
|
||||||
other => {
|
|
||||||
return Err(ShellError::OnlySupportsThisInputType {
|
|
||||||
exp_input_type: "int".to_string(),
|
|
||||||
wrong_type: other.get_type().to_string(),
|
|
||||||
dst_span: head,
|
|
||||||
src_span: other.span(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => now.year(),
|
|
||||||
};
|
|
||||||
let month = match record.get("month") {
|
|
||||||
Some(col_val) => {
|
|
||||||
record_column_default = RecordColumnDefault::Zero;
|
|
||||||
parse_value_from_record_as_u32("month", col_val, &head, &span)?
|
|
||||||
}
|
|
||||||
None => match record_column_default {
|
|
||||||
RecordColumnDefault::Now => now.month(),
|
|
||||||
RecordColumnDefault::Zero => 1,
|
|
||||||
},
|
},
|
||||||
};
|
span,
|
||||||
let day = match record.get("day") {
|
)
|
||||||
Some(col_val) => {
|
|
||||||
record_column_default = RecordColumnDefault::Zero;
|
|
||||||
parse_value_from_record_as_u32("day", col_val, &head, &span)?
|
|
||||||
}
|
|
||||||
None => match record_column_default {
|
|
||||||
RecordColumnDefault::Now => now.day(),
|
|
||||||
RecordColumnDefault::Zero => 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let hour = match record.get("hour") {
|
|
||||||
Some(col_val) => {
|
|
||||||
record_column_default = RecordColumnDefault::Zero;
|
|
||||||
parse_value_from_record_as_u32("hour", col_val, &head, &span)?
|
|
||||||
}
|
|
||||||
None => match record_column_default {
|
|
||||||
RecordColumnDefault::Now => now.hour(),
|
|
||||||
RecordColumnDefault::Zero => 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let minute = match record.get("minute") {
|
|
||||||
Some(col_val) => {
|
|
||||||
record_column_default = RecordColumnDefault::Zero;
|
|
||||||
parse_value_from_record_as_u32("minute", col_val, &head, &span)?
|
|
||||||
}
|
|
||||||
None => match record_column_default {
|
|
||||||
RecordColumnDefault::Now => now.minute(),
|
|
||||||
RecordColumnDefault::Zero => 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let second = match record.get("second") {
|
|
||||||
Some(col_val) => {
|
|
||||||
record_column_default = RecordColumnDefault::Zero;
|
|
||||||
parse_value_from_record_as_u32("second", col_val, &head, &span)?
|
|
||||||
}
|
|
||||||
None => match record_column_default {
|
|
||||||
RecordColumnDefault::Now => now.second(),
|
|
||||||
RecordColumnDefault::Zero => 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let millisecond = match record.get("millisecond") {
|
|
||||||
Some(col_val) => {
|
|
||||||
record_column_default = RecordColumnDefault::Zero;
|
|
||||||
parse_value_from_record_as_u32("millisecond", col_val, &head, &span)?
|
|
||||||
}
|
|
||||||
None => match record_column_default {
|
|
||||||
RecordColumnDefault::Now => now_millisecond,
|
|
||||||
RecordColumnDefault::Zero => 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let microsecond = match record.get("microsecond") {
|
|
||||||
Some(col_val) => {
|
|
||||||
record_column_default = RecordColumnDefault::Zero;
|
|
||||||
parse_value_from_record_as_u32("microsecond", col_val, &head, &span)?
|
|
||||||
}
|
|
||||||
None => match record_column_default {
|
|
||||||
RecordColumnDefault::Now => now_microsecond,
|
|
||||||
RecordColumnDefault::Zero => 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let nanosecond = match record.get("nanosecond") {
|
|
||||||
Some(col_val) => parse_value_from_record_as_u32("nanosecond", col_val, &head, &span)?,
|
|
||||||
None => match record_column_default {
|
|
||||||
RecordColumnDefault::Now => now_nanosecond,
|
|
||||||
RecordColumnDefault::Zero => 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let offset: FixedOffset = match record.get("timezone") {
|
|
||||||
Some(timezone) => parse_timezone_from_record(timezone, &head, &timezone.span())?,
|
|
||||||
None => now.offset().to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let total_nanoseconds = nanosecond + microsecond * 1_000 + millisecond * 1_000_000;
|
|
||||||
|
|
||||||
let date = match NaiveDate::from_ymd_opt(year, month, day) {
|
|
||||||
Some(d) => d,
|
|
||||||
None => {
|
|
||||||
return Err(ShellError::IncorrectValue {
|
|
||||||
msg: "one of more values are incorrect and do not represent valid date".to_string(),
|
|
||||||
val_span: head,
|
|
||||||
call_span: span,
|
|
||||||
})
|
})
|
||||||
}
|
.collect::<Vec<Value>>();
|
||||||
};
|
|
||||||
let time = match NaiveTime::from_hms_nano_opt(hour, minute, second, total_nanoseconds) {
|
|
||||||
Some(t) => t,
|
|
||||||
None => {
|
|
||||||
return Err(ShellError::IncorrectValue {
|
|
||||||
msg: "one of more values are incorrect and do not represent valid time".to_string(),
|
|
||||||
val_span: head,
|
|
||||||
call_span: span,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let date_time = NaiveDateTime::new(date, time);
|
|
||||||
|
|
||||||
let date_time_fixed = match offset.from_local_datetime(&date_time).single() {
|
Value::list(records, span)
|
||||||
Some(d) => d,
|
|
||||||
None => {
|
|
||||||
return Err(ShellError::IncorrectValue {
|
|
||||||
msg: "Ambiguous or invalid timezone conversion".to_string(),
|
|
||||||
val_span: head,
|
|
||||||
call_span: span,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(Value::date(date_time_fixed, span))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_value_from_record_as_u32(
|
|
||||||
col: &str,
|
|
||||||
col_val: &Value,
|
|
||||||
head: &Span,
|
|
||||||
span: &Span,
|
|
||||||
) -> Result<u32, ShellError> {
|
|
||||||
let value: u32 = match col_val {
|
|
||||||
Value::Int { val, .. } => {
|
|
||||||
if *val < 0 || *val > u32::MAX as i64 {
|
|
||||||
return Err(ShellError::IncorrectValue {
|
|
||||||
msg: format!("incorrect value for {}", col),
|
|
||||||
val_span: *head,
|
|
||||||
call_span: *span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
*val as u32
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
return Err(ShellError::OnlySupportsThisInputType {
|
|
||||||
exp_input_type: "int".to_string(),
|
|
||||||
wrong_type: other.get_type().to_string(),
|
|
||||||
dst_span: *head,
|
|
||||||
src_span: other.span(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_timezone_from_record(
|
|
||||||
timezone: &Value,
|
|
||||||
head: &Span,
|
|
||||||
span: &Span,
|
|
||||||
) -> Result<FixedOffset, ShellError> {
|
|
||||||
match timezone {
|
|
||||||
Value::String { val, .. } => {
|
|
||||||
let offset: FixedOffset = match val.parse() {
|
|
||||||
Ok(offset) => offset,
|
|
||||||
Err(_) => {
|
|
||||||
return Err(ShellError::IncorrectValue {
|
|
||||||
msg: "invalid timezone".to_string(),
|
|
||||||
val_span: *span,
|
|
||||||
call_span: *head,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(offset)
|
|
||||||
}
|
|
||||||
other => Err(ShellError::OnlySupportsThisInputType {
|
|
||||||
exp_input_type: "string".to_string(),
|
|
||||||
wrong_type: other.get_type().to_string(),
|
|
||||||
dst_span: *head,
|
|
||||||
src_span: other.span(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_with_format(val: &str, fmt: &str, head: Span) -> Result<Value, ()> {
|
|
||||||
// try parsing at date + time
|
|
||||||
if let Ok(dt) = NaiveDateTime::parse_from_str(val, fmt) {
|
|
||||||
let dt_native = Local.from_local_datetime(&dt).single().unwrap_or_default();
|
|
||||||
return Ok(Value::date(dt_native.into(), head));
|
|
||||||
}
|
|
||||||
|
|
||||||
// try parsing at date only
|
|
||||||
if let Ok(date) = NaiveDate::parse_from_str(val, fmt) {
|
|
||||||
if let Some(dt) = date.and_hms_opt(0, 0, 0) {
|
|
||||||
let dt_native = Local.from_local_datetime(&dt).single().unwrap_or_default();
|
|
||||||
return Ok(Value::date(dt_native.into(), head));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// try parsing at time only
|
|
||||||
if let Ok(time) = NaiveTime::parse_from_str(val, fmt) {
|
|
||||||
let now = Local::now().naive_local().date();
|
|
||||||
let dt_native = Local
|
|
||||||
.from_local_datetime(&now.and_time(time))
|
|
||||||
.single()
|
|
||||||
.unwrap_or_default();
|
|
||||||
return Ok(Value::date(dt_native.into(), head));
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -769,10 +508,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn takes_a_date_format_with_timezone() {
|
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(Spanned {
|
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
||||||
item: DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()),
|
|
||||||
span: Span::test_data(),
|
|
||||||
});
|
|
||||||
let args = Arguments {
|
let args = Arguments {
|
||||||
zone_options: None,
|
zone_options: None,
|
||||||
format_options: fmt_options,
|
format_options: fmt_options,
|
||||||
@ -787,12 +523,16 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
fn takes_a_date_format_without_timezone() {
|
fn takes_a_date_format_without_timezone() {
|
||||||
|
// Ignoring this test for now because we changed the human-date-parser to use
|
||||||
|
// the users timezone instead of UTC. We may continue to tweak this behavior.
|
||||||
|
// Another hacky solution is to set the timezone to UTC in the test, which works
|
||||||
|
// on MacOS and Linux but hasn't been tested on Windows. Plus it kind of defeats
|
||||||
|
// the purpose of a "without_timezone" test.
|
||||||
|
// std::env::set_var("TZ", "UTC");
|
||||||
let date_str = Value::test_string("16.11.1984 8:00 am");
|
let date_str = Value::test_string("16.11.1984 8:00 am");
|
||||||
let fmt_options = Some(Spanned {
|
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P".to_string()));
|
||||||
item: DatetimeFormat("%d.%m.%Y %H:%M %P".to_string()),
|
|
||||||
span: Span::test_data(),
|
|
||||||
});
|
|
||||||
let args = Arguments {
|
let args = Arguments {
|
||||||
zone_options: None,
|
zone_options: None,
|
||||||
format_options: fmt_options,
|
format_options: fmt_options,
|
||||||
@ -874,10 +614,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn takes_int_with_formatstring() {
|
fn takes_int_with_formatstring() {
|
||||||
let date_int = Value::test_int(1_614_434_140);
|
let date_int = Value::test_int(1_614_434_140);
|
||||||
let fmt_options = Some(Spanned {
|
let fmt_options = Some(DatetimeFormat("%s".to_string()));
|
||||||
item: DatetimeFormat("%s".to_string()),
|
|
||||||
span: Span::test_data(),
|
|
||||||
});
|
|
||||||
let args = Arguments {
|
let args = Arguments {
|
||||||
zone_options: None,
|
zone_options: None,
|
||||||
format_options: fmt_options,
|
format_options: fmt_options,
|
||||||
@ -892,55 +629,6 @@ mod tests {
|
|||||||
assert_eq!(actual, expected)
|
assert_eq!(actual, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn takes_timestamp_offset_as_int_with_formatting() {
|
|
||||||
let date_int = Value::test_int(1_614_434_140);
|
|
||||||
let timezone_option = Some(Spanned {
|
|
||||||
item: Zone::East(8),
|
|
||||||
span: Span::test_data(),
|
|
||||||
});
|
|
||||||
let fmt_options = Some(Spanned {
|
|
||||||
item: DatetimeFormat("%s".to_string()),
|
|
||||||
span: Span::test_data(),
|
|
||||||
});
|
|
||||||
let args = Arguments {
|
|
||||||
zone_options: timezone_option,
|
|
||||||
format_options: fmt_options,
|
|
||||||
cell_paths: None,
|
|
||||||
};
|
|
||||||
let actual = action(&date_int, &args, Span::test_data());
|
|
||||||
let expected = Value::date(
|
|
||||||
DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z").unwrap(),
|
|
||||||
Span::test_data(),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(actual, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn takes_timestamp_offset_as_int_with_local_timezone() {
|
|
||||||
let date_int = Value::test_int(1_614_434_140);
|
|
||||||
let timezone_option = Some(Spanned {
|
|
||||||
item: Zone::Local,
|
|
||||||
span: Span::test_data(),
|
|
||||||
});
|
|
||||||
let fmt_options = Some(Spanned {
|
|
||||||
item: DatetimeFormat("%s".to_string()),
|
|
||||||
span: Span::test_data(),
|
|
||||||
});
|
|
||||||
let args = Arguments {
|
|
||||||
zone_options: timezone_option,
|
|
||||||
format_options: fmt_options,
|
|
||||||
cell_paths: None,
|
|
||||||
};
|
|
||||||
let actual = action(&date_int, &args, Span::test_data());
|
|
||||||
let expected = Value::date(
|
|
||||||
Utc.timestamp_opt(1_614_434_140, 0).unwrap().into(),
|
|
||||||
Span::test_data(),
|
|
||||||
);
|
|
||||||
assert_eq!(actual, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn takes_timestamp() {
|
fn takes_timestamp() {
|
||||||
let date_str = Value::test_string("1614434140000000000");
|
let date_str = Value::test_string("1614434140000000000");
|
||||||
@ -955,7 +643,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let actual = action(&date_str, &args, Span::test_data());
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
let expected = Value::date(
|
let expected = Value::date(
|
||||||
Local.timestamp_opt(1_614_434_140, 0).unwrap().into(),
|
Local.timestamp_opt(1614434140, 0).unwrap().into(),
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -974,7 +662,7 @@ mod tests {
|
|||||||
cell_paths: None,
|
cell_paths: None,
|
||||||
};
|
};
|
||||||
let expected = Value::date(
|
let expected = Value::date(
|
||||||
Local.timestamp_opt(1_614_434_140, 0).unwrap().into(),
|
Local.timestamp_opt(1614434140, 0).unwrap().into(),
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
);
|
);
|
||||||
let actual = action(&expected, &args, Span::test_data());
|
let actual = action(&expected, &args, Span::test_data());
|
||||||
@ -993,7 +681,7 @@ mod tests {
|
|||||||
let actual = action(&date_str, &args, Span::test_data());
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
|
|
||||||
let expected = Value::date(
|
let expected = Value::date(
|
||||||
Utc.timestamp_opt(1_614_434_140, 0).unwrap().into(),
|
Utc.timestamp_opt(1614434140, 0).unwrap().into(),
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1003,10 +691,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn communicates_parsing_error_given_an_invalid_datetimelike_string() {
|
fn communicates_parsing_error_given_an_invalid_datetimelike_string() {
|
||||||
let date_str = Value::test_string("16.11.1984 8:00 am Oops0000");
|
let date_str = Value::test_string("16.11.1984 8:00 am Oops0000");
|
||||||
let fmt_options = Some(Spanned {
|
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
||||||
item: DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()),
|
|
||||||
span: Span::test_data(),
|
|
||||||
});
|
|
||||||
let args = Arguments {
|
let args = Arguments {
|
||||||
zone_options: None,
|
zone_options: None,
|
||||||
format_options: fmt_options,
|
format_options: fmt_options,
|
||||||
|
@ -1,41 +1,8 @@
|
|||||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_parser::{parse_unit_value, DURATION_UNIT_GROUPS};
|
use nu_parser::{parse_unit_value, DURATION_UNIT_GROUPS};
|
||||||
use nu_protocol::{ast::Expr, Unit};
|
use nu_protocol::{ast::Expr, Unit};
|
||||||
|
|
||||||
const NS_PER_US: i64 = 1_000;
|
|
||||||
const NS_PER_MS: i64 = 1_000_000;
|
|
||||||
const NS_PER_SEC: i64 = 1_000_000_000;
|
const NS_PER_SEC: i64 = 1_000_000_000;
|
||||||
const NS_PER_MINUTE: i64 = 60 * NS_PER_SEC;
|
|
||||||
const NS_PER_HOUR: i64 = 60 * NS_PER_MINUTE;
|
|
||||||
const NS_PER_DAY: i64 = 24 * NS_PER_HOUR;
|
|
||||||
const NS_PER_WEEK: i64 = 7 * NS_PER_DAY;
|
|
||||||
|
|
||||||
const ALLOWED_COLUMNS: [&str; 9] = [
|
|
||||||
"week",
|
|
||||||
"day",
|
|
||||||
"hour",
|
|
||||||
"minute",
|
|
||||||
"second",
|
|
||||||
"millisecond",
|
|
||||||
"microsecond",
|
|
||||||
"nanosecond",
|
|
||||||
"sign",
|
|
||||||
];
|
|
||||||
const ALLOWED_SIGNS: [&str; 2] = ["+", "-"];
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct Arguments {
|
|
||||||
unit: Option<Spanned<String>>,
|
|
||||||
cell_paths: Option<Vec<CellPath>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CmdArgument for Arguments {
|
|
||||||
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
|
||||||
self.cell_paths.take()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct IntoDuration;
|
pub struct IntoDuration;
|
||||||
|
|
||||||
@ -48,17 +15,13 @@ impl Command for IntoDuration {
|
|||||||
Signature::build("into duration")
|
Signature::build("into duration")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::Int, Type::Duration),
|
(Type::Int, Type::Duration),
|
||||||
(Type::Float, Type::Duration),
|
|
||||||
(Type::String, Type::Duration),
|
(Type::String, Type::Duration),
|
||||||
(Type::Duration, Type::Duration),
|
(Type::Duration, Type::Duration),
|
||||||
// FIXME: https://github.com/nushell/nushell/issues/15485
|
|
||||||
// 'record -> any' was added as a temporary workaround to avoid type inference issues. The Any arm needs to be appear first.
|
|
||||||
(Type::record(), Type::Any),
|
|
||||||
(Type::record(), Type::record()),
|
|
||||||
(Type::record(), Type::Duration),
|
|
||||||
(Type::table(), Type::table()),
|
(Type::table(), Type::table()),
|
||||||
|
//todo: record<hour,minute,sign> | into duration -> Duration
|
||||||
|
//(Type::record(), Type::record()),
|
||||||
])
|
])
|
||||||
.allow_variants_without_examples(true)
|
//.allow_variants_without_examples(true)
|
||||||
.named(
|
.named(
|
||||||
"unit",
|
"unit",
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
@ -92,35 +55,7 @@ impl Command for IntoDuration {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let cell_paths = call.rest(engine_state, stack, 0)?;
|
into_duration(engine_state, stack, call, input)
|
||||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
|
||||||
|
|
||||||
let span = match input.span() {
|
|
||||||
Some(t) => t,
|
|
||||||
None => call.head,
|
|
||||||
};
|
|
||||||
let unit = match call.get_flag::<Spanned<String>>(engine_state, stack, "unit")? {
|
|
||||||
Some(spanned_unit) => {
|
|
||||||
if ["ns", "us", "µs", "ms", "sec", "min", "hr", "day", "wk"]
|
|
||||||
.contains(&spanned_unit.item.as_str())
|
|
||||||
{
|
|
||||||
Some(spanned_unit)
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::CantConvertToDuration {
|
|
||||||
details: spanned_unit.item,
|
|
||||||
dst_span: span,
|
|
||||||
src_span: span,
|
|
||||||
help: Some(
|
|
||||||
"supported units are ns, us/µs, ms, sec, min, hr, day, and wk"
|
|
||||||
.to_string(),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
let args = Arguments { unit, cell_paths };
|
|
||||||
operate(action, args, input, call.head, engine_state.signals())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -174,23 +109,67 @@ impl Command for IntoDuration {
|
|||||||
example: "1_234 | into duration --unit ms",
|
example: "1_234 | into duration --unit ms",
|
||||||
result: Some(Value::test_duration(1_234 * 1_000_000)),
|
result: Some(Value::test_duration(1_234 * 1_000_000)),
|
||||||
},
|
},
|
||||||
Example {
|
|
||||||
description: "Convert a floating point number of an arbitrary unit to duration",
|
|
||||||
example: "1.234 | into duration --unit sec",
|
|
||||||
result: Some(Value::test_duration(1_234 * 1_000_000)),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Convert a record to a duration",
|
|
||||||
example: "{day: 10, hour: 2, minute: 6, second: 50, sign: '+'} | into duration",
|
|
||||||
result: Some(Value::duration(
|
|
||||||
10 * NS_PER_DAY + 2 * NS_PER_HOUR + 6 * NS_PER_MINUTE + 50 * NS_PER_SEC,
|
|
||||||
Span::test_data(),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn into_duration(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let span = match input.span() {
|
||||||
|
Some(t) => t,
|
||||||
|
None => call.head,
|
||||||
|
};
|
||||||
|
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
let unit = match call.get_flag::<String>(engine_state, stack, "unit")? {
|
||||||
|
Some(sep) => {
|
||||||
|
if ["ns", "us", "µs", "ms", "sec", "min", "hr", "day", "wk"]
|
||||||
|
.iter()
|
||||||
|
.any(|d| d == &sep)
|
||||||
|
{
|
||||||
|
sep
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::CantConvertToDuration {
|
||||||
|
details: sep,
|
||||||
|
dst_span: span,
|
||||||
|
src_span: span,
|
||||||
|
help: Some(
|
||||||
|
"supported units are ns, us/µs, ms, sec, min, hr, day, and wk".to_string(),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => "ns".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |v| {
|
||||||
|
if column_paths.is_empty() {
|
||||||
|
action(&v, &unit.clone(), span)
|
||||||
|
} else {
|
||||||
|
let unitclone = &unit.clone();
|
||||||
|
let mut ret = v;
|
||||||
|
for path in &column_paths {
|
||||||
|
let r = ret.update_cell_path(
|
||||||
|
&path.members,
|
||||||
|
Box::new(move |old| action(old, unitclone, span)),
|
||||||
|
);
|
||||||
|
if let Err(error) = r {
|
||||||
|
return Value::error(error, span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
},
|
||||||
|
engine_state.signals(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn split_whitespace_indices(s: &str, span: Span) -> impl Iterator<Item = (&str, Span)> {
|
fn split_whitespace_indices(s: &str, span: Span) -> impl Iterator<Item = (&str, Span)> {
|
||||||
s.split_whitespace().map(move |sub| {
|
s.split_whitespace().map(move |sub| {
|
||||||
// Gets the offset of the `sub` substring inside the string `s`.
|
// Gets the offset of the `sub` substring inside the string `s`.
|
||||||
@ -253,51 +232,27 @@ fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
fn action(input: &Value, unit: &str, span: Span) -> Value {
|
||||||
let value_span = input.span();
|
let value_span = input.span();
|
||||||
let unit_option = &args.unit;
|
|
||||||
|
|
||||||
if let Value::Record { .. } | Value::Duration { .. } = input {
|
|
||||||
if let Some(unit) = unit_option {
|
|
||||||
return Value::error(
|
|
||||||
ShellError::IncompatibleParameters {
|
|
||||||
left_message: "got a record as input".into(),
|
|
||||||
left_span: head,
|
|
||||||
right_message: "the units should be included in the record".into(),
|
|
||||||
right_span: unit.span,
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let unit: &str = match unit_option {
|
|
||||||
Some(unit) => &unit.item,
|
|
||||||
None => "ns",
|
|
||||||
};
|
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
Value::Duration { .. } => input.clone(),
|
Value::Duration { .. } => input.clone(),
|
||||||
Value::Record { val, .. } => {
|
Value::String { val, .. } => match compound_to_duration(val, value_span) {
|
||||||
merge_record(val, head, value_span).unwrap_or_else(|err| Value::error(err, value_span))
|
Ok(val) => Value::duration(val, span),
|
||||||
}
|
Err(error) => Value::error(error, span),
|
||||||
Value::String { val, .. } => {
|
},
|
||||||
if let Ok(num) = val.parse::<f64>() {
|
|
||||||
let ns = unit_to_ns_factor(unit);
|
|
||||||
return Value::duration((num * (ns as f64)) as i64, head);
|
|
||||||
}
|
|
||||||
match compound_to_duration(val, value_span) {
|
|
||||||
Ok(val) => Value::duration(val, head),
|
|
||||||
Err(error) => Value::error(error, head),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Float { val, .. } => {
|
|
||||||
let ns = unit_to_ns_factor(unit);
|
|
||||||
Value::duration((*val * (ns as f64)) as i64, head)
|
|
||||||
}
|
|
||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
let ns = unit_to_ns_factor(unit);
|
let ns = match unit {
|
||||||
Value::duration(*val * ns, head)
|
"ns" => 1,
|
||||||
|
"us" | "µs" => 1_000,
|
||||||
|
"ms" => 1_000_000,
|
||||||
|
"sec" => NS_PER_SEC,
|
||||||
|
"min" => NS_PER_SEC * 60,
|
||||||
|
"hr" => NS_PER_SEC * 60 * 60,
|
||||||
|
"day" => NS_PER_SEC * 60 * 60 * 24,
|
||||||
|
"wk" => NS_PER_SEC * 60 * 60 * 24 * 7,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
Value::duration(*val * ns, span)
|
||||||
}
|
}
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
Value::Error { .. } => input.clone(),
|
Value::Error { .. } => input.clone(),
|
||||||
@ -305,134 +260,14 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
ShellError::OnlySupportsThisInputType {
|
ShellError::OnlySupportsThisInputType {
|
||||||
exp_input_type: "string or duration".into(),
|
exp_input_type: "string or duration".into(),
|
||||||
wrong_type: other.get_type().to_string(),
|
wrong_type: other.get_type().to_string(),
|
||||||
dst_span: head,
|
dst_span: span,
|
||||||
src_span: other.span(),
|
src_span: other.span(),
|
||||||
},
|
},
|
||||||
head,
|
span,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge_record(record: &Record, head: Span, span: Span) -> Result<Value, ShellError> {
|
|
||||||
if let Some(invalid_col) = record
|
|
||||||
.columns()
|
|
||||||
.find(|key| !ALLOWED_COLUMNS.contains(&key.as_str()))
|
|
||||||
{
|
|
||||||
let allowed_cols = ALLOWED_COLUMNS.join(", ");
|
|
||||||
return Err(ShellError::UnsupportedInput {
|
|
||||||
msg: format!(
|
|
||||||
"Column '{invalid_col}' is not valid for a structured duration. Allowed columns are: {allowed_cols}"
|
|
||||||
),
|
|
||||||
input: "value originates from here".into(),
|
|
||||||
msg_span: head,
|
|
||||||
input_span: span
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut duration: i64 = 0;
|
|
||||||
|
|
||||||
if let Some(col_val) = record.get("week") {
|
|
||||||
let week = parse_number_from_record(col_val, &head)?;
|
|
||||||
duration += week * NS_PER_WEEK;
|
|
||||||
};
|
|
||||||
if let Some(col_val) = record.get("day") {
|
|
||||||
let day = parse_number_from_record(col_val, &head)?;
|
|
||||||
duration += day * NS_PER_DAY;
|
|
||||||
};
|
|
||||||
if let Some(col_val) = record.get("hour") {
|
|
||||||
let hour = parse_number_from_record(col_val, &head)?;
|
|
||||||
duration += hour * NS_PER_HOUR;
|
|
||||||
};
|
|
||||||
if let Some(col_val) = record.get("minute") {
|
|
||||||
let minute = parse_number_from_record(col_val, &head)?;
|
|
||||||
duration += minute * NS_PER_MINUTE;
|
|
||||||
};
|
|
||||||
if let Some(col_val) = record.get("second") {
|
|
||||||
let second = parse_number_from_record(col_val, &head)?;
|
|
||||||
duration += second * NS_PER_SEC;
|
|
||||||
};
|
|
||||||
if let Some(col_val) = record.get("millisecond") {
|
|
||||||
let millisecond = parse_number_from_record(col_val, &head)?;
|
|
||||||
duration += millisecond * NS_PER_MS;
|
|
||||||
};
|
|
||||||
if let Some(col_val) = record.get("microsecond") {
|
|
||||||
let microsecond = parse_number_from_record(col_val, &head)?;
|
|
||||||
duration += microsecond * NS_PER_US;
|
|
||||||
};
|
|
||||||
if let Some(col_val) = record.get("nanosecond") {
|
|
||||||
let nanosecond = parse_number_from_record(col_val, &head)?;
|
|
||||||
duration += nanosecond;
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(sign) = record.get("sign") {
|
|
||||||
match sign {
|
|
||||||
Value::String { val, .. } => {
|
|
||||||
if !ALLOWED_SIGNS.contains(&val.as_str()) {
|
|
||||||
let allowed_signs = ALLOWED_SIGNS.join(", ");
|
|
||||||
return Err(ShellError::IncorrectValue {
|
|
||||||
msg: format!("Invalid sign. Allowed signs are {}", allowed_signs)
|
|
||||||
.to_string(),
|
|
||||||
val_span: sign.span(),
|
|
||||||
call_span: head,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if val == "-" {
|
|
||||||
duration = -duration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
return Err(ShellError::OnlySupportsThisInputType {
|
|
||||||
exp_input_type: "int".to_string(),
|
|
||||||
wrong_type: other.get_type().to_string(),
|
|
||||||
dst_span: head,
|
|
||||||
src_span: other.span(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Value::duration(duration, span))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_number_from_record(col_val: &Value, head: &Span) -> Result<i64, ShellError> {
|
|
||||||
let value = match col_val {
|
|
||||||
Value::Int { val, .. } => {
|
|
||||||
if *val < 0 {
|
|
||||||
return Err(ShellError::IncorrectValue {
|
|
||||||
msg: "number should be positive".to_string(),
|
|
||||||
val_span: col_val.span(),
|
|
||||||
call_span: *head,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
*val
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
return Err(ShellError::OnlySupportsThisInputType {
|
|
||||||
exp_input_type: "int".to_string(),
|
|
||||||
wrong_type: other.get_type().to_string(),
|
|
||||||
dst_span: *head,
|
|
||||||
src_span: other.span(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unit_to_ns_factor(unit: &str) -> i64 {
|
|
||||||
match unit {
|
|
||||||
"ns" => 1,
|
|
||||||
"us" | "µs" => NS_PER_US,
|
|
||||||
"ms" => NS_PER_MS,
|
|
||||||
"sec" => NS_PER_SEC,
|
|
||||||
"min" => NS_PER_MINUTE,
|
|
||||||
"hr" => NS_PER_HOUR,
|
|
||||||
"day" => NS_PER_DAY,
|
|
||||||
"wk" => NS_PER_WEEK,
|
|
||||||
_ => 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -449,27 +284,24 @@ mod test {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case("3ns", 3)]
|
#[case("3ns", 3)]
|
||||||
#[case("4us", 4 * NS_PER_US)]
|
#[case("4us", 4*1000)]
|
||||||
#[case("4\u{00B5}s", 4 * NS_PER_US)] // micro sign
|
#[case("4\u{00B5}s", 4*1000)] // micro sign
|
||||||
#[case("4\u{03BC}s", 4 * NS_PER_US)] // mu symbol
|
#[case("4\u{03BC}s", 4*1000)] // mu symbol
|
||||||
#[case("5ms", 5 * NS_PER_MS)]
|
#[case("5ms", 5 * 1000 * 1000)]
|
||||||
#[case("1sec", NS_PER_SEC)]
|
#[case("1sec", NS_PER_SEC)]
|
||||||
#[case("7min", 7 * NS_PER_MINUTE)]
|
#[case("7min", 7 * 60 * NS_PER_SEC)]
|
||||||
#[case("42hr", 42 * NS_PER_HOUR)]
|
#[case("42hr", 42 * 60 * 60 * NS_PER_SEC)]
|
||||||
#[case("123day", 123 * NS_PER_DAY)]
|
#[case("123day", 123 * 24 * 60 * 60 * NS_PER_SEC)]
|
||||||
#[case("3wk", 3 * NS_PER_WEEK)]
|
#[case("3wk", 3 * 7 * 24 * 60 * 60 * NS_PER_SEC)]
|
||||||
#[case("86hr 26ns", 86 * 3600 * NS_PER_SEC + 26)] // compound duration string
|
#[case("86hr 26ns", 86 * 3600 * NS_PER_SEC + 26)] // compound duration string
|
||||||
#[case("14ns 3hr 17sec", 14 + 3 * NS_PER_HOUR + 17 * NS_PER_SEC)] // compound string with units in random order
|
#[case("14ns 3hr 17sec", 14 + 3 * 3600 * NS_PER_SEC + 17 * NS_PER_SEC)] // compound string with units in random order
|
||||||
|
|
||||||
fn turns_string_to_duration(#[case] phrase: &str, #[case] expected_duration_val: i64) {
|
fn turns_string_to_duration(#[case] phrase: &str, #[case] expected_duration_val: i64) {
|
||||||
let args = Arguments {
|
let actual = action(
|
||||||
unit: Some(Spanned {
|
&Value::test_string(phrase),
|
||||||
item: "ns".to_string(),
|
"ns",
|
||||||
span: Span::test_data(),
|
Span::new(0, phrase.len()),
|
||||||
}),
|
);
|
||||||
cell_paths: None,
|
|
||||||
};
|
|
||||||
let actual = action(&Value::test_string(phrase), &args, Span::test_data());
|
|
||||||
match actual {
|
match actual {
|
||||||
Value::Duration {
|
Value::Duration {
|
||||||
val: observed_val, ..
|
val: observed_val, ..
|
||||||
|
@ -208,7 +208,7 @@ fn process_cell(val: Value, display_as_filesizes: bool, span: Span) -> Result<Va
|
|||||||
}
|
}
|
||||||
} else if DATETIME_DMY_RE.is_match(&val_str).unwrap_or(false) {
|
} else if DATETIME_DMY_RE.is_match(&val_str).unwrap_or(false) {
|
||||||
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
|
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
|
||||||
to_type: "datetime".to_string(),
|
to_type: "date".to_string(),
|
||||||
from_type: "string".to_string(),
|
from_type: "string".to_string(),
|
||||||
span,
|
span,
|
||||||
help: Some(format!(
|
help: Some(format!(
|
||||||
@ -219,7 +219,7 @@ fn process_cell(val: Value, display_as_filesizes: bool, span: Span) -> Result<Va
|
|||||||
Ok(Value::date(dt, span))
|
Ok(Value::date(dt, span))
|
||||||
} else if DATETIME_YMD_RE.is_match(&val_str).unwrap_or(false) {
|
} else if DATETIME_YMD_RE.is_match(&val_str).unwrap_or(false) {
|
||||||
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
|
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
|
||||||
to_type: "datetime".to_string(),
|
to_type: "date".to_string(),
|
||||||
from_type: "string".to_string(),
|
from_type: "string".to_string(),
|
||||||
span,
|
span,
|
||||||
help: Some(format!(
|
help: Some(format!(
|
||||||
@ -230,7 +230,7 @@ fn process_cell(val: Value, display_as_filesizes: bool, span: Span) -> Result<Va
|
|||||||
Ok(Value::date(dt, span))
|
Ok(Value::date(dt, span))
|
||||||
} else if DATETIME_YMDZ_RE.is_match(&val_str).unwrap_or(false) {
|
} else if DATETIME_YMDZ_RE.is_match(&val_str).unwrap_or(false) {
|
||||||
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
|
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
|
||||||
to_type: "datetime".to_string(),
|
to_type: "date".to_string(),
|
||||||
from_type: "string".to_string(),
|
from_type: "string".to_string(),
|
||||||
span,
|
span,
|
||||||
help: Some(format!(
|
help: Some(format!(
|
||||||
|
@ -40,7 +40,6 @@ impl Command for SplitCellPath {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let input_type = input.get_type();
|
|
||||||
|
|
||||||
let src_span = match input {
|
let src_span = match input {
|
||||||
// Early return on correct type and empty pipeline
|
// Early return on correct type and empty pipeline
|
||||||
@ -55,9 +54,8 @@ impl Command for SplitCellPath {
|
|||||||
PipelineData::ListStream(stream, ..) => stream.span(),
|
PipelineData::ListStream(stream, ..) => stream.span(),
|
||||||
PipelineData::ByteStream(stream, ..) => stream.span(),
|
PipelineData::ByteStream(stream, ..) => stream.span(),
|
||||||
};
|
};
|
||||||
Err(ShellError::OnlySupportsThisInputType {
|
Err(ShellError::PipelineMismatch {
|
||||||
exp_input_type: "cell-path".into(),
|
exp_input_type: "cell-path".into(),
|
||||||
wrong_type: input_type.to_string(),
|
|
||||||
dst_span: head,
|
dst_span: head,
|
||||||
src_span,
|
src_span,
|
||||||
})
|
})
|
||||||
|
@ -1,261 +0,0 @@
|
|||||||
use chrono::{Local, TimeZone};
|
|
||||||
use human_date_parser::{from_human_time, ParseResult};
|
|
||||||
use nu_engine::command_prelude::*;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct DateFromHuman;
|
|
||||||
|
|
||||||
impl Command for DateFromHuman {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"date from-human"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("date from-human")
|
|
||||||
.input_output_types(vec![
|
|
||||||
(Type::String, Type::Date),
|
|
||||||
(Type::Nothing, Type::table()),
|
|
||||||
])
|
|
||||||
.allow_variants_without_examples(true)
|
|
||||||
.switch(
|
|
||||||
"list",
|
|
||||||
"Show human-readable datetime parsing examples",
|
|
||||||
Some('l'),
|
|
||||||
)
|
|
||||||
.category(Category::Date)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
"Convert a human readable datetime string to a datetime."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec![
|
|
||||||
"relative",
|
|
||||||
"now",
|
|
||||||
"today",
|
|
||||||
"tomorrow",
|
|
||||||
"yesterday",
|
|
||||||
"weekday",
|
|
||||||
"weekday_name",
|
|
||||||
"timezone",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
if call.has_flag(engine_state, stack, "list")? {
|
|
||||||
return Ok(list_human_readable_examples(call.head).into_pipeline_data());
|
|
||||||
}
|
|
||||||
let head = call.head;
|
|
||||||
// This doesn't match explicit nulls
|
|
||||||
if matches!(input, PipelineData::Empty) {
|
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
|
||||||
}
|
|
||||||
input.map(move |value| helper(value, head), engine_state.signals())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
description: "Parsing human readable datetime",
|
|
||||||
example: "'Today at 18:30' | date from-human",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Parsing human readable datetime",
|
|
||||||
example: "'Last Friday at 19:45' | date from-human",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Parsing human readable datetime",
|
|
||||||
example: "'In 5 minutes and 30 seconds' | date from-human",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "PShow human-readable datetime parsing examples",
|
|
||||||
example: "date from-human --list",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn helper(value: Value, head: Span) -> Value {
|
|
||||||
let span = value.span();
|
|
||||||
let input_val = match value {
|
|
||||||
Value::String { val, .. } => val,
|
|
||||||
other => {
|
|
||||||
return Value::error(
|
|
||||||
ShellError::OnlySupportsThisInputType {
|
|
||||||
exp_input_type: "string".to_string(),
|
|
||||||
wrong_type: other.get_type().to_string(),
|
|
||||||
dst_span: head,
|
|
||||||
src_span: span,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let now = Local::now();
|
|
||||||
|
|
||||||
if let Ok(date) = from_human_time(&input_val, now.naive_local()) {
|
|
||||||
match date {
|
|
||||||
ParseResult::Date(date) => {
|
|
||||||
let time = now.time();
|
|
||||||
let combined = date.and_time(time);
|
|
||||||
let local_offset = *now.offset();
|
|
||||||
let dt_fixed = TimeZone::from_local_datetime(&local_offset, &combined)
|
|
||||||
.single()
|
|
||||||
.unwrap_or_default();
|
|
||||||
return Value::date(dt_fixed, span);
|
|
||||||
}
|
|
||||||
ParseResult::DateTime(date) => {
|
|
||||||
let local_offset = *now.offset();
|
|
||||||
let dt_fixed = match local_offset.from_local_datetime(&date) {
|
|
||||||
chrono::LocalResult::Single(dt) => dt,
|
|
||||||
chrono::LocalResult::Ambiguous(_, _) => {
|
|
||||||
return Value::error(
|
|
||||||
ShellError::DatetimeParseError {
|
|
||||||
msg: "Ambiguous datetime".to_string(),
|
|
||||||
span,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
chrono::LocalResult::None => {
|
|
||||||
return Value::error(
|
|
||||||
ShellError::DatetimeParseError {
|
|
||||||
msg: "Invalid datetime".to_string(),
|
|
||||||
span,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return Value::date(dt_fixed, span);
|
|
||||||
}
|
|
||||||
ParseResult::Time(time) => {
|
|
||||||
let date = now.date_naive();
|
|
||||||
let combined = date.and_time(time);
|
|
||||||
let local_offset = *now.offset();
|
|
||||||
let dt_fixed = TimeZone::from_local_datetime(&local_offset, &combined)
|
|
||||||
.single()
|
|
||||||
.unwrap_or_default();
|
|
||||||
return Value::date(dt_fixed, span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match from_human_time(&input_val, now.naive_local()) {
|
|
||||||
Ok(date) => match date {
|
|
||||||
ParseResult::Date(date) => {
|
|
||||||
let time = now.time();
|
|
||||||
let combined = date.and_time(time);
|
|
||||||
let local_offset = *now.offset();
|
|
||||||
let dt_fixed = TimeZone::from_local_datetime(&local_offset, &combined)
|
|
||||||
.single()
|
|
||||||
.unwrap_or_default();
|
|
||||||
Value::date(dt_fixed, span)
|
|
||||||
}
|
|
||||||
ParseResult::DateTime(date) => {
|
|
||||||
let local_offset = *now.offset();
|
|
||||||
let dt_fixed = match local_offset.from_local_datetime(&date) {
|
|
||||||
chrono::LocalResult::Single(dt) => dt,
|
|
||||||
chrono::LocalResult::Ambiguous(_, _) => {
|
|
||||||
return Value::error(
|
|
||||||
ShellError::DatetimeParseError {
|
|
||||||
msg: "Ambiguous datetime".to_string(),
|
|
||||||
span,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
chrono::LocalResult::None => {
|
|
||||||
return Value::error(
|
|
||||||
ShellError::DatetimeParseError {
|
|
||||||
msg: "Invalid datetime".to_string(),
|
|
||||||
span,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Value::date(dt_fixed, span)
|
|
||||||
}
|
|
||||||
ParseResult::Time(time) => {
|
|
||||||
let date = now.date_naive();
|
|
||||||
let combined = date.and_time(time);
|
|
||||||
let local_offset = *now.offset();
|
|
||||||
let dt_fixed = TimeZone::from_local_datetime(&local_offset, &combined)
|
|
||||||
.single()
|
|
||||||
.unwrap_or_default();
|
|
||||||
Value::date(dt_fixed, span)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(_) => Value::error(
|
|
||||||
ShellError::IncorrectValue {
|
|
||||||
msg: "Cannot parse as humanized date".to_string(),
|
|
||||||
val_span: head,
|
|
||||||
call_span: span,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_human_readable_examples(span: Span) -> Value {
|
|
||||||
let examples: Vec<String> = vec![
|
|
||||||
"Today 18:30".into(),
|
|
||||||
"2022-11-07 13:25:30".into(),
|
|
||||||
"15:20 Friday".into(),
|
|
||||||
"This Friday 17:00".into(),
|
|
||||||
"13:25, Next Tuesday".into(),
|
|
||||||
"Last Friday at 19:45".into(),
|
|
||||||
"In 3 days".into(),
|
|
||||||
"In 2 hours".into(),
|
|
||||||
"10 hours and 5 minutes ago".into(),
|
|
||||||
"1 years ago".into(),
|
|
||||||
"A year ago".into(),
|
|
||||||
"A month ago".into(),
|
|
||||||
"A week ago".into(),
|
|
||||||
"A day ago".into(),
|
|
||||||
"An hour ago".into(),
|
|
||||||
"A minute ago".into(),
|
|
||||||
"A second ago".into(),
|
|
||||||
"Now".into(),
|
|
||||||
];
|
|
||||||
|
|
||||||
let records = examples
|
|
||||||
.iter()
|
|
||||||
.map(|s| {
|
|
||||||
Value::record(
|
|
||||||
record! {
|
|
||||||
"parseable human datetime examples" => Value::test_string(s.to_string()),
|
|
||||||
"result" => helper(Value::test_string(s.to_string()), span),
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<Value>>();
|
|
||||||
|
|
||||||
Value::list(records, span)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
use crate::test_examples;
|
|
||||||
|
|
||||||
test_examples(DateFromHuman {})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
mod date_;
|
mod date_;
|
||||||
mod from_human;
|
|
||||||
mod humanize;
|
mod humanize;
|
||||||
mod list_timezone;
|
mod list_timezone;
|
||||||
mod now;
|
mod now;
|
||||||
@ -8,7 +7,6 @@ mod to_timezone;
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
pub use date_::Date;
|
pub use date_::Date;
|
||||||
pub use from_human::DateFromHuman;
|
|
||||||
pub use humanize::DateHumanize;
|
pub use humanize::DateHumanize;
|
||||||
pub use list_timezone::DateListTimezones;
|
pub use list_timezone::DateListTimezones;
|
||||||
pub use now::DateNow;
|
pub use now::DateNow;
|
||||||
|
@ -38,16 +38,10 @@ impl Command for DateNow {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Get the current date and format it in a given format string.",
|
description: "Get the current date and display it in a given format string.",
|
||||||
example: r#"date now | format date "%Y-%m-%d %H:%M:%S""#,
|
example: r#"date now | format date "%Y-%m-%d %H:%M:%S""#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
|
||||||
description:
|
|
||||||
"Get the current date and format it according to the RFC 3339 standard.",
|
|
||||||
example: r#"date now | format date "%+""#,
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
Example {
|
||||||
description: "Get the time duration since 2019-04-30.",
|
description: "Get the time duration since 2019-04-30.",
|
||||||
example: r#"(date now) - 2019-05-01"#,
|
example: r#"(date now) - 2019-05-01"#,
|
||||||
@ -59,8 +53,7 @@ impl Command for DateNow {
|
|||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description:
|
description: "Get current time in full RFC 3339 format with time zone.",
|
||||||
"Get current time and format it in the debug format (RFC 2822 with timezone)",
|
|
||||||
example: r#"date now | debug"#,
|
example: r#"date now | debug"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
@ -9,11 +9,7 @@ pub(crate) fn parse_date_from_string(
|
|||||||
Ok((native_dt, fixed_offset)) => {
|
Ok((native_dt, fixed_offset)) => {
|
||||||
let offset = match fixed_offset {
|
let offset = match fixed_offset {
|
||||||
Some(offset) => offset,
|
Some(offset) => offset,
|
||||||
None => *Local
|
None => *(Local::now().offset()),
|
||||||
.from_local_datetime(&native_dt)
|
|
||||||
.single()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.offset(),
|
|
||||||
};
|
};
|
||||||
match offset.from_local_datetime(&native_dt) {
|
match offset.from_local_datetime(&native_dt) {
|
||||||
LocalResult::Single(d) => Ok(d),
|
LocalResult::Single(d) => Ok(d),
|
||||||
|
@ -23,11 +23,6 @@ impl Command for Debug {
|
|||||||
])
|
])
|
||||||
.category(Category::Debug)
|
.category(Category::Debug)
|
||||||
.switch("raw", "Prints the raw value representation", Some('r'))
|
.switch("raw", "Prints the raw value representation", Some('r'))
|
||||||
.switch(
|
|
||||||
"raw-value",
|
|
||||||
"Prints the raw value representation but not the nushell value part",
|
|
||||||
Some('v'),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -40,7 +35,6 @@ impl Command for Debug {
|
|||||||
let head = call.head;
|
let head = call.head;
|
||||||
let config = stack.get_config(engine_state);
|
let config = stack.get_config(engine_state);
|
||||||
let raw = call.has_flag(engine_state, stack, "raw")?;
|
let raw = call.has_flag(engine_state, stack, "raw")?;
|
||||||
let raw_value = call.has_flag(engine_state, stack, "raw-value")?;
|
|
||||||
|
|
||||||
// Should PipelineData::Empty result in an error here?
|
// Should PipelineData::Empty result in an error here?
|
||||||
|
|
||||||
@ -48,11 +42,6 @@ impl Command for Debug {
|
|||||||
move |x| {
|
move |x| {
|
||||||
if raw {
|
if raw {
|
||||||
Value::string(x.to_debug_string(), head)
|
Value::string(x.to_debug_string(), head)
|
||||||
} else if raw_value {
|
|
||||||
match x.coerce_into_string_all() {
|
|
||||||
Ok(s) => Value::string(format!("{s:#?}"), head),
|
|
||||||
Err(e) => Value::error(e, head),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Value::string(x.to_expanded_string(", ", &config), head)
|
Value::string(x.to_expanded_string(", ", &config), head)
|
||||||
}
|
}
|
||||||
@ -89,75 +78,10 @@ impl Command for Debug {
|
|||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Example {
|
|
||||||
description: "Debug print an ansi escape encoded string and get the raw value",
|
|
||||||
example: "$'(ansi red)nushell(ansi reset)' | debug -v",
|
|
||||||
result: Some(Value::test_string("\"\\u{1b}[31mnushell\\u{1b}[0m\"")),
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is just a local Value Extension trait to avoid having to
|
|
||||||
// put another *_to_string() converter in nu_protocol
|
|
||||||
trait ValueExt {
|
|
||||||
fn coerce_into_string_all(&self) -> Result<String, ShellError>;
|
|
||||||
fn cant_convert_to<T>(&self, typ: &str) -> Result<T, ShellError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ValueExt for Value {
|
|
||||||
fn cant_convert_to<T>(&self, typ: &str) -> Result<T, ShellError> {
|
|
||||||
Err(ShellError::CantConvert {
|
|
||||||
to_type: typ.into(),
|
|
||||||
from_type: self.get_type().to_string(),
|
|
||||||
span: self.span(),
|
|
||||||
help: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn coerce_into_string_all(&self) -> Result<String, ShellError> {
|
|
||||||
let span = self.span();
|
|
||||||
match self {
|
|
||||||
Value::Bool { val, .. } => Ok(val.to_string()),
|
|
||||||
Value::Int { val, .. } => Ok(val.to_string()),
|
|
||||||
Value::Float { val, .. } => Ok(val.to_string()),
|
|
||||||
Value::String { val, .. } => Ok(val.to_string()),
|
|
||||||
Value::Glob { val, .. } => Ok(val.to_string()),
|
|
||||||
Value::Filesize { val, .. } => Ok(val.get().to_string()),
|
|
||||||
Value::Duration { val, .. } => Ok(val.to_string()),
|
|
||||||
Value::Date { val, .. } => Ok(val.to_rfc3339_opts(chrono::SecondsFormat::Nanos, true)),
|
|
||||||
Value::Range { val, .. } => Ok(val.to_string()),
|
|
||||||
Value::Record { val, .. } => Ok(format!(
|
|
||||||
"{{{}}}",
|
|
||||||
val.iter()
|
|
||||||
.map(|(x, y)| match y.coerce_into_string_all() {
|
|
||||||
Ok(value) => format!("{x}: {value}"),
|
|
||||||
Err(err) => format!("Error: {err}"),
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", ")
|
|
||||||
)),
|
|
||||||
Value::List { vals, .. } => Ok(format!(
|
|
||||||
"[{}]",
|
|
||||||
vals.iter()
|
|
||||||
.map(|x| match x.coerce_into_string_all() {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(err) => format!("Error: {err}"),
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", ")
|
|
||||||
)),
|
|
||||||
Value::Binary { val, .. } => match String::from_utf8(val.to_vec()) {
|
|
||||||
Ok(s) => Ok(s),
|
|
||||||
Err(err) => Value::binary(err.into_bytes(), span).cant_convert_to("string"),
|
|
||||||
},
|
|
||||||
Value::CellPath { val, .. } => Ok(val.to_string()),
|
|
||||||
Value::Nothing { .. } => Ok("nothing".to_string()),
|
|
||||||
val => val.cant_convert_to("string"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -118,7 +118,7 @@ fn increase_string_width(text: &mut String, total: usize) {
|
|||||||
let rest = total - width;
|
let rest = total - width;
|
||||||
|
|
||||||
if rest > 0 {
|
if rest > 0 {
|
||||||
text.extend(std::iter::repeat_n(' ', rest));
|
text.extend(std::iter::repeat(' ').take(rest));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,7 +272,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
// Date
|
// Date
|
||||||
bind_command! {
|
bind_command! {
|
||||||
Date,
|
Date,
|
||||||
DateFromHuman,
|
|
||||||
DateHumanize,
|
DateHumanize,
|
||||||
DateListTimezones,
|
DateListTimezones,
|
||||||
DateNow,
|
DateNow,
|
||||||
@ -452,18 +451,9 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
JobSpawn,
|
JobSpawn,
|
||||||
JobList,
|
JobList,
|
||||||
JobKill,
|
JobKill,
|
||||||
JobId,
|
|
||||||
JobTag,
|
|
||||||
Job,
|
Job,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
bind_command! {
|
|
||||||
JobSend,
|
|
||||||
JobRecv,
|
|
||||||
JobFlush,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(unix, feature = "os"))]
|
#[cfg(all(unix, feature = "os"))]
|
||||||
bind_command! {
|
bind_command! {
|
||||||
JobUnfreeze,
|
JobUnfreeze,
|
||||||
|
16
crates/nu-command/src/env/config/config_.rs
vendored
16
crates/nu-command/src/env/config/config_.rs
vendored
@ -1,11 +1,7 @@
|
|||||||
use nu_cmd_base::util::get_editor;
|
use nu_cmd_base::util::get_editor;
|
||||||
use nu_engine::{command_prelude::*, env_to_strings, get_full_help};
|
use nu_engine::{command_prelude::*, env_to_strings, get_full_help};
|
||||||
use nu_protocol::shell_error::io::IoError;
|
|
||||||
use nu_system::ForegroundChild;
|
use nu_system::ForegroundChild;
|
||||||
|
|
||||||
#[cfg(feature = "os")]
|
|
||||||
use nu_protocol::process::PostWaitCallback;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ConfigMeta;
|
pub struct ConfigMeta;
|
||||||
|
|
||||||
@ -65,6 +61,7 @@ pub(super) fn start_editor(
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
// Find the editor executable.
|
// Find the editor executable.
|
||||||
|
|
||||||
|
use nu_protocol::shell_error::io::IoError;
|
||||||
let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?;
|
let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?;
|
||||||
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
|
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
|
||||||
let cwd = engine_state.cwd(Some(stack))?;
|
let cwd = engine_state.cwd(Some(stack))?;
|
||||||
@ -122,17 +119,8 @@ pub(super) fn start_editor(
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let post_wait_callback = PostWaitCallback::for_job_control(engine_state, None, None);
|
|
||||||
|
|
||||||
// Wrap the output into a `PipelineData::ByteStream`.
|
// Wrap the output into a `PipelineData::ByteStream`.
|
||||||
let child = nu_protocol::process::ChildProcess::new(
|
let child = nu_protocol::process::ChildProcess::new(child, None, false, call.head, None)?;
|
||||||
child,
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
call.head,
|
|
||||||
Some(post_wait_callback),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(PipelineData::ByteStream(
|
Ok(PipelineData::ByteStream(
|
||||||
ByteStream::child(child, call.head),
|
ByteStream::child(child, call.head),
|
||||||
None,
|
None,
|
||||||
|
@ -10,7 +10,7 @@ impl Command for Job {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("job")
|
Signature::build("job")
|
||||||
.category(Category::Experimental)
|
.category(Category::Strings)
|
||||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
use nu_engine::command_prelude::*;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct JobFlush;
|
|
||||||
|
|
||||||
impl Command for JobFlush {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"job flush"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
"Clear this job's mailbox."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extra_description(&self) -> &str {
|
|
||||||
r#"
|
|
||||||
This command removes all messages in the mailbox of the current job.
|
|
||||||
If a message is received while this command is executing, it may also be discarded.
|
|
||||||
"#
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
|
||||||
Signature::build("job flush")
|
|
||||||
.category(Category::Experimental)
|
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
|
||||||
.allow_variants_without_examples(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
_stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let mut mailbox = engine_state
|
|
||||||
.current_job
|
|
||||||
.mailbox
|
|
||||||
.lock()
|
|
||||||
.expect("failed to acquire lock");
|
|
||||||
|
|
||||||
mailbox.clear();
|
|
||||||
|
|
||||||
Ok(Value::nothing(call.head).into_pipeline_data())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
example: "job flush",
|
|
||||||
description: "Clear the mailbox of the current job.",
|
|
||||||
result: None,
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
use nu_engine::command_prelude::*;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct JobId;
|
|
||||||
|
|
||||||
impl Command for JobId {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"job id"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
"Get id of current job."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extra_description(&self) -> &str {
|
|
||||||
"This command returns the job id for the current background job.
|
|
||||||
The special id 0 indicates that this command was not called from a background job thread, and
|
|
||||||
was instead spawned by main nushell execution thread."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
|
||||||
Signature::build("job id")
|
|
||||||
.category(Category::Experimental)
|
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Int)])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["self", "this", "my-id", "this-id"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
_stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
|
|
||||||
Ok(Value::int(engine_state.current_job.id.get() as i64, head).into_pipeline_data())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
example: "job id",
|
|
||||||
description: "Get id of current job",
|
|
||||||
result: None,
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
@ -37,7 +37,7 @@ impl Command for JobList {
|
|||||||
let values = jobs
|
let values = jobs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(id, job)| {
|
.map(|(id, job)| {
|
||||||
let mut record = record! {
|
let record = record! {
|
||||||
"id" => Value::int(id.get() as i64, head),
|
"id" => Value::int(id.get() as i64, head),
|
||||||
"type" => match job {
|
"type" => match job {
|
||||||
Job::Thread(_) => Value::string("thread", head),
|
Job::Thread(_) => Value::string("thread", head),
|
||||||
@ -52,15 +52,11 @@ impl Command for JobList {
|
|||||||
head,
|
head,
|
||||||
),
|
),
|
||||||
|
|
||||||
Job::Frozen(FrozenJob { unfreeze, .. }) => {
|
Job::Frozen(FrozenJob { unfreeze }) => {
|
||||||
Value::list(vec![ Value::int(unfreeze.pid() as i64, head) ], head)
|
Value::list(vec![ Value::int(unfreeze.pid() as i64, head) ], head)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(tag) = job.tag() {
|
|
||||||
record.push("tag", Value::string(tag, head));
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Value::record(record, head)
|
Value::record(record, head)
|
||||||
})
|
})
|
||||||
|
@ -1,181 +0,0 @@
|
|||||||
use std::{
|
|
||||||
sync::mpsc::{RecvTimeoutError, TryRecvError},
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
|
|
||||||
use nu_engine::command_prelude::*;
|
|
||||||
|
|
||||||
use nu_protocol::{
|
|
||||||
engine::{FilterTag, Mailbox},
|
|
||||||
Signals,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct JobRecv;
|
|
||||||
|
|
||||||
const CTRL_C_CHECK_INTERVAL: Duration = Duration::from_millis(100);
|
|
||||||
|
|
||||||
impl Command for JobRecv {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"job recv"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
"Read a message from the mailbox."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extra_description(&self) -> &str {
|
|
||||||
r#"When messages are sent to the current process, they get stored in what is called the "mailbox".
|
|
||||||
This commands reads and returns a message from the mailbox, in a first-in-first-out fashion.
|
|
||||||
|
|
||||||
Messages may have numeric flags attached to them. This commands supports filtering out messages that do not satisfy a given tag, by using the `tag` flag.
|
|
||||||
If no tag is specified, this command will accept any message.
|
|
||||||
|
|
||||||
If no message with the specified tag (if any) is available in the mailbox, this command will block the current thread until one arrives.
|
|
||||||
By default this command block indefinitely until a matching message arrives, but a timeout duration can be specified.
|
|
||||||
If a timeout duration of zero is specified, it will succeed only if there already is a message in the mailbox.
|
|
||||||
|
|
||||||
Note: When using par-each, only one thread at a time can utilize this command.
|
|
||||||
In the case of two or more threads running this command, they will wait until other threads are done using it,
|
|
||||||
in no particular order, regardless of the specified timeout parameter.
|
|
||||||
"#
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
|
||||||
Signature::build("job recv")
|
|
||||||
.category(Category::Experimental)
|
|
||||||
.named("tag", SyntaxShape::Int, "A tag for the message", None)
|
|
||||||
.named(
|
|
||||||
"timeout",
|
|
||||||
SyntaxShape::Duration,
|
|
||||||
"The maximum time duration to wait for.",
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Any)])
|
|
||||||
.allow_variants_without_examples(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["receive"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
|
|
||||||
let tag_arg: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "tag")?;
|
|
||||||
|
|
||||||
if let Some(tag) = tag_arg {
|
|
||||||
if tag.item < 0 {
|
|
||||||
return Err(ShellError::NeedsPositiveValue { span: tag.span });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let tag = tag_arg.map(|it| it.item as FilterTag);
|
|
||||||
|
|
||||||
let duration: Option<i64> = call.get_flag(engine_state, stack, "timeout")?;
|
|
||||||
|
|
||||||
let timeout = duration.map(|it| Duration::from_nanos(it as u64));
|
|
||||||
|
|
||||||
let mut mailbox = engine_state
|
|
||||||
.current_job
|
|
||||||
.mailbox
|
|
||||||
.lock()
|
|
||||||
.expect("failed to acquire lock");
|
|
||||||
|
|
||||||
if let Some(timeout) = timeout {
|
|
||||||
if timeout == Duration::ZERO {
|
|
||||||
recv_instantly(&mut mailbox, tag, head)
|
|
||||||
} else {
|
|
||||||
recv_with_time_limit(&mut mailbox, tag, engine_state.signals(), head, timeout)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
recv_without_time_limit(&mut mailbox, tag, engine_state.signals(), head)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
example: "job recv",
|
|
||||||
description: "Block the current thread while no message arrives",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
example: "job recv --timeout 10sec",
|
|
||||||
description: "Receive a message, wait for at most 10 seconds.",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
example: "job recv --timeout 0sec",
|
|
||||||
description: "Get a message or fail if no message is available immediately",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn recv_without_time_limit(
|
|
||||||
mailbox: &mut Mailbox,
|
|
||||||
tag: Option<FilterTag>,
|
|
||||||
signals: &Signals,
|
|
||||||
span: Span,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
loop {
|
|
||||||
if signals.interrupted() {
|
|
||||||
return Err(ShellError::Interrupted { span });
|
|
||||||
}
|
|
||||||
match mailbox.recv_timeout(tag, CTRL_C_CHECK_INTERVAL) {
|
|
||||||
Ok(value) => return Ok(value),
|
|
||||||
Err(RecvTimeoutError::Timeout) => {} // try again
|
|
||||||
Err(RecvTimeoutError::Disconnected) => return Err(ShellError::Interrupted { span }),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn recv_instantly(
|
|
||||||
mailbox: &mut Mailbox,
|
|
||||||
tag: Option<FilterTag>,
|
|
||||||
span: Span,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
match mailbox.try_recv(tag) {
|
|
||||||
Ok(value) => Ok(value),
|
|
||||||
Err(TryRecvError::Empty) => Err(ShellError::RecvTimeout { span }),
|
|
||||||
Err(TryRecvError::Disconnected) => Err(ShellError::Interrupted { span }),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn recv_with_time_limit(
|
|
||||||
mailbox: &mut Mailbox,
|
|
||||||
tag: Option<FilterTag>,
|
|
||||||
signals: &Signals,
|
|
||||||
span: Span,
|
|
||||||
timeout: Duration,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let deadline = Instant::now() + timeout;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if signals.interrupted() {
|
|
||||||
return Err(ShellError::Interrupted { span });
|
|
||||||
}
|
|
||||||
|
|
||||||
let time_until_deadline = deadline.saturating_duration_since(Instant::now());
|
|
||||||
|
|
||||||
let time_to_sleep = time_until_deadline.min(CTRL_C_CHECK_INTERVAL);
|
|
||||||
|
|
||||||
match mailbox.recv_timeout(tag, time_to_sleep) {
|
|
||||||
Ok(value) => return Ok(value),
|
|
||||||
Err(RecvTimeoutError::Timeout) => {} // try again
|
|
||||||
Err(RecvTimeoutError::Disconnected) => return Err(ShellError::Interrupted { span }),
|
|
||||||
}
|
|
||||||
|
|
||||||
if time_until_deadline.is_zero() {
|
|
||||||
return Err(ShellError::RecvTimeout { span });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,112 +0,0 @@
|
|||||||
use nu_engine::command_prelude::*;
|
|
||||||
use nu_protocol::{engine::FilterTag, JobId};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct JobSend;
|
|
||||||
|
|
||||||
impl Command for JobSend {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"job send"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
"Send a message to the mailbox of a job."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extra_description(&self) -> &str {
|
|
||||||
r#"
|
|
||||||
This command sends a message to a background job, which can then read sent messages
|
|
||||||
in a first-in-first-out fashion with `job recv`. When it does so, it may additionally specify a numeric filter tag,
|
|
||||||
in which case it will only read messages sent with the exact same filter tag.
|
|
||||||
In particular, the id 0 refers to the main/initial nushell thread.
|
|
||||||
|
|
||||||
A message can be any nushell value, and streams are always collected before being sent.
|
|
||||||
|
|
||||||
This command never blocks.
|
|
||||||
"#
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
|
||||||
Signature::build("job send")
|
|
||||||
.category(Category::Experimental)
|
|
||||||
.required(
|
|
||||||
"id",
|
|
||||||
SyntaxShape::Int,
|
|
||||||
"The id of the job to send the message to.",
|
|
||||||
)
|
|
||||||
.named("tag", SyntaxShape::Int, "A tag for the message", None)
|
|
||||||
.input_output_types(vec![(Type::Any, Type::Nothing)])
|
|
||||||
.allow_variants_without_examples(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
|
|
||||||
let id_arg: Spanned<i64> = call.req(engine_state, stack, 0)?;
|
|
||||||
let tag_arg: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "tag")?;
|
|
||||||
|
|
||||||
let id = id_arg.item;
|
|
||||||
|
|
||||||
if id < 0 {
|
|
||||||
return Err(ShellError::NeedsPositiveValue { span: id_arg.span });
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(tag) = tag_arg {
|
|
||||||
if tag.item < 0 {
|
|
||||||
return Err(ShellError::NeedsPositiveValue { span: tag.span });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let tag = tag_arg.map(|it| it.item as FilterTag);
|
|
||||||
|
|
||||||
if id == 0 {
|
|
||||||
engine_state
|
|
||||||
.root_job_sender
|
|
||||||
.send((tag, input))
|
|
||||||
.expect("this should NEVER happen.");
|
|
||||||
} else {
|
|
||||||
let jobs = engine_state.jobs.lock().expect("failed to acquire lock");
|
|
||||||
|
|
||||||
if let Some(job) = jobs.lookup(JobId::new(id as usize)) {
|
|
||||||
match job {
|
|
||||||
nu_protocol::engine::Job::Thread(thread_job) => {
|
|
||||||
// it is ok to send this value while holding the lock, because
|
|
||||||
// mail channels are always unbounded, so this send never blocks
|
|
||||||
let _ = thread_job.sender.send((tag, input));
|
|
||||||
}
|
|
||||||
nu_protocol::engine::Job::Frozen(_) => {
|
|
||||||
return Err(ShellError::JobIsFrozen {
|
|
||||||
id: id as usize,
|
|
||||||
span: id_arg.span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::JobNotFound {
|
|
||||||
id: id as usize,
|
|
||||||
span: id_arg.span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Value::nothing(head).into_pipeline_data())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
example: "let id = job spawn { job recv | save sent.txt }; 'hi' | job send $id",
|
|
||||||
description: "Send a message to a newly spawned job",
|
|
||||||
result: None,
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +1,15 @@
|
|||||||
use std::{
|
use std::{
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, AtomicU32},
|
atomic::{AtomicBool, AtomicU32},
|
||||||
mpsc, Arc, Mutex,
|
Arc,
|
||||||
},
|
},
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use nu_engine::{command_prelude::*, ClosureEvalOnce};
|
use nu_engine::{command_prelude::*, ClosureEvalOnce};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{Closure, CurrentJob, Job, Mailbox, Redirection, ThreadJob},
|
engine::{Closure, Job, ThreadJob},
|
||||||
report_shell_error, OutDest, Signals,
|
report_shell_error, Signals,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -28,12 +28,6 @@ impl Command for JobSpawn {
|
|||||||
Signature::build("job spawn")
|
Signature::build("job spawn")
|
||||||
.category(Category::Experimental)
|
.category(Category::Experimental)
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Int)])
|
.input_output_types(vec![(Type::Nothing, Type::Int)])
|
||||||
.named(
|
|
||||||
"tag",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"An optional description tag for this job",
|
|
||||||
Some('t'),
|
|
||||||
)
|
|
||||||
.required(
|
.required(
|
||||||
"closure",
|
"closure",
|
||||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||||
@ -56,12 +50,11 @@ impl Command for JobSpawn {
|
|||||||
|
|
||||||
let closure: Closure = call.req(engine_state, stack, 0)?;
|
let closure: Closure = call.req(engine_state, stack, 0)?;
|
||||||
|
|
||||||
let tag: Option<String> = call.get_flag(engine_state, stack, "tag")?;
|
|
||||||
let job_stack = stack.clone();
|
|
||||||
|
|
||||||
let mut job_state = engine_state.clone();
|
let mut job_state = engine_state.clone();
|
||||||
job_state.is_interactive = false;
|
job_state.is_interactive = false;
|
||||||
|
|
||||||
|
let job_stack = stack.clone();
|
||||||
|
|
||||||
// the new job should have its ctrl-c independent of foreground
|
// the new job should have its ctrl-c independent of foreground
|
||||||
let job_signals = Signals::new(Arc::new(AtomicBool::new(false)));
|
let job_signals = Signals::new(Arc::new(AtomicBool::new(false)));
|
||||||
job_state.set_signals(job_signals.clone());
|
job_state.set_signals(job_signals.clone());
|
||||||
@ -74,37 +67,24 @@ impl Command for JobSpawn {
|
|||||||
let jobs = job_state.jobs.clone();
|
let jobs = job_state.jobs.clone();
|
||||||
let mut jobs = jobs.lock().expect("jobs lock is poisoned!");
|
let mut jobs = jobs.lock().expect("jobs lock is poisoned!");
|
||||||
|
|
||||||
let (send, recv) = mpsc::channel();
|
|
||||||
|
|
||||||
let id = {
|
let id = {
|
||||||
let thread_job = ThreadJob::new(job_signals, tag, send);
|
let thread_job = ThreadJob::new(job_signals);
|
||||||
|
job_state.current_thread_job = Some(thread_job.clone());
|
||||||
let id = jobs.add_job(Job::Thread(thread_job.clone()));
|
jobs.add_job(Job::Thread(thread_job))
|
||||||
|
|
||||||
job_state.current_job = CurrentJob {
|
|
||||||
id,
|
|
||||||
background_thread_job: Some(thread_job),
|
|
||||||
mailbox: Arc::new(Mutex::new(Mailbox::new(recv))),
|
|
||||||
};
|
|
||||||
|
|
||||||
id
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = thread::Builder::new()
|
let result = thread::Builder::new()
|
||||||
.name(format!("background job {}", id.get()))
|
.name(format!("background job {}", id.get()))
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
let mut stack = job_stack.reset_pipes();
|
ClosureEvalOnce::new(&job_state, &job_stack, closure)
|
||||||
let stack = stack.push_redirection(
|
|
||||||
Some(Redirection::Pipe(OutDest::Null)),
|
|
||||||
Some(Redirection::Pipe(OutDest::Null)),
|
|
||||||
);
|
|
||||||
ClosureEvalOnce::new_preserve_out_dest(&job_state, &stack, closure)
|
|
||||||
.run_with_input(Value::nothing(head).into_pipeline_data())
|
.run_with_input(Value::nothing(head).into_pipeline_data())
|
||||||
.and_then(|data| data.drain())
|
.and_then(|data| data.into_value(head))
|
||||||
.unwrap_or_else(|err| {
|
.unwrap_or_else(|err| {
|
||||||
if !job_state.signals().interrupted() {
|
if !job_state.signals().interrupted() {
|
||||||
report_shell_error(&job_state, &err);
|
report_shell_error(&job_state, &err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Value::nothing(head)
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
use nu_engine::command_prelude::*;
|
|
||||||
use nu_protocol::JobId;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct JobTag;
|
|
||||||
|
|
||||||
impl Command for JobTag {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"job tag"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
"Add a description tag to a background job."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
|
||||||
Signature::build("job tag")
|
|
||||||
.category(Category::Experimental)
|
|
||||||
.required("id", SyntaxShape::Int, "The id of the job to tag.")
|
|
||||||
.required(
|
|
||||||
"tag",
|
|
||||||
SyntaxShape::OneOf(vec![SyntaxShape::String, SyntaxShape::Nothing]),
|
|
||||||
"The tag to assign to the job.",
|
|
||||||
)
|
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["describe", "desc"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
|
|
||||||
let id_arg: Spanned<i64> = call.req(engine_state, stack, 0)?;
|
|
||||||
|
|
||||||
if id_arg.item < 0 {
|
|
||||||
return Err(ShellError::NeedsPositiveValue { span: id_arg.span });
|
|
||||||
}
|
|
||||||
|
|
||||||
let id: JobId = JobId::new(id_arg.item as usize);
|
|
||||||
|
|
||||||
let tag: Option<String> = call.req(engine_state, stack, 1)?;
|
|
||||||
|
|
||||||
let mut jobs = engine_state.jobs.lock().expect("jobs lock is poisoned!");
|
|
||||||
|
|
||||||
match jobs.lookup_mut(id) {
|
|
||||||
None => {
|
|
||||||
return Err(ShellError::JobNotFound {
|
|
||||||
id: id.get(),
|
|
||||||
span: head,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(job) => job.assign_tag(tag),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Value::nothing(head).into_pipeline_data())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
example: "let id = job spawn { sleep 10sec }; job tag $id abc ",
|
|
||||||
description: "Tag a newly spawned job",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
example: "let id = job spawn { sleep 10sec }; job tag $id abc; job tag $id null",
|
|
||||||
description: "Remove the tag of a job",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -112,13 +112,10 @@ fn unfreeze_job(
|
|||||||
span,
|
span,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Job::Frozen(FrozenJob {
|
Job::Frozen(FrozenJob { unfreeze: handle }) => {
|
||||||
unfreeze: handle,
|
|
||||||
tag,
|
|
||||||
}) => {
|
|
||||||
let pid = handle.pid();
|
let pid = handle.pid();
|
||||||
|
|
||||||
if let Some(thread_job) = &state.current_thread_job() {
|
if let Some(thread_job) = &state.current_thread_job {
|
||||||
if !thread_job.try_add_pid(pid) {
|
if !thread_job.try_add_pid(pid) {
|
||||||
kill_by_pid(pid.into()).map_err(|err| {
|
kill_by_pid(pid.into()).map_err(|err| {
|
||||||
ShellError::Io(IoError::new_internal(
|
ShellError::Io(IoError::new_internal(
|
||||||
@ -136,7 +133,7 @@ fn unfreeze_job(
|
|||||||
.then(|| state.pipeline_externals_state.clone()),
|
.then(|| state.pipeline_externals_state.clone()),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(thread_job) = &state.current_thread_job() {
|
if let Some(thread_job) = &state.current_thread_job {
|
||||||
thread_job.remove_pid(pid);
|
thread_job.remove_pid(pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,13 +141,7 @@ fn unfreeze_job(
|
|||||||
Ok(ForegroundWaitStatus::Frozen(handle)) => {
|
Ok(ForegroundWaitStatus::Frozen(handle)) => {
|
||||||
let mut jobs = state.jobs.lock().expect("jobs lock is poisoned!");
|
let mut jobs = state.jobs.lock().expect("jobs lock is poisoned!");
|
||||||
|
|
||||||
jobs.add_job_with_id(
|
jobs.add_job_with_id(old_id, Job::Frozen(FrozenJob { unfreeze: handle }))
|
||||||
old_id,
|
|
||||||
Job::Frozen(FrozenJob {
|
|
||||||
unfreeze: handle,
|
|
||||||
tag,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.expect("job was supposed to be removed");
|
.expect("job was supposed to be removed");
|
||||||
|
|
||||||
if state.is_interactive {
|
if state.is_interactive {
|
||||||
|
@ -1,35 +1,18 @@
|
|||||||
mod is_admin;
|
mod is_admin;
|
||||||
mod job;
|
mod job;
|
||||||
mod job_id;
|
|
||||||
mod job_kill;
|
mod job_kill;
|
||||||
mod job_list;
|
mod job_list;
|
||||||
mod job_spawn;
|
mod job_spawn;
|
||||||
mod job_tag;
|
|
||||||
|
|
||||||
#[cfg(all(unix, feature = "os"))]
|
#[cfg(all(unix, feature = "os"))]
|
||||||
mod job_unfreeze;
|
mod job_unfreeze;
|
||||||
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
mod job_flush;
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
mod job_recv;
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
mod job_send;
|
|
||||||
|
|
||||||
pub use is_admin::IsAdmin;
|
pub use is_admin::IsAdmin;
|
||||||
pub use job::Job;
|
pub use job::Job;
|
||||||
pub use job_id::JobId;
|
|
||||||
pub use job_kill::JobKill;
|
pub use job_kill::JobKill;
|
||||||
pub use job_list::JobList;
|
pub use job_list::JobList;
|
||||||
pub use job_spawn::JobSpawn;
|
|
||||||
pub use job_tag::JobTag;
|
|
||||||
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
pub use job_spawn::JobSpawn;
|
||||||
pub use job_flush::JobFlush;
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
pub use job_recv::JobRecv;
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
pub use job_send::JobSend;
|
|
||||||
|
|
||||||
#[cfg(all(unix, feature = "os"))]
|
#[cfg(all(unix, feature = "os"))]
|
||||||
pub use job_unfreeze::JobUnfreeze;
|
pub use job_unfreeze::JobUnfreeze;
|
||||||
|
@ -132,7 +132,7 @@ impl Command for Cd {
|
|||||||
stack.set_cwd(path)?;
|
stack.set_cwd(path)?;
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
PermissionResult::PermissionDenied => {
|
PermissionResult::PermissionDenied(_) => {
|
||||||
Err(IoError::new(std::io::ErrorKind::PermissionDenied, call.head, path).into())
|
Err(IoError::new(std::io::ErrorKind::PermissionDenied, call.head, path).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,11 +35,6 @@ impl Command for Glob {
|
|||||||
"Whether to filter out symlinks from the returned paths",
|
"Whether to filter out symlinks from the returned paths",
|
||||||
Some('S'),
|
Some('S'),
|
||||||
)
|
)
|
||||||
.switch(
|
|
||||||
"follow-symlinks",
|
|
||||||
"Whether to follow symbolic links to their targets",
|
|
||||||
Some('l'),
|
|
||||||
)
|
|
||||||
.named(
|
.named(
|
||||||
"exclude",
|
"exclude",
|
||||||
SyntaxShape::List(Box::new(SyntaxShape::String)),
|
SyntaxShape::List(Box::new(SyntaxShape::String)),
|
||||||
@ -116,11 +111,6 @@ impl Command for Glob {
|
|||||||
example: r#"glob **/* --exclude [**/target/** **/.git/** */]"#,
|
example: r#"glob **/* --exclude [**/target/** **/.git/** */]"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
|
||||||
description: "Search for files following symbolic links to their targets",
|
|
||||||
example: r#"glob "**/*.txt" --follow-symlinks"#,
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +132,6 @@ impl Command for Glob {
|
|||||||
let no_dirs = call.has_flag(engine_state, stack, "no-dir")?;
|
let no_dirs = call.has_flag(engine_state, stack, "no-dir")?;
|
||||||
let no_files = call.has_flag(engine_state, stack, "no-file")?;
|
let no_files = call.has_flag(engine_state, stack, "no-file")?;
|
||||||
let no_symlinks = call.has_flag(engine_state, stack, "no-symlink")?;
|
let no_symlinks = call.has_flag(engine_state, stack, "no-symlink")?;
|
||||||
let follow_symlinks = call.has_flag(engine_state, stack, "follow-symlinks")?;
|
|
||||||
let paths_to_exclude: Option<Value> = call.get_flag(engine_state, stack, "exclude")?;
|
let paths_to_exclude: Option<Value> = call.get_flag(engine_state, stack, "exclude")?;
|
||||||
|
|
||||||
let (not_patterns, not_pattern_span): (Vec<String>, Span) = match paths_to_exclude {
|
let (not_patterns, not_pattern_span): (Vec<String>, Span) = match paths_to_exclude {
|
||||||
@ -224,11 +213,6 @@ impl Command for Glob {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let link_behavior = match follow_symlinks {
|
|
||||||
true => wax::LinkBehavior::ReadTarget,
|
|
||||||
false => wax::LinkBehavior::ReadFile,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = if !not_patterns.is_empty() {
|
let result = if !not_patterns.is_empty() {
|
||||||
let np: Vec<&str> = not_patterns.iter().map(|s| s as &str).collect();
|
let np: Vec<&str> = not_patterns.iter().map(|s| s as &str).collect();
|
||||||
let glob_results = glob
|
let glob_results = glob
|
||||||
@ -236,7 +220,7 @@ impl Command for Glob {
|
|||||||
path,
|
path,
|
||||||
WalkBehavior {
|
WalkBehavior {
|
||||||
depth: folder_depth,
|
depth: folder_depth,
|
||||||
link: link_behavior,
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.into_owned()
|
.into_owned()
|
||||||
@ -263,7 +247,7 @@ impl Command for Glob {
|
|||||||
path,
|
path,
|
||||||
WalkBehavior {
|
WalkBehavior {
|
||||||
depth: folder_depth,
|
depth: folder_depth,
|
||||||
link: link_behavior,
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.into_owned()
|
.into_owned()
|
||||||
|
@ -378,7 +378,10 @@ fn ls_for_one_pattern(
|
|||||||
.par_bridge()
|
.par_bridge()
|
||||||
.filter_map(move |x| match x {
|
.filter_map(move |x| match x {
|
||||||
Ok(path) => {
|
Ok(path) => {
|
||||||
let metadata = std::fs::symlink_metadata(&path).ok();
|
let metadata = match std::fs::symlink_metadata(&path) {
|
||||||
|
Ok(metadata) => Some(metadata),
|
||||||
|
Err(_) => None,
|
||||||
|
};
|
||||||
let hidden_dir_clone = Arc::clone(&hidden_dirs);
|
let hidden_dir_clone = Arc::clone(&hidden_dirs);
|
||||||
let mut hidden_dir_mutex = hidden_dir_clone
|
let mut hidden_dir_mutex = hidden_dir_clone
|
||||||
.lock()
|
.lock()
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
use nu_engine::{command_prelude::*, current_dir, eval_call};
|
use nu_engine::{command_prelude::*, current_dir, get_eval_block};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast,
|
ast,
|
||||||
debugger::{WithDebug, WithoutDebug},
|
|
||||||
shell_error::{self, io::IoError},
|
shell_error::{self, io::IoError},
|
||||||
DataSource, NuGlob, PipelineMetadata,
|
DataSource, NuGlob, PipelineMetadata,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::path::{Path, PathBuf};
|
||||||
collections::HashMap,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "sqlite")]
|
#[cfg(feature = "sqlite")]
|
||||||
use crate::database::SQLiteDatabase;
|
use crate::database::SQLiteDatabase;
|
||||||
@ -34,14 +30,7 @@ impl Command for Open {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec![
|
vec!["load", "read", "load_file", "read_file"]
|
||||||
"load",
|
|
||||||
"read",
|
|
||||||
"load_file",
|
|
||||||
"read_file",
|
|
||||||
"cat",
|
|
||||||
"get-content",
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
@ -74,6 +63,7 @@ impl Command for Open {
|
|||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
let cwd = current_dir(engine_state, stack)?;
|
let cwd = current_dir(engine_state, stack)?;
|
||||||
let mut paths = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
|
let mut paths = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
|
||||||
|
let eval_block = get_eval_block(engine_state);
|
||||||
|
|
||||||
if paths.is_empty() && !call.has_positional_args(stack, 0) {
|
if paths.is_empty() && !call.has_positional_args(stack, 0) {
|
||||||
// try to use path from pipeline input if there were no positional or spread args
|
// try to use path from pipeline input if there were no positional or spread args
|
||||||
@ -202,16 +192,13 @@ impl Command for Open {
|
|||||||
|
|
||||||
match converter {
|
match converter {
|
||||||
Some((converter_id, ext)) => {
|
Some((converter_id, ext)) => {
|
||||||
let open_call = ast::Call {
|
let decl = engine_state.get_decl(converter_id);
|
||||||
decl_id: converter_id,
|
let command_output = if let Some(block_id) = decl.block_id() {
|
||||||
head: call_span,
|
let block = engine_state.get_block(block_id);
|
||||||
arguments: vec![],
|
eval_block(engine_state, stack, block, stream)
|
||||||
parser_info: HashMap::new(),
|
|
||||||
};
|
|
||||||
let command_output = if engine_state.is_debugging() {
|
|
||||||
eval_call::<WithDebug>(engine_state, stack, &open_call, stream)
|
|
||||||
} else {
|
} else {
|
||||||
eval_call::<WithoutDebug>(engine_state, stack, &open_call, stream)
|
let call = ast::Call::new(call_span);
|
||||||
|
decl.run(engine_state, stack, &(&call).into(), stream)
|
||||||
};
|
};
|
||||||
output.push(command_output.map_err(|inner| {
|
output.push(command_output.map_err(|inner| {
|
||||||
ShellError::GenericError{
|
ShellError::GenericError{
|
||||||
@ -281,16 +268,6 @@ impl Command for Open {
|
|||||||
example: r#"def "from ndjson" [] { from json -o }; open myfile.ndjson"#,
|
example: r#"def "from ndjson" [] { from json -o }; open myfile.ndjson"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
|
||||||
description: "Show the extensions for which the `open` command will automatically parse",
|
|
||||||
example: r#"scope commands
|
|
||||||
| where name starts-with "from "
|
|
||||||
| insert extension { get name | str replace -r "^from " "" | $"*.($in)" }
|
|
||||||
| select extension name
|
|
||||||
| rename extension command
|
|
||||||
"#,
|
|
||||||
result: None,
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,11 @@ use nu_protocol::{
|
|||||||
};
|
};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::prelude::FileTypeExt;
|
use std::os::unix::prelude::FileTypeExt;
|
||||||
use std::{collections::HashMap, io::Error, path::PathBuf};
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
io::{Error, ErrorKind},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
const TRASH_SUPPORTED: bool = cfg!(all(
|
const TRASH_SUPPORTED: bool = cfg!(all(
|
||||||
feature = "trash-support",
|
feature = "trash-support",
|
||||||
@ -375,7 +379,7 @@ fn rm(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let result = if let Err(e) = interaction {
|
let result = if let Err(e) = interaction {
|
||||||
Err(Error::other(&*e.to_string()))
|
Err(Error::new(ErrorKind::Other, &*e.to_string()))
|
||||||
} else if interactive && !confirmed {
|
} else if interactive && !confirmed {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else if TRASH_SUPPORTED && (trash || (rm_always_trash && !permanent)) {
|
} else if TRASH_SUPPORTED && (trash || (rm_always_trash && !permanent)) {
|
||||||
@ -385,7 +389,7 @@ fn rm(
|
|||||||
))]
|
))]
|
||||||
{
|
{
|
||||||
trash::delete(&f).map_err(|e: trash::Error| {
|
trash::delete(&f).map_err(|e: trash::Error| {
|
||||||
Error::other(format!("{e:?}\nTry '--permanent' flag"))
|
Error::new(ErrorKind::Other, format!("{e:?}\nTry '--permanent' flag"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,17 +263,6 @@ impl Command for Save {
|
|||||||
example: r#"do -i {} | save foo.txt --stderr bar.txt"#,
|
example: r#"do -i {} | save foo.txt --stderr bar.txt"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
|
||||||
description:
|
|
||||||
"Show the extensions for which the `save` command will automatically serialize",
|
|
||||||
example: r#"scope commands
|
|
||||||
| where name starts-with "to "
|
|
||||||
| insert extension { get name | str replace -r "^to " "" | $"*.($in)" }
|
|
||||||
| select extension name
|
|
||||||
| rename extension command
|
|
||||||
"#,
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,8 +158,10 @@ impl Command for UTouch {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut expanded_globs =
|
let mut expanded_globs = glob(
|
||||||
glob(&file_path.to_string_lossy(), engine_state.signals().clone())
|
&file_path.to_string_lossy(),
|
||||||
|
Some(engine_state.signals().clone()),
|
||||||
|
)
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
panic!(
|
panic!(
|
||||||
"Failed to process file path: {}",
|
"Failed to process file path: {}",
|
||||||
|
@ -30,11 +30,6 @@ impl Command for All {
|
|||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
|
||||||
description: "Check if a list contains only true values",
|
|
||||||
example: "[false true true false] | all {}",
|
|
||||||
result: Some(Value::test_bool(false)),
|
|
||||||
},
|
|
||||||
Example {
|
Example {
|
||||||
description: "Check if each row's status is the string 'UP'",
|
description: "Check if each row's status is the string 'UP'",
|
||||||
example: "[[status]; [UP] [UP]] | all {|el| $el.status == UP }",
|
example: "[[status]; [UP] [UP]] | all {|el| $el.status == UP }",
|
||||||
|
@ -30,11 +30,6 @@ impl Command for Any {
|
|||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
|
||||||
description: "Check if a list contains any true values",
|
|
||||||
example: "[false true true false] | any {}",
|
|
||||||
result: Some(Value::test_bool(true)),
|
|
||||||
},
|
|
||||||
Example {
|
Example {
|
||||||
description: "Check if any row's status is the string 'DOWN'",
|
description: "Check if any row's status is the string 'DOWN'",
|
||||||
example: "[[status]; [UP] [DOWN] [UP]] | any {|el| $el.status == DOWN }",
|
example: "[[status]; [UP] [DOWN] [UP]] | any {|el| $el.status == DOWN }",
|
||||||
|
@ -243,7 +243,7 @@ mod test {
|
|||||||
let chunks = chunk_read.map(|e| e.unwrap()).collect::<Vec<_>>();
|
let chunks = chunk_read.map(|e| e.unwrap()).collect::<Vec<_>>();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
chunks,
|
chunks,
|
||||||
[&s.as_bytes()[..4], &s.as_bytes()[4..8], &s.as_bytes()[8..]]
|
[s[..4].as_bytes(), s[4..8].as_bytes(), s[8..].as_bytes()]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,7 +260,7 @@ mod test {
|
|||||||
let chunks = chunk_read.map(|e| e.unwrap()).collect::<Vec<_>>();
|
let chunks = chunk_read.map(|e| e.unwrap()).collect::<Vec<_>>();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
chunks,
|
chunks,
|
||||||
[&s.as_bytes()[..4], &s.as_bytes()[4..8], &s.as_bytes()[8..]]
|
[s[..4].as_bytes(), s[4..8].as_bytes(), s[8..].as_bytes()]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{ListStream, Signals};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Default;
|
pub struct Default;
|
||||||
@ -147,20 +146,6 @@ fn default(
|
|||||||
&& matches!(input, PipelineData::Value(ref value, _) if value.is_empty()))
|
&& matches!(input, PipelineData::Value(ref value, _) if value.is_empty()))
|
||||||
{
|
{
|
||||||
Ok(value.into_pipeline_data())
|
Ok(value.into_pipeline_data())
|
||||||
} else if default_when_empty && matches!(input, PipelineData::ListStream(..)) {
|
|
||||||
let PipelineData::ListStream(ls, metadata) = input else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
|
||||||
let span = ls.span();
|
|
||||||
let mut stream = ls.into_inner().peekable();
|
|
||||||
if stream.peek().is_none() {
|
|
||||||
return Ok(value.into_pipeline_data());
|
|
||||||
}
|
|
||||||
|
|
||||||
// stream's internal state already preserves the original signals config, so if this
|
|
||||||
// Signals::empty list stream gets interrupted it will be caught by the underlying iterator
|
|
||||||
let ls = ListStream::new(stream, span, Signals::empty());
|
|
||||||
Ok(PipelineData::ListStream(ls, metadata))
|
|
||||||
} else {
|
} else {
|
||||||
Ok(input)
|
Ok(input)
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,17 @@ pub fn empty(
|
|||||||
if !columns.is_empty() {
|
if !columns.is_empty() {
|
||||||
for val in input {
|
for val in input {
|
||||||
for column in &columns {
|
for column in &columns {
|
||||||
if !val.follow_cell_path(&column.members, false)?.is_nothing() {
|
let val = val.clone();
|
||||||
return Ok(Value::bool(negate, head).into_pipeline_data());
|
match val.follow_cell_path(&column.members, false) {
|
||||||
|
Ok(Value::Nothing { .. }) => {}
|
||||||
|
Ok(_) => {
|
||||||
|
if negate {
|
||||||
|
return Ok(Value::bool(true, head).into_pipeline_data());
|
||||||
|
} else {
|
||||||
|
return Ok(Value::bool(false, head).into_pipeline_data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{ast::PathMember, Signals};
|
use nu_protocol::{ast::PathMember, Signals};
|
||||||
|
|
||||||
@ -190,11 +188,9 @@ fn action(
|
|||||||
let input = input.into_value(span)?;
|
let input = input.into_value(span)?;
|
||||||
|
|
||||||
for path in paths {
|
for path in paths {
|
||||||
output.push(
|
let val = input.clone().follow_cell_path(&path.members, !sensitive);
|
||||||
input
|
|
||||||
.follow_cell_path(&path.members, !sensitive)?
|
output.push(val?);
|
||||||
.into_owned(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(output.into_iter().into_pipeline_data(span, signals))
|
Ok(output.into_iter().into_pipeline_data(span, signals))
|
||||||
@ -227,10 +223,10 @@ pub fn follow_cell_path_into_stream(
|
|||||||
.map(move |value| {
|
.map(move |value| {
|
||||||
let span = value.span();
|
let span = value.span();
|
||||||
|
|
||||||
value
|
match value.follow_cell_path(&cell_path, insensitive) {
|
||||||
.follow_cell_path(&cell_path, insensitive)
|
Ok(v) => v,
|
||||||
.map(Cow::into_owned)
|
Err(error) => Value::error(error, span),
|
||||||
.unwrap_or_else(|error| Value::error(error, span))
|
}
|
||||||
})
|
})
|
||||||
.into_pipeline_data(head, signals);
|
.into_pipeline_data(head, signals);
|
||||||
|
|
||||||
|
@ -322,9 +322,11 @@ fn group_cell_path(
|
|||||||
let mut groups = IndexMap::<_, Vec<_>>::new();
|
let mut groups = IndexMap::<_, Vec<_>>::new();
|
||||||
|
|
||||||
for value in values.into_iter() {
|
for value in values.into_iter() {
|
||||||
let key = value.follow_cell_path(&column_name.members, false)?;
|
let key = value
|
||||||
|
.clone()
|
||||||
|
.follow_cell_path(&column_name.members, false)?;
|
||||||
|
|
||||||
if key.is_nothing() {
|
if matches!(key, Value::Nothing { .. }) {
|
||||||
continue; // likely the result of a failed optional access, ignore this value
|
continue; // likely the result of a failed optional access, ignore this value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce};
|
use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce};
|
||||||
use nu_protocol::ast::PathMember;
|
use nu_protocol::ast::PathMember;
|
||||||
|
|
||||||
@ -301,8 +299,8 @@ fn insert_value_by_closure(
|
|||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let value_at_path = if first_path_member_int {
|
let value_at_path = if first_path_member_int {
|
||||||
value
|
value
|
||||||
|
.clone()
|
||||||
.follow_cell_path(cell_path, false)
|
.follow_cell_path(cell_path, false)
|
||||||
.map(Cow::into_owned)
|
|
||||||
.unwrap_or(Value::nothing(span))
|
.unwrap_or(Value::nothing(span))
|
||||||
} else {
|
} else {
|
||||||
value.clone()
|
value.clone()
|
||||||
@ -321,8 +319,8 @@ fn insert_single_value_by_closure(
|
|||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let value_at_path = if first_path_member_int {
|
let value_at_path = if first_path_member_int {
|
||||||
value
|
value
|
||||||
|
.clone()
|
||||||
.follow_cell_path(cell_path, false)
|
.follow_cell_path(cell_path, false)
|
||||||
.map(Cow::into_owned)
|
|
||||||
.unwrap_or(Value::nothing(span))
|
.unwrap_or(Value::nothing(span))
|
||||||
} else {
|
} else {
|
||||||
value.clone()
|
value.clone()
|
||||||
|
@ -255,16 +255,6 @@ fn join_rows(
|
|||||||
config: &Config,
|
config: &Config,
|
||||||
span: Span,
|
span: Span,
|
||||||
) {
|
) {
|
||||||
if !this
|
|
||||||
.iter()
|
|
||||||
.any(|this_record| match this_record.as_record() {
|
|
||||||
Ok(record) => record.contains(this_join_key),
|
|
||||||
Err(_) => false,
|
|
||||||
})
|
|
||||||
{
|
|
||||||
// `this` table does not contain the join column; do nothing
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for this_row in this {
|
for this_row in this {
|
||||||
if let Value::Record {
|
if let Value::Record {
|
||||||
val: this_record, ..
|
val: this_record, ..
|
||||||
@ -291,14 +281,10 @@ fn join_rows(
|
|||||||
result.push(Value::record(record, span))
|
result.push(Value::record(record, span))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
} else if !matches!(join_type, JoinType::Inner) {
|
||||||
}
|
|
||||||
}
|
|
||||||
if !matches!(join_type, JoinType::Inner) {
|
|
||||||
// Either `this` row is missing a value for the join column or
|
|
||||||
// `other` table did not contain any rows matching
|
// `other` table did not contain any rows matching
|
||||||
// `this` row on the join column; emit a single joined
|
// `this` row on the join column; emit a single joined
|
||||||
// row with null values for columns not present
|
// row with null values for columns not present,
|
||||||
let other_record = other_keys
|
let other_record = other_keys
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&key| {
|
.map(|&key| {
|
||||||
@ -319,12 +305,15 @@ fn join_rows(
|
|||||||
JoinType::Inner | JoinType::Right => {
|
JoinType::Inner | JoinType::Right => {
|
||||||
merge_records(&other_record, this_record, shared_join_key)
|
merge_records(&other_record, this_record, shared_join_key)
|
||||||
}
|
}
|
||||||
JoinType::Left => merge_records(this_record, &other_record, shared_join_key),
|
JoinType::Left => {
|
||||||
|
merge_records(this_record, &other_record, shared_join_key)
|
||||||
|
}
|
||||||
_ => panic!("not implemented"),
|
_ => panic!("not implemented"),
|
||||||
};
|
};
|
||||||
|
|
||||||
result.push(Value::record(record, span))
|
result.push(Value::record(record, span))
|
||||||
}
|
}
|
||||||
|
} // else { a row is missing a value for the join column }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,9 +42,8 @@ pub(crate) fn typecheck_merge(lhs: &Value, rhs: &Value, head: Span) -> Result<()
|
|||||||
match (lhs.get_type(), rhs.get_type()) {
|
match (lhs.get_type(), rhs.get_type()) {
|
||||||
(Type::Record { .. }, Type::Record { .. }) => Ok(()),
|
(Type::Record { .. }, Type::Record { .. }) => Ok(()),
|
||||||
(_, _) if is_list_of_records(lhs) && is_list_of_records(rhs) => Ok(()),
|
(_, _) if is_list_of_records(lhs) && is_list_of_records(rhs) => Ok(()),
|
||||||
other => Err(ShellError::OnlySupportsThisInputType {
|
_ => Err(ShellError::PipelineMismatch {
|
||||||
exp_input_type: "input and argument to be both record or both table".to_string(),
|
exp_input_type: "input and argument to be both record or both table".to_string(),
|
||||||
wrong_type: format!("{} and {}", other.0, other.1).to_string(),
|
|
||||||
dst_span: head,
|
dst_span: head,
|
||||||
src_span: lhs.span(),
|
src_span: lhs.span(),
|
||||||
}),
|
}),
|
||||||
|
@ -174,9 +174,8 @@ impl Command for Move {
|
|||||||
PipelineData::Value(Value::Record { val, .. }, ..) => {
|
PipelineData::Value(Value::Record { val, .. }, ..) => {
|
||||||
Ok(move_record_columns(&val, &columns, &location, head)?.into_pipeline_data())
|
Ok(move_record_columns(&val, &columns, &location, head)?.into_pipeline_data())
|
||||||
}
|
}
|
||||||
other => Err(ShellError::OnlySupportsThisInputType {
|
_ => Err(ShellError::PipelineMismatch {
|
||||||
exp_input_type: "record or table".to_string(),
|
exp_input_type: "record or table".to_string(),
|
||||||
wrong_type: other.get_type().to_string(),
|
|
||||||
dst_span: head,
|
dst_span: head,
|
||||||
src_span: Span::new(head.start, head.start),
|
src_span: Span::new(head.start, head.start),
|
||||||
}),
|
}),
|
||||||
|
@ -229,15 +229,17 @@ fn select(
|
|||||||
match v {
|
match v {
|
||||||
Value::List {
|
Value::List {
|
||||||
vals: input_vals, ..
|
vals: input_vals, ..
|
||||||
} => Ok(input_vals
|
} => {
|
||||||
|
Ok(input_vals
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |input_val| {
|
.map(move |input_val| {
|
||||||
if !columns.is_empty() {
|
if !columns.is_empty() {
|
||||||
let mut record = Record::new();
|
let mut record = Record::new();
|
||||||
for path in &columns {
|
for path in &columns {
|
||||||
match input_val.follow_cell_path(&path.members, false) {
|
//FIXME: improve implementation to not clone
|
||||||
|
match input_val.clone().follow_cell_path(&path.members, false) {
|
||||||
Ok(fetcher) => {
|
Ok(fetcher) => {
|
||||||
record.push(path.to_column_name(), fetcher.into_owned());
|
record.push(path.to_column_name(), fetcher);
|
||||||
}
|
}
|
||||||
Err(e) => return Value::error(e, call_span),
|
Err(e) => return Value::error(e, call_span),
|
||||||
}
|
}
|
||||||
@ -252,14 +254,20 @@ fn select(
|
|||||||
call_span,
|
call_span,
|
||||||
engine_state.signals().clone(),
|
engine_state.signals().clone(),
|
||||||
metadata,
|
metadata,
|
||||||
)),
|
))
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if !columns.is_empty() {
|
if !columns.is_empty() {
|
||||||
let mut record = Record::new();
|
let mut record = Record::new();
|
||||||
|
|
||||||
for cell_path in columns {
|
for cell_path in columns {
|
||||||
let result = v.follow_cell_path(&cell_path.members, false)?;
|
// FIXME: remove clone
|
||||||
record.push(cell_path.to_column_name(), result.into_owned());
|
match v.clone().follow_cell_path(&cell_path.members, false) {
|
||||||
|
Ok(result) => {
|
||||||
|
record.push(cell_path.to_column_name(), result);
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::record(record, call_span)
|
Ok(Value::record(record, call_span)
|
||||||
@ -270,14 +278,16 @@ fn select(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PipelineData::ListStream(stream, metadata, ..) => Ok(stream
|
PipelineData::ListStream(stream, metadata, ..) => {
|
||||||
|
Ok(stream
|
||||||
.map(move |x| {
|
.map(move |x| {
|
||||||
if !columns.is_empty() {
|
if !columns.is_empty() {
|
||||||
let mut record = Record::new();
|
let mut record = Record::new();
|
||||||
for path in &columns {
|
for path in &columns {
|
||||||
match x.follow_cell_path(&path.members, false) {
|
//FIXME: improve implementation to not clone
|
||||||
|
match x.clone().follow_cell_path(&path.members, false) {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
record.push(path.to_column_name(), value.into_owned());
|
record.push(path.to_column_name(), value);
|
||||||
}
|
}
|
||||||
Err(e) => return Value::error(e, call_span),
|
Err(e) => return Value::error(e, call_span),
|
||||||
}
|
}
|
||||||
@ -287,7 +297,12 @@ fn select(
|
|||||||
x
|
x
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.into_pipeline_data_with_metadata(call_span, engine_state.signals().clone(), metadata)),
|
.into_pipeline_data_with_metadata(
|
||||||
|
call_span,
|
||||||
|
engine_state.signals().clone(),
|
||||||
|
metadata,
|
||||||
|
))
|
||||||
|
}
|
||||||
_ => Ok(PipelineData::empty()),
|
_ => Ok(PipelineData::empty()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use rand::{prelude::SliceRandom, rng};
|
use rand::{prelude::SliceRandom, thread_rng};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Shuffle;
|
pub struct Shuffle;
|
||||||
@ -31,7 +31,7 @@ impl Command for Shuffle {
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let metadata = input.metadata();
|
let metadata = input.metadata();
|
||||||
let mut values = input.into_iter_strict(call.head)?.collect::<Vec<_>>();
|
let mut values = input.into_iter_strict(call.head)?.collect::<Vec<_>>();
|
||||||
values.shuffle(&mut rng());
|
values.shuffle(&mut thread_rng());
|
||||||
let iter = values.into_iter();
|
let iter = values.into_iter();
|
||||||
Ok(iter.into_pipeline_data_with_metadata(
|
Ok(iter.into_pipeline_data_with_metadata(
|
||||||
call.head,
|
call.head,
|
||||||
|
@ -184,10 +184,9 @@ impl Command for Sort {
|
|||||||
dst_span: value.span(),
|
dst_span: value.span(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ref other => {
|
_ => {
|
||||||
return Err(ShellError::OnlySupportsThisInputType {
|
return Err(ShellError::PipelineMismatch {
|
||||||
exp_input_type: "record or list".to_string(),
|
exp_input_type: "record or list".to_string(),
|
||||||
wrong_type: other.get_type().to_string(),
|
|
||||||
dst_span: call.head,
|
dst_span: call.head,
|
||||||
src_span: value.span(),
|
src_span: value.span(),
|
||||||
})
|
})
|
||||||
|
@ -394,7 +394,7 @@ impl<R: Read> Read for IoTee<R> {
|
|||||||
if let Some(thread) = self.thread.take() {
|
if let Some(thread) = self.thread.take() {
|
||||||
if thread.is_finished() {
|
if thread.is_finished() {
|
||||||
if let Err(err) = thread.join().unwrap_or_else(|_| Err(panic_error())) {
|
if let Err(err) = thread.join().unwrap_or_else(|_| Err(panic_error())) {
|
||||||
return Err(io::Error::other(err));
|
return Err(io::Error::new(io::ErrorKind::Other, err));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.thread = Some(thread)
|
self.thread = Some(thread)
|
||||||
@ -405,7 +405,7 @@ impl<R: Read> Read for IoTee<R> {
|
|||||||
self.sender = None;
|
self.sender = None;
|
||||||
if let Some(thread) = self.thread.take() {
|
if let Some(thread) = self.thread.take() {
|
||||||
if let Err(err) = thread.join().unwrap_or_else(|_| Err(panic_error())) {
|
if let Err(err) = thread.join().unwrap_or_else(|_| Err(panic_error())) {
|
||||||
return Err(io::Error::other(err));
|
return Err(io::Error::new(io::ErrorKind::Other, err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(sender) = self.sender.as_mut() {
|
} else if let Some(sender) = self.sender.as_mut() {
|
||||||
|
@ -243,17 +243,17 @@ fn update_value_by_closure(
|
|||||||
cell_path: &[PathMember],
|
cell_path: &[PathMember],
|
||||||
first_path_member_int: bool,
|
first_path_member_int: bool,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let value_at_path = value.follow_cell_path(cell_path, false)?;
|
let value_at_path = value.clone().follow_cell_path(cell_path, false)?;
|
||||||
|
|
||||||
let arg = if first_path_member_int {
|
let arg = if first_path_member_int {
|
||||||
value_at_path.as_ref()
|
&value_at_path
|
||||||
} else {
|
} else {
|
||||||
&*value
|
&*value
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_value = closure
|
let new_value = closure
|
||||||
.add_arg(arg.clone())
|
.add_arg(arg.clone())
|
||||||
.run_with_input(value_at_path.into_owned().into_pipeline_data())?
|
.run_with_input(value_at_path.into_pipeline_data())?
|
||||||
.into_value(span)?;
|
.into_value(span)?;
|
||||||
|
|
||||||
value.update_data_at_cell_path(cell_path, new_value)
|
value.update_data_at_cell_path(cell_path, new_value)
|
||||||
@ -266,17 +266,17 @@ fn update_single_value_by_closure(
|
|||||||
cell_path: &[PathMember],
|
cell_path: &[PathMember],
|
||||||
first_path_member_int: bool,
|
first_path_member_int: bool,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let value_at_path = value.follow_cell_path(cell_path, false)?;
|
let value_at_path = value.clone().follow_cell_path(cell_path, false)?;
|
||||||
|
|
||||||
let arg = if first_path_member_int {
|
let arg = if first_path_member_int {
|
||||||
value_at_path.as_ref()
|
&value_at_path
|
||||||
} else {
|
} else {
|
||||||
&*value
|
&*value
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_value = closure
|
let new_value = closure
|
||||||
.add_arg(arg.clone())
|
.add_arg(arg.clone())
|
||||||
.run_with_input(value_at_path.into_owned().into_pipeline_data())?
|
.run_with_input(value_at_path.into_pipeline_data())?
|
||||||
.into_value(span)?;
|
.into_value(span)?;
|
||||||
|
|
||||||
value.update_data_at_cell_path(cell_path, new_value)
|
value.update_data_at_cell_path(cell_path, new_value)
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce};
|
use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce};
|
||||||
use nu_protocol::ast::PathMember;
|
use nu_protocol::ast::PathMember;
|
||||||
|
|
||||||
@ -321,19 +319,15 @@ fn upsert_value_by_closure(
|
|||||||
cell_path: &[PathMember],
|
cell_path: &[PathMember],
|
||||||
first_path_member_int: bool,
|
first_path_member_int: bool,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let value_at_path = value.follow_cell_path(cell_path, false);
|
let value_at_path = value.clone().follow_cell_path(cell_path, false);
|
||||||
|
|
||||||
let arg = if first_path_member_int {
|
let arg = if first_path_member_int {
|
||||||
value_at_path
|
value_at_path.clone().unwrap_or(Value::nothing(span))
|
||||||
.as_deref()
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or(Value::nothing(span))
|
|
||||||
} else {
|
} else {
|
||||||
value.clone()
|
value.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let input = value_at_path
|
let input = value_at_path
|
||||||
.map(Cow::into_owned)
|
|
||||||
.map(IntoPipelineData::into_pipeline_data)
|
.map(IntoPipelineData::into_pipeline_data)
|
||||||
.unwrap_or(PipelineData::Empty);
|
.unwrap_or(PipelineData::Empty);
|
||||||
|
|
||||||
@ -352,19 +346,15 @@ fn upsert_single_value_by_closure(
|
|||||||
cell_path: &[PathMember],
|
cell_path: &[PathMember],
|
||||||
first_path_member_int: bool,
|
first_path_member_int: bool,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let value_at_path = value.follow_cell_path(cell_path, false);
|
let value_at_path = value.clone().follow_cell_path(cell_path, false);
|
||||||
|
|
||||||
let arg = if first_path_member_int {
|
let arg = if first_path_member_int {
|
||||||
value_at_path
|
value_at_path.clone().unwrap_or(Value::nothing(span))
|
||||||
.as_deref()
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or(Value::nothing(span))
|
|
||||||
} else {
|
} else {
|
||||||
value.clone()
|
value.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let input = value_at_path
|
let input = value_at_path
|
||||||
.map(Cow::into_owned)
|
|
||||||
.map(IntoPipelineData::into_pipeline_data)
|
.map(IntoPipelineData::into_pipeline_data)
|
||||||
.unwrap_or(PipelineData::Empty);
|
.unwrap_or(PipelineData::Empty);
|
||||||
|
|
||||||
|
@ -36,13 +36,13 @@ impl Command for ToMd {
|
|||||||
Example {
|
Example {
|
||||||
description: "Outputs an MD string representing the contents of this table",
|
description: "Outputs an MD string representing the contents of this table",
|
||||||
example: "[[foo bar]; [1 2]] | to md",
|
example: "[[foo bar]; [1 2]] | to md",
|
||||||
result: Some(Value::test_string("|foo|bar|\n|-|-|\n|1|2|")),
|
result: Some(Value::test_string("|foo|bar|\n|-|-|\n|1|2|\n")),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Optionally, output a formatted markdown string",
|
description: "Optionally, output a formatted markdown string",
|
||||||
example: "[[foo bar]; [1 2]] | to md --pretty",
|
example: "[[foo bar]; [1 2]] | to md --pretty",
|
||||||
result: Some(Value::test_string(
|
result: Some(Value::test_string(
|
||||||
"| foo | bar |\n| --- | --- |\n| 1 | 2 |",
|
"| foo | bar |\n| --- | --- |\n| 1 | 2 |\n",
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
@ -57,13 +57,6 @@ impl Command for ToMd {
|
|||||||
example: "[0 1 2] | to md --pretty",
|
example: "[0 1 2] | to md --pretty",
|
||||||
result: Some(Value::test_string("0\n1\n2")),
|
result: Some(Value::test_string("0\n1\n2")),
|
||||||
},
|
},
|
||||||
Example {
|
|
||||||
description: "Separate list into markdown tables",
|
|
||||||
example: "[ {foo: 1, bar: 2} {foo: 3, bar: 4} {foo: 5}] | to md --per-element",
|
|
||||||
result: Some(Value::test_string(
|
|
||||||
"|foo|bar|\n|-|-|\n|1|2|\n|3|4|\n|foo|\n|-|\n|5|",
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,14 +94,11 @@ fn to_md(
|
|||||||
grouped_input
|
grouped_input
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |val| match val {
|
.map(move |val| match val {
|
||||||
Value::List { .. } => {
|
Value::List { .. } => table(val.into_pipeline_data(), pretty, config),
|
||||||
format!("{}\n", table(val.into_pipeline_data(), pretty, config))
|
|
||||||
}
|
|
||||||
other => fragment(other, pretty, config),
|
other => fragment(other, pretty, config),
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("")
|
.join(""),
|
||||||
.trim(),
|
|
||||||
head,
|
head,
|
||||||
)
|
)
|
||||||
.into_pipeline_data_with_metadata(Some(metadata)));
|
.into_pipeline_data_with_metadata(Some(metadata)));
|
||||||
@ -162,13 +152,7 @@ fn collect_headers(headers: &[String]) -> (Vec<String>, Vec<usize>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn table(input: PipelineData, pretty: bool, config: &Config) -> String {
|
fn table(input: PipelineData, pretty: bool, config: &Config) -> String {
|
||||||
let vec_of_values = input
|
let vec_of_values = input.into_iter().collect::<Vec<Value>>();
|
||||||
.into_iter()
|
|
||||||
.flat_map(|val| match val {
|
|
||||||
Value::List { vals, .. } => vals,
|
|
||||||
other => vec![other],
|
|
||||||
})
|
|
||||||
.collect::<Vec<Value>>();
|
|
||||||
let mut headers = merge_descriptors(&vec_of_values);
|
let mut headers = merge_descriptors(&vec_of_values);
|
||||||
|
|
||||||
let mut empty_header_index = 0;
|
let mut empty_header_index = 0;
|
||||||
@ -480,39 +464,6 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_empty_row_value() {
|
|
||||||
let value = Value::test_list(vec![
|
|
||||||
Value::test_record(record! {
|
|
||||||
"foo" => Value::test_string("1"),
|
|
||||||
"bar" => Value::test_string("2"),
|
|
||||||
}),
|
|
||||||
Value::test_record(record! {
|
|
||||||
"foo" => Value::test_string("3"),
|
|
||||||
"bar" => Value::test_string("4"),
|
|
||||||
}),
|
|
||||||
Value::test_record(record! {
|
|
||||||
"foo" => Value::test_string("5"),
|
|
||||||
"bar" => Value::test_string(""),
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
table(
|
|
||||||
value.clone().into_pipeline_data(),
|
|
||||||
false,
|
|
||||||
&Config::default()
|
|
||||||
),
|
|
||||||
one(r#"
|
|
||||||
|foo|bar|
|
|
||||||
|-|-|
|
|
||||||
|1|2|
|
|
||||||
|3|4|
|
|
||||||
|5||
|
|
||||||
"#)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_content_type_metadata() {
|
fn test_content_type_metadata() {
|
||||||
let mut engine_state = Box::new(EngineState::new());
|
let mut engine_state = Box::new(EngineState::new());
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use chrono::Datelike;
|
|
||||||
use chrono_humanize::HumanTime;
|
use chrono_humanize::HumanTime;
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{format_duration, shell_error::io::IoError, ByteStream, PipelineMetadata};
|
use nu_protocol::{format_duration, shell_error::io::IoError, ByteStream, PipelineMetadata};
|
||||||
@ -168,17 +167,7 @@ fn local_into_string(
|
|||||||
Value::Filesize { val, .. } => val.to_string(),
|
Value::Filesize { val, .. } => val.to_string(),
|
||||||
Value::Duration { val, .. } => format_duration(val),
|
Value::Duration { val, .. } => format_duration(val),
|
||||||
Value::Date { val, .. } => {
|
Value::Date { val, .. } => {
|
||||||
format!(
|
format!("{} ({})", val.to_rfc2822(), HumanTime::from(val))
|
||||||
"{} ({})",
|
|
||||||
{
|
|
||||||
if val.year() >= 0 {
|
|
||||||
val.to_rfc2822()
|
|
||||||
} else {
|
|
||||||
val.to_rfc3339()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
HumanTime::from(val)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Value::Range { val, .. } => val.to_string(),
|
Value::Range { val, .. } => val.to_string(),
|
||||||
Value::String { val, .. } => val,
|
Value::String { val, .. } => val,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use crate::math::utils::ensure_bounded;
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -22,7 +21,6 @@ impl Command for MathAbs {
|
|||||||
Type::List(Box::new(Type::Duration)),
|
Type::List(Box::new(Type::Duration)),
|
||||||
Type::List(Box::new(Type::Duration)),
|
Type::List(Box::new(Type::Duration)),
|
||||||
),
|
),
|
||||||
(Type::Range, Type::List(Box::new(Type::Number))),
|
|
||||||
])
|
])
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
@ -48,16 +46,6 @@ impl Command for MathAbs {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
if let PipelineData::Value(
|
|
||||||
Value::Range {
|
|
||||||
ref val,
|
|
||||||
internal_span,
|
|
||||||
},
|
|
||||||
..,
|
|
||||||
) = input
|
|
||||||
{
|
|
||||||
ensure_bounded(val.as_ref(), internal_span, head)?;
|
|
||||||
}
|
|
||||||
input.map(move |value| abs_helper(value, head), engine_state.signals())
|
input.map(move |value| abs_helper(value, head), engine_state.signals())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,16 +56,6 @@ impl Command for MathAbs {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
if let PipelineData::Value(
|
|
||||||
Value::Range {
|
|
||||||
ref val,
|
|
||||||
internal_span,
|
|
||||||
},
|
|
||||||
..,
|
|
||||||
) = input
|
|
||||||
{
|
|
||||||
ensure_bounded(val.as_ref(), internal_span, head)?;
|
|
||||||
}
|
|
||||||
input.map(
|
input.map(
|
||||||
move |value| abs_helper(value, head),
|
move |value| abs_helper(value, head),
|
||||||
working_set.permanent().signals(),
|
working_set.permanent().signals(),
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use crate::math::utils::ensure_bounded;
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -17,7 +16,6 @@ impl Command for MathCeil {
|
|||||||
Type::List(Box::new(Type::Number)),
|
Type::List(Box::new(Type::Number)),
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
),
|
),
|
||||||
(Type::Range, Type::List(Box::new(Type::Number))),
|
|
||||||
])
|
])
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
@ -47,16 +45,6 @@ impl Command for MathCeil {
|
|||||||
if matches!(input, PipelineData::Empty) {
|
if matches!(input, PipelineData::Empty) {
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
}
|
}
|
||||||
if let PipelineData::Value(
|
|
||||||
Value::Range {
|
|
||||||
ref val,
|
|
||||||
internal_span,
|
|
||||||
},
|
|
||||||
..,
|
|
||||||
) = input
|
|
||||||
{
|
|
||||||
ensure_bounded(val.as_ref(), internal_span, head)?;
|
|
||||||
}
|
|
||||||
input.map(move |value| operate(value, head), engine_state.signals())
|
input.map(move |value| operate(value, head), engine_state.signals())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,16 +59,6 @@ impl Command for MathCeil {
|
|||||||
if matches!(input, PipelineData::Empty) {
|
if matches!(input, PipelineData::Empty) {
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
}
|
}
|
||||||
if let PipelineData::Value(
|
|
||||||
Value::Range {
|
|
||||||
ref val,
|
|
||||||
internal_span,
|
|
||||||
},
|
|
||||||
..,
|
|
||||||
) = input
|
|
||||||
{
|
|
||||||
ensure_bounded(val.as_ref(), internal_span, head)?;
|
|
||||||
}
|
|
||||||
input.map(
|
input.map(
|
||||||
move |value| operate(value, head),
|
move |value| operate(value, head),
|
||||||
working_set.permanent().signals(),
|
working_set.permanent().signals(),
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use crate::math::utils::ensure_bounded;
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -17,7 +16,6 @@ impl Command for MathFloor {
|
|||||||
Type::List(Box::new(Type::Number)),
|
Type::List(Box::new(Type::Number)),
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
),
|
),
|
||||||
(Type::Range, Type::List(Box::new(Type::Number))),
|
|
||||||
])
|
])
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
@ -47,16 +45,6 @@ impl Command for MathFloor {
|
|||||||
if matches!(input, PipelineData::Empty) {
|
if matches!(input, PipelineData::Empty) {
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
}
|
}
|
||||||
if let PipelineData::Value(
|
|
||||||
Value::Range {
|
|
||||||
ref val,
|
|
||||||
internal_span,
|
|
||||||
},
|
|
||||||
..,
|
|
||||||
) = input
|
|
||||||
{
|
|
||||||
ensure_bounded(val.as_ref(), internal_span, head)?;
|
|
||||||
}
|
|
||||||
input.map(move |value| operate(value, head), engine_state.signals())
|
input.map(move |value| operate(value, head), engine_state.signals())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,16 +59,6 @@ impl Command for MathFloor {
|
|||||||
if matches!(input, PipelineData::Empty) {
|
if matches!(input, PipelineData::Empty) {
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
}
|
}
|
||||||
if let PipelineData::Value(
|
|
||||||
Value::Range {
|
|
||||||
ref val,
|
|
||||||
internal_span,
|
|
||||||
},
|
|
||||||
..,
|
|
||||||
) = input
|
|
||||||
{
|
|
||||||
ensure_bounded(val.as_ref(), internal_span, head)?;
|
|
||||||
}
|
|
||||||
input.map(
|
input.map(
|
||||||
move |value| operate(value, head),
|
move |value| operate(value, head),
|
||||||
working_set.permanent().signals(),
|
working_set.permanent().signals(),
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use crate::math::utils::ensure_bounded;
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::Signals;
|
use nu_protocol::Signals;
|
||||||
|
|
||||||
@ -23,7 +22,6 @@ impl Command for MathLog {
|
|||||||
Type::List(Box::new(Type::Number)),
|
Type::List(Box::new(Type::Number)),
|
||||||
Type::List(Box::new(Type::Float)),
|
Type::List(Box::new(Type::Float)),
|
||||||
),
|
),
|
||||||
(Type::Range, Type::List(Box::new(Type::Number))),
|
|
||||||
])
|
])
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
@ -48,18 +46,7 @@ impl Command for MathLog {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
|
||||||
let base: Spanned<f64> = call.req(engine_state, stack, 0)?;
|
let base: Spanned<f64> = call.req(engine_state, stack, 0)?;
|
||||||
if let PipelineData::Value(
|
|
||||||
Value::Range {
|
|
||||||
ref val,
|
|
||||||
internal_span,
|
|
||||||
},
|
|
||||||
..,
|
|
||||||
) = input
|
|
||||||
{
|
|
||||||
ensure_bounded(val.as_ref(), internal_span, head)?;
|
|
||||||
}
|
|
||||||
log(base, call.head, input, engine_state.signals())
|
log(base, call.head, input, engine_state.signals())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,18 +56,7 @@ impl Command for MathLog {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
|
||||||
let base: Spanned<f64> = call.req_const(working_set, 0)?;
|
let base: Spanned<f64> = call.req_const(working_set, 0)?;
|
||||||
if let PipelineData::Value(
|
|
||||||
Value::Range {
|
|
||||||
ref val,
|
|
||||||
internal_span,
|
|
||||||
},
|
|
||||||
..,
|
|
||||||
) = input
|
|
||||||
{
|
|
||||||
ensure_bounded(val.as_ref(), internal_span, head)?;
|
|
||||||
}
|
|
||||||
log(base, call.head, input, working_set.permanent().signals())
|
log(base, call.head, input, working_set.permanent().signals())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ impl Command for MathMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mode(values: &[Value], span: Span, head: Span) -> Result<Value, ShellError> {
|
pub fn mode(values: &[Value], _span: Span, head: Span) -> Result<Value, ShellError> {
|
||||||
//In e-q, Value doesn't implement Hash or Eq, so we have to get the values inside
|
//In e-q, Value doesn't implement Hash or Eq, so we have to get the values inside
|
||||||
// But f64 doesn't implement Hash, so we get the binary representation to use as
|
// But f64 doesn't implement Hash, so we get the binary representation to use as
|
||||||
// key in the HashMap
|
// key in the HashMap
|
||||||
@ -130,11 +130,11 @@ pub fn mode(values: &[Value], span: Span, head: Span) -> Result<Value, ShellErro
|
|||||||
NumberTypes::Filesize,
|
NumberTypes::Filesize,
|
||||||
)),
|
)),
|
||||||
Value::Error { error, .. } => Err(*error.clone()),
|
Value::Error { error, .. } => Err(*error.clone()),
|
||||||
_ => Err(ShellError::UnsupportedInput {
|
other => Err(ShellError::UnsupportedInput {
|
||||||
msg: "Unable to give a result with this input".to_string(),
|
msg: "Unable to give a result with this input".to_string(),
|
||||||
input: "value originates from here".into(),
|
input: "value originates from here".into(),
|
||||||
msg_span: head,
|
msg_span: head,
|
||||||
input_span: span,
|
input_span: other.span(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<HashableType>, ShellError>>()?;
|
.collect::<Result<Vec<HashableType>, ShellError>>()?;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use crate::math::utils::ensure_bounded;
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -17,7 +16,6 @@ impl Command for MathRound {
|
|||||||
Type::List(Box::new(Type::Number)),
|
Type::List(Box::new(Type::Number)),
|
||||||
Type::List(Box::new(Type::Number)),
|
Type::List(Box::new(Type::Number)),
|
||||||
),
|
),
|
||||||
(Type::Range, Type::List(Box::new(Type::Number))),
|
|
||||||
])
|
])
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.named(
|
.named(
|
||||||
@ -54,16 +52,6 @@ impl Command for MathRound {
|
|||||||
if matches!(input, PipelineData::Empty) {
|
if matches!(input, PipelineData::Empty) {
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
}
|
}
|
||||||
if let PipelineData::Value(
|
|
||||||
Value::Range {
|
|
||||||
ref val,
|
|
||||||
internal_span,
|
|
||||||
},
|
|
||||||
..,
|
|
||||||
) = input
|
|
||||||
{
|
|
||||||
ensure_bounded(val.as_ref(), internal_span, head)?;
|
|
||||||
}
|
|
||||||
input.map(
|
input.map(
|
||||||
move |value| operate(value, head, precision_param),
|
move |value| operate(value, head, precision_param),
|
||||||
engine_state.signals(),
|
engine_state.signals(),
|
||||||
@ -82,16 +70,6 @@ impl Command for MathRound {
|
|||||||
if matches!(input, PipelineData::Empty) {
|
if matches!(input, PipelineData::Empty) {
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
}
|
}
|
||||||
if let PipelineData::Value(
|
|
||||||
Value::Range {
|
|
||||||
ref val,
|
|
||||||
internal_span,
|
|
||||||
},
|
|
||||||
..,
|
|
||||||
) = input
|
|
||||||
{
|
|
||||||
ensure_bounded(val.as_ref(), internal_span, head)?;
|
|
||||||
}
|
|
||||||
input.map(
|
input.map(
|
||||||
move |value| operate(value, head, precision_param),
|
move |value| operate(value, head, precision_param),
|
||||||
working_set.permanent().signals(),
|
working_set.permanent().signals(),
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use crate::math::utils::ensure_bounded;
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -17,7 +16,6 @@ impl Command for MathSqrt {
|
|||||||
Type::List(Box::new(Type::Number)),
|
Type::List(Box::new(Type::Number)),
|
||||||
Type::List(Box::new(Type::Float)),
|
Type::List(Box::new(Type::Float)),
|
||||||
),
|
),
|
||||||
(Type::Range, Type::List(Box::new(Type::Number))),
|
|
||||||
])
|
])
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
@ -47,16 +45,6 @@ impl Command for MathSqrt {
|
|||||||
if matches!(input, PipelineData::Empty) {
|
if matches!(input, PipelineData::Empty) {
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
}
|
}
|
||||||
if let PipelineData::Value(
|
|
||||||
Value::Range {
|
|
||||||
ref val,
|
|
||||||
internal_span,
|
|
||||||
},
|
|
||||||
..,
|
|
||||||
) = input
|
|
||||||
{
|
|
||||||
ensure_bounded(val.as_ref(), internal_span, head)?;
|
|
||||||
}
|
|
||||||
input.map(move |value| operate(value, head), engine_state.signals())
|
input.map(move |value| operate(value, head), engine_state.signals())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,16 +59,6 @@ impl Command for MathSqrt {
|
|||||||
if matches!(input, PipelineData::Empty) {
|
if matches!(input, PipelineData::Empty) {
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
}
|
}
|
||||||
if let PipelineData::Value(
|
|
||||||
Value::Range {
|
|
||||||
ref val,
|
|
||||||
internal_span,
|
|
||||||
},
|
|
||||||
..,
|
|
||||||
) = input
|
|
||||||
{
|
|
||||||
ensure_bounded(val.as_ref(), internal_span, head)?;
|
|
||||||
}
|
|
||||||
input.map(
|
input.map(
|
||||||
move |value| operate(value, head),
|
move |value| operate(value, head),
|
||||||
working_set.permanent().signals(),
|
working_set.permanent().signals(),
|
||||||
|
@ -14,7 +14,6 @@ impl Command for MathStddev {
|
|||||||
Signature::build("math stddev")
|
Signature::build("math stddev")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::List(Box::new(Type::Number)), Type::Number),
|
(Type::List(Box::new(Type::Number)), Type::Number),
|
||||||
(Type::Range, Type::Number),
|
|
||||||
(Type::table(), Type::record()),
|
(Type::table(), Type::record()),
|
||||||
(Type::record(), Type::record()),
|
(Type::record(), Type::record()),
|
||||||
])
|
])
|
||||||
@ -54,18 +53,6 @@ impl Command for MathStddev {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let sample = call.has_flag(engine_state, stack, "sample")?;
|
let sample = call.has_flag(engine_state, stack, "sample")?;
|
||||||
let name = call.head;
|
|
||||||
let span = input.span().unwrap_or(name);
|
|
||||||
let input: PipelineData = match input.try_expand_range() {
|
|
||||||
Err(_) => {
|
|
||||||
return Err(ShellError::IncorrectValue {
|
|
||||||
msg: "Range must be bounded".to_string(),
|
|
||||||
val_span: span,
|
|
||||||
call_span: name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(val) => val,
|
|
||||||
};
|
|
||||||
run_with_function(call, input, compute_stddev(sample))
|
run_with_function(call, input, compute_stddev(sample))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,18 +63,6 @@ impl Command for MathStddev {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let sample = call.has_flag_const(working_set, "sample")?;
|
let sample = call.has_flag_const(working_set, "sample")?;
|
||||||
let name = call.head;
|
|
||||||
let span = input.span().unwrap_or(name);
|
|
||||||
let input: PipelineData = match input.try_expand_range() {
|
|
||||||
Err(_) => {
|
|
||||||
return Err(ShellError::IncorrectValue {
|
|
||||||
msg: "Range must be bounded".to_string(),
|
|
||||||
val_span: span,
|
|
||||||
call_span: name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(val) => val,
|
|
||||||
};
|
|
||||||
run_with_function(call, input, compute_stddev(sample))
|
run_with_function(call, input, compute_stddev(sample))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use core::slice;
|
use core::{ops::Bound, slice};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::Call, IntoPipelineData, PipelineData, Range, ShellError, Signals, Span, Value,
|
engine::Call, IntoPipelineData, PipelineData, Range, ShellError, Signals, Span, Value,
|
||||||
@ -93,7 +93,10 @@ pub fn calculate(
|
|||||||
Ok(Value::record(record, span))
|
Ok(Value::record(record, span))
|
||||||
}
|
}
|
||||||
PipelineData::Value(Value::Range { val, .. }, ..) => {
|
PipelineData::Value(Value::Range { val, .. }, ..) => {
|
||||||
ensure_bounded(val.as_ref(), span, name)?;
|
match *val {
|
||||||
|
Range::IntRange(range) => ensure_bounded(range.end(), span, name)?,
|
||||||
|
Range::FloatRange(range) => ensure_bounded(range.end(), span, name)?,
|
||||||
|
}
|
||||||
let new_vals: Result<Vec<Value>, ShellError> = val
|
let new_vals: Result<Vec<Value>, ShellError> = val
|
||||||
.into_range_iter(span, Signals::empty())
|
.into_range_iter(span, Signals::empty())
|
||||||
.map(|val| mf(&[val], span, name))
|
.map(|val| mf(&[val], span, name))
|
||||||
@ -102,7 +105,7 @@ pub fn calculate(
|
|||||||
mf(&new_vals?, span, name)
|
mf(&new_vals?, span, name)
|
||||||
}
|
}
|
||||||
PipelineData::Value(val, ..) => mf(&[val], span, name),
|
PipelineData::Value(val, ..) => mf(&[val], span, name),
|
||||||
PipelineData::Empty => Err(ShellError::PipelineEmpty { dst_span: name }),
|
PipelineData::Empty { .. } => Err(ShellError::PipelineEmpty { dst_span: name }),
|
||||||
val => Err(ShellError::UnsupportedInput {
|
val => Err(ShellError::UnsupportedInput {
|
||||||
msg: "Only ints, floats, lists, records, or ranges are supported".into(),
|
msg: "Only ints, floats, lists, records, or ranges are supported".into(),
|
||||||
input: "value originates from here".into(),
|
input: "value originates from here".into(),
|
||||||
@ -114,13 +117,17 @@ pub fn calculate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_bounded(range: &Range, val_span: Span, call_span: Span) -> Result<(), ShellError> {
|
pub fn ensure_bounded<T>(
|
||||||
if range.is_bounded() {
|
bound: Bound<T>,
|
||||||
return Ok(());
|
val_span: Span,
|
||||||
}
|
call_span: Span,
|
||||||
Err(ShellError::IncorrectValue {
|
) -> Result<(), ShellError> {
|
||||||
|
match bound {
|
||||||
|
Bound::<T>::Unbounded => Err(ShellError::IncorrectValue {
|
||||||
msg: "Range must be bounded".to_string(),
|
msg: "Range must be bounded".to_string(),
|
||||||
val_span,
|
val_span,
|
||||||
call_span,
|
call_span,
|
||||||
})
|
}),
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@ impl Command for MathVariance {
|
|||||||
Signature::build("math variance")
|
Signature::build("math variance")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::List(Box::new(Type::Number)), Type::Number),
|
(Type::List(Box::new(Type::Number)), Type::Number),
|
||||||
(Type::Range, Type::Number),
|
|
||||||
(Type::table(), Type::record()),
|
(Type::table(), Type::record()),
|
||||||
(Type::record(), Type::record()),
|
(Type::record(), Type::record()),
|
||||||
])
|
])
|
||||||
@ -46,18 +45,6 @@ impl Command for MathVariance {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let sample = call.has_flag(engine_state, stack, "sample")?;
|
let sample = call.has_flag(engine_state, stack, "sample")?;
|
||||||
let name = call.head;
|
|
||||||
let span = input.span().unwrap_or(name);
|
|
||||||
let input: PipelineData = match input.try_expand_range() {
|
|
||||||
Err(_) => {
|
|
||||||
return Err(ShellError::IncorrectValue {
|
|
||||||
msg: "Range must be bounded".to_string(),
|
|
||||||
val_span: span,
|
|
||||||
call_span: name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(val) => val,
|
|
||||||
};
|
|
||||||
run_with_function(call, input, compute_variance(sample))
|
run_with_function(call, input, compute_variance(sample))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,18 +55,6 @@ impl Command for MathVariance {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let sample = call.has_flag_const(working_set, "sample")?;
|
let sample = call.has_flag_const(working_set, "sample")?;
|
||||||
let name = call.head;
|
|
||||||
let span = input.span().unwrap_or(name);
|
|
||||||
let input: PipelineData = match input.try_expand_range() {
|
|
||||||
Err(_) => {
|
|
||||||
return Err(ShellError::IncorrectValue {
|
|
||||||
msg: "Range must be bounded".to_string(),
|
|
||||||
val_span: span,
|
|
||||||
call_span: name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(val) => val,
|
|
||||||
};
|
|
||||||
run_with_function(call, input, compute_variance(sample))
|
run_with_function(call, input, compute_variance(sample))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -723,7 +723,7 @@ fn transform_response_using_content_type(
|
|||||||
)
|
)
|
||||||
})?
|
})?
|
||||||
.path_segments()
|
.path_segments()
|
||||||
.and_then(|mut segments| segments.next_back())
|
.and_then(|segments| segments.last())
|
||||||
.and_then(|name| if name.is_empty() { None } else { Some(name) })
|
.and_then(|name| if name.is_empty() { None } else { Some(name) })
|
||||||
.and_then(|name| {
|
.and_then(|name| {
|
||||||
PathBuf::from(name)
|
PathBuf::from(name)
|
||||||
|
@ -117,13 +117,8 @@ impl Command for HttpDelete {
|
|||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "http delete from example.com, with custom header using a record",
|
description: "http delete from example.com, with custom header",
|
||||||
example: "http delete --headers {my-header-key: my-header-value} https://www.example.com",
|
example: "http delete --headers [my-header-key my-header-value] https://www.example.com",
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "http delete from example.com, with custom header using a list",
|
|
||||||
example: "http delete --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
|
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
|
@ -115,12 +115,12 @@ impl Command for HttpGet {
|
|||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Get content from example.com, with custom header using a record",
|
description: "Get content from example.com, with custom header",
|
||||||
example: "http get --headers {my-header-key: my-header-value} https://www.example.com",
|
example: "http get --headers [my-header-key my-header-value] https://www.example.com",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Get content from example.com, with custom headers using a list",
|
description: "Get content from example.com, with custom headers",
|
||||||
example: "http get --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
|
example: "http get --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user