forked from extern/nushell
Compare commits
28 Commits
dependabot
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
c74cff213e | ||
|
2ea5819b02 | ||
|
1115190a49 | ||
|
7132c5ad02 | ||
|
1920ece759 | ||
|
6f59abaf43 | ||
|
5f7425a7b4 | ||
|
1ab9ec3ebc | ||
|
f2095ed0cc | ||
|
7e26b4fcc2 | ||
|
ad95e4cc27 | ||
|
ee5a18167c | ||
|
75c9e3e5df | ||
|
77f10eb270 | ||
|
42bb42a2e1 | ||
|
f597380112 | ||
|
b92b4120dc | ||
|
de5ad5de19 | ||
|
64695cd67c | ||
|
21b3eeed99 | ||
|
a86a7e6c29 | ||
|
15421dc45e | ||
|
9522052063 | ||
|
ba880277bf | ||
|
40241e9be6 | ||
|
34f3da7150 | ||
|
534287ed65 | ||
|
913c2b8d1c |
1
.github/.typos.toml
vendored
1
.github/.typos.toml
vendored
@ -12,3 +12,4 @@ IIF = "IIF"
|
||||
numer = "numer"
|
||||
ratatui = "ratatui"
|
||||
doas = "doas"
|
||||
wheres = "wheres"
|
||||
|
2
.github/workflows/nightly-build.yml
vendored
2
.github/workflows/nightly-build.yml
vendored
@ -119,7 +119,7 @@ jobs:
|
||||
os: ubuntu-20.04
|
||||
target_rustflags: ''
|
||||
- target: riscv64gc-unknown-linux-gnu
|
||||
os: ubuntu-20.04
|
||||
os: ubuntu-latest
|
||||
target_rustflags: ''
|
||||
|
||||
runs-on: ${{matrix.os}}
|
||||
|
8
.github/workflows/release-pkg.nu
vendored
8
.github/workflows/release-pkg.nu
vendored
@ -82,8 +82,8 @@ print $'Start building ($bin)...'; hr-line
|
||||
# ----------------------------------------------------------------------------
|
||||
# Build for Ubuntu and macOS
|
||||
# ----------------------------------------------------------------------------
|
||||
if $os in [$USE_UBUNTU, 'macos-latest'] {
|
||||
if $os == $USE_UBUNTU {
|
||||
if $os in [$USE_UBUNTU, 'macos-latest', 'ubuntu-latest'] {
|
||||
if $os starts-with ubuntu {
|
||||
sudo apt update
|
||||
sudo apt-get install libxcb-composite0-dev -y
|
||||
}
|
||||
@ -106,7 +106,7 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
|
||||
_ => {
|
||||
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||
# Actually just for x86_64-unknown-linux-musl target
|
||||
if $os == $USE_UBUNTU { sudo apt install musl-tools -y }
|
||||
if $os starts-with ubuntu { sudo apt install musl-tools -y }
|
||||
cargo-build-nu $flags
|
||||
}
|
||||
}
|
||||
@ -153,7 +153,7 @@ if ($ver | str trim | is-empty) {
|
||||
# Create a release archive and send it to output for the following steps
|
||||
# ----------------------------------------------------------------------------
|
||||
cd $dist; print $'(char nl)Creating release archive...'; hr-line
|
||||
if $os in [$USE_UBUNTU, 'macos-latest'] {
|
||||
if $os in [$USE_UBUNTU, 'macos-latest', 'ubuntu-latest'] {
|
||||
|
||||
let files = (ls | get name)
|
||||
let dest = if $env.RELEASE_TYPE == 'full' { $'($bin)-($version)-($FULL_NAME)' } else { $'($bin)-($version)-($target)' }
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -66,7 +66,7 @@ jobs:
|
||||
os: ubuntu-20.04
|
||||
target_rustflags: ''
|
||||
- target: riscv64gc-unknown-linux-gnu
|
||||
os: ubuntu-20.04
|
||||
os: ubuntu-latest
|
||||
target_rustflags: ''
|
||||
|
||||
runs-on: ${{matrix.os}}
|
||||
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@ -10,6 +10,6 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@v1.16.25
|
||||
uses: crate-ci/typos@v1.17.0
|
||||
with:
|
||||
config: ./.github/.typos.toml
|
||||
|
122
Cargo.lock
generated
122
Cargo.lock
generated
@ -494,15 +494,15 @@ checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc"
|
||||
|
||||
[[package]]
|
||||
name = "calamine"
|
||||
version = "0.23.1"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47a4d6ea525ea187df1e3a1c4b23469b1cbe60c5bafc1c0ef14b2b8738a8303d"
|
||||
checksum = "fe0ba51a659bb6c8bffd6f7c1c5ffafcafa0c97e4769411d841c3cc5c154ab47"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"codepage",
|
||||
"encoding_rs",
|
||||
"log",
|
||||
"quick-xml 0.31.0",
|
||||
"quick-xml",
|
||||
"serde",
|
||||
"zip",
|
||||
]
|
||||
@ -1281,7 +1281,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"rustix 0.38.28",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -1802,9 +1802,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ical"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "356d82bd58997815d55ea6f9081bd4cac149e50ca943f7a4f7c050fec7271c1f"
|
||||
checksum = "26393c372d4c4d51616084afe36c0b44e4467febaa6f91f11f789094b4863bf9"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
@ -1884,17 +1884,6 @@ version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0508c56cfe9bfd5dfeb0c22ab9a6abfda2f27bdca422132e494266351ed8d83c"
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-docker"
|
||||
version = "0.2.0"
|
||||
@ -1911,7 +1900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"rustix 0.38.28",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -2233,12 +2222,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.12"
|
||||
@ -2293,9 +2276,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lsp-types"
|
||||
version = "0.94.1"
|
||||
version = "0.95.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1"
|
||||
checksum = "158c1911354ef73e8fe42da6b10c0484cb65c7f1007f28022e847706c1ab6984"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"serde",
|
||||
@ -2697,7 +2680,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"reedline",
|
||||
"rstest",
|
||||
"sysinfo",
|
||||
"sysinfo 0.30.4",
|
||||
"unicode-segmentation",
|
||||
"uuid",
|
||||
"which 5.0.0",
|
||||
@ -2861,7 +2844,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"print-positions",
|
||||
"procfs",
|
||||
"quick-xml 0.30.0",
|
||||
"quick-xml",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
"rand",
|
||||
@ -2876,7 +2859,7 @@ dependencies = [
|
||||
"serde_urlencoded",
|
||||
"serde_yaml",
|
||||
"sha2",
|
||||
"sysinfo",
|
||||
"sysinfo 0.30.4",
|
||||
"tabled",
|
||||
"terminal_size 0.3.0",
|
||||
"titlecase",
|
||||
@ -2924,7 +2907,7 @@ dependencies = [
|
||||
"nu-utils",
|
||||
"ratatui",
|
||||
"strip-ansi-escapes",
|
||||
"terminal_size 0.2.6",
|
||||
"terminal_size 0.3.0",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
@ -3060,8 +3043,8 @@ dependencies = [
|
||||
"ntapi",
|
||||
"once_cell",
|
||||
"procfs",
|
||||
"sysinfo",
|
||||
"winapi",
|
||||
"sysinfo 0.30.4",
|
||||
"windows 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4089,7 +4072,7 @@ dependencies = [
|
||||
"polars-error",
|
||||
"rayon",
|
||||
"smartstring",
|
||||
"sysinfo",
|
||||
"sysinfo 0.29.11",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
@ -4194,7 +4177,7 @@ dependencies = [
|
||||
"hex",
|
||||
"lazy_static",
|
||||
"procfs-core",
|
||||
"rustix 0.38.28",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4250,15 +4233,6 @@ name = "quick-xml"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"memchr",
|
||||
@ -4401,12 +4375,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "reedline"
|
||||
version = "0.27.1"
|
||||
source = "git+https://github.com/nushell/reedline.git?branch=main#e097b88dab538705c7b165cf3a1f5cf3a74a23bb"
|
||||
source = "git+https://github.com/nushell/reedline.git?branch=main#b68ce33c750b700bb4f8adccbc2e160d1b7c8742"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossterm",
|
||||
"fd-lock",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.12.0",
|
||||
"nu-ansi-term",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
@ -4653,20 +4627,6 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys 0.3.8",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.28"
|
||||
@ -4676,7 +4636,7 @@ dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.12",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@ -4908,9 +4868,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shadow-rs"
|
||||
version = "0.25.0"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "615d846f7174a0850dca101bca72f6913e3376a64c5fda2b965d7fc3d1ff60cb"
|
||||
checksum = "878cb1e3162d98ee1016b832efbb683ad6302b462a2894c54f488dc0bd96f11c"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"is_debug",
|
||||
@ -5289,10 +5249,24 @@ dependencies = [
|
||||
"libc",
|
||||
"ntapi",
|
||||
"once_cell",
|
||||
"rayon",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.30.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "717570a2533606f81f8cfac02a1915a620e725ffb78f6fc5e259769a4d747407"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"ntapi",
|
||||
"once_cell",
|
||||
"rayon",
|
||||
"windows 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tabled"
|
||||
version = "0.14.0"
|
||||
@ -5320,7 +5294,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall",
|
||||
"rustix 0.38.28",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -5354,23 +5328,13 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237"
|
||||
dependencies = [
|
||||
"rustix 0.37.27",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
|
||||
dependencies = [
|
||||
"rustix 0.38.28",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -6078,7 +6042,7 @@ dependencies = [
|
||||
"either",
|
||||
"home",
|
||||
"once_cell",
|
||||
"rustix 0.38.28",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6090,7 +6054,7 @@ dependencies = [
|
||||
"either",
|
||||
"home",
|
||||
"once_cell",
|
||||
"rustix 0.38.28",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -6405,8 +6369,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7dae5072fe1f8db8f8d29059189ac175196e410e40ba42d5d4684ae2f750995"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.12",
|
||||
"rustix 0.38.28",
|
||||
"linux-raw-sys",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -37,7 +37,7 @@ miette = { version = "5.10", features = ["fancy-no-backtrace"] }
|
||||
once_cell = "1.18"
|
||||
percent-encoding = "2"
|
||||
pathdiff = "0.2"
|
||||
sysinfo = "0.29"
|
||||
sysinfo = "0.30"
|
||||
unicode-segmentation = "1.10"
|
||||
uuid = { version = "1.6.0", features = ["v4"] }
|
||||
which = "5.0.0"
|
||||
|
@ -354,9 +354,7 @@ impl NuCompleter {
|
||||
if let Some(external_result) = self.external_completion(
|
||||
block_id, &spans, offset, new_span,
|
||||
) {
|
||||
if !external_result.is_empty() {
|
||||
return external_result;
|
||||
}
|
||||
return external_result;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ use std::{
|
||||
sync::atomic::Ordering,
|
||||
time::Instant,
|
||||
};
|
||||
use sysinfo::SystemExt;
|
||||
use sysinfo::System;
|
||||
|
||||
// According to Daniel Imms @Tyriar, we need to do these this way:
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
@ -135,17 +135,6 @@ pub fn evaluate_repl(
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let sys = sysinfo::System::new();
|
||||
perf(
|
||||
"get sysinfo",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
if let Some(s) = prerun_command {
|
||||
eval_source(
|
||||
engine_state,
|
||||
@ -428,7 +417,7 @@ pub fn evaluate_repl(
|
||||
|
||||
match input {
|
||||
Ok(Signal::Success(s)) => {
|
||||
let hostname = sys.host_name();
|
||||
let hostname = System::host_name();
|
||||
let history_supports_meta =
|
||||
matches!(config.history_file_format, HistoryFileFormat::Sqlite);
|
||||
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
|
||||
|
@ -385,6 +385,7 @@ fn find_matching_block_end_in_expr(
|
||||
Argument::Named((_, _, opt_expr)) => opt_expr.as_ref(),
|
||||
Argument::Positional(inner_expr) => Some(inner_expr),
|
||||
Argument::Unknown(inner_expr) => Some(inner_expr),
|
||||
Argument::Spread(inner_expr) => Some(inner_expr),
|
||||
};
|
||||
|
||||
if let Some(inner_expr) = opt_expr {
|
||||
|
@ -20,10 +20,10 @@ nu-ansi-term = "0.49.0"
|
||||
|
||||
fancy-regex = "0.12"
|
||||
itertools = "0.12"
|
||||
shadow-rs = { version = "0.25", default-features = false }
|
||||
shadow-rs = { version = "0.26", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { version = "0.25", default-features = false }
|
||||
shadow-rs = { version = "0.26", default-features = false }
|
||||
|
||||
[features]
|
||||
mimalloc = []
|
||||
|
@ -32,7 +32,7 @@ alphanumeric-sort = "1.5"
|
||||
base64 = "0.21"
|
||||
byteorder = "1.5"
|
||||
bytesize = "1.3"
|
||||
calamine = "0.23"
|
||||
calamine = "0.22"
|
||||
chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false }
|
||||
chrono-humanize = "0.2.3"
|
||||
chrono-tz = "0.8"
|
||||
@ -80,7 +80,7 @@ serde_json = "1.0"
|
||||
serde_urlencoded = "0.7"
|
||||
serde_yaml = "0.9"
|
||||
sha2 = "0.10"
|
||||
sysinfo = "0.29"
|
||||
sysinfo = "0.30"
|
||||
tabled = { version = "0.14.0", features = ["color"], default-features = false }
|
||||
terminal_size = "0.3"
|
||||
titlecase = "2.0"
|
||||
|
@ -1,4 +1,4 @@
|
||||
use nu_engine::eval_expression;
|
||||
use nu_engine::{eval_expression, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
@ -48,8 +48,7 @@ impl Command for BytesBuild {
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let mut output = vec![];
|
||||
for expr in call.positional_iter() {
|
||||
let val = eval_expression(engine_state, stack, expr)?;
|
||||
for val in call.rest_iter_flattened(0, |expr| eval_expression(engine_state, stack, expr))? {
|
||||
match val {
|
||||
Value::Binary { mut val, .. } => output.append(&mut val),
|
||||
// Explicitly propagate errors instead of dropping them.
|
||||
|
@ -105,6 +105,13 @@ impl Command for BytesRemove {
|
||||
vec![0x10, 0xAA, 0x10, 0xBB, 0xCC, 0xAA],
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Remove find binary from end not found",
|
||||
example: "0x[10 AA 10 BB CC AA 10] | bytes remove --end 0x[11]",
|
||||
result: Some(Value::test_binary (
|
||||
vec![0x10, 0xAA, 0x10, 0xBB, 0xCC, 0xAA, 0x10],
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Remove all occurrences of find binary in table",
|
||||
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes remove 0x[11] ColA ColC",
|
||||
@ -159,8 +166,11 @@ fn remove_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||
}
|
||||
// append the remaining thing to result, this can be happening when
|
||||
// we have something to remove and remove_all is False.
|
||||
let mut remain = input[..left as usize].iter().copied().rev().collect();
|
||||
result.append(&mut remain);
|
||||
// check if the left is positive, if it is not, we don't need to append anything.
|
||||
if left > 0 {
|
||||
let mut remain = input[..left as usize].iter().copied().rev().collect();
|
||||
result.append(&mut remain);
|
||||
}
|
||||
result = result.into_iter().rev().collect();
|
||||
Value::binary(result, span)
|
||||
} else {
|
||||
|
@ -191,6 +191,24 @@ fn get_arguments(engine_state: &EngineState, stack: &mut Stack, call: Call) -> V
|
||||
let arg_value_name_span_start = evaled_span.start as i64;
|
||||
let arg_value_name_span_end = evaled_span.end as i64;
|
||||
|
||||
let record = record! {
|
||||
"arg_type" => Value::string(arg_type, span),
|
||||
"name" => Value::string(arg_value_name, inner_expr.span),
|
||||
"type" => Value::string(arg_value_type, span),
|
||||
"span_start" => Value::int(arg_value_name_span_start, span),
|
||||
"span_end" => Value::int(arg_value_name_span_end, span),
|
||||
};
|
||||
arg_value.push(Value::record(record, inner_expr.span));
|
||||
}
|
||||
Argument::Spread(inner_expr) => {
|
||||
let arg_type = "spread";
|
||||
let evaluated_expression = get_expression_as_value(engine_state, stack, inner_expr);
|
||||
let arg_value_name = debug_string_without_formatting(&evaluated_expression);
|
||||
let arg_value_type = &evaluated_expression.get_type().to_string();
|
||||
let evaled_span = evaluated_expression.span();
|
||||
let arg_value_name_span_start = evaled_span.start as i64;
|
||||
let arg_value_name_span_end = evaled_span.end as i64;
|
||||
|
||||
let record = record! {
|
||||
"arg_type" => Value::string(arg_type, span),
|
||||
"name" => Value::string(arg_value_name, inner_expr.span),
|
||||
|
@ -4,7 +4,7 @@ use nu_protocol::{
|
||||
record, Category, Example, IntoPipelineData, LazyRecord, PipelineData, Record, ShellError,
|
||||
Signature, Span, Type, Value,
|
||||
};
|
||||
use sysinfo::{Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt};
|
||||
use sysinfo::{MemoryRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
|
||||
const ENV_PATH_SEPARATOR_CHAR: char = {
|
||||
#[cfg(target_family = "windows")]
|
||||
{
|
||||
@ -98,8 +98,9 @@ impl LazySystemInfoRecord {
|
||||
}
|
||||
"system" => {
|
||||
// only get information requested
|
||||
let system_opt =
|
||||
SystemOpt::from((system_option, || RefreshKind::new().with_memory()));
|
||||
let system_opt = SystemOpt::from((system_option, || {
|
||||
RefreshKind::new().with_memory(MemoryRefreshKind::everything())
|
||||
}));
|
||||
|
||||
let system = system_opt.get_system();
|
||||
|
||||
@ -135,53 +136,66 @@ impl LazySystemInfoRecord {
|
||||
"virtual_memory" => Value::filesize(p.virtual_memory() as i64, self.span),
|
||||
"status" => Value::string(p.status().to_string(), self.span),
|
||||
"root" => {
|
||||
if let Some(filename) = p.exe().parent() {
|
||||
Value::string(filename.to_string_lossy().to_string(), self.span)
|
||||
if let Some(path) = p.exe().and_then(|p| p.parent()) {
|
||||
Value::string(path.to_string_lossy().to_string(), self.span)
|
||||
} else {
|
||||
Value::nothing(self.span)
|
||||
}
|
||||
},
|
||||
"cwd" => Value::string(p.cwd().to_string_lossy().to_string(), self.span),
|
||||
"exe_path" => Value::string(p.exe().to_string_lossy().to_string(), self.span),
|
||||
"cwd" => {
|
||||
if let Some(path) = p.cwd() {
|
||||
Value::string(path.to_string_lossy().to_string(), self.span)
|
||||
}else{
|
||||
Value::nothing(self.span)
|
||||
}
|
||||
},
|
||||
"exe_path" => {
|
||||
if let Some(path)= p.exe() {
|
||||
Value::string(path.to_string_lossy().to_string(), self.span)
|
||||
}else{
|
||||
Value::nothing(self.span)
|
||||
}
|
||||
},
|
||||
"command" => Value::string(p.cmd().join(" "), self.span),
|
||||
"name" => Value::string(p.name().to_string(), self.span),
|
||||
"environment" => {
|
||||
let mut env_rec = Record::new();
|
||||
for val in p.environ() {
|
||||
if let Some((key, value)) = val.split_once('=') {
|
||||
let is_env_var_a_list = {
|
||||
"environment" => {
|
||||
let mut env_rec = Record::new();
|
||||
for val in p.environ() {
|
||||
if let Some((key, value)) = val.split_once('=') {
|
||||
let is_env_var_a_list = {
|
||||
{
|
||||
#[cfg(target_family = "windows")]
|
||||
{
|
||||
#[cfg(target_family = "windows")]
|
||||
{
|
||||
key == "Path" || key == "PATHEXT" || key == "PSMODULEPATH" || key == "PSModulePath"
|
||||
}
|
||||
#[cfg(not(target_family = "windows"))]
|
||||
{
|
||||
key == "PATH" || key == "DYLD_FALLBACK_LIBRARY_PATH"
|
||||
}
|
||||
key == "Path" || key == "PATHEXT" || key == "PSMODULEPATH" || key == "PSModulePath"
|
||||
}
|
||||
#[cfg(not(target_family = "windows"))]
|
||||
{
|
||||
key == "PATH" || key == "DYLD_FALLBACK_LIBRARY_PATH"
|
||||
}
|
||||
};
|
||||
if is_env_var_a_list {
|
||||
let items = value.split(ENV_PATH_SEPARATOR_CHAR).map(|r| Value::string(r.to_string(), self.span)).collect::<Vec<_>>();
|
||||
env_rec.push(key.to_string(), Value::list(items, self.span));
|
||||
} else if key == "LS_COLORS" { // LS_COLORS is a special case, it's a colon separated list of key=value pairs
|
||||
let items = value.split(':').map(|r| Value::string(r.to_string(), self.span)).collect::<Vec<_>>();
|
||||
env_rec.push(key.to_string(), Value::list(items, self.span));
|
||||
} else {
|
||||
env_rec.push(key.to_string(), Value::string(value.to_string(), self.span));
|
||||
}
|
||||
};
|
||||
if is_env_var_a_list {
|
||||
let items = value.split(ENV_PATH_SEPARATOR_CHAR).map(|r| Value::string(r.to_string(), self.span)).collect::<Vec<_>>();
|
||||
env_rec.push(key.to_string(), Value::list(items, self.span));
|
||||
} else if key == "LS_COLORS" { // LS_COLORS is a special case, it's a colon separated list of key=value pairs
|
||||
let items = value.split(':').map(|r| Value::string(r.to_string(), self.span)).collect::<Vec<_>>();
|
||||
env_rec.push(key.to_string(), Value::list(items, self.span));
|
||||
} else {
|
||||
env_rec.push(key.to_string(), Value::string(value.to_string(), self.span));
|
||||
}
|
||||
}
|
||||
Value::record(env_rec, self.span)
|
||||
},
|
||||
}
|
||||
Value::record(env_rec, self.span)
|
||||
},
|
||||
},
|
||||
self.span,
|
||||
))
|
||||
} else {
|
||||
// If we can't get the process information, just return the system information
|
||||
// only get information requested
|
||||
let system_opt =
|
||||
SystemOpt::from((system_option, || RefreshKind::new().with_memory()));
|
||||
let system_opt = SystemOpt::from((system_option, || {
|
||||
RefreshKind::new().with_memory(MemoryRefreshKind::everything())
|
||||
}));
|
||||
let system = system_opt.get_system();
|
||||
|
||||
Ok(Value::record(
|
||||
@ -228,7 +242,7 @@ impl<'a> LazyRecord<'a> for LazySystemInfoRecord {
|
||||
.without_disk_usage()
|
||||
.without_user(),
|
||||
)
|
||||
.with_memory();
|
||||
.with_memory(MemoryRefreshKind::everything());
|
||||
// only get information requested
|
||||
let system = System::new_with_specifics(rk);
|
||||
|
||||
|
@ -381,11 +381,23 @@ fn rm(
|
||||
{
|
||||
unreachable!()
|
||||
}
|
||||
} else if metadata.is_file()
|
||||
|| is_socket
|
||||
|| is_fifo
|
||||
|| metadata.file_type().is_symlink()
|
||||
{
|
||||
} else if metadata.is_symlink() {
|
||||
// In Windows, symlink pointing to a directory can be removed using
|
||||
// std::fs::remove_dir instead of std::fs::remove_file.
|
||||
#[cfg(windows)]
|
||||
{
|
||||
f.metadata().and_then(|metadata| {
|
||||
if metadata.is_dir() {
|
||||
std::fs::remove_dir(&f)
|
||||
} else {
|
||||
std::fs::remove_file(&f)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
std::fs::remove_file(&f)
|
||||
} else if metadata.is_file() || is_socket || is_fifo {
|
||||
std::fs::remove_file(&f)
|
||||
} else {
|
||||
std::fs::remove_dir_all(&f)
|
||||
|
@ -34,10 +34,10 @@ impl Command for ToXml {
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"Every XML entry is represented via a record with tag, attribute and content fields.
|
||||
To represent different types of entries different values must be written to this fields:
|
||||
1. Tag entry: `{tag: <tag name> attrs: {<attr name>: "<string value>" ...} content: [<entries>]}`
|
||||
2. Comment entry: `{tag: '!' attrs: null content: "<comment string>"}`
|
||||
3. Processing instruction (PI): `{tag: '?<pi name>' attrs: null content: "<pi content string>"}`
|
||||
4. Text: `{tag: null attrs: null content: "<text>"}`. Or as plain `<text>` instead of record.
|
||||
1. Tag entry: `{tag: <tag name> attributes: {<attr name>: "<string value>" ...} content: [<entries>]}`
|
||||
2. Comment entry: `{tag: '!' attributes: null content: "<comment string>"}`
|
||||
3. Processing instruction (PI): `{tag: '?<pi name>' attributes: null content: "<pi content string>"}`
|
||||
4. Text: `{tag: null attributes: null content: "<text>"}`. Or as plain `<text>` instead of record.
|
||||
|
||||
Additionally any field which is: empty record, empty list or null, can be omitted."#
|
||||
}
|
||||
@ -46,7 +46,7 @@ Additionally any field which is: empty record, empty list or null, can be omitte
|
||||
vec![
|
||||
Example {
|
||||
description: "Outputs an XML string representing the contents of this table",
|
||||
example: r#"{tag: note attributes: {} content : [{tag: remember attributes: {} content : [{tag: null attrs: null content : Event}]}]} | to xml"#,
|
||||
example: r#"{tag: note attributes: {} content : [{tag: remember attributes: {} content : [{tag: null attributes: null content : Event}]}]} | to xml"#,
|
||||
result: Some(Value::test_string(
|
||||
"<note><remember>Event</remember></note>",
|
||||
)),
|
||||
@ -110,6 +110,17 @@ fn to_xml_entry<W: Write>(
|
||||
}
|
||||
|
||||
if let Value::Record { val: record, .. } = &entry {
|
||||
if let Some(bad_column) = find_invalid_column(record) {
|
||||
return Err(ShellError::CantConvert {
|
||||
to_type: "XML".into(),
|
||||
from_type: "record".into(),
|
||||
span: entry_span,
|
||||
help: Some(format!(
|
||||
"Invalid column \"{}\" in xml entry. Only \"{}\", \"{}\" and \"{}\" are permitted",
|
||||
bad_column, COLUMN_TAG_NAME, COLUMN_ATTRS_NAME, COLUMN_CONTENT_NAME
|
||||
)),
|
||||
});
|
||||
}
|
||||
// If key is not found it is assumed to be nothing. This way
|
||||
// user can write a tag like {tag: a content: [...]} instead
|
||||
// of longer {tag: a attributes: {} content: [...]}
|
||||
@ -144,7 +155,12 @@ fn to_xml_entry<W: Write>(
|
||||
(Value::String { val: tag_name, .. }, attrs, children) => to_tag_like(
|
||||
entry_span, tag_name, tag_span, attrs, children, top_level, writer,
|
||||
),
|
||||
_ => Ok(()),
|
||||
_ => Err(ShellError::CantConvert {
|
||||
to_type: "XML".into(),
|
||||
from_type: "record".into(),
|
||||
span: entry_span,
|
||||
help: Some("Tag missing or is not a string".into()),
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::CantConvert {
|
||||
@ -156,6 +172,14 @@ fn to_xml_entry<W: Write>(
|
||||
}
|
||||
}
|
||||
|
||||
fn find_invalid_column(record: &Record) -> Option<&String> {
|
||||
const VALID_COLS: [&str; 3] = [COLUMN_TAG_NAME, COLUMN_ATTRS_NAME, COLUMN_CONTENT_NAME];
|
||||
record
|
||||
.cols
|
||||
.iter()
|
||||
.find(|col| !VALID_COLS.contains(&col.as_str()))
|
||||
}
|
||||
|
||||
/// Convert record to tag-like entry: tag, PI, comment.
|
||||
fn to_tag_like<W: Write>(
|
||||
entry_span: Span,
|
||||
|
@ -5,7 +5,8 @@ use base64::{alphabet, Engine};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
record, BufferedReader, IntoPipelineData, PipelineData, RawStream, ShellError, Span, Value,
|
||||
record, BufferedReader, IntoPipelineData, PipelineData, RawStream, ShellError, Span, Spanned,
|
||||
Value,
|
||||
};
|
||||
use ureq::{Error, ErrorKind, Request, Response};
|
||||
|
||||
@ -26,29 +27,45 @@ pub enum BodyType {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
// Only panics if the user agent is invalid but we define it statically so either
|
||||
// it always or never fails
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum RedirectMode {
|
||||
Follow,
|
||||
Error,
|
||||
Manual,
|
||||
}
|
||||
|
||||
pub fn http_client(
|
||||
allow_insecure: bool,
|
||||
redirect_mode: RedirectMode,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
) -> ureq::Agent {
|
||||
) -> Result<ureq::Agent, ShellError> {
|
||||
let tls = native_tls::TlsConnector::builder()
|
||||
.danger_accept_invalid_certs(allow_insecure)
|
||||
.build()
|
||||
.expect("Failed to build network tls");
|
||||
.map_err(|e| ShellError::GenericError {
|
||||
error: format!("Failed to build network tls: {}", e),
|
||||
msg: String::new(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
|
||||
let mut agent_builder = ureq::builder()
|
||||
.user_agent("nushell")
|
||||
.tls_connector(std::sync::Arc::new(tls));
|
||||
|
||||
if let RedirectMode::Manual | RedirectMode::Error = redirect_mode {
|
||||
agent_builder = agent_builder.redirects(0);
|
||||
}
|
||||
|
||||
if let Some(http_proxy) = retrieve_http_proxy_from_env(engine_state, stack) {
|
||||
if let Ok(proxy) = ureq::Proxy::new(http_proxy) {
|
||||
agent_builder = agent_builder.proxy(proxy);
|
||||
}
|
||||
};
|
||||
|
||||
agent_builder.build()
|
||||
Ok(agent_builder.build())
|
||||
}
|
||||
|
||||
pub fn http_parse_url(
|
||||
@ -68,6 +85,18 @@ pub fn http_parse_url(
|
||||
Ok((requested_url, url))
|
||||
}
|
||||
|
||||
pub fn http_parse_redirect_mode(mode: Option<Spanned<String>>) -> Result<RedirectMode, ShellError> {
|
||||
mode.map_or(Ok(RedirectMode::Follow), |v| match &v.item[..] {
|
||||
"follow" | "f" => Ok(RedirectMode::Follow),
|
||||
"error" | "e" => Ok(RedirectMode::Error),
|
||||
"manual" | "m" => Ok(RedirectMode::Manual),
|
||||
_ => Err(ShellError::TypeMismatch {
|
||||
err_message: "Invalid redirect handling mode".to_string(),
|
||||
span: v.span,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn response_to_buffer(
|
||||
response: Response,
|
||||
engine_state: &EngineState,
|
||||
@ -452,6 +481,26 @@ fn transform_response_using_content_type(
|
||||
};
|
||||
}
|
||||
|
||||
pub fn check_response_redirection(
|
||||
redirect_mode: RedirectMode,
|
||||
span: Span,
|
||||
response: &Result<Response, ShellErrorOrRequestError>,
|
||||
) -> Result<(), ShellError> {
|
||||
if let Ok(resp) = response {
|
||||
if RedirectMode::Error == redirect_mode && (300..400).contains(&resp.status()) {
|
||||
return Err(ShellError::NetworkFailure {
|
||||
msg: format!(
|
||||
"Redirect encountered when redirect handling mode was 'error' ({} {})",
|
||||
resp.status(),
|
||||
resp.status_text()
|
||||
),
|
||||
span,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn request_handle_response_content(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
|
@ -2,12 +2,13 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
use crate::network::http::client::{
|
||||
http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers,
|
||||
request_handle_response, request_set_timeout, send_request,
|
||||
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
|
||||
request_add_authorization_header, request_add_custom_headers, request_handle_response,
|
||||
request_set_timeout, send_request,
|
||||
};
|
||||
|
||||
use super::client::RequestFlags;
|
||||
@ -79,6 +80,11 @@ impl Command for SubCommand {
|
||||
"allow-errors",
|
||||
"do not fail if the server returns an error code",
|
||||
Some('e'),
|
||||
).named(
|
||||
"redirect-mode",
|
||||
SyntaxShape::String,
|
||||
"What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
|
||||
Some('R')
|
||||
)
|
||||
.filter()
|
||||
.category(Category::Network)
|
||||
@ -150,6 +156,7 @@ struct Arguments {
|
||||
timeout: Option<Value>,
|
||||
full: bool,
|
||||
allow_errors: bool,
|
||||
redirect: Option<Spanned<String>>,
|
||||
}
|
||||
|
||||
fn run_delete(
|
||||
@ -170,6 +177,7 @@ fn run_delete(
|
||||
timeout: call.get_flag(engine_state, stack, "max-time")?,
|
||||
full: call.has_flag("full"),
|
||||
allow_errors: call.has_flag("allow-errors"),
|
||||
redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
|
||||
};
|
||||
|
||||
helper(engine_state, stack, call, args)
|
||||
@ -186,8 +194,9 @@ fn helper(
|
||||
let span = args.url.span();
|
||||
let ctrl_c = engine_state.ctrlc.clone();
|
||||
let (requested_url, _) = http_parse_url(call, span, args.url)?;
|
||||
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
|
||||
|
||||
let client = http_client(args.insecure, engine_state, stack);
|
||||
let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
|
||||
let mut request = client.delete(&requested_url);
|
||||
|
||||
request = request_set_timeout(args.timeout, request)?;
|
||||
@ -202,6 +211,7 @@ fn helper(
|
||||
allow_errors: args.allow_errors,
|
||||
};
|
||||
|
||||
check_response_redirection(redirect_mode, span, &response)?;
|
||||
request_handle_response(
|
||||
engine_state,
|
||||
stack,
|
||||
|
@ -2,16 +2,15 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
use crate::network::http::client::{
|
||||
http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers,
|
||||
request_handle_response, request_set_timeout, send_request,
|
||||
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
|
||||
request_add_authorization_header, request_add_custom_headers, request_handle_response,
|
||||
request_set_timeout, send_request, RequestFlags,
|
||||
};
|
||||
|
||||
use super::client::RequestFlags;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
@ -73,6 +72,12 @@ impl Command for SubCommand {
|
||||
"do not fail if the server returns an error code",
|
||||
Some('e'),
|
||||
)
|
||||
.named(
|
||||
"redirect-mode",
|
||||
SyntaxShape::String,
|
||||
"What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
|
||||
Some('R')
|
||||
)
|
||||
.filter()
|
||||
.category(Category::Network)
|
||||
}
|
||||
@ -137,6 +142,7 @@ struct Arguments {
|
||||
timeout: Option<Value>,
|
||||
full: bool,
|
||||
allow_errors: bool,
|
||||
redirect: Option<Spanned<String>>,
|
||||
}
|
||||
|
||||
fn run_get(
|
||||
@ -155,6 +161,7 @@ fn run_get(
|
||||
timeout: call.get_flag(engine_state, stack, "max-time")?,
|
||||
full: call.has_flag("full"),
|
||||
allow_errors: call.has_flag("allow-errors"),
|
||||
redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
|
||||
};
|
||||
helper(engine_state, stack, call, args)
|
||||
}
|
||||
@ -170,8 +177,9 @@ fn helper(
|
||||
let span = args.url.span();
|
||||
let ctrl_c = engine_state.ctrlc.clone();
|
||||
let (requested_url, _) = http_parse_url(call, span, args.url)?;
|
||||
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
|
||||
|
||||
let client = http_client(args.insecure, engine_state, stack);
|
||||
let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
|
||||
let mut request = client.get(&requested_url);
|
||||
|
||||
request = request_set_timeout(args.timeout, request)?;
|
||||
@ -186,6 +194,7 @@ fn helper(
|
||||
allow_errors: args.allow_errors,
|
||||
};
|
||||
|
||||
check_response_redirection(redirect_mode, span, &response)?;
|
||||
request_handle_response(
|
||||
engine_state,
|
||||
stack,
|
||||
|
@ -5,12 +5,13 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
use crate::network::http::client::{
|
||||
http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers,
|
||||
request_handle_response_headers, request_set_timeout, send_request,
|
||||
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
|
||||
request_add_authorization_header, request_add_custom_headers, request_handle_response_headers,
|
||||
request_set_timeout, send_request,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -58,6 +59,11 @@ impl Command for SubCommand {
|
||||
"insecure",
|
||||
"allow insecure server connections when using SSL",
|
||||
Some('k'),
|
||||
).named(
|
||||
"redirect-mode",
|
||||
SyntaxShape::String,
|
||||
"What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
|
||||
Some('R')
|
||||
)
|
||||
.filter()
|
||||
.category(Category::Network)
|
||||
@ -114,6 +120,7 @@ struct Arguments {
|
||||
user: Option<String>,
|
||||
password: Option<String>,
|
||||
timeout: Option<Value>,
|
||||
redirect: Option<Spanned<String>>,
|
||||
}
|
||||
|
||||
fn run_head(
|
||||
@ -129,6 +136,7 @@ fn run_head(
|
||||
user: call.get_flag(engine_state, stack, "user")?,
|
||||
password: call.get_flag(engine_state, stack, "password")?,
|
||||
timeout: call.get_flag(engine_state, stack, "max-time")?,
|
||||
redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
|
||||
};
|
||||
let ctrl_c = engine_state.ctrlc.clone();
|
||||
|
||||
@ -146,8 +154,9 @@ fn helper(
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = args.url.span();
|
||||
let (requested_url, _) = http_parse_url(call, span, args.url)?;
|
||||
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
|
||||
|
||||
let client = http_client(args.insecure, engine_state, stack);
|
||||
let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
|
||||
let mut request = client.head(&requested_url);
|
||||
|
||||
request = request_set_timeout(args.timeout, request)?;
|
||||
@ -155,6 +164,7 @@ fn helper(
|
||||
request = request_add_custom_headers(args.headers, request)?;
|
||||
|
||||
let response = send_request(request, None, None, ctrlc);
|
||||
check_response_redirection(redirect_mode, span, &response)?;
|
||||
request_handle_response_headers(span, response)
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ use crate::network::http::client::{
|
||||
request_handle_response, request_set_timeout, send_request,
|
||||
};
|
||||
|
||||
use super::client::RequestFlags;
|
||||
use super::client::{RedirectMode, RequestFlags};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
@ -160,7 +160,7 @@ fn helper(
|
||||
let ctrl_c = engine_state.ctrlc.clone();
|
||||
let (requested_url, _) = http_parse_url(call, span, args.url)?;
|
||||
|
||||
let client = http_client(args.insecure, engine_state, stack);
|
||||
let client = http_client(args.insecure, RedirectMode::Follow, engine_state, stack)?;
|
||||
let mut request = client.request("OPTIONS", &requested_url);
|
||||
|
||||
request = request_set_timeout(args.timeout, request)?;
|
||||
|
@ -2,12 +2,13 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
use crate::network::http::client::{
|
||||
http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers,
|
||||
request_handle_response, request_set_timeout, send_request,
|
||||
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
|
||||
request_add_authorization_header, request_add_custom_headers, request_handle_response,
|
||||
request_set_timeout, send_request,
|
||||
};
|
||||
|
||||
use super::client::RequestFlags;
|
||||
@ -75,6 +76,11 @@ impl Command for SubCommand {
|
||||
"allow-errors",
|
||||
"do not fail if the server returns an error code",
|
||||
Some('e'),
|
||||
).named(
|
||||
"redirect-mode",
|
||||
SyntaxShape::String,
|
||||
"What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
|
||||
Some('R')
|
||||
)
|
||||
.filter()
|
||||
.category(Category::Network)
|
||||
@ -142,6 +148,7 @@ struct Arguments {
|
||||
timeout: Option<Value>,
|
||||
full: bool,
|
||||
allow_errors: bool,
|
||||
redirect: Option<Spanned<String>>,
|
||||
}
|
||||
|
||||
fn run_patch(
|
||||
@ -162,6 +169,7 @@ fn run_patch(
|
||||
timeout: call.get_flag(engine_state, stack, "max-time")?,
|
||||
full: call.has_flag("full"),
|
||||
allow_errors: call.has_flag("allow-errors"),
|
||||
redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
|
||||
};
|
||||
|
||||
helper(engine_state, stack, call, args)
|
||||
@ -178,8 +186,9 @@ fn helper(
|
||||
let span = args.url.span();
|
||||
let ctrl_c = engine_state.ctrlc.clone();
|
||||
let (requested_url, _) = http_parse_url(call, span, args.url)?;
|
||||
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
|
||||
|
||||
let client = http_client(args.insecure, engine_state, stack);
|
||||
let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
|
||||
let mut request = client.patch(&requested_url);
|
||||
|
||||
request = request_set_timeout(args.timeout, request)?;
|
||||
@ -194,6 +203,7 @@ fn helper(
|
||||
allow_errors: args.allow_errors,
|
||||
};
|
||||
|
||||
check_response_redirection(redirect_mode, span, &response)?;
|
||||
request_handle_response(
|
||||
engine_state,
|
||||
stack,
|
||||
|
@ -2,12 +2,13 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
use crate::network::http::client::{
|
||||
http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers,
|
||||
request_handle_response, request_set_timeout, send_request,
|
||||
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
|
||||
request_add_authorization_header, request_add_custom_headers, request_handle_response,
|
||||
request_set_timeout, send_request,
|
||||
};
|
||||
|
||||
use super::client::RequestFlags;
|
||||
@ -75,6 +76,11 @@ impl Command for SubCommand {
|
||||
"allow-errors",
|
||||
"do not fail if the server returns an error code",
|
||||
Some('e'),
|
||||
).named(
|
||||
"redirect-mode",
|
||||
SyntaxShape::String,
|
||||
"What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
|
||||
Some('R')
|
||||
)
|
||||
.filter()
|
||||
.category(Category::Network)
|
||||
@ -140,6 +146,7 @@ struct Arguments {
|
||||
timeout: Option<Value>,
|
||||
full: bool,
|
||||
allow_errors: bool,
|
||||
redirect: Option<Spanned<String>>,
|
||||
}
|
||||
|
||||
fn run_post(
|
||||
@ -160,6 +167,7 @@ fn run_post(
|
||||
timeout: call.get_flag(engine_state, stack, "max-time")?,
|
||||
full: call.has_flag("full"),
|
||||
allow_errors: call.has_flag("allow-errors"),
|
||||
redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
|
||||
};
|
||||
|
||||
helper(engine_state, stack, call, args)
|
||||
@ -176,8 +184,9 @@ fn helper(
|
||||
let span = args.url.span();
|
||||
let ctrl_c = engine_state.ctrlc.clone();
|
||||
let (requested_url, _) = http_parse_url(call, span, args.url)?;
|
||||
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
|
||||
|
||||
let client = http_client(args.insecure, engine_state, stack);
|
||||
let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
|
||||
let mut request = client.post(&requested_url);
|
||||
|
||||
request = request_set_timeout(args.timeout, request)?;
|
||||
@ -192,6 +201,7 @@ fn helper(
|
||||
allow_errors: args.allow_errors,
|
||||
};
|
||||
|
||||
check_response_redirection(redirect_mode, span, &response)?;
|
||||
request_handle_response(
|
||||
engine_state,
|
||||
stack,
|
||||
|
@ -2,12 +2,13 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
use crate::network::http::client::{
|
||||
http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers,
|
||||
request_handle_response, request_set_timeout, send_request,
|
||||
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
|
||||
request_add_authorization_header, request_add_custom_headers, request_handle_response,
|
||||
request_set_timeout, send_request,
|
||||
};
|
||||
|
||||
use super::client::RequestFlags;
|
||||
@ -75,6 +76,11 @@ impl Command for SubCommand {
|
||||
"allow-errors",
|
||||
"do not fail if the server returns an error code",
|
||||
Some('e'),
|
||||
).named(
|
||||
"redirect-mode",
|
||||
SyntaxShape::String,
|
||||
"What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
|
||||
Some('R')
|
||||
)
|
||||
.filter()
|
||||
.category(Category::Network)
|
||||
@ -140,6 +146,7 @@ struct Arguments {
|
||||
timeout: Option<Value>,
|
||||
full: bool,
|
||||
allow_errors: bool,
|
||||
redirect: Option<Spanned<String>>,
|
||||
}
|
||||
|
||||
fn run_put(
|
||||
@ -160,6 +167,7 @@ fn run_put(
|
||||
timeout: call.get_flag(engine_state, stack, "max-time")?,
|
||||
full: call.has_flag("full"),
|
||||
allow_errors: call.has_flag("allow-errors"),
|
||||
redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
|
||||
};
|
||||
|
||||
helper(engine_state, stack, call, args)
|
||||
@ -176,8 +184,9 @@ fn helper(
|
||||
let span = args.url.span();
|
||||
let ctrl_c = engine_state.ctrlc.clone();
|
||||
let (requested_url, _) = http_parse_url(call, span, args.url)?;
|
||||
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
|
||||
|
||||
let client = http_client(args.insecure, engine_state, stack);
|
||||
let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
|
||||
let mut request = client.put(&requested_url);
|
||||
|
||||
request = request_set_timeout(args.timeout, request)?;
|
||||
@ -192,6 +201,7 @@ fn helper(
|
||||
allow_errors: args.allow_errors,
|
||||
};
|
||||
|
||||
check_response_redirection(redirect_mode, span, &response)?;
|
||||
request_handle_response(
|
||||
engine_state,
|
||||
stack,
|
||||
|
@ -1,8 +1,9 @@
|
||||
use nu_cmd_base::hook::eval_hook;
|
||||
use nu_engine::env_to_strings;
|
||||
use nu_engine::eval_expression;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::{Call, Expr, Expression},
|
||||
ast::{Call, Expr},
|
||||
did_you_mean,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, Span, Spanned,
|
||||
@ -113,7 +114,6 @@ pub fn create_external_command(
|
||||
trim_end_newline: bool,
|
||||
) -> Result<ExternalCommand, ShellError> {
|
||||
let name: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
let args: Vec<Value> = call.rest(engine_state, stack, 1)?;
|
||||
|
||||
// Translate environment variables from Values to Strings
|
||||
let env_vars_str = env_to_strings(engine_state, stack)?;
|
||||
@ -132,11 +132,24 @@ pub fn create_external_command(
|
||||
}
|
||||
|
||||
let mut spanned_args = vec![];
|
||||
let args_expr: Vec<Expression> = call.positional_iter().skip(1).cloned().collect();
|
||||
let mut arg_keep_raw = vec![];
|
||||
for (one_arg, one_arg_expr) in args.into_iter().zip(args_expr) {
|
||||
match one_arg {
|
||||
for (arg, spread) in call.rest_iter(1) {
|
||||
// TODO: Disallow automatic spreading entirely later. This match block will
|
||||
// have to be refactored, and lists will have to be disallowed in the parser too
|
||||
match eval_expression(engine_state, stack, arg)? {
|
||||
Value::List { vals, .. } => {
|
||||
if !spread {
|
||||
nu_protocol::report_error_new(
|
||||
engine_state,
|
||||
&ShellError::GenericError {
|
||||
error: "Automatically spreading lists is deprecated".into(),
|
||||
msg: "Spreading lists automatically when calling external commands is deprecated and will be removed in 0.91.".into(),
|
||||
span: Some(arg.span),
|
||||
help: Some("Use the spread operator (put a '...' before the argument)".into()),
|
||||
inner: vec![],
|
||||
},
|
||||
);
|
||||
}
|
||||
// turn all the strings in the array into params.
|
||||
// Example: one_arg may be something like ["ls" "-a"]
|
||||
// convert it to "ls" "-a"
|
||||
@ -147,15 +160,20 @@ pub fn create_external_command(
|
||||
}
|
||||
}
|
||||
val => {
|
||||
spanned_args.push(value_as_spanned(val)?);
|
||||
match one_arg_expr.expr {
|
||||
// refer to `parse_dollar_expr` function
|
||||
// the expression type of $variable_name, $"($variable_name)"
|
||||
// will be Expr::StringInterpolation, Expr::FullCellPath
|
||||
Expr::StringInterpolation(_) | Expr::FullCellPath(_) => arg_keep_raw.push(true),
|
||||
_ => arg_keep_raw.push(false),
|
||||
if spread {
|
||||
return Err(ShellError::CannotSpreadAsList { span: arg.span });
|
||||
} else {
|
||||
spanned_args.push(value_as_spanned(val)?);
|
||||
match arg.expr {
|
||||
// refer to `parse_dollar_expr` function
|
||||
// the expression type of $variable_name, $"($variable_name)"
|
||||
// will be Expr::StringInterpolation, Expr::FullCellPath
|
||||
Expr::StringInterpolation(_) | Expr::FullCellPath(_) => {
|
||||
arg_keep_raw.push(true)
|
||||
}
|
||||
_ => arg_keep_raw.push(false),
|
||||
}
|
||||
}
|
||||
{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use nu_protocol::{
|
||||
};
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
use sysinfo::{
|
||||
ComponentExt, CpuExt, CpuRefreshKind, DiskExt, NetworkExt, System, SystemExt, UserExt,
|
||||
Components, CpuRefreshKind, Disks, Networks, System, Users, MINIMUM_CPU_UPDATE_INTERVAL,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -106,14 +106,12 @@ pub fn trim_cstyle_null(s: String) -> String {
|
||||
}
|
||||
|
||||
pub fn disks(span: Span) -> Value {
|
||||
let mut sys = System::new();
|
||||
sys.refresh_disks();
|
||||
sys.refresh_disks_list();
|
||||
let disks = Disks::new_with_refreshed_list();
|
||||
|
||||
let mut output = vec![];
|
||||
for disk in sys.disks() {
|
||||
for disk in disks.list() {
|
||||
let device = trim_cstyle_null(disk.name().to_string_lossy().to_string());
|
||||
let typ = trim_cstyle_null(String::from_utf8_lossy(disk.file_system()).to_string());
|
||||
let typ = trim_cstyle_null(disk.file_system().to_string_lossy().to_string());
|
||||
|
||||
let record = record! {
|
||||
"device" => Value::string(device, span),
|
||||
@ -131,12 +129,10 @@ pub fn disks(span: Span) -> Value {
|
||||
}
|
||||
|
||||
pub fn net(span: Span) -> Value {
|
||||
let mut sys = System::new();
|
||||
sys.refresh_networks();
|
||||
sys.refresh_networks_list();
|
||||
let networks = Networks::new_with_refreshed_list();
|
||||
|
||||
let mut output = vec![];
|
||||
for (iface, data) in sys.networks() {
|
||||
for (iface, data) in networks.list() {
|
||||
let record = record! {
|
||||
"name" => Value::string(trim_cstyle_null(iface.to_string()), span),
|
||||
"sent" => Value::filesize(data.total_transmitted() as i64, span),
|
||||
@ -154,7 +150,7 @@ pub fn cpu(span: Span) -> Value {
|
||||
// We must refresh the CPU twice a while apart to get valid usage data.
|
||||
// In theory we could just sleep MINIMUM_CPU_UPDATE_INTERVAL, but I've noticed that
|
||||
// that gives poor results (error of ~5%). Decided to wait 2x that long, somewhat arbitrarily
|
||||
std::thread::sleep(System::MINIMUM_CPU_UPDATE_INTERVAL * 2);
|
||||
std::thread::sleep(MINIMUM_CPU_UPDATE_INTERVAL * 2);
|
||||
sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage());
|
||||
|
||||
let mut output = vec![];
|
||||
@ -163,7 +159,7 @@ pub fn cpu(span: Span) -> Value {
|
||||
// Round to 1DP (chosen somewhat arbitrarily) so people aren't misled by high-precision floats.
|
||||
let rounded_usage = (cpu.cpu_usage() * 10.0).round() / 10.0;
|
||||
|
||||
let load_avg = sys.load_average();
|
||||
let load_avg = System::load_average();
|
||||
let load_avg = trim_cstyle_null(format!(
|
||||
"{:.2}, {:.2}, {:.2}",
|
||||
load_avg.one, load_avg.five, load_avg.fifteen
|
||||
@ -211,42 +207,39 @@ pub fn mem(span: Span) -> Value {
|
||||
}
|
||||
|
||||
pub fn host(span: Span) -> Value {
|
||||
let mut sys = System::new();
|
||||
sys.refresh_users_list();
|
||||
|
||||
let mut record = Record::new();
|
||||
|
||||
if let Some(name) = sys.name() {
|
||||
if let Some(name) = System::name() {
|
||||
record.push("name", Value::string(trim_cstyle_null(name), span));
|
||||
}
|
||||
if let Some(version) = sys.os_version() {
|
||||
if let Some(version) = System::os_version() {
|
||||
record.push("os_version", Value::string(trim_cstyle_null(version), span));
|
||||
}
|
||||
|
||||
if let Some(long_version) = sys.long_os_version() {
|
||||
if let Some(long_version) = System::long_os_version() {
|
||||
record.push(
|
||||
"long_os_version",
|
||||
Value::string(trim_cstyle_null(long_version), span),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(version) = sys.kernel_version() {
|
||||
if let Some(version) = System::kernel_version() {
|
||||
record.push(
|
||||
"kernel_version",
|
||||
Value::string(trim_cstyle_null(version), span),
|
||||
);
|
||||
}
|
||||
if let Some(hostname) = sys.host_name() {
|
||||
if let Some(hostname) = System::host_name() {
|
||||
record.push("hostname", Value::string(trim_cstyle_null(hostname), span));
|
||||
}
|
||||
|
||||
record.push(
|
||||
"uptime",
|
||||
Value::duration(1000000000 * sys.uptime() as i64, span),
|
||||
Value::duration(1000000000 * System::uptime() as i64, span),
|
||||
);
|
||||
|
||||
// Creates a new SystemTime from the specified number of whole seconds
|
||||
let d = UNIX_EPOCH + Duration::from_secs(sys.boot_time());
|
||||
let d = UNIX_EPOCH + Duration::from_secs(System::boot_time());
|
||||
// Create DateTime from SystemTime
|
||||
let datetime = DateTime::<Local>::from(d);
|
||||
// Convert to local time and then rfc3339
|
||||
@ -254,11 +247,16 @@ pub fn host(span: Span) -> Value {
|
||||
|
||||
record.push("boot_time", Value::string(timestamp_str, span));
|
||||
|
||||
let mut users = vec![];
|
||||
for user in sys.users() {
|
||||
let users = Users::new_with_refreshed_list();
|
||||
|
||||
let mut users_list = vec![];
|
||||
for user in users.list() {
|
||||
let mut groups = vec![];
|
||||
for group in user.groups() {
|
||||
groups.push(Value::string(trim_cstyle_null(group.to_string()), span));
|
||||
groups.push(Value::string(
|
||||
trim_cstyle_null(group.name().to_string()),
|
||||
span,
|
||||
));
|
||||
}
|
||||
|
||||
let record = record! {
|
||||
@ -266,24 +264,22 @@ pub fn host(span: Span) -> Value {
|
||||
"groups" => Value::list(groups, span),
|
||||
};
|
||||
|
||||
users.push(Value::record(record, span));
|
||||
users_list.push(Value::record(record, span));
|
||||
}
|
||||
|
||||
if !users.is_empty() {
|
||||
record.push("sessions", Value::list(users, span));
|
||||
record.push("sessions", Value::list(users_list, span));
|
||||
}
|
||||
|
||||
Value::record(record, span)
|
||||
}
|
||||
|
||||
pub fn temp(span: Span) -> Value {
|
||||
let mut sys = System::new();
|
||||
sys.refresh_components();
|
||||
sys.refresh_components_list();
|
||||
let components = Components::new_with_refreshed_list();
|
||||
|
||||
let mut output = vec![];
|
||||
|
||||
for component in sys.components() {
|
||||
for component in components.list() {
|
||||
let mut record = record! {
|
||||
"unit" => Value::string(component.label(), span),
|
||||
"temp" => Value::float(component.temperature() as f64, span),
|
||||
|
@ -1,12 +1,24 @@
|
||||
use nu_test_support::nu;
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let actual = nu!(r#"
|
||||
(^echo a | complete) == {stdout: "a\n", exit_code: 0}
|
||||
fn basic_stdout() {
|
||||
let without_complete = nu!(r#"
|
||||
nu --testbin cococo test
|
||||
"#);
|
||||
let with_complete = nu!(r#"
|
||||
(nu --testbin cococo test | complete).stdout
|
||||
"#);
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
assert_eq!(with_complete.out, without_complete.out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_exit_code() {
|
||||
let with_complete = nu!(r#"
|
||||
(nu --testbin cococo test | complete).exit_code
|
||||
"#);
|
||||
|
||||
assert_eq!(with_complete.out, "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -197,7 +197,7 @@ fn def_wrapped_with_block() {
|
||||
#[test]
|
||||
fn def_wrapped_from_module() {
|
||||
let actual = nu!(r#"module spam {
|
||||
export def --wrapped my-echo [...rest] { ^echo $rest }
|
||||
export def --wrapped my-echo [...rest] { nu --testbin cococo ...$rest }
|
||||
}
|
||||
|
||||
use spam
|
||||
|
@ -49,7 +49,7 @@ fn exec_misc_values() {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
nu -c 'let x = "abc"; exec nu --testbin cococo $x [ a b c ]'
|
||||
nu -c 'let x = "abc"; exec nu --testbin cococo $x ...[ a b c ]'
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -38,3 +38,68 @@ fn http_delete_failed_due_to_server_error() {
|
||||
|
||||
assert!(actual.err.contains("Bad request (400)"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_delete_follows_redirect() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server.mock("GET", "/bar").with_body("bar").create();
|
||||
let _mock = server
|
||||
.mock("DELETE", "/foo")
|
||||
.with_status(301)
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!("http delete {url}/foo", url = server.url()).as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_delete_redirect_mode_manual() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("DELETE", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http delete --redirect-mode manual {url}/foo",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_delete_redirect_mode_error() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("DELETE", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http delete --redirect-mode error {url}/foo",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||
assert!(&actual.err.contains(
|
||||
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
|
||||
));
|
||||
}
|
||||
|
@ -176,6 +176,71 @@ fn http_get_full_response() {
|
||||
assert_eq!(header["value"], "close");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_get_follows_redirect() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server.mock("GET", "/bar").with_body("bar").create();
|
||||
let _mock = server
|
||||
.mock("GET", "/foo")
|
||||
.with_status(301)
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!("http get {url}/foo", url = server.url()).as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_get_redirect_mode_manual() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("GET", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http get --redirect-mode manual {url}/foo",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_get_redirect_mode_error() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("GET", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http get --redirect-mode error {url}/foo",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||
assert!(&actual.err.contains(
|
||||
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
|
||||
));
|
||||
}
|
||||
|
||||
// These tests require network access; they use badssl.com which is a Google-affiliated site for testing various SSL errors.
|
||||
// Revisit this if these tests prove to be flaky or unstable.
|
||||
|
||||
|
@ -39,3 +39,75 @@ fn http_head_failed_due_to_server_error() {
|
||||
|
||||
assert!(actual.err.contains("Bad request (400)"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_head_follows_redirect() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("HEAD", "/bar")
|
||||
.with_header("bar", "bar")
|
||||
.create();
|
||||
let _mock = server
|
||||
.mock("HEAD", "/foo")
|
||||
.with_status(301)
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http head {url}/foo | (where name == bar).0.value",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_head_redirect_mode_manual() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("HEAD", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http head --redirect-mode manual {url}/foo | (where name == location).0.value",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "/bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_head_redirect_mode_error() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("HEAD", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http head --redirect-mode error {url}/foo",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||
assert!(&actual.err.contains(
|
||||
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
|
||||
));
|
||||
}
|
||||
|
@ -76,3 +76,68 @@ fn http_patch_failed_due_to_unexpected_body() {
|
||||
|
||||
assert!(actual.err.contains("Cannot make request"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_patch_follows_redirect() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server.mock("GET", "/bar").with_body("bar").create();
|
||||
let _mock = server
|
||||
.mock("PATCH", "/foo")
|
||||
.with_status(301)
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!("http patch {url}/foo patchbody", url = server.url()).as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_patch_redirect_mode_manual() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("PATCH", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http patch --redirect-mode manual {url}/foo patchbody",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_patch_redirect_mode_error() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("PATCH", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http patch --redirect-mode error {url}/foo patchbody",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||
assert!(&actual.err.contains(
|
||||
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
|
||||
));
|
||||
}
|
||||
|
@ -112,3 +112,68 @@ fn http_post_json_list_is_success() {
|
||||
mock.assert();
|
||||
assert!(actual.out.is_empty())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_post_follows_redirect() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server.mock("GET", "/bar").with_body("bar").create();
|
||||
let _mock = server
|
||||
.mock("POST", "/foo")
|
||||
.with_status(301)
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!("http post {url}/foo postbody", url = server.url()).as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_post_redirect_mode_manual() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("POST", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http post --redirect-mode manual {url}/foo postbody",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_post_redirect_mode_error() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("POST", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http post --redirect-mode error {url}/foo postbody",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||
assert!(&actual.err.contains(
|
||||
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
|
||||
));
|
||||
}
|
||||
|
@ -76,3 +76,68 @@ fn http_put_failed_due_to_unexpected_body() {
|
||||
|
||||
assert!(actual.err.contains("Cannot make request"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_put_follows_redirect() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server.mock("GET", "/bar").with_body("bar").create();
|
||||
let _mock = server
|
||||
.mock("PUT", "/foo")
|
||||
.with_status(301)
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!("http put {url}/foo putbody", url = server.url()).as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_put_redirect_mode_manual() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("PUT", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http put --redirect-mode manual {url}/foo putbody",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_put_redirect_mode_error() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("PUT", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http put --redirect-mode error {url}/foo putbody",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||
assert!(&actual.err.contains(
|
||||
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
|
||||
));
|
||||
}
|
||||
|
@ -375,6 +375,19 @@ fn removes_symlink() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removes_symlink_pointing_to_directory() {
|
||||
Playground::setup("rm_symlink_to_directory", |dirs, sandbox| {
|
||||
sandbox.mkdir("test").symlink("test", "test_link");
|
||||
|
||||
nu!(cwd: sandbox.cwd(), "rm test_link");
|
||||
|
||||
assert!(!dirs.test().join("test_link").exists());
|
||||
// The pointed directory should not be deleted.
|
||||
assert!(dirs.test().join("test").exists());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removes_file_after_cd() {
|
||||
Playground::setup("rm_after_cd", |dirs, sandbox| {
|
||||
|
@ -139,14 +139,13 @@ fn failed_command_with_semicolon_will_not_execute_following_cmds() {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn external_args_with_quoted() {
|
||||
Playground::setup("external failed command with semicolon", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^echo "foo=bar 'hi'"
|
||||
nu --testbin cococo "foo=bar 'hi'"
|
||||
"#
|
||||
));
|
||||
|
||||
@ -187,14 +186,13 @@ fn external_arg_with_variable_name() {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn external_command_escape_args() {
|
||||
Playground::setup("external failed command with semicolon", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^echo "\"abcd"
|
||||
nu --testbin cococo "\"abcd"
|
||||
"#
|
||||
));
|
||||
|
||||
@ -308,13 +306,12 @@ fn can_run_batch_files_without_bat_extension() {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn quotes_trimmed_when_shelling_out() {
|
||||
// regression test for a bug where we weren't trimming quotes around string args before shelling out to cmd.exe
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
^echo "foo"
|
||||
nu --testbin cococo "foo"
|
||||
"#
|
||||
));
|
||||
|
||||
@ -328,7 +325,7 @@ fn redirect_combine() {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
run-external --redirect-combine sh [-c 'echo Foo; echo >&2 Bar']
|
||||
run-external --redirect-combine sh ...[-c 'echo Foo; echo >&2 Bar']
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -22,3 +22,39 @@ fn table_to_xml_text_and_from_xml_text_back_into_table() {
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_xml_error_unknown_column() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
{tag: a bad_column: b} | to xml
|
||||
"#
|
||||
));
|
||||
|
||||
assert!(actual.err.contains("Invalid column \"bad_column\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_xml_error_no_tag() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
{attributes: {a: b c: d}} | to xml
|
||||
"#
|
||||
));
|
||||
|
||||
assert!(actual.err.contains("Tag missing"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_xml_error_tag_not_string() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
{tag: 1 attributes: {a: b c: d}} | to xml
|
||||
"#
|
||||
));
|
||||
|
||||
assert!(actual.err.contains("not a string"));
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
ast::{Call, Expression},
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
eval_const::eval_constant,
|
||||
FromValue, ShellError,
|
||||
FromValue, ShellError, Value,
|
||||
};
|
||||
|
||||
use crate::eval_expression;
|
||||
@ -34,6 +34,10 @@ pub trait CallExt {
|
||||
starting_pos: usize,
|
||||
) -> Result<Vec<T>, ShellError>;
|
||||
|
||||
fn rest_iter_flattened<F>(&self, start: usize, eval: F) -> Result<Vec<Value>, ShellError>
|
||||
where
|
||||
F: FnMut(&Expression) -> Result<Value, ShellError>;
|
||||
|
||||
fn opt<T: FromValue>(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
@ -98,8 +102,9 @@ impl CallExt for Call {
|
||||
) -> Result<Vec<T>, ShellError> {
|
||||
let mut output = vec![];
|
||||
|
||||
for expr in self.positional_iter().skip(starting_pos) {
|
||||
let result = eval_expression(engine_state, stack, expr)?;
|
||||
for result in self.rest_iter_flattened(starting_pos, |expr| {
|
||||
eval_expression(engine_state, stack, expr)
|
||||
})? {
|
||||
output.push(FromValue::from_value(result)?);
|
||||
}
|
||||
|
||||
@ -113,14 +118,36 @@ impl CallExt for Call {
|
||||
) -> Result<Vec<T>, ShellError> {
|
||||
let mut output = vec![];
|
||||
|
||||
for expr in self.positional_iter().skip(starting_pos) {
|
||||
let result = eval_constant(working_set, expr)?;
|
||||
for result in
|
||||
self.rest_iter_flattened(starting_pos, |expr| eval_constant(working_set, expr))?
|
||||
{
|
||||
output.push(FromValue::from_value(result)?);
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn rest_iter_flattened<F>(&self, start: usize, mut eval: F) -> Result<Vec<Value>, ShellError>
|
||||
where
|
||||
F: FnMut(&Expression) -> Result<Value, ShellError>,
|
||||
{
|
||||
let mut output = Vec::new();
|
||||
|
||||
for (expr, spread) in self.rest_iter(start) {
|
||||
let result = eval(expr)?;
|
||||
if spread {
|
||||
match result {
|
||||
Value::List { mut vals, .. } => output.append(&mut vals),
|
||||
_ => return Err(ShellError::CannotSpreadAsList { span: expr.span }),
|
||||
}
|
||||
} else {
|
||||
output.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn opt<T: FromValue>(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::{current_dir_str, get_full_help};
|
||||
use crate::{call_ext::CallExt, current_dir_str, get_full_help};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::{
|
||||
ast::{
|
||||
Argument, Assignment, Block, Call, Expr, Expression, PathMember, PipelineElement,
|
||||
Redirection,
|
||||
Argument, Assignment, Block, Call, Expr, Expression, ExternalArgument, PathMember,
|
||||
PipelineElement, Redirection,
|
||||
},
|
||||
engine::{Closure, EngineState, Stack},
|
||||
eval_base::Eval,
|
||||
@ -66,11 +66,11 @@ pub fn eval_call(
|
||||
if let Some(rest_positional) = decl.signature().rest_positional {
|
||||
let mut rest_items = vec![];
|
||||
|
||||
for arg in call.positional_iter().skip(
|
||||
for result in call.rest_iter_flattened(
|
||||
decl.signature().required_positional.len()
|
||||
+ decl.signature().optional_positional.len(),
|
||||
) {
|
||||
let result = eval_expression(engine_state, caller_stack, arg)?;
|
||||
|expr| eval_expression(engine_state, caller_stack, expr),
|
||||
)? {
|
||||
rest_items.push(result);
|
||||
}
|
||||
|
||||
@ -182,7 +182,7 @@ fn eval_external(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
head: &Expression,
|
||||
args: &[Expression],
|
||||
args: &[ExternalArgument],
|
||||
input: PipelineData,
|
||||
redirect_target: RedirectTarget,
|
||||
is_subexpression: bool,
|
||||
@ -198,7 +198,10 @@ fn eval_external(
|
||||
call.add_positional(head.clone());
|
||||
|
||||
for arg in args {
|
||||
call.add_positional(arg.clone())
|
||||
match arg {
|
||||
ExternalArgument::Regular(expr) => call.add_positional(expr.clone()),
|
||||
ExternalArgument::Spread(expr) => call.add_spread(expr.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
match redirect_target {
|
||||
@ -947,7 +950,7 @@ impl Eval for EvalRuntime {
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
head: &Expression,
|
||||
args: &[Expression],
|
||||
args: &[ExternalArgument],
|
||||
is_subexpression: bool,
|
||||
_: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
@ -1099,7 +1102,18 @@ impl Eval for EvalRuntime {
|
||||
.get_block(block_id)
|
||||
.captures
|
||||
.iter()
|
||||
.map(|&id| stack.get_var(id, span).map(|var| (id, var)))
|
||||
.map(|&id| {
|
||||
stack
|
||||
.get_var(id, span)
|
||||
.or_else(|_| {
|
||||
engine_state
|
||||
.get_var(id)
|
||||
.const_val
|
||||
.clone()
|
||||
.ok_or(ShellError::VariableNotFoundAtRuntime { span })
|
||||
})
|
||||
.map(|var| (id, var))
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(Value::closure(Closure { block_id, captures }, span))
|
||||
|
@ -20,7 +20,7 @@ nu-table = { path = "../nu-table", version = "0.88.2" }
|
||||
nu-json = { path = "../nu-json", version = "0.88.2" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.88.2" }
|
||||
|
||||
terminal_size = "0.2"
|
||||
terminal_size = "0.3"
|
||||
strip-ansi-escapes = "0.2.0"
|
||||
crossterm = "0.27"
|
||||
ratatui = "0.23"
|
||||
|
@ -15,7 +15,7 @@ nu-protocol = { path = "../nu-protocol", version = "0.88.2" }
|
||||
reedline = { version = "0.27" }
|
||||
|
||||
crossbeam-channel = "0.5.8"
|
||||
lsp-types = "0.94.1"
|
||||
lsp-types = "0.95.0"
|
||||
lsp-server = "0.7.5"
|
||||
miette = "5.10"
|
||||
ropey = "1.6.1"
|
||||
|
@ -1,6 +1,6 @@
|
||||
use nu_protocol::ast::{
|
||||
Block, Expr, Expression, ImportPatternMember, MatchPattern, PathMember, Pattern, Pipeline,
|
||||
PipelineElement, RecordItem,
|
||||
Argument, Block, Expr, Expression, ExternalArgument, ImportPatternMember, MatchPattern,
|
||||
PathMember, Pattern, Pipeline, PipelineElement, RecordItem,
|
||||
};
|
||||
use nu_protocol::{engine::StateWorkingSet, Span};
|
||||
use nu_protocol::{DeclId, VarId};
|
||||
@ -193,17 +193,28 @@ pub fn flatten_expression(
|
||||
}
|
||||
|
||||
let mut args = vec![];
|
||||
for positional in call.positional_iter() {
|
||||
let flattened = flatten_expression(working_set, positional);
|
||||
args.extend(flattened);
|
||||
}
|
||||
for named in call.named_iter() {
|
||||
if named.0.span.end != 0 {
|
||||
// Ignore synthetic flags
|
||||
args.push((named.0.span, FlatShape::Flag));
|
||||
}
|
||||
if let Some(expr) = &named.2 {
|
||||
args.extend(flatten_expression(working_set, expr));
|
||||
for arg in &call.arguments {
|
||||
match arg {
|
||||
Argument::Positional(positional) | Argument::Unknown(positional) => {
|
||||
let flattened = flatten_expression(working_set, positional);
|
||||
args.extend(flattened);
|
||||
}
|
||||
Argument::Named(named) => {
|
||||
if named.0.span.end != 0 {
|
||||
// Ignore synthetic flags
|
||||
args.push((named.0.span, FlatShape::Flag));
|
||||
}
|
||||
if let Some(expr) = &named.2 {
|
||||
args.extend(flatten_expression(working_set, expr));
|
||||
}
|
||||
}
|
||||
Argument::Spread(expr) => {
|
||||
args.push((
|
||||
Span::new(expr.span.start - 3, expr.span.start),
|
||||
FlatShape::Operator,
|
||||
));
|
||||
args.extend(flatten_expression(working_set, expr));
|
||||
}
|
||||
}
|
||||
}
|
||||
// sort these since flags and positional args can be intermixed
|
||||
@ -231,15 +242,24 @@ pub fn flatten_expression(
|
||||
for arg in args {
|
||||
//output.push((*arg, FlatShape::ExternalArg));
|
||||
match arg {
|
||||
Expression {
|
||||
expr: Expr::String(..),
|
||||
span,
|
||||
..
|
||||
} => {
|
||||
output.push((*span, FlatShape::ExternalArg));
|
||||
}
|
||||
_ => {
|
||||
output.extend(flatten_expression(working_set, arg));
|
||||
ExternalArgument::Regular(expr) => match expr {
|
||||
Expression {
|
||||
expr: Expr::String(..),
|
||||
span,
|
||||
..
|
||||
} => {
|
||||
output.push((*span, FlatShape::ExternalArg));
|
||||
}
|
||||
_ => {
|
||||
output.extend(flatten_expression(working_set, expr));
|
||||
}
|
||||
},
|
||||
ExternalArgument::Spread(expr) => {
|
||||
output.push((
|
||||
Span::new(expr.span.start - 3, expr.span.start),
|
||||
FlatShape::Operator,
|
||||
));
|
||||
output.extend(flatten_expression(working_set, expr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -106,6 +106,7 @@ impl Command for KnownExternal {
|
||||
}
|
||||
}
|
||||
Argument::Unknown(unknown) => extern_call.add_unknown(unknown.clone()),
|
||||
Argument::Spread(args) => extern_call.add_spread(args.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,9 +12,9 @@ use nu_engine::DIR_VAR_PARSER_INFO;
|
||||
use nu_protocol::{
|
||||
ast::{
|
||||
Argument, Assignment, Bits, Block, Boolean, Call, CellPath, Comparison, Expr, Expression,
|
||||
FullCellPath, ImportPattern, ImportPatternHead, ImportPatternMember, MatchPattern, Math,
|
||||
Operator, PathMember, Pattern, Pipeline, PipelineElement, RangeInclusion, RangeOperator,
|
||||
RecordItem,
|
||||
ExternalArgument, FullCellPath, ImportPattern, ImportPatternHead, ImportPatternMember,
|
||||
MatchPattern, Math, Operator, PathMember, Pattern, Pipeline, PipelineElement,
|
||||
RangeInclusion, RangeOperator, RecordItem,
|
||||
},
|
||||
engine::StateWorkingSet,
|
||||
eval_const::eval_constant,
|
||||
@ -266,13 +266,22 @@ pub fn check_name<'a>(working_set: &mut StateWorkingSet, spans: &'a [Span]) -> O
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> Expression {
|
||||
fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> ExternalArgument {
|
||||
let contents = working_set.get_span_contents(span);
|
||||
|
||||
if contents.starts_with(b"$") || contents.starts_with(b"(") {
|
||||
parse_dollar_expr(working_set, span)
|
||||
ExternalArgument::Regular(parse_dollar_expr(working_set, span))
|
||||
} else if contents.starts_with(b"[") {
|
||||
parse_list_expression(working_set, span, &SyntaxShape::Any)
|
||||
ExternalArgument::Regular(parse_list_expression(working_set, span, &SyntaxShape::Any))
|
||||
} else if contents.len() > 3
|
||||
&& contents.starts_with(b"...")
|
||||
&& (contents[3] == b'$' || contents[3] == b'[' || contents[3] == b'(')
|
||||
{
|
||||
ExternalArgument::Spread(parse_value(
|
||||
working_set,
|
||||
Span::new(span.start + 3, span.end),
|
||||
&SyntaxShape::List(Box::new(SyntaxShape::Any)),
|
||||
))
|
||||
} else {
|
||||
// Eval stage trims the quotes, so we don't have to do the same thing when parsing.
|
||||
let contents = if contents.starts_with(b"\"") {
|
||||
@ -285,12 +294,12 @@ fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> Expressi
|
||||
String::from_utf8_lossy(contents).to_string()
|
||||
};
|
||||
|
||||
Expression {
|
||||
ExternalArgument::Regular(Expression {
|
||||
expr: Expr::String(contents),
|
||||
span,
|
||||
ty: Type::String,
|
||||
custom_completion: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -978,6 +987,49 @@ pub fn parse_internal_call(
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
let contents = working_set.get_span_contents(spans[spans_idx]);
|
||||
|
||||
if contents.len() > 3
|
||||
&& contents.starts_with(b"...")
|
||||
&& (contents[3] == b'$' || contents[3] == b'[' || contents[3] == b'(')
|
||||
{
|
||||
if signature.rest_positional.is_none() && !signature.allows_unknown_args {
|
||||
working_set.error(ParseError::UnexpectedSpreadArg(
|
||||
signature.call_signature(),
|
||||
arg_span,
|
||||
));
|
||||
call.add_positional(Expression::garbage(arg_span));
|
||||
} else if positional_idx < signature.required_positional.len() {
|
||||
working_set.error(ParseError::MissingPositional(
|
||||
signature.required_positional[positional_idx].name.clone(),
|
||||
Span::new(spans[spans_idx].start, spans[spans_idx].start),
|
||||
signature.call_signature(),
|
||||
));
|
||||
call.add_positional(Expression::garbage(arg_span));
|
||||
} else {
|
||||
let rest_shape = match &signature.rest_positional {
|
||||
Some(arg) => arg.shape.clone(),
|
||||
None => SyntaxShape::Any,
|
||||
};
|
||||
// Parse list of arguments to be spread
|
||||
let args = parse_value(
|
||||
working_set,
|
||||
Span::new(arg_span.start + 3, arg_span.end),
|
||||
&SyntaxShape::List(Box::new(rest_shape)),
|
||||
);
|
||||
|
||||
call.add_spread(args);
|
||||
// Let the parser know that it's parsing rest arguments now
|
||||
positional_idx =
|
||||
signature.required_positional.len() + signature.optional_positional.len();
|
||||
}
|
||||
|
||||
spans_idx += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse a positional arg if there is one
|
||||
if let Some(positional) = signature.get_positional(positional_idx) {
|
||||
let end = calculate_end_span(working_set, &signature, spans, spans_idx, positional_idx);
|
||||
@ -5272,15 +5324,43 @@ pub fn parse_record(working_set: &mut StateWorkingSet, span: Span) -> Expression
|
||||
|
||||
idx += 1;
|
||||
if idx == tokens.len() {
|
||||
working_set.error(ParseError::Expected("record", span));
|
||||
return garbage(span);
|
||||
working_set.error(ParseError::Expected(
|
||||
"':'",
|
||||
Span::new(curr_span.end, curr_span.end),
|
||||
));
|
||||
output.push(RecordItem::Pair(
|
||||
garbage(curr_span),
|
||||
garbage(Span::new(curr_span.end, curr_span.end)),
|
||||
));
|
||||
break;
|
||||
}
|
||||
let colon = working_set.get_span_contents(tokens[idx].span);
|
||||
let colon_span = tokens[idx].span;
|
||||
let colon = working_set.get_span_contents(colon_span);
|
||||
idx += 1;
|
||||
if idx == tokens.len() || colon != b":" {
|
||||
//FIXME: need better error
|
||||
working_set.error(ParseError::Expected("record", span));
|
||||
return garbage(span);
|
||||
if colon != b":" {
|
||||
working_set.error(ParseError::Expected(
|
||||
"':'",
|
||||
Span::new(colon_span.start, colon_span.start),
|
||||
));
|
||||
output.push(RecordItem::Pair(
|
||||
field,
|
||||
garbage(Span::new(
|
||||
colon_span.start,
|
||||
tokens[tokens.len() - 1].span.end,
|
||||
)),
|
||||
));
|
||||
break;
|
||||
}
|
||||
if idx == tokens.len() {
|
||||
working_set.error(ParseError::Expected(
|
||||
"value for record field",
|
||||
Span::new(colon_span.end, colon_span.end),
|
||||
));
|
||||
output.push(RecordItem::Pair(
|
||||
garbage(Span::new(curr_span.start, colon_span.end)),
|
||||
garbage(Span::new(colon_span.end, tokens[tokens.len() - 1].span.end)),
|
||||
));
|
||||
break;
|
||||
}
|
||||
let value = parse_value(working_set, tokens[idx].span, &SyntaxShape::Any);
|
||||
idx += 1;
|
||||
@ -5857,22 +5937,27 @@ pub fn discover_captures_in_expr(
|
||||
}
|
||||
}
|
||||
|
||||
for named in call.named_iter() {
|
||||
if let Some(arg) = &named.2 {
|
||||
discover_captures_in_expr(working_set, arg, seen, seen_blocks, output)?;
|
||||
for arg in &call.arguments {
|
||||
match arg {
|
||||
Argument::Named(named) => {
|
||||
if let Some(arg) = &named.2 {
|
||||
discover_captures_in_expr(working_set, arg, seen, seen_blocks, output)?;
|
||||
}
|
||||
}
|
||||
Argument::Positional(expr)
|
||||
| Argument::Unknown(expr)
|
||||
| Argument::Spread(expr) => {
|
||||
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for positional in call.positional_iter() {
|
||||
discover_captures_in_expr(working_set, positional, seen, seen_blocks, output)?;
|
||||
}
|
||||
}
|
||||
Expr::CellPath(_) => {}
|
||||
Expr::DateTime(_) => {}
|
||||
Expr::ExternalCall(head, exprs, _) => {
|
||||
Expr::ExternalCall(head, args, _) => {
|
||||
discover_captures_in_expr(working_set, head, seen, seen_blocks, output)?;
|
||||
|
||||
for expr in exprs {
|
||||
for ExternalArgument::Regular(expr) | ExternalArgument::Spread(expr) in args {
|
||||
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
|
||||
}
|
||||
}
|
||||
@ -6086,11 +6171,8 @@ fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression)
|
||||
default_value: None,
|
||||
});
|
||||
|
||||
let mut expr = expr.clone();
|
||||
expr.replace_in_variable(working_set, var_id);
|
||||
|
||||
let block = Block {
|
||||
pipelines: vec![Pipeline::from_vec(vec![expr])],
|
||||
pipelines: vec![Pipeline::from_vec(vec![expr.clone()])],
|
||||
signature: Box::new(signature),
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
use nu_engine::eval_expression;
|
||||
use nu_engine::{eval_expression, CallExt};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{EngineState, Stack},
|
||||
@ -33,10 +33,8 @@ impl EvaluatedCall {
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
) -> Result<Self, ShellError> {
|
||||
let positional = call
|
||||
.positional_iter()
|
||||
.map(|expr| eval_expression(engine_state, stack, expr))
|
||||
.collect::<Result<Vec<Value>, ShellError>>()?;
|
||||
let positional =
|
||||
call.rest_iter_flattened(0, |expr| eval_expression(engine_state, stack, expr))?;
|
||||
|
||||
let mut named = Vec::with_capacity(call.named_len());
|
||||
for (string, _, expr) in call.named_iter() {
|
||||
|
@ -10,6 +10,7 @@ pub enum Argument {
|
||||
Positional(Expression),
|
||||
Named((Spanned<String>, Option<Spanned<String>>, Option<Expression>)),
|
||||
Unknown(Expression), // unknown argument used in "fall-through" signatures
|
||||
Spread(Expression), // a list spread to fill in rest arguments
|
||||
}
|
||||
|
||||
impl Argument {
|
||||
@ -30,10 +31,17 @@ impl Argument {
|
||||
Span::new(start, end)
|
||||
}
|
||||
Argument::Unknown(e) => e.span,
|
||||
Argument::Spread(e) => e.span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ExternalArgument {
|
||||
Regular(Expression),
|
||||
Spread(Expression),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Call {
|
||||
/// identifier of the declaration to call
|
||||
@ -85,6 +93,7 @@ impl Call {
|
||||
Argument::Named(named) => Some(named),
|
||||
Argument::Positional(_) => None,
|
||||
Argument::Unknown(_) => None,
|
||||
Argument::Spread(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
@ -96,6 +105,7 @@ impl Call {
|
||||
Argument::Named(named) => Some(named),
|
||||
Argument::Positional(_) => None,
|
||||
Argument::Unknown(_) => None,
|
||||
Argument::Spread(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
@ -118,26 +128,45 @@ impl Call {
|
||||
self.arguments.push(Argument::Unknown(unknown));
|
||||
}
|
||||
|
||||
pub fn add_spread(&mut self, args: Expression) {
|
||||
self.arguments.push(Argument::Spread(args));
|
||||
}
|
||||
|
||||
pub fn positional_iter(&self) -> impl Iterator<Item = &Expression> {
|
||||
self.arguments.iter().filter_map(|arg| match arg {
|
||||
Argument::Named(_) => None,
|
||||
Argument::Positional(positional) => Some(positional),
|
||||
Argument::Unknown(unknown) => Some(unknown),
|
||||
})
|
||||
self.arguments
|
||||
.iter()
|
||||
.take_while(|arg| match arg {
|
||||
Argument::Spread(_) => false, // Don't include positional arguments given to rest parameter
|
||||
_ => true,
|
||||
})
|
||||
.filter_map(|arg| match arg {
|
||||
Argument::Named(_) => None,
|
||||
Argument::Positional(positional) => Some(positional),
|
||||
Argument::Unknown(unknown) => Some(unknown),
|
||||
Argument::Spread(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn positional_iter_mut(&mut self) -> impl Iterator<Item = &mut Expression> {
|
||||
self.arguments.iter_mut().filter_map(|arg| match arg {
|
||||
Argument::Named(_) => None,
|
||||
Argument::Positional(positional) => Some(positional),
|
||||
Argument::Unknown(unknown) => Some(unknown),
|
||||
})
|
||||
self.arguments
|
||||
.iter_mut()
|
||||
.take_while(|arg| match arg {
|
||||
Argument::Spread(_) => false, // Don't include positional arguments given to rest parameter
|
||||
_ => true,
|
||||
})
|
||||
.filter_map(|arg| match arg {
|
||||
Argument::Named(_) => None,
|
||||
Argument::Positional(positional) => Some(positional),
|
||||
Argument::Unknown(unknown) => Some(unknown),
|
||||
Argument::Spread(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn positional_nth(&self, i: usize) -> Option<&Expression> {
|
||||
self.positional_iter().nth(i)
|
||||
}
|
||||
|
||||
// TODO this method is never used. Delete?
|
||||
pub fn positional_nth_mut(&mut self, i: usize) -> Option<&mut Expression> {
|
||||
self.positional_iter_mut().nth(i)
|
||||
}
|
||||
@ -146,6 +175,24 @@ impl Call {
|
||||
self.positional_iter().count()
|
||||
}
|
||||
|
||||
/// Returns every argument to the rest parameter, as well as whether each argument
|
||||
/// is spread or a normal positional argument (true for spread, false for normal)
|
||||
pub fn rest_iter(&self, start: usize) -> impl Iterator<Item = (&Expression, bool)> {
|
||||
// todo maybe rewrite to be more elegant or something
|
||||
let args = self
|
||||
.arguments
|
||||
.iter()
|
||||
.filter_map(|arg| match arg {
|
||||
Argument::Named(_) => None,
|
||||
Argument::Positional(positional) => Some((positional, false)),
|
||||
Argument::Unknown(unknown) => Some((unknown, false)),
|
||||
Argument::Spread(args) => Some((args, true)),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let spread_start = args.iter().position(|(_, spread)| *spread).unwrap_or(start);
|
||||
args.into_iter().skip(start.min(spread_start))
|
||||
}
|
||||
|
||||
pub fn get_parser_info(&self, name: &str) -> Option<&Expression> {
|
||||
self.parser_info.get(name)
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
use chrono::FixedOffset;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{Call, CellPath, Expression, FullCellPath, MatchPattern, Operator, RangeOperator};
|
||||
use super::{
|
||||
Call, CellPath, Expression, ExternalArgument, FullCellPath, MatchPattern, Operator,
|
||||
RangeOperator,
|
||||
};
|
||||
use crate::{ast::ImportPattern, BlockId, Signature, Span, Spanned, Unit, VarId};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
@ -19,7 +22,7 @@ pub enum Expr {
|
||||
Var(VarId),
|
||||
VarDecl(VarId),
|
||||
Call(Box<Call>),
|
||||
ExternalCall(Box<Expression>, Vec<Expression>, bool), // head, args, is_subexpression
|
||||
ExternalCall(Box<Expression>, Vec<ExternalArgument>, bool), // head, args, is_subexpression
|
||||
Operator(Operator),
|
||||
RowCondition(BlockId),
|
||||
UnaryNot(Box<Expression>),
|
||||
|
@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{Expr, RecordItem};
|
||||
use super::{Argument, Expr, ExternalArgument, RecordItem};
|
||||
use crate::ast::ImportPattern;
|
||||
use crate::DeclId;
|
||||
use crate::{engine::StateWorkingSet, BlockId, Signature, Span, Type, VarId, IN_VARIABLE_ID};
|
||||
@ -162,15 +162,21 @@ impl Expression {
|
||||
Expr::Binary(_) => false,
|
||||
Expr::Bool(_) => false,
|
||||
Expr::Call(call) => {
|
||||
for positional in call.positional_iter() {
|
||||
if positional.has_in_variable(working_set) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for named in call.named_iter() {
|
||||
if let Some(expr) = &named.2 {
|
||||
if expr.has_in_variable(working_set) {
|
||||
return true;
|
||||
for arg in &call.arguments {
|
||||
match arg {
|
||||
Argument::Positional(expr)
|
||||
| Argument::Unknown(expr)
|
||||
| Argument::Spread(expr) => {
|
||||
if expr.has_in_variable(working_set) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Argument::Named(named) => {
|
||||
if let Some(expr) = &named.2 {
|
||||
if expr.has_in_variable(working_set) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -182,8 +188,8 @@ impl Expression {
|
||||
if head.has_in_variable(working_set) {
|
||||
return true;
|
||||
}
|
||||
for arg in args {
|
||||
if arg.has_in_variable(working_set) {
|
||||
for ExternalArgument::Regular(expr) | ExternalArgument::Spread(expr) in args {
|
||||
if expr.has_in_variable(working_set) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -301,204 +307,6 @@ impl Expression {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_in_variable(&mut self, working_set: &mut StateWorkingSet, new_var_id: VarId) {
|
||||
match &mut self.expr {
|
||||
Expr::BinaryOp(left, _, right) => {
|
||||
left.replace_in_variable(working_set, new_var_id);
|
||||
right.replace_in_variable(working_set, new_var_id);
|
||||
}
|
||||
Expr::UnaryNot(expr) => {
|
||||
expr.replace_in_variable(working_set, new_var_id);
|
||||
}
|
||||
Expr::Block(block_id) => {
|
||||
let block = working_set.get_block(*block_id);
|
||||
|
||||
let new_expr = if let Some(pipeline) = block.pipelines.first() {
|
||||
if let Some(element) = pipeline.elements.first() {
|
||||
let mut new_element = element.clone();
|
||||
new_element.replace_in_variable(working_set, new_var_id);
|
||||
Some(new_element)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let block = working_set.get_block_mut(*block_id);
|
||||
|
||||
if let Some(new_expr) = new_expr {
|
||||
if let Some(pipeline) = block.pipelines.get_mut(0) {
|
||||
if let Some(expr) = pipeline.elements.get_mut(0) {
|
||||
*expr = new_expr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
block.captures = block
|
||||
.captures
|
||||
.iter()
|
||||
.map(|x| if *x != IN_VARIABLE_ID { *x } else { new_var_id })
|
||||
.collect();
|
||||
}
|
||||
Expr::Closure(block_id) => {
|
||||
let block = working_set.get_block(*block_id);
|
||||
|
||||
let new_element = if let Some(pipeline) = block.pipelines.first() {
|
||||
if let Some(element) = pipeline.elements.first() {
|
||||
let mut new_element = element.clone();
|
||||
new_element.replace_in_variable(working_set, new_var_id);
|
||||
Some(new_element)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let block = working_set.get_block_mut(*block_id);
|
||||
|
||||
if let Some(new_element) = new_element {
|
||||
if let Some(pipeline) = block.pipelines.get_mut(0) {
|
||||
if let Some(element) = pipeline.elements.get_mut(0) {
|
||||
*element = new_element
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
block.captures = block
|
||||
.captures
|
||||
.iter()
|
||||
.map(|x| if *x != IN_VARIABLE_ID { *x } else { new_var_id })
|
||||
.collect();
|
||||
}
|
||||
Expr::Binary(_) => {}
|
||||
Expr::Bool(_) => {}
|
||||
Expr::Call(call) => {
|
||||
for positional in call.positional_iter_mut() {
|
||||
positional.replace_in_variable(working_set, new_var_id);
|
||||
}
|
||||
for named in call.named_iter_mut() {
|
||||
if let Some(expr) = &mut named.2 {
|
||||
expr.replace_in_variable(working_set, new_var_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::CellPath(_) => {}
|
||||
Expr::DateTime(_) => {}
|
||||
Expr::ExternalCall(head, args, _) => {
|
||||
head.replace_in_variable(working_set, new_var_id);
|
||||
for arg in args {
|
||||
arg.replace_in_variable(working_set, new_var_id)
|
||||
}
|
||||
}
|
||||
Expr::Filepath(_) => {}
|
||||
Expr::Directory(_) => {}
|
||||
Expr::Float(_) => {}
|
||||
Expr::FullCellPath(full_cell_path) => {
|
||||
full_cell_path
|
||||
.head
|
||||
.replace_in_variable(working_set, new_var_id);
|
||||
}
|
||||
Expr::ImportPattern(_) => {}
|
||||
Expr::Overlay(_) => {}
|
||||
Expr::Garbage => {}
|
||||
Expr::Nothing => {}
|
||||
Expr::GlobPattern(_) => {}
|
||||
Expr::Int(_) => {}
|
||||
Expr::MatchBlock(_) => {}
|
||||
Expr::Keyword(_, _, expr) => expr.replace_in_variable(working_set, new_var_id),
|
||||
Expr::List(list) => {
|
||||
for l in list {
|
||||
l.replace_in_variable(working_set, new_var_id)
|
||||
}
|
||||
}
|
||||
Expr::Operator(_) => {}
|
||||
Expr::Range(left, middle, right, ..) => {
|
||||
if let Some(left) = left {
|
||||
left.replace_in_variable(working_set, new_var_id)
|
||||
}
|
||||
if let Some(middle) = middle {
|
||||
middle.replace_in_variable(working_set, new_var_id)
|
||||
}
|
||||
if let Some(right) = right {
|
||||
right.replace_in_variable(working_set, new_var_id)
|
||||
}
|
||||
}
|
||||
Expr::Record(items) => {
|
||||
for item in items {
|
||||
match item {
|
||||
RecordItem::Pair(field_name, field_value) => {
|
||||
field_name.replace_in_variable(working_set, new_var_id);
|
||||
field_value.replace_in_variable(working_set, new_var_id);
|
||||
}
|
||||
RecordItem::Spread(_, record) => {
|
||||
record.replace_in_variable(working_set, new_var_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Signature(_) => {}
|
||||
Expr::String(_) => {}
|
||||
Expr::StringInterpolation(items) => {
|
||||
for i in items {
|
||||
i.replace_in_variable(working_set, new_var_id)
|
||||
}
|
||||
}
|
||||
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
||||
let block = working_set.get_block(*block_id);
|
||||
|
||||
let new_element = if let Some(pipeline) = block.pipelines.first() {
|
||||
if let Some(element) = pipeline.elements.first() {
|
||||
let mut new_element = element.clone();
|
||||
new_element.replace_in_variable(working_set, new_var_id);
|
||||
Some(new_element)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let block = working_set.get_block_mut(*block_id);
|
||||
|
||||
if let Some(new_element) = new_element {
|
||||
if let Some(pipeline) = block.pipelines.get_mut(0) {
|
||||
if let Some(element) = pipeline.elements.get_mut(0) {
|
||||
*element = new_element
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
block.captures = block
|
||||
.captures
|
||||
.iter()
|
||||
.map(|x| if *x != IN_VARIABLE_ID { *x } else { new_var_id })
|
||||
.collect();
|
||||
}
|
||||
Expr::Table(headers, cells) => {
|
||||
for header in headers {
|
||||
header.replace_in_variable(working_set, new_var_id)
|
||||
}
|
||||
|
||||
for row in cells {
|
||||
for cell in row.iter_mut() {
|
||||
cell.replace_in_variable(working_set, new_var_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Expr::ValueWithUnit(expr, _) => expr.replace_in_variable(working_set, new_var_id),
|
||||
Expr::Var(x) => {
|
||||
if *x == IN_VARIABLE_ID {
|
||||
*x = new_var_id
|
||||
}
|
||||
}
|
||||
Expr::VarDecl(_) => {}
|
||||
Expr::Spread(expr) => expr.replace_in_variable(working_set, new_var_id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_span(
|
||||
&mut self,
|
||||
working_set: &mut StateWorkingSet,
|
||||
@ -544,12 +352,18 @@ impl Expression {
|
||||
if replaced.contains_span(call.head) {
|
||||
call.head = new_span;
|
||||
}
|
||||
for positional in call.positional_iter_mut() {
|
||||
positional.replace_span(working_set, replaced, new_span);
|
||||
}
|
||||
for named in call.named_iter_mut() {
|
||||
if let Some(expr) = &mut named.2 {
|
||||
expr.replace_span(working_set, replaced, new_span)
|
||||
for arg in call.arguments.iter_mut() {
|
||||
match arg {
|
||||
Argument::Positional(expr)
|
||||
| Argument::Unknown(expr)
|
||||
| Argument::Spread(expr) => {
|
||||
expr.replace_span(working_set, replaced, new_span);
|
||||
}
|
||||
Argument::Named(named) => {
|
||||
if let Some(expr) = &mut named.2 {
|
||||
expr.replace_span(working_set, replaced, new_span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -557,8 +371,8 @@ impl Expression {
|
||||
Expr::DateTime(_) => {}
|
||||
Expr::ExternalCall(head, args, _) => {
|
||||
head.replace_span(working_set, replaced, new_span);
|
||||
for arg in args {
|
||||
arg.replace_span(working_set, replaced, new_span)
|
||||
for ExternalArgument::Regular(expr) | ExternalArgument::Spread(expr) in args {
|
||||
expr.replace_span(working_set, replaced, new_span);
|
||||
}
|
||||
}
|
||||
Expr::Filepath(_) => {}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{ast::Expression, engine::StateWorkingSet, Span, VarId};
|
||||
use crate::{ast::Expression, engine::StateWorkingSet, Span};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
@ -88,30 +88,6 @@ impl PipelineElement {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_in_variable(&mut self, working_set: &mut StateWorkingSet, new_var_id: VarId) {
|
||||
match self {
|
||||
PipelineElement::Expression(_, expression)
|
||||
| PipelineElement::Redirection(_, _, expression, _)
|
||||
| PipelineElement::And(_, expression)
|
||||
| PipelineElement::Or(_, expression)
|
||||
| PipelineElement::SameTargetRedirection {
|
||||
cmd: (_, expression),
|
||||
..
|
||||
} => expression.replace_in_variable(working_set, new_var_id),
|
||||
PipelineElement::SeparateRedirection {
|
||||
out: (_, out_expr, _),
|
||||
err: (_, err_expr, _),
|
||||
} => {
|
||||
if out_expr.has_in_variable(working_set) {
|
||||
out_expr.replace_in_variable(working_set, new_var_id)
|
||||
}
|
||||
if err_expr.has_in_variable(working_set) {
|
||||
err_expr.replace_in_variable(working_set, new_var_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_span(
|
||||
&mut self,
|
||||
working_set: &mut StateWorkingSet,
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
ast::{
|
||||
eval_operator, Assignment, Bits, Boolean, Call, Comparison, Expr, Expression, Math,
|
||||
Operator, RecordItem,
|
||||
eval_operator, Assignment, Bits, Boolean, Call, Comparison, Expr, Expression,
|
||||
ExternalArgument, Math, Operator, RecordItem,
|
||||
},
|
||||
Range, Record, ShellError, Span, Value, VarId,
|
||||
};
|
||||
@ -319,7 +319,7 @@ pub trait Eval {
|
||||
state: Self::State<'_>,
|
||||
mut_state: &mut Self::MutState,
|
||||
head: &Expression,
|
||||
args: &[Expression],
|
||||
args: &[ExternalArgument],
|
||||
is_subexpression: bool,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError>;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
ast::{Assignment, Block, Call, Expr, Expression, PipelineElement},
|
||||
ast::{Assignment, Block, Call, Expr, Expression, ExternalArgument, PipelineElement},
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
eval_base::Eval,
|
||||
record, HistoryFileFormat, PipelineData, Record, ShellError, Span, Value, VarId,
|
||||
@ -317,7 +317,7 @@ impl Eval for EvalConst {
|
||||
_: &StateWorkingSet,
|
||||
_: &mut (),
|
||||
_: &Expression,
|
||||
_: &[Expression],
|
||||
_: &[ExternalArgument],
|
||||
_: bool,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
|
@ -484,9 +484,12 @@ pub enum ParseError {
|
||||
#[label("...and here")] Option<Span>,
|
||||
),
|
||||
|
||||
#[error("Unexpected spread operator outside list")]
|
||||
#[diagnostic(code(nu::parser::unexpected_spread_operator))]
|
||||
UnexpectedSpread(#[label("Spread operator not allowed here")] Span),
|
||||
#[error("This command does not have a ...rest parameter")]
|
||||
#[diagnostic(
|
||||
code(nu::parser::unexpected_spread_arg),
|
||||
help("To spread arguments, the command needs to define a multi-positional parameter in its signature, such as ...rest")
|
||||
)]
|
||||
UnexpectedSpreadArg(String, #[label = "unexpected spread argument"] Span),
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
@ -573,7 +576,7 @@ impl ParseError {
|
||||
ParseError::InvalidLiteral(_, _, s) => *s,
|
||||
ParseError::LabeledErrorWithHelp { span: s, .. } => *s,
|
||||
ParseError::RedirectionInLetMut(s, _) => *s,
|
||||
ParseError::UnexpectedSpread(s) => *s,
|
||||
ParseError::UnexpectedSpreadArg(_, s) => *s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -861,10 +861,13 @@ pub fn print_if_stream(
|
||||
// NOTE: currently we don't need anything from stderr
|
||||
// so we just consume and throw away `stderr_stream` to make sure the pipe doesn't fill up
|
||||
|
||||
thread::Builder::new()
|
||||
.name("stderr consumer".to_string())
|
||||
.spawn(move || stderr_stream.map(|x| x.into_bytes()))
|
||||
.expect("could not create thread");
|
||||
if let Some(stderr_stream) = stderr_stream {
|
||||
thread::Builder::new()
|
||||
.name("stderr consumer".to_string())
|
||||
.spawn(move || stderr_stream.into_bytes())
|
||||
.expect("could not create thread");
|
||||
}
|
||||
|
||||
if let Some(stream) = stream {
|
||||
for s in stream {
|
||||
let s_live = s?;
|
||||
|
@ -1281,15 +1281,15 @@ This is an internal Nushell error, please file an issue https://github.com/nushe
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// Tried spreading a non-list inside a list.
|
||||
/// Tried spreading a non-list inside a list or command call.
|
||||
///
|
||||
/// ## Resolution
|
||||
///
|
||||
/// Only lists can be spread inside lists. Try converting the value to a list before spreading.
|
||||
/// Only lists can be spread inside lists and command calls. Try converting the value to a list before spreading.
|
||||
#[error("Not a list")]
|
||||
#[diagnostic(
|
||||
code(nu::shell::cannot_spread_as_list),
|
||||
help("Only lists can be spread inside lists. Try converting the value to a list before spreading")
|
||||
help("Only lists can be spread inside lists and command calls. Try converting the value to a list before spreading.")
|
||||
)]
|
||||
CannotSpreadAsList {
|
||||
#[label = "cannot spread value"]
|
||||
|
@ -90,127 +90,6 @@ export def --env "path add" [
|
||||
}
|
||||
}
|
||||
|
||||
# print a command name as dimmed and italic
|
||||
def pretty-command [] {
|
||||
let command = $in
|
||||
return $"(ansi default_dimmed)(ansi default_italic)($command)(ansi reset)"
|
||||
}
|
||||
|
||||
# give a hint error when the clip command is not available on the system
|
||||
def check-clipboard [
|
||||
clipboard: string # the clipboard command name
|
||||
--system: string # some information about the system running, for better error
|
||||
] {
|
||||
if (which $clipboard | is-empty) {
|
||||
error make --unspanned {
|
||||
msg: $"(ansi red)clipboard_not_found(ansi reset):
|
||||
you are running ($system)
|
||||
but
|
||||
the ($clipboard | pretty-command) clipboard command was not found on your system."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# put the end of a pipe into the system clipboard.
|
||||
#
|
||||
# Dependencies:
|
||||
# - xclip on linux x11
|
||||
# - wl-copy on linux wayland
|
||||
# - clip.exe on windows
|
||||
# - termux-api on termux
|
||||
#
|
||||
# Examples:
|
||||
# put a simple string to the clipboard, will be stripped to remove ANSI sequences
|
||||
# >_ "my wonderful string" | clip
|
||||
# my wonderful string
|
||||
# saved to clipboard (stripped)
|
||||
#
|
||||
# put a whole table to the clipboard
|
||||
# >_ ls *.toml | clip
|
||||
# ╭───┬─────────────────────┬──────┬────────┬───────────────╮
|
||||
# │ # │ name │ type │ size │ modified │
|
||||
# ├───┼─────────────────────┼──────┼────────┼───────────────┤
|
||||
# │ 0 │ Cargo.toml │ file │ 5.0 KB │ 3 minutes ago │
|
||||
# │ 1 │ Cross.toml │ file │ 363 B │ 2 weeks ago │
|
||||
# │ 2 │ rust-toolchain.toml │ file │ 1.1 KB │ 2 weeks ago │
|
||||
# ╰───┴─────────────────────┴──────┴────────┴───────────────╯
|
||||
#
|
||||
# saved to clipboard
|
||||
#
|
||||
# put huge structured data in the clipboard, but silently
|
||||
# >_ open Cargo.toml --raw | from toml | clip --silent
|
||||
#
|
||||
# when the clipboard system command is not installed
|
||||
# >_ "mm this is fishy..." | clip
|
||||
# Error:
|
||||
# × clipboard_not_found:
|
||||
# │ you are using xorg on linux
|
||||
# │ but
|
||||
# │ the xclip clipboard command was not found on your system.
|
||||
export def clip [
|
||||
--silent # do not print the content of the clipboard to the standard output
|
||||
--no-notify # do not throw a notification (only on linux)
|
||||
--no-strip # do not strip ANSI escape sequences from a string
|
||||
--expand (-e) # auto-expand the data given as input
|
||||
--codepage (-c): int # the id of the codepage to use (only on Windows), see https://en.wikipedia.org/wiki/Windows_code_page, e.g. 65001 is for UTF-8
|
||||
] {
|
||||
let input = $in
|
||||
|
||||
print $"Warning: (char -u 26a0) (ansi yellow_bold)deprecated_command(ansi reset)"
|
||||
print "| the `std clip` command is deprecated and will be removed in Nushell 0.89"
|
||||
print ""
|
||||
print $"(ansi cyan)help(ansi reset): please use (ansi {fg: cyan, attr: du})[`modules/system clip`]\(https://github.com/nushell/nu_scripts/tree/main/modules#system\)(ansi reset)"
|
||||
|
||||
let input = $input
|
||||
| if $expand { table --expand } else { table }
|
||||
| into string
|
||||
| if $no_strip {} else { ansi strip }
|
||||
|
||||
match $nu.os-info.name {
|
||||
"linux" => {
|
||||
if ($env.WAYLAND_DISPLAY? | is-empty) {
|
||||
check-clipboard xclip --system $"('xorg' | pretty-command) on linux"
|
||||
$input | xclip -sel clip
|
||||
} else {
|
||||
check-clipboard wl-copy --system $"('wayland' | pretty-command) on linux"
|
||||
$input | wl-copy
|
||||
}
|
||||
},
|
||||
"windows" => {
|
||||
if $codepage != null {
|
||||
chcp $codepage
|
||||
}
|
||||
check-clipboard clip.exe --system "Windows"
|
||||
$input | clip.exe
|
||||
},
|
||||
"macos" => {
|
||||
check-clipboard pbcopy --system "MacOS"
|
||||
$input | pbcopy
|
||||
},
|
||||
"android" => {
|
||||
check-clipboard termux-clipboard-set --system "Termux"
|
||||
$input | termux-clipboard-set
|
||||
},
|
||||
_ => {
|
||||
error make --unspanned {
|
||||
msg: $"(ansi red)unknown_operating_system(ansi reset):
|
||||
'($nu.os-info.name)' is not supported by the ('clip' | pretty-command) command.
|
||||
|
||||
please open a feature request in the [issue tracker](char lparen)https://github.com/nushell/nushell/issues/new/choose(char rparen) to add your operating system to the standard library."
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
if not $silent {
|
||||
print $input
|
||||
print $"(ansi white_italic)(ansi white_dimmed)saved to clipboard(ansi reset)"
|
||||
}
|
||||
|
||||
if (not $no_notify) and ($nu.os-info.name == linux) {
|
||||
notify-send "std clip" "saved to clipboard"
|
||||
}
|
||||
}
|
||||
|
||||
# convert an integer amount of nanoseconds to a real duration
|
||||
def "from ns" [] {
|
||||
[$in "ns"] | str join | into duration
|
||||
|
@ -15,7 +15,7 @@ bench = false
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
sysinfo = "0.29"
|
||||
sysinfo = "0.30"
|
||||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
nix = { version = "0.27", default-features = false, features = ["fs", "term", "process", "signal"] }
|
||||
@ -28,36 +28,20 @@ libproc = "0.14"
|
||||
mach2 = "0.4"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
chrono = { version = "0.4", default-features = false }
|
||||
chrono = { version = "0.4", default-features = false, features = ["clock"] }
|
||||
ntapi = "0.4"
|
||||
once_cell = "1.18"
|
||||
winapi = { version = "0.3", features = [
|
||||
"tlhelp32",
|
||||
"fileapi",
|
||||
"handleapi",
|
||||
"ifdef",
|
||||
"ioapiset",
|
||||
"minwindef",
|
||||
"pdh",
|
||||
"psapi",
|
||||
"synchapi",
|
||||
"sysinfoapi",
|
||||
"winbase",
|
||||
"winerror",
|
||||
"winioctl",
|
||||
"winnt",
|
||||
"oleauto",
|
||||
"wbemcli",
|
||||
"rpcdce",
|
||||
"combaseapi",
|
||||
"objidl",
|
||||
"powerbase",
|
||||
"netioapi",
|
||||
"lmcons",
|
||||
"lmaccess",
|
||||
"lmapibuf",
|
||||
"memoryapi",
|
||||
"shellapi",
|
||||
"std",
|
||||
"securitybaseapi",
|
||||
] }
|
||||
windows = { version = "0.52", features = [
|
||||
"Wdk_System_SystemServices",
|
||||
"Wdk_System_Threading",
|
||||
"Win32_Foundation",
|
||||
"Win32_Security",
|
||||
"Win32_System_Diagnostics_Debug",
|
||||
"Win32_System_Diagnostics_ToolHelp",
|
||||
"Win32_System_Kernel",
|
||||
"Win32_System_Memory",
|
||||
"Win32_System_ProcessStatus",
|
||||
"Win32_System_SystemInformation",
|
||||
"Win32_System_Threading",
|
||||
"Win32_UI_Shell",
|
||||
]}
|
||||
|
@ -1,5 +1,3 @@
|
||||
use sysinfo::SystemExt;
|
||||
|
||||
pub fn get_os_name() -> &'static str {
|
||||
std::env::consts::OS
|
||||
}
|
||||
@ -13,8 +11,7 @@ pub fn get_os_family() -> &'static str {
|
||||
}
|
||||
|
||||
pub fn get_kernel_version() -> String {
|
||||
let sys = sysinfo::System::new();
|
||||
match sys.kernel_version() {
|
||||
match sysinfo::System::kernel_version() {
|
||||
Some(v) => v,
|
||||
None => "unknown".to_string(),
|
||||
}
|
||||
|
@ -4,13 +4,10 @@
|
||||
use chrono::offset::TimeZone;
|
||||
use chrono::{Local, NaiveDate};
|
||||
use libc::c_void;
|
||||
use ntapi::ntpebteb::PEB;
|
||||
use ntapi::ntpsapi::{
|
||||
NtQueryInformationProcess, ProcessBasicInformation, ProcessCommandLineInformation,
|
||||
ProcessWow64Information, PROCESSINFOCLASS, PROCESS_BASIC_INFORMATION,
|
||||
};
|
||||
use ntapi::ntrtl::{RtlGetVersion, PRTL_USER_PROCESS_PARAMETERS, RTL_USER_PROCESS_PARAMETERS};
|
||||
use ntapi::ntwow64::{PEB32, PRTL_USER_PROCESS_PARAMETERS32, RTL_USER_PROCESS_PARAMETERS32};
|
||||
|
||||
use ntapi::ntrtl::RTL_USER_PROCESS_PARAMETERS;
|
||||
use ntapi::ntwow64::{PEB32, RTL_USER_PROCESS_PARAMETERS32};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
@ -22,33 +19,48 @@ use std::ptr;
|
||||
use std::ptr::null_mut;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
use winapi::shared::basetsd::SIZE_T;
|
||||
use winapi::shared::minwindef::{DWORD, FALSE, FILETIME, LPVOID, MAX_PATH, TRUE, ULONG};
|
||||
use winapi::shared::ntdef::{NT_SUCCESS, UNICODE_STRING};
|
||||
use winapi::shared::ntstatus::{
|
||||
STATUS_BUFFER_OVERFLOW, STATUS_BUFFER_TOO_SMALL, STATUS_INFO_LENGTH_MISMATCH,
|
||||
|
||||
use windows::core::{PCWSTR, PWSTR};
|
||||
|
||||
use windows::Wdk::System::SystemServices::RtlGetVersion;
|
||||
use windows::Wdk::System::Threading::{
|
||||
NtQueryInformationProcess, ProcessBasicInformation, ProcessCommandLineInformation,
|
||||
ProcessWow64Information, PROCESSINFOCLASS,
|
||||
};
|
||||
use winapi::um::handleapi::CloseHandle;
|
||||
use winapi::um::memoryapi::{ReadProcessMemory, VirtualQueryEx};
|
||||
use winapi::um::processthreadsapi::{
|
||||
GetCurrentProcess, GetPriorityClass, GetProcessTimes, OpenProcess, OpenProcessToken,
|
||||
|
||||
use windows::Win32::Foundation::{
|
||||
CloseHandle, LocalFree, FALSE, FILETIME, HANDLE, HLOCAL, HMODULE, MAX_PATH, PSID,
|
||||
STATUS_BUFFER_OVERFLOW, STATUS_BUFFER_TOO_SMALL, STATUS_INFO_LENGTH_MISMATCH, UNICODE_STRING,
|
||||
};
|
||||
use winapi::um::psapi::{
|
||||
|
||||
use windows::Win32::Security::{
|
||||
AdjustTokenPrivileges, GetTokenInformation, LookupAccountSidW, LookupPrivilegeValueW,
|
||||
TokenGroups, TokenUser, SE_DEBUG_NAME, SE_PRIVILEGE_ENABLED, SID, SID_NAME_USE,
|
||||
TOKEN_ADJUST_PRIVILEGES, TOKEN_GROUPS, TOKEN_PRIVILEGES, TOKEN_QUERY, TOKEN_USER,
|
||||
};
|
||||
|
||||
use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory;
|
||||
use windows::Win32::System::Diagnostics::ToolHelp::{
|
||||
CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS,
|
||||
};
|
||||
|
||||
use windows::Win32::System::Memory::{VirtualQueryEx, MEMORY_BASIC_INFORMATION};
|
||||
|
||||
use windows::Win32::System::ProcessStatus::{
|
||||
GetModuleBaseNameW, GetProcessMemoryInfo, K32EnumProcesses, PROCESS_MEMORY_COUNTERS,
|
||||
PROCESS_MEMORY_COUNTERS_EX,
|
||||
};
|
||||
use winapi::um::securitybaseapi::{AdjustTokenPrivileges, GetTokenInformation};
|
||||
use winapi::um::tlhelp32::{
|
||||
CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS,
|
||||
};
|
||||
use winapi::um::winbase::{GetProcessIoCounters, LookupAccountSidW, LookupPrivilegeValueW};
|
||||
use winapi::um::winnt::{
|
||||
TokenGroups, TokenUser, HANDLE, IO_COUNTERS, MEMORY_BASIC_INFORMATION,
|
||||
PROCESS_QUERY_INFORMATION, PROCESS_VM_READ, PSID, RTL_OSVERSIONINFOEXW, SE_DEBUG_NAME,
|
||||
SE_PRIVILEGE_ENABLED, SID, TOKEN_ADJUST_PRIVILEGES, TOKEN_GROUPS, TOKEN_PRIVILEGES,
|
||||
TOKEN_QUERY, TOKEN_USER,
|
||||
|
||||
use windows::Win32::System::SystemInformation::OSVERSIONINFOEXW;
|
||||
|
||||
use windows::Win32::System::Threading::{
|
||||
GetCurrentProcess, GetPriorityClass, GetProcessIoCounters, GetProcessTimes, OpenProcess,
|
||||
OpenProcessToken, IO_COUNTERS, PEB, PROCESS_BASIC_INFORMATION, PROCESS_QUERY_INFORMATION,
|
||||
PROCESS_VM_READ,
|
||||
};
|
||||
|
||||
use windows::Win32::UI::Shell::CommandLineToArgvW;
|
||||
|
||||
pub struct ProcessInfo {
|
||||
pub pid: i32,
|
||||
pub command: String,
|
||||
@ -235,7 +247,7 @@ pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec<ProcessInfo>
|
||||
}
|
||||
|
||||
unsafe {
|
||||
CloseHandle(handle);
|
||||
let _ = CloseHandle(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -249,32 +261,20 @@ fn set_privilege() -> bool {
|
||||
let handle = GetCurrentProcess();
|
||||
let mut token: HANDLE = zeroed();
|
||||
let ret = OpenProcessToken(handle, TOKEN_ADJUST_PRIVILEGES, &mut token);
|
||||
if ret == 0 {
|
||||
if ret.is_err() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut tps: TOKEN_PRIVILEGES = zeroed();
|
||||
let se_debug_name: Vec<u16> = format!("{}\0", SE_DEBUG_NAME).encode_utf16().collect();
|
||||
tps.PrivilegeCount = 1;
|
||||
let ret = LookupPrivilegeValueW(
|
||||
ptr::null(),
|
||||
se_debug_name.as_ptr(),
|
||||
&mut tps.Privileges[0].Luid,
|
||||
);
|
||||
if ret == 0 {
|
||||
if LookupPrivilegeValueW(PCWSTR::null(), SE_DEBUG_NAME, &mut tps.Privileges[0].Luid)
|
||||
.is_err()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
tps.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||
let ret = AdjustTokenPrivileges(
|
||||
token,
|
||||
FALSE,
|
||||
&mut tps,
|
||||
0,
|
||||
ptr::null::<TOKEN_PRIVILEGES>() as *mut TOKEN_PRIVILEGES,
|
||||
ptr::null::<u32>() as *mut u32,
|
||||
);
|
||||
if ret == 0 {
|
||||
if AdjustTokenPrivileges(token, FALSE, Some(&tps), 0, None, None).is_err() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -284,21 +284,21 @@ fn set_privilege() -> bool {
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
fn get_pids() -> Vec<i32> {
|
||||
let dword_size = size_of::<DWORD>();
|
||||
let mut pids: Vec<DWORD> = Vec::with_capacity(10192);
|
||||
let dword_size = size_of::<u32>();
|
||||
let mut pids: Vec<u32> = Vec::with_capacity(10192);
|
||||
let mut cb_needed = 0;
|
||||
|
||||
unsafe {
|
||||
pids.set_len(10192);
|
||||
let result = K32EnumProcesses(
|
||||
pids.as_mut_ptr(),
|
||||
(dword_size * pids.len()) as DWORD,
|
||||
(dword_size * pids.len()) as u32,
|
||||
&mut cb_needed,
|
||||
);
|
||||
if result == 0 {
|
||||
if !result.as_bool() {
|
||||
return Vec::new();
|
||||
}
|
||||
let pids_len = cb_needed / dword_size as DWORD;
|
||||
let pids_len = cb_needed / dword_size as u32;
|
||||
pids.set_len(pids_len as usize);
|
||||
}
|
||||
|
||||
@ -311,18 +311,20 @@ fn get_ppid_threads() -> (HashMap<i32, i32>, HashMap<i32, i32>) {
|
||||
let mut threads = HashMap::new();
|
||||
|
||||
unsafe {
|
||||
let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
let Ok(snapshot) = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) else {
|
||||
return (ppids, threads);
|
||||
};
|
||||
let mut entry: PROCESSENTRY32 = zeroed();
|
||||
entry.dwSize = size_of::<PROCESSENTRY32>() as u32;
|
||||
let mut not_the_end = Process32First(snapshot, &mut entry);
|
||||
|
||||
while not_the_end != 0 {
|
||||
while not_the_end.is_ok() {
|
||||
ppids.insert(entry.th32ProcessID as i32, entry.th32ParentProcessID as i32);
|
||||
threads.insert(entry.th32ProcessID as i32, entry.cntThreads as i32);
|
||||
not_the_end = Process32Next(snapshot, &mut entry);
|
||||
}
|
||||
|
||||
CloseHandle(snapshot);
|
||||
let _ = CloseHandle(snapshot);
|
||||
}
|
||||
|
||||
(ppids, threads)
|
||||
@ -338,14 +340,14 @@ fn get_handle(pid: i32) -> Option<HANDLE> {
|
||||
OpenProcess(
|
||||
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
|
||||
FALSE,
|
||||
pid as DWORD,
|
||||
pid as u32,
|
||||
)
|
||||
};
|
||||
}
|
||||
.ok();
|
||||
|
||||
if handle.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(handle)
|
||||
match handle {
|
||||
Some(h) if h.is_invalid() => None,
|
||||
h => h,
|
||||
}
|
||||
}
|
||||
|
||||
@ -370,7 +372,7 @@ fn get_times(handle: HANDLE) -> Option<(u64, u64, u64, u64)> {
|
||||
let sys = u64::from(sys.dwHighDateTime) << 32 | u64::from(sys.dwLowDateTime);
|
||||
let user = u64::from(user.dwHighDateTime) << 32 | u64::from(user.dwLowDateTime);
|
||||
|
||||
if ret != 0 {
|
||||
if ret.is_ok() {
|
||||
Some((start, exit, sys, user))
|
||||
} else {
|
||||
None
|
||||
@ -386,10 +388,10 @@ fn get_memory_info(handle: HANDLE) -> Option<MemoryInfo> {
|
||||
handle,
|
||||
&mut pmc as *mut PROCESS_MEMORY_COUNTERS_EX as *mut c_void
|
||||
as *mut PROCESS_MEMORY_COUNTERS,
|
||||
size_of::<PROCESS_MEMORY_COUNTERS_EX>() as DWORD,
|
||||
size_of::<PROCESS_MEMORY_COUNTERS_EX>() as u32,
|
||||
);
|
||||
|
||||
if ret != 0 {
|
||||
if ret.is_ok() {
|
||||
let info = MemoryInfo {
|
||||
page_fault_count: u64::from(pmc.PageFaultCount),
|
||||
peak_working_set_size: pmc.PeakWorkingSetSize as u64,
|
||||
@ -412,15 +414,10 @@ fn get_memory_info(handle: HANDLE) -> Option<MemoryInfo> {
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
fn get_command(handle: HANDLE) -> Option<String> {
|
||||
unsafe {
|
||||
let mut exe_buf = [0u16; MAX_PATH + 1];
|
||||
let h_mod = std::ptr::null_mut();
|
||||
let mut exe_buf = [0u16; MAX_PATH as usize + 1];
|
||||
let h_mod = HMODULE::default();
|
||||
|
||||
let ret = GetModuleBaseNameW(
|
||||
handle,
|
||||
h_mod as _,
|
||||
exe_buf.as_mut_ptr(),
|
||||
MAX_PATH as DWORD + 1,
|
||||
);
|
||||
let ret = GetModuleBaseNameW(handle, h_mod, exe_buf.as_mut_slice());
|
||||
|
||||
let mut pos = 0;
|
||||
for x in exe_buf.iter() {
|
||||
@ -460,7 +457,7 @@ macro_rules! impl_RtlUserProcessParameters {
|
||||
fn get_environ(&self, handle: HANDLE) -> Result<Vec<u16>, &'static str> {
|
||||
let ptr = self.Environment;
|
||||
unsafe {
|
||||
let size = get_region_size(handle, ptr as LPVOID)?;
|
||||
let size = get_region_size(handle, ptr as _)?;
|
||||
get_process_data(handle, ptr as _, size as _)
|
||||
}
|
||||
}
|
||||
@ -483,30 +480,41 @@ unsafe fn null_terminated_wchar_to_string(slice: &[u16]) -> String {
|
||||
#[allow(clippy::uninit_vec)]
|
||||
unsafe fn get_process_data(
|
||||
handle: HANDLE,
|
||||
ptr: LPVOID,
|
||||
ptr: *const c_void,
|
||||
size: usize,
|
||||
) -> Result<Vec<u16>, &'static str> {
|
||||
let mut buffer: Vec<u16> = Vec::with_capacity(size / 2 + 1);
|
||||
buffer.set_len(size / 2);
|
||||
let mut bytes_read = 0;
|
||||
|
||||
if ReadProcessMemory(
|
||||
handle,
|
||||
ptr as *mut _,
|
||||
buffer.as_mut_ptr() as *mut _,
|
||||
ptr,
|
||||
buffer.as_mut_ptr().cast(),
|
||||
size,
|
||||
std::ptr::null_mut(),
|
||||
) != TRUE
|
||||
Some(&mut bytes_read),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return Err("Unable to read process data");
|
||||
}
|
||||
|
||||
// Documentation states that the function fails if not all data is accessible.
|
||||
if bytes_read != size {
|
||||
return Err("ReadProcessMemory returned unexpected number of bytes read");
|
||||
}
|
||||
|
||||
buffer.set_len(size / 2);
|
||||
buffer.push(0);
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
unsafe fn get_region_size(handle: HANDLE, ptr: LPVOID) -> Result<usize, &'static str> {
|
||||
unsafe fn get_region_size(handle: HANDLE, ptr: *const c_void) -> Result<usize, &'static str> {
|
||||
let mut meminfo = MaybeUninit::<MEMORY_BASIC_INFORMATION>::uninit();
|
||||
if VirtualQueryEx(
|
||||
handle,
|
||||
ptr,
|
||||
meminfo.as_mut_ptr() as *mut _,
|
||||
Some(ptr),
|
||||
meminfo.as_mut_ptr().cast(),
|
||||
size_of::<MEMORY_BASIC_INFORMATION>(),
|
||||
) == 0
|
||||
{
|
||||
@ -521,46 +529,51 @@ unsafe fn ph_query_process_variable_size(
|
||||
process_handle: HANDLE,
|
||||
process_information_class: PROCESSINFOCLASS,
|
||||
) -> Option<Vec<u16>> {
|
||||
let mut return_length = MaybeUninit::<ULONG>::uninit();
|
||||
let mut return_length = MaybeUninit::<u32>::uninit();
|
||||
|
||||
let mut status = NtQueryInformationProcess(
|
||||
if let Err(err) = NtQueryInformationProcess(
|
||||
process_handle,
|
||||
process_information_class,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
return_length.as_mut_ptr() as *mut _,
|
||||
);
|
||||
|
||||
if status != STATUS_BUFFER_OVERFLOW
|
||||
&& status != STATUS_BUFFER_TOO_SMALL
|
||||
&& status != STATUS_INFO_LENGTH_MISMATCH
|
||||
)
|
||||
.ok()
|
||||
{
|
||||
return None;
|
||||
if ![
|
||||
STATUS_BUFFER_OVERFLOW.into(),
|
||||
STATUS_BUFFER_TOO_SMALL.into(),
|
||||
STATUS_INFO_LENGTH_MISMATCH.into(),
|
||||
]
|
||||
.contains(&err.code())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let mut return_length = return_length.assume_init();
|
||||
let buf_len = (return_length as usize) / 2;
|
||||
let mut buffer: Vec<u16> = Vec::with_capacity(buf_len + 1);
|
||||
buffer.set_len(buf_len);
|
||||
|
||||
status = NtQueryInformationProcess(
|
||||
if NtQueryInformationProcess(
|
||||
process_handle,
|
||||
process_information_class,
|
||||
buffer.as_mut_ptr() as *mut _,
|
||||
return_length,
|
||||
&mut return_length as *mut _,
|
||||
);
|
||||
if !NT_SUCCESS(status) {
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
buffer.set_len(buf_len);
|
||||
buffer.push(0);
|
||||
Some(buffer)
|
||||
}
|
||||
|
||||
unsafe fn get_cmdline_from_buffer(buffer: *const u16) -> Vec<String> {
|
||||
unsafe fn get_cmdline_from_buffer(buffer: PCWSTR) -> Vec<String> {
|
||||
// Get argc and argv from the command line
|
||||
let mut argc = MaybeUninit::<i32>::uninit();
|
||||
let argv_p = winapi::um::shellapi::CommandLineToArgvW(buffer, argc.as_mut_ptr());
|
||||
let argv_p = CommandLineToArgvW(buffer, argc.as_mut_ptr());
|
||||
if argv_p.is_null() {
|
||||
return Vec::new();
|
||||
}
|
||||
@ -569,12 +582,10 @@ unsafe fn get_cmdline_from_buffer(buffer: *const u16) -> Vec<String> {
|
||||
|
||||
let mut res = Vec::new();
|
||||
for arg in argv {
|
||||
let len = libc::wcslen(*arg);
|
||||
let str_slice = std::slice::from_raw_parts(*arg, len);
|
||||
res.push(String::from_utf16_lossy(str_slice));
|
||||
res.push(String::from_utf16_lossy(arg.as_wide()));
|
||||
}
|
||||
|
||||
winapi::um::winbase::LocalFree(argv_p as *mut _);
|
||||
let _err = LocalFree(HLOCAL(argv_p as _));
|
||||
|
||||
res
|
||||
}
|
||||
@ -587,15 +598,16 @@ unsafe fn get_process_params(
|
||||
}
|
||||
|
||||
// First check if target process is running in wow64 compatibility emulator
|
||||
let mut pwow32info = MaybeUninit::<LPVOID>::uninit();
|
||||
let result = NtQueryInformationProcess(
|
||||
let mut pwow32info = MaybeUninit::<*const c_void>::uninit();
|
||||
if NtQueryInformationProcess(
|
||||
handle,
|
||||
ProcessWow64Information,
|
||||
pwow32info.as_mut_ptr() as *mut _,
|
||||
size_of::<LPVOID>() as u32,
|
||||
pwow32info.as_mut_ptr().cast(),
|
||||
size_of::<*const c_void>() as u32,
|
||||
null_mut(),
|
||||
);
|
||||
if !NT_SUCCESS(result) {
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return Err("Unable to check WOW64 information about the process");
|
||||
}
|
||||
let pwow32info = pwow32info.assume_init();
|
||||
@ -604,14 +616,15 @@ unsafe fn get_process_params(
|
||||
// target is a 64 bit process
|
||||
|
||||
let mut pbasicinfo = MaybeUninit::<PROCESS_BASIC_INFORMATION>::uninit();
|
||||
let result = NtQueryInformationProcess(
|
||||
if NtQueryInformationProcess(
|
||||
handle,
|
||||
ProcessBasicInformation,
|
||||
pbasicinfo.as_mut_ptr() as *mut _,
|
||||
pbasicinfo.as_mut_ptr().cast(),
|
||||
size_of::<PROCESS_BASIC_INFORMATION>() as u32,
|
||||
null_mut(),
|
||||
);
|
||||
if !NT_SUCCESS(result) {
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return Err("Unable to get basic process information");
|
||||
}
|
||||
let pinfo = pbasicinfo.assume_init();
|
||||
@ -619,11 +632,12 @@ unsafe fn get_process_params(
|
||||
let mut peb = MaybeUninit::<PEB>::uninit();
|
||||
if ReadProcessMemory(
|
||||
handle,
|
||||
pinfo.PebBaseAddress as *mut _,
|
||||
peb.as_mut_ptr() as *mut _,
|
||||
size_of::<PEB>() as SIZE_T,
|
||||
std::ptr::null_mut(),
|
||||
) != TRUE
|
||||
pinfo.PebBaseAddress.cast(),
|
||||
peb.as_mut_ptr().cast(),
|
||||
size_of::<PEB>(),
|
||||
None,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return Err("Unable to read process PEB");
|
||||
}
|
||||
@ -633,11 +647,12 @@ unsafe fn get_process_params(
|
||||
let mut proc_params = MaybeUninit::<RTL_USER_PROCESS_PARAMETERS>::uninit();
|
||||
if ReadProcessMemory(
|
||||
handle,
|
||||
peb.ProcessParameters as *mut PRTL_USER_PROCESS_PARAMETERS as *mut _,
|
||||
proc_params.as_mut_ptr() as *mut _,
|
||||
size_of::<RTL_USER_PROCESS_PARAMETERS>() as SIZE_T,
|
||||
std::ptr::null_mut(),
|
||||
) != TRUE
|
||||
peb.ProcessParameters.cast(),
|
||||
proc_params.as_mut_ptr().cast(),
|
||||
size_of::<RTL_USER_PROCESS_PARAMETERS>(),
|
||||
None,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return Err("Unable to read process parameters");
|
||||
}
|
||||
@ -655,10 +670,11 @@ unsafe fn get_process_params(
|
||||
if ReadProcessMemory(
|
||||
handle,
|
||||
pwow32info,
|
||||
peb32.as_mut_ptr() as *mut _,
|
||||
size_of::<PEB32>() as SIZE_T,
|
||||
std::ptr::null_mut(),
|
||||
) != TRUE
|
||||
peb32.as_mut_ptr().cast(),
|
||||
size_of::<PEB32>(),
|
||||
None,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return Err("Unable to read PEB32");
|
||||
}
|
||||
@ -667,11 +683,12 @@ unsafe fn get_process_params(
|
||||
let mut proc_params = MaybeUninit::<RTL_USER_PROCESS_PARAMETERS32>::uninit();
|
||||
if ReadProcessMemory(
|
||||
handle,
|
||||
peb32.ProcessParameters as *mut PRTL_USER_PROCESS_PARAMETERS32 as *mut _,
|
||||
proc_params.as_mut_ptr() as *mut _,
|
||||
size_of::<RTL_USER_PROCESS_PARAMETERS32>() as SIZE_T,
|
||||
std::ptr::null_mut(),
|
||||
) != TRUE
|
||||
peb32.ProcessParameters as *mut _,
|
||||
proc_params.as_mut_ptr().cast(),
|
||||
size_of::<RTL_USER_PROCESS_PARAMETERS32>(),
|
||||
None,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return Err("Unable to read 32 bit process parameters");
|
||||
}
|
||||
@ -683,13 +700,11 @@ unsafe fn get_process_params(
|
||||
))
|
||||
}
|
||||
|
||||
static WINDOWS_8_1_OR_NEWER: Lazy<bool> = Lazy::new(|| {
|
||||
let mut version_info: RTL_OSVERSIONINFOEXW = unsafe { MaybeUninit::zeroed().assume_init() };
|
||||
static WINDOWS_8_1_OR_NEWER: Lazy<bool> = Lazy::new(|| unsafe {
|
||||
let mut version_info: OSVERSIONINFOEXW = MaybeUninit::zeroed().assume_init();
|
||||
|
||||
version_info.dwOSVersionInfoSize = std::mem::size_of::<RTL_OSVERSIONINFOEXW>() as u32;
|
||||
if !NT_SUCCESS(unsafe {
|
||||
RtlGetVersion(&mut version_info as *mut RTL_OSVERSIONINFOEXW as *mut _)
|
||||
}) {
|
||||
version_info.dwOSVersionInfoSize = std::mem::size_of::<OSVERSIONINFOEXW>() as u32;
|
||||
if RtlGetVersion((&mut version_info as *mut OSVERSIONINFOEXW).cast()).is_err() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -713,16 +728,16 @@ fn get_cmd_line_new(handle: HANDLE) -> Vec<String> {
|
||||
{
|
||||
let buffer = (*(buffer.as_ptr() as *const UNICODE_STRING)).Buffer;
|
||||
|
||||
get_cmdline_from_buffer(buffer)
|
||||
get_cmdline_from_buffer(PCWSTR::from_raw(buffer.as_ptr()))
|
||||
} else {
|
||||
vec![]
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cmd_line_old<T: RtlUserProcessParameters>(params: &T, handle: HANDLE) -> Vec<String> {
|
||||
match params.get_cmdline(handle) {
|
||||
Ok(buffer) => unsafe { get_cmdline_from_buffer(buffer.as_ptr()) },
|
||||
Ok(buffer) => unsafe { get_cmdline_from_buffer(PCWSTR::from_raw(buffer.as_ptr())) },
|
||||
Err(_e) => Vec::new(),
|
||||
}
|
||||
}
|
||||
@ -769,7 +784,7 @@ fn get_io(handle: HANDLE) -> Option<(u64, u64)> {
|
||||
let mut io: IO_COUNTERS = zeroed();
|
||||
let ret = GetProcessIoCounters(handle, &mut io);
|
||||
|
||||
if ret != 0 {
|
||||
if ret.is_ok() {
|
||||
Some((io.ReadTransferCount, io.WriteTransferCount))
|
||||
} else {
|
||||
None
|
||||
@ -790,7 +805,7 @@ fn get_user(handle: HANDLE) -> Option<SidName> {
|
||||
let mut token: HANDLE = zeroed();
|
||||
let ret = OpenProcessToken(handle, TOKEN_QUERY, &mut token);
|
||||
|
||||
if ret == 0 {
|
||||
if ret.is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@ -798,7 +813,7 @@ fn get_user(handle: HANDLE) -> Option<SidName> {
|
||||
let _ = GetTokenInformation(
|
||||
token,
|
||||
TokenUser,
|
||||
ptr::null::<c_void>() as *mut c_void,
|
||||
Some(ptr::null::<c_void>() as *mut c_void),
|
||||
0,
|
||||
&mut cb_needed,
|
||||
);
|
||||
@ -808,13 +823,13 @@ fn get_user(handle: HANDLE) -> Option<SidName> {
|
||||
let ret = GetTokenInformation(
|
||||
token,
|
||||
TokenUser,
|
||||
buf.as_mut_ptr() as *mut c_void,
|
||||
Some(buf.as_mut_ptr() as *mut c_void),
|
||||
cb_needed,
|
||||
&mut cb_needed,
|
||||
);
|
||||
buf.set_len(cb_needed as usize);
|
||||
|
||||
if ret == 0 {
|
||||
if ret.is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@ -843,7 +858,7 @@ fn get_groups(handle: HANDLE) -> Option<Vec<SidName>> {
|
||||
let mut token: HANDLE = zeroed();
|
||||
let ret = OpenProcessToken(handle, TOKEN_QUERY, &mut token);
|
||||
|
||||
if ret == 0 {
|
||||
if ret.is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@ -851,7 +866,7 @@ fn get_groups(handle: HANDLE) -> Option<Vec<SidName>> {
|
||||
let _ = GetTokenInformation(
|
||||
token,
|
||||
TokenGroups,
|
||||
ptr::null::<c_void>() as *mut c_void,
|
||||
Some(ptr::null::<c_void>() as *mut c_void),
|
||||
0,
|
||||
&mut cb_needed,
|
||||
);
|
||||
@ -861,13 +876,13 @@ fn get_groups(handle: HANDLE) -> Option<Vec<SidName>> {
|
||||
let ret = GetTokenInformation(
|
||||
token,
|
||||
TokenGroups,
|
||||
buf.as_mut_ptr() as *mut c_void,
|
||||
Some(buf.as_mut_ptr() as *mut c_void),
|
||||
cb_needed,
|
||||
&mut cb_needed,
|
||||
);
|
||||
buf.set_len(cb_needed as usize);
|
||||
|
||||
if ret == 0 {
|
||||
if ret.is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@ -901,7 +916,7 @@ fn get_groups(handle: HANDLE) -> Option<Vec<SidName>> {
|
||||
fn get_sid(psid: PSID) -> Vec<u64> {
|
||||
unsafe {
|
||||
let mut ret = Vec::new();
|
||||
let psid = psid as *const SID;
|
||||
let psid = psid.0 as *const SID;
|
||||
|
||||
let mut ia = 0;
|
||||
ia |= u64::from((*psid).IdentifierAuthority.Value[0]) << 40;
|
||||
@ -924,7 +939,7 @@ fn get_sid(psid: PSID) -> Vec<u64> {
|
||||
}
|
||||
|
||||
thread_local!(
|
||||
pub static NAME_CACHE: RefCell<HashMap<PSID, Option<(String, String)>>> =
|
||||
pub static NAME_CACHE: RefCell<HashMap<*mut c_void, Option<(String, String)>>> =
|
||||
RefCell::new(HashMap::new());
|
||||
);
|
||||
|
||||
@ -932,11 +947,11 @@ thread_local!(
|
||||
fn get_name_cached(psid: PSID) -> Option<(String, String)> {
|
||||
NAME_CACHE.with(|c| {
|
||||
let mut c = c.borrow_mut();
|
||||
if let Some(x) = c.get(&psid) {
|
||||
if let Some(x) = c.get(&psid.0) {
|
||||
x.clone()
|
||||
} else {
|
||||
let x = get_name(psid);
|
||||
c.insert(psid, x.clone());
|
||||
c.insert(psid.0, x.clone());
|
||||
x
|
||||
}
|
||||
})
|
||||
@ -947,13 +962,13 @@ fn get_name(psid: PSID) -> Option<(String, String)> {
|
||||
unsafe {
|
||||
let mut cc_name = 0;
|
||||
let mut cc_domainname = 0;
|
||||
let mut pe_use = 0;
|
||||
let mut pe_use = SID_NAME_USE::default();
|
||||
let _ = LookupAccountSidW(
|
||||
ptr::null::<u16>() as *mut u16,
|
||||
PCWSTR::null(),
|
||||
psid,
|
||||
ptr::null::<u16>() as *mut u16,
|
||||
PWSTR::null(),
|
||||
&mut cc_name,
|
||||
ptr::null::<u16>() as *mut u16,
|
||||
PWSTR::null(),
|
||||
&mut cc_domainname,
|
||||
&mut pe_use,
|
||||
);
|
||||
@ -966,17 +981,17 @@ fn get_name(psid: PSID) -> Option<(String, String)> {
|
||||
let mut domainname: Vec<u16> = Vec::with_capacity(cc_domainname as usize);
|
||||
name.set_len(cc_name as usize);
|
||||
domainname.set_len(cc_domainname as usize);
|
||||
let ret = LookupAccountSidW(
|
||||
ptr::null::<u16>() as *mut u16,
|
||||
if LookupAccountSidW(
|
||||
PCWSTR::null(),
|
||||
psid,
|
||||
name.as_mut_ptr(),
|
||||
PWSTR::from_raw(name.as_mut_ptr()),
|
||||
&mut cc_name,
|
||||
domainname.as_mut_ptr(),
|
||||
PWSTR::from_raw(domainname.as_mut_ptr()),
|
||||
&mut cc_domainname,
|
||||
&mut pe_use,
|
||||
);
|
||||
|
||||
if ret == 0 {
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ pub fn pipeline(commands: &str) -> String {
|
||||
}
|
||||
|
||||
pub fn nu_repl_code(source_lines: &[&str]) -> String {
|
||||
let mut out = String::from("nu --testbin=nu_repl [ ");
|
||||
let mut out = String::from("nu --testbin=nu_repl ...[ ");
|
||||
|
||||
for line in source_lines.iter() {
|
||||
// convert each "line" to really be a single line to prevent nu! macro joining the newlines
|
||||
|
@ -14,5 +14,5 @@ nu-protocol = { path = "../nu-protocol", version = "0.88.2", features = ["plugin
|
||||
|
||||
indexmap = "2.1"
|
||||
eml-parser = "0.1"
|
||||
ical = "0.8"
|
||||
ical = "0.9"
|
||||
rust-ini = "0.20.0"
|
||||
|
@ -16,4 +16,4 @@ profile = "default"
|
||||
# use in nushell, we may opt to use the bleeding edge stable version of rust.
|
||||
# I believe rust is on a 6 week release cycle and nushell is on a 4 week release cycle.
|
||||
# So, every two nushell releases, this version number should be bumped by one.
|
||||
channel = "1.72.1"
|
||||
channel = "1.73.0"
|
||||
|
@ -99,8 +99,8 @@ fn known_external_misc_values() -> TestResult {
|
||||
run_test(
|
||||
r#"
|
||||
let x = 'abc'
|
||||
extern echo []
|
||||
echo $x [ a b c ]
|
||||
extern echo [...args]
|
||||
echo $x ...[ a b c ]
|
||||
"#,
|
||||
"abc a b c",
|
||||
)
|
||||
|
@ -763,3 +763,14 @@ fn properly_typecheck_rest_param() -> TestResult {
|
||||
fn implied_collect_has_compatible_type() -> TestResult {
|
||||
run_test(r#"let idx = 3 | $in; $idx < 1"#, "false")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_expected_colon() -> TestResult {
|
||||
fail_test(r#"{ a: 2 b }"#, "expected ':'")?;
|
||||
fail_test(r#"{ a: 2 b 3 }"#, "expected ':'")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_missing_value() -> TestResult {
|
||||
fail_test(r#"{ a: 2 b: }"#, "expected value for record field")
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::tests::{fail_test, run_test, TestResult};
|
||||
use nu_test_support::nu;
|
||||
|
||||
#[test]
|
||||
fn spread_in_list() -> TestResult {
|
||||
@ -24,30 +25,6 @@ fn spread_in_list() -> TestResult {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_spread_in_list() -> TestResult {
|
||||
run_test(r#"const x = [...[]]; $x | to nuon"#, "[]").unwrap();
|
||||
run_test(
|
||||
r#"const x = [1 2 ...[[3] {x: 1}] 5]; $x | to nuon"#,
|
||||
"[1, 2, [3], {x: 1}, 5]",
|
||||
)
|
||||
.unwrap();
|
||||
run_test(
|
||||
r#"const x = [...([f o o]) 10]; $x | to nuon"#,
|
||||
"[f, o, o, 10]",
|
||||
)
|
||||
.unwrap();
|
||||
run_test(
|
||||
r#"const l = [1, 2, [3]]; const x = [...$l $l]; $x | to nuon"#,
|
||||
"[1, 2, [3], [1, 2, [3]]]",
|
||||
)
|
||||
.unwrap();
|
||||
run_test(
|
||||
r#"[ ...[ ...[ ...[ a ] b ] c ] d ] | to nuon"#,
|
||||
"[a, b, c, d]",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_spread() -> TestResult {
|
||||
run_test(r#"def ... [x] { $x }; ... ..."#, "...").unwrap();
|
||||
@ -95,15 +72,6 @@ fn spread_in_record() -> TestResult {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_spread_in_record() -> TestResult {
|
||||
run_test(r#"const x = {...{...{...{}}}}; $x | to nuon"#, "{}").unwrap();
|
||||
run_test(
|
||||
r#"const x = {foo: bar ...{a: {x: 1}} b: 3}; $x | to nuon"#,
|
||||
"{foo: bar, a: {x: 1}, b: 3}",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_cols() -> TestResult {
|
||||
fail_test(r#"{a: 1, ...{a: 3}}"#, "column used twice").unwrap();
|
||||
@ -111,16 +79,6 @@ fn duplicate_cols() -> TestResult {
|
||||
fail_test(r#"{...{a: 0, x: 2}, ...{x: 5}}"#, "column used twice")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_duplicate_cols() -> TestResult {
|
||||
fail_test(r#"const _ = {a: 1, ...{a: 3}}"#, "column used twice").unwrap();
|
||||
fail_test(r#"const _ = {...{a: 4, x: 3}, x: 1}"#, "column used twice").unwrap();
|
||||
fail_test(
|
||||
r#"const _ = {...{a: 0, x: 2}, ...{x: 5}}"#,
|
||||
"column used twice",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_spread_on_non_record() -> TestResult {
|
||||
fail_test(r#"let x = 5; { ...$x }"#, "cannot spread").unwrap();
|
||||
@ -139,3 +97,96 @@ fn spread_type_record() -> TestResult {
|
||||
"type_mismatch",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spread_external_args() {
|
||||
assert_eq!(
|
||||
nu!(r#"nu --testbin cococo ...[1 "foo"] 2 ...[3 "bar"]"#).out,
|
||||
"1 foo 2 3 bar",
|
||||
);
|
||||
// exec doesn't have rest parameters but allows unknown arguments
|
||||
assert_eq!(
|
||||
nu!(r#"exec nu --testbin cococo "foo" ...[5 6]"#).out,
|
||||
"foo 5 6"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spread_internal_args() -> TestResult {
|
||||
run_test(
|
||||
r#"
|
||||
let list = ["foo" 4]
|
||||
def f [a b c? d? ...x] { [$a $b $c $d $x] | to nuon }
|
||||
f 1 2 ...[5 6] 7 ...$list"#,
|
||||
"[1, 2, null, null, [5, 6, 7, foo, 4]]",
|
||||
)
|
||||
.unwrap();
|
||||
run_test(
|
||||
r#"
|
||||
def f [a b c? d? ...x] { [$a $b $c $d $x] | to nuon }
|
||||
f 1 2 3 ...[5 6]"#,
|
||||
"[1, 2, 3, null, [5, 6]]",
|
||||
)
|
||||
.unwrap();
|
||||
run_test(
|
||||
r#"
|
||||
def f [--flag: int ...x] { [$flag $x] | to nuon }
|
||||
f 2 ...[foo] 4 --flag 5 6 ...[7 8]"#,
|
||||
"[5, [2, foo, 4, 6, 7, 8]]",
|
||||
)
|
||||
.unwrap();
|
||||
run_test(
|
||||
r#"
|
||||
def f [a b? --flag: int ...x] { [$a $b $flag $x] | to nuon }
|
||||
f 1 ...[foo] 4 --flag 5 6 ...[7 8]"#,
|
||||
"[1, null, 5, [foo, 4, 6, 7, 8]]",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_spread_internal_args() -> TestResult {
|
||||
fail_test(
|
||||
r#"
|
||||
def f [a b c? d? ...x] { echo $a $b $c $d $x }
|
||||
f 1 ...[5 6]"#,
|
||||
"Missing required positional argument",
|
||||
)
|
||||
.unwrap();
|
||||
fail_test(
|
||||
r#"
|
||||
def f [a b?] { echo a b c d }
|
||||
f ...[5 6]"#,
|
||||
"unexpected spread argument",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spread_non_list_args() {
|
||||
fail_test(r#"echo ...(1)"#, "cannot spread value").unwrap();
|
||||
assert!(nu!(r#"nu --testbin cococo ...(1)"#)
|
||||
.err
|
||||
.contains("cannot spread value"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spread_args_type() -> TestResult {
|
||||
fail_test(r#"def f [...x: int] {}; f ...["abc"]"#, "expected int")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explain_spread_args() -> TestResult {
|
||||
run_test(
|
||||
r#"(explain { || echo ...[1 2] }).cmd_args.0 | select arg_type name type | to nuon"#,
|
||||
r#"[[arg_type, name, type]; [spread, "[1 2]", list<int>]]"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deprecate_implicit_spread_for_externals() {
|
||||
// TODO: When automatic spreading is removed, test that list literals fail at parse time
|
||||
let result = nu!(r#"nu --testbin cococo [1 2]"#);
|
||||
assert!(result
|
||||
.err
|
||||
.contains("Automatically spreading lists is deprecated"));
|
||||
assert_eq!(result.out, "1 2");
|
||||
}
|
||||
|
@ -304,6 +304,19 @@ fn const_captures_work() {
|
||||
assert_eq!(actual.out, "xy");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_captures_in_closures_work() {
|
||||
let module = "module foo {
|
||||
const a = 'world'
|
||||
export def bar [] {
|
||||
'hello ' + $a
|
||||
}
|
||||
}";
|
||||
let inp = &[module, "use foo", "do { foo bar }"];
|
||||
let actual = nu!(&inp.join("; "));
|
||||
assert_eq!(actual.out, "hello world");
|
||||
}
|
||||
|
||||
#[ignore = "TODO: Need to fix `overlay hide` to hide the constants brough by `overlay use`"]
|
||||
#[test]
|
||||
fn complex_const_overlay_use_hide() {
|
||||
|
@ -85,7 +85,7 @@ fn execute_binary_in_string() {
|
||||
|
||||
#[test]
|
||||
fn single_quote_dollar_external() {
|
||||
let actual = nu!("let author = 'JT'; ^echo $'foo=($author)'");
|
||||
let actual = nu!("let author = 'JT'; nu --testbin cococo $'foo=($author)'");
|
||||
|
||||
assert_eq!(actual.out, "foo=JT");
|
||||
}
|
||||
@ -354,7 +354,7 @@ mod nu_commands {
|
||||
#[test]
|
||||
fn command_list_arg_test() {
|
||||
let actual = nu!("
|
||||
nu ['-c' 'version']
|
||||
nu ...['-c' 'version']
|
||||
");
|
||||
|
||||
assert!(actual.out.contains("version"));
|
||||
@ -365,7 +365,7 @@ mod nu_commands {
|
||||
#[test]
|
||||
fn command_cell_path_arg_test() {
|
||||
let actual = nu!("
|
||||
nu ([ '-c' 'version' ])
|
||||
nu ...([ '-c' 'version' ])
|
||||
");
|
||||
|
||||
assert!(actual.out.contains("version"));
|
||||
@ -436,7 +436,7 @@ mod external_command_arguments {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
"
|
||||
nu --testbin cococo (ls | get name)
|
||||
nu --testbin cococo ...(ls | get name)
|
||||
"
|
||||
));
|
||||
|
||||
@ -493,18 +493,16 @@ mod external_command_arguments {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn semicolons_are_sanitized_before_passing_to_subshell() {
|
||||
let actual = nu!("^echo \"a;b\"");
|
||||
let actual = nu!("nu --testbin cococo \"a;b\"");
|
||||
|
||||
assert_eq!(actual.out, "a;b");
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn ampersands_are_sanitized_before_passing_to_subshell() {
|
||||
let actual = nu!("^echo \"a&b\"");
|
||||
let actual = nu!("nu --testbin cococo \"a&b\"");
|
||||
|
||||
assert_eq!(actual.out, "a&b");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user