mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 15:11:52 +02:00
Compare commits
100 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 | |||
b746d8427c | |||
3beaca0d06 | |||
f7647584a3 | |||
f44473d510 | |||
d66a5398d1 | |||
43905caa46 | |||
b47bd22b37 | |||
7f21b7fd7e | |||
0ab4e5af2a | |||
848550771a | |||
d323ac3edc | |||
2e23d4d734 | |||
d9d14b38de | |||
03b7dd2725 | |||
ad0c6bf7d5 | |||
9aed95408d | |||
71844755e5 | |||
0b9dd87ca8 | |||
d704b05b7a | |||
15ebf45f46 | |||
b086f34fa2 | |||
5491634dda | |||
e7bf89b311 | |||
4fdfd3d15e | |||
35a521d762 | |||
f0ae6ffe12 | |||
10b9c65cb7 | |||
02e3f49bce | |||
f6c791f199 | |||
cc62e4db26 | |||
56bb9e92cb | |||
2791251268 | |||
b159bf2c28 | |||
12a0fe39f7 | |||
df6a7b6f5c | |||
8564c5371f | |||
d08212409f | |||
4490e97a13 | |||
2bb367f570 | |||
367f79cb4f | |||
4926865c4e | |||
9ee4086dfa | |||
3e0655cdba |
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 }}
|
||||
|
||||
|
806
Cargo.lock
generated
806
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
53
Cargo.toml
53
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.68.1"
|
||||
version = "0.70.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -39,21 +38,22 @@ 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.68.1" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.68.1" }
|
||||
nu-command = { path="./crates/nu-command", version = "0.68.1" }
|
||||
nu-engine = { path="./crates/nu-engine", version = "0.68.1" }
|
||||
nu-json = { path="./crates/nu-json", version = "0.68.1" }
|
||||
nu-parser = { path="./crates/nu-parser", version = "0.68.1" }
|
||||
nu-path = { path="./crates/nu-path", version = "0.68.1" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.68.1" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.68.1" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.68.1" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.68.1" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.68.1" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.68.1" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.68.1" }
|
||||
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"
|
||||
simplelog = "0.12.0"
|
||||
@ -64,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.68.1" }
|
||||
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"
|
||||
@ -74,14 +82,11 @@ 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"]
|
||||
default = ["plugin", "which-support", "trash-support"]
|
||||
stable = ["default"]
|
||||
extra = ["default", "dataframe", "database"]
|
||||
wasi = []
|
||||
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
|
||||
static-link-openssl = ["dep:openssl"]
|
||||
@ -122,3 +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.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.
|
||||
|
@ -1,3 +1,3 @@
|
||||
To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
|
||||
|
||||
> register -e json ./nu_plugin_query
|
||||
> register ./nu_plugin_query
|
||||
|
@ -5,22 +5,22 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.68.1"
|
||||
version = "0.70.0"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="../nu-test-support", version = "0.68.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.68.1" }
|
||||
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.68.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.68.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.68.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.68.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.68.1" }
|
||||
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.68.1" }
|
||||
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"
|
||||
@ -31,8 +31,9 @@ is_executable = "1.0.1"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4"
|
||||
miette = { version = "5.1.0", features = ["fancy"] }
|
||||
percent-encoding = "2"
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
sysinfo = "0.25.2"
|
||||
sysinfo = "0.26.2"
|
||||
thiserror = "1.0.31"
|
||||
|
||||
[features]
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -422,7 +431,7 @@ fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option<MatchedAl
|
||||
}
|
||||
// Push the rest to names vector.
|
||||
if pos < input.len() {
|
||||
vec_names.push((&input[pos..]).to_owned());
|
||||
vec_names.push(input[pos..].to_owned());
|
||||
}
|
||||
|
||||
for name in &vec_names {
|
||||
|
@ -666,6 +666,7 @@ fn add_parsed_keybinding(
|
||||
|
||||
KeyCode::Char(char)
|
||||
}
|
||||
"space" => KeyCode::Char(' '),
|
||||
"down" => KeyCode::Down,
|
||||
"up" => KeyCode::Up,
|
||||
"left" => KeyCode::Left,
|
||||
@ -821,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,16 +11,19 @@ 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, Stack, StateWorkingSet},
|
||||
engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
|
||||
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
|
||||
Spanned, Type, Value, VarId,
|
||||
};
|
||||
use reedline::{DefaultHinter, Emacs, SqliteBackedHistory, Vi};
|
||||
use std::io::{self, Write};
|
||||
use std::{sync::atomic::Ordering, time::Instant};
|
||||
use reedline::{DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
sync::atomic::Ordering,
|
||||
time::Instant,
|
||||
};
|
||||
use strip_ansi_escapes::strip;
|
||||
use sysinfo::SystemExt;
|
||||
|
||||
@ -98,14 +101,21 @@ pub fn evaluate_repl(
|
||||
);
|
||||
}
|
||||
|
||||
// Get the config once for the history `max_history_size`
|
||||
// Updating that will not be possible in one session
|
||||
let config = engine_state.get_config();
|
||||
|
||||
if is_perf_true {
|
||||
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
let mut line_editor = Reedline::create();
|
||||
|
||||
// Now that reedline is created, get the history session id and store it in engine_state
|
||||
let hist_sesh = match line_editor.get_history_session_id() {
|
||||
Some(id) => i64::from(id),
|
||||
None => 0,
|
||||
};
|
||||
engine_state.history_session_id = hist_sesh;
|
||||
|
||||
let config = engine_state.get_config();
|
||||
|
||||
let history_path = crate::config_files::get_history_path(
|
||||
nushell_path,
|
||||
engine_state.config.history_file_format,
|
||||
@ -332,6 +342,7 @@ pub fn evaluate_repl(
|
||||
|
||||
match input {
|
||||
Ok(Signal::Success(s)) => {
|
||||
let hostname = sys.host_name();
|
||||
let history_supports_meta =
|
||||
matches!(config.history_file_format, HistoryFileFormat::Sqlite);
|
||||
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
|
||||
@ -339,7 +350,7 @@ pub fn evaluate_repl(
|
||||
line_editor
|
||||
.update_last_command_context(&|mut c| {
|
||||
c.start_timestamp = Some(chrono::Utc::now());
|
||||
c.hostname = sys.host_name();
|
||||
c.hostname = hostname.clone();
|
||||
|
||||
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
|
||||
c
|
||||
@ -347,6 +358,12 @@ pub fn evaluate_repl(
|
||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||
}
|
||||
|
||||
engine_state
|
||||
.repl_buffer_state
|
||||
.lock()
|
||||
.expect("repl buffer state mutex")
|
||||
.replace(line_editor.current_buffer_contents().to_string());
|
||||
|
||||
// Right before we start running the code the user gave us,
|
||||
// fire the "pre_execution" hook
|
||||
if let Some(hook) = config.hooks.pre_execution.clone() {
|
||||
@ -363,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
|
||||
@ -435,7 +456,7 @@ pub fn evaluate_repl(
|
||||
span,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
} else if !s.trim().is_empty() {
|
||||
trace!("eval source: {}", s);
|
||||
|
||||
eval_source(
|
||||
@ -473,6 +494,21 @@ pub fn evaluate_repl(
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
let path = cwd.as_string()?;
|
||||
|
||||
// Communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
|
||||
run_ansi_sequence(&format!(
|
||||
"\x1b]7;file://{}{}{}\x1b\\",
|
||||
percent_encoding::utf8_percent_encode(
|
||||
&hostname.unwrap_or_else(|| "localhost".to_string()),
|
||||
percent_encoding::CONTROLS
|
||||
),
|
||||
if path.starts_with('/') { "" } else { "/" },
|
||||
percent_encoding::utf8_percent_encode(
|
||||
&path,
|
||||
percent_encoding::CONTROLS
|
||||
)
|
||||
))?;
|
||||
|
||||
// Try to abbreviate string for windows title
|
||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||
path.replace(&p.as_path().display().to_string(), "~")
|
||||
@ -489,6 +525,24 @@ pub fn evaluate_repl(
|
||||
}
|
||||
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
||||
}
|
||||
|
||||
let mut ops = engine_state
|
||||
.repl_operation_queue
|
||||
.lock()
|
||||
.expect("repl op queue mutex");
|
||||
while let Some(op) = ops.pop_front() {
|
||||
match op {
|
||||
ReplOperation::Append(s) => line_editor.run_edit_commands(&[
|
||||
EditCommand::MoveToEnd,
|
||||
EditCommand::InsertString(s),
|
||||
]),
|
||||
ReplOperation::Insert(s) => {
|
||||
line_editor.run_edit_commands(&[EditCommand::InsertString(s)])
|
||||
}
|
||||
ReplOperation::Replace(s) => line_editor
|
||||
.run_edit_commands(&[EditCommand::Clear, EditCommand::InsertString(s)]),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Signal::CtrlC) => {
|
||||
// `Reedline` clears the line content. New prompt is shown
|
||||
@ -528,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(),
|
||||
@ -545,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
|
||||
...
|
||||
}}{}
|
||||
@ -612,9 +666,7 @@ pub fn eval_string_with_input(
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
return Err(err);
|
||||
}
|
||||
engine_state.merge_delta(delta)?;
|
||||
|
||||
let input_as_pipeline_data = match input {
|
||||
Some(input) => PipelineData::Value(input, None),
|
||||
|
@ -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
|
||||
|
@ -34,8 +34,20 @@ fn completer_strings() -> NuCompleter {
|
||||
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variables_dollar_sign_with_varialblecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "$ ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
assert_eq!(7, suggestions.len());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_completions_double_dash_argument(mut completer: NuCompleter) {
|
||||
fn variables_double_dash_argument_with_flagcompletion(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst --", 6);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into()];
|
||||
// dbg!(&expected, &suggestions);
|
||||
@ -43,28 +55,30 @@ fn variables_completions_double_dash_argument(mut completer: NuCompleter) {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_completions_single_dash_argument(mut completer: NuCompleter) {
|
||||
fn variables_single_dash_argument_with_flagcompletion(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst -", 5);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_completions_command(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command ", 9);
|
||||
fn variables_command_with_commandcompletion(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-c ", 4);
|
||||
let expected: Vec<String> = vec!["my-command".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_completions_subcommands(mut completer_strings: NuCompleter) {
|
||||
fn variables_subcommands_with_customcompletion(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command ", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_completions_subcommands_2(mut completer_strings: NuCompleter) {
|
||||
fn variables_customcompletion_subcommands_with_customcompletion_2(
|
||||
mut completer_strings: NuCompleter,
|
||||
) {
|
||||
let suggestions = completer_strings.complete("my-command ", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
@ -100,7 +114,7 @@ fn external_completer_trailing_space() {
|
||||
let block = "let external_completer = {|spans| $spans}";
|
||||
let input = "gh alias ".to_string();
|
||||
|
||||
let suggestions = run_external_completion(&block, &input);
|
||||
let suggestions = run_external_completion(block, &input);
|
||||
assert_eq!(3, suggestions.len());
|
||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
||||
@ -112,7 +126,7 @@ fn external_completer_no_trailing_space() {
|
||||
let block = "let external_completer = {|spans| $spans}";
|
||||
let input = "gh alias".to_string();
|
||||
|
||||
let suggestions = run_external_completion(&block, &input);
|
||||
let suggestions = run_external_completion(block, &input);
|
||||
assert_eq!(2, suggestions.len());
|
||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
||||
@ -123,7 +137,7 @@ fn external_completer_pass_flags() {
|
||||
let block = "let external_completer = {|spans| $spans}";
|
||||
let input = "gh api --".to_string();
|
||||
|
||||
let suggestions = run_external_completion(&block, &input);
|
||||
let suggestions = run_external_completion(block, &input);
|
||||
assert_eq!(3, suggestions.len());
|
||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||
assert_eq!("api", suggestions.get(1).unwrap().value);
|
||||
@ -168,7 +182,7 @@ fn file_completions() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_ls_completion() {
|
||||
fn command_ls_with_filecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
@ -200,7 +214,7 @@ fn command_ls_completion() {
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
#[test]
|
||||
fn command_open_completion() {
|
||||
fn command_open_with_filecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
@ -233,7 +247,7 @@ fn command_open_completion() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_rm_completion() {
|
||||
fn command_rm_with_globcompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
@ -266,7 +280,7 @@ fn command_rm_completion() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_cp_completion() {
|
||||
fn command_cp_with_globcompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
@ -299,7 +313,7 @@ fn command_cp_completion() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_save_completion() {
|
||||
fn command_save_with_filecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
@ -332,7 +346,7 @@ fn command_save_completion() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_touch_completion() {
|
||||
fn command_touch_with_filecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
@ -365,7 +379,7 @@ fn command_touch_completion() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_watch_completion() {
|
||||
fn command_watch_with_filecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
@ -431,7 +445,7 @@ fn flag_completions() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_completions() {
|
||||
fn folder_with_directorycompletions() {
|
||||
// Create a new engine
|
||||
let (dir, dir_str, engine, stack) = new_engine();
|
||||
|
||||
@ -623,7 +637,7 @@ fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
|
||||
// Instatiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);
|
||||
|
||||
completer.complete(&input, input.len())
|
||||
completer.complete(input, input.len())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -658,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);
|
||||
}
|
||||
|
@ -104,9 +104,7 @@ pub fn merge_input(
|
||||
(block, working_set.render())
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
return Err(err);
|
||||
}
|
||||
engine_state.merge_delta(delta)?;
|
||||
|
||||
assert!(eval_block(
|
||||
engine_state,
|
||||
|
@ -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.68.1"
|
||||
version = "0.70.0"
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.68.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-json = { path = "../nu-json", version = "0.68.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.68.1" }
|
||||
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.68.1"
|
||||
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.68.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.68.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.68.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.68.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.68.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.68.1" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.68.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.68.1" }
|
||||
nu-system = { path = "../nu-system", version = "0.68.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.68.1" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.68.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.68.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.68.1" }
|
||||
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"
|
||||
@ -70,6 +70,7 @@ rayon = "1.5.1"
|
||||
reqwest = {version = "0.11", features = ["blocking", "json"] }
|
||||
roxmltree = "0.14.0"
|
||||
rust-embed = "6.3.0"
|
||||
same-file = "1.0.6"
|
||||
serde = { version="1.0.123", features=["derive"] }
|
||||
serde_ini = "0.2.0"
|
||||
serde_urlencoded = "0.7.0"
|
||||
@ -78,7 +79,7 @@ sha2 = "0.10.0"
|
||||
# Disable default features b/c the default features build Git (very slow to compile)
|
||||
shadow-rs = { version = "0.16.1", default-features = false }
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
sysinfo = "0.25.2"
|
||||
sysinfo = "0.26.2"
|
||||
terminal_size = "0.2.1"
|
||||
thiserror = "1.0.31"
|
||||
titlecase = "2.0.0"
|
||||
@ -87,10 +88,13 @@ 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.16.0", features = ["serde"], 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"
|
||||
@ -115,6 +119,7 @@ features = [
|
||||
"dtype-struct",
|
||||
"dtype-categorical",
|
||||
"dynamic_groupby",
|
||||
"ipc",
|
||||
"is_in",
|
||||
"json",
|
||||
"lazy",
|
||||
|
@ -215,8 +215,8 @@ fn histogram_impl(
|
||||
const MAX_FREQ_COUNT: f64 = 100.0;
|
||||
for (val, count) in counter.into_iter() {
|
||||
let quantile = match calc_method {
|
||||
PercentageCalcMethod::Normalize => (count as f64 / total_cnt as f64),
|
||||
PercentageCalcMethod::Relative => (count as f64 / max_cnt as f64),
|
||||
PercentageCalcMethod::Normalize => count as f64 / total_cnt as f64,
|
||||
PercentageCalcMethod::Relative => count as f64 / max_cnt as f64,
|
||||
};
|
||||
|
||||
let percentage = format!("{:.2}%", quantile * 100_f64);
|
||||
|
@ -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(),
|
||||
@ -315,9 +326,6 @@ fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, Shel
|
||||
Unit::Hour => return Ok(x * 60 * 60 * 1000 * 1000 * 1000),
|
||||
Unit::Day => return Ok(x * 24 * 60 * 60 * 1000 * 1000 * 1000),
|
||||
Unit::Week => return Ok(x * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000),
|
||||
Unit::Month => return Ok(x * 30 * 24 * 60 * 60 * 1000 * 1000 * 1000), //30 days to a month
|
||||
Unit::Year => return Ok(x * 365 * 24 * 60 * 60 * 1000 * 1000 * 1000), //365 days to a year
|
||||
Unit::Decade => return Ok(x * 10 * 365 * 24 * 60 * 60 * 1000 * 1000 * 1000), //365 days to a year
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@ -353,9 +361,6 @@ fn string_to_unit_duration(
|
||||
Unit::Hour => return Ok(("hr", x)),
|
||||
Unit::Day => return Ok(("day", x)),
|
||||
Unit::Week => return Ok(("wk", x)),
|
||||
Unit::Month => return Ok(("month", x)), //30 days to a month
|
||||
Unit::Year => return Ok(("yr", x)), //365 days to a year
|
||||
Unit::Decade => return Ok(("dec", x)), //365 days to a year
|
||||
|
||||
_ => return Ok(("ns", 0)),
|
||||
}
|
||||
@ -375,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()
|
||||
@ -408,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 {
|
||||
@ -456,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);
|
||||
}
|
||||
|
||||
@ -470,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);
|
||||
}
|
||||
|
||||
@ -484,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);
|
||||
}
|
||||
|
||||
@ -498,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);
|
||||
}
|
||||
|
||||
@ -512,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);
|
||||
}
|
||||
|
||||
@ -526,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);
|
||||
}
|
||||
|
||||
@ -540,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);
|
||||
}
|
||||
|
||||
@ -554,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);
|
||||
}
|
||||
}
|
||||
|
84
crates/nu-command/src/core_commands/commandline.rs
Normal file
84
crates/nu-command/src/core_commands/commandline.rs
Normal file
@ -0,0 +1,84 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::ReplOperation;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Category;
|
||||
use nu_protocol::IntoPipelineData;
|
||||
use nu_protocol::{PipelineData, ShellError, Signature, SyntaxShape, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Commandline;
|
||||
|
||||
impl Command for Commandline {
|
||||
fn name(&self) -> &str {
|
||||
"commandline"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("commandline")
|
||||
.switch(
|
||||
"append",
|
||||
"appends the string to the end of the buffer",
|
||||
Some('a'),
|
||||
)
|
||||
.switch(
|
||||
"insert",
|
||||
"inserts the string into the buffer at the cursor position",
|
||||
Some('i'),
|
||||
)
|
||||
.switch(
|
||||
"replace",
|
||||
"replaces the current contents of the buffer (default)",
|
||||
Some('r'),
|
||||
)
|
||||
.optional(
|
||||
"cmd",
|
||||
SyntaxShape::String,
|
||||
"the string to perform the operation with",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"View or modify the current command line input buffer"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["repl", "interactive"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
|
||||
let mut ops = engine_state
|
||||
.repl_operation_queue
|
||||
.lock()
|
||||
.expect("repl op queue mutex");
|
||||
ops.push_back(if call.has_flag("append") {
|
||||
ReplOperation::Append(cmd.as_string()?)
|
||||
} else if call.has_flag("insert") {
|
||||
ReplOperation::Insert(cmd.as_string()?)
|
||||
} else {
|
||||
ReplOperation::Replace(cmd.as_string()?)
|
||||
});
|
||||
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
||||
} else if let Some(ref cmd) = *engine_state
|
||||
.repl_buffer_state
|
||||
.lock()
|
||||
.expect("repl buffer state mutex")
|
||||
{
|
||||
Ok(Value::String {
|
||||
val: cmd.clone(),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
} else {
|
||||
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
}
|
@ -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"]
|
||||
}
|
||||
}
|
||||
|
@ -1,62 +0,0 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportEnvModule;
|
||||
|
||||
impl Command for ExportEnvModule {
|
||||
fn name(&self) -> &str {
|
||||
"export env"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Export a block from a module that will be evaluated as an environment variable when imported."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("export env")
|
||||
.required(
|
||||
"name",
|
||||
SyntaxShape::String,
|
||||
"name of the environment variable",
|
||||
)
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block(Some(vec![])),
|
||||
"body of the environment variable definition",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
//TODO: Add the env to stack
|
||||
Ok(PipelineData::new(call.head))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Import and evaluate environment variable from a module",
|
||||
example: r#"module foo { export env FOO_ENV { "BAZ" } }; use foo FOO_ENV; $env.FOO_ENV"#,
|
||||
result: Some(Value::String {
|
||||
val: "BAZ".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
}
|
@ -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"]
|
||||
}
|
||||
}
|
||||
|
@ -362,11 +362,16 @@ pub fn highlight_search_string(
|
||||
));
|
||||
}
|
||||
};
|
||||
// strip haystack to remove existing ansi style
|
||||
let stripped_haystack: String = match strip_ansi_escapes::strip(haystack) {
|
||||
Ok(i) => String::from_utf8(i).unwrap_or_else(|_| String::from(haystack)),
|
||||
Err(_) => String::from(haystack),
|
||||
};
|
||||
let mut last_match_end = 0;
|
||||
let style = Style::new().fg(White).on(Red);
|
||||
let mut highlighted = String::new();
|
||||
|
||||
for cap in regex.captures_iter(haystack) {
|
||||
for cap in regex.captures_iter(stripped_haystack.as_str()) {
|
||||
match cap {
|
||||
Ok(capture) => {
|
||||
let start = match capture.get(0) {
|
||||
@ -379,10 +384,10 @@ pub fn highlight_search_string(
|
||||
};
|
||||
highlighted.push_str(
|
||||
&string_style
|
||||
.paint(&haystack[last_match_end..start])
|
||||
.paint(&stripped_haystack[last_match_end..start])
|
||||
.to_string(),
|
||||
);
|
||||
highlighted.push_str(&style.paint(&haystack[start..end]).to_string());
|
||||
highlighted.push_str(&style.paint(&stripped_haystack[start..end]).to_string());
|
||||
last_match_end = end;
|
||||
}
|
||||
Err(e) => {
|
||||
@ -397,6 +402,10 @@ pub fn highlight_search_string(
|
||||
}
|
||||
}
|
||||
|
||||
highlighted.push_str(&string_style.paint(&haystack[last_match_end..]).to_string());
|
||||
highlighted.push_str(
|
||||
&string_style
|
||||
.paint(&stripped_haystack[last_match_end..])
|
||||
.to_string(),
|
||||
);
|
||||
Ok(highlighted)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use nu_protocol::ast::{Call, Expr, Expression, ImportPatternMember};
|
||||
use nu_protocol::ast::{Call, Expr, Expression};
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -40,12 +40,15 @@ This command is a parser keyword. For details, check:
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let import_pattern = if let Some(Expression {
|
||||
let env_var_name = if let Some(Expression {
|
||||
expr: Expr::ImportPattern(pat),
|
||||
..
|
||||
}) = call.positional_nth(0)
|
||||
{
|
||||
pat
|
||||
Spanned {
|
||||
item: String::from_utf8_lossy(&pat.head.name).to_string(),
|
||||
span: pat.head.span,
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::GenericError(
|
||||
"Unexpected import".into(),
|
||||
@ -56,78 +59,7 @@ This command is a parser keyword. For details, check:
|
||||
));
|
||||
};
|
||||
|
||||
let head_name_str = if let Ok(s) = String::from_utf8(import_pattern.head.name.clone()) {
|
||||
s
|
||||
} else {
|
||||
return Err(ShellError::NonUtf8(import_pattern.head.span));
|
||||
};
|
||||
|
||||
if let Some(module_id) = engine_state.find_module(&import_pattern.head.name, &[]) {
|
||||
// The first word is a module
|
||||
let module = engine_state.get_module(module_id);
|
||||
|
||||
let env_vars_to_hide = if import_pattern.members.is_empty() {
|
||||
module.env_vars_with_head(&import_pattern.head.name)
|
||||
} else {
|
||||
match &import_pattern.members[0] {
|
||||
ImportPatternMember::Glob { .. } => module.env_vars(),
|
||||
ImportPatternMember::Name { name, span } => {
|
||||
let mut output = vec![];
|
||||
|
||||
if let Some((name, id)) =
|
||||
module.env_var_with_head(name, &import_pattern.head.name)
|
||||
{
|
||||
output.push((name, id));
|
||||
} else if !(module.has_alias(name) || module.has_decl(name)) {
|
||||
return Err(ShellError::EnvVarNotFoundAtRuntime(
|
||||
String::from_utf8_lossy(name).into(),
|
||||
*span,
|
||||
));
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
ImportPatternMember::List { names } => {
|
||||
let mut output = vec![];
|
||||
|
||||
for (name, span) in names {
|
||||
if let Some((name, id)) =
|
||||
module.env_var_with_head(name, &import_pattern.head.name)
|
||||
{
|
||||
output.push((name, id));
|
||||
} else if !(module.has_alias(name) || module.has_decl(name)) {
|
||||
return Err(ShellError::EnvVarNotFoundAtRuntime(
|
||||
String::from_utf8_lossy(name).into(),
|
||||
*span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (name, _) in env_vars_to_hide {
|
||||
let name = if let Ok(s) = String::from_utf8(name.clone()) {
|
||||
s
|
||||
} else {
|
||||
return Err(ShellError::NonUtf8(import_pattern.span()));
|
||||
};
|
||||
|
||||
if stack.remove_env_var(engine_state, &name).is_none() {
|
||||
return Err(ShellError::NotFound(
|
||||
call.positional_nth(0)
|
||||
.expect("already checked for present positional")
|
||||
.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if !import_pattern.hidden.contains(&import_pattern.head.name)
|
||||
&& stack.remove_env_var(engine_state, &head_name_str).is_none()
|
||||
{
|
||||
// TODO: we may want to error in the future
|
||||
}
|
||||
stack.remove_env_var(engine_state, &env_var_name.item);
|
||||
|
||||
Ok(PipelineData::new(call.head))
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
mod alias;
|
||||
mod ast;
|
||||
mod commandline;
|
||||
mod debug;
|
||||
mod def;
|
||||
mod def_env;
|
||||
@ -11,7 +12,6 @@ mod export;
|
||||
mod export_alias;
|
||||
mod export_def;
|
||||
mod export_def_env;
|
||||
mod export_env;
|
||||
mod export_extern;
|
||||
mod export_use;
|
||||
mod extern_;
|
||||
@ -30,6 +30,7 @@ mod version;
|
||||
|
||||
pub use alias::Alias;
|
||||
pub use ast::Ast;
|
||||
pub use commandline::Commandline;
|
||||
pub use debug::Debug;
|
||||
pub use def::Def;
|
||||
pub use def_env::DefEnv;
|
||||
@ -41,7 +42,6 @@ pub use export::ExportCommand;
|
||||
pub use export_alias::ExportAlias;
|
||||
pub use export_def::ExportDef;
|
||||
pub use export_def_env::ExportDefEnv;
|
||||
pub use export_env::ExportEnvModule;
|
||||
pub use export_extern::ExportExtern;
|
||||
pub use export_use::ExportUse;
|
||||
pub use extern_::Extern;
|
||||
|
@ -55,8 +55,8 @@ impl Command for Module {
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Define an environment variable in a module and evaluate it",
|
||||
example: r#"module foo { export env FOO_ENV { "BAZ" } }; use foo FOO_ENV; $env.FOO_ENV"#,
|
||||
description: "Define an environment variable in a module",
|
||||
example: r#"module foo { export-env { let-env FOO = "BAZ" } }; use foo; $env.FOO"#,
|
||||
result: Some(Value::String {
|
||||
val: "BAZ".to_string(),
|
||||
span: Span::test_data(),
|
||||
|
@ -20,13 +20,13 @@ impl Command for OverlayHide {
|
||||
.optional("name", SyntaxShape::String, "Overlay to hide")
|
||||
.switch(
|
||||
"keep-custom",
|
||||
"Keep all newly added symbols within the next activated overlay",
|
||||
"Keep all newly added commands and aliases in the next activated overlay",
|
||||
Some('k'),
|
||||
)
|
||||
.named(
|
||||
"keep-env",
|
||||
SyntaxShape::List(Box::new(SyntaxShape::String)),
|
||||
"List of environment variables to keep from the hidden overlay",
|
||||
"List of environment variables to keep in the next activated overlay",
|
||||
Some('e'),
|
||||
)
|
||||
.category(Category::Core)
|
||||
@ -67,23 +67,7 @@ impl Command for OverlayHide {
|
||||
let keep_env: Option<Vec<Spanned<String>>> =
|
||||
call.get_flag(engine_state, stack, "keep-env")?;
|
||||
|
||||
let env_vars_to_keep = if call.has_flag("keep-custom") {
|
||||
if let Some(overlay_id) = engine_state.find_overlay(overlay_name.item.as_bytes()) {
|
||||
let overlay_frame = engine_state.get_overlay(overlay_id);
|
||||
let origin_module = engine_state.get_module(overlay_frame.origin);
|
||||
|
||||
stack
|
||||
.get_overlay_env_vars(engine_state, &overlay_name.item)
|
||||
.into_iter()
|
||||
.filter(|(name, _)| !origin_module.has_env_var(name.as_bytes()))
|
||||
.collect()
|
||||
} else {
|
||||
return Err(ShellError::OverlayNotFoundAtRuntime(
|
||||
overlay_name.item,
|
||||
overlay_name.span,
|
||||
));
|
||||
}
|
||||
} else if let Some(env_var_names_to_keep) = keep_env {
|
||||
let env_vars_to_keep = if let Some(env_var_names_to_keep) = keep_env {
|
||||
let mut env_vars_to_keep = vec![];
|
||||
|
||||
for name in env_var_names_to_keep.into_iter() {
|
||||
@ -110,10 +94,13 @@ impl Command for OverlayHide {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Hide an overlay created from a module",
|
||||
description: "Keep a custom command after hiding the overlay",
|
||||
example: r#"module spam { export def foo [] { "foo" } }
|
||||
overlay use spam
|
||||
overlay hide spam"#,
|
||||
def bar [] { "bar" }
|
||||
overlay hide spam --keep-custom
|
||||
bar
|
||||
"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
@ -125,7 +112,7 @@ impl Command for OverlayHide {
|
||||
},
|
||||
Example {
|
||||
description: "Hide the last activated overlay",
|
||||
example: r#"module spam { export env FOO { "foo" } }
|
||||
example: r#"module spam { export-env { let-env FOO = "foo" } }
|
||||
overlay use spam
|
||||
overlay hide"#,
|
||||
result: None,
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::{eval_block, find_in_dirs_env, redirect_env, CallExt};
|
||||
use nu_parser::trim_quotes_str;
|
||||
use nu_protocol::ast::{Call, Expr};
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
@ -55,7 +56,8 @@ impl Command for OverlayUse {
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
|
||||
let mut name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
|
||||
name_arg.item = trim_quotes_str(&name_arg.item).to_string();
|
||||
|
||||
let origin_module_id = if let Some(overlay_expr) = call.positional_nth(0) {
|
||||
if let Expr::Overlay(module_id) = overlay_expr.expr {
|
||||
@ -121,28 +123,6 @@ impl Command for OverlayUse {
|
||||
|
||||
let module = engine_state.get_module(module_id);
|
||||
|
||||
for (name, block_id) in module.env_vars() {
|
||||
let name = if let Ok(s) = String::from_utf8(name.clone()) {
|
||||
s
|
||||
} else {
|
||||
return Err(ShellError::NonUtf8(call.head));
|
||||
};
|
||||
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let val = eval_block(
|
||||
engine_state,
|
||||
caller_stack,
|
||||
block,
|
||||
PipelineData::new(call.head),
|
||||
false,
|
||||
true,
|
||||
)?
|
||||
.into_value(call.head);
|
||||
|
||||
caller_stack.add_env_var(name, val);
|
||||
}
|
||||
|
||||
// Evaluate the export-env block (if any) and keep its environment
|
||||
if let Some(block_id) = module.env_block {
|
||||
let maybe_path = find_in_dirs_env(&name_arg.item, engine_state, caller_stack)?;
|
||||
@ -191,6 +171,13 @@ impl Command for OverlayUse {
|
||||
description: "Create an overlay from a module",
|
||||
example: r#"module spam { export def foo [] { "foo" } }
|
||||
overlay use spam
|
||||
foo"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Create an overlay from a module and rename it",
|
||||
example: r#"module spam { export def foo [] { "foo" } }
|
||||
overlay use spam as spam_new
|
||||
foo"#,
|
||||
result: None,
|
||||
},
|
||||
@ -203,7 +190,7 @@ impl Command for OverlayUse {
|
||||
},
|
||||
Example {
|
||||
description: "Create an overlay from a file",
|
||||
example: r#"echo 'export env FOO { "foo" }' | save spam.nu
|
||||
example: r#"echo 'export-env { let-env FOO = "foo" }' | save spam.nu
|
||||
overlay use spam.nu
|
||||
$env.FOO"#,
|
||||
result: None,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use nu_engine::eval_block;
|
||||
use nu_protocol::ast::{Call, Expr, Expression, ImportPatternMember};
|
||||
use nu_engine::{eval_block, find_in_dirs_env, redirect_env};
|
||||
use nu_protocol::ast::{Call, Expr, Expression};
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||
@ -35,9 +35,9 @@ impl Command for Use {
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
caller_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let import_pattern = if let Some(Expression {
|
||||
expr: Expr::ImportPattern(pat),
|
||||
@ -58,68 +58,47 @@ impl Command for Use {
|
||||
if let Some(module_id) = import_pattern.head.id {
|
||||
let module = engine_state.get_module(module_id);
|
||||
|
||||
let env_vars_to_use = if import_pattern.members.is_empty() {
|
||||
module.env_vars_with_head(&import_pattern.head.name)
|
||||
} else {
|
||||
match &import_pattern.members[0] {
|
||||
ImportPatternMember::Glob { .. } => module.env_vars(),
|
||||
ImportPatternMember::Name { name, span } => {
|
||||
let mut output = vec![];
|
||||
|
||||
if let Some(id) = module.get_env_var_id(name) {
|
||||
output.push((name.clone(), id));
|
||||
} else if !module.has_decl(name) && !module.has_alias(name) {
|
||||
return Err(ShellError::EnvVarNotFoundAtRuntime(
|
||||
String::from_utf8_lossy(name).into(),
|
||||
*span,
|
||||
));
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
ImportPatternMember::List { names } => {
|
||||
let mut output = vec![];
|
||||
|
||||
for (name, span) in names {
|
||||
if let Some(id) = module.get_env_var_id(name) {
|
||||
output.push((name.clone(), id));
|
||||
} else if !module.has_decl(name) && !module.has_alias(name) {
|
||||
return Err(ShellError::EnvVarNotFoundAtRuntime(
|
||||
String::from_utf8_lossy(name).into(),
|
||||
*span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (name, block_id) in env_vars_to_use {
|
||||
let name = if let Ok(s) = String::from_utf8(name.clone()) {
|
||||
s
|
||||
} else {
|
||||
return Err(ShellError::NonUtf8(import_pattern.head.span));
|
||||
};
|
||||
|
||||
// Evaluate the export-env block if there is one
|
||||
if let Some(block_id) = module.env_block {
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let val = eval_block(
|
||||
engine_state,
|
||||
stack,
|
||||
block,
|
||||
PipelineData::new(call.head),
|
||||
false,
|
||||
true,
|
||||
)?
|
||||
.into_value(call.head);
|
||||
// See if the module is a file
|
||||
let module_arg_str = String::from_utf8_lossy(
|
||||
engine_state.get_span_contents(&import_pattern.head.span),
|
||||
);
|
||||
let maybe_parent = if let Some(path) =
|
||||
find_in_dirs_env(&module_arg_str, engine_state, caller_stack)?
|
||||
{
|
||||
path.parent().map(|p| p.to_path_buf()).or(None)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
stack.add_env_var(name, val);
|
||||
let mut callee_stack = caller_stack.gather_captures(&block.captures);
|
||||
|
||||
// If so, set the currently evaluated directory (file-relative PWD)
|
||||
if let Some(parent) = maybe_parent {
|
||||
let file_pwd = Value::String {
|
||||
val: parent.to_string_lossy().to_string(),
|
||||
span: call.head,
|
||||
};
|
||||
callee_stack.add_env_var("FILE_PWD".to_string(), file_pwd);
|
||||
}
|
||||
|
||||
// Run the block (discard the result)
|
||||
let _ = eval_block(
|
||||
engine_state,
|
||||
&mut callee_stack,
|
||||
block,
|
||||
input,
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
)?;
|
||||
|
||||
// Merge the block's environment to the current stack
|
||||
redirect_env(engine_state, caller_stack, &callee_stack);
|
||||
}
|
||||
} else {
|
||||
// TODO: This is a workaround since call.positional[0].span points at 0 for some reason
|
||||
// when this error is triggered
|
||||
return Err(ShellError::GenericError(
|
||||
format!(
|
||||
"Could not import from '{}'",
|
||||
@ -145,14 +124,6 @@ impl Command for Use {
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Define an environment variable in a module and evaluate it",
|
||||
example: r#"module foo { export env FOO_ENV { "BAZ" } }; use foo FOO_ENV; $env.FOO_ENV"#,
|
||||
result: Some(Value::String {
|
||||
val: "BAZ".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Define a custom command that participates in the environment in a module and call it",
|
||||
example: r#"module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
|
||||
|
@ -59,7 +59,7 @@ pub fn version(
|
||||
|
||||
cols.push("branch".to_string());
|
||||
vals.push(Value::String {
|
||||
val: shadow_rs::branch(),
|
||||
val: shadow::BRANCH.to_string(),
|
||||
span: call.head,
|
||||
});
|
||||
|
||||
|
@ -125,7 +125,7 @@ impl Command for AndDb {
|
||||
}
|
||||
|
||||
fn modify_query(query: &mut Box<Query>, expression: Expr, span: Span) -> Result<(), ShellError> {
|
||||
match query.body {
|
||||
match *query.body {
|
||||
SetExpr::Select(ref mut select) => modify_select(select, expression, span)?,
|
||||
_ => {
|
||||
return Err(ShellError::GenericError(
|
||||
|
@ -113,7 +113,7 @@ fn alias_db(
|
||||
Vec::new(),
|
||||
)),
|
||||
Some(statement) => match statement {
|
||||
Statement::Query(query) => match &mut query.body {
|
||||
Statement::Query(query) => match &mut *query.body {
|
||||
SetExpr::Select(select) => {
|
||||
select.as_mut().from.iter_mut().for_each(|table| {
|
||||
let new_alias = Some(TableAlias {
|
@ -17,7 +17,7 @@ pub fn value_into_table_factor(
|
||||
Ok(TableFactor::Table {
|
||||
name: ObjectName(vec![ident]),
|
||||
alias,
|
||||
args: Vec::new(),
|
||||
args: None,
|
||||
with_hints: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
@ -96,12 +96,12 @@ fn create_statement(
|
||||
) -> Result<Statement, ShellError> {
|
||||
let query = Query {
|
||||
with: None,
|
||||
body: SetExpr::Select(Box::new(create_select(
|
||||
body: Box::new(SetExpr::Select(Box::new(create_select(
|
||||
connection,
|
||||
engine_state,
|
||||
stack,
|
||||
call,
|
||||
)?)),
|
||||
)?))),
|
||||
order_by: Vec::new(),
|
||||
limit: None,
|
||||
offset: None,
|
||||
@ -121,18 +121,18 @@ fn modify_statement(
|
||||
) -> Result<Statement, ShellError> {
|
||||
match statement {
|
||||
Statement::Query(ref mut query) => {
|
||||
match query.body {
|
||||
match *query.body {
|
||||
SetExpr::Select(ref mut select) => {
|
||||
let table = create_table(connection, engine_state, stack, call)?;
|
||||
select.from.push(table);
|
||||
}
|
||||
_ => {
|
||||
query.as_mut().body = SetExpr::Select(Box::new(create_select(
|
||||
query.as_mut().body = Box::new(SetExpr::Select(Box::new(create_select(
|
||||
connection,
|
||||
engine_state,
|
||||
stack,
|
||||
call,
|
||||
)?));
|
||||
)?)));
|
||||
}
|
||||
};
|
||||
|
||||
@ -167,6 +167,7 @@ fn create_select(
|
||||
distribute_by: Vec::new(),
|
||||
sort_by: Vec::new(),
|
||||
having: None,
|
||||
qualify: None,
|
||||
})
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ impl Command for GroupByDb {
|
||||
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
match db.statement.as_mut() {
|
||||
Some(statement) => match statement {
|
||||
Statement::Query(ref mut query) => match &mut query.body {
|
||||
Statement::Query(ref mut query) => match &mut *query.body {
|
||||
SetExpr::Select(ref mut select) => select.group_by = expressions,
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
|
@ -146,7 +146,7 @@ fn modify_statement(
|
||||
) -> Result<Statement, ShellError> {
|
||||
match statement {
|
||||
Statement::Query(ref mut query) => {
|
||||
match &mut query.body {
|
||||
match &mut *query.body {
|
||||
SetExpr::Select(ref mut select) => {
|
||||
modify_from(connection, select, engine_state, stack, call)?
|
||||
}
|
||||
|
@ -1,45 +1,45 @@
|
||||
// Conversions between value and sqlparser objects
|
||||
pub mod conversions;
|
||||
|
||||
mod alias;
|
||||
mod and;
|
||||
mod as_;
|
||||
mod collect;
|
||||
mod describe;
|
||||
mod from;
|
||||
mod from_table;
|
||||
mod group_by;
|
||||
mod into_db;
|
||||
mod into_sqlite;
|
||||
mod join;
|
||||
mod limit;
|
||||
mod open;
|
||||
mod open_db;
|
||||
mod or;
|
||||
mod order_by;
|
||||
mod query;
|
||||
mod query_db;
|
||||
mod schema;
|
||||
mod select;
|
||||
mod to_db;
|
||||
mod where_;
|
||||
|
||||
// Temporal module to create Query objects
|
||||
mod testing;
|
||||
use testing::TestingDb;
|
||||
mod testing_db;
|
||||
use testing_db::TestingDb;
|
||||
|
||||
use alias::AliasDb;
|
||||
use and::AndDb;
|
||||
use as_::AliasDb;
|
||||
use collect::CollectDb;
|
||||
pub(crate) use describe::DescribeDb;
|
||||
pub(crate) use from::FromDb;
|
||||
pub(crate) use from_table::FromDb;
|
||||
use group_by::GroupByDb;
|
||||
pub(crate) use into_db::ToDataBase;
|
||||
use into_sqlite::IntoSqliteDb;
|
||||
use join::JoinDb;
|
||||
use limit::LimitDb;
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use open::OpenDb;
|
||||
use open_db::OpenDb;
|
||||
use or::OrDb;
|
||||
use order_by::OrderByDb;
|
||||
use query::QueryDb;
|
||||
use query_db::QueryDb;
|
||||
use schema::SchemaDb;
|
||||
pub(crate) use select::ProjectionDb;
|
||||
pub(crate) use to_db::ToDataBase;
|
||||
use where_::WhereDb;
|
||||
|
||||
pub fn add_commands_decls(working_set: &mut StateWorkingSet) {
|
||||
|
@ -125,7 +125,7 @@ impl Command for OrDb {
|
||||
}
|
||||
|
||||
fn modify_query(query: &mut Box<Query>, expression: Expr, span: Span) -> Result<(), ShellError> {
|
||||
match query.body {
|
||||
match *query.body {
|
||||
SetExpr::Select(ref mut select) => modify_select(select, expression, span)?,
|
||||
_ => {
|
||||
return Err(ShellError::GenericError(
|
||||
|
@ -108,7 +108,7 @@ impl Command for ProjectionDb {
|
||||
fn create_statement(expressions: Vec<SelectItem>) -> Statement {
|
||||
let query = Query {
|
||||
with: None,
|
||||
body: SetExpr::Select(Box::new(create_select(expressions))),
|
||||
body: Box::new(SetExpr::Select(Box::new(create_select(expressions)))),
|
||||
order_by: Vec::new(),
|
||||
limit: None,
|
||||
offset: None,
|
||||
@ -126,10 +126,11 @@ fn modify_statement(
|
||||
) -> Result<Statement, ShellError> {
|
||||
match statement {
|
||||
Statement::Query(ref mut query) => {
|
||||
match query.body {
|
||||
match *query.body {
|
||||
SetExpr::Select(ref mut select) => select.as_mut().projection = expressions,
|
||||
_ => {
|
||||
query.as_mut().body = SetExpr::Select(Box::new(create_select(expressions)));
|
||||
query.as_mut().body =
|
||||
Box::new(SetExpr::Select(Box::new(create_select(expressions))));
|
||||
}
|
||||
};
|
||||
|
||||
@ -159,6 +160,7 @@ fn create_select(projection: Vec<SelectItem>) -> Select {
|
||||
distribute_by: Vec::new(),
|
||||
sort_by: Vec::new(),
|
||||
having: None,
|
||||
qualify: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,10 +99,10 @@ impl Command for WhereDb {
|
||||
}
|
||||
|
||||
fn modify_query(query: &mut Box<Query>, expression: Expr) {
|
||||
match query.body {
|
||||
match *query.body {
|
||||
SetExpr::Select(ref mut select) => modify_select(select, expression),
|
||||
_ => {
|
||||
query.as_mut().body = SetExpr::Select(Box::new(create_select(expression)));
|
||||
query.as_mut().body = Box::new(SetExpr::Select(Box::new(create_select(expression))));
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -125,6 +125,7 @@ fn create_select(expression: Expr) -> Select {
|
||||
distribute_by: Vec::new(),
|
||||
sort_by: Vec::new(),
|
||||
having: None,
|
||||
qualify: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,6 +132,7 @@ impl Command for FunctionExpr {
|
||||
args,
|
||||
over: None,
|
||||
distinct: call.has_flag("distinct"),
|
||||
special: false,
|
||||
})
|
||||
.into();
|
||||
|
||||
|
@ -339,7 +339,7 @@ impl ExprDb {
|
||||
Expr::TypedString { .. } => todo!(),
|
||||
Expr::MapAccess { .. } => todo!(),
|
||||
Expr::Case { .. } => todo!(),
|
||||
Expr::Exists(_) => todo!(),
|
||||
Expr::Exists { .. } => todo!(),
|
||||
Expr::Subquery(_) => todo!(),
|
||||
Expr::ListAgg(_) => todo!(),
|
||||
Expr::GroupingSets(_) => todo!(),
|
||||
@ -348,6 +348,25 @@ impl ExprDb {
|
||||
Expr::Tuple(_) => todo!(),
|
||||
Expr::ArrayIndex { .. } => todo!(),
|
||||
Expr::Array(_) => todo!(),
|
||||
Expr::JsonAccess { .. } => todo!(),
|
||||
Expr::CompositeAccess { .. } => todo!(),
|
||||
Expr::IsFalse(_) => todo!(),
|
||||
Expr::IsNotFalse(_) => todo!(),
|
||||
Expr::IsTrue(_) => todo!(),
|
||||
Expr::IsNotTrue(_) => todo!(),
|
||||
Expr::IsUnknown(_) => todo!(),
|
||||
Expr::IsNotUnknown(_) => todo!(),
|
||||
Expr::Like { .. } => todo!(),
|
||||
Expr::ILike { .. } => todo!(),
|
||||
Expr::SimilarTo { .. } => todo!(),
|
||||
Expr::AnyOp(_) => todo!(),
|
||||
Expr::AllOp(_) => todo!(),
|
||||
Expr::SafeCast { .. } => todo!(),
|
||||
Expr::AtTimeZone { .. } => todo!(),
|
||||
Expr::Position { .. } => todo!(),
|
||||
Expr::Overlay { .. } => todo!(),
|
||||
Expr::AggregateExpressionWithFilter { .. } => todo!(),
|
||||
Expr::ArraySubquery(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,11 +13,15 @@ mod last;
|
||||
mod list;
|
||||
mod melt;
|
||||
mod open;
|
||||
mod query_df;
|
||||
mod rename;
|
||||
mod sample;
|
||||
mod shape;
|
||||
mod slice;
|
||||
mod sql_context;
|
||||
mod sql_expr;
|
||||
mod take;
|
||||
mod to_arrow;
|
||||
mod to_csv;
|
||||
mod to_df;
|
||||
mod to_nu;
|
||||
@ -41,11 +45,15 @@ pub use last::LastDF;
|
||||
pub use list::ListDF;
|
||||
pub use melt::MeltDF;
|
||||
pub use open::OpenDataFrame;
|
||||
pub use query_df::QueryDf;
|
||||
pub use rename::RenameDF;
|
||||
pub use sample::SampleDF;
|
||||
pub use shape::ShapeDF;
|
||||
pub use slice::SliceDF;
|
||||
pub use sql_context::SQLContext;
|
||||
pub use sql_expr::parse_sql_expr;
|
||||
pub use take::TakeDF;
|
||||
pub use to_arrow::ToArrow;
|
||||
pub use to_csv::ToCSV;
|
||||
pub use to_df::ToDataFrame;
|
||||
pub use to_nu::ToNu;
|
||||
@ -79,11 +87,13 @@ pub fn add_eager_decls(working_set: &mut StateWorkingSet) {
|
||||
ListDF,
|
||||
MeltDF,
|
||||
OpenDataFrame,
|
||||
QueryDf,
|
||||
RenameDF,
|
||||
SampleDF,
|
||||
ShapeDF,
|
||||
SliceDF,
|
||||
TakeDF,
|
||||
ToArrow,
|
||||
ToCSV,
|
||||
ToDataFrame,
|
||||
ToNu,
|
||||
|
@ -9,8 +9,8 @@ use nu_protocol::{
|
||||
use std::{fs::File, io::BufReader, path::PathBuf};
|
||||
|
||||
use polars::prelude::{
|
||||
CsvEncoding, CsvReader, JsonReader, LazyCsvReader, LazyFrame, ParallelStrategy, ParquetReader,
|
||||
ScanArgsParquet, SerReader,
|
||||
CsvEncoding, CsvReader, IpcReader, JsonReader, LazyCsvReader, LazyFrame, ParallelStrategy,
|
||||
ParquetReader, ScanArgsIpc, ScanArgsParquet, SerReader,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -22,7 +22,7 @@ impl Command for OpenDataFrame {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Opens csv, json or parquet file to create dataframe"
|
||||
"Opens csv, json, arrow, or parquet file to create dataframe"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
@ -33,6 +33,12 @@ impl Command for OpenDataFrame {
|
||||
"file path to load values from",
|
||||
)
|
||||
.switch("lazy", "creates a lazy dataframe", Some('l'))
|
||||
.named(
|
||||
"type",
|
||||
SyntaxShape::String,
|
||||
"File type: csv, tsv, json, parquet, arrow. If omitted, derive from file extension",
|
||||
Some('t'),
|
||||
)
|
||||
.named(
|
||||
"delimiter",
|
||||
SyntaxShape::String,
|
||||
@ -93,15 +99,33 @@ fn command(
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||
|
||||
match file.item.extension() {
|
||||
Some(e) => match e.to_str() {
|
||||
Some("csv") | Some("tsv") => from_csv(engine_state, stack, call),
|
||||
Some("parquet") => from_parquet(engine_state, stack, call),
|
||||
Some("json") => from_json(engine_state, stack, call),
|
||||
_ => Err(ShellError::FileNotFoundCustom(
|
||||
"Not a csv, tsv, parquet or json file".into(),
|
||||
let type_option: Option<Spanned<String>> = call.get_flag(engine_state, stack, "type")?;
|
||||
|
||||
let type_id = match &type_option {
|
||||
Some(ref t) => Some((t.item.to_owned(), "Invalid type", t.span)),
|
||||
None => match file.item.extension() {
|
||||
Some(e) => Some((
|
||||
e.to_string_lossy().into_owned(),
|
||||
"Invalid extension",
|
||||
file.span,
|
||||
)),
|
||||
None => None,
|
||||
},
|
||||
};
|
||||
|
||||
match type_id {
|
||||
Some((e, msg, blamed)) => match e.as_str() {
|
||||
"csv" | "tsv" => from_csv(engine_state, stack, call),
|
||||
"parquet" => from_parquet(engine_state, stack, call),
|
||||
"ipc" | "arrow" => from_ipc(engine_state, stack, call),
|
||||
"json" => from_json(engine_state, stack, call),
|
||||
_ => Err(ShellError::FileNotFoundCustom(
|
||||
format!(
|
||||
"{}. Supported values: csv, tsv, parquet, ipc, arrow, json",
|
||||
msg
|
||||
),
|
||||
blamed,
|
||||
)),
|
||||
},
|
||||
None => Err(ShellError::FileNotFoundCustom(
|
||||
"File without extension".into(),
|
||||
@ -177,6 +201,70 @@ fn from_parquet(
|
||||
}
|
||||
}
|
||||
|
||||
fn from_ipc(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<Value, ShellError> {
|
||||
if call.has_flag("lazy") {
|
||||
let file: String = call.req(engine_state, stack, 0)?;
|
||||
let args = ScanArgsIpc {
|
||||
n_rows: None,
|
||||
cache: true,
|
||||
rechunk: false,
|
||||
row_count: None,
|
||||
};
|
||||
|
||||
let df: NuLazyFrame = LazyFrame::scan_ipc(file, args)
|
||||
.map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"IPC reader error".into(),
|
||||
format!("{:?}", e),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?
|
||||
.into();
|
||||
|
||||
df.into_value(call.head)
|
||||
} else {
|
||||
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
|
||||
|
||||
let r = File::open(&file.item).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error opening file".into(),
|
||||
e.to_string(),
|
||||
Some(file.span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?;
|
||||
let reader = IpcReader::new(r);
|
||||
|
||||
let reader = match columns {
|
||||
None => reader,
|
||||
Some(columns) => reader.with_columns(Some(columns)),
|
||||
};
|
||||
|
||||
let df: NuDataFrame = reader
|
||||
.finish()
|
||||
.map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"IPC reader error".into(),
|
||||
format!("{:?}", e),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?
|
||||
.into();
|
||||
|
||||
Ok(df.into_value(call.head))
|
||||
}
|
||||
}
|
||||
|
||||
fn from_json(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
|
106
crates/nu-command/src/dataframe/eager/query_df.rs
Normal file
106
crates/nu-command/src/dataframe/eager/query_df.rs
Normal file
@ -0,0 +1,106 @@
|
||||
use super::super::values::NuDataFrame;
|
||||
use crate::dataframe::values::Column;
|
||||
use crate::dataframe::{eager::SQLContext, values::NuLazyFrame};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
// attribution:
|
||||
// sql_context.rs, and sql_expr.rs were copied from polars-sql. thank you.
|
||||
// maybe we should just use the crate at some point but it's not published yet.
|
||||
// https://github.com/pola-rs/polars/tree/master/polars-sql
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct QueryDf;
|
||||
|
||||
impl Command for QueryDf {
|
||||
fn name(&self) -> &str {
|
||||
"query df"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Query dataframe using SQL. Note: The dataframe is always named 'df' in your query's from clause."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("sql", SyntaxShape::String, "sql query")
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
.output_type(Type::Custom("dataframe".into()))
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["dataframe", "sql", "search"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Query dataframe using SQL",
|
||||
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(),
|
||||
vec![Value::test_int(1), Value::test_int(3)],
|
||||
)])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
command(engine_state, stack, call, input)
|
||||
}
|
||||
}
|
||||
|
||||
fn command(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let sql_query: String = call.req(engine_state, stack, 0)?;
|
||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||
|
||||
let mut ctx = SQLContext::new();
|
||||
ctx.register("df", &df.df);
|
||||
let df_sql = ctx.execute(&sql_query).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Dataframe Error".into(),
|
||||
e.to_string(),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?;
|
||||
let lazy = NuLazyFrame::new(false, df_sql);
|
||||
|
||||
let eager = lazy.collect(call.head)?;
|
||||
let value = Value::CustomValue {
|
||||
val: Box::new(eager),
|
||||
span: call.head,
|
||||
};
|
||||
|
||||
Ok(PipelineData::Value(value, None))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::test_dataframe::test_dataframe;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_dataframe(vec![Box::new(QueryDf {})])
|
||||
}
|
||||
}
|
220
crates/nu-command/src/dataframe/eager/sql_context.rs
Normal file
220
crates/nu-command/src/dataframe/eager/sql_context.rs
Normal file
@ -0,0 +1,220 @@
|
||||
use crate::dataframe::eager::sql_expr::parse_sql_expr;
|
||||
use polars::error::PolarsError;
|
||||
use polars::prelude::{col, DataFrame, DataType, IntoLazy, LazyFrame};
|
||||
use sqlparser::ast::{
|
||||
Expr as SqlExpr, Select, SelectItem, SetExpr, Statement, TableFactor, Value as SQLValue,
|
||||
};
|
||||
use sqlparser::dialect::GenericDialect;
|
||||
use sqlparser::parser::Parser;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SQLContext {
|
||||
table_map: HashMap<String, LazyFrame>,
|
||||
dialect: GenericDialect,
|
||||
}
|
||||
|
||||
impl SQLContext {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
table_map: HashMap::new(),
|
||||
dialect: GenericDialect::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(&mut self, name: &str, df: &DataFrame) {
|
||||
self.table_map.insert(name.to_owned(), df.clone().lazy());
|
||||
}
|
||||
|
||||
fn execute_select(&self, select_stmt: &Select) -> Result<LazyFrame, PolarsError> {
|
||||
// Determine involved dataframe
|
||||
// Implicit join require some more work in query parsers, Explicit join are preferred for now.
|
||||
let tbl = select_stmt.from.get(0).ok_or_else(|| {
|
||||
PolarsError::NotFound("No table found in select statement".to_string())
|
||||
})?;
|
||||
let mut alias_map = HashMap::new();
|
||||
let tbl_name = match &tbl.relation {
|
||||
TableFactor::Table { name, alias, .. } => {
|
||||
let tbl_name = name
|
||||
.0
|
||||
.get(0)
|
||||
.ok_or_else(|| {
|
||||
PolarsError::NotFound("No table found in select statement".to_string())
|
||||
})?
|
||||
.value
|
||||
.to_string();
|
||||
if self.table_map.contains_key(&tbl_name) {
|
||||
if let Some(alias) = alias {
|
||||
alias_map.insert(alias.name.value.clone(), tbl_name.to_owned());
|
||||
};
|
||||
tbl_name
|
||||
} else {
|
||||
return Err(PolarsError::ComputeError(
|
||||
format!("Table name {tbl_name} was not found").into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
// Support bare table, optional with alias for now
|
||||
_ => return Err(PolarsError::ComputeError("Not implemented".into())),
|
||||
};
|
||||
let df = &self.table_map[&tbl_name];
|
||||
let mut raw_projection_before_alias: HashMap<String, usize> = HashMap::new();
|
||||
let mut contain_wildcard = false;
|
||||
// Filter Expression
|
||||
let df = match select_stmt.selection.as_ref() {
|
||||
Some(expr) => {
|
||||
let filter_expression = parse_sql_expr(expr)?;
|
||||
df.clone().filter(filter_expression)
|
||||
}
|
||||
None => df.clone(),
|
||||
};
|
||||
// Column Projections
|
||||
let projection = select_stmt
|
||||
.projection
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, select_item)| {
|
||||
Ok(match select_item {
|
||||
SelectItem::UnnamedExpr(expr) => {
|
||||
let expr = parse_sql_expr(expr)?;
|
||||
raw_projection_before_alias.insert(format!("{:?}", expr), i);
|
||||
expr
|
||||
}
|
||||
SelectItem::ExprWithAlias { expr, alias } => {
|
||||
let expr = parse_sql_expr(expr)?;
|
||||
raw_projection_before_alias.insert(format!("{:?}", expr), i);
|
||||
expr.alias(&alias.value)
|
||||
}
|
||||
SelectItem::QualifiedWildcard(_) | SelectItem::Wildcard => {
|
||||
contain_wildcard = true;
|
||||
col("*")
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, PolarsError>>()?;
|
||||
// Check for group by
|
||||
// After projection since there might be number.
|
||||
let group_by = select_stmt
|
||||
.group_by
|
||||
.iter()
|
||||
.map(
|
||||
|e|match e {
|
||||
SqlExpr::Value(SQLValue::Number(idx, _)) => {
|
||||
let idx = match idx.parse::<usize>() {
|
||||
Ok(0)| Err(_) => Err(
|
||||
PolarsError::ComputeError(
|
||||
format!("Group By Error: Only positive number or expression are supported, got {idx}").into()
|
||||
)),
|
||||
Ok(idx) => Ok(idx)
|
||||
}?;
|
||||
Ok(projection[idx].clone())
|
||||
}
|
||||
SqlExpr::Value(_) => Err(
|
||||
PolarsError::ComputeError("Group By Error: Only positive number or expression are supported".into())
|
||||
),
|
||||
_ => parse_sql_expr(e)
|
||||
}
|
||||
)
|
||||
.collect::<Result<Vec<_>, PolarsError>>()?;
|
||||
|
||||
let df = if group_by.is_empty() {
|
||||
df.select(projection)
|
||||
} else {
|
||||
// check groupby and projection due to difference between SQL and polars
|
||||
// Return error on wild card, shouldn't process this
|
||||
if contain_wildcard {
|
||||
return Err(PolarsError::ComputeError(
|
||||
"Group By Error: Can't processed wildcard in groupby".into(),
|
||||
));
|
||||
}
|
||||
// Default polars group by will have group by columns at the front
|
||||
// need some container to contain position of group by columns and its position
|
||||
// at the final agg projection, check the schema for the existence of group by column
|
||||
// and its projections columns, keeping the original index
|
||||
let (exclude_expr, groupby_pos): (Vec<_>, Vec<_>) = group_by
|
||||
.iter()
|
||||
.map(|expr| raw_projection_before_alias.get(&format!("{:?}", expr)))
|
||||
.enumerate()
|
||||
.filter(|(_, proj_p)| proj_p.is_some())
|
||||
.map(|(gb_p, proj_p)| (*proj_p.unwrap_or(&0), (*proj_p.unwrap_or(&0), gb_p)))
|
||||
.unzip();
|
||||
let (agg_projection, agg_proj_pos): (Vec<_>, Vec<_>) = projection
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(i, _)| !exclude_expr.contains(i))
|
||||
.enumerate()
|
||||
.map(|(agg_pj, (proj_p, expr))| (expr.clone(), (proj_p, agg_pj + group_by.len())))
|
||||
.unzip();
|
||||
let agg_df = df.groupby(group_by).agg(agg_projection);
|
||||
let mut final_proj_pos = groupby_pos
|
||||
.into_iter()
|
||||
.chain(agg_proj_pos.into_iter())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
final_proj_pos.sort_by(|(proj_pa, _), (proj_pb, _)| proj_pa.cmp(proj_pb));
|
||||
let final_proj = final_proj_pos
|
||||
.into_iter()
|
||||
.map(|(_, shm_p)| {
|
||||
col(agg_df
|
||||
.clone()
|
||||
// FIXME: had to do this mess to get get_index to work, not sure why. need help
|
||||
.collect()
|
||||
.unwrap_or_default()
|
||||
.schema()
|
||||
.get_index(shm_p)
|
||||
.unwrap_or((&"".to_string(), &DataType::Null))
|
||||
.0)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
agg_df.select(final_proj)
|
||||
};
|
||||
Ok(df)
|
||||
}
|
||||
|
||||
pub fn execute(&self, query: &str) -> Result<LazyFrame, PolarsError> {
|
||||
let ast = Parser::parse_sql(&self.dialect, query)
|
||||
.map_err(|e| PolarsError::ComputeError(format!("{:?}", e).into()))?;
|
||||
if ast.len() != 1 {
|
||||
Err(PolarsError::ComputeError(
|
||||
"One and only one statement at a time please".into(),
|
||||
))
|
||||
} else {
|
||||
let ast = ast
|
||||
.get(0)
|
||||
.ok_or_else(|| PolarsError::NotFound("No statement found".to_string()))?;
|
||||
Ok(match ast {
|
||||
Statement::Query(query) => {
|
||||
let rs = match &*query.body {
|
||||
SetExpr::Select(select_stmt) => self.execute_select(select_stmt)?,
|
||||
_ => {
|
||||
return Err(PolarsError::ComputeError(
|
||||
"INSERT, UPDATE is not supported for polars".into(),
|
||||
))
|
||||
}
|
||||
};
|
||||
match &query.limit {
|
||||
Some(SqlExpr::Value(SQLValue::Number(nrow, _))) => {
|
||||
let nrow = nrow.parse().map_err(|err| {
|
||||
PolarsError::ComputeError(
|
||||
format!("Conversion Error: {:?}", err).into(),
|
||||
)
|
||||
})?;
|
||||
rs.limit(nrow)
|
||||
}
|
||||
None => rs,
|
||||
_ => {
|
||||
return Err(PolarsError::ComputeError(
|
||||
"Only support number argument to LIMIT clause".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(PolarsError::ComputeError(
|
||||
format!("Statement type {:?} is not supported", ast).into(),
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
191
crates/nu-command/src/dataframe/eager/sql_expr.rs
Normal file
191
crates/nu-command/src/dataframe/eager/sql_expr.rs
Normal file
@ -0,0 +1,191 @@
|
||||
use polars::error::PolarsError;
|
||||
use polars::prelude::{col, lit, DataType, Expr, LiteralValue, Result, TimeUnit};
|
||||
|
||||
use sqlparser::ast::{
|
||||
BinaryOperator as SQLBinaryOperator, DataType as SQLDataType, Expr as SqlExpr,
|
||||
Function as SQLFunction, Value as SqlValue, WindowSpec,
|
||||
};
|
||||
|
||||
fn map_sql_polars_datatype(data_type: &SQLDataType) -> Result<DataType> {
|
||||
Ok(match data_type {
|
||||
SQLDataType::Char(_)
|
||||
| SQLDataType::Varchar(_)
|
||||
| SQLDataType::Uuid
|
||||
| SQLDataType::Clob(_)
|
||||
| SQLDataType::Text
|
||||
| SQLDataType::String => DataType::Utf8,
|
||||
SQLDataType::Float(_) => DataType::Float32,
|
||||
SQLDataType::Real => DataType::Float32,
|
||||
SQLDataType::Double => DataType::Float64,
|
||||
SQLDataType::TinyInt(_) => DataType::Int8,
|
||||
SQLDataType::UnsignedTinyInt(_) => DataType::UInt8,
|
||||
SQLDataType::SmallInt(_) => DataType::Int16,
|
||||
SQLDataType::UnsignedSmallInt(_) => DataType::UInt16,
|
||||
SQLDataType::Int(_) => DataType::Int32,
|
||||
SQLDataType::UnsignedInt(_) => DataType::UInt32,
|
||||
SQLDataType::BigInt(_) => DataType::Int64,
|
||||
SQLDataType::UnsignedBigInt(_) => DataType::UInt64,
|
||||
|
||||
SQLDataType::Boolean => DataType::Boolean,
|
||||
SQLDataType::Date => DataType::Date,
|
||||
SQLDataType::Time => DataType::Time,
|
||||
SQLDataType::Timestamp => DataType::Datetime(TimeUnit::Milliseconds, None),
|
||||
SQLDataType::Interval => DataType::Duration(TimeUnit::Milliseconds),
|
||||
SQLDataType::Array(inner_type) => {
|
||||
DataType::List(Box::new(map_sql_polars_datatype(inner_type)?))
|
||||
}
|
||||
_ => {
|
||||
return Err(PolarsError::ComputeError(
|
||||
format!(
|
||||
"SQL Datatype {:?} was not supported in polars-sql yet!",
|
||||
data_type
|
||||
)
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn cast_(expr: Expr, data_type: &SQLDataType) -> Result<Expr> {
|
||||
let polars_type = map_sql_polars_datatype(data_type)?;
|
||||
Ok(expr.cast(polars_type))
|
||||
}
|
||||
|
||||
fn binary_op_(left: Expr, right: Expr, op: &SQLBinaryOperator) -> Result<Expr> {
|
||||
Ok(match op {
|
||||
SQLBinaryOperator::Plus => left + right,
|
||||
SQLBinaryOperator::Minus => left - right,
|
||||
SQLBinaryOperator::Multiply => left * right,
|
||||
SQLBinaryOperator::Divide => left / right,
|
||||
SQLBinaryOperator::Modulo => left % right,
|
||||
SQLBinaryOperator::StringConcat => left.cast(DataType::Utf8) + right.cast(DataType::Utf8),
|
||||
SQLBinaryOperator::Gt => left.gt(right),
|
||||
SQLBinaryOperator::Lt => left.lt(right),
|
||||
SQLBinaryOperator::GtEq => left.gt_eq(right),
|
||||
SQLBinaryOperator::LtEq => left.lt_eq(right),
|
||||
SQLBinaryOperator::Eq => left.eq(right),
|
||||
SQLBinaryOperator::NotEq => left.eq(right).not(),
|
||||
SQLBinaryOperator::And => left.and(right),
|
||||
SQLBinaryOperator::Or => left.or(right),
|
||||
SQLBinaryOperator::Xor => left.xor(right),
|
||||
_ => {
|
||||
return Err(PolarsError::ComputeError(
|
||||
format!("SQL Operator {:?} was not supported in polars-sql yet!", op).into(),
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn literal_expr(value: &SqlValue) -> Result<Expr> {
|
||||
Ok(match value {
|
||||
SqlValue::Number(s, _) => {
|
||||
// Check for existence of decimal separator dot
|
||||
if s.contains('.') {
|
||||
s.parse::<f64>().map(lit).map_err(|_| {
|
||||
PolarsError::ComputeError(format!("Can't parse literal {:?}", s).into())
|
||||
})
|
||||
} else {
|
||||
s.parse::<i64>().map(lit).map_err(|_| {
|
||||
PolarsError::ComputeError(format!("Can't parse literal {:?}", s).into())
|
||||
})
|
||||
}?
|
||||
}
|
||||
SqlValue::SingleQuotedString(s) => lit(s.clone()),
|
||||
SqlValue::NationalStringLiteral(s) => lit(s.clone()),
|
||||
SqlValue::HexStringLiteral(s) => lit(s.clone()),
|
||||
SqlValue::DoubleQuotedString(s) => lit(s.clone()),
|
||||
SqlValue::Boolean(b) => lit(*b),
|
||||
SqlValue::Null => Expr::Literal(LiteralValue::Null),
|
||||
_ => {
|
||||
return Err(PolarsError::ComputeError(
|
||||
format!(
|
||||
"Parsing SQL Value {:?} was not supported in polars-sql yet!",
|
||||
value
|
||||
)
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_sql_expr(expr: &SqlExpr) -> Result<Expr> {
|
||||
Ok(match expr {
|
||||
SqlExpr::Identifier(e) => col(&e.value),
|
||||
SqlExpr::BinaryOp { left, op, right } => {
|
||||
let left = parse_sql_expr(left)?;
|
||||
let right = parse_sql_expr(right)?;
|
||||
binary_op_(left, right, op)?
|
||||
}
|
||||
SqlExpr::Function(sql_function) => parse_sql_function(sql_function)?,
|
||||
SqlExpr::Cast { expr, data_type } => cast_(parse_sql_expr(expr)?, data_type)?,
|
||||
SqlExpr::Nested(expr) => parse_sql_expr(expr)?,
|
||||
SqlExpr::Value(value) => literal_expr(value)?,
|
||||
_ => {
|
||||
return Err(PolarsError::ComputeError(
|
||||
format!(
|
||||
"Expression: {:?} was not supported in polars-sql yet!",
|
||||
expr
|
||||
)
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn apply_window_spec(expr: Expr, window_spec: &Option<WindowSpec>) -> Result<Expr> {
|
||||
Ok(match &window_spec {
|
||||
Some(window_spec) => {
|
||||
// Process for simple window specification, partition by first
|
||||
let partition_by = window_spec
|
||||
.partition_by
|
||||
.iter()
|
||||
.map(parse_sql_expr)
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
expr.over(partition_by)
|
||||
// Order by and Row range may not be supported at the moment
|
||||
}
|
||||
None => expr,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_sql_function(sql_function: &SQLFunction) -> Result<Expr> {
|
||||
use sqlparser::ast::{FunctionArg, FunctionArgExpr};
|
||||
// Function name mostly do not have name space, so it mostly take the first args
|
||||
let function_name = sql_function.name.0[0].value.to_lowercase();
|
||||
let args = sql_function
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| match arg {
|
||||
FunctionArg::Named { arg, .. } => arg,
|
||||
FunctionArg::Unnamed(arg) => arg,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok(
|
||||
match (
|
||||
function_name.as_str(),
|
||||
args.as_slice(),
|
||||
sql_function.distinct,
|
||||
) {
|
||||
("sum", [FunctionArgExpr::Expr(expr)], false) => {
|
||||
apply_window_spec(parse_sql_expr(expr)?, &sql_function.over)?.sum()
|
||||
}
|
||||
("count", [FunctionArgExpr::Expr(expr)], false) => {
|
||||
apply_window_spec(parse_sql_expr(expr)?, &sql_function.over)?.count()
|
||||
}
|
||||
("count", [FunctionArgExpr::Expr(expr)], true) => {
|
||||
apply_window_spec(parse_sql_expr(expr)?, &sql_function.over)?.n_unique()
|
||||
}
|
||||
// Special case for wildcard args to count function.
|
||||
("count", [FunctionArgExpr::Wildcard], false) => lit(1i32).count(),
|
||||
_ => {
|
||||
return Err(PolarsError::ComputeError(
|
||||
format!(
|
||||
"Function {:?} with args {:?} was not supported in polars-sql yet!",
|
||||
function_name, args
|
||||
)
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
94
crates/nu-command/src/dataframe/eager/to_arrow.rs
Normal file
94
crates/nu-command/src/dataframe/eager/to_arrow.rs
Normal file
@ -0,0 +1,94 @@
|
||||
use std::{fs::File, path::PathBuf};
|
||||
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
use polars::prelude::{IpcWriter, SerWriter};
|
||||
|
||||
use super::super::values::NuDataFrame;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ToArrow;
|
||||
|
||||
impl Command for ToArrow {
|
||||
fn name(&self) -> &str {
|
||||
"to arrow"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Saves dataframe to arrow file"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("file", SyntaxShape::Filepath, "file path to save dataframe")
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
.output_type(Type::Any)
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Saves dataframe to arrow file",
|
||||
example: "[[a b]; [1 2] [3 4]] | into df | to arrow test.arrow",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
command(engine_state, stack, call, input)
|
||||
}
|
||||
}
|
||||
|
||||
fn command(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let file_name: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||
|
||||
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||
|
||||
let mut file = File::create(&file_name.item).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error with file name".into(),
|
||||
e.to_string(),
|
||||
Some(file_name.span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
IpcWriter::new(&mut file).finish(df.as_mut()).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error saving file".into(),
|
||||
e.to_string(),
|
||||
Some(file_name.span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let file_value = Value::String {
|
||||
val: format!("saved {:?}", &file_name.item),
|
||||
span: file_name.span,
|
||||
};
|
||||
|
||||
Ok(PipelineData::Value(
|
||||
Value::List {
|
||||
vals: vec![file_value],
|
||||
span: call.head,
|
||||
},
|
||||
None,
|
||||
))
|
||||
}
|
@ -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()))
|
||||
|
@ -30,6 +30,7 @@ pub fn create_default_context() -> EngineState {
|
||||
bind_command! {
|
||||
Alias,
|
||||
Ast,
|
||||
Commandline,
|
||||
Debug,
|
||||
Def,
|
||||
DefEnv,
|
||||
@ -41,7 +42,6 @@ pub fn create_default_context() -> EngineState {
|
||||
ExportCommand,
|
||||
ExportDef,
|
||||
ExportDefEnv,
|
||||
ExportEnvModule,
|
||||
ExportExtern,
|
||||
ExportUse,
|
||||
Extern,
|
||||
@ -137,6 +137,7 @@ pub fn create_default_context() -> EngineState {
|
||||
bind_command! {
|
||||
History,
|
||||
Tutor,
|
||||
HistorySession,
|
||||
};
|
||||
|
||||
// Path
|
||||
@ -157,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",
|
||||
@ -200,6 +206,7 @@ pub fn create_default_context() -> EngineState {
|
||||
StrDistance,
|
||||
StrDowncase,
|
||||
StrEndswith,
|
||||
StrJoin,
|
||||
StrReplace,
|
||||
StrIndexOf,
|
||||
StrKebabCase,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
2
crates/nu-command/src/env/with_env.rs
vendored
2
crates/nu-command/src/env/with_env.rs
vendored
@ -110,7 +110,7 @@ fn with_env(
|
||||
// primitive values([X Y W Z])
|
||||
for row in table.chunks(2) {
|
||||
if row.len() == 2 {
|
||||
env.insert(row[0].as_string()?, (&row[1]).clone());
|
||||
env.insert(row[0].as_string()?, row[1].clone());
|
||||
}
|
||||
// TODO: else error?
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ use crate::To;
|
||||
#[cfg(test)]
|
||||
use super::{
|
||||
Ansi, Date, From, If, Into, LetEnv, Math, Path, Random, Split, SplitColumn, SplitRow, Str,
|
||||
StrCollect, StrLength, StrReplace, Url, Wrap,
|
||||
StrJoin, StrLength, StrReplace, Url, Wrap,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
@ -29,7 +29,7 @@ pub fn test_examples(cmd: impl Command + 'static) {
|
||||
// Try to keep this working set small to keep tests running as fast as possible
|
||||
let mut working_set = StateWorkingSet::new(&*engine_state);
|
||||
working_set.add_decl(Box::new(Str));
|
||||
working_set.add_decl(Box::new(StrCollect));
|
||||
working_set.add_decl(Box::new(StrJoin));
|
||||
working_set.add_decl(Box::new(StrLength));
|
||||
working_set.add_decl(Box::new(StrReplace));
|
||||
working_set.add_decl(Box::new(BuildString));
|
||||
|
@ -189,9 +189,9 @@ impl Command for ViewSource {
|
||||
},
|
||||
Example {
|
||||
description: "View the source of a module",
|
||||
example: r#"module mod-foo { export env FOO_ENV { 'BAZ' } }; view-source mod-foo"#,
|
||||
example: r#"module mod-foo { export-env { let-env FOO_ENV = 'BAZ' } }; view-source mod-foo"#,
|
||||
result: Some(Value::String {
|
||||
val: " export env FOO_ENV { 'BAZ' }".to_string(),
|
||||
val: " export-env { let-env FOO_ENV = 'BAZ' }".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,10 +154,7 @@ impl Command for Mv {
|
||||
}
|
||||
|
||||
if let Some(Ok(_filename)) = some_if_source_is_destination {
|
||||
sources = sources
|
||||
.into_iter()
|
||||
.filter(|f| matches!(f, Ok(f) if !destination.starts_with(f)))
|
||||
.collect();
|
||||
sources.retain(|f| matches!(f, Ok(f) if !destination.starts_with(f)));
|
||||
}
|
||||
|
||||
let span = call.head;
|
||||
@ -253,8 +250,12 @@ fn move_file(
|
||||
return Err(ShellError::DirectoryNotFound(to_span, None));
|
||||
}
|
||||
|
||||
// This can happen when changing case on a case-insensitive filesystem (ex: changing foo to Foo on Windows)
|
||||
// When it does, we want to do a plain rename instead of moving `from` into `to`
|
||||
let from_to_are_same_file = same_file::is_same_file(&from, &to).unwrap_or(false);
|
||||
|
||||
let mut to = to;
|
||||
if to.is_dir() {
|
||||
if !from_to_are_same_file && to.is_dir() {
|
||||
let from_file_name = match from.file_name() {
|
||||
Some(name) => name,
|
||||
None => return Err(ShellError::DirectoryNotFound(to_span, None)),
|
||||
|
@ -36,7 +36,7 @@ impl Command for Save {
|
||||
Signature::build("save")
|
||||
.required("filename", SyntaxShape::Filepath, "the filename to use")
|
||||
.switch("raw", "save file as raw binary", Some('r'))
|
||||
.switch("append", "append input to the end of the file", None)
|
||||
.switch("append", "append input to the end of the file", Some('a'))
|
||||
.category(Category::FileSystem)
|
||||
}
|
||||
|
||||
|
@ -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> {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::help::highlight_search_string;
|
||||
use fancy_regex::Regex;
|
||||
use lscolors::Style as LsColors_Style;
|
||||
use nu_ansi_term::{Color::Default, Style};
|
||||
use lscolors::{Color as LsColors_Color, Style as LsColors_Style};
|
||||
use nu_ansi_term::{Color, Color::Default, Style};
|
||||
use nu_color_config::get_color_config;
|
||||
use nu_engine::{env_to_string, eval_block, CallExt};
|
||||
use nu_protocol::{
|
||||
@ -384,10 +384,15 @@ fn find_with_rest_and_highlight(
|
||||
|
||||
let ls_colored_val =
|
||||
ansi_style.apply(&val_str).to_string();
|
||||
|
||||
let ansi_term_style = style
|
||||
.map(to_nu_ansi_term_style)
|
||||
.unwrap_or_else(|| string_style);
|
||||
|
||||
let hi = match highlight_search_string(
|
||||
&ls_colored_val,
|
||||
&term_str,
|
||||
&string_style,
|
||||
&ansi_term_style,
|
||||
) {
|
||||
Ok(hi) => hi,
|
||||
Err(_) => string_style
|
||||
@ -535,6 +540,47 @@ fn find_with_rest_and_highlight(
|
||||
}
|
||||
}
|
||||
|
||||
fn to_nu_ansi_term_style(style: &LsColors_Style) -> Style {
|
||||
fn to_nu_ansi_term_color(color: &LsColors_Color) -> Color {
|
||||
match *color {
|
||||
LsColors_Color::Fixed(n) => Color::Fixed(n),
|
||||
LsColors_Color::RGB(r, g, b) => Color::Rgb(r, g, b),
|
||||
LsColors_Color::Black => Color::Black,
|
||||
LsColors_Color::Red => Color::Red,
|
||||
LsColors_Color::Green => Color::Green,
|
||||
LsColors_Color::Yellow => Color::Yellow,
|
||||
LsColors_Color::Blue => Color::Blue,
|
||||
LsColors_Color::Magenta => Color::Magenta,
|
||||
LsColors_Color::Cyan => Color::Cyan,
|
||||
LsColors_Color::White => Color::White,
|
||||
|
||||
// Below items are a rough translations to 256 colors as
|
||||
// nu-ansi-term do not have bright varients
|
||||
LsColors_Color::BrightBlack => Color::Fixed(8),
|
||||
LsColors_Color::BrightRed => Color::Fixed(9),
|
||||
LsColors_Color::BrightGreen => Color::Fixed(10),
|
||||
LsColors_Color::BrightYellow => Color::Fixed(11),
|
||||
LsColors_Color::BrightBlue => Color::Fixed(12),
|
||||
LsColors_Color::BrightMagenta => Color::Fixed(13),
|
||||
LsColors_Color::BrightCyan => Color::Fixed(14),
|
||||
LsColors_Color::BrightWhite => Color::Fixed(15),
|
||||
}
|
||||
}
|
||||
|
||||
Style {
|
||||
foreground: style.foreground.as_ref().map(to_nu_ansi_term_color),
|
||||
background: style.background.as_ref().map(to_nu_ansi_term_color),
|
||||
is_bold: style.font_style.bold,
|
||||
is_dimmed: style.font_style.dimmed,
|
||||
is_italic: style.font_style.italic,
|
||||
is_underline: style.font_style.underline,
|
||||
is_blink: style.font_style.slow_blink || style.font_style.rapid_blink,
|
||||
is_reverse: style.font_style.reverse,
|
||||
is_hidden: style.font_style.hidden,
|
||||
is_strikethrough: style.font_style.strikethrough,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -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)),
|
||||
|
@ -28,7 +28,7 @@ impl Command for Get {
|
||||
.rest("rest", SyntaxShape::CellPath, "additional cell paths")
|
||||
.switch(
|
||||
"ignore-errors",
|
||||
"return nothing if path can't be found",
|
||||
"when there are empty cells, instead of erroring out, replace them with nothing",
|
||||
Some('i'),
|
||||
)
|
||||
.switch(
|
||||
|
@ -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'))
|
||||
|
@ -17,6 +17,11 @@ impl Command for Select {
|
||||
// FIXME: also add support for --skip
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("select")
|
||||
.switch(
|
||||
"ignore-errors",
|
||||
"when a column has empty cells, instead of erroring out, replace them with nothing",
|
||||
Some('i'),
|
||||
)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
@ -42,8 +47,9 @@ impl Command for Select {
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let columns: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
let span = call.head;
|
||||
let ignore_empty = call.has_flag("ignore-errors");
|
||||
|
||||
select(engine_state, span, columns, input)
|
||||
select(engine_state, span, columns, input, ignore_empty)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -67,6 +73,7 @@ fn select(
|
||||
span: Span,
|
||||
columns: Vec<CellPath>,
|
||||
input: PipelineData,
|
||||
ignore_empty: bool,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let mut rows = vec![];
|
||||
|
||||
@ -121,6 +128,7 @@ fn select(
|
||||
..,
|
||||
) => {
|
||||
let mut output = vec![];
|
||||
let mut columns_with_value = Vec::new();
|
||||
|
||||
for input_val in input_vals {
|
||||
if !columns.is_empty() {
|
||||
@ -128,10 +136,25 @@ fn select(
|
||||
let mut vals = vec![];
|
||||
for path in &columns {
|
||||
//FIXME: improve implementation to not clone
|
||||
let fetcher = input_val.clone().follow_cell_path(&path.members, false)?;
|
||||
if ignore_empty {
|
||||
let fetcher = input_val.clone().follow_cell_path(&path.members, false);
|
||||
|
||||
cols.push(path.into_string().replace('.', "_"));
|
||||
vals.push(fetcher);
|
||||
cols.push(path.into_string().replace('.', "_"));
|
||||
if let Ok(fetcher) = fetcher {
|
||||
vals.push(fetcher);
|
||||
if !columns_with_value.contains(&path) {
|
||||
columns_with_value.push(path);
|
||||
}
|
||||
} else {
|
||||
vals.push(Value::nothing(span));
|
||||
}
|
||||
} else {
|
||||
let fetcher =
|
||||
input_val.clone().follow_cell_path(&path.members, false)?;
|
||||
|
||||
cols.push(path.into_string().replace('.', "_"));
|
||||
vals.push(fetcher);
|
||||
}
|
||||
}
|
||||
|
||||
output.push(Value::Record { cols, vals, span })
|
||||
|
@ -14,23 +14,31 @@ impl Command for Uniq {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("uniq")
|
||||
.switch("count", "Count the unique rows", Some('c'))
|
||||
.switch(
|
||||
"count",
|
||||
"Return a table containing the distinct input values together with their counts",
|
||||
Some('c'),
|
||||
)
|
||||
.switch(
|
||||
"repeated",
|
||||
"Count the rows that has more than one value",
|
||||
"Return the input values that occur more than once",
|
||||
Some('d'),
|
||||
)
|
||||
.switch(
|
||||
"ignore-case",
|
||||
"Ignore differences in case when comparing",
|
||||
"Ignore differences in case when comparing input values",
|
||||
Some('i'),
|
||||
)
|
||||
.switch("unique", "Only return unique values", Some('u'))
|
||||
.switch(
|
||||
"unique",
|
||||
"Return the input values that occur once only",
|
||||
Some('u'),
|
||||
)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Return the unique rows."
|
||||
"Return the distinct values in the input."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
@ -50,7 +58,7 @@ impl Command for Uniq {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Remove duplicate rows of a list/table",
|
||||
description: "Return the distinct values of a list/table (remove duplicates so that each value occurs once only)",
|
||||
example: "[2 3 3 4] | uniq",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(4)],
|
||||
@ -58,7 +66,7 @@ impl Command for Uniq {
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Only print duplicate lines, one for each group",
|
||||
description: "Return the input values that occur more than once",
|
||||
example: "[1 2 2] | uniq -d",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(2)],
|
||||
@ -66,7 +74,7 @@ impl Command for Uniq {
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Only print unique lines lines",
|
||||
description: "Return the input values that occur once only",
|
||||
example: "[1 2 2] | uniq -u",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(1)],
|
||||
@ -74,7 +82,7 @@ impl Command for Uniq {
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Ignore differences in case when comparing",
|
||||
description: "Ignore differences in case when comparing input values",
|
||||
example: "['hello' 'goodbye' 'Hello'] | uniq -i",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_string("hello"), Value::test_string("goodbye")],
|
||||
@ -82,7 +90,7 @@ impl Command for Uniq {
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Remove duplicate rows and show counts of a list/table",
|
||||
description: "Return a table containing the distinct input values together with their counts",
|
||||
example: "[1 2 2] | uniq -c",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
|
@ -62,7 +62,7 @@ impl Command for Upsert {
|
||||
result: Some(Value::List { vals: vec![Value::Record { cols: vec!["count".into(), "fruit".into()], vals: vec![Value::test_int(2), Value::test_string("apple")], span: Span::test_data()}], span: Span::test_data()}),
|
||||
}, Example {
|
||||
description: "Use in block form for more involved updating logic",
|
||||
example: "echo [[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | upsert authors {|a| $a.authors | str collect ','}",
|
||||
example: "echo [[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | upsert authors {|a| $a.authors | str join ','}",
|
||||
result: Some(Value::List { vals: vec![Value::Record { cols: vec!["project".into(), "authors".into()], vals: vec![Value::test_string("nu"), Value::test_string("Andrés,JT,Yehuda")], span: Span::test_data()}], span: Span::test_data()}),
|
||||
}]
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
@ -526,35 +526,6 @@ fn convert_to_value(
|
||||
expr.span,
|
||||
)),
|
||||
},
|
||||
Unit::Month => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 30) {
|
||||
Some(val) => Ok(Value::Duration { val, span }),
|
||||
None => Err(ShellError::OutsideSpannedLabeledError(
|
||||
original_text.to_string(),
|
||||
"month duration too large".into(),
|
||||
"month duration too large".into(),
|
||||
expr.span,
|
||||
)),
|
||||
},
|
||||
Unit::Year => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 365) {
|
||||
Some(val) => Ok(Value::Duration { val, span }),
|
||||
None => Err(ShellError::OutsideSpannedLabeledError(
|
||||
original_text.to_string(),
|
||||
"year duration too large".into(),
|
||||
"year duration too large".into(),
|
||||
expr.span,
|
||||
)),
|
||||
},
|
||||
Unit::Decade => {
|
||||
match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 365 * 10) {
|
||||
Some(val) => Ok(Value::Duration { val, span }),
|
||||
None => Err(ShellError::OutsideSpannedLabeledError(
|
||||
original_text.to_string(),
|
||||
"decade duration too large".into(),
|
||||
"decade duration too large".into(),
|
||||
expr.span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Var(..) => Err(ShellError::OutsideSpannedLabeledError(
|
||||
|
@ -110,7 +110,7 @@ fn fragment(input: Value, pretty: bool, config: &Config) -> String {
|
||||
let mut out = String::new();
|
||||
|
||||
if headers.len() == 1 {
|
||||
let markup = match (&headers[0]).to_ascii_lowercase().as_ref() {
|
||||
let markup = match headers[0].to_ascii_lowercase().as_ref() {
|
||||
"h1" => "# ".to_string(),
|
||||
"h2" => "## ".to_string(),
|
||||
"h3" => "### ".to_string(),
|
||||
|
@ -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 {
|
||||
|
43
crates/nu-command/src/misc/history_session.rs
Normal file
43
crates/nu-command/src/misc/history_session.rs
Normal file
@ -0,0 +1,43 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, IntoPipelineData, PipelineData, Signature, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HistorySession;
|
||||
|
||||
impl Command for HistorySession {
|
||||
fn name(&self) -> &str {
|
||||
"history session"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Get the command history session"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("history session").category(Category::Misc)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
example: "history session",
|
||||
description: "Get current history session",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
Ok(Value::Record {
|
||||
cols: vec!["session-id".into()],
|
||||
vals: vec![Value::int(engine_state.history_session_id, call.head)],
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
mod history;
|
||||
mod history_session;
|
||||
mod tutor;
|
||||
|
||||
pub use history::History;
|
||||
pub use history_session::HistorySession;
|
||||
pub use tutor::Tutor;
|
||||
|
@ -427,7 +427,7 @@ fn helper(
|
||||
// primitive values ([key1 val1 key2 val2])
|
||||
for row in table.chunks(2) {
|
||||
if row.len() == 2 {
|
||||
custom_headers.insert(row[0].as_string()?, (&row[1]).clone());
|
||||
custom_headers.insert(row[0].as_string()?, row[1].clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -281,7 +281,7 @@ fn helper(
|
||||
// primitive values ([key1 val1 key2 val2])
|
||||
for row in table.chunks(2) {
|
||||
if row.len() == 2 {
|
||||
custom_headers.insert(row[0].as_string()?, (&row[1]).clone());
|
||||
custom_headers.insert(row[0].as_string()?, row[1].clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ lazy_static! {
|
||||
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
|
||||
// Another good reference http://ascii-table.com/ansi-escape-sequences.php
|
||||
|
||||
// For setting title like `echo [(char title) (pwd) (char bel)] | str collect`
|
||||
// For setting title like `echo [(char title) (pwd) (char bel)] | str join`
|
||||
AnsiCode{short_name: None, long_name:"title", code: "\x1b]2;".to_string()}, // ESC]2; xterm sets window title using OSC syntax escapes
|
||||
|
||||
// Ansi Erase Sequences
|
||||
@ -258,7 +258,7 @@ following values:
|
||||
https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
|
||||
OSC: '\x1b]' is not required for --osc parameter
|
||||
Example: echo [(ansi -o '0') 'some title' (char bel)] | str collect
|
||||
Example: echo [(ansi -o '0') 'some title' (char bel)] | str join
|
||||
Format: #
|
||||
0 Set window title and icon name
|
||||
1 Set icon name
|
||||
@ -285,14 +285,14 @@ Format: #
|
||||
Example {
|
||||
description:
|
||||
"Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)",
|
||||
example: r#"echo [(ansi rb) Hello " " (ansi gb) Nu " " (ansi pb) World (ansi reset)] | str collect"#,
|
||||
example: r#"echo [(ansi rb) Hello " " (ansi gb) Nu " " (ansi pb) World (ansi reset)] | str join"#,
|
||||
result: Some(Value::test_string(
|
||||
"\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld\u{1b}[0m",
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Use ansi to color text (italic bright yellow on red 'Hello' with green bold 'Nu' and purple bold 'World')",
|
||||
example: r#"echo [(ansi -e '3;93;41m') Hello (ansi reset) " " (ansi gb) Nu " " (ansi pb) World (ansi reset)] | str collect"#,
|
||||
example: r#"echo [(ansi -e '3;93;41m') Hello (ansi reset) " " (ansi gb) Nu " " (ansi pb) World (ansi reset)] | str join"#,
|
||||
result: Some(Value::test_string(
|
||||
"\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld\u{1b}[0m",
|
||||
)),
|
||||
|
@ -40,7 +40,7 @@ impl Command for SubCommand {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Strip ANSI escape sequences from a string",
|
||||
example: r#"echo [ (ansi green) (ansi cursor_on) "hello" ] | str collect | ansi strip"#,
|
||||
example: r#"echo [ (ansi green) (ansi cursor_on) "hello" ] | str join | ansi strip"#,
|
||||
result: Some(Value::test_string("hello")),
|
||||
}]
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ impl Command for Char {
|
||||
},
|
||||
Example {
|
||||
description: "Output prompt character, newline and a hamburger character",
|
||||
example: r#"echo [(char prompt) (char newline) (char hamburger)] | str collect"#,
|
||||
example: r#"echo [(char prompt) (char newline) (char hamburger)] | str join"#,
|
||||
result: Some(Value::test_string("\u{25b6}\n\u{2261}")),
|
||||
},
|
||||
Example {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user