Merge branch 'main'
This commit is contained in:
commit
748b3c39af
2
.github/workflows/audit.yml
vendored
2
.github/workflows/audit.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
|||
# Prevent sudden announcement of a new advisory from failing ci:
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.5
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: rustsec/audit-check@v1.4.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
@ -33,7 +33,7 @@ jobs:
|
|||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.5
|
||||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
|
@ -66,7 +66,7 @@ jobs:
|
|||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.5
|
||||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
|
@ -95,7 +95,7 @@ jobs:
|
|||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.5
|
||||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
|
@ -146,7 +146,7 @@ jobs:
|
|||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.5
|
||||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
|
|
6
.github/workflows/nightly-build.yml
vendored
6
.github/workflows/nightly-build.yml
vendored
|
@ -27,7 +27,7 @@ jobs:
|
|||
# if: github.repository == 'nushell/nightly'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.5
|
||||
uses: actions/checkout@v4.1.6
|
||||
if: github.repository == 'nushell/nightly'
|
||||
with:
|
||||
ref: main
|
||||
|
@ -112,7 +112,7 @@ jobs:
|
|||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.5
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
|
@ -181,7 +181,7 @@ jobs:
|
|||
- name: Waiting for Release
|
||||
run: sleep 1800
|
||||
|
||||
- uses: actions/checkout@v4.1.5
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
ref: main
|
||||
|
||||
|
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -62,7 +62,7 @@ jobs:
|
|||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.5
|
||||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
- name: Update Rust Toolchain Target
|
||||
run: |
|
||||
|
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
|
@ -7,7 +7,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v4.1.5
|
||||
uses: actions/checkout@v4.1.6
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@v1.21.0
|
||||
|
|
83
Cargo.lock
generated
83
Cargo.lock
generated
|
@ -2779,7 +2779,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"crossterm",
|
||||
|
@ -2832,7 +2832,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-cli"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossterm",
|
||||
|
@ -2867,7 +2867,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-cmd-base"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"miette",
|
||||
|
@ -2879,7 +2879,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-cmd-extra"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"fancy-regex",
|
||||
"heck 0.5.0",
|
||||
|
@ -2904,7 +2904,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"itertools 0.12.1",
|
||||
"nu-engine",
|
||||
|
@ -2916,7 +2916,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-cmd-plugin"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"itertools 0.12.1",
|
||||
"nu-engine",
|
||||
|
@ -2927,7 +2927,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-color-config"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-engine",
|
||||
|
@ -2939,7 +2939,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-command"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"alphanumeric-sort",
|
||||
"base64 0.22.1",
|
||||
|
@ -3021,6 +3021,7 @@ dependencies = [
|
|||
"sha2",
|
||||
"sysinfo",
|
||||
"tabled",
|
||||
"tempfile",
|
||||
"terminal_size",
|
||||
"titlecase",
|
||||
"toml 0.8.12",
|
||||
|
@ -3058,7 +3059,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-engine"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"nu-glob",
|
||||
"nu-path",
|
||||
|
@ -3068,7 +3069,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-explore"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"ansi-str",
|
||||
"anyhow",
|
||||
|
@ -3093,14 +3094,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-glob"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-json"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
"num-traits",
|
||||
|
@ -3110,7 +3111,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-lsp"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"assert-json-diff",
|
||||
"crossbeam-channel",
|
||||
|
@ -3131,7 +3132,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-parser"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"bytesize",
|
||||
"chrono",
|
||||
|
@ -3147,7 +3148,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-path"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"dirs-next",
|
||||
"omnipath",
|
||||
|
@ -3156,7 +3157,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-plugin"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nix",
|
||||
|
@ -3171,7 +3172,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-plugin-core"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"interprocess",
|
||||
"log",
|
||||
|
@ -3185,7 +3186,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-plugin-engine"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nu-engine",
|
||||
|
@ -3200,7 +3201,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-plugin-protocol"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"nu-protocol",
|
||||
|
@ -3212,7 +3213,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-plugin-test-support"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-cmd-lang",
|
||||
|
@ -3230,7 +3231,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-pretty-hex"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"heapless",
|
||||
"nu-ansi-term",
|
||||
|
@ -3239,7 +3240,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-protocol"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"brotli 5.0.0",
|
||||
"byte-unit",
|
||||
|
@ -3271,7 +3272,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-std"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"miette",
|
||||
|
@ -3282,9 +3283,10 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-system"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"itertools 0.12.1",
|
||||
"libc",
|
||||
"libproc",
|
||||
"log",
|
||||
|
@ -3299,7 +3301,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-table"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"fancy-regex",
|
||||
"nu-ansi-term",
|
||||
|
@ -3313,7 +3315,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-term-grid"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"nu-utils",
|
||||
"unicode-width",
|
||||
|
@ -3321,7 +3323,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-test-support"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"nu-glob",
|
||||
"nu-path",
|
||||
|
@ -3333,7 +3335,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-utils"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"crossterm_winapi",
|
||||
"log",
|
||||
|
@ -3359,7 +3361,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_example"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"nu-cmd-lang",
|
||||
"nu-plugin",
|
||||
|
@ -3369,7 +3371,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_formats"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"eml-parser",
|
||||
"ical",
|
||||
|
@ -3382,7 +3384,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_gstat"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"git2",
|
||||
"nu-plugin",
|
||||
|
@ -3391,7 +3393,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_inc"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"nu-plugin",
|
||||
"nu-protocol",
|
||||
|
@ -3400,12 +3402,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_polars"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz 0.9.0",
|
||||
"fancy-regex",
|
||||
"indexmap",
|
||||
"mimalloc",
|
||||
"nu-cmd-lang",
|
||||
"nu-command",
|
||||
"nu-engine",
|
||||
|
@ -3418,7 +3421,6 @@ dependencies = [
|
|||
"polars",
|
||||
"polars-arrow",
|
||||
"polars-io",
|
||||
"polars-lazy",
|
||||
"polars-ops",
|
||||
"polars-plan",
|
||||
"polars-utils",
|
||||
|
@ -3431,7 +3433,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_query"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"gjson",
|
||||
"nu-plugin",
|
||||
|
@ -3443,7 +3445,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_stress_internals"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"interprocess",
|
||||
"serde",
|
||||
|
@ -3569,7 +3571,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
|||
|
||||
[[package]]
|
||||
name = "nuon"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fancy-regex",
|
||||
|
@ -4840,7 +4842,8 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "reedline"
|
||||
version = "0.32.0"
|
||||
source = "git+https://github.com/nushell/reedline?branch=main#a580ea56d4e5a889468b2969d2a1534379504ab6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abf59e4c97b5049ba96b052cdb652368305a2eddcbce9bf1c16f9d003139eeea"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"chrono",
|
||||
|
@ -5423,9 +5426,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shadow-rs"
|
||||
version = "0.27.1"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7960cbd6ba74691bb15e7ebf97f7136bd02d1115f5695a58c1f31d5645750128"
|
||||
checksum = "1d75516bdaee8f640543ad1f6e292448c23ce57143f812c3736ab4b0874383df"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"is_debug",
|
||||
|
|
44
Cargo.toml
44
Cargo.toml
|
@ -11,7 +11,7 @@ license = "MIT"
|
|||
name = "nu"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.77.2"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
@ -180,22 +180,22 @@ windows = "0.54"
|
|||
winreg = "0.52"
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.93.1" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.93.1" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.93.1" }
|
||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.93.1", optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.93.1" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.93.1" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.93.1" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.93.1" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.93.1" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.93.1" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.93.1" }
|
||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.93.1" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.93.1" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.93.1" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.93.1" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.93.1" }
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.94.1" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.94.1" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.94.1" }
|
||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.94.1", optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.94.1" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.94.1" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.94.1" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.94.1" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.94.1" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.94.1" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.94.1" }
|
||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.94.1" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.94.1" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.94.1" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.94.1" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.94.1" }
|
||||
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
|
@ -224,9 +224,9 @@ nix = { workspace = true, default-features = false, features = [
|
|||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.93.1" }
|
||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.93.1" }
|
||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.93.1" }
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.94.1" }
|
||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.94.1" }
|
||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.94.1" }
|
||||
assert_cmd = "2.0"
|
||||
dirs-next = { workspace = true }
|
||||
tango-bench = "0.5"
|
||||
|
@ -304,11 +304,11 @@ bench = false
|
|||
# To use a development version of a dependency please use a global override here
|
||||
# changing versions in each sub-crate of the workspace is tedious
|
||||
[patch.crates-io]
|
||||
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||
# reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
||||
|
||||
# Run all benchmarks with `cargo bench`
|
||||
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
harness = false
|
|
@ -5,27 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.93.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.93.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.93.1" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.94.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.1" }
|
||||
rstest = { workspace = true, default-features = false }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.93.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.93.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.93.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.93.1" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.93.1", optional = true }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.93.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.93.1" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.1" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.1", optional = true }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.94.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
|
@ -46,4 +46,4 @@ which = { workspace = true }
|
|||
|
||||
[features]
|
||||
plugin = ["nu-plugin-engine"]
|
||||
system-clipboard = ["reedline/system_clipboard"]
|
||||
system-clipboard = ["reedline/system_clipboard"]
|
|
@ -1110,9 +1110,9 @@ fn run_shell_integration_osc9_9(engine_state: &EngineState, stack: &mut Stack, u
|
|||
let start_time = Instant::now();
|
||||
|
||||
// Otherwise, communicate the path as OSC 9;9 from ConEmu (often used for spawning new tabs in the same dir)
|
||||
// This is helpful in Windows Terminal with Duplicate Tab
|
||||
run_ansi_sequence(&format!(
|
||||
"\x1b]9;9;{}{}\x1b\\",
|
||||
if path.starts_with('/') { "" } else { "/" },
|
||||
"\x1b]9;9;{}\x1b\\",
|
||||
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
|
||||
));
|
||||
|
||||
|
|
|
@ -292,6 +292,8 @@ fn partial_completions() {
|
|||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![
|
||||
file(dir.join("partial_a").join("have_ext.exe")),
|
||||
file(dir.join("partial_a").join("have_ext.txt")),
|
||||
file(dir.join("partial_a").join("hello")),
|
||||
file(dir.join("partial_a").join("hola")),
|
||||
file(dir.join("partial_b").join("hello_b")),
|
||||
|
@ -310,6 +312,8 @@ fn partial_completions() {
|
|||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![
|
||||
file(dir.join("partial_a").join("anotherfile")),
|
||||
file(dir.join("partial_a").join("have_ext.exe")),
|
||||
file(dir.join("partial_a").join("have_ext.txt")),
|
||||
file(dir.join("partial_a").join("hello")),
|
||||
file(dir.join("partial_a").join("hola")),
|
||||
file(dir.join("partial_b").join("hello_b")),
|
||||
|
@ -360,6 +364,34 @@ fn partial_completions() {
|
|||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
|
||||
// Test completion for all files under directories whose names begin with "pa"
|
||||
let file_str = file(dir.join("partial_a").join("have"));
|
||||
let target_file = format!("rm {file_str}");
|
||||
let suggestions = completer.complete(&target_file, target_file.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![
|
||||
file(dir.join("partial_a").join("have_ext.exe")),
|
||||
file(dir.join("partial_a").join("have_ext.txt")),
|
||||
];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
|
||||
// Test completion for all files under directories whose names begin with "pa"
|
||||
let file_str = file(dir.join("partial_a").join("have_ext."));
|
||||
let file_dir = format!("rm {file_str}");
|
||||
let suggestions = completer.complete(&file_dir, file_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![
|
||||
file(dir.join("partial_a").join("have_ext.exe")),
|
||||
file(dir.join("partial_a").join("have_ext.txt")),
|
||||
];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -394,6 +426,13 @@ fn command_ls_with_filecompletion() {
|
|||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
|
||||
let target_dir = "ls custom_completion.";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
let expected_paths: Vec<String> = vec!["custom_completion.nu".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
#[test]
|
||||
|
@ -428,6 +467,13 @@ fn command_open_with_filecompletion() {
|
|||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
|
||||
let target_dir = "open custom_completion.";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
let expected_paths: Vec<String> = vec!["custom_completion.nu".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,17 +5,17 @@ edition = "2021"
|
|||
license = "MIT"
|
||||
name = "nu-cmd-base"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.93.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.93.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.93.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1" }
|
||||
|
||||
indexmap = { workspace = true }
|
||||
miette = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
[dev-dependencies]
|
|
@ -25,8 +25,8 @@ pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> {
|
|||
Range::IntRange(range) => {
|
||||
let start = range.start().try_into().unwrap_or(0);
|
||||
let end = match range.end() {
|
||||
Bound::Included(v) => v as isize,
|
||||
Bound::Excluded(v) => (v - 1) as isize,
|
||||
Bound::Included(v) => (v + 1) as isize,
|
||||
Bound::Excluded(v) => v as isize,
|
||||
Bound::Unbounded => isize::MAX,
|
||||
};
|
||||
Ok((start, end))
|
||||
|
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||
license = "MIT"
|
||||
name = "nu-cmd-extra"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
@ -13,13 +13,13 @@ version = "0.93.1"
|
|||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.93.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.93.1" }
|
||||
nu-json = { version = "0.93.1", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.93.1" }
|
||||
nu-pretty-hex = { version = "0.93.1", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.93.1" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.1" }
|
||||
nu-json = { version = "0.94.1", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.1" }
|
||||
nu-pretty-hex = { version = "0.94.1", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.1" }
|
||||
|
||||
# Potential dependencies for extras
|
||||
heck = { workspace = true }
|
||||
|
@ -37,6 +37,6 @@ extra = ["default"]
|
|||
default = []
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.93.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.93.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.93.1" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.94.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.1" }
|
|
@ -6,22 +6,22 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.93.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.93.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.93.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.1" }
|
||||
|
||||
itertools = { workspace = true }
|
||||
shadow-rs = { version = "0.27", default-features = false }
|
||||
shadow-rs = { version = "0.28", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { version = "0.27", default-features = false }
|
||||
shadow-rs = { version = "0.28", default-features = false }
|
||||
|
||||
[features]
|
||||
mimalloc = []
|
||||
|
@ -29,4 +29,4 @@ which-support = []
|
|||
trash-support = []
|
||||
sqlite = []
|
||||
static-link-openssl = []
|
||||
system-clipboard = []
|
||||
system-clipboard = []
|
|
@ -298,9 +298,7 @@ fn bind_args_to(
|
|||
if let Some(rest_positional) = &signature.rest_positional {
|
||||
let mut rest_items = vec![];
|
||||
|
||||
for result in
|
||||
val_iter.skip(signature.required_positional.len() + signature.optional_positional.len())
|
||||
{
|
||||
for result in val_iter {
|
||||
rest_items.push(result);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,16 +5,16 @@ edition = "2021"
|
|||
license = "MIT"
|
||||
name = "nu-cmd-plugin"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.93.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.93.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1", features = ["plugin"] }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.93.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1", features = ["plugin"] }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.1" }
|
||||
|
||||
itertools = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
[dev-dependencies]
|
|
@ -5,18 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-color-config"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.93.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.93.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.94.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.93.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.1" }
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||
license = "MIT"
|
||||
name = "nu-command"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
@ -13,21 +13,21 @@ version = "0.93.1"
|
|||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.93.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.93.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.93.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.93.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.93.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.93.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.93.1" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.93.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1" }
|
||||
nu-system = { path = "../nu-system", version = "0.93.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.93.1" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.93.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.93.1" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.94.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.94.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.94.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.1" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.94.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1" }
|
||||
nu-system = { path = "../nu-system", version = "0.94.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.94.1" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.94.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
nuon = { path = "../nuon", version = "0.93.1" }
|
||||
nuon = { path = "../nuon", version = "0.94.1" }
|
||||
|
||||
alphanumeric-sort = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
|
@ -99,7 +99,7 @@ uu_whoami = { workspace = true }
|
|||
uuid = { workspace = true, features = ["v4"] }
|
||||
v_htmlescape = { workspace = true }
|
||||
wax = { workspace = true }
|
||||
which = { workspace = true, optional = true }
|
||||
which = { workspace = true }
|
||||
unicode-width = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
|
@ -134,11 +134,11 @@ workspace = true
|
|||
plugin = ["nu-parser/plugin"]
|
||||
sqlite = ["rusqlite"]
|
||||
trash-support = ["trash"]
|
||||
which-support = ["which"]
|
||||
which-support = []
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.93.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.93.1" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.1" }
|
||||
|
||||
dirs-next = { workspace = true }
|
||||
mockito = { workspace = true, default-features = false }
|
||||
|
@ -146,3 +146,4 @@ quickcheck = { workspace = true }
|
|||
quickcheck_macros = { workspace = true }
|
||||
rstest = { workspace = true, default-features = false }
|
||||
pretty_assertions = { workspace = true }
|
||||
tempfile = { workspace = true }
|
|
@ -160,7 +160,7 @@ fn string_helper(
|
|||
// Just set the type - that should be good enough. There is no guarantee that the data
|
||||
// within a string stream is actually valid UTF-8. But refuse to do it if it was already set
|
||||
// to binary
|
||||
if stream.type_() != ByteStreamType::Binary {
|
||||
if stream.type_().is_string_coercible() {
|
||||
Ok(PipelineData::ByteStream(
|
||||
stream.with_type(ByteStreamType::String),
|
||||
metadata,
|
||||
|
|
|
@ -28,6 +28,7 @@ impl Command for DebugProfile {
|
|||
Some('v'),
|
||||
)
|
||||
.switch("expr", "Collect expression types", Some('x'))
|
||||
.switch("lines", "Collect line numbers", Some('l'))
|
||||
.named(
|
||||
"max-depth",
|
||||
SyntaxShape::Int,
|
||||
|
@ -90,6 +91,7 @@ confusing the id/parent_id hierarchy. The --expr flag is helpful for investigati
|
|||
let collect_expanded_source = call.has_flag(engine_state, stack, "expanded-source")?;
|
||||
let collect_values = call.has_flag(engine_state, stack, "values")?;
|
||||
let collect_exprs = call.has_flag(engine_state, stack, "expr")?;
|
||||
let collect_lines = call.has_flag(engine_state, stack, "lines")?;
|
||||
let max_depth = call
|
||||
.get_flag(engine_state, stack, "max-depth")?
|
||||
.unwrap_or(2);
|
||||
|
@ -101,6 +103,7 @@ confusing the id/parent_id hierarchy. The --expr flag is helpful for investigati
|
|||
collect_expanded_source,
|
||||
collect_values,
|
||||
collect_exprs,
|
||||
collect_lines,
|
||||
call.span(),
|
||||
);
|
||||
|
||||
|
@ -118,14 +121,11 @@ confusing the id/parent_id hierarchy. The --expr flag is helpful for investigati
|
|||
|
||||
let result = ClosureEvalOnce::new(engine_state, stack, closure).run_with_input(input);
|
||||
|
||||
// TODO: See eval_source()
|
||||
match result {
|
||||
Ok(pipeline_data) => {
|
||||
let _ = pipeline_data.into_value(call.span());
|
||||
// pipeline_data.print(engine_state, caller_stack, true, false)
|
||||
}
|
||||
Err(_e) => (), // TODO: Report error
|
||||
}
|
||||
// Return potential errors
|
||||
let pipeline_data = result?;
|
||||
|
||||
// Collect the output
|
||||
let _ = pipeline_data.into_value(call.span());
|
||||
|
||||
Ok(engine_state
|
||||
.deactivate_debugger()
|
||||
|
|
|
@ -164,6 +164,9 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||
#[cfg(any(
|
||||
target_os = "android",
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "macos",
|
||||
target_os = "windows"
|
||||
))]
|
||||
|
|
75
crates/nu-command/src/env/config/config_env.rs
vendored
75
crates/nu-command/src/env/config/config_env.rs
vendored
|
@ -1,6 +1,7 @@
|
|||
use super::utils::gen_command;
|
||||
use nu_cmd_base::util::get_editor;
|
||||
use nu_engine::{command_prelude::*, env_to_strings};
|
||||
use nu_protocol::{process::ChildProcess, ByteStream};
|
||||
use nu_system::ForegroundChild;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConfigEnv;
|
||||
|
@ -47,7 +48,7 @@ impl Command for ConfigEnv {
|
|||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
// `--default` flag handling
|
||||
if call.has_flag(engine_state, stack, "default")? {
|
||||
|
@ -55,27 +56,59 @@ impl Command for ConfigEnv {
|
|||
return Ok(Value::string(nu_utils::get_default_env(), head).into_pipeline_data());
|
||||
}
|
||||
|
||||
let env_vars_str = env_to_strings(engine_state, stack)?;
|
||||
let nu_config = match engine_state.get_config_path("env-path") {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Could not find $nu.env-path".into(),
|
||||
msg: "Could not find $nu.env-path".into(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
// Find the editor executable.
|
||||
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 cwd = engine_state.cwd(Some(stack))?;
|
||||
let editor_executable =
|
||||
crate::which(&editor_name, &paths, &cwd).ok_or(ShellError::ExternalCommand {
|
||||
label: format!("`{editor_name}` not found"),
|
||||
help: "Failed to find the editor executable".into(),
|
||||
span: call.head,
|
||||
})?;
|
||||
|
||||
let Some(env_path) = engine_state.get_config_path("env-path") else {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Could not find $nu.env-path".into(),
|
||||
msg: "Could not find $nu.env-path".into(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
};
|
||||
let env_path = env_path.to_string_lossy().to_string();
|
||||
|
||||
let (item, config_args) = get_editor(engine_state, stack, call.head)?;
|
||||
// Create the command.
|
||||
let mut command = std::process::Command::new(editor_executable);
|
||||
|
||||
gen_command(call.head, nu_config, item, config_args, env_vars_str).run_with_input(
|
||||
engine_state,
|
||||
stack,
|
||||
input,
|
||||
true,
|
||||
)
|
||||
// Configure PWD.
|
||||
command.current_dir(cwd);
|
||||
|
||||
// Configure environment variables.
|
||||
let envs = env_to_strings(engine_state, stack)?;
|
||||
command.env_clear();
|
||||
command.envs(envs);
|
||||
|
||||
// Configure args.
|
||||
command.arg(env_path);
|
||||
command.args(editor_args);
|
||||
|
||||
// Spawn the child process. On Unix, also put the child process to
|
||||
// foreground if we're in an interactive session.
|
||||
#[cfg(windows)]
|
||||
let child = ForegroundChild::spawn(command)?;
|
||||
#[cfg(unix)]
|
||||
let child = ForegroundChild::spawn(
|
||||
command,
|
||||
engine_state.is_interactive,
|
||||
&engine_state.pipeline_externals_state,
|
||||
)?;
|
||||
|
||||
// Wrap the output into a `PipelineData::ByteStream`.
|
||||
let child = ChildProcess::new(child, None, false, call.head)?;
|
||||
Ok(PipelineData::ByteStream(
|
||||
ByteStream::child(child, call.head),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
75
crates/nu-command/src/env/config/config_nu.rs
vendored
75
crates/nu-command/src/env/config/config_nu.rs
vendored
|
@ -1,6 +1,7 @@
|
|||
use super::utils::gen_command;
|
||||
use nu_cmd_base::util::get_editor;
|
||||
use nu_engine::{command_prelude::*, env_to_strings};
|
||||
use nu_protocol::{process::ChildProcess, ByteStream};
|
||||
use nu_system::ForegroundChild;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConfigNu;
|
||||
|
@ -51,7 +52,7 @@ impl Command for ConfigNu {
|
|||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
// `--default` flag handling
|
||||
if call.has_flag(engine_state, stack, "default")? {
|
||||
|
@ -59,27 +60,59 @@ impl Command for ConfigNu {
|
|||
return Ok(Value::string(nu_utils::get_default_config(), head).into_pipeline_data());
|
||||
}
|
||||
|
||||
let env_vars_str = env_to_strings(engine_state, stack)?;
|
||||
let nu_config = match engine_state.get_config_path("config-path") {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Could not find $nu.config-path".into(),
|
||||
msg: "Could not find $nu.config-path".into(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
// Find the editor executable.
|
||||
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 cwd = engine_state.cwd(Some(stack))?;
|
||||
let editor_executable =
|
||||
crate::which(&editor_name, &paths, &cwd).ok_or(ShellError::ExternalCommand {
|
||||
label: format!("`{editor_name}` not found"),
|
||||
help: "Failed to find the editor executable".into(),
|
||||
span: call.head,
|
||||
})?;
|
||||
|
||||
let Some(config_path) = engine_state.get_config_path("config-path") else {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Could not find $nu.config-path".into(),
|
||||
msg: "Could not find $nu.config-path".into(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
};
|
||||
let config_path = config_path.to_string_lossy().to_string();
|
||||
|
||||
let (item, config_args) = get_editor(engine_state, stack, call.head)?;
|
||||
// Create the command.
|
||||
let mut command = std::process::Command::new(editor_executable);
|
||||
|
||||
gen_command(call.head, nu_config, item, config_args, env_vars_str).run_with_input(
|
||||
engine_state,
|
||||
stack,
|
||||
input,
|
||||
true,
|
||||
)
|
||||
// Configure PWD.
|
||||
command.current_dir(cwd);
|
||||
|
||||
// Configure environment variables.
|
||||
let envs = env_to_strings(engine_state, stack)?;
|
||||
command.env_clear();
|
||||
command.envs(envs);
|
||||
|
||||
// Configure args.
|
||||
command.arg(config_path);
|
||||
command.args(editor_args);
|
||||
|
||||
// Spawn the child process. On Unix, also put the child process to
|
||||
// foreground if we're in an interactive session.
|
||||
#[cfg(windows)]
|
||||
let child = ForegroundChild::spawn(command)?;
|
||||
#[cfg(unix)]
|
||||
let child = ForegroundChild::spawn(
|
||||
command,
|
||||
engine_state.is_interactive,
|
||||
&engine_state.pipeline_externals_state,
|
||||
)?;
|
||||
|
||||
// Wrap the output into a `PipelineData::ByteStream`.
|
||||
let child = ChildProcess::new(child, None, false, call.head)?;
|
||||
Ok(PipelineData::ByteStream(
|
||||
ByteStream::child(child, call.head),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
1
crates/nu-command/src/env/config/mod.rs
vendored
1
crates/nu-command/src/env/config/mod.rs
vendored
|
@ -2,7 +2,6 @@ mod config_;
|
|||
mod config_env;
|
||||
mod config_nu;
|
||||
mod config_reset;
|
||||
mod utils;
|
||||
pub use config_::ConfigMeta;
|
||||
pub use config_env::ConfigEnv;
|
||||
pub use config_nu::ConfigNu;
|
||||
|
|
36
crates/nu-command/src/env/config/utils.rs
vendored
36
crates/nu-command/src/env/config/utils.rs
vendored
|
@ -1,36 +0,0 @@
|
|||
use crate::ExternalCommand;
|
||||
use nu_protocol::{OutDest, Span, Spanned};
|
||||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
pub(crate) fn gen_command(
|
||||
span: Span,
|
||||
config_path: &Path,
|
||||
item: String,
|
||||
config_args: Vec<String>,
|
||||
env_vars_str: HashMap<String, String>,
|
||||
) -> ExternalCommand {
|
||||
let name = Spanned { item, span };
|
||||
|
||||
let mut args = vec![Spanned {
|
||||
item: config_path.to_string_lossy().to_string(),
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
|
||||
let number_of_args = config_args.len() + 1;
|
||||
|
||||
for arg in config_args {
|
||||
args.push(Spanned {
|
||||
item: arg,
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
ExternalCommand {
|
||||
name,
|
||||
args,
|
||||
arg_keep_raw: vec![false; number_of_args],
|
||||
out: OutDest::Inherit,
|
||||
err: OutDest::Inherit,
|
||||
env_vars: env_vars_str,
|
||||
}
|
||||
}
|
69
crates/nu-command/src/env/with_env.rs
vendored
69
crates/nu-command/src/env/with_env.rs
vendored
|
@ -1,6 +1,5 @@
|
|||
use nu_engine::{command_prelude::*, eval_block};
|
||||
use nu_protocol::{debugger::WithoutDebug, engine::Closure};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WithEnv;
|
||||
|
@ -58,78 +57,14 @@ fn with_env(
|
|||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let variable: Value = call.req(engine_state, stack, 0)?;
|
||||
|
||||
let env: Record = call.req(engine_state, stack, 0)?;
|
||||
let capture_block: Closure = call.req(engine_state, stack, 1)?;
|
||||
let block = engine_state.get_block(capture_block.block_id);
|
||||
let mut stack = stack.captures_to_stack_preserve_out_dest(capture_block.captures);
|
||||
|
||||
let mut env: HashMap<String, Value> = HashMap::new();
|
||||
|
||||
match &variable {
|
||||
Value::List { vals: table, .. } => {
|
||||
nu_protocol::report_error_new(
|
||||
engine_state,
|
||||
&ShellError::GenericError {
|
||||
error: "Deprecated argument type".into(),
|
||||
msg: "providing the variables to `with-env` as a list or single row table has been deprecated".into(),
|
||||
span: Some(variable.span()),
|
||||
help: Some("use the record form instead".into()),
|
||||
inner: vec![],
|
||||
},
|
||||
);
|
||||
if table.len() == 1 {
|
||||
// single row([[X W]; [Y Z]])
|
||||
match &table[0] {
|
||||
Value::Record { val, .. } => {
|
||||
for (k, v) in &**val {
|
||||
env.insert(k.to_string(), v.clone());
|
||||
}
|
||||
}
|
||||
x => {
|
||||
return Err(ShellError::CantConvert {
|
||||
to_type: "record".into(),
|
||||
from_type: x.get_type().to_string(),
|
||||
span: x.span(),
|
||||
help: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// primitive values([X Y W Z])
|
||||
for row in table.chunks(2) {
|
||||
if row.len() == 2 {
|
||||
env.insert(row[0].coerce_string()?, row[1].clone());
|
||||
}
|
||||
if row.len() == 1 {
|
||||
return Err(ShellError::IncorrectValue {
|
||||
msg: format!("Missing value for $env.{}", row[0].coerce_string()?),
|
||||
val_span: row[0].span(),
|
||||
call_span: call.head,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// when get object by `open x.json` or `from json`
|
||||
Value::Record { val, .. } => {
|
||||
for (k, v) in &**val {
|
||||
env.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
x => {
|
||||
return Err(ShellError::CantConvert {
|
||||
to_type: "record".into(),
|
||||
from_type: x.get_type().to_string(),
|
||||
span: x.span(),
|
||||
help: None,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: factor list of prohibited env vars into common place
|
||||
for prohibited in ["PWD", "FILE_PWD", "CURRENT_FILE"] {
|
||||
if env.contains_key(prohibited) {
|
||||
if env.contains(prohibited) {
|
||||
return Err(ShellError::AutomaticEnvVarSetManually {
|
||||
envvar_name: prohibited.into(),
|
||||
span: call.head,
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
use filetime::FileTime;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::NuGlob;
|
||||
|
||||
use std::{fs::OpenOptions, path::Path, time::SystemTime};
|
||||
use std::{fs::OpenOptions, time::SystemTime};
|
||||
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
|
||||
|
@ -69,6 +68,8 @@ impl Command for Touch {
|
|||
let no_create: bool = call.has_flag(engine_state, stack, "no-create")?;
|
||||
let files: Vec<Spanned<NuGlob>> = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
|
||||
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
|
||||
if files.is_empty() {
|
||||
return Err(ShellError::MissingParameter {
|
||||
param_name: "requires file paths".to_string(),
|
||||
|
@ -86,7 +87,7 @@ impl Command for Touch {
|
|||
}
|
||||
|
||||
if let Some(reference) = reference {
|
||||
let reference_path = Path::new(&reference.item);
|
||||
let reference_path = nu_path::expand_path_with(reference.item, &cwd, true);
|
||||
if !reference_path.exists() {
|
||||
return Err(ShellError::FileNotFoundCustom {
|
||||
msg: "Reference path not found".into(),
|
||||
|
@ -114,9 +115,6 @@ impl Command for Touch {
|
|||
})?;
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
|
||||
for glob in files {
|
||||
let path = expand_path_with(glob.item.as_ref(), &cwd, glob.item.is_expand());
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::command_prelude::*;
|
||||
use std::io::Read;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct First;
|
||||
|
@ -171,10 +172,9 @@ fn first_helper(
|
|||
}
|
||||
}
|
||||
PipelineData::ByteStream(stream, metadata) => {
|
||||
if stream.type_() == ByteStreamType::Binary {
|
||||
if stream.type_().is_binary_coercible() {
|
||||
let span = stream.span();
|
||||
if let Some(mut reader) = stream.reader() {
|
||||
use std::io::Read;
|
||||
if return_single_element {
|
||||
// Take a single byte
|
||||
let mut byte = [0u8];
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use nu_engine::command_prelude::*;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::{collections::VecDeque, io::Read};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Last;
|
||||
|
@ -161,10 +160,9 @@ impl Command for Last {
|
|||
}
|
||||
}
|
||||
PipelineData::ByteStream(stream, ..) => {
|
||||
if stream.type_() == ByteStreamType::Binary {
|
||||
if stream.type_().is_binary_coercible() {
|
||||
let span = stream.span();
|
||||
if let Some(mut reader) = stream.reader() {
|
||||
use std::io::Read;
|
||||
// Have to be a bit tricky here, but just consume into a VecDeque that we
|
||||
// shrink to fit each time
|
||||
const TAKE: u64 = 8192;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::command_prelude::*;
|
||||
use std::io::{self, Read};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Skip;
|
||||
|
@ -94,12 +95,11 @@ impl Command for Skip {
|
|||
let input_span = input.span().unwrap_or(call.head);
|
||||
match input {
|
||||
PipelineData::ByteStream(stream, metadata) => {
|
||||
if stream.type_() == ByteStreamType::Binary {
|
||||
if stream.type_().is_binary_coercible() {
|
||||
let span = stream.span();
|
||||
if let Some(mut reader) = stream.reader() {
|
||||
use std::io::Read;
|
||||
// Copy the number of skipped bytes into the sink before proceeding
|
||||
std::io::copy(&mut (&mut reader).take(n as u64), &mut std::io::sink())
|
||||
io::copy(&mut (&mut reader).take(n as u64), &mut io::sink())
|
||||
.err_span(span)?;
|
||||
Ok(PipelineData::ByteStream(
|
||||
ByteStream::read(reader, call.head, None, ByteStreamType::Binary),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::command_prelude::*;
|
||||
use std::io::Read;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Take;
|
||||
|
@ -79,9 +80,8 @@ impl Command for Take {
|
|||
metadata,
|
||||
)),
|
||||
PipelineData::ByteStream(stream, metadata) => {
|
||||
if stream.type_() == ByteStreamType::Binary {
|
||||
if stream.type_().is_binary_coercible() {
|
||||
if let Some(reader) = stream.reader() {
|
||||
use std::io::Read;
|
||||
// Just take 'rows' bytes off the stream, mimicking the binary behavior
|
||||
Ok(PipelineData::ByteStream(
|
||||
ByteStream::read(
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
use csv::{ReaderBuilder, Trim};
|
||||
use nu_protocol::{IntoPipelineData, PipelineData, ShellError, Span, Value};
|
||||
use nu_protocol::{ByteStream, ListStream, PipelineData, ShellError, Span, Value};
|
||||
|
||||
fn from_delimited_string_to_value(
|
||||
fn from_csv_error(err: csv::Error, span: Span) -> ShellError {
|
||||
ShellError::DelimiterError {
|
||||
msg: err.to_string(),
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_delimited_stream(
|
||||
DelimitedReaderConfig {
|
||||
separator,
|
||||
comment,
|
||||
|
@ -12,9 +19,15 @@ fn from_delimited_string_to_value(
|
|||
no_infer,
|
||||
trim,
|
||||
}: DelimitedReaderConfig,
|
||||
s: String,
|
||||
input: ByteStream,
|
||||
span: Span,
|
||||
) -> Result<Value, csv::Error> {
|
||||
) -> Result<ListStream, ShellError> {
|
||||
let input_reader = if let Some(stream) = input.reader() {
|
||||
stream
|
||||
} else {
|
||||
return Ok(ListStream::new(std::iter::empty(), span, None));
|
||||
};
|
||||
|
||||
let mut reader = ReaderBuilder::new()
|
||||
.has_headers(!noheaders)
|
||||
.flexible(flexible)
|
||||
|
@ -23,19 +36,29 @@ fn from_delimited_string_to_value(
|
|||
.quote(quote as u8)
|
||||
.escape(escape.map(|c| c as u8))
|
||||
.trim(trim)
|
||||
.from_reader(s.as_bytes());
|
||||
.from_reader(input_reader);
|
||||
|
||||
let headers = if noheaders {
|
||||
(1..=reader.headers()?.len())
|
||||
(1..=reader
|
||||
.headers()
|
||||
.map_err(|err| from_csv_error(err, span))?
|
||||
.len())
|
||||
.map(|i| format!("column{i}"))
|
||||
.collect::<Vec<String>>()
|
||||
} else {
|
||||
reader.headers()?.iter().map(String::from).collect()
|
||||
reader
|
||||
.headers()
|
||||
.map_err(|err| from_csv_error(err, span))?
|
||||
.iter()
|
||||
.map(String::from)
|
||||
.collect()
|
||||
};
|
||||
|
||||
let mut rows = vec![];
|
||||
for row in reader.records() {
|
||||
let row = row?;
|
||||
let iter = reader.into_records().map(move |row| {
|
||||
let row = match row {
|
||||
Ok(row) => row,
|
||||
Err(err) => return Value::error(from_csv_error(err, span), span),
|
||||
};
|
||||
let columns = headers.iter().cloned();
|
||||
let values = row
|
||||
.into_iter()
|
||||
|
@ -57,10 +80,10 @@ fn from_delimited_string_to_value(
|
|||
//
|
||||
// Otherwise, if there are less values than headers,
|
||||
// then `Value::nothing(span)` is used to fill the remaining columns.
|
||||
rows.push(Value::record(columns.zip(values).collect(), span));
|
||||
}
|
||||
Value::record(columns.zip(values).collect(), span)
|
||||
});
|
||||
|
||||
Ok(Value::list(rows, span))
|
||||
Ok(ListStream::new(iter, span, None))
|
||||
}
|
||||
|
||||
pub(super) struct DelimitedReaderConfig {
|
||||
|
@ -79,14 +102,27 @@ pub(super) fn from_delimited_data(
|
|||
input: PipelineData,
|
||||
name: Span,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let (concat_string, _span, metadata) = input.collect_string_strict(name)?;
|
||||
|
||||
Ok(from_delimited_string_to_value(config, concat_string, name)
|
||||
.map_err(|x| ShellError::DelimiterError {
|
||||
msg: x.to_string(),
|
||||
span: name,
|
||||
})?
|
||||
.into_pipeline_data_with_metadata(metadata))
|
||||
match input {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Value(value, metadata) => {
|
||||
let string = value.into_string()?;
|
||||
let byte_stream = ByteStream::read_string(string, name, None);
|
||||
Ok(PipelineData::ListStream(
|
||||
from_delimited_stream(config, byte_stream, name)?,
|
||||
metadata,
|
||||
))
|
||||
}
|
||||
PipelineData::ListStream(list_stream, _) => Err(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "string".into(),
|
||||
wrong_type: "list".into(),
|
||||
dst_span: name,
|
||||
src_span: list_stream.span(),
|
||||
}),
|
||||
PipelineData::ByteStream(byte_stream, metadata) => Ok(PipelineData::ListStream(
|
||||
from_delimited_stream(config, byte_stream, name)?,
|
||||
metadata,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trim_from_str(trim: Option<Value>) -> Result<Trim, ShellError> {
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
use std::{
|
||||
io::{BufRead, Cursor},
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::ListStream;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FromJson;
|
||||
|
@ -45,6 +51,15 @@ impl Command for FromJson {
|
|||
"b" => Value::test_int(2),
|
||||
})),
|
||||
},
|
||||
Example {
|
||||
example: r#"'{ "a": 1 }
|
||||
{ "b": 2 }' | from json --objects"#,
|
||||
description: "Parse a stream of line-delimited JSON values",
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_record(record! {"a" => Value::test_int(1)}),
|
||||
Value::test_record(record! {"b" => Value::test_int(2)}),
|
||||
])),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -56,49 +71,80 @@ impl Command for FromJson {
|
|||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = call.head;
|
||||
let (string_input, span, metadata) = input.collect_string_strict(span)?;
|
||||
|
||||
if string_input.is_empty() {
|
||||
return Ok(Value::nothing(span).into_pipeline_data());
|
||||
}
|
||||
|
||||
let strict = call.has_flag(engine_state, stack, "strict")?;
|
||||
|
||||
// TODO: turn this into a structured underline of the nu_json error
|
||||
if call.has_flag(engine_state, stack, "objects")? {
|
||||
let lines = string_input.lines().filter(|line| !line.trim().is_empty());
|
||||
|
||||
let converted_lines: Vec<_> = if strict {
|
||||
lines
|
||||
.map(|line| {
|
||||
convert_string_to_value_strict(line, span)
|
||||
.unwrap_or_else(|err| Value::error(err, span))
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
lines
|
||||
.map(|line| {
|
||||
convert_string_to_value(line, span)
|
||||
.unwrap_or_else(|err| Value::error(err, span))
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
Ok(converted_lines.into_pipeline_data_with_metadata(
|
||||
span,
|
||||
engine_state.ctrlc.clone(),
|
||||
metadata,
|
||||
))
|
||||
} else if strict {
|
||||
Ok(convert_string_to_value_strict(&string_input, span)?
|
||||
.into_pipeline_data_with_metadata(metadata))
|
||||
// Return a stream of JSON values, one for each non-empty line
|
||||
match input {
|
||||
PipelineData::Value(Value::String { val, .. }, metadata) => {
|
||||
Ok(PipelineData::ListStream(
|
||||
read_json_lines(Cursor::new(val), span, strict, engine_state.ctrlc.clone()),
|
||||
metadata,
|
||||
))
|
||||
}
|
||||
PipelineData::ByteStream(stream, metadata)
|
||||
if stream.type_() != ByteStreamType::Binary =>
|
||||
{
|
||||
if let Some(reader) = stream.reader() {
|
||||
Ok(PipelineData::ListStream(
|
||||
read_json_lines(reader, span, strict, None),
|
||||
metadata,
|
||||
))
|
||||
} else {
|
||||
Ok(PipelineData::Empty)
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "string".into(),
|
||||
wrong_type: input.get_type().to_string(),
|
||||
dst_span: call.head,
|
||||
src_span: input.span().unwrap_or(call.head),
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Ok(convert_string_to_value(&string_input, span)?
|
||||
.into_pipeline_data_with_metadata(metadata))
|
||||
// Return a single JSON value
|
||||
let (string_input, span, metadata) = input.collect_string_strict(span)?;
|
||||
|
||||
if string_input.is_empty() {
|
||||
return Ok(Value::nothing(span).into_pipeline_data());
|
||||
}
|
||||
|
||||
if strict {
|
||||
Ok(convert_string_to_value_strict(&string_input, span)?
|
||||
.into_pipeline_data_with_metadata(metadata))
|
||||
} else {
|
||||
Ok(convert_string_to_value(&string_input, span)?
|
||||
.into_pipeline_data_with_metadata(metadata))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a stream of values from a reader that produces line-delimited JSON
|
||||
fn read_json_lines(
|
||||
input: impl BufRead + Send + 'static,
|
||||
span: Span,
|
||||
strict: bool,
|
||||
interrupt: Option<Arc<AtomicBool>>,
|
||||
) -> ListStream {
|
||||
let iter = input
|
||||
.lines()
|
||||
.filter(|line| line.as_ref().is_ok_and(|line| !line.trim().is_empty()) || line.is_err())
|
||||
.map(move |line| {
|
||||
let line = line.err_span(span)?;
|
||||
if strict {
|
||||
convert_string_to_value_strict(&line, span)
|
||||
} else {
|
||||
convert_string_to_value(&line, span)
|
||||
}
|
||||
})
|
||||
.map(move |result| result.unwrap_or_else(|err| Value::error(err, span)));
|
||||
|
||||
ListStream::new(iter, span, interrupt)
|
||||
}
|
||||
|
||||
fn convert_nujson_to_value(value: nu_json::Value, span: Span) -> Value {
|
||||
match value {
|
||||
nu_json::Value::Array(array) => Value::list(
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::formats::to::delimited::to_delimited_data;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::Config;
|
||||
|
@ -27,26 +29,37 @@ impl Command for ToCsv {
|
|||
"do not output the columns names as the first row",
|
||||
Some('n'),
|
||||
)
|
||||
.named(
|
||||
"columns",
|
||||
SyntaxShape::List(SyntaxShape::String.into()),
|
||||
"the names (in order) of the columns to use",
|
||||
None,
|
||||
)
|
||||
.category(Category::Formats)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Outputs an CSV string representing the contents of this table",
|
||||
description: "Outputs a CSV string representing the contents of this table",
|
||||
example: "[[foo bar]; [1 2]] | to csv",
|
||||
result: Some(Value::test_string("foo,bar\n1,2\n")),
|
||||
},
|
||||
Example {
|
||||
description: "Outputs an CSV string representing the contents of this table",
|
||||
description: "Outputs a CSV string representing the contents of this table",
|
||||
example: "[[foo bar]; [1 2]] | to csv --separator ';' ",
|
||||
result: Some(Value::test_string("foo;bar\n1;2\n")),
|
||||
},
|
||||
Example {
|
||||
description: "Outputs an CSV string representing the contents of this record",
|
||||
description: "Outputs a CSV string representing the contents of this record",
|
||||
example: "{a: 1 b: 2} | to csv",
|
||||
result: Some(Value::test_string("a,b\n1,2\n")),
|
||||
},
|
||||
Example {
|
||||
description: "Outputs a CSV stream with column names pre-determined",
|
||||
example: "[[foo bar baz]; [1 2 3]] | to csv --columns [baz foo]",
|
||||
result: Some(Value::test_string("baz,foo\n3,1\n")),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -64,8 +77,9 @@ impl Command for ToCsv {
|
|||
let head = call.head;
|
||||
let noheaders = call.has_flag(engine_state, stack, "noheaders")?;
|
||||
let separator: Option<Spanned<String>> = call.get_flag(engine_state, stack, "separator")?;
|
||||
let config = engine_state.get_config();
|
||||
to_csv(input, noheaders, separator, head, config)
|
||||
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
|
||||
let config = engine_state.config.clone();
|
||||
to_csv(input, noheaders, separator, columns, head, config)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,13 +87,14 @@ fn to_csv(
|
|||
input: PipelineData,
|
||||
noheaders: bool,
|
||||
separator: Option<Spanned<String>>,
|
||||
columns: Option<Vec<String>>,
|
||||
head: Span,
|
||||
config: &Config,
|
||||
config: Arc<Config>,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let sep = match separator {
|
||||
Some(Spanned { item: s, span, .. }) => {
|
||||
if s == r"\t" {
|
||||
'\t'
|
||||
Spanned { item: '\t', span }
|
||||
} else {
|
||||
let vec_s: Vec<char> = s.chars().collect();
|
||||
if vec_s.len() != 1 {
|
||||
|
@ -89,13 +104,19 @@ fn to_csv(
|
|||
span,
|
||||
});
|
||||
};
|
||||
vec_s[0]
|
||||
Spanned {
|
||||
item: vec_s[0],
|
||||
span: head,
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => ',',
|
||||
_ => Spanned {
|
||||
item: ',',
|
||||
span: head,
|
||||
},
|
||||
};
|
||||
|
||||
to_delimited_data(noheaders, sep, "CSV", input, head, config)
|
||||
to_delimited_data(noheaders, sep, columns, "CSV", input, head, config)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1,113 +1,31 @@
|
|||
use csv::{Writer, WriterBuilder};
|
||||
use csv::WriterBuilder;
|
||||
use nu_cmd_base::formats::to::delimited::merge_descriptors;
|
||||
use nu_protocol::{Config, IntoPipelineData, PipelineData, Record, ShellError, Span, Value};
|
||||
use std::{collections::VecDeque, error::Error};
|
||||
use nu_protocol::{
|
||||
ByteStream, ByteStreamType, Config, PipelineData, ShellError, Span, Spanned, Value,
|
||||
};
|
||||
use std::{iter, sync::Arc};
|
||||
|
||||
fn from_value_to_delimited_string(
|
||||
value: &Value,
|
||||
separator: char,
|
||||
config: &Config,
|
||||
head: Span,
|
||||
) -> Result<String, ShellError> {
|
||||
let span = value.span();
|
||||
match value {
|
||||
Value::Record { val, .. } => record_to_delimited(val, span, separator, config, head),
|
||||
Value::List { vals, .. } => table_to_delimited(vals, span, separator, config, head),
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { error, .. } => Err(*error.clone()),
|
||||
v => Err(make_unsupported_input_error(v, head, v.span())),
|
||||
}
|
||||
}
|
||||
|
||||
fn record_to_delimited(
|
||||
record: &Record,
|
||||
span: Span,
|
||||
separator: char,
|
||||
config: &Config,
|
||||
head: Span,
|
||||
) -> Result<String, ShellError> {
|
||||
let mut wtr = WriterBuilder::new()
|
||||
.delimiter(separator as u8)
|
||||
.from_writer(vec![]);
|
||||
let mut fields: VecDeque<String> = VecDeque::new();
|
||||
let mut values: VecDeque<String> = VecDeque::new();
|
||||
|
||||
for (k, v) in record {
|
||||
fields.push_back(k.clone());
|
||||
|
||||
values.push_back(to_string_tagged_value(v, config, head, span)?);
|
||||
}
|
||||
|
||||
wtr.write_record(fields).expect("can not write.");
|
||||
wtr.write_record(values).expect("can not write.");
|
||||
|
||||
writer_to_string(wtr).map_err(|_| make_conversion_error("record", span))
|
||||
}
|
||||
|
||||
fn table_to_delimited(
|
||||
vals: &[Value],
|
||||
span: Span,
|
||||
separator: char,
|
||||
config: &Config,
|
||||
head: Span,
|
||||
) -> Result<String, ShellError> {
|
||||
if let Some(val) = find_non_record(vals) {
|
||||
return Err(make_unsupported_input_error(val, head, span));
|
||||
}
|
||||
|
||||
let mut wtr = WriterBuilder::new()
|
||||
.delimiter(separator as u8)
|
||||
.from_writer(vec![]);
|
||||
|
||||
let merged_descriptors = merge_descriptors(vals);
|
||||
|
||||
if merged_descriptors.is_empty() {
|
||||
let vals = vals
|
||||
.iter()
|
||||
.map(|ele| {
|
||||
to_string_tagged_value(ele, config, head, span).unwrap_or_else(|_| String::new())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
wtr.write_record(vals).expect("can not write");
|
||||
} else {
|
||||
wtr.write_record(merged_descriptors.iter().map(|item| &item[..]))
|
||||
.expect("can not write.");
|
||||
|
||||
for l in vals {
|
||||
// should always be true because of `find_non_record` above
|
||||
if let Value::Record { val: l, .. } = l {
|
||||
let mut row = vec![];
|
||||
for desc in &merged_descriptors {
|
||||
row.push(match l.get(desc) {
|
||||
Some(s) => to_string_tagged_value(s, config, head, span)?,
|
||||
None => String::new(),
|
||||
});
|
||||
}
|
||||
wtr.write_record(&row).expect("can not write");
|
||||
}
|
||||
fn make_csv_error(error: csv::Error, format_name: &str, head: Span) -> ShellError {
|
||||
if let csv::ErrorKind::Io(error) = error.kind() {
|
||||
ShellError::IOErrorSpanned {
|
||||
msg: error.to_string(),
|
||||
span: head,
|
||||
}
|
||||
} else {
|
||||
ShellError::GenericError {
|
||||
error: format!("Failed to generate {format_name} data"),
|
||||
msg: error.to_string(),
|
||||
span: Some(head),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
}
|
||||
}
|
||||
writer_to_string(wtr).map_err(|_| make_conversion_error("table", span))
|
||||
}
|
||||
|
||||
fn writer_to_string(writer: Writer<Vec<u8>>) -> Result<String, Box<dyn Error>> {
|
||||
Ok(String::from_utf8(writer.into_inner()?)?)
|
||||
}
|
||||
|
||||
fn make_conversion_error(type_from: &str, span: Span) -> ShellError {
|
||||
ShellError::CantConvert {
|
||||
to_type: type_from.to_string(),
|
||||
from_type: "string".to_string(),
|
||||
span,
|
||||
help: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_string_tagged_value(
|
||||
v: &Value,
|
||||
config: &Config,
|
||||
span: Span,
|
||||
head: Span,
|
||||
format_name: &'static str,
|
||||
) -> Result<String, ShellError> {
|
||||
match &v {
|
||||
Value::String { .. }
|
||||
|
@ -123,50 +41,124 @@ fn to_string_tagged_value(
|
|||
Value::Nothing { .. } => Ok(String::new()),
|
||||
// Propagate existing errors
|
||||
Value::Error { error, .. } => Err(*error.clone()),
|
||||
_ => Err(make_unsupported_input_error(v, head, span)),
|
||||
_ => Err(make_cant_convert_error(v, format_name)),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_unsupported_input_error(value: &Value, head: Span, span: Span) -> ShellError {
|
||||
fn make_unsupported_input_error(
|
||||
r#type: impl std::fmt::Display,
|
||||
head: Span,
|
||||
span: Span,
|
||||
) -> ShellError {
|
||||
ShellError::UnsupportedInput {
|
||||
msg: "Unexpected type".to_string(),
|
||||
input: format!("input type: {:?}", value.get_type()),
|
||||
msg: "expected table or record".to_string(),
|
||||
input: format!("input type: {}", r#type),
|
||||
msg_span: head,
|
||||
input_span: span,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_non_record(values: &[Value]) -> Option<&Value> {
|
||||
values
|
||||
.iter()
|
||||
.find(|val| !matches!(val, Value::Record { .. }))
|
||||
fn make_cant_convert_error(value: &Value, format_name: &'static str) -> ShellError {
|
||||
ShellError::CantConvert {
|
||||
to_type: "string".into(),
|
||||
from_type: value.get_type().to_string(),
|
||||
span: value.span(),
|
||||
help: Some(format!(
|
||||
"only simple values are supported for {format_name} output"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_delimited_data(
|
||||
noheaders: bool,
|
||||
sep: char,
|
||||
separator: Spanned<char>,
|
||||
columns: Option<Vec<String>>,
|
||||
format_name: &'static str,
|
||||
input: PipelineData,
|
||||
span: Span,
|
||||
config: &Config,
|
||||
head: Span,
|
||||
config: Arc<Config>,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value = input.into_value(span)?;
|
||||
let output = match from_value_to_delimited_string(&value, sep, config, span) {
|
||||
Ok(mut x) => {
|
||||
if noheaders {
|
||||
if let Some(second_line) = x.find('\n') {
|
||||
let start = second_line + 1;
|
||||
x.replace_range(0..start, "");
|
||||
}
|
||||
}
|
||||
Ok(x)
|
||||
let mut input = input;
|
||||
let span = input.span().unwrap_or(head);
|
||||
let metadata = input.metadata();
|
||||
|
||||
let separator = u8::try_from(separator.item).map_err(|_| ShellError::IncorrectValue {
|
||||
msg: "separator must be an ASCII character".into(),
|
||||
val_span: separator.span,
|
||||
call_span: head,
|
||||
})?;
|
||||
|
||||
// Check to ensure the input is likely one of our supported types first. We can't check a stream
|
||||
// without consuming it though
|
||||
match input {
|
||||
PipelineData::Value(Value::List { .. } | Value::Record { .. }, _) => (),
|
||||
PipelineData::Value(Value::Error { error, .. }, _) => return Err(*error),
|
||||
PipelineData::Value(other, _) => {
|
||||
return Err(make_unsupported_input_error(other.get_type(), head, span))
|
||||
}
|
||||
Err(_) => Err(ShellError::CantConvert {
|
||||
to_type: format_name.into(),
|
||||
from_type: value.get_type().to_string(),
|
||||
span: value.span(),
|
||||
help: None,
|
||||
}),
|
||||
}?;
|
||||
Ok(Value::string(output, span).into_pipeline_data())
|
||||
PipelineData::ByteStream(..) => {
|
||||
return Err(make_unsupported_input_error("byte stream", head, span))
|
||||
}
|
||||
PipelineData::ListStream(..) => (),
|
||||
PipelineData::Empty => (),
|
||||
}
|
||||
|
||||
// Determine the columns we'll use. This is necessary even if we don't write the header row,
|
||||
// because we need to write consistent columns.
|
||||
let columns = match columns {
|
||||
Some(columns) => columns,
|
||||
None => {
|
||||
// The columns were not provided. We need to detect them, and in order to do so, we have
|
||||
// to convert the input into a value first, so that we can find all of them
|
||||
let value = input.into_value(span)?;
|
||||
let columns = match &value {
|
||||
Value::List { vals, .. } => merge_descriptors(vals),
|
||||
Value::Record { val, .. } => val.columns().cloned().collect(),
|
||||
_ => return Err(make_unsupported_input_error(value.get_type(), head, span)),
|
||||
};
|
||||
input = PipelineData::Value(value, metadata.clone());
|
||||
columns
|
||||
}
|
||||
};
|
||||
|
||||
// Generate a byte stream of all of the values in the pipeline iterator, with a non-strict
|
||||
// iterator so we can still accept plain records.
|
||||
let mut iter = input.into_iter();
|
||||
|
||||
// If we're configured to generate a header, we generate it first, then set this false
|
||||
let mut is_header = !noheaders;
|
||||
|
||||
let stream = ByteStream::from_fn(head, None, ByteStreamType::String, move |buffer| {
|
||||
let mut wtr = WriterBuilder::new()
|
||||
.delimiter(separator)
|
||||
.from_writer(buffer);
|
||||
|
||||
if is_header {
|
||||
// Unless we are configured not to write a header, we write the header row now, once,
|
||||
// before everything else.
|
||||
wtr.write_record(&columns)
|
||||
.map_err(|err| make_csv_error(err, format_name, head))?;
|
||||
is_header = false;
|
||||
Ok(true)
|
||||
} else if let Some(row) = iter.next() {
|
||||
// Write each column of a normal row, in order
|
||||
let record = row.into_record()?;
|
||||
for column in &columns {
|
||||
let field = record
|
||||
.get(column)
|
||||
.map(|v| to_string_tagged_value(v, &config, format_name))
|
||||
.unwrap_or(Ok(String::new()))?;
|
||||
wtr.write_field(field)
|
||||
.map_err(|err| make_csv_error(err, format_name, head))?;
|
||||
}
|
||||
// End the row
|
||||
wtr.write_record(iter::empty::<String>())
|
||||
.map_err(|err| make_csv_error(err, format_name, head))?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
});
|
||||
|
||||
Ok(PipelineData::ByteStream(stream, metadata))
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::formats::to::delimited::to_delimited_data;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::Config;
|
||||
|
@ -21,6 +23,12 @@ impl Command for ToTsv {
|
|||
"do not output the column names as the first row",
|
||||
Some('n'),
|
||||
)
|
||||
.named(
|
||||
"columns",
|
||||
SyntaxShape::List(SyntaxShape::String.into()),
|
||||
"the names (in order) of the columns to use",
|
||||
None,
|
||||
)
|
||||
.category(Category::Formats)
|
||||
}
|
||||
|
||||
|
@ -31,15 +39,20 @@ impl Command for ToTsv {
|
|||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Outputs an TSV string representing the contents of this table",
|
||||
description: "Outputs a TSV string representing the contents of this table",
|
||||
example: "[[foo bar]; [1 2]] | to tsv",
|
||||
result: Some(Value::test_string("foo\tbar\n1\t2\n")),
|
||||
},
|
||||
Example {
|
||||
description: "Outputs an TSV string representing the contents of this record",
|
||||
description: "Outputs a TSV string representing the contents of this record",
|
||||
example: "{a: 1 b: 2} | to tsv",
|
||||
result: Some(Value::test_string("a\tb\n1\t2\n")),
|
||||
},
|
||||
Example {
|
||||
description: "Outputs a TSV stream with column names pre-determined",
|
||||
example: "[[foo bar baz]; [1 2 3]] | to tsv --columns [baz foo]",
|
||||
result: Some(Value::test_string("baz\tfoo\n3\t1\n")),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -52,18 +65,24 @@ impl Command for ToTsv {
|
|||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let noheaders = call.has_flag(engine_state, stack, "noheaders")?;
|
||||
let config = engine_state.get_config();
|
||||
to_tsv(input, noheaders, head, config)
|
||||
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
|
||||
let config = engine_state.config.clone();
|
||||
to_tsv(input, noheaders, columns, head, config)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_tsv(
|
||||
input: PipelineData,
|
||||
noheaders: bool,
|
||||
columns: Option<Vec<String>>,
|
||||
head: Span,
|
||||
config: &Config,
|
||||
config: Arc<Config>,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
to_delimited_data(noheaders, '\t', "TSV", input, head, config)
|
||||
let sep = Spanned {
|
||||
item: '\t',
|
||||
span: head,
|
||||
};
|
||||
to_delimited_data(noheaders, sep, columns, "TSV", input, head, config)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use super::PathSubcommandArguments;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_path::expand_tilde;
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
struct Arguments;
|
||||
struct Arguments {
|
||||
pwd: PathBuf,
|
||||
}
|
||||
|
||||
impl PathSubcommandArguments for Arguments {}
|
||||
|
||||
|
@ -45,19 +46,21 @@ If nothing is found, an empty string will be returned."#
|
|||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let args = Arguments;
|
||||
let args = Arguments {
|
||||
pwd: engine_state.cwd(Some(stack))?,
|
||||
};
|
||||
|
||||
// This doesn't match explicit nulls
|
||||
if matches!(input, PipelineData::Empty) {
|
||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||
}
|
||||
input.map(
|
||||
move |value| super::operate(&r#type, &args, value, head),
|
||||
move |value| super::operate(&path_type, &args, value, head),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
@ -69,14 +72,16 @@ If nothing is found, an empty string will be returned."#
|
|||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let args = Arguments;
|
||||
let args = Arguments {
|
||||
pwd: working_set.permanent().cwd(None)?,
|
||||
};
|
||||
|
||||
// This doesn't match explicit nulls
|
||||
if matches!(input, PipelineData::Empty) {
|
||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||
}
|
||||
input.map(
|
||||
move |value| super::operate(&r#type, &args, value, head),
|
||||
move |value| super::operate(&path_type, &args, value, head),
|
||||
working_set.permanent().ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
@ -97,21 +102,11 @@ If nothing is found, an empty string will be returned."#
|
|||
}
|
||||
}
|
||||
|
||||
fn r#type(path: &Path, span: Span, _: &Arguments) -> Value {
|
||||
let meta = if path.starts_with("~") {
|
||||
let p = expand_tilde(path);
|
||||
std::fs::symlink_metadata(p)
|
||||
} else {
|
||||
std::fs::symlink_metadata(path)
|
||||
};
|
||||
|
||||
Value::string(
|
||||
match &meta {
|
||||
Ok(data) => get_file_type(data),
|
||||
Err(_) => "",
|
||||
},
|
||||
span,
|
||||
)
|
||||
fn path_type(path: &Path, span: Span, args: &Arguments) -> Value {
|
||||
let path = nu_path::expand_path_with(path, &args.pwd, true);
|
||||
let meta = path.symlink_metadata();
|
||||
let ty = meta.as_ref().map(get_file_type).unwrap_or("");
|
||||
Value::string(ty, span)
|
||||
}
|
||||
|
||||
fn get_file_type(md: &std::fs::Metadata) -> &str {
|
||||
|
|
|
@ -405,7 +405,7 @@ mod tests {
|
|||
let range = Range::new(
|
||||
Value::int(0, Span::test_data()),
|
||||
Value::int(1, Span::test_data()),
|
||||
Value::int(3, Span::test_data()),
|
||||
Value::int(2, Span::test_data()),
|
||||
RangeInclusion::Inclusive,
|
||||
Span::test_data(),
|
||||
)
|
||||
|
|
|
@ -70,7 +70,7 @@ impl Command for SubCommand {
|
|||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Get part of a string. Note that the start is included but the end is excluded, and that the first character of a string is index 0."
|
||||
"Get part of a string. Note that the first character of a string is index 0."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -108,12 +108,12 @@ impl Command for SubCommand {
|
|||
Example {
|
||||
description:
|
||||
"Get a substring \"nushell\" from the text \"good nushell\" using a range",
|
||||
example: " 'good nushell' | str substring 5..12",
|
||||
example: " 'good nushell' | str substring 5..11",
|
||||
result: Some(Value::test_string("nushell")),
|
||||
},
|
||||
Example {
|
||||
description: "Count indexes and split using grapheme clusters",
|
||||
example: " '🇯🇵ほげ ふが ぴよ' | str substring --grapheme-clusters 4..6",
|
||||
example: " '🇯🇵ほげ ふが ぴよ' | str substring --grapheme-clusters 4..5",
|
||||
result: Some(Value::test_string("ふが")),
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
use super::run_external::create_external_command;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_protocol::OutDest;
|
||||
use nu_engine::{command_prelude::*, env_to_strings};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Exec;
|
||||
|
@ -35,7 +32,66 @@ On Windows based systems, Nushell will wait for the command to finish and then e
|
|||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
exec(engine_state, stack, call)
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
|
||||
// Find the absolute path to the executable. If the command is not
|
||||
// found, display a helpful error message.
|
||||
let name: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
let executable = {
|
||||
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
|
||||
let Some(executable) = crate::which(&name.item, &paths, &cwd) else {
|
||||
return Err(crate::command_not_found(
|
||||
&name.item,
|
||||
call.head,
|
||||
engine_state,
|
||||
stack,
|
||||
));
|
||||
};
|
||||
executable
|
||||
};
|
||||
|
||||
// Create the command.
|
||||
let mut command = std::process::Command::new(executable);
|
||||
|
||||
// Configure PWD.
|
||||
command.current_dir(cwd);
|
||||
|
||||
// Configure environment variables.
|
||||
let envs = env_to_strings(engine_state, stack)?;
|
||||
command.env_clear();
|
||||
command.envs(envs);
|
||||
|
||||
// Configure args.
|
||||
let args = crate::eval_arguments_from_call(engine_state, stack, call)?;
|
||||
command.args(args.into_iter().map(|s| s.item));
|
||||
|
||||
// Execute the child process, replacing/terminating the current process
|
||||
// depending on platform.
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::process::CommandExt;
|
||||
|
||||
let err = command.exec();
|
||||
Err(ShellError::ExternalCommand {
|
||||
label: "Failed to exec into new process".into(),
|
||||
help: err.to_string(),
|
||||
span: call.head,
|
||||
})
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut child = command.spawn().map_err(|err| ShellError::ExternalCommand {
|
||||
label: "Failed to exec into new process".into(),
|
||||
help: err.to_string(),
|
||||
span: call.head,
|
||||
})?;
|
||||
let status = child.wait().map_err(|err| ShellError::ExternalCommand {
|
||||
label: "Failed to wait for child process".into(),
|
||||
help: err.to_string(),
|
||||
span: call.head,
|
||||
})?;
|
||||
std::process::exit(status.code().expect("status.code() succeeds on Windows"))
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -53,57 +109,3 @@ On Windows based systems, Nushell will wait for the command to finish and then e
|
|||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn exec(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let mut external_command = create_external_command(engine_state, stack, call)?;
|
||||
external_command.out = OutDest::Inherit;
|
||||
external_command.err = OutDest::Inherit;
|
||||
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let mut command = external_command.spawn_simple_command(&cwd.to_string_lossy())?;
|
||||
command.current_dir(cwd);
|
||||
command.envs(external_command.env_vars);
|
||||
|
||||
// this either replaces our process and should not return,
|
||||
// or the exec fails and we get an error back
|
||||
exec_impl(command, call.head)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn exec_impl(mut command: std::process::Command, span: Span) -> Result<PipelineData, ShellError> {
|
||||
use std::os::unix::process::CommandExt;
|
||||
|
||||
let error = command.exec();
|
||||
|
||||
Err(ShellError::GenericError {
|
||||
error: "Error on exec".into(),
|
||||
msg: error.to_string(),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn exec_impl(mut command: std::process::Command, span: Span) -> Result<PipelineData, ShellError> {
|
||||
match command.spawn() {
|
||||
Ok(mut child) => match child.wait() {
|
||||
Ok(status) => std::process::exit(status.code().unwrap_or(0)),
|
||||
Err(e) => Err(ShellError::ExternalCommand {
|
||||
label: "Error in external command".into(),
|
||||
help: e.to_string(),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Err(e) => Err(ShellError::ExternalCommand {
|
||||
label: "Error spawning external command".into(),
|
||||
help: e.to_string(),
|
||||
span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ mod nu_check;
|
|||
target_os = "android",
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "macos",
|
||||
target_os = "windows"
|
||||
))]
|
||||
|
@ -23,13 +25,15 @@ pub use nu_check::NuCheck;
|
|||
target_os = "android",
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "macos",
|
||||
target_os = "windows"
|
||||
))]
|
||||
pub use ps::Ps;
|
||||
#[cfg(windows)]
|
||||
pub use registry_query::RegistryQuery;
|
||||
pub use run_external::{External, ExternalCommand};
|
||||
pub use run_external::{command_not_found, eval_arguments_from_call, which, External};
|
||||
pub use sys::*;
|
||||
pub use uname::UName;
|
||||
pub use which_::Which;
|
||||
|
|
|
@ -2,20 +2,13 @@
|
|||
use itertools::Itertools;
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
#[cfg(all(
|
||||
unix,
|
||||
not(target_os = "freebsd"),
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "windows"),
|
||||
not(target_os = "android"),
|
||||
))]
|
||||
#[cfg(target_os = "linux")]
|
||||
use procfs::WithCurrentSystemInfo;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Ps;
|
||||
|
||||
#[cfg(not(target_os = "freebsd"))]
|
||||
impl Command for Ps {
|
||||
fn name(&self) -> &str {
|
||||
"ps"
|
||||
|
@ -82,7 +75,6 @@ impl Command for Ps {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "freebsd"))]
|
||||
fn run_ps(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
|
@ -111,12 +103,7 @@ fn run_ps(
|
|||
|
||||
if long {
|
||||
record.push("command", Value::string(proc.command(), span));
|
||||
#[cfg(all(
|
||||
unix,
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "windows"),
|
||||
not(target_os = "android"),
|
||||
))]
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let proc_stat = proc
|
||||
.curr_proc
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,6 +5,7 @@ use nu_engine::{command_prelude::*, env_to_string};
|
|||
use nu_protocol::Config;
|
||||
use nu_term_grid::grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions};
|
||||
use nu_utils::get_ls_colors;
|
||||
use std::path::Path;
|
||||
use terminal_size::{Height, Width};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -67,6 +68,7 @@ prints out the list properly."#
|
|||
};
|
||||
let use_grid_icons = config.use_grid_icons;
|
||||
let use_color: bool = color_param && config.use_ansi_coloring;
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
|
||||
match input {
|
||||
PipelineData::Value(Value::List { vals, .. }, ..) => {
|
||||
|
@ -81,6 +83,7 @@ prints out the list properly."#
|
|||
separator_param,
|
||||
env_str,
|
||||
use_grid_icons,
|
||||
&cwd,
|
||||
)?)
|
||||
} else {
|
||||
Ok(PipelineData::empty())
|
||||
|
@ -98,6 +101,7 @@ prints out the list properly."#
|
|||
separator_param,
|
||||
env_str,
|
||||
use_grid_icons,
|
||||
&cwd,
|
||||
)?)
|
||||
} else {
|
||||
// dbg!(data);
|
||||
|
@ -120,6 +124,7 @@ prints out the list properly."#
|
|||
separator_param,
|
||||
env_str,
|
||||
use_grid_icons,
|
||||
&cwd,
|
||||
)?)
|
||||
}
|
||||
x => {
|
||||
|
@ -161,6 +166,7 @@ prints out the list properly."#
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn create_grid_output(
|
||||
items: Vec<(usize, String, String)>,
|
||||
call: &Call,
|
||||
|
@ -169,6 +175,7 @@ fn create_grid_output(
|
|||
separator_param: Option<String>,
|
||||
env_str: Option<String>,
|
||||
use_grid_icons: bool,
|
||||
cwd: &Path,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let ls_colors = get_ls_colors(env_str);
|
||||
|
||||
|
@ -196,8 +203,8 @@ fn create_grid_output(
|
|||
if use_color {
|
||||
if use_grid_icons {
|
||||
let no_ansi = nu_utils::strip_ansi_unlikely(&value);
|
||||
let path = std::path::Path::new(no_ansi.as_ref());
|
||||
let icon = icon_for_file(path, call.head)?;
|
||||
let path = cwd.join(no_ansi.as_ref());
|
||||
let icon = icon_for_file(&path, call.head)?;
|
||||
let ls_colors_style = ls_colors.style_for_path(path);
|
||||
|
||||
let icon_style = match ls_colors_style {
|
||||
|
|
|
@ -55,7 +55,9 @@ fn checks_if_all_returns_error_with_invalid_command() {
|
|||
"#
|
||||
));
|
||||
|
||||
assert!(actual.err.contains("can't run executable") || actual.err.contains("did you mean"));
|
||||
assert!(
|
||||
actual.err.contains("Command `st` not found") && actual.err.contains("Did you mean `ast`?")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -41,7 +41,9 @@ fn checks_if_any_returns_error_with_invalid_command() {
|
|||
"#
|
||||
));
|
||||
|
||||
assert!(actual.err.contains("can't run executable") || actual.err.contains("did you mean"));
|
||||
assert!(
|
||||
actual.err.contains("Command `st` not found") && actual.err.contains("Did you mean `ast`?")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -24,7 +24,7 @@ fn basic_exit_code() {
|
|||
#[test]
|
||||
fn error() {
|
||||
let actual = nu!("not-found | complete");
|
||||
assert!(actual.err.contains("executable was not found"));
|
||||
assert!(actual.err.contains("Command `not-found` not found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -31,12 +31,12 @@ fn detect_columns_with_legacy_and_flag_c() {
|
|||
(
|
||||
"$\"c1 c2 c3 c4 c5(char nl)a b c d e\"",
|
||||
"[[c1,c3,c4,c5]; ['a b',c,d,e]]",
|
||||
"0..1",
|
||||
"0..0",
|
||||
),
|
||||
(
|
||||
"$\"c1 c2 c3 c4 c5(char nl)a b c d e\"",
|
||||
"[[c1,c2,c3,c4]; [a,b,c,'d e']]",
|
||||
"(-2)..(-1)",
|
||||
"(-2)..(-2)",
|
||||
),
|
||||
(
|
||||
"$\"c1 c2 c3 c4 c5(char nl)a b c d e\"",
|
||||
|
@ -77,7 +77,7 @@ drwxr-xr-x 4 root root 4.0K Mar 20 08:18 ~(char nl)
|
|||
['drwxr-xr-x', '4', 'root', 'root', '4.0K', 'Mar 20 08:18', '~'],
|
||||
['-rw-r--r--', '1', 'root', 'root', '3.0K', 'Mar 20 07:23', '~asdf']
|
||||
]";
|
||||
let range = "5..7";
|
||||
let range = "5..6";
|
||||
let cmd = format!(
|
||||
"({} | detect columns -c {} -s 1 --no-headers) == {}",
|
||||
pipeline(body),
|
||||
|
|
|
@ -74,3 +74,14 @@ fn returns_type_of_existing_file_const() {
|
|||
assert_eq!(actual.out, "dir");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn respects_cwd() {
|
||||
Playground::setup("path_type_respects_cwd", |dirs, sandbox| {
|
||||
sandbox.within("foo").with_files(&[EmptyFile("bar.txt")]);
|
||||
|
||||
let actual = nu!(cwd: dirs.test(), "cd foo; 'bar.txt' | path type");
|
||||
|
||||
assert_eq!(actual.out, "file");
|
||||
})
|
||||
}
|
||||
|
|
|
@ -283,13 +283,17 @@ fn source_env_is_scoped() {
|
|||
|
||||
let actual = nu!(cwd: dirs.test(), &inp.join("; "));
|
||||
|
||||
assert!(actual.err.contains("executable was not found"));
|
||||
assert!(actual
|
||||
.err
|
||||
.contains("Command `no-name-similar-to-this` not found"));
|
||||
|
||||
let inp = &[r#"source-env spam.nu"#, r#"nor-similar-to-this"#];
|
||||
|
||||
let actual = nu!(cwd: dirs.test(), &inp.join("; "));
|
||||
|
||||
assert!(actual.err.contains("executable was not found"));
|
||||
assert!(actual
|
||||
.err
|
||||
.contains("Command `nor-similar-to-this` not found"));
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -269,7 +269,7 @@ fn substring_errors_if_start_index_is_greater_than_end_index() {
|
|||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open sample.toml
|
||||
| str substring 6..5 fortune.teller.phone
|
||||
| str substring 6..4 fortune.teller.phone
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -342,7 +342,7 @@ fn substrings_the_input_and_treats_start_index_as_zero_if_blank_start_index_give
|
|||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open sample.toml
|
||||
| str substring ..2 package.name
|
||||
| str substring ..1 package.name
|
||||
| get package.name
|
||||
"#
|
||||
));
|
||||
|
|
|
@ -515,3 +515,16 @@ fn respects_cwd() {
|
|||
assert!(path.exists());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reference_respects_cwd() {
|
||||
Playground::setup("touch_reference_respects_cwd", |dirs, _sandbox| {
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"mkdir 'dir'; cd 'dir'; touch 'ref.txt'; touch --reference 'ref.txt' 'foo.txt'"
|
||||
);
|
||||
|
||||
let path = dirs.test().join("dir/foo.txt");
|
||||
assert!(path.exists());
|
||||
})
|
||||
}
|
||||
|
|
|
@ -189,9 +189,7 @@ fn use_module_creates_accurate_did_you_mean_1() {
|
|||
let actual = nu!(r#"
|
||||
module spam { export def foo [] { "foo" } }; use spam; foo
|
||||
"#);
|
||||
assert!(actual.err.contains(
|
||||
"command 'foo' was not found but it was imported from module 'spam'; try using `spam foo`"
|
||||
));
|
||||
assert!(actual.err.contains("Did you mean `spam foo`"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -199,9 +197,9 @@ fn use_module_creates_accurate_did_you_mean_2() {
|
|||
let actual = nu!(r#"
|
||||
module spam { export def foo [] { "foo" } }; foo
|
||||
"#);
|
||||
assert!(actual.err.contains(
|
||||
"command 'foo' was not found but it exists in module 'spam'; try importing it with `use`"
|
||||
));
|
||||
assert!(actual
|
||||
.err
|
||||
.contains("A command with that name exists in module `spam`"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -96,6 +96,32 @@ fn from_json_text_recognizing_objects_independently_to_table() {
|
|||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_json_text_objects_is_stream() {
|
||||
Playground::setup("filter_from_json_test_2_is_stream", |dirs, sandbox| {
|
||||
sandbox.with_files(&[FileWithContentToBeTrimmed(
|
||||
"katz.txt",
|
||||
r#"
|
||||
{"name": "Yehuda", "rusty_luck": 1}
|
||||
{"name": "JT", "rusty_luck": 1}
|
||||
{"name": "Andres", "rusty_luck": 1}
|
||||
{"name":"GorbyPuff", "rusty_luck": 3}
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open katz.txt
|
||||
| from json -o
|
||||
| describe -n
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "stream");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_json_text_recognizing_objects_independently_to_table_strict() {
|
||||
Playground::setup("filter_from_json_test_2_strict", |dirs, sandbox| {
|
||||
|
|
|
@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-engine"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-engine"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.93.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.93.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.93.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.93.1" }
|
||||
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.94.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.94.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.1" }
|
||||
|
||||
[features]
|
||||
plugin = []
|
||||
plugin = []
|
|
@ -5,21 +5,21 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-explore"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-explore"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.93.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.93.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.93.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.93.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.93.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.93.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.94.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.94.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.94.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.93.1" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.94.1" }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
@ -32,4 +32,4 @@ ansi-str = { workspace = true }
|
|||
unicode-width = { workspace = true }
|
||||
lscolors = { workspace = true, default-features = false, features = [
|
||||
"nu-ansi-term",
|
||||
] }
|
||||
] }
|
|
@ -20,11 +20,17 @@ pub struct BinaryWidget<'a> {
|
|||
data: &'a [u8],
|
||||
opts: BinarySettings,
|
||||
style: BinaryStyle,
|
||||
row_offset: usize,
|
||||
}
|
||||
|
||||
impl<'a> BinaryWidget<'a> {
|
||||
pub fn new(data: &'a [u8], opts: BinarySettings, style: BinaryStyle) -> Self {
|
||||
Self { data, opts, style }
|
||||
Self {
|
||||
data,
|
||||
opts,
|
||||
style,
|
||||
row_offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn count_lines(&self) -> usize {
|
||||
|
@ -35,37 +41,22 @@ impl<'a> BinaryWidget<'a> {
|
|||
self.opts.count_segments * self.opts.segment_size
|
||||
}
|
||||
|
||||
pub fn set_index_offset(&mut self, offset: usize) {
|
||||
self.opts.index_offset = offset;
|
||||
pub fn set_row_offset(&mut self, offset: usize) {
|
||||
self.row_offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct BinarySettings {
|
||||
disable_index: bool,
|
||||
disable_ascii: bool,
|
||||
disable_data: bool,
|
||||
segment_size: usize,
|
||||
count_segments: usize,
|
||||
index_offset: usize,
|
||||
}
|
||||
|
||||
impl BinarySettings {
|
||||
pub fn new(
|
||||
disable_index: bool,
|
||||
disable_ascii: bool,
|
||||
disable_data: bool,
|
||||
segment_size: usize,
|
||||
count_segments: usize,
|
||||
index_offset: usize,
|
||||
) -> Self {
|
||||
pub fn new(segment_size: usize, count_segments: usize) -> Self {
|
||||
Self {
|
||||
disable_index,
|
||||
disable_ascii,
|
||||
disable_data,
|
||||
segment_size,
|
||||
count_segments,
|
||||
index_offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +66,6 @@ pub struct BinaryStyle {
|
|||
color_index: Option<NuStyle>,
|
||||
column_padding_left: u16,
|
||||
column_padding_right: u16,
|
||||
show_split: bool,
|
||||
}
|
||||
|
||||
impl BinaryStyle {
|
||||
|
@ -83,13 +73,11 @@ impl BinaryStyle {
|
|||
color_index: Option<NuStyle>,
|
||||
column_padding_left: u16,
|
||||
column_padding_right: u16,
|
||||
show_split: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
color_index,
|
||||
column_padding_left,
|
||||
column_padding_right,
|
||||
show_split,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,10 +90,6 @@ impl Widget for BinaryWidget<'_> {
|
|||
return;
|
||||
}
|
||||
|
||||
if self.opts.disable_index && self.opts.disable_data && self.opts.disable_ascii {
|
||||
return;
|
||||
}
|
||||
|
||||
render_hexdump(area, buf, self);
|
||||
}
|
||||
}
|
||||
|
@ -114,11 +98,6 @@ impl Widget for BinaryWidget<'_> {
|
|||
fn render_hexdump(area: Rect, buf: &mut Buffer, w: BinaryWidget) {
|
||||
const MIN_INDEX_SIZE: usize = 8;
|
||||
|
||||
let show_index = !w.opts.disable_index;
|
||||
let show_data = !w.opts.disable_data;
|
||||
let show_ascii = !w.opts.disable_ascii;
|
||||
let show_split = w.style.show_split;
|
||||
|
||||
let index_width = get_max_index_size(&w).max(MIN_INDEX_SIZE) as u16; // safe as it's checked before hand that we have enough space
|
||||
|
||||
let mut last_line = None;
|
||||
|
@ -126,7 +105,7 @@ fn render_hexdump(area: Rect, buf: &mut Buffer, w: BinaryWidget) {
|
|||
for line in 0..area.height {
|
||||
let data_line_length = w.opts.count_segments * w.opts.segment_size;
|
||||
let start_index = line as usize * data_line_length;
|
||||
let address = w.opts.index_offset + start_index;
|
||||
let address = w.row_offset + start_index;
|
||||
|
||||
if start_index > w.data.len() {
|
||||
last_line = Some(line);
|
||||
|
@ -137,31 +116,24 @@ fn render_hexdump(area: Rect, buf: &mut Buffer, w: BinaryWidget) {
|
|||
let y = line;
|
||||
let line = &w.data[start_index..];
|
||||
|
||||
if show_index {
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_left);
|
||||
x += render_hex_usize(buf, x, y, address, index_width, get_index_style(&w));
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_right);
|
||||
}
|
||||
// index column
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_left);
|
||||
x += render_hex_usize(buf, x, y, address, index_width, get_index_style(&w));
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_right);
|
||||
|
||||
if show_split {
|
||||
x += render_split(buf, x, y);
|
||||
}
|
||||
x += render_vertical_split(buf, x, y);
|
||||
|
||||
if show_data {
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_left);
|
||||
x += render_data_line(buf, x, y, line, &w);
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_right);
|
||||
}
|
||||
// data/hex column
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_left);
|
||||
x += render_data_line(buf, x, y, line, &w);
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_right);
|
||||
|
||||
if show_split {
|
||||
x += render_split(buf, x, y);
|
||||
}
|
||||
x += render_vertical_split(buf, x, y);
|
||||
|
||||
if show_ascii {
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_left);
|
||||
x += render_ascii_line(buf, x, y, line, &w);
|
||||
render_space(buf, x, y, 1, w.style.column_padding_right);
|
||||
}
|
||||
// ASCII column
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_left);
|
||||
x += render_ascii_line(buf, x, y, line, &w);
|
||||
render_space(buf, x, y, 1, w.style.column_padding_right);
|
||||
}
|
||||
|
||||
let data_line_size = (w.opts.count_segments * (w.opts.segment_size * 2)
|
||||
|
@ -172,36 +144,29 @@ fn render_hexdump(area: Rect, buf: &mut Buffer, w: BinaryWidget) {
|
|||
for line in last_line..area.height {
|
||||
let data_line_length = w.opts.count_segments * w.opts.segment_size;
|
||||
let start_index = line as usize * data_line_length;
|
||||
let address = w.opts.index_offset + start_index;
|
||||
let address = w.row_offset + start_index;
|
||||
|
||||
let mut x = 0;
|
||||
let y = line;
|
||||
|
||||
if show_index {
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_left);
|
||||
x += render_hex_usize(buf, x, y, address, index_width, get_index_style(&w));
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_right);
|
||||
}
|
||||
// index column
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_left);
|
||||
x += render_hex_usize(buf, x, y, address, index_width, get_index_style(&w));
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_right);
|
||||
|
||||
if show_split {
|
||||
x += render_split(buf, x, y);
|
||||
}
|
||||
x += render_vertical_split(buf, x, y);
|
||||
|
||||
if show_data {
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_left);
|
||||
x += render_space(buf, x, y, 1, data_line_size);
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_right);
|
||||
}
|
||||
// data/hex column
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_left);
|
||||
x += render_space(buf, x, y, 1, data_line_size);
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_right);
|
||||
|
||||
if show_split {
|
||||
x += render_split(buf, x, y);
|
||||
}
|
||||
x += render_vertical_split(buf, x, y);
|
||||
|
||||
if show_ascii {
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_left);
|
||||
x += render_space(buf, x, y, 1, ascii_line_size);
|
||||
render_space(buf, x, y, 1, w.style.column_padding_right);
|
||||
}
|
||||
// ASCII column
|
||||
x += render_space(buf, x, y, 1, w.style.column_padding_left);
|
||||
x += render_space(buf, x, y, 1, ascii_line_size);
|
||||
render_space(buf, x, y, 1, w.style.column_padding_right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -336,12 +301,15 @@ fn get_index_style(w: &BinaryWidget) -> Option<NuStyle> {
|
|||
w.style.color_index
|
||||
}
|
||||
|
||||
fn render_space(buf: &mut Buffer, x: u16, y: u16, height: u16, padding: u16) -> u16 {
|
||||
repeat_vertical(buf, x, y, padding, height, ' ', TextStyle::default());
|
||||
padding
|
||||
/// Render blank characters starting at the given position, going right `width` characters and down `height` characters.
|
||||
/// Returns `width` for convenience.
|
||||
fn render_space(buf: &mut Buffer, x: u16, y: u16, height: u16, width: u16) -> u16 {
|
||||
repeat_vertical(buf, x, y, width, height, ' ', TextStyle::default());
|
||||
width
|
||||
}
|
||||
|
||||
fn render_split(buf: &mut Buffer, x: u16, y: u16) -> u16 {
|
||||
/// Render a vertical split (│) at the given position. Returns the width of the split (always 1) for convenience.
|
||||
fn render_vertical_split(buf: &mut Buffer, x: u16, y: u16) -> u16 {
|
||||
repeat_vertical(buf, x, y, 1, 1, '│', TextStyle::default());
|
||||
1
|
||||
}
|
||||
|
@ -369,7 +337,7 @@ fn repeat_vertical(
|
|||
fn get_max_index_size(w: &BinaryWidget) -> usize {
|
||||
let line_size = w.opts.count_segments * (w.opts.segment_size * 2);
|
||||
let count_lines = w.data.len() / line_size;
|
||||
let max_index = w.opts.index_offset + count_lines * line_size;
|
||||
let max_index = w.row_offset + count_lines * line_size;
|
||||
usize_to_hex(max_index, 0).len()
|
||||
}
|
||||
|
||||
|
@ -379,7 +347,7 @@ fn get_widget_width(w: &BinaryWidget) -> usize {
|
|||
let line_size = w.opts.count_segments * (w.opts.segment_size * 2);
|
||||
let count_lines = w.data.len() / line_size;
|
||||
|
||||
let max_index = w.opts.index_offset + count_lines * line_size;
|
||||
let max_index = w.row_offset + count_lines * line_size;
|
||||
let index_size = usize_to_hex(max_index, 0).len();
|
||||
let index_size = index_size.max(MIN_INDEX_SIZE);
|
||||
|
||||
|
@ -388,17 +356,16 @@ fn get_widget_width(w: &BinaryWidget) -> usize {
|
|||
|
||||
let ascii_size = w.opts.count_segments * w.opts.segment_size;
|
||||
|
||||
let split = w.style.show_split as usize;
|
||||
#[allow(clippy::identity_op)]
|
||||
let min_width = 0
|
||||
+ w.style.column_padding_left as usize
|
||||
+ index_size
|
||||
+ w.style.column_padding_right as usize
|
||||
+ split
|
||||
+ 1 // split
|
||||
+ w.style.column_padding_left as usize
|
||||
+ data_size
|
||||
+ w.style.column_padding_right as usize
|
||||
+ split
|
||||
+ 1 //split
|
||||
+ w.style.column_padding_left as usize
|
||||
+ ascii_size
|
||||
+ w.style.column_padding_right as usize;
|
||||
|
|
|
@ -107,7 +107,7 @@ fn create_binary_widget(v: &BinaryView) -> BinaryWidget<'_> {
|
|||
let data = &v.data[index..];
|
||||
|
||||
let mut w = BinaryWidget::new(data, v.settings.opts, v.settings.style.clone());
|
||||
w.set_index_offset(index);
|
||||
w.set_row_offset(index);
|
||||
|
||||
w
|
||||
}
|
||||
|
@ -184,29 +184,17 @@ fn settings_from_config(config: &ConfigMap) -> Settings {
|
|||
|
||||
Settings {
|
||||
opts: BinarySettings::new(
|
||||
!config_get_bool(config, "show_index", true),
|
||||
!config_get_bool(config, "show_ascii", true),
|
||||
!config_get_bool(config, "show_data", true),
|
||||
config_get_usize(config, "segment_size", 2),
|
||||
config_get_usize(config, "count_segments", 8),
|
||||
0,
|
||||
),
|
||||
style: BinaryStyle::new(
|
||||
colors.get("color_index").cloned(),
|
||||
config_get_usize(config, "column_padding_left", 1) as u16,
|
||||
config_get_usize(config, "column_padding_right", 1) as u16,
|
||||
config_get_bool(config, "split", false),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn config_get_bool(config: &ConfigMap, key: &str, default: bool) -> bool {
|
||||
config
|
||||
.get(key)
|
||||
.and_then(|v| v.as_bool().ok())
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
fn config_get_usize(config: &ConfigMap, key: &str, default: usize) -> usize {
|
||||
config
|
||||
.get(key)
|
||||
|
|
|
@ -8,24 +8,27 @@ use ratatui::{
|
|||
widgets::Widget,
|
||||
};
|
||||
|
||||
pub struct ColoredTextW<'a> {
|
||||
/// A widget that represents a single line of text with ANSI styles.
|
||||
pub struct ColoredTextWidget<'a> {
|
||||
text: &'a str,
|
||||
/// Column to start rendering from
|
||||
col: usize,
|
||||
}
|
||||
|
||||
impl<'a> ColoredTextW<'a> {
|
||||
impl<'a> ColoredTextWidget<'a> {
|
||||
pub fn new(text: &'a str, col: usize) -> Self {
|
||||
Self { text, col }
|
||||
}
|
||||
|
||||
pub fn what(&self, area: Rect) -> String {
|
||||
cut_string(self.text, self.col, area.width as usize)
|
||||
/// Return a window of the text that fits into the given width, with ANSI styles stripped.
|
||||
pub fn get_plain_text(&self, max_width: usize) -> String {
|
||||
cut_string(self.text, self.col, max_width)
|
||||
.ansi_strip()
|
||||
.into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for ColoredTextW<'_> {
|
||||
impl Widget for ColoredTextWidget<'_> {
|
||||
fn render(self, area: Rect, buf: &mut ratatui::buffer::Buffer) {
|
||||
let text = cut_string(self.text, self.col, area.width as usize);
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
mod binary;
|
||||
mod coloredtextw;
|
||||
mod colored_text_widget;
|
||||
mod cursor;
|
||||
mod interactive;
|
||||
mod preview;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::{coloredtextw::ColoredTextW, cursor::XYCursor, Layout, View, ViewConfig};
|
||||
use super::{colored_text_widget::ColoredTextWidget, cursor::XYCursor, Layout, View, ViewConfig};
|
||||
use crate::{
|
||||
nu_common::{NuSpan, NuText},
|
||||
pager::{report::Report, Frame, Transition, ViewInfo},
|
||||
|
@ -43,13 +43,14 @@ impl View for Preview {
|
|||
|
||||
let lines = &self.lines[self.cursor.row_starts_at()..];
|
||||
for (i, line) in lines.iter().enumerate().take(area.height as usize) {
|
||||
let text = ColoredTextW::new(line, self.cursor.column());
|
||||
let s = text.what(area);
|
||||
let text_widget = ColoredTextWidget::new(line, self.cursor.column());
|
||||
let plain_text = text_widget.get_plain_text(area.width as usize);
|
||||
|
||||
let area = Rect::new(area.x, area.y + i as u16, area.width, 1);
|
||||
f.render_widget(text, area);
|
||||
f.render_widget(text_widget, area);
|
||||
|
||||
layout.push(&s, area.x, area.y, area.width, area.height);
|
||||
// push the plain text to layout so it can be searched
|
||||
layout.push(&plain_text, area.x, area.y, area.width, area.height);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
mod tablew;
|
||||
mod table_widget;
|
||||
|
||||
use self::tablew::{TableStyle, TableW, TableWState};
|
||||
use self::table_widget::{TableStyle, TableWidget, TableWidgetState};
|
||||
use super::{
|
||||
cursor::XYCursor,
|
||||
util::{make_styled_string, nu_style_to_tui},
|
||||
|
@ -25,7 +25,7 @@ use nu_protocol::{
|
|||
use ratatui::{layout::Rect, widgets::Block};
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
|
||||
pub use self::tablew::Orientation;
|
||||
pub use self::table_widget::Orientation;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RecordView<'a> {
|
||||
|
@ -175,7 +175,7 @@ impl<'a> RecordView<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn create_tablew(&'a self, cfg: ViewConfig<'a>) -> TableW<'a> {
|
||||
fn create_tablew(&'a self, cfg: ViewConfig<'a>) -> TableWidget<'a> {
|
||||
let layer = self.get_layer_last();
|
||||
let mut data = convert_records_to_string(&layer.records, cfg.nu_config, cfg.style_computer);
|
||||
|
||||
|
@ -185,7 +185,7 @@ impl<'a> RecordView<'a> {
|
|||
let style_computer = cfg.style_computer;
|
||||
let (row, column) = self.get_current_offset();
|
||||
|
||||
TableW::new(
|
||||
TableWidget::new(
|
||||
headers,
|
||||
data,
|
||||
style_computer,
|
||||
|
@ -225,7 +225,7 @@ impl<'a> RecordView<'a> {
|
|||
|
||||
impl View for RecordView<'_> {
|
||||
fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) {
|
||||
let mut table_layout = TableWState::default();
|
||||
let mut table_layout = TableWidgetState::default();
|
||||
let table = self.create_tablew(cfg);
|
||||
f.render_stateful_widget(table, area, &mut table_layout);
|
||||
|
||||
|
|
|
@ -18,13 +18,13 @@ use std::{
|
|||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TableW<'a> {
|
||||
pub struct TableWidget<'a> {
|
||||
columns: Cow<'a, [String]>,
|
||||
data: Cow<'a, [Vec<NuText>]>,
|
||||
index_row: usize,
|
||||
index_column: usize,
|
||||
style: TableStyle,
|
||||
head_position: Orientation,
|
||||
header_position: Orientation,
|
||||
style_computer: &'a StyleComputer<'a>,
|
||||
}
|
||||
|
||||
|
@ -38,14 +38,13 @@ pub enum Orientation {
|
|||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct TableStyle {
|
||||
pub splitline_style: NuStyle,
|
||||
pub shift_line_style: NuStyle,
|
||||
pub show_index: bool,
|
||||
pub show_header: bool,
|
||||
pub column_padding_left: usize,
|
||||
pub column_padding_right: usize,
|
||||
}
|
||||
|
||||
impl<'a> TableW<'a> {
|
||||
impl<'a> TableWidget<'a> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
columns: impl Into<Cow<'a, [String]>>,
|
||||
|
@ -54,7 +53,7 @@ impl<'a> TableW<'a> {
|
|||
index_row: usize,
|
||||
index_column: usize,
|
||||
style: TableStyle,
|
||||
head_position: Orientation,
|
||||
header_position: Orientation,
|
||||
) -> Self {
|
||||
Self {
|
||||
columns: columns.into(),
|
||||
|
@ -63,21 +62,21 @@ impl<'a> TableW<'a> {
|
|||
index_row,
|
||||
index_column,
|
||||
style,
|
||||
head_position,
|
||||
header_position,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TableWState {
|
||||
pub struct TableWidgetState {
|
||||
pub layout: Layout,
|
||||
pub count_rows: usize,
|
||||
pub count_columns: usize,
|
||||
pub data_height: u16,
|
||||
}
|
||||
|
||||
impl StatefulWidget for TableW<'_> {
|
||||
type State = TableWState;
|
||||
impl StatefulWidget for TableWidget<'_> {
|
||||
type State = TableWidgetState;
|
||||
|
||||
fn render(
|
||||
self,
|
||||
|
@ -89,7 +88,7 @@ impl StatefulWidget for TableW<'_> {
|
|||
return;
|
||||
}
|
||||
|
||||
let is_horizontal = matches!(self.head_position, Orientation::Top);
|
||||
let is_horizontal = matches!(self.header_position, Orientation::Top);
|
||||
if is_horizontal {
|
||||
self.render_table_horizontal(area, buf, state);
|
||||
} else {
|
||||
|
@ -99,8 +98,8 @@ impl StatefulWidget for TableW<'_> {
|
|||
}
|
||||
|
||||
// todo: refactoring these to methods as they have quite a bit in common.
|
||||
impl<'a> TableW<'a> {
|
||||
fn render_table_horizontal(self, area: Rect, buf: &mut Buffer, state: &mut TableWState) {
|
||||
impl<'a> TableWidget<'a> {
|
||||
fn render_table_horizontal(self, area: Rect, buf: &mut Buffer, state: &mut TableWidgetState) {
|
||||
let padding_l = self.style.column_padding_left as u16;
|
||||
let padding_r = self.style.column_padding_right as u16;
|
||||
|
||||
|
@ -108,7 +107,6 @@ impl<'a> TableW<'a> {
|
|||
let show_head = self.style.show_header;
|
||||
|
||||
let splitline_s = self.style.splitline_style;
|
||||
let shift_column_s = self.style.shift_line_style;
|
||||
|
||||
let mut data_height = area.height;
|
||||
let mut data_y = area.y;
|
||||
|
@ -164,7 +162,8 @@ impl<'a> TableW<'a> {
|
|||
);
|
||||
}
|
||||
|
||||
let mut do_render_shift_column = false;
|
||||
// if there is more data than we can show, add an ellipsis to the column headers to hint at that
|
||||
let mut show_overflow_indicator = false;
|
||||
state.count_rows = data.len();
|
||||
state.count_columns = 0;
|
||||
state.data_height = data_height;
|
||||
|
@ -191,11 +190,11 @@ impl<'a> TableW<'a> {
|
|||
|
||||
let pad = padding_l + padding_r;
|
||||
let head = show_head.then_some(&mut head);
|
||||
let (w, ok, shift) =
|
||||
let (w, ok, overflow) =
|
||||
truncate_column_width(space, 1, use_space, pad, is_last, &mut column, head);
|
||||
|
||||
if shift {
|
||||
do_render_shift_column = true;
|
||||
if overflow {
|
||||
show_overflow_indicator = true;
|
||||
}
|
||||
|
||||
if w == 0 && !ok {
|
||||
|
@ -232,14 +231,14 @@ impl<'a> TableW<'a> {
|
|||
|
||||
state.count_columns += 1;
|
||||
|
||||
if do_render_shift_column {
|
||||
if show_overflow_indicator {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if do_render_shift_column && show_head {
|
||||
if show_overflow_indicator && show_head {
|
||||
width += render_space(buf, width, data_y, data_height, padding_l);
|
||||
width += render_shift_column(buf, width, head_y, 1, shift_column_s);
|
||||
width += render_overflow_column(buf, width, head_y, 1);
|
||||
width += render_space(buf, width, data_y, data_height, padding_r);
|
||||
}
|
||||
|
||||
|
@ -264,7 +263,7 @@ impl<'a> TableW<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_table_vertical(self, area: Rect, buf: &mut Buffer, state: &mut TableWState) {
|
||||
fn render_table_vertical(self, area: Rect, buf: &mut Buffer, state: &mut TableWidgetState) {
|
||||
if area.width == 0 || area.height == 0 {
|
||||
return;
|
||||
}
|
||||
|
@ -275,7 +274,6 @@ impl<'a> TableW<'a> {
|
|||
let show_index = self.style.show_index;
|
||||
let show_head = self.style.show_header;
|
||||
let splitline_s = self.style.splitline_style;
|
||||
let shift_column_s = self.style.shift_line_style;
|
||||
|
||||
let mut left_w = 0;
|
||||
|
||||
|
@ -358,7 +356,8 @@ impl<'a> TableW<'a> {
|
|||
);
|
||||
}
|
||||
|
||||
let mut do_render_shift_column = false;
|
||||
// if there is more data than we can show, add an ellipsis to the column headers to hint at that
|
||||
let mut show_overflow_indicator = false;
|
||||
|
||||
state.count_rows = columns.len();
|
||||
state.count_columns = 0;
|
||||
|
@ -375,11 +374,11 @@ impl<'a> TableW<'a> {
|
|||
let available = area.width - left_w;
|
||||
let is_last = col + 1 == self.data.len();
|
||||
let pad = padding_l + padding_r;
|
||||
let (column_width, ok, shift) =
|
||||
let (column_width, ok, overflow) =
|
||||
truncate_column_width(available, 1, column_width, pad, is_last, &mut column, None);
|
||||
|
||||
if shift {
|
||||
do_render_shift_column = true;
|
||||
if overflow {
|
||||
show_overflow_indicator = true;
|
||||
}
|
||||
|
||||
if column_width == 0 && !ok {
|
||||
|
@ -403,16 +402,16 @@ impl<'a> TableW<'a> {
|
|||
state.count_columns += 1;
|
||||
}
|
||||
|
||||
if do_render_shift_column {
|
||||
if show_overflow_indicator {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if do_render_shift_column {
|
||||
if show_overflow_indicator {
|
||||
let x = area.x + left_w;
|
||||
left_w += render_space(buf, x, area.y, area.height, padding_l);
|
||||
let x = area.x + left_w;
|
||||
left_w += render_shift_column(buf, x, area.y, area.height, shift_column_s);
|
||||
left_w += render_overflow_column(buf, x, area.y, area.height);
|
||||
let x = area.x + left_w;
|
||||
left_w += render_space(buf, x, area.y, area.height, padding_r);
|
||||
}
|
||||
|
@ -433,13 +432,13 @@ fn truncate_column_width(
|
|||
) -> (u16, bool, bool) {
|
||||
let result = check_column_width(space, min, w, pad, is_last);
|
||||
|
||||
let (width, shift_column) = match result {
|
||||
let (width, overflow) = match result {
|
||||
Some(result) => result,
|
||||
None => return (w, true, false),
|
||||
};
|
||||
|
||||
if width == 0 {
|
||||
return (0, false, shift_column);
|
||||
return (0, false, overflow);
|
||||
}
|
||||
|
||||
truncate_list(column, width as usize);
|
||||
|
@ -447,7 +446,7 @@ fn truncate_column_width(
|
|||
truncate_str(head, width as usize);
|
||||
}
|
||||
|
||||
(width, false, shift_column)
|
||||
(width, false, overflow)
|
||||
}
|
||||
|
||||
fn check_column_width(
|
||||
|
@ -652,10 +651,11 @@ fn truncate_list(list: &mut [NuText], width: usize) {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_shift_column(buf: &mut Buffer, x: u16, y: u16, height: u16, style: NuStyle) -> u16 {
|
||||
/// Render a column with an ellipsis in the header to indicate that there is more data than can be displayed
|
||||
fn render_overflow_column(buf: &mut Buffer, x: u16, y: u16, height: u16) -> u16 {
|
||||
let style = TextStyle {
|
||||
alignment: Alignment::Left,
|
||||
color_style: Some(style),
|
||||
color_style: None,
|
||||
};
|
||||
|
||||
repeat_vertical(buf, x, y, 1, height, '…', style);
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "nu-glob"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
authors = ["The Nushell Project Developers", "The Rust Project Developers"]
|
||||
license = "MIT/Apache-2.0"
|
||||
description = """
|
||||
|
@ -14,4 +14,4 @@ categories = ["filesystem"]
|
|||
bench = false
|
||||
|
||||
[dev-dependencies]
|
||||
doc-comment = "0.3"
|
||||
doc-comment = "0.3"
|
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-json"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
@ -23,5 +23,5 @@ serde = { workspace = true }
|
|||
serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# nu-path = { path="../nu-path", version = "0.93.1" }
|
||||
# serde_json = "1.0"
|
||||
# nu-path = { path="../nu-path", version = "0.94.1" }
|
||||
# serde_json = "1.0"
|
|
@ -3,14 +3,14 @@ authors = ["The Nushell Project Developers"]
|
|||
description = "Nushell's integrated LSP server"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-lsp"
|
||||
name = "nu-lsp"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { path = "../nu-cli", version = "0.93.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.93.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1" }
|
||||
nu-cli = { path = "../nu-cli", version = "0.94.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1" }
|
||||
|
||||
reedline = { workspace = true }
|
||||
|
||||
|
@ -23,8 +23,8 @@ serde = { workspace = true }
|
|||
serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.93.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.93.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.93.1" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.94.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.1" }
|
||||
|
||||
assert-json-diff = "2.0"
|
||||
assert-json-diff = "2.0"
|
|
@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-parser"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-parser"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
exclude = ["/fuzz"]
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.93.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.93.1" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.93.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.1" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.94.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1" }
|
||||
|
||||
bytesize = { workspace = true }
|
||||
chrono = { default-features = false, features = ['std'], workspace = true }
|
||||
|
@ -27,4 +27,4 @@ serde_json = { workspace = true }
|
|||
rstest = { workspace = true, default-features = false }
|
||||
|
||||
[features]
|
||||
plugin = ["nu-plugin-engine"]
|
||||
plugin = ["nu-plugin-engine"]
|
|
@ -26,4 +26,4 @@ debug = 1
|
|||
name = "parse"
|
||||
path = "fuzz_targets/parse.rs"
|
||||
test = false
|
||||
doc = false
|
||||
doc = false
|
|
@ -51,12 +51,21 @@ impl LiteCommand {
|
|||
self.parts.push(span);
|
||||
}
|
||||
|
||||
fn check_accepts_redirection(&self, span: Span) -> Option<ParseError> {
|
||||
self.parts
|
||||
.is_empty()
|
||||
.then_some(ParseError::UnexpectedRedirection { span })
|
||||
}
|
||||
|
||||
fn try_add_redirection(
|
||||
&mut self,
|
||||
source: RedirectionSource,
|
||||
target: LiteRedirectionTarget,
|
||||
) -> Result<(), ParseError> {
|
||||
let redirection = match (self.redirection.take(), source) {
|
||||
(None, _) if self.parts.is_empty() => Err(ParseError::UnexpectedRedirection {
|
||||
span: target.connector(),
|
||||
}),
|
||||
(None, source) => Ok(LiteRedirection::Single { source, target }),
|
||||
(
|
||||
Some(LiteRedirection::Single {
|
||||
|
@ -162,94 +171,59 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
|
|||
|
||||
for (idx, token) in tokens.iter().enumerate() {
|
||||
if let Some((source, append, span)) = file_redirection.take() {
|
||||
if command.parts.is_empty() {
|
||||
error = error.or(Some(ParseError::LabeledError(
|
||||
"Redirection without command or expression".into(),
|
||||
"there is nothing to redirect".into(),
|
||||
span,
|
||||
)));
|
||||
|
||||
command.push(span);
|
||||
|
||||
match token.contents {
|
||||
TokenContents::Comment => {
|
||||
command.comments.push(token.span);
|
||||
curr_comment = None;
|
||||
}
|
||||
TokenContents::Pipe
|
||||
| TokenContents::ErrGreaterPipe
|
||||
| TokenContents::OutErrGreaterPipe => {
|
||||
pipeline.push(&mut command);
|
||||
command.pipe = Some(token.span);
|
||||
}
|
||||
TokenContents::Semicolon => {
|
||||
pipeline.push(&mut command);
|
||||
block.push(&mut pipeline);
|
||||
}
|
||||
TokenContents::Eol => {
|
||||
pipeline.push(&mut command);
|
||||
}
|
||||
_ => command.push(token.span),
|
||||
match &token.contents {
|
||||
TokenContents::PipePipe => {
|
||||
error = error.or(Some(ParseError::ShellOrOr(token.span)));
|
||||
command.push(span);
|
||||
command.push(token.span);
|
||||
}
|
||||
} else {
|
||||
match &token.contents {
|
||||
TokenContents::PipePipe => {
|
||||
error = error.or(Some(ParseError::ShellOrOr(token.span)));
|
||||
TokenContents::Item => {
|
||||
let target = LiteRedirectionTarget::File {
|
||||
connector: span,
|
||||
file: token.span,
|
||||
append,
|
||||
};
|
||||
if let Err(err) = command.try_add_redirection(source, target) {
|
||||
error = error.or(Some(err));
|
||||
command.push(span);
|
||||
command.push(token.span);
|
||||
}
|
||||
TokenContents::Item => {
|
||||
let target = LiteRedirectionTarget::File {
|
||||
connector: span,
|
||||
file: token.span,
|
||||
append,
|
||||
};
|
||||
if let Err(err) = command.try_add_redirection(source, target) {
|
||||
error = error.or(Some(err));
|
||||
command.push(span);
|
||||
command.push(token.span)
|
||||
}
|
||||
}
|
||||
TokenContents::OutGreaterThan
|
||||
| TokenContents::OutGreaterGreaterThan
|
||||
| TokenContents::ErrGreaterThan
|
||||
| TokenContents::ErrGreaterGreaterThan
|
||||
| TokenContents::OutErrGreaterThan
|
||||
| TokenContents::OutErrGreaterGreaterThan => {
|
||||
error =
|
||||
error.or(Some(ParseError::Expected("redirection target", token.span)));
|
||||
command.push(span);
|
||||
command.push(token.span);
|
||||
}
|
||||
TokenContents::Pipe
|
||||
| TokenContents::ErrGreaterPipe
|
||||
| TokenContents::OutErrGreaterPipe => {
|
||||
error =
|
||||
error.or(Some(ParseError::Expected("redirection target", token.span)));
|
||||
command.push(span);
|
||||
pipeline.push(&mut command);
|
||||
command.pipe = Some(token.span);
|
||||
}
|
||||
TokenContents::Eol => {
|
||||
error =
|
||||
error.or(Some(ParseError::Expected("redirection target", token.span)));
|
||||
command.push(span);
|
||||
pipeline.push(&mut command);
|
||||
}
|
||||
TokenContents::Semicolon => {
|
||||
error =
|
||||
error.or(Some(ParseError::Expected("redirection target", token.span)));
|
||||
command.push(span);
|
||||
pipeline.push(&mut command);
|
||||
block.push(&mut pipeline);
|
||||
}
|
||||
TokenContents::Comment => {
|
||||
error = error.or(Some(ParseError::Expected("redirection target", span)));
|
||||
command.push(span);
|
||||
command.comments.push(token.span);
|
||||
curr_comment = None;
|
||||
command.push(token.span)
|
||||
}
|
||||
}
|
||||
TokenContents::OutGreaterThan
|
||||
| TokenContents::OutGreaterGreaterThan
|
||||
| TokenContents::ErrGreaterThan
|
||||
| TokenContents::ErrGreaterGreaterThan
|
||||
| TokenContents::OutErrGreaterThan
|
||||
| TokenContents::OutErrGreaterGreaterThan => {
|
||||
error = error.or(Some(ParseError::Expected("redirection target", token.span)));
|
||||
command.push(span);
|
||||
command.push(token.span);
|
||||
}
|
||||
TokenContents::Pipe
|
||||
| TokenContents::ErrGreaterPipe
|
||||
| TokenContents::OutErrGreaterPipe => {
|
||||
error = error.or(Some(ParseError::Expected("redirection target", token.span)));
|
||||
command.push(span);
|
||||
pipeline.push(&mut command);
|
||||
command.pipe = Some(token.span);
|
||||
}
|
||||
TokenContents::Eol => {
|
||||
error = error.or(Some(ParseError::Expected("redirection target", token.span)));
|
||||
command.push(span);
|
||||
pipeline.push(&mut command);
|
||||
}
|
||||
TokenContents::Semicolon => {
|
||||
error = error.or(Some(ParseError::Expected("redirection target", token.span)));
|
||||
command.push(span);
|
||||
pipeline.push(&mut command);
|
||||
block.push(&mut pipeline);
|
||||
}
|
||||
TokenContents::Comment => {
|
||||
error = error.or(Some(ParseError::Expected("redirection target", span)));
|
||||
command.push(span);
|
||||
command.comments.push(token.span);
|
||||
curr_comment = None;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match &token.contents {
|
||||
|
@ -278,22 +252,28 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
|
|||
command.push(token.span);
|
||||
}
|
||||
TokenContents::OutGreaterThan => {
|
||||
error = error.or(command.check_accepts_redirection(token.span));
|
||||
file_redirection = Some((RedirectionSource::Stdout, false, token.span));
|
||||
}
|
||||
TokenContents::OutGreaterGreaterThan => {
|
||||
error = error.or(command.check_accepts_redirection(token.span));
|
||||
file_redirection = Some((RedirectionSource::Stdout, true, token.span));
|
||||
}
|
||||
TokenContents::ErrGreaterThan => {
|
||||
error = error.or(command.check_accepts_redirection(token.span));
|
||||
file_redirection = Some((RedirectionSource::Stderr, false, token.span));
|
||||
}
|
||||
TokenContents::ErrGreaterGreaterThan => {
|
||||
error = error.or(command.check_accepts_redirection(token.span));
|
||||
file_redirection = Some((RedirectionSource::Stderr, true, token.span));
|
||||
}
|
||||
TokenContents::OutErrGreaterThan => {
|
||||
error = error.or(command.check_accepts_redirection(token.span));
|
||||
file_redirection =
|
||||
Some((RedirectionSource::StdoutAndStderr, false, token.span));
|
||||
}
|
||||
TokenContents::OutErrGreaterGreaterThan => {
|
||||
error = error.or(command.check_accepts_redirection(token.span));
|
||||
file_redirection = Some((RedirectionSource::StdoutAndStderr, true, token.span));
|
||||
}
|
||||
TokenContents::ErrGreaterPipe => {
|
||||
|
|
|
@ -278,13 +278,14 @@ pub fn parse_external_call(working_set: &mut StateWorkingSet, spans: &[Span]) ->
|
|||
let arg = parse_expression(working_set, &[head_span]);
|
||||
Box::new(arg)
|
||||
} else {
|
||||
let (contents, err) = unescape_unquote_string(&head_contents, head_span);
|
||||
// Eval stage will unquote the string, so we don't bother with that here
|
||||
let (contents, err) = unescape_string(&head_contents, head_span);
|
||||
if let Some(err) = err {
|
||||
working_set.error(err)
|
||||
}
|
||||
|
||||
Box::new(Expression {
|
||||
expr: Expr::String(contents),
|
||||
expr: Expr::String(String::from_utf8_lossy(&contents).into_owned()),
|
||||
span: head_span,
|
||||
ty: Type::String,
|
||||
custom_completion: None,
|
||||
|
|
|
@ -727,6 +727,45 @@ fn test_redirection_with_letmut(#[case] phase: &[u8]) {
|
|||
));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(b"o>")]
|
||||
#[case(b"o>>")]
|
||||
#[case(b"e>")]
|
||||
#[case(b"e>>")]
|
||||
#[case(b"o+e>")]
|
||||
#[case(b"o+e>>")]
|
||||
#[case(b"e>|")]
|
||||
#[case(b"o+e>|")]
|
||||
#[case(b"|o>")]
|
||||
#[case(b"|o>>")]
|
||||
#[case(b"|e>")]
|
||||
#[case(b"|e>>")]
|
||||
#[case(b"|o+e>")]
|
||||
#[case(b"|o+e>>")]
|
||||
#[case(b"|e>|")]
|
||||
#[case(b"|o+e>|")]
|
||||
#[case(b"e> file")]
|
||||
#[case(b"e>> file")]
|
||||
#[case(b"o> file")]
|
||||
#[case(b"o>> file")]
|
||||
#[case(b"o+e> file")]
|
||||
#[case(b"o+e>> file")]
|
||||
#[case(b"|e> file")]
|
||||
#[case(b"|e>> file")]
|
||||
#[case(b"|o> file")]
|
||||
#[case(b"|o>> file")]
|
||||
#[case(b"|o+e> file")]
|
||||
#[case(b"|o+e>> file")]
|
||||
fn test_redirecting_nothing(#[case] text: &[u8]) {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
let _ = parse(&mut working_set, None, text, true);
|
||||
assert!(matches!(
|
||||
working_set.parse_errors.first(),
|
||||
Some(ParseError::UnexpectedRedirection { .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nothing_comparison_neq() {
|
||||
let engine_state = EngineState::new();
|
||||
|
|
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-path"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-path"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
exclude = ["/fuzz"]
|
||||
|
||||
[lib]
|
||||
|
@ -18,4 +18,4 @@ dirs-next = { workspace = true }
|
|||
omnipath = { workspace = true }
|
||||
|
||||
[target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "android")))'.dependencies]
|
||||
pwd = { workspace = true }
|
||||
pwd = { workspace = true }
|
|
@ -24,4 +24,4 @@ debug = 1
|
|||
name = "path"
|
||||
path = "fuzz_targets/path_fuzzer.rs"
|
||||
test = false
|
||||
doc = false
|
||||
doc = false
|
|
@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-core
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-plugin-core"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1" }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.93.1", default-features = false }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1" }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.94.1", default-features = false }
|
||||
|
||||
rmp-serde = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
@ -25,4 +25,4 @@ default = ["local-socket"]
|
|||
local-socket = ["interprocess", "nu-plugin-protocol/local-socket"]
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows = { workspace = true }
|
||||
windows = { workspace = true }
|
|
@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-engi
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-plugin-engine"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.93.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1" }
|
||||
nu-system = { path = "../nu-system", version = "0.93.1" }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.93.1" }
|
||||
nu-plugin-core = { path = "../nu-plugin-core", version = "0.93.1", default-features = false }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1" }
|
||||
nu-system = { path = "../nu-system", version = "0.94.1" }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.94.1" }
|
||||
nu-plugin-core = { path = "../nu-plugin-core", version = "0.94.1", default-features = false }
|
||||
|
||||
serde = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
@ -31,4 +31,4 @@ local-socket = ["nu-plugin-core/local-socket"]
|
|||
windows = { workspace = true, features = [
|
||||
# For setting process creation flags
|
||||
"Win32_System_Threading",
|
||||
] }
|
||||
] }
|
|
@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-prot
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-plugin-protocol"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1", features = ["plugin"] }
|
||||
nu-utils = { path = "../nu-utils", version = "0.93.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1", features = ["plugin"] }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.1" }
|
||||
|
||||
bincode = "1.3"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
@ -21,4 +21,4 @@ typetag = "0.2"
|
|||
|
||||
[features]
|
||||
default = ["local-socket"]
|
||||
local-socket = []
|
||||
local-socket = []
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "nu-plugin-test-support"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
description = "Testing support for Nushell plugins"
|
||||
|
@ -12,17 +12,17 @@ bench = false
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.93.1", features = ["plugin"] }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1", features = ["plugin"] }
|
||||
nu-parser = { path = "../nu-parser", version = "0.93.1", features = ["plugin"] }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.93.1" }
|
||||
nu-plugin-core = { path = "../nu-plugin-core", version = "0.93.1" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.93.1" }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.93.1" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.93.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.1", features = ["plugin"] }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1", features = ["plugin"] }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.1", features = ["plugin"] }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.94.1" }
|
||||
nu-plugin-core = { path = "../nu-plugin-core", version = "0.94.1" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.1" }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.94.1" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
similar = "2.5"
|
||||
|
||||
[dev-dependencies]
|
||||
typetag = "0.2"
|
||||
serde = "1.0"
|
||||
serde = "1.0"
|
|
@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-plugin"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.93.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1" }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.93.1" }
|
||||
nu-plugin-core = { path = "../nu-plugin-core", version = "0.93.1", default-features = false }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1" }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.94.1" }
|
||||
nu-plugin-core = { path = "../nu-plugin-core", version = "0.94.1", default-features = false }
|
||||
|
||||
log = { workspace = true }
|
||||
thiserror = "1.0"
|
||||
|
@ -29,4 +29,4 @@ local-socket = ["nu-plugin-core/local-socket"]
|
|||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
# For setting the process group ID (EnterForeground / LeaveForeground)
|
||||
nix = { workspace = true, default-features = false, features = ["process"] }
|
||||
nix = { workspace = true, default-features = false, features = ["process"] }
|
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-pretty-hex"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-pretty-hex"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
@ -18,4 +18,4 @@ nu-ansi-term = { workspace = true }
|
|||
|
||||
[dev-dependencies]
|
||||
heapless = { version = "0.8", default-features = false }
|
||||
rand = "0.8"
|
||||
rand = "0.8"
|
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-protocol"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-protocol"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
@ -13,9 +13,9 @@ version = "0.93.1"
|
|||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-utils = { path = "../nu-utils", version = "0.93.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.93.1" }
|
||||
nu-system = { path = "../nu-system", version = "0.93.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.1" }
|
||||
nu-system = { path = "../nu-system", version = "0.94.1" }
|
||||
nu-derive-value = { path = "../nu-derive-value", version = "0.93.1" }
|
||||
|
||||
brotli = { workspace = true, optional = true }
|
||||
|
@ -46,11 +46,11 @@ plugin = [
|
|||
serde_json = { workspace = true }
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.93.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.1" }
|
||||
pretty_assertions = { workspace = true }
|
||||
rstest = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
os_pipe = { workspace = true }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
all-features = true
|
|
@ -9,6 +9,7 @@ use crate::{
|
|||
engine::EngineState,
|
||||
record, PipelineData, ShellError, Span, Value,
|
||||
};
|
||||
use std::io::BufRead;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
@ -50,11 +51,13 @@ pub struct Profiler {
|
|||
collect_expanded_source: bool,
|
||||
collect_values: bool,
|
||||
collect_exprs: bool,
|
||||
collect_lines: bool,
|
||||
elements: Vec<ElementInfo>,
|
||||
element_stack: Vec<ElementId>,
|
||||
}
|
||||
|
||||
impl Profiler {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
max_depth: i64,
|
||||
collect_spans: bool,
|
||||
|
@ -62,6 +65,7 @@ impl Profiler {
|
|||
collect_expanded_source: bool,
|
||||
collect_values: bool,
|
||||
collect_exprs: bool,
|
||||
collect_lines: bool,
|
||||
span: Span,
|
||||
) -> Self {
|
||||
let first = ElementInfo {
|
||||
|
@ -82,6 +86,7 @@ impl Profiler {
|
|||
collect_expanded_source,
|
||||
collect_values,
|
||||
collect_exprs,
|
||||
collect_lines,
|
||||
elements: vec![first],
|
||||
element_stack: vec![ElementId(0)],
|
||||
}
|
||||
|
@ -262,6 +267,31 @@ fn expr_to_string(engine_state: &EngineState, expr: &Expr) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
// Find a file name and a line number (indexed from 1) of a span
|
||||
fn find_file_of_span(engine_state: &EngineState, span: Span) -> Option<(&str, usize)> {
|
||||
for file in engine_state.files() {
|
||||
if file.covered_span.contains_span(span) {
|
||||
// count the number of lines between file start and the searched span start
|
||||
let chunk =
|
||||
engine_state.get_span_contents(Span::new(file.covered_span.start, span.start));
|
||||
let nlines = chunk.lines().count();
|
||||
// account for leading part of current line being counted as a separate line
|
||||
let line_num = if chunk.last() == Some(&b'\n') {
|
||||
nlines + 1
|
||||
} else {
|
||||
nlines
|
||||
};
|
||||
|
||||
// first line has no previous line, clamp up to `1`
|
||||
let line_num = usize::max(line_num, 1);
|
||||
|
||||
return Some((&file.name, line_num));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn collect_data(
|
||||
engine_state: &EngineState,
|
||||
profiler: &Profiler,
|
||||
|
@ -277,6 +307,16 @@ fn collect_data(
|
|||
"parent_id" => Value::int(parent_id.0 as i64, profiler_span),
|
||||
};
|
||||
|
||||
if profiler.collect_lines {
|
||||
if let Some((fname, line_num)) = find_file_of_span(engine_state, element.element_span) {
|
||||
row.push("file", Value::string(fname, profiler_span));
|
||||
row.push("line", Value::int(line_num as i64, profiler_span));
|
||||
} else {
|
||||
row.push("file", Value::nothing(profiler_span));
|
||||
row.push("line", Value::nothing(profiler_span));
|
||||
}
|
||||
}
|
||||
|
||||
if profiler.collect_spans {
|
||||
let span_start = i64::try_from(element.element_span.start)
|
||||
.map_err(|_| profiler_error("error converting span start to i64", profiler_span))?;
|
||||
|
|
|
@ -93,6 +93,13 @@ pub enum ParseError {
|
|||
#[label = "second redirection"] Span,
|
||||
),
|
||||
|
||||
#[error("Unexpected redirection.")]
|
||||
#[diagnostic(code(nu::parser::unexpected_redirection))]
|
||||
UnexpectedRedirection {
|
||||
#[label = "redirecting nothing"]
|
||||
span: Span,
|
||||
},
|
||||
|
||||
#[error("{0} is not supported on values of type {3}")]
|
||||
#[diagnostic(code(nu::parser::unsupported_operation))]
|
||||
UnsupportedOperationLHS(
|
||||
|
@ -564,6 +571,7 @@ impl ParseError {
|
|||
ParseError::ShellErrRedirect(s) => *s,
|
||||
ParseError::ShellOutErrRedirect(s) => *s,
|
||||
ParseError::MultipleRedirections(_, _, s) => *s,
|
||||
ParseError::UnexpectedRedirection { span } => *span,
|
||||
ParseError::UnknownOperator(_, _, s) => *s,
|
||||
ParseError::InvalidLiteral(_, _, s) => *s,
|
||||
ParseError::LabeledErrorWithHelp { span: s, .. } => *s,
|
||||
|
|
|
@ -114,6 +114,16 @@ impl ByteStreamType {
|
|||
ByteStreamType::Unknown => "byte stream",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the type is `Binary` or `Unknown`
|
||||
pub fn is_binary_coercible(self) -> bool {
|
||||
matches!(self, ByteStreamType::Binary | ByteStreamType::Unknown)
|
||||
}
|
||||
|
||||
/// Returns true if the type is `String` or `Unknown`
|
||||
pub fn is_string_coercible(self) -> bool {
|
||||
matches!(self, ByteStreamType::String | ByteStreamType::Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ByteStreamType> for Type {
|
||||
|
@ -483,7 +493,7 @@ impl ByteStream {
|
|||
/// data would have been valid UTF-8.
|
||||
pub fn into_string(self) -> Result<String, ShellError> {
|
||||
let span = self.span;
|
||||
if self.type_ != ByteStreamType::Binary {
|
||||
if self.type_.is_string_coercible() {
|
||||
let trim = self.stream.is_external();
|
||||
let bytes = self.into_bytes()?;
|
||||
let mut string = String::from_utf8(bytes).map_err(|err| ShellError::NonUtf8Custom {
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
engine::{EngineState, Stack},
|
||||
process::{ChildPipe, ChildProcess, ExitStatus},
|
||||
ByteStream, ByteStreamType, Config, ErrSpan, ListStream, OutDest, PipelineMetadata, Range,
|
||||
ShellError, Span, Value,
|
||||
ShellError, Span, Type, Value,
|
||||
};
|
||||
use nu_utils::{stderr_write_all_and_flush, stdout_write_all_and_flush};
|
||||
use std::{
|
||||
|
@ -99,6 +99,24 @@ impl PipelineData {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get a type that is representative of the `PipelineData`.
|
||||
///
|
||||
/// The type returned here makes no effort to collect a stream, so it may be a different type
|
||||
/// than would be returned by [`Value::get_type()`] on the result of [`.into_value()`].
|
||||
///
|
||||
/// Specifically, a `ListStream` results in [`list stream`](Type::ListStream) rather than
|
||||
/// the fully complete [`list`](Type::List) type (which would require knowing the contents),
|
||||
/// and a `ByteStream` with [unknown](crate::ByteStreamType::Unknown) type results in
|
||||
/// [`any`](Type::Any) rather than [`string`](Type::String) or [`binary`](Type::Binary).
|
||||
pub fn get_type(&self) -> Type {
|
||||
match self {
|
||||
PipelineData::Empty => Type::Nothing,
|
||||
PipelineData::Value(value, _) => value.get_type(),
|
||||
PipelineData::ListStream(_, _) => Type::ListStream,
|
||||
PipelineData::ByteStream(stream, _) => stream.type_().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_value(self, span: Span) -> Result<Value, ShellError> {
|
||||
match self {
|
||||
PipelineData::Empty => Ok(Value::nothing(span)),
|
||||
|
|
|
@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-std"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-std"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
[dependencies]
|
||||
nu-parser = { version = "0.93.1", path = "../nu-parser" }
|
||||
nu-protocol = { version = "0.93.1", path = "../nu-protocol" }
|
||||
nu-engine = { version = "0.93.1", path = "../nu-engine" }
|
||||
nu-parser = { version = "0.94.1", path = "../nu-parser" }
|
||||
nu-protocol = { version = "0.94.1", path = "../nu-protocol" }
|
||||
nu-engine = { version = "0.94.1", path = "../nu-engine" }
|
||||
miette = { workspace = true, features = ["fancy-no-backtrace"] }
|
||||
|
||||
log = "0.4"
|
||||
log = "0.4"
|
|
@ -515,7 +515,7 @@ def build-command-page [command: record] {
|
|||
$"- (ansi cyan)does not create(ansi reset) a scope."
|
||||
}
|
||||
) | append (
|
||||
if ($command.is_builtin) {
|
||||
if ($command.type == "built-in") {
|
||||
$"- (ansi cyan)is(ansi reset) a built-in command."
|
||||
} else {
|
||||
$"- (ansi cyan)is not(ansi reset) a built-in command."
|
||||
|
@ -527,19 +527,19 @@ def build-command-page [command: record] {
|
|||
$"- (ansi cyan)is not(ansi reset) a subcommand."
|
||||
}
|
||||
) | append (
|
||||
if ($command.is_plugin) {
|
||||
if ($command.type == "plugin") {
|
||||
$"- (ansi cyan)is part(ansi reset) of a plugin."
|
||||
} else {
|
||||
$"- (ansi cyan)is not part(ansi reset) of a plugin."
|
||||
}
|
||||
) | append (
|
||||
if ($command.is_custom) {
|
||||
if ($command.type == "custom") {
|
||||
$"- (ansi cyan)is(ansi reset) a custom command."
|
||||
} else {
|
||||
$"- (ansi cyan)is not(ansi reset) a custom command."
|
||||
}
|
||||
) | append (
|
||||
if ($command.is_keyword) {
|
||||
if ($command.type == "keyword") {
|
||||
$"- (ansi cyan)is(ansi reset) a keyword."
|
||||
} else {
|
||||
$"- (ansi cyan)is not(ansi reset) a keyword."
|
||||
|
@ -689,7 +689,7 @@ export def commands [
|
|||
...command: string@"nu-complete list-commands" # the name of command to get help on
|
||||
--find (-f): string # string to find in command names and usage
|
||||
] {
|
||||
let commands = (scope commands | where not is_extern | reject is_extern | sort-by name)
|
||||
let commands = (scope commands | sort-by name)
|
||||
|
||||
if not ($find | is-empty) {
|
||||
# TODO: impl find for external commands
|
||||
|
|
9
crates/nu-std/tests/test_help.nu
Normal file
9
crates/nu-std/tests/test_help.nu
Normal file
|
@ -0,0 +1,9 @@
|
|||
use std assert
|
||||
use std help
|
||||
|
||||
#[test]
|
||||
def show_help_on_commands [] {
|
||||
let help_result = (help alias)
|
||||
assert ("item not found" not-in $help_result)
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ authors = ["The Nushell Project Developers", "procs creators"]
|
|||
description = "Nushell system querying"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-system"
|
||||
name = "nu-system"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
|
@ -16,6 +16,7 @@ bench = false
|
|||
libc = { workspace = true }
|
||||
log = { workspace = true }
|
||||
sysinfo = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
nix = { workspace = true, default-features = false, features = ["fs", "term", "process", "signal"] }
|
||||
|
@ -44,4 +45,4 @@ windows = { workspace = true, features = [
|
|||
"Win32_System_SystemInformation",
|
||||
"Win32_System_Threading",
|
||||
"Win32_UI_Shell",
|
||||
]}
|
||||
]}
|
305
crates/nu-system/src/freebsd.rs
Normal file
305
crates/nu-system/src/freebsd.rs
Normal file
|
@ -0,0 +1,305 @@
|
|||
use itertools::{EitherOrBoth, Itertools};
|
||||
use libc::{
|
||||
kinfo_proc, sysctl, CTL_HW, CTL_KERN, KERN_PROC, KERN_PROC_ALL, KERN_PROC_ARGS, TDF_IDLETD,
|
||||
};
|
||||
use std::{
|
||||
ffi::CStr,
|
||||
io,
|
||||
mem::{self, MaybeUninit},
|
||||
ptr,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProcessInfo {
|
||||
pub pid: i32,
|
||||
pub ppid: i32,
|
||||
pub name: String,
|
||||
pub argv: Vec<u8>,
|
||||
pub stat: i8,
|
||||
pub percent_cpu: f64,
|
||||
pub mem_resident: u64, // in bytes
|
||||
pub mem_virtual: u64, // in bytes
|
||||
}
|
||||
|
||||
pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec<ProcessInfo> {
|
||||
compare_procs(interval).unwrap_or_else(|err| {
|
||||
log::warn!("Failed to get processes: {}", err);
|
||||
vec![]
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_procs(interval: Duration) -> io::Result<Vec<ProcessInfo>> {
|
||||
let pagesize = get_pagesize()? as u64;
|
||||
|
||||
// Compare two full snapshots of all of the processes over the interval
|
||||
let now = Instant::now();
|
||||
let procs_a = get_procs()?;
|
||||
std::thread::sleep(interval);
|
||||
let procs_b = get_procs()?;
|
||||
let true_interval = Instant::now().saturating_duration_since(now);
|
||||
let true_interval_sec = true_interval.as_secs_f64();
|
||||
|
||||
// Group all of the threads in each process together
|
||||
let a_grouped = procs_a.into_iter().group_by(|proc| proc.ki_pid);
|
||||
let b_grouped = procs_b.into_iter().group_by(|proc| proc.ki_pid);
|
||||
|
||||
// Join the processes between the two snapshots
|
||||
Ok(a_grouped
|
||||
.into_iter()
|
||||
.merge_join_by(b_grouped.into_iter(), |(pid_a, _), (pid_b, _)| {
|
||||
pid_a.cmp(pid_b)
|
||||
})
|
||||
.map(|threads| {
|
||||
// Join the threads between the two snapshots for the process
|
||||
let mut threads = {
|
||||
let (left, right) = threads.left_and_right();
|
||||
left.into_iter()
|
||||
.flat_map(|(_, threads)| threads)
|
||||
.merge_join_by(
|
||||
right.into_iter().flat_map(|(_, threads)| threads),
|
||||
|thread_a, thread_b| thread_a.ki_tid.cmp(&thread_b.ki_tid),
|
||||
)
|
||||
.peekable()
|
||||
};
|
||||
|
||||
// Pick the later process entry of the first thread to use for basic process information
|
||||
let proc = match threads.peek().ok_or(io::ErrorKind::NotFound)? {
|
||||
EitherOrBoth::Both(_, b) => b,
|
||||
EitherOrBoth::Left(a) => a,
|
||||
EitherOrBoth::Right(b) => b,
|
||||
}
|
||||
.clone();
|
||||
|
||||
// Skip over the idle process. It always appears with high CPU usage when the
|
||||
// system is idle
|
||||
if proc.ki_tdflags as u64 & TDF_IDLETD as u64 != 0 {
|
||||
return Err(io::ErrorKind::NotFound.into());
|
||||
}
|
||||
|
||||
// Aggregate all of the threads that exist in both snapshots and sum their runtime.
|
||||
let (runtime_a, runtime_b) =
|
||||
threads
|
||||
.flat_map(|t| t.both())
|
||||
.fold((0., 0.), |(runtime_a, runtime_b), (a, b)| {
|
||||
let runtime_in_seconds =
|
||||
|proc: &kinfo_proc| proc.ki_runtime as f64 /* µsec */ / 1_000_000.0;
|
||||
(
|
||||
runtime_a + runtime_in_seconds(&a),
|
||||
runtime_b + runtime_in_seconds(&b),
|
||||
)
|
||||
});
|
||||
|
||||
// The percentage CPU is the ratio of how much runtime occurred for the process out of
|
||||
// the true measured interval that occurred.
|
||||
let percent_cpu = 100. * (runtime_b - runtime_a).max(0.) / true_interval_sec;
|
||||
|
||||
let info = ProcessInfo {
|
||||
pid: proc.ki_pid,
|
||||
ppid: proc.ki_ppid,
|
||||
name: read_cstr(&proc.ki_comm).to_string_lossy().into_owned(),
|
||||
argv: get_proc_args(proc.ki_pid)?,
|
||||
stat: proc.ki_stat,
|
||||
percent_cpu,
|
||||
mem_resident: proc.ki_rssize.max(0) as u64 * pagesize,
|
||||
mem_virtual: proc.ki_size.max(0) as u64,
|
||||
};
|
||||
Ok(info)
|
||||
})
|
||||
// Remove errors from the list - probably just processes that are gone now
|
||||
.flat_map(|result: io::Result<_>| result.ok())
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn check(err: libc::c_int) -> std::io::Result<()> {
|
||||
if err < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a bounds-checked way to read a `CStr` from a slice of `c_char`
|
||||
fn read_cstr(slice: &[libc::c_char]) -> &CStr {
|
||||
unsafe {
|
||||
// SAFETY: ensure that c_char and u8 are the same size
|
||||
mem::transmute::<libc::c_char, u8>(0);
|
||||
let slice: &[u8] = mem::transmute(slice);
|
||||
CStr::from_bytes_until_nul(slice).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_procs() -> io::Result<Vec<libc::kinfo_proc>> {
|
||||
// To understand what's going on here, see the sysctl(3) manpage for FreeBSD.
|
||||
unsafe {
|
||||
const STRUCT_SIZE: usize = mem::size_of::<libc::kinfo_proc>();
|
||||
let ctl_name = [CTL_KERN, KERN_PROC, KERN_PROC_ALL];
|
||||
|
||||
// First, try to figure out how large a buffer we need to allocate
|
||||
// (calling with NULL just tells us that)
|
||||
let mut data_len = 0;
|
||||
check(sysctl(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
ptr::null_mut(),
|
||||
&mut data_len,
|
||||
ptr::null(),
|
||||
0,
|
||||
))?;
|
||||
|
||||
// data_len will be set in bytes, so divide by the size of the structure
|
||||
let expected_len = data_len.div_ceil(STRUCT_SIZE);
|
||||
|
||||
// Now allocate the Vec and set data_len to the real number of bytes allocated
|
||||
let mut vec: Vec<libc::kinfo_proc> = Vec::with_capacity(expected_len);
|
||||
data_len = vec.capacity() * STRUCT_SIZE;
|
||||
|
||||
// Call sysctl() again to put the result in the vec
|
||||
check(sysctl(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
vec.as_mut_ptr() as *mut libc::c_void,
|
||||
&mut data_len,
|
||||
ptr::null(),
|
||||
0,
|
||||
))?;
|
||||
|
||||
// If that was ok, we can set the actual length of the vec to whatever
|
||||
// data_len was changed to, since that should now all be properly initialized data.
|
||||
let true_len = data_len.div_ceil(STRUCT_SIZE);
|
||||
vec.set_len(true_len);
|
||||
|
||||
// Sort the procs by pid and then tid before using them
|
||||
vec.sort_by_key(|p| (p.ki_pid, p.ki_tid));
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_proc_args(pid: i32) -> io::Result<Vec<u8>> {
|
||||
unsafe {
|
||||
let ctl_name = [CTL_KERN, KERN_PROC, KERN_PROC_ARGS, pid];
|
||||
|
||||
// First, try to figure out how large a buffer we need to allocate
|
||||
// (calling with NULL just tells us that)
|
||||
let mut data_len = 0;
|
||||
check(sysctl(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
ptr::null_mut(),
|
||||
&mut data_len,
|
||||
ptr::null(),
|
||||
0,
|
||||
))?;
|
||||
|
||||
// Now allocate the Vec and set data_len to the real number of bytes allocated
|
||||
let mut vec: Vec<u8> = Vec::with_capacity(data_len);
|
||||
data_len = vec.capacity();
|
||||
|
||||
// Call sysctl() again to put the result in the vec
|
||||
check(sysctl(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
vec.as_mut_ptr() as *mut libc::c_void,
|
||||
&mut data_len,
|
||||
ptr::null(),
|
||||
0,
|
||||
))?;
|
||||
|
||||
// If that was ok, we can set the actual length of the vec to whatever
|
||||
// data_len was changed to, since that should now all be properly initialized data.
|
||||
vec.set_len(data_len);
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
|
||||
// For getting simple values from the sysctl interface
|
||||
unsafe fn get_ctl<T>(ctl_name: &[i32]) -> io::Result<T> {
|
||||
let mut value: MaybeUninit<T> = MaybeUninit::uninit();
|
||||
let mut value_len = mem::size_of_val(&value);
|
||||
check(sysctl(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
value.as_mut_ptr() as *mut libc::c_void,
|
||||
&mut value_len,
|
||||
ptr::null(),
|
||||
0,
|
||||
))?;
|
||||
Ok(value.assume_init())
|
||||
}
|
||||
|
||||
fn get_pagesize() -> io::Result<libc::c_int> {
|
||||
// not in libc for some reason
|
||||
const HW_PAGESIZE: i32 = 7;
|
||||
unsafe { get_ctl(&[CTL_HW, HW_PAGESIZE]) }
|
||||
}
|
||||
|
||||
impl ProcessInfo {
|
||||
/// PID of process
|
||||
pub fn pid(&self) -> i32 {
|
||||
self.pid
|
||||
}
|
||||
|
||||
/// Parent PID of process
|
||||
pub fn ppid(&self) -> i32 {
|
||||
self.ppid
|
||||
}
|
||||
|
||||
/// Name of command
|
||||
pub fn name(&self) -> String {
|
||||
let argv_name = self
|
||||
.argv
|
||||
.split(|b| *b == 0)
|
||||
.next()
|
||||
.map(String::from_utf8_lossy)
|
||||
.unwrap_or_default()
|
||||
.into_owned();
|
||||
|
||||
if !argv_name.is_empty() {
|
||||
argv_name
|
||||
} else {
|
||||
// Just use the command name alone.
|
||||
self.name.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Full name of command, with arguments
|
||||
pub fn command(&self) -> String {
|
||||
if let Some(last_nul) = self.argv.iter().rposition(|b| *b == 0) {
|
||||
// The command string is NUL separated
|
||||
// Take the string up to the last NUL, then replace the NULs with spaces
|
||||
String::from_utf8_lossy(&self.argv[0..last_nul]).replace("\0", " ")
|
||||
} else {
|
||||
// The argv is empty, so use the name instead
|
||||
self.name()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the status of the process
|
||||
pub fn status(&self) -> String {
|
||||
match self.stat {
|
||||
libc::SIDL | libc::SRUN => "Running",
|
||||
libc::SSLEEP => "Sleeping",
|
||||
libc::SSTOP => "Stopped",
|
||||
libc::SWAIT => "Waiting",
|
||||
libc::SLOCK => "Locked",
|
||||
libc::SZOMB => "Zombie",
|
||||
_ => "Unknown",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// CPU usage as a percent of total
|
||||
pub fn cpu_usage(&self) -> f64 {
|
||||
self.percent_cpu
|
||||
}
|
||||
|
||||
/// Memory size in number of bytes
|
||||
pub fn mem_size(&self) -> u64 {
|
||||
self.mem_resident
|
||||
}
|
||||
|
||||
/// Virtual memory size in bytes
|
||||
pub fn virtual_size(&self) -> u64 {
|
||||
self.mem_virtual
|
||||
}
|
||||
}
|
|
@ -1,8 +1,13 @@
|
|||
mod foreground;
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
mod freebsd;
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
mod linux;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
|
||||
mod netbsd;
|
||||
pub mod os_info;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows;
|
||||
|
@ -10,9 +15,14 @@ mod windows;
|
|||
#[cfg(unix)]
|
||||
pub use self::foreground::stdin_fd;
|
||||
pub use self::foreground::{ForegroundChild, ForegroundGuard};
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
pub use self::freebsd::*;
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
pub use self::linux::*;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use self::macos::*;
|
||||
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
|
||||
pub use self::netbsd::*;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use self::windows::*;
|
||||
|
|
336
crates/nu-system/src/netbsd.rs
Normal file
336
crates/nu-system/src/netbsd.rs
Normal file
|
@ -0,0 +1,336 @@
|
|||
//! This is used for both NetBSD and OpenBSD, because they are fairly similar.
|
||||
|
||||
use itertools::{EitherOrBoth, Itertools};
|
||||
use libc::{sysctl, CTL_HW, CTL_KERN, KERN_PROC_ALL, KERN_PROC_ARGS, KERN_PROC_ARGV};
|
||||
use std::{
|
||||
io,
|
||||
mem::{self, MaybeUninit},
|
||||
ptr,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
#[cfg(target_os = "netbsd")]
|
||||
type KInfoProc = libc::kinfo_proc2;
|
||||
#[cfg(target_os = "openbsd")]
|
||||
type KInfoProc = libc::kinfo_proc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProcessInfo {
|
||||
pub pid: i32,
|
||||
pub ppid: i32,
|
||||
pub argv: Vec<u8>,
|
||||
pub stat: i8,
|
||||
pub percent_cpu: f64,
|
||||
pub mem_resident: u64, // in bytes
|
||||
pub mem_virtual: u64, // in bytes
|
||||
}
|
||||
|
||||
pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec<ProcessInfo> {
|
||||
compare_procs(interval).unwrap_or_else(|err| {
|
||||
log::warn!("Failed to get processes: {}", err);
|
||||
vec![]
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_procs(interval: Duration) -> io::Result<Vec<ProcessInfo>> {
|
||||
let pagesize = get_pagesize()? as u64;
|
||||
|
||||
// Compare two full snapshots of all of the processes over the interval
|
||||
let now = Instant::now();
|
||||
let procs_a = get_procs()?;
|
||||
std::thread::sleep(interval);
|
||||
let procs_b = get_procs()?;
|
||||
let true_interval = Instant::now().saturating_duration_since(now);
|
||||
let true_interval_sec = true_interval.as_secs_f64();
|
||||
|
||||
// Join the processes between the two snapshots
|
||||
Ok(procs_a
|
||||
.into_iter()
|
||||
.merge_join_by(procs_b.into_iter(), |a, b| a.p_pid.cmp(&b.p_pid))
|
||||
.map(|proc| {
|
||||
// Take both snapshotted processes if we can, but if not then just keep the one that
|
||||
// exists and set prev_proc to None
|
||||
let (prev_proc, proc) = match proc {
|
||||
EitherOrBoth::Both(a, b) => (Some(a), b),
|
||||
EitherOrBoth::Left(a) => (None, a),
|
||||
EitherOrBoth::Right(b) => (None, b),
|
||||
};
|
||||
|
||||
// The percentage CPU is the ratio of how much runtime occurred for the process out of
|
||||
// the true measured interval that occurred.
|
||||
let percent_cpu = if let Some(prev_proc) = prev_proc {
|
||||
let prev_rtime =
|
||||
prev_proc.p_rtime_sec as f64 + prev_proc.p_rtime_usec as f64 / 1_000_000.0;
|
||||
let rtime = proc.p_rtime_sec as f64 + proc.p_rtime_usec as f64 / 1_000_000.0;
|
||||
100. * (rtime - prev_rtime).max(0.) / true_interval_sec
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
Ok(ProcessInfo {
|
||||
pid: proc.p_pid,
|
||||
ppid: proc.p_ppid,
|
||||
argv: get_proc_args(proc.p_pid, KERN_PROC_ARGV)?,
|
||||
stat: proc.p_stat,
|
||||
percent_cpu,
|
||||
mem_resident: proc.p_vm_rssize.max(0) as u64 * pagesize,
|
||||
#[cfg(target_os = "netbsd")]
|
||||
mem_virtual: proc.p_vm_msize.max(0) as u64 * pagesize,
|
||||
#[cfg(target_os = "openbsd")]
|
||||
mem_virtual: proc.p_vm_map_size.max(0) as u64 * pagesize,
|
||||
})
|
||||
})
|
||||
// Remove errors from the list - probably just processes that are gone now
|
||||
.flat_map(|result: io::Result<_>| result.ok())
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn check(err: libc::c_int) -> std::io::Result<()> {
|
||||
if err < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Call `sysctl()` in read mode (i.e. the last two arguments are NULL and zero)
|
||||
unsafe fn sysctl_get(
|
||||
name: *const i32,
|
||||
name_len: u32,
|
||||
data: *mut libc::c_void,
|
||||
data_len: *mut usize,
|
||||
) -> i32 {
|
||||
sysctl(
|
||||
name,
|
||||
name_len,
|
||||
data,
|
||||
data_len,
|
||||
// NetBSD and OpenBSD differ in mutability for this pointer, but it's null anyway
|
||||
#[cfg(target_os = "netbsd")]
|
||||
ptr::null(),
|
||||
#[cfg(target_os = "openbsd")]
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_procs() -> io::Result<Vec<KInfoProc>> {
|
||||
// To understand what's going on here, see the sysctl(3) and sysctl(7) manpages for NetBSD.
|
||||
unsafe {
|
||||
const STRUCT_SIZE: usize = mem::size_of::<KInfoProc>();
|
||||
|
||||
#[cfg(target_os = "netbsd")]
|
||||
const TGT_KERN_PROC: i32 = libc::KERN_PROC2;
|
||||
#[cfg(target_os = "openbsd")]
|
||||
const TGT_KERN_PROC: i32 = libc::KERN_PROC;
|
||||
|
||||
let mut ctl_name = [
|
||||
CTL_KERN,
|
||||
TGT_KERN_PROC,
|
||||
KERN_PROC_ALL,
|
||||
0,
|
||||
STRUCT_SIZE as i32,
|
||||
0,
|
||||
];
|
||||
|
||||
// First, try to figure out how large a buffer we need to allocate
|
||||
// (calling with NULL just tells us that)
|
||||
let mut data_len = 0;
|
||||
check(sysctl_get(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
ptr::null_mut(),
|
||||
&mut data_len,
|
||||
))?;
|
||||
|
||||
// data_len will be set in bytes, so divide by the size of the structure
|
||||
let expected_len = data_len.div_ceil(STRUCT_SIZE);
|
||||
|
||||
// Now allocate the Vec and set data_len to the real number of bytes allocated
|
||||
let mut vec: Vec<KInfoProc> = Vec::with_capacity(expected_len);
|
||||
data_len = vec.capacity() * STRUCT_SIZE;
|
||||
|
||||
// We are also supposed to set ctl_name[5] to the number of structures we want
|
||||
ctl_name[5] = expected_len.try_into().expect("expected_len too big");
|
||||
|
||||
// Call sysctl() again to put the result in the vec
|
||||
check(sysctl_get(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
vec.as_mut_ptr() as *mut libc::c_void,
|
||||
&mut data_len,
|
||||
))?;
|
||||
|
||||
// If that was ok, we can set the actual length of the vec to whatever
|
||||
// data_len was changed to, since that should now all be properly initialized data.
|
||||
let true_len = data_len.div_ceil(STRUCT_SIZE);
|
||||
vec.set_len(true_len);
|
||||
|
||||
// Sort the procs by pid before using them
|
||||
vec.sort_by_key(|p| p.p_pid);
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_proc_args(pid: i32, what: i32) -> io::Result<Vec<u8>> {
|
||||
unsafe {
|
||||
let ctl_name = [CTL_KERN, KERN_PROC_ARGS, pid, what];
|
||||
|
||||
// First, try to figure out how large a buffer we need to allocate
|
||||
// (calling with NULL just tells us that)
|
||||
let mut data_len = 0;
|
||||
check(sysctl_get(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
ptr::null_mut(),
|
||||
&mut data_len,
|
||||
))?;
|
||||
|
||||
// Now allocate the Vec and set data_len to the real number of bytes allocated
|
||||
let mut vec: Vec<u8> = Vec::with_capacity(data_len);
|
||||
data_len = vec.capacity();
|
||||
|
||||
// Call sysctl() again to put the result in the vec
|
||||
check(sysctl_get(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
vec.as_mut_ptr() as *mut libc::c_void,
|
||||
&mut data_len,
|
||||
))?;
|
||||
|
||||
// If that was ok, we can set the actual length of the vec to whatever
|
||||
// data_len was changed to, since that should now all be properly initialized data.
|
||||
vec.set_len(data_len);
|
||||
|
||||
// On OpenBSD we have to do an extra step, because it fills the buffer with pointers to the
|
||||
// strings first, even though the strings are within the buffer as well.
|
||||
#[cfg(target_os = "openbsd")]
|
||||
let vec = {
|
||||
use std::ffi::CStr;
|
||||
|
||||
// Set up some bounds checking. We assume there will be some pointers at the base until
|
||||
// we reach NULL, but we want to make sure we only ever read data within the range of
|
||||
// min_ptr..max_ptr.
|
||||
let ptrs = vec.as_ptr() as *const *const u8;
|
||||
let min_ptr = vec.as_ptr() as *const u8;
|
||||
let max_ptr = vec.as_ptr().add(vec.len()) as *const u8;
|
||||
let max_index: isize = (vec.len() / mem::size_of::<*const u8>())
|
||||
.try_into()
|
||||
.expect("too big for isize");
|
||||
|
||||
let mut new_vec = Vec::with_capacity(vec.len());
|
||||
for index in 0..max_index {
|
||||
let ptr = ptrs.offset(index);
|
||||
if *ptr == ptr::null() {
|
||||
break;
|
||||
} else {
|
||||
// Make sure it's within the bounds of the buffer
|
||||
assert!(
|
||||
*ptr >= min_ptr && *ptr < max_ptr,
|
||||
"pointer out of bounds of the buffer returned by sysctl()"
|
||||
);
|
||||
// Also bounds-check the C strings, to make sure we don't overrun the buffer
|
||||
new_vec.extend(
|
||||
CStr::from_bytes_until_nul(std::slice::from_raw_parts(
|
||||
*ptr,
|
||||
max_ptr.offset_from(*ptr) as usize,
|
||||
))
|
||||
.expect("invalid C string")
|
||||
.to_bytes_with_nul(),
|
||||
);
|
||||
}
|
||||
}
|
||||
new_vec
|
||||
};
|
||||
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
|
||||
// For getting simple values from the sysctl interface
|
||||
unsafe fn get_ctl<T>(ctl_name: &[i32]) -> io::Result<T> {
|
||||
let mut value: MaybeUninit<T> = MaybeUninit::uninit();
|
||||
let mut value_len = mem::size_of_val(&value);
|
||||
check(sysctl_get(
|
||||
ctl_name.as_ptr(),
|
||||
ctl_name.len() as u32,
|
||||
value.as_mut_ptr() as *mut libc::c_void,
|
||||
&mut value_len,
|
||||
))?;
|
||||
Ok(value.assume_init())
|
||||
}
|
||||
|
||||
fn get_pagesize() -> io::Result<libc::c_int> {
|
||||
// not in libc for some reason
|
||||
const HW_PAGESIZE: i32 = 7;
|
||||
unsafe { get_ctl(&[CTL_HW, HW_PAGESIZE]) }
|
||||
}
|
||||
|
||||
impl ProcessInfo {
|
||||
/// PID of process
|
||||
pub fn pid(&self) -> i32 {
|
||||
self.pid
|
||||
}
|
||||
|
||||
/// Parent PID of process
|
||||
pub fn ppid(&self) -> i32 {
|
||||
self.ppid
|
||||
}
|
||||
|
||||
/// Name of command
|
||||
pub fn name(&self) -> String {
|
||||
self.argv
|
||||
.split(|b| *b == 0)
|
||||
.next()
|
||||
.map(String::from_utf8_lossy)
|
||||
.unwrap_or_default()
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
/// Full name of command, with arguments
|
||||
pub fn command(&self) -> String {
|
||||
if let Some(last_nul) = self.argv.iter().rposition(|b| *b == 0) {
|
||||
// The command string is NUL separated
|
||||
// Take the string up to the last NUL, then replace the NULs with spaces
|
||||
String::from_utf8_lossy(&self.argv[0..last_nul]).replace("\0", " ")
|
||||
} else {
|
||||
"".into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the status of the process
|
||||
pub fn status(&self) -> String {
|
||||
// see sys/proc.h (OpenBSD), sys/lwp.h (NetBSD)
|
||||
// the names given here are the NetBSD ones, starting with LS*, but the OpenBSD ones are
|
||||
// the same, just starting with S* instead
|
||||
match self.stat {
|
||||
1 /* LSIDL */ => "",
|
||||
2 /* LSRUN */ => "Waiting",
|
||||
3 /* LSSLEEP */ => "Sleeping",
|
||||
4 /* LSSTOP */ => "Stopped",
|
||||
5 /* LSZOMB */ => "Zombie",
|
||||
#[cfg(target_os = "openbsd")] // removed in NetBSD
|
||||
6 /* LSDEAD */ => "Dead",
|
||||
7 /* LSONPROC */ => "Running",
|
||||
#[cfg(target_os = "netbsd")] // doesn't exist in OpenBSD
|
||||
8 /* LSSUSPENDED */ => "Suspended",
|
||||
_ => "Unknown",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// CPU usage as a percent of total
|
||||
pub fn cpu_usage(&self) -> f64 {
|
||||
self.percent_cpu
|
||||
}
|
||||
|
||||
/// Memory size in number of bytes
|
||||
pub fn mem_size(&self) -> u64 {
|
||||
self.mem_resident
|
||||
}
|
||||
|
||||
/// Virtual memory size in bytes
|
||||
pub fn virtual_size(&self) -> u64 {
|
||||
self.mem_virtual
|
||||
}
|
||||
}
|
|
@ -5,20 +5,20 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-table"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-table"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.93.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.93.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.93.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.94.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
fancy-regex = { workspace = true }
|
||||
tabled = { workspace = true, features = ["color"], default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
# nu-test-support = { path="../nu-test-support", version = "0.93.1" }
|
||||
# nu-test-support = { path="../nu-test-support", version = "0.94.1" }
|
|
@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-term-grid"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-term-grid"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-utils = { path = "../nu-utils", version = "0.93.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.1" }
|
||||
|
||||
unicode-width = { workspace = true }
|
||||
unicode-width = { workspace = true }
|
|
@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-test-suppor
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-test-support"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-path = { path = "../nu-path", version = "0.93.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.93.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.93.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.94.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.1" }
|
||||
|
||||
num-format = { workspace = true }
|
||||
which = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tempfile = { workspace = true }
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||
license = "MIT"
|
||||
name = "nu-utils"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-utils"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[[bin]]
|
||||
|
@ -29,4 +29,4 @@ unicase = "2.7.0"
|
|||
crossterm_winapi = "0.9"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { workspace = true, default-features = false, features = ["user", "fs"] }
|
||||
nix = { workspace = true, default-features = false, features = ["user", "fs"] }
|
|
@ -1,6 +1,6 @@
|
|||
# Nushell Config File
|
||||
#
|
||||
# version = "0.93.1"
|
||||
# version = "0.94.1"
|
||||
|
||||
# For more information on defining custom themes, see
|
||||
# https://www.nushell.sh/book/coloring_and_theming.html
|
||||
|
@ -892,4 +892,4 @@ $env.config = {
|
|||
event: { edit: selectall }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
# Nushell Environment Config File
|
||||
#
|
||||
# version = "0.93.1"
|
||||
# version = "0.94.1"
|
||||
|
||||
def create_left_prompt [] {
|
||||
let dir = match (do --ignore-shell-errors { $env.PWD | path relative-to $nu.home-path }) {
|
||||
|
@ -97,4 +97,4 @@ $env.NU_PLUGIN_DIRS = [
|
|||
# $env.PATH = ($env.PATH | uniq)
|
||||
|
||||
# To load from a custom file you can use:
|
||||
# source ($nu.default-config-dir | path join 'custom.nu')
|
||||
# source ($nu.default-config-dir | path join 'custom.nu')
|
|
@ -10,10 +10,10 @@ name = "nu_plugin_custom_values"
|
|||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.93.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1", features = ["plugin"] }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.94.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1", features = ["plugin"] }
|
||||
serde = { workspace = true, default-features = false }
|
||||
typetag = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.93.1" }
|
||||
nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.94.1" }
|
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_exam
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_example"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_example"
|
||||
|
@ -15,9 +15,9 @@ bench = false
|
|||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.93.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1", features = ["plugin"] }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.94.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1", features = ["plugin"] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.93.1" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.93.1" }
|
||||
nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.94.1" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.1" }
|
|
@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_form
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_formats"
|
||||
version = "0.93.1"
|
||||
version = "0.94.1"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.93.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.93.1", features = ["plugin"] }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.94.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.1", features = ["plugin"] }
|
||||
|
||||
indexmap = { workspace = true }
|
||||
eml-parser = "0.1"
|
||||
|
@ -18,4 +18,4 @@ ical = "0.11"
|
|||
rust-ini = "0.21.0"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.93.1" }
|
||||
nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.94.1" }
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user