mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 15:11:52 +02:00
Compare commits
57 Commits
Author | SHA1 | Date | |
---|---|---|---|
9ef65dcd69 | |||
f99c002426 | |||
f0420c5a6c | |||
46eec5e3a2 | |||
378248341e | |||
803f9d4daf | |||
ce809881eb | |||
1a99893e2d | |||
ec8e57cde9 | |||
a498234f1d | |||
de77cb0cc4 | |||
7d5d53cf85 | |||
1572808adb | |||
9d77e3fc7c | |||
e22f2e9f13 | |||
4ffa4ac42a | |||
da6f548dfd | |||
7532991544 | |||
b7f47317c2 | |||
5849e4f6e3 | |||
868d94f573 | |||
1344ae3a65 | |||
804b155035 | |||
9446e3960b | |||
90ba39184a | |||
d40a73aafe | |||
5815f122ed | |||
0bbb3a20df | |||
1998bce19f | |||
2f1711f783 | |||
34c8b276ab | |||
fde56cfe99 | |||
118033e4a5 | |||
7910d20e50 | |||
e1d5180e6d | |||
79ce13abef | |||
5921c19bc0 | |||
e629ef203a | |||
5959d1366a | |||
530ff3893e | |||
6f59167960 | |||
ca715bb929 | |||
4af0a6a3fa | |||
6486364610 | |||
6aa8a0073b | |||
f5e1b08e6a | |||
7b9ad9d2e5 | |||
1a3762b905 | |||
32fbcf39cc | |||
dd578926c3 | |||
5c99921e15 | |||
d2e4f03d19 | |||
23bba9935f | |||
8a5abc7afc | |||
ec711cb79d | |||
f2ad7fae1f | |||
13a4474512 |
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
@ -15,3 +15,7 @@ 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 --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
|
||||
- [ ] `cargo test --workspace --features=extra` to check that all the tests pass
|
||||
|
||||
# Documentation
|
||||
|
||||
- [ ] If your PR touches a user-facing nushell feature then make sure that there is an entry in the documentation (https://github.com/nushell/nushell.github.io) for the feature, and update it if necessary.
|
||||
|
4
.github/workflows/release-pkg.nu
vendored
4
.github/workflows/release-pkg.nu
vendored
@ -76,9 +76,9 @@ cp -v README.release.txt $'($dist)/README.txt'
|
||||
|
||||
$'(char nl)Check binary release version detail:'; hr-line
|
||||
let ver = if $os == 'windows-latest' {
|
||||
(do -i { ./output/nu.exe -c 'version' }) | str collect
|
||||
(do -i { ./output/nu.exe -c 'version' }) | str join
|
||||
} else {
|
||||
(do -i { ./output/nu -c 'version' }) | str collect
|
||||
(do -i { ./output/nu -c 'version' }) | str join
|
||||
}
|
||||
if ($ver | str trim | is-empty) {
|
||||
$'(ansi r)Incompatible nu binary...(ansi reset)'
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -72,7 +72,7 @@ jobs:
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v2.1
|
||||
with:
|
||||
version: 0.68.0
|
||||
version: 0.69.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
796
Cargo.lock
generated
796
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
50
Cargo.toml
50
Cargo.toml
@ -8,10 +8,9 @@ exclude = ["images"]
|
||||
homepage = "https://www.nushell.sh"
|
||||
license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.60"
|
||||
version = "0.69.0"
|
||||
version = "0.70.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -39,21 +38,21 @@ ctrlc = "3.2.1"
|
||||
log = "0.4"
|
||||
miette = "5.1.0"
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-cli = { path="./crates/nu-cli", version = "0.69.0" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.69.0" }
|
||||
nu-command = { path="./crates/nu-command", version = "0.69.0" }
|
||||
nu-engine = { path="./crates/nu-engine", version = "0.69.0" }
|
||||
nu-json = { path="./crates/nu-json", version = "0.69.0" }
|
||||
nu-parser = { path="./crates/nu-parser", version = "0.69.0" }
|
||||
nu-path = { path="./crates/nu-path", version = "0.69.0" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.69.0" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.69.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.69.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.69.0" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.69.0" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.69.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.69.0" }
|
||||
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
|
||||
nu-cli = { path="./crates/nu-cli", version = "0.70.0" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.70.0" }
|
||||
nu-command = { path="./crates/nu-command", version = "0.70.0" }
|
||||
nu-engine = { path="./crates/nu-engine", version = "0.70.0" }
|
||||
nu-json = { path="./crates/nu-json", version = "0.70.0" }
|
||||
nu-parser = { path="./crates/nu-parser", version = "0.70.0" }
|
||||
nu-path = { path="./crates/nu-path", version = "0.70.0" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.70.0" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.70.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.70.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.70.0" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.70.0" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.70.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.70.0" }
|
||||
reedline = { version = "0.13.0", features = ["bashisms", "sqlite"]}
|
||||
|
||||
rayon = "1.5.1"
|
||||
is_executable = "1.0.1"
|
||||
@ -65,8 +64,16 @@ time = "0.3.12"
|
||||
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 = "0.24"
|
||||
atty = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="./crates/nu-test-support", version = "0.69.0" }
|
||||
nu-test-support = { path="./crates/nu-test-support", version = "0.70.0" }
|
||||
tempfile = "3.2.0"
|
||||
assert_cmd = "2.0.2"
|
||||
pretty_assertions = "1.0.0"
|
||||
@ -75,9 +82,6 @@ hamcrest2 = "0.3.0"
|
||||
rstest = {version = "0.15.0", default-features = false}
|
||||
itertools = "0.10.3"
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
winres = "0.1"
|
||||
|
||||
[features]
|
||||
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
||||
extra = ["default", "dataframe", "database"]
|
||||
@ -123,5 +127,7 @@ debug = false
|
||||
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", branch = "main" }
|
||||
# reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" }
|
||||
|
@ -71,7 +71,7 @@ Additionally, commands can output structured data (you can think of this as a th
|
||||
Commands that work in the pipeline fit into one of three categories:
|
||||
|
||||
- Commands that produce a stream (e.g., `ls`)
|
||||
- Commands that filter a stream (eg, `where type == "dir"`)
|
||||
- Commands that filter a stream (e.g., `where type == "dir"`)
|
||||
- Commands that consume the output of the pipeline (e.g., `table`)
|
||||
|
||||
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
||||
|
@ -5,22 +5,22 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.69.0"
|
||||
version = "0.70.0"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="../nu-test-support", version = "0.69.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.69.0" }
|
||||
nu-test-support = { path="../nu-test-support", version = "0.70.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.70.0" }
|
||||
rstest = {version = "0.15.0", default-features = false}
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.69.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.69.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.69.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.69.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.70.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.70.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.70.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.70.0" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.69.0" }
|
||||
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.70.0" }
|
||||
reedline = { version = "0.13.0", features = ["bashisms", "sqlite"]}
|
||||
|
||||
atty = "0.2.14"
|
||||
chrono = "0.4.21"
|
||||
|
@ -60,10 +60,10 @@ impl NuCompleter {
|
||||
fn external_completion(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
spans: Vec<String>,
|
||||
spans: &[String],
|
||||
offset: usize,
|
||||
span: Span,
|
||||
) -> Vec<Suggestion> {
|
||||
) -> Option<Vec<Suggestion>> {
|
||||
let stack = self.stack.clone();
|
||||
let block = self.engine_state.get_block(block_id);
|
||||
let mut callee_stack = stack.gather_captures(&block.captures);
|
||||
@ -75,9 +75,9 @@ impl NuCompleter {
|
||||
var_id,
|
||||
Value::List {
|
||||
vals: spans
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|it| Value::String {
|
||||
val: it,
|
||||
val: it.to_string(),
|
||||
span: Span::unknown(),
|
||||
})
|
||||
.collect(),
|
||||
@ -109,13 +109,13 @@ impl NuCompleter {
|
||||
offset,
|
||||
);
|
||||
|
||||
return result;
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
Err(err) => println!("failed to eval completer block: {}", err),
|
||||
}
|
||||
|
||||
vec![]
|
||||
None
|
||||
}
|
||||
|
||||
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
@ -123,7 +123,8 @@ impl NuCompleter {
|
||||
let offset = working_set.next_span_start();
|
||||
let (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set);
|
||||
let initial_line = line.to_string();
|
||||
new_line.push(b'a');
|
||||
let alias_total_offset: usize = alias_offset.iter().sum();
|
||||
new_line.insert(alias_total_offset + pos, b'a');
|
||||
let pos = offset + pos;
|
||||
let config = self.engine_state.get_config();
|
||||
|
||||
@ -169,9 +170,10 @@ impl NuCompleter {
|
||||
}
|
||||
};
|
||||
|
||||
// Parses the prefix
|
||||
// Parses the prefix. Completion should look up to the cursor position, not after.
|
||||
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||
prefix.remove(pos - (flat.0.start - span_offset));
|
||||
let index = pos - (flat.0.start - span_offset);
|
||||
prefix.drain(index..);
|
||||
|
||||
// Variables completion
|
||||
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
||||
@ -211,7 +213,11 @@ impl NuCompleter {
|
||||
// We got no results for internal completion
|
||||
// now we can check if external completer is set and use it
|
||||
if let Some(block_id) = config.external_completer {
|
||||
return self.external_completion(block_id, spans, offset, new_span);
|
||||
if let Some(external_result) =
|
||||
self.external_completion(block_id, &spans, offset, new_span)
|
||||
{
|
||||
return external_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -338,6 +344,15 @@ impl NuCompleter {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Try to complete using an external completer (if set)
|
||||
if let Some(block_id) = config.external_completer {
|
||||
if let Some(external_result) =
|
||||
self.external_completion(block_id, &spans, offset, new_span)
|
||||
{
|
||||
return external_result;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for file completion
|
||||
let mut completer = FileCompletion::new(self.engine_state.clone());
|
||||
out = self.process_completion(
|
||||
@ -352,12 +367,6 @@ impl NuCompleter {
|
||||
if !out.is_empty() {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Try to complete using an exnternal compelter (if set)
|
||||
if let Some(block_id) = config.external_completer {
|
||||
return self
|
||||
.external_completion(block_id, spans, offset, new_span);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -822,6 +822,8 @@ fn event_from_record(
|
||||
"ctrld" => ReedlineEvent::CtrlD,
|
||||
"ctrlc" => ReedlineEvent::CtrlC,
|
||||
"enter" => ReedlineEvent::Enter,
|
||||
"submit" => ReedlineEvent::Submit,
|
||||
"submitornewline" => ReedlineEvent::SubmitOrNewline,
|
||||
"esc" | "escape" => ReedlineEvent::Esc,
|
||||
"up" => ReedlineEvent::Up,
|
||||
"down" => ReedlineEvent::Down,
|
||||
|
@ -11,7 +11,7 @@ use log::{info, trace, warn};
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use nu_color_config::get_color_config;
|
||||
use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::{lex, parse};
|
||||
use nu_parser::{lex, parse, trim_quotes_str};
|
||||
use nu_protocol::{
|
||||
ast::PathMember,
|
||||
engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
|
||||
@ -380,9 +380,13 @@ pub fn evaluate_repl(
|
||||
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
||||
// Check if this is a single call to a directory, if so auto-cd
|
||||
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
|
||||
let path = nu_path::expand_path_with(&s, &cwd);
|
||||
|
||||
let orig = s.clone();
|
||||
let mut orig = s.clone();
|
||||
if orig.starts_with('`') {
|
||||
orig = trim_quotes_str(&orig).to_string()
|
||||
}
|
||||
|
||||
let path = nu_path::expand_path_with(&orig, &cwd);
|
||||
|
||||
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
|
||||
// We have an auto-cd
|
||||
@ -452,7 +456,7 @@ pub fn evaluate_repl(
|
||||
span,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
} else if !s.trim().is_empty() {
|
||||
trace!("eval source: {}", s);
|
||||
|
||||
eval_source(
|
||||
@ -578,7 +582,7 @@ fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String {
|
||||
engine_state,
|
||||
stack,
|
||||
None,
|
||||
"(date now) - ('05/10/2019' | into datetime)",
|
||||
"(date now) - ('2019-05-10 09:59:12-0700' | into datetime)",
|
||||
) {
|
||||
Ok(Value::Duration { val, .. }) => format_duration(val),
|
||||
_ => "".to_string(),
|
||||
@ -595,13 +599,13 @@ Our {}GitHub{} repository is at {}https://github.com/nushell/nushell{}
|
||||
Our {}Documentation{} is located at {}http://nushell.sh{}
|
||||
{}Tweet{} us at {}@nu_shell{}
|
||||
|
||||
{}Nushell{} has been around for:
|
||||
It's been this long since {}Nushell{}'s first commit:
|
||||
{}
|
||||
|
||||
{}You can disable this banner using the {}config nu{}{} command
|
||||
to modify the config.nu file and setting show_banner to false.
|
||||
|
||||
let-env config {{
|
||||
let-env config = {{
|
||||
show_banner: false
|
||||
...
|
||||
}}{}
|
||||
|
@ -231,23 +231,18 @@ pub fn eval_source(
|
||||
}
|
||||
|
||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
Ok(mut pipeline_data) => {
|
||||
if let PipelineData::ExternalStream { exit_code, .. } = &mut pipeline_data {
|
||||
if let Some(exit_code) = exit_code.take().and_then(|it| it.last()) {
|
||||
stack.add_env_var("LAST_EXIT_CODE".to_string(), exit_code);
|
||||
} else {
|
||||
set_last_exit_code(stack, 0);
|
||||
Ok(pipeline_data) => {
|
||||
match pipeline_data.print(engine_state, stack, false, false) {
|
||||
Err(err) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &err);
|
||||
|
||||
return false;
|
||||
}
|
||||
Ok(exit_code) => {
|
||||
set_last_exit_code(stack, exit_code);
|
||||
}
|
||||
} else {
|
||||
set_last_exit_code(stack, 0);
|
||||
}
|
||||
|
||||
if let Err(err) = pipeline_data.print(engine_state, stack, false, false) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &err);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||
|
@ -63,7 +63,7 @@ fn variables_single_dash_argument_with_flagcompletion(mut completer: NuCompleter
|
||||
|
||||
#[rstest]
|
||||
fn variables_command_with_commandcompletion(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command ", 9);
|
||||
let suggestions = completer_strings.complete("my-c ", 4);
|
||||
let expected: Vec<String> = vec!["my-command".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
@ -672,3 +672,63 @@ fn unknown_command_completion() {
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn flagcompletion_triggers_after_cursor(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst -h", 5);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn customcompletion_triggers_after_cursor(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command c", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn customcompletion_triggers_after_cursor_piped(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command c | ls", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn flagcompletion_triggers_after_cursor_piped(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst -h | ls", 5);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filecompletions_triggers_after_cursor() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let suggestions = completer.complete("cp test_c", 3);
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
}
|
||||
|
@ -5,11 +5,11 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-color-config"
|
||||
version = "0.69.0"
|
||||
version = "0.70.0"
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-json = { path = "../nu-json", version = "0.69.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.69.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.70.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.70.0" }
|
||||
serde = { version="1.0.123", features=["derive"] }
|
||||
|
@ -5,27 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-command"
|
||||
version = "0.69.0"
|
||||
version = "0.70.0"
|
||||
build = "build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.69.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.69.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.69.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.69.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.69.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.69.0" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.69.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
|
||||
nu-system = { path = "../nu-system", version = "0.69.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.69.0" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.69.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.69.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.69.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.70.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.70.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.70.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.70.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.70.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.70.0" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.70.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
|
||||
nu-system = { path = "../nu-system", version = "0.70.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.70.0" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.70.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.70.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.70.0" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
num-format = { version = "0.4.0" }
|
||||
num-format = { version = "0.4.3" }
|
||||
|
||||
# Potential dependencies for extras
|
||||
alphanumeric-sort = "1.4.4"
|
||||
@ -88,11 +88,14 @@ unicode-segmentation = "1.8.0"
|
||||
url = "2.2.1"
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
which = { version = "4.3.0", optional = true }
|
||||
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
|
||||
reedline = { version = "0.13.0", features = ["bashisms", "sqlite"]}
|
||||
wax = { version = "0.5.0", features = ["diagnostics"] }
|
||||
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
||||
sqlparser = { version = "0.23.0", features = ["serde"], optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winreg = "0.10.1"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
umask = "2.0.0"
|
||||
users = "0.11.0"
|
||||
|
@ -114,14 +114,21 @@ impl Command for SubCommand {
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Convert string to a named duration",
|
||||
description: "Convert string to the requested duration as a string",
|
||||
example: "'7min' | into duration --convert sec",
|
||||
result: Some(Value::String {
|
||||
val: "420 sec".to_string(),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
|
||||
Example {
|
||||
description: "Convert duration to the requested duration as a string",
|
||||
example: "420sec | into duration --convert min",
|
||||
result: Some(Value::String {
|
||||
val: "7 min".to_string(),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -135,18 +142,20 @@ fn into_duration(
|
||||
let head = 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();
|
||||
let float_precision = config.float_precision as usize;
|
||||
|
||||
input.map(
|
||||
move |v| {
|
||||
if column_paths.is_empty() {
|
||||
action(&v, &convert_to_unit, head)
|
||||
action(&v, &convert_to_unit, float_precision, head)
|
||||
} 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, head)),
|
||||
Box::new(move |old| action(old, &d, float_precision, head)),
|
||||
);
|
||||
if let Err(error) = r {
|
||||
return Value::Error { error };
|
||||
@ -166,127 +175,129 @@ fn convert_str_from_unit_to_unit(
|
||||
to_unit: &str,
|
||||
span: Span,
|
||||
value_span: Span,
|
||||
) -> Result<i64, ShellError> {
|
||||
) -> Result<f64, ShellError> {
|
||||
match (from_unit, to_unit) {
|
||||
("ns", "ns") => Ok(val),
|
||||
("ns", "us") => Ok(val / 1000),
|
||||
("ns", "ms") => Ok(val / 1000 / 1000),
|
||||
("ns", "sec") => Ok(val / 1000 / 1000 / 1000),
|
||||
("ns", "min") => Ok(val / 1000 / 1000 / 1000 / 60),
|
||||
("ns", "hr") => Ok(val / 1000 / 1000 / 1000 / 60 / 60),
|
||||
("ns", "day") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24),
|
||||
("ns", "wk") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 7),
|
||||
("ns", "month") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 30),
|
||||
("ns", "yr") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 365),
|
||||
("ns", "dec") => Ok(val / 10 / 1000 / 1000 / 1000 / 60 / 60 / 24 / 365),
|
||||
("ns", "ns") => Ok(val as f64),
|
||||
("ns", "us") => Ok(val as f64 / 1000.0),
|
||||
("ns", "ms") => Ok(val as f64 / 1000.0 / 1000.0),
|
||||
("ns", "sec") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0),
|
||||
("ns", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0),
|
||||
("ns", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0),
|
||||
("ns", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||
("ns", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||
("ns", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||
("ns", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
("ns", "dec") => {
|
||||
Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0)
|
||||
}
|
||||
|
||||
("us", "ns") => Ok(val * 1000),
|
||||
("us", "us") => Ok(val),
|
||||
("us", "ms") => Ok(val / 1000),
|
||||
("us", "sec") => Ok(val / 1000 / 1000),
|
||||
("us", "min") => Ok(val / 1000 / 1000 / 60),
|
||||
("us", "hr") => Ok(val / 1000 / 1000 / 60 / 60),
|
||||
("us", "day") => Ok(val / 1000 / 1000 / 60 / 60 / 24),
|
||||
("us", "wk") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 7),
|
||||
("us", "month") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 30),
|
||||
("us", "yr") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 365),
|
||||
("us", "dec") => Ok(val / 10 / 1000 / 1000 / 60 / 60 / 24 / 365),
|
||||
("us", "ns") => Ok(val as f64 * 1000.0),
|
||||
("us", "us") => Ok(val as f64),
|
||||
("us", "ms") => Ok(val as f64 / 1000.0),
|
||||
("us", "sec") => Ok(val as f64 / 1000.0 / 1000.0),
|
||||
("us", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0),
|
||||
("us", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0),
|
||||
("us", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||
("us", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||
("us", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||
("us", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
("us", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
|
||||
("ms", "ns") => Ok(val * 1000 * 1000),
|
||||
("ms", "us") => Ok(val * 1000),
|
||||
("ms", "ms") => Ok(val),
|
||||
("ms", "sec") => Ok(val / 1000),
|
||||
("ms", "min") => Ok(val / 1000 / 60),
|
||||
("ms", "hr") => Ok(val / 1000 / 60 / 60),
|
||||
("ms", "day") => Ok(val / 1000 / 60 / 60 / 24),
|
||||
("ms", "wk") => Ok(val / 1000 / 60 / 60 / 24 / 7),
|
||||
("ms", "month") => Ok(val / 1000 / 60 / 60 / 24 / 30),
|
||||
("ms", "yr") => Ok(val / 1000 / 60 / 60 / 24 / 365),
|
||||
("ms", "dec") => Ok(val / 10 / 1000 / 60 / 60 / 24 / 365),
|
||||
("ms", "ns") => Ok(val as f64 * 1000.0 * 1000.0),
|
||||
("ms", "us") => Ok(val as f64 * 1000.0),
|
||||
("ms", "ms") => Ok(val as f64),
|
||||
("ms", "sec") => Ok(val as f64 / 1000.0),
|
||||
("ms", "min") => Ok(val as f64 / 1000.0 / 60.0),
|
||||
("ms", "hr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0),
|
||||
("ms", "day") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||
("ms", "wk") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||
("ms", "month") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||
("ms", "yr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
("ms", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
|
||||
("sec", "ns") => Ok(val * 1000 * 1000 * 1000),
|
||||
("sec", "us") => Ok(val * 1000 * 1000),
|
||||
("sec", "ms") => Ok(val * 1000),
|
||||
("sec", "sec") => Ok(val),
|
||||
("sec", "min") => Ok(val / 60),
|
||||
("sec", "hr") => Ok(val / 60 / 60),
|
||||
("sec", "day") => Ok(val / 60 / 60 / 24),
|
||||
("sec", "wk") => Ok(val / 60 / 60 / 24 / 7),
|
||||
("sec", "month") => Ok(val / 60 / 60 / 24 / 30),
|
||||
("sec", "yr") => Ok(val / 60 / 60 / 24 / 365),
|
||||
("sec", "dec") => Ok(val / 10 / 60 / 60 / 24 / 365),
|
||||
("sec", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0),
|
||||
("sec", "us") => Ok(val as f64 * 1000.0 * 1000.0),
|
||||
("sec", "ms") => Ok(val as f64 * 1000.0),
|
||||
("sec", "sec") => Ok(val as f64),
|
||||
("sec", "min") => Ok(val as f64 / 60.0),
|
||||
("sec", "hr") => Ok(val as f64 / 60.0 / 60.0),
|
||||
("sec", "day") => Ok(val as f64 / 60.0 / 60.0 / 24.0),
|
||||
("sec", "wk") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||
("sec", "month") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||
("sec", "yr") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
("sec", "dec") => Ok(val as f64 / 10.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
|
||||
("min", "ns") => Ok(val * 1000 * 1000 * 1000 * 60),
|
||||
("min", "us") => Ok(val * 1000 * 1000 * 60),
|
||||
("min", "ms") => Ok(val * 1000 * 60),
|
||||
("min", "sec") => Ok(val * 60),
|
||||
("min", "min") => Ok(val),
|
||||
("min", "hr") => Ok(val / 60),
|
||||
("min", "day") => Ok(val / 60 / 24),
|
||||
("min", "wk") => Ok(val / 60 / 24 / 7),
|
||||
("min", "month") => Ok(val / 60 / 24 / 30),
|
||||
("min", "yr") => Ok(val / 60 / 24 / 365),
|
||||
("min", "dec") => Ok(val / 10 / 60 / 24 / 365),
|
||||
("min", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0),
|
||||
("min", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0),
|
||||
("min", "ms") => Ok(val as f64 * 1000.0 * 60.0),
|
||||
("min", "sec") => Ok(val as f64 * 60.0),
|
||||
("min", "min") => Ok(val as f64),
|
||||
("min", "hr") => Ok(val as f64 / 60.0),
|
||||
("min", "day") => Ok(val as f64 / 60.0 / 24.0),
|
||||
("min", "wk") => Ok(val as f64 / 60.0 / 24.0 / 7.0),
|
||||
("min", "month") => Ok(val as f64 / 60.0 / 24.0 / 30.0),
|
||||
("min", "yr") => Ok(val as f64 / 60.0 / 24.0 / 365.0),
|
||||
("min", "dec") => Ok(val as f64 / 10.0 / 60.0 / 24.0 / 365.0),
|
||||
|
||||
("hr", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60),
|
||||
("hr", "us") => Ok(val * 1000 * 1000 * 60 * 60),
|
||||
("hr", "ms") => Ok(val * 1000 * 60 * 60),
|
||||
("hr", "sec") => Ok(val * 60 * 60),
|
||||
("hr", "min") => Ok(val * 60),
|
||||
("hr", "hr") => Ok(val),
|
||||
("hr", "day") => Ok(val / 24),
|
||||
("hr", "wk") => Ok(val / 24 / 7),
|
||||
("hr", "month") => Ok(val / 24 / 30),
|
||||
("hr", "yr") => Ok(val / 24 / 365),
|
||||
("hr", "dec") => Ok(val / 10 / 24 / 365),
|
||||
("hr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0),
|
||||
("hr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0),
|
||||
("hr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0),
|
||||
("hr", "sec") => Ok(val as f64 * 60.0 * 60.0),
|
||||
("hr", "min") => Ok(val as f64 * 60.0),
|
||||
("hr", "hr") => Ok(val as f64),
|
||||
("hr", "day") => Ok(val as f64 / 24.0),
|
||||
("hr", "wk") => Ok(val as f64 / 24.0 / 7.0),
|
||||
("hr", "month") => Ok(val as f64 / 24.0 / 30.0),
|
||||
("hr", "yr") => Ok(val as f64 / 24.0 / 365.0),
|
||||
("hr", "dec") => Ok(val as f64 / 10.0 / 24.0 / 365.0),
|
||||
|
||||
("day", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24),
|
||||
("day", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24),
|
||||
("day", "ms") => Ok(val * 1000 * 60 * 60 * 24),
|
||||
("day", "sec") => Ok(val * 60 * 60 * 24),
|
||||
("day", "min") => Ok(val * 60 * 24),
|
||||
("day", "hr") => Ok(val * 24),
|
||||
("day", "day") => Ok(val),
|
||||
("day", "wk") => Ok(val / 7),
|
||||
("day", "month") => Ok(val / 30),
|
||||
("day", "yr") => Ok(val / 365),
|
||||
("day", "dec") => Ok(val / 10 / 365),
|
||||
("day", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||
("day", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||
("day", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||
("day", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0),
|
||||
("day", "min") => Ok(val as f64 * 60.0 * 24.0),
|
||||
("day", "hr") => Ok(val as f64 * 24.0),
|
||||
("day", "day") => Ok(val as f64),
|
||||
("day", "wk") => Ok(val as f64 / 7.0),
|
||||
("day", "month") => Ok(val as f64 / 30.0),
|
||||
("day", "yr") => Ok(val as f64 / 365.0),
|
||||
("day", "dec") => Ok(val as f64 / 10.0 / 365.0),
|
||||
|
||||
("wk", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 7),
|
||||
("wk", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 7),
|
||||
("wk", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 7),
|
||||
("wk", "sec") => Ok(val * 60 * 60 * 24 * 7),
|
||||
("wk", "min") => Ok(val * 60 * 24 * 7),
|
||||
("wk", "hr") => Ok(val * 24 * 7),
|
||||
("wk", "day") => Ok(val * 7),
|
||||
("wk", "wk") => Ok(val),
|
||||
("wk", "month") => Ok(val / 4),
|
||||
("wk", "yr") => Ok(val / 52),
|
||||
("wk", "dec") => Ok(val / 10 / 52),
|
||||
("wk", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||
("wk", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||
("wk", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||
("wk", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||
("wk", "min") => Ok(val as f64 * 60.0 * 24.0 * 7.0),
|
||||
("wk", "hr") => Ok(val as f64 * 24.0 * 7.0),
|
||||
("wk", "day") => Ok(val as f64 * 7.0),
|
||||
("wk", "wk") => Ok(val as f64),
|
||||
("wk", "month") => Ok(val as f64 / 4.0),
|
||||
("wk", "yr") => Ok(val as f64 / 52.0),
|
||||
("wk", "dec") => Ok(val as f64 / 10.0 / 52.0),
|
||||
|
||||
("month", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 30),
|
||||
("month", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 30),
|
||||
("month", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 30),
|
||||
("month", "sec") => Ok(val * 60 * 60 * 24 * 30),
|
||||
("month", "min") => Ok(val * 60 * 24 * 30),
|
||||
("month", "hr") => Ok(val * 24 * 30),
|
||||
("month", "day") => Ok(val * 30),
|
||||
("month", "wk") => Ok(val * 4),
|
||||
("month", "month") => Ok(val),
|
||||
("month", "yr") => Ok(val / 12),
|
||||
("month", "dec") => Ok(val / 10 / 12),
|
||||
("month", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||
("month", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||
("month", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||
("month", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||
("month", "min") => Ok(val as f64 * 60.0 * 24.0 * 30.0),
|
||||
("month", "hr") => Ok(val as f64 * 24.0 * 30.0),
|
||||
("month", "day") => Ok(val as f64 * 30.0),
|
||||
("month", "wk") => Ok(val as f64 * 4.0),
|
||||
("month", "month") => Ok(val as f64),
|
||||
("month", "yr") => Ok(val as f64 / 12.0),
|
||||
("month", "dec") => Ok(val as f64 / 10.0 / 12.0),
|
||||
|
||||
("yr", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 365),
|
||||
("yr", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 365),
|
||||
("yr", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 365),
|
||||
("yr", "sec") => Ok(val * 60 * 60 * 24 * 365),
|
||||
("yr", "min") => Ok(val * 60 * 24 * 365),
|
||||
("yr", "hr") => Ok(val * 24 * 365),
|
||||
("yr", "day") => Ok(val * 365),
|
||||
("yr", "wk") => Ok(val * 52),
|
||||
("yr", "month") => Ok(val * 12),
|
||||
("yr", "yr") => Ok(val),
|
||||
("yr", "dec") => Ok(val / 10),
|
||||
("yr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||
("yr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||
("yr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||
("yr", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||
("yr", "min") => Ok(val as f64 * 60.0 * 24.0 * 365.0),
|
||||
("yr", "hr") => Ok(val as f64 * 24.0 * 365.0),
|
||||
("yr", "day") => Ok(val as f64 * 365.0),
|
||||
("yr", "wk") => Ok(val as f64 * 52.0),
|
||||
("yr", "month") => Ok(val as f64 * 12.0),
|
||||
("yr", "yr") => Ok(val as f64),
|
||||
("yr", "dec") => Ok(val as f64 / 10.0),
|
||||
|
||||
_ => Err(ShellError::CantConvertWithValue(
|
||||
"string duration".to_string(),
|
||||
@ -369,19 +380,41 @@ fn string_to_unit_duration(
|
||||
))
|
||||
}
|
||||
|
||||
fn action(input: &Value, convert_to_unit: &Option<Spanned<String>>, span: Span) -> Value {
|
||||
fn action(
|
||||
input: &Value,
|
||||
convert_to_unit: &Option<Spanned<String>>,
|
||||
float_precision: usize,
|
||||
span: Span,
|
||||
) -> Value {
|
||||
match input {
|
||||
Value::Duration {
|
||||
val: _val_num,
|
||||
span: _value_span,
|
||||
val: val_num,
|
||||
span: value_span,
|
||||
} => {
|
||||
if let Some(_to_unit) = convert_to_unit {
|
||||
Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
"Cannot convert from a Value::Duration right now. Try making it a string."
|
||||
.into(),
|
||||
span,
|
||||
),
|
||||
if let Some(to_unit) = convert_to_unit {
|
||||
let from_unit = "ns";
|
||||
let duration = *val_num;
|
||||
match convert_str_from_unit_to_unit(
|
||||
duration,
|
||||
from_unit,
|
||||
&to_unit.item,
|
||||
span,
|
||||
*value_span,
|
||||
) {
|
||||
Ok(d) => {
|
||||
if d.fract() == 0.0 {
|
||||
Value::String {
|
||||
val: format!("{} {}", d, &to_unit.item),
|
||||
span: *value_span,
|
||||
}
|
||||
} else {
|
||||
Value::String {
|
||||
val: format!("{:.float_precision$} {}", d, &to_unit.item),
|
||||
span: *value_span,
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => Value::Error { error: e },
|
||||
}
|
||||
} else {
|
||||
input.clone()
|
||||
@ -402,10 +435,19 @@ fn action(input: &Value, convert_to_unit: &Option<Spanned<String>>, span: Span)
|
||||
span,
|
||||
*value_span,
|
||||
) {
|
||||
Ok(d) => Value::String {
|
||||
val: format!("{} {}", d, &to_unit.item),
|
||||
span: *value_span,
|
||||
},
|
||||
Ok(d) => {
|
||||
if d.fract() == 0.0 {
|
||||
Value::String {
|
||||
val: format!("{} {}", d, &to_unit.item),
|
||||
span: *value_span,
|
||||
}
|
||||
} else {
|
||||
Value::String {
|
||||
val: format!("{:.float_precision$} {}", d, &to_unit.item),
|
||||
span: *value_span,
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => Value::Error { error: e },
|
||||
}
|
||||
} else {
|
||||
@ -450,7 +492,7 @@ mod test {
|
||||
let expected = Value::Duration { val: 3, span };
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, span);
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -464,7 +506,7 @@ mod test {
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, span);
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -478,7 +520,7 @@ mod test {
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, span);
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -492,7 +534,7 @@ mod test {
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, span);
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -506,7 +548,7 @@ mod test {
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, span);
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -520,7 +562,7 @@ mod test {
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, span);
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -534,7 +576,7 @@ mod test {
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, span);
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -548,7 +590,7 @@ mod test {
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, span);
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ use nu_engine::{eval_block, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value,
|
||||
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, SyntaxShape,
|
||||
Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -102,9 +103,75 @@ impl Command for Do {
|
||||
Err(_) => Ok(PipelineData::new(call.head)),
|
||||
}
|
||||
} else if capture_errors {
|
||||
// collect stdout and stderr and check exit code.
|
||||
// if exit code is not 0, return back ShellError.
|
||||
match result {
|
||||
Ok(x) => Ok(x),
|
||||
Err(err) => Ok((Value::Error { error: err }).into_pipeline_data()),
|
||||
Ok(PipelineData::ExternalStream {
|
||||
stdout,
|
||||
stderr,
|
||||
exit_code,
|
||||
span,
|
||||
metadata,
|
||||
}) => {
|
||||
// collect all output first.
|
||||
let mut stderr_ctrlc = None;
|
||||
let stderr_msg = match stderr {
|
||||
None => "".to_string(),
|
||||
Some(stderr_stream) => {
|
||||
stderr_ctrlc = stderr_stream.ctrlc.clone();
|
||||
stderr_stream.into_string().map(|s| s.item)?
|
||||
}
|
||||
};
|
||||
|
||||
let mut stdout_ctrlc = None;
|
||||
let stdout_msg = match stdout {
|
||||
None => "".to_string(),
|
||||
Some(stdout_stream) => {
|
||||
stdout_ctrlc = stdout_stream.ctrlc.clone();
|
||||
stdout_stream.into_string().map(|s| s.item)?
|
||||
}
|
||||
};
|
||||
|
||||
let mut exit_code_ctrlc = None;
|
||||
let exit_code: Vec<Value> = match exit_code {
|
||||
None => vec![],
|
||||
Some(exit_code_stream) => {
|
||||
exit_code_ctrlc = exit_code_stream.ctrlc.clone();
|
||||
exit_code_stream.into_iter().collect()
|
||||
}
|
||||
};
|
||||
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
|
||||
// if exit_code is not 0, it indicates error occured, return back Err.
|
||||
if *code != 0 {
|
||||
return Err(ShellError::ExternalCommand(
|
||||
"External command runs to failed".to_string(),
|
||||
stderr_msg,
|
||||
span,
|
||||
));
|
||||
}
|
||||
}
|
||||
// construct pipeline data to our caller
|
||||
Ok(PipelineData::ExternalStream {
|
||||
stdout: Some(RawStream::new(
|
||||
Box::new(vec![Ok(stdout_msg.into_bytes())].into_iter()),
|
||||
stdout_ctrlc,
|
||||
span,
|
||||
)),
|
||||
stderr: Some(RawStream::new(
|
||||
Box::new(vec![Ok(stderr_msg.into_bytes())].into_iter()),
|
||||
stderr_ctrlc,
|
||||
span,
|
||||
)),
|
||||
exit_code: Some(ListStream::from_stream(
|
||||
exit_code.into_iter(),
|
||||
exit_code_ctrlc,
|
||||
)),
|
||||
span,
|
||||
metadata,
|
||||
})
|
||||
}
|
||||
Ok(other) => Ok(other),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
result
|
||||
|
@ -59,4 +59,8 @@ impl Command for ExportCommand {
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["module"]
|
||||
}
|
||||
}
|
||||
|
@ -51,4 +51,8 @@ impl Command for ExportAlias {
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["aka", "abbr", "module"]
|
||||
}
|
||||
}
|
||||
|
@ -55,4 +55,8 @@ impl Command for ExportDef {
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["module"]
|
||||
}
|
||||
}
|
||||
|
@ -81,4 +81,8 @@ export def-env cd_with_fallback [arg = ""] {
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["module"]
|
||||
}
|
||||
}
|
||||
|
@ -47,4 +47,8 @@ impl Command for ExportExtern {
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["signature", "module", "declare"]
|
||||
}
|
||||
}
|
||||
|
@ -53,4 +53,8 @@ impl Command for ExportUse {
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["reexport", "import", "module"]
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ mod last;
|
||||
mod list;
|
||||
mod melt;
|
||||
mod open;
|
||||
mod query_dfr;
|
||||
mod query_df;
|
||||
mod rename;
|
||||
mod sample;
|
||||
mod shape;
|
||||
@ -45,7 +45,7 @@ pub use last::LastDF;
|
||||
pub use list::ListDF;
|
||||
pub use melt::MeltDF;
|
||||
pub use open::OpenDataFrame;
|
||||
pub use query_dfr::QueryDfr;
|
||||
pub use query_df::QueryDf;
|
||||
pub use rename::RenameDF;
|
||||
pub use sample::SampleDF;
|
||||
pub use shape::ShapeDF;
|
||||
@ -87,7 +87,7 @@ pub fn add_eager_decls(working_set: &mut StateWorkingSet) {
|
||||
ListDF,
|
||||
MeltDF,
|
||||
OpenDataFrame,
|
||||
QueryDfr,
|
||||
QueryDf,
|
||||
RenameDF,
|
||||
SampleDF,
|
||||
ShapeDF,
|
||||
|
@ -14,11 +14,11 @@ use nu_protocol::{
|
||||
// https://github.com/pola-rs/polars/tree/master/polars-sql
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct QueryDfr;
|
||||
pub struct QueryDf;
|
||||
|
||||
impl Command for QueryDfr {
|
||||
impl Command for QueryDf {
|
||||
fn name(&self) -> &str {
|
||||
"query dfr"
|
||||
"query df"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -40,7 +40,7 @@ impl Command for QueryDfr {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Query dataframe using SQL",
|
||||
example: "[[a b]; [1 2] [3 4]] | into df | query dfr 'select a from df'",
|
||||
example: "[[a b]; [1 2] [3 4]] | into df | query df 'select a from df'",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![Column::new(
|
||||
"a".to_string(),
|
||||
@ -101,6 +101,6 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_dataframe(vec![Box::new(QueryDfr {})])
|
||||
test_dataframe(vec![Box::new(QueryDf {})])
|
||||
}
|
||||
}
|
@ -19,6 +19,10 @@ impl Command for ArgMax {
|
||||
"Return index for max value in series"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["argmax", "maximum", "most", "largest", "greatest"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
|
@ -19,6 +19,10 @@ impl Command for ArgMin {
|
||||
"Return index for min value in series"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["argmin", "minimum", "least", "smallest", "lowest"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
|
@ -19,6 +19,10 @@ impl Command for ArgSort {
|
||||
"Returns indexes for a sorted series"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["argsort", "order", "arrange"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.switch("reverse", "reverse order", Some('r'))
|
||||
|
@ -19,6 +19,10 @@ impl Command for ArgTrue {
|
||||
"Returns indexes where values are true"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["argtrue", "truth", "boolean-true"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
|
@ -19,6 +19,10 @@ impl Command for ArgUnique {
|
||||
"Returns indexes for unique values"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["argunique", "distinct", "noduplicate", "unrepeated"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
|
@ -19,6 +19,10 @@ impl Command for ToUpperCase {
|
||||
"Uppercase the strings in the column"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["capitalize, caps, capital"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
|
@ -158,12 +158,17 @@ pub fn create_default_context() -> EngineState {
|
||||
bind_command! {
|
||||
Benchmark,
|
||||
Complete,
|
||||
Exec,
|
||||
External,
|
||||
NuCheck,
|
||||
Sys,
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
bind_command! { Exec }
|
||||
|
||||
#[cfg(windows)]
|
||||
bind_command! { RegistryQuery }
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
target_os = "linux",
|
||||
|
@ -37,7 +37,7 @@ impl Command for ConfigEnv {
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
_call: &Call,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let env_vars_str = env_to_strings(engine_state, stack)?;
|
||||
@ -59,7 +59,7 @@ impl Command for ConfigEnv {
|
||||
|
||||
let name = Spanned {
|
||||
item: get_editor(engine_state, stack)?,
|
||||
span: Span { start: 0, end: 0 },
|
||||
span: call.head,
|
||||
};
|
||||
|
||||
let args = vec![Spanned {
|
||||
@ -76,6 +76,6 @@ impl Command for ConfigEnv {
|
||||
env_vars: env_vars_str,
|
||||
};
|
||||
|
||||
command.run_with_input(engine_state, stack, input)
|
||||
command.run_with_input(engine_state, stack, input, true)
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ impl Command for ConfigNu {
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
_call: &Call,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let env_vars_str = env_to_strings(engine_state, stack)?;
|
||||
@ -59,7 +59,7 @@ impl Command for ConfigNu {
|
||||
|
||||
let name = Spanned {
|
||||
item: get_editor(engine_state, stack)?,
|
||||
span: Span { start: 0, end: 0 },
|
||||
span: call.head,
|
||||
};
|
||||
|
||||
let args = vec![Spanned {
|
||||
@ -76,6 +76,6 @@ impl Command for ConfigNu {
|
||||
env_vars: env_vars_str,
|
||||
};
|
||||
|
||||
command.run_with_input(engine_state, stack, input)
|
||||
command.run_with_input(engine_state, stack, input, true)
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +99,16 @@ impl Command for Glob {
|
||||
let glob_pattern: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
let depth = call.get_flag(engine_state, stack, "depth")?;
|
||||
|
||||
if glob_pattern.item.is_empty() {
|
||||
return Err(ShellError::GenericError(
|
||||
"glob pattern must not be empty".to_string(),
|
||||
"".to_string(),
|
||||
Some(glob_pattern.span),
|
||||
Some("add characters to the glob pattern".to_string()),
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
|
||||
let folder_depth = if let Some(depth) = depth {
|
||||
depth
|
||||
} else {
|
||||
|
@ -491,7 +491,10 @@ pub(crate) fn dir_entry_dict(
|
||||
span,
|
||||
});
|
||||
} else {
|
||||
vals.push(Value::nothing(span))
|
||||
vals.push(Value::Int {
|
||||
val: md.uid() as i64,
|
||||
span,
|
||||
})
|
||||
}
|
||||
|
||||
cols.push("group".into());
|
||||
@ -501,7 +504,10 @@ pub(crate) fn dir_entry_dict(
|
||||
span,
|
||||
});
|
||||
} else {
|
||||
vals.push(Value::nothing(span))
|
||||
vals.push(Value::Int {
|
||||
val: md.gid() as i64,
|
||||
span,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::fs::OpenOptions;
|
||||
use std::path::Path;
|
||||
|
||||
use chrono::{DateTime, Datelike, Local};
|
||||
use chrono::{DateTime, Local};
|
||||
use filetime::FileTime;
|
||||
|
||||
use nu_engine::CallExt;
|
||||
@ -9,13 +9,6 @@ use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape};
|
||||
|
||||
use crate::parse_date_from_string;
|
||||
|
||||
enum AddYear {
|
||||
Full,
|
||||
FirstDigits,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Touch;
|
||||
|
||||
@ -35,18 +28,6 @@ impl Command for Touch {
|
||||
SyntaxShape::Filepath,
|
||||
"the path of the file you want to create",
|
||||
)
|
||||
.named(
|
||||
"timestamp",
|
||||
SyntaxShape::String,
|
||||
"change the file or directory time to a timestamp. Format: [[CC]YY]MMDDhhmm[.ss]\n\n If neither YY or CC is given, the current year will be assumed. If YY is specified, but CC is not, CC will be derived as follows:\n \tIf YY is between [69, 99], CC is 19\n \tIf YY is between [00, 68], CC is 20\n Note: It is expected that in a future version of this standard the default century inferred from a 2-digit year will change",
|
||||
Some('t'),
|
||||
)
|
||||
.named(
|
||||
"date",
|
||||
SyntaxShape::String,
|
||||
"change the file or directory time to a date",
|
||||
Some('d'),
|
||||
)
|
||||
.named(
|
||||
"reference",
|
||||
SyntaxShape::String,
|
||||
@ -85,8 +66,6 @@ impl Command for Touch {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let mut change_mtime: bool = call.has_flag("modified");
|
||||
let mut change_atime: bool = call.has_flag("access");
|
||||
let use_stamp: bool = call.has_flag("timestamp");
|
||||
let use_date: bool = call.has_flag("date");
|
||||
let use_reference: bool = call.has_flag("reference");
|
||||
let no_create: bool = call.has_flag("no-create");
|
||||
let target: String = call.req(engine_state, stack, 0)?;
|
||||
@ -105,111 +84,6 @@ impl Command for Touch {
|
||||
date = Some(Local::now());
|
||||
}
|
||||
|
||||
if use_stamp || use_date {
|
||||
let (val, span) = if use_stamp {
|
||||
let stamp: Option<Spanned<String>> =
|
||||
call.get_flag(engine_state, stack, "timestamp")?;
|
||||
let (stamp, span) = match stamp {
|
||||
Some(stamp) => (stamp.item, stamp.span),
|
||||
None => {
|
||||
return Err(ShellError::MissingParameter(
|
||||
"timestamp".to_string(),
|
||||
call.head,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Checks for the seconds stamp and removes the '.' delimiter if any
|
||||
let (val, has_sec): (String, bool) = match stamp.split_once('.') {
|
||||
Some((dtime, sec)) => match sec.parse::<u8>() {
|
||||
Ok(sec) if sec < 60 => (format!("{}{}", dtime, sec), true),
|
||||
_ => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"input has an invalid timestamp".to_string(),
|
||||
span,
|
||||
))
|
||||
}
|
||||
},
|
||||
None => (stamp.to_string(), false),
|
||||
};
|
||||
|
||||
let size = val.len();
|
||||
|
||||
// Each stamp is a 2 digit number and the whole stamp must not be less than 4 or greater than 7 pairs
|
||||
if (size % 2 != 0 || !(8..=14).contains(&size)) || val.parse::<u64>().is_err() {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"input has an invalid timestamp".to_string(),
|
||||
span,
|
||||
));
|
||||
}
|
||||
|
||||
let add_year: Option<AddYear> = if has_sec {
|
||||
match size {
|
||||
10 => Some(AddYear::Full),
|
||||
12 => Some(AddYear::FirstDigits),
|
||||
14 => None,
|
||||
_ => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"input has an invalid timestamp".to_string(),
|
||||
span,
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match size {
|
||||
8 => Some(AddYear::Full),
|
||||
10 => Some(AddYear::FirstDigits),
|
||||
12 => None,
|
||||
_ => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"input has an invalid timestamp".to_string(),
|
||||
span,
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(add_year) = add_year {
|
||||
let year = Local::now().year();
|
||||
match add_year {
|
||||
AddYear::Full => (format!("{}{}", year, val), span),
|
||||
AddYear::FirstDigits => {
|
||||
// Compliance with the Unix version of touch
|
||||
let yy = val[0..2]
|
||||
.parse::<u8>()
|
||||
.expect("should be a valid 2 digit number");
|
||||
let mut year = 20;
|
||||
if (69..=99).contains(&yy) {
|
||||
year = 19;
|
||||
}
|
||||
(format!("{}{}", year, val), span)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
(val, span)
|
||||
}
|
||||
} else {
|
||||
let date_string: Option<Spanned<String>> =
|
||||
call.get_flag(engine_state, stack, "date")?;
|
||||
match date_string {
|
||||
Some(date_string) => (date_string.item, date_string.span),
|
||||
None => {
|
||||
return Err(ShellError::MissingParameter("date".to_string(), call.head));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
date = if let Ok(parsed_date) = parse_date_from_string(&val, span) {
|
||||
Some(parsed_date.into())
|
||||
} else {
|
||||
let flag = if use_stamp { "timestamp" } else { "date" };
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
format!("input has an invalid {}", flag),
|
||||
span,
|
||||
));
|
||||
};
|
||||
}
|
||||
|
||||
if use_reference {
|
||||
let reference: Option<Spanned<String>> =
|
||||
call.get_flag(engine_state, stack, "reference")?;
|
||||
@ -336,11 +210,6 @@ impl Command for Touch {
|
||||
example: "touch -m fixture.json",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Creates files d and e and set its last modified time to a timestamp",
|
||||
example: "touch -m -t 201908241230.30 d e",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Changes the last modified time of files a, b and c to a date",
|
||||
example: r#"touch -m -d "yesterday" a b c"#,
|
||||
@ -356,11 +225,6 @@ impl Command for Touch {
|
||||
example: r#"touch -a -d "August 24, 2019; 12:30:30" fixture.json"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Changes both last modified and accessed time of a, b and c to a timestamp only if they exist",
|
||||
example: r#"touch -c -t 201908241230.30 a b c"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use nu_engine::{eval_block, CallExt};
|
||||
use nu_engine::{eval_block, redirect_env, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
@ -20,6 +20,11 @@ impl Command for Collect {
|
||||
SyntaxShape::Block(Some(vec![SyntaxShape::Any])),
|
||||
"the block to run once the stream is collected",
|
||||
)
|
||||
.switch(
|
||||
"keep-env",
|
||||
"let the block affect environment variables",
|
||||
None,
|
||||
)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
@ -37,26 +42,43 @@ impl Command for Collect {
|
||||
let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?;
|
||||
|
||||
let block = engine_state.get_block(capture_block.block_id).clone();
|
||||
let mut stack = stack.captures_to_stack(&capture_block.captures);
|
||||
let mut stack_captures = stack.captures_to_stack(&capture_block.captures);
|
||||
|
||||
let metadata = input.metadata();
|
||||
let input: Value = input.into_value(call.head);
|
||||
|
||||
let mut saved_positional = None;
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, input.clone());
|
||||
stack_captures.add_var(*var_id, input.clone());
|
||||
saved_positional = Some(*var_id);
|
||||
}
|
||||
}
|
||||
|
||||
eval_block(
|
||||
let result = eval_block(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
&mut stack_captures,
|
||||
&block,
|
||||
input.into_pipeline_data(),
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
)
|
||||
.map(|x| x.set_metadata(metadata))
|
||||
.map(|x| x.set_metadata(metadata));
|
||||
|
||||
if call.has_flag("keep-env") {
|
||||
redirect_env(engine_state, stack, &stack_captures);
|
||||
// for when we support `data | let x = $in;`
|
||||
// remove the variables added earlier
|
||||
for var_id in capture_block.captures.keys() {
|
||||
stack_captures.vars.remove(var_id);
|
||||
}
|
||||
if let Some(u) = saved_positional {
|
||||
stack_captures.vars.remove(&u);
|
||||
}
|
||||
// add any new variables to the stack
|
||||
stack.vars.extend(stack_captures.vars);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -146,7 +146,7 @@ fn first_helper(
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if rows_desired == 1 {
|
||||
if rows_desired == 1 && rows.is_none() {
|
||||
match input_peek.next() {
|
||||
Some(val) => Ok(val.into_pipeline_data()),
|
||||
None => Err(ShellError::AccessBeyondEndOfStream(head)),
|
||||
|
@ -11,6 +11,10 @@ impl Command for Roll {
|
||||
"roll"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["rotate", "shift", "move"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name()).category(Category::Filters)
|
||||
}
|
||||
|
@ -16,6 +16,10 @@ impl Command for RollDown {
|
||||
"roll down"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["rotate", "shift", "move", "row"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.named("by", SyntaxShape::Int, "Number of rows to roll", Some('b'))
|
||||
|
@ -16,6 +16,10 @@ impl Command for RollLeft {
|
||||
"roll left"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["rotate", "shift", "move", "column"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.named(
|
||||
|
@ -16,6 +16,10 @@ impl Command for RollRight {
|
||||
"roll right"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["rotate", "shift", "move", "column"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.named(
|
||||
|
@ -16,6 +16,10 @@ impl Command for RollUp {
|
||||
"roll up"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["rotate", "shift", "move", "row"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.named("by", SyntaxShape::Int, "Number of rows to roll", Some('b'))
|
||||
|
@ -23,6 +23,11 @@ impl Command for Window {
|
||||
"the number of rows to slide over between windows",
|
||||
Some('s'),
|
||||
)
|
||||
.switch(
|
||||
"remainder",
|
||||
"yield last chunks even if they have fewer elements than size",
|
||||
Some('r'),
|
||||
)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
@ -115,6 +120,39 @@ impl Command for Window {
|
||||
},
|
||||
];
|
||||
|
||||
let stream_test_3 = vec![
|
||||
Value::List {
|
||||
vals: vec![
|
||||
Value::Int {
|
||||
val: 1,
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Int {
|
||||
val: 2,
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Int {
|
||||
val: 3,
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::List {
|
||||
vals: vec![
|
||||
Value::Int {
|
||||
val: 4,
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Int {
|
||||
val: 5,
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
];
|
||||
|
||||
vec![
|
||||
Example {
|
||||
example: "echo [1 2 3 4] | window 2",
|
||||
@ -132,6 +170,14 @@ impl Command for Window {
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
example: "[1, 2, 3, 4, 5] | window 3 --stride 3 --remainder",
|
||||
description: "A sliding window of equal stride that includes remainder. Equivalent to chunking",
|
||||
result: Some(Value::List {
|
||||
vals: stream_test_3,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -146,6 +192,7 @@ impl Command for Window {
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let metadata = input.metadata();
|
||||
let stride: Option<usize> = call.get_flag(engine_state, stack, "stride")?;
|
||||
let remainder = call.has_flag("remainder");
|
||||
|
||||
let stride = stride.unwrap_or(1);
|
||||
|
||||
@ -155,8 +202,9 @@ impl Command for Window {
|
||||
group_size: group_size.item,
|
||||
input: Box::new(input.into_iter()),
|
||||
span: call.head,
|
||||
previous: vec![],
|
||||
previous: None,
|
||||
stride,
|
||||
remainder,
|
||||
};
|
||||
|
||||
Ok(each_group_iterator
|
||||
@ -169,15 +217,23 @@ struct EachWindowIterator {
|
||||
group_size: usize,
|
||||
input: Box<dyn Iterator<Item = Value> + Send>,
|
||||
span: Span,
|
||||
previous: Vec<Value>,
|
||||
previous: Option<Vec<Value>>,
|
||||
stride: usize,
|
||||
remainder: bool,
|
||||
}
|
||||
|
||||
impl Iterator for EachWindowIterator {
|
||||
type Item = Value;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut group = self.previous.clone();
|
||||
let mut group = self.previous.take().unwrap_or_else(|| {
|
||||
let mut vec = Vec::new();
|
||||
|
||||
// We default to a Vec of capacity size + stride as striding pushes n extra elements to the end
|
||||
vec.try_reserve(self.group_size + self.stride).ok();
|
||||
|
||||
vec
|
||||
});
|
||||
let mut current_count = 0;
|
||||
|
||||
if group.is_empty() {
|
||||
@ -193,7 +249,13 @@ impl Iterator for EachWindowIterator {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => return None,
|
||||
None => {
|
||||
if self.remainder {
|
||||
break;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -211,23 +273,31 @@ impl Iterator for EachWindowIterator {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => return None,
|
||||
None => {
|
||||
if self.remainder {
|
||||
break;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..current_count {
|
||||
let _ = group.remove(0);
|
||||
}
|
||||
// We now have elements + stride in our group, and need to
|
||||
// drop the skipped elements. Drain to preserve allocation and capacity
|
||||
// Dropping this iterator consumes it.
|
||||
group.drain(..self.stride.min(group.len()));
|
||||
}
|
||||
|
||||
if group.is_empty() || current_count == 0 {
|
||||
if group.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.previous = group.clone();
|
||||
let return_group = group.clone();
|
||||
self.previous = Some(group);
|
||||
|
||||
Some(Value::List {
|
||||
vals: group,
|
||||
vals: return_group,
|
||||
span: self.span,
|
||||
})
|
||||
}
|
||||
|
@ -173,12 +173,11 @@ fn value_to_string(v: &Value, span: Span) -> Result<String, ShellError> {
|
||||
fn value_to_string_without_quotes(v: &Value, span: Span) -> Result<String, ShellError> {
|
||||
match v {
|
||||
Value::String { val, .. } => Ok({
|
||||
let mut quoted = escape_quote_string(val);
|
||||
if !needs_quotes(val) {
|
||||
quoted.remove(0);
|
||||
quoted.pop();
|
||||
if needs_quotes(val) {
|
||||
escape_quote_string(val)
|
||||
} else {
|
||||
val.clone()
|
||||
}
|
||||
quoted
|
||||
}),
|
||||
_ => value_to_string(v, span),
|
||||
}
|
||||
@ -209,6 +208,7 @@ fn needs_quotes(string: &str) -> bool {
|
||||
|| string.contains('\t')
|
||||
|| string.contains('\n')
|
||||
|| string.contains('\r')
|
||||
|| string.contains('\"')
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -66,10 +66,6 @@ impl Command for ToText {
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["convert"]
|
||||
}
|
||||
}
|
||||
|
||||
fn local_into_string(value: Value, separator: &str, config: &Config) -> String {
|
||||
|
@ -149,7 +149,7 @@ impl Command for Input {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get input from the user, and assign to a variable",
|
||||
example: "let user-input = (input)",
|
||||
example: "let user_input = (input)",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
@ -12,35 +12,28 @@ impl Command for TermSize {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Returns the terminal size"
|
||||
"Returns a record containing the number of columns (width) and rows (height) of the terminal"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("term size")
|
||||
.switch(
|
||||
"columns",
|
||||
"Report only the width of the terminal",
|
||||
Some('c'),
|
||||
)
|
||||
.switch("rows", "Report only the height of the terminal", Some('r'))
|
||||
.category(Category::Platform)
|
||||
Signature::build("term size").category(Category::Platform)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Return the width height of the terminal",
|
||||
description: "Return the columns (width) and rows (height) of the terminal",
|
||||
example: "term size",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Return the width (columns) of the terminal",
|
||||
example: "term size -c",
|
||||
description: "Return the columns (width) of the terminal",
|
||||
example: "(term size).columns",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Return the height (rows) of the terminal",
|
||||
example: "term size -r",
|
||||
description: "Return the rows (height) of the terminal",
|
||||
example: "(term size).rows",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
@ -54,60 +47,26 @@ impl Command for TermSize {
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let head = call.head;
|
||||
let wide = call.has_flag("columns");
|
||||
let tall = call.has_flag("rows");
|
||||
|
||||
let (cols, rows) = match terminal_size() {
|
||||
Some((w, h)) => (Width(w.0), Height(h.0)),
|
||||
None => (Width(0), Height(0)),
|
||||
};
|
||||
|
||||
Ok((match (wide, tall) {
|
||||
(true, false) => Value::Record {
|
||||
cols: vec!["columns".into()],
|
||||
vals: vec![Value::Int {
|
||||
Ok(Value::Record {
|
||||
cols: vec!["columns".into(), "rows".into()],
|
||||
vals: vec![
|
||||
Value::Int {
|
||||
val: cols.0 as i64,
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: head,
|
||||
},
|
||||
(true, true) => Value::Record {
|
||||
cols: vec!["columns".into(), "rows".into()],
|
||||
vals: vec![
|
||||
Value::Int {
|
||||
val: cols.0 as i64,
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Int {
|
||||
val: rows.0 as i64,
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: head,
|
||||
},
|
||||
(false, true) => Value::Record {
|
||||
cols: vec!["rows".into()],
|
||||
vals: vec![Value::Int {
|
||||
},
|
||||
Value::Int {
|
||||
val: rows.0 as i64,
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: head,
|
||||
},
|
||||
(false, false) => Value::Record {
|
||||
cols: vec!["columns".into(), "rows".into()],
|
||||
vals: vec![
|
||||
Value::Int {
|
||||
val: cols.0 as i64,
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Int {
|
||||
val: rows.0 as i64,
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: head,
|
||||
},
|
||||
})
|
||||
},
|
||||
],
|
||||
span: head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ use std::sync::Arc;
|
||||
|
||||
struct Arguments {
|
||||
all: bool,
|
||||
find: String,
|
||||
replace: String,
|
||||
find: Spanned<String>,
|
||||
replace: Spanned<String>,
|
||||
column_paths: Vec<CellPath>,
|
||||
literal_replace: bool,
|
||||
no_regex: bool,
|
||||
@ -168,8 +168,8 @@ fn operate(
|
||||
|
||||
let options = Arc::new(Arguments {
|
||||
all: call.has_flag("all"),
|
||||
find: find.item,
|
||||
replace: replace.item,
|
||||
find,
|
||||
replace,
|
||||
column_paths: call.rest(engine_state, stack, 2)?,
|
||||
literal_replace,
|
||||
no_regex,
|
||||
@ -214,23 +214,23 @@ fn action(
|
||||
) -> Value {
|
||||
match input {
|
||||
Value::String { val, .. } => {
|
||||
let FindReplace(find, replacement) = FindReplace(find, replace);
|
||||
let FindReplace(find_str, replace_str) = FindReplace(&find.item, &replace.item);
|
||||
if *no_regex {
|
||||
// just use regular string replacement vs regular expressions
|
||||
if *all {
|
||||
Value::String {
|
||||
val: val.replace(find, replacement),
|
||||
val: val.replace(find_str, replace_str),
|
||||
span: head,
|
||||
}
|
||||
} else {
|
||||
Value::String {
|
||||
val: val.replacen(find, replacement, 1),
|
||||
val: val.replacen(find_str, replace_str, 1),
|
||||
span: head,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// use regular expressions to replace strings
|
||||
let regex = Regex::new(find);
|
||||
let regex = Regex::new(find_str);
|
||||
|
||||
match regex {
|
||||
Ok(re) => {
|
||||
@ -238,9 +238,9 @@ fn action(
|
||||
Value::String {
|
||||
val: {
|
||||
if *literal_replace {
|
||||
re.replace_all(val, NoExpand(replacement)).to_string()
|
||||
re.replace_all(val, NoExpand(replace_str)).to_string()
|
||||
} else {
|
||||
re.replace_all(val, replacement).to_string()
|
||||
re.replace_all(val, replace_str).to_string()
|
||||
}
|
||||
},
|
||||
span: head,
|
||||
@ -249,18 +249,17 @@ fn action(
|
||||
Value::String {
|
||||
val: {
|
||||
if *literal_replace {
|
||||
re.replace(val, NoExpand(replacement)).to_string()
|
||||
re.replace(val, NoExpand(replace_str)).to_string()
|
||||
} else {
|
||||
re.replace(val, replacement).to_string()
|
||||
re.replace(val, replace_str).to_string()
|
||||
}
|
||||
},
|
||||
span: head,
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => Value::String {
|
||||
val: val.to_string(),
|
||||
span: head,
|
||||
Err(e) => Value::Error {
|
||||
error: ShellError::UnsupportedInput(format!("{e}"), find.span),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -282,6 +281,13 @@ mod tests {
|
||||
use super::*;
|
||||
use super::{action, Arguments, SubCommand};
|
||||
|
||||
fn test_spanned_string(val: &str) -> Spanned<String> {
|
||||
Spanned {
|
||||
item: String::from(val),
|
||||
span: Span::test_data(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
@ -297,8 +303,8 @@ mod tests {
|
||||
};
|
||||
|
||||
let options = Arguments {
|
||||
find: String::from("Cargo.(.+)"),
|
||||
replace: String::from("Carga.$1"),
|
||||
find: test_spanned_string("Cargo.(.+)"),
|
||||
replace: test_spanned_string("Carga.$1"),
|
||||
column_paths: vec![],
|
||||
literal_replace: false,
|
||||
all: false,
|
||||
|
@ -37,6 +37,35 @@ impl Command for Complete {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
// use a thread to receive stderr message.
|
||||
// Or we may get a deadlock if child process sends out too much bytes to stdout.
|
||||
//
|
||||
// For example: in normal linux system, stdout pipe's limit is 65535 bytes.
|
||||
// if child process sends out 65536 bytes, the process will be hanged because no consumer
|
||||
// consumes the first 65535 bytes
|
||||
// So we need a thread to receive stderr message, then the current thread can continue to consume
|
||||
// stdout messages.
|
||||
let stderr_handler = stderr.map(|stderr| {
|
||||
let stderr_span = stderr.span;
|
||||
(
|
||||
std::thread::spawn(move || {
|
||||
let stderr = stderr.into_bytes()?;
|
||||
if let Ok(st) = String::from_utf8(stderr.item.clone()) {
|
||||
Ok::<_, ShellError>(Value::String {
|
||||
val: st,
|
||||
span: stderr.span,
|
||||
})
|
||||
} else {
|
||||
Ok::<_, ShellError>(Value::Binary {
|
||||
val: stderr.item,
|
||||
span: stderr.span,
|
||||
})
|
||||
}
|
||||
}),
|
||||
stderr_span,
|
||||
)
|
||||
});
|
||||
|
||||
if let Some(stdout) = stdout {
|
||||
cols.push("stdout".to_string());
|
||||
let stdout = stdout.into_bytes()?;
|
||||
@ -53,21 +82,17 @@ impl Command for Complete {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(stderr) = stderr {
|
||||
if let Some((handler, stderr_span)) = stderr_handler {
|
||||
cols.push("stderr".to_string());
|
||||
let stderr = stderr.into_bytes()?;
|
||||
if let Ok(st) = String::from_utf8(stderr.item.clone()) {
|
||||
vals.push(Value::String {
|
||||
val: st,
|
||||
span: stderr.span,
|
||||
})
|
||||
} else {
|
||||
vals.push(Value::Binary {
|
||||
val: stderr.item,
|
||||
span: stderr.span,
|
||||
})
|
||||
};
|
||||
}
|
||||
let res = handler.join().map_err(|err| {
|
||||
ShellError::ExternalCommand(
|
||||
"Fail to receive external commands stderr message".to_string(),
|
||||
format!("{err:?}"),
|
||||
stderr_span,
|
||||
)
|
||||
})??;
|
||||
vals.push(res)
|
||||
};
|
||||
|
||||
if let Some(exit_code) = exit_code {
|
||||
let mut v: Vec<_> = exit_code.collect();
|
||||
|
@ -1,8 +1,11 @@
|
||||
use super::run_external::ExternalCommand;
|
||||
use nu_engine::{current_dir, env_to_strings, CallExt};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
ast::{Call, Expr},
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape,
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||
};
|
||||
use std::os::unix::process::CommandExt;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Exec;
|
||||
@ -57,19 +60,11 @@ impl Command for Exec {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn exec(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
use std::os::unix::process::CommandExt;
|
||||
|
||||
use super::run_external::ExternalCommand;
|
||||
use nu_engine::{current_dir, env_to_strings, CallExt};
|
||||
use nu_protocol::ast::Expr;
|
||||
use nu_protocol::Spanned;
|
||||
|
||||
let name: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
let name_span = name.span;
|
||||
|
||||
@ -113,18 +108,3 @@ fn exec(
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn exec(
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
Err(ShellError::GenericError(
|
||||
"Error on exec".to_string(),
|
||||
"exec is not supported on your platform".to_string(),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
mod benchmark;
|
||||
mod complete;
|
||||
#[cfg(unix)]
|
||||
mod exec;
|
||||
mod nu_check;
|
||||
#[cfg(any(
|
||||
@ -9,12 +10,15 @@ mod nu_check;
|
||||
target_os = "windows"
|
||||
))]
|
||||
mod ps;
|
||||
#[cfg(windows)]
|
||||
mod registry_query;
|
||||
mod run_external;
|
||||
mod sys;
|
||||
mod which_;
|
||||
|
||||
pub use benchmark::Benchmark;
|
||||
pub use complete::Complete;
|
||||
#[cfg(unix)]
|
||||
pub use exec::Exec;
|
||||
pub use nu_check::NuCheck;
|
||||
#[cfg(any(
|
||||
@ -24,6 +28,8 @@ pub use nu_check::NuCheck;
|
||||
target_os = "windows"
|
||||
))]
|
||||
pub use ps::Ps;
|
||||
#[cfg(windows)]
|
||||
pub use registry_query::RegistryQuery;
|
||||
pub use run_external::{External, ExternalCommand};
|
||||
pub use sys::Sys;
|
||||
pub use which_::Which;
|
||||
|
273
crates/nu-command/src/system/registry_query.rs
Normal file
273
crates/nu-command/src/system/registry_query.rs
Normal file
@ -0,0 +1,273 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
|
||||
Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
use winreg::{enums::*, RegKey};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RegistryQuery;
|
||||
|
||||
struct RegistryQueryArgs {
|
||||
hkcr: bool,
|
||||
hkcu: bool,
|
||||
hklm: bool,
|
||||
hku: bool,
|
||||
hkpd: bool,
|
||||
hkpt: bool,
|
||||
hkpnls: bool,
|
||||
hkcc: bool,
|
||||
hkdd: bool,
|
||||
hkculs: bool,
|
||||
key: String,
|
||||
}
|
||||
|
||||
impl Command for RegistryQuery {
|
||||
fn name(&self) -> &str {
|
||||
"registry query"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("registry query")
|
||||
.switch("hkcr", "query the hkey_classes_root hive", None)
|
||||
.switch("hkcu", "query the hkey_current_user hive", None)
|
||||
.switch("hklm", "query the hkey_local_machine hive", None)
|
||||
.switch("hku", "query the hkey_users hive", None)
|
||||
.switch("hkpd", "query the hkey_performance_data hive", None)
|
||||
.switch("hkpt", "query the hkey_performance_text hive", None)
|
||||
.switch("hkpnls", "query the hkey_performance_nls_text hive", None)
|
||||
.switch("hkcc", "query the hkey_current_config hive", None)
|
||||
.switch("hkdd", "query the hkey_dyn_data hive", None)
|
||||
.switch(
|
||||
"hkculs",
|
||||
"query the hkey_current_user_local_settings hive",
|
||||
None,
|
||||
)
|
||||
.required("key", SyntaxShape::String, "registry key to query")
|
||||
.optional(
|
||||
"value",
|
||||
SyntaxShape::String,
|
||||
"optionally supply a registry value to query",
|
||||
)
|
||||
.category(Category::System)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Query the Windows registry."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
"Currently supported only on Windows systems."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
registry_query(engine_state, stack, call)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Query the HKEY_CURRENT_USER hive",
|
||||
example: "registry query --hkcu environment",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Query the HKEY_LOCAL_MACHINE hive",
|
||||
example: r"registry query --hklm 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn registry_query(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let registry_key: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
let registry_key_span = ®istry_key.clone().span;
|
||||
let registry_value: Option<Spanned<String>> = call.opt(engine_state, stack, 1)?;
|
||||
|
||||
let reg_params = RegistryQueryArgs {
|
||||
hkcr: call.has_flag("hkcr"),
|
||||
hkcu: call.has_flag("hkcu"),
|
||||
hklm: call.has_flag("hklm"),
|
||||
hku: call.has_flag("hku"),
|
||||
hkpd: call.has_flag("hkpd"),
|
||||
hkpt: call.has_flag("hkpt"),
|
||||
hkpnls: call.has_flag("hkpnls"),
|
||||
hkcc: call.has_flag("hkcc"),
|
||||
hkdd: call.has_flag("hkdd"),
|
||||
hkculs: call.has_flag("hkculs"),
|
||||
key: registry_key.item,
|
||||
};
|
||||
|
||||
let reg_key = get_reg_key(reg_params)?;
|
||||
|
||||
if registry_value.is_none() {
|
||||
let mut reg_values = vec![];
|
||||
for (name, val) in reg_key.enum_values().flatten() {
|
||||
let (nu_value, reg_type) = reg_value_to_nu_value(val);
|
||||
reg_values.push(Value::Record {
|
||||
cols: vec!["name".to_string(), "value".to_string(), "type".to_string()],
|
||||
vals: vec![
|
||||
Value::string(name, Span::test_data()),
|
||||
nu_value,
|
||||
Value::string(format!("{:?}", reg_type), Span::test_data()),
|
||||
],
|
||||
span: *registry_key_span,
|
||||
})
|
||||
}
|
||||
Ok(reg_values.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
} else {
|
||||
match registry_value {
|
||||
Some(value) => {
|
||||
let reg_value = reg_key.get_raw_value(value.item.as_str());
|
||||
match reg_value {
|
||||
Ok(val) => {
|
||||
let (nu_value, reg_type) = reg_value_to_nu_value(val);
|
||||
Ok(Value::Record {
|
||||
cols: vec!["name".to_string(), "value".to_string(), "type".to_string()],
|
||||
vals: vec![
|
||||
Value::string(value.item, Span::test_data()),
|
||||
nu_value,
|
||||
Value::string(format!("{:?}", reg_type), Span::test_data()),
|
||||
],
|
||||
span: value.span,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
Err(_) => Ok(Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"Unable to find registry key/value".to_string(),
|
||||
format!("Registry value: {} was not found", value.item),
|
||||
Some(value.span),
|
||||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
}
|
||||
.into_pipeline_data()),
|
||||
}
|
||||
}
|
||||
None => Ok(Value::nothing(Span::test_data()).into_pipeline_data()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_reg_key(reg_params: RegistryQueryArgs) -> Result<RegKey, ShellError> {
|
||||
let mut key_count = 0;
|
||||
let registry_key = if reg_params.hkcr {
|
||||
key_count += 1;
|
||||
RegKey::predef(HKEY_CLASSES_ROOT).open_subkey(reg_params.key)?
|
||||
} else if reg_params.hkcu {
|
||||
key_count += 1;
|
||||
RegKey::predef(HKEY_CURRENT_USER).open_subkey(reg_params.key)?
|
||||
} else if reg_params.hklm {
|
||||
key_count += 1;
|
||||
RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey(reg_params.key)?
|
||||
} else if reg_params.hku {
|
||||
key_count += 1;
|
||||
RegKey::predef(HKEY_USERS).open_subkey(reg_params.key)?
|
||||
} else if reg_params.hkpd {
|
||||
key_count += 1;
|
||||
RegKey::predef(HKEY_PERFORMANCE_DATA).open_subkey(reg_params.key)?
|
||||
} else if reg_params.hkpt {
|
||||
key_count += 1;
|
||||
RegKey::predef(HKEY_PERFORMANCE_TEXT).open_subkey(reg_params.key)?
|
||||
} else if reg_params.hkpnls {
|
||||
key_count += 1;
|
||||
RegKey::predef(HKEY_PERFORMANCE_NLSTEXT).open_subkey(reg_params.key)?
|
||||
} else if reg_params.hkcc {
|
||||
key_count += 1;
|
||||
RegKey::predef(HKEY_CURRENT_CONFIG).open_subkey(reg_params.key)?
|
||||
} else if reg_params.hkdd {
|
||||
key_count += 1;
|
||||
RegKey::predef(HKEY_DYN_DATA).open_subkey(reg_params.key)?
|
||||
} else if reg_params.hkculs {
|
||||
key_count += 1;
|
||||
RegKey::predef(HKEY_CURRENT_USER_LOCAL_SETTINGS).open_subkey(reg_params.key)?
|
||||
} else {
|
||||
RegKey::predef(HKEY_CURRENT_USER).open_subkey(reg_params.key)?
|
||||
};
|
||||
|
||||
if key_count > 1 {
|
||||
return Err(ShellError::GenericError(
|
||||
"Only one registry key can be specified".into(),
|
||||
"Only one registry key can be specified".into(),
|
||||
Some(Span::test_data()),
|
||||
None,
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
Ok(registry_key)
|
||||
}
|
||||
|
||||
fn reg_value_to_nu_value(
|
||||
reg_value: winreg::RegValue,
|
||||
) -> (nu_protocol::Value, winreg::enums::RegType) {
|
||||
match reg_value.vtype {
|
||||
REG_NONE => (Value::nothing(Span::test_data()), reg_value.vtype),
|
||||
REG_SZ => (
|
||||
Value::string(reg_value.to_string(), Span::test_data()),
|
||||
reg_value.vtype,
|
||||
),
|
||||
REG_EXPAND_SZ => (
|
||||
Value::string(reg_value.to_string(), Span::test_data()),
|
||||
reg_value.vtype,
|
||||
),
|
||||
REG_BINARY => (
|
||||
Value::binary(reg_value.bytes, Span::test_data()),
|
||||
reg_value.vtype,
|
||||
),
|
||||
REG_DWORD => (
|
||||
Value::int(
|
||||
unsafe { *(reg_value.bytes.as_ptr() as *const u32) } as i64,
|
||||
Span::test_data(),
|
||||
),
|
||||
reg_value.vtype,
|
||||
),
|
||||
REG_DWORD_BIG_ENDIAN => (
|
||||
Value::int(
|
||||
unsafe { *(reg_value.bytes.as_ptr() as *const u32) } as i64,
|
||||
Span::test_data(),
|
||||
),
|
||||
reg_value.vtype,
|
||||
),
|
||||
REG_LINK => (
|
||||
Value::string(reg_value.to_string(), Span::test_data()),
|
||||
reg_value.vtype,
|
||||
),
|
||||
REG_MULTI_SZ => (
|
||||
Value::string(reg_value.to_string(), Span::test_data()),
|
||||
reg_value.vtype,
|
||||
),
|
||||
REG_RESOURCE_LIST => (
|
||||
Value::string(reg_value.to_string(), Span::test_data()),
|
||||
reg_value.vtype,
|
||||
),
|
||||
REG_FULL_RESOURCE_DESCRIPTOR => (
|
||||
Value::string(reg_value.to_string(), Span::test_data()),
|
||||
reg_value.vtype,
|
||||
),
|
||||
REG_RESOURCE_REQUIREMENTS_LIST => (
|
||||
Value::string(reg_value.to_string(), Span::test_data()),
|
||||
reg_value.vtype,
|
||||
),
|
||||
REG_QWORD => (
|
||||
Value::int(
|
||||
unsafe { *(reg_value.bytes.as_ptr() as *const u32) } as i64,
|
||||
Span::test_data(),
|
||||
),
|
||||
reg_value.vtype,
|
||||
),
|
||||
}
|
||||
}
|
@ -7,13 +7,15 @@ use nu_protocol::did_you_mean;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::{ast::Call, engine::Command, ShellError, Signature, SyntaxShape, Value};
|
||||
use nu_protocol::{Category, Example, ListStream, PipelineData, RawStream, Span, Spanned};
|
||||
use nu_system::ForegroundProcess;
|
||||
use pathdiff::diff_paths;
|
||||
use std::collections::HashMap;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command as CommandSys, Stdio};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::{self, SyncSender};
|
||||
use std::sync::Arc;
|
||||
|
||||
const OUTPUT_BUFFER_SIZE: usize = 1024;
|
||||
const OUTPUT_BUFFERS_IN_FLIGHT: usize = 3;
|
||||
@ -32,9 +34,9 @@ impl Command for External {
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build(self.name())
|
||||
.switch("redirect-stdout", "redirect-stdout", None)
|
||||
.switch("redirect-stderr", "redirect-stderr", None)
|
||||
.required("command", SyntaxShape::Any, "external comamdn to run")
|
||||
.switch("redirect-stdout", "redirect stdout to the pipeline", None)
|
||||
.switch("redirect-stderr", "redirect stderr to the pipeline", None)
|
||||
.required("command", SyntaxShape::Any, "external command to run")
|
||||
.rest("args", SyntaxShape::Any, "arguments for external command")
|
||||
.category(Category::System)
|
||||
}
|
||||
@ -108,15 +110,22 @@ impl Command for External {
|
||||
redirect_stderr,
|
||||
env_vars: env_vars_str,
|
||||
};
|
||||
command.run_with_input(engine_state, stack, input)
|
||||
command.run_with_input(engine_state, stack, input, false)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Run an external command",
|
||||
example: r#"run-external "echo" "-n" "hello""#,
|
||||
result: None,
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Run an external command",
|
||||
example: r#"run-external "echo" "-n" "hello""#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Redirect stdout from an external command into the pipeline",
|
||||
example: r#"run-external --redirect-stdout "echo" "-n" "hello" | split chars"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,12 +145,16 @@ impl ExternalCommand {
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
input: PipelineData,
|
||||
reconfirm_command_name: bool,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = self.name.span;
|
||||
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
|
||||
let mut process = self.create_process(&input, false, head)?;
|
||||
let mut fg_process = ForegroundProcess::new(
|
||||
self.create_process(&input, false, head)?,
|
||||
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;
|
||||
@ -156,8 +169,7 @@ impl ExternalCommand {
|
||||
// fails to be run as a normal executable:
|
||||
// 1. "shell out" to cmd.exe if the command is a known cmd.exe internal command
|
||||
// 2. Otherwise, use `which-rs` to look for batch files etc. then run those in cmd.exe
|
||||
|
||||
match process.spawn() {
|
||||
match fg_process.spawn() {
|
||||
Err(err) => {
|
||||
// set the default value, maybe we'll override it later
|
||||
child = Err(err);
|
||||
@ -174,7 +186,10 @@ impl ExternalCommand {
|
||||
.any(|&cmd| command_name_upper == cmd);
|
||||
|
||||
if looks_like_cmd_internal {
|
||||
let mut cmd_process = self.create_process(&input, true, head)?;
|
||||
let mut cmd_process = ForegroundProcess::new(
|
||||
self.create_process(&input, true, head)?,
|
||||
engine_state.pipeline_externals_state.clone(),
|
||||
);
|
||||
child = cmd_process.spawn();
|
||||
} else {
|
||||
#[cfg(feature = "which-support")]
|
||||
@ -202,8 +217,11 @@ impl ExternalCommand {
|
||||
item: file_name.to_string_lossy().to_string(),
|
||||
span: self.name.span,
|
||||
};
|
||||
let mut cmd_process = new_command
|
||||
.create_process(&input, true, head)?;
|
||||
let mut cmd_process = ForegroundProcess::new(
|
||||
new_command
|
||||
.create_process(&input, true, head)?,
|
||||
engine_state.pipeline_externals_state.clone(),
|
||||
);
|
||||
child = cmd_process.spawn();
|
||||
}
|
||||
}
|
||||
@ -221,7 +239,7 @@ impl ExternalCommand {
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
child = process.spawn()
|
||||
child = fg_process.spawn()
|
||||
}
|
||||
|
||||
match child {
|
||||
@ -246,8 +264,23 @@ impl ExternalCommand {
|
||||
|
||||
let suggestion = suggest_command(&self.name.item, engine_state);
|
||||
let label = match suggestion {
|
||||
Some(s) => format!("did you mean '{s}'?"),
|
||||
None => "can't run executable".into(),
|
||||
Some(s) => {
|
||||
if reconfirm_command_name {
|
||||
format!(
|
||||
"'{}' was not found, did you mean '{s}'?",
|
||||
self.name.item
|
||||
)
|
||||
} else {
|
||||
format!("did you mean '{s}'?")
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if reconfirm_command_name {
|
||||
format!("executable '{}' was not found", self.name.item)
|
||||
} else {
|
||||
"executable was not found".into()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Err(ShellError::ExternalCommand(
|
||||
@ -273,7 +306,7 @@ impl ExternalCommand {
|
||||
engine_state.config.use_ansi_coloring = false;
|
||||
|
||||
// if there is a string or a stream, that is sent to the pipe std
|
||||
if let Some(mut stdin_write) = child.stdin.take() {
|
||||
if let Some(mut stdin_write) = child.as_mut().stdin.take() {
|
||||
std::thread::spawn(move || {
|
||||
let input = crate::Table::run(
|
||||
&crate::Table,
|
||||
@ -305,55 +338,20 @@ impl ExternalCommand {
|
||||
let redirect_stderr = self.redirect_stderr;
|
||||
let span = self.name.span;
|
||||
let output_ctrlc = ctrlc.clone();
|
||||
let stderr_ctrlc = ctrlc.clone();
|
||||
let (stdout_tx, stdout_rx) = mpsc::sync_channel(OUTPUT_BUFFERS_IN_FLIGHT);
|
||||
let (stderr_tx, stderr_rx) = mpsc::sync_channel(OUTPUT_BUFFERS_IN_FLIGHT);
|
||||
let (exit_code_tx, exit_code_rx) = mpsc::channel();
|
||||
|
||||
let stdout = child.as_mut().stdout.take();
|
||||
let stderr = child.as_mut().stderr.take();
|
||||
// If this external is not the last expression, then its output is piped to a channel
|
||||
// and we create a ListStream that can be consumed
|
||||
//
|
||||
// Create two threads: one for redirect stdout message, and wait for child process to complete.
|
||||
// The other may be created when we want to redirect stderr message.
|
||||
std::thread::spawn(move || {
|
||||
// If this external is not the last expression, then its output is piped to a channel
|
||||
// and we create a ListStream that can be consumed
|
||||
|
||||
if redirect_stderr {
|
||||
let stderr = child.stderr.take().ok_or_else(|| {
|
||||
ShellError::ExternalCommand(
|
||||
"Error taking stderr from external".to_string(),
|
||||
"Redirects need access to stderr of an external command"
|
||||
.to_string(),
|
||||
span,
|
||||
)
|
||||
})?;
|
||||
|
||||
// Stderr is read using the Buffer reader. It will do so until there is an
|
||||
// error or there are no more bytes to read
|
||||
let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, stderr);
|
||||
while let Ok(bytes) = buf_read.fill_buf() {
|
||||
if bytes.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
// The Cow generated from the function represents the conversion
|
||||
// from bytes to String. If no replacements are required, then the
|
||||
// borrowed value is a proper UTF-8 string. The Owned option represents
|
||||
// a string where the values had to be replaced, thus marking it as bytes
|
||||
let bytes = bytes.to_vec();
|
||||
let length = bytes.len();
|
||||
buf_read.consume(length);
|
||||
|
||||
if let Some(ctrlc) = &ctrlc {
|
||||
if ctrlc.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match stderr_tx.send(bytes) {
|
||||
Ok(_) => continue,
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if redirect_stdout {
|
||||
let stdout = child.stdout.take().ok_or_else(|| {
|
||||
let stdout = stdout.ok_or_else(|| {
|
||||
ShellError::ExternalCommand(
|
||||
"Error taking stdout from external".to_string(),
|
||||
"Redirects need access to stdout of an external command"
|
||||
@ -362,36 +360,10 @@ impl ExternalCommand {
|
||||
)
|
||||
})?;
|
||||
|
||||
// Stdout is read using the Buffer reader. It will do so until there is an
|
||||
// error or there are no more bytes to read
|
||||
let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, stdout);
|
||||
while let Ok(bytes) = buf_read.fill_buf() {
|
||||
if bytes.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
// The Cow generated from the function represents the conversion
|
||||
// from bytes to String. If no replacements are required, then the
|
||||
// borrowed value is a proper UTF-8 string. The Owned option represents
|
||||
// a string where the values had to be replaced, thus marking it as bytes
|
||||
let bytes = bytes.to_vec();
|
||||
let length = bytes.len();
|
||||
buf_read.consume(length);
|
||||
|
||||
if let Some(ctrlc) = &ctrlc {
|
||||
if ctrlc.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match stdout_tx.send(bytes) {
|
||||
Ok(_) => continue,
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
read_and_redirect_message(stdout, stdout_tx, ctrlc)
|
||||
}
|
||||
|
||||
match child.wait() {
|
||||
match child.as_mut().wait() {
|
||||
Err(err) => Err(ShellError::ExternalCommand(
|
||||
"External command exited with error".into(),
|
||||
err.to_string(),
|
||||
@ -415,6 +387,24 @@ impl ExternalCommand {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let (stderr_tx, stderr_rx) = mpsc::sync_channel(OUTPUT_BUFFERS_IN_FLIGHT);
|
||||
if redirect_stderr {
|
||||
std::thread::spawn(move || {
|
||||
let stderr = stderr.ok_or_else(|| {
|
||||
ShellError::ExternalCommand(
|
||||
"Error taking stderr from external".to_string(),
|
||||
"Redirects need access to stderr of an external command"
|
||||
.to_string(),
|
||||
span,
|
||||
)
|
||||
})?;
|
||||
|
||||
read_and_redirect_message(stderr, stderr_tx, stderr_ctrlc);
|
||||
Ok::<(), ShellError>(())
|
||||
});
|
||||
}
|
||||
|
||||
let stdout_receiver = ChannelReceiver::new(stdout_rx);
|
||||
let stderr_receiver = ChannelReceiver::new(stderr_rx);
|
||||
let exit_code_receiver = ValueReceiver::new(exit_code_rx);
|
||||
@ -429,11 +419,15 @@ impl ExternalCommand {
|
||||
} else {
|
||||
None
|
||||
},
|
||||
stderr: Some(RawStream::new(
|
||||
Box::new(stderr_receiver),
|
||||
output_ctrlc.clone(),
|
||||
head,
|
||||
)),
|
||||
stderr: if redirect_stderr {
|
||||
Some(RawStream::new(
|
||||
Box::new(stderr_receiver),
|
||||
output_ctrlc.clone(),
|
||||
head,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
exit_code: Some(ListStream::from_stream(
|
||||
Box::new(exit_code_receiver),
|
||||
output_ctrlc,
|
||||
@ -689,6 +683,46 @@ fn remove_quotes(input: String) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// read message from given `reader`, and send out through `sender`.
|
||||
//
|
||||
// `ctrlc` is used to control the process, if ctrl-c is pressed, the read and redirect
|
||||
// process will be breaked.
|
||||
fn read_and_redirect_message<R>(
|
||||
reader: R,
|
||||
sender: SyncSender<Vec<u8>>,
|
||||
ctrlc: Option<Arc<AtomicBool>>,
|
||||
) where
|
||||
R: Read,
|
||||
{
|
||||
// read using the BufferReader. It will do so until there is an
|
||||
// error or there are no more bytes to read
|
||||
let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, reader);
|
||||
while let Ok(bytes) = buf_read.fill_buf() {
|
||||
if bytes.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
// The Cow generated from the function represents the conversion
|
||||
// from bytes to String. If no replacements are required, then the
|
||||
// borrowed value is a proper UTF-8 string. The Owned option represents
|
||||
// a string where the values had to be replaced, thus marking it as bytes
|
||||
let bytes = bytes.to_vec();
|
||||
let length = bytes.len();
|
||||
buf_read.consume(length);
|
||||
|
||||
if let Some(ctrlc) = &ctrlc {
|
||||
if ctrlc.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match sender.send(bytes) {
|
||||
Ok(_) => continue,
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Receiver used for the RawStream
|
||||
// It implements iterator so it can be used as a RawStream
|
||||
struct ChannelReceiver {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -40,3 +40,36 @@ fn alias_hiding_2() {
|
||||
|
||||
assert_eq!(actual.out, "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_fails_with_invalid_name() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
alias 1234 = echo "test"
|
||||
"#
|
||||
));
|
||||
assert!(actual
|
||||
.err
|
||||
.contains("alias name can't be a number or a filesize"));
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
alias 5gib = echo "test"
|
||||
"#
|
||||
));
|
||||
assert!(actual
|
||||
.err
|
||||
.contains("alias name can't be a number or a filesize"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_alone_lists_aliases() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
alias a = 3; alias
|
||||
"#
|
||||
));
|
||||
assert!(actual.out.contains("alias") && actual.out.contains("expansion"));
|
||||
}
|
||||
|
@ -3,6 +3,20 @@ use nu_test_support::nu;
|
||||
use nu_test_support::playground::Playground;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn cd_works_with_in_var() {
|
||||
Playground::setup("cd_test_1", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.root(),
|
||||
r#"
|
||||
"cd_test_1" | cd $in; $env.PWD | path split | last
|
||||
"#
|
||||
);
|
||||
|
||||
assert_eq!("cd_test_1", actual.out);
|
||||
})
|
||||
}
|
||||
|
||||
// FIXME: jt: needs more work
|
||||
#[ignore]
|
||||
#[test]
|
||||
|
@ -5,9 +5,147 @@ fn capture_errors_works() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
do -c {$env.use} | describe
|
||||
do -c {$env.use}
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "error");
|
||||
assert!(actual.err.contains("column_not_found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capture_errors_works_for_external() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
do -c {nu --testbin fail}
|
||||
"#
|
||||
));
|
||||
assert!(actual.err.contains("External command runs to failed"));
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capture_errors_works_for_external_with_pipeline() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
do -c {nu --testbin fail} | echo `text`
|
||||
"#
|
||||
));
|
||||
assert!(actual.err.contains("External command runs to failed"));
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capture_errors_works_for_external_with_semicolon() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
do -c {nu --testbin fail}; echo `text`
|
||||
"#
|
||||
));
|
||||
assert!(actual.err.contains("External command runs to failed"));
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn do_with_semicolon_break_on_failed_external() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
do { nu --not_exist_flag }; `text`
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn ignore_error_with_too_much_stderr_not_hang_nushell() {
|
||||
use nu_test_support::fs::Stub::FileWithContent;
|
||||
use nu_test_support::pipeline;
|
||||
use nu_test_support::playground::Playground;
|
||||
Playground::setup("external with many stderr message", |dirs, sandbox| {
|
||||
let bytes: usize = 81920;
|
||||
let mut large_file_body = String::with_capacity(bytes);
|
||||
for _ in 0..bytes {
|
||||
large_file_body.push('a');
|
||||
}
|
||||
sandbox.with_files(vec![FileWithContent("a_large_file.txt", &large_file_body)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
do -i {sh -c "cat a_large_file.txt 1>&2"} | complete | get stderr
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, large_file_body);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn ignore_error_with_too_much_stdout_not_hang_nushell() {
|
||||
use nu_test_support::fs::Stub::FileWithContent;
|
||||
use nu_test_support::pipeline;
|
||||
use nu_test_support::playground::Playground;
|
||||
Playground::setup("external with many stdout message", |dirs, sandbox| {
|
||||
let bytes: usize = 81920;
|
||||
let mut large_file_body = String::with_capacity(bytes);
|
||||
for _ in 0..bytes {
|
||||
large_file_body.push('a');
|
||||
}
|
||||
sandbox.with_files(vec![FileWithContent("a_large_file.txt", &large_file_body)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
do -i {sh -c "cat a_large_file.txt"} | complete | get stdout
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, large_file_body);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn ignore_error_with_both_stdout_stderr_messages_not_hang_nushell() {
|
||||
use nu_test_support::fs::Stub::FileWithContent;
|
||||
use nu_test_support::playground::Playground;
|
||||
Playground::setup(
|
||||
"external with many stdout and stderr messages",
|
||||
|dirs, sandbox| {
|
||||
let script_body = r#"
|
||||
x=$(printf '=%.0s' {1..40960})
|
||||
echo $x
|
||||
echo $x 1>&2
|
||||
"#;
|
||||
let mut expect_body = String::new();
|
||||
for _ in 0..40960 {
|
||||
expect_body.push('=');
|
||||
}
|
||||
|
||||
sandbox.with_files(vec![FileWithContent("test.sh", script_body)]);
|
||||
|
||||
// check for stdout
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
do -i {bash test.sh} | complete | get stdout | str trim
|
||||
"#
|
||||
));
|
||||
assert_eq!(actual.out, expect_body);
|
||||
// check for stderr
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
do -i {bash test.sh} | complete | get stderr | str trim
|
||||
"#
|
||||
));
|
||||
assert_eq!(actual.out, expect_body);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -65,3 +65,17 @@ fn gets_first_row_when_no_amount_given() {
|
||||
assert_eq!(actual.out, "1");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gets_first_row_as_list_when_amount_given() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
[1, 2, 3]
|
||||
| first 1
|
||||
| describe
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "list<int>");
|
||||
}
|
||||
|
39
crates/nu-command/tests/commands/glob.rs
Normal file
39
crates/nu-command/tests/commands/glob.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use nu_test_support::fs::Stub::EmptyFile;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn empty_glob_pattern_triggers_error() {
|
||||
Playground::setup("glob_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
EmptyFile("yehuda.txt"),
|
||||
EmptyFile("jonathan.txt"),
|
||||
EmptyFile("andres.txt"),
|
||||
]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
"glob ''",
|
||||
);
|
||||
|
||||
assert!(actual.err.contains("must not be empty"));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonempty_glob_lists_matching_paths() {
|
||||
Playground::setup("glob_sanity_star", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
EmptyFile("yehuda.txt"),
|
||||
EmptyFile("jonathan.txt"),
|
||||
EmptyFile("andres.txt"),
|
||||
]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
pipeline("glob '*' | length"),
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "3");
|
||||
})
|
||||
}
|
@ -64,3 +64,17 @@ fn requests_more_rows_than_table_has() {
|
||||
|
||||
assert_eq!(actual.out, "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gets_last_row_as_list_when_amount_given() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
[1, 2, 3]
|
||||
| last 1
|
||||
| describe
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "list<int>");
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ fn lines() {
|
||||
| lines
|
||||
| skip while $it != "[dependencies]"
|
||||
| skip 1
|
||||
| first 1
|
||||
| first
|
||||
| split column "="
|
||||
| get column1.0
|
||||
| str trim
|
||||
|
@ -282,6 +282,66 @@ fn modulo() {
|
||||
assert_eq!(actual.out, "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_multiplication_math() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
1mb * 2
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "1.9 MiB");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_multiplication_float_math() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
1mb * 1.2
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "1.1 MiB");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_float_floor_division_math() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
1mb // 3.0
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "325.5 KiB");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_division_math() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
1mb / 4
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "244.1 KiB");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_float_division_math() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
1mb / 3.1
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "315.0 KiB");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_math() {
|
||||
let actual = nu!(
|
||||
@ -342,6 +402,18 @@ fn duration_decimal_math_with_all_units() {
|
||||
assert_eq!(actual.out, "1wk 3day 8hr 10min 16sec 121ms 11µs 12ns");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_decimal_dans_test() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
3.14sec
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "3sec 140ms");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_math_with_negative() {
|
||||
let actual = nu!(
|
||||
|
@ -24,6 +24,7 @@ mod flatten;
|
||||
mod format;
|
||||
mod g;
|
||||
mod get;
|
||||
mod glob;
|
||||
mod group_by;
|
||||
mod hash_;
|
||||
mod headers;
|
||||
@ -74,6 +75,7 @@ mod split_by;
|
||||
mod split_column;
|
||||
mod split_row;
|
||||
mod str_;
|
||||
mod table;
|
||||
mod take;
|
||||
mod touch;
|
||||
mod transpose;
|
||||
|
@ -154,7 +154,7 @@ fn parses_tsv() {
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
open caco3_plastics.tsv
|
||||
| first 1
|
||||
| first
|
||||
| get origin
|
||||
"#
|
||||
));
|
||||
@ -216,7 +216,7 @@ fn parses_arrow_ipc() {
|
||||
r#"
|
||||
open-df caco3_plastics.arrow
|
||||
| into nu
|
||||
| first 1
|
||||
| first
|
||||
| get origin
|
||||
"#
|
||||
));
|
||||
|
@ -4,7 +4,7 @@ use nu_test_support::nu;
|
||||
fn can_get_reverse_first() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats",
|
||||
"ls | sort-by name | reverse | first 1 | get name | str trim "
|
||||
"ls | sort-by name | reverse | first | get name | str trim "
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "utf16.ini");
|
||||
|
@ -186,6 +186,7 @@ fn external_arg_with_variable_name() {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn external_command_escape_args() {
|
||||
Playground::setup("external failed command with semicolon", |dirs, _| {
|
||||
|
@ -12,7 +12,7 @@ fn by_column() {
|
||||
| split column "="
|
||||
| sort-by column1
|
||||
| skip 1
|
||||
| first 1
|
||||
| first
|
||||
| get column1
|
||||
| str trim
|
||||
"#
|
||||
@ -33,7 +33,7 @@ fn by_invalid_column() {
|
||||
| split column "="
|
||||
| sort-by ColumnThatDoesNotExist
|
||||
| skip 1
|
||||
| first 1
|
||||
| first
|
||||
| get column1
|
||||
| str trim
|
||||
"#
|
||||
@ -69,7 +69,7 @@ fn sort_primitive_values() {
|
||||
| skip 1
|
||||
| first 6
|
||||
| sort-by
|
||||
| first 1
|
||||
| first
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -284,12 +284,12 @@ fn source_env_is_scoped() {
|
||||
|
||||
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
|
||||
|
||||
assert!(actual.err.contains("can't run executable"));
|
||||
assert!(actual.err.contains("executable was not found"));
|
||||
|
||||
let inp = &[r#"source-env spam.nu"#, r#"nor-similar-to-this"#];
|
||||
|
||||
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
|
||||
|
||||
assert!(actual.err.contains("can't run executable"));
|
||||
assert!(actual.err.contains("executable was not found"));
|
||||
})
|
||||
}
|
||||
|
@ -172,18 +172,6 @@ fn from_nothing() {
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_error() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
do -c {$env.use} | into string
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "nu::shell::column_not_found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_into_string() {
|
||||
let actual = nu!(
|
||||
|
162
crates/nu-command/tests/commands/table.rs
Normal file
162
crates/nu-command/tests/commands/table.rs
Normal file
@ -0,0 +1,162 @@
|
||||
use nu_test_support::nu;
|
||||
|
||||
#[test]
|
||||
fn table_0() {
|
||||
let actual = nu!(r#"[[a b, c]; [1 2 3] [4 5 [1 2 3]]] | table"#);
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
"╭───┬───┬───┬────────────────╮\
|
||||
│ # │ a │ b │ c │\
|
||||
├───┼───┼───┼────────────────┤\
|
||||
│ 0 │ 1 │ 2 │ 3 │\
|
||||
│ 1 │ 4 │ 5 │ [list 3 items] │\
|
||||
╰───┴───┴───┴────────────────╯"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_collapse_0() {
|
||||
let actual = nu!(r#"[[a b, c]; [1 2 3] [4 5 [1 2 3]]] | table --collapse"#);
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
"\u{1b}[37m╭───\u{1b}[39m\u{1b}[37m┬───\u{1b}[39m\u{1b}[37m┬───╮\u{1b}[39m\u{1b}[37m│\u{1b}[39m a \u{1b}[37m│\u{1b}[39m b \u{1b}[37m│\u{1b}[39m c \u{1b}[37m│\u{1b}[39m\u{1b}[37m ───\u{1b}[39m\u{1b}[37m ───\u{1b}[39m\u{1b}[37m ─── \u{1b}[39m\u{1b}[37m│\u{1b}[39m 1 \u{1b}[37m│\u{1b}[39m 2 \u{1b}[37m│\u{1b}[39m 3 \u{1b}[37m│\u{1b}[39m\u{1b}[37m ───\u{1b}[39m\u{1b}[37m ───\u{1b}[39m\u{1b}[37m ─── \u{1b}[39m\u{1b}[37m│\u{1b}[39m 4 \u{1b}[37m│\u{1b}[39m 5 \u{1b}[37m│\u{1b}[39m 1 \u{1b}[37m│\u{1b}[39m\u{1b}[37m│\u{1b}[39m \u{1b}[37m│\u{1b}[39m \u{1b}[37m ─── \u{1b}[39m\u{1b}[37m│\u{1b}[39m \u{1b}[37m│\u{1b}[39m \u{1b}[37m│\u{1b}[39m 2 \u{1b}[37m│\u{1b}[39m\u{1b}[37m│\u{1b}[39m \u{1b}[37m│\u{1b}[39m \u{1b}[37m ─── \u{1b}[39m\u{1b}[37m│\u{1b}[39m \u{1b}[37m│\u{1b}[39m \u{1b}[37m│\u{1b}[39m 3 \u{1b}[37m│\u{1b}[39m\u{1b}[37m╰───\u{1b}[39m\u{1b}[37m┴───\u{1b}[39m\u{1b}[37m┴───╯\u{1b}[39m"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_expand_0() {
|
||||
let actual = nu!(r#"[[a b, c]; [1 2 3] [4 5 [1 2 3]]] | table --expand"#);
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
"╭───┬───┬───┬───────────╮\
|
||||
│ # │ a │ b │ c │\
|
||||
├───┼───┼───┼───────────┤\
|
||||
│ 0 │ 1 │ 2 │ 3 │\
|
||||
│ 1 │ 4 │ 5 │ ╭───┬───╮ │\
|
||||
│ │ │ │ │ 0 │ 1 │ │\
|
||||
│ │ │ │ │ 1 │ 2 │ │\
|
||||
│ │ │ │ │ 2 │ 3 │ │\
|
||||
│ │ │ │ ╰───┴───╯ │\
|
||||
╰───┴───┴───┴───────────╯"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_expand_deep_0() {
|
||||
let actual = nu!(r#"[[a b, c]; [1 2 3] [4 5 [1 2 [1 2 3]]]] | table --expand --expand-deep=1"#);
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
"╭───┬───┬───┬────────────────────────╮\
|
||||
│ # │ a │ b │ c │\
|
||||
├───┼───┼───┼────────────────────────┤\
|
||||
│ 0 │ 1 │ 2 │ 3 │\
|
||||
│ 1 │ 4 │ 5 │ ╭───┬────────────────╮ │\
|
||||
│ │ │ │ │ 0 │ 1 │ │\
|
||||
│ │ │ │ │ 1 │ 2 │ │\
|
||||
│ │ │ │ │ 2 │ [list 3 items] │ │\
|
||||
│ │ │ │ ╰───┴────────────────╯ │\
|
||||
╰───┴───┴───┴────────────────────────╯"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_expand_deep_1() {
|
||||
let actual = nu!(r#"[[a b, c]; [1 2 3] [4 5 [1 2 [1 2 3]]]] | table --expand --expand-deep=0"#);
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
"╭───┬───┬───┬────────────────╮\
|
||||
│ # │ a │ b │ c │\
|
||||
├───┼───┼───┼────────────────┤\
|
||||
│ 0 │ 1 │ 2 │ 3 │\
|
||||
│ 1 │ 4 │ 5 │ [list 3 items] │\
|
||||
╰───┴───┴───┴────────────────╯"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_expand_flatten_0() {
|
||||
let actual = nu!(r#"[[a b, c]; [1 2 3] [4 5 [1 2 [1 1 1]]]] | table --expand --flatten "#);
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
"╭───┬───┬───┬───────────────╮\
|
||||
│ # │ a │ b │ c │\
|
||||
├───┼───┼───┼───────────────┤\
|
||||
│ 0 │ 1 │ 2 │ 3 │\
|
||||
│ 1 │ 4 │ 5 │ ╭───┬───────╮ │\
|
||||
│ │ │ │ │ 0 │ 1 │ │\
|
||||
│ │ │ │ │ 1 │ 2 │ │\
|
||||
│ │ │ │ │ 2 │ 1 1 1 │ │\
|
||||
│ │ │ │ ╰───┴───────╯ │\
|
||||
╰───┴───┴───┴───────────────╯"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_expand_flatten_1() {
|
||||
let actual = nu!(
|
||||
r#"[[a b, c]; [1 2 3] [4 5 [1 2 [1 1 1]]]] | table --expand --flatten --flatten-separator=,"#
|
||||
);
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
"╭───┬───┬───┬───────────────╮\
|
||||
│ # │ a │ b │ c │\
|
||||
├───┼───┼───┼───────────────┤\
|
||||
│ 0 │ 1 │ 2 │ 3 │\
|
||||
│ 1 │ 4 │ 5 │ ╭───┬───────╮ │\
|
||||
│ │ │ │ │ 0 │ 1 │ │\
|
||||
│ │ │ │ │ 1 │ 2 │ │\
|
||||
│ │ │ │ │ 2 │ 1,1,1 │ │\
|
||||
│ │ │ │ ╰───┴───────╯ │\
|
||||
╰───┴───┴───┴───────────────╯"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_expand_flatten_and_deep_1() {
|
||||
let actual = nu!(
|
||||
r#"[[a b, c]; [1 2 3] [4 5 [1 2 [1 [1 1 1] 1]]]] | table --expand --expand-deep=2 --flatten --flatten-separator=,"#
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
"╭───┬───┬───┬────────────────────────────────╮\
|
||||
│ # │ a │ b │ c │\
|
||||
├───┼───┼───┼────────────────────────────────┤\
|
||||
│ 0 │ 1 │ 2 │ 3 │\
|
||||
│ 1 │ 4 │ 5 │ ╭───┬────────────────────────╮ │\
|
||||
│ │ │ │ │ 0 │ 1 │ │\
|
||||
│ │ │ │ │ 1 │ 2 │ │\
|
||||
│ │ │ │ │ 2 │ ╭───┬────────────────╮ │ │\
|
||||
│ │ │ │ │ │ │ 0 │ 1 │ │ │\
|
||||
│ │ │ │ │ │ │ 1 │ [list 3 items] │ │ │\
|
||||
│ │ │ │ │ │ │ 2 │ 1 │ │ │\
|
||||
│ │ │ │ │ │ ╰───┴────────────────╯ │ │\
|
||||
│ │ │ │ ╰───┴────────────────────────╯ │\
|
||||
╰───┴───┴───┴────────────────────────────────╯"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn external_with_too_much_stdout_should_not_hang_nu() {
|
||||
use nu_test_support::fs::Stub::FileWithContent;
|
||||
use nu_test_support::pipeline;
|
||||
use nu_test_support::playground::Playground;
|
||||
Playground::setup("external with too much stdout", |dirs, sandbox| {
|
||||
let bytes: usize = 81920;
|
||||
let mut large_file_body = String::with_capacity(bytes);
|
||||
for _ in 0..bytes {
|
||||
large_file_body.push('a');
|
||||
}
|
||||
sandbox.with_files(vec![FileWithContent("a_large_file.txt", &large_file_body)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
cat a_large_file.txt | table
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, large_file_body);
|
||||
})
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use chrono::{Date, DateTime, Local, TimeZone};
|
||||
use chrono::{Date, DateTime, Local};
|
||||
use nu_test_support::fs::Stub;
|
||||
use nu_test_support::nu;
|
||||
use nu_test_support::playground::Playground;
|
||||
@ -32,185 +32,6 @@ fn creates_two_files() {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_modified_time_of_file() {
|
||||
Playground::setup("change_time_test_3", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -t 201908241230.30 file.txt"
|
||||
);
|
||||
|
||||
let path = dirs.test().join("file.txt");
|
||||
|
||||
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
|
||||
let actual_time: DateTime<Local> =
|
||||
DateTime::from(path.metadata().unwrap().modified().unwrap());
|
||||
|
||||
assert_eq!(time, actual_time);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_and_change_modified_time_of_file() {
|
||||
Playground::setup("change_time_test_4", |dirs, _sandbox| {
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -t 201908241230 i_will_be_created.txt"
|
||||
);
|
||||
|
||||
let path = dirs.test().join("i_will_be_created.txt");
|
||||
assert!(path.exists());
|
||||
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 0);
|
||||
|
||||
let actual_time: DateTime<Local> =
|
||||
DateTime::from(path.metadata().unwrap().modified().unwrap());
|
||||
|
||||
assert_eq!(time, actual_time);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_modified_time_of_file_no_year() {
|
||||
Playground::setup("change_time_test_5", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -t 08241230.12 file.txt"
|
||||
);
|
||||
|
||||
let path = dirs.test().join("file.txt");
|
||||
|
||||
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 12);
|
||||
let actual_time: DateTime<Local> =
|
||||
DateTime::from(path.metadata().unwrap().modified().unwrap());
|
||||
|
||||
assert_eq!(time, actual_time);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_modified_time_of_file_no_year_no_second() {
|
||||
Playground::setup("change_time_test_6", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -t 08241230 file.txt"
|
||||
);
|
||||
|
||||
let path = dirs.test().join("file.txt");
|
||||
|
||||
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 0);
|
||||
let actual_time: DateTime<Local> =
|
||||
DateTime::from(path.metadata().unwrap().modified().unwrap());
|
||||
|
||||
assert_eq!(time, actual_time);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_modified_time_of_files() {
|
||||
Playground::setup("change_time_test_7", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
Stub::EmptyFile("file.txt"),
|
||||
Stub::EmptyFile("file2.txt"),
|
||||
]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -t 1908241230.30 file.txt file2.txt"
|
||||
);
|
||||
|
||||
let path = dirs.test().join("file.txt");
|
||||
|
||||
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
|
||||
let actual_time: DateTime<Local> =
|
||||
DateTime::from(path.metadata().unwrap().modified().unwrap());
|
||||
|
||||
assert_eq!(time, actual_time);
|
||||
|
||||
let path = dirs.test().join("file2.txt");
|
||||
|
||||
let actual_time: DateTime<Local> =
|
||||
DateTime::from(path.metadata().unwrap().modified().unwrap());
|
||||
|
||||
assert_eq!(time, actual_time);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_if_change_modified_time_of_file_with_invalid_timestamp() {
|
||||
Playground::setup("change_time_test_8", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
|
||||
|
||||
let mut outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -t 1908241230.3030 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -t 1908241230.3O file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -t 08241230.3 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -t 8241230 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -t 01908241230 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -t 082412.3012 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -t 0824.123012 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -t 08.24123012 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -t 0.824123012 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_modified_time_of_file_to_today() {
|
||||
Playground::setup("change_time_test_9", |dirs, sandbox| {
|
||||
@ -232,234 +53,6 @@ fn change_modified_time_of_file_to_today() {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_modified_time_to_date() {
|
||||
Playground::setup("change_time_test_10", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
r#"touch -m -d "August 24, 2019; 12:30:30" file.txt"#
|
||||
);
|
||||
|
||||
let path = dirs.test().join("file.txt");
|
||||
|
||||
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
|
||||
let actual_time: DateTime<Local> =
|
||||
DateTime::from(path.metadata().unwrap().modified().unwrap());
|
||||
|
||||
assert_eq!(time, actual_time);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_modified_time_to_time_of_reference() {
|
||||
Playground::setup("change_time_test_11", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
Stub::EmptyFile("file.txt"),
|
||||
Stub::EmptyFile("reference.txt"),
|
||||
]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
r#"touch -m -t 201908241230.30 reference.txt"#
|
||||
);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
r#"touch -m -r reference.txt file.txt"#
|
||||
);
|
||||
|
||||
let path = dirs.test().join("file.txt");
|
||||
let ref_path = dirs.test().join("reference.txt");
|
||||
|
||||
let time: DateTime<Local> = DateTime::from(path.metadata().unwrap().modified().unwrap());
|
||||
let ref_time: DateTime<Local> =
|
||||
DateTime::from(ref_path.metadata().unwrap().modified().unwrap());
|
||||
|
||||
assert_eq!(time, ref_time);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_access_time_of_file() {
|
||||
Playground::setup("change_time_test_12", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -t 201908241230.30 file.txt"
|
||||
);
|
||||
|
||||
let path = dirs.test().join("file.txt");
|
||||
|
||||
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
|
||||
let actual_time: DateTime<Local> =
|
||||
DateTime::from(path.metadata().unwrap().accessed().unwrap());
|
||||
|
||||
assert_eq!(time, actual_time);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_and_change_access_time_of_file() {
|
||||
Playground::setup("change_time_test_13", |dirs, _sandbox| {
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -t 201908241230 i_will_be_created.txt"
|
||||
);
|
||||
|
||||
let path = dirs.test().join("i_will_be_created.txt");
|
||||
assert!(path.exists());
|
||||
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 0);
|
||||
|
||||
let actual_time: DateTime<Local> =
|
||||
DateTime::from(path.metadata().unwrap().accessed().unwrap());
|
||||
|
||||
assert_eq!(time, actual_time);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_access_time_of_file_no_year() {
|
||||
Playground::setup("change_time_test_14", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -t 08241230.12 file.txt"
|
||||
);
|
||||
|
||||
let path = dirs.test().join("file.txt");
|
||||
|
||||
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 12);
|
||||
let actual_time: DateTime<Local> =
|
||||
DateTime::from(path.metadata().unwrap().accessed().unwrap());
|
||||
|
||||
assert_eq!(time, actual_time);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_access_time_of_file_no_year_no_second() {
|
||||
Playground::setup("change_time_test_15", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -t 08241230 file.txt"
|
||||
);
|
||||
|
||||
let path = dirs.test().join("file.txt");
|
||||
|
||||
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 0);
|
||||
let actual_time: DateTime<Local> =
|
||||
DateTime::from(path.metadata().unwrap().accessed().unwrap());
|
||||
|
||||
assert_eq!(time, actual_time);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_access_time_of_files() {
|
||||
Playground::setup("change_time_test_16", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
Stub::EmptyFile("file.txt"),
|
||||
Stub::EmptyFile("file2.txt"),
|
||||
]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -t 1908241230.30 file.txt file2.txt"
|
||||
);
|
||||
|
||||
let path = dirs.test().join("file.txt");
|
||||
|
||||
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
|
||||
let actual_time: DateTime<Local> =
|
||||
DateTime::from(path.metadata().unwrap().accessed().unwrap());
|
||||
|
||||
assert_eq!(time, actual_time);
|
||||
|
||||
let path = dirs.test().join("file2.txt");
|
||||
|
||||
let actual_time: DateTime<Local> =
|
||||
DateTime::from(path.metadata().unwrap().accessed().unwrap());
|
||||
|
||||
assert_eq!(time, actual_time);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_if_change_access_time_of_file_with_invalid_timestamp() {
|
||||
Playground::setup("change_time_test_17", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
|
||||
|
||||
let mut outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -t 1908241230.3030 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -t 1908241230.3O file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -t 08241230.3 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -t 8241230 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -t 01908241230 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -t 082412.3012 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -t 0824.123012 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -t 08.24123012 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -t 0.824123012 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_access_time_of_file_to_today() {
|
||||
Playground::setup("change_time_test_18", |dirs, sandbox| {
|
||||
@ -481,246 +74,6 @@ fn change_access_time_of_file_to_today() {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_access_time_to_date() {
|
||||
Playground::setup("change_time_test_19", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
r#"touch -a -d "August 24, 2019; 12:30:30" file.txt"#
|
||||
);
|
||||
|
||||
let path = dirs.test().join("file.txt");
|
||||
|
||||
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
|
||||
let actual_time: DateTime<Local> =
|
||||
DateTime::from(path.metadata().unwrap().accessed().unwrap());
|
||||
|
||||
assert_eq!(time, actual_time);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_access_time_to_time_of_reference() {
|
||||
Playground::setup("change_time_test_20", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
Stub::EmptyFile("file.txt"),
|
||||
Stub::EmptyFile("reference.txt"),
|
||||
]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
r#"touch -a -t 201908241230.30 reference.txt"#
|
||||
);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
r#"touch -a -r reference.txt file.txt"#
|
||||
);
|
||||
|
||||
let path = dirs.test().join("file.txt");
|
||||
let ref_path = dirs.test().join("reference.txt");
|
||||
|
||||
let time: DateTime<Local> = DateTime::from(path.metadata().unwrap().accessed().unwrap());
|
||||
let ref_time: DateTime<Local> =
|
||||
DateTime::from(ref_path.metadata().unwrap().accessed().unwrap());
|
||||
|
||||
assert_eq!(time, ref_time);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_modified_and_access_time_of_file() {
|
||||
Playground::setup("change_time_test_21", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -a -t 201908241230.30 file.txt"
|
||||
);
|
||||
|
||||
let path = dirs.test().join("file.txt");
|
||||
let metadata = path.metadata().unwrap();
|
||||
|
||||
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
|
||||
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
|
||||
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
|
||||
|
||||
assert_eq!(time, atime);
|
||||
assert_eq!(time, mtime);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_and_change_modified_and_access_time_of_file() {
|
||||
Playground::setup("change_time_test_22", |dirs, _sandbox| {
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -t 201908241230 i_will_be_created.txt"
|
||||
);
|
||||
|
||||
let path = dirs.test().join("i_will_be_created.txt");
|
||||
assert!(path.exists());
|
||||
|
||||
let metadata = path.metadata().unwrap();
|
||||
|
||||
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 0);
|
||||
|
||||
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
|
||||
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
|
||||
|
||||
assert_eq!(time, atime);
|
||||
assert_eq!(time, mtime);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_modified_and_access_time_of_file_no_year() {
|
||||
Playground::setup("change_time_test_23", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -m -t 08241230.12 file.txt"
|
||||
);
|
||||
|
||||
let metadata = dirs.test().join("file.txt").metadata().unwrap();
|
||||
|
||||
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 12);
|
||||
|
||||
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
|
||||
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
|
||||
|
||||
assert_eq!(time, atime);
|
||||
assert_eq!(time, mtime);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_modified_and_access_time_of_file_no_year_no_second() {
|
||||
Playground::setup("change_time_test_24", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -t 08241230 file.txt"
|
||||
);
|
||||
|
||||
let metadata = dirs.test().join("file.txt").metadata().unwrap();
|
||||
|
||||
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 0);
|
||||
|
||||
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
|
||||
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
|
||||
|
||||
assert_eq!(time, atime);
|
||||
assert_eq!(time, mtime);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_modified_and_access_time_of_files() {
|
||||
Playground::setup("change_time_test_25", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
Stub::EmptyFile("file.txt"),
|
||||
Stub::EmptyFile("file2.txt"),
|
||||
]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -m -t 1908241230.30 file.txt file2.txt"
|
||||
);
|
||||
|
||||
let metadata = dirs.test().join("file.txt").metadata().unwrap();
|
||||
|
||||
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
|
||||
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
|
||||
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
|
||||
|
||||
assert_eq!(time, atime);
|
||||
assert_eq!(time, mtime);
|
||||
|
||||
let metadata = dirs.test().join("file2.txt").metadata().unwrap();
|
||||
|
||||
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
|
||||
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
|
||||
|
||||
assert_eq!(time, atime);
|
||||
assert_eq!(time, mtime);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_if_change_modified_and_access_time_of_file_with_invalid_timestamp() {
|
||||
Playground::setup("change_time_test_26", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
|
||||
|
||||
let mut outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -t 1908241230.3030 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -a -m -t 1908241230.3O file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -t 08241230.3 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -a -t 8241230 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -t 01908241230 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -a -t 082412.3012 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -a -t 0824.123012 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -a -t 08.24123012 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
"touch -m -a -t 0.824123012 file.txt"
|
||||
);
|
||||
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_modified_and_access_time_of_file_to_today() {
|
||||
Playground::setup("change_time_test_27", |dirs, sandbox| {
|
||||
@ -743,97 +96,12 @@ fn change_modified_and_access_time_of_file_to_today() {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_modified_and_access_time_to_date() {
|
||||
Playground::setup("change_time_test_28", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
r#"touch -d "August 24, 2019; 12:30:30" file.txt"#
|
||||
);
|
||||
|
||||
let metadata = dirs.test().join("file.txt").metadata().unwrap();
|
||||
|
||||
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
|
||||
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
|
||||
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
|
||||
|
||||
assert_eq!(time, atime);
|
||||
assert_eq!(time, mtime);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_modified_and_access_time_to_time_of_reference() {
|
||||
Playground::setup("change_time_test_29", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
Stub::EmptyFile("file.txt"),
|
||||
Stub::EmptyFile("reference.txt"),
|
||||
]);
|
||||
|
||||
let path = dirs.test().join("file.txt");
|
||||
let ref_path = dirs.test().join("reference.txt");
|
||||
|
||||
// Set the same time for the modified and access time of the reference file
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
r#"touch -a -m -t 201908241230.30 reference.txt"#
|
||||
);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
r#"touch -r reference.txt file.txt"#
|
||||
);
|
||||
|
||||
let atime: DateTime<Local> = DateTime::from(path.metadata().unwrap().accessed().unwrap());
|
||||
let ref_atime: DateTime<Local> =
|
||||
DateTime::from(ref_path.metadata().unwrap().accessed().unwrap());
|
||||
|
||||
assert_eq!(atime, ref_atime);
|
||||
|
||||
let mtime: DateTime<Local> = DateTime::from(path.metadata().unwrap().modified().unwrap());
|
||||
let ref_mtime: DateTime<Local> =
|
||||
DateTime::from(ref_path.metadata().unwrap().modified().unwrap());
|
||||
|
||||
assert_eq!(mtime, ref_mtime);
|
||||
|
||||
// Set different time for the modified and access time of the reference file
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
r#"touch -a -t 201908241230.30 reference.txt"#
|
||||
);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
r#"touch -m -t 202009251340.40 reference.txt"#
|
||||
);
|
||||
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
r#"touch -a -m -r reference.txt file.txt"#
|
||||
);
|
||||
|
||||
let atime: DateTime<Local> = DateTime::from(path.metadata().unwrap().accessed().unwrap());
|
||||
let ref_atime: DateTime<Local> =
|
||||
DateTime::from(ref_path.metadata().unwrap().accessed().unwrap());
|
||||
|
||||
assert_eq!(atime, ref_atime);
|
||||
|
||||
let mtime: DateTime<Local> = DateTime::from(path.metadata().unwrap().modified().unwrap());
|
||||
let ref_mtime: DateTime<Local> =
|
||||
DateTime::from(ref_path.metadata().unwrap().modified().unwrap());
|
||||
|
||||
assert_eq!(mtime, ref_mtime);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_create_file_if_it_not_exists() {
|
||||
Playground::setup("change_time_test_28", |dirs, _sandbox| {
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
r#"touch -c -d "August 24, 2019; 12:30:30" file.txt"#
|
||||
r#"touch -c file.txt"#
|
||||
);
|
||||
|
||||
let path = dirs.test().join("file.txt");
|
||||
@ -850,20 +118,3 @@ fn not_create_file_if_it_not_exists() {
|
||||
assert!(!path.exists());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_timestamp() {
|
||||
Playground::setup("test_invalid_timestamp", |dirs, _sandbox| {
|
||||
let outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
r#"touch -t 20220729. file.txt"#
|
||||
);
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
|
||||
let outcome = nu!(
|
||||
cwd: dirs.test(),
|
||||
r#"touch -t 20220729120099 file.txt"#
|
||||
);
|
||||
assert!(outcome.err.contains("input has an invalid timestamp"));
|
||||
})
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use nu_test_support::pipeline;
|
||||
fn filters_by_unit_size_comparison() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats",
|
||||
"ls | where size > 1kib | sort-by size | get name | first 1 | str trim"
|
||||
"ls | where size > 1kib | sort-by size | get name | first | str trim"
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "cargo_sample.toml");
|
||||
@ -103,7 +103,7 @@ fn binary_operator_comparisons() {
|
||||
open sample.db
|
||||
| get ints
|
||||
| where z != 1
|
||||
| first 1
|
||||
| first
|
||||
| get z
|
||||
"#
|
||||
));
|
||||
|
@ -6,7 +6,7 @@ use nu_test_support::{nu, pipeline};
|
||||
fn table_to_csv_text_and_from_csv_text_back_into_table() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats",
|
||||
"open caco3_plastics.csv | to csv | from csv | first 1 | get origin "
|
||||
"open caco3_plastics.csv | to csv | from csv | first | get origin "
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "SPAIN");
|
||||
|
@ -103,6 +103,51 @@ fn to_nuon_escaping2() {
|
||||
assert_eq!(actual.out, "hello\\world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_nuon_escaping3() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
["hello\\world"]
|
||||
| to nuon
|
||||
| from nuon
|
||||
| $in == [hello\world]
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_nuon_escaping4() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
["hello\"world"]
|
||||
| to nuon
|
||||
| from nuon
|
||||
| $in == ["hello\"world"]
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_nuon_escaping5() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
{s: "hello\"world"}
|
||||
| to nuon
|
||||
| from nuon
|
||||
| $in == {s: "hello\"world"}
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_nuon_negative_int() {
|
||||
let actual = nu!(
|
||||
|
@ -6,7 +6,7 @@ use nu_test_support::{nu, pipeline};
|
||||
fn table_to_tsv_text_and_from_tsv_text_back_into_table() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats",
|
||||
"open caco3_plastics.tsv | to tsv | from tsv | first 1 | get origin"
|
||||
"open caco3_plastics.tsv | to tsv | from tsv | first | get origin"
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "SPAIN");
|
||||
@ -16,7 +16,7 @@ fn table_to_tsv_text_and_from_tsv_text_back_into_table() {
|
||||
fn table_to_tsv_text_and_from_tsv_text_back_into_table_using_csv_separator() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats",
|
||||
r"open caco3_plastics.tsv | to tsv | from csv --separator '\t' | first 1 | get origin"
|
||||
r"open caco3_plastics.tsv | to tsv | from csv --separator '\t' | first | get origin"
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "SPAIN");
|
||||
|
@ -5,13 +5,13 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-engine"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-engine"
|
||||
version = "0.69.0"
|
||||
version = "0.70.0"
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.69.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.69.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.69.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.69.0" }
|
||||
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.70.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.70.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.70.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.70.0" }
|
||||
|
||||
chrono = { version="0.4.21", features=["serde"] }
|
||||
sysinfo = "0.26.2"
|
||||
|
@ -4,7 +4,7 @@ use nu_protocol::{
|
||||
ast::{Block, Call, Expr, Expression, Operator},
|
||||
engine::{EngineState, Stack, Visibility},
|
||||
Config, HistoryFileFormat, IntoInterruptiblePipelineData, IntoPipelineData, ListStream,
|
||||
PipelineData, Range, ShellError, Span, Spanned, SyntaxShape, Unit, Value, VarId,
|
||||
PipelineData, Range, RawStream, ShellError, Span, Spanned, SyntaxShape, Unit, Value, VarId,
|
||||
ENV_VARIABLE_ID,
|
||||
};
|
||||
use nu_utils::stdout_write_all_and_flush;
|
||||
@ -201,7 +201,7 @@ fn eval_external(
|
||||
input: PipelineData,
|
||||
redirect_stdout: bool,
|
||||
redirect_stderr: bool,
|
||||
) -> Result<(PipelineData, bool), ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let decl_id = engine_state
|
||||
.find_decl("run-external".as_bytes(), &[])
|
||||
.ok_or(ShellError::ExternalNotSupported(head.span))?;
|
||||
@ -238,54 +238,7 @@ fn eval_external(
|
||||
))
|
||||
}
|
||||
|
||||
// when the external command doesn't redirect output, we eagerly check the result
|
||||
// and find if the command runs to failed.
|
||||
let mut runs_to_failed = false;
|
||||
let result = command.run(engine_state, stack, &call, input)?;
|
||||
if let PipelineData::ExternalStream {
|
||||
stdout: None,
|
||||
stderr,
|
||||
mut exit_code,
|
||||
span,
|
||||
metadata,
|
||||
} = result
|
||||
{
|
||||
let exit_code = exit_code.take();
|
||||
match exit_code {
|
||||
Some(exit_code_stream) => {
|
||||
let ctrlc = exit_code_stream.ctrlc.clone();
|
||||
let exit_code: Vec<Value> = exit_code_stream.into_iter().collect();
|
||||
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
|
||||
// if exit_code is not 0, it indicates error occured, return back Err.
|
||||
if *code != 0 {
|
||||
runs_to_failed = true;
|
||||
}
|
||||
}
|
||||
Ok((
|
||||
PipelineData::ExternalStream {
|
||||
stdout: None,
|
||||
stderr,
|
||||
exit_code: Some(ListStream::from_stream(exit_code.into_iter(), ctrlc)),
|
||||
span,
|
||||
metadata,
|
||||
},
|
||||
runs_to_failed,
|
||||
))
|
||||
}
|
||||
None => Ok((
|
||||
PipelineData::ExternalStream {
|
||||
stdout: None,
|
||||
stderr,
|
||||
exit_code: None,
|
||||
span,
|
||||
metadata,
|
||||
},
|
||||
runs_to_failed,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok((result, runs_to_failed))
|
||||
}
|
||||
command.run(engine_state, stack, &call, input)
|
||||
}
|
||||
|
||||
pub fn eval_expression(
|
||||
@ -383,7 +336,6 @@ pub fn eval_expression(
|
||||
false,
|
||||
false,
|
||||
)?
|
||||
.0
|
||||
.into_value(span))
|
||||
}
|
||||
Expr::DateTime(dt) => Ok(Value::Date {
|
||||
@ -408,7 +360,7 @@ pub fn eval_expression(
|
||||
|
||||
match op {
|
||||
Operator::And => {
|
||||
if !lhs.is_true() {
|
||||
if lhs.is_false() {
|
||||
Ok(Value::Bool {
|
||||
val: false,
|
||||
span: expr.span,
|
||||
@ -682,7 +634,6 @@ pub fn eval_expression_with_input(
|
||||
redirect_stdout: bool,
|
||||
redirect_stderr: bool,
|
||||
) -> Result<(PipelineData, bool), ShellError> {
|
||||
let mut external_failed = false;
|
||||
match expr {
|
||||
Expression {
|
||||
expr: Expr::Call(call),
|
||||
@ -702,7 +653,7 @@ pub fn eval_expression_with_input(
|
||||
expr: Expr::ExternalCall(head, args),
|
||||
..
|
||||
} => {
|
||||
let external_result = eval_external(
|
||||
input = eval_external(
|
||||
engine_state,
|
||||
stack,
|
||||
head,
|
||||
@ -711,8 +662,6 @@ pub fn eval_expression_with_input(
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
)?;
|
||||
input = external_result.0;
|
||||
external_failed = external_result.1
|
||||
}
|
||||
|
||||
Expression {
|
||||
@ -728,9 +677,88 @@ pub fn eval_expression_with_input(
|
||||
elem => {
|
||||
input = eval_expression(engine_state, stack, elem)?.into_pipeline_data();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok((input, external_failed))
|
||||
Ok(might_consume_external_result(input))
|
||||
}
|
||||
|
||||
// if the result is ExternalStream without redirecting output.
|
||||
// that indicates we have no more commands to execute currently.
|
||||
// we can try to catch and detect if external command runs to failed.
|
||||
//
|
||||
// This is useful to commands with semicolon, we can detect errors early to avoid
|
||||
// commands after semicolon running.
|
||||
fn might_consume_external_result(input: PipelineData) -> (PipelineData, bool) {
|
||||
let mut runs_to_failed = false;
|
||||
if let PipelineData::ExternalStream {
|
||||
stdout: None,
|
||||
stderr,
|
||||
mut exit_code,
|
||||
span,
|
||||
metadata,
|
||||
} = input
|
||||
{
|
||||
let exit_code = exit_code.take();
|
||||
|
||||
// Note:
|
||||
// In run-external's implementation detail, the result sender thread
|
||||
// send out stderr message first, then stdout message, then exit_code.
|
||||
//
|
||||
// In this clause, we already make sure that `stdout` is None
|
||||
// But not the case of `stderr`, so if `stderr` is not None
|
||||
// We need to consume stderr message before reading external commands' exit code.
|
||||
//
|
||||
// Or we'll never have a chance to read exit_code if stderr producer produce too much stderr message.
|
||||
// So we consume stderr stream and rebuild it.
|
||||
let stderr = stderr.map(|stderr_stream| {
|
||||
let stderr_ctrlc = stderr_stream.ctrlc.clone();
|
||||
let stderr_span = stderr_stream.span;
|
||||
let stderr_bytes = match stderr_stream.into_bytes() {
|
||||
Err(_) => vec![],
|
||||
Ok(bytes) => bytes.item,
|
||||
};
|
||||
RawStream::new(
|
||||
Box::new(vec![Ok(stderr_bytes)].into_iter()),
|
||||
stderr_ctrlc,
|
||||
stderr_span,
|
||||
)
|
||||
});
|
||||
|
||||
match exit_code {
|
||||
Some(exit_code_stream) => {
|
||||
let ctrlc = exit_code_stream.ctrlc.clone();
|
||||
let exit_code: Vec<Value> = exit_code_stream.into_iter().collect();
|
||||
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
|
||||
// if exit_code is not 0, it indicates error occured, return back Err.
|
||||
if *code != 0 {
|
||||
runs_to_failed = true;
|
||||
}
|
||||
}
|
||||
(
|
||||
PipelineData::ExternalStream {
|
||||
stdout: None,
|
||||
stderr,
|
||||
exit_code: Some(ListStream::from_stream(exit_code.into_iter(), ctrlc)),
|
||||
span,
|
||||
metadata,
|
||||
},
|
||||
runs_to_failed,
|
||||
)
|
||||
}
|
||||
None => (
|
||||
PipelineData::ExternalStream {
|
||||
stdout: None,
|
||||
stderr,
|
||||
exit_code: None,
|
||||
span,
|
||||
metadata,
|
||||
},
|
||||
runs_to_failed,
|
||||
),
|
||||
}
|
||||
} else {
|
||||
(input, false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_block(
|
||||
@ -1538,20 +1566,36 @@ fn compute(size: i64, unit: Unit, span: Span) -> Value {
|
||||
val: size * 1000 * 1000 * 1000,
|
||||
span,
|
||||
},
|
||||
Unit::Minute => Value::Duration {
|
||||
val: size * 1000 * 1000 * 1000 * 60,
|
||||
span,
|
||||
Unit::Minute => match size.checked_mul(1000 * 1000 * 1000 * 60) {
|
||||
Some(val) => Value::Duration { val, span },
|
||||
None => Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"duration too large".into(),
|
||||
"duration too large".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
},
|
||||
},
|
||||
Unit::Hour => Value::Duration {
|
||||
val: size * 1000 * 1000 * 1000 * 60 * 60,
|
||||
span,
|
||||
Unit::Hour => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60) {
|
||||
Some(val) => Value::Duration { val, span },
|
||||
None => Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"duration too large".into(),
|
||||
"duration too large".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
},
|
||||
},
|
||||
Unit::Day => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24) {
|
||||
Some(val) => Value::Duration { val, span },
|
||||
None => Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"day duration too large".into(),
|
||||
"day duration too large".into(),
|
||||
"duration too large".into(),
|
||||
"duration too large".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
@ -1562,8 +1606,8 @@ fn compute(size: i64, unit: Unit, span: Span) -> Value {
|
||||
Some(val) => Value::Duration { val, span },
|
||||
None => Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"week duration too large".into(),
|
||||
"week duration too large".into(),
|
||||
"duration too large".into(),
|
||||
"duration too large".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nu-glob"
|
||||
version = "0.69.0"
|
||||
version = "0.70.0"
|
||||
authors = ["The Nushell Project Developers", "The Rust Project Developers"]
|
||||
license = "MIT/Apache-2.0"
|
||||
description = """
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-json"
|
||||
version = "0.69.0"
|
||||
version = "0.70.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -21,5 +21,5 @@ num-traits = "0.2.14"
|
||||
serde = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-path = { path="../nu-path", version = "0.69.0" }
|
||||
nu-path = { path="../nu-path", version = "0.70.0" }
|
||||
serde_json = "1.0"
|
||||
|
@ -5,18 +5,19 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-parser"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-parser"
|
||||
version = "0.69.0"
|
||||
version = "0.70.0"
|
||||
|
||||
[dependencies]
|
||||
bytesize = "1.1.0"
|
||||
chrono = "0.4.21"
|
||||
itertools = "0.10"
|
||||
miette = "5.1.0"
|
||||
thiserror = "1.0.31"
|
||||
serde_json = "1.0"
|
||||
nu-path = {path = "../nu-path", version = "0.69.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
|
||||
nu-plugin = { path = "../nu-plugin", optional = true, version = "0.69.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.69.0" }
|
||||
nu-path = {path = "../nu-path", version = "0.70.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
|
||||
nu-plugin = { path = "../nu-plugin", optional = true, version = "0.70.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.70.0" }
|
||||
log = "0.4"
|
||||
|
||||
[features]
|
||||
|
@ -112,6 +112,10 @@ pub enum ParseError {
|
||||
#[diagnostic(code(nu::parser::variable_not_valid), url(docsrs))]
|
||||
VariableNotValid(#[label = "variable name can't contain spaces or quotes"] Span),
|
||||
|
||||
#[error("Alias name not supported.")]
|
||||
#[diagnostic(code(nu::parser::variable_not_valid), url(docsrs))]
|
||||
AliasNotValid(#[label = "alias name can't be a number or a filesize"] Span),
|
||||
|
||||
#[error("Module not found.")]
|
||||
#[diagnostic(
|
||||
code(nu::parser::module_not_found),
|
||||
@ -344,6 +348,7 @@ impl ParseError {
|
||||
ParseError::MultipleRestParams(s) => *s,
|
||||
ParseError::VariableNotFound(s) => *s,
|
||||
ParseError::VariableNotValid(s) => *s,
|
||||
ParseError::AliasNotValid(s) => *s,
|
||||
ParseError::ModuleNotFound(s) => *s,
|
||||
ParseError::CyclicalModuleImport(_, s) => *s,
|
||||
ParseError::ModuleOrOverlayNotFound(s) => *s,
|
||||
|
@ -2,7 +2,7 @@ use nu_path::canonicalize_with;
|
||||
use nu_protocol::{
|
||||
ast::{
|
||||
Argument, Block, Call, Expr, Expression, ImportPattern, ImportPatternHead,
|
||||
ImportPatternMember, Pipeline,
|
||||
ImportPatternMember, PathMember, Pipeline,
|
||||
},
|
||||
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
|
||||
span, BlockId, Exportable, Module, PositionalArg, Span, Spanned, SyntaxShape, Type,
|
||||
@ -544,16 +544,72 @@ pub fn parse_alias(
|
||||
spans: &[Span],
|
||||
expand_aliases_denylist: &[usize],
|
||||
) -> (Pipeline, Option<ParseError>) {
|
||||
let (name_span, split_id) =
|
||||
// if the call is "alias", turn it into "print $nu.scope.aliases"
|
||||
if spans.len() == 1 {
|
||||
let head = Expression {
|
||||
expr: Expr::Var(nu_protocol::NU_VARIABLE_ID),
|
||||
span: Span::new(0, 0),
|
||||
ty: Type::Any,
|
||||
custom_completion: None,
|
||||
};
|
||||
let tail = vec![
|
||||
PathMember::String {
|
||||
val: "scope".to_string(),
|
||||
span: Span::new(0, 0),
|
||||
},
|
||||
PathMember::String {
|
||||
val: "aliases".to_string(),
|
||||
span: Span::new(0, 0),
|
||||
},
|
||||
];
|
||||
let expr = Expression {
|
||||
ty: Type::Any,
|
||||
expr: Expr::FullCellPath(Box::new(nu_protocol::ast::FullCellPath { head, tail })),
|
||||
span: Span::new(0, 0),
|
||||
custom_completion: None,
|
||||
};
|
||||
if let Some(decl_id) = working_set.find_decl(b"print", &Type::Any) {
|
||||
let print_call = Expr::Call(Box::new(Call {
|
||||
head: spans[0],
|
||||
arguments: vec![Argument::Positional(expr)],
|
||||
decl_id,
|
||||
redirect_stdout: true,
|
||||
redirect_stderr: false,
|
||||
}));
|
||||
return (
|
||||
Pipeline::from_vec(vec![Expression {
|
||||
expr: print_call,
|
||||
span: spans[0],
|
||||
ty: Type::Any,
|
||||
custom_completion: None,
|
||||
}]),
|
||||
None,
|
||||
);
|
||||
}
|
||||
return (Pipeline::from_vec(vec![expr]), None);
|
||||
}
|
||||
|
||||
let (name_span, alias_name, split_id) =
|
||||
if spans.len() > 1 && working_set.get_span_contents(spans[0]) == b"export" {
|
||||
(spans[1], 2)
|
||||
(spans[1], spans.get(2), 2)
|
||||
} else {
|
||||
(spans[0], 1)
|
||||
(spans[0], spans.get(1), 1)
|
||||
};
|
||||
|
||||
let name = working_set.get_span_contents(name_span);
|
||||
|
||||
if name == b"alias" {
|
||||
if let Some(alias_name) = alias_name {
|
||||
let alias_name = String::from_utf8_lossy(working_set.get_span_contents(*alias_name));
|
||||
if alias_name.parse::<bytesize::ByteSize>().is_ok() || alias_name.parse::<f64>().is_ok()
|
||||
{
|
||||
return (
|
||||
Pipeline::from_vec(vec![garbage(name_span)]),
|
||||
Some(ParseError::AliasNotValid(name_span)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((span, err)) = check_name(working_set, spans) {
|
||||
return (Pipeline::from_vec(vec![garbage(*span)]), Some(err));
|
||||
}
|
||||
|
@ -2175,23 +2175,50 @@ pub fn parse_duration(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_duration_bytes(bytes: &[u8], span: Span) -> Option<Expression> {
|
||||
fn parse_decimal_str_to_number(decimal: &str) -> Option<i64> {
|
||||
let string_to_parse = format!("0.{}", decimal);
|
||||
if let Ok(x) = string_to_parse.parse::<f64>() {
|
||||
return Some((1_f64 / x) as i64);
|
||||
// Borrowed from libm at https://github.com/rust-lang/libm/blob/master/src/math/modf.rs
|
||||
pub fn modf(x: f64) -> (f64, f64) {
|
||||
let rv2: f64;
|
||||
let mut u = x.to_bits();
|
||||
let e = ((u >> 52 & 0x7ff) as i32) - 0x3ff;
|
||||
|
||||
/* no fractional part */
|
||||
if e >= 52 {
|
||||
rv2 = x;
|
||||
if e == 0x400 && (u << 12) != 0 {
|
||||
/* nan */
|
||||
return (x, rv2);
|
||||
}
|
||||
None
|
||||
u &= 1 << 63;
|
||||
return (f64::from_bits(u), rv2);
|
||||
}
|
||||
|
||||
if bytes.is_empty() || (!bytes[0].is_ascii_digit() && bytes[0] != b'-') {
|
||||
/* no integral part*/
|
||||
if e < 0 {
|
||||
u &= 1 << 63;
|
||||
rv2 = f64::from_bits(u);
|
||||
return (x, rv2);
|
||||
}
|
||||
|
||||
let mask = ((!0) >> 12) >> e;
|
||||
if (u & mask) == 0 {
|
||||
rv2 = x;
|
||||
u &= 1 << 63;
|
||||
return (f64::from_bits(u), rv2);
|
||||
}
|
||||
u &= !mask;
|
||||
rv2 = f64::from_bits(u);
|
||||
(x - rv2, rv2)
|
||||
}
|
||||
|
||||
pub fn parse_duration_bytes(num_with_unit_bytes: &[u8], span: Span) -> Option<Expression> {
|
||||
if num_with_unit_bytes.is_empty()
|
||||
|| (!num_with_unit_bytes[0].is_ascii_digit() && num_with_unit_bytes[0] != b'-')
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let token = String::from_utf8_lossy(bytes).to_string();
|
||||
|
||||
let upper = token.to_uppercase();
|
||||
|
||||
let num_with_unit = String::from_utf8_lossy(num_with_unit_bytes).to_string();
|
||||
let uppercase_num_with_unit = num_with_unit.to_uppercase();
|
||||
let unit_groups = [
|
||||
(Unit::Nanosecond, "NS", None),
|
||||
(Unit::Microsecond, "US", Some((Unit::Nanosecond, 1000))),
|
||||
@ -2202,34 +2229,33 @@ pub fn parse_duration_bytes(bytes: &[u8], span: Span) -> Option<Expression> {
|
||||
(Unit::Day, "DAY", Some((Unit::Minute, 1440))),
|
||||
(Unit::Week, "WK", Some((Unit::Day, 7))),
|
||||
];
|
||||
if let Some(unit) = unit_groups.iter().find(|&x| upper.ends_with(x.1)) {
|
||||
let mut lhs = token;
|
||||
|
||||
if let Some(unit) = unit_groups
|
||||
.iter()
|
||||
.find(|&x| uppercase_num_with_unit.ends_with(x.1))
|
||||
{
|
||||
let mut lhs = num_with_unit;
|
||||
for _ in 0..unit.1.len() {
|
||||
lhs.pop();
|
||||
}
|
||||
|
||||
let input: Vec<&str> = lhs.split('.').collect();
|
||||
let (value, unit_to_use) = match &input[..] {
|
||||
[number_str] => (number_str.parse::<i64>().ok(), unit.0),
|
||||
[number_str, decimal_part_str] => match unit.2 {
|
||||
Some(unit_to_convert_to) => match (
|
||||
number_str.parse::<i64>(),
|
||||
parse_decimal_str_to_number(decimal_part_str),
|
||||
) {
|
||||
(Ok(number), Some(decimal_part)) => (
|
||||
Some(
|
||||
(number * unit_to_convert_to.1) + (unit_to_convert_to.1 / decimal_part),
|
||||
),
|
||||
unit_to_convert_to.0,
|
||||
),
|
||||
_ => (None, unit.0),
|
||||
},
|
||||
None => (None, unit.0),
|
||||
},
|
||||
_ => (None, unit.0),
|
||||
let (decimal_part, number_part) = modf(match lhs.parse::<f64>() {
|
||||
Ok(x) => x,
|
||||
Err(_) => return None,
|
||||
});
|
||||
|
||||
let (num, unit_to_use) = match unit.2 {
|
||||
Some(unit_to_convert_to) => (
|
||||
Some(
|
||||
((number_part * unit_to_convert_to.1 as f64)
|
||||
+ (decimal_part * unit_to_convert_to.1 as f64)) as i64,
|
||||
),
|
||||
unit_to_convert_to.0,
|
||||
),
|
||||
None => (Some(number_part as i64), unit.0),
|
||||
};
|
||||
|
||||
if let Some(x) = value {
|
||||
if let Some(x) = num {
|
||||
trace!("-- found {} {:?}", x, unit_to_use);
|
||||
|
||||
let lhs_span = Span::new(span.start, span.start + lhs.len());
|
||||
@ -2262,33 +2288,32 @@ pub fn parse_filesize(
|
||||
working_set: &StateWorkingSet,
|
||||
span: Span,
|
||||
) -> (Expression, Option<ParseError>) {
|
||||
trace!("parsing: duration");
|
||||
|
||||
fn parse_decimal_str_to_number(decimal: &str) -> Option<i64> {
|
||||
let string_to_parse = format!("0.{}", decimal);
|
||||
if let Ok(x) = string_to_parse.parse::<f64>() {
|
||||
return Some((1_f64 / x) as i64);
|
||||
}
|
||||
None
|
||||
}
|
||||
trace!("parsing: filesize");
|
||||
|
||||
let bytes = working_set.get_span_contents(span);
|
||||
|
||||
if bytes.is_empty() || (!bytes[0].is_ascii_digit() && bytes[0] != b'-') {
|
||||
return (
|
||||
match parse_filesize_bytes(bytes, span) {
|
||||
Some(expression) => (expression, None),
|
||||
None => (
|
||||
garbage(span),
|
||||
Some(ParseError::Mismatch(
|
||||
"filesize".into(),
|
||||
"non-filesize unit".into(),
|
||||
span,
|
||||
)),
|
||||
);
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_filesize_bytes(num_with_unit_bytes: &[u8], span: Span) -> Option<Expression> {
|
||||
if num_with_unit_bytes.is_empty()
|
||||
|| (!num_with_unit_bytes[0].is_ascii_digit() && num_with_unit_bytes[0] != b'-')
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let token = String::from_utf8_lossy(bytes).to_string();
|
||||
|
||||
let upper = token.to_uppercase();
|
||||
|
||||
let num_with_unit = String::from_utf8_lossy(num_with_unit_bytes).to_string();
|
||||
let uppercase_num_with_unit = num_with_unit.to_uppercase();
|
||||
let unit_groups = [
|
||||
(Unit::Kilobyte, "KB", Some((Unit::Byte, 1000))),
|
||||
(Unit::Megabyte, "MB", Some((Unit::Kilobyte, 1000))),
|
||||
@ -2306,69 +2331,58 @@ pub fn parse_filesize(
|
||||
(Unit::Zebibyte, "ZIB", Some((Unit::Exbibyte, 1024))),
|
||||
(Unit::Byte, "B", None),
|
||||
];
|
||||
if let Some(unit) = unit_groups.iter().find(|&x| upper.ends_with(x.1)) {
|
||||
let mut lhs = token;
|
||||
|
||||
if let Some(unit) = unit_groups
|
||||
.iter()
|
||||
.find(|&x| uppercase_num_with_unit.ends_with(x.1))
|
||||
{
|
||||
let mut lhs = num_with_unit;
|
||||
for _ in 0..unit.1.len() {
|
||||
lhs.pop();
|
||||
}
|
||||
|
||||
let input: Vec<&str> = lhs.split('.').collect();
|
||||
let (value, unit_to_use) = match &input[..] {
|
||||
[number_str] => (number_str.parse::<i64>().ok(), unit.0),
|
||||
[number_str, decimal_part_str] => match unit.2 {
|
||||
Some(unit_to_convert_to) => match (
|
||||
number_str.parse::<i64>(),
|
||||
parse_decimal_str_to_number(decimal_part_str),
|
||||
) {
|
||||
(Ok(number), Some(decimal_part)) => (
|
||||
Some(
|
||||
(number * unit_to_convert_to.1) + (unit_to_convert_to.1 / decimal_part),
|
||||
),
|
||||
unit_to_convert_to.0,
|
||||
),
|
||||
_ => (None, unit.0),
|
||||
},
|
||||
None => (None, unit.0),
|
||||
},
|
||||
_ => (None, unit.0),
|
||||
let (decimal_part, number_part) = modf(match lhs.parse::<f64>() {
|
||||
Ok(x) => x,
|
||||
Err(_) => return None,
|
||||
});
|
||||
|
||||
let (num, unit_to_use) = match unit.2 {
|
||||
Some(unit_to_convert_to) => (
|
||||
Some(
|
||||
((number_part * unit_to_convert_to.1 as f64)
|
||||
+ (decimal_part * unit_to_convert_to.1 as f64)) as i64,
|
||||
),
|
||||
unit_to_convert_to.0,
|
||||
),
|
||||
None => (Some(number_part as i64), unit.0),
|
||||
};
|
||||
|
||||
if let Some(x) = value {
|
||||
if let Some(x) = num {
|
||||
trace!("-- found {} {:?}", x, unit_to_use);
|
||||
|
||||
let lhs_span = Span::new(span.start, span.start + lhs.len());
|
||||
let unit_span = Span::new(span.start + lhs.len(), span.end);
|
||||
return (
|
||||
Expression {
|
||||
expr: Expr::ValueWithUnit(
|
||||
Box::new(Expression {
|
||||
expr: Expr::Int(x),
|
||||
span: lhs_span,
|
||||
ty: Type::Number,
|
||||
custom_completion: None,
|
||||
}),
|
||||
Spanned {
|
||||
item: unit_to_use,
|
||||
span: unit_span,
|
||||
},
|
||||
),
|
||||
span,
|
||||
ty: Type::Filesize,
|
||||
custom_completion: None,
|
||||
},
|
||||
None,
|
||||
);
|
||||
return Some(Expression {
|
||||
expr: Expr::ValueWithUnit(
|
||||
Box::new(Expression {
|
||||
expr: Expr::Int(x),
|
||||
span: lhs_span,
|
||||
ty: Type::Number,
|
||||
custom_completion: None,
|
||||
}),
|
||||
Spanned {
|
||||
item: unit_to_use,
|
||||
span: unit_span,
|
||||
},
|
||||
),
|
||||
span,
|
||||
ty: Type::Filesize,
|
||||
custom_completion: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
garbage(span),
|
||||
Some(ParseError::Mismatch(
|
||||
"filesize".into(),
|
||||
"non-filesize unit".into(),
|
||||
span,
|
||||
)),
|
||||
)
|
||||
None
|
||||
}
|
||||
|
||||
pub fn parse_glob_pattern(
|
||||
@ -5353,6 +5367,15 @@ fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression)
|
||||
custom_completion: None,
|
||||
}));
|
||||
|
||||
output.push(Argument::Named((
|
||||
Spanned {
|
||||
item: "keep-env".to_string(),
|
||||
span: Span::new(0, 0),
|
||||
},
|
||||
None,
|
||||
None,
|
||||
)));
|
||||
|
||||
// The containing, synthetic call to `collect`.
|
||||
// We don't want to have a real span as it will confuse flattening
|
||||
// The args are where we'll get the real info
|
||||
|
@ -101,11 +101,14 @@ pub fn math_result_type(
|
||||
(Type::Float, Type::Int) => (Type::Float, None),
|
||||
(Type::Int, Type::Float) => (Type::Float, None),
|
||||
(Type::Float, Type::Float) => (Type::Float, None),
|
||||
|
||||
(Type::Filesize, Type::Int) => (Type::Filesize, None),
|
||||
(Type::Int, Type::Filesize) => (Type::Filesize, None),
|
||||
(Type::Duration, Type::Int) => (Type::Filesize, None),
|
||||
(Type::Int, Type::Duration) => (Type::Filesize, None),
|
||||
(Type::Filesize, Type::Float) => (Type::Filesize, None),
|
||||
(Type::Float, Type::Filesize) => (Type::Filesize, None),
|
||||
(Type::Duration, Type::Int) => (Type::Duration, None),
|
||||
(Type::Int, Type::Duration) => (Type::Duration, None),
|
||||
(Type::Duration, Type::Float) => (Type::Duration, None),
|
||||
(Type::Float, Type::Duration) => (Type::Duration, None),
|
||||
|
||||
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None),
|
||||
(Type::Custom(a), _) => (Type::Custom(a.to_string()), None),
|
||||
@ -157,10 +160,11 @@ pub fn math_result_type(
|
||||
(Type::Int, Type::Float) => (Type::Float, None),
|
||||
(Type::Float, Type::Float) => (Type::Float, None),
|
||||
(Type::Filesize, Type::Filesize) => (Type::Float, None),
|
||||
(Type::Duration, Type::Duration) => (Type::Float, None),
|
||||
|
||||
(Type::Filesize, Type::Int) => (Type::Filesize, None),
|
||||
(Type::Filesize, Type::Float) => (Type::Filesize, None),
|
||||
(Type::Duration, Type::Duration) => (Type::Float, None),
|
||||
(Type::Duration, Type::Int) => (Type::Duration, None),
|
||||
(Type::Duration, Type::Float) => (Type::Duration, None),
|
||||
|
||||
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None),
|
||||
(Type::Custom(a), _) => (Type::Custom(a.to_string()), None),
|
||||
@ -187,10 +191,11 @@ pub fn math_result_type(
|
||||
(Type::Int, Type::Float) => (Type::Int, None),
|
||||
(Type::Float, Type::Float) => (Type::Int, None),
|
||||
(Type::Filesize, Type::Filesize) => (Type::Int, None),
|
||||
(Type::Duration, Type::Duration) => (Type::Int, None),
|
||||
|
||||
(Type::Filesize, Type::Int) => (Type::Filesize, None),
|
||||
(Type::Filesize, Type::Float) => (Type::Filesize, None),
|
||||
(Type::Duration, Type::Duration) => (Type::Int, None),
|
||||
(Type::Duration, Type::Int) => (Type::Duration, None),
|
||||
(Type::Duration, Type::Float) => (Type::Duration, None),
|
||||
|
||||
(Type::Any, _) => (Type::Any, None),
|
||||
(_, Type::Any) => (Type::Any, None),
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-path"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-path"
|
||||
version = "0.69.0"
|
||||
version = "0.70.0"
|
||||
|
||||
[dependencies]
|
||||
dirs-next = "2.0.0"
|
||||
|
@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-plugin"
|
||||
version = "0.69.0"
|
||||
version = "0.70.0"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.3.3"
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.69.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.70.0" }
|
||||
serde = {version = "1.0.143", features = ["derive"]}
|
||||
serde_json = { version = "1.0"}
|
||||
byte-order = "0.3.0"
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-pretty-hex"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-pretty-hex"
|
||||
version = "0.69.0"
|
||||
version = "0.70.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-protocol"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-protocol"
|
||||
version = "0.69.0"
|
||||
version = "0.70.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-utils = { path = "../nu-utils", version = "0.69.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.69.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.69.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.70.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.70.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.70.0" }
|
||||
|
||||
byte-unit = "4.0.9"
|
||||
chrono = { version="0.4.21", features=["serde"] }
|
||||
@ -20,7 +20,7 @@ chrono-humanize = "0.2.1"
|
||||
fancy-regex = "0.10.0"
|
||||
indexmap = { version="1.7", features=["serde-1"] }
|
||||
miette = { version = "5.1.0", features = ["fancy"] }
|
||||
num-format = "0.4.0"
|
||||
num-format = "0.4.3"
|
||||
serde = {version = "1.0.130", features = ["derive"]}
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
sys-locale = "0.2.0"
|
||||
|
@ -77,7 +77,7 @@ pub struct Config {
|
||||
pub rm_always_trash: bool,
|
||||
pub shell_integration: bool,
|
||||
pub buffer_editor: String,
|
||||
pub disable_table_indexes: bool,
|
||||
pub table_index_mode: TableIndexMode,
|
||||
pub cd_with_abbreviations: bool,
|
||||
pub case_sensitive_completions: bool,
|
||||
pub enable_external_completion: bool,
|
||||
@ -114,7 +114,7 @@ impl Default for Config {
|
||||
rm_always_trash: false,
|
||||
shell_integration: false,
|
||||
buffer_editor: String::new(),
|
||||
disable_table_indexes: false,
|
||||
table_index_mode: TableIndexMode::Always,
|
||||
cd_with_abbreviations: false,
|
||||
case_sensitive_completions: false,
|
||||
enable_external_completion: true,
|
||||
@ -145,6 +145,16 @@ pub enum HistoryFileFormat {
|
||||
PlainText,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub enum TableIndexMode {
|
||||
/// Always show indexes
|
||||
Always,
|
||||
/// Never show indexes
|
||||
Never,
|
||||
/// Show indexes when a table has "index" column
|
||||
Auto,
|
||||
}
|
||||
|
||||
/// A Table view configuration, for a situation where
|
||||
/// we need to limit cell width in order to adjust for a terminal size.
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
@ -370,11 +380,19 @@ impl Value {
|
||||
eprintln!("$config.buffer_editor is not a string")
|
||||
}
|
||||
}
|
||||
"disable_table_indexes" => {
|
||||
if let Ok(b) = value.as_bool() {
|
||||
config.disable_table_indexes = b;
|
||||
"table_index_mode" => {
|
||||
if let Ok(b) = value.as_string() {
|
||||
let val_str = b.to_lowercase();
|
||||
match val_str.as_ref() {
|
||||
"always" => config.table_index_mode = TableIndexMode::Always,
|
||||
"never" => config.table_index_mode = TableIndexMode::Never,
|
||||
"auto" => config.table_index_mode = TableIndexMode::Auto,
|
||||
_ => eprintln!(
|
||||
"$config.table_index_mode must be a never, always or auto"
|
||||
),
|
||||
}
|
||||
} else {
|
||||
eprintln!("$config.disable_table_indexes is not a bool")
|
||||
eprintln!("$config.table_index_mode is not a string")
|
||||
}
|
||||
}
|
||||
"cd_with_abbreviations" => {
|
||||
|
@ -10,7 +10,10 @@ use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
sync::{atomic::AtomicBool, Arc, Mutex},
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicU32},
|
||||
Arc, Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
static PWD_ENV: &str = "PWD";
|
||||
@ -80,6 +83,7 @@ pub struct EngineState {
|
||||
pub env_vars: EnvVars,
|
||||
pub previous_env_vars: HashMap<String, Value>,
|
||||
pub config: Config,
|
||||
pub pipeline_externals_state: Arc<(AtomicU32, AtomicU32)>,
|
||||
pub repl_buffer_state: Arc<Mutex<Option<String>>>,
|
||||
pub repl_operation_queue: Arc<Mutex<VecDeque<ReplOperation>>>,
|
||||
#[cfg(feature = "plugin")]
|
||||
@ -121,6 +125,7 @@ impl EngineState {
|
||||
env_vars: EnvVars::from([(DEFAULT_OVERLAY_NAME.to_string(), HashMap::new())]),
|
||||
previous_env_vars: HashMap::new(),
|
||||
config: Config::default(),
|
||||
pipeline_externals_state: Arc::new((AtomicU32::new(0), AtomicU32::new(0))),
|
||||
repl_buffer_state: Arc::new(Mutex::new(None)),
|
||||
repl_operation_queue: Arc::new(Mutex::new(VecDeque::new())),
|
||||
#[cfg(feature = "plugin")]
|
||||
|
@ -424,7 +424,7 @@ impl PipelineData {
|
||||
stack: &mut Stack,
|
||||
no_newline: bool,
|
||||
to_stderr: bool,
|
||||
) -> Result<(), ShellError> {
|
||||
) -> Result<i64, ShellError> {
|
||||
// If the table function is in the declarations, then we can use it
|
||||
// to create the table value that will be printed in the terminal
|
||||
|
||||
@ -433,10 +433,14 @@ impl PipelineData {
|
||||
|
||||
if let PipelineData::ExternalStream {
|
||||
stdout: stream,
|
||||
stderr: stderr_stream,
|
||||
exit_code,
|
||||
..
|
||||
} = self
|
||||
{
|
||||
// NOTE: currently we don't need anything from stderr
|
||||
// so directly consumes `stderr_stream` to make sure that everything is done.
|
||||
std::thread::spawn(move || stderr_stream.map(|x| x.into_bytes()));
|
||||
if let Some(stream) = stream {
|
||||
for s in stream {
|
||||
let s_live = s?;
|
||||
@ -452,10 +456,13 @@ impl PipelineData {
|
||||
|
||||
// Make sure everything has finished
|
||||
if let Some(exit_code) = exit_code {
|
||||
let _: Vec<_> = exit_code.into_iter().collect();
|
||||
let mut exit_codes: Vec<_> = exit_code.into_iter().collect();
|
||||
if let Some(Value::Int { val, .. }) = exit_codes.pop() {
|
||||
return Ok(val);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
match engine_state.find_decl("table".as_bytes(), &[]) {
|
||||
@ -474,7 +481,7 @@ impl PipelineData {
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn write_all_and_flush(
|
||||
@ -483,7 +490,7 @@ impl PipelineData {
|
||||
config: &Config,
|
||||
no_newline: bool,
|
||||
to_stderr: bool,
|
||||
) -> Result<(), ShellError> {
|
||||
) -> Result<i64, ShellError> {
|
||||
for item in self {
|
||||
let mut out = if let Value::Error { error } = item {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
@ -506,7 +513,7 @@ impl PipelineData {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1176,6 +1176,10 @@ impl Value {
|
||||
matches!(self, Value::Bool { val: true, .. })
|
||||
}
|
||||
|
||||
pub fn is_false(&self) -> bool {
|
||||
matches!(self, Value::Bool { val: false, .. })
|
||||
}
|
||||
|
||||
pub fn columns(&self) -> Vec<String> {
|
||||
match self {
|
||||
Value::Record { cols, .. } => cols.clone(),
|
||||
@ -1676,6 +1680,7 @@ impl Value {
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sub(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
||||
match (self, rhs) {
|
||||
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
@ -1754,6 +1759,7 @@ impl Value {
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mul(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
||||
match (self, rhs) {
|
||||
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
@ -1790,6 +1796,18 @@ impl Value {
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::Float { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
||||
Ok(Value::Filesize {
|
||||
val: (*lhs * *rhs as f64) as i64,
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
||||
Ok(Value::Filesize {
|
||||
val: (*lhs as f64 * *rhs) as i64,
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::Int { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
|
||||
Ok(Value::Duration {
|
||||
val: *lhs * *rhs,
|
||||
@ -1802,6 +1820,18 @@ impl Value {
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::Duration { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
||||
Ok(Value::Duration {
|
||||
val: (*lhs as f64 * *rhs) as i64,
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::Float { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
|
||||
Ok(Value::Duration {
|
||||
val: (*lhs * *rhs as f64) as i64,
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::CustomValue { val: lhs, span }, rhs) => {
|
||||
lhs.operation(*span, Operator::Multiply, op, rhs)
|
||||
}
|
||||
@ -1815,6 +1845,7 @@ impl Value {
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn div(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
||||
match (self, rhs) {
|
||||
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
@ -1881,6 +1912,26 @@ impl Value {
|
||||
Err(ShellError::DivisionByZero(op))
|
||||
}
|
||||
}
|
||||
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
if *rhs != 0 {
|
||||
Ok(Value::Filesize {
|
||||
val: ((*lhs as f64) / (*rhs as f64)) as i64,
|
||||
span,
|
||||
})
|
||||
} else {
|
||||
Err(ShellError::DivisionByZero(op))
|
||||
}
|
||||
}
|
||||
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
||||
if *rhs != 0.0 {
|
||||
Ok(Value::Filesize {
|
||||
val: (*lhs as f64 / rhs) as i64,
|
||||
span,
|
||||
})
|
||||
} else {
|
||||
Err(ShellError::DivisionByZero(op))
|
||||
}
|
||||
}
|
||||
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
|
||||
if *rhs != 0 {
|
||||
if lhs % rhs == 0 {
|
||||
@ -1898,20 +1949,20 @@ impl Value {
|
||||
Err(ShellError::DivisionByZero(op))
|
||||
}
|
||||
}
|
||||
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
(Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
if *rhs != 0 {
|
||||
Ok(Value::Filesize {
|
||||
val: lhs / rhs,
|
||||
Ok(Value::Duration {
|
||||
val: ((*lhs as f64) / (*rhs as f64)) as i64,
|
||||
span,
|
||||
})
|
||||
} else {
|
||||
Err(ShellError::DivisionByZero(op))
|
||||
}
|
||||
}
|
||||
(Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
if *rhs != 0 {
|
||||
(Value::Duration { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
||||
if *rhs != 0.0 {
|
||||
Ok(Value::Duration {
|
||||
val: lhs / rhs,
|
||||
val: ((*lhs as f64) / rhs) as i64,
|
||||
span,
|
||||
})
|
||||
} else {
|
||||
@ -1931,6 +1982,7 @@ impl Value {
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn floor_div(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
||||
match (self, rhs) {
|
||||
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
@ -1998,6 +2050,32 @@ impl Value {
|
||||
Err(ShellError::DivisionByZero(op))
|
||||
}
|
||||
}
|
||||
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
if *rhs != 0 {
|
||||
Ok(Value::Filesize {
|
||||
val: ((*lhs as f64) / (*rhs as f64))
|
||||
.max(std::i64::MIN as f64)
|
||||
.min(std::i64::MAX as f64)
|
||||
.floor() as i64,
|
||||
span,
|
||||
})
|
||||
} else {
|
||||
Err(ShellError::DivisionByZero(op))
|
||||
}
|
||||
}
|
||||
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
||||
if *rhs != 0.0 {
|
||||
Ok(Value::Filesize {
|
||||
val: (*lhs as f64 / *rhs)
|
||||
.max(std::i64::MIN as f64)
|
||||
.min(std::i64::MAX as f64)
|
||||
.floor() as i64,
|
||||
span,
|
||||
})
|
||||
} else {
|
||||
Err(ShellError::DivisionByZero(op))
|
||||
}
|
||||
}
|
||||
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
|
||||
if *rhs != 0 {
|
||||
Ok(Value::Int {
|
||||
@ -2011,20 +2089,26 @@ impl Value {
|
||||
Err(ShellError::DivisionByZero(op))
|
||||
}
|
||||
}
|
||||
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
(Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
if *rhs != 0 {
|
||||
Ok(Value::Filesize {
|
||||
val: lhs / rhs,
|
||||
Ok(Value::Duration {
|
||||
val: (*lhs as f64 / *rhs as f64)
|
||||
.max(std::i64::MIN as f64)
|
||||
.min(std::i64::MAX as f64)
|
||||
.floor() as i64,
|
||||
span,
|
||||
})
|
||||
} else {
|
||||
Err(ShellError::DivisionByZero(op))
|
||||
}
|
||||
}
|
||||
(Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
if *rhs != 0 {
|
||||
(Value::Duration { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
||||
if *rhs != 0.0 {
|
||||
Ok(Value::Duration {
|
||||
val: lhs / rhs,
|
||||
val: (*lhs as f64 / *rhs)
|
||||
.max(std::i64::MIN as f64)
|
||||
.min(std::i64::MAX as f64)
|
||||
.floor() as i64,
|
||||
span,
|
||||
})
|
||||
} else {
|
||||
@ -2044,6 +2128,7 @@ impl Value {
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lt(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
||||
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
|
||||
return lhs.operation(*span, Operator::LessThan, op, rhs);
|
||||
@ -2070,6 +2155,7 @@ impl Value {
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lte(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
||||
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
|
||||
return lhs.operation(*span, Operator::LessThanOrEqual, op, rhs);
|
||||
@ -2096,6 +2182,7 @@ impl Value {
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gt(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
||||
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
|
||||
return lhs.operation(*span, Operator::GreaterThan, op, rhs);
|
||||
@ -2122,6 +2209,7 @@ impl Value {
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gte(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
||||
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
|
||||
return lhs.operation(*span, Operator::GreaterThanOrEqual, op, rhs);
|
||||
@ -2148,6 +2236,7 @@ impl Value {
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
||||
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
|
||||
return lhs.operation(*span, Operator::Equal, op, rhs);
|
||||
@ -2172,6 +2261,7 @@ impl Value {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ne(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
||||
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
|
||||
return lhs.operation(*span, Operator::NotEqual, op, rhs);
|
||||
|
2
crates/nu-system/Cargo.lock
generated
2
crates/nu-system/Cargo.lock
generated
@ -148,7 +148,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-system"
|
||||
version = "0.69.0"
|
||||
version = "0.70.0"
|
||||
dependencies = [
|
||||
"errno",
|
||||
"libproc",
|
||||
|
@ -3,7 +3,7 @@ authors = ["The Nushell Project Developers", "procs creators"]
|
||||
description = "Nushell system querying"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-system"
|
||||
name = "nu-system"
|
||||
version = "0.69.0"
|
||||
version = "0.70.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
@ -16,6 +16,10 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
nix = "0.24"
|
||||
atty = "0.2"
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
|
||||
procfs = "0.14.0"
|
||||
|
||||
|
180
crates/nu-system/src/foreground.rs
Normal file
180
crates/nu-system/src/foreground.rs
Normal file
@ -0,0 +1,180 @@
|
||||
use std::{
|
||||
process::{Child, Command},
|
||||
sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
/// A simple wrapper for `std::process::Command`
|
||||
///
|
||||
/// ## Spawn behavior
|
||||
/// ### Unix
|
||||
///
|
||||
/// The spawned child process will get its own process group id, and it's going to foreground (by making stdin belong's to child's process group).
|
||||
///
|
||||
/// On drop, the calling process's group will become the foreground process group once again.
|
||||
///
|
||||
/// ### Windows
|
||||
/// It does nothing special on windows system, `spawn` is the same as [std::process::Command::spawn](std::process::Command::spawn)
|
||||
pub struct ForegroundProcess {
|
||||
inner: Command,
|
||||
pipeline_state: Arc<(AtomicU32, AtomicU32)>,
|
||||
}
|
||||
|
||||
/// A simple wrapper for `std::process::Child`
|
||||
///
|
||||
/// It can only be created by `ForegroundProcess::spawn`.
|
||||
pub struct ForegroundChild {
|
||||
inner: Child,
|
||||
pipeline_state: Arc<(AtomicU32, AtomicU32)>,
|
||||
}
|
||||
|
||||
impl ForegroundProcess {
|
||||
pub fn new(cmd: Command, pipeline_state: Arc<(AtomicU32, AtomicU32)>) -> Self {
|
||||
Self {
|
||||
inner: cmd,
|
||||
pipeline_state,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn(&mut self) -> std::io::Result<ForegroundChild> {
|
||||
let (ref pgrp, ref pcnt) = *self.pipeline_state;
|
||||
let existing_pgrp = pgrp.load(Ordering::SeqCst);
|
||||
fg_process_setup::prepare_to_foreground(&mut self.inner, existing_pgrp);
|
||||
self.inner
|
||||
.spawn()
|
||||
.map(|child| {
|
||||
fg_process_setup::set_foreground(&child, existing_pgrp);
|
||||
let _ = pcnt.fetch_add(1, Ordering::SeqCst);
|
||||
if existing_pgrp == 0 {
|
||||
pgrp.store(child.id(), Ordering::SeqCst);
|
||||
}
|
||||
ForegroundChild {
|
||||
inner: child,
|
||||
pipeline_state: self.pipeline_state.clone(),
|
||||
}
|
||||
})
|
||||
.map_err(|e| {
|
||||
fg_process_setup::reset_foreground_id();
|
||||
e
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<Child> for ForegroundChild {
|
||||
fn as_mut(&mut self) -> &mut Child {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ForegroundChild {
|
||||
fn drop(&mut self) {
|
||||
let (ref pgrp, ref pcnt) = *self.pipeline_state;
|
||||
if pcnt.fetch_sub(1, Ordering::SeqCst) == 1 {
|
||||
pgrp.store(0, Ordering::SeqCst);
|
||||
fg_process_setup::reset_foreground_id()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// It's a simpler version of fish shell's external process handling.
|
||||
#[cfg(target_family = "unix")]
|
||||
mod fg_process_setup {
|
||||
use nix::{
|
||||
sys::signal,
|
||||
unistd::{self, Pid},
|
||||
};
|
||||
use std::os::unix::prelude::{CommandExt, RawFd};
|
||||
|
||||
// TODO: when raising MSRV past 1.63.0, switch to OwnedFd
|
||||
struct TtyHandle(RawFd);
|
||||
|
||||
impl Drop for TtyHandle {
|
||||
fn drop(&mut self) {
|
||||
let _ = unistd::close(self.0);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn prepare_to_foreground(
|
||||
external_command: &mut std::process::Command,
|
||||
existing_pgrp: u32,
|
||||
) {
|
||||
let tty = TtyHandle(unistd::dup(nix::libc::STDIN_FILENO).expect("dup"));
|
||||
unsafe {
|
||||
// Safety:
|
||||
// POSIX only allows async-signal-safe functions to be called.
|
||||
// `sigprocmask`, `setpgid` and `tcsetpgrp` are async-signal-safe according to:
|
||||
// https://manpages.ubuntu.com/manpages/bionic/man7/signal-safety.7.html
|
||||
external_command.pre_exec(move || {
|
||||
// When this callback is run, std::process has already done:
|
||||
// - pthread_sigmask(SIG_SETMASK) with an empty sigset
|
||||
// - signal(SIGPIPE, SIG_DFL)
|
||||
// However, we do need TTOU/TTIN blocked again during this setup.
|
||||
let mut sigset = signal::SigSet::empty();
|
||||
sigset.add(signal::Signal::SIGTSTP);
|
||||
sigset.add(signal::Signal::SIGTTOU);
|
||||
sigset.add(signal::Signal::SIGTTIN);
|
||||
sigset.add(signal::Signal::SIGCHLD);
|
||||
signal::sigprocmask(signal::SigmaskHow::SIG_BLOCK, Some(&sigset), None)
|
||||
.expect("signal mask");
|
||||
|
||||
// According to glibc's job control manual:
|
||||
// https://www.gnu.org/software/libc/manual/html_node/Launching-Jobs.html
|
||||
// This has to be done *both* in the parent and here in the child due to race conditions.
|
||||
set_foreground_pid(unistd::getpid(), existing_pgrp, tty.0);
|
||||
|
||||
// Now let the child process have all the signals by resetting with SIG_SETMASK.
|
||||
let mut sigset = signal::SigSet::empty();
|
||||
sigset.add(signal::Signal::SIGTSTP); // for now not really all: we don't support background jobs, so keep this one blocked
|
||||
signal::sigprocmask(signal::SigmaskHow::SIG_SETMASK, Some(&sigset), None)
|
||||
.expect("signal mask");
|
||||
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn set_foreground(process: &std::process::Child, existing_pgrp: u32) {
|
||||
// called from the parent shell process - do the stdin tty check here
|
||||
if atty::is(atty::Stream::Stdin) {
|
||||
set_foreground_pid(
|
||||
Pid::from_raw(process.id() as i32),
|
||||
existing_pgrp,
|
||||
nix::libc::STDIN_FILENO,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// existing_pgrp is 0 when we don't have an existing foreground process in the pipeline.
|
||||
// Conveniently, 0 means "current pid" to setpgid. But not to tcsetpgrp.
|
||||
fn set_foreground_pid(pid: Pid, existing_pgrp: u32, tty: RawFd) {
|
||||
let _ = unistd::setpgid(pid, Pid::from_raw(existing_pgrp as i32));
|
||||
let _ = unistd::tcsetpgrp(
|
||||
tty,
|
||||
if existing_pgrp == 0 {
|
||||
pid
|
||||
} else {
|
||||
Pid::from_raw(existing_pgrp as i32)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Reset the foreground process group to the shell
|
||||
pub(super) fn reset_foreground_id() {
|
||||
if atty::is(atty::Stream::Stdin) {
|
||||
if let Err(e) = nix::unistd::tcsetpgrp(nix::libc::STDIN_FILENO, unistd::getpgrp()) {
|
||||
println!("ERROR: reset foreground id failed, tcsetpgrp result: {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "unix"))]
|
||||
mod fg_process_setup {
|
||||
pub(super) fn prepare_to_foreground(_: &mut std::process::Command, _: u32) {}
|
||||
|
||||
pub(super) fn set_foreground(_: &std::process::Child, _: u32) {}
|
||||
|
||||
pub(super) fn reset_foreground_id() {}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
mod foreground;
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
mod linux;
|
||||
#[cfg(target_os = "macos")]
|
||||
@ -5,6 +6,7 @@ mod macos;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows;
|
||||
|
||||
pub use self::foreground::{ForegroundChild, ForegroundProcess};
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
pub use self::linux::*;
|
||||
#[cfg(target_os = "macos")]
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-table"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-table"
|
||||
version = "0.69.0"
|
||||
version = "0.70.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[[bin]]
|
||||
@ -14,7 +14,9 @@ path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
atty = "0.2.14"
|
||||
tabled = { version = "0.8.0", features = ["color"] }
|
||||
tabled = { version = "0.9.0", features = ["color"], default-features = false }
|
||||
json_to_table = { version = "0.1.0", features = ["color"] }
|
||||
serde_json = "*"
|
||||
|
@ -1,8 +1,13 @@
|
||||
mod nu_protocol_table;
|
||||
mod table;
|
||||
mod table_theme;
|
||||
mod textstyle;
|
||||
mod width_control;
|
||||
|
||||
pub use nu_protocol_table::NuTable;
|
||||
pub use table::{Alignments, Table};
|
||||
pub use table_theme::TableTheme;
|
||||
pub use textstyle::{Alignment, StyledString, TextStyle};
|
||||
pub use textstyle::{Alignment, TextStyle};
|
||||
|
||||
pub fn string_width(text: &str) -> usize {
|
||||
tabled::papergrid::util::string_width_multiline_tab(text, 4)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use nu_protocol::Config;
|
||||
use nu_table::{Alignments, StyledString, Table, TableTheme, TextStyle};
|
||||
use nu_table::{Alignments, Table, TableTheme, TextStyle};
|
||||
use std::collections::HashMap;
|
||||
use tabled::papergrid::records::{cell_info::CellInfo, tcell::TCell};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<_> = std::env::args().collect();
|
||||
@ -23,14 +24,23 @@ fn main() {
|
||||
// The table rows
|
||||
let rows = vec_of_str_to_vec_of_styledstr(&row_data, false);
|
||||
// The table itself
|
||||
let table = Table::new(headers, vec![rows; 3], TableTheme::rounded());
|
||||
let count_cols = std::cmp::max(rows.len(), headers.len());
|
||||
let mut rows = vec![rows; 3];
|
||||
rows.insert(0, headers);
|
||||
let table = Table::new(rows, (3, count_cols), width, true, false);
|
||||
// FIXME: Config isn't available from here so just put these here to compile
|
||||
let color_hm: HashMap<String, nu_ansi_term::Style> = HashMap::new();
|
||||
// get the default config
|
||||
let config = Config::default();
|
||||
// Capture the table as a string
|
||||
let output_table = table
|
||||
.draw_table(&config, &color_hm, Alignments::default(), width)
|
||||
.draw_table(
|
||||
&config,
|
||||
&color_hm,
|
||||
Alignments::default(),
|
||||
&TableTheme::rounded(),
|
||||
width,
|
||||
)
|
||||
.unwrap_or_else(|| format!("Couldn't fit table into {} columns!", width));
|
||||
// Draw the table
|
||||
println!("{}", output_table)
|
||||
@ -74,17 +84,23 @@ fn make_table_data() -> (Vec<&'static str>, Vec<&'static str>) {
|
||||
(table_headers, row_data)
|
||||
}
|
||||
|
||||
fn vec_of_str_to_vec_of_styledstr(data: &[&str], is_header: bool) -> Vec<StyledString> {
|
||||
fn vec_of_str_to_vec_of_styledstr(
|
||||
data: &[&str],
|
||||
is_header: bool,
|
||||
) -> Vec<TCell<CellInfo<'static>, TextStyle>> {
|
||||
let mut v = vec![];
|
||||
|
||||
for x in data {
|
||||
if is_header {
|
||||
v.push(StyledString::new(
|
||||
v.push(Table::create_cell(
|
||||
String::from(*x),
|
||||
TextStyle::default_header(),
|
||||
))
|
||||
} else {
|
||||
v.push(StyledString::new(String::from(*x), TextStyle::basic_left()))
|
||||
v.push(Table::create_cell(
|
||||
String::from(*x),
|
||||
TextStyle::basic_left(),
|
||||
))
|
||||
}
|
||||
}
|
||||
v
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user