Merge branch 'main'

This commit is contained in:
Tim 'Piepmatz' Hesse 2024-06-01 09:48:54 +02:00
commit 748b3c39af
166 changed files with 3807 additions and 2188 deletions

View File

@ -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 }}

View File

@ -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

View File

@ -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

View File

@ -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: |

View File

@ -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
View File

@ -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",

View File

@ -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

View File

@ -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"]

View File

@ -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)
));

View File

@ -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)
}

View File

@ -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]

View File

@ -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))

View File

@ -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" }

View File

@ -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 = []

View File

@ -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);
}

View File

@ -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]

View File

@ -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" }

View File

@ -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 }

View File

@ -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,

View File

@ -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()

View File

@ -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"
))]

View File

@ -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,
))
}
}

View File

@ -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,
))
}
}

View File

@ -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;

View File

@ -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,
}
}

View File

@ -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,

View File

@ -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());

View File

@ -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];

View File

@ -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;

View File

@ -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),

View File

@ -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(

View File

@ -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> {

View File

@ -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(

View File

@ -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)]

View File

@ -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))
}

View File

@ -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)]

View File

@ -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 {

View File

@ -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(),
)

View File

@ -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("ふが")),
},
]

View File

@ -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,
}),
}
}

View File

@ -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;

View File

@ -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

View File

@ -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 {

View File

@ -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]

View File

@ -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]

View File

@ -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]

View File

@ -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),

View File

@ -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");
})
}

View 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"));
})
}

View File

@ -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
"#
));

View File

@ -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());
})
}

View File

@ -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]

View File

@ -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| {

View File

@ -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 = []

View File

@ -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",
] }
] }

View File

@ -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;

View File

@ -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)

View File

@ -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);

View File

@ -1,5 +1,5 @@
mod binary;
mod coloredtextw;
mod colored_text_widget;
mod cursor;
mod interactive;
mod preview;

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"]

View File

@ -26,4 +26,4 @@ debug = 1
name = "parse"
path = "fuzz_targets/parse.rs"
test = false
doc = false
doc = false

View File

@ -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 => {

View File

@ -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,

View File

@ -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();

View File

@ -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 }

View File

@ -24,4 +24,4 @@ debug = 1
name = "path"
path = "fuzz_targets/path_fuzzer.rs"
test = false
doc = false
doc = false

View File

@ -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 }

View File

@ -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",
] }
] }

View File

@ -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 = []

View File

@ -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"

View File

@ -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"] }

View File

@ -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"

View File

@ -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

View File

@ -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))?;

View File

@ -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,

View File

@ -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 {

View File

@ -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)),

View File

@ -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"

View File

@ -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

View 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)
}

View File

@ -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",
]}
]}

View 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
}
}

View File

@ -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::*;

View 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
}
}

View File

@ -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" }

View File

@ -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 }

View File

@ -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 }

View File

@ -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"] }

View File

@ -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 }
}
]
}
}

View File

@ -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')

View File

@ -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" }

View File

@ -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" }

View File

@ -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