mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02:00
Compare commits
127 Commits
Author | SHA1 | Date | |
---|---|---|---|
3016d7a64c | |||
9a07b41c9d | |||
565c6409d9 | |||
27793e7452 | |||
7066cc5004 | |||
71aacf5032 | |||
3ee2fc60f9 | |||
669659f974 | |||
4cda183103 | |||
9a9fdd7a35 | |||
626d597527 | |||
8c112c9efd | |||
98525043ed | |||
872aa78373 | |||
8948c350d4 | |||
9ff92c6878 | |||
38a42905ae | |||
39cf43ef06 | |||
262914cf92 | |||
4c4609d646 | |||
65e5abaa3e | |||
f24877ba08 | |||
ab08328a30 | |||
345edbbe10 | |||
e69a02d379 | |||
0126620c19 | |||
387328fe73 | |||
eaedb30a8c | |||
c6cb406a53 | |||
d3895d71db | |||
7b95e37bbe | |||
bf425874b8 | |||
f6e248f343 | |||
ecaed7f0ae | |||
0aae485395 | |||
523d57b2d8 | |||
2a721bad52 | |||
f4d9ddd3ad | |||
daeb56fe90 | |||
43687207b4 | |||
c0ff0f12f0 | |||
2697ea9a25 | |||
55b67e17bb | |||
123547444c | |||
88f1f386bb | |||
461f69ac5d | |||
e09b4817e1 | |||
96744e3155 | |||
4372d00ea9 | |||
7884de1941 | |||
098527b263 | |||
995989dad4 | |||
67a63162b2 | |||
6be91a68f3 | |||
f7d647ac3c | |||
a2a1c1656f | |||
99ba365c4a | |||
28f58057b6 | |||
f17f857b1f | |||
cf68334fa0 | |||
6ff3a4180b | |||
1058707a29 | |||
6e590fe0a2 | |||
b23fe30530 | |||
123bf2d736 | |||
7c1eb6b0c8 | |||
63ccc62a24 | |||
c0bac5a440 | |||
68a3d7c430 | |||
d11080e9ab | |||
a4ef7c1ac4 | |||
6a1691f378 | |||
a71abdaf15 | |||
1350f1eff7 | |||
752d25b004 | |||
68fcd71898 | |||
fb4251aba7 | |||
28f0f32ae7 | |||
06c590d894 | |||
0487e9ffcb | |||
1c49ca503a | |||
360ebeb0bc | |||
7f1e025cc9 | |||
1b220815b9 | |||
671bd08bcd | |||
bce2627e45 | |||
903afda6d9 | |||
317653d5d2 | |||
a20b24a712 | |||
74d62581b9 | |||
c68324762d | |||
525acf9d9e | |||
cb67de675e | |||
fd7eef1499 | |||
a603b067e5 | |||
b0600449e5 | |||
4073783d6e | |||
779a3c075e | |||
d77eeeb5dd | |||
7a181960b7 | |||
1bf016bae3 | |||
da4c918392 | |||
db4b3a561d | |||
d74c59eace | |||
06b0bea77f | |||
3ec0655577 | |||
005b8b02b2 | |||
5042f19d1b | |||
bacc1f6317 | |||
5536c9c7bc | |||
58c6fea60b | |||
e7f1bf8535 | |||
84517138bc | |||
fb7f6fc08b | |||
3633772d52 | |||
4de9a3eb34 | |||
b2092df27e | |||
09f513bb53 | |||
857c522808 | |||
9fa2b77611 | |||
f8a8eca836 | |||
20aa59085b | |||
4b91ed57dd | |||
366348dea0 | |||
b6b36e00c6 | |||
c79432f33c | |||
08931e976e |
36
.github/workflows/nightly-build.yml
vendored
36
.github/workflows/nightly-build.yml
vendored
@ -36,12 +36,10 @@ jobs:
|
|||||||
token: ${{ secrets.WORKFLOW_TOKEN }}
|
token: ${{ secrets.WORKFLOW_TOKEN }}
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.8
|
uses: hustcer/setup-nu@v3.9
|
||||||
if: github.repository == 'nushell/nightly'
|
if: github.repository == 'nushell/nightly'
|
||||||
with:
|
with:
|
||||||
version: 0.86.0
|
version: 0.90.1
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
||||||
- name: Prepare for Nightly Release
|
- name: Prepare for Nightly Release
|
||||||
@ -81,7 +79,7 @@ jobs:
|
|||||||
- x86_64-unknown-linux-musl
|
- x86_64-unknown-linux-musl
|
||||||
- aarch64-unknown-linux-gnu
|
- aarch64-unknown-linux-gnu
|
||||||
- armv7-unknown-linux-gnueabihf
|
- armv7-unknown-linux-gnueabihf
|
||||||
# - riscv64gc-unknown-linux-gnu
|
- riscv64gc-unknown-linux-gnu
|
||||||
extra: ['bin']
|
extra: ['bin']
|
||||||
include:
|
include:
|
||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
@ -118,9 +116,9 @@ jobs:
|
|||||||
- target: armv7-unknown-linux-gnueabihf
|
- target: armv7-unknown-linux-gnueabihf
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
target_rustflags: ''
|
target_rustflags: ''
|
||||||
# - target: riscv64gc-unknown-linux-gnu
|
- target: riscv64gc-unknown-linux-gnu
|
||||||
# os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
# target_rustflags: ''
|
target_rustflags: ''
|
||||||
|
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
@ -141,11 +139,9 @@ jobs:
|
|||||||
rustflags: ''
|
rustflags: ''
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.8
|
uses: hustcer/setup-nu@v3.9
|
||||||
with:
|
with:
|
||||||
version: 0.86.0
|
version: 0.90.1
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
@ -160,7 +156,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create an Issue for Release Failure
|
- name: Create an Issue for Release Failure
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
uses: JasonEtco/create-an-issue@v2.9.1
|
uses: JasonEtco/create-an-issue@v2.9.2
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
@ -255,11 +251,9 @@ jobs:
|
|||||||
rustflags: ''
|
rustflags: ''
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.8
|
uses: hustcer/setup-nu@v3.9
|
||||||
with:
|
with:
|
||||||
version: 0.86.0
|
version: 0.90.1
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
@ -274,7 +268,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create an Issue for Release Failure
|
- name: Create an Issue for Release Failure
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
uses: JasonEtco/create-an-issue@v2.9.1
|
uses: JasonEtco/create-an-issue@v2.9.2
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
@ -321,11 +315,9 @@ jobs:
|
|||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.8
|
uses: hustcer/setup-nu@v3.9
|
||||||
with:
|
with:
|
||||||
version: 0.86.0
|
version: 0.90.1
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
# Keep the last a few releases
|
# Keep the last a few releases
|
||||||
- name: Delete Older Releases
|
- name: Delete Older Releases
|
||||||
|
20
.github/workflows/release.yml
vendored
20
.github/workflows/release.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
|||||||
- x86_64-unknown-linux-musl
|
- x86_64-unknown-linux-musl
|
||||||
- aarch64-unknown-linux-gnu
|
- aarch64-unknown-linux-gnu
|
||||||
- armv7-unknown-linux-gnueabihf
|
- armv7-unknown-linux-gnueabihf
|
||||||
# - riscv64gc-unknown-linux-gnu
|
- riscv64gc-unknown-linux-gnu
|
||||||
extra: ['bin']
|
extra: ['bin']
|
||||||
include:
|
include:
|
||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
@ -65,9 +65,9 @@ jobs:
|
|||||||
- target: armv7-unknown-linux-gnueabihf
|
- target: armv7-unknown-linux-gnueabihf
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
target_rustflags: ''
|
target_rustflags: ''
|
||||||
# - target: riscv64gc-unknown-linux-gnu
|
- target: riscv64gc-unknown-linux-gnu
|
||||||
# os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
# target_rustflags: ''
|
target_rustflags: ''
|
||||||
|
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
@ -85,11 +85,9 @@ jobs:
|
|||||||
rustflags: ''
|
rustflags: ''
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.8
|
uses: hustcer/setup-nu@v3.9
|
||||||
with:
|
with:
|
||||||
version: 0.86.0
|
version: 0.90.1
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
@ -176,11 +174,9 @@ jobs:
|
|||||||
rustflags: ''
|
rustflags: ''
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.8
|
uses: hustcer/setup-nu@v3.9
|
||||||
with:
|
with:
|
||||||
version: 0.86.0
|
version: 0.90.1
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
|
4
.github/workflows/typos.yml
vendored
4
.github/workflows/typos.yml
vendored
@ -10,6 +10,4 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Check spelling
|
- name: Check spelling
|
||||||
uses: crate-ci/typos@v1.18.0
|
uses: crate-ci/typos@v1.19.0
|
||||||
with:
|
|
||||||
config: ./.github/.typos.toml
|
|
||||||
|
673
Cargo.lock
generated
673
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
61
Cargo.toml
61
Cargo.toml
@ -10,8 +10,8 @@ homepage = "https://www.nushell.sh"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu"
|
name = "nu"
|
||||||
repository = "https://github.com/nushell/nushell"
|
repository = "https://github.com/nushell/nushell"
|
||||||
rust-version = "1.72.1"
|
rust-version = "1.74.1"
|
||||||
version = "0.90.1"
|
version = "0.91.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -43,47 +43,42 @@ members = [
|
|||||||
"crates/nu_plugin_inc",
|
"crates/nu_plugin_inc",
|
||||||
"crates/nu_plugin_gstat",
|
"crates/nu_plugin_gstat",
|
||||||
"crates/nu_plugin_example",
|
"crates/nu_plugin_example",
|
||||||
|
"crates/nu_plugin_stream_example",
|
||||||
"crates/nu_plugin_query",
|
"crates/nu_plugin_query",
|
||||||
"crates/nu_plugin_custom_values",
|
"crates/nu_plugin_custom_values",
|
||||||
"crates/nu_plugin_formats",
|
"crates/nu_plugin_formats",
|
||||||
"crates/nu-std",
|
"crates/nu-std",
|
||||||
"crates/nu-table",
|
"crates/nu-table",
|
||||||
"crates/nu-term-grid",
|
"crates/nu-term-grid",
|
||||||
|
"crates/nu-test-support",
|
||||||
"crates/nu-utils",
|
"crates/nu-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cli = { path = "./crates/nu-cli", version = "0.90.1" }
|
nu-cli = { path = "./crates/nu-cli", version = "0.91.0" }
|
||||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.90.1" }
|
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.91.0" }
|
||||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.90.1" }
|
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.91.0" }
|
||||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.90.1" }
|
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.91.0", features = [
|
||||||
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.90.1", features = [
|
|
||||||
"dataframe",
|
"dataframe",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.90.1", optional = true }
|
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.91.0", optional = true }
|
||||||
nu-command = { path = "./crates/nu-command", version = "0.90.1" }
|
nu-command = { path = "./crates/nu-command", version = "0.91.0" }
|
||||||
nu-engine = { path = "./crates/nu-engine", version = "0.90.1" }
|
nu-engine = { path = "./crates/nu-engine", version = "0.91.0" }
|
||||||
nu-explore = { path = "./crates/nu-explore", version = "0.90.1" }
|
nu-explore = { path = "./crates/nu-explore", version = "0.91.0" }
|
||||||
nu-json = { path = "./crates/nu-json", version = "0.90.1" }
|
nu-lsp = { path = "./crates/nu-lsp/", version = "0.91.0" }
|
||||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.90.1" }
|
nu-parser = { path = "./crates/nu-parser", version = "0.91.0" }
|
||||||
nu-parser = { path = "./crates/nu-parser", version = "0.90.1" }
|
nu-path = { path = "./crates/nu-path", version = "0.91.0" }
|
||||||
nu-path = { path = "./crates/nu-path", version = "0.90.1" }
|
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.91.0" }
|
||||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.90.1" }
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.91.0" }
|
||||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.90.1" }
|
nu-std = { path = "./crates/nu-std", version = "0.91.0" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.90.1" }
|
nu-utils = { path = "./crates/nu-utils", version = "0.91.0" }
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.90.1" }
|
|
||||||
nu-table = { path = "./crates/nu-table", version = "0.90.1" }
|
|
||||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.90.1" }
|
|
||||||
nu-std = { path = "./crates/nu-std", version = "0.90.1" }
|
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.90.1" }
|
|
||||||
|
|
||||||
nu-ansi-term = "0.50.0"
|
reedline = { version = "0.30.0", features = ["bashisms", "sqlite"] }
|
||||||
reedline = { version = "0.29.0", features = ["bashisms", "sqlite"] }
|
|
||||||
|
|
||||||
crossterm = "0.27"
|
crossterm = "0.27"
|
||||||
ctrlc = "3.4"
|
ctrlc = "3.4"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
miette = { version = "5.10", features = ["fancy-no-backtrace"] }
|
miette = { version = "7.1", features = ["fancy-no-backtrace", "fancy"] }
|
||||||
mimalloc = { version = "0.1.37", default-features = false, optional = true }
|
mimalloc = { version = "0.1.37", default-features = false, optional = true }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
simplelog = "0.12"
|
simplelog = "0.12"
|
||||||
@ -105,13 +100,13 @@ nix = { version = "0.27", default-features = false, features = [
|
|||||||
] }
|
] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.90.1" }
|
nu-test-support = { path = "./crates/nu-test-support", version = "0.91.0" }
|
||||||
assert_cmd = "2.0"
|
assert_cmd = "2.0"
|
||||||
criterion = "0.5"
|
divan = "0.1.14"
|
||||||
pretty_assertions = "1.4"
|
pretty_assertions = "1.4"
|
||||||
rstest = { version = "0.18", default-features = false }
|
rstest = { version = "0.18", default-features = false }
|
||||||
serial_test = "2.0"
|
serial_test = "3.0"
|
||||||
tempfile = "3.8"
|
tempfile = "3.10"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = [
|
plugin = [
|
||||||
@ -183,12 +178,10 @@ bench = false
|
|||||||
|
|
||||||
# To use a development version of a dependency please use a global override here
|
# To use a development version of a dependency please use a global override here
|
||||||
# changing versions in each sub-crate of the workspace is tedious
|
# changing versions in each sub-crate of the workspace is tedious
|
||||||
#[patch.crates-io]
|
[patch.crates-io]
|
||||||
#reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
# reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||||
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
||||||
# uu_cp = { git = "https://github.com/uutils/coreutils.git", branch = "main" }
|
|
||||||
|
|
||||||
# Criterion benchmarking setup
|
|
||||||
# Run all benchmarks with `cargo bench`
|
# Run all benchmarks with `cargo bench`
|
||||||
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
||||||
[[bench]]
|
[[bench]]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Criterion benchmarks
|
# Divan benchmarks
|
||||||
|
|
||||||
These are benchmarks using [Criterion](https://github.com/bheisler/criterion.rs), a microbenchmarking tool for Rust.
|
These are benchmarks using [Divan](https://github.com/nvzqz/divan), a microbenchmarking tool for Rust.
|
||||||
|
|
||||||
Run all benchmarks with `cargo bench`
|
Run all benchmarks with `cargo bench`
|
||||||
|
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
|
||||||
use nu_cli::eval_source;
|
use nu_cli::eval_source;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_plugin::{EncodingType, PluginResponse};
|
use nu_plugin::{Encoder, EncodingType, PluginCallResponse, PluginOutput};
|
||||||
use nu_protocol::{engine::EngineState, PipelineData, Span, Value};
|
use nu_protocol::{
|
||||||
|
engine::EngineState, eval_const::create_nu_constant, PipelineData, Span, Value, NU_VARIABLE_ID,
|
||||||
|
};
|
||||||
use nu_utils::{get_default_config, get_default_env};
|
use nu_utils::{get_default_config, get_default_env};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Run registered benchmarks.
|
||||||
|
divan::main();
|
||||||
|
}
|
||||||
|
|
||||||
fn load_bench_commands() -> EngineState {
|
fn load_bench_commands() -> EngineState {
|
||||||
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context())
|
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context())
|
||||||
}
|
}
|
||||||
@ -29,105 +35,92 @@ fn get_home_path(engine_state: &EngineState) -> PathBuf {
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_engine() -> EngineState {
|
||||||
|
let mut engine_state = load_bench_commands();
|
||||||
|
let home_path = get_home_path(&engine_state);
|
||||||
|
|
||||||
|
// parsing config.nu breaks without PWD set, so set a valid path
|
||||||
|
engine_state.add_env_var(
|
||||||
|
"PWD".into(),
|
||||||
|
Value::string(home_path.to_string_lossy(), Span::test_data()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let nu_const = create_nu_constant(&engine_state, Span::unknown())
|
||||||
|
.expect("Failed to create nushell constant.");
|
||||||
|
engine_state.set_variable_const_val(NU_VARIABLE_ID, nu_const);
|
||||||
|
|
||||||
|
engine_state
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: All benchmarks live in this 1 file to speed up build times when benchmarking.
|
// FIXME: All benchmarks live in this 1 file to speed up build times when benchmarking.
|
||||||
// When the *_benchmarks functions were in different files, `cargo bench` would build
|
// When the *_benchmarks functions were in different files, `cargo bench` would build
|
||||||
// an executable for every single one - incredibly slowly. Would be nice to figure out
|
// an executable for every single one - incredibly slowly. Would be nice to figure out
|
||||||
// a way to split things up again.
|
// a way to split things up again.
|
||||||
|
|
||||||
fn parser_benchmarks(c: &mut Criterion) {
|
#[divan::bench_group()]
|
||||||
let mut engine_state = load_bench_commands();
|
mod parser_benchmarks {
|
||||||
let home_path = get_home_path(&engine_state);
|
use super::*;
|
||||||
|
|
||||||
// parsing config.nu breaks without PWD set, so set a valid path
|
#[divan::bench()]
|
||||||
engine_state.add_env_var(
|
fn parse_default_config_file(bencher: divan::Bencher) {
|
||||||
"PWD".into(),
|
let engine_state = setup_engine();
|
||||||
Value::string(home_path.to_string_lossy(), Span::test_data()),
|
let default_env = get_default_config().as_bytes();
|
||||||
);
|
|
||||||
|
|
||||||
let default_env = get_default_env().as_bytes();
|
bencher
|
||||||
c.bench_function("parse_default_env_file", |b| {
|
.with_inputs(|| nu_protocol::engine::StateWorkingSet::new(&engine_state))
|
||||||
b.iter_batched(
|
.bench_refs(|mut working_set| parse(&mut working_set, None, default_env, false))
|
||||||
|| nu_protocol::engine::StateWorkingSet::new(&engine_state),
|
}
|
||||||
|mut working_set| parse(&mut working_set, None, default_env, false),
|
|
||||||
BatchSize::SmallInput,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let default_config = get_default_config().as_bytes();
|
#[divan::bench()]
|
||||||
c.bench_function("parse_default_config_file", |b| {
|
fn parse_default_env_file(bencher: divan::Bencher) {
|
||||||
b.iter_batched(
|
let engine_state = setup_engine();
|
||||||
|| nu_protocol::engine::StateWorkingSet::new(&engine_state),
|
let default_env = get_default_env().as_bytes();
|
||||||
|mut working_set| parse(&mut working_set, None, default_config, false),
|
|
||||||
BatchSize::SmallInput,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
c.bench_function("eval default_env.nu", |b| {
|
bencher
|
||||||
b.iter(|| {
|
.with_inputs(|| nu_protocol::engine::StateWorkingSet::new(&engine_state))
|
||||||
let mut stack = nu_protocol::engine::Stack::new();
|
.bench_refs(|mut working_set| parse(&mut working_set, None, default_env, false))
|
||||||
eval_source(
|
}
|
||||||
&mut engine_state,
|
|
||||||
&mut stack,
|
|
||||||
get_default_env().as_bytes(),
|
|
||||||
"default_env.nu",
|
|
||||||
PipelineData::empty(),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
c.bench_function("eval default_config.nu", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut stack = nu_protocol::engine::Stack::new();
|
|
||||||
eval_source(
|
|
||||||
&mut engine_state,
|
|
||||||
&mut stack,
|
|
||||||
get_default_config().as_bytes(),
|
|
||||||
"default_config.nu",
|
|
||||||
PipelineData::empty(),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_benchmarks(c: &mut Criterion) {
|
#[divan::bench_group()]
|
||||||
let mut engine_state = load_bench_commands();
|
mod eval_benchmarks {
|
||||||
let home_path = get_home_path(&engine_state);
|
use super::*;
|
||||||
|
|
||||||
// parsing config.nu breaks without PWD set, so set a valid path
|
#[divan::bench()]
|
||||||
engine_state.add_env_var(
|
fn eval_default_env(bencher: divan::Bencher) {
|
||||||
"PWD".into(),
|
let default_env = get_default_env().as_bytes();
|
||||||
Value::string(home_path.to_string_lossy(), Span::test_data()),
|
let fname = "default_env.nu";
|
||||||
);
|
bencher
|
||||||
|
.with_inputs(|| (setup_engine(), nu_protocol::engine::Stack::new()))
|
||||||
|
.bench_values(|(mut engine_state, mut stack)| {
|
||||||
|
eval_source(
|
||||||
|
&mut engine_state,
|
||||||
|
&mut stack,
|
||||||
|
default_env,
|
||||||
|
fname,
|
||||||
|
PipelineData::empty(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
c.bench_function("eval default_env.nu", |b| {
|
#[divan::bench()]
|
||||||
b.iter(|| {
|
fn eval_default_config(bencher: divan::Bencher) {
|
||||||
let mut stack = nu_protocol::engine::Stack::new();
|
let default_env = get_default_config().as_bytes();
|
||||||
eval_source(
|
let fname = "default_config.nu";
|
||||||
&mut engine_state,
|
bencher
|
||||||
&mut stack,
|
.with_inputs(|| (setup_engine(), nu_protocol::engine::Stack::new()))
|
||||||
get_default_env().as_bytes(),
|
.bench_values(|(mut engine_state, mut stack)| {
|
||||||
"default_env.nu",
|
eval_source(
|
||||||
PipelineData::empty(),
|
&mut engine_state,
|
||||||
false,
|
&mut stack,
|
||||||
)
|
default_env,
|
||||||
})
|
fname,
|
||||||
});
|
PipelineData::empty(),
|
||||||
|
false,
|
||||||
c.bench_function("eval default_config.nu", |b| {
|
)
|
||||||
b.iter(|| {
|
})
|
||||||
let mut stack = nu_protocol::engine::Stack::new();
|
}
|
||||||
eval_source(
|
|
||||||
&mut engine_state,
|
|
||||||
&mut stack,
|
|
||||||
get_default_config().as_bytes(),
|
|
||||||
"default_config.nu",
|
|
||||||
PipelineData::empty(),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate a new table data with `row_cnt` rows, `col_cnt` columns.
|
// generate a new table data with `row_cnt` rows, `col_cnt` columns.
|
||||||
@ -141,50 +134,76 @@ fn encoding_test_data(row_cnt: usize, col_cnt: usize) -> Value {
|
|||||||
Value::list(vec![record; row_cnt], Span::test_data())
|
Value::list(vec![record; row_cnt], Span::test_data())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encoding_benchmarks(c: &mut Criterion) {
|
#[divan::bench_group()]
|
||||||
let mut group = c.benchmark_group("Encoding");
|
mod encoding_benchmarks {
|
||||||
let test_cnt_pairs = [(100, 5), (100, 15), (10000, 5), (10000, 15)];
|
use super::*;
|
||||||
for (row_cnt, col_cnt) in test_cnt_pairs.into_iter() {
|
|
||||||
for fmt in ["json", "msgpack"] {
|
#[divan::bench(args = [(100, 5), (10000, 15)])]
|
||||||
group.bench_function(&format!("{fmt} encode {row_cnt} * {col_cnt}"), |b| {
|
fn json_encode(bencher: divan::Bencher, (row_cnt, col_cnt): (usize, usize)) {
|
||||||
let mut res = vec![];
|
let test_data = PluginOutput::CallResponse(
|
||||||
let test_data =
|
0,
|
||||||
PluginResponse::Value(Box::new(encoding_test_data(row_cnt, col_cnt)));
|
PluginCallResponse::value(encoding_test_data(row_cnt, col_cnt)),
|
||||||
let encoder = EncodingType::try_from_bytes(fmt.as_bytes()).unwrap();
|
);
|
||||||
b.iter(|| encoder.encode_response(&test_data, &mut res))
|
let encoder = EncodingType::try_from_bytes(b"json").unwrap();
|
||||||
});
|
bencher
|
||||||
}
|
.with_inputs(|| (vec![]))
|
||||||
|
.bench_values(|mut res| encoder.encode(&test_data, &mut res))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = [(100, 5), (10000, 15)])]
|
||||||
|
fn msgpack_encode(bencher: divan::Bencher, (row_cnt, col_cnt): (usize, usize)) {
|
||||||
|
let test_data = PluginOutput::CallResponse(
|
||||||
|
0,
|
||||||
|
PluginCallResponse::value(encoding_test_data(row_cnt, col_cnt)),
|
||||||
|
);
|
||||||
|
let encoder = EncodingType::try_from_bytes(b"msgpack").unwrap();
|
||||||
|
bencher
|
||||||
|
.with_inputs(|| (vec![]))
|
||||||
|
.bench_values(|mut res| encoder.encode(&test_data, &mut res))
|
||||||
}
|
}
|
||||||
group.finish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decoding_benchmarks(c: &mut Criterion) {
|
#[divan::bench_group()]
|
||||||
let mut group = c.benchmark_group("Decoding");
|
mod decoding_benchmarks {
|
||||||
let test_cnt_pairs = [(100, 5), (100, 15), (10000, 5), (10000, 15)];
|
use super::*;
|
||||||
for (row_cnt, col_cnt) in test_cnt_pairs.into_iter() {
|
|
||||||
for fmt in ["json", "msgpack"] {
|
|
||||||
group.bench_function(&format!("{fmt} decode for {row_cnt} * {col_cnt}"), |b| {
|
|
||||||
let mut res = vec![];
|
|
||||||
let test_data =
|
|
||||||
PluginResponse::Value(Box::new(encoding_test_data(row_cnt, col_cnt)));
|
|
||||||
let encoder = EncodingType::try_from_bytes(fmt.as_bytes()).unwrap();
|
|
||||||
encoder.encode_response(&test_data, &mut res).unwrap();
|
|
||||||
let mut binary_data = std::io::Cursor::new(res);
|
|
||||||
b.iter(|| {
|
|
||||||
binary_data.set_position(0);
|
|
||||||
encoder.decode_response(&mut binary_data)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
group.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(
|
#[divan::bench(args = [(100, 5), (10000, 15)])]
|
||||||
benches,
|
fn json_decode(bencher: divan::Bencher, (row_cnt, col_cnt): (usize, usize)) {
|
||||||
parser_benchmarks,
|
let test_data = PluginOutput::CallResponse(
|
||||||
eval_benchmarks,
|
0,
|
||||||
encoding_benchmarks,
|
PluginCallResponse::value(encoding_test_data(row_cnt, col_cnt)),
|
||||||
decoding_benchmarks
|
);
|
||||||
);
|
let encoder = EncodingType::try_from_bytes(b"json").unwrap();
|
||||||
criterion_main!(benches);
|
let mut res = vec![];
|
||||||
|
encoder.encode(&test_data, &mut res).unwrap();
|
||||||
|
bencher
|
||||||
|
.with_inputs(|| {
|
||||||
|
let mut binary_data = std::io::Cursor::new(res.clone());
|
||||||
|
binary_data.set_position(0);
|
||||||
|
binary_data
|
||||||
|
})
|
||||||
|
.bench_values(|mut binary_data| -> Result<Option<PluginOutput>, _> {
|
||||||
|
encoder.decode(&mut binary_data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = [(100, 5), (10000, 15)])]
|
||||||
|
fn msgpack_decode(bencher: divan::Bencher, (row_cnt, col_cnt): (usize, usize)) {
|
||||||
|
let test_data = PluginOutput::CallResponse(
|
||||||
|
0,
|
||||||
|
PluginCallResponse::value(encoding_test_data(row_cnt, col_cnt)),
|
||||||
|
);
|
||||||
|
let encoder = EncodingType::try_from_bytes(b"msgpack").unwrap();
|
||||||
|
let mut res = vec![];
|
||||||
|
encoder.encode(&test_data, &mut res).unwrap();
|
||||||
|
bencher
|
||||||
|
.with_inputs(|| {
|
||||||
|
let mut binary_data = std::io::Cursor::new(res.clone());
|
||||||
|
binary_data.set_position(0);
|
||||||
|
binary_data
|
||||||
|
})
|
||||||
|
.bench_values(|mut binary_data| -> Result<Option<PluginOutput>, _> {
|
||||||
|
encoder.decode(&mut binary_data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,42 +5,43 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cli"
|
name = "nu-cli"
|
||||||
version = "0.90.1"
|
version = "0.91.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.90.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.91.0" }
|
||||||
nu-command = { path = "../nu-command", version = "0.90.1" }
|
nu-command = { path = "../nu-command", version = "0.91.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.90.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.91.0" }
|
||||||
rstest = { version = "0.18.1", default-features = false }
|
rstest = { version = "0.18.1", default-features = false }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.90.1" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.91.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.90.1" }
|
nu-engine = { path = "../nu-engine", version = "0.91.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.90.1" }
|
nu-path = { path = "../nu-path", version = "0.91.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.90.1" }
|
nu-parser = { path = "../nu-parser", version = "0.91.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.90.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.91.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.90.1" }
|
nu-utils = { path = "../nu-utils", version = "0.91.0" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.90.1" }
|
nu-color-config = { path = "../nu-color-config", version = "0.91.0" }
|
||||||
nu-ansi-term = "0.50.0"
|
nu-ansi-term = "0.50.0"
|
||||||
reedline = { version = "0.29.0", features = ["bashisms", "sqlite"] }
|
reedline = { version = "0.30.0", features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
chrono = { default-features = false, features = ["std"], version = "0.4" }
|
chrono = { default-features = false, features = ["std"], version = "0.4" }
|
||||||
crossterm = "0.27"
|
crossterm = "0.27"
|
||||||
fancy-regex = "0.12"
|
fancy-regex = "0.13"
|
||||||
fuzzy-matcher = "0.3"
|
fuzzy-matcher = "0.3"
|
||||||
is_executable = "1.0"
|
is_executable = "1.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
miette = { version = "5.10", features = ["fancy-no-backtrace"] }
|
miette = { version = "7.1", features = ["fancy-no-backtrace"] }
|
||||||
|
lscolors = { version = "0.17", default-features = false, features = ["nu-ansi-term"] }
|
||||||
once_cell = "1.18"
|
once_cell = "1.18"
|
||||||
percent-encoding = "2"
|
percent-encoding = "2"
|
||||||
pathdiff = "0.2"
|
pathdiff = "0.2"
|
||||||
sysinfo = "0.30"
|
sysinfo = "0.30"
|
||||||
unicode-segmentation = "1.10"
|
unicode-segmentation = "1.11"
|
||||||
uuid = { version = "1.6.0", features = ["v4"] }
|
uuid = { version = "1.6.0", features = ["v4"] }
|
||||||
which = "5.0.0"
|
which = "6.0.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = []
|
plugin = []
|
||||||
|
@ -69,11 +69,23 @@ impl Command for Commandline {
|
|||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
|
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
|
||||||
|
let span = cmd.span();
|
||||||
|
let cmd = cmd.coerce_into_string()?;
|
||||||
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||||
|
|
||||||
if call.has_flag(engine_state, stack, "cursor")? {
|
if call.has_flag(engine_state, stack, "cursor")? {
|
||||||
let cmd_str = cmd.as_string()?;
|
nu_protocol::report_error_new(
|
||||||
match cmd_str.parse::<i64>() {
|
engine_state,
|
||||||
|
&ShellError::GenericError {
|
||||||
|
error: "`--cursor (-c)` is deprecated".into(),
|
||||||
|
msg: "Setting the current cursor position by `--cursor (-c)` is deprecated"
|
||||||
|
.into(),
|
||||||
|
span: Some(call.arguments_span()),
|
||||||
|
help: Some("Use `commandline set-cursor`".into()),
|
||||||
|
inner: vec![],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
match cmd.parse::<i64>() {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
repl.cursor_pos = if n <= 0 {
|
repl.cursor_pos = if n <= 0 {
|
||||||
0usize
|
0usize
|
||||||
@ -89,31 +101,79 @@ impl Command for Commandline {
|
|||||||
return Err(ShellError::CantConvert {
|
return Err(ShellError::CantConvert {
|
||||||
to_type: "int".to_string(),
|
to_type: "int".to_string(),
|
||||||
from_type: "string".to_string(),
|
from_type: "string".to_string(),
|
||||||
span: cmd.span(),
|
span,
|
||||||
help: Some(format!(
|
help: Some(format!(r#"string "{cmd}" does not represent a valid int"#)),
|
||||||
r#"string "{cmd_str}" does not represent a valid int"#
|
|
||||||
)),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if call.has_flag(engine_state, stack, "append")? {
|
} else if call.has_flag(engine_state, stack, "append")? {
|
||||||
repl.buffer.push_str(&cmd.as_string()?);
|
nu_protocol::report_error_new(
|
||||||
|
engine_state,
|
||||||
|
&ShellError::GenericError {
|
||||||
|
error: "`--append (-a)` is deprecated".into(),
|
||||||
|
msg: "Appending the string to the end of the buffer by `--append (-a)` is deprecated".into(),
|
||||||
|
span: Some(call.arguments_span()),
|
||||||
|
help: Some("Use `commandline edit --append (-a)`".into()),
|
||||||
|
inner: vec![],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
repl.buffer.push_str(&cmd);
|
||||||
} else if call.has_flag(engine_state, stack, "insert")? {
|
} else if call.has_flag(engine_state, stack, "insert")? {
|
||||||
let cmd_str = cmd.as_string()?;
|
nu_protocol::report_error_new(
|
||||||
|
engine_state,
|
||||||
|
&ShellError::GenericError {
|
||||||
|
error: "`--insert (-i)` is deprecated".into(),
|
||||||
|
msg: "Inserts the string into the buffer at the cursor position by `--insert (-i)` is deprecated".into(),
|
||||||
|
span: Some(call.arguments_span()),
|
||||||
|
help: Some("Use `commandline edit --insert (-i)`".into()),
|
||||||
|
inner: vec![],
|
||||||
|
},
|
||||||
|
);
|
||||||
let cursor_pos = repl.cursor_pos;
|
let cursor_pos = repl.cursor_pos;
|
||||||
repl.buffer.insert_str(cursor_pos, &cmd_str);
|
repl.buffer.insert_str(cursor_pos, &cmd);
|
||||||
repl.cursor_pos += cmd_str.len();
|
repl.cursor_pos += cmd.len();
|
||||||
} else {
|
} else {
|
||||||
repl.buffer = cmd.as_string()?;
|
nu_protocol::report_error_new(
|
||||||
|
engine_state,
|
||||||
|
&ShellError::GenericError {
|
||||||
|
error: "`--replace (-r)` is deprecated".into(),
|
||||||
|
msg: "Replaceing the current contents of the buffer by `--replace (-p)` or positional argument is deprecated".into(),
|
||||||
|
span: Some(call.arguments_span()),
|
||||||
|
help: Some("Use `commandline edit --replace (-r)`".into()),
|
||||||
|
inner: vec![],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
repl.buffer = cmd;
|
||||||
repl.cursor_pos = repl.buffer.len();
|
repl.cursor_pos = repl.buffer.len();
|
||||||
}
|
}
|
||||||
Ok(Value::nothing(call.head).into_pipeline_data())
|
Ok(Value::nothing(call.head).into_pipeline_data())
|
||||||
} else {
|
} else {
|
||||||
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||||
if call.has_flag(engine_state, stack, "cursor-end")? {
|
if call.has_flag(engine_state, stack, "cursor-end")? {
|
||||||
|
nu_protocol::report_error_new(
|
||||||
|
engine_state,
|
||||||
|
&ShellError::GenericError {
|
||||||
|
error: "`--cursor-end (-e)` is deprecated".into(),
|
||||||
|
msg: "Setting the current cursor position to the end of the buffer by `--cursor-end (-e)` is deprecated".into(),
|
||||||
|
span: Some(call.arguments_span()),
|
||||||
|
help: Some("Use `commandline set-cursor --end (-e)`".into()),
|
||||||
|
inner: vec![],
|
||||||
|
},
|
||||||
|
);
|
||||||
repl.cursor_pos = repl.buffer.len();
|
repl.cursor_pos = repl.buffer.len();
|
||||||
Ok(Value::nothing(call.head).into_pipeline_data())
|
Ok(Value::nothing(call.head).into_pipeline_data())
|
||||||
} else if call.has_flag(engine_state, stack, "cursor")? {
|
} else if call.has_flag(engine_state, stack, "cursor")? {
|
||||||
|
nu_protocol::report_error_new(
|
||||||
|
engine_state,
|
||||||
|
&ShellError::GenericError {
|
||||||
|
error: "`--cursor (-c)` is deprecated".into(),
|
||||||
|
msg: "Getting the current cursor position by `--cursor (-c)` is deprecated"
|
||||||
|
.into(),
|
||||||
|
span: Some(call.arguments_span()),
|
||||||
|
help: Some("Use `commandline get-cursor`".into()),
|
||||||
|
inner: vec![],
|
||||||
|
},
|
||||||
|
);
|
||||||
let char_pos = repl
|
let char_pos = repl
|
||||||
.buffer
|
.buffer
|
||||||
.grapheme_indices(true)
|
.grapheme_indices(true)
|
71
crates/nu-cli/src/commands/commandline/edit.rs
Normal file
71
crates/nu-cli/src/commands/commandline/edit.rs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"commandline edit"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.switch(
|
||||||
|
"append",
|
||||||
|
"appends the string to the end of the buffer",
|
||||||
|
Some('a'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"insert",
|
||||||
|
"inserts the string into the buffer at the cursor position",
|
||||||
|
Some('i'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"replace",
|
||||||
|
"replaces the current contents of the buffer (default)",
|
||||||
|
Some('r'),
|
||||||
|
)
|
||||||
|
.required(
|
||||||
|
"str",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the string to perform the operation with",
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Modify the current command line input buffer."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["repl", "interactive"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let str: String = call.req(engine_state, stack, 0)?;
|
||||||
|
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||||
|
if call.has_flag(engine_state, stack, "append")? {
|
||||||
|
repl.buffer.push_str(&str);
|
||||||
|
} else if call.has_flag(engine_state, stack, "insert")? {
|
||||||
|
let cursor_pos = repl.cursor_pos;
|
||||||
|
repl.buffer.insert_str(cursor_pos, &str);
|
||||||
|
repl.cursor_pos += str.len();
|
||||||
|
} else {
|
||||||
|
repl.buffer = str;
|
||||||
|
repl.cursor_pos = repl.buffer.len();
|
||||||
|
}
|
||||||
|
Ok(Value::nothing(call.head).into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
56
crates/nu-cli/src/commands/commandline/get_cursor.rs
Normal file
56
crates/nu-cli/src/commands/commandline/get_cursor.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||||
|
};
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"commandline get-cursor"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Int)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Get the current cursor position."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["repl", "interactive"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||||
|
let char_pos = repl
|
||||||
|
.buffer
|
||||||
|
.grapheme_indices(true)
|
||||||
|
.chain(std::iter::once((repl.buffer.len(), "")))
|
||||||
|
.position(|(i, _c)| i == repl.cursor_pos)
|
||||||
|
.expect("Cursor position isn't on a grapheme boundary");
|
||||||
|
match i64::try_from(char_pos) {
|
||||||
|
Ok(pos) => Ok(Value::int(pos, call.head).into_pipeline_data()),
|
||||||
|
Err(e) => Err(ShellError::GenericError {
|
||||||
|
error: "Failed to convert cursor position to int".to_string(),
|
||||||
|
msg: e.to_string(),
|
||||||
|
span: None,
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
crates/nu-cli/src/commands/commandline/mod.rs
Normal file
9
crates/nu-cli/src/commands/commandline/mod.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
mod commandline_;
|
||||||
|
mod edit;
|
||||||
|
mod get_cursor;
|
||||||
|
mod set_cursor;
|
||||||
|
|
||||||
|
pub use commandline_::Commandline;
|
||||||
|
pub use edit::SubCommand as CommandlineEdit;
|
||||||
|
pub use get_cursor::SubCommand as CommandlineGetCursor;
|
||||||
|
pub use set_cursor::SubCommand as CommandlineSetCursor;
|
69
crates/nu-cli/src/commands/commandline/set_cursor.rs
Normal file
69
crates/nu-cli/src/commands/commandline/set_cursor.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"commandline set-cursor"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.switch(
|
||||||
|
"end",
|
||||||
|
"set the current cursor position to the end of the buffer",
|
||||||
|
Some('e'),
|
||||||
|
)
|
||||||
|
.optional("pos", SyntaxShape::Int, "Cursor position to be set")
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Set the current cursor position."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["repl", "interactive"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||||
|
if let Some(pos) = call.opt::<i64>(engine_state, stack, 0)? {
|
||||||
|
repl.cursor_pos = if pos <= 0 {
|
||||||
|
0usize
|
||||||
|
} else {
|
||||||
|
repl.buffer
|
||||||
|
.grapheme_indices(true)
|
||||||
|
.map(|(i, _c)| i)
|
||||||
|
.nth(pos as usize)
|
||||||
|
.unwrap_or(repl.buffer.len())
|
||||||
|
};
|
||||||
|
Ok(Value::nothing(call.head).into_pipeline_data())
|
||||||
|
} else if call.has_flag(engine_state, stack, "end")? {
|
||||||
|
repl.cursor_pos = repl.buffer.len();
|
||||||
|
Ok(Value::nothing(call.head).into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
Err(ShellError::GenericError {
|
||||||
|
error: "Required a positional argument or a flag".to_string(),
|
||||||
|
msg: "".to_string(),
|
||||||
|
span: None,
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,9 @@ pub fn add_cli_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
|
|
||||||
bind_command! {
|
bind_command! {
|
||||||
Commandline,
|
Commandline,
|
||||||
|
CommandlineEdit,
|
||||||
|
CommandlineGetCursor,
|
||||||
|
CommandlineSetCursor,
|
||||||
History,
|
History,
|
||||||
HistorySession,
|
HistorySession,
|
||||||
Keybindings,
|
Keybindings,
|
||||||
|
@ -72,7 +72,7 @@ impl Command for History {
|
|||||||
} else {
|
} else {
|
||||||
let history_reader: Option<Box<dyn ReedlineHistory>> = match history.file_format {
|
let history_reader: Option<Box<dyn ReedlineHistory>> = match history.file_format {
|
||||||
HistoryFileFormat::Sqlite => {
|
HistoryFileFormat::Sqlite => {
|
||||||
SqliteBackedHistory::with_file(history_path, None, None)
|
SqliteBackedHistory::with_file(history_path.clone(), None, None)
|
||||||
.map(|inner| {
|
.map(|inner| {
|
||||||
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
||||||
boxed
|
boxed
|
||||||
@ -80,14 +80,15 @@ impl Command for History {
|
|||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryFileFormat::PlainText => {
|
HistoryFileFormat::PlainText => FileBackedHistory::with_file(
|
||||||
FileBackedHistory::with_file(history.max_size as usize, history_path)
|
history.max_size as usize,
|
||||||
.map(|inner| {
|
history_path.clone(),
|
||||||
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
)
|
||||||
boxed
|
.map(|inner| {
|
||||||
})
|
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
||||||
.ok()
|
boxed
|
||||||
}
|
})
|
||||||
|
.ok(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match history.file_format {
|
match history.file_format {
|
||||||
@ -107,7 +108,10 @@ impl Command for History {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.ok_or(ShellError::FileNotFound { span: head })?
|
.ok_or(ShellError::FileNotFound {
|
||||||
|
file: history_path.display().to_string(),
|
||||||
|
span: head,
|
||||||
|
})?
|
||||||
.into_pipeline_data(ctrlc)),
|
.into_pipeline_data(ctrlc)),
|
||||||
HistoryFileFormat::Sqlite => Ok(history_reader
|
HistoryFileFormat::Sqlite => Ok(history_reader
|
||||||
.and_then(|h| {
|
.and_then(|h| {
|
||||||
@ -119,12 +123,15 @@ impl Command for History {
|
|||||||
create_history_record(idx, entry, long, head)
|
create_history_record(idx, entry, long, head)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.ok_or(ShellError::FileNotFound { span: head })?
|
.ok_or(ShellError::FileNotFound {
|
||||||
|
file: history_path.display().to_string(),
|
||||||
|
span: head,
|
||||||
|
})?
|
||||||
.into_pipeline_data(ctrlc)),
|
.into_pipeline_data(ctrlc)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::FileNotFound { span: head })
|
Err(ShellError::ConfigDirNotFound { span: Some(head) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
|||||||
let o = match v {
|
let o = match v {
|
||||||
Value::Record { val, .. } => val
|
Value::Record { val, .. } => val
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(x, y)| format!("{}: {}", x, y.into_string("", config)))
|
.map(|(x, y)| format!("{}: {}", x, y.to_expanded_string("", config)))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(", "),
|
.join(", "),
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ mod keybindings_default;
|
|||||||
mod keybindings_list;
|
mod keybindings_list;
|
||||||
mod keybindings_listen;
|
mod keybindings_listen;
|
||||||
|
|
||||||
pub use commandline::Commandline;
|
pub use commandline::{Commandline, CommandlineEdit, CommandlineGetCursor, CommandlineSetCursor};
|
||||||
pub use history::{History, HistorySession};
|
pub use history::{History, HistorySession};
|
||||||
pub use keybindings::Keybindings;
|
pub use keybindings::Keybindings;
|
||||||
pub use keybindings_default::KeybindingsDefault;
|
pub use keybindings_default::KeybindingsDefault;
|
||||||
|
@ -43,9 +43,9 @@ impl CommandCompletion {
|
|||||||
if let Some(paths) = paths {
|
if let Some(paths) = paths {
|
||||||
if let Ok(paths) = paths.as_list() {
|
if let Ok(paths) = paths.as_list() {
|
||||||
for path in paths {
|
for path in paths {
|
||||||
let path = path.as_string().unwrap_or_default();
|
let path = path.coerce_str().unwrap_or_default();
|
||||||
|
|
||||||
if let Ok(mut contents) = std::fs::read_dir(path) {
|
if let Ok(mut contents) = std::fs::read_dir(path.as_ref()) {
|
||||||
while let Some(Ok(item)) = contents.next() {
|
while let Some(Ok(item)) = contents.next() {
|
||||||
if self.engine_state.config.max_external_completion_results
|
if self.engine_state.config.max_external_completion_results
|
||||||
> executables.len() as i64
|
> executables.len() as i64
|
||||||
@ -253,7 +253,7 @@ mod command_completions_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_find_non_whitespace_index() {
|
fn test_find_non_whitespace_index() {
|
||||||
let commands = vec![
|
let commands = [
|
||||||
(" hello", 4),
|
(" hello", 4),
|
||||||
("sudo ", 0),
|
("sudo ", 0),
|
||||||
(" sudo ", 2),
|
(" sudo ", 2),
|
||||||
@ -273,7 +273,7 @@ mod command_completions_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_last_command_passthrough() {
|
fn test_is_last_command_passthrough() {
|
||||||
let commands = vec![
|
let commands = [
|
||||||
(" hello", false),
|
(" hello", false),
|
||||||
(" sudo ", true),
|
(" sudo ", true),
|
||||||
("sudo ", true),
|
("sudo ", true),
|
||||||
|
@ -129,6 +129,8 @@ impl NuCompleter {
|
|||||||
for pipeline_element in pipeline.elements {
|
for pipeline_element in pipeline.elements {
|
||||||
match pipeline_element {
|
match pipeline_element {
|
||||||
PipelineElement::Expression(_, expr)
|
PipelineElement::Expression(_, expr)
|
||||||
|
| PipelineElement::ErrPipedExpression(_, expr)
|
||||||
|
| PipelineElement::OutErrPipedExpression(_, expr)
|
||||||
| PipelineElement::Redirection(_, _, expr, _)
|
| PipelineElement::Redirection(_, _, expr, _)
|
||||||
| PipelineElement::And(_, expr)
|
| PipelineElement::And(_, expr)
|
||||||
| PipelineElement::Or(_, expr)
|
| PipelineElement::Or(_, expr)
|
||||||
@ -264,8 +266,10 @@ impl NuCompleter {
|
|||||||
|| prev_expr_str == b"overlay use"
|
|| prev_expr_str == b"overlay use"
|
||||||
|| prev_expr_str == b"source-env"
|
|| prev_expr_str == b"source-env"
|
||||||
{
|
{
|
||||||
let mut completer =
|
let mut completer = DotNuCompletion::new(
|
||||||
DotNuCompletion::new(self.engine_state.clone());
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
return self.process_completion(
|
return self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
@ -276,8 +280,10 @@ impl NuCompleter {
|
|||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
} else if prev_expr_str == b"ls" {
|
} else if prev_expr_str == b"ls" {
|
||||||
let mut completer =
|
let mut completer = FileCompletion::new(
|
||||||
FileCompletion::new(self.engine_state.clone());
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
return self.process_completion(
|
return self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
@ -311,8 +317,10 @@ impl NuCompleter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
FlatShape::Directory => {
|
FlatShape::Directory => {
|
||||||
let mut completer =
|
let mut completer = DirectoryCompletion::new(
|
||||||
DirectoryCompletion::new(self.engine_state.clone());
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
return self.process_completion(
|
return self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
@ -324,8 +332,10 @@ impl NuCompleter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
FlatShape::Filepath | FlatShape::GlobPattern => {
|
FlatShape::Filepath | FlatShape::GlobPattern => {
|
||||||
let mut completer =
|
let mut completer = FileCompletion::new(
|
||||||
FileCompletion::new(self.engine_state.clone());
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
return self.process_completion(
|
return self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
@ -372,8 +382,10 @@ impl NuCompleter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for file completion
|
// Check for file completion
|
||||||
let mut completer =
|
let mut completer = FileCompletion::new(
|
||||||
FileCompletion::new(self.engine_state.clone());
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
);
|
||||||
out = self.process_completion(
|
out = self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
&working_set,
|
&working_set,
|
||||||
@ -462,7 +474,7 @@ pub fn map_value_completions<'a>(
|
|||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
list.filter_map(move |x| {
|
list.filter_map(move |x| {
|
||||||
// Match for string values
|
// Match for string values
|
||||||
if let Ok(s) = x.as_string() {
|
if let Ok(s) = x.coerce_string() {
|
||||||
return Some(Suggestion {
|
return Some(Suggestion {
|
||||||
value: s,
|
value: s,
|
||||||
description: None,
|
description: None,
|
||||||
@ -495,7 +507,7 @@ pub fn map_value_completions<'a>(
|
|||||||
// Match `value` column
|
// Match `value` column
|
||||||
if it.0 == "value" {
|
if it.0 == "value" {
|
||||||
// Convert the value to string
|
// Convert the value to string
|
||||||
if let Ok(val_str) = it.1.as_string() {
|
if let Ok(val_str) = it.1.coerce_string() {
|
||||||
// Update the suggestion value
|
// Update the suggestion value
|
||||||
suggestion.value = val_str;
|
suggestion.value = val_str;
|
||||||
}
|
}
|
||||||
@ -504,7 +516,7 @@ pub fn map_value_completions<'a>(
|
|||||||
// Match `description` column
|
// Match `description` column
|
||||||
if it.0 == "description" {
|
if it.0 == "description" {
|
||||||
// Convert the value to string
|
// Convert the value to string
|
||||||
if let Ok(desc_str) = it.1.as_string() {
|
if let Ok(desc_str) = it.1.coerce_string() {
|
||||||
// Update the suggestion value
|
// Update the suggestion value
|
||||||
suggestion.description = Some(desc_str);
|
suggestion.description = Some(desc_str);
|
||||||
}
|
}
|
||||||
@ -552,7 +564,7 @@ mod completer_tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let mut completer = NuCompleter::new(engine_state.into(), Stack::new());
|
let mut completer = NuCompleter::new(engine_state.into(), Stack::new());
|
||||||
let dataset = vec![
|
let dataset = [
|
||||||
("sudo", false, "", Vec::new()),
|
("sudo", false, "", Vec::new()),
|
||||||
("sudo l", true, "l", vec!["ls", "let", "lines", "loop"]),
|
("sudo l", true, "l", vec!["ls", "let", "lines", "loop"]),
|
||||||
(" sudo", false, "", Vec::new()),
|
(" sudo", false, "", Vec::new()),
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
use crate::completions::{matches, CompletionOptions};
|
use crate::completions::{matches, CompletionOptions};
|
||||||
|
use nu_ansi_term::Style;
|
||||||
|
use nu_engine::env_to_string;
|
||||||
use nu_path::home_dir;
|
use nu_path::home_dir;
|
||||||
|
use nu_protocol::engine::{EngineState, Stack};
|
||||||
use nu_protocol::{engine::StateWorkingSet, Span};
|
use nu_protocol::{engine::StateWorkingSet, Span};
|
||||||
|
use nu_utils::get_ls_colors;
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP};
|
use std::path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP};
|
||||||
|
|
||||||
fn complete_rec(
|
fn complete_rec(
|
||||||
@ -92,12 +97,31 @@ pub fn complete_item(
|
|||||||
partial: &str,
|
partial: &str,
|
||||||
cwd: &str,
|
cwd: &str,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<(nu_protocol::Span, String)> {
|
engine_state: &EngineState,
|
||||||
|
stack: &Stack,
|
||||||
|
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
|
||||||
let partial = surround_remove(partial);
|
let partial = surround_remove(partial);
|
||||||
let isdir = partial.ends_with(is_separator);
|
let isdir = partial.ends_with(is_separator);
|
||||||
let cwd_pathbuf = Path::new(cwd).to_path_buf();
|
let cwd_pathbuf = Path::new(cwd).to_path_buf();
|
||||||
|
let ls_colors = (engine_state.config.use_ls_colors_completions
|
||||||
|
&& engine_state.config.use_ansi_coloring)
|
||||||
|
.then(|| {
|
||||||
|
let ls_colors_env_str = match stack.get_env_var(engine_state, "LS_COLORS") {
|
||||||
|
Some(v) => env_to_string("LS_COLORS", &v, engine_state, stack).ok(),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
get_ls_colors(ls_colors_env_str)
|
||||||
|
});
|
||||||
let mut original_cwd = OriginalCwd::None;
|
let mut original_cwd = OriginalCwd::None;
|
||||||
let mut components = Path::new(&partial).components().peekable();
|
let mut components_vec: Vec<Component> = Path::new(&partial).components().collect();
|
||||||
|
|
||||||
|
// Path components that end with a single "." get normalized away,
|
||||||
|
// so if the partial path ends in a literal "." we must add it back in manually
|
||||||
|
if partial.ends_with('.') && partial.len() > 1 {
|
||||||
|
components_vec.push(Component::Normal(OsStr::new(".")));
|
||||||
|
};
|
||||||
|
let mut components = components_vec.into_iter().peekable();
|
||||||
|
|
||||||
let mut cwd = match components.peek().cloned() {
|
let mut cwd = match components.peek().cloned() {
|
||||||
Some(c @ Component::Prefix(..)) => {
|
Some(c @ Component::Prefix(..)) => {
|
||||||
// windows only by definition
|
// windows only by definition
|
||||||
@ -148,12 +172,35 @@ pub fn complete_item(
|
|||||||
|
|
||||||
complete_rec(partial.as_slice(), &cwd, options, want_directory, isdir)
|
complete_rec(partial.as_slice(), &cwd, options, want_directory, isdir)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|p| (span, escape_path(original_cwd.apply(&p), want_directory)))
|
.map(|p| {
|
||||||
|
let path = original_cwd.apply(&p);
|
||||||
|
let style = ls_colors.as_ref().map(|lsc| {
|
||||||
|
lsc.style_for_path_with_metadata(
|
||||||
|
&path,
|
||||||
|
std::fs::symlink_metadata(&path).ok().as_ref(),
|
||||||
|
)
|
||||||
|
.map(lscolors::Style::to_nu_ansi_term_style)
|
||||||
|
.unwrap_or_default()
|
||||||
|
});
|
||||||
|
(span, escape_path(path, want_directory), style)
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix files or folders with quotes or hashes
|
// Fix files or folders with quotes or hashes
|
||||||
pub fn escape_path(path: String, dir: bool) -> String {
|
pub fn escape_path(path: String, dir: bool) -> String {
|
||||||
|
// make glob pattern have the highest priority.
|
||||||
|
let glob_contaminated = path.contains(['[', '*', ']', '?']);
|
||||||
|
if glob_contaminated {
|
||||||
|
return if path.contains('\'') {
|
||||||
|
// decide to use double quote, also need to escape `"` in path
|
||||||
|
// or else users can't do anything with completed path either.
|
||||||
|
format!("\"{}\"", path.replace('"', r#"\""#))
|
||||||
|
} else {
|
||||||
|
format!("'{path}'")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let filename_contaminated = !dir && path.contains(['\'', '"', ' ', '#', '(', ')']);
|
let filename_contaminated = !dir && path.contains(['\'', '"', ' ', '#', '(', ')']);
|
||||||
let dirname_contaminated = dir && path.contains(['\'', '"', ' ', '#']);
|
let dirname_contaminated = dir && path.contains(['\'', '"', ' ', '#']);
|
||||||
let maybe_flag = path.starts_with('-');
|
let maybe_flag = path.starts_with('-');
|
||||||
|
@ -117,7 +117,7 @@ impl Completer for CustomCompletion {
|
|||||||
},
|
},
|
||||||
match_algorithm: match options.get("completion_algorithm") {
|
match_algorithm: match options.get("completion_algorithm") {
|
||||||
Some(option) => option
|
Some(option) => option
|
||||||
.as_string()
|
.coerce_string()
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|option| option.try_into().ok())
|
.and_then(|option| option.try_into().ok())
|
||||||
.unwrap_or(MatchAlgorithm::Prefix),
|
.unwrap_or(MatchAlgorithm::Prefix),
|
||||||
|
@ -2,8 +2,9 @@ use crate::completions::{
|
|||||||
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
||||||
Completer, CompletionOptions, SortBy,
|
Completer, CompletionOptions, SortBy,
|
||||||
};
|
};
|
||||||
|
use nu_ansi_term::Style;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
levenshtein_distance, Span,
|
levenshtein_distance, Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
@ -13,11 +14,15 @@ use std::sync::Arc;
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DirectoryCompletion {
|
pub struct DirectoryCompletion {
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
|
stack: Stack,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirectoryCompletion {
|
impl DirectoryCompletion {
|
||||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
|
||||||
Self { engine_state }
|
Self {
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,12 +44,14 @@ impl Completer for DirectoryCompletion {
|
|||||||
&prefix,
|
&prefix,
|
||||||
&self.engine_state.current_work_dir(),
|
&self.engine_state.current_work_dir(),
|
||||||
options,
|
options,
|
||||||
|
self.engine_state.as_ref(),
|
||||||
|
&self.stack,
|
||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: x.1,
|
value: x.1,
|
||||||
description: None,
|
description: None,
|
||||||
style: None,
|
style: x.2,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: x.0.start - offset,
|
start: x.0.start - offset,
|
||||||
@ -113,6 +120,8 @@ pub fn directory_completion(
|
|||||||
partial: &str,
|
partial: &str,
|
||||||
cwd: &str,
|
cwd: &str,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<(nu_protocol::Span, String)> {
|
engine_state: &EngineState,
|
||||||
complete_item(true, span, partial, cwd, options)
|
stack: &Stack,
|
||||||
|
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
|
||||||
|
complete_item(true, span, partial, cwd, options, engine_state, stack)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::completions::{file_path_completion, Completer, CompletionOptions, SortBy};
|
use crate::completions::{file_path_completion, Completer, CompletionOptions, SortBy};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Span,
|
Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
@ -12,11 +12,15 @@ use std::{
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DotNuCompletion {
|
pub struct DotNuCompletion {
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
|
stack: Stack,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DotNuCompletion {
|
impl DotNuCompletion {
|
||||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
|
||||||
Self { engine_state }
|
Self {
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +54,7 @@ impl Completer for DotNuCompletion {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|it| {
|
.flat_map(|it| {
|
||||||
it.iter().map(|x| {
|
it.iter().map(|x| {
|
||||||
x.as_path()
|
x.to_path()
|
||||||
.expect("internal error: failed to convert lib path")
|
.expect("internal error: failed to convert lib path")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -92,7 +96,14 @@ impl Completer for DotNuCompletion {
|
|||||||
let output: Vec<Suggestion> = search_dirs
|
let output: Vec<Suggestion> = search_dirs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|search_dir| {
|
.flat_map(|search_dir| {
|
||||||
let completions = file_path_completion(span, &partial, &search_dir, options);
|
let completions = file_path_completion(
|
||||||
|
span,
|
||||||
|
&partial,
|
||||||
|
&search_dir,
|
||||||
|
options,
|
||||||
|
self.engine_state.as_ref(),
|
||||||
|
&self.stack,
|
||||||
|
);
|
||||||
completions
|
completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(move |it| {
|
.filter(move |it| {
|
||||||
@ -111,7 +122,7 @@ impl Completer for DotNuCompletion {
|
|||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: x.1,
|
value: x.1,
|
||||||
description: None,
|
description: None,
|
||||||
style: None,
|
style: x.2,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: x.0.start - offset,
|
start: x.0.start - offset,
|
||||||
|
@ -2,8 +2,9 @@ use crate::completions::{
|
|||||||
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
||||||
Completer, CompletionOptions, SortBy,
|
Completer, CompletionOptions, SortBy,
|
||||||
};
|
};
|
||||||
|
use nu_ansi_term::Style;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
levenshtein_distance, Span,
|
levenshtein_distance, Span,
|
||||||
};
|
};
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
@ -14,11 +15,15 @@ use std::sync::Arc;
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FileCompletion {
|
pub struct FileCompletion {
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
|
stack: Stack,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileCompletion {
|
impl FileCompletion {
|
||||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
|
||||||
Self { engine_state }
|
Self {
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,12 +49,14 @@ impl Completer for FileCompletion {
|
|||||||
&prefix,
|
&prefix,
|
||||||
&self.engine_state.current_work_dir(),
|
&self.engine_state.current_work_dir(),
|
||||||
options,
|
options,
|
||||||
|
self.engine_state.as_ref(),
|
||||||
|
&self.stack,
|
||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: x.1,
|
value: x.1,
|
||||||
description: None,
|
description: None,
|
||||||
style: None,
|
style: x.2,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: x.0.start - offset,
|
start: x.0.start - offset,
|
||||||
@ -118,8 +125,10 @@ pub fn file_path_completion(
|
|||||||
partial: &str,
|
partial: &str,
|
||||||
cwd: &str,
|
cwd: &str,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<(nu_protocol::Span, String)> {
|
engine_state: &EngineState,
|
||||||
complete_item(false, span, partial, cwd, options)
|
stack: &Stack,
|
||||||
|
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
|
||||||
|
complete_item(false, span, partial, cwd, options, engine_state, stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
|
pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
|
||||||
|
@ -61,17 +61,18 @@ pub fn add_plugin_file(
|
|||||||
plugin_file: Option<Spanned<String>>,
|
plugin_file: Option<Spanned<String>>,
|
||||||
storage_path: &str,
|
storage_path: &str,
|
||||||
) {
|
) {
|
||||||
if let Some(plugin_file) = plugin_file {
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let cwd = working_set.get_cwd();
|
||||||
let cwd = working_set.get_cwd();
|
|
||||||
|
|
||||||
|
if let Some(plugin_file) = plugin_file {
|
||||||
if let Ok(path) = canonicalize_with(&plugin_file.item, cwd) {
|
if let Ok(path) = canonicalize_with(&plugin_file.item, cwd) {
|
||||||
engine_state.plugin_signatures = Some(path)
|
engine_state.plugin_signatures = Some(path)
|
||||||
} else {
|
} else {
|
||||||
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
|
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
|
||||||
report_error(&working_set, &e);
|
report_error(&working_set, &e);
|
||||||
}
|
}
|
||||||
} else if let Some(mut plugin_path) = nu_path::config_dir() {
|
} else if let Some(plugin_path) = nu_path::config_dir() {
|
||||||
|
let mut plugin_path = canonicalize_with(&plugin_path, cwd).unwrap_or(plugin_path);
|
||||||
// Path to store plugins signatures
|
// Path to store plugins signatures
|
||||||
plugin_path.push(storage_path);
|
plugin_path.push(storage_path);
|
||||||
plugin_path.push(PLUGIN_FILE);
|
plugin_path.push(PLUGIN_FILE);
|
||||||
|
@ -28,7 +28,7 @@ pub fn evaluate_commands(
|
|||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
if let Some(ref t_mode) = table_mode {
|
if let Some(ref t_mode) = table_mode {
|
||||||
let mut config = engine_state.get_config().clone();
|
let mut config = engine_state.get_config().clone();
|
||||||
config.table_mode = t_mode.as_string()?.parse().unwrap_or_default();
|
config.table_mode = t_mode.coerce_str()?.parse().unwrap_or_default();
|
||||||
engine_state.set_config(config);
|
engine_state.set_config(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ pub fn evaluate_commands(
|
|||||||
Ok(pipeline_data) => {
|
Ok(pipeline_data) => {
|
||||||
let mut config = engine_state.get_config().clone();
|
let mut config = engine_state.get_config().clone();
|
||||||
if let Some(t_mode) = table_mode {
|
if let Some(t_mode) = table_mode {
|
||||||
config.table_mode = t_mode.as_string()?.parse().unwrap_or_default();
|
config.table_mode = t_mode.coerce_str()?.parse().unwrap_or_default();
|
||||||
}
|
}
|
||||||
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
|
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
|
||||||
}
|
}
|
||||||
|
@ -256,7 +256,7 @@ fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, co
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let out = item.into_string("\n", config) + "\n";
|
let out = item.to_expanded_string("\n", config) + "\n";
|
||||||
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{err}"));
|
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{err}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ impl NuHelpCompleter {
|
|||||||
|
|
||||||
if !sig.named.is_empty() {
|
if !sig.named.is_empty() {
|
||||||
long_desc.push_str(&get_flags_section(Some(&*self.0.clone()), sig, |v| {
|
long_desc.push_str(&get_flags_section(Some(&*self.0.clone()), sig, |v| {
|
||||||
v.into_string_parsable(", ", &self.0.config)
|
v.to_parsable_string(", ", &self.0.config)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ impl NuHelpCompleter {
|
|||||||
let opt_suffix = if let Some(value) = &positional.default_value {
|
let opt_suffix = if let Some(value) = &positional.default_value {
|
||||||
format!(
|
format!(
|
||||||
" (optional, default: {})",
|
" (optional, default: {})",
|
||||||
&value.into_string_parsable(", ", &self.0.config),
|
&value.to_parsable_string(", ", &self.0.config),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(" (optional)").to_string()
|
(" (optional)").to_string()
|
||||||
|
@ -83,10 +83,12 @@ fn convert_to_suggestions(
|
|||||||
Value::Record { val, .. } => {
|
Value::Record { val, .. } => {
|
||||||
let text = val
|
let text = val
|
||||||
.get("value")
|
.get("value")
|
||||||
.and_then(|val| val.as_string().ok())
|
.and_then(|val| val.coerce_string().ok())
|
||||||
.unwrap_or_else(|| "No value key".to_string());
|
.unwrap_or_else(|| "No value key".to_string());
|
||||||
|
|
||||||
let description = val.get("description").and_then(|val| val.as_string().ok());
|
let description = val
|
||||||
|
.get("description")
|
||||||
|
.and_then(|val| val.coerce_string().ok());
|
||||||
|
|
||||||
let span = match val.get("span") {
|
let span = match val.get("span") {
|
||||||
Some(Value::Record { val: span, .. }) => {
|
Some(Value::Record { val: span, .. }) => {
|
||||||
|
@ -45,10 +45,9 @@ impl Command for NuHighlight {
|
|||||||
};
|
};
|
||||||
|
|
||||||
input.map(
|
input.map(
|
||||||
move |x| match x.as_string() {
|
move |x| match x.coerce_into_string() {
|
||||||
Ok(line) => {
|
Ok(line) => {
|
||||||
let highlights = highlighter.highlight(&line, line.len());
|
let highlights = highlighter.highlight(&line, line.len());
|
||||||
|
|
||||||
Value::string(highlights.render_simple(), head)
|
Value::string(highlights.render_simple(), head)
|
||||||
}
|
}
|
||||||
Err(err) => Value::error(err, head),
|
Err(err) => Value::error(err, head),
|
||||||
|
@ -84,7 +84,7 @@ pub(crate) fn add_menus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Checking if the default menus have been added from the config file
|
// Checking if the default menus have been added from the config file
|
||||||
let default_menus = vec![
|
let default_menus = [
|
||||||
("completion_menu", DEFAULT_COMPLETION_MENU),
|
("completion_menu", DEFAULT_COMPLETION_MENU),
|
||||||
("history_menu", DEFAULT_HISTORY_MENU),
|
("history_menu", DEFAULT_HISTORY_MENU),
|
||||||
("help_menu", DEFAULT_HELP_MENU),
|
("help_menu", DEFAULT_HELP_MENU),
|
||||||
@ -94,7 +94,7 @@ pub(crate) fn add_menus(
|
|||||||
if !config
|
if !config
|
||||||
.menus
|
.menus
|
||||||
.iter()
|
.iter()
|
||||||
.any(|menu| menu.name.into_string("", config) == name)
|
.any(|menu| menu.name.to_expanded_string("", config) == name)
|
||||||
{
|
{
|
||||||
let (block, _) = {
|
let (block, _) = {
|
||||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
@ -133,7 +133,7 @@ fn add_menu(
|
|||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
let span = menu.menu_type.span();
|
let span = menu.menu_type.span();
|
||||||
if let Value::Record { val, .. } = &menu.menu_type {
|
if let Value::Record { val, .. } = &menu.menu_type {
|
||||||
let layout = extract_value("layout", val, span)?.into_string("", config);
|
let layout = extract_value("layout", val, span)?.to_expanded_string("", config);
|
||||||
|
|
||||||
match layout.as_str() {
|
match layout.as_str() {
|
||||||
"columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config),
|
"columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config),
|
||||||
@ -142,14 +142,14 @@ fn add_menu(
|
|||||||
"description" => add_description_menu(line_editor, menu, engine_state, stack, config),
|
"description" => add_description_menu(line_editor, menu, engine_state, stack, config),
|
||||||
_ => Err(ShellError::UnsupportedConfigValue {
|
_ => Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "columnar, list, ide or description".to_string(),
|
expected: "columnar, list, ide or description".to_string(),
|
||||||
value: menu.menu_type.into_abbreviated_string(config),
|
value: menu.menu_type.to_abbreviated_string(config),
|
||||||
span: menu.menu_type.span(),
|
span: menu.menu_type.span(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::UnsupportedConfigValue {
|
Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "only record type".to_string(),
|
expected: "only record type".to_string(),
|
||||||
value: menu.menu_type.into_abbreviated_string(config),
|
value: menu.menu_type.to_abbreviated_string(config),
|
||||||
span: menu.menu_type.span(),
|
span: menu.menu_type.span(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -181,7 +181,7 @@ pub(crate) fn add_columnar_menu(
|
|||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
let span = menu.menu_type.span();
|
let span = menu.menu_type.span();
|
||||||
let name = menu.name.into_string("", config);
|
let name = menu.name.to_expanded_string("", config);
|
||||||
let mut columnar_menu = ColumnarMenu::default().with_name(&name);
|
let mut columnar_menu = ColumnarMenu::default().with_name(&name);
|
||||||
|
|
||||||
if let Value::Record { val, .. } = &menu.menu_type {
|
if let Value::Record { val, .. } = &menu.menu_type {
|
||||||
@ -254,7 +254,7 @@ pub(crate) fn add_columnar_menu(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let marker = menu.marker.into_string("", config);
|
let marker = menu.marker.to_expanded_string("", config);
|
||||||
columnar_menu = columnar_menu.with_marker(&marker);
|
columnar_menu = columnar_menu.with_marker(&marker);
|
||||||
|
|
||||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
@ -280,7 +280,7 @@ pub(crate) fn add_columnar_menu(
|
|||||||
}
|
}
|
||||||
_ => Err(ShellError::UnsupportedConfigValue {
|
_ => Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "block or omitted value".to_string(),
|
expected: "block or omitted value".to_string(),
|
||||||
value: menu.source.into_abbreviated_string(config),
|
value: menu.source.to_abbreviated_string(config),
|
||||||
span,
|
span,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -294,7 +294,7 @@ pub(crate) fn add_list_menu(
|
|||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
let name = menu.name.into_string("", config);
|
let name = menu.name.to_expanded_string("", config);
|
||||||
let mut list_menu = ListMenu::default().with_name(&name);
|
let mut list_menu = ListMenu::default().with_name(&name);
|
||||||
|
|
||||||
let span = menu.menu_type.span();
|
let span = menu.menu_type.span();
|
||||||
@ -336,7 +336,7 @@ pub(crate) fn add_list_menu(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let marker = menu.marker.into_string("", config);
|
let marker = menu.marker.to_expanded_string("", config);
|
||||||
list_menu = list_menu.with_marker(&marker);
|
list_menu = list_menu.with_marker(&marker);
|
||||||
|
|
||||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
@ -362,7 +362,7 @@ pub(crate) fn add_list_menu(
|
|||||||
}
|
}
|
||||||
_ => Err(ShellError::UnsupportedConfigValue {
|
_ => Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "block or omitted value".to_string(),
|
expected: "block or omitted value".to_string(),
|
||||||
value: menu.source.into_abbreviated_string(config),
|
value: menu.source.to_abbreviated_string(config),
|
||||||
span: menu.source.span(),
|
span: menu.source.span(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -377,7 +377,7 @@ pub(crate) fn add_ide_menu(
|
|||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
let span = menu.menu_type.span();
|
let span = menu.menu_type.span();
|
||||||
let name = menu.name.into_string("", config);
|
let name = menu.name.to_expanded_string("", config);
|
||||||
let mut ide_menu = IdeMenu::default().with_name(&name);
|
let mut ide_menu = IdeMenu::default().with_name(&name);
|
||||||
|
|
||||||
if let Value::Record { val, .. } = &menu.menu_type {
|
if let Value::Record { val, .. } = &menu.menu_type {
|
||||||
@ -402,7 +402,7 @@ pub(crate) fn add_ide_menu(
|
|||||||
let max_completion_height = max_completion_height.as_int()?;
|
let max_completion_height = max_completion_height.as_int()?;
|
||||||
ide_menu.with_max_completion_height(max_completion_height as u16)
|
ide_menu.with_max_completion_height(max_completion_height as u16)
|
||||||
}
|
}
|
||||||
Err(_) => ide_menu,
|
Err(_) => ide_menu.with_max_completion_height(10u16),
|
||||||
};
|
};
|
||||||
|
|
||||||
ide_menu = match extract_value("padding", val, span) {
|
ide_menu = match extract_value("padding", val, span) {
|
||||||
@ -442,12 +442,12 @@ pub(crate) fn add_ide_menu(
|
|||||||
} else {
|
} else {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "bool or record".to_string(),
|
expected: "bool or record".to_string(),
|
||||||
value: border.into_abbreviated_string(config),
|
value: border.to_abbreviated_string(config),
|
||||||
span: border.span(),
|
span: border.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => ide_menu,
|
Err(_) => ide_menu.with_default_border(),
|
||||||
};
|
};
|
||||||
|
|
||||||
ide_menu = match extract_value("cursor_offset", val, span) {
|
ide_menu = match extract_value("cursor_offset", val, span) {
|
||||||
@ -459,21 +459,18 @@ pub(crate) fn add_ide_menu(
|
|||||||
};
|
};
|
||||||
|
|
||||||
ide_menu = match extract_value("description_mode", val, span) {
|
ide_menu = match extract_value("description_mode", val, span) {
|
||||||
Ok(description_mode) => {
|
Ok(description_mode) => match description_mode.coerce_str()?.as_ref() {
|
||||||
let description_mode_str = description_mode.as_string()?;
|
"left" => ide_menu.with_description_mode(DescriptionMode::Left),
|
||||||
match description_mode_str.as_str() {
|
"right" => ide_menu.with_description_mode(DescriptionMode::Right),
|
||||||
"left" => ide_menu.with_description_mode(DescriptionMode::Left),
|
"prefer_right" => ide_menu.with_description_mode(DescriptionMode::PreferRight),
|
||||||
"right" => ide_menu.with_description_mode(DescriptionMode::Right),
|
_ => {
|
||||||
"prefer_right" => ide_menu.with_description_mode(DescriptionMode::PreferRight),
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
_ => {
|
expected: "\"left\", \"right\" or \"prefer_right\"".to_string(),
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
value: description_mode.to_abbreviated_string(config),
|
||||||
expected: "\"left\", \"right\" or \"prefer_right\"".to_string(),
|
span: description_mode.span(),
|
||||||
value: description_mode.into_abbreviated_string(config),
|
});
|
||||||
span: description_mode.span(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
Err(_) => ide_menu,
|
Err(_) => ide_menu,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -562,7 +559,7 @@ pub(crate) fn add_ide_menu(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let marker = menu.marker.into_string("", config);
|
let marker = menu.marker.to_expanded_string("", config);
|
||||||
ide_menu = ide_menu.with_marker(&marker);
|
ide_menu = ide_menu.with_marker(&marker);
|
||||||
|
|
||||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
@ -588,7 +585,7 @@ pub(crate) fn add_ide_menu(
|
|||||||
}
|
}
|
||||||
_ => Err(ShellError::UnsupportedConfigValue {
|
_ => Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "block or omitted value".to_string(),
|
expected: "block or omitted value".to_string(),
|
||||||
value: menu.source.into_abbreviated_string(config),
|
value: menu.source.to_abbreviated_string(config),
|
||||||
span,
|
span,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -602,7 +599,7 @@ pub(crate) fn add_description_menu(
|
|||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
let name = menu.name.into_string("", config);
|
let name = menu.name.to_expanded_string("", config);
|
||||||
let mut description_menu = DescriptionMenu::default().with_name(&name);
|
let mut description_menu = DescriptionMenu::default().with_name(&name);
|
||||||
|
|
||||||
let span = menu.menu_type.span();
|
let span = menu.menu_type.span();
|
||||||
@ -676,7 +673,7 @@ pub(crate) fn add_description_menu(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let marker = menu.marker.into_string("", config);
|
let marker = menu.marker.to_expanded_string("", config);
|
||||||
description_menu = description_menu.with_marker(&marker);
|
description_menu = description_menu.with_marker(&marker);
|
||||||
|
|
||||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
@ -706,7 +703,7 @@ pub(crate) fn add_description_menu(
|
|||||||
}
|
}
|
||||||
_ => Err(ShellError::UnsupportedConfigValue {
|
_ => Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "closure or omitted value".to_string(),
|
expected: "closure or omitted value".to_string(),
|
||||||
value: menu.source.into_abbreviated_string(config),
|
value: menu.source.to_abbreviated_string(config),
|
||||||
span: menu.source.span(),
|
span: menu.source.span(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -845,7 +842,7 @@ fn add_keybinding(
|
|||||||
}
|
}
|
||||||
v => Err(ShellError::UnsupportedConfigValue {
|
v => Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "string or list of strings".to_string(),
|
expected: "string or list of strings".to_string(),
|
||||||
value: v.into_abbreviated_string(config),
|
value: v.to_abbreviated_string(config),
|
||||||
span: v.span(),
|
span: v.span(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -858,7 +855,7 @@ fn add_parsed_keybinding(
|
|||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let modifier = match keybinding
|
let modifier = match keybinding
|
||||||
.modifier
|
.modifier
|
||||||
.into_string("", config)
|
.to_expanded_string("", config)
|
||||||
.to_ascii_lowercase()
|
.to_ascii_lowercase()
|
||||||
.as_str()
|
.as_str()
|
||||||
{
|
{
|
||||||
@ -875,7 +872,7 @@ fn add_parsed_keybinding(
|
|||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "CONTROL, SHIFT, ALT or NONE".to_string(),
|
expected: "CONTROL, SHIFT, ALT or NONE".to_string(),
|
||||||
value: keybinding.modifier.into_abbreviated_string(config),
|
value: keybinding.modifier.to_abbreviated_string(config),
|
||||||
span: keybinding.modifier.span(),
|
span: keybinding.modifier.span(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -883,7 +880,7 @@ fn add_parsed_keybinding(
|
|||||||
|
|
||||||
let keycode = match keybinding
|
let keycode = match keybinding
|
||||||
.keycode
|
.keycode
|
||||||
.into_string("", config)
|
.to_expanded_string("", config)
|
||||||
.to_ascii_lowercase()
|
.to_ascii_lowercase()
|
||||||
.as_str()
|
.as_str()
|
||||||
{
|
{
|
||||||
@ -936,7 +933,7 @@ fn add_parsed_keybinding(
|
|||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "crossterm KeyCode".to_string(),
|
expected: "crossterm KeyCode".to_string(),
|
||||||
value: keybinding.keycode.into_abbreviated_string(config),
|
value: keybinding.keycode.to_abbreviated_string(config),
|
||||||
span: keybinding.keycode.span(),
|
span: keybinding.keycode.span(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -974,7 +971,10 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
match value {
|
match value {
|
||||||
Value::Record { val: record, .. } => match EventType::try_from_record(record, span)? {
|
Value::Record { val: record, .. } => match EventType::try_from_record(record, span)? {
|
||||||
EventType::Send(value) => event_from_record(
|
EventType::Send(value) => event_from_record(
|
||||||
value.into_string("", config).to_ascii_lowercase().as_str(),
|
value
|
||||||
|
.to_expanded_string("", config)
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
.as_str(),
|
||||||
record,
|
record,
|
||||||
config,
|
config,
|
||||||
span,
|
span,
|
||||||
@ -982,7 +982,10 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
.map(Some),
|
.map(Some),
|
||||||
EventType::Edit(value) => {
|
EventType::Edit(value) => {
|
||||||
let edit = edit_from_record(
|
let edit = edit_from_record(
|
||||||
value.into_string("", config).to_ascii_lowercase().as_str(),
|
value
|
||||||
|
.to_expanded_string("", config)
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
.as_str(),
|
||||||
record,
|
record,
|
||||||
config,
|
config,
|
||||||
span,
|
span,
|
||||||
@ -1010,7 +1013,7 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
}
|
}
|
||||||
v => Err(ShellError::UnsupportedConfigValue {
|
v => Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "list of events".to_string(),
|
expected: "list of events".to_string(),
|
||||||
value: v.into_abbreviated_string(config),
|
value: v.to_abbreviated_string(config),
|
||||||
span: v.span(),
|
span: v.span(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -1036,7 +1039,7 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
Value::Nothing { .. } => Ok(None),
|
Value::Nothing { .. } => Ok(None),
|
||||||
v => Err(ShellError::UnsupportedConfigValue {
|
v => Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "record or list of records, null to unbind key".to_string(),
|
expected: "record or list of records, null to unbind key".to_string(),
|
||||||
value: v.into_abbreviated_string(config),
|
value: v.to_abbreviated_string(config),
|
||||||
span: v.span(),
|
span: v.span(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -1079,11 +1082,11 @@ fn event_from_record(
|
|||||||
"openeditor" => ReedlineEvent::OpenEditor,
|
"openeditor" => ReedlineEvent::OpenEditor,
|
||||||
"menu" => {
|
"menu" => {
|
||||||
let menu = extract_value("name", record, span)?;
|
let menu = extract_value("name", record, span)?;
|
||||||
ReedlineEvent::Menu(menu.into_string("", config))
|
ReedlineEvent::Menu(menu.to_expanded_string("", config))
|
||||||
}
|
}
|
||||||
"executehostcommand" => {
|
"executehostcommand" => {
|
||||||
let cmd = extract_value("cmd", record, span)?;
|
let cmd = extract_value("cmd", record, span)?;
|
||||||
ReedlineEvent::ExecuteHostCommand(cmd.into_string("", config))
|
ReedlineEvent::ExecuteHostCommand(cmd.to_expanded_string("", config))
|
||||||
}
|
}
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
@ -1188,7 +1191,7 @@ fn edit_from_record(
|
|||||||
}
|
}
|
||||||
"insertstring" => {
|
"insertstring" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
EditCommand::InsertString(value.into_string("", config))
|
EditCommand::InsertString(value.to_expanded_string("", config))
|
||||||
}
|
}
|
||||||
"insertnewline" => EditCommand::InsertNewline,
|
"insertnewline" => EditCommand::InsertNewline,
|
||||||
"backspace" => EditCommand::Backspace,
|
"backspace" => EditCommand::Backspace,
|
||||||
@ -1289,7 +1292,7 @@ fn edit_from_record(
|
|||||||
fn extract_char(value: &Value, config: &Config) -> Result<char, ShellError> {
|
fn extract_char(value: &Value, config: &Config) -> Result<char, ShellError> {
|
||||||
let span = value.span();
|
let span = value.span();
|
||||||
value
|
value
|
||||||
.into_string("", config)
|
.to_expanded_string("", config)
|
||||||
.chars()
|
.chars()
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| ShellError::MissingConfigValue {
|
.ok_or_else(|| ShellError::MissingConfigValue {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -264,6 +264,8 @@ fn find_matching_block_end_in_block(
|
|||||||
for e in &p.elements {
|
for e in &p.elements {
|
||||||
match e {
|
match e {
|
||||||
PipelineElement::Expression(_, e)
|
PipelineElement::Expression(_, e)
|
||||||
|
| PipelineElement::ErrPipedExpression(_, e)
|
||||||
|
| PipelineElement::OutErrPipedExpression(_, e)
|
||||||
| PipelineElement::Redirection(_, _, e, _)
|
| PipelineElement::Redirection(_, _, e, _)
|
||||||
| PipelineElement::And(_, e)
|
| PipelineElement::And(_, e)
|
||||||
| PipelineElement::Or(_, e)
|
| PipelineElement::Or(_, e)
|
||||||
|
@ -247,6 +247,16 @@ fn file_completions() {
|
|||||||
|
|
||||||
// Match the results
|
// Match the results
|
||||||
match_suggestions(expected_paths, suggestions);
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
|
||||||
|
// Test completions for hidden files
|
||||||
|
let target_dir = format!("ls {}/.", folder(dir.join(".hidden_folder")));
|
||||||
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
|
let expected_paths: Vec<String> =
|
||||||
|
vec![file(dir.join(".hidden_folder").join(".hidden_subfile"))];
|
||||||
|
|
||||||
|
// Match the results
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -584,6 +594,7 @@ fn file_completion_quoted() {
|
|||||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"\'[a] bc.txt\'".to_string(),
|
||||||
"`--help`".to_string(),
|
"`--help`".to_string(),
|
||||||
"`-42`".to_string(),
|
"`-42`".to_string(),
|
||||||
"`-inf`".to_string(),
|
"`-inf`".to_string(),
|
||||||
|
@ -5,21 +5,17 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-base"
|
name = "nu-cmd-base"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
||||||
version = "0.90.1"
|
version = "0.91.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.90.1" }
|
nu-engine = { path = "../nu-engine", version = "0.91.0" }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.90.1" }
|
nu-parser = { path = "../nu-parser", version = "0.91.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.90.1" }
|
nu-path = { path = "../nu-path", version = "0.91.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.90.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.91.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.90.1" }
|
|
||||||
nu-utils = { path = "../nu-utils", version = "0.90.1" }
|
|
||||||
|
|
||||||
indexmap = "2.1"
|
indexmap = "2.2"
|
||||||
miette = "5.10.0"
|
miette = "7.1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.90.1" }
|
|
||||||
rstest = "0.18.2"
|
|
||||||
|
@ -150,7 +150,7 @@ pub fn eval_hook(
|
|||||||
// If it returns true (the default if a condition block is not specified), the hook should be run.
|
// If it returns true (the default if a condition block is not specified), the hook should be run.
|
||||||
let do_run_hook = if let Some(condition) = val.get("condition") {
|
let do_run_hook = if let Some(condition) = val.get("condition") {
|
||||||
let other_span = condition.span();
|
let other_span = condition.span();
|
||||||
if let Ok(block_id) = condition.as_block() {
|
if let Ok(block_id) = condition.coerce_block() {
|
||||||
match run_hook_block(
|
match run_hook_block(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
|
@ -66,7 +66,7 @@ fn get_editor_commandline(
|
|||||||
match value {
|
match value {
|
||||||
Value::String { val, .. } if !val.is_empty() => Ok((val.to_string(), Vec::new())),
|
Value::String { val, .. } if !val.is_empty() => Ok((val.to_string(), Vec::new())),
|
||||||
Value::List { vals, .. } if !vals.is_empty() => {
|
Value::List { vals, .. } if !vals.is_empty() => {
|
||||||
let mut editor_cmd = vals.iter().map(|l| l.as_string());
|
let mut editor_cmd = vals.iter().map(|l| l.coerce_string());
|
||||||
match editor_cmd.next().transpose()? {
|
match editor_cmd.next().transpose()? {
|
||||||
Some(editor) if !editor.is_empty() => {
|
Some(editor) if !editor.is_empty() => {
|
||||||
let params = editor_cmd.collect::<Result<_, ShellError>>()?;
|
let params = editor_cmd.collect::<Result<_, ShellError>>()?;
|
||||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-dataframe"
|
name = "nu-cmd-dataframe"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-dataframe"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-dataframe"
|
||||||
version = "0.90.1"
|
version = "0.91.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -13,23 +13,23 @@ version = "0.90.1"
|
|||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.90.1" }
|
nu-engine = { path = "../nu-engine", version = "0.91.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.90.1" }
|
nu-parser = { path = "../nu-parser", version = "0.91.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.90.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.91.0" }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false }
|
chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false }
|
||||||
chrono-tz = "0.8"
|
chrono-tz = "0.8"
|
||||||
fancy-regex = "0.12"
|
fancy-regex = "0.13"
|
||||||
indexmap = { version = "2.1" }
|
indexmap = { version = "2.2" }
|
||||||
num = { version = "0.4", optional = true }
|
num = { version = "0.4", optional = true }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
sqlparser = { version = "0.43", optional = true }
|
sqlparser = { version = "0.43", optional = true }
|
||||||
polars-io = { version = "0.36", features = ["avro"], optional = true }
|
polars-io = { version = "0.37", features = ["avro"], optional = true }
|
||||||
polars-arrow = { version = "0.36", optional = true }
|
polars-arrow = { version = "0.37", optional = true }
|
||||||
polars-ops = { version = "0.36", optional = true }
|
polars-ops = { version = "0.37", optional = true }
|
||||||
polars-plan = { version = "0.36", optional = true }
|
polars-plan = { version = "0.37", features = ["regex"], optional = true }
|
||||||
polars-utils = { version = "0.36", optional = true }
|
polars-utils = { version = "0.37", optional = true }
|
||||||
|
|
||||||
[dependencies.polars]
|
[dependencies.polars]
|
||||||
features = [
|
features = [
|
||||||
@ -63,12 +63,11 @@ features = [
|
|||||||
"to_dummies",
|
"to_dummies",
|
||||||
]
|
]
|
||||||
optional = true
|
optional = true
|
||||||
version = "0.36"
|
version = "0.37"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
dataframe = ["num", "polars", "polars-io", "polars-arrow", "polars-ops", "polars-plan", "polars-utils", "sqlparser"]
|
dataframe = ["num", "polars", "polars-io", "polars-arrow", "polars-ops", "polars-plan", "polars-utils", "sqlparser"]
|
||||||
default = []
|
default = []
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.90.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.91.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.90.1" }
|
|
||||||
|
201
crates/nu-cmd-dataframe/src/dataframe/eager/cast.rs
Normal file
201
crates/nu-cmd-dataframe/src/dataframe/eager/cast.rs
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
use crate::dataframe::values::{str_to_dtype, NuExpression, NuLazyFrame};
|
||||||
|
|
||||||
|
use super::super::values::NuDataFrame;
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
record, Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
use polars::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CastDF;
|
||||||
|
|
||||||
|
impl Command for CastDF {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"dfr cast"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Cast a column to a different dtype."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.input_output_types(vec![
|
||||||
|
(
|
||||||
|
Type::Custom("expression".into()),
|
||||||
|
Type::Custom("expression".into()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Type::Custom("dataframe".into()),
|
||||||
|
Type::Custom("dataframe".into()),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.required(
|
||||||
|
"dtype",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"The dtype to cast the column to",
|
||||||
|
)
|
||||||
|
.optional(
|
||||||
|
"column",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"The column to cast. Required when used with a dataframe.",
|
||||||
|
)
|
||||||
|
.category(Category::Custom("dataframe".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Cast a column in a dataframe to a different dtype",
|
||||||
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr cast u8 a | dfr schema",
|
||||||
|
result: Some(Value::record(
|
||||||
|
record! {
|
||||||
|
"a" => Value::string("u8", Span::test_data()),
|
||||||
|
"b" => Value::string("i64", Span::test_data()),
|
||||||
|
},
|
||||||
|
Span::test_data(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Cast a column in a lazy dataframe to a different dtype",
|
||||||
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr into-lazy | dfr cast u8 a | dfr schema",
|
||||||
|
result: Some(Value::record(
|
||||||
|
record! {
|
||||||
|
"a" => Value::string("u8", Span::test_data()),
|
||||||
|
"b" => Value::string("i64", Span::test_data()),
|
||||||
|
},
|
||||||
|
Span::test_data(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Cast a column in a expression to a different dtype",
|
||||||
|
example: r#"[[a b]; [1 2] [1 4]] | dfr into-df | dfr group-by a | dfr agg [ (dfr col b | dfr cast u8 | dfr min | dfr as "b_min") ] | dfr schema"#,
|
||||||
|
result: None
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let value = input.into_value(call.head);
|
||||||
|
if NuLazyFrame::can_downcast(&value) {
|
||||||
|
let (dtype, column_nm) = df_args(engine_state, stack, call)?;
|
||||||
|
let df = NuLazyFrame::try_from_value(value)?;
|
||||||
|
command_lazy(call, column_nm, dtype, df)
|
||||||
|
} else if NuDataFrame::can_downcast(&value) {
|
||||||
|
let (dtype, column_nm) = df_args(engine_state, stack, call)?;
|
||||||
|
let df = NuDataFrame::try_from_value(value)?;
|
||||||
|
command_eager(call, column_nm, dtype, df)
|
||||||
|
} else {
|
||||||
|
let dtype: String = call.req(engine_state, stack, 0)?;
|
||||||
|
let dtype = str_to_dtype(&dtype, call.head)?;
|
||||||
|
|
||||||
|
let expr = NuExpression::try_from_value(value)?;
|
||||||
|
let expr: NuExpression = expr.into_polars().cast(dtype).into();
|
||||||
|
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
NuExpression::into_value(expr, call.head),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn df_args(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<(DataType, String), ShellError> {
|
||||||
|
let dtype = dtype_arg(engine_state, stack, call)?;
|
||||||
|
let column_nm: String =
|
||||||
|
call.opt(engine_state, stack, 1)?
|
||||||
|
.ok_or(ShellError::MissingParameter {
|
||||||
|
param_name: "column_name".into(),
|
||||||
|
span: call.head,
|
||||||
|
})?;
|
||||||
|
Ok((dtype, column_nm))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dtype_arg(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<DataType, ShellError> {
|
||||||
|
let dtype: String = call.req(engine_state, stack, 0)?;
|
||||||
|
str_to_dtype(&dtype, call.head)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_lazy(
|
||||||
|
call: &Call,
|
||||||
|
column_nm: String,
|
||||||
|
dtype: DataType,
|
||||||
|
lazy: NuLazyFrame,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let column = col(&column_nm).cast(dtype);
|
||||||
|
let lazy = lazy.into_polars().with_columns(&[column]);
|
||||||
|
let lazy = NuLazyFrame::new(false, lazy);
|
||||||
|
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
NuLazyFrame::into_value(lazy, call.head)?,
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_eager(
|
||||||
|
call: &Call,
|
||||||
|
column_nm: String,
|
||||||
|
dtype: DataType,
|
||||||
|
nu_df: NuDataFrame,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let mut df = nu_df.df;
|
||||||
|
let column = df
|
||||||
|
.column(&column_nm)
|
||||||
|
.map_err(|e| ShellError::GenericError {
|
||||||
|
error: format!("{e}"),
|
||||||
|
msg: "".into(),
|
||||||
|
span: Some(call.head),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let casted = column.cast(&dtype).map_err(|e| ShellError::GenericError {
|
||||||
|
error: format!("{e}"),
|
||||||
|
msg: "".into(),
|
||||||
|
span: Some(call.head),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let _ = df
|
||||||
|
.with_column(casted)
|
||||||
|
.map_err(|e| ShellError::GenericError {
|
||||||
|
error: format!("{e}"),
|
||||||
|
msg: "".into(),
|
||||||
|
span: Some(call.head),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let df = NuDataFrame::new(false, df);
|
||||||
|
Ok(PipelineData::Value(df.into_value(call.head), None))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
|
||||||
|
use super::super::super::test_dataframe::test_dataframe;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
test_dataframe(vec![Box::new(CastDF {})])
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
mod append;
|
mod append;
|
||||||
|
mod cast;
|
||||||
mod columns;
|
mod columns;
|
||||||
mod drop;
|
mod drop;
|
||||||
mod drop_duplicates;
|
mod drop_duplicates;
|
||||||
@ -35,6 +36,7 @@ use nu_protocol::engine::StateWorkingSet;
|
|||||||
|
|
||||||
pub use self::open::OpenDataFrame;
|
pub use self::open::OpenDataFrame;
|
||||||
pub use append::AppendDF;
|
pub use append::AppendDF;
|
||||||
|
pub use cast::CastDF;
|
||||||
pub use columns::ColumnsDF;
|
pub use columns::ColumnsDF;
|
||||||
pub use drop::DropDF;
|
pub use drop::DropDF;
|
||||||
pub use drop_duplicates::DropDuplicates;
|
pub use drop_duplicates::DropDuplicates;
|
||||||
@ -78,6 +80,7 @@ pub fn add_eager_decls(working_set: &mut StateWorkingSet) {
|
|||||||
// Dataframe commands
|
// Dataframe commands
|
||||||
bind_command!(
|
bind_command!(
|
||||||
AppendDF,
|
AppendDF,
|
||||||
|
CastDF,
|
||||||
ColumnsDF,
|
ColumnsDF,
|
||||||
DataTypes,
|
DataTypes,
|
||||||
Summary,
|
Summary,
|
||||||
|
@ -154,7 +154,7 @@ fn from_parquet(
|
|||||||
cache: true,
|
cache: true,
|
||||||
parallel: ParallelStrategy::Auto,
|
parallel: ParallelStrategy::Auto,
|
||||||
rechunk: false,
|
rechunk: false,
|
||||||
row_count: None,
|
row_index: None,
|
||||||
low_memory: false,
|
low_memory: false,
|
||||||
cloud_options: None,
|
cloud_options: None,
|
||||||
use_statistics: false,
|
use_statistics: false,
|
||||||
@ -252,7 +252,7 @@ fn from_ipc(
|
|||||||
n_rows: None,
|
n_rows: None,
|
||||||
cache: true,
|
cache: true,
|
||||||
rechunk: false,
|
rechunk: false,
|
||||||
row_count: None,
|
row_index: None,
|
||||||
memmap: true,
|
memmap: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, Record, ShellError, Signature, Span, Type, Value,
|
record, Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -33,13 +33,10 @@ impl Command for SchemaDF {
|
|||||||
description: "Dataframe schema",
|
description: "Dataframe schema",
|
||||||
example: r#"[[a b]; [1 "foo"] [3 "bar"]] | dfr into-df | dfr schema"#,
|
example: r#"[[a b]; [1 "foo"] [3 "bar"]] | dfr into-df | dfr schema"#,
|
||||||
result: Some(Value::record(
|
result: Some(Value::record(
|
||||||
Record::from_raw_cols_vals_unchecked(
|
record! {
|
||||||
vec!["a".to_string(), "b".to_string()],
|
"a" => Value::string("i64", Span::test_data()),
|
||||||
vec![
|
"b" => Value::string("str", Span::test_data()),
|
||||||
Value::string("i64", Span::test_data()),
|
},
|
||||||
Value::string("str", Span::test_data()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)),
|
)),
|
||||||
}]
|
}]
|
||||||
@ -98,10 +95,11 @@ fn datatype_list(span: Span) -> Value {
|
|||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(dtype, note)| {
|
.map(|(dtype, note)| {
|
||||||
Value::record(Record::from_raw_cols_vals_unchecked(
|
Value::record(record! {
|
||||||
vec!["dtype".to_string(), "note".to_string()],
|
"dtype" => Value::string(*dtype, span),
|
||||||
vec![Value::string(*dtype, span), Value::string(*note, span)],
|
"note" => Value::string(*note, span),
|
||||||
),span)
|
},
|
||||||
|
span)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Value::list(types, span)
|
Value::list(types, span)
|
||||||
|
@ -148,6 +148,18 @@ impl Command for ToDataFrame {
|
|||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert to a dataframe and provide a schema that adds a new column",
|
||||||
|
example: r#"[[a b]; [1 "foo"] [2 "bar"]] | dfr into-df -s {a: u8, b:str, c:i64} | dfr fill-null 3"#,
|
||||||
|
result: Some(NuDataFrame::try_from_series(vec![
|
||||||
|
Series::new("a", [1u8, 2]),
|
||||||
|
Series::new("b", ["foo", "bar"]),
|
||||||
|
Series::new("c", [3i64, 3]),
|
||||||
|
], Span::test_data())
|
||||||
|
.expect("simple df for test should not fail")
|
||||||
|
.into_value(Span::test_data()),
|
||||||
|
),
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,8 +175,12 @@ impl Command for ToDataFrame {
|
|||||||
.map(|schema| NuSchema::try_from(&schema))
|
.map(|schema| NuSchema::try_from(&schema))
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
NuDataFrame::try_from_iter(input.into_iter(), maybe_schema)
|
let df = NuDataFrame::try_from_iter(input.into_iter(), maybe_schema.clone())?;
|
||||||
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
|
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
NuDataFrame::into_value(df, call.head),
|
||||||
|
None,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ impl Command for ExprConcatStr {
|
|||||||
let value: Value = call.req(engine_state, stack, 1)?;
|
let value: Value = call.req(engine_state, stack, 1)?;
|
||||||
|
|
||||||
let expressions = NuExpression::extract_exprs(value)?;
|
let expressions = NuExpression::extract_exprs(value)?;
|
||||||
let expr: NuExpression = concat_str(expressions, &separator).into();
|
let expr: NuExpression = concat_str(expressions, &separator, false).into();
|
||||||
|
|
||||||
Ok(PipelineData::Value(expr.into_value(call.head), None))
|
Ok(PipelineData::Value(expr.into_value(call.head), None))
|
||||||
}
|
}
|
||||||
|
@ -193,7 +193,7 @@ fn get_col_name(expr: &Expr) -> Option<String> {
|
|||||||
| Expr::Window { .. }
|
| Expr::Window { .. }
|
||||||
| Expr::Wildcard
|
| Expr::Wildcard
|
||||||
| Expr::RenameAlias { .. }
|
| Expr::RenameAlias { .. }
|
||||||
| Expr::Count
|
| Expr::Len
|
||||||
| Expr::Nth(_)
|
| Expr::Nth(_)
|
||||||
| Expr::SubPlan(_, _)
|
| Expr::SubPlan(_, _)
|
||||||
| Expr::Selector(_) => None,
|
| Expr::Selector(_) => None,
|
||||||
|
@ -20,7 +20,7 @@ use crate::dataframe::lazy::aggregate::LazyAggregate;
|
|||||||
pub use crate::dataframe::lazy::collect::LazyCollect;
|
pub use crate::dataframe::lazy::collect::LazyCollect;
|
||||||
use crate::dataframe::lazy::fetch::LazyFetch;
|
use crate::dataframe::lazy::fetch::LazyFetch;
|
||||||
use crate::dataframe::lazy::fill_nan::LazyFillNA;
|
use crate::dataframe::lazy::fill_nan::LazyFillNA;
|
||||||
use crate::dataframe::lazy::fill_null::LazyFillNull;
|
pub use crate::dataframe::lazy::fill_null::LazyFillNull;
|
||||||
use crate::dataframe::lazy::filter::LazyFilter;
|
use crate::dataframe::lazy::filter::LazyFilter;
|
||||||
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
||||||
use crate::dataframe::lazy::join::LazyJoin;
|
use crate::dataframe::lazy::join::LazyJoin;
|
||||||
|
@ -6,7 +6,10 @@ use nu_protocol::{
|
|||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use polars::prelude::{IntoSeries, StringNameSpaceImpl};
|
use polars::{
|
||||||
|
prelude::{IntoSeries, NamedFrom, StringNameSpaceImpl},
|
||||||
|
series::Series,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct StrSlice;
|
pub struct StrSlice;
|
||||||
@ -32,25 +35,46 @@ impl Command for StrSlice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
description: "Creates slices from the strings",
|
Example {
|
||||||
example: "[abcded abc321 abc123] | dfr into-df | dfr str-slice 1 --length 2",
|
description: "Creates slices from the strings",
|
||||||
result: Some(
|
example: "[abcded abc321 abc123] | dfr into-df | dfr str-slice 1 --length 2",
|
||||||
NuDataFrame::try_from_columns(
|
result: Some(
|
||||||
vec![Column::new(
|
NuDataFrame::try_from_columns(
|
||||||
"0".to_string(),
|
vec![Column::new(
|
||||||
vec![
|
"0".to_string(),
|
||||||
Value::test_string("bc"),
|
vec![
|
||||||
Value::test_string("bc"),
|
Value::test_string("bc"),
|
||||||
Value::test_string("bc"),
|
Value::test_string("bc"),
|
||||||
],
|
Value::test_string("bc"),
|
||||||
)],
|
],
|
||||||
None,
|
)],
|
||||||
)
|
None,
|
||||||
.expect("simple df for test should not fail")
|
)
|
||||||
.into_value(Span::test_data()),
|
.expect("simple df for test should not fail")
|
||||||
),
|
.into_value(Span::test_data()),
|
||||||
}]
|
),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Creates slices from the strings without length",
|
||||||
|
example: "[abcded abc321 abc123] | dfr into-df | dfr str-slice 1",
|
||||||
|
result: Some(
|
||||||
|
NuDataFrame::try_from_columns(
|
||||||
|
vec![Column::new(
|
||||||
|
"0".to_string(),
|
||||||
|
vec![
|
||||||
|
Value::test_string("bcded"),
|
||||||
|
Value::test_string("bc321"),
|
||||||
|
Value::test_string("bc123"),
|
||||||
|
],
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("simple df for test should not fail")
|
||||||
|
.into_value(Span::test_data()),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -71,9 +95,13 @@ fn command(
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let start: i64 = call.req(engine_state, stack, 0)?;
|
let start: i64 = call.req(engine_state, stack, 0)?;
|
||||||
|
let start = Series::new("", &[start]);
|
||||||
|
|
||||||
let length: Option<i64> = call.get_flag(engine_state, stack, "length")?;
|
let length: Option<i64> = call.get_flag(engine_state, stack, "length")?;
|
||||||
let length = length.map(|v| v as u64);
|
let length = match length {
|
||||||
|
Some(v) => Series::new("", &[v as u64]),
|
||||||
|
None => Series::new_null("", 1),
|
||||||
|
};
|
||||||
|
|
||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
let series = df.as_series(call.head)?;
|
let series = df.as_series(call.head)?;
|
||||||
@ -86,8 +114,16 @@ fn command(
|
|||||||
inner: vec![],
|
inner: vec![],
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut res = chunked.str_slice(start, length);
|
let res = chunked
|
||||||
res.rename(series.name());
|
.str_slice(&start, &length)
|
||||||
|
.map_err(|e| ShellError::GenericError {
|
||||||
|
error: "Dataframe Error".into(),
|
||||||
|
msg: e.to_string(),
|
||||||
|
span: Some(call.head),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
})?
|
||||||
|
.with_name(series.name());
|
||||||
|
|
||||||
NuDataFrame::try_from_series(vec![res.into_series()], call.head)
|
NuDataFrame::try_from_series(vec![res.into_series()], call.head)
|
||||||
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
|
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
|
||||||
|
@ -5,8 +5,9 @@ use nu_protocol::{
|
|||||||
Example, PipelineData, Span,
|
Example, PipelineData, Span,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::eager::ToDataFrame;
|
use super::eager::{SchemaDF, ToDataFrame};
|
||||||
use super::expressions::ExprCol;
|
use super::expressions::ExprCol;
|
||||||
|
use super::lazy::LazyFillNull;
|
||||||
use super::lazy::{LazyCollect, ToLazyFrame};
|
use super::lazy::{LazyCollect, ToLazyFrame};
|
||||||
use nu_cmd_lang::Let;
|
use nu_cmd_lang::Let;
|
||||||
|
|
||||||
@ -36,6 +37,8 @@ pub fn build_test_engine_state(cmds: Vec<Box<dyn Command + 'static>>) -> Box<Eng
|
|||||||
working_set.add_decl(Box::new(ToLazyFrame));
|
working_set.add_decl(Box::new(ToLazyFrame));
|
||||||
working_set.add_decl(Box::new(LazyCollect));
|
working_set.add_decl(Box::new(LazyCollect));
|
||||||
working_set.add_decl(Box::new(ExprCol));
|
working_set.add_decl(Box::new(ExprCol));
|
||||||
|
working_set.add_decl(Box::new(SchemaDF));
|
||||||
|
working_set.add_decl(Box::new(LazyFillNull));
|
||||||
|
|
||||||
// Adding the command that is being tested to the working set
|
// Adding the command that is being tested to the working set
|
||||||
for cmd in cmds.clone() {
|
for cmd in cmds.clone() {
|
||||||
|
@ -10,5 +10,5 @@ pub use nu_dataframe::{Axis, Column, NuDataFrame};
|
|||||||
pub use nu_expression::NuExpression;
|
pub use nu_expression::NuExpression;
|
||||||
pub use nu_lazyframe::NuLazyFrame;
|
pub use nu_lazyframe::NuLazyFrame;
|
||||||
pub use nu_lazygroupby::NuLazyGroupBy;
|
pub use nu_lazygroupby::NuLazyGroupBy;
|
||||||
pub use nu_schema::NuSchema;
|
pub use nu_schema::{str_to_dtype, NuSchema};
|
||||||
pub use nu_when::NuWhen;
|
pub use nu_when::NuWhen;
|
||||||
|
@ -342,7 +342,7 @@ fn typed_column_to_series(name: &str, column: TypedColumn) -> Result<Series, She
|
|||||||
}
|
}
|
||||||
DataType::String => {
|
DataType::String => {
|
||||||
let series_values: Result<Vec<_>, _> =
|
let series_values: Result<Vec<_>, _> =
|
||||||
column.values.iter().map(|v| v.as_string()).collect();
|
column.values.iter().map(|v| v.coerce_string()).collect();
|
||||||
Ok(Series::new(name, series_values?))
|
Ok(Series::new(name, series_values?))
|
||||||
}
|
}
|
||||||
DataType::Object(_, _) => value_to_series(name, &column.values),
|
DataType::Object(_, _) => value_to_series(name, &column.values),
|
||||||
@ -561,7 +561,7 @@ fn input_type_list_to_series(
|
|||||||
let value_list = v
|
let value_list = v
|
||||||
.as_list()?
|
.as_list()?
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| v.as_string())
|
.map(|v| v.coerce_string())
|
||||||
.collect::<Result<Vec<String>, _>>()
|
.collect::<Result<Vec<String>, _>>()
|
||||||
.map_err(inconsistent_error)?;
|
.map_err(inconsistent_error)?;
|
||||||
builder.append_values_iter(value_list.iter().map(AsRef::as_ref));
|
builder.append_values_iter(value_list.iter().map(AsRef::as_ref));
|
||||||
@ -1033,15 +1033,14 @@ fn series_to_values(
|
|||||||
Either::Right(it)
|
Either::Right(it)
|
||||||
}
|
}
|
||||||
.map(|any_values| {
|
.map(|any_values| {
|
||||||
let vals: Result<Vec<Value>, ShellError> = any_values
|
let record = polar_fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| any_value_to_value(v, span))
|
.zip(any_values)
|
||||||
.collect();
|
.map(|(field, val)| {
|
||||||
let cols: Vec<String> = polar_fields
|
any_value_to_value(val, span).map(|val| (field.name.to_string(), val))
|
||||||
.iter()
|
})
|
||||||
.map(|field| field.name.to_string())
|
.collect::<Result<_, _>>()?;
|
||||||
.collect();
|
|
||||||
let record = Record::from_raw_cols_vals_unchecked(cols, vals?);
|
|
||||||
Ok(Value::record(record, span))
|
Ok(Value::record(record, span))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@ -1138,20 +1137,16 @@ fn any_value_to_value(any_value: &AnyValue, span: Span) -> Result<Value, ShellEr
|
|||||||
any_value_to_value(&static_value, span)
|
any_value_to_value(&static_value, span)
|
||||||
}
|
}
|
||||||
AnyValue::StructOwned(struct_tuple) => {
|
AnyValue::StructOwned(struct_tuple) => {
|
||||||
let values: Result<Vec<Value>, ShellError> = struct_tuple
|
let record = struct_tuple
|
||||||
.0
|
|
||||||
.iter()
|
|
||||||
.map(|s| any_value_to_value(s, span))
|
|
||||||
.collect();
|
|
||||||
let fields = struct_tuple
|
|
||||||
.1
|
.1
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f| f.name().to_string())
|
.zip(&struct_tuple.0)
|
||||||
.collect();
|
.map(|(field, val)| {
|
||||||
Ok(Value::Record {
|
any_value_to_value(val, span).map(|val| (field.name.to_string(), val))
|
||||||
val: Record::from_raw_cols_vals_unchecked(fields, values?),
|
})
|
||||||
internal_span: span,
|
.collect::<Result<_, _>>()?;
|
||||||
})
|
|
||||||
|
Ok(Value::record(record, span))
|
||||||
}
|
}
|
||||||
AnyValue::StringOwned(s) => Ok(Value::string(s.to_string(), span)),
|
AnyValue::StringOwned(s) => Ok(Value::string(s.to_string(), span)),
|
||||||
AnyValue::Binary(bytes) => Ok(Value::binary(*bytes, span)),
|
AnyValue::Binary(bytes) => Ok(Value::binary(*bytes, span)),
|
||||||
@ -1426,7 +1421,7 @@ mod tests {
|
|||||||
let test_int_arr = PrimitiveArray::from([Some(1_i32)]);
|
let test_int_arr = PrimitiveArray::from([Some(1_i32)]);
|
||||||
let test_bool_arr = BooleanArray::from([Some(true)]);
|
let test_bool_arr = BooleanArray::from([Some(true)]);
|
||||||
let test_struct_arr = StructArray::new(
|
let test_struct_arr = StructArray::new(
|
||||||
DataType::Struct(fields.clone()).to_arrow(),
|
DataType::Struct(fields.clone()).to_arrow(true),
|
||||||
vec![Box::new(test_int_arr), Box::new(test_bool_arr)],
|
vec![Box::new(test_int_arr), Box::new(test_bool_arr)],
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
@ -9,9 +9,10 @@ pub use operations::Axis;
|
|||||||
use indexmap::map::IndexMap;
|
use indexmap::map::IndexMap;
|
||||||
use nu_protocol::{did_you_mean, PipelineData, Record, ShellError, Span, Value};
|
use nu_protocol::{did_you_mean, PipelineData, Record, ShellError, Span, Value};
|
||||||
use polars::prelude::{DataFrame, DataType, IntoLazy, LazyFrame, PolarsObject, Series};
|
use polars::prelude::{DataFrame, DataType, IntoLazy, LazyFrame, PolarsObject, Series};
|
||||||
|
use polars_plan::prelude::{lit, Expr, Null};
|
||||||
use polars_utils::total_ord::TotalEq;
|
use polars_utils::total_ord::TotalEq;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{cmp::Ordering, fmt::Display, hash::Hasher};
|
use std::{cmp::Ordering, collections::HashSet, fmt::Display, hash::Hasher};
|
||||||
|
|
||||||
use super::{nu_schema::NuSchema, utils::DEFAULT_ROWS, NuLazyFrame};
|
use super::{nu_schema::NuSchema, utils::DEFAULT_ROWS, NuLazyFrame};
|
||||||
|
|
||||||
@ -154,15 +155,13 @@ impl NuDataFrame {
|
|||||||
match value {
|
match value {
|
||||||
Value::CustomValue { .. } => return Self::try_from_value(value),
|
Value::CustomValue { .. } => return Self::try_from_value(value),
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
let cols = (0..vals.len())
|
let record = vals
|
||||||
.map(|i| format!("{i}"))
|
.into_iter()
|
||||||
.collect::<Vec<String>>();
|
.enumerate()
|
||||||
|
.map(|(i, val)| (format!("{i}"), val))
|
||||||
|
.collect();
|
||||||
|
|
||||||
conversion::insert_record(
|
conversion::insert_record(&mut column_values, record, &maybe_schema)?
|
||||||
&mut column_values,
|
|
||||||
Record::from_raw_cols_vals_unchecked(cols, vals),
|
|
||||||
&maybe_schema,
|
|
||||||
)?
|
|
||||||
}
|
}
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
conversion::insert_record(&mut column_values, record, &maybe_schema)?
|
conversion::insert_record(&mut column_values, record, &maybe_schema)?
|
||||||
@ -174,7 +173,8 @@ impl NuDataFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conversion::from_parsed_columns(column_values)
|
let df = conversion::from_parsed_columns(column_values)?;
|
||||||
|
add_missing_columns(df, &maybe_schema, Span::unknown())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_from_series(columns: Vec<Series>, span: Span) -> Result<Self, ShellError> {
|
pub fn try_from_series(columns: Vec<Series>, span: Span) -> Result<Self, ShellError> {
|
||||||
@ -202,7 +202,8 @@ impl NuDataFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conversion::from_parsed_columns(column_values)
|
let df = conversion::from_parsed_columns(column_values)?;
|
||||||
|
add_missing_columns(df, &maybe_schema, Span::unknown())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fill_list_nan(list: Vec<Value>, list_span: Span, fill: Value) -> Value {
|
pub fn fill_list_nan(list: Vec<Value>, list_span: Span, fill: Value) -> Value {
|
||||||
@ -512,3 +513,44 @@ impl NuDataFrame {
|
|||||||
NuSchema::new(self.df.schema())
|
NuSchema::new(self.df.schema())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_missing_columns(
|
||||||
|
df: NuDataFrame,
|
||||||
|
maybe_schema: &Option<NuSchema>,
|
||||||
|
span: Span,
|
||||||
|
) -> Result<NuDataFrame, ShellError> {
|
||||||
|
// If there are fields that are in the schema, but not in the dataframe
|
||||||
|
// add them to the dataframe.
|
||||||
|
if let Some(schema) = maybe_schema {
|
||||||
|
let fields = df.df.fields();
|
||||||
|
let df_field_names: HashSet<&str> = fields.iter().map(|f| f.name().as_str()).collect();
|
||||||
|
|
||||||
|
let missing: Vec<(&str, &DataType)> = schema
|
||||||
|
.schema
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(name, dtype)| {
|
||||||
|
let name = name.as_str();
|
||||||
|
if !df_field_names.contains(name) {
|
||||||
|
Some((name, dtype))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let missing_exprs: Vec<Expr> = missing
|
||||||
|
.iter()
|
||||||
|
.map(|(name, dtype)| lit(Null {}).cast((*dtype).to_owned()).alias(name))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let df = if !missing.is_empty() {
|
||||||
|
let with_columns = df.lazy().with_columns(missing_exprs);
|
||||||
|
NuLazyFrame::new(true, with_columns).collect(span)?
|
||||||
|
} else {
|
||||||
|
df
|
||||||
|
};
|
||||||
|
Ok(df)
|
||||||
|
} else {
|
||||||
|
Ok(df)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -252,7 +252,7 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Result<Value, ShellError> {
|
|||||||
span,
|
span,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Expr::Count => Ok(Value::record(
|
Expr::Len => Ok(Value::record(
|
||||||
record! { "expr" => Value::string("count", span) },
|
record! { "expr" => Value::string("count", span) },
|
||||||
span,
|
span,
|
||||||
)),
|
)),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use nu_protocol::{Record, ShellError, Span, Value};
|
use nu_protocol::{ShellError, Span, Value};
|
||||||
use polars::prelude::{DataType, Field, Schema, SchemaRef, TimeUnit};
|
use polars::prelude::{DataType, Field, Schema, SchemaRef, TimeUnit};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -37,15 +37,14 @@ impl From<NuSchema> for SchemaRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fields_to_value(fields: impl Iterator<Item = Field>, span: Span) -> Value {
|
fn fields_to_value(fields: impl Iterator<Item = Field>, span: Span) -> Value {
|
||||||
let (cols, vals) = fields
|
let record = fields
|
||||||
.map(|field| {
|
.map(|field| {
|
||||||
let val = dtype_to_value(field.data_type(), span);
|
|
||||||
let col = field.name().to_string();
|
let col = field.name().to_string();
|
||||||
|
let val = dtype_to_value(field.data_type(), span);
|
||||||
(col, val)
|
(col, val)
|
||||||
})
|
})
|
||||||
.unzip();
|
.collect();
|
||||||
|
|
||||||
let record = Record::from_raw_cols_vals_unchecked(cols, vals);
|
|
||||||
Value::record(record, Span::unknown())
|
Value::record(record, Span::unknown())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +72,7 @@ fn value_to_fields(value: &Value, span: Span) -> Result<Vec<Field>, ShellError>
|
|||||||
Ok(Field::new(col, dtype))
|
Ok(Field::new(col, dtype))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let dtype = dtype_str_to_schema(&val.as_string()?, span)?;
|
let dtype = str_to_dtype(&val.coerce_string()?, span)?;
|
||||||
Ok(Field::new(col, dtype))
|
Ok(Field::new(col, dtype))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -81,7 +80,7 @@ fn value_to_fields(value: &Value, span: Span) -> Result<Vec<Field>, ShellError>
|
|||||||
Ok(fields)
|
Ok(fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dtype_str_to_schema(dtype: &str, span: Span) -> Result<DataType, ShellError> {
|
pub fn str_to_dtype(dtype: &str, span: Span) -> Result<DataType, ShellError> {
|
||||||
match dtype {
|
match dtype {
|
||||||
"bool" => Ok(DataType::Boolean),
|
"bool" => Ok(DataType::Boolean),
|
||||||
"u8" => Ok(DataType::UInt8),
|
"u8" => Ok(DataType::UInt8),
|
||||||
@ -107,7 +106,7 @@ fn dtype_str_to_schema(dtype: &str, span: Span) -> Result<DataType, ShellError>
|
|||||||
.trim_start_matches('<')
|
.trim_start_matches('<')
|
||||||
.trim_end_matches('>')
|
.trim_end_matches('>')
|
||||||
.trim();
|
.trim();
|
||||||
let dtype = dtype_str_to_schema(dtype, span)?;
|
let dtype = str_to_dtype(dtype, span)?;
|
||||||
Ok(DataType::List(Box::new(dtype)))
|
Ok(DataType::List(Box::new(dtype)))
|
||||||
}
|
}
|
||||||
_ if dtype.starts_with("datetime") => {
|
_ if dtype.starts_with("datetime") => {
|
||||||
@ -188,42 +187,23 @@ fn str_to_time_unit(ts_string: &str, span: Span) -> Result<TimeUnit, ShellError>
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
|
use nu_protocol::record;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_value_to_schema() {
|
fn test_value_to_schema() {
|
||||||
let value = Value::Record {
|
let address = record! {
|
||||||
val: Record::from_raw_cols_vals_unchecked(
|
"street" => Value::test_string("str"),
|
||||||
vec!["name".into(), "age".into(), "address".into()],
|
"city" => Value::test_string("str"),
|
||||||
vec![
|
|
||||||
Value::String {
|
|
||||||
val: "str".into(),
|
|
||||||
internal_span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "i32".into(),
|
|
||||||
internal_span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::Record {
|
|
||||||
val: Record::from_raw_cols_vals_unchecked(
|
|
||||||
vec!["street".into(), "city".into()],
|
|
||||||
vec![
|
|
||||||
Value::String {
|
|
||||||
val: "str".into(),
|
|
||||||
internal_span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "str".into(),
|
|
||||||
internal_span: Span::test_data(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
),
|
|
||||||
internal_span: Span::test_data(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
),
|
|
||||||
internal_span: Span::test_data(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let value = Value::test_record(record! {
|
||||||
|
"name" => Value::test_string("str"),
|
||||||
|
"age" => Value::test_string("i32"),
|
||||||
|
"address" => Value::test_record(address)
|
||||||
|
});
|
||||||
|
|
||||||
let schema = value_to_schema(&value, Span::unknown()).unwrap();
|
let schema = value_to_schema(&value, Span::unknown()).unwrap();
|
||||||
let expected = Schema::from_iter(vec![
|
let expected = Schema::from_iter(vec![
|
||||||
Field::new("name", DataType::String),
|
Field::new("name", DataType::String),
|
||||||
@ -242,82 +222,82 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_dtype_str_to_schema_simple_types() {
|
fn test_dtype_str_to_schema_simple_types() {
|
||||||
let dtype = "bool";
|
let dtype = "bool";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Boolean;
|
let expected = DataType::Boolean;
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "u8";
|
let dtype = "u8";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::UInt8;
|
let expected = DataType::UInt8;
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "u16";
|
let dtype = "u16";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::UInt16;
|
let expected = DataType::UInt16;
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "u32";
|
let dtype = "u32";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::UInt32;
|
let expected = DataType::UInt32;
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "u64";
|
let dtype = "u64";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::UInt64;
|
let expected = DataType::UInt64;
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "i8";
|
let dtype = "i8";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Int8;
|
let expected = DataType::Int8;
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "i16";
|
let dtype = "i16";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Int16;
|
let expected = DataType::Int16;
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "i32";
|
let dtype = "i32";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Int32;
|
let expected = DataType::Int32;
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "i64";
|
let dtype = "i64";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Int64;
|
let expected = DataType::Int64;
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "str";
|
let dtype = "str";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::String;
|
let expected = DataType::String;
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "binary";
|
let dtype = "binary";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Binary;
|
let expected = DataType::Binary;
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "date";
|
let dtype = "date";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Date;
|
let expected = DataType::Date;
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "time";
|
let dtype = "time";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Time;
|
let expected = DataType::Time;
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "null";
|
let dtype = "null";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Null;
|
let expected = DataType::Null;
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "unknown";
|
let dtype = "unknown";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Unknown;
|
let expected = DataType::Unknown;
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "object";
|
let dtype = "object";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Object("unknown", None);
|
let expected = DataType::Object("unknown", None);
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
}
|
}
|
||||||
@ -325,54 +305,54 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_dtype_str_schema_datetime() {
|
fn test_dtype_str_schema_datetime() {
|
||||||
let dtype = "datetime<ms, *>";
|
let dtype = "datetime<ms, *>";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Datetime(TimeUnit::Milliseconds, None);
|
let expected = DataType::Datetime(TimeUnit::Milliseconds, None);
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "datetime<us, *>";
|
let dtype = "datetime<us, *>";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Datetime(TimeUnit::Microseconds, None);
|
let expected = DataType::Datetime(TimeUnit::Microseconds, None);
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "datetime<μs, *>";
|
let dtype = "datetime<μs, *>";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Datetime(TimeUnit::Microseconds, None);
|
let expected = DataType::Datetime(TimeUnit::Microseconds, None);
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "datetime<ns, *>";
|
let dtype = "datetime<ns, *>";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Datetime(TimeUnit::Nanoseconds, None);
|
let expected = DataType::Datetime(TimeUnit::Nanoseconds, None);
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "datetime<ms, UTC>";
|
let dtype = "datetime<ms, UTC>";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Datetime(TimeUnit::Milliseconds, Some("UTC".into()));
|
let expected = DataType::Datetime(TimeUnit::Milliseconds, Some("UTC".into()));
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "invalid";
|
let dtype = "invalid";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown());
|
let schema = str_to_dtype(dtype, Span::unknown());
|
||||||
assert!(schema.is_err())
|
assert!(schema.is_err())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dtype_str_schema_duration() {
|
fn test_dtype_str_schema_duration() {
|
||||||
let dtype = "duration<ms>";
|
let dtype = "duration<ms>";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Duration(TimeUnit::Milliseconds);
|
let expected = DataType::Duration(TimeUnit::Milliseconds);
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "duration<us>";
|
let dtype = "duration<us>";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Duration(TimeUnit::Microseconds);
|
let expected = DataType::Duration(TimeUnit::Microseconds);
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "duration<μs>";
|
let dtype = "duration<μs>";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Duration(TimeUnit::Microseconds);
|
let expected = DataType::Duration(TimeUnit::Microseconds);
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "duration<ns>";
|
let dtype = "duration<ns>";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::Duration(TimeUnit::Nanoseconds);
|
let expected = DataType::Duration(TimeUnit::Nanoseconds);
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
}
|
}
|
||||||
@ -380,17 +360,17 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_dtype_str_to_schema_list_types() {
|
fn test_dtype_str_to_schema_list_types() {
|
||||||
let dtype = "list<i32>";
|
let dtype = "list<i32>";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::List(Box::new(DataType::Int32));
|
let expected = DataType::List(Box::new(DataType::Int32));
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "list<duration<ms>>";
|
let dtype = "list<duration<ms>>";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::List(Box::new(DataType::Duration(TimeUnit::Milliseconds)));
|
let expected = DataType::List(Box::new(DataType::Duration(TimeUnit::Milliseconds)));
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
|
|
||||||
let dtype = "list<datetime<ms, *>>";
|
let dtype = "list<datetime<ms, *>>";
|
||||||
let schema = dtype_str_to_schema(dtype, Span::unknown()).unwrap();
|
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||||
let expected = DataType::List(Box::new(DataType::Datetime(TimeUnit::Milliseconds, None)));
|
let expected = DataType::List(Box::new(DataType::Datetime(TimeUnit::Milliseconds, None)));
|
||||||
assert_eq!(schema, expected);
|
assert_eq!(schema, expected);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-extra"
|
name = "nu-cmd-extra"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
||||||
version = "0.90.1"
|
version = "0.91.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -13,30 +13,30 @@ version = "0.90.1"
|
|||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.90.1" }
|
nu-engine = { path = "../nu-engine", version = "0.91.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.90.1" }
|
nu-parser = { path = "../nu-parser", version = "0.91.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.90.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.91.0" }
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.90.1" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.91.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.90.1" }
|
nu-utils = { path = "../nu-utils", version = "0.91.0" }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
heck = "0.4.1"
|
heck = "0.4.1"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
ahash = "0.8.3"
|
|
||||||
nu-ansi-term = "0.50.0"
|
nu-ansi-term = "0.50.0"
|
||||||
fancy-regex = "0.12.0"
|
fancy-regex = "0.13.0"
|
||||||
rust-embed = "8.2.0"
|
rust-embed = "8.2.0"
|
||||||
serde = "1.0.164"
|
serde = "1.0.164"
|
||||||
nu-pretty-hex = { version = "0.90.1", path = "../nu-pretty-hex" }
|
nu-pretty-hex = { version = "0.91.0", path = "../nu-pretty-hex" }
|
||||||
nu-json = { version = "0.90.1", path = "../nu-json" }
|
nu-json = { version = "0.91.0", path = "../nu-json" }
|
||||||
serde_urlencoded = "0.7.1"
|
serde_urlencoded = "0.7.1"
|
||||||
v_htmlescape = "0.15.0"
|
v_htmlescape = "0.15.0"
|
||||||
|
itertools = "0.12"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
extra = ["default"]
|
extra = ["default"]
|
||||||
default = []
|
default = []
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.90.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.91.0" }
|
||||||
nu-command = { path = "../nu-command", version = "0.90.1" }
|
nu-command = { path = "../nu-command", version = "0.91.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.90.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.91.0" }
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
use super::binary_op;
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -17,17 +18,32 @@ impl Command for BitsAnd {
|
|||||||
Signature::build("bits and")
|
Signature::build("bits and")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::Int, Type::Int),
|
(Type::Int, Type::Int),
|
||||||
|
(Type::Binary, Type::Binary),
|
||||||
(
|
(
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
Type::List(Box::new(Type::Binary)),
|
||||||
|
Type::List(Box::new(Type::Binary)),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
.required("target", SyntaxShape::Int, "target int to perform bit and")
|
.required(
|
||||||
|
"target",
|
||||||
|
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]),
|
||||||
|
"right-hand side of the operation",
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"endian",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"byte encode endian, available options: native(default), little, big",
|
||||||
|
Some('e'),
|
||||||
|
)
|
||||||
.category(Category::Bits)
|
.category(Category::Bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Performs bitwise and for ints."
|
"Performs bitwise and for ints or binary values."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
@ -42,14 +58,32 @@ impl Command for BitsAnd {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let target: i64 = call.req(engine_state, stack, 0)?;
|
let target: Value = call.req(engine_state, stack, 0)?;
|
||||||
|
let endian = call.get_flag::<Spanned<String>>(engine_state, stack, "endian")?;
|
||||||
|
|
||||||
|
let little_endian = if let Some(endian) = endian {
|
||||||
|
match endian.item.as_str() {
|
||||||
|
"native" => cfg!(target_endian = "little"),
|
||||||
|
"little" => true,
|
||||||
|
"big" => false,
|
||||||
|
_ => {
|
||||||
|
return Err(ShellError::TypeMismatch {
|
||||||
|
err_message: "Endian must be one of native, little, big".to_string(),
|
||||||
|
span: endian.span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg!(target_endian = "little")
|
||||||
|
};
|
||||||
|
|
||||||
// This doesn't match explicit nulls
|
// This doesn't match explicit nulls
|
||||||
if matches!(input, PipelineData::Empty) {
|
if matches!(input, PipelineData::Empty) {
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
}
|
}
|
||||||
|
|
||||||
input.map(
|
input.map(
|
||||||
move |value| operate(value, target, head),
|
move |value| binary_op(&value, &target, little_endian, |(l, r)| l & r, head),
|
||||||
engine_state.ctrlc.clone(),
|
engine_state.ctrlc.clone(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -57,40 +91,47 @@ impl Command for BitsAnd {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Apply bits and to two numbers",
|
description: "Apply bitwise and to two numbers",
|
||||||
example: "2 | bits and 2",
|
example: "2 | bits and 2",
|
||||||
result: Some(Value::test_int(2)),
|
result: Some(Value::test_int(2)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Apply logical and to a list of numbers",
|
description: "Apply bitwise and to two binary values",
|
||||||
|
example: "0x[ab cd] | bits and 0x[99 99]",
|
||||||
|
result: Some(Value::test_binary([0x89, 0x89])),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply bitwise and to a list of numbers",
|
||||||
example: "[4 3 2] | bits and 2",
|
example: "[4 3 2] | bits and 2",
|
||||||
result: Some(Value::list(
|
result: Some(Value::test_list(vec![
|
||||||
vec![Value::test_int(0), Value::test_int(2), Value::test_int(2)],
|
Value::test_int(0),
|
||||||
Span::test_data(),
|
Value::test_int(2),
|
||||||
)),
|
Value::test_int(2),
|
||||||
|
])),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply bitwise and to a list of binary data",
|
||||||
|
example: "[0x[7f ff] 0x[ff f0]] | bits and 0x[99 99]",
|
||||||
|
result: Some(Value::test_list(vec![
|
||||||
|
Value::test_binary([0x19, 0x99]),
|
||||||
|
Value::test_binary([0x99, 0x90]),
|
||||||
|
])),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Apply bitwise and to binary data of varying lengths with specified endianness",
|
||||||
|
example: "0x[c0 ff ee] | bits and 0x[ff] --endian big",
|
||||||
|
result: Some(Value::test_binary(vec![0x00, 0x00, 0xee])),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply bitwise and to input binary data smaller than the operand",
|
||||||
|
example: "0x[ff] | bits and 0x[12 34 56] --endian little",
|
||||||
|
result: Some(Value::test_binary(vec![0x12, 0x00, 0x00])),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(value: Value, target: i64, head: Span) -> Value {
|
|
||||||
let span = value.span();
|
|
||||||
match value {
|
|
||||||
Value::Int { val, .. } => Value::int(val & target, span),
|
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
|
||||||
Value::Error { .. } => value,
|
|
||||||
other => Value::error(
|
|
||||||
ShellError::OnlySupportsThisInputType {
|
|
||||||
exp_input_type: "int".into(),
|
|
||||||
wrong_type: other.get_type().to_string(),
|
|
||||||
dst_span: head,
|
|
||||||
src_span: other.span(),
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -13,6 +13,7 @@ pub use and::BitsAnd;
|
|||||||
pub use bits_::Bits;
|
pub use bits_::Bits;
|
||||||
pub use into::BitsInto;
|
pub use into::BitsInto;
|
||||||
pub use not::BitsNot;
|
pub use not::BitsNot;
|
||||||
|
use nu_protocol::{ShellError, Value};
|
||||||
pub use or::BitsOr;
|
pub use or::BitsOr;
|
||||||
pub use rotate_left::BitsRol;
|
pub use rotate_left::BitsRol;
|
||||||
pub use rotate_right::BitsRor;
|
pub use rotate_right::BitsRor;
|
||||||
@ -20,7 +21,8 @@ pub use shift_left::BitsShl;
|
|||||||
pub use shift_right::BitsShr;
|
pub use shift_right::BitsShr;
|
||||||
pub use xor::BitsXor;
|
pub use xor::BitsXor;
|
||||||
|
|
||||||
use nu_protocol::Spanned;
|
use nu_protocol::{Span, Spanned};
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
enum NumberBytes {
|
enum NumberBytes {
|
||||||
@ -29,7 +31,6 @@ enum NumberBytes {
|
|||||||
Four,
|
Four,
|
||||||
Eight,
|
Eight,
|
||||||
Auto,
|
Auto,
|
||||||
Invalid,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
@ -44,17 +45,22 @@ enum InputNumType {
|
|||||||
SignedEight,
|
SignedEight,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_number_bytes(number_bytes: Option<&Spanned<String>>) -> NumberBytes {
|
fn get_number_bytes(
|
||||||
match number_bytes.as_ref() {
|
number_bytes: Option<Spanned<usize>>,
|
||||||
None => NumberBytes::Eight,
|
head: Span,
|
||||||
Some(size) => match size.item.as_str() {
|
) -> Result<NumberBytes, ShellError> {
|
||||||
"1" => NumberBytes::One,
|
match number_bytes {
|
||||||
"2" => NumberBytes::Two,
|
None => Ok(NumberBytes::Auto),
|
||||||
"4" => NumberBytes::Four,
|
Some(Spanned { item: 1, .. }) => Ok(NumberBytes::One),
|
||||||
"8" => NumberBytes::Eight,
|
Some(Spanned { item: 2, .. }) => Ok(NumberBytes::Two),
|
||||||
"auto" => NumberBytes::Auto,
|
Some(Spanned { item: 4, .. }) => Ok(NumberBytes::Four),
|
||||||
_ => NumberBytes::Invalid,
|
Some(Spanned { item: 8, .. }) => Ok(NumberBytes::Eight),
|
||||||
},
|
Some(Spanned { span, .. }) => Err(ShellError::UnsupportedInput {
|
||||||
|
msg: "Only 1, 2, 4, or 8 bytes are supported as word sizes".to_string(),
|
||||||
|
input: "value originates from here".to_string(),
|
||||||
|
msg_span: head,
|
||||||
|
input_span: span,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +82,6 @@ fn get_input_num_type(val: i64, signed: bool, number_size: NumberBytes) -> Input
|
|||||||
InputNumType::SignedEight
|
InputNumType::SignedEight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NumberBytes::Invalid => InputNumType::SignedFour,
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match number_size {
|
match number_size {
|
||||||
@ -95,7 +100,68 @@ fn get_input_num_type(val: i64, signed: bool, number_size: NumberBytes) -> Input
|
|||||||
InputNumType::Eight
|
InputNumType::Eight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NumberBytes::Invalid => InputNumType::Four,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn binary_op<F>(lhs: &Value, rhs: &Value, little_endian: bool, f: F, head: Span) -> Value
|
||||||
|
where
|
||||||
|
F: Fn((i64, i64)) -> i64,
|
||||||
|
{
|
||||||
|
let span = lhs.span();
|
||||||
|
match (lhs, rhs) {
|
||||||
|
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||||
|
Value::int(f((*lhs, *rhs)), span)
|
||||||
|
}
|
||||||
|
(Value::Binary { val: lhs, .. }, Value::Binary { val: rhs, .. }) => {
|
||||||
|
let (lhs, rhs, max_len, min_len) = match (lhs.len(), rhs.len()) {
|
||||||
|
(max, min) if max > min => (lhs, rhs, max, min),
|
||||||
|
(min, max) => (rhs, lhs, max, min),
|
||||||
|
};
|
||||||
|
|
||||||
|
let pad = iter::repeat(0).take(max_len - min_len);
|
||||||
|
|
||||||
|
let mut a;
|
||||||
|
let mut b;
|
||||||
|
|
||||||
|
let padded: &mut dyn Iterator<Item = u8> = if little_endian {
|
||||||
|
a = rhs.iter().copied().chain(pad);
|
||||||
|
&mut a
|
||||||
|
} else {
|
||||||
|
b = pad.chain(rhs.iter().copied());
|
||||||
|
&mut b
|
||||||
|
};
|
||||||
|
|
||||||
|
let bytes: Vec<u8> = lhs
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.zip(padded)
|
||||||
|
.map(|(lhs, rhs)| f((lhs as i64, rhs as i64)) as u8)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Value::binary(bytes, span)
|
||||||
|
}
|
||||||
|
(Value::Binary { .. }, Value::Int { .. }) | (Value::Int { .. }, Value::Binary { .. }) => {
|
||||||
|
Value::error(
|
||||||
|
ShellError::PipelineMismatch {
|
||||||
|
exp_input_type: "input, and argument, to be both int or both binary"
|
||||||
|
.to_string(),
|
||||||
|
dst_span: rhs.span(),
|
||||||
|
src_span: span,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
(e @ Value::Error { .. }, _) | (_, e @ Value::Error { .. }) => e.clone(),
|
||||||
|
(other, Value::Int { .. } | Value::Binary { .. }) | (_, other) => Value::error(
|
||||||
|
ShellError::OnlySupportsThisInputType {
|
||||||
|
exp_input_type: "int or binary".into(),
|
||||||
|
wrong_type: other.get_type().to_string(),
|
||||||
|
dst_span: head,
|
||||||
|
src_span: other.span(),
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use super::{get_number_bytes, NumberBytes};
|
use super::{get_number_bytes, NumberBytes};
|
||||||
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::{Call, CellPath};
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
@ -9,6 +10,18 @@ use nu_protocol::{
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BitsNot;
|
pub struct BitsNot;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Arguments {
|
||||||
|
signed: bool,
|
||||||
|
number_size: NumberBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Command for BitsNot {
|
impl Command for BitsNot {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"bits not"
|
"bits not"
|
||||||
@ -18,10 +31,15 @@ impl Command for BitsNot {
|
|||||||
Signature::build("bits not")
|
Signature::build("bits not")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::Int, Type::Int),
|
(Type::Int, Type::Int),
|
||||||
|
(Type::Binary, Type::Binary),
|
||||||
(
|
(
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
Type::List(Box::new(Type::Binary)),
|
||||||
|
Type::List(Box::new(Type::Binary)),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.switch(
|
.switch(
|
||||||
@ -31,7 +49,7 @@ impl Command for BitsNot {
|
|||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
"number-bytes",
|
"number-bytes",
|
||||||
SyntaxShape::String,
|
SyntaxShape::Int,
|
||||||
"the size of unsigned number in bytes, it can be 1, 2, 4, 8, auto",
|
"the size of unsigned number in bytes, it can be 1, 2, 4, 8, auto",
|
||||||
Some('n'),
|
Some('n'),
|
||||||
)
|
)
|
||||||
@ -55,28 +73,21 @@ impl Command for BitsNot {
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||||
let number_bytes: Option<Spanned<String>> =
|
let number_bytes: Option<Spanned<usize>> =
|
||||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
let bytes_len = get_number_bytes(number_bytes.as_ref());
|
let number_size = get_number_bytes(number_bytes, head)?;
|
||||||
if let NumberBytes::Invalid = bytes_len {
|
|
||||||
if let Some(val) = number_bytes {
|
|
||||||
return Err(ShellError::UnsupportedInput {
|
|
||||||
msg: "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
|
||||||
input: "value originates from here".to_string(),
|
|
||||||
msg_span: head,
|
|
||||||
input_span: val.span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This doesn't match explicit nulls
|
// This doesn't match explicit nulls
|
||||||
if matches!(input, PipelineData::Empty) {
|
if matches!(input, PipelineData::Empty) {
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
}
|
}
|
||||||
input.map(
|
|
||||||
move |value| operate(value, head, signed, bytes_len),
|
let args = Arguments {
|
||||||
engine_state.ctrlc.clone(),
|
signed,
|
||||||
)
|
number_size,
|
||||||
|
};
|
||||||
|
|
||||||
|
operate(action, args, input, head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -86,9 +97,9 @@ impl Command for BitsNot {
|
|||||||
example: "[4 3 2] | bits not",
|
example: "[4 3 2] | bits not",
|
||||||
result: Some(Value::list(
|
result: Some(Value::list(
|
||||||
vec![
|
vec![
|
||||||
Value::test_int(140737488355323),
|
Value::test_int(251),
|
||||||
Value::test_int(140737488355324),
|
Value::test_int(252),
|
||||||
Value::test_int(140737488355325),
|
Value::test_int(253),
|
||||||
],
|
],
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)),
|
)),
|
||||||
@ -96,7 +107,7 @@ impl Command for BitsNot {
|
|||||||
Example {
|
Example {
|
||||||
description:
|
description:
|
||||||
"Apply logical negation to a list of numbers, treat input as 2 bytes number",
|
"Apply logical negation to a list of numbers, treat input as 2 bytes number",
|
||||||
example: "[4 3 2] | bits not --number-bytes '2'",
|
example: "[4 3 2] | bits not --number-bytes 2",
|
||||||
result: Some(Value::list(
|
result: Some(Value::list(
|
||||||
vec![
|
vec![
|
||||||
Value::test_int(65531),
|
Value::test_int(65531),
|
||||||
@ -119,14 +130,23 @@ impl Command for BitsNot {
|
|||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply logical negation to binary data",
|
||||||
|
example: "0x[ff 00 7f] | bits not",
|
||||||
|
result: Some(Value::binary(vec![0x00, 0xff, 0x80], Span::test_data())),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(value: Value, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
let span = value.span();
|
let Arguments {
|
||||||
match value {
|
signed,
|
||||||
|
number_size,
|
||||||
|
} = *args;
|
||||||
|
match input {
|
||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
|
let val = *val;
|
||||||
if signed || val < 0 {
|
if signed || val < 0 {
|
||||||
Value::int(!val, span)
|
Value::int(!val, span)
|
||||||
} else {
|
} else {
|
||||||
@ -147,25 +167,24 @@ fn operate(value: Value, head: Span, signed: bool, number_size: NumberBytes) ->
|
|||||||
!val & 0x7F_FF_FF_FF_FF_FF
|
!val & 0x7F_FF_FF_FF_FF_FF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// This case shouldn't happen here, as it's handled before
|
|
||||||
Invalid => 0,
|
|
||||||
};
|
};
|
||||||
Value::int(out_val, span)
|
Value::int(out_val, span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
other => match other {
|
Value::Binary { val, .. } => {
|
||||||
// Propagate errors inside the value
|
Value::binary(val.iter().copied().map(|b| !b).collect::<Vec<_>>(), span)
|
||||||
Value::Error { .. } => other,
|
}
|
||||||
_ => Value::error(
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
ShellError::OnlySupportsThisInputType {
|
Value::Error { .. } => input.clone(),
|
||||||
exp_input_type: "int".into(),
|
other => Value::error(
|
||||||
wrong_type: other.get_type().to_string(),
|
ShellError::OnlySupportsThisInputType {
|
||||||
dst_span: head,
|
exp_input_type: "int or binary".into(),
|
||||||
src_span: other.span(),
|
wrong_type: other.get_type().to_string(),
|
||||||
},
|
dst_span: other.span(),
|
||||||
head,
|
src_span: span,
|
||||||
),
|
},
|
||||||
},
|
span,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
use super::binary_op;
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -17,17 +18,33 @@ impl Command for BitsOr {
|
|||||||
Signature::build("bits or")
|
Signature::build("bits or")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::Int, Type::Int),
|
(Type::Int, Type::Int),
|
||||||
|
(Type::Binary, Type::Binary),
|
||||||
(
|
(
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
Type::List(Box::new(Type::Binary)),
|
||||||
|
Type::List(Box::new(Type::Binary)),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
.required("target", SyntaxShape::Int, "target int to perform bit or")
|
.allow_variants_without_examples(true)
|
||||||
|
.required(
|
||||||
|
"target",
|
||||||
|
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]),
|
||||||
|
"right-hand side of the operation",
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"endian",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"byte encode endian, available options: native(default), little, big",
|
||||||
|
Some('e'),
|
||||||
|
)
|
||||||
.category(Category::Bits)
|
.category(Category::Bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Performs bitwise or for ints."
|
"Performs bitwise or for ints or binary values."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
@ -42,14 +59,32 @@ impl Command for BitsOr {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let target: i64 = call.req(engine_state, stack, 0)?;
|
let target: Value = call.req(engine_state, stack, 0)?;
|
||||||
|
let endian = call.get_flag::<Spanned<String>>(engine_state, stack, "endian")?;
|
||||||
|
|
||||||
|
let little_endian = if let Some(endian) = endian {
|
||||||
|
match endian.item.as_str() {
|
||||||
|
"native" => cfg!(target_endian = "little"),
|
||||||
|
"little" => true,
|
||||||
|
"big" => false,
|
||||||
|
_ => {
|
||||||
|
return Err(ShellError::TypeMismatch {
|
||||||
|
err_message: "Endian must be one of native, little, big".to_string(),
|
||||||
|
span: endian.span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg!(target_endian = "little")
|
||||||
|
};
|
||||||
|
|
||||||
// This doesn't match explicit nulls
|
// This doesn't match explicit nulls
|
||||||
if matches!(input, PipelineData::Empty) {
|
if matches!(input, PipelineData::Empty) {
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
}
|
}
|
||||||
|
|
||||||
input.map(
|
input.map(
|
||||||
move |value| operate(value, target, head),
|
move |value| binary_op(&value, &target, little_endian, |(l, r)| l | r, head),
|
||||||
engine_state.ctrlc.clone(),
|
engine_state.ctrlc.clone(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -62,35 +97,34 @@ impl Command for BitsOr {
|
|||||||
result: Some(Value::test_int(6)),
|
result: Some(Value::test_int(6)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Apply logical or to a list of numbers",
|
description: "Apply bitwise or to a list of numbers",
|
||||||
example: "[8 3 2] | bits or 2",
|
example: "[8 3 2] | bits or 2",
|
||||||
result: Some(Value::list(
|
result: Some(Value::test_list(vec![
|
||||||
vec![Value::test_int(10), Value::test_int(3), Value::test_int(2)],
|
Value::test_int(10),
|
||||||
Span::test_data(),
|
Value::test_int(3),
|
||||||
)),
|
Value::test_int(2),
|
||||||
|
])),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply bitwise or to binary data",
|
||||||
|
example: "0x[88 cc] | bits or 0x[42 32]",
|
||||||
|
result: Some(Value::test_binary(vec![0xca, 0xfe])),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Apply bitwise or to binary data of varying lengths with specified endianness",
|
||||||
|
example: "0x[c0 ff ee] | bits or 0x[ff] --endian big",
|
||||||
|
result: Some(Value::test_binary(vec![0xc0, 0xff, 0xff])),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply bitwise or to input binary data smaller than the operor",
|
||||||
|
example: "0x[ff] | bits or 0x[12 34 56] --endian little",
|
||||||
|
result: Some(Value::test_binary(vec![0xff, 0x34, 0x56])),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(value: Value, target: i64, head: Span) -> Value {
|
|
||||||
let span = value.span();
|
|
||||||
match value {
|
|
||||||
Value::Int { val, .. } => Value::int(val | target, span),
|
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
|
||||||
Value::Error { .. } => value,
|
|
||||||
other => Value::error(
|
|
||||||
ShellError::OnlySupportsThisInputType {
|
|
||||||
exp_input_type: "int".into(),
|
|
||||||
wrong_type: other.get_type().to_string(),
|
|
||||||
dst_span: head,
|
|
||||||
src_span: other.span(),
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -1,12 +1,26 @@
|
|||||||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::ast::CellPath;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Spanned;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use num_traits::int::PrimInt;
|
|
||||||
use std::fmt::Display;
|
struct Arguments {
|
||||||
|
signed: bool,
|
||||||
|
bits: usize,
|
||||||
|
number_size: NumberBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BitsRol;
|
pub struct BitsRol;
|
||||||
@ -20,11 +34,17 @@ impl Command for BitsRol {
|
|||||||
Signature::build("bits rol")
|
Signature::build("bits rol")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::Int, Type::Int),
|
(Type::Int, Type::Int),
|
||||||
|
(Type::Binary, Type::Binary),
|
||||||
(
|
(
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
Type::List(Box::new(Type::Binary)),
|
||||||
|
Type::List(Box::new(Type::Binary)),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
.required("bits", SyntaxShape::Int, "number of bits to rotate left")
|
.required("bits", SyntaxShape::Int, "number of bits to rotate left")
|
||||||
.switch(
|
.switch(
|
||||||
"signed",
|
"signed",
|
||||||
@ -33,7 +53,7 @@ impl Command for BitsRol {
|
|||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
"number-bytes",
|
"number-bytes",
|
||||||
SyntaxShape::String,
|
SyntaxShape::Int,
|
||||||
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||||
Some('n'),
|
Some('n'),
|
||||||
)
|
)
|
||||||
@ -41,7 +61,7 @@ impl Command for BitsRol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Bitwise rotate left for ints."
|
"Bitwise rotate left for ints or binary values."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
@ -58,27 +78,22 @@ impl Command for BitsRol {
|
|||||||
let head = call.head;
|
let head = call.head;
|
||||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||||
let number_bytes: Option<Spanned<String>> =
|
let number_bytes: Option<Spanned<usize>> =
|
||||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
let bytes_len = get_number_bytes(number_bytes.as_ref());
|
let number_size = get_number_bytes(number_bytes, head)?;
|
||||||
if let NumberBytes::Invalid = bytes_len {
|
|
||||||
if let Some(val) = number_bytes {
|
|
||||||
return Err(ShellError::UnsupportedInput {
|
|
||||||
msg: "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
|
||||||
input: "value originates from here".to_string(),
|
|
||||||
msg_span: head,
|
|
||||||
input_span: val.span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This doesn't match explicit nulls
|
// This doesn't match explicit nulls
|
||||||
if matches!(input, PipelineData::Empty) {
|
if matches!(input, PipelineData::Empty) {
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
}
|
}
|
||||||
input.map(
|
|
||||||
move |value| operate(value, bits, head, signed, bytes_len),
|
let args = Arguments {
|
||||||
engine_state.ctrlc.clone(),
|
signed,
|
||||||
)
|
number_size,
|
||||||
|
bits,
|
||||||
|
};
|
||||||
|
|
||||||
|
operate(action, args, input, head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -96,61 +111,82 @@ impl Command for BitsRol {
|
|||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "rotate left binary data",
|
||||||
|
example: "0x[c0 ff ee] | bits rol 10",
|
||||||
|
result: Some(Value::binary(vec![0xff, 0xbb, 0x03], Span::test_data())),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rotate_left<T: Display + PrimInt>(val: T, bits: u32, span: Span) -> Value
|
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
where
|
let Arguments {
|
||||||
i64: std::convert::TryFrom<T>,
|
signed,
|
||||||
{
|
number_size,
|
||||||
let rotate_result = i64::try_from(val.rotate_left(bits));
|
bits,
|
||||||
match rotate_result {
|
} = *args;
|
||||||
Ok(val) => Value::int(val, span),
|
|
||||||
Err(_) => Value::error(
|
|
||||||
ShellError::GenericError {
|
|
||||||
error: "Rotate left result beyond the range of 64 bit signed number".into(),
|
|
||||||
msg: format!(
|
|
||||||
"{val} of the specified number of bytes rotate left {bits} bits exceed limit"
|
|
||||||
),
|
|
||||||
span: Some(span),
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
match input {
|
||||||
let span = value.span();
|
|
||||||
match value {
|
|
||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
use InputNumType::*;
|
use InputNumType::*;
|
||||||
// let bits = (((bits % 64) + 64) % 64) as u32;
|
let val = *val;
|
||||||
let bits = bits as u32;
|
let bits = bits as u32;
|
||||||
let input_type = get_input_num_type(val, signed, number_size);
|
let input_num_type = get_input_num_type(val, signed, number_size);
|
||||||
match input_type {
|
|
||||||
One => get_rotate_left(val as u8, bits, span),
|
let int = match input_num_type {
|
||||||
Two => get_rotate_left(val as u16, bits, span),
|
One => (val as u8).rotate_left(bits) as i64,
|
||||||
Four => get_rotate_left(val as u32, bits, span),
|
Two => (val as u16).rotate_left(bits) as i64,
|
||||||
Eight => get_rotate_left(val as u64, bits, span),
|
Four => (val as u32).rotate_left(bits) as i64,
|
||||||
SignedOne => get_rotate_left(val as i8, bits, span),
|
Eight => {
|
||||||
SignedTwo => get_rotate_left(val as i16, bits, span),
|
let Ok(i) = i64::try_from((val as u64).rotate_left(bits)) else {
|
||||||
SignedFour => get_rotate_left(val as i32, bits, span),
|
return Value::error(
|
||||||
SignedEight => get_rotate_left(val, bits, span),
|
ShellError::GenericError {
|
||||||
}
|
error: "result out of range for specified number".into(),
|
||||||
|
msg: format!(
|
||||||
|
"rotating left by {bits} is out of range for the value {val}"
|
||||||
|
),
|
||||||
|
span: Some(span),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
i
|
||||||
|
}
|
||||||
|
SignedOne => (val as i8).rotate_left(bits) as i64,
|
||||||
|
SignedTwo => (val as i16).rotate_left(bits) as i64,
|
||||||
|
SignedFour => (val as i32).rotate_left(bits) as i64,
|
||||||
|
SignedEight => val.rotate_left(bits),
|
||||||
|
};
|
||||||
|
|
||||||
|
Value::int(int, span)
|
||||||
|
}
|
||||||
|
Value::Binary { val, .. } => {
|
||||||
|
let byte_shift = bits / 8;
|
||||||
|
let bit_rotate = bits % 8;
|
||||||
|
|
||||||
|
let mut bytes = val
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.circular_tuple_windows::<(u8, u8)>()
|
||||||
|
.map(|(lhs, rhs)| (lhs << bit_rotate) | (rhs >> (8 - bit_rotate)))
|
||||||
|
.collect::<Vec<u8>>();
|
||||||
|
bytes.rotate_left(byte_shift);
|
||||||
|
|
||||||
|
Value::binary(bytes, span)
|
||||||
}
|
}
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
Value::Error { .. } => value,
|
Value::Error { .. } => input.clone(),
|
||||||
other => Value::error(
|
other => Value::error(
|
||||||
ShellError::OnlySupportsThisInputType {
|
ShellError::OnlySupportsThisInputType {
|
||||||
exp_input_type: "int".into(),
|
exp_input_type: "int or binary".into(),
|
||||||
wrong_type: other.get_type().to_string(),
|
wrong_type: other.get_type().to_string(),
|
||||||
dst_span: head,
|
dst_span: span,
|
||||||
src_span: other.span(),
|
src_span: other.span(),
|
||||||
},
|
},
|
||||||
head,
|
span,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,26 @@
|
|||||||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::ast::CellPath;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Spanned;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use num_traits::int::PrimInt;
|
|
||||||
use std::fmt::Display;
|
struct Arguments {
|
||||||
|
signed: bool,
|
||||||
|
bits: usize,
|
||||||
|
number_size: NumberBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BitsRor;
|
pub struct BitsRor;
|
||||||
@ -20,11 +34,17 @@ impl Command for BitsRor {
|
|||||||
Signature::build("bits ror")
|
Signature::build("bits ror")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::Int, Type::Int),
|
(Type::Int, Type::Int),
|
||||||
|
(Type::Binary, Type::Binary),
|
||||||
(
|
(
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
Type::List(Box::new(Type::Binary)),
|
||||||
|
Type::List(Box::new(Type::Binary)),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
.required("bits", SyntaxShape::Int, "number of bits to rotate right")
|
.required("bits", SyntaxShape::Int, "number of bits to rotate right")
|
||||||
.switch(
|
.switch(
|
||||||
"signed",
|
"signed",
|
||||||
@ -33,7 +53,7 @@ impl Command for BitsRor {
|
|||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
"number-bytes",
|
"number-bytes",
|
||||||
SyntaxShape::String,
|
SyntaxShape::Int,
|
||||||
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||||
Some('n'),
|
Some('n'),
|
||||||
)
|
)
|
||||||
@ -41,7 +61,7 @@ impl Command for BitsRor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Bitwise rotate right for ints."
|
"Bitwise rotate right for ints or binary values."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
@ -58,103 +78,119 @@ impl Command for BitsRor {
|
|||||||
let head = call.head;
|
let head = call.head;
|
||||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||||
let number_bytes: Option<Spanned<String>> =
|
let number_bytes: Option<Spanned<usize>> =
|
||||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
let bytes_len = get_number_bytes(number_bytes.as_ref());
|
let number_size = get_number_bytes(number_bytes, head)?;
|
||||||
if let NumberBytes::Invalid = bytes_len {
|
|
||||||
if let Some(val) = number_bytes {
|
|
||||||
return Err(ShellError::UnsupportedInput {
|
|
||||||
msg: "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
|
||||||
input: "value originates from here".to_string(),
|
|
||||||
msg_span: head,
|
|
||||||
input_span: val.span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This doesn't match explicit nulls
|
// This doesn't match explicit nulls
|
||||||
if matches!(input, PipelineData::Empty) {
|
if matches!(input, PipelineData::Empty) {
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
}
|
}
|
||||||
input.map(
|
|
||||||
move |value| operate(value, bits, head, signed, bytes_len),
|
let args = Arguments {
|
||||||
engine_state.ctrlc.clone(),
|
signed,
|
||||||
)
|
number_size,
|
||||||
|
bits,
|
||||||
|
};
|
||||||
|
|
||||||
|
operate(action, args, input, head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Rotate right a number with 60 bits",
|
description: "rotate right a number with 2 bits",
|
||||||
example: "17 | bits ror 60",
|
example: "17 | bits ror 2",
|
||||||
result: Some(Value::test_int(272)),
|
result: Some(Value::test_int(68)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Rotate right a list of numbers of one byte",
|
description: "rotate right a list of numbers of two bytes",
|
||||||
example: "[15 33 92] | bits ror 2 --number-bytes '1'",
|
example: "[15 33 92] | bits ror 2 --number-bytes 2",
|
||||||
result: Some(Value::list(
|
result: Some(Value::list(
|
||||||
vec![
|
vec![
|
||||||
Value::test_int(195),
|
Value::test_int(49155),
|
||||||
Value::test_int(72),
|
Value::test_int(16392),
|
||||||
Value::test_int(23),
|
Value::test_int(23),
|
||||||
],
|
],
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "rotate right binary data",
|
||||||
|
example: "0x[ff bb 03] | bits ror 10",
|
||||||
|
result: Some(Value::binary(vec![0xc0, 0xff, 0xee], Span::test_data())),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rotate_right<T: Display + PrimInt>(val: T, bits: u32, span: Span) -> Value
|
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
where
|
let Arguments {
|
||||||
i64: std::convert::TryFrom<T>,
|
signed,
|
||||||
{
|
number_size,
|
||||||
let rotate_result = i64::try_from(val.rotate_right(bits));
|
bits,
|
||||||
match rotate_result {
|
} = *args;
|
||||||
Ok(val) => Value::int(val, span),
|
|
||||||
Err(_) => Value::error(
|
|
||||||
ShellError::GenericError {
|
|
||||||
error: "Rotate right result beyond the range of 64 bit signed number".into(),
|
|
||||||
msg: format!(
|
|
||||||
"{val} of the specified number of bytes rotate right {bits} bits exceed limit"
|
|
||||||
),
|
|
||||||
span: Some(span),
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
match input {
|
||||||
let span = value.span();
|
|
||||||
match value {
|
|
||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
use InputNumType::*;
|
use InputNumType::*;
|
||||||
// let bits = (((bits % 64) + 64) % 64) as u32;
|
let val = *val;
|
||||||
let bits = bits as u32;
|
let bits = bits as u32;
|
||||||
let input_type = get_input_num_type(val, signed, number_size);
|
let input_num_type = get_input_num_type(val, signed, number_size);
|
||||||
match input_type {
|
|
||||||
One => get_rotate_right(val as u8, bits, span),
|
let int = match input_num_type {
|
||||||
Two => get_rotate_right(val as u16, bits, span),
|
One => (val as u8).rotate_right(bits) as i64,
|
||||||
Four => get_rotate_right(val as u32, bits, span),
|
Two => (val as u16).rotate_right(bits) as i64,
|
||||||
Eight => get_rotate_right(val as u64, bits, span),
|
Four => (val as u32).rotate_right(bits) as i64,
|
||||||
SignedOne => get_rotate_right(val as i8, bits, span),
|
Eight => {
|
||||||
SignedTwo => get_rotate_right(val as i16, bits, span),
|
let Ok(i) = i64::try_from((val as u64).rotate_right(bits)) else {
|
||||||
SignedFour => get_rotate_right(val as i32, bits, span),
|
return Value::error(
|
||||||
SignedEight => get_rotate_right(val, bits, span),
|
ShellError::GenericError {
|
||||||
}
|
error: "result out of range for specified number".into(),
|
||||||
|
msg: format!(
|
||||||
|
"rotating right by {bits} is out of range for the value {val}"
|
||||||
|
),
|
||||||
|
span: Some(span),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
i
|
||||||
|
}
|
||||||
|
SignedOne => (val as i8).rotate_right(bits) as i64,
|
||||||
|
SignedTwo => (val as i16).rotate_right(bits) as i64,
|
||||||
|
SignedFour => (val as i32).rotate_right(bits) as i64,
|
||||||
|
SignedEight => val.rotate_right(bits),
|
||||||
|
};
|
||||||
|
|
||||||
|
Value::int(int, span)
|
||||||
|
}
|
||||||
|
Value::Binary { val, .. } => {
|
||||||
|
let byte_shift = bits / 8;
|
||||||
|
let bit_rotate = bits % 8;
|
||||||
|
|
||||||
|
let mut bytes = val
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.circular_tuple_windows::<(u8, u8)>()
|
||||||
|
.map(|(lhs, rhs)| (lhs >> bit_rotate) | (rhs << (8 - bit_rotate)))
|
||||||
|
.collect::<Vec<u8>>();
|
||||||
|
bytes.rotate_right(byte_shift);
|
||||||
|
|
||||||
|
Value::binary(bytes, span)
|
||||||
}
|
}
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
Value::Error { .. } => value,
|
Value::Error { .. } => input.clone(),
|
||||||
other => Value::error(
|
other => Value::error(
|
||||||
ShellError::OnlySupportsThisInputType {
|
ShellError::OnlySupportsThisInputType {
|
||||||
exp_input_type: "int".into(),
|
exp_input_type: "int or binary".into(),
|
||||||
wrong_type: other.get_type().to_string(),
|
wrong_type: other.get_type().to_string(),
|
||||||
dst_span: head,
|
dst_span: span,
|
||||||
src_span: other.span(),
|
src_span: other.span(),
|
||||||
},
|
},
|
||||||
head,
|
span,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,27 @@
|
|||||||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::ast::CellPath;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Spanned;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use num_traits::CheckedShl;
|
use std::iter;
|
||||||
use std::fmt::Display;
|
|
||||||
|
struct Arguments {
|
||||||
|
signed: bool,
|
||||||
|
bits: usize,
|
||||||
|
number_size: NumberBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BitsShl;
|
pub struct BitsShl;
|
||||||
@ -20,11 +35,17 @@ impl Command for BitsShl {
|
|||||||
Signature::build("bits shl")
|
Signature::build("bits shl")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::Int, Type::Int),
|
(Type::Int, Type::Int),
|
||||||
|
(Type::Binary, Type::Binary),
|
||||||
(
|
(
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
Type::List(Box::new(Type::Binary)),
|
||||||
|
Type::List(Box::new(Type::Binary)),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
.required("bits", SyntaxShape::Int, "number of bits to shift left")
|
.required("bits", SyntaxShape::Int, "number of bits to shift left")
|
||||||
.switch(
|
.switch(
|
||||||
"signed",
|
"signed",
|
||||||
@ -33,7 +54,7 @@ impl Command for BitsShl {
|
|||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
"number-bytes",
|
"number-bytes",
|
||||||
SyntaxShape::String,
|
SyntaxShape::Int,
|
||||||
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||||
Some('n'),
|
Some('n'),
|
||||||
)
|
)
|
||||||
@ -41,7 +62,7 @@ impl Command for BitsShl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Bitwise shift left for ints."
|
"Bitwise shift left for ints or binary values."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
@ -58,27 +79,22 @@ impl Command for BitsShl {
|
|||||||
let head = call.head;
|
let head = call.head;
|
||||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||||
let number_bytes: Option<Spanned<String>> =
|
let number_bytes: Option<Spanned<usize>> =
|
||||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
let bytes_len = get_number_bytes(number_bytes.as_ref());
|
let number_size = get_number_bytes(number_bytes, head)?;
|
||||||
if let NumberBytes::Invalid = bytes_len {
|
|
||||||
if let Some(val) = number_bytes {
|
|
||||||
return Err(ShellError::UnsupportedInput {
|
|
||||||
msg: "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
|
||||||
input: "value originates from here".to_string(),
|
|
||||||
msg_span: head,
|
|
||||||
input_span: val.span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This doesn't match explicit nulls
|
// This doesn't match explicit nulls
|
||||||
if matches!(input, PipelineData::Empty) {
|
if matches!(input, PipelineData::Empty) {
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
}
|
}
|
||||||
input.map(
|
|
||||||
move |value| operate(value, bits, head, signed, bytes_len),
|
let args = Arguments {
|
||||||
engine_state.ctrlc.clone(),
|
signed,
|
||||||
)
|
number_size,
|
||||||
|
bits,
|
||||||
|
};
|
||||||
|
|
||||||
|
operate(action, args, input, head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -86,17 +102,17 @@ impl Command for BitsShl {
|
|||||||
Example {
|
Example {
|
||||||
description: "Shift left a number by 7 bits",
|
description: "Shift left a number by 7 bits",
|
||||||
example: "2 | bits shl 7",
|
example: "2 | bits shl 7",
|
||||||
result: Some(Value::test_int(256)),
|
result: Some(Value::test_int(0)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Shift left a number with 1 byte by 7 bits",
|
description: "Shift left a number with 2 byte by 7 bits",
|
||||||
example: "2 | bits shl 7 --number-bytes '1'",
|
example: "2 | bits shl 7 --number-bytes 2",
|
||||||
result: Some(Value::test_int(0)),
|
result: Some(Value::test_int(256)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Shift left a signed number by 1 bit",
|
description: "Shift left a signed number by 1 bit",
|
||||||
example: "0x7F | bits shl 1 --signed",
|
example: "0x7F | bits shl 1 --signed",
|
||||||
result: Some(Value::test_int(254)),
|
result: Some(Value::test_int(-2)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Shift left a list of numbers",
|
description: "Shift left a list of numbers",
|
||||||
@ -106,75 +122,88 @@ impl Command for BitsShl {
|
|||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Shift left a binary value",
|
||||||
|
example: "0x[4f f4] | bits shl 4",
|
||||||
|
result: Some(Value::binary(vec![0xff, 0x40], Span::test_data())),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_shift_left<T: CheckedShl + Display + Copy>(val: T, bits: u32, span: Span) -> Value
|
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
where
|
let Arguments {
|
||||||
i64: std::convert::TryFrom<T>,
|
signed,
|
||||||
{
|
number_size,
|
||||||
match val.checked_shl(bits) {
|
bits,
|
||||||
Some(val) => {
|
} = *args;
|
||||||
let shift_result = i64::try_from(val);
|
|
||||||
match shift_result {
|
|
||||||
Ok(val) => Value::int( val, span ),
|
|
||||||
Err(_) => Value::error(
|
|
||||||
ShellError::GenericError {
|
|
||||||
error:"Shift left result beyond the range of 64 bit signed number".into(),
|
|
||||||
msg: format!(
|
|
||||||
"{val} of the specified number of bytes shift left {bits} bits exceed limit"
|
|
||||||
),
|
|
||||||
span: Some(span),
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => Value::error(
|
|
||||||
ShellError::GenericError {
|
|
||||||
error: "Shift left failed".into(),
|
|
||||||
msg: format!("{val} shift left {bits} bits failed, you may shift too many bits"),
|
|
||||||
span: Some(span),
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
match input {
|
||||||
let span = value.span();
|
|
||||||
match value {
|
|
||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
use InputNumType::*;
|
use InputNumType::*;
|
||||||
// let bits = (((bits % 64) + 64) % 64) as u32;
|
let val = *val;
|
||||||
let bits = bits as u32;
|
let bits = bits as u64;
|
||||||
let input_type = get_input_num_type(val, signed, number_size);
|
|
||||||
match input_type {
|
let input_num_type = get_input_num_type(val, signed, number_size);
|
||||||
One => get_shift_left(val as u8, bits, span),
|
let int = match input_num_type {
|
||||||
Two => get_shift_left(val as u16, bits, span),
|
One => ((val as u8) << bits) as i64,
|
||||||
Four => get_shift_left(val as u32, bits, span),
|
Two => ((val as u16) << bits) as i64,
|
||||||
Eight => get_shift_left(val as u64, bits, span),
|
Four => ((val as u32) << bits) as i64,
|
||||||
SignedOne => get_shift_left(val as i8, bits, span),
|
Eight => {
|
||||||
SignedTwo => get_shift_left(val as i16, bits, span),
|
let Ok(i) = i64::try_from((val as u64) << bits) else {
|
||||||
SignedFour => get_shift_left(val as i32, bits, span),
|
return Value::error(
|
||||||
SignedEight => get_shift_left(val, bits, span),
|
ShellError::GenericError {
|
||||||
}
|
error: "result out of range for specified number".into(),
|
||||||
|
msg: format!(
|
||||||
|
"shifting left by {bits} is out of range for the value {val}"
|
||||||
|
),
|
||||||
|
span: Some(span),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
i
|
||||||
|
}
|
||||||
|
SignedOne => ((val as i8) << bits) as i64,
|
||||||
|
SignedTwo => ((val as i16) << bits) as i64,
|
||||||
|
SignedFour => ((val as i32) << bits) as i64,
|
||||||
|
SignedEight => val << bits,
|
||||||
|
};
|
||||||
|
|
||||||
|
Value::int(int, span)
|
||||||
|
}
|
||||||
|
Value::Binary { val, .. } => {
|
||||||
|
let byte_shift = bits / 8;
|
||||||
|
let bit_shift = bits % 8;
|
||||||
|
|
||||||
|
use itertools::Position::*;
|
||||||
|
let bytes = val
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.skip(byte_shift)
|
||||||
|
.circular_tuple_windows::<(u8, u8)>()
|
||||||
|
.with_position()
|
||||||
|
.map(|(pos, (lhs, rhs))| match pos {
|
||||||
|
Last | Only => lhs << bit_shift,
|
||||||
|
_ => (lhs << bit_shift) | (rhs >> bit_shift),
|
||||||
|
})
|
||||||
|
.chain(iter::repeat(0).take(byte_shift))
|
||||||
|
.collect::<Vec<u8>>();
|
||||||
|
|
||||||
|
Value::binary(bytes, span)
|
||||||
}
|
}
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
Value::Error { .. } => value,
|
Value::Error { .. } => input.clone(),
|
||||||
other => Value::error(
|
other => Value::error(
|
||||||
ShellError::OnlySupportsThisInputType {
|
ShellError::OnlySupportsThisInputType {
|
||||||
exp_input_type: "int".into(),
|
exp_input_type: "int or binary".into(),
|
||||||
wrong_type: other.get_type().to_string(),
|
wrong_type: other.get_type().to_string(),
|
||||||
dst_span: head,
|
dst_span: span,
|
||||||
src_span: other.span(),
|
src_span: other.span(),
|
||||||
},
|
},
|
||||||
head,
|
span,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,27 @@
|
|||||||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::ast::CellPath;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Spanned;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use num_traits::CheckedShr;
|
use std::iter;
|
||||||
use std::fmt::Display;
|
|
||||||
|
struct Arguments {
|
||||||
|
signed: bool,
|
||||||
|
bits: usize,
|
||||||
|
number_size: NumberBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BitsShr;
|
pub struct BitsShr;
|
||||||
@ -20,11 +35,17 @@ impl Command for BitsShr {
|
|||||||
Signature::build("bits shr")
|
Signature::build("bits shr")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::Int, Type::Int),
|
(Type::Int, Type::Int),
|
||||||
|
(Type::Binary, Type::Binary),
|
||||||
(
|
(
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
Type::List(Box::new(Type::Binary)),
|
||||||
|
Type::List(Box::new(Type::Binary)),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
.required("bits", SyntaxShape::Int, "number of bits to shift right")
|
.required("bits", SyntaxShape::Int, "number of bits to shift right")
|
||||||
.switch(
|
.switch(
|
||||||
"signed",
|
"signed",
|
||||||
@ -33,7 +54,7 @@ impl Command for BitsShr {
|
|||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
"number-bytes",
|
"number-bytes",
|
||||||
SyntaxShape::String,
|
SyntaxShape::Int,
|
||||||
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||||
Some('n'),
|
Some('n'),
|
||||||
)
|
)
|
||||||
@ -41,7 +62,7 @@ impl Command for BitsShr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Bitwise shift right for ints."
|
"Bitwise shift right for ints or binary values."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
@ -58,27 +79,22 @@ impl Command for BitsShr {
|
|||||||
let head = call.head;
|
let head = call.head;
|
||||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||||
let number_bytes: Option<Spanned<String>> =
|
let number_bytes: Option<Spanned<usize>> =
|
||||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
let bytes_len = get_number_bytes(number_bytes.as_ref());
|
let number_size = get_number_bytes(number_bytes, head)?;
|
||||||
if let NumberBytes::Invalid = bytes_len {
|
|
||||||
if let Some(val) = number_bytes {
|
|
||||||
return Err(ShellError::UnsupportedInput {
|
|
||||||
msg: "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
|
||||||
input: "value originates from here".to_string(),
|
|
||||||
msg_span: head,
|
|
||||||
input_span: val.span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This doesn't match explicit nulls
|
// This doesn't match explicit nulls
|
||||||
if matches!(input, PipelineData::Empty) {
|
if matches!(input, PipelineData::Empty) {
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
}
|
}
|
||||||
input.map(
|
|
||||||
move |value| operate(value, bits, head, signed, bytes_len),
|
let args = Arguments {
|
||||||
engine_state.ctrlc.clone(),
|
signed,
|
||||||
)
|
number_size,
|
||||||
|
bits,
|
||||||
|
};
|
||||||
|
|
||||||
|
operate(action, args, input, head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -96,75 +112,75 @@ impl Command for BitsShr {
|
|||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Shift right a binary value",
|
||||||
|
example: "0x[4f f4] | bits shr 4",
|
||||||
|
result: Some(Value::binary(vec![0x04, 0xff], Span::test_data())),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_shift_right<T: CheckedShr + Display + Copy>(val: T, bits: u32, span: Span) -> Value
|
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
where
|
let Arguments {
|
||||||
i64: std::convert::TryFrom<T>,
|
signed,
|
||||||
{
|
number_size,
|
||||||
match val.checked_shr(bits) {
|
bits,
|
||||||
Some(val) => {
|
} = *args;
|
||||||
let shift_result = i64::try_from(val);
|
|
||||||
match shift_result {
|
|
||||||
Ok(val) => Value::int( val, span ),
|
|
||||||
Err(_) => Value::error(
|
|
||||||
ShellError::GenericError {
|
|
||||||
error: "Shift right result beyond the range of 64 bit signed number".into(),
|
|
||||||
msg: format!(
|
|
||||||
"{val} of the specified number of bytes shift right {bits} bits exceed limit"
|
|
||||||
),
|
|
||||||
span: Some(span),
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => Value::error(
|
|
||||||
ShellError::GenericError {
|
|
||||||
error: "Shift right failed".into(),
|
|
||||||
msg: format!("{val} shift right {bits} bits failed, you may shift too many bits"),
|
|
||||||
span: Some(span),
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
match input {
|
||||||
let span = value.span();
|
|
||||||
match value {
|
|
||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
use InputNumType::*;
|
use InputNumType::*;
|
||||||
// let bits = (((bits % 64) + 64) % 64) as u32;
|
let val = *val;
|
||||||
let bits = bits as u32;
|
let bits = bits as u32;
|
||||||
let input_type = get_input_num_type(val, signed, number_size);
|
let input_num_type = get_input_num_type(val, signed, number_size);
|
||||||
match input_type {
|
|
||||||
One => get_shift_right(val as u8, bits, span),
|
let int = match input_num_type {
|
||||||
Two => get_shift_right(val as u16, bits, span),
|
One => ((val as u8) >> bits) as i64,
|
||||||
Four => get_shift_right(val as u32, bits, span),
|
Two => ((val as u16) >> bits) as i64,
|
||||||
Eight => get_shift_right(val as u64, bits, span),
|
Four => ((val as u32) >> bits) as i64,
|
||||||
SignedOne => get_shift_right(val as i8, bits, span),
|
Eight => ((val as u64) >> bits) as i64,
|
||||||
SignedTwo => get_shift_right(val as i16, bits, span),
|
SignedOne => ((val as i8) >> bits) as i64,
|
||||||
SignedFour => get_shift_right(val as i32, bits, span),
|
SignedTwo => ((val as i16) >> bits) as i64,
|
||||||
SignedEight => get_shift_right(val, bits, span),
|
SignedFour => ((val as i32) >> bits) as i64,
|
||||||
}
|
SignedEight => val >> bits,
|
||||||
|
};
|
||||||
|
|
||||||
|
Value::int(int, span)
|
||||||
|
}
|
||||||
|
Value::Binary { val, .. } => {
|
||||||
|
let byte_shift = bits / 8;
|
||||||
|
let bit_shift = bits % 8;
|
||||||
|
|
||||||
|
let len = val.len();
|
||||||
|
use itertools::Position::*;
|
||||||
|
let bytes = iter::repeat(0)
|
||||||
|
.take(byte_shift)
|
||||||
|
.chain(
|
||||||
|
val.iter()
|
||||||
|
.copied()
|
||||||
|
.circular_tuple_windows::<(u8, u8)>()
|
||||||
|
.with_position()
|
||||||
|
.map(|(pos, (lhs, rhs))| match pos {
|
||||||
|
First | Only => lhs >> bit_shift,
|
||||||
|
_ => (lhs >> bit_shift) | (rhs << bit_shift),
|
||||||
|
})
|
||||||
|
.take(len - byte_shift),
|
||||||
|
)
|
||||||
|
.collect::<Vec<u8>>();
|
||||||
|
|
||||||
|
Value::binary(bytes, span)
|
||||||
}
|
}
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
Value::Error { .. } => value,
|
Value::Error { .. } => input.clone(),
|
||||||
other => Value::error(
|
other => Value::error(
|
||||||
ShellError::OnlySupportsThisInputType {
|
ShellError::OnlySupportsThisInputType {
|
||||||
exp_input_type: "int".into(),
|
exp_input_type: "int or binary".into(),
|
||||||
wrong_type: other.get_type().to_string(),
|
wrong_type: other.get_type().to_string(),
|
||||||
dst_span: head,
|
dst_span: span,
|
||||||
src_span: other.span(),
|
src_span: other.span(),
|
||||||
},
|
},
|
||||||
head,
|
span,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
use super::binary_op;
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -17,17 +18,33 @@ impl Command for BitsXor {
|
|||||||
Signature::build("bits xor")
|
Signature::build("bits xor")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::Int, Type::Int),
|
(Type::Int, Type::Int),
|
||||||
|
(Type::Binary, Type::Binary),
|
||||||
(
|
(
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
Type::List(Box::new(Type::Int)),
|
Type::List(Box::new(Type::Int)),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
Type::List(Box::new(Type::Binary)),
|
||||||
|
Type::List(Box::new(Type::Binary)),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
.required("target", SyntaxShape::Int, "target int to perform bit xor")
|
.allow_variants_without_examples(true)
|
||||||
|
.required(
|
||||||
|
"target",
|
||||||
|
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]),
|
||||||
|
"right-hand side of the operation",
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"endian",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"byte encode endian, available options: native(default), little, big",
|
||||||
|
Some('e'),
|
||||||
|
)
|
||||||
.category(Category::Bits)
|
.category(Category::Bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Performs bitwise xor for ints."
|
"Performs bitwise xor for ints or binary values."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
@ -42,13 +59,32 @@ impl Command for BitsXor {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let target: i64 = call.req(engine_state, stack, 0)?;
|
let target: Value = call.req(engine_state, stack, 0)?;
|
||||||
|
let endian = call.get_flag::<Spanned<String>>(engine_state, stack, "endian")?;
|
||||||
|
|
||||||
|
let little_endian = if let Some(endian) = endian {
|
||||||
|
match endian.item.as_str() {
|
||||||
|
"native" => cfg!(target_endian = "little"),
|
||||||
|
"little" => true,
|
||||||
|
"big" => false,
|
||||||
|
_ => {
|
||||||
|
return Err(ShellError::TypeMismatch {
|
||||||
|
err_message: "Endian must be one of native, little, big".to_string(),
|
||||||
|
span: endian.span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg!(target_endian = "little")
|
||||||
|
};
|
||||||
|
|
||||||
// This doesn't match explicit nulls
|
// This doesn't match explicit nulls
|
||||||
if matches!(input, PipelineData::Empty) {
|
if matches!(input, PipelineData::Empty) {
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
}
|
}
|
||||||
|
|
||||||
input.map(
|
input.map(
|
||||||
move |value| operate(value, target, head),
|
move |value| binary_op(&value, &target, little_endian, |(l, r)| l ^ r, head),
|
||||||
engine_state.ctrlc.clone(),
|
engine_state.ctrlc.clone(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -61,35 +97,34 @@ impl Command for BitsXor {
|
|||||||
result: Some(Value::test_int(0)),
|
result: Some(Value::test_int(0)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Apply logical xor to a list of numbers",
|
description: "Apply bitwise xor to a list of numbers",
|
||||||
example: "[8 3 2] | bits xor 2",
|
example: "[8 3 2] | bits xor 2",
|
||||||
result: Some(Value::list(
|
result: Some(Value::test_list(vec![
|
||||||
vec![Value::test_int(10), Value::test_int(1), Value::test_int(0)],
|
Value::test_int(10),
|
||||||
Span::test_data(),
|
Value::test_int(1),
|
||||||
)),
|
Value::test_int(0),
|
||||||
|
])),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply bitwise xor to binary data",
|
||||||
|
example: "0x[ca fe] | bits xor 0x[ba be]",
|
||||||
|
result: Some(Value::test_binary(vec![0x70, 0x40])),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Apply bitwise xor to binary data of varying lengths with specified endianness",
|
||||||
|
example: "0x[ca fe] | bits xor 0x[aa] --endian big",
|
||||||
|
result: Some(Value::test_binary(vec![0xca, 0x54])),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply bitwise xor to input binary data smaller than the operand",
|
||||||
|
example: "0x[ff] | bits xor 0x[12 34 56] --endian little",
|
||||||
|
result: Some(Value::test_binary(vec![0xed, 0x34, 0x56])),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(value: Value, target: i64, head: Span) -> Value {
|
|
||||||
let span = value.span();
|
|
||||||
match value {
|
|
||||||
Value::Int { val, .. } => Value::int(val ^ target, span),
|
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
|
||||||
Value::Error { .. } => value,
|
|
||||||
other => Value::error(
|
|
||||||
ShellError::OnlySupportsThisInputType {
|
|
||||||
exp_input_type: "int".into(),
|
|
||||||
wrong_type: other.get_type().to_string(),
|
|
||||||
dst_span: head,
|
|
||||||
src_span: other.span(),
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -4,7 +4,7 @@ mod roll_left;
|
|||||||
mod roll_right;
|
mod roll_right;
|
||||||
mod roll_up;
|
mod roll_up;
|
||||||
|
|
||||||
use nu_protocol::{Record, ShellError, Value};
|
use nu_protocol::{ShellError, Value};
|
||||||
pub use roll_::Roll;
|
pub use roll_::Roll;
|
||||||
pub use roll_down::RollDown;
|
pub use roll_down::RollDown;
|
||||||
pub use roll_left::RollLeft;
|
pub use roll_left::RollLeft;
|
||||||
@ -70,10 +70,8 @@ fn horizontal_rotate_value(
|
|||||||
HorizontalDirection::Left => vals.rotate_left(rotations),
|
HorizontalDirection::Left => vals.rotate_left(rotations),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::record(
|
let record = cols.into_iter().zip(vals).collect();
|
||||||
Record::from_raw_cols_vals_unchecked(cols, vals),
|
Ok(Value::record(record, span))
|
||||||
span,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
let values = vals
|
let values = vals
|
||||||
|
@ -6,7 +6,6 @@ use nu_protocol::{
|
|||||||
PipelineIterator, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
PipelineIterator, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::iter::FromIterator;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct UpdateCells;
|
pub struct UpdateCells;
|
||||||
@ -114,14 +113,12 @@ impl Command for UpdateCells {
|
|||||||
// the columns to update
|
// the columns to update
|
||||||
let columns: Option<Value> = call.get_flag(&engine_state, &mut stack, "columns")?;
|
let columns: Option<Value> = call.get_flag(&engine_state, &mut stack, "columns")?;
|
||||||
let columns: Option<HashSet<String>> = match columns {
|
let columns: Option<HashSet<String>> = match columns {
|
||||||
Some(val) => {
|
Some(val) => Some(
|
||||||
let cols = val
|
val.into_list()?
|
||||||
.as_list()?
|
.into_iter()
|
||||||
.iter()
|
.map(Value::coerce_into_string)
|
||||||
.map(|val| val.as_string())
|
.collect::<Result<HashSet<String>, ShellError>>()?,
|
||||||
.collect::<Result<Vec<String>, ShellError>>()?;
|
),
|
||||||
Some(HashSet::from_iter(cols))
|
|
||||||
}
|
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -432,7 +432,7 @@ fn html_value(value: Value, config: &Config) -> String {
|
|||||||
output_string.push_str("</pre>");
|
output_string.push_str("</pre>");
|
||||||
}
|
}
|
||||||
other => output_string.push_str(
|
other => output_string.push_str(
|
||||||
&v_htmlescape::escape(&other.into_abbreviated_string(config))
|
&v_htmlescape::escape(&other.to_abbreviated_string(config))
|
||||||
.to_string()
|
.to_string()
|
||||||
.replace('\n', "<br>"),
|
.replace('\n', "<br>"),
|
||||||
),
|
),
|
||||||
|
@ -104,7 +104,7 @@ impl Command for SubCommand {
|
|||||||
fn value_to_color(v: Option<Value>) -> Result<Option<Rgb>, ShellError> {
|
fn value_to_color(v: Option<Value>) -> Result<Option<Rgb>, ShellError> {
|
||||||
let s = match v {
|
let s = match v {
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
Some(x) => x.as_string()?,
|
Some(x) => x.coerce_into_string()?,
|
||||||
};
|
};
|
||||||
Ok(Some(Rgb::from_hex_string(s)))
|
Ok(Some(Rgb::from_hex_string(s)))
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,8 @@ impl Command for FormatPattern {
|
|||||||
match specified_pattern {
|
match specified_pattern {
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
Ok(pattern) => {
|
Ok(pattern) => {
|
||||||
let string_pattern = pattern.as_string()?;
|
|
||||||
let string_span = pattern.span();
|
let string_span = pattern.span();
|
||||||
|
let string_pattern = pattern.coerce_into_string()?;
|
||||||
// the string span is start as `"`, we don't need the character
|
// the string span is start as `"`, we don't need the character
|
||||||
// to generate proper span for sub expression.
|
// to generate proper span for sub expression.
|
||||||
let ops = extract_formatting_operations(
|
let ops = extract_formatting_operations(
|
||||||
@ -287,7 +287,7 @@ fn format_record(
|
|||||||
.collect();
|
.collect();
|
||||||
match data_as_value.clone().follow_cell_path(&path_members, false) {
|
match data_as_value.clone().follow_cell_path(&path_members, false) {
|
||||||
Ok(value_at_column) => {
|
Ok(value_at_column) => {
|
||||||
output.push_str(value_at_column.into_string(", ", config).as_str())
|
output.push_str(value_at_column.to_expanded_string(", ", config).as_str())
|
||||||
}
|
}
|
||||||
Err(se) => return Err(se),
|
Err(se) => return Err(se),
|
||||||
}
|
}
|
||||||
@ -298,7 +298,7 @@ fn format_record(
|
|||||||
None => {
|
None => {
|
||||||
let parsed_result = eval_expression(engine_state, stack, &exp);
|
let parsed_result = eval_expression(engine_state, stack, &exp);
|
||||||
if let Ok(val) = parsed_result {
|
if let Ok(val) = parsed_result {
|
||||||
output.push_str(&val.into_abbreviated_string(config))
|
output.push_str(&val.to_abbreviated_string(config))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(err) => {
|
Some(err) => {
|
||||||
|
@ -6,19 +6,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-lang"
|
name = "nu-cmd-lang"
|
||||||
version = "0.90.1"
|
version = "0.91.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.90.1" }
|
nu-engine = { path = "../nu-engine", version = "0.91.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.90.1" }
|
nu-parser = { path = "../nu-parser", version = "0.91.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.90.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.91.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.90.1" }
|
nu-utils = { path = "../nu-utils", version = "0.91.0" }
|
||||||
nu-ansi-term = "0.50.0"
|
|
||||||
|
|
||||||
fancy-regex = "0.12"
|
|
||||||
itertools = "0.12"
|
itertools = "0.12"
|
||||||
shadow-rs = { version = "0.26", default-features = false }
|
shadow-rs = { version = "0.26", default-features = false }
|
||||||
|
|
||||||
|
@ -305,7 +305,7 @@ fn describe_value(
|
|||||||
| Value::Date { .. }
|
| Value::Date { .. }
|
||||||
| Value::Range { .. }
|
| Value::Range { .. }
|
||||||
| Value::String { .. }
|
| Value::String { .. }
|
||||||
| Value::QuotedString { .. }
|
| Value::Glob { .. }
|
||||||
| Value::Nothing { .. } => Value::record(
|
| Value::Nothing { .. } => Value::record(
|
||||||
record!(
|
record!(
|
||||||
"type" => Value::string(value.get_type().to_string(), head),
|
"type" => Value::string(value.get_type().to_string(), head),
|
||||||
|
@ -4,8 +4,8 @@ use nu_engine::{eval_block_with_early_return, redirect_env, CallExt};
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
|
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, SyntaxShape,
|
Category, Example, IntoSpanned, ListStream, PipelineData, RawStream, ShellError, Signature,
|
||||||
Type, Value,
|
SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -147,23 +147,25 @@ impl Command for Do {
|
|||||||
// consumes the first 65535 bytes
|
// consumes the first 65535 bytes
|
||||||
// So we need a thread to receive stdout message, then the current thread can continue to consume
|
// So we need a thread to receive stdout message, then the current thread can continue to consume
|
||||||
// stderr messages.
|
// stderr messages.
|
||||||
let stdout_handler = stdout.map(|stdout_stream| {
|
let stdout_handler = stdout
|
||||||
thread::Builder::new()
|
.map(|stdout_stream| {
|
||||||
.name("stderr redirector".to_string())
|
thread::Builder::new()
|
||||||
.spawn(move || {
|
.name("stderr redirector".to_string())
|
||||||
let ctrlc = stdout_stream.ctrlc.clone();
|
.spawn(move || {
|
||||||
let span = stdout_stream.span;
|
let ctrlc = stdout_stream.ctrlc.clone();
|
||||||
RawStream::new(
|
let span = stdout_stream.span;
|
||||||
Box::new(
|
RawStream::new(
|
||||||
vec![stdout_stream.into_bytes().map(|s| s.item)].into_iter(),
|
Box::new(std::iter::once(
|
||||||
),
|
stdout_stream.into_bytes().map(|s| s.item),
|
||||||
ctrlc,
|
)),
|
||||||
span,
|
ctrlc,
|
||||||
None,
|
span,
|
||||||
)
|
None,
|
||||||
})
|
)
|
||||||
.expect("Failed to create thread")
|
})
|
||||||
});
|
.map_err(|e| e.into_spanned(call.head))
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
// Intercept stderr so we can return it in the error if the exit code is non-zero.
|
// Intercept stderr so we can return it in the error if the exit code is non-zero.
|
||||||
// The threading issues mentioned above dictate why we also need to intercept stdout.
|
// The threading issues mentioned above dictate why we also need to intercept stdout.
|
||||||
@ -213,7 +215,7 @@ impl Command for Do {
|
|||||||
Ok(PipelineData::ExternalStream {
|
Ok(PipelineData::ExternalStream {
|
||||||
stdout,
|
stdout,
|
||||||
stderr: Some(RawStream::new(
|
stderr: Some(RawStream::new(
|
||||||
Box::new(vec![Ok(stderr_msg.into_bytes())].into_iter()),
|
Box::new(std::iter::once(Ok(stderr_msg.into_bytes()))),
|
||||||
stderr_ctrlc,
|
stderr_ctrlc,
|
||||||
span,
|
span,
|
||||||
None,
|
None,
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use nu_engine::{eval_block, CallExt};
|
use nu_engine::{eval_block, CallExt};
|
||||||
@ -5,7 +7,7 @@ use nu_protocol::ast::Call;
|
|||||||
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
|
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoPipelineData, LazyRecord, PipelineData, ShellError, Signature, Span,
|
Category, Example, IntoPipelineData, LazyRecord, PipelineData, ShellError, Signature, Span,
|
||||||
SyntaxShape, Type, Value,
|
Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -58,18 +60,36 @@ impl Command for LazyMake {
|
|||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let span = call.head;
|
let span = call.head;
|
||||||
let columns: Vec<String> = call
|
let columns: Vec<Spanned<String>> = call
|
||||||
.get_flag(engine_state, stack, "columns")?
|
.get_flag(engine_state, stack, "columns")?
|
||||||
.expect("required flag");
|
.expect("required flag");
|
||||||
|
|
||||||
let get_value: Closure = call
|
let get_value: Closure = call
|
||||||
.get_flag(engine_state, stack, "get-value")?
|
.get_flag(engine_state, stack, "get-value")?
|
||||||
.expect("required flag");
|
.expect("required flag");
|
||||||
|
|
||||||
|
let mut unique = HashMap::with_capacity(columns.len());
|
||||||
|
|
||||||
|
for col in &columns {
|
||||||
|
match unique.entry(&col.item) {
|
||||||
|
Entry::Occupied(entry) => {
|
||||||
|
return Err(ShellError::ColumnDefinedTwice {
|
||||||
|
col_name: col.item.clone(),
|
||||||
|
second_use: col.span,
|
||||||
|
first_use: *entry.get(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
entry.insert(col.span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Value::lazy_record(
|
Ok(Value::lazy_record(
|
||||||
Box::new(NuLazyRecord {
|
Box::new(NuLazyRecord {
|
||||||
engine_state: engine_state.clone(),
|
engine_state: engine_state.clone(),
|
||||||
stack: Arc::new(Mutex::new(stack.clone())),
|
stack: Arc::new(Mutex::new(stack.clone())),
|
||||||
columns,
|
columns: columns.into_iter().map(|s| s.item).collect(),
|
||||||
get_value,
|
get_value,
|
||||||
span,
|
span,
|
||||||
}),
|
}),
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Let;
|
pub struct Let;
|
||||||
@ -61,8 +63,24 @@ impl Command for Let {
|
|||||||
.expect("internal error: missing right hand side");
|
.expect("internal error: missing right hand side");
|
||||||
|
|
||||||
let block = engine_state.get_block(block_id);
|
let block = engine_state.get_block(block_id);
|
||||||
|
|
||||||
let pipeline_data = eval_block(engine_state, stack, block, input, true, false)?;
|
let pipeline_data = eval_block(engine_state, stack, block, input, true, false)?;
|
||||||
stack.add_var(var_id, pipeline_data.into_value(call.head));
|
let mut value = pipeline_data.into_value(call.head);
|
||||||
|
|
||||||
|
// if given variable type is Glob, and our result is string
|
||||||
|
// then nushell need to convert from Value::String to Value::Glob
|
||||||
|
// it's assigned by demand, then it's not quoted, and it's required to expand
|
||||||
|
// if we pass it to other commands.
|
||||||
|
let var_type = &engine_state.get_var(var_id).ty;
|
||||||
|
let val_span = value.span();
|
||||||
|
match value {
|
||||||
|
Value::String { val, .. } if var_type == &Type::Glob => {
|
||||||
|
value = Value::glob(val, false, val_span);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
stack.add_var(var_id, value);
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Mut;
|
pub struct Mut;
|
||||||
@ -69,10 +71,22 @@ impl Command for Mut {
|
|||||||
call.redirect_stdout,
|
call.redirect_stdout,
|
||||||
call.redirect_stderr,
|
call.redirect_stderr,
|
||||||
)?;
|
)?;
|
||||||
|
let mut value = pipeline_data.into_value(call.head);
|
||||||
|
|
||||||
//println!("Adding: {:?} to {}", rhs, var_id);
|
// if given variable type is Glob, and our result is string
|
||||||
|
// then nushell need to convert from Value::String to Value::Glob
|
||||||
|
// it's assigned by demand, then it's not quoted, and it's required to expand
|
||||||
|
// if we pass it to other commands.
|
||||||
|
let var_type = &engine_state.get_var(var_id).ty;
|
||||||
|
let val_span = value.span();
|
||||||
|
match value {
|
||||||
|
Value::String { val, .. } if var_type == &Type::Glob => {
|
||||||
|
value = Value::glob(val, false, val_span);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
stack.add_var(var_id, pipeline_data.into_value(call.head));
|
stack.add_var(var_id, value);
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,7 +228,7 @@ impl<'a> std::fmt::Debug for DebuggableValue<'a> {
|
|||||||
val.from, val.to, val.incr
|
val.from, val.to, val.incr
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
Value::String { val, .. } | Value::QuotedString { val, .. } => {
|
Value::String { val, .. } | Value::Glob { val, .. } => {
|
||||||
write!(f, "{:?}", val)
|
write!(f, "{:?}", val)
|
||||||
}
|
}
|
||||||
Value::Record { val, .. } => {
|
Value::Record { val, .. } => {
|
||||||
|
@ -5,19 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-color-config"
|
name = "nu-color-config"
|
||||||
version = "0.90.1"
|
version = "0.91.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.90.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.91.0" }
|
||||||
nu-ansi-term = "0.50.0"
|
nu-ansi-term = "0.50.0"
|
||||||
nu-utils = { path = "../nu-utils", version = "0.90.1" }
|
nu-engine = { path = "../nu-engine", version = "0.91.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.90.1" }
|
nu-json = { path = "../nu-json", version = "0.91.0" }
|
||||||
nu-json = { path = "../nu-json", version = "0.90.1" }
|
|
||||||
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.90.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.91.0" }
|
||||||
|
@ -6,7 +6,7 @@ pub fn get_matching_brackets_style(default_style: Style, conf: &Config) -> Style
|
|||||||
const MATCHING_BRACKETS_CONFIG_KEY: &str = "shape_matching_brackets";
|
const MATCHING_BRACKETS_CONFIG_KEY: &str = "shape_matching_brackets";
|
||||||
|
|
||||||
match conf.color_config.get(MATCHING_BRACKETS_CONFIG_KEY) {
|
match conf.color_config.get(MATCHING_BRACKETS_CONFIG_KEY) {
|
||||||
Some(int_color) => match int_color.as_string() {
|
Some(int_color) => match int_color.coerce_str() {
|
||||||
Ok(int_color) => merge_styles(default_style, lookup_ansi_color_style(&int_color)),
|
Ok(int_color) => merge_styles(default_style, lookup_ansi_color_style(&int_color)),
|
||||||
Err(_) => default_style,
|
Err(_) => default_style,
|
||||||
},
|
},
|
||||||
|
@ -104,7 +104,7 @@ pub fn color_record_to_nustyle(value: &Value) -> Style {
|
|||||||
for (k, v) in record {
|
for (k, v) in record {
|
||||||
// Because config already type-checked the color_config records, this doesn't bother giving errors
|
// Because config already type-checked the color_config records, this doesn't bother giving errors
|
||||||
// if there are unrecognised keys or bad values.
|
// if there are unrecognised keys or bad values.
|
||||||
if let Ok(v) = v.as_string() {
|
if let Ok(v) = v.coerce_string() {
|
||||||
match k.as_str() {
|
match k.as_str() {
|
||||||
"fg" => fg = Some(v),
|
"fg" => fg = Some(v),
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ impl<'a> StyleComputer<'a> {
|
|||||||
Value::Range { .. } => TextStyle::with_style(Left, s),
|
Value::Range { .. } => TextStyle::with_style(Left, s),
|
||||||
Value::Float { .. } => TextStyle::with_style(Right, s),
|
Value::Float { .. } => TextStyle::with_style(Right, s),
|
||||||
Value::String { .. } => TextStyle::with_style(Left, s),
|
Value::String { .. } => TextStyle::with_style(Left, s),
|
||||||
Value::QuotedString { .. } => TextStyle::with_style(Left, s),
|
Value::Glob { .. } => TextStyle::with_style(Left, s),
|
||||||
Value::Nothing { .. } => TextStyle::with_style(Left, s),
|
Value::Nothing { .. } => TextStyle::with_style(Left, s),
|
||||||
Value::Binary { .. } => TextStyle::with_style(Left, s),
|
Value::Binary { .. } => TextStyle::with_style(Left, s),
|
||||||
Value::CellPath { .. } => TextStyle::with_style(Left, s),
|
Value::CellPath { .. } => TextStyle::with_style(Left, s),
|
||||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-command"
|
name = "nu-command"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||||
version = "0.90.1"
|
version = "0.91.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -14,26 +14,26 @@ bench = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-ansi-term = "0.50.0"
|
nu-ansi-term = "0.50.0"
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.90.1" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.91.0" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.90.1" }
|
nu-color-config = { path = "../nu-color-config", version = "0.91.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.90.1" }
|
nu-engine = { path = "../nu-engine", version = "0.91.0" }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.90.1" }
|
nu-glob = { path = "../nu-glob", version = "0.91.0" }
|
||||||
nu-json = { path = "../nu-json", version = "0.90.1" }
|
nu-json = { path = "../nu-json", version = "0.91.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.90.1" }
|
nu-parser = { path = "../nu-parser", version = "0.91.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.90.1" }
|
nu-path = { path = "../nu-path", version = "0.91.0" }
|
||||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.90.1" }
|
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.91.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.90.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.91.0" }
|
||||||
nu-system = { path = "../nu-system", version = "0.90.1" }
|
nu-system = { path = "../nu-system", version = "0.91.0" }
|
||||||
nu-table = { path = "../nu-table", version = "0.90.1" }
|
nu-table = { path = "../nu-table", version = "0.91.0" }
|
||||||
nu-term-grid = { path = "../nu-term-grid", version = "0.90.1" }
|
nu-term-grid = { path = "../nu-term-grid", version = "0.91.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.90.1" }
|
nu-utils = { path = "../nu-utils", version = "0.91.0" }
|
||||||
|
|
||||||
alphanumeric-sort = "1.5"
|
alphanumeric-sort = "1.5"
|
||||||
base64 = "0.21"
|
base64 = "0.21"
|
||||||
byteorder = "1.5"
|
byteorder = "1.5"
|
||||||
bytesize = "1.3"
|
bytesize = "1.3"
|
||||||
calamine = "0.23.1"
|
calamine = { version = "0.24.0", features = ["dates"] }
|
||||||
chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false }
|
chrono = { version = "0.4.34", features = ["std", "unstable-locales", "clock"], default-features = false }
|
||||||
chrono-humanize = "0.2.3"
|
chrono-humanize = "0.2.3"
|
||||||
chrono-tz = "0.8"
|
chrono-tz = "0.8"
|
||||||
crossterm = "0.27"
|
crossterm = "0.27"
|
||||||
@ -42,23 +42,21 @@ dialoguer = { default-features = false, features = ["fuzzy-select"], version = "
|
|||||||
digest = { default-features = false, version = "0.10" }
|
digest = { default-features = false, version = "0.10" }
|
||||||
dtparse = "2.0"
|
dtparse = "2.0"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
fancy-regex = "0.12"
|
fancy-regex = "0.13"
|
||||||
filesize = "0.2"
|
filesize = "0.2"
|
||||||
filetime = "0.2"
|
filetime = "0.2"
|
||||||
fs_extra = "1.3"
|
fs_extra = "1.3"
|
||||||
human-date-parser = "0.1.1"
|
human-date-parser = "0.1.1"
|
||||||
indexmap = "2.1"
|
indexmap = "2.2"
|
||||||
indicatif = "0.17"
|
indicatif = "0.17"
|
||||||
itertools = "0.12"
|
itertools = "0.12"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
lscolors = { version = "0.17", default-features = false, features = ["nu-ansi-term"] }
|
lscolors = { version = "0.17", default-features = false, features = ["nu-ansi-term"] }
|
||||||
md5 = { package = "md-5", version = "0.10" }
|
md5 = { package = "md-5", version = "0.10" }
|
||||||
miette = { version = "5.10", features = ["fancy-no-backtrace"] }
|
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
mime_guess = "2.0"
|
mime_guess = "2.0"
|
||||||
native-tls = "0.2"
|
native-tls = "0.2"
|
||||||
notify-debouncer-full = { version = "0.3", default-features = false }
|
notify-debouncer-full = { version = "0.3", default-features = false }
|
||||||
num = { version = "0.4", optional = true }
|
|
||||||
num-format = { version = "0.4" }
|
num-format = { version = "0.4" }
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
once_cell = "1.18"
|
once_cell = "1.18"
|
||||||
@ -71,8 +69,8 @@ quick-xml = "0.31.0"
|
|||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rayon = "1.8"
|
rayon = "1.8"
|
||||||
regex = "1.9.5"
|
regex = "1.9.5"
|
||||||
roxmltree = "0.18"
|
roxmltree = "0.19"
|
||||||
rusqlite = { version = "0.29", features = ["bundled", "backup", "chrono"], optional = true }
|
rusqlite = { version = "0.31", features = ["bundled", "backup", "chrono"], optional = true }
|
||||||
same-file = "1.0"
|
same-file = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||||
@ -84,7 +82,7 @@ tabled = { version = "0.14.0", features = ["color"], default-features = false }
|
|||||||
terminal_size = "0.3"
|
terminal_size = "0.3"
|
||||||
titlecase = "2.0"
|
titlecase = "2.0"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
unicode-segmentation = "1.10"
|
unicode-segmentation = "1.11"
|
||||||
ureq = { version = "2.9", default-features = false, features = ["charset", "gzip", "json", "native-tls"] }
|
ureq = { version = "2.9", default-features = false, features = ["charset", "gzip", "json", "native-tls"] }
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
uu_mv = "0.0.23"
|
uu_mv = "0.0.23"
|
||||||
@ -95,7 +93,7 @@ uu_mktemp = "0.0.23"
|
|||||||
uuid = { version = "1.6", features = ["v4"] }
|
uuid = { version = "1.6", features = ["v4"] }
|
||||||
v_htmlescape = "0.15.0"
|
v_htmlescape = "0.15.0"
|
||||||
wax = { version = "0.6" }
|
wax = { version = "0.6" }
|
||||||
which = { version = "5.0", optional = true }
|
which = { version = "6.0", optional = true }
|
||||||
bracoxide = "0.1.2"
|
bracoxide = "0.1.2"
|
||||||
chardetng = "0.1.17"
|
chardetng = "0.1.17"
|
||||||
|
|
||||||
@ -112,7 +110,7 @@ procfs = "0.16.0"
|
|||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
|
||||||
optional = true
|
optional = true
|
||||||
version = "3.1"
|
version = "3.3"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies.windows]
|
[target.'cfg(windows)'.dependencies.windows]
|
||||||
features = [
|
features = [
|
||||||
@ -132,11 +130,11 @@ trash-support = ["trash"]
|
|||||||
which-support = ["which"]
|
which-support = ["which"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.90.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.91.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.90.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.91.0" }
|
||||||
|
|
||||||
dirs-next = "2.0"
|
dirs-next = "2.0"
|
||||||
mockito = { version = "1.2", default-features = false }
|
mockito = { version = "1.3", default-features = false }
|
||||||
quickcheck = "1.0"
|
quickcheck = "1.0"
|
||||||
quickcheck_macros = "1.0"
|
quickcheck_macros = "1.0"
|
||||||
rstest = { version = "0.18", default-features = false }
|
rstest = { version = "0.18", default-features = false }
|
||||||
|
@ -4,7 +4,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
record, Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
|
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
|
||||||
Spanned, SyntaxShape, Type, Value,
|
Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -127,15 +127,16 @@ impl Command for Histogram {
|
|||||||
|
|
||||||
let span = call.head;
|
let span = call.head;
|
||||||
let data_as_value = input.into_value(span);
|
let data_as_value = input.into_value(span);
|
||||||
|
let value_span = data_as_value.span();
|
||||||
// `input` is not a list, here we can return an error.
|
// `input` is not a list, here we can return an error.
|
||||||
run_histogram(
|
run_histogram(
|
||||||
data_as_value.as_list()?.to_vec(),
|
data_as_value.into_list()?,
|
||||||
column_name,
|
column_name,
|
||||||
frequency_column_name,
|
frequency_column_name,
|
||||||
calc_method,
|
calc_method,
|
||||||
span,
|
span,
|
||||||
// Note that as_list() filters out Value::Error here.
|
// Note that as_list() filters out Value::Error here.
|
||||||
data_as_value.span(),
|
value_span,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -238,13 +239,6 @@ fn histogram_impl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
let result_cols = vec![
|
|
||||||
value_column_name.to_string(),
|
|
||||||
"count".to_string(),
|
|
||||||
"quantile".to_string(),
|
|
||||||
"percentage".to_string(),
|
|
||||||
freq_column.to_string(),
|
|
||||||
];
|
|
||||||
const MAX_FREQ_COUNT: f64 = 100.0;
|
const MAX_FREQ_COUNT: f64 = 100.0;
|
||||||
for (val, count) in counter.into_iter().sorted() {
|
for (val, count) in counter.into_iter().sorted() {
|
||||||
let quantile = match calc_method {
|
let quantile = match calc_method {
|
||||||
@ -258,16 +252,13 @@ fn histogram_impl(
|
|||||||
result.push((
|
result.push((
|
||||||
count, // attach count first for easily sorting.
|
count, // attach count first for easily sorting.
|
||||||
Value::record(
|
Value::record(
|
||||||
Record::from_raw_cols_vals_unchecked(
|
record! {
|
||||||
result_cols.clone(),
|
value_column_name => val.into_value(),
|
||||||
vec![
|
"count" => Value::int(count, span),
|
||||||
val.into_value(),
|
"quantile" => Value::float(quantile, span),
|
||||||
Value::int(count, span),
|
"percentage" => Value::string(percentage, span),
|
||||||
Value::float(quantile, span),
|
freq_column => Value::string(freq, span),
|
||||||
Value::string(percentage, span),
|
},
|
||||||
Value::string(freq, span),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
span,
|
span,
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
@ -261,11 +261,12 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
|
|
||||||
// Let's try dtparse first
|
// Let's try dtparse first
|
||||||
if matches!(input, Value::String { .. }) && dateformat.is_none() {
|
if matches!(input, Value::String { .. }) && dateformat.is_none() {
|
||||||
if let Ok(input_val) = input.as_spanned_string() {
|
let span = input.span();
|
||||||
match parse_date_from_string(&input_val.item, input_val.span) {
|
if let Ok(input_val) = input.coerce_str() {
|
||||||
Ok(date) => return Value::date(date, input_val.span),
|
match parse_date_from_string(&input_val, span) {
|
||||||
|
Ok(date) => return Value::date(date, span),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
if let Ok(date) = from_human_time(&input_val.item) {
|
if let Ok(date) = from_human_time(&input_val) {
|
||||||
match date {
|
match date {
|
||||||
ParseResult::Date(date) => {
|
ParseResult::Date(date) => {
|
||||||
let time = NaiveTime::from_hms_opt(0, 0, 0).expect("valid time");
|
let time = NaiveTime::from_hms_opt(0, 0, 0).expect("valid time");
|
||||||
@ -274,10 +275,10 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
combined,
|
combined,
|
||||||
*Local::now().offset(),
|
*Local::now().offset(),
|
||||||
);
|
);
|
||||||
return Value::date(dt_fixed, input_val.span);
|
return Value::date(dt_fixed, span);
|
||||||
}
|
}
|
||||||
ParseResult::DateTime(date) => {
|
ParseResult::DateTime(date) => {
|
||||||
return Value::date(date.fixed_offset(), input_val.span)
|
return Value::date(date.fixed_offset(), span)
|
||||||
}
|
}
|
||||||
ParseResult::Time(time) => {
|
ParseResult::Time(time) => {
|
||||||
let date = Local::now().date_naive();
|
let date = Local::now().date_naive();
|
||||||
@ -286,7 +287,7 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
combined,
|
combined,
|
||||||
*Local::now().offset(),
|
*Local::now().offset(),
|
||||||
);
|
);
|
||||||
return Value::date(dt_fixed, input_val.span);
|
return Value::date(dt_fixed, span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -338,7 +339,7 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
}
|
}
|
||||||
None => Value::error(
|
None => Value::error(
|
||||||
ShellError::DatetimeParseError {
|
ShellError::DatetimeParseError {
|
||||||
msg: input.debug_value(),
|
msg: input.to_debug_string(),
|
||||||
span: *span,
|
span: *span,
|
||||||
},
|
},
|
||||||
*span,
|
*span,
|
||||||
@ -351,7 +352,7 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
}
|
}
|
||||||
None => Value::error(
|
None => Value::error(
|
||||||
ShellError::DatetimeParseError {
|
ShellError::DatetimeParseError {
|
||||||
msg: input.debug_value(),
|
msg: input.to_debug_string(),
|
||||||
span: *span,
|
span: *span,
|
||||||
},
|
},
|
||||||
*span,
|
*span,
|
||||||
|
133
crates/nu-command/src/conversions/into/glob.rs
Normal file
133
crates/nu-command/src/conversions/into/glob.rs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::{Call, CellPath},
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
|
Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"into glob"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("into glob")
|
||||||
|
.input_output_types(vec![
|
||||||
|
(Type::String, Type::Glob),
|
||||||
|
(
|
||||||
|
Type::List(Box::new(Type::String)),
|
||||||
|
Type::List(Box::new(Type::Glob)),
|
||||||
|
),
|
||||||
|
(Type::Table(vec![]), Type::Table(vec![])),
|
||||||
|
(Type::Record(vec![]), Type::Record(vec![])),
|
||||||
|
])
|
||||||
|
.allow_variants_without_examples(true) // https://github.com/nushell/nushell/issues/7032
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"For a data structure input, convert data at the given cell paths.",
|
||||||
|
)
|
||||||
|
.category(Category::Conversions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Convert value to glob."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["convert", "text"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
glob_helper(engine_state, stack, call, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "convert string to glob",
|
||||||
|
example: "'1234' | into glob",
|
||||||
|
result: Some(Value::test_glob("1234")),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "convert filepath to glob",
|
||||||
|
example: "ls Cargo.toml | get name | into glob",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn glob_helper(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let cell_paths = call.rest(engine_state, stack, 0)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let args = Arguments { cell_paths };
|
||||||
|
match input {
|
||||||
|
PipelineData::ExternalStream { stdout: None, .. } => {
|
||||||
|
Ok(Value::glob(String::new(), false, head).into_pipeline_data())
|
||||||
|
}
|
||||||
|
PipelineData::ExternalStream {
|
||||||
|
stdout: Some(stream),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// TODO: in the future, we may want this to stream out, converting each to bytes
|
||||||
|
let output = stream.into_string()?;
|
||||||
|
Ok(Value::glob(output.item, false, head).into_pipeline_data())
|
||||||
|
}
|
||||||
|
_ => operate(action, args, input, head, engine_state.ctrlc.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
||||||
|
match input {
|
||||||
|
Value::String { val, .. } => Value::glob(val.to_string(), false, span),
|
||||||
|
x => Value::error(
|
||||||
|
ShellError::CantConvert {
|
||||||
|
to_type: String::from("glob"),
|
||||||
|
from_type: x.get_type().to_string(),
|
||||||
|
span,
|
||||||
|
help: None,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ use nu_utils::get_system_locale;
|
|||||||
struct Arguments {
|
struct Arguments {
|
||||||
radix: u32,
|
radix: u32,
|
||||||
cell_paths: Option<Vec<CellPath>>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
signed: bool,
|
||||||
little_endian: bool,
|
little_endian: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +80,11 @@ impl Command for SubCommand {
|
|||||||
"byte encode endian, available options: native(default), little, big",
|
"byte encode endian, available options: native(default), little, big",
|
||||||
Some('e'),
|
Some('e'),
|
||||||
)
|
)
|
||||||
|
.switch(
|
||||||
|
"signed",
|
||||||
|
"always treat input number as a signed number",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
@ -148,9 +154,12 @@ impl Command for SubCommand {
|
|||||||
None => cfg!(target_endian = "little"),
|
None => cfg!(target_endian = "little"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||||
|
|
||||||
let args = Arguments {
|
let args = Arguments {
|
||||||
radix,
|
radix,
|
||||||
little_endian,
|
little_endian,
|
||||||
|
signed,
|
||||||
cell_paths,
|
cell_paths,
|
||||||
};
|
};
|
||||||
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
@ -221,12 +230,23 @@ impl Command for SubCommand {
|
|||||||
example: "'0010132' | into int --radix 8",
|
example: "'0010132' | into int --radix 8",
|
||||||
result: Some(Value::test_int(4186)),
|
result: Some(Value::test_int(4186)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert binary value to int",
|
||||||
|
example: "0x[10] | into int",
|
||||||
|
result: Some(Value::test_int(16)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert binary value to signed int",
|
||||||
|
example: "0x[a0] | into int --signed",
|
||||||
|
result: Some(Value::test_int(-96)),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
let radix = args.radix;
|
let radix = args.radix;
|
||||||
|
let signed = args.signed;
|
||||||
let little_endian = args.little_endian;
|
let little_endian = args.little_endian;
|
||||||
let val_span = input.span();
|
let val_span = input.span();
|
||||||
match input {
|
match input {
|
||||||
@ -307,21 +327,42 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
use byteorder::{BigEndian, ByteOrder, LittleEndian};
|
use byteorder::{BigEndian, ByteOrder, LittleEndian};
|
||||||
|
|
||||||
let mut val = val.to_vec();
|
let mut val = val.to_vec();
|
||||||
|
let size = val.len();
|
||||||
|
|
||||||
if little_endian {
|
if size == 0 {
|
||||||
while val.len() < 8 {
|
return Value::int(0, span);
|
||||||
val.push(0);
|
}
|
||||||
|
|
||||||
|
if size > 8 {
|
||||||
|
return Value::error(
|
||||||
|
ShellError::IncorrectValue {
|
||||||
|
msg: format!("binary input is too large to convert to int ({size} bytes)"),
|
||||||
|
val_span,
|
||||||
|
call_span: span,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
match (little_endian, signed) {
|
||||||
|
(true, true) => Value::int(LittleEndian::read_int(&val, size), span),
|
||||||
|
(false, true) => Value::int(BigEndian::read_int(&val, size), span),
|
||||||
|
(true, false) => {
|
||||||
|
while val.len() < 8 {
|
||||||
|
val.push(0);
|
||||||
|
}
|
||||||
|
val.resize(8, 0);
|
||||||
|
|
||||||
|
Value::int(LittleEndian::read_i64(&val), span)
|
||||||
}
|
}
|
||||||
val.resize(8, 0);
|
(false, false) => {
|
||||||
|
while val.len() < 8 {
|
||||||
|
val.insert(0, 0);
|
||||||
|
}
|
||||||
|
val.resize(8, 0);
|
||||||
|
|
||||||
Value::int(LittleEndian::read_i64(&val), val_span)
|
Value::int(BigEndian::read_i64(&val), span)
|
||||||
} else {
|
|
||||||
while val.len() < 8 {
|
|
||||||
val.insert(0, 0);
|
|
||||||
}
|
}
|
||||||
val.resize(8, 0);
|
|
||||||
|
|
||||||
Value::int(BigEndian::read_i64(&val), val_span)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
@ -497,6 +538,7 @@ mod test {
|
|||||||
&Arguments {
|
&Arguments {
|
||||||
radix: 10,
|
radix: 10,
|
||||||
cell_paths: None,
|
cell_paths: None,
|
||||||
|
signed: false,
|
||||||
little_endian: false,
|
little_endian: false,
|
||||||
},
|
},
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
@ -512,6 +554,7 @@ mod test {
|
|||||||
&Arguments {
|
&Arguments {
|
||||||
radix: 10,
|
radix: 10,
|
||||||
cell_paths: None,
|
cell_paths: None,
|
||||||
|
signed: false,
|
||||||
little_endian: false,
|
little_endian: false,
|
||||||
},
|
},
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
@ -527,6 +570,7 @@ mod test {
|
|||||||
&Arguments {
|
&Arguments {
|
||||||
radix: 16,
|
radix: 16,
|
||||||
cell_paths: None,
|
cell_paths: None,
|
||||||
|
signed: false,
|
||||||
little_endian: false,
|
little_endian: false,
|
||||||
},
|
},
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
@ -543,6 +587,7 @@ mod test {
|
|||||||
&Arguments {
|
&Arguments {
|
||||||
radix: 10,
|
radix: 10,
|
||||||
cell_paths: None,
|
cell_paths: None,
|
||||||
|
signed: false,
|
||||||
little_endian: false,
|
little_endian: false,
|
||||||
},
|
},
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
@ -565,6 +610,7 @@ mod test {
|
|||||||
&Arguments {
|
&Arguments {
|
||||||
radix: 10,
|
radix: 10,
|
||||||
cell_paths: None,
|
cell_paths: None,
|
||||||
|
signed: false,
|
||||||
little_endian: false,
|
little_endian: false,
|
||||||
},
|
},
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
@ -587,6 +633,7 @@ mod test {
|
|||||||
&Arguments {
|
&Arguments {
|
||||||
radix: 10,
|
radix: 10,
|
||||||
cell_paths: None,
|
cell_paths: None,
|
||||||
|
signed: false,
|
||||||
little_endian: false,
|
little_endian: false,
|
||||||
},
|
},
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
|
@ -6,6 +6,7 @@ mod datetime;
|
|||||||
mod duration;
|
mod duration;
|
||||||
mod filesize;
|
mod filesize;
|
||||||
mod float;
|
mod float;
|
||||||
|
mod glob;
|
||||||
mod int;
|
mod int;
|
||||||
mod record;
|
mod record;
|
||||||
mod string;
|
mod string;
|
||||||
@ -19,6 +20,7 @@ pub use command::Into;
|
|||||||
pub use datetime::SubCommand as IntoDatetime;
|
pub use datetime::SubCommand as IntoDatetime;
|
||||||
pub use duration::SubCommand as IntoDuration;
|
pub use duration::SubCommand as IntoDuration;
|
||||||
pub use float::SubCommand as IntoFloat;
|
pub use float::SubCommand as IntoFloat;
|
||||||
|
pub use glob::SubCommand as IntoGlob;
|
||||||
pub use int::SubCommand as IntoInt;
|
pub use int::SubCommand as IntoInt;
|
||||||
pub use record::SubCommand as IntoRecord;
|
pub use record::SubCommand as IntoRecord;
|
||||||
pub use string::SubCommand as IntoString;
|
pub use string::SubCommand as IntoString;
|
||||||
|
@ -36,6 +36,7 @@ impl Command for SubCommand {
|
|||||||
(Type::Int, Type::String),
|
(Type::Int, Type::String),
|
||||||
(Type::Number, Type::String),
|
(Type::Number, Type::String),
|
||||||
(Type::String, Type::String),
|
(Type::String, Type::String),
|
||||||
|
(Type::Glob, Type::String),
|
||||||
(Type::Bool, Type::String),
|
(Type::Bool, Type::String),
|
||||||
(Type::Filesize, Type::String),
|
(Type::Filesize, Type::String),
|
||||||
(Type::Date, Type::String),
|
(Type::Date, Type::String),
|
||||||
@ -202,9 +203,12 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
Value::Bool { val, .. } => Value::string(val.to_string(), span),
|
Value::Bool { val, .. } => Value::string(val.to_string(), span),
|
||||||
Value::Date { val, .. } => Value::string(val.format("%c").to_string(), span),
|
Value::Date { val, .. } => Value::string(val.format("%c").to_string(), span),
|
||||||
Value::String { val, .. } => Value::string(val.to_string(), span),
|
Value::String { val, .. } => Value::string(val.to_string(), span),
|
||||||
|
Value::Glob { val, .. } => Value::string(val.to_string(), span),
|
||||||
|
|
||||||
Value::Filesize { val: _, .. } => Value::string(input.into_string(", ", config), span),
|
Value::Filesize { val: _, .. } => {
|
||||||
Value::Duration { val: _, .. } => Value::string(input.into_string("", config), span),
|
Value::string(input.to_expanded_string(", ", config), span)
|
||||||
|
}
|
||||||
|
Value::Duration { val: _, .. } => Value::string(input.to_expanded_string("", config), span),
|
||||||
|
|
||||||
Value::Error { error, .. } => Value::string(into_code(error).unwrap_or_default(), span),
|
Value::Error { error, .. } => Value::string(into_code(error).unwrap_or_default(), span),
|
||||||
Value::Nothing { .. } => Value::string("".to_string(), span),
|
Value::Nothing { .. } => Value::string("".to_string(), span),
|
||||||
|
@ -8,7 +8,7 @@ use nu_protocol::{
|
|||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::{Regex, RegexBuilder};
|
use regex::{Regex, RegexBuilder};
|
||||||
use std::{collections::HashSet, iter::FromIterator};
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct IntoValue;
|
pub struct IntoValue;
|
||||||
@ -27,6 +27,11 @@ impl Command for IntoValue {
|
|||||||
"list of columns to update",
|
"list of columns to update",
|
||||||
Some('c'),
|
Some('c'),
|
||||||
)
|
)
|
||||||
|
.switch(
|
||||||
|
"prefer-filesizes",
|
||||||
|
"For ints display them as human-readable file sizes",
|
||||||
|
Some('f'),
|
||||||
|
)
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.category(Category::Filters)
|
.category(Category::Filters)
|
||||||
}
|
}
|
||||||
@ -61,24 +66,24 @@ impl Command for IntoValue {
|
|||||||
let metadata = input.metadata();
|
let metadata = input.metadata();
|
||||||
let ctrlc = engine_state.ctrlc.clone();
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
let span = call.head;
|
let span = call.head;
|
||||||
|
let display_as_filesizes = call.has_flag(&engine_state, stack, "prefer-filesizes")?;
|
||||||
|
|
||||||
// the columns to update
|
// the columns to update
|
||||||
let columns: Option<Value> = call.get_flag(&engine_state, stack, "columns")?;
|
let columns: Option<Value> = call.get_flag(&engine_state, stack, "columns")?;
|
||||||
let columns: Option<HashSet<String>> = match columns {
|
let columns: Option<HashSet<String>> = match columns {
|
||||||
Some(val) => {
|
Some(val) => Some(
|
||||||
let cols = val
|
val.into_list()?
|
||||||
.as_list()?
|
.into_iter()
|
||||||
.iter()
|
.map(Value::coerce_into_string)
|
||||||
.map(|val| val.as_string())
|
.collect::<Result<HashSet<String>, ShellError>>()?,
|
||||||
.collect::<Result<Vec<String>, ShellError>>()?;
|
),
|
||||||
Some(HashSet::from_iter(cols))
|
|
||||||
}
|
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(UpdateCellIterator {
|
Ok(UpdateCellIterator {
|
||||||
input: input.into_iter(),
|
input: input.into_iter(),
|
||||||
columns,
|
columns,
|
||||||
|
display_as_filesizes,
|
||||||
span,
|
span,
|
||||||
}
|
}
|
||||||
.into_pipeline_data(ctrlc)
|
.into_pipeline_data(ctrlc)
|
||||||
@ -89,6 +94,7 @@ impl Command for IntoValue {
|
|||||||
struct UpdateCellIterator {
|
struct UpdateCellIterator {
|
||||||
input: PipelineIterator,
|
input: PipelineIterator,
|
||||||
columns: Option<HashSet<String>>,
|
columns: Option<HashSet<String>>,
|
||||||
|
display_as_filesizes: bool,
|
||||||
span: Span,
|
span: Span,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +118,7 @@ impl Iterator for UpdateCellIterator {
|
|||||||
Some(cols) if !cols.contains(&col) => (col, val),
|
Some(cols) if !cols.contains(&col) => (col, val),
|
||||||
_ => (
|
_ => (
|
||||||
col,
|
col,
|
||||||
match process_cell(val, span) {
|
match process_cell(val, self.display_as_filesizes, span) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(err) => Value::error(err, span),
|
Err(err) => Value::error(err, span),
|
||||||
},
|
},
|
||||||
@ -121,7 +127,7 @@ impl Iterator for UpdateCellIterator {
|
|||||||
.collect(),
|
.collect(),
|
||||||
span,
|
span,
|
||||||
)),
|
)),
|
||||||
val => match process_cell(val, self.span) {
|
val => match process_cell(val, self.display_as_filesizes, self.span) {
|
||||||
Ok(val) => Some(val),
|
Ok(val) => Some(val),
|
||||||
Err(err) => Some(Value::error(err, self.span)),
|
Err(err) => Some(Value::error(err, self.span)),
|
||||||
},
|
},
|
||||||
@ -134,9 +140,9 @@ impl Iterator for UpdateCellIterator {
|
|||||||
|
|
||||||
// This function will check each cell to see if it matches a regular expression
|
// This function will check each cell to see if it matches a regular expression
|
||||||
// for a particular datatype. If it does, it will convert the cell to that datatype.
|
// for a particular datatype. If it does, it will convert the cell to that datatype.
|
||||||
fn process_cell(val: Value, span: Span) -> Result<Value, ShellError> {
|
fn process_cell(val: Value, display_as_filesizes: bool, span: Span) -> Result<Value, ShellError> {
|
||||||
// step 1: convert value to string
|
// step 1: convert value to string
|
||||||
let val_str = val.as_string().unwrap_or_default();
|
let val_str = val.coerce_str().unwrap_or_default();
|
||||||
|
|
||||||
// step 2: bounce string up against regexes
|
// step 2: bounce string up against regexes
|
||||||
if BOOLEAN_RE.is_match(&val_str) {
|
if BOOLEAN_RE.is_match(&val_str) {
|
||||||
@ -177,9 +183,13 @@ fn process_cell(val: Value, span: Span) -> Result<Value, ShellError> {
|
|||||||
)),
|
)),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Value::int(ival, span))
|
if display_as_filesizes {
|
||||||
|
Ok(Value::filesize(ival, span))
|
||||||
|
} else {
|
||||||
|
Ok(Value::int(ival, span))
|
||||||
|
}
|
||||||
} else if INTEGER_WITH_DELIMS_RE.is_match(&val_str) {
|
} else if INTEGER_WITH_DELIMS_RE.is_match(&val_str) {
|
||||||
let mut val_str = val_str;
|
let mut val_str = val_str.into_owned();
|
||||||
val_str.retain(|x| !['_', ','].contains(&x));
|
val_str.retain(|x| !['_', ','].contains(&x));
|
||||||
|
|
||||||
let ival = val_str
|
let ival = val_str
|
||||||
@ -193,7 +203,11 @@ fn process_cell(val: Value, span: Span) -> Result<Value, ShellError> {
|
|||||||
)),
|
)),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Value::int(ival, span))
|
if display_as_filesizes {
|
||||||
|
Ok(Value::filesize(ival, span))
|
||||||
|
} else {
|
||||||
|
Ok(Value::int(ival, span))
|
||||||
|
}
|
||||||
} else if DATETIME_DMY_RE.is_match(&val_str) {
|
} else if DATETIME_DMY_RE.is_match(&val_str) {
|
||||||
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
|
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
|
||||||
to_type: "date".to_string(),
|
to_type: "date".to_string(),
|
||||||
|
@ -162,7 +162,7 @@ fn operate(
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let span = call.head;
|
let span = call.head;
|
||||||
let file_name: Spanned<String> = call.req(engine_state, stack, 0)?;
|
let file_name: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||||
let table_name: Option<Spanned<String>> = call.get_flag(engine_state, stack, "table_name")?;
|
let table_name: Option<Spanned<String>> = call.get_flag(engine_state, stack, "table-name")?;
|
||||||
let table = Table::new(&file_name, table_name)?;
|
let table = Table::new(&file_name, table_name)?;
|
||||||
|
|
||||||
match action(input, table, span) {
|
match action(input, table, span) {
|
||||||
@ -354,6 +354,7 @@ fn nu_value_to_sqlite_type(val: &Value) -> Result<&'static str, ShellError> {
|
|||||||
| Type::Range
|
| Type::Range
|
||||||
| Type::Record(_)
|
| Type::Record(_)
|
||||||
| Type::Signature
|
| Type::Signature
|
||||||
|
| Type::Glob
|
||||||
| Type::Table(_) => Err(ShellError::OnlySupportsThisInputType {
|
| Type::Table(_) => Err(ShellError::OnlySupportsThisInputType {
|
||||||
exp_input_type: "sql".into(),
|
exp_input_type: "sql".into(),
|
||||||
wrong_type: val.get_type().to_string(),
|
wrong_type: val.get_type().to_string(),
|
||||||
|
@ -441,15 +441,15 @@ fn prepared_statement_to_nu_list(
|
|||||||
) -> Result<Value, SqliteError> {
|
) -> Result<Value, SqliteError> {
|
||||||
let column_names = stmt
|
let column_names = stmt
|
||||||
.column_names()
|
.column_names()
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|c| c.to_string())
|
.map(String::from)
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
let row_results = stmt.query_map([], |row| {
|
let row_results = stmt.query_map([], |row| {
|
||||||
Ok(convert_sqlite_row_to_nu_value(
|
Ok(convert_sqlite_row_to_nu_value(
|
||||||
row,
|
row,
|
||||||
call_span,
|
call_span,
|
||||||
column_names.clone(),
|
&column_names,
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -491,18 +491,19 @@ fn read_entire_sqlite_db(
|
|||||||
Ok(Value::record(tables, call_span))
|
Ok(Value::record(tables, call_span))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_sqlite_row_to_nu_value(row: &Row, span: Span, column_names: Vec<String>) -> Value {
|
pub fn convert_sqlite_row_to_nu_value(row: &Row, span: Span, column_names: &[String]) -> Value {
|
||||||
let mut vals = Vec::with_capacity(column_names.len());
|
let record = column_names
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, col)| {
|
||||||
|
(
|
||||||
|
col.clone(),
|
||||||
|
convert_sqlite_value_to_nu_value(row.get_ref_unwrap(i), span),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
for i in 0..column_names.len() {
|
Value::record(record, span)
|
||||||
let val = convert_sqlite_value_to_nu_value(row.get_ref_unwrap(i), span);
|
|
||||||
vals.push(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value::record(
|
|
||||||
Record::from_raw_cols_vals_unchecked(column_names, vals),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_sqlite_value_to_nu_value(value: ValueRef, span: Span) -> Value {
|
pub fn convert_sqlite_value_to_nu_value(value: ValueRef, span: Span) -> Value {
|
||||||
@ -521,6 +522,27 @@ pub fn convert_sqlite_value_to_nu_value(value: ValueRef, span: Span) -> Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn open_connection_in_memory_custom() -> Result<Connection, ShellError> {
|
||||||
|
let flags = OpenFlags::default();
|
||||||
|
Connection::open_with_flags(MEMORY_DB, flags).map_err(|e| ShellError::GenericError {
|
||||||
|
error: "Failed to open SQLite custom connection in memory".into(),
|
||||||
|
msg: e.to_string(),
|
||||||
|
span: Some(Span::test_data()),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_connection_in_memory() -> Result<Connection, ShellError> {
|
||||||
|
Connection::open_in_memory().map_err(|e| ShellError::GenericError {
|
||||||
|
error: "Failed to open SQLite standard connection in memory".into(),
|
||||||
|
msg: e.to_string(),
|
||||||
|
span: Some(Span::test_data()),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -598,24 +620,3 @@ mod test {
|
|||||||
assert_eq!(converted_db, expected);
|
assert_eq!(converted_db, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_connection_in_memory_custom() -> Result<Connection, ShellError> {
|
|
||||||
let flags = OpenFlags::default();
|
|
||||||
Connection::open_with_flags(MEMORY_DB, flags).map_err(|e| ShellError::GenericError {
|
|
||||||
error: "Failed to open SQLite custom connection in memory".into(),
|
|
||||||
msg: e.to_string(),
|
|
||||||
span: Some(Span::test_data()),
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open_connection_in_memory() -> Result<Connection, ShellError> {
|
|
||||||
Connection::open_in_memory().map_err(|e| ShellError::GenericError {
|
|
||||||
error: "Failed to open SQLite standard connection in memory".into(),
|
|
||||||
msg: e.to_string(),
|
|
||||||
span: Some(Span::test_data()),
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@ -80,7 +80,7 @@ fn helper(value: Value, head: Span) -> Value {
|
|||||||
Value::Date { val, .. } => Value::string(humanize_date(val), head),
|
Value::Date { val, .. } => Value::string(humanize_date(val), head),
|
||||||
_ => Value::error(
|
_ => Value::error(
|
||||||
ShellError::DatetimeParseError {
|
ShellError::DatetimeParseError {
|
||||||
msg: value.debug_value(),
|
msg: value.to_debug_string(),
|
||||||
span: head,
|
span: head,
|
||||||
},
|
},
|
||||||
head,
|
head,
|
||||||
|
@ -51,7 +51,7 @@ impl Command for SubCommand {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
example: "date list-timezone | where timezone =~ Shanghai",
|
example: "date list-timezone | where timezone =~ Shanghai",
|
||||||
description: "Show timezone(s) that contains 'Shanghai'",
|
description: "Show time zone(s) that contains 'Shanghai'",
|
||||||
result: Some(Value::test_list(vec![Value::test_record(record! {
|
result: Some(Value::test_list(vec![Value::test_record(record! {
|
||||||
"timezone" => Value::test_string("Asia/Shanghai"),
|
"timezone" => Value::test_string("Asia/Shanghai"),
|
||||||
})])),
|
})])),
|
||||||
|
@ -46,17 +46,17 @@ impl Command for SubCommand {
|
|||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Get the time duration from 2019-04-30 to now",
|
description: "Get the time duration since 2019-04-30.",
|
||||||
example: r#"(date now) - 2019-05-01"#,
|
example: r#"(date now) - 2019-05-01"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Get the time duration since a more accurate time",
|
description: "Get the time duration since a more specific time.",
|
||||||
example: r#"(date now) - 2019-05-01T04:12:05.20+08:00"#,
|
example: r#"(date now) - 2019-05-01T04:12:05.20+08:00"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Get current time in full RFC3339 format with timezone",
|
description: "Get current time in full RFC 3339 format with time zone.",
|
||||||
example: r#"date now | debug"#,
|
example: r#"date now | debug"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
@ -50,11 +50,6 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
|
||||||
description: "Convert the current date into a record.",
|
|
||||||
example: "date to-record",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
Example {
|
||||||
description: "Convert the current date into a record.",
|
description: "Convert the current date into a record.",
|
||||||
example: "date now | date to-record",
|
example: "date now | date to-record",
|
||||||
@ -123,7 +118,7 @@ fn helper(val: Value, head: Span) -> Value {
|
|||||||
Value::Date { val, .. } => parse_date_into_table(val, head),
|
Value::Date { val, .. } => parse_date_into_table(val, head),
|
||||||
_ => Value::error(
|
_ => Value::error(
|
||||||
DatetimeParseError {
|
DatetimeParseError {
|
||||||
msg: val.debug_value(),
|
msg: val.to_debug_string(),
|
||||||
span: head,
|
span: head,
|
||||||
},
|
},
|
||||||
head,
|
head,
|
||||||
|
@ -52,11 +52,6 @@ impl Command for SubCommand {
|
|||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Convert the current date into a table.",
|
description: "Convert the current date into a table.",
|
||||||
example: "date to-table",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Convert the date into a table.",
|
|
||||||
example: "date now | date to-table",
|
example: "date now | date to-table",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
@ -122,7 +117,7 @@ fn helper(val: Value, head: Span) -> Value {
|
|||||||
Value::Date { val, .. } => parse_date_into_table(val, head),
|
Value::Date { val, .. } => parse_date_into_table(val, head),
|
||||||
_ => Value::error(
|
_ => Value::error(
|
||||||
DatetimeParseError {
|
DatetimeParseError {
|
||||||
msg: val.debug_value(),
|
msg: val.to_debug_string(),
|
||||||
span: head,
|
span: head,
|
||||||
},
|
},
|
||||||
head,
|
head,
|
||||||
|
@ -77,27 +77,27 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Get the current date in UTC+05:00",
|
description: "Get the current date in UTC+05:00.",
|
||||||
example: "date now | date to-timezone '+0500'",
|
example: "date now | date to-timezone '+0500'",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Get the current local date",
|
description: "Get the current date in the local time zone.",
|
||||||
example: "date now | date to-timezone local",
|
example: "date now | date to-timezone local",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Get the current date in Hawaii",
|
description: "Get the current date in Hawaii.",
|
||||||
example: "date now | date to-timezone US/Hawaii",
|
example: "date now | date to-timezone US/Hawaii",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Get the current date in Hawaii",
|
description: "Get a date in a different time zone, from a string.",
|
||||||
example: r#""2020-10-10 10:00:00 +02:00" | date to-timezone "+0500""#,
|
example: r#""2020-10-10 10:00:00 +02:00" | date to-timezone "+0500""#,
|
||||||
result: example_result_1(),
|
result: example_result_1(),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Get the current date in Hawaii, from a datetime object",
|
description: "Get a date in a different time zone, from a datetime.",
|
||||||
example: r#""2020-10-10 10:00:00 +02:00" | into datetime | date to-timezone "+0500""#,
|
example: r#""2020-10-10 10:00:00 +02:00" | into datetime | date to-timezone "+0500""#,
|
||||||
result: example_result_1(),
|
result: example_result_1(),
|
||||||
},
|
},
|
||||||
@ -123,7 +123,7 @@ fn helper(value: Value, head: Span, timezone: &Spanned<String>) -> Value {
|
|||||||
}
|
}
|
||||||
_ => Value::error(
|
_ => Value::error(
|
||||||
ShellError::DatetimeParseError {
|
ShellError::DatetimeParseError {
|
||||||
msg: value.debug_value(),
|
msg: value.to_debug_string(),
|
||||||
span: head,
|
span: head,
|
||||||
},
|
},
|
||||||
head,
|
head,
|
||||||
|
@ -46,7 +46,7 @@ pub(crate) fn generate_strftime_list(head: Span, show_parse_only_formats: bool)
|
|||||||
description: &'a str,
|
description: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
let specifications = vec![
|
let specifications = [
|
||||||
FormatSpecification {
|
FormatSpecification {
|
||||||
spec: "%Y",
|
spec: "%Y",
|
||||||
description: "The full proleptic Gregorian year, zero-padded to 4 digits.",
|
description: "The full proleptic Gregorian year, zero-padded to 4 digits.",
|
||||||
|
@ -44,9 +44,9 @@ impl Command for Debug {
|
|||||||
input.map(
|
input.map(
|
||||||
move |x| {
|
move |x| {
|
||||||
if raw {
|
if raw {
|
||||||
Value::string(x.debug_value(), head)
|
Value::string(x.to_debug_string(), head)
|
||||||
} else {
|
} else {
|
||||||
Value::string(x.debug_string(", ", &config), head)
|
Value::string(x.to_expanded_string(", ", &config), head)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
engine_state.ctrlc.clone(),
|
engine_state.ctrlc.clone(),
|
||||||
|
@ -251,7 +251,7 @@ pub fn debug_string_without_formatting(value: &Value) -> String {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Value::String { val, .. } => val.clone(),
|
Value::String { val, .. } => val.clone(),
|
||||||
Value::QuotedString { val, .. } => val.clone(),
|
Value::Glob { val, .. } => val.clone(),
|
||||||
Value::List { vals: val, .. } => format!(
|
Value::List { vals: val, .. } => format!(
|
||||||
"[{}]",
|
"[{}]",
|
||||||
val.iter()
|
val.iter()
|
||||||
|
@ -79,12 +79,7 @@ impl LazySystemInfoRecord {
|
|||||||
"ppid" => {
|
"ppid" => {
|
||||||
// only get information requested
|
// only get information requested
|
||||||
let system_opt = SystemOpt::from((system_option, || {
|
let system_opt = SystemOpt::from((system_option, || {
|
||||||
RefreshKind::new().with_processes(
|
RefreshKind::new().with_processes(ProcessRefreshKind::everything())
|
||||||
ProcessRefreshKind::new()
|
|
||||||
.without_cpu()
|
|
||||||
.without_disk_usage()
|
|
||||||
.without_user(),
|
|
||||||
)
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let system = system_opt.get_system();
|
let system = system_opt.get_system();
|
||||||
@ -117,16 +112,10 @@ impl LazySystemInfoRecord {
|
|||||||
"process" => {
|
"process" => {
|
||||||
// only get information requested
|
// only get information requested
|
||||||
let system_opt = SystemOpt::from((system_option, || {
|
let system_opt = SystemOpt::from((system_option, || {
|
||||||
RefreshKind::new().with_processes(
|
RefreshKind::new().with_processes(ProcessRefreshKind::everything())
|
||||||
ProcessRefreshKind::new()
|
|
||||||
.without_cpu()
|
|
||||||
.without_disk_usage()
|
|
||||||
.without_user(),
|
|
||||||
)
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let system = system_opt.get_system();
|
let system = system_opt.get_system();
|
||||||
// get the process information for the nushell pid
|
|
||||||
let pinfo = system.process(pid);
|
let pinfo = system.process(pid);
|
||||||
|
|
||||||
if let Some(p) = pinfo {
|
if let Some(p) = pinfo {
|
||||||
@ -236,12 +225,7 @@ impl<'a> LazyRecord<'a> for LazySystemInfoRecord {
|
|||||||
|
|
||||||
fn collect(&'a self) -> Result<Value, ShellError> {
|
fn collect(&'a self) -> Result<Value, ShellError> {
|
||||||
let rk = RefreshKind::new()
|
let rk = RefreshKind::new()
|
||||||
.with_processes(
|
.with_processes(ProcessRefreshKind::everything())
|
||||||
ProcessRefreshKind::new()
|
|
||||||
.without_cpu()
|
|
||||||
.without_disk_usage()
|
|
||||||
.without_user(),
|
|
||||||
)
|
|
||||||
.with_memory(MemoryRefreshKind::everything());
|
.with_memory(MemoryRefreshKind::everything());
|
||||||
// only get information requested
|
// only get information requested
|
||||||
let system = System::new_with_specifics(rk);
|
let system = System::new_with_specifics(rk);
|
||||||
|
@ -46,8 +46,19 @@ impl Command for ViewSource {
|
|||||||
let vec_of_optional = &sig.optional_positional;
|
let vec_of_optional = &sig.optional_positional;
|
||||||
let rest = &sig.rest_positional;
|
let rest = &sig.rest_positional;
|
||||||
let vec_of_flags = &sig.named;
|
let vec_of_flags = &sig.named;
|
||||||
|
|
||||||
|
if decl.is_alias() {
|
||||||
|
if let Some(alias) = &decl.as_alias() {
|
||||||
|
let contents = String::from_utf8_lossy(
|
||||||
|
engine_state.get_span_contents(alias.wrapped_call.span),
|
||||||
|
);
|
||||||
|
Ok(Value::string(contents, call.head).into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
Ok(Value::string("no alias found", call.head).into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
// gets vector of positionals.
|
// gets vector of positionals.
|
||||||
if let Some(block_id) = decl.get_block_id() {
|
else if let Some(block_id) = decl.get_block_id() {
|
||||||
let block = engine_state.get_block(block_id);
|
let block = engine_state.get_block(block_id);
|
||||||
if let Some(block_span) = block.span {
|
if let Some(block_span) = block.span {
|
||||||
let contents = engine_state.get_span_contents(block_span);
|
let contents = engine_state.get_span_contents(block_span);
|
||||||
@ -87,7 +98,7 @@ impl Command for ViewSource {
|
|||||||
} else {
|
} else {
|
||||||
Err(ShellError::GenericError {
|
Err(ShellError::GenericError {
|
||||||
error: "Cannot view value".to_string(),
|
error: "Cannot view value".to_string(),
|
||||||
msg: "the command does not have a viewable block".to_string(),
|
msg: "the command does not have a viewable block span".to_string(),
|
||||||
span: Some(arg_span),
|
span: Some(arg_span),
|
||||||
help: None,
|
help: None,
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
@ -129,7 +140,7 @@ impl Command for ViewSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
value => {
|
value => {
|
||||||
if let Ok(block_id) = value.as_block() {
|
if let Ok(block_id) = value.coerce_block() {
|
||||||
let block = engine_state.get_block(block_id);
|
let block = engine_state.get_block(block_id);
|
||||||
|
|
||||||
if let Some(span) = block.span {
|
if let Some(span) = block.span {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user