mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 15:11:52 +02:00
Compare commits
84 Commits
Author | SHA1 | Date | |
---|---|---|---|
8695b57584 | |||
c9652bce00 | |||
bf86cd50a5 | |||
b4a1f0f003 | |||
8d8304cf91 | |||
92c1051143 | |||
9ebb61fc2d | |||
2254805a6d | |||
6cbd42974b | |||
8584aa79a2 | |||
a3bf2bff49 | |||
5e8754bd85 | |||
a45bd0301a | |||
45c17d9664 | |||
39cdf56214 | |||
9e9fe83bfd | |||
e735d0c561 | |||
39e51f1953 | |||
43a3983d36 | |||
a8b4e81408 | |||
6c13c67528 | |||
2a484a3e7e | |||
a78cd6e231 | |||
a528c043fe | |||
47f9fd3644 | |||
fe9f732c5f | |||
a5d02a0737 | |||
7a945848de | |||
a92949b5c3 | |||
d5ae979094 | |||
388e84e7ef | |||
a5af77dd72 | |||
a2dd948e71 | |||
fe60fb8679 | |||
250071939b | |||
0ea973b78b | |||
a2a346e39c | |||
6dc7ff2335 | |||
1acc2bfd96 | |||
10d65b611f | |||
edb61fc1d5 | |||
155de9f6fc | |||
b82e279f9d | |||
6bbe5b6255 | |||
1019acb7a3 | |||
83b1ec83c9 | |||
d9a00a876b | |||
e4625acf24 | |||
7fb48b9a2f | |||
5fcbefb7b4 | |||
345cdef113 | |||
a7c1b363eb | |||
d45e9671d4 | |||
517dc6d39e | |||
e590d3587c | |||
bdaa32666a | |||
9804cd82f8 | |||
59b85e549c | |||
dae4a9b091 | |||
fb10e1dfc5 | |||
4ca47258a0 | |||
b37662c7e1 | |||
3076378373 | |||
4c4c1f6147 | |||
35c8485442 | |||
3268ecd116 | |||
44493dac51 | |||
6f9b9914cf | |||
ffb9ab9eef | |||
5fe0ca418d | |||
ecc820a8c1 | |||
6047b04208 | |||
8d8b011702 | |||
07c9f681c7 | |||
c422c6cc3d | |||
e251f3a0b4 | |||
77ca73f414 | |||
48c75831fc | |||
4b8a259916 | |||
5733a13409 | |||
f9049c4c6c | |||
66b5931438 | |||
503052b669 | |||
7d6a32c5f8 |
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,5 +1,6 @@
|
||||
name: Bug Report
|
||||
description: Create a report to help us improve
|
||||
labels: ["needs-triage"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
|
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,6 +1,6 @@
|
||||
name: Feature Request
|
||||
description: "When you want a new feature for something that doesn't already exist"
|
||||
labels: "enhancement"
|
||||
labels: ["needs-triage", "enhancement"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
|
@ -2,7 +2,7 @@
|
||||
name: standard library bug or feature report
|
||||
about: Used to submit issues related to the nu standard library
|
||||
title: ''
|
||||
labels: std-library
|
||||
labels: ['needs-triage', 'std-library']
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@ -16,7 +16,7 @@ Don't forget to add tests that cover your changes.
|
||||
Make sure you've run and fixed any issues with these commands:
|
||||
|
||||
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
||||
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
|
||||
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err` to check that you're using the standard code style
|
||||
- `cargo test --workspace` to check that all tests pass
|
||||
- `cargo run -- crates/nu-std/tests/run.nu` to run the tests for the standard library
|
||||
|
||||
|
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -46,7 +46,7 @@ jobs:
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy --workspace ${{ matrix.flags }}--exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
run: cargo clippy --workspace ${{ matrix.flags }}--exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err
|
||||
|
||||
nu-tests:
|
||||
env:
|
||||
@ -84,7 +84,7 @@ jobs:
|
||||
|
||||
std-lib-and-python-virtualenv:
|
||||
env:
|
||||
NU_LOG_LEVEL: DEBUG
|
||||
NU_LOG_LEVEL: DEBUG
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
@ -163,7 +163,7 @@ jobs:
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.4.4
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
run: cargo clippy --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err
|
||||
|
||||
- name: Tests
|
||||
run: cargo test --profile ci --package nu_plugin_*
|
||||
|
@ -59,7 +59,7 @@ The most comprehensive test suite we have is the `nu-test-support` crate. For te
|
||||
- Run Clippy on Nushell:
|
||||
|
||||
```shell
|
||||
cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err
|
||||
```
|
||||
or via the `toolkit.nu` command:
|
||||
```shell
|
||||
@ -72,6 +72,12 @@ The most comprehensive test suite we have is the `nu-test-support` crate. For te
|
||||
```shell
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
along with dataframe tests
|
||||
|
||||
```shell
|
||||
cargo test --workspace --features=dataframe
|
||||
```
|
||||
or via the `toolkit.nu` command:
|
||||
```shell
|
||||
use toolkit.nu test
|
||||
@ -139,7 +145,7 @@ This includes discarded approaches. Also we want to quickly identify regressions
|
||||
|
||||
### How we merge PRs
|
||||
|
||||
In general the maintainers **squash** all changes of your PR into a single commit when merging.
|
||||
In general the maintainers **squash** all changes of your PR into a single commit when merging.
|
||||
|
||||
This keeps a clean enough linear history, while not forcing you to conform to a too strict style while iterating in your PR or fixing small problems. As an added benefit the commits on the `main` branch are tied to the discussion that happened in the PR through their `#1234` issue number.
|
||||
|
||||
@ -201,13 +207,13 @@ You can help us to make the review process a smooth experience:
|
||||
- In general, added tests help us to understand which assumptions go into a particular addition/change.
|
||||
- Try to also test corner cases where those assumptions might break. This can be more valuable than simply adding many similar tests.
|
||||
- Commit history inside a PR during code review:
|
||||
- Good **atomic commits** can help follow larger changes, but we are not pedantic.
|
||||
- Good **atomic commits** can help follow larger changes, but we are not pedantic.
|
||||
- We don't shame fixup commits while you try to figure out a problem. They can help others see what you tried and what didn't work. (see our [squash policy](#how-we-merge-prs))
|
||||
- During active review constant **force pushing** just to amend changes can be confusing!
|
||||
- GitHub's UI presents reviewers with less options to compare diffs
|
||||
- fetched branches for experimentation become invalid!
|
||||
- the notification a maintainer receives has a low signal-to-noise ratio
|
||||
- Git pros *can* use their judgement to rebase/squash to clean up the history *if it aids the understanding* of a larger change during review
|
||||
- Git pros *can* use their judgement to rebase/squash to clean up the history *if it aids the understanding* of a larger change during review
|
||||
- Merge conflicts:
|
||||
- In general you should take care of resolving merge conflicts.
|
||||
- Use your judgement whether to `git merge main` or to `git rebase main`
|
||||
|
464
Cargo.lock
generated
464
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
40
Cargo.toml
40
Cargo.toml
@ -10,7 +10,7 @@ license = "MIT"
|
||||
name = "nu"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.60"
|
||||
version = "0.79.0"
|
||||
version = "0.80.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -47,25 +47,25 @@ crossterm = "0.26"
|
||||
ctrlc = "3.2.1"
|
||||
log = "0.4"
|
||||
miette = { version = "5.7.0", features = ["fancy-no-backtrace"] }
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.79.0" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.79.0" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.79.0" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.79.0" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.79.0" }
|
||||
nu-json = { path = "./crates/nu-json", version = "0.79.0" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.79.0" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.79.0" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.79.0" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.79.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.79.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.79.0" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.79.0" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.79.0" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.79.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.79.0" }
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.80.0" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.80.0" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.80.0" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.80.0" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.80.0" }
|
||||
nu-json = { path = "./crates/nu-json", version = "0.80.0" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.80.0" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.80.0" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.80.0" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.80.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.80.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.80.0" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.80.0" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.80.0" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.80.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.80.0" }
|
||||
|
||||
nu-ansi-term = "0.47.0"
|
||||
reedline = { version = "0.19.0", features = ["bashisms", "sqlite"]}
|
||||
reedline = { version = "0.19.1", features = ["bashisms", "sqlite"]}
|
||||
|
||||
rayon = "1.7.0"
|
||||
is_executable = "1.0.1"
|
||||
@ -80,7 +80,7 @@ signal-hook = { version = "0.3.14", default-features = false }
|
||||
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
winres = "0.1"
|
||||
winresource = "0.1"
|
||||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
nix = { version = "0.26", default-features = false, features = [
|
||||
@ -92,7 +92,7 @@ nix = { version = "0.26", default-features = false, features = [
|
||||
atty = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.79.0" }
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.80.0" }
|
||||
tempfile = "3.5.0"
|
||||
assert_cmd = "2.0.2"
|
||||
criterion = "0.4"
|
||||
|
@ -46,8 +46,6 @@ To quickly install Nu:
|
||||
brew install nushell
|
||||
# Windows
|
||||
winget install nushell
|
||||
# Cross Platform installation if you have node and npm installed, Note that nu plugins were not included
|
||||
npm install -g nushell
|
||||
```
|
||||
|
||||
To use `Nu` in GitHub Action, check [setup-nu](https://github.com/marketplace/actions/setup-nu) for more detail.
|
||||
|
@ -1,36 +1,30 @@
|
||||
@echo off
|
||||
@echo -------------------------------------------------------------------
|
||||
@echo Building nushell (nu.exe) with dataframes and all the plugins
|
||||
@echo -------------------------------------------------------------------
|
||||
@echo.
|
||||
echo -------------------------------------------------------------------
|
||||
echo Building nushell (nu.exe) with dataframes and all the plugins
|
||||
echo -------------------------------------------------------------------
|
||||
echo.
|
||||
|
||||
echo Building nushell.exe
|
||||
cargo build --features=dataframe
|
||||
@echo.
|
||||
echo.
|
||||
|
||||
@cd crates\nu_plugin_example
|
||||
echo Building nu_plugin_example.exe
|
||||
cargo build
|
||||
@echo.
|
||||
call :build crates\nu_plugin_example nu_plugin_example.exe
|
||||
call :build ..\..\crates\nu_plugin_gstat nu_plugin_gstat.exe
|
||||
call :build ..\..\crates\nu_plugin_inc nu_plugin_inc.exe
|
||||
call :build ..\..\crates\nu_plugin_query nu_plugin_query.exe
|
||||
call :build ..\..\crates\nu_plugin_custom_values nu_plugin_custom_values.exe
|
||||
|
||||
@cd ..\..\crates\nu_plugin_gstat
|
||||
echo Building nu_plugin_gstat.exe
|
||||
cargo build
|
||||
@echo.
|
||||
cd ..\..
|
||||
exit /b 0
|
||||
|
||||
@cd ..\..\crates\nu_plugin_inc
|
||||
echo Building nu_plugin_inc.exe
|
||||
cargo build
|
||||
@echo.
|
||||
:build
|
||||
setlocal
|
||||
set "location=%~1"
|
||||
set "target=%~2"
|
||||
|
||||
@cd ..\..\crates\nu_plugin_query
|
||||
echo Building nu_plugin_query.exe
|
||||
cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..\crates\nu_plugin_custom_values
|
||||
echo Building nu_plugin_custom_values.exe
|
||||
cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..
|
||||
cd "%location%"
|
||||
echo Building %target%
|
||||
cargo build
|
||||
echo.
|
||||
endlocal
|
||||
exit /b 0
|
||||
|
2
build.rs
2
build.rs
@ -1,6 +1,6 @@
|
||||
#[cfg(windows)]
|
||||
fn main() {
|
||||
let mut res = winres::WindowsResource::new();
|
||||
let mut res = winresource::WindowsResource::new();
|
||||
res.set("ProductName", "Nushell");
|
||||
res.set("FileDescription", "Nushell");
|
||||
res.set("LegalCopyright", "Copyright (C) 2022");
|
||||
|
@ -5,26 +5,26 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.79.0"
|
||||
version = "0.80.0"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.79.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.80.0" }
|
||||
rstest = { version = "0.17.0", default-features = false }
|
||||
|
||||
[dependencies]
|
||||
nu-command = { path = "../nu-command", version = "0.79.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.79.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.79.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.79.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.79.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.79.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.79.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.80.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.80.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.80.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.80.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.80.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.80.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.80.0" }
|
||||
|
||||
nu-ansi-term = "0.47.0"
|
||||
reedline = { version = "0.19.0", features = ["bashisms", "sqlite"]}
|
||||
reedline = { version = "0.19.1", features = ["bashisms", "sqlite"]}
|
||||
|
||||
atty = "0.2.14"
|
||||
chrono = { default-features = false, features = ["std"], version = "0.4.23" }
|
||||
|
@ -118,8 +118,9 @@ impl Command for Commandline {
|
||||
.expect("repl cursor pos mutex");
|
||||
let char_pos = buffer
|
||||
.grapheme_indices(true)
|
||||
.chain(std::iter::once((buffer.len(), "")))
|
||||
.position(|(i, _c)| i == *cursor_pos)
|
||||
.unwrap_or(buffer.len());
|
||||
.expect("Cursor position isn't on a grapheme boundary");
|
||||
Ok(Value::String {
|
||||
val: char_pos.to_string(),
|
||||
span: call.head,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::completions::{Completer, CompletionOptions};
|
||||
use nu_engine::eval_variable;
|
||||
use nu_engine::{column::get_columns, eval_variable};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Span, Value,
|
||||
@ -267,7 +267,19 @@ fn nested_suggestions(
|
||||
|
||||
output
|
||||
}
|
||||
Value::List { vals, span: _ } => {
|
||||
for column_name in get_columns(vals.as_slice()) {
|
||||
output.push(Suggestion {
|
||||
value: column_name,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
_ => output,
|
||||
}
|
||||
}
|
||||
@ -295,6 +307,38 @@ fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
|
||||
span: Span::unknown(),
|
||||
};
|
||||
}
|
||||
Value::LazyRecord { val, span: _ } => {
|
||||
for col in val.column_names() {
|
||||
if col.as_bytes().to_vec() == next_sublevel {
|
||||
return recursive_value(
|
||||
val.get_column_value(col).unwrap_or_default(),
|
||||
sublevels.into_iter().skip(1).collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Current sublevel value not found
|
||||
return Value::Nothing {
|
||||
span: Span::unknown(),
|
||||
};
|
||||
}
|
||||
Value::List { vals, span } => {
|
||||
for col in get_columns(vals.as_slice()) {
|
||||
if col.as_bytes().to_vec() == next_sublevel {
|
||||
return recursive_value(
|
||||
Value::List { vals, span }
|
||||
.get_data_by_key(&col)
|
||||
.unwrap_or_default(),
|
||||
sublevels.into_iter().skip(1).collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Current sublevel value not found
|
||||
return Value::Nothing {
|
||||
span: Span::unknown(),
|
||||
};
|
||||
}
|
||||
_ => return val,
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,9 @@ impl NuHelpCompleter {
|
||||
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
|
||||
|
||||
if !sig.named.is_empty() {
|
||||
long_desc.push_str(&get_flags_section(sig))
|
||||
long_desc.push_str(&get_flags_section(sig, |v| {
|
||||
v.into_string_parsable(", ", &self.0.config)
|
||||
}))
|
||||
}
|
||||
|
||||
if !sig.required_positional.is_empty()
|
||||
@ -69,10 +71,18 @@ impl NuHelpCompleter {
|
||||
let _ = write!(long_desc, " {}: {}\r\n", positional.name, positional.desc);
|
||||
}
|
||||
for positional in &sig.optional_positional {
|
||||
let opt_suffix = if let Some(value) = &positional.default_value {
|
||||
format!(
|
||||
" (optional, default: {})",
|
||||
&value.into_string_parsable(", ", &self.0.config),
|
||||
)
|
||||
} else {
|
||||
(" (optional)").to_string()
|
||||
};
|
||||
let _ = write!(
|
||||
long_desc,
|
||||
" (optional) {}: {}\r\n",
|
||||
positional.name, positional.desc
|
||||
" (optional) {}: {}{}\r\n",
|
||||
positional.name, positional.desc, opt_suffix
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -106,11 +106,13 @@ impl Prompt for NushellPrompt {
|
||||
prompt_string.replace('\n', "\r\n").into()
|
||||
} else {
|
||||
let default = DefaultPrompt::default();
|
||||
default
|
||||
let prompt = default
|
||||
.render_prompt_left()
|
||||
.to_string()
|
||||
.replace('\n', "\r\n")
|
||||
.into()
|
||||
+ " ";
|
||||
|
||||
prompt.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,13 +11,13 @@ use miette::{IntoDiagnostic, Result};
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_command::hook::eval_hook;
|
||||
use nu_command::util::get_guaranteed_cwd;
|
||||
use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::{lex, parse, trim_quotes_str};
|
||||
use nu_engine::convert_env_values;
|
||||
use nu_parser::{lex, trim_quotes_str};
|
||||
use nu_protocol::{
|
||||
config::NuCursorShape,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
format_duration, report_error, report_error_new, HistoryFileFormat, PipelineData, ShellError,
|
||||
Span, Spanned, Value,
|
||||
report_error, report_error_new, HistoryFileFormat, PipelineData, ShellError, Span, Spanned,
|
||||
Value,
|
||||
};
|
||||
use nu_utils::utils::perf;
|
||||
use reedline::{CursorConfig, DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
|
||||
@ -43,6 +43,7 @@ pub fn evaluate_repl(
|
||||
stack: &mut Stack,
|
||||
nushell_path: &str,
|
||||
prerun_command: Option<Spanned<String>>,
|
||||
load_std_lib: Option<Spanned<String>>,
|
||||
entire_start_time: Instant,
|
||||
) -> Result<()> {
|
||||
use nu_command::hook;
|
||||
@ -105,6 +106,20 @@ pub fn evaluate_repl(
|
||||
);
|
||||
|
||||
let config = engine_state.get_config();
|
||||
if config.bracketed_paste {
|
||||
// try to enable bracketed paste
|
||||
// It doesn't work on windows system: https://github.com/crossterm-rs/crossterm/issues/737
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let _ = line_editor.enable_bracketed_paste();
|
||||
}
|
||||
|
||||
// Setup history_isolation aka "history per session"
|
||||
let history_isolation = config.history_isolation;
|
||||
let history_session_id = if history_isolation {
|
||||
Reedline::create_history_session_id()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let history_path = crate::config_files::get_history_path(
|
||||
@ -124,7 +139,9 @@ pub fn evaluate_repl(
|
||||
SqliteBackedHistory::with_file(history_path.to_path_buf()).into_diagnostic()?,
|
||||
),
|
||||
};
|
||||
line_editor = line_editor.with_history(history);
|
||||
line_editor = line_editor
|
||||
.with_history_session_id(history_session_id)
|
||||
.with_history(history);
|
||||
};
|
||||
perf(
|
||||
"setup history",
|
||||
@ -137,19 +154,8 @@ pub fn evaluate_repl(
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let sys = sysinfo::System::new();
|
||||
|
||||
let show_banner = config.show_banner;
|
||||
let use_ansi = config.use_ansi_coloring;
|
||||
if show_banner {
|
||||
let banner = get_banner(engine_state, stack);
|
||||
if use_ansi {
|
||||
println!("{banner}");
|
||||
} else {
|
||||
println!("{}", nu_utils::strip_ansi_string_likely(banner));
|
||||
}
|
||||
}
|
||||
perf(
|
||||
"get sysinfo/show banner",
|
||||
"get sysinfo",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
@ -169,6 +175,19 @@ pub fn evaluate_repl(
|
||||
engine_state.merge_env(stack, get_guaranteed_cwd(engine_state, stack))?;
|
||||
}
|
||||
|
||||
engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64);
|
||||
|
||||
if load_std_lib.is_none() && engine_state.get_config().show_banner {
|
||||
eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
r#"use std banner; banner"#.as_bytes(),
|
||||
"show_banner",
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
loop {
|
||||
let loop_start_time = std::time::Instant::now();
|
||||
|
||||
@ -429,16 +448,6 @@ pub fn evaluate_repl(
|
||||
|
||||
entry_num += 1;
|
||||
|
||||
if entry_num == 1 {
|
||||
engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64);
|
||||
if show_banner {
|
||||
println!(
|
||||
"Startup Time: {}",
|
||||
format_duration(engine_state.get_startup_time())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let input = line_editor.read_line(prompt);
|
||||
let shell_integration = config.shell_integration;
|
||||
@ -477,6 +486,12 @@ pub fn evaluate_repl(
|
||||
}
|
||||
}
|
||||
|
||||
let mut repl_cursor = engine_state
|
||||
.repl_cursor_pos
|
||||
.lock()
|
||||
.expect("repl cursor pos mutex");
|
||||
*repl_cursor = line_editor.current_insertion_point();
|
||||
drop(repl_cursor);
|
||||
let mut repl_buffer = engine_state
|
||||
.repl_buffer_state
|
||||
.lock()
|
||||
@ -710,109 +725,14 @@ pub fn evaluate_repl(
|
||||
fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> SetCursorStyle {
|
||||
match shape {
|
||||
NuCursorShape::Block => SetCursorStyle::SteadyBlock,
|
||||
NuCursorShape::UnderScore => SetCursorStyle::DefaultUserShape,
|
||||
NuCursorShape::Line => SetCursorStyle::BlinkingBar,
|
||||
NuCursorShape::UnderScore => SetCursorStyle::SteadyUnderScore,
|
||||
NuCursorShape::Line => SetCursorStyle::SteadyBar,
|
||||
NuCursorShape::BlinkBlock => SetCursorStyle::BlinkingBlock,
|
||||
NuCursorShape::BlinkUnderScore => SetCursorStyle::BlinkingUnderScore,
|
||||
NuCursorShape::BlinkLine => SetCursorStyle::BlinkingBar,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String {
|
||||
let age = match eval_string_with_input(
|
||||
engine_state,
|
||||
stack,
|
||||
None,
|
||||
"(date now) - ('2019-05-10 09:59:12-0700' | into datetime)",
|
||||
) {
|
||||
Ok(Value::Duration { val, .. }) => format_duration(val),
|
||||
_ => "".to_string(),
|
||||
};
|
||||
|
||||
let banner = format!(
|
||||
r#"{} __ ,
|
||||
{} .--()°'.' {}Welcome to {}Nushell{},
|
||||
{}'|, . ,' {}based on the {}nu{} language,
|
||||
{} !_-(_\ {}where all data is structured!
|
||||
|
||||
Please join our {}Discord{} community at {}https://discord.gg/NtAbbGn{}
|
||||
Our {}GitHub{} repository is at {}https://github.com/nushell/nushell{}
|
||||
Our {}Documentation{} is located at {}https://nushell.sh{}
|
||||
{}Tweet{} us at {}@nu_shell{}
|
||||
Learn how to remove this at: {}https://nushell.sh/book/configuration.html#remove-welcome-message{}
|
||||
|
||||
It's been this long since {}Nushell{}'s first commit:
|
||||
{}{}
|
||||
"#,
|
||||
"\x1b[32m", //start line 1 green
|
||||
"\x1b[32m", //start line 2
|
||||
"\x1b[0m", //before welcome
|
||||
"\x1b[32m", //before nushell
|
||||
"\x1b[0m", //after nushell
|
||||
"\x1b[32m", //start line 3
|
||||
"\x1b[0m", //before based
|
||||
"\x1b[32m", //before nu
|
||||
"\x1b[0m", //after nu
|
||||
"\x1b[32m", //start line 4
|
||||
"\x1b[0m", //before where
|
||||
"\x1b[35m", //before Discord purple
|
||||
"\x1b[0m", //after Discord
|
||||
"\x1b[35m", //before Discord URL
|
||||
"\x1b[0m", //after Discord URL
|
||||
"\x1b[1;32m", //before GitHub green_bold
|
||||
"\x1b[0m", //after GitHub
|
||||
"\x1b[1;32m", //before GitHub URL
|
||||
"\x1b[0m", //after GitHub URL
|
||||
"\x1b[32m", //before Documentation
|
||||
"\x1b[0m", //after Documentation
|
||||
"\x1b[32m", //before Documentation URL
|
||||
"\x1b[0m", //after Documentation URL
|
||||
"\x1b[36m", //before Tweet blue
|
||||
"\x1b[0m", //after Tweet
|
||||
"\x1b[1;36m", //before @nu_shell cyan_bold
|
||||
"\x1b[0m", //after @nu_shell
|
||||
"\x1b[32m", //before Welcome Message
|
||||
"\x1b[0m", //after Welcome Message
|
||||
"\x1b[32m", //before Nushell
|
||||
"\x1b[0m", //after Nushell
|
||||
age,
|
||||
"\x1b[0m", //after banner disable
|
||||
);
|
||||
|
||||
banner
|
||||
}
|
||||
|
||||
// Taken from Nana's simple_eval
|
||||
/// Evaluate a block of Nu code, optionally with input.
|
||||
/// For example, source="$in * 2" will multiply the value in input by 2.
|
||||
pub fn eval_string_with_input(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
input: Option<Value>,
|
||||
source: &str,
|
||||
) -> Result<Value, ShellError> {
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let output = parse(&mut working_set, None, source.as_bytes(), false);
|
||||
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
engine_state.merge_delta(delta)?;
|
||||
|
||||
let input_as_pipeline_data = match input {
|
||||
Some(input) => PipelineData::Value(input, None),
|
||||
None => PipelineData::empty(),
|
||||
};
|
||||
|
||||
eval_block(
|
||||
engine_state,
|
||||
stack,
|
||||
&block,
|
||||
input_as_pipeline_data,
|
||||
false,
|
||||
true,
|
||||
)
|
||||
.map(|x| x.into_value(Span::unknown()))
|
||||
}
|
||||
|
||||
pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String {
|
||||
let exit_code = stack
|
||||
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
||||
|
@ -556,6 +556,100 @@ fn variables_completions() {
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for $nu.os-info
|
||||
let suggestions = completer.complete("$nu.os-info.", 12);
|
||||
assert_eq!(4, suggestions.len());
|
||||
let expected: Vec<String> = vec![
|
||||
"arch".into(),
|
||||
"family".into(),
|
||||
"kernel_version".into(),
|
||||
"name".into(),
|
||||
];
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for $nu.scope
|
||||
let suggestions = completer.complete("$nu.scope.", 10);
|
||||
assert_eq!(5, suggestions.len());
|
||||
let expected: Vec<String> = vec![
|
||||
"aliases".into(),
|
||||
"commands".into(),
|
||||
"engine_state".into(),
|
||||
"modules".into(),
|
||||
"vars".into(),
|
||||
];
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for $nu.scope.commands
|
||||
let suggestions = completer.complete("$nu.scope.commands.", 19);
|
||||
assert_eq!(15, suggestions.len());
|
||||
let expected: Vec<String> = vec![
|
||||
"category".into(),
|
||||
"creates_scope".into(),
|
||||
"examples".into(),
|
||||
"extra_usage".into(),
|
||||
"is_builtin".into(),
|
||||
"is_custom".into(),
|
||||
"is_extern".into(),
|
||||
"is_keyword".into(),
|
||||
"is_plugin".into(),
|
||||
"is_sub".into(),
|
||||
"module_name".into(),
|
||||
"name".into(),
|
||||
"search_terms".into(),
|
||||
"signatures".into(),
|
||||
"usage".into(),
|
||||
];
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for $nu.scope.commands.signatures
|
||||
let suggestions = completer.complete("$nu.scope.commands.signatures.", 30);
|
||||
assert_eq!(17, suggestions.len());
|
||||
let expected: Vec<String> = vec![
|
||||
"any".into(),
|
||||
"binary".into(),
|
||||
"bool".into(),
|
||||
"datetime".into(),
|
||||
"duration".into(),
|
||||
"filesize".into(),
|
||||
"int".into(),
|
||||
"list<any>".into(),
|
||||
"list<binary>".into(),
|
||||
"list<number>".into(),
|
||||
"list<string>".into(),
|
||||
"nothing".into(),
|
||||
"number".into(),
|
||||
"range".into(),
|
||||
"record".into(),
|
||||
"string".into(),
|
||||
"table".into(),
|
||||
];
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for $nu.scope.engine_state
|
||||
let suggestions = completer.complete("$nu.scope.engine_state.", 23);
|
||||
assert_eq!(6, suggestions.len());
|
||||
let expected: Vec<String> = vec![
|
||||
"num_blocks".into(),
|
||||
"num_decls".into(),
|
||||
"num_env_vars".into(),
|
||||
"num_modules".into(),
|
||||
"num_vars".into(),
|
||||
"source_bytes".into(),
|
||||
];
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for $nu.scope.vars
|
||||
let suggestions = completer.complete("$nu.scope.vars.", 15);
|
||||
assert_eq!(3, suggestions.len());
|
||||
let expected: Vec<String> = vec!["name".into(), "type".into(), "value".into()];
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for custom var
|
||||
let suggestions = completer.complete("$actor.", 7);
|
||||
|
||||
|
@ -6,17 +6,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.79.0"
|
||||
version = "0.80.0"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.79.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.79.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.79.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.79.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.79.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.80.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.80.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.80.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.80.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.80.0" }
|
||||
|
||||
nu-ansi-term = "0.47.0"
|
||||
|
||||
@ -29,4 +29,4 @@ shadow-rs = { version = "0.21.0", default-features = false }
|
||||
shadow-rs = { version = "0.21.0", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="../nu-test-support", version = "0.79.0" }
|
||||
nu-test-support = { path="../nu-test-support", version = "0.80.0" }
|
||||
|
@ -16,7 +16,11 @@ impl Command for ErrorMake {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("error make")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Error)])
|
||||
.required("error_struct", SyntaxShape::Record, "the error to create")
|
||||
.required(
|
||||
"error_struct",
|
||||
SyntaxShape::Record(vec![]),
|
||||
"the error to create",
|
||||
)
|
||||
.switch(
|
||||
"unspanned",
|
||||
"remove the origin label from the error",
|
||||
|
75
crates/nu-cmd-lang/src/core_commands/export_module.rs
Normal file
75
crates/nu-cmd-lang/src/core_commands/export_module.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportModule;
|
||||
|
||||
impl Command for ExportModule {
|
||||
fn name(&self) -> &str {
|
||||
"export module"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Export a custom module from a module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("export module")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.allow_variants_without_examples(true)
|
||||
.required("module", SyntaxShape::String, "module name or module path")
|
||||
.optional(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"body of the module if 'module' parameter is not a path",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Define a custom command in a submodule of a module and call it",
|
||||
example: r#"module spam {
|
||||
export module eggs {
|
||||
export def foo [] { "foo" }
|
||||
}
|
||||
}
|
||||
use spam eggs
|
||||
eggs foo"#,
|
||||
result: Some(Value::test_string("foo")),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(ExportModule {})
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ impl Command for Extern {
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("def_name", SyntaxShape::String, "definition name")
|
||||
.required("params", SyntaxShape::Signature, "parameters")
|
||||
.optional("body", SyntaxShape::Block, "wrapper function block")
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ mod export_alias;
|
||||
mod export_def;
|
||||
mod export_def_env;
|
||||
mod export_extern;
|
||||
mod export_module;
|
||||
mod export_use;
|
||||
mod extern_;
|
||||
mod for_;
|
||||
@ -55,6 +56,7 @@ pub use export_alias::ExportAlias;
|
||||
pub use export_def::ExportDef;
|
||||
pub use export_def_env::ExportDefEnv;
|
||||
pub use export_extern::ExportExtern;
|
||||
pub use export_module::ExportModule;
|
||||
pub use export_use::ExportUse;
|
||||
pub use extern_::Extern;
|
||||
pub use for_::For;
|
||||
|
@ -19,8 +19,13 @@ impl Command for Module {
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("module")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("module_name", SyntaxShape::String, "module name")
|
||||
.required("block", SyntaxShape::Block, "body of the module")
|
||||
.allow_variants_without_examples(true)
|
||||
.required("module", SyntaxShape::String, "module name or module path")
|
||||
.optional(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"body of the module if 'module' parameter is not a module path",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
|
@ -14,14 +14,14 @@ impl Command for Use {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Use definitions from a module."
|
||||
"Use definitions from a module, making them available in your shell."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("use")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("module", SyntaxShape::String, "Module or module file")
|
||||
.optional(
|
||||
.rest(
|
||||
"members",
|
||||
SyntaxShape::Any,
|
||||
"Which members of the module to import",
|
||||
@ -30,7 +30,10 @@ impl Command for Use {
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
r#"See `help std` for the standard library module.
|
||||
See `help modules` to list all available modules.
|
||||
|
||||
This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
@ -134,6 +137,26 @@ impl Command for Use {
|
||||
example: r#"module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
|
||||
result: Some(Value::test_string("BAZ")),
|
||||
},
|
||||
Example {
|
||||
description: "Use a plain module name to import its definitions qualified by the module name",
|
||||
example: r#"module spam { export def foo [] { "foo" }; export def bar [] { "bar" } }; use spam; (spam foo) + (spam bar)"#,
|
||||
result: Some(Value::test_string("foobar")),
|
||||
},
|
||||
Example {
|
||||
description: "Specify * to use all definitions in a module",
|
||||
example: r#"module spam { export def foo [] { "foo" }; export def bar [] { "bar" } }; use spam *; (foo) + (bar)"#,
|
||||
result: Some(Value::test_string("foobar")),
|
||||
},
|
||||
Example {
|
||||
description: "To use commands with spaces, like subcommands, surround them with quotes",
|
||||
example: r#"module spam { export def 'foo bar' [] { "baz" } }; use spam 'foo bar'; foo bar"#,
|
||||
result: Some(Value::test_string("baz")),
|
||||
},
|
||||
Example {
|
||||
description: "To use multiple definitions from a module, wrap them in a list",
|
||||
example: r#"module spam { export def foo [] { "foo" }; export def 'foo bar' [] { "baz" } }; use spam ['foo', 'foo bar']; (foo) + (foo bar)"#,
|
||||
result: Some(Value::test_string("foobaz")),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ pub fn create_default_context() -> EngineState {
|
||||
ExportDefEnv,
|
||||
ExportExtern,
|
||||
ExportUse,
|
||||
ExportModule,
|
||||
Extern,
|
||||
For,
|
||||
Help,
|
||||
|
@ -13,11 +13,12 @@ mod test_examples {
|
||||
check_example_evaluates_to_expected_output,
|
||||
check_example_input_and_output_types_match_command_signature,
|
||||
};
|
||||
use crate::{Break, Collect, Describe, Mut};
|
||||
use crate::{Echo, If, Let};
|
||||
use crate::{
|
||||
Break, Collect, Def, Describe, Echo, ExportCommand, ExportDef, If, Let, Module, Mut, Use,
|
||||
};
|
||||
use nu_protocol::{
|
||||
engine::{Command, EngineState, StateWorkingSet},
|
||||
Type,
|
||||
Type, Value,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
|
||||
@ -55,18 +56,28 @@ mod test_examples {
|
||||
|
||||
fn make_engine_state(cmd: Box<dyn Command>) -> Box<EngineState> {
|
||||
let mut engine_state = Box::new(EngineState::new());
|
||||
let cwd = std::env::current_dir()
|
||||
.expect("Could not get current working directory.")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
engine_state.add_env_var("PWD".to_string(), Value::test_string(cwd));
|
||||
|
||||
let delta = {
|
||||
// Base functions that are needed for testing
|
||||
// Try to keep this working set small to keep tests running as fast as possible
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
working_set.add_decl(Box::new(Break));
|
||||
working_set.add_decl(Box::new(Collect));
|
||||
working_set.add_decl(Box::new(Def));
|
||||
working_set.add_decl(Box::new(Describe));
|
||||
working_set.add_decl(Box::new(Echo));
|
||||
working_set.add_decl(Box::new(ExportCommand));
|
||||
working_set.add_decl(Box::new(ExportDef));
|
||||
working_set.add_decl(Box::new(If));
|
||||
working_set.add_decl(Box::new(Let));
|
||||
working_set.add_decl(Box::new(Module));
|
||||
working_set.add_decl(Box::new(Mut));
|
||||
working_set.add_decl(Box::new(Collect));
|
||||
working_set.add_decl(Box::new(Use));
|
||||
|
||||
// Adding the command that is being tested to the working set
|
||||
working_set.add_decl(cmd);
|
||||
|
@ -5,21 +5,19 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-color-config"
|
||||
version = "0.79.0"
|
||||
version = "0.80.0"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
serde = { version="1.0.123", features=["derive"] }
|
||||
# used only for text_style Alignments
|
||||
tabled = { version = "0.10.0", features = ["color"], default-features = false }
|
||||
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.79.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.80.0" }
|
||||
nu-ansi-term = "0.47.0"
|
||||
nu-utils = { path = "../nu-utils", version = "0.79.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.79.0" }
|
||||
nu-json = { path="../nu-json", version = "0.79.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.80.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.80.0" }
|
||||
nu-json = { path="../nu-json", version = "0.80.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="../nu-test-support", version = "0.79.0" }
|
||||
nu-test-support = { path="../nu-test-support", version = "0.80.0" }
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::text_style::Alignment;
|
||||
use crate::{color_record_to_nustyle, lookup_ansi_color_style, TextStyle};
|
||||
use nu_ansi_term::{Color, Style};
|
||||
use nu_engine::eval_block;
|
||||
@ -5,7 +6,6 @@ use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
CliError, IntoPipelineData, Value,
|
||||
};
|
||||
use tabled::alignment::AlignmentHorizontal;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@ -111,34 +111,28 @@ impl<'a> StyleComputer<'a> {
|
||||
|
||||
// Used only by the `table` command.
|
||||
pub fn style_primitive(&self, value: &Value) -> TextStyle {
|
||||
use Alignment::*;
|
||||
let s = self.compute(&value.get_type().get_non_specified_string(), value);
|
||||
match *value {
|
||||
Value::Bool { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||
|
||||
Value::Int { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s),
|
||||
|
||||
Value::Filesize { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s),
|
||||
|
||||
Value::Duration { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s),
|
||||
|
||||
Value::Date { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||
|
||||
Value::Range { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||
|
||||
Value::Float { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s),
|
||||
|
||||
Value::String { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||
|
||||
Value::Nothing { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||
|
||||
Value::Binary { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||
|
||||
Value::CellPath { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||
|
||||
Value::Bool { .. } => TextStyle::with_style(Left, s),
|
||||
Value::Int { .. } => TextStyle::with_style(Right, s),
|
||||
Value::Filesize { .. } => TextStyle::with_style(Right, s),
|
||||
Value::Duration { .. } => TextStyle::with_style(Right, s),
|
||||
Value::Date { .. } => TextStyle::with_style(Left, s),
|
||||
Value::Range { .. } => TextStyle::with_style(Left, s),
|
||||
Value::Float { .. } => TextStyle::with_style(Right, s),
|
||||
Value::String { .. } => TextStyle::with_style(Left, s),
|
||||
Value::Nothing { .. } => TextStyle::with_style(Left, s),
|
||||
Value::Binary { .. } => TextStyle::with_style(Left, s),
|
||||
Value::CellPath { .. } => TextStyle::with_style(Left, s),
|
||||
Value::Record { .. } | Value::List { .. } | Value::Block { .. } => {
|
||||
TextStyle::with_style(AlignmentHorizontal::Left, s)
|
||||
TextStyle::with_style(Left, s)
|
||||
}
|
||||
_ => TextStyle::basic_left(),
|
||||
Value::Closure { .. }
|
||||
| Value::CustomValue { .. }
|
||||
| Value::Error { .. }
|
||||
| Value::LazyRecord { .. }
|
||||
| Value::MatchPattern { .. } => TextStyle::basic_left(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,11 @@
|
||||
use nu_ansi_term::{Color, Style};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub type Alignment = tabled::alignment::AlignmentHorizontal;
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Alignment {
|
||||
Center,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TextStyle {
|
||||
@ -240,23 +244,3 @@ impl Default for TextStyle {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl tabled::papergrid::Color for TextStyle {
|
||||
fn fmt_prefix(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(color) = &self.color_style {
|
||||
color.prefix().fmt(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fmt_suffix(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(color) = &self.color_style {
|
||||
if !color.is_plain() {
|
||||
f.write_str("\u{1b}[0m")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-command"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||
version = "0.79.0"
|
||||
version = "0.80.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -13,22 +13,21 @@ version = "0.79.0"
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.79.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.79.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.79.0" }
|
||||
nu-explore = { path = "../nu-explore", version = "0.79.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.79.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.79.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.79.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.79.0" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.79.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.79.0" }
|
||||
nu-system = { path = "../nu-system", version = "0.79.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.79.0" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.79.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.79.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.80.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.80.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.80.0" }
|
||||
nu-explore = { path = "../nu-explore", version = "0.80.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.80.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.80.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.80.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.80.0" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.80.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.80.0" }
|
||||
nu-system = { path = "../nu-system", version = "0.80.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.80.0" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.80.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.80.0" }
|
||||
num-format = { version = "0.4.3" }
|
||||
|
||||
nu-ansi-term = "0.47.0"
|
||||
|
||||
# Potential dependencies for extras
|
||||
@ -39,12 +38,15 @@ base64 = "0.21.0"
|
||||
byteorder = "1.4.3"
|
||||
bytesize = "1.2.0"
|
||||
calamine = "0.19.1"
|
||||
chrono = { version = "0.4.23", features = ["std", "unstable-locales"], default-features = false }
|
||||
chrono = { version = "0.4.23", features = [
|
||||
"std",
|
||||
"unstable-locales",
|
||||
], default-features = false }
|
||||
chrono-humanize = "0.2.1"
|
||||
chrono-tz = "0.8.1"
|
||||
crossterm = "0.26"
|
||||
csv = "1.2.0"
|
||||
dialoguer = { default-features = false, version = "0.10.3" }
|
||||
dialoguer = { default-features = false, features = ["fuzzy-select"], version = "0.10.3" }
|
||||
digest = { default-features = false, version = "0.10.0" }
|
||||
dtparse = "1.4.0"
|
||||
encoding_rs = "0.8.30"
|
||||
@ -74,12 +76,18 @@ quick-xml = "0.28"
|
||||
rand = "0.8"
|
||||
rayon = "1.7.0"
|
||||
regex = "1.7.1"
|
||||
ureq = { version = "2.6.2", default-features = false, features = ["json", "charset", "native-tls", "gzip"] }
|
||||
ureq = { version = "2.6.2", default-features = false, features = [
|
||||
"json",
|
||||
"charset",
|
||||
"native-tls",
|
||||
"gzip",
|
||||
] }
|
||||
native-tls = "0.2.11"
|
||||
roxmltree = "0.18.0"
|
||||
rust-embed = "6.6.0"
|
||||
same-file = "1.0.6"
|
||||
serde = { version = "1.0.123", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_urlencoded = "0.7.0"
|
||||
serde_yaml = "0.9.4"
|
||||
sha2 = "0.10.0"
|
||||
@ -88,7 +96,7 @@ percent-encoding = "2.2.0"
|
||||
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
||||
sqlparser = { version = "0.32.0", features = ["serde"], optional = true }
|
||||
sysinfo = "0.28.2"
|
||||
tabled = "0.10.0"
|
||||
tabled = "0.12.0"
|
||||
terminal_size = "0.2.1"
|
||||
thiserror = "1.0.31"
|
||||
titlecase = "2.0.0"
|
||||
@ -100,6 +108,7 @@ uuid = { version = "1.3.0", features = ["v4"] }
|
||||
wax = { version = "0.5.0" }
|
||||
which = { version = "4.4.0", optional = true }
|
||||
print-positions = "0.6.1"
|
||||
os_pipe = "1.1.3"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winreg = "0.50.0"
|
||||
@ -119,7 +128,7 @@ features = [
|
||||
"checked_arithmetic",
|
||||
"concat_str",
|
||||
"cross_join",
|
||||
"csv-file",
|
||||
"csv",
|
||||
"cum_agg",
|
||||
"default",
|
||||
"dtype-categorical",
|
||||
@ -142,7 +151,7 @@ features = [
|
||||
"to_dummies",
|
||||
]
|
||||
optional = true
|
||||
version = "0.27.2"
|
||||
version = "0.29.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
features = ["Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_SystemServices"]
|
||||
@ -151,12 +160,14 @@ version = "0.48.0"
|
||||
[features]
|
||||
dataframe = ["num", "polars", "sqlparser"]
|
||||
plugin = ["nu-parser/plugin"]
|
||||
sqlite = ["rusqlite"] # TODO: given that rusqlite is included in reedline, should we just always include it?
|
||||
sqlite = [
|
||||
"rusqlite",
|
||||
] # TODO: given that rusqlite is included in reedline, should we just always include it?
|
||||
trash-support = ["trash"]
|
||||
which-support = ["which"]
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.79.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.80.0" }
|
||||
mockito = "1.0.0"
|
||||
|
||||
dirs-next = "2.0.0"
|
||||
|
@ -179,7 +179,10 @@ fn into_duration(
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let span = match input.span() {
|
||||
Some(t) => t,
|
||||
None => call.head,
|
||||
};
|
||||
let convert_to_unit: Option<Spanned<String>> = call.get_flag(engine_state, stack, "convert")?;
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
let config = engine_state.get_config();
|
||||
@ -188,14 +191,14 @@ fn into_duration(
|
||||
input.map(
|
||||
move |v| {
|
||||
if column_paths.is_empty() {
|
||||
action(&v, &convert_to_unit, float_precision, head)
|
||||
action(&v, &convert_to_unit, float_precision, span)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
for path in &column_paths {
|
||||
let d = convert_to_unit.clone();
|
||||
let r = ret.update_cell_path(
|
||||
&path.members,
|
||||
Box::new(move |old| action(old, &d, float_precision, head)),
|
||||
Box::new(move |old| action(old, &d, float_precision, span)),
|
||||
);
|
||||
if let Err(error) = r {
|
||||
return Value::Error {
|
||||
|
@ -97,7 +97,7 @@ fn command(
|
||||
};
|
||||
|
||||
df.as_ref()
|
||||
.unique(subset_slice, keep_strategy)
|
||||
.unique(subset_slice, keep_strategy, None)
|
||||
.map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error dropping duplicates".into(),
|
||||
|
@ -9,8 +9,8 @@ use nu_protocol::{
|
||||
use std::{fs::File, io::BufReader, path::PathBuf};
|
||||
|
||||
use polars::prelude::{
|
||||
CsvEncoding, CsvReader, IpcReader, JsonReader, LazyCsvReader, LazyFrame, ParallelStrategy,
|
||||
ParquetReader, ScanArgsIpc, ScanArgsParquet, SerReader,
|
||||
CsvEncoding, CsvReader, IpcReader, JsonReader, LazyCsvReader, LazyFileListReader, LazyFrame,
|
||||
ParallelStrategy, ParquetReader, ScanArgsIpc, ScanArgsParquet, SerReader,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -146,6 +146,7 @@ fn from_parquet(
|
||||
row_count: None,
|
||||
low_memory: false,
|
||||
cloud_options: None,
|
||||
use_statistics: false,
|
||||
};
|
||||
|
||||
let df: NuLazyFrame = LazyFrame::scan_parquet(file, args)
|
||||
|
@ -163,8 +163,8 @@ impl SQLContext {
|
||||
.collect()
|
||||
.unwrap_or_default()
|
||||
.schema()
|
||||
.get_index(shm_p)
|
||||
.unwrap_or((&"".to_string(), &DataType::Null))
|
||||
.get_at_index(shm_p)
|
||||
.unwrap_or((&"".into(), &DataType::Null))
|
||||
.0)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -134,15 +134,15 @@ macro_rules! expr_command {
|
||||
// Expands to a command definition for a list expression
|
||||
expr_command!(
|
||||
ExprList,
|
||||
"dfr list",
|
||||
"dfr implode",
|
||||
"Aggregates a group to a Series",
|
||||
vec![Example {
|
||||
description: "",
|
||||
example: "",
|
||||
result: None,
|
||||
}],
|
||||
list,
|
||||
test_list
|
||||
implode,
|
||||
test_implode
|
||||
);
|
||||
|
||||
// ExprAggGroups command
|
||||
|
@ -36,7 +36,7 @@ impl Command for ExprLit {
|
||||
example: "dfr lit 2 | dfr into-nu",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["expr".into(), "value".into()],
|
||||
vals: vec![Value::test_string("literal"), Value::test_string("2i64")],
|
||||
vals: vec![Value::test_string("literal"), Value::test_string("2")],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
|
@ -160,7 +160,7 @@ fn get_col_name(expr: &Expr) -> Option<String> {
|
||||
| polars::prelude::AggExpr::First(e)
|
||||
| polars::prelude::AggExpr::Last(e)
|
||||
| polars::prelude::AggExpr::Mean(e)
|
||||
| polars::prelude::AggExpr::List(e)
|
||||
| polars::prelude::AggExpr::Implode(e)
|
||||
| polars::prelude::AggExpr::Count(e)
|
||||
| polars::prelude::AggExpr::Sum(e)
|
||||
| polars::prelude::AggExpr::AggGroups(e)
|
||||
@ -170,6 +170,7 @@ fn get_col_name(expr: &Expr) -> Option<String> {
|
||||
},
|
||||
Expr::Filter { input: expr, .. }
|
||||
| Expr::Slice { input: expr, .. }
|
||||
| Expr::Cache { input: expr, .. }
|
||||
| Expr::Cast { expr, .. }
|
||||
| Expr::Sort { expr, .. }
|
||||
| Expr::Take { expr, .. }
|
||||
|
@ -98,7 +98,10 @@ fn command(
|
||||
multithreaded: true,
|
||||
};
|
||||
|
||||
let mut res = df.as_series(call.head)?.argsort(sort_options).into_series();
|
||||
let mut res = df
|
||||
.as_series(call.head)?
|
||||
.arg_sort(sort_options)
|
||||
.into_series();
|
||||
res.rename("arg_sort");
|
||||
|
||||
NuDataFrame::try_from_series(vec![res], call.head)
|
||||
|
@ -146,6 +146,7 @@ fn command(
|
||||
by: None,
|
||||
closed_window: None,
|
||||
tu: None,
|
||||
tz: None,
|
||||
};
|
||||
let res = match roll_type {
|
||||
RollType::Max => series.rolling_max(rolling_opts),
|
||||
|
@ -82,7 +82,7 @@ fn command(
|
||||
)
|
||||
})?;
|
||||
|
||||
let res = chunked.contains(&pattern).map_err(|e| {
|
||||
let res = chunked.contains(&pattern, false).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error searching in series".into(),
|
||||
e.to_string(),
|
||||
|
@ -80,7 +80,18 @@ fn command(
|
||||
)
|
||||
})?;
|
||||
|
||||
let res = casted.strftime(&fmt).into_series();
|
||||
let res = casted
|
||||
.strftime(&fmt)
|
||||
.map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error formatting datetime".into(),
|
||||
e.to_string(),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?
|
||||
.into_series();
|
||||
|
||||
NuDataFrame::try_from_series(vec![res.into_series()], call.head)
|
||||
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
|
||||
|
@ -736,7 +736,7 @@ fn contains_series_pat(series: &Series, pat: &str, span: Span) -> Result<Value,
|
||||
let casted = series.utf8();
|
||||
match casted {
|
||||
Ok(casted) => {
|
||||
let res = casted.contains(pat);
|
||||
let res = casted.contains(pat, false);
|
||||
|
||||
match res {
|
||||
Ok(res) => {
|
||||
|
@ -277,7 +277,7 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
|
||||
| AggExpr::First(expr)
|
||||
| AggExpr::Last(expr)
|
||||
| AggExpr::Mean(expr)
|
||||
| AggExpr::List(expr)
|
||||
| AggExpr::Implode(expr)
|
||||
| AggExpr::Count(expr)
|
||||
| AggExpr::Sum(expr)
|
||||
| AggExpr::AggGroups(expr)
|
||||
@ -426,25 +426,29 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
|
||||
span,
|
||||
}
|
||||
}
|
||||
Expr::SortBy { expr, by, reverse } => {
|
||||
Expr::SortBy {
|
||||
expr,
|
||||
by,
|
||||
descending,
|
||||
} => {
|
||||
let expr = expr_to_value(expr.as_ref(), span);
|
||||
let by: Vec<Value> = by.iter().map(|b| expr_to_value(b, span)).collect();
|
||||
let by = Value::List { vals: by, span };
|
||||
|
||||
let reverse: Vec<Value> = reverse
|
||||
let descending: Vec<Value> = descending
|
||||
.iter()
|
||||
.map(|r| Value::Bool { val: *r, span })
|
||||
.collect();
|
||||
let reverse = Value::List {
|
||||
vals: reverse,
|
||||
let descending = Value::List {
|
||||
vals: descending,
|
||||
span,
|
||||
};
|
||||
|
||||
let cols = vec!["expr".into(), "by".into(), "reverse".into()];
|
||||
let cols = vec!["expr".into(), "by".into(), "descending".into()];
|
||||
|
||||
Value::Record {
|
||||
cols,
|
||||
vals: vec![expr, by, reverse],
|
||||
vals: vec![expr, by, descending],
|
||||
span,
|
||||
}
|
||||
}
|
||||
@ -574,6 +578,21 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
|
||||
span,
|
||||
}
|
||||
}
|
||||
Expr::Cache { input, id } => {
|
||||
let input = expr_to_value(input.as_ref(), span);
|
||||
let id = Value::String {
|
||||
val: format!("{id:?}"),
|
||||
span,
|
||||
};
|
||||
|
||||
let cols = vec!["input".into(), "id".into()];
|
||||
|
||||
Value::Record {
|
||||
cols,
|
||||
vals: vec![input, id],
|
||||
span,
|
||||
}
|
||||
}
|
||||
Expr::Window {
|
||||
function,
|
||||
partition_by,
|
||||
|
@ -3,8 +3,8 @@ use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack, StateWorkingSet},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||
Type, Value,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||
SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -27,6 +27,8 @@ impl Command for Ast {
|
||||
SyntaxShape::String,
|
||||
"the pipeline to print the ast for",
|
||||
)
|
||||
.switch("json", "serialize to json", Some('j'))
|
||||
.switch("minify", "minify the nuon or json output", Some('m'))
|
||||
.allow_variants_without_examples(true)
|
||||
.category(Category::Debug)
|
||||
}
|
||||
@ -39,26 +41,86 @@ impl Command for Ast {
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let pipeline: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
let to_json = call.has_flag("json");
|
||||
let minify = call.has_flag("minify");
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
let block_output = parse(&mut working_set, None, pipeline.item.as_bytes(), false);
|
||||
|
||||
let error_output = working_set.parse_errors.first();
|
||||
let block_span = match &block_output.span {
|
||||
Some(span) => span,
|
||||
None => &pipeline.span,
|
||||
};
|
||||
if to_json {
|
||||
// Get the block as json
|
||||
let serde_block_str = if minify {
|
||||
serde_json::to_string(&block_output)
|
||||
} else {
|
||||
serde_json::to_string_pretty(&block_output)
|
||||
};
|
||||
let block_json = match serde_block_str {
|
||||
Ok(json) => json,
|
||||
Err(e) => Err(ShellError::CantConvert {
|
||||
to_type: "string".to_string(),
|
||||
from_type: "block".to_string(),
|
||||
span: *block_span,
|
||||
help: Some(format!(
|
||||
"Error: {e}\nCan't convert {block_output:?} to string"
|
||||
)),
|
||||
})?,
|
||||
};
|
||||
// Get the error as json
|
||||
let serde_error_str = if minify {
|
||||
serde_json::to_string(&error_output)
|
||||
} else {
|
||||
serde_json::to_string_pretty(&error_output)
|
||||
};
|
||||
|
||||
let block_value = Value::String {
|
||||
val: format!("{block_output:#?}"),
|
||||
span: pipeline.span,
|
||||
};
|
||||
let error_value = Value::String {
|
||||
val: format!("{error_output:#?}"),
|
||||
span: pipeline.span,
|
||||
};
|
||||
let output_record = Value::Record {
|
||||
cols: vec!["block".to_string(), "error".to_string()],
|
||||
vals: vec![block_value, error_value],
|
||||
span: pipeline.span,
|
||||
};
|
||||
Ok(output_record.into_pipeline_data())
|
||||
let error_json = match serde_error_str {
|
||||
Ok(json) => json,
|
||||
Err(e) => Err(ShellError::CantConvert {
|
||||
to_type: "string".to_string(),
|
||||
from_type: "error".to_string(),
|
||||
span: *block_span,
|
||||
help: Some(format!(
|
||||
"Error: {e}\nCan't convert {error_output:?} to string"
|
||||
)),
|
||||
})?,
|
||||
};
|
||||
|
||||
// Create a new output record, merging the block and error
|
||||
let output_record = Value::Record {
|
||||
cols: vec!["block".to_string(), "error".to_string()],
|
||||
vals: vec![
|
||||
Value::string(block_json, *block_span),
|
||||
Value::string(error_json, Span::test_data()),
|
||||
],
|
||||
span: pipeline.span,
|
||||
};
|
||||
Ok(output_record.into_pipeline_data())
|
||||
} else {
|
||||
let block_value = Value::String {
|
||||
val: if minify {
|
||||
format!("{block_output:?}")
|
||||
} else {
|
||||
format!("{block_output:#?}")
|
||||
},
|
||||
span: pipeline.span,
|
||||
};
|
||||
let error_value = Value::String {
|
||||
val: if minify {
|
||||
format!("{error_output:?}")
|
||||
} else {
|
||||
format!("{error_output:#?}")
|
||||
},
|
||||
span: pipeline.span,
|
||||
};
|
||||
let output_record = Value::Record {
|
||||
cols: vec!["block".to_string(), "error".to_string()],
|
||||
vals: vec![block_value, error_value],
|
||||
span: pipeline.span,
|
||||
};
|
||||
Ok(output_record.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -78,6 +140,17 @@ impl Command for Ast {
|
||||
example: "ast 'for x in 1..10 { echo $x '",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Print the ast of a pipeline with an error, as json, in a nushell table",
|
||||
example: "ast 'for x in 1..10 { echo $x ' --json | get block | from json",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Print the ast of a pipeline with an error, as json, minified",
|
||||
example: "ast 'for x in 1..10 { echo $x ' -j -m",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,123 +1,193 @@
|
||||
use nu_protocol::Value;
|
||||
use nu_table::{string_width, string_wrap};
|
||||
use tabled::{
|
||||
builder::Builder,
|
||||
peaker::PriorityMax,
|
||||
width::{MinWidth, Wrap},
|
||||
Style,
|
||||
grid::config::ColoredConfig,
|
||||
settings::{peaker::PriorityMax, width::Wrap, Settings, Style},
|
||||
Table,
|
||||
};
|
||||
|
||||
use self::{
|
||||
global_horizontal_char::SetHorizontalCharOnFirstRow, peak2::Peak2,
|
||||
table_column_width::get_first_cell_width, truncate_table::TruncateTable,
|
||||
width_increase::IncWidth,
|
||||
use crate::debug::inspect_table::{
|
||||
global_horizontal_char::SetHorizontalChar, set_widths::SetWidths,
|
||||
};
|
||||
|
||||
pub fn build_table(value: Value, description: String, termsize: usize) -> String {
|
||||
let (head, mut data) = util::collect_input(value);
|
||||
let count_columns = head.len();
|
||||
data.insert(0, head);
|
||||
|
||||
let mut val_table = Builder::from(data).build();
|
||||
let val_table_width = val_table.total_width();
|
||||
let mut desc = description;
|
||||
let mut desc_width = string_width(&desc);
|
||||
let mut desc_table_width = get_total_width_2_column_table(11, desc_width);
|
||||
|
||||
let desc = vec![vec![String::from("description"), description]];
|
||||
let cfg = Table::default().with(Style::modern()).get_config().clone();
|
||||
let mut widths = get_data_widths(&data, count_columns);
|
||||
truncate_data(&mut data, &mut widths, &cfg, termsize);
|
||||
|
||||
let mut desc_table = Builder::from(desc).build();
|
||||
let desc_table_width = desc_table.total_width();
|
||||
let val_table_width = get_total_width2(&widths, &cfg);
|
||||
if val_table_width < desc_table_width {
|
||||
increase_widths(&mut widths, desc_table_width - val_table_width);
|
||||
increase_data_width(&mut data, &widths);
|
||||
}
|
||||
|
||||
if val_table_width > desc_table_width {
|
||||
increase_string_width(&mut desc, val_table_width);
|
||||
}
|
||||
|
||||
if desc_table_width > termsize {
|
||||
let delete_width = desc_table_width - termsize;
|
||||
if delete_width >= desc_width {
|
||||
// we can't fit in a description; we consider it's no point in showing then?
|
||||
return String::new();
|
||||
}
|
||||
|
||||
desc_width -= delete_width;
|
||||
desc = string_wrap(&desc, desc_width, false);
|
||||
desc_table_width = termsize;
|
||||
}
|
||||
|
||||
add_padding_to_widths(&mut widths);
|
||||
|
||||
#[allow(clippy::manual_clamp)]
|
||||
let width = val_table_width.max(desc_table_width).min(termsize);
|
||||
|
||||
desc_table
|
||||
.with(Style::rounded().off_bottom())
|
||||
.with(Wrap::new(width).priority::<PriorityMax>())
|
||||
.with(MinWidth::new(width).priority::<Peak2>());
|
||||
let mut desc_table = Table::from_iter([[String::from("description"), desc]]);
|
||||
desc_table.with(Style::rounded().remove_bottom().remove_horizontals());
|
||||
|
||||
val_table
|
||||
.with(Style::rounded().top_left_corner('├').top_right_corner('┤'))
|
||||
.with(TruncateTable(width))
|
||||
.with(Wrap::new(width).priority::<PriorityMax>())
|
||||
.with(IncWidth(width));
|
||||
|
||||
// we use only 1, cause left border considered 0 position
|
||||
let count_split_lines = 1;
|
||||
let desc_width = get_first_cell_width(&mut desc_table) + count_split_lines;
|
||||
|
||||
val_table.with(SetHorizontalCharOnFirstRow::new('┼', '┴', desc_width));
|
||||
let mut val_table = Table::from_iter(data);
|
||||
val_table.with(
|
||||
Settings::default()
|
||||
.with(Style::rounded().corner_top_left('├').corner_top_right('┤'))
|
||||
.with(SetWidths(widths))
|
||||
.with(Wrap::new(width).priority::<PriorityMax>())
|
||||
.with(SetHorizontalChar::new('┼', '┴', 11 + 2 + 1)),
|
||||
);
|
||||
|
||||
format!("{desc_table}\n{val_table}")
|
||||
}
|
||||
|
||||
mod truncate_table {
|
||||
use tabled::{
|
||||
papergrid::{
|
||||
records::{Records, RecordsMut, Resizable},
|
||||
width::{CfgWidthFunction, WidthEstimator},
|
||||
Estimate,
|
||||
},
|
||||
TableOption,
|
||||
};
|
||||
|
||||
pub struct TruncateTable(pub usize);
|
||||
|
||||
impl<R> TableOption<R> for TruncateTable
|
||||
where
|
||||
R: Records + RecordsMut<String> + Resizable,
|
||||
{
|
||||
fn change(&mut self, table: &mut tabled::Table<R>) {
|
||||
let width = table.total_width();
|
||||
if width <= self.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let count_columns = table.get_records().count_columns();
|
||||
if count_columns < 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut evaluator = WidthEstimator::default();
|
||||
evaluator.estimate(table.get_records(), table.get_config());
|
||||
let columns_width: Vec<_> = evaluator.into();
|
||||
|
||||
const SPLIT_LINE_WIDTH: usize = 1;
|
||||
let mut width = 0;
|
||||
let mut i = 0;
|
||||
for w in columns_width {
|
||||
width += w + SPLIT_LINE_WIDTH;
|
||||
|
||||
if width >= self.0 {
|
||||
break;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if i == 0 && count_columns > 0 {
|
||||
i = 1;
|
||||
} else if i + 1 == count_columns {
|
||||
// we want to left at least 1 column
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
let count_columns = table.get_records().count_columns();
|
||||
let y = count_columns - i;
|
||||
|
||||
let mut column = count_columns;
|
||||
for _ in 0..y {
|
||||
column -= 1;
|
||||
table.get_records_mut().remove_column(column);
|
||||
}
|
||||
|
||||
table.get_records_mut().push_column();
|
||||
|
||||
let width_ctrl = CfgWidthFunction::from_cfg(table.get_config());
|
||||
let last_column = table.get_records().count_columns() - 1;
|
||||
for row in 0..table.get_records().count_rows() {
|
||||
table
|
||||
.get_records_mut()
|
||||
.set((row, last_column), String::from("‥"), &width_ctrl)
|
||||
}
|
||||
fn get_data_widths(data: &[Vec<String>], count_columns: usize) -> Vec<usize> {
|
||||
let mut widths = vec![0; count_columns];
|
||||
for row in data {
|
||||
for col in 0..count_columns {
|
||||
let text = &row[col];
|
||||
let width = string_width(text);
|
||||
widths[col] = std::cmp::max(widths[col], width);
|
||||
}
|
||||
}
|
||||
|
||||
widths
|
||||
}
|
||||
|
||||
fn add_padding_to_widths(widths: &mut [usize]) {
|
||||
for width in widths {
|
||||
*width += 2;
|
||||
}
|
||||
}
|
||||
|
||||
fn increase_widths(widths: &mut [usize], need: usize) {
|
||||
let all = need / widths.len();
|
||||
let mut rest = need - all * widths.len();
|
||||
|
||||
for width in widths {
|
||||
*width += all;
|
||||
|
||||
if rest > 0 {
|
||||
*width += 1;
|
||||
rest -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn increase_data_width(data: &mut Vec<Vec<String>>, widths: &[usize]) {
|
||||
for row in data {
|
||||
for (col, max_width) in widths.iter().enumerate() {
|
||||
let text = &mut row[col];
|
||||
increase_string_width(text, *max_width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn increase_string_width(text: &mut String, total: usize) {
|
||||
let width = string_width(text);
|
||||
let rest = total - width;
|
||||
|
||||
if rest > 0 {
|
||||
text.extend(std::iter::repeat(' ').take(rest));
|
||||
}
|
||||
}
|
||||
|
||||
fn get_total_width_2_column_table(col1: usize, col2: usize) -> usize {
|
||||
const PAD: usize = 1;
|
||||
const SPLIT_LINE: usize = 1;
|
||||
SPLIT_LINE + PAD + col1 + PAD + SPLIT_LINE + PAD + col2 + PAD + SPLIT_LINE
|
||||
}
|
||||
|
||||
fn truncate_data(
|
||||
data: &mut Vec<Vec<String>>,
|
||||
widths: &mut Vec<usize>,
|
||||
cfg: &ColoredConfig,
|
||||
expected_width: usize,
|
||||
) {
|
||||
const SPLIT_LINE_WIDTH: usize = 1;
|
||||
const PAD: usize = 2;
|
||||
|
||||
let total_width = get_total_width2(widths, cfg);
|
||||
if total_width <= expected_width {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut width = 0;
|
||||
let mut peak_count = 0;
|
||||
for column_width in widths.iter() {
|
||||
let next_width = width + *column_width + SPLIT_LINE_WIDTH + PAD;
|
||||
if next_width >= expected_width {
|
||||
break;
|
||||
}
|
||||
|
||||
width = next_width;
|
||||
peak_count += 1;
|
||||
}
|
||||
|
||||
debug_assert!(peak_count < widths.len());
|
||||
|
||||
let left_space = expected_width - width;
|
||||
let has_space_for_truncation_column = left_space > PAD;
|
||||
if !has_space_for_truncation_column {
|
||||
peak_count -= 1;
|
||||
}
|
||||
|
||||
remove_columns(data, peak_count);
|
||||
widths.drain(peak_count..);
|
||||
push_empty_column(data);
|
||||
widths.push(1);
|
||||
}
|
||||
|
||||
fn remove_columns(data: &mut Vec<Vec<String>>, peak_count: usize) {
|
||||
if peak_count == 0 {
|
||||
for row in data {
|
||||
row.clear();
|
||||
}
|
||||
} else {
|
||||
for row in data {
|
||||
row.drain(peak_count..);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_total_width2(widths: &[usize], cfg: &ColoredConfig) -> usize {
|
||||
let pad = 2;
|
||||
let total = widths.iter().sum::<usize>() + pad * widths.len();
|
||||
let countv = cfg.count_vertical(widths.len());
|
||||
let margin = cfg.get_margin();
|
||||
|
||||
total + countv + margin.left.size + margin.right.size
|
||||
}
|
||||
|
||||
fn push_empty_column(data: &mut Vec<Vec<String>>) {
|
||||
let empty_cell = String::from("‥");
|
||||
for row in data {
|
||||
row.push(empty_cell.clone());
|
||||
}
|
||||
}
|
||||
|
||||
mod util {
|
||||
@ -223,135 +293,74 @@ mod util {
|
||||
}
|
||||
}
|
||||
|
||||
mod style_no_left_right_1st {
|
||||
use tabled::{papergrid::records::Records, Table, TableOption};
|
||||
|
||||
struct StyleOffLeftRightFirstLine;
|
||||
|
||||
impl<R> TableOption<R> for StyleOffLeftRightFirstLine
|
||||
where
|
||||
R: Records,
|
||||
{
|
||||
fn change(&mut self, table: &mut Table<R>) {
|
||||
let shape = table.shape();
|
||||
let cfg = table.get_config_mut();
|
||||
|
||||
let mut b = cfg.get_border((0, 0), shape);
|
||||
b.left = Some(' ');
|
||||
cfg.set_border((0, 0), b);
|
||||
|
||||
let mut b = cfg.get_border((0, shape.1 - 1), shape);
|
||||
b.right = Some(' ');
|
||||
cfg.set_border((0, 0), b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod peak2 {
|
||||
use tabled::peaker::Peaker;
|
||||
|
||||
pub struct Peak2;
|
||||
|
||||
impl Peaker for Peak2 {
|
||||
fn create() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn peak(&mut self, _: &[usize], _: &[usize]) -> Option<usize> {
|
||||
Some(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod table_column_width {
|
||||
use tabled::{
|
||||
papergrid::{records::Records, width::CfgWidthFunction},
|
||||
Table,
|
||||
};
|
||||
|
||||
pub fn get_first_cell_width<R: Records>(table: &mut Table<R>) -> usize {
|
||||
let mut opt = GetFirstCellWidth(0);
|
||||
table.with(&mut opt);
|
||||
opt.0
|
||||
}
|
||||
|
||||
struct GetFirstCellWidth(pub usize);
|
||||
|
||||
impl<R: Records> tabled::TableOption<R> for GetFirstCellWidth {
|
||||
fn change(&mut self, table: &mut tabled::Table<R>) {
|
||||
let w = table
|
||||
.get_records()
|
||||
.get_width((0, 0), CfgWidthFunction::default());
|
||||
let pad = table
|
||||
.get_config()
|
||||
.get_padding(tabled::papergrid::Entity::Cell(0, 0));
|
||||
let pad = pad.left.size + pad.right.size;
|
||||
|
||||
self.0 = w + pad;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod global_horizontal_char {
|
||||
use tabled::{
|
||||
papergrid::{records::Records, width::WidthEstimator, Estimate, Offset::Begin},
|
||||
Table, TableOption,
|
||||
grid::{
|
||||
config::{ColoredConfig, Offset},
|
||||
dimension::{CompleteDimensionVecRecords, Dimension},
|
||||
records::{ExactRecords, Records},
|
||||
},
|
||||
settings::TableOption,
|
||||
};
|
||||
|
||||
pub struct SetHorizontalCharOnFirstRow {
|
||||
c1: char,
|
||||
c2: char,
|
||||
pos: usize,
|
||||
pub struct SetHorizontalChar {
|
||||
intersection: char,
|
||||
split: char,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl SetHorizontalCharOnFirstRow {
|
||||
pub fn new(c1: char, c2: char, pos: usize) -> Self {
|
||||
Self { c1, c2, pos }
|
||||
impl SetHorizontalChar {
|
||||
pub fn new(intersection: char, split: char, index: usize) -> Self {
|
||||
Self {
|
||||
intersection,
|
||||
split,
|
||||
index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> TableOption<R> for SetHorizontalCharOnFirstRow
|
||||
where
|
||||
R: Records,
|
||||
impl<R: Records + ExactRecords> TableOption<R, CompleteDimensionVecRecords<'_>, ColoredConfig>
|
||||
for SetHorizontalChar
|
||||
{
|
||||
fn change(&mut self, table: &mut Table<R>) {
|
||||
if table.is_empty() {
|
||||
fn change(
|
||||
self,
|
||||
records: &mut R,
|
||||
cfg: &mut ColoredConfig,
|
||||
dimension: &mut CompleteDimensionVecRecords<'_>,
|
||||
) {
|
||||
let count_columns = records.count_columns();
|
||||
let count_rows = records.count_rows();
|
||||
|
||||
if count_columns == 0 || count_rows == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let shape = table.shape();
|
||||
let widths = get_widths(dimension, records.count_columns());
|
||||
|
||||
let mut evaluator = WidthEstimator::default();
|
||||
evaluator.estimate(table.get_records(), table.get_config());
|
||||
let widths: Vec<_> = evaluator.into();
|
||||
|
||||
let has_vertical = table.get_config().has_vertical(0, shape.1);
|
||||
if has_vertical && self.pos == 0 {
|
||||
let mut border = table.get_config().get_border((0, 0), shape);
|
||||
border.left_top_corner = Some(self.c1);
|
||||
table.get_config_mut().set_border((0, 0), border);
|
||||
let has_vertical = cfg.has_vertical(0, count_columns);
|
||||
if has_vertical && self.index == 0 {
|
||||
let mut border = cfg.get_border((0, 0), (count_rows, count_columns));
|
||||
border.left_top_corner = Some(self.intersection);
|
||||
cfg.set_border((0, 0), border);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut i = 1;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for (col, width) in widths.into_iter().enumerate() {
|
||||
if self.pos < i + width {
|
||||
let o = self.pos - i;
|
||||
table
|
||||
.get_config_mut()
|
||||
.override_horizontal_border((0, col), self.c2, Begin(o));
|
||||
if self.index < i + width {
|
||||
let o = self.index - i;
|
||||
cfg.set_horizontal_char((0, col), self.split, Offset::Begin(o));
|
||||
return;
|
||||
}
|
||||
|
||||
i += width;
|
||||
|
||||
let has_vertical = table.get_config().has_vertical(col, shape.1);
|
||||
let has_vertical = cfg.has_vertical(col, count_columns);
|
||||
if has_vertical {
|
||||
if self.pos == i {
|
||||
let mut border = table.get_config().get_border((0, col), shape);
|
||||
border.right_top_corner = Some(self.c1);
|
||||
table.get_config_mut().set_border((0, col), border);
|
||||
if self.index == i {
|
||||
let mut border = cfg.get_border((0, col), (count_rows, count_columns));
|
||||
border.right_top_corner = Some(self.intersection);
|
||||
cfg.set_border((0, col), border);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -360,96 +369,33 @@ mod global_horizontal_char {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod width_increase {
|
||||
use tabled::{
|
||||
object::Cell,
|
||||
papergrid::{
|
||||
records::{Records, RecordsMut},
|
||||
width::WidthEstimator,
|
||||
Entity, Estimate, GridConfig,
|
||||
},
|
||||
peaker::PriorityNone,
|
||||
Modify, Width,
|
||||
};
|
||||
|
||||
use tabled::{peaker::Peaker, Table, TableOption};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IncWidth(pub usize);
|
||||
|
||||
impl<R> TableOption<R> for IncWidth
|
||||
where
|
||||
R: Records + RecordsMut<String>,
|
||||
{
|
||||
fn change(&mut self, table: &mut Table<R>) {
|
||||
if table.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let (widths, total_width) =
|
||||
get_table_widths_with_total(table.get_records(), table.get_config());
|
||||
if total_width >= self.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let increase_list =
|
||||
get_increase_list(widths, self.0, total_width, PriorityNone::default());
|
||||
|
||||
for (col, width) in increase_list.into_iter().enumerate() {
|
||||
for row in 0..table.get_records().count_rows() {
|
||||
let pad = table.get_config().get_padding(Entity::Cell(row, col));
|
||||
let width = width - pad.left.size - pad.right.size;
|
||||
|
||||
table.with(Modify::new(Cell(row, col)).with(Width::increase(width)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_increase_list<F>(
|
||||
mut widths: Vec<usize>,
|
||||
total_width: usize,
|
||||
mut width: usize,
|
||||
mut peaker: F,
|
||||
) -> Vec<usize>
|
||||
where
|
||||
F: Peaker,
|
||||
{
|
||||
while width != total_width {
|
||||
let col = match peaker.peak(&[], &widths) {
|
||||
Some(col) => col,
|
||||
None => break,
|
||||
};
|
||||
|
||||
widths[col] += 1;
|
||||
width += 1;
|
||||
fn get_widths(dims: &CompleteDimensionVecRecords<'_>, count_columns: usize) -> Vec<usize> {
|
||||
let mut widths = vec![0; count_columns];
|
||||
for (col, width) in widths.iter_mut().enumerate() {
|
||||
*width = dims.get_width(col);
|
||||
}
|
||||
|
||||
widths
|
||||
}
|
||||
}
|
||||
|
||||
fn get_table_widths_with_total<R>(records: R, cfg: &GridConfig) -> (Vec<usize>, usize)
|
||||
where
|
||||
R: Records,
|
||||
{
|
||||
let mut evaluator = WidthEstimator::default();
|
||||
evaluator.estimate(&records, cfg);
|
||||
let total_width = get_table_total_width(&records, cfg, &evaluator);
|
||||
let widths = evaluator.into();
|
||||
mod set_widths {
|
||||
use tabled::{
|
||||
grid::{config::ColoredConfig, dimension::CompleteDimensionVecRecords},
|
||||
settings::TableOption,
|
||||
};
|
||||
|
||||
(widths, total_width)
|
||||
}
|
||||
pub struct SetWidths(pub Vec<usize>);
|
||||
|
||||
pub(crate) fn get_table_total_width<W, R>(records: R, cfg: &GridConfig, ctrl: &W) -> usize
|
||||
where
|
||||
W: Estimate<R>,
|
||||
R: Records,
|
||||
{
|
||||
ctrl.total()
|
||||
+ cfg.count_vertical(records.count_columns())
|
||||
+ cfg.get_margin().left.size
|
||||
+ cfg.get_margin().right.size
|
||||
impl<R> TableOption<R, CompleteDimensionVecRecords<'_>, ColoredConfig> for SetWidths {
|
||||
fn change(
|
||||
self,
|
||||
_: &mut R,
|
||||
_: &mut ColoredConfig,
|
||||
dims: &mut CompleteDimensionVecRecords<'_>,
|
||||
) {
|
||||
dims.set_widths(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,6 +103,7 @@ pub fn create_default_context() -> EngineState {
|
||||
|
||||
// Misc
|
||||
bind_command! {
|
||||
Source,
|
||||
Tutor,
|
||||
};
|
||||
|
||||
@ -256,6 +257,7 @@ pub fn create_default_context() -> EngineState {
|
||||
Clear,
|
||||
Du,
|
||||
Input,
|
||||
InputList,
|
||||
Kill,
|
||||
Sleep,
|
||||
TermSize,
|
||||
@ -275,12 +277,7 @@ pub fn create_default_context() -> EngineState {
|
||||
|
||||
// Shells
|
||||
bind_command! {
|
||||
Enter,
|
||||
Exit,
|
||||
GotoShell,
|
||||
NextShell,
|
||||
PrevShell,
|
||||
Shells,
|
||||
};
|
||||
|
||||
// Formats
|
||||
@ -437,13 +434,10 @@ pub fn create_default_context() -> EngineState {
|
||||
|
||||
// Deprecated
|
||||
bind_command! {
|
||||
ExportOldAlias,
|
||||
HashBase64,
|
||||
LPadDeprecated,
|
||||
MathEvalDeprecated,
|
||||
OldAlias,
|
||||
RPadDeprecated,
|
||||
Source,
|
||||
StrCollectDeprecated,
|
||||
StrDatetimeDeprecated,
|
||||
StrDecimalDeprecated,
|
||||
|
@ -24,5 +24,6 @@ pub fn deprecated_commands() -> HashMap<String, String> {
|
||||
("str rpad".to_string(), "fill".to_string()),
|
||||
("benchmark".to_string(), "timeit".to_string()),
|
||||
("str collect".to_string(), "str join".to_string()),
|
||||
("old-alias".to_string(), "alias".to_string()),
|
||||
])
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportOldAlias;
|
||||
|
||||
impl Command for ExportOldAlias {
|
||||
fn name(&self) -> &str {
|
||||
"export old-alias"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define an alias and export it from a module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("export old-alias")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("name", SyntaxShape::String, "name of the alias")
|
||||
.required(
|
||||
"initial_value",
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||
"equals sign followed by value",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "export an alias of ll to ls -l, from a module",
|
||||
example: "export old-alias ll = ls -l",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["aka", "abbr", "module"]
|
||||
}
|
||||
}
|
@ -1,12 +1,9 @@
|
||||
mod collect;
|
||||
mod deprecated_commands;
|
||||
mod export_old_alias;
|
||||
mod hash_base64;
|
||||
mod lpad;
|
||||
mod math_eval;
|
||||
mod old_alias;
|
||||
mod rpad;
|
||||
mod source;
|
||||
mod str_datetime;
|
||||
mod str_decimal;
|
||||
mod str_find_replace;
|
||||
@ -14,13 +11,10 @@ mod str_int;
|
||||
|
||||
pub use collect::StrCollectDeprecated;
|
||||
pub use deprecated_commands::*;
|
||||
pub use export_old_alias::ExportOldAlias;
|
||||
pub use hash_base64::HashBase64;
|
||||
pub use lpad::LPadDeprecated;
|
||||
pub use math_eval::SubCommand as MathEvalDeprecated;
|
||||
pub use old_alias::OldAlias;
|
||||
pub use rpad::RPadDeprecated;
|
||||
pub use source::Source;
|
||||
pub use str_datetime::StrDatetimeDeprecated;
|
||||
pub use str_decimal::StrDecimalDeprecated;
|
||||
pub use str_find_replace::StrFindReplaceDeprecated;
|
||||
|
@ -1,68 +0,0 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OldAlias;
|
||||
|
||||
impl Command for OldAlias {
|
||||
fn name(&self) -> &str {
|
||||
"old-alias"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Alias a command (with optional flags) to a new name."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("old-alias")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("name", SyntaxShape::String, "name of the alias")
|
||||
.required(
|
||||
"initial_value",
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||
"equals sign followed by value",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["abbr", "aka", "fn", "func", "function"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Alias ll to ls -l",
|
||||
example: "old-alias ll = ls -l",
|
||||
result: Some(Value::nothing(Span::test_data())),
|
||||
},
|
||||
Example {
|
||||
description: "Make an alias that makes a list of all custom commands",
|
||||
example: "old-alias customs = ($nu.scope.commands | where is_custom | get command)",
|
||||
result: Some(Value::nothing(Span::test_data())),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
1
crates/nu-command/src/env/config/utils.rs
vendored
1
crates/nu-command/src/env/config/utils.rs
vendored
@ -71,6 +71,7 @@ pub(crate) fn gen_command(
|
||||
arg_keep_raw: vec![false; number_of_args],
|
||||
redirect_stdout: false,
|
||||
redirect_stderr: false,
|
||||
redirect_combine: false,
|
||||
env_vars: env_vars_str,
|
||||
trim_end_newline: false,
|
||||
}
|
||||
|
2
crates/nu-command/src/env/load_env.rs
vendored
2
crates/nu-command/src/env/load_env.rs
vendored
@ -23,7 +23,7 @@ impl Command for LoadEnv {
|
||||
.allow_variants_without_examples(true)
|
||||
.optional(
|
||||
"update",
|
||||
SyntaxShape::Record,
|
||||
SyntaxShape::Record(vec![]),
|
||||
"the record to use for updates",
|
||||
)
|
||||
.category(Category::FileSystem)
|
||||
|
@ -1,5 +1,4 @@
|
||||
use crate::filesystem::cd_query::query;
|
||||
use crate::{get_current_shell, get_shells};
|
||||
#[cfg(unix)]
|
||||
use libc::gid_t;
|
||||
use nu_engine::{current_dir, CallExt};
|
||||
@ -164,23 +163,6 @@ impl Command for Cd {
|
||||
val: path.clone(),
|
||||
span,
|
||||
};
|
||||
let cwd = Value::string(cwd.to_string_lossy(), call.head);
|
||||
|
||||
let mut shells = get_shells(engine_state, stack, cwd);
|
||||
let current_shell = get_current_shell(engine_state, stack);
|
||||
shells[current_shell] = path_value.clone();
|
||||
|
||||
stack.add_env_var(
|
||||
"NUSHELL_SHELLS".into(),
|
||||
Value::List {
|
||||
vals: shells,
|
||||
span: call.head,
|
||||
},
|
||||
);
|
||||
stack.add_env_var(
|
||||
"NUSHELL_CURRENT_SHELL".into(),
|
||||
Value::int(current_shell as i64, call.head),
|
||||
);
|
||||
|
||||
if let Some(oldpwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
stack.add_env_var("OLDPWD".into(), oldpwd)
|
||||
|
@ -1,12 +1,15 @@
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
use nu_engine::env::current_dir;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Spanned,
|
||||
SyntaxShape, Type, Value,
|
||||
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
|
||||
Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
use wax::{Glob as WaxGlob, WalkBehavior};
|
||||
use wax::{Glob as WaxGlob, WalkBehavior, WalkEntry};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Glob;
|
||||
@ -41,6 +44,12 @@ impl Command for Glob {
|
||||
"Whether to filter out symlinks from the returned paths",
|
||||
Some('S'),
|
||||
)
|
||||
.named(
|
||||
"not",
|
||||
SyntaxShape::String,
|
||||
"Pattern to exclude from the results",
|
||||
Some('n'),
|
||||
)
|
||||
.category(Category::FileSystem)
|
||||
}
|
||||
|
||||
@ -101,6 +110,12 @@ impl Command for Glob {
|
||||
example: r#"glob "[A-Z]*" --no-file --no-symlink"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Search for files named tsconfig.json that are not in node_modules directories",
|
||||
example: r#"glob **/tsconfig.json --not **/node_modules/**"#,
|
||||
result: None,
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
@ -115,14 +130,15 @@ impl Command for Glob {
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let span = call.head;
|
||||
let path = current_dir(engine_state, stack)?;
|
||||
let glob_pattern: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
let depth = call.get_flag(engine_state, stack, "depth")?;
|
||||
|
||||
let no_dirs = call.has_flag("no-dir");
|
||||
let no_files = call.has_flag("no-file");
|
||||
let no_symlinks = call.has_flag("no-symlink");
|
||||
let not_pattern: Option<Spanned<String>> = call.get_flag(engine_state, stack, "not")?;
|
||||
|
||||
if glob_pattern.item.is_empty() {
|
||||
return Err(ShellError::GenericError(
|
||||
@ -153,31 +169,80 @@ impl Command for Glob {
|
||||
}
|
||||
};
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
let glob_results: Vec<Value> = glob
|
||||
.walk_with_behavior(
|
||||
path,
|
||||
WalkBehavior {
|
||||
depth: folder_depth,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.flatten()
|
||||
.filter(|entry| {
|
||||
let file_type = entry.file_type();
|
||||
let (not_pat, not_span) = if let Some(not_pat) = not_pattern.clone() {
|
||||
(not_pat.item, not_pat.span)
|
||||
} else {
|
||||
(String::new(), Span::test_data())
|
||||
};
|
||||
|
||||
!(no_dirs && file_type.is_dir()
|
||||
|| no_files && file_type.is_file()
|
||||
|| no_symlinks && file_type.is_symlink())
|
||||
})
|
||||
.map(|entry| Value::String {
|
||||
val: entry.into_path().to_string_lossy().to_string(),
|
||||
span,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(glob_results
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
Ok(if not_pattern.is_some() {
|
||||
let glob_results = glob
|
||||
.walk_with_behavior(
|
||||
path,
|
||||
WalkBehavior {
|
||||
depth: folder_depth,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.not([not_pat.as_str()])
|
||||
.map_err(|err| {
|
||||
ShellError::GenericError(
|
||||
"error with glob's not pattern".to_string(),
|
||||
format!("{err}"),
|
||||
Some(not_span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?
|
||||
.flatten();
|
||||
let result = glob_to_value(ctrlc, glob_results, no_dirs, no_files, no_symlinks, span)?;
|
||||
result
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone())
|
||||
} else {
|
||||
let glob_results = glob
|
||||
.walk_with_behavior(
|
||||
path,
|
||||
WalkBehavior {
|
||||
depth: folder_depth,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.flatten();
|
||||
let result = glob_to_value(ctrlc, glob_results, no_dirs, no_files, no_symlinks, span)?;
|
||||
result
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn glob_to_value<'a>(
|
||||
ctrlc: Option<Arc<AtomicBool>>,
|
||||
glob_results: impl Iterator<Item = WalkEntry<'a>>,
|
||||
no_dirs: bool,
|
||||
no_files: bool,
|
||||
no_symlinks: bool,
|
||||
span: Span,
|
||||
) -> Result<Vec<Value>, ShellError> {
|
||||
let mut result: Vec<Value> = Vec::new();
|
||||
for entry in glob_results {
|
||||
if nu_utils::ctrl_c::was_pressed(&ctrlc) {
|
||||
result.clear();
|
||||
return Err(ShellError::InterruptedByUser { span: None });
|
||||
}
|
||||
let file_type = entry.file_type();
|
||||
|
||||
if !(no_dirs && file_type.is_dir()
|
||||
|| no_files && file_type.is_file()
|
||||
|| no_symlinks && file_type.is_symlink())
|
||||
{
|
||||
result.push(Value::String {
|
||||
val: entry.into_path().to_string_lossy().to_string(),
|
||||
span,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ impl Command for Ls {
|
||||
} else if is_empty_dir(current_dir(engine_state, stack)?) {
|
||||
return Ok(Value::list(vec![], call_span).into_pipeline_data());
|
||||
} else {
|
||||
(PathBuf::from("./*"), call_span, false)
|
||||
(PathBuf::from("*"), call_span, false)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -318,8 +318,8 @@ fn rm(
|
||||
}
|
||||
|
||||
all_targets
|
||||
.into_keys()
|
||||
.map(move |f| {
|
||||
.into_iter()
|
||||
.map(move |(f, span)| {
|
||||
let is_empty = || match f.read_dir() {
|
||||
Ok(mut p) => p.next().is_none(),
|
||||
Err(_) => false,
|
||||
|
@ -124,7 +124,7 @@ impl Command for Find {
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Find value in records",
|
||||
description: "Find value in records using regex",
|
||||
example: r#"[[version name]; ['0.1.0' nushell] ['0.1.1' fish] ['0.2.0' zsh]] | find -r "nu""#,
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_record(
|
||||
@ -137,6 +137,51 @@ impl Command for Find {
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Find inverted values in records using regex",
|
||||
example: r#"[[version name]; ['0.1.0' nushell] ['0.1.1' fish] ['0.2.0' zsh]] | find -r "nu" --invert"#,
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::test_record(
|
||||
vec!["version", "name"],
|
||||
vec![
|
||||
Value::test_string("0.1.1"),
|
||||
Value::test_string("fish".to_string()),
|
||||
],
|
||||
),
|
||||
Value::test_record(
|
||||
vec!["version", "name"],
|
||||
vec![
|
||||
Value::test_string("0.2.0"),
|
||||
Value::test_string("zsh".to_string()),
|
||||
],
|
||||
),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Find value in list using regex",
|
||||
example: r#"[["Larry", "Moe"], ["Victor", "Marina"]] | find -r "rr""#,
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::List {
|
||||
vals: vec![Value::test_string("Larry"), Value::test_string("Moe")],
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Find inverted values in records using regex",
|
||||
example: r#"[["Larry", "Moe"], ["Victor", "Marina"]] | find -r "rr" --invert"#,
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::List {
|
||||
vals: vec![Value::test_string("Victor"), Value::test_string("Marina")],
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Remove ANSI sequences from result",
|
||||
example: "[[foo bar]; [abc 123] [def 456]] | find 123 | get bar | ansi strip",
|
||||
@ -144,29 +189,23 @@ impl Command for Find {
|
||||
},
|
||||
Example {
|
||||
description: "Find and highlight text in specific columns",
|
||||
example: "[[col1 col2 col3]; [moe larry curly] [larry curly moe]] | find moe -c [col1 col3]",
|
||||
example:
|
||||
"[[col1 col2 col3]; [moe larry curly] [larry curly moe]] | find moe -c [col1]",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::test_record(
|
||||
vec!["col1".to_string(), "col2".to_string(), "col3".to_string()],
|
||||
vec![
|
||||
Value::test_string("\u{1b}[37m\u{1b}[0m\u{1b}[41;37mmoe\u{1b}[0m\u{1b}[37m\u{1b}[0m".to_string()),
|
||||
Value::test_string("larry".to_string()),
|
||||
Value::test_string("curly".to_string()),
|
||||
]
|
||||
),
|
||||
Value::test_record(
|
||||
vec!["col1".to_string(), "col2".to_string(), "col3".to_string()],
|
||||
vec![
|
||||
Value::test_string("larry".to_string()),
|
||||
Value::test_string("curly".to_string()),
|
||||
Value::test_string("\u{1b}[37m\u{1b}[0m\u{1b}[41;37mmoe\u{1b}[0m\u{1b}[37m\u{1b}[0m".to_string()),
|
||||
]
|
||||
),
|
||||
],
|
||||
vals: vec![Value::test_record(
|
||||
vec!["col1".to_string(), "col2".to_string(), "col3".to_string()],
|
||||
vec![
|
||||
Value::test_string(
|
||||
"\u{1b}[37m\u{1b}[0m\u{1b}[41;37mmoe\u{1b}[0m\u{1b}[37m\u{1b}[0m"
|
||||
.to_string(),
|
||||
),
|
||||
Value::test_string("larry".to_string()),
|
||||
Value::test_string("curly".to_string()),
|
||||
],
|
||||
)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -229,27 +268,8 @@ fn find_with_regex(
|
||||
input.filter(
|
||||
move |value| match value {
|
||||
Value::String { val, .. } => re.is_match(val.as_str()).unwrap_or(false) != invert,
|
||||
Value::Record { cols: _, vals, .. } => {
|
||||
let matches: Vec<bool> = vals
|
||||
.iter()
|
||||
.map(|v| {
|
||||
re.is_match(v.into_string(" ", &config).as_str())
|
||||
.unwrap_or(false)
|
||||
!= invert
|
||||
})
|
||||
.collect();
|
||||
matches.iter().any(|b| *b)
|
||||
}
|
||||
Value::List { vals, .. } => {
|
||||
let matches: Vec<bool> = vals
|
||||
.iter()
|
||||
.map(|v| {
|
||||
re.is_match(v.into_string(" ", &config).as_str())
|
||||
.unwrap_or(false)
|
||||
!= invert
|
||||
})
|
||||
.collect();
|
||||
matches.iter().any(|b| *b)
|
||||
Value::Record { vals, .. } | Value::List { vals, .. } => {
|
||||
values_match_find(vals, &re, &config, invert)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
@ -257,6 +277,20 @@ fn find_with_regex(
|
||||
)
|
||||
}
|
||||
|
||||
fn values_match_find(values: &[Value], re: &Regex, config: &Config, invert: bool) -> bool {
|
||||
match invert {
|
||||
true => !record_matches_regex(values, re, config),
|
||||
false => record_matches_regex(values, re, config),
|
||||
}
|
||||
}
|
||||
|
||||
fn record_matches_regex(values: &[Value], re: &Regex, config: &Config) -> bool {
|
||||
values.iter().any(|v| {
|
||||
re.is_match(v.into_string(" ", config).as_str())
|
||||
.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn highlight_terms_in_record_with_search_columns(
|
||||
search_cols: &Vec<String>,
|
||||
@ -274,61 +308,39 @@ fn highlight_terms_in_record_with_search_columns(
|
||||
search_cols.to_vec()
|
||||
};
|
||||
let mut output = vec![];
|
||||
let mut potential_output = vec![];
|
||||
let mut found_a_hit = false;
|
||||
|
||||
// We iterate every column in the record and every search term for matches
|
||||
for (cur_col, val) in cols.iter().zip(vals) {
|
||||
let val_str = val.into_string("", config);
|
||||
let lower_val = val.into_string("", config).to_lowercase();
|
||||
let mut term_added_to_output = false;
|
||||
for term in terms {
|
||||
let term_str = term.into_string("", config);
|
||||
let lower_term = term.into_string("", config).to_lowercase();
|
||||
if lower_val.contains(&lower_term) && cols_to_search.contains(cur_col) {
|
||||
found_a_hit = true;
|
||||
term_added_to_output = true;
|
||||
if config.use_ls_colors {
|
||||
// Get the original LS_COLORS color
|
||||
let style = ls_colors.style_for_path(val_str.clone());
|
||||
let ansi_style = style
|
||||
.map(LsColors_Style::to_nu_ansi_term_style)
|
||||
.unwrap_or_default();
|
||||
|
||||
let ls_colored_val = ansi_style.paint(&val_str).to_string();
|
||||
|
||||
let ansi_term_style = style
|
||||
.map(to_nu_ansi_term_style)
|
||||
.unwrap_or_else(|| string_style);
|
||||
|
||||
let hi =
|
||||
match highlight_search_string(&ls_colored_val, &term_str, &ansi_term_style)
|
||||
{
|
||||
Ok(hi) => hi,
|
||||
Err(_) => string_style.paint(term_str.to_string()).to_string(),
|
||||
};
|
||||
potential_output.push(Value::String {
|
||||
val: hi,
|
||||
span: *span,
|
||||
});
|
||||
} else {
|
||||
// No LS_COLORS support, so just use the original value
|
||||
let hi = match highlight_search_string(&val_str, &term_str, &string_style) {
|
||||
Ok(hi) => hi,
|
||||
Err(_) => string_style.paint(term_str.to_string()).to_string(),
|
||||
let output_value =
|
||||
if contains_ignore_case(&val_str, &term_str) && cols_to_search.contains(cur_col) {
|
||||
let (value_to_highlight, highlight_string_style) = if config.use_ls_colors {
|
||||
// Get the original LS_COLORS color
|
||||
get_colored_value_and_string_style(ls_colors, &val_str, &string_style)
|
||||
} else {
|
||||
// No LS_COLORS support, so just use the original value
|
||||
(val_str.clone(), string_style)
|
||||
};
|
||||
output.push(Value::String {
|
||||
val: hi,
|
||||
span: *span,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if !term_added_to_output {
|
||||
potential_output.push(val.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if found_a_hit {
|
||||
output.append(&mut potential_output);
|
||||
let highlighted_str = match highlight_search_string(
|
||||
&value_to_highlight,
|
||||
&term_str,
|
||||
&highlight_string_style,
|
||||
) {
|
||||
Ok(highlighted_str) => highlighted_str,
|
||||
Err(_) => string_style.paint(term_str).to_string(),
|
||||
};
|
||||
Value::String {
|
||||
val: highlighted_str,
|
||||
span: *span,
|
||||
}
|
||||
} else {
|
||||
val.clone()
|
||||
};
|
||||
output.push(output_value);
|
||||
}
|
||||
}
|
||||
|
||||
Value::Record {
|
||||
@ -338,6 +350,28 @@ fn highlight_terms_in_record_with_search_columns(
|
||||
}
|
||||
}
|
||||
|
||||
fn get_colored_value_and_string_style(
|
||||
ls_colors: &LsColors,
|
||||
val_str: &str,
|
||||
string_style: &Style,
|
||||
) -> (String, Style) {
|
||||
let style = ls_colors.style_for_path(val_str);
|
||||
let ansi_style = style
|
||||
.map(LsColors_Style::to_nu_ansi_term_style)
|
||||
.unwrap_or_default();
|
||||
|
||||
let ls_colored_val = ansi_style.paint(val_str).to_string();
|
||||
|
||||
let ansi_term_style = style
|
||||
.map(to_nu_ansi_term_style)
|
||||
.unwrap_or_else(|| *string_style);
|
||||
(ls_colored_val, ansi_term_style)
|
||||
}
|
||||
|
||||
fn contains_ignore_case(string: &str, substring: &str) -> bool {
|
||||
string.to_lowercase().contains(&substring.to_lowercase())
|
||||
}
|
||||
|
||||
fn find_with_rest_and_highlight(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
@ -361,7 +395,6 @@ fn find_with_rest_and_highlight(
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Value>>();
|
||||
let columns_to_search: Option<Vec<String>> = call.get_flag(&engine_state, stack, "columns")?;
|
||||
|
||||
let style_computer = StyleComputer::from_config(&engine_state, stack);
|
||||
// Currently, search results all use the same style.
|
||||
@ -375,11 +408,13 @@ fn find_with_rest_and_highlight(
|
||||
};
|
||||
let ls_colors = get_ls_colors(ls_colors_env_str);
|
||||
|
||||
let cols_to_search = match columns_to_search {
|
||||
let cols_to_search_in_map = match call.get_flag(&engine_state, stack, "columns")? {
|
||||
Some(cols) => cols,
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
let cols_to_search_in_filter = cols_to_search_in_map.clone();
|
||||
|
||||
match input {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Value(_, _) => input
|
||||
@ -387,7 +422,7 @@ fn find_with_rest_and_highlight(
|
||||
move |mut x| match &mut x {
|
||||
Value::Record { cols, vals, span } => {
|
||||
highlight_terms_in_record_with_search_columns(
|
||||
&cols_to_search,
|
||||
&cols_to_search_in_map,
|
||||
cols,
|
||||
vals,
|
||||
span,
|
||||
@ -403,69 +438,14 @@ fn find_with_rest_and_highlight(
|
||||
)?
|
||||
.filter(
|
||||
move |value| {
|
||||
let lower_value = if let Ok(span) = value.span() {
|
||||
Value::string(value.into_string("", &filter_config).to_lowercase(), span)
|
||||
} else {
|
||||
value.clone()
|
||||
};
|
||||
|
||||
lower_terms.iter().any(|term| match value {
|
||||
Value::Bool { .. }
|
||||
| Value::Int { .. }
|
||||
| Value::Filesize { .. }
|
||||
| Value::Duration { .. }
|
||||
| Value::Date { .. }
|
||||
| Value::Range { .. }
|
||||
| Value::Float { .. }
|
||||
| Value::Block { .. }
|
||||
| Value::Closure { .. }
|
||||
| Value::Nothing { .. }
|
||||
| Value::Error { .. } => lower_value
|
||||
.eq(span, term, span)
|
||||
.map_or(false, |val| val.is_true()),
|
||||
Value::String { .. }
|
||||
| Value::List { .. }
|
||||
| Value::CellPath { .. }
|
||||
| Value::CustomValue { .. } => term
|
||||
.r#in(span, &lower_value, span)
|
||||
.map_or(false, |val| val.is_true()),
|
||||
Value::Record { vals, .. } => vals.iter().any(|val| {
|
||||
if let Ok(span) = val.span() {
|
||||
let lower_val = Value::string(
|
||||
val.into_string("", &filter_config).to_lowercase(),
|
||||
Span::test_data(),
|
||||
);
|
||||
|
||||
term.r#in(span, &lower_val, span)
|
||||
.map_or(false, |aval| aval.is_true())
|
||||
} else {
|
||||
term.r#in(span, val, span)
|
||||
.map_or(false, |aval| aval.is_true())
|
||||
}
|
||||
}),
|
||||
Value::LazyRecord { val, .. } => match val.collect() {
|
||||
Ok(val) => match val {
|
||||
Value::Record { vals, .. } => vals.iter().any(|val| {
|
||||
if let Ok(span) = val.span() {
|
||||
let lower_val = Value::string(
|
||||
val.into_string("", &filter_config).to_lowercase(),
|
||||
Span::test_data(),
|
||||
);
|
||||
|
||||
term.r#in(span, &lower_val, span)
|
||||
.map_or(false, |aval| aval.is_true())
|
||||
} else {
|
||||
term.r#in(span, val, span)
|
||||
.map_or(false, |aval| aval.is_true())
|
||||
}
|
||||
}),
|
||||
_ => false,
|
||||
},
|
||||
Err(_) => false,
|
||||
},
|
||||
Value::Binary { .. } => false,
|
||||
Value::MatchPattern { .. } => false,
|
||||
}) != invert
|
||||
value_should_be_printed(
|
||||
value,
|
||||
&filter_config,
|
||||
&lower_terms,
|
||||
&span,
|
||||
&cols_to_search_in_filter,
|
||||
invert,
|
||||
)
|
||||
},
|
||||
ctrlc,
|
||||
),
|
||||
@ -474,7 +454,7 @@ fn find_with_rest_and_highlight(
|
||||
.map(move |mut x| match &mut x {
|
||||
Value::Record { cols, vals, span } => {
|
||||
highlight_terms_in_record_with_search_columns(
|
||||
&cols_to_search,
|
||||
&cols_to_search_in_map,
|
||||
cols,
|
||||
vals,
|
||||
span,
|
||||
@ -487,69 +467,14 @@ fn find_with_rest_and_highlight(
|
||||
_ => x,
|
||||
})
|
||||
.filter(move |value| {
|
||||
let lower_value = if let Ok(span) = value.span() {
|
||||
Value::string(value.into_string("", &filter_config).to_lowercase(), span)
|
||||
} else {
|
||||
value.clone()
|
||||
};
|
||||
|
||||
lower_terms.iter().any(|term| match value {
|
||||
Value::Bool { .. }
|
||||
| Value::Int { .. }
|
||||
| Value::Filesize { .. }
|
||||
| Value::Duration { .. }
|
||||
| Value::Date { .. }
|
||||
| Value::Range { .. }
|
||||
| Value::Float { .. }
|
||||
| Value::Block { .. }
|
||||
| Value::Closure { .. }
|
||||
| Value::Nothing { .. }
|
||||
| Value::Error { .. } => lower_value
|
||||
.eq(span, term, span)
|
||||
.map_or(false, |value| value.is_true()),
|
||||
Value::String { .. }
|
||||
| Value::List { .. }
|
||||
| Value::CellPath { .. }
|
||||
| Value::CustomValue { .. } => term
|
||||
.r#in(span, &lower_value, span)
|
||||
.map_or(false, |value| value.is_true()),
|
||||
Value::Record { vals, .. } => vals.iter().any(|val| {
|
||||
if let Ok(span) = val.span() {
|
||||
let lower_val = Value::string(
|
||||
val.into_string("", &filter_config).to_lowercase(),
|
||||
Span::test_data(),
|
||||
);
|
||||
|
||||
term.r#in(span, &lower_val, span)
|
||||
.map_or(false, |value| value.is_true())
|
||||
} else {
|
||||
term.r#in(span, val, span)
|
||||
.map_or(false, |value| value.is_true())
|
||||
}
|
||||
}),
|
||||
Value::LazyRecord { val, .. } => match val.collect() {
|
||||
Ok(val) => match val {
|
||||
Value::Record { vals, .. } => vals.iter().any(|val| {
|
||||
if let Ok(span) = val.span() {
|
||||
let lower_val = Value::string(
|
||||
val.into_string("", &filter_config).to_lowercase(),
|
||||
Span::test_data(),
|
||||
);
|
||||
|
||||
term.r#in(span, &lower_val, span)
|
||||
.map_or(false, |value| value.is_true())
|
||||
} else {
|
||||
term.r#in(span, val, span)
|
||||
.map_or(false, |value| value.is_true())
|
||||
}
|
||||
}),
|
||||
_ => false,
|
||||
},
|
||||
Err(_) => false,
|
||||
},
|
||||
Value::Binary { .. } => false,
|
||||
Value::MatchPattern { .. } => false,
|
||||
}) != invert
|
||||
value_should_be_printed(
|
||||
value,
|
||||
&filter_config,
|
||||
&lower_terms,
|
||||
&span,
|
||||
&cols_to_search_in_filter,
|
||||
invert,
|
||||
)
|
||||
}),
|
||||
ctrlc.clone(),
|
||||
)
|
||||
@ -607,6 +532,96 @@ fn find_with_rest_and_highlight(
|
||||
}
|
||||
}
|
||||
|
||||
fn value_should_be_printed(
|
||||
value: &Value,
|
||||
filter_config: &Config,
|
||||
lower_terms: &[Value],
|
||||
span: &Span,
|
||||
columns_to_search: &Vec<String>,
|
||||
invert: bool,
|
||||
) -> bool {
|
||||
let lower_value = if let Ok(span) = value.span() {
|
||||
Value::string(value.into_string("", filter_config).to_lowercase(), span)
|
||||
} else {
|
||||
value.clone()
|
||||
};
|
||||
|
||||
let mut match_found = lower_terms.iter().any(|term| match value {
|
||||
Value::Bool { .. }
|
||||
| Value::Int { .. }
|
||||
| Value::Filesize { .. }
|
||||
| Value::Duration { .. }
|
||||
| Value::Date { .. }
|
||||
| Value::Range { .. }
|
||||
| Value::Float { .. }
|
||||
| Value::Block { .. }
|
||||
| Value::Closure { .. }
|
||||
| Value::Nothing { .. }
|
||||
| Value::Error { .. } => term_equals_value(term, &lower_value, span),
|
||||
Value::String { .. }
|
||||
| Value::List { .. }
|
||||
| Value::CellPath { .. }
|
||||
| Value::CustomValue { .. } => term_contains_value(term, &lower_value, span),
|
||||
Value::Record { cols, vals, .. } => {
|
||||
record_matches_term(cols, vals, columns_to_search, filter_config, term, span)
|
||||
}
|
||||
Value::LazyRecord { val, .. } => match val.collect() {
|
||||
Ok(val) => match val {
|
||||
Value::Record { cols, vals, .. } => {
|
||||
record_matches_term(&cols, &vals, columns_to_search, filter_config, term, span)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
Err(_) => false,
|
||||
},
|
||||
Value::Binary { .. } => false,
|
||||
Value::MatchPattern { .. } => false,
|
||||
});
|
||||
if invert {
|
||||
match_found = !match_found;
|
||||
}
|
||||
match_found
|
||||
}
|
||||
|
||||
fn term_contains_value(term: &Value, value: &Value, span: &Span) -> bool {
|
||||
term.r#in(*span, value, *span)
|
||||
.map_or(false, |value| value.is_true())
|
||||
}
|
||||
|
||||
fn term_equals_value(term: &Value, value: &Value, span: &Span) -> bool {
|
||||
term.eq(*span, value, *span)
|
||||
.map_or(false, |value| value.is_true())
|
||||
}
|
||||
|
||||
fn record_matches_term(
|
||||
cols: &[String],
|
||||
vals: &[Value],
|
||||
columns_to_search: &Vec<String>,
|
||||
filter_config: &Config,
|
||||
term: &Value,
|
||||
span: &Span,
|
||||
) -> bool {
|
||||
let cols_to_search = if columns_to_search.is_empty() {
|
||||
cols.to_vec()
|
||||
} else {
|
||||
columns_to_search.to_vec()
|
||||
};
|
||||
cols.iter().zip(vals).any(|(col, val)| {
|
||||
if !cols_to_search.contains(col) {
|
||||
return false;
|
||||
}
|
||||
let lower_val = if val.span().is_ok() {
|
||||
Value::string(
|
||||
val.into_string("", filter_config).to_lowercase(),
|
||||
Span::test_data(),
|
||||
)
|
||||
} else {
|
||||
(*val).clone()
|
||||
};
|
||||
term_contains_value(term, &lower_val, span)
|
||||
})
|
||||
}
|
||||
|
||||
fn to_nu_ansi_term_style(style: &LsColors_Style) -> Style {
|
||||
fn to_nu_ansi_term_color(color: &LsColors_Color) -> Color {
|
||||
match *color {
|
||||
|
@ -114,8 +114,10 @@ impl Command for ParEach {
|
||||
let max_threads = threads.unwrap_or(0);
|
||||
let metadata = input.metadata();
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let outer_ctrlc = engine_state.ctrlc.clone();
|
||||
let block_id = capture_block.block_id;
|
||||
let mut stack = stack.captures_to_stack(&capture_block.captures);
|
||||
let span = call.head;
|
||||
let redirect_stdout = call.redirect_stdout;
|
||||
let redirect_stderr = call.redirect_stderr;
|
||||
|
||||
@ -146,16 +148,15 @@ impl Command for ParEach {
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Ok(v) => v.into_value(span),
|
||||
|
||||
Err(error) => Value::Error {
|
||||
error: Box::new(chain_error_with_input(error, val_span)),
|
||||
}
|
||||
.into_pipeline_data(),
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.into_pipeline_data(ctrlc)
|
||||
})),
|
||||
PipelineData::Value(Value::List { vals: val, .. }, ..) => Ok(create_pool(max_threads)?
|
||||
@ -181,16 +182,14 @@ impl Command for ParEach {
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Ok(v) => v.into_value(span),
|
||||
Err(error) => Value::Error {
|
||||
error: Box::new(chain_error_with_input(error, val_span)),
|
||||
}
|
||||
.into_pipeline_data(),
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.into_pipeline_data(ctrlc)
|
||||
})),
|
||||
PipelineData::ListStream(stream, ..) => Ok(create_pool(max_threads)?.install(|| {
|
||||
@ -216,16 +215,14 @@ impl Command for ParEach {
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Ok(v) => v.into_value(span),
|
||||
Err(error) => Value::Error {
|
||||
error: Box::new(chain_error_with_input(error, val_span)),
|
||||
}
|
||||
.into_pipeline_data(),
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.into_pipeline_data(ctrlc)
|
||||
})),
|
||||
PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::empty()),
|
||||
@ -242,7 +239,6 @@ impl Command for ParEach {
|
||||
return Value::Error {
|
||||
error: Box::new(err),
|
||||
}
|
||||
.into_pipeline_data()
|
||||
}
|
||||
};
|
||||
|
||||
@ -264,16 +260,14 @@ impl Command for ParEach {
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Ok(v) => v.into_value(span),
|
||||
Err(error) => Value::Error {
|
||||
error: Box::new(error),
|
||||
}
|
||||
.into_pipeline_data(),
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.into_pipeline_data(ctrlc)
|
||||
})),
|
||||
// This match allows non-iterables to be accepted,
|
||||
@ -297,6 +291,7 @@ impl Command for ParEach {
|
||||
)
|
||||
}
|
||||
}
|
||||
.and_then(|x| x.filter(|v| !v.is_nothing(), outer_ctrlc))
|
||||
.map(|res| res.set_metadata(metadata))
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use indexmap::IndexMap;
|
||||
use itertools::Itertools;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
@ -6,7 +7,6 @@ use nu_protocol::{
|
||||
Value,
|
||||
};
|
||||
use serde::de::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FromYaml;
|
||||
@ -113,7 +113,8 @@ fn convert_yaml_value_to_nu_value(
|
||||
}
|
||||
serde_yaml::Value::Mapping(t) => {
|
||||
let mut collected = Spanned {
|
||||
item: HashMap::new(),
|
||||
// Using an IndexMap ensures consistent ordering
|
||||
item: IndexMap::new(),
|
||||
span,
|
||||
};
|
||||
|
||||
@ -339,6 +340,68 @@ mod test {
|
||||
test_examples(FromYaml {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_consistent_mapping_ordering() {
|
||||
let test_yaml = "- a: b
|
||||
b: c
|
||||
- a: g
|
||||
b: h";
|
||||
|
||||
// Before the fix this test is verifying, the ordering of columns in the resulting
|
||||
// table was non-deterministic. It would take a few executions of the YAML conversion to
|
||||
// see this ordering difference. This loop should be far more than enough to catch a regression.
|
||||
for ii in 1..1000 {
|
||||
let actual = from_yaml_string_to_value(
|
||||
String::from(test_yaml),
|
||||
Span::test_data(),
|
||||
Span::test_data(),
|
||||
);
|
||||
|
||||
let expected: Result<Value, ShellError> = Ok(Value::List {
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["a".to_string(), "b".to_string()],
|
||||
vals: vec![Value::test_string("b"), Value::test_string("c")],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: vec!["a".to_string(), "b".to_string()],
|
||||
vals: vec![Value::test_string("g"), Value::test_string("h")],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
});
|
||||
|
||||
// Unfortunately the eq function for Value doesn't compare well enough to detect
|
||||
// ordering errors in List columns or values.
|
||||
|
||||
assert!(actual.is_ok());
|
||||
let actual = actual.ok().unwrap();
|
||||
let expected = expected.ok().unwrap();
|
||||
|
||||
let actual_vals = actual.as_list().unwrap();
|
||||
let expected_vals = expected.as_list().unwrap();
|
||||
assert_eq!(expected_vals.len(), actual_vals.len(), "iteration {ii}");
|
||||
|
||||
for jj in 0..expected_vals.len() {
|
||||
let actual_record = actual_vals[jj].as_record().unwrap();
|
||||
let expected_record = expected_vals[jj].as_record().unwrap();
|
||||
|
||||
let actual_columns = actual_record.0;
|
||||
let expected_columns = expected_record.0;
|
||||
assert_eq!(
|
||||
expected_columns, actual_columns,
|
||||
"record {jj}, iteration {ii}"
|
||||
);
|
||||
|
||||
let actual_vals = actual_record.1;
|
||||
let expected_vals = expected_record.1;
|
||||
assert_eq!(expected_vals, actual_vals, "record {jj}, iteration {ii}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_yaml_value_to_nu_value_for_tagged_values() {
|
||||
struct TestCase {
|
||||
|
@ -75,11 +75,33 @@ impl Command for SubCommand {
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Apply negative precision to a list of numbers",
|
||||
example: "[123, 123.3, -123.4] | math round -p -1",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::test_int(120),
|
||||
Value::test_int(120),
|
||||
Value::test_int(-120),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(value: Value, head: Span, precision: Option<i64>) -> Value {
|
||||
// We treat int values as float values in order to avoid code repetition in the match closure
|
||||
let value = if let Value::Int { val, span } = value {
|
||||
Value::Float {
|
||||
val: val as f64,
|
||||
span,
|
||||
}
|
||||
} else {
|
||||
value
|
||||
};
|
||||
|
||||
match value {
|
||||
Value::Float { val, span } => match precision {
|
||||
Some(precision_number) => Value::Float {
|
||||
@ -92,7 +114,6 @@ fn operate(value: Value, head: Span, precision: Option<i64>) -> Value {
|
||||
span,
|
||||
},
|
||||
},
|
||||
Value::Int { .. } => value,
|
||||
Value::Error { .. } => value,
|
||||
other => Value::Error {
|
||||
error: Box::new(ShellError::OnlySupportsThisInputType {
|
||||
|
@ -1,3 +1,5 @@
|
||||
mod source;
|
||||
mod tutor;
|
||||
|
||||
pub use source::Source;
|
||||
pub use tutor::Tutor;
|
||||
|
241
crates/nu-command/src/platform/input/list.rs
Normal file
241
crates/nu-command/src/platform/input/list.rs
Normal file
@ -0,0 +1,241 @@
|
||||
use dialoguer::{console::Term, Select};
|
||||
use dialoguer::{FuzzySelect, MultiSelect};
|
||||
use nu_ansi_term::Color;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type,
|
||||
Value,
|
||||
};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
enum InteractMode {
|
||||
Single(Option<usize>),
|
||||
Multi(Option<Vec<usize>>),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Options {
|
||||
name: String,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
impl Display for Options {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InputList;
|
||||
|
||||
const INTERACT_ERROR: &str = "Interact error, could not process options";
|
||||
|
||||
impl Command for InputList {
|
||||
fn name(&self) -> &str {
|
||||
"input list"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("input list")
|
||||
.input_output_types(vec![(
|
||||
Type::List(Box::new(Type::Any)),
|
||||
Type::List(Box::new(Type::Any)),
|
||||
)])
|
||||
.optional("prompt", SyntaxShape::String, "the prompt to display")
|
||||
.switch(
|
||||
"multi",
|
||||
"Use multiple results, you can press a to toggle all options on/off",
|
||||
Some('m'),
|
||||
)
|
||||
.switch("fuzzy", "Use a fuzzy select.", Some('f'))
|
||||
.allow_variants_without_examples(true)
|
||||
.category(Category::Platform)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Interactive list selection."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
"Abort with esc or q."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["prompt", "ask", "menu"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let prompt: Option<String> = call.opt(engine_state, stack, 0)?;
|
||||
|
||||
let options: Vec<Options> = match input {
|
||||
PipelineData::Value(Value::Range { .. }, ..)
|
||||
| PipelineData::Value(Value::List { .. }, ..)
|
||||
| PipelineData::ListStream { .. }
|
||||
| PipelineData::Value(Value::Record { .. }, ..) => input
|
||||
.into_iter()
|
||||
.map_while(move |x| {
|
||||
if let Ok(val) = x.as_string() {
|
||||
Some(Options {
|
||||
name: val,
|
||||
value: x,
|
||||
})
|
||||
} else if let Ok(record) = x.as_record() {
|
||||
let mut options = Vec::new();
|
||||
for (col, val) in record.0.iter().zip(record.1.iter()) {
|
||||
if let Ok(val) = val.as_string() {
|
||||
options.push(format!(
|
||||
" {}{}{}: {} |\t",
|
||||
Color::Cyan.prefix(),
|
||||
col,
|
||||
Color::Cyan.suffix(),
|
||||
&val
|
||||
));
|
||||
}
|
||||
}
|
||||
Some(Options {
|
||||
name: options.join(""),
|
||||
value: x,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
|
||||
_ => {
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message: "expected a list or table".to_string(),
|
||||
span: head,
|
||||
})
|
||||
}
|
||||
};
|
||||
let prompt = prompt.unwrap_or_default();
|
||||
|
||||
if options.is_empty() {
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message: "expected a list or table, it can also be a problem with the an inner type of your list.".to_string(),
|
||||
span: head,
|
||||
});
|
||||
}
|
||||
|
||||
if call.has_flag("multi") && call.has_flag("fuzzy") {
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message: "Fuzzy search is not supported for multi select".to_string(),
|
||||
span: head,
|
||||
});
|
||||
}
|
||||
|
||||
// could potentially be used to map the use theme colors at some point
|
||||
// let theme = dialoguer::theme::ColorfulTheme {
|
||||
// active_item_style: Style::new().fg(Color::Cyan).bold(),
|
||||
// ..Default::default()
|
||||
// };
|
||||
|
||||
let ans: InteractMode = if call.has_flag("multi") {
|
||||
let mut multi_select = MultiSelect::new(); //::with_theme(&theme);
|
||||
|
||||
InteractMode::Multi(
|
||||
if !prompt.is_empty() {
|
||||
multi_select.with_prompt(&prompt)
|
||||
} else {
|
||||
&mut multi_select
|
||||
}
|
||||
.items(&options)
|
||||
.report(false)
|
||||
.interact_on_opt(&Term::stderr())
|
||||
.map_err(|err| ShellError::IOError(format!("{}: {}", INTERACT_ERROR, err)))?,
|
||||
)
|
||||
} else if call.has_flag("fuzzy") {
|
||||
let mut fuzzy_select = FuzzySelect::new(); //::with_theme(&theme);
|
||||
|
||||
InteractMode::Single(
|
||||
if !prompt.is_empty() {
|
||||
fuzzy_select.with_prompt(&prompt)
|
||||
} else {
|
||||
&mut fuzzy_select
|
||||
}
|
||||
.items(&options)
|
||||
.default(0)
|
||||
.report(false)
|
||||
.interact_on_opt(&Term::stderr())
|
||||
.map_err(|err| ShellError::IOError(format!("{}: {}", INTERACT_ERROR, err)))?,
|
||||
)
|
||||
} else {
|
||||
let mut select = Select::new(); //::with_theme(&theme);
|
||||
InteractMode::Single(
|
||||
if !prompt.is_empty() {
|
||||
select.with_prompt(&prompt)
|
||||
} else {
|
||||
&mut select
|
||||
}
|
||||
.items(&options)
|
||||
.default(0)
|
||||
.report(false)
|
||||
.interact_on_opt(&Term::stderr())
|
||||
.map_err(|err| ShellError::IOError(format!("{}: {}", INTERACT_ERROR, err)))?,
|
||||
)
|
||||
};
|
||||
|
||||
Ok(match ans {
|
||||
InteractMode::Multi(res) => match res {
|
||||
Some(opts) => Value::List {
|
||||
vals: opts.iter().map(|s| options[*s].value.clone()).collect(),
|
||||
span: head,
|
||||
},
|
||||
None => Value::List {
|
||||
vals: vec![],
|
||||
span: head,
|
||||
},
|
||||
},
|
||||
InteractMode::Single(res) => match res {
|
||||
Some(opt) => options[opt].value.clone(),
|
||||
None => Value::String {
|
||||
val: "".to_string(),
|
||||
span: head,
|
||||
},
|
||||
},
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Return a single value from a list",
|
||||
example: r#"[1 2 3 4 5] | input list 'Rate it'"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Return multiple values from a list",
|
||||
example: r#"[Banana Kiwi Pear Peach Strawberry] | input list -m 'Add fruits to the basket'"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Return a single record from a table with fuzzy search",
|
||||
example: r#"ls | input list -f 'Select the target'"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(InputList {})
|
||||
}
|
||||
}
|
5
crates/nu-command/src/platform/input/mod.rs
Normal file
5
crates/nu-command/src/platform/input/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod input_;
|
||||
mod list;
|
||||
|
||||
pub use input_::Input;
|
||||
pub use list::InputList;
|
@ -12,6 +12,7 @@ pub use clear::Clear;
|
||||
pub use dir_info::{DirBuilder, DirInfo, FileInfo};
|
||||
pub use du::Du;
|
||||
pub use input::Input;
|
||||
pub use input::InputList;
|
||||
pub use kill::Kill;
|
||||
pub use sleep::Sleep;
|
||||
pub use term_size::TermSize;
|
||||
|
@ -1,103 +0,0 @@
|
||||
use super::{get_current_shell, get_shells};
|
||||
use nu_engine::{current_dir, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
/// Source a file for environment variables.
|
||||
#[derive(Clone)]
|
||||
pub struct Enter;
|
||||
|
||||
impl Command for Enter {
|
||||
fn name(&self) -> &str {
|
||||
"enter"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("enter")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required(
|
||||
"path",
|
||||
SyntaxShape::Filepath,
|
||||
"the path to enter as a new shell",
|
||||
)
|
||||
.category(Category::Shells)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Enters a new shell at the given path."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let new_path: Value = call.req(engine_state, stack, 0)?;
|
||||
let path_span = new_path.span()?;
|
||||
|
||||
let new_path = new_path.as_path()?;
|
||||
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let new_path = nu_path::canonicalize_with(new_path, &cwd)?;
|
||||
|
||||
if !new_path.exists() {
|
||||
return Err(ShellError::DirectoryNotFound(path_span, None));
|
||||
}
|
||||
|
||||
if !new_path.is_dir() {
|
||||
return Err(ShellError::DirectoryNotFoundCustom(
|
||||
"not a directory".to_string(),
|
||||
path_span,
|
||||
));
|
||||
}
|
||||
|
||||
let new_path = Value::string(new_path.to_string_lossy(), call.head);
|
||||
|
||||
let cwd = Value::string(cwd.to_string_lossy(), call.head);
|
||||
|
||||
let mut shells = get_shells(engine_state, stack, cwd);
|
||||
let mut current_shell = get_current_shell(engine_state, stack);
|
||||
|
||||
stack.add_env_var(
|
||||
"NUSHELL_LAST_SHELL".into(),
|
||||
Value::int(current_shell as i64, call.head),
|
||||
);
|
||||
|
||||
if current_shell + 1 > shells.len() {
|
||||
shells.push(new_path.clone());
|
||||
current_shell = shells.len();
|
||||
} else {
|
||||
shells.insert(current_shell + 1, new_path.clone());
|
||||
current_shell += 1;
|
||||
}
|
||||
|
||||
stack.add_env_var(
|
||||
"NUSHELL_SHELLS".into(),
|
||||
Value::List {
|
||||
vals: shells,
|
||||
span: call.head,
|
||||
},
|
||||
);
|
||||
stack.add_env_var(
|
||||
"NUSHELL_CURRENT_SHELL".into(),
|
||||
Value::int(current_shell as i64, call.head),
|
||||
);
|
||||
|
||||
stack.add_env_var("PWD".into(), new_path);
|
||||
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Enter a new shell at path '../dir-foo'",
|
||||
example: r#"enter ../dir-foo"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
@ -1,10 +1,7 @@
|
||||
use super::{get_current_shell, get_last_shell, get_shells};
|
||||
use nu_engine::{current_dir, CallExt};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Exit;
|
||||
@ -22,11 +19,6 @@ impl Command for Exit {
|
||||
SyntaxShape::Int,
|
||||
"Exit code to return immediately with",
|
||||
)
|
||||
.switch(
|
||||
"now",
|
||||
"Exit out of all shells immediately (exiting Nu)",
|
||||
Some('n'),
|
||||
)
|
||||
.category(Category::Shells)
|
||||
}
|
||||
|
||||
@ -51,66 +43,14 @@ impl Command for Exit {
|
||||
std::process::exit(exit_code as i32);
|
||||
}
|
||||
|
||||
if call.has_flag("now") {
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let cwd = Value::string(cwd.to_string_lossy(), call.head);
|
||||
|
||||
let mut shells = get_shells(engine_state, stack, cwd);
|
||||
let mut current_shell = get_current_shell(engine_state, stack);
|
||||
let mut last_shell = get_last_shell(engine_state, stack);
|
||||
|
||||
shells.remove(current_shell);
|
||||
|
||||
if current_shell <= last_shell {
|
||||
last_shell = 0;
|
||||
}
|
||||
|
||||
if current_shell == shells.len() && !shells.is_empty() {
|
||||
current_shell -= 1;
|
||||
}
|
||||
|
||||
if shells.is_empty() {
|
||||
std::process::exit(0);
|
||||
} else {
|
||||
let new_path = shells[current_shell].clone();
|
||||
|
||||
stack.add_env_var(
|
||||
"NUSHELL_SHELLS".into(),
|
||||
Value::List {
|
||||
vals: shells,
|
||||
span: call.head,
|
||||
},
|
||||
);
|
||||
stack.add_env_var(
|
||||
"NUSHELL_CURRENT_SHELL".into(),
|
||||
Value::int(current_shell as i64, call.head),
|
||||
);
|
||||
stack.add_env_var(
|
||||
"NUSHELL_LAST_SHELL".into(),
|
||||
Value::int(last_shell as i64, call.head),
|
||||
);
|
||||
|
||||
stack.add_env_var("PWD".into(), new_path);
|
||||
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Exit the current shell",
|
||||
example: "exit",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Exit all shells (exiting Nu)",
|
||||
example: "exit --now",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
vec![Example {
|
||||
description: "Exit the current shell",
|
||||
example: "exit",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
@ -1,97 +0,0 @@
|
||||
use super::{list_shells, switch_shell, SwitchTo};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
/// Source a file for environment variables.
|
||||
#[derive(Clone)]
|
||||
pub struct GotoShell;
|
||||
|
||||
impl Command for GotoShell {
|
||||
fn name(&self) -> &str {
|
||||
"g"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("g")
|
||||
.input_output_types(vec![
|
||||
(Type::Nothing, Type::Nothing),
|
||||
(Type::Nothing, Type::Table(vec![])),
|
||||
])
|
||||
.optional(
|
||||
"shell_number",
|
||||
SyntaxShape::OneOf(vec![SyntaxShape::Int, SyntaxShape::String]),
|
||||
"shell number to change to",
|
||||
)
|
||||
.category(Category::Shells)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Switch to a given shell, or list all shells if no given shell number."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let new_shell: Option<Value> = call.opt(engine_state, stack, 0)?;
|
||||
|
||||
match new_shell {
|
||||
Some(shell_span) => match &shell_span {
|
||||
Value::String { val, span } => {
|
||||
if val == "-" {
|
||||
switch_shell(engine_state, stack, call, *span, SwitchTo::Last)
|
||||
} else {
|
||||
Err(ShellError::TypeMismatch {
|
||||
err_message: "int or '-'".into(),
|
||||
span: *span,
|
||||
})
|
||||
}
|
||||
}
|
||||
Value::Int { val, span } => switch_shell(
|
||||
engine_state,
|
||||
stack,
|
||||
call,
|
||||
*span,
|
||||
SwitchTo::Nth(*val as usize),
|
||||
),
|
||||
_ => Err(ShellError::TypeMismatch {
|
||||
err_message: "int or '-'".into(),
|
||||
span: call.head,
|
||||
}),
|
||||
},
|
||||
None => list_shells(engine_state, stack, call.head),
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Lists all open shells",
|
||||
example: r#"g"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Make two directories and enter new shells for them, use `g` to jump to the specific shell",
|
||||
example: r#"mkdir foo bar; enter foo; enter ../bar; g 1"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Use `shells` to show all the opened shells and run `g 2` to jump to the third one",
|
||||
example: r#"shells; g 2"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Make two directories and enter new shells for them, use `g -` to jump to the last used shell",
|
||||
example: r#"mkdir foo bar; enter foo; enter ../bar; g -"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
@ -1,147 +1,3 @@
|
||||
mod enter;
|
||||
mod exit;
|
||||
mod g;
|
||||
mod n;
|
||||
mod p;
|
||||
mod shells_;
|
||||
|
||||
pub use enter::Enter;
|
||||
pub use exit::Exit;
|
||||
pub use g::GotoShell;
|
||||
pub use n::NextShell;
|
||||
use nu_engine::current_dir;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::{IntoInterruptiblePipelineData, PipelineData, ShellError, Span, Value};
|
||||
pub use p::PrevShell;
|
||||
pub use shells_::Shells;
|
||||
|
||||
enum SwitchTo {
|
||||
Next,
|
||||
Prev,
|
||||
Last,
|
||||
Nth(usize),
|
||||
}
|
||||
|
||||
pub fn get_shells(engine_state: &EngineState, stack: &mut Stack, cwd: Value) -> Vec<Value> {
|
||||
let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS");
|
||||
let shells = if let Some(v) = shells {
|
||||
v.as_list()
|
||||
.map(|x| x.to_vec())
|
||||
.unwrap_or_else(|_| vec![cwd])
|
||||
} else {
|
||||
vec![cwd]
|
||||
};
|
||||
shells
|
||||
}
|
||||
|
||||
pub fn get_current_shell(engine_state: &EngineState, stack: &mut Stack) -> usize {
|
||||
let current_shell = stack.get_env_var(engine_state, "NUSHELL_CURRENT_SHELL");
|
||||
if let Some(v) = current_shell {
|
||||
v.as_integer().unwrap_or_default() as usize
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn get_last_shell(engine_state: &EngineState, stack: &mut Stack) -> usize {
|
||||
let last_shell = stack.get_env_var(engine_state, "NUSHELL_LAST_SHELL");
|
||||
if let Some(v) = last_shell {
|
||||
v.as_integer().unwrap_or_default() as usize
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn switch_shell(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
span: Span,
|
||||
switch_to: SwitchTo,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let cwd = Value::string(cwd.to_string_lossy(), call.head);
|
||||
|
||||
let shells = get_shells(engine_state, stack, cwd);
|
||||
let current_shell = get_current_shell(engine_state, stack);
|
||||
|
||||
let new_shell = match switch_to {
|
||||
SwitchTo::Next => {
|
||||
let mut new_shell = current_shell + 1;
|
||||
|
||||
if new_shell == shells.len() {
|
||||
new_shell = 0;
|
||||
}
|
||||
|
||||
new_shell
|
||||
}
|
||||
SwitchTo::Prev => {
|
||||
if current_shell == 0 {
|
||||
shells.len() - 1
|
||||
} else {
|
||||
current_shell - 1
|
||||
}
|
||||
}
|
||||
SwitchTo::Last => get_last_shell(engine_state, stack),
|
||||
SwitchTo::Nth(n) => n,
|
||||
};
|
||||
|
||||
let new_path = shells
|
||||
.get(new_shell)
|
||||
.ok_or(ShellError::NotFound { span })?
|
||||
.to_owned();
|
||||
|
||||
stack.add_env_var(
|
||||
"NUSHELL_SHELLS".into(),
|
||||
Value::List {
|
||||
vals: shells,
|
||||
span: call.head,
|
||||
},
|
||||
);
|
||||
|
||||
stack.add_env_var(
|
||||
"NUSHELL_CURRENT_SHELL".into(),
|
||||
Value::int(new_shell as i64, call.head),
|
||||
);
|
||||
|
||||
stack.add_env_var(
|
||||
"NUSHELL_LAST_SHELL".into(),
|
||||
Value::int(current_shell as i64, call.head),
|
||||
);
|
||||
|
||||
stack.add_env_var("PWD".into(), new_path);
|
||||
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn list_shells(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
span: Span,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let cwd = Value::String {
|
||||
val: cwd.to_string_lossy().to_string(),
|
||||
span,
|
||||
};
|
||||
|
||||
let shells = get_shells(engine_state, stack, cwd);
|
||||
let current_shell = get_current_shell(engine_state, stack);
|
||||
|
||||
Ok(shells
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(move |(idx, val)| Value::Record {
|
||||
cols: vec!["active".to_string(), "path".to_string()],
|
||||
vals: vec![
|
||||
Value::Bool {
|
||||
val: idx == current_shell,
|
||||
span,
|
||||
},
|
||||
val,
|
||||
],
|
||||
span,
|
||||
})
|
||||
.into_pipeline_data(None))
|
||||
}
|
||||
|
@ -1,49 +0,0 @@
|
||||
use super::{switch_shell, SwitchTo};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Type};
|
||||
|
||||
/// Source a file for environment variables.
|
||||
#[derive(Clone)]
|
||||
pub struct NextShell;
|
||||
|
||||
impl Command for NextShell {
|
||||
fn name(&self) -> &str {
|
||||
"n"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("n")
|
||||
.category(Category::Shells)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Switch to the next shell."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
switch_shell(engine_state, stack, call, call.head, SwitchTo::Next)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Make two directories and enter new shells for them, use `n` to jump to the next shell",
|
||||
example: r#"mkdir foo bar; enter foo; enter ../bar; n"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Run `n` several times and note the changes of current directory",
|
||||
example: r#"n"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
use super::{switch_shell, SwitchTo};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Type};
|
||||
|
||||
/// Source a file for environment variables.
|
||||
#[derive(Clone)]
|
||||
pub struct PrevShell;
|
||||
|
||||
impl Command for PrevShell {
|
||||
fn name(&self) -> &str {
|
||||
"p"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("p")
|
||||
.category(Category::Shells)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Switch to the previous shell."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
switch_shell(engine_state, stack, call, call.head, SwitchTo::Prev)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Make two directories and enter new shells for them, use `p` to jump to the previous shell",
|
||||
example: r#"mkdir foo bar; enter foo; enter ../bar; p"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Run `p` several times and note the changes of current directory",
|
||||
example: r#"p"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
use super::list_shells;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Type};
|
||||
|
||||
/// Source a file for environment variables.
|
||||
#[derive(Clone)]
|
||||
pub struct Shells;
|
||||
|
||||
impl Command for Shells {
|
||||
fn name(&self) -> &str {
|
||||
"shells"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("shells")
|
||||
.category(Category::Shells)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Lists all open shells."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
list_shells(engine_state, stack, call.head)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Enter a new shell at parent path '..' and show all opened shells",
|
||||
example: r#"enter ..; shells"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Show currently active shell",
|
||||
example: r#"shells | where active == true"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
@ -67,6 +67,7 @@ fn exec(
|
||||
|
||||
let redirect_stdout = call.has_flag("redirect-stdout");
|
||||
let redirect_stderr = call.has_flag("redirect-stderr");
|
||||
let redirect_combine = call.has_flag("redirect-combine");
|
||||
let trim_end_newline = call.has_flag("trim-end-newline");
|
||||
|
||||
let external_command = create_external_command(
|
||||
@ -75,6 +76,7 @@ fn exec(
|
||||
call,
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
redirect_combine,
|
||||
trim_end_newline,
|
||||
)?;
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
use crate::hook::eval_hook;
|
||||
use fancy_regex::Regex;
|
||||
use itertools::Itertools;
|
||||
use nu_engine::env_to_strings;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
@ -11,6 +9,7 @@ use nu_protocol::{
|
||||
SyntaxShape, Type, Value,
|
||||
};
|
||||
use nu_system::ForegroundProcess;
|
||||
use os_pipe::PipeReader;
|
||||
use pathdiff::diff_paths;
|
||||
use std::collections::HashMap;
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
@ -41,6 +40,11 @@ impl Command for External {
|
||||
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||
.switch("redirect-stdout", "redirect stdout to the pipeline", None)
|
||||
.switch("redirect-stderr", "redirect stderr to the pipeline", None)
|
||||
.switch(
|
||||
"redirect-combine",
|
||||
"redirect both stdout and stderr combined to the pipeline (collected in stdout)",
|
||||
None,
|
||||
)
|
||||
.switch("trim-end-newline", "trimming end newlines", None)
|
||||
.required("command", SyntaxShape::String, "external command to run")
|
||||
.rest("args", SyntaxShape::Any, "arguments for external command")
|
||||
@ -56,14 +60,25 @@ impl Command for External {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let redirect_stdout = call.has_flag("redirect-stdout");
|
||||
let redirect_stderr = call.has_flag("redirect-stderr");
|
||||
let redirect_combine = call.has_flag("redirect-combine");
|
||||
let trim_end_newline = call.has_flag("trim-end-newline");
|
||||
|
||||
if redirect_combine && (redirect_stdout || redirect_stderr) {
|
||||
return Err(ShellError::ExternalCommand {
|
||||
label: "Cannot use --redirect-combine with --redirect-stdout or --redirect-stderr"
|
||||
.into(),
|
||||
help: "use either --redirect-combine or redirect a single output stream".into(),
|
||||
span: call.head,
|
||||
});
|
||||
}
|
||||
|
||||
let command = create_external_command(
|
||||
engine_state,
|
||||
stack,
|
||||
call,
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
redirect_combine,
|
||||
trim_end_newline,
|
||||
)?;
|
||||
|
||||
@ -93,6 +108,7 @@ pub fn create_external_command(
|
||||
call: &Call,
|
||||
redirect_stdout: bool,
|
||||
redirect_stderr: bool,
|
||||
redirect_combine: bool,
|
||||
trim_end_newline: bool,
|
||||
) -> Result<ExternalCommand, ShellError> {
|
||||
let name: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
@ -149,6 +165,7 @@ pub fn create_external_command(
|
||||
arg_keep_raw,
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
redirect_combine,
|
||||
env_vars: env_vars_str,
|
||||
trim_end_newline,
|
||||
})
|
||||
@ -161,6 +178,7 @@ pub struct ExternalCommand {
|
||||
pub arg_keep_raw: Vec<bool>,
|
||||
pub redirect_stdout: bool,
|
||||
pub redirect_stderr: bool,
|
||||
pub redirect_combine: bool,
|
||||
pub env_vars: HashMap<String, String>,
|
||||
pub trim_end_newline: bool,
|
||||
}
|
||||
@ -177,10 +195,10 @@ impl ExternalCommand {
|
||||
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
|
||||
let mut fg_process = ForegroundProcess::new(
|
||||
self.create_process(&input, false, head)?,
|
||||
engine_state.pipeline_externals_state.clone(),
|
||||
);
|
||||
#[allow(unused_mut)]
|
||||
let (cmd, mut reader) = self.create_process(&input, false, head)?;
|
||||
let mut fg_process =
|
||||
ForegroundProcess::new(cmd, engine_state.pipeline_externals_state.clone());
|
||||
// mut is used in the windows branch only, suppress warning on other platforms
|
||||
#[allow(unused_mut)]
|
||||
let mut child;
|
||||
@ -211,8 +229,10 @@ impl ExternalCommand {
|
||||
.any(|&cmd| command_name_upper == cmd);
|
||||
|
||||
if looks_like_cmd_internal {
|
||||
let (cmd, new_reader) = self.create_process(&input, true, head)?;
|
||||
reader = new_reader;
|
||||
let mut cmd_process = ForegroundProcess::new(
|
||||
self.create_process(&input, true, head)?,
|
||||
cmd,
|
||||
engine_state.pipeline_externals_state.clone(),
|
||||
);
|
||||
child = cmd_process.spawn();
|
||||
@ -242,9 +262,11 @@ impl ExternalCommand {
|
||||
item: file_name.to_string_lossy().to_string(),
|
||||
span: self.name.span,
|
||||
};
|
||||
let (cmd, new_reader) = new_command
|
||||
.create_process(&input, true, head)?;
|
||||
reader = new_reader;
|
||||
let mut cmd_process = ForegroundProcess::new(
|
||||
new_command
|
||||
.create_process(&input, true, head)?,
|
||||
cmd,
|
||||
engine_state.pipeline_externals_state.clone(),
|
||||
);
|
||||
child = cmd_process.spawn();
|
||||
@ -419,6 +441,7 @@ impl ExternalCommand {
|
||||
let commandname = self.name.item.clone();
|
||||
let redirect_stdout = self.redirect_stdout;
|
||||
let redirect_stderr = self.redirect_stderr;
|
||||
let redirect_combine = self.redirect_combine;
|
||||
let span = self.name.span;
|
||||
let output_ctrlc = ctrlc.clone();
|
||||
let stderr_ctrlc = ctrlc.clone();
|
||||
@ -441,6 +464,12 @@ impl ExternalCommand {
|
||||
.to_string(), span }
|
||||
})?;
|
||||
|
||||
read_and_redirect_message(stdout, stdout_tx, ctrlc)
|
||||
} else if redirect_combine {
|
||||
let stdout = reader.ok_or_else(|| {
|
||||
ShellError::ExternalCommand { label: "Error taking combined stdout and stderr from external".to_string(), help: "Combined redirects need access to reader pipe of an external command"
|
||||
.to_string(), span }
|
||||
})?;
|
||||
read_and_redirect_message(stdout, stdout_tx, ctrlc)
|
||||
}
|
||||
|
||||
@ -516,7 +545,7 @@ impl ExternalCommand {
|
||||
let exit_code_receiver = ValueReceiver::new(exit_code_rx);
|
||||
|
||||
Ok(PipelineData::ExternalStream {
|
||||
stdout: if redirect_stdout {
|
||||
stdout: if redirect_stdout || redirect_combine {
|
||||
Some(RawStream::new(
|
||||
Box::new(stdout_receiver),
|
||||
output_ctrlc.clone(),
|
||||
@ -553,7 +582,7 @@ impl ExternalCommand {
|
||||
input: &PipelineData,
|
||||
use_cmd: bool,
|
||||
span: Span,
|
||||
) -> Result<CommandSys, ShellError> {
|
||||
) -> Result<(CommandSys, Option<PipeReader>), ShellError> {
|
||||
let mut process = if let Some(d) = self.env_vars.get("PWD") {
|
||||
let mut process = if use_cmd {
|
||||
self.spawn_cmd_command(d)
|
||||
@ -583,13 +612,22 @@ impl ExternalCommand {
|
||||
|
||||
// If the external is not the last command, its output will get piped
|
||||
// either as a string or binary
|
||||
if self.redirect_stdout {
|
||||
process.stdout(Stdio::piped());
|
||||
}
|
||||
let reader = if self.redirect_combine {
|
||||
let (reader, writer) = os_pipe::pipe()?;
|
||||
let writer_clone = writer.try_clone()?;
|
||||
process.stdout(writer);
|
||||
process.stderr(writer_clone);
|
||||
Some(reader)
|
||||
} else {
|
||||
if self.redirect_stdout {
|
||||
process.stdout(Stdio::piped());
|
||||
}
|
||||
|
||||
if self.redirect_stderr {
|
||||
process.stderr(Stdio::piped());
|
||||
}
|
||||
if self.redirect_stderr {
|
||||
process.stderr(Stdio::piped());
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
// If there is an input from the pipeline. The stdin from the process
|
||||
// is piped so it can be used to send the input information
|
||||
@ -597,7 +635,7 @@ impl ExternalCommand {
|
||||
process.stdin(Stdio::piped());
|
||||
}
|
||||
|
||||
Ok(process)
|
||||
Ok((process, reader))
|
||||
}
|
||||
|
||||
fn create_command(&self, cwd: &str) -> Result<CommandSys, ShellError> {
|
||||
@ -611,8 +649,6 @@ impl ExternalCommand {
|
||||
} else {
|
||||
self.spawn_simple_command(cwd)
|
||||
}
|
||||
} else if self.name.item.ends_with(".sh") {
|
||||
Ok(self.spawn_sh_command())
|
||||
} else {
|
||||
self.spawn_simple_command(cwd)
|
||||
}
|
||||
@ -658,19 +694,6 @@ impl ExternalCommand {
|
||||
|
||||
process
|
||||
}
|
||||
|
||||
/// Spawn a sh command with `sh -c args...`
|
||||
pub fn spawn_sh_command(&self) -> std::process::Command {
|
||||
let joined_and_escaped_arguments = self
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| shell_arg_escape(&arg.item))
|
||||
.join(" ");
|
||||
let cmd_with_args = vec![self.name.item.clone(), joined_and_escaped_arguments].join(" ");
|
||||
let mut process = std::process::Command::new("sh");
|
||||
process.arg("-c").arg(cmd_with_args);
|
||||
process
|
||||
}
|
||||
}
|
||||
|
||||
fn trim_expand_and_apply_arg(
|
||||
@ -756,23 +779,6 @@ fn suggest_command(attempted_command: &str, engine_state: &EngineState) -> Optio
|
||||
}
|
||||
}
|
||||
|
||||
fn has_unsafe_shell_characters(arg: &str) -> bool {
|
||||
let re: Regex = Regex::new(r"[^\w@%+=:,./-]").expect("regex to be valid");
|
||||
|
||||
re.is_match(arg).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn shell_arg_escape(arg: &str) -> String {
|
||||
match arg {
|
||||
"" => String::from("''"),
|
||||
s if !has_unsafe_shell_characters(s) => String::from(s),
|
||||
_ => {
|
||||
let single_quotes_escaped = arg.split('\'').join("'\"'\"'");
|
||||
format!("'{single_quotes_escaped}'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This function returns a tuple with 3 items:
|
||||
/// 1st item: trimmed string.
|
||||
/// 2nd item: a boolean value indicate if it's ok to run glob expansion.
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -37,6 +37,20 @@ param:string #My cool attractive param
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn def_errors_with_no_space_between_params_and_name_1() {
|
||||
let actual = nu!("def test-command[] {}");
|
||||
|
||||
assert!(actual.err.contains("expected space"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn def_errors_with_no_space_between_params_and_name_2() {
|
||||
let actual = nu!("def-env test-command() {}");
|
||||
|
||||
assert!(actual.err.contains("expected space"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn def_errors_with_multiple_short_flags() {
|
||||
let actual = nu!("def test-command [ --long(-l)(-o) ] {}");
|
||||
@ -141,3 +155,11 @@ fn def_with_paren_params() {
|
||||
|
||||
assert_eq!(actual.out, "3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extern_with_block() {
|
||||
let actual =
|
||||
nu!("extern foo [...rest] { print ($rest | str join ',' ) }; foo --bar baz -- -q -u -x");
|
||||
|
||||
assert_eq!(actual.out, "--bar,baz,--,-q,-u,-x");
|
||||
}
|
||||
|
@ -1,73 +0,0 @@
|
||||
use nu_test_support::fs::{files_exist_at, Stub::EmptyFile};
|
||||
use nu_test_support::nu;
|
||||
use nu_test_support::playground::Playground;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn knows_the_filesystems_entered() {
|
||||
Playground::setup("enter_test_1", |dirs, sandbox| {
|
||||
sandbox
|
||||
.within("red_pill")
|
||||
.with_files(vec![
|
||||
EmptyFile("andres.nu"),
|
||||
EmptyFile("jtnu"),
|
||||
EmptyFile("yehuda.nu"),
|
||||
])
|
||||
.within("blue_pill")
|
||||
.with_files(vec![
|
||||
EmptyFile("bash.nxt"),
|
||||
EmptyFile("korn.nxt"),
|
||||
EmptyFile("powedsh.nxt"),
|
||||
])
|
||||
.mkdir("expected");
|
||||
|
||||
let red_pill_dir = dirs.test().join("red_pill");
|
||||
let blue_pill_dir = dirs.test().join("blue_pill");
|
||||
let expected = dirs.test().join("expected");
|
||||
let expected_recycled = expected.join("recycled");
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"
|
||||
enter expected
|
||||
mkdir recycled
|
||||
enter ../red_pill
|
||||
mv jtnu ../expected
|
||||
enter ../blue_pill
|
||||
cp *.nxt ../expected/recycled
|
||||
p
|
||||
p
|
||||
mv ../red_pill/yehuda.nu .
|
||||
n
|
||||
mv andres.nu ../expected/andres.nu
|
||||
exit
|
||||
cd ..
|
||||
rm red_pill --recursive
|
||||
exit
|
||||
n
|
||||
rm blue_pill --recursive
|
||||
exit
|
||||
"
|
||||
);
|
||||
|
||||
assert!(!red_pill_dir.exists());
|
||||
assert!(files_exist_at(
|
||||
vec![
|
||||
Path::new("andres.nu"),
|
||||
Path::new("jtnu"),
|
||||
Path::new("yehuda.nu"),
|
||||
],
|
||||
expected
|
||||
));
|
||||
|
||||
assert!(!blue_pill_dir.exists());
|
||||
assert!(files_exist_at(
|
||||
vec![
|
||||
Path::new("bash.nxt"),
|
||||
Path::new("korn.nxt"),
|
||||
Path::new("powedsh.nxt"),
|
||||
],
|
||||
expected_recycled
|
||||
));
|
||||
})
|
||||
}
|
@ -1,123 +1,96 @@
|
||||
use nu_test_support::fs::Stub::EmptyFile;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
use nu_test_support::nu;
|
||||
|
||||
#[test]
|
||||
fn find_with_list_search_with_string() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
[moe larry curly] | find moe | get 0
|
||||
"#
|
||||
));
|
||||
let actual = nu!("[moe larry curly] | find moe | get 0");
|
||||
|
||||
assert_eq!(actual.out, "moe");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_with_list_search_with_char() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
[moe larry curly] | find l | to json -r
|
||||
"#
|
||||
));
|
||||
let actual = nu!("[moe larry curly] | find l | to json -r");
|
||||
|
||||
assert_eq!(actual.out, r#"["larry","curly"]"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_with_list_search_with_number() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
[1 2 3 4 5] | find 3 | get 0
|
||||
"#
|
||||
));
|
||||
let actual = nu!("[1 2 3 4 5] | find 3 | get 0");
|
||||
|
||||
assert_eq!(actual.out, "3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_with_string_search_with_string() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
echo Cargo.toml | find toml
|
||||
"#
|
||||
));
|
||||
let actual = nu!("echo Cargo.toml | find toml");
|
||||
|
||||
assert_eq!(actual.out, "Cargo.toml");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_with_string_search_with_string_not_found() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
[moe larry curly] | find shemp | is-empty
|
||||
"#
|
||||
));
|
||||
let actual = nu!("[moe larry curly] | find shemp | is-empty");
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_with_filepath_search_with_string() {
|
||||
Playground::setup("filepath_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
EmptyFile("amigos.txt"),
|
||||
EmptyFile("arepas.clu"),
|
||||
EmptyFile("los.txt"),
|
||||
EmptyFile("tres.txt"),
|
||||
]);
|
||||
let actual =
|
||||
nu!(r#"["amigos.txt","arepas.clu","los.txt","tres.txt"] | find arep | to json -r"#);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
ls
|
||||
| get name
|
||||
| find arep
|
||||
| to json -r
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, r#"["arepas.clu"]"#);
|
||||
})
|
||||
assert_eq!(actual.out, r#"["arepas.clu"]"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_with_filepath_search_with_multiple_patterns() {
|
||||
Playground::setup("filepath_test_2", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
EmptyFile("amigos.txt"),
|
||||
EmptyFile("arepas.clu"),
|
||||
EmptyFile("los.txt"),
|
||||
EmptyFile("tres.txt"),
|
||||
]);
|
||||
let actual =
|
||||
nu!(r#"["amigos.txt","arepas.clu","los.txt","tres.txt"] | find arep ami | to json -r"#);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
ls
|
||||
| get name
|
||||
| find arep ami
|
||||
| to json -r
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, r#"["amigos.txt","arepas.clu"]"#);
|
||||
})
|
||||
assert_eq!(actual.out, r#"["amigos.txt","arepas.clu"]"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_takes_into_account_linebreaks_in_string() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
"atest\nanothertest\nnohit\n" | find a | length
|
||||
"#
|
||||
));
|
||||
let actual = nu!(r#""atest\nanothertest\nnohit\n" | find a | length"#);
|
||||
|
||||
assert_eq!(actual.out, "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_with_regex_in_table_keeps_row_if_one_column_matches() {
|
||||
let actual = nu!(
|
||||
"[[name nickname]; [Maurice moe] [Laurence larry]] | find --regex ce | get name | to json -r"
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, r#"["Maurice","Laurence"]"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inverted_find_with_regex_in_table_keeps_row_if_none_of_the_columns_matches() {
|
||||
let actual = nu!(
|
||||
"[[name nickname]; [Maurice moe] [Laurence larry]] | find --regex moe --invert | get name | to json -r"
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, r#"["Laurence"]"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_in_table_only_keep_rows_with_matches_on_selected_columns() {
|
||||
let actual = nu!(
|
||||
"[[name nickname]; [Maurice moe] [Laurence larry]] | find r --columns [nickname] | get name | to json -r"
|
||||
);
|
||||
|
||||
assert!(actual.out.contains("Laurence"));
|
||||
assert!(!actual.out.contains("Maurice"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inverted_find_in_table_keeps_row_if_none_of_the_selected_columns_matches() {
|
||||
let actual = nu!(
|
||||
"[[name nickname]; [Maurice moe] [Laurence larry]] | find r --columns [nickname] --invert | get name | to json -r"
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, r#"["Maurice"]"#);
|
||||
}
|
||||
|
@ -1,91 +0,0 @@
|
||||
use nu_test_support::{nu, pipeline, playground::Playground};
|
||||
|
||||
#[test]
|
||||
fn list_shells() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"g | get path | length "#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enter_shell() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"g 0"#
|
||||
));
|
||||
|
||||
assert!(actual.err.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enter_not_exist_shell() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"g 1"#
|
||||
));
|
||||
|
||||
assert!(actual.err.contains("Not found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switch_to_last_used_shell_1() {
|
||||
Playground::setup("switch_last_used_shell_1", |dirs, sandbox| {
|
||||
sandbox.mkdir("foo").mkdir("bar");
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
pipeline(
|
||||
r#"enter foo; enter ../bar; g 0; g -;g | get active.2"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switch_to_last_used_shell_2() {
|
||||
Playground::setup("switch_last_used_shell_2", |dirs, sandbox| {
|
||||
sandbox.mkdir("foo").mkdir("bar");
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
pipeline(
|
||||
r#"enter foo; enter ../bar; n; g -;g | get active.2"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switch_to_last_used_shell_3() {
|
||||
Playground::setup("switch_last_used_shell_3", |dirs, sandbox| {
|
||||
sandbox.mkdir("foo").mkdir("bar");
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
pipeline(
|
||||
r#"enter foo; enter ../bar; p; g -;g | get active.2"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switch_to_last_used_shell_4() {
|
||||
Playground::setup("switch_last_used_shell_4", |dirs, sandbox| {
|
||||
sandbox.mkdir("foo").mkdir("bar");
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
pipeline(
|
||||
r#"enter foo; enter ../bar; g 2; exit; g -;g | get active.0"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
})
|
||||
}
|
@ -94,37 +94,17 @@ fn summarizes_by_values() {
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
Playground::setup("histogram_test_3", |dirs, _sandbox| {
|
||||
let help_command = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
help histogram
|
||||
"#
|
||||
));
|
||||
let help_command = nu!("help histogram");
|
||||
let help_short = nu!("histogram -h");
|
||||
let help_long = nu!("histogram --help");
|
||||
|
||||
let help_short = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
histogram -h
|
||||
"#
|
||||
));
|
||||
|
||||
let help_long = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
histogram --help
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(help_short.out, help_command.out);
|
||||
assert_eq!(help_long.out, help_command.out);
|
||||
})
|
||||
assert_eq!(help_short.out, help_command.out);
|
||||
assert_eq!(help_long.out, help_command.out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
echo [[bit]; [1] [0] [0] [0] [0] [0] [0] [1] [1]]
|
||||
| histogram bit --percentage-type relative
|
||||
|
@ -2,20 +2,35 @@ use nu_test_support::nu;
|
||||
|
||||
#[test]
|
||||
fn can_round_very_large_numbers() {
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
"echo 18.1372544780074142289927665486772012345 | math round"
|
||||
);
|
||||
let actual = nu!("18.1372544780074142289927665486772012345 | math round");
|
||||
|
||||
assert_eq!(actual.out, "18")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_round_very_large_numbers_with_precision() {
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
"echo 18.13725447800741422899276654867720121457878988 | math round -p 10"
|
||||
);
|
||||
let actual = nu!("18.13725447800741422899276654867720121457878988 | math round -p 10");
|
||||
|
||||
assert_eq!(actual.out, "18.137254478")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_round_integer_with_negative_precision() {
|
||||
let actual = nu!("123 | math round -p -1");
|
||||
|
||||
assert_eq!(actual.out, "120")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_round_float_with_negative_precision() {
|
||||
let actual = nu!("123.3 | math round -p -1");
|
||||
|
||||
assert_eq!(actual.out, "120")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_with_wrong_input_type() {
|
||||
let actual = nu!("\"not_a_number\" | math round");
|
||||
|
||||
assert!(actual.err.contains("Input type not supported"))
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ mod drop;
|
||||
mod each;
|
||||
mod echo;
|
||||
mod empty;
|
||||
mod enter;
|
||||
mod error_make;
|
||||
mod every;
|
||||
#[cfg(not(windows))]
|
||||
@ -30,7 +29,6 @@ mod first;
|
||||
mod flatten;
|
||||
mod for_;
|
||||
mod format;
|
||||
mod g;
|
||||
mod get;
|
||||
mod glob;
|
||||
mod group_by;
|
||||
@ -54,11 +52,10 @@ mod merge;
|
||||
mod mkdir;
|
||||
mod move_;
|
||||
mod mut_;
|
||||
mod n;
|
||||
mod network;
|
||||
mod nu_check;
|
||||
mod open;
|
||||
mod p;
|
||||
mod par_each;
|
||||
mod parse;
|
||||
mod path;
|
||||
mod platform;
|
||||
@ -83,7 +80,6 @@ mod select;
|
||||
mod semicolon;
|
||||
mod seq;
|
||||
mod seq_char;
|
||||
mod shells;
|
||||
mod skip;
|
||||
mod sort;
|
||||
mod sort_by;
|
||||
|
@ -1,31 +0,0 @@
|
||||
use nu_test_support::{nu, pipeline, playground::Playground};
|
||||
|
||||
#[test]
|
||||
fn switch_to_next_shell_1() {
|
||||
Playground::setup("switch_to_next_shell_1", |dirs, sandbox| {
|
||||
sandbox.mkdir("foo").mkdir("bar");
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
pipeline(
|
||||
r#"enter foo; enter ../bar; n; g | get active.0"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switch_to_next_shell_2() {
|
||||
Playground::setup("switch_to_next_shell_2", |dirs, sandbox| {
|
||||
sandbox.mkdir("foo").mkdir("bar");
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
pipeline(
|
||||
r#"enter foo; enter ../bar; n; n; g | get active.1"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
})
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
use nu_test_support::{nu, pipeline, playground::Playground};
|
||||
|
||||
#[test]
|
||||
fn switch_to_prev_shell_1() {
|
||||
Playground::setup("switch_to_next_shell_1", |dirs, sandbox| {
|
||||
sandbox.mkdir("foo").mkdir("bar");
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
pipeline(
|
||||
r#"enter foo; enter ../bar; p; g | get active.1"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switch_to_prev_shell_2() {
|
||||
Playground::setup("switch_to_next_shell_2", |dirs, sandbox| {
|
||||
sandbox.mkdir("foo").mkdir("bar");
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
pipeline(
|
||||
r#"enter foo; enter ../bar; p; p; p; g | get active.2"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
})
|
||||
}
|
12
crates/nu-command/tests/commands/par_each.rs
Normal file
12
crates/nu-command/tests/commands/par_each.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn par_each_does_not_flatten_nested_structures() {
|
||||
// This is a regression test for issue #8497
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"[1 2 3] | par-each { |it| [$it, $it] } | sort | to json --raw"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "[[1,1],[2,2],[3,3]]");
|
||||
}
|
@ -2,7 +2,7 @@ use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn regular_columns() {
|
||||
let actual = nu!(cwd: ".", pipeline(
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
echo [
|
||||
[first_name, last_name, rusty_at, type];
|
||||
@ -22,15 +22,14 @@ fn regular_columns() {
|
||||
|
||||
#[test]
|
||||
fn skip_cell_rejection() {
|
||||
let actual = nu!(cwd: ".", pipeline(
|
||||
r#"[ {a: 1, b: 2,c:txt}, { a:val } ] | reject a | get c?.0"#));
|
||||
let actual = nu!("[ {a: 1, b: 2,c:txt}, { a:val } ] | reject a | get c?.0");
|
||||
|
||||
assert_eq!(actual.out, "txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complex_nested_columns() {
|
||||
let actual = nu!(cwd: ".", pipeline(
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
{
|
||||
"nu": {
|
||||
@ -63,7 +62,7 @@ fn complex_nested_columns() {
|
||||
|
||||
#[test]
|
||||
fn ignores_duplicate_columns_rejected() {
|
||||
let actual = nu!(cwd: ".", pipeline(
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
echo [
|
||||
["first name", "last name"];
|
||||
@ -82,60 +81,36 @@ fn ignores_duplicate_columns_rejected() {
|
||||
|
||||
#[test]
|
||||
fn reject_record_from_raw_eval() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
{"a": 3} | reject a | describe
|
||||
"#
|
||||
)
|
||||
);
|
||||
let actual = nu!(r#"{"a": 3} | reject a | describe"#);
|
||||
|
||||
assert!(actual.out.contains("record"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reject_table_from_raw_eval() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
[{"a": 3}] | reject a
|
||||
"#
|
||||
)
|
||||
);
|
||||
let actual = nu!(r#"[{"a": 3}] | reject a"#);
|
||||
|
||||
assert!(actual.out.contains("record 0 fields"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reject_nested_field() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
{a:{b:3,c:5}} | reject a.b | debug
|
||||
"#
|
||||
)
|
||||
);
|
||||
let actual = nu!("{a:{b:3,c:5}} | reject a.b | debug");
|
||||
|
||||
assert_eq!(actual.out, "{a: {c: 5}}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reject_two_identical_elements() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"[[a, a]; [1, 2]] | reject a"#
|
||||
)
|
||||
);
|
||||
let actual = nu!("[[a, a]; [1, 2]] | reject a");
|
||||
|
||||
assert!(actual.out.contains("record 0 fields"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reject_large_vec_with_two_identical_elements() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"[[a, b, c, d, e, a]; [1323, 23, 45, 100, 2, 2423]] | reject a"#
|
||||
)
|
||||
);
|
||||
let actual = nu!("[[a, b, c, d, e, a]; [1323, 23, 45, 100, 2, 2423]] | reject a");
|
||||
|
||||
assert!(!actual.out.contains("1323"));
|
||||
assert!(!actual.out.contains("2423"));
|
||||
assert!(actual.out.contains('b'));
|
||||
|
@ -320,3 +320,18 @@ fn quotes_trimmed_when_shelling_out() {
|
||||
|
||||
assert_eq!(actual.out, "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redirect_combine() {
|
||||
Playground::setup("redirect_combine", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
run-external --redirect-combine sh [-c 'echo Foo; echo >&2 Bar']
|
||||
"#
|
||||
));
|
||||
|
||||
// Lines are collapsed in the nu! macro
|
||||
assert_eq!(actual.out, "FooBar");
|
||||
});
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use nu_test_support::nu;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn semicolon_allows_lhs_to_complete() {
|
||||
@ -18,12 +18,7 @@ fn semicolon_allows_lhs_to_complete() {
|
||||
|
||||
#[test]
|
||||
fn semicolon_lhs_error_stops_processing() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
where 1 1; echo done
|
||||
"#
|
||||
));
|
||||
let actual = nu!("where 1 1; echo done");
|
||||
|
||||
assert!(!actual.out.contains("done"));
|
||||
}
|
||||
|
@ -1,25 +1,15 @@
|
||||
use nu_test_support::{nu, pipeline};
|
||||
use nu_test_support::nu;
|
||||
|
||||
#[test]
|
||||
fn float_in_seq_leads_to_lists_of_floats() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
seq 1.0 0.5 6 | describe
|
||||
"#
|
||||
));
|
||||
let actual = nu!("seq 1.0 0.5 6 | describe");
|
||||
|
||||
assert_eq!(actual.out, "list<float> (stream)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ints_in_seq_leads_to_lists_of_ints() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
seq 1 2 6 | describe
|
||||
"#
|
||||
));
|
||||
let actual = nu!("seq 1 2 6 | describe");
|
||||
|
||||
assert_eq!(actual.out, "list<int> (stream)");
|
||||
}
|
||||
|
@ -1,25 +1,15 @@
|
||||
use nu_test_support::{nu, pipeline};
|
||||
use nu_test_support::nu;
|
||||
|
||||
#[test]
|
||||
fn fails_when_first_arg_is_multiple_chars() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
seq char aa z
|
||||
"#
|
||||
));
|
||||
let actual = nu!("seq char aa z");
|
||||
|
||||
assert!(actual.err.contains("should be 1 character long"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_when_second_arg_is_multiple_chars() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
seq char a zz
|
||||
"#
|
||||
));
|
||||
let actual = nu!("seq char a zz");
|
||||
|
||||
assert!(actual.err.contains("should be 1 character long"));
|
||||
}
|
||||
|
@ -1,31 +0,0 @@
|
||||
use nu_test_support::{nu, pipeline, playground::Playground};
|
||||
|
||||
#[test]
|
||||
fn list_shells_1() {
|
||||
Playground::setup("list_shells_1", |dirs, sandbox| {
|
||||
sandbox.mkdir("foo").mkdir("bar");
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
pipeline(
|
||||
r#"enter foo; enter ../bar; g| get active.2"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_shells_2() {
|
||||
Playground::setup("list_shells_2", |dirs, sandbox| {
|
||||
sandbox.mkdir("foo").mkdir("bar");
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
pipeline(
|
||||
r#"enter foo; enter ../bar; shells| get active.2"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
})
|
||||
}
|
@ -35,12 +35,7 @@ fn sort_primitive_values() {
|
||||
|
||||
#[test]
|
||||
fn sort_different_types() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
[a, 1, b, 2, c, 3, [4, 5, 6], d, 4, [1, 2, 3]] | sort | to json --raw
|
||||
"#
|
||||
));
|
||||
let actual = nu!("[a, 1, b, 2, c, 3, [4, 5, 6], d, 4, [1, 2, 3]] | sort | to json --raw");
|
||||
|
||||
let json_output = r#"[1,2,3,4,"a","b","c","d",[1,2,3],[4,5,6]]"#;
|
||||
assert_eq!(actual.out, json_output);
|
||||
@ -48,20 +43,14 @@ fn sort_different_types() {
|
||||
|
||||
#[test]
|
||||
fn sort_natural() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"['1' '2' '3' '4' '5' '10' '100'] | sort -n | to nuon"#
|
||||
));
|
||||
let actual = nu!("['1' '2' '3' '4' '5' '10' '100'] | sort -n | to nuon");
|
||||
|
||||
assert_eq!(actual.out, r#"["1", "2", "3", "4", "5", "10", "100"]"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sort_record_natural() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"{10:0,99:0,1:0,9:0,100:0} | sort -n | to nuon"#
|
||||
));
|
||||
let actual = nu!("{10:0,99:0,1:0,9:0,100:0} | sort -n | to nuon");
|
||||
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
@ -71,50 +60,35 @@ fn sort_record_natural() {
|
||||
|
||||
#[test]
|
||||
fn sort_record_insensitive() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"{abe:1,zed:2,ABE:3} | sort -i | to nuon"#
|
||||
));
|
||||
let actual = nu!("{abe:1,zed:2,ABE:3} | sort -i | to nuon");
|
||||
|
||||
assert_eq!(actual.out, r#"{abe: 1, ABE: 3, zed: 2}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sort_record_insensitive_reverse() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"{abe:1,zed:2,ABE:3} | sort -ir | to nuon"#
|
||||
));
|
||||
let actual = nu!("{abe:1,zed:2,ABE:3} | sort -ir | to nuon");
|
||||
|
||||
assert_eq!(actual.out, r#"{zed: 2, ABE: 3, abe: 1}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sort_record_values_natural() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"{1:"1",2:"2",4:"100",3:"10"} | sort -vn | to nuon"#
|
||||
));
|
||||
let actual = nu!(r#"{1:"1",2:"2",4:"100",3:"10"} | sort -vn | to nuon"#);
|
||||
|
||||
assert_eq!(actual.out, r#"{"1": "1", "2": "2", "3": "10", "4": "100"}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sort_record_values_insensitive() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"{1:abe,2:zed,3:ABE} | sort -vi | to nuon"#
|
||||
));
|
||||
let actual = nu!("{1:abe,2:zed,3:ABE} | sort -vi | to nuon");
|
||||
|
||||
assert_eq!(actual.out, r#"{"1": abe, "3": ABE, "2": zed}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sort_record_values_insensitive_reverse() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"{1:abe,2:zed,3:ABE} | sort -vir | to nuon"#
|
||||
));
|
||||
let actual = nu!("{1:abe,2:zed,3:ABE} | sort -vir | to nuon");
|
||||
|
||||
assert_eq!(actual.out, r#"{"2": zed, "3": ABE, "1": abe}"#);
|
||||
}
|
||||
|
@ -117,19 +117,14 @@ fn ls_sort_by_type_name_insensitive() {
|
||||
|
||||
#[test]
|
||||
fn no_column_specified_fails() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
[2 0 1] | sort-by
|
||||
"#
|
||||
));
|
||||
let actual = nu!("[2 0 1] | sort-by");
|
||||
|
||||
assert!(actual.err.contains("missing parameter"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fail_on_non_iterator() {
|
||||
let actual = nu!(cwd: ".", pipeline("1 | sort-by"));
|
||||
let actual = nu!("1 | sort-by");
|
||||
|
||||
assert!(actual.err.contains("only_supports_this_input_type"));
|
||||
}
|
||||
|
@ -154,11 +154,11 @@ fn table_collapse_none() {
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
concat!(
|
||||
" a b c ",
|
||||
" 1 2 3 ",
|
||||
" 4 5 1 ",
|
||||
" 2 ",
|
||||
" 3 ",
|
||||
" a b c ",
|
||||
" 1 2 3 ",
|
||||
" 4 5 1 ",
|
||||
" 2 ",
|
||||
" 3 ",
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -232,11 +232,20 @@ fn table_collapse_hearts() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_collapse_doesnot_support_width_control() {
|
||||
fn table_collapse_does_wrapping_for_long_strings() {
|
||||
let actual = nu!(
|
||||
r#"[[a]; [11111111111111111111111111111111111111111111111111111111111111111111111111111111]] | table --collapse"#
|
||||
);
|
||||
assert_eq!(actual.out, "Couldn't fit table into 80 columns!");
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
"╭────────────────────────────────╮\
|
||||
│ a │\
|
||||
├────────────────────────────────┤\
|
||||
│ 111111111111111109312339230430 │\
|
||||
│ 179149313814687359833671239329 │\
|
||||
│ 01313323321729744896.0000 │\
|
||||
╰────────────────────────────────╯"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1795,6 +1804,526 @@ fn table_expande_with_no_header_internally_1() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collapse_big_0() {
|
||||
Playground::setup("test_expand_big_0", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"sample.toml",
|
||||
r#"
|
||||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
default-run = "nu"
|
||||
description = "A new type of shell"
|
||||
documentation = "https://www.nushell.sh/book/"
|
||||
edition = "2021"
|
||||
exclude = ["images"]
|
||||
homepage = "https://www.nushell.sh"
|
||||
license = "MIT"
|
||||
name = "nu"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.60"
|
||||
version = "0.74.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[package.metadata.binstall]
|
||||
pkg-url = "{ repo }/releases/download/{ version }/{ name }-{ version }-{ target }.{ archive-format }"
|
||||
pkg-fmt = "tgz"
|
||||
|
||||
[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
|
||||
pkg-fmt = "zip"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/nu-cli",
|
||||
"crates/nu-engine",
|
||||
"crates/nu-parser",
|
||||
"crates/nu-system",
|
||||
"crates/nu-command",
|
||||
"crates/nu-protocol",
|
||||
"crates/nu-plugin",
|
||||
"crates/nu_plugin_inc",
|
||||
"crates/nu_plugin_gstat",
|
||||
"crates/nu_plugin_example",
|
||||
"crates/nu_plugin_query",
|
||||
"crates/nu_plugin_custom_values",
|
||||
"crates/nu-utils",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.23", features = ["serde"] }
|
||||
crossterm = "0.24.0"
|
||||
ctrlc = "3.2.1"
|
||||
log = "0.4"
|
||||
miette = { version = "5.5.0", features = ["fancy-no-backtrace"] }
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.74.1" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.74.1" }
|
||||
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"] }
|
||||
|
||||
rayon = "1.6.1"
|
||||
is_executable = "1.0.1"
|
||||
simplelog = "0.12.0"
|
||||
time = "0.3.12"
|
||||
|
||||
[target.'cfg(not(target_os = "windows"))'.dependencies]
|
||||
# Our dependencies don't use OpenSSL on Windows
|
||||
openssl = { version = "0.10.38", features = ["vendored"], optional = true }
|
||||
signal-hook = { version = "0.3.14", default-features = false }
|
||||
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
winres = "0.1"
|
||||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
nix = { version = "0.25", default-features = false, features = ["signal", "process", "fs", "term"] }
|
||||
atty = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.74.1" }
|
||||
tempfile = "3.2.0"
|
||||
assert_cmd = "2.0.2"
|
||||
criterion = "0.4"
|
||||
pretty_assertions = "1.0.0"
|
||||
serial_test = "0.10.0"
|
||||
hamcrest2 = "0.3.0"
|
||||
rstest = { version = "0.15.0", default-features = false }
|
||||
itertools = "0.10.3"
|
||||
|
||||
[features]
|
||||
plugin = [
|
||||
"nu-plugin",
|
||||
"nu-cli/plugin",
|
||||
"nu-parser/plugin",
|
||||
"nu-command/plugin",
|
||||
"nu-protocol/plugin",
|
||||
"nu-engine/plugin",
|
||||
]
|
||||
# extra used to be more useful but now it's the same as default. Leaving it in for backcompat with existing build scripts
|
||||
extra = ["default"]
|
||||
default = ["plugin", "which-support", "trash-support", "sqlite"]
|
||||
stable = ["default"]
|
||||
wasi = []
|
||||
|
||||
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
|
||||
static-link-openssl = ["dep:openssl"]
|
||||
|
||||
# Stable (Default)
|
||||
which-support = ["nu-command/which-support"]
|
||||
trash-support = ["nu-command/trash-support"]
|
||||
|
||||
# Main nu binary
|
||||
[[bin]]
|
||||
name = "nu"
|
||||
path = "src/main.rs"
|
||||
|
||||
# To use a development version of a dependency please use a global override here
|
||||
# changing versions in each sub-crate of the workspace is tedious
|
||||
[patch.crates-io]
|
||||
reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" }
|
||||
|
||||
# Criterion benchmarking setup
|
||||
# Run all benchmarks with `cargo bench`
|
||||
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
"open sample.toml | table --collapse"
|
||||
));
|
||||
|
||||
_print_lines(&actual.out, 80);
|
||||
|
||||
let expected = join_lines([
|
||||
"╭──────────────────┬─────────┬─────────────────────────────────────────────────╮",
|
||||
"│ bench │ harness │ name │",
|
||||
"│ ├─────────┼─────────────────────────────────────────────────┤",
|
||||
"│ │ false │ benchmarks │",
|
||||
"├──────────────────┼──────┬──┴─────────────────────────────────────────────────┤",
|
||||
"│ bin │ name │ path │",
|
||||
"│ ├──────┼────────────────────────────────────────────────────┤",
|
||||
"│ │ nu │ src/main.rs │",
|
||||
"├──────────────────┼──────┴────────┬──────────┬────────────────────────────────┤",
|
||||
"│ dependencies │ chrono │ features │ serde │",
|
||||
"│ │ ├──────────┼────────────────────────────────┤",
|
||||
"│ │ │ version │ 0.4.23 │",
|
||||
"│ ├───────────────┼──────────┴────────────────────────────────┤",
|
||||
"│ │ crossterm │ 0.24.0 │",
|
||||
"│ ├───────────────┼───────────────────────────────────────────┤",
|
||||
"│ │ ctrlc │ 3.2.1 │",
|
||||
"│ ├───────────────┼───────────────────────────────────────────┤",
|
||||
"│ │ is_executable │ 1.0.1 │",
|
||||
"│ ├───────────────┼───────────────────────────────────────────┤",
|
||||
"│ │ log │ 0.4 │",
|
||||
"│ ├───────────────┼──────────┬────────────────────────────────┤",
|
||||
"│ │ miette │ features │ fancy-no-backtrace │",
|
||||
"│ │ ├──────────┼────────────────────────────────┤",
|
||||
"│ │ │ version │ 5.5.0 │",
|
||||
"│ ├───────────────┼──────────┴────────────────────────────────┤",
|
||||
"│ │ nu-ansi-term │ 0.46.0 │",
|
||||
"│ ├───────────────┼─────────┬─────────────────────────────────┤",
|
||||
"│ │ nu-cli │ path │ ./crates/nu-cli │",
|
||||
"│ │ ├─────────┼─────────────────────────────────┤",
|
||||
"│ │ │ version │ 0.74.1 │",
|
||||
"│ ├───────────────┼─────────┼─────────────────────────────────┤",
|
||||
"│ │ nu-engine │ path │ ./crates/nu-engine │",
|
||||
"│ │ ├─────────┼─────────────────────────────────┤",
|
||||
"│ │ │ version │ 0.74.1 │",
|
||||
"│ ├───────────────┼─────────┴─────────────────────────────────┤",
|
||||
"│ │ rayon │ 1.6.1 │",
|
||||
"│ ├───────────────┼──────────┬────────────────────────────────┤",
|
||||
"│ │ reedline │ features │ bashisms │",
|
||||
"│ │ │ ├────────────────────────────────┤",
|
||||
"│ │ │ │ sqlite │",
|
||||
"│ │ ├──────────┼────────────────────────────────┤",
|
||||
"│ │ │ version │ 0.14.0 │",
|
||||
"│ ├───────────────┼──────────┴────────────────────────────────┤",
|
||||
"│ │ simplelog │ 0.12.0 │",
|
||||
"│ ├───────────────┼───────────────────────────────────────────┤",
|
||||
"│ │ time │ 0.3.12 │",
|
||||
"├──────────────────┼───────────────┴───┬───────────────────────────────────────┤",
|
||||
"│ dev-dependencies │ assert_cmd │ 2.0.2 │",
|
||||
"│ ├───────────────────┼───────────────────────────────────────┤",
|
||||
"│ │ criterion │ 0.4 │",
|
||||
"│ ├───────────────────┼───────────────────────────────────────┤",
|
||||
"│ │ hamcrest2 │ 0.3.0 │",
|
||||
"│ ├───────────────────┼───────────────────────────────────────┤",
|
||||
"│ │ itertools │ 0.10.3 │",
|
||||
"│ ├───────────────────┼─────────┬─────────────────────────────┤",
|
||||
"│ │ nu-test-support │ path │ ./crates/nu-test-support │",
|
||||
"│ │ ├─────────┼─────────────────────────────┤",
|
||||
"│ │ │ version │ 0.74.1 │",
|
||||
"│ ├───────────────────┼─────────┴─────────────────────────────┤",
|
||||
"│ │ pretty_assertions │ 1.0.0 │",
|
||||
"│ ├───────────────────┼──────────────────┬────────────────────┤",
|
||||
"│ │ rstest │ default-features │ false │",
|
||||
"│ │ ├──────────────────┼────────────────────┤",
|
||||
"│ │ │ version │ 0.15.0 │",
|
||||
"│ ├───────────────────┼──────────────────┴────────────────────┤",
|
||||
"│ │ serial_test │ 0.10.0 │",
|
||||
"│ ├───────────────────┼───────────────────────────────────────┤",
|
||||
"│ │ tempfile │ 3.2.0 │",
|
||||
"├──────────────────┼───────────────────┴─┬─────────────────────────────────────┤",
|
||||
"│ features │ default │ plugin │",
|
||||
"│ │ ├─────────────────────────────────────┤",
|
||||
"│ │ │ which-support │",
|
||||
"│ │ ├─────────────────────────────────────┤",
|
||||
"│ │ │ trash-support │",
|
||||
"│ │ ├─────────────────────────────────────┤",
|
||||
"│ │ │ sqlite │",
|
||||
"│ ├─────────────────────┼─────────────────────────────────────┤",
|
||||
"│ │ extra │ default │",
|
||||
"│ ├─────────────────────┼─────────────────────────────────────┤",
|
||||
"│ │ plugin │ nu-plugin │",
|
||||
"│ │ ├─────────────────────────────────────┤",
|
||||
"│ │ │ nu-cli/plugin │",
|
||||
"│ │ ├─────────────────────────────────────┤",
|
||||
"│ │ │ nu-parser/plugin │",
|
||||
"│ │ ├─────────────────────────────────────┤",
|
||||
"│ │ │ nu-command/plugin │",
|
||||
"│ │ ├─────────────────────────────────────┤",
|
||||
"│ │ │ nu-protocol/plugin │",
|
||||
"│ │ ├─────────────────────────────────────┤",
|
||||
"│ │ │ nu-engine/plugin │",
|
||||
"│ ├─────────────────────┼─────────────────────────────────────┤",
|
||||
"│ │ stable │ default │",
|
||||
"│ ├─────────────────────┼─────────────────────────────────────┤",
|
||||
"│ │ static-link-openssl │ dep:openssl │",
|
||||
"│ ├─────────────────────┼─────────────────────────────────────┤",
|
||||
"│ │ trash-support │ nu-command/trash-support │",
|
||||
"│ ├─────────────────────┼─────────────────────────────────────┤",
|
||||
"│ │ wasi │ │",
|
||||
"│ ├─────────────────────┼─────────────────────────────────────┤",
|
||||
"│ │ which-support │ nu-command/which-support │",
|
||||
"├──────────────────┼───────────────┬─────┴─────────────────────────────────────┤",
|
||||
"│ package │ authors │ The Nushell Project Developers │",
|
||||
"│ ├───────────────┼───────────────────────────────────────────┤",
|
||||
"│ │ default-run │ nu │",
|
||||
"│ ├───────────────┼───────────────────────────────────────────┤",
|
||||
"│ │ description │ A new type of shell │",
|
||||
"│ ├───────────────┼───────────────────────────────────────────┤",
|
||||
"│ │ documentation │ https://www.nushell.sh/book/ │",
|
||||
"│ ├───────────────┼───────────────────────────────────────────┤",
|
||||
"│ │ edition │ 2021 │",
|
||||
"│ ├───────────────┼───────────────────────────────────────────┤",
|
||||
"│ │ exclude │ images │",
|
||||
"│ ├───────────────┼───────────────────────────────────────────┤",
|
||||
"│ │ homepage │ https://www.nushell.sh │",
|
||||
"│ ├───────────────┼───────────────────────────────────────────┤",
|
||||
"│ │ license │ MIT │",
|
||||
"│ ├───────────────┼──────────┬───────────┬────────────────────┤",
|
||||
"│ │ metadata │ binstall │ overrides │ ... │",
|
||||
"│ │ │ ├───────────┼────────────────────┤",
|
||||
"│ │ │ │ pkg-fmt │ tgz │",
|
||||
"│ │ │ ├───────────┼────────────────────┤",
|
||||
"│ │ │ │ pkg-url │ { repo }/releases/ │",
|
||||
"│ │ │ │ │ download/{ v │",
|
||||
"│ │ │ │ │ ersion │",
|
||||
"│ │ │ │ │ }/{ name }-{ vers │",
|
||||
"│ │ │ │ │ ion }- │",
|
||||
"│ │ │ │ │ { target }.{ │",
|
||||
"│ │ │ │ │ archive-format } │",
|
||||
"│ ├───────────────┼──────────┴───────────┴────────────────────┤",
|
||||
"│ │ name │ nu │",
|
||||
"│ ├───────────────┼───────────────────────────────────────────┤",
|
||||
"│ │ repository │ https://github.com/nushell/nushell │",
|
||||
"│ ├───────────────┼───────────────────────────────────────────┤",
|
||||
"│ │ rust-version │ 1.60 │",
|
||||
"│ ├───────────────┼───────────────────────────────────────────┤",
|
||||
"│ │ version │ 0.74.1 │",
|
||||
"├──────────────────┼───────────┬───┴──────┬────────┬───────────────────────────┤",
|
||||
"│ patch │ crates-io │ reedline │ branch │ main │",
|
||||
"│ │ │ ├────────┼───────────────────────────┤",
|
||||
"│ │ │ │ git │ https://github.com/nushel │",
|
||||
"│ │ │ │ │ l/reedline.git │",
|
||||
"├──────────────────┼───────────┴──────────┴────────┴─┬──────────────┬──────────┤",
|
||||
"│ target │ cfg(not(target_os = \"windows\")) │ dependencies │ ... │",
|
||||
"│ │ │ ├──────────┤",
|
||||
"│ │ │ │ ... │",
|
||||
"│ ├─────────────────────────────────┼──────────────┼──────────┤",
|
||||
"│ │ cfg(target_family = \"unix\") │ dependencies │ ... │",
|
||||
"│ │ │ ├──────────┤",
|
||||
"│ │ │ │ ... │",
|
||||
"│ ├─────────────────────────────────┼──────────────┴──────────┤",
|
||||
"│ │ cfg(windows) │ ... │",
|
||||
"├──────────────────┼─────────┬───────────────────────┴─────────────────────────┤",
|
||||
"│ workspace │ members │ crates/nu-cli │",
|
||||
"│ │ ├─────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu-engine │",
|
||||
"│ │ ├─────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu-parser │",
|
||||
"│ │ ├─────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu-system │",
|
||||
"│ │ ├─────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu-command │",
|
||||
"│ │ ├─────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu-protocol │",
|
||||
"│ │ ├─────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu-plugin │",
|
||||
"│ │ ├─────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu_plugin_inc │",
|
||||
"│ │ ├─────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu_plugin_gstat │",
|
||||
"│ │ ├─────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu_plugin_example │",
|
||||
"│ │ ├─────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu_plugin_query │",
|
||||
"│ │ ├─────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu_plugin_custom_values │",
|
||||
"│ │ ├─────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu-utils │",
|
||||
"╰──────────────────┴─────────┴─────────────────────────────────────────────────╯",
|
||||
]);
|
||||
|
||||
assert_eq!(actual.out, expected);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
"open sample.toml | table --collapse --width=160"
|
||||
));
|
||||
|
||||
_print_lines(&actual.out, 111);
|
||||
|
||||
let expected = join_lines([
|
||||
"╭──────────────────┬─────────┬────────────────────────────────────────────────────────────────────────────────╮",
|
||||
"│ bench │ harness │ name │",
|
||||
"│ ├─────────┼────────────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ false │ benchmarks │",
|
||||
"├──────────────────┼──────┬──┴────────────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ bin │ name │ path │",
|
||||
"│ ├──────┼───────────────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ nu │ src/main.rs │",
|
||||
"├──────────────────┼──────┴────────┬──────────┬───────────────────────────────────────────────────────────────┤",
|
||||
"│ dependencies │ chrono │ features │ serde │",
|
||||
"│ │ ├──────────┼───────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ version │ 0.4.23 │",
|
||||
"│ ├───────────────┼──────────┴───────────────────────────────────────────────────────────────┤",
|
||||
"│ │ crossterm │ 0.24.0 │",
|
||||
"│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ ctrlc │ 3.2.1 │",
|
||||
"│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ is_executable │ 1.0.1 │",
|
||||
"│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ log │ 0.4 │",
|
||||
"│ ├───────────────┼──────────┬───────────────────────────────────────────────────────────────┤",
|
||||
"│ │ miette │ features │ fancy-no-backtrace │",
|
||||
"│ │ ├──────────┼───────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ version │ 5.5.0 │",
|
||||
"│ ├───────────────┼──────────┴───────────────────────────────────────────────────────────────┤",
|
||||
"│ │ nu-ansi-term │ 0.46.0 │",
|
||||
"│ ├───────────────┼─────────┬────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ nu-cli │ path │ ./crates/nu-cli │",
|
||||
"│ │ ├─────────┼────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ version │ 0.74.1 │",
|
||||
"│ ├───────────────┼─────────┼────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ nu-engine │ path │ ./crates/nu-engine │",
|
||||
"│ │ ├─────────┼────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ version │ 0.74.1 │",
|
||||
"│ ├───────────────┼─────────┴────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ rayon │ 1.6.1 │",
|
||||
"│ ├───────────────┼──────────┬───────────────────────────────────────────────────────────────┤",
|
||||
"│ │ reedline │ features │ bashisms │",
|
||||
"│ │ │ ├───────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ │ sqlite │",
|
||||
"│ │ ├──────────┼───────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ version │ 0.14.0 │",
|
||||
"│ ├───────────────┼──────────┴───────────────────────────────────────────────────────────────┤",
|
||||
"│ │ simplelog │ 0.12.0 │",
|
||||
"│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ time │ 0.3.12 │",
|
||||
"├──────────────────┼───────────────┴───┬──────────────────────────────────────────────────────────────────────┤",
|
||||
"│ dev-dependencies │ assert_cmd │ 2.0.2 │",
|
||||
"│ ├───────────────────┼──────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ criterion │ 0.4 │",
|
||||
"│ ├───────────────────┼──────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ hamcrest2 │ 0.3.0 │",
|
||||
"│ ├───────────────────┼──────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ itertools │ 0.10.3 │",
|
||||
"│ ├───────────────────┼─────────┬────────────────────────────────────────────────────────────┤",
|
||||
"│ │ nu-test-support │ path │ ./crates/nu-test-support │",
|
||||
"│ │ ├─────────┼────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ version │ 0.74.1 │",
|
||||
"│ ├───────────────────┼─────────┴────────────────────────────────────────────────────────────┤",
|
||||
"│ │ pretty_assertions │ 1.0.0 │",
|
||||
"│ ├───────────────────┼──────────────────┬───────────────────────────────────────────────────┤",
|
||||
"│ │ rstest │ default-features │ false │",
|
||||
"│ │ ├──────────────────┼───────────────────────────────────────────────────┤",
|
||||
"│ │ │ version │ 0.15.0 │",
|
||||
"│ ├───────────────────┼──────────────────┴───────────────────────────────────────────────────┤",
|
||||
"│ │ serial_test │ 0.10.0 │",
|
||||
"│ ├───────────────────┼──────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ tempfile │ 3.2.0 │",
|
||||
"├──────────────────┼───────────────────┴─┬────────────────────────────────────────────────────────────────────┤",
|
||||
"│ features │ default │ plugin │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ which-support │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ trash-support │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ sqlite │",
|
||||
"│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ extra │ default │",
|
||||
"│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ plugin │ nu-plugin │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ nu-cli/plugin │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ nu-parser/plugin │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ nu-command/plugin │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ nu-protocol/plugin │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ nu-engine/plugin │",
|
||||
"│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ stable │ default │",
|
||||
"│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ static-link-openssl │ dep:openssl │",
|
||||
"│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ trash-support │ nu-command/trash-support │",
|
||||
"│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ wasi │ │",
|
||||
"│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ which-support │ nu-command/which-support │",
|
||||
"├──────────────────┼───────────────┬─────┴────────────────────────────────────────────────────────────────────┤",
|
||||
"│ package │ authors │ The Nushell Project Developers │",
|
||||
"│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ default-run │ nu │",
|
||||
"│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ description │ A new type of shell │",
|
||||
"│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ documentation │ https://www.nushell.sh/book/ │",
|
||||
"│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ edition │ 2021 │",
|
||||
"│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ exclude │ images │",
|
||||
"│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ homepage │ https://www.nushell.sh │",
|
||||
"│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ license │ MIT │",
|
||||
"│ ├───────────────┼──────────┬───────────┬────────────────────────┬─────────┬────────────────┤",
|
||||
"│ │ metadata │ binstall │ overrides │ x86_64-pc-windows-msvc │ pkg-fmt │ zip │",
|
||||
"│ │ │ ├───────────┼────────────────────────┴─────────┴────────────────┤",
|
||||
"│ │ │ │ pkg-fmt │ tgz │",
|
||||
"│ │ │ ├───────────┼───────────────────────────────────────────────────┤",
|
||||
"│ │ │ │ pkg-url │ { repo }/releases/download/{ v │",
|
||||
"│ │ │ │ │ ersion }/{ name }-{ version }- │",
|
||||
"│ │ │ │ │ { target }.{ archive-format } │",
|
||||
"│ ├───────────────┼──────────┴───────────┴───────────────────────────────────────────────────┤",
|
||||
"│ │ name │ nu │",
|
||||
"│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ repository │ https://github.com/nushell/nushell │",
|
||||
"│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ rust-version │ 1.60 │",
|
||||
"│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ version │ 0.74.1 │",
|
||||
"├──────────────────┼───────────┬───┴──────┬────────┬──────────────────────────────────────────────────────────┤",
|
||||
"│ patch │ crates-io │ reedline │ branch │ main │",
|
||||
"│ │ │ ├────────┼──────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ │ git │ https://github.com/nushell/reedline.git │",
|
||||
"├──────────────────┼───────────┴──────────┴────────┴─┬──────────────┬─────────────┬──────────┬────────────────┤",
|
||||
"│ target │ cfg(not(target_os = \"windows\")) │ dependencies │ openssl │ features │ vendored │",
|
||||
"│ │ │ │ ├──────────┼────────────────┤",
|
||||
"│ │ │ │ │ optional │ true │",
|
||||
"│ │ │ │ ├──────────┼────────────────┤",
|
||||
"│ │ │ │ │ version │ 0.10.38 │",
|
||||
"│ │ │ ├─────────────┼──────────┴───────┬────────┤",
|
||||
"│ │ │ │ signal-hook │ default-features │ false │",
|
||||
"│ │ │ │ ├──────────────────┼────────┤",
|
||||
"│ │ │ │ │ version │ 0.3.14 │",
|
||||
"│ ├─────────────────────────────────┼──────────────┼──────┬──────┴──────────────────┴────────┤",
|
||||
"│ │ cfg(target_family = \"unix\") │ dependencies │ atty │ 0.2 │",
|
||||
"│ │ │ ├──────┼──────────────────┬───────────────┤",
|
||||
"│ │ │ │ nix │ default-features │ false │",
|
||||
"│ │ │ │ ├──────────────────┼───────────────┤",
|
||||
"│ │ │ │ │ features │ signal │",
|
||||
"│ │ │ │ │ ├───────────────┤",
|
||||
"│ │ │ │ │ │ process │",
|
||||
"│ │ │ │ │ ├───────────────┤",
|
||||
"│ │ │ │ │ │ fs │",
|
||||
"│ │ │ │ │ ├───────────────┤",
|
||||
"│ │ │ │ │ │ term │",
|
||||
"│ │ │ │ ├──────────────────┼───────────────┤",
|
||||
"│ │ │ │ │ version │ 0.25 │",
|
||||
"│ ├─────────────────────────────────┼──────────────┴─────┬┴───────┬──────────┴───────────────┤",
|
||||
"│ │ cfg(windows) │ build-dependencies │ winres │ 0.1 │",
|
||||
"├──────────────────┼─────────┬───────────────────────┴────────────────────┴────────┴──────────────────────────┤",
|
||||
"│ workspace │ members │ crates/nu-cli │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu-engine │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu-parser │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu-system │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu-command │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu-protocol │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu-plugin │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu_plugin_inc │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu_plugin_gstat │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu_plugin_example │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu_plugin_query │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu_plugin_custom_values │",
|
||||
"│ │ ├────────────────────────────────────────────────────────────────────────────────┤",
|
||||
"│ │ │ crates/nu-utils │",
|
||||
"╰──────────────────┴─────────┴────────────────────────────────────────────────────────────────────────────────╯",
|
||||
]);
|
||||
|
||||
assert_eq!(actual.out, expected);
|
||||
})
|
||||
}
|
||||
|
||||
fn join_lines(lines: impl IntoIterator<Item = impl AsRef<str>>) -> String {
|
||||
lines
|
||||
.into_iter()
|
||||
|
@ -1,37 +1,22 @@
|
||||
use nu_test_support::{nu, pipeline};
|
||||
use nu_test_support::nu;
|
||||
|
||||
#[test]
|
||||
fn row() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
[[key value]; [foo 1] [foo 2]] | transpose -r | debug
|
||||
"#
|
||||
));
|
||||
let actual = nu!("[[key value]; [foo 1] [foo 2]] | transpose -r | debug");
|
||||
|
||||
assert!(actual.out.contains("foo: 1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row_but_last() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
[[key value]; [foo 1] [foo 2]] | transpose -r -l | debug
|
||||
"#
|
||||
));
|
||||
let actual = nu!("[[key value]; [foo 1] [foo 2]] | transpose -r -l | debug");
|
||||
|
||||
assert!(actual.out.contains("foo: 2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row_but_all() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
[[key value]; [foo 1] [foo 2]] | transpose -r -a | debug
|
||||
"#
|
||||
));
|
||||
let actual = nu!("[[key value]; [foo 1] [foo 2]] | transpose -r -a | debug");
|
||||
|
||||
assert!(actual.out.contains("foo: [1, 2]"));
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ fn removes_duplicate_rows() {
|
||||
open los_tres_caballeros.csv
|
||||
| uniq
|
||||
| length
|
||||
|
||||
"#
|
||||
));
|
||||
|
||||
@ -53,7 +52,6 @@ fn uniq_values() {
|
||||
| select type
|
||||
| uniq
|
||||
| length
|
||||
|
||||
"#
|
||||
));
|
||||
|
||||
@ -125,7 +123,6 @@ fn nested_json_structures() {
|
||||
open nested_json_structures.json
|
||||
| uniq
|
||||
| length
|
||||
|
||||
"#
|
||||
));
|
||||
assert_eq!(actual.out, "3");
|
||||
@ -134,13 +131,11 @@ fn nested_json_structures() {
|
||||
|
||||
#[test]
|
||||
fn uniq_when_keys_out_of_order() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
[{"a": "a", "b": [1,2,3]}, {"b": [1,2,3], "a": "a"}]
|
||||
| uniq
|
||||
| length
|
||||
|
||||
"#
|
||||
));
|
||||
|
||||
@ -149,8 +144,7 @@ fn uniq_when_keys_out_of_order() {
|
||||
|
||||
#[test]
|
||||
fn uniq_counting() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
["A", "B", "A"]
|
||||
| wrap item
|
||||
@ -163,10 +157,9 @@ fn uniq_counting() {
|
||||
));
|
||||
assert_eq!(actual.out, "2");
|
||||
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
echo ["A", "B", "A"]
|
||||
["A", "B", "A"]
|
||||
| wrap item
|
||||
| uniq --count
|
||||
| flatten
|
||||
@ -180,89 +173,41 @@ fn uniq_counting() {
|
||||
|
||||
#[test]
|
||||
fn uniq_unique() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo [1 2 3 4 1 5]
|
||||
| uniq --unique
|
||||
"#
|
||||
));
|
||||
let expected = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo [2 3 4 5]
|
||||
"#
|
||||
));
|
||||
print!("{}", actual.out);
|
||||
print!("{}", expected.out);
|
||||
let actual = nu!("[1 2 3 4 1 5] | uniq --unique");
|
||||
let expected = nu!("[2 3 4 5]");
|
||||
assert_eq!(actual.out, expected.out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uniq_simple_vals_ints() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo [1 2 3 4 1 5]
|
||||
| uniq
|
||||
"#
|
||||
));
|
||||
let expected = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo [1 2 3 4 5]
|
||||
"#
|
||||
));
|
||||
print!("{}", actual.out);
|
||||
print!("{}", expected.out);
|
||||
let actual = nu!("[1 2 3 4 1 5] | uniq");
|
||||
let expected = nu!("[1 2 3 4 5]");
|
||||
assert_eq!(actual.out, expected.out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uniq_simple_vals_strs() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo [A B C A]
|
||||
| uniq
|
||||
"#
|
||||
));
|
||||
let expected = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo [A B C]
|
||||
"#
|
||||
));
|
||||
print!("{}", actual.out);
|
||||
print!("{}", expected.out);
|
||||
let actual = nu!("[A B C A] | uniq");
|
||||
let expected = nu!("[A B C]");
|
||||
assert_eq!(actual.out, expected.out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
[[fruit day]; [apple monday] [apple friday] [Apple friday] [apple monday] [pear monday] [orange tuesday]]
|
||||
| uniq
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo [[fruit day]; [apple monday] [apple friday] [Apple friday] [pear monday] [orange tuesday]]
|
||||
"#
|
||||
));
|
||||
print!("{}", actual.out);
|
||||
print!("{}", expected.out);
|
||||
let expected = nu!("[[fruit day]; [apple monday] [apple friday] [Apple friday] [pear monday] [orange tuesday]]");
|
||||
assert_eq!(actual.out, expected.out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_with_ignore_case() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
[[origin, people];
|
||||
[World, (
|
||||
@ -284,8 +229,7 @@ fn table_with_ignore_case() {
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
let expected = nu!(pipeline(
|
||||
r#"
|
||||
echo [[origin, people];
|
||||
[World, (
|
||||
@ -302,8 +246,5 @@ fn table_with_ignore_case() {
|
||||
"#
|
||||
));
|
||||
|
||||
print!("{}", actual.out);
|
||||
print!("{}", expected.out);
|
||||
assert_eq!(actual.out, expected.out);
|
||||
assert_eq!(actual.out, expected.out);
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ fn removes_duplicate_rows() {
|
||||
open los_tres_caballeros.csv
|
||||
| uniq-by last_name
|
||||
| length
|
||||
|
||||
"#
|
||||
));
|
||||
|
||||
@ -32,30 +31,15 @@ fn removes_duplicate_rows() {
|
||||
|
||||
#[test]
|
||||
fn uniq_when_keys_out_of_order() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
[{"a": "a", "b": [1,2,3]}, {"b": [1,2,3,4], "a": "a"}]
|
||||
| uniq-by a
|
||||
"#
|
||||
));
|
||||
let expected = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo [{"a": "a", "b": [1,2,3]}]
|
||||
"#
|
||||
));
|
||||
let actual = nu!(r#"[{"a": "a", "b": [1,2,3]}, {"b": [1,2,3,4], "a": "a"}] | uniq-by a"#);
|
||||
let expected = nu!(r#"[{"a": "a", "b": [1,2,3]}]"#);
|
||||
|
||||
print!("{}", actual.out);
|
||||
print!("{}", expected.out);
|
||||
assert_eq!(actual.out, expected.out);
|
||||
assert_eq!(actual.out, expected.out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uniq_counting() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
["A", "B", "A"]
|
||||
| wrap item
|
||||
@ -68,10 +52,9 @@ fn uniq_counting() {
|
||||
));
|
||||
assert_eq!(actual.out, "2");
|
||||
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
echo ["A", "B", "A"]
|
||||
["A", "B", "A"]
|
||||
| wrap item
|
||||
| uniq-by item --count
|
||||
| flatten
|
||||
@ -85,8 +68,7 @@ fn uniq_counting() {
|
||||
|
||||
#[test]
|
||||
fn uniq_unique() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
echo [1 2 3 4 1 5]
|
||||
| wrap item
|
||||
@ -94,29 +76,20 @@ fn uniq_unique() {
|
||||
| get item
|
||||
"#
|
||||
));
|
||||
let expected = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo [2 3 4 5]
|
||||
"#
|
||||
));
|
||||
print!("{}", actual.out);
|
||||
print!("{}", expected.out);
|
||||
let expected = nu!("[2 3 4 5]");
|
||||
assert_eq!(actual.out, expected.out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
[[fruit day]; [apple monday] [apple friday] [Apple friday] [apple monday] [pear monday] [orange tuesday]]
|
||||
| uniq-by fruit
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
let expected = nu!(pipeline(
|
||||
r#"
|
||||
echo [[fruit day]; [apple monday] [Apple friday] [pear monday] [orange tuesday]]
|
||||
"#
|
||||
@ -135,29 +108,24 @@ fn uniq_by_empty() {
|
||||
|
||||
#[test]
|
||||
fn uniq_by_multiple_columns() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
[[fruit day]; [apple monday] [apple friday] [Apple friday] [apple monday] [pear monday] [orange tuesday]]
|
||||
| uniq-by fruit day
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
let expected = nu!(pipeline(
|
||||
r#"
|
||||
echo [[fruit day]; [apple monday] [apple friday] [Apple friday] [pear monday] [orange tuesday]]
|
||||
"#
|
||||
));
|
||||
print!("{}", actual.out);
|
||||
print!("{}", expected.out);
|
||||
assert_eq!(actual.out, expected.out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_with_ignore_case() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
[[origin, people];
|
||||
[World, (
|
||||
@ -179,8 +147,7 @@ fn table_with_ignore_case() {
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
let expected = nu!(pipeline(
|
||||
r#"
|
||||
echo [[origin, people];
|
||||
[World, (
|
||||
@ -197,33 +164,19 @@ fn table_with_ignore_case() {
|
||||
"#
|
||||
));
|
||||
|
||||
print!("{}", actual.out);
|
||||
print!("{}", expected.out);
|
||||
assert_eq!(actual.out, expected.out);
|
||||
assert_eq!(actual.out, expected.out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_parameter() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
[11 22 33] | uniq-by
|
||||
"#
|
||||
));
|
||||
let actual = nu!("[11 22 33] | uniq-by");
|
||||
|
||||
assert!(actual.err.contains("missing parameter"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_column() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
[[fruit day]; [apple monday] [apple friday]]
|
||||
| uniq-by column1
|
||||
"#
|
||||
));
|
||||
let actual = nu!("[[fruit day]; [apple monday] [apple friday]] | uniq-by column1");
|
||||
|
||||
assert!(actual.err.contains("cannot find column 'column1'"));
|
||||
}
|
||||
|
@ -16,9 +16,7 @@ fn sets_the_column() {
|
||||
|
||||
#[test]
|
||||
fn doesnt_convert_record_to_table() {
|
||||
let actual = nu!(
|
||||
cwd: ".", r#"{a:1} | update a 2 | to nuon"#
|
||||
);
|
||||
let actual = nu!("{a:1} | update a 2 | to nuon");
|
||||
|
||||
assert_eq!(actual.out, "{a: 2}");
|
||||
}
|
||||
@ -83,44 +81,27 @@ fn upsert_column_missing() {
|
||||
|
||||
#[test]
|
||||
fn update_list() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
[1, 2, 3] | update 1 abc | to json -r
|
||||
"#
|
||||
));
|
||||
|
||||
let actual = nu!("[1, 2, 3] | update 1 abc | to json -r");
|
||||
assert_eq!(actual.out, r#"[1,"abc",3]"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_past_end_list() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
[1, 2, 3] | update 5 abc | to json -r
|
||||
"#
|
||||
));
|
||||
|
||||
let actual = nu!("[1, 2, 3] | update 5 abc | to json -r");
|
||||
assert!(actual.err.contains("too large"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_nonexistent_column() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"{a:1} | update b 2"#
|
||||
));
|
||||
|
||||
let actual = nu!("{a:1} | update b 2");
|
||||
assert!(actual.err.contains("cannot find column 'b'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_uses_enumerate_index() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"[[a]; [7] [6]] | enumerate | update item.a {|el| $el.index + 1 + $el.item.a } | flatten | to nuon"#
|
||||
));
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "[[index, a]; [0, 8], [1, 8]]");
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user