forked from extern/nushell
Compare commits
32 Commits
patch-rele
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
b52ec7f7b2 | |||
8ad5d8bb6a | |||
869b01205c | |||
f5b2f5a9ee | |||
adfa4d00c0 | |||
12effd9b4e | |||
c26fca7419 | |||
da59dfe7d2 | |||
08715e6308 | |||
07d7899a97 | |||
494a5a5286 | |||
f41c93b2d3 | |||
5063e01c12 | |||
d1137cc700 | |||
3966c0a9fd | |||
dbdb1f6600 | |||
84cdc0d521 | |||
ab59dab129 | |||
e0c8a3d14c | |||
e93e51d672 | |||
4205edbc70 | |||
80bee40807 | |||
461837773b | |||
52d4259f58 | |||
274a8366c6 | |||
a1dfc35968 | |||
5886a74ccc | |||
4367aa9f58 | |||
e9c298713e | |||
c110ddff66 | |||
a806717f35 | |||
2b5f1ee5b3 |
855
Cargo.lock
generated
855
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
46
Cargo.toml
46
Cargo.toml
@ -11,7 +11,7 @@ license = "MIT"
|
|||||||
name = "nu"
|
name = "nu"
|
||||||
repository = "https://github.com/nushell/nushell"
|
repository = "https://github.com/nushell/nushell"
|
||||||
rust-version = "1.60"
|
rust-version = "1.60"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -47,27 +47,27 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cli = { path = "./crates/nu-cli", version = "0.87.1" }
|
nu-cli = { path = "./crates/nu-cli", version = "0.87.2" }
|
||||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.87.1" }
|
nu-color-config = { path = "./crates/nu-color-config", version = "0.87.2" }
|
||||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.87.1" }
|
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.87.2" }
|
||||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.87.1" }
|
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.87.2" }
|
||||||
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.87.1", features = ["dataframe"], optional = true }
|
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.87.2", features = ["dataframe"], optional = true }
|
||||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.87.1", optional = true }
|
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.87.2", optional = true }
|
||||||
nu-command = { path = "./crates/nu-command", version = "0.87.1" }
|
nu-command = { path = "./crates/nu-command", version = "0.87.2" }
|
||||||
nu-engine = { path = "./crates/nu-engine", version = "0.87.1" }
|
nu-engine = { path = "./crates/nu-engine", version = "0.87.2" }
|
||||||
nu-explore = { path = "./crates/nu-explore", version = "0.87.1" }
|
nu-explore = { path = "./crates/nu-explore", version = "0.87.2" }
|
||||||
nu-json = { path = "./crates/nu-json", version = "0.87.1" }
|
nu-json = { path = "./crates/nu-json", version = "0.87.2" }
|
||||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.87.1" }
|
nu-lsp = { path = "./crates/nu-lsp/", version = "0.87.2" }
|
||||||
nu-parser = { path = "./crates/nu-parser", version = "0.87.1" }
|
nu-parser = { path = "./crates/nu-parser", version = "0.87.2" }
|
||||||
nu-path = { path = "./crates/nu-path", version = "0.87.1" }
|
nu-path = { path = "./crates/nu-path", version = "0.87.2" }
|
||||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.87.1" }
|
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.87.2" }
|
||||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.87.1" }
|
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.87.2" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.87.1" }
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.87.2" }
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.87.1" }
|
nu-system = { path = "./crates/nu-system", version = "0.87.2" }
|
||||||
nu-table = { path = "./crates/nu-table", version = "0.87.1" }
|
nu-table = { path = "./crates/nu-table", version = "0.87.2" }
|
||||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.87.1" }
|
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.87.2" }
|
||||||
nu-std = { path = "./crates/nu-std", version = "0.87.1" }
|
nu-std = { path = "./crates/nu-std", version = "0.87.2" }
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.87.1" }
|
nu-utils = { path = "./crates/nu-utils", version = "0.87.2" }
|
||||||
nu-ansi-term = "0.49.0"
|
nu-ansi-term = "0.49.0"
|
||||||
reedline = { version = "0.26.0", features = ["bashisms", "sqlite"] }
|
reedline = { version = "0.26.0", features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ nix = { version = "0.27", default-features = false, features = [
|
|||||||
] }
|
] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.87.1" }
|
nu-test-support = { path = "./crates/nu-test-support", version = "0.87.2" }
|
||||||
assert_cmd = "2.0"
|
assert_cmd = "2.0"
|
||||||
criterion = "0.5"
|
criterion = "0.5"
|
||||||
pretty_assertions = "1.4"
|
pretty_assertions = "1.4"
|
||||||
|
@ -5,25 +5,25 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cli"
|
name = "nu-cli"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.2" }
|
||||||
nu-command = { path = "../nu-command", version = "0.87.1" }
|
nu-command = { path = "../nu-command", version = "0.87.2" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.87.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.87.2" }
|
||||||
rstest = { version = "0.18.1", default-features = false }
|
rstest = { version = "0.18.1", default-features = false }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.87.1" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.87.2" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.87.1" }
|
nu-engine = { path = "../nu-engine", version = "0.87.2" }
|
||||||
nu-path = { path = "../nu-path", version = "0.87.1" }
|
nu-path = { path = "../nu-path", version = "0.87.2" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.87.1" }
|
nu-parser = { path = "../nu-parser", version = "0.87.2" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.87.1" }
|
nu-utils = { path = "../nu-utils", version = "0.87.2" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.87.1" }
|
nu-color-config = { path = "../nu-color-config", version = "0.87.2" }
|
||||||
nu-ansi-term = "0.49.0"
|
nu-ansi-term = "0.49.0"
|
||||||
reedline = { version = "0.26.0", features = ["bashisms", "sqlite"] }
|
reedline = { version = "0.26.0", features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ percent-encoding = "2"
|
|||||||
pathdiff = "0.2"
|
pathdiff = "0.2"
|
||||||
sysinfo = "0.29"
|
sysinfo = "0.29"
|
||||||
unicode-segmentation = "1.10"
|
unicode-segmentation = "1.10"
|
||||||
uuid = { version = "1.5.0", features = ["v4"] }
|
uuid = { version = "1.6.0", features = ["v4"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = []
|
plugin = []
|
||||||
|
@ -67,7 +67,7 @@ impl NuCompleter {
|
|||||||
let mut callee_stack = stack.gather_captures(&self.engine_state, &block.captures);
|
let mut callee_stack = stack.gather_captures(&self.engine_state, &block.captures);
|
||||||
|
|
||||||
// Line
|
// Line
|
||||||
if let Some(pos_arg) = block.signature.required_positional.get(0) {
|
if let Some(pos_arg) = block.signature.required_positional.first() {
|
||||||
if let Some(var_id) = pos_arg.var_id {
|
if let Some(var_id) = pos_arg.var_id {
|
||||||
callee_stack.add_var(
|
callee_stack.add_var(
|
||||||
var_id,
|
var_id,
|
||||||
|
@ -111,7 +111,7 @@ fn gather_env_vars(
|
|||||||
let name = if let Some(Token {
|
let name = if let Some(Token {
|
||||||
contents: TokenContents::Item,
|
contents: TokenContents::Item,
|
||||||
span,
|
span,
|
||||||
}) = parts.get(0)
|
}) = parts.first()
|
||||||
{
|
{
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
let bytes = working_set.get_span_contents(*span);
|
let bytes = working_set.get_span_contents(*span);
|
||||||
|
@ -122,14 +122,14 @@ fn dotnu_completions() {
|
|||||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||||
|
|
||||||
assert_eq!(1, suggestions.len());
|
assert_eq!(1, suggestions.len());
|
||||||
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
assert_eq!("custom_completion.nu", suggestions.first().unwrap().value);
|
||||||
|
|
||||||
// Test use completion
|
// Test use completion
|
||||||
let completion_str = "use ".to_string();
|
let completion_str = "use ".to_string();
|
||||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||||
|
|
||||||
assert_eq!(1, suggestions.len());
|
assert_eq!(1, suggestions.len());
|
||||||
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
assert_eq!("custom_completion.nu", suggestions.first().unwrap().value);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -141,7 +141,7 @@ fn external_completer_trailing_space() {
|
|||||||
|
|
||||||
let suggestions = run_external_completion(block, &input);
|
let suggestions = run_external_completion(block, &input);
|
||||||
assert_eq!(3, suggestions.len());
|
assert_eq!(3, suggestions.len());
|
||||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
assert_eq!("gh", suggestions.first().unwrap().value);
|
||||||
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
||||||
assert_eq!("", suggestions.get(2).unwrap().value);
|
assert_eq!("", suggestions.get(2).unwrap().value);
|
||||||
}
|
}
|
||||||
@ -153,7 +153,7 @@ fn external_completer_no_trailing_space() {
|
|||||||
|
|
||||||
let suggestions = run_external_completion(block, &input);
|
let suggestions = run_external_completion(block, &input);
|
||||||
assert_eq!(2, suggestions.len());
|
assert_eq!(2, suggestions.len());
|
||||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
assert_eq!("gh", suggestions.first().unwrap().value);
|
||||||
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +164,7 @@ fn external_completer_pass_flags() {
|
|||||||
|
|
||||||
let suggestions = run_external_completion(block, &input);
|
let suggestions = run_external_completion(block, &input);
|
||||||
assert_eq!(3, suggestions.len());
|
assert_eq!(3, suggestions.len());
|
||||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
assert_eq!("gh", suggestions.first().unwrap().value);
|
||||||
assert_eq!("api", suggestions.get(1).unwrap().value);
|
assert_eq!("api", suggestions.get(1).unwrap().value);
|
||||||
assert_eq!("--", suggestions.get(2).unwrap().value);
|
assert_eq!("--", suggestions.get(2).unwrap().value);
|
||||||
}
|
}
|
||||||
|
@ -5,21 +5,21 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-base"
|
name = "nu-cmd-base"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.87.1" }
|
nu-engine = { path = "../nu-engine", version = "0.87.2" }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.87.1" }
|
nu-glob = { path = "../nu-glob", version = "0.87.2" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.87.1" }
|
nu-parser = { path = "../nu-parser", version = "0.87.2" }
|
||||||
nu-path = { path = "../nu-path", version = "0.87.1" }
|
nu-path = { path = "../nu-path", version = "0.87.2" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.87.1" }
|
nu-utils = { path = "../nu-utils", version = "0.87.2" }
|
||||||
|
|
||||||
indexmap = "2.1"
|
indexmap = "2.1"
|
||||||
miette = "5.10.0"
|
miette = "5.10.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.87.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.87.2" }
|
||||||
rstest = "0.18.2"
|
rstest = "0.18.2"
|
||||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-dataframe"
|
name = "nu-cmd-dataframe"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-dataframe"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-dataframe"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -13,9 +13,9 @@ version = "0.87.1"
|
|||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.87.1" }
|
nu-engine = { path = "../nu-engine", version = "0.87.2" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.87.1" }
|
nu-parser = { path = "../nu-parser", version = "0.87.2" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false }
|
chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false }
|
||||||
@ -66,5 +66,5 @@ dataframe = ["num", "polars", "polars-io", "sqlparser"]
|
|||||||
default = []
|
default = []
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.2" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.87.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.87.2" }
|
||||||
|
@ -68,7 +68,7 @@ fn command(
|
|||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
let new_df = col_string
|
let new_df = col_string
|
||||||
.get(0)
|
.first()
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ShellError::GenericError(
|
ShellError::GenericError(
|
||||||
"Empty names list".into(),
|
"Empty names list".into(),
|
||||||
|
@ -29,7 +29,7 @@ impl SQLContext {
|
|||||||
fn execute_select(&self, select_stmt: &Select) -> Result<LazyFrame, PolarsError> {
|
fn execute_select(&self, select_stmt: &Select) -> Result<LazyFrame, PolarsError> {
|
||||||
// Determine involved dataframe
|
// Determine involved dataframe
|
||||||
// Implicit join require some more work in query parsers, Explicit join are preferred for now.
|
// 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(|| {
|
let tbl = select_stmt.from.first().ok_or_else(|| {
|
||||||
PolarsError::ComputeError(ErrString::from("No table found in select statement"))
|
PolarsError::ComputeError(ErrString::from("No table found in select statement"))
|
||||||
})?;
|
})?;
|
||||||
let mut alias_map = HashMap::new();
|
let mut alias_map = HashMap::new();
|
||||||
@ -37,7 +37,7 @@ impl SQLContext {
|
|||||||
TableFactor::Table { name, alias, .. } => {
|
TableFactor::Table { name, alias, .. } => {
|
||||||
let tbl_name = name
|
let tbl_name = name
|
||||||
.0
|
.0
|
||||||
.get(0)
|
.first()
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
PolarsError::ComputeError(ErrString::from(
|
PolarsError::ComputeError(ErrString::from(
|
||||||
"No table found in select statement",
|
"No table found in select statement",
|
||||||
@ -182,7 +182,7 @@ impl SQLContext {
|
|||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
let ast = ast
|
let ast = ast
|
||||||
.get(0)
|
.first()
|
||||||
.ok_or_else(|| PolarsError::ComputeError(ErrString::from("No statement found")))?;
|
.ok_or_else(|| PolarsError::ComputeError(ErrString::from("No statement found")))?;
|
||||||
Ok(match ast {
|
Ok(match ast {
|
||||||
Statement::Query(query) => {
|
Statement::Query(query) => {
|
||||||
|
@ -325,7 +325,7 @@ impl NuDataFrame {
|
|||||||
let series = self
|
let series = self
|
||||||
.df
|
.df
|
||||||
.get_columns()
|
.get_columns()
|
||||||
.get(0)
|
.first()
|
||||||
.expect("We have already checked that the width is 1");
|
.expect("We have already checked that the width is 1");
|
||||||
|
|
||||||
Ok(series.clone())
|
Ok(series.clone())
|
||||||
|
@ -11,7 +11,7 @@ pub(crate) fn convert_columns(
|
|||||||
) -> Result<(Vec<Spanned<String>>, Span), ShellError> {
|
) -> Result<(Vec<Spanned<String>>, Span), ShellError> {
|
||||||
// First column span
|
// First column span
|
||||||
let mut col_span = columns
|
let mut col_span = columns
|
||||||
.get(0)
|
.first()
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ShellError::GenericError(
|
ShellError::GenericError(
|
||||||
"Empty column list".into(),
|
"Empty column list".into(),
|
||||||
@ -54,7 +54,7 @@ pub(crate) fn convert_columns_string(
|
|||||||
) -> Result<(Vec<String>, Span), ShellError> {
|
) -> Result<(Vec<String>, Span), ShellError> {
|
||||||
// First column span
|
// First column span
|
||||||
let mut col_span = columns
|
let mut col_span = columns
|
||||||
.get(0)
|
.first()
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ShellError::GenericError(
|
ShellError::GenericError(
|
||||||
"Empty column list".into(),
|
"Empty column list".into(),
|
||||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-extra"
|
name = "nu-cmd-extra"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -13,11 +13,11 @@ version = "0.87.1"
|
|||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.87.1" }
|
nu-engine = { path = "../nu-engine", version = "0.87.2" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.87.1" }
|
nu-parser = { path = "../nu-parser", version = "0.87.2" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.87.1" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.87.2" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.87.1" }
|
nu-utils = { path = "../nu-utils", version = "0.87.2" }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
heck = "0.4.1"
|
heck = "0.4.1"
|
||||||
@ -27,8 +27,8 @@ nu-ansi-term = "0.49.0"
|
|||||||
fancy-regex = "0.11.0"
|
fancy-regex = "0.11.0"
|
||||||
rust-embed = "8.0.0"
|
rust-embed = "8.0.0"
|
||||||
serde = "1.0.164"
|
serde = "1.0.164"
|
||||||
nu-pretty-hex = { version = "0.87.1", path = "../nu-pretty-hex" }
|
nu-pretty-hex = { version = "0.87.2", path = "../nu-pretty-hex" }
|
||||||
nu-json = { version = "0.87.1", path = "../nu-json" }
|
nu-json = { version = "0.87.2", path = "../nu-json" }
|
||||||
serde_urlencoded = "0.7.1"
|
serde_urlencoded = "0.7.1"
|
||||||
htmlescape = "0.3.1"
|
htmlescape = "0.3.1"
|
||||||
|
|
||||||
@ -37,6 +37,6 @@ extra = ["default"]
|
|||||||
default = []
|
default = []
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.2" }
|
||||||
nu-command = { path = "../nu-command", version = "0.87.1" }
|
nu-command = { path = "../nu-command", version = "0.87.2" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.87.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.87.2" }
|
||||||
|
@ -6,16 +6,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-lang"
|
name = "nu-cmd-lang"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.87.1" }
|
nu-engine = { path = "../nu-engine", version = "0.87.2" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.87.1" }
|
nu-parser = { path = "../nu-parser", version = "0.87.2" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.87.1" }
|
nu-utils = { path = "../nu-utils", version = "0.87.2" }
|
||||||
nu-ansi-term = "0.49.0"
|
nu-ansi-term = "0.49.0"
|
||||||
|
|
||||||
fancy-regex = "0.11"
|
fancy-regex = "0.11"
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
use nu_protocol::ast::Call;
|
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
|
||||||
use nu_protocol::{
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct DefEnv;
|
|
||||||
|
|
||||||
impl Command for DefEnv {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"def-env"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Define a custom command, which participates in the caller environment."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
|
||||||
Signature::build("def-env")
|
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
|
||||||
.required("def_name", SyntaxShape::String, "definition name")
|
|
||||||
.required("params", SyntaxShape::Signature, "parameters")
|
|
||||||
.required("block", SyntaxShape::Block, "body of the 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<PipelineData, ShellError> {
|
|
||||||
nu_protocol::report_error_new(
|
|
||||||
engine_state,
|
|
||||||
&ShellError::GenericError(
|
|
||||||
"Deprecated command".into(),
|
|
||||||
"`def-env` is deprecated and will be removed in 0.88.".into(),
|
|
||||||
Some(call.head),
|
|
||||||
Some("Use `def --env` instead".into()),
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Ok(PipelineData::empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
description: "Set environment variable by call a custom command",
|
|
||||||
example: r#"def-env foo [] { $env.BAR = "BAZ" }; foo; $env.BAR"#,
|
|
||||||
result: Some(Value::test_string("BAZ")),
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
use nu_protocol::ast::Call;
|
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
|
||||||
use nu_protocol::{
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ExportDefEnv;
|
|
||||||
|
|
||||||
impl Command for ExportDefEnv {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"export def-env"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Define a custom command that participates in the environment and export it from a module."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
|
||||||
Signature::build("export def-env")
|
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
|
||||||
.required("name", SyntaxShape::String, "definition name")
|
|
||||||
.required("params", SyntaxShape::Signature, "parameters")
|
|
||||||
.required("block", SyntaxShape::Block, "body of the 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
|
|
||||||
|
|
||||||
=== EXTRA NOTE ===
|
|
||||||
All blocks are scoped, including variable definition and environment variable changes.
|
|
||||||
|
|
||||||
Because of this, the following doesn't work:
|
|
||||||
|
|
||||||
export def-env cd_with_fallback [arg = ""] {
|
|
||||||
let fall_back_path = "/tmp"
|
|
||||||
if $arg != "" {
|
|
||||||
cd $arg
|
|
||||||
} else {
|
|
||||||
cd $fall_back_path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Instead, you have to use cd in the top level scope:
|
|
||||||
|
|
||||||
export def-env cd_with_fallback [arg = ""] {
|
|
||||||
let fall_back_path = "/tmp"
|
|
||||||
let path = if $arg != "" {
|
|
||||||
$arg
|
|
||||||
} else {
|
|
||||||
$fall_back_path
|
|
||||||
}
|
|
||||||
cd $path
|
|
||||||
}"#
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
_stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
nu_protocol::report_error_new(
|
|
||||||
engine_state,
|
|
||||||
&ShellError::GenericError(
|
|
||||||
"Deprecated command".into(),
|
|
||||||
"`export def-env` is deprecated and will be removed in 0.88.".into(),
|
|
||||||
Some(call.head),
|
|
||||||
Some("Use `export def --env` instead".into()),
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Ok(PipelineData::empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![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 [] { $env.FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
|
|
||||||
result: Some(Value::test_string("BAZ")),
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["module"]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
use nu_protocol::ast::Call;
|
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
|
||||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ExportExternWrapped;
|
|
||||||
|
|
||||||
impl Command for ExportExternWrapped {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"export extern-wrapped"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Define an extern with a custom code block and export it from a module."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
|
||||||
Signature::build("export extern-wrapped")
|
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
|
||||||
.required("def_name", SyntaxShape::String, "definition name")
|
|
||||||
.required("params", SyntaxShape::Signature, "parameters")
|
|
||||||
.required("body", SyntaxShape::Block, "wrapper code block")
|
|
||||||
.category(Category::Core)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
|
||||||
r#"This command is a parser keyword. For details, check:
|
|
||||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
_stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
nu_protocol::report_error_new(
|
|
||||||
engine_state,
|
|
||||||
&ShellError::GenericError(
|
|
||||||
"Deprecated command".into(),
|
|
||||||
"`export extern-wrapped` is deprecated and will be removed in 0.88.".into(),
|
|
||||||
Some(call.head),
|
|
||||||
Some("Use `export def --wrapped` instead".into()),
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Ok(PipelineData::empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
description: "Export the signature for an external command",
|
|
||||||
example: r#"export extern-wrapped my-echo [...rest] { echo $rest }"#,
|
|
||||||
result: None,
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["signature", "module", "declare"]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
use nu_protocol::ast::Call;
|
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
|
||||||
use nu_protocol::{
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ExternWrapped;
|
|
||||||
|
|
||||||
impl Command for ExternWrapped {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"extern-wrapped"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Define a signature for an external command with a custom code block."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
|
||||||
Signature::build("extern-wrapped")
|
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
|
||||||
.allow_variants_without_examples(true)
|
|
||||||
.required("def_name", SyntaxShape::String, "definition name")
|
|
||||||
.required("params", SyntaxShape::Signature, "parameters")
|
|
||||||
.required("body", SyntaxShape::Block, "wrapper code block")
|
|
||||||
.category(Category::Core)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
|
||||||
r#"This command is a parser keyword. For details, check:
|
|
||||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
_stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
nu_protocol::report_error_new(
|
|
||||||
engine_state,
|
|
||||||
&ShellError::GenericError(
|
|
||||||
"Deprecated command".into(),
|
|
||||||
"`extern-wrapped` is deprecated and will be removed in 0.88.".into(),
|
|
||||||
Some(call.head),
|
|
||||||
Some("Use `def --wrapped` instead".into()),
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Ok(PipelineData::empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
description: "Define a custom wrapper for an external command",
|
|
||||||
example: r#"extern-wrapped my-echo [...rest] { echo $rest }; my-echo spam"#,
|
|
||||||
result: Some(Value::test_list(vec![Value::test_string("spam")])),
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
use super::ExternWrapped;
|
|
||||||
use crate::test_examples;
|
|
||||||
test_examples(ExternWrapped {})
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,7 +4,6 @@ mod collect;
|
|||||||
mod const_;
|
mod const_;
|
||||||
mod continue_;
|
mod continue_;
|
||||||
mod def;
|
mod def;
|
||||||
mod def_env;
|
|
||||||
mod describe;
|
mod describe;
|
||||||
mod do_;
|
mod do_;
|
||||||
mod echo;
|
mod echo;
|
||||||
@ -13,13 +12,10 @@ mod export;
|
|||||||
mod export_alias;
|
mod export_alias;
|
||||||
mod export_const;
|
mod export_const;
|
||||||
mod export_def;
|
mod export_def;
|
||||||
mod export_def_env;
|
|
||||||
mod export_extern;
|
mod export_extern;
|
||||||
mod export_extern_wrapped;
|
|
||||||
mod export_module;
|
mod export_module;
|
||||||
mod export_use;
|
mod export_use;
|
||||||
mod extern_;
|
mod extern_;
|
||||||
mod extern_wrapped;
|
|
||||||
mod for_;
|
mod for_;
|
||||||
mod hide;
|
mod hide;
|
||||||
mod hide_env;
|
mod hide_env;
|
||||||
@ -45,7 +41,6 @@ pub use collect::Collect;
|
|||||||
pub use const_::Const;
|
pub use const_::Const;
|
||||||
pub use continue_::Continue;
|
pub use continue_::Continue;
|
||||||
pub use def::Def;
|
pub use def::Def;
|
||||||
pub use def_env::DefEnv;
|
|
||||||
pub use describe::Describe;
|
pub use describe::Describe;
|
||||||
pub use do_::Do;
|
pub use do_::Do;
|
||||||
pub use echo::Echo;
|
pub use echo::Echo;
|
||||||
@ -54,13 +49,10 @@ pub use export::ExportCommand;
|
|||||||
pub use export_alias::ExportAlias;
|
pub use export_alias::ExportAlias;
|
||||||
pub use export_const::ExportConst;
|
pub use export_const::ExportConst;
|
||||||
pub use export_def::ExportDef;
|
pub use export_def::ExportDef;
|
||||||
pub use export_def_env::ExportDefEnv;
|
|
||||||
pub use export_extern::ExportExtern;
|
pub use export_extern::ExportExtern;
|
||||||
pub use export_extern_wrapped::ExportExternWrapped;
|
|
||||||
pub use export_module::ExportModule;
|
pub use export_module::ExportModule;
|
||||||
pub use export_use::ExportUse;
|
pub use export_use::ExportUse;
|
||||||
pub use extern_::Extern;
|
pub use extern_::Extern;
|
||||||
pub use extern_wrapped::ExternWrapped;
|
|
||||||
pub use for_::For;
|
pub use for_::For;
|
||||||
pub use hide::Hide;
|
pub use hide::Hide;
|
||||||
pub use hide_env::HideEnv;
|
pub use hide_env::HideEnv;
|
||||||
|
@ -22,7 +22,6 @@ pub fn create_default_context() -> EngineState {
|
|||||||
Const,
|
Const,
|
||||||
Continue,
|
Continue,
|
||||||
Def,
|
Def,
|
||||||
DefEnv,
|
|
||||||
Describe,
|
Describe,
|
||||||
Do,
|
Do,
|
||||||
Echo,
|
Echo,
|
||||||
@ -31,13 +30,10 @@ pub fn create_default_context() -> EngineState {
|
|||||||
ExportCommand,
|
ExportCommand,
|
||||||
ExportConst,
|
ExportConst,
|
||||||
ExportDef,
|
ExportDef,
|
||||||
ExportDefEnv,
|
|
||||||
ExportExtern,
|
ExportExtern,
|
||||||
ExportExternWrapped,
|
|
||||||
ExportUse,
|
ExportUse,
|
||||||
ExportModule,
|
ExportModule,
|
||||||
Extern,
|
Extern,
|
||||||
ExternWrapped,
|
|
||||||
For,
|
For,
|
||||||
Hide,
|
Hide,
|
||||||
HideEnv,
|
HideEnv,
|
||||||
|
@ -14,8 +14,7 @@ mod test_examples {
|
|||||||
check_example_input_and_output_types_match_command_signature,
|
check_example_input_and_output_types_match_command_signature,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
Break, Collect, Def, DefEnv, Describe, Echo, ExportCommand, ExportDef, ExportDefEnv, If,
|
Break, Collect, Def, Describe, Echo, ExportCommand, ExportDef, If, Let, Module, Mut, Use,
|
||||||
Let, Module, Mut, Use,
|
|
||||||
};
|
};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{Command, EngineState, StateWorkingSet},
|
engine::{Command, EngineState, StateWorkingSet},
|
||||||
@ -69,12 +68,10 @@ mod test_examples {
|
|||||||
working_set.add_decl(Box::new(Break));
|
working_set.add_decl(Box::new(Break));
|
||||||
working_set.add_decl(Box::new(Collect));
|
working_set.add_decl(Box::new(Collect));
|
||||||
working_set.add_decl(Box::new(Def));
|
working_set.add_decl(Box::new(Def));
|
||||||
working_set.add_decl(Box::new(DefEnv));
|
|
||||||
working_set.add_decl(Box::new(Describe));
|
working_set.add_decl(Box::new(Describe));
|
||||||
working_set.add_decl(Box::new(Echo));
|
working_set.add_decl(Box::new(Echo));
|
||||||
working_set.add_decl(Box::new(ExportCommand));
|
working_set.add_decl(Box::new(ExportCommand));
|
||||||
working_set.add_decl(Box::new(ExportDef));
|
working_set.add_decl(Box::new(ExportDef));
|
||||||
working_set.add_decl(Box::new(ExportDefEnv));
|
|
||||||
working_set.add_decl(Box::new(If));
|
working_set.add_decl(Box::new(If));
|
||||||
working_set.add_decl(Box::new(Let));
|
working_set.add_decl(Box::new(Let));
|
||||||
working_set.add_decl(Box::new(Module));
|
working_set.add_decl(Box::new(Module));
|
||||||
|
@ -5,19 +5,19 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-color-config"
|
name = "nu-color-config"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
|
||||||
nu-ansi-term = "0.49.0"
|
nu-ansi-term = "0.49.0"
|
||||||
nu-utils = { path = "../nu-utils", version = "0.87.1" }
|
nu-utils = { path = "../nu-utils", version = "0.87.2" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.87.1" }
|
nu-engine = { path = "../nu-engine", version = "0.87.2" }
|
||||||
nu-json = { path = "../nu-json", version = "0.87.1" }
|
nu-json = { path = "../nu-json", version = "0.87.2" }
|
||||||
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.87.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.87.2" }
|
||||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-command"
|
name = "nu-command"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -14,19 +14,19 @@ bench = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-ansi-term = "0.49.0"
|
nu-ansi-term = "0.49.0"
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.87.1" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.87.2" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.87.1" }
|
nu-color-config = { path = "../nu-color-config", version = "0.87.2" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.87.1" }
|
nu-engine = { path = "../nu-engine", version = "0.87.2" }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.87.1" }
|
nu-glob = { path = "../nu-glob", version = "0.87.2" }
|
||||||
nu-json = { path = "../nu-json", version = "0.87.1" }
|
nu-json = { path = "../nu-json", version = "0.87.2" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.87.1" }
|
nu-parser = { path = "../nu-parser", version = "0.87.2" }
|
||||||
nu-path = { path = "../nu-path", version = "0.87.1" }
|
nu-path = { path = "../nu-path", version = "0.87.2" }
|
||||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.87.1" }
|
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.87.2" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
|
||||||
nu-system = { path = "../nu-system", version = "0.87.1" }
|
nu-system = { path = "../nu-system", version = "0.87.2" }
|
||||||
nu-table = { path = "../nu-table", version = "0.87.1" }
|
nu-table = { path = "../nu-table", version = "0.87.2" }
|
||||||
nu-term-grid = { path = "../nu-term-grid", version = "0.87.1" }
|
nu-term-grid = { path = "../nu-term-grid", version = "0.87.2" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.87.1" }
|
nu-utils = { path = "../nu-utils", version = "0.87.2" }
|
||||||
|
|
||||||
alphanumeric-sort = "1.5"
|
alphanumeric-sort = "1.5"
|
||||||
base64 = "0.21"
|
base64 = "0.21"
|
||||||
@ -47,6 +47,7 @@ filesize = "0.2"
|
|||||||
filetime = "0.2"
|
filetime = "0.2"
|
||||||
fs_extra = "1.3"
|
fs_extra = "1.3"
|
||||||
htmlescape = "0.3"
|
htmlescape = "0.3"
|
||||||
|
human-date-parser = "0.1.1"
|
||||||
indexmap = "2.1"
|
indexmap = "2.1"
|
||||||
indicatif = "0.17"
|
indicatif = "0.17"
|
||||||
itertools = "0.11"
|
itertools = "0.11"
|
||||||
@ -67,7 +68,7 @@ os_pipe = "1.1"
|
|||||||
pathdiff = "0.2"
|
pathdiff = "0.2"
|
||||||
percent-encoding = "2.3"
|
percent-encoding = "2.3"
|
||||||
print-positions = "0.6"
|
print-positions = "0.6"
|
||||||
quick-xml = "0.30"
|
quick-xml = "0.31"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rayon = "1.8"
|
rayon = "1.8"
|
||||||
regex = "1.9.5"
|
regex = "1.9.5"
|
||||||
@ -87,23 +88,27 @@ toml = "0.8"
|
|||||||
unicode-segmentation = "1.10"
|
unicode-segmentation = "1.10"
|
||||||
ureq = { version = "2.8", default-features = false, features = ["charset", "gzip", "json", "native-tls"] }
|
ureq = { version = "2.8", default-features = false, features = ["charset", "gzip", "json", "native-tls"] }
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
uu_cp = "0.0.22"
|
uu_cp = "0.0.23"
|
||||||
uu_whoami = "0.0.22"
|
uu_whoami = "0.0.23"
|
||||||
uu_mkdir = "0.0.22"
|
uu_mkdir = "0.0.23"
|
||||||
uuid = { version = "1.5", features = ["v4"] }
|
uu_mktemp = "0.0.23"
|
||||||
|
uuid = { version = "1.6", features = ["v4"] }
|
||||||
wax = { version = "0.6" }
|
wax = { version = "0.6" }
|
||||||
which = { version = "5.0", optional = true }
|
which = { version = "5.0", optional = true }
|
||||||
bracoxide = "0.1.2"
|
bracoxide = "0.1.2"
|
||||||
chardetng = "0.1.17"
|
chardetng = "0.1.17"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winreg = "0.51"
|
winreg = "0.52"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
umask = "2.1"
|
umask = "2.1"
|
||||||
nix = { version = "0.27", default-features = false, features = ["user"] }
|
nix = { version = "0.27", default-features = false, features = ["user"] }
|
||||||
|
|
||||||
|
[target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "android"), not(target_os = "ios")))'.dependencies]
|
||||||
|
procfs = "0.16.0"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
|
||||||
optional = true
|
optional = true
|
||||||
version = "3.1"
|
version = "3.1"
|
||||||
@ -126,8 +131,8 @@ trash-support = ["trash"]
|
|||||||
which-support = ["which"]
|
which-support = ["which"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.2" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.87.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.87.2" }
|
||||||
|
|
||||||
dirs-next = "2.0"
|
dirs-next = "2.0"
|
||||||
mockito = { version = "1.2", default-features = false }
|
mockito = { version = "1.2", default-features = false }
|
||||||
|
@ -187,12 +187,14 @@ pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
|||||||
let val = if cfg!(target_endian = "little") {
|
let val = if cfg!(target_endian = "little") {
|
||||||
match val.iter().rposition(|&x| x != 0) {
|
match val.iter().rposition(|&x| x != 0) {
|
||||||
Some(idx) => &val[..idx + 1],
|
Some(idx) => &val[..idx + 1],
|
||||||
None => &val,
|
|
||||||
|
// all 0s should just return a single 0 byte
|
||||||
|
None => &[0],
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match val.iter().position(|&x| x != 0) {
|
match val.iter().position(|&x| x != 0) {
|
||||||
Some(idx) => &val[idx..],
|
Some(idx) => &val[idx..],
|
||||||
None => &val,
|
None => &[0],
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
use crate::{generate_strftime_list, parse_date_from_string};
|
use crate::{generate_strftime_list, parse_date_from_string};
|
||||||
|
use chrono::NaiveTime;
|
||||||
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
|
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
|
||||||
|
use human_date_parser::{from_human_time, ParseResult};
|
||||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
|
||||||
use nu_protocol::ast::CellPath;
|
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
ast::{Call, CellPath},
|
||||||
SyntaxShape, Type, Value,
|
engine::{Command, EngineState, Stack},
|
||||||
|
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
|
||||||
|
Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
@ -95,6 +96,11 @@ impl Command for SubCommand {
|
|||||||
"Show all possible variables for use in --format flag",
|
"Show all possible variables for use in --format flag",
|
||||||
Some('l'),
|
Some('l'),
|
||||||
)
|
)
|
||||||
|
.switch(
|
||||||
|
"list-human",
|
||||||
|
"Show human-readable datetime parsing examples",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
@ -112,6 +118,8 @@ impl Command for SubCommand {
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
if call.has_flag("list") {
|
if call.has_flag("list") {
|
||||||
Ok(generate_strftime_list(call.head, true).into_pipeline_data())
|
Ok(generate_strftime_list(call.head, true).into_pipeline_data())
|
||||||
|
} else if call.has_flag("list-human") {
|
||||||
|
Ok(list_human_readable_examples(call.head).into_pipeline_data())
|
||||||
} else {
|
} else {
|
||||||
let cell_paths = call.rest(engine_state, stack, 0)?;
|
let cell_paths = call.rest(engine_state, stack, 0)?;
|
||||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
@ -225,6 +233,21 @@ impl Command for SubCommand {
|
|||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Parsing human readable datetimes",
|
||||||
|
example: "'Today at 18:30' | into datetime",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Parsing human readable datetimes",
|
||||||
|
example: "'Last Friday at 19:45' | into datetime",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Parsing human readable datetimes",
|
||||||
|
example: "'In 5 minutes and 30 seconds' | into datetime",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,7 +264,33 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
if let Ok(input_val) = input.as_spanned_string() {
|
if let Ok(input_val) = input.as_spanned_string() {
|
||||||
match parse_date_from_string(&input_val.item, input_val.span) {
|
match parse_date_from_string(&input_val.item, input_val.span) {
|
||||||
Ok(date) => return Value::date(date, input_val.span),
|
Ok(date) => return Value::date(date, input_val.span),
|
||||||
Err(err) => err,
|
Err(_) => {
|
||||||
|
if let Ok(date) = from_human_time(&input_val.item) {
|
||||||
|
match date {
|
||||||
|
ParseResult::Date(date) => {
|
||||||
|
let time = NaiveTime::from_hms_opt(0, 0, 0).expect("valid time");
|
||||||
|
let combined = date.and_time(time);
|
||||||
|
let dt_fixed = DateTime::from_naive_utc_and_offset(
|
||||||
|
combined,
|
||||||
|
*Local::now().offset(),
|
||||||
|
);
|
||||||
|
return Value::date(dt_fixed, input_val.span);
|
||||||
|
}
|
||||||
|
ParseResult::DateTime(date) => {
|
||||||
|
return Value::date(date.fixed_offset(), input_val.span)
|
||||||
|
}
|
||||||
|
ParseResult::Time(time) => {
|
||||||
|
let date = Local::now().date_naive();
|
||||||
|
let combined = date.and_time(time);
|
||||||
|
let dt_fixed = DateTime::from_naive_utc_and_offset(
|
||||||
|
combined,
|
||||||
|
*Local::now().offset(),
|
||||||
|
);
|
||||||
|
return Value::date(dt_fixed, input_val.span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -362,6 +411,44 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn list_human_readable_examples(span: Span) -> Value {
|
||||||
|
let examples: Vec<String> = vec![
|
||||||
|
"Today 18:30".into(),
|
||||||
|
"2022-11-07 13:25:30".into(),
|
||||||
|
"15:20 Friday".into(),
|
||||||
|
"This Friday 17:00".into(),
|
||||||
|
"13:25, Next Tuesday".into(),
|
||||||
|
"Last Friday at 19:45".into(),
|
||||||
|
"In 3 days".into(),
|
||||||
|
"In 2 hours".into(),
|
||||||
|
"10 hours and 5 minutes ago".into(),
|
||||||
|
"1 years ago".into(),
|
||||||
|
"A year ago".into(),
|
||||||
|
"A month ago".into(),
|
||||||
|
"A week ago".into(),
|
||||||
|
"A day ago".into(),
|
||||||
|
"An hour ago".into(),
|
||||||
|
"A minute ago".into(),
|
||||||
|
"A second ago".into(),
|
||||||
|
"Now".into(),
|
||||||
|
];
|
||||||
|
|
||||||
|
let records = examples
|
||||||
|
.iter()
|
||||||
|
.map(|s| {
|
||||||
|
Value::record(
|
||||||
|
record! {
|
||||||
|
"parseable human datetime examples" => Value::test_string(s.to_string()),
|
||||||
|
"result" => action(&Value::test_string(s.to_string()), &Arguments { zone_options: None, format_options: None, cell_paths: None }, span)
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<Value>>();
|
||||||
|
|
||||||
|
Value::list(records, span)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -22,7 +22,15 @@ impl Command for ViewFiles {
|
|||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("view files")
|
Signature::build("view files")
|
||||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
.input_output_types(vec![(
|
||||||
|
Type::Nothing,
|
||||||
|
Type::Table(vec![
|
||||||
|
("filename".into(), Type::String),
|
||||||
|
("start".into(), Type::Int),
|
||||||
|
("end".into(), Type::Int),
|
||||||
|
("size".into(), Type::Int),
|
||||||
|
]),
|
||||||
|
)])
|
||||||
.category(Category::Debug)
|
.category(Category::Debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,10 +59,17 @@ impl Command for ViewFiles {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
description: "View the files registered in nushell's EngineState memory",
|
Example {
|
||||||
|
description: "View the files registered in Nushell's EngineState memory",
|
||||||
example: r#"view files"#,
|
example: r#"view files"#,
|
||||||
result: None,
|
result: None,
|
||||||
}]
|
},
|
||||||
|
Example {
|
||||||
|
description: "View how Nushell was originally invoked",
|
||||||
|
example: r#"view files | get 0"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,7 +169,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
EncodeBase64,
|
EncodeBase64,
|
||||||
DetectColumns,
|
DetectColumns,
|
||||||
Parse,
|
Parse,
|
||||||
Size,
|
|
||||||
Split,
|
Split,
|
||||||
SplitChars,
|
SplitChars,
|
||||||
SplitColumn,
|
SplitColumn,
|
||||||
@ -203,6 +202,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
Ls,
|
Ls,
|
||||||
Mkdir,
|
Mkdir,
|
||||||
UMkdir,
|
UMkdir,
|
||||||
|
Mktemp,
|
||||||
Mv,
|
Mv,
|
||||||
Cp,
|
Cp,
|
||||||
UCp,
|
UCp,
|
||||||
@ -382,7 +382,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
Seq,
|
Seq,
|
||||||
SeqDate,
|
SeqDate,
|
||||||
SeqChar,
|
SeqChar,
|
||||||
Unfold, // deprecated
|
|
||||||
Generate,
|
Generate,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,12 +43,6 @@ impl Command for Glob {
|
|||||||
"Whether to filter out symlinks from the returned paths",
|
"Whether to filter out symlinks from the returned paths",
|
||||||
Some('S'),
|
Some('S'),
|
||||||
)
|
)
|
||||||
.named(
|
|
||||||
"not",
|
|
||||||
SyntaxShape::List(Box::new(SyntaxShape::String)),
|
|
||||||
"DEPRECATED OPTION: Patterns to exclude from the results",
|
|
||||||
Some('n'),
|
|
||||||
)
|
|
||||||
.named(
|
.named(
|
||||||
"exclude",
|
"exclude",
|
||||||
SyntaxShape::List(Box::new(SyntaxShape::String)),
|
SyntaxShape::List(Box::new(SyntaxShape::String)),
|
||||||
@ -147,35 +141,7 @@ impl Command for Glob {
|
|||||||
let no_files = call.has_flag("no-file");
|
let no_files = call.has_flag("no-file");
|
||||||
let no_symlinks = call.has_flag("no-symlink");
|
let no_symlinks = call.has_flag("no-symlink");
|
||||||
|
|
||||||
if call.has_flag("not") {
|
let paths_to_exclude: Option<Value> = call.get_flag(engine_state, stack, "exclude")?;
|
||||||
nu_protocol::report_error_new(
|
|
||||||
engine_state,
|
|
||||||
&ShellError::GenericError(
|
|
||||||
"Deprecated option".into(),
|
|
||||||
"`glob --not {list<string>}` is deprecated and will be removed in 0.88.".into(),
|
|
||||||
Some(call.head),
|
|
||||||
Some("Please use `glob --exclude {list<string>}` instead.".into()),
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let not_flag: Option<Value> = call.get_flag(engine_state, stack, "not")?;
|
|
||||||
let exclude_flag: Option<Value> = call.get_flag(engine_state, stack, "exclude")?;
|
|
||||||
|
|
||||||
let paths_to_exclude = match (not_flag, exclude_flag) {
|
|
||||||
(Some(not_flag), Some(exclude_flag)) => {
|
|
||||||
return Err(ShellError::IncompatibleParameters {
|
|
||||||
left_message: "Cannot pass --not".into(),
|
|
||||||
left_span: not_flag.span(),
|
|
||||||
right_message: "and --exclude".into(),
|
|
||||||
right_span: exclude_flag.span(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
(Some(not_flag), None) => Some(not_flag),
|
|
||||||
(None, Some(exclude_flag)) => Some(exclude_flag),
|
|
||||||
(None, None) => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (not_patterns, not_pattern_span): (Vec<String>, Span) = match paths_to_exclude {
|
let (not_patterns, not_pattern_span): (Vec<String>, Span) = match paths_to_exclude {
|
||||||
None => (vec![], span),
|
None => (vec![], span),
|
||||||
|
129
crates/nu-command/src/filesystem/mktemp.rs
Normal file
129
crates/nu-command/src/filesystem/mktemp.rs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
use nu_engine::env::current_dir;
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Mktemp;
|
||||||
|
|
||||||
|
impl Command for Mktemp {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"mktemp"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Create temporary files or directories using uutils/coreutils mktemp."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec![
|
||||||
|
"coreutils",
|
||||||
|
"create",
|
||||||
|
"directory",
|
||||||
|
"file",
|
||||||
|
"folder",
|
||||||
|
"temporary",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("mktemp")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||||
|
.optional(
|
||||||
|
"template",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"Optional pattern from which the name of the file or directory is derived. Must contain at least three 'X's in last component.",
|
||||||
|
)
|
||||||
|
.named("suffix", SyntaxShape::String, "Append suffix to template; must not contain a slash.", None)
|
||||||
|
.named("tmpdir-path", SyntaxShape::Filepath, "Interpret TEMPLATE relative to tmpdir-path. If tmpdir-path is not set use $TMPDIR", Some('p'))
|
||||||
|
.switch("tmpdir", "Interpret TEMPLATE relative to the system temporary directory.", Some('t'))
|
||||||
|
.switch("directory", "Create a directory instead of a file.", Some('d'))
|
||||||
|
.category(Category::FileSystem)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Make a temporary file with the given suffix in the current working directory.",
|
||||||
|
example: "mktemp --suffix .txt",
|
||||||
|
result: Some(Value::test_string("<WORKING_DIR>/tmp.lekjbhelyx.txt")),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Make a temporary file named testfile.XXX with the 'X's as random characters in the current working directory.",
|
||||||
|
example: "mktemp testfile.XXX",
|
||||||
|
result: Some(Value::test_string("<WORKING_DIR>/testfile.4kh")),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Make a temporary file with a template in the system temp directory.",
|
||||||
|
example: "mktemp -t testfile.XXX",
|
||||||
|
result: Some(Value::test_string("/tmp/testfile.4kh")),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Make a temporary directory with randomly generated name in the temporary directory.",
|
||||||
|
example: "mktemp -d",
|
||||||
|
result: Some(Value::test_string("/tmp/tmp.NMw9fJr8K0")),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let span = call.head;
|
||||||
|
let template = call
|
||||||
|
.rest(engine_state, stack, 0)?
|
||||||
|
.first()
|
||||||
|
.cloned()
|
||||||
|
.map(|i: Spanned<String>| i.item)
|
||||||
|
.unwrap_or("tmp.XXXXXXXXXX".to_string()); // same as default in coreutils
|
||||||
|
let directory = call.has_flag("directory");
|
||||||
|
let suffix = call.get_flag(engine_state, stack, "suffix")?;
|
||||||
|
let tmpdir = call.has_flag("tmpdir");
|
||||||
|
let tmpdir_path = call
|
||||||
|
.get_flag(engine_state, stack, "tmpdir-path")?
|
||||||
|
.map(|i: Spanned<PathBuf>| i.item);
|
||||||
|
|
||||||
|
let tmpdir = if tmpdir_path.is_some() {
|
||||||
|
tmpdir_path
|
||||||
|
} else if directory || tmpdir {
|
||||||
|
Some(std::env::temp_dir())
|
||||||
|
} else {
|
||||||
|
Some(current_dir(engine_state, stack)?)
|
||||||
|
};
|
||||||
|
|
||||||
|
let options = uu_mktemp::Options {
|
||||||
|
directory,
|
||||||
|
dry_run: false,
|
||||||
|
quiet: false,
|
||||||
|
suffix,
|
||||||
|
template,
|
||||||
|
tmpdir,
|
||||||
|
treat_as_template: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = match uu_mktemp::mktemp(&options) {
|
||||||
|
Ok(res) => res
|
||||||
|
.into_os_string()
|
||||||
|
.into_string()
|
||||||
|
.map_err(|e| ShellError::IOErrorSpanned(e.to_string_lossy().to_string(), span))?,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(ShellError::GenericError(
|
||||||
|
format!("{}", e),
|
||||||
|
format!("{}", e),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(PipelineData::Value(Value::string(res, span), None))
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ mod cp;
|
|||||||
mod glob;
|
mod glob;
|
||||||
mod ls;
|
mod ls;
|
||||||
mod mkdir;
|
mod mkdir;
|
||||||
|
mod mktemp;
|
||||||
mod mv;
|
mod mv;
|
||||||
mod open;
|
mod open;
|
||||||
mod rm;
|
mod rm;
|
||||||
@ -20,6 +21,7 @@ pub use cp::Cp;
|
|||||||
pub use glob::Glob;
|
pub use glob::Glob;
|
||||||
pub use ls::Ls;
|
pub use ls::Ls;
|
||||||
pub use mkdir::Mkdir;
|
pub use mkdir::Mkdir;
|
||||||
|
pub use mktemp::Mktemp;
|
||||||
pub use mv::Mv;
|
pub use mv::Mv;
|
||||||
pub use rm::Rm;
|
pub use rm::Rm;
|
||||||
pub use save::Save;
|
pub use save::Save;
|
||||||
|
@ -304,7 +304,8 @@ fn highlight_terms_in_record_with_search_columns(
|
|||||||
let val_str = val.into_string("", config);
|
let val_str = val.into_string("", config);
|
||||||
let Some(term_str) = term_strs
|
let Some(term_str) = term_strs
|
||||||
.iter()
|
.iter()
|
||||||
.find(|term_str| contains_ignore_case(&val_str, term_str)) else {
|
.find(|term_str| contains_ignore_case(&val_str, term_str))
|
||||||
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -271,7 +271,7 @@ fn group_closure(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let value = match collection.get(0) {
|
let value = match collection.first() {
|
||||||
Some(Value::Error { .. }) | None => Value::string(error_key, span),
|
Some(Value::Error { .. }) | None => Value::string(error_key, span),
|
||||||
Some(return_value) => return_value.clone(),
|
Some(return_value) => return_value.clone(),
|
||||||
};
|
};
|
||||||
|
@ -171,7 +171,7 @@ fn insert(
|
|||||||
ctrlc,
|
ctrlc,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
if let Some(PathMember::Int { val, .. }) = cell_path.members.get(0) {
|
if let Some(PathMember::Int { val, .. }) = cell_path.members.first() {
|
||||||
let mut input = input.into_iter();
|
let mut input = input.into_iter();
|
||||||
let mut pre_elems = vec![];
|
let mut pre_elems = vec![];
|
||||||
|
|
||||||
|
@ -221,7 +221,7 @@ fn reject(
|
|||||||
let mut new_rows = vec![];
|
let mut new_rows = vec![];
|
||||||
for column in cell_paths {
|
for column in cell_paths {
|
||||||
let CellPath { ref members } = column;
|
let CellPath { ref members } = column;
|
||||||
match members.get(0) {
|
match members.first() {
|
||||||
Some(PathMember::Int { val, span, .. }) => {
|
Some(PathMember::Int { val, span, .. }) => {
|
||||||
if members.len() > 1 {
|
if members.len() > 1 {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError(
|
||||||
|
@ -212,7 +212,7 @@ fn select(
|
|||||||
|
|
||||||
for column in columns {
|
for column in columns {
|
||||||
let CellPath { ref members } = column;
|
let CellPath { ref members } = column;
|
||||||
match members.get(0) {
|
match members.first() {
|
||||||
Some(PathMember::Int { val, span, .. }) => {
|
Some(PathMember::Int { val, span, .. }) => {
|
||||||
if members.len() > 1 {
|
if members.len() > 1 {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError(
|
||||||
|
@ -190,7 +190,7 @@ pub fn transpose(
|
|||||||
|
|
||||||
if args.header_row {
|
if args.header_row {
|
||||||
for i in input.iter() {
|
for i in input.iter() {
|
||||||
if let Some(desc) = descs.get(0) {
|
if let Some(desc) = descs.first() {
|
||||||
match &i.get_data_by_key(desc) {
|
match &i.get_data_by_key(desc) {
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
if let Ok(s) = x.as_string() {
|
if let Ok(s) = x.as_string() {
|
||||||
|
@ -171,7 +171,7 @@ fn update(
|
|||||||
)?
|
)?
|
||||||
.set_metadata(mdclone))
|
.set_metadata(mdclone))
|
||||||
} else {
|
} else {
|
||||||
if let Some(PathMember::Int { val, span, .. }) = cell_path.members.get(0) {
|
if let Some(PathMember::Int { val, span, .. }) = cell_path.members.first() {
|
||||||
let mut input = input.into_iter();
|
let mut input = input.into_iter();
|
||||||
let mut pre_elems = vec![];
|
let mut pre_elems = vec![];
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ fn upsert(
|
|||||||
ctrlc,
|
ctrlc,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
if let Some(PathMember::Int { val, span, .. }) = cell_path.members.get(0) {
|
if let Some(PathMember::Int { val, span, .. }) = cell_path.members.first() {
|
||||||
let mut input = input.into_iter();
|
let mut input = input.into_iter();
|
||||||
let mut pre_elems = vec![];
|
let mut pre_elems = vec![];
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ impl Command for FromNuon {
|
|||||||
let mut block = nu_parser::parse(&mut working_set, None, string_input.as_bytes(), false);
|
let mut block = nu_parser::parse(&mut working_set, None, string_input.as_bytes(), false);
|
||||||
|
|
||||||
if let Some(pipeline) = block.pipelines.get(1) {
|
if let Some(pipeline) = block.pipelines.get(1) {
|
||||||
if let Some(element) = pipeline.elements.get(0) {
|
if let Some(element) = pipeline.elements.first() {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError(
|
||||||
"error when loading nuon text".into(),
|
"error when loading nuon text".into(),
|
||||||
"could not load nuon text".into(),
|
"could not load nuon text".into(),
|
||||||
|
@ -3,11 +3,9 @@ mod generate;
|
|||||||
mod seq;
|
mod seq;
|
||||||
mod seq_char;
|
mod seq_char;
|
||||||
mod seq_date;
|
mod seq_date;
|
||||||
mod unfold;
|
|
||||||
|
|
||||||
pub use cal::Cal;
|
pub use cal::Cal;
|
||||||
pub use generate::Generate;
|
pub use generate::Generate;
|
||||||
pub use seq::Seq;
|
pub use seq::Seq;
|
||||||
pub use seq_char::SeqChar;
|
pub use seq_char::SeqChar;
|
||||||
pub use seq_date::SeqDate;
|
pub use seq_date::SeqDate;
|
||||||
pub use unfold::Unfold;
|
|
||||||
|
@ -1,242 +0,0 @@
|
|||||||
use itertools::unfold;
|
|
||||||
|
|
||||||
use nu_engine::{eval_block_with_early_return, CallExt};
|
|
||||||
use nu_protocol::ast::Call;
|
|
||||||
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
|
|
||||||
use nu_protocol::{
|
|
||||||
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
|
|
||||||
Signature, Span, Spanned, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Unfold;
|
|
||||||
|
|
||||||
impl Command for Unfold {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"unfold"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("unfold")
|
|
||||||
.input_output_types(vec![
|
|
||||||
(Type::Nothing, Type::List(Box::new(Type::Any))),
|
|
||||||
(
|
|
||||||
Type::List(Box::new(Type::Any)),
|
|
||||||
Type::List(Box::new(Type::Any)),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
.required("initial", SyntaxShape::Any, "initial value")
|
|
||||||
.required(
|
|
||||||
"closure",
|
|
||||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
|
||||||
"generator function",
|
|
||||||
)
|
|
||||||
.allow_variants_without_examples(true)
|
|
||||||
.category(Category::Generators)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Generate a list of values by successively invoking a closure."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
|
||||||
r#"The generator closure accepts a single argument and returns a record
|
|
||||||
containing two optional keys: 'out' and 'next'. Each invocation, the 'out'
|
|
||||||
value, if present, is added to the stream. If a 'next' key is present, it is
|
|
||||||
used as the next argument to the closure, otherwise generation stops.
|
|
||||||
"#
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["generate", "stream"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
example: "unfold 0 {|i| if $i <= 10 { {out: $i, next: ($i + 2)} }}",
|
|
||||||
description: "Generate a sequence of numbers",
|
|
||||||
result: Some(Value::list(
|
|
||||||
vec![
|
|
||||||
Value::test_int(0),
|
|
||||||
Value::test_int(2),
|
|
||||||
Value::test_int(4),
|
|
||||||
Value::test_int(6),
|
|
||||||
Value::test_int(8),
|
|
||||||
Value::test_int(10),
|
|
||||||
],
|
|
||||||
Span::test_data(),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
example: "unfold [0, 1] {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} } | first 10",
|
|
||||||
description: "Generate a stream of fibonacci numbers",
|
|
||||||
result: Some(Value::list(
|
|
||||||
vec![
|
|
||||||
Value::test_int(0),
|
|
||||||
Value::test_int(1),
|
|
||||||
Value::test_int(1),
|
|
||||||
Value::test_int(2),
|
|
||||||
Value::test_int(3),
|
|
||||||
Value::test_int(5),
|
|
||||||
Value::test_int(8),
|
|
||||||
Value::test_int(13),
|
|
||||||
Value::test_int(21),
|
|
||||||
Value::test_int(34),
|
|
||||||
],
|
|
||||||
Span::test_data(),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
nu_protocol::report_error_new(
|
|
||||||
engine_state,
|
|
||||||
&ShellError::GenericError(
|
|
||||||
"Deprecated option".into(),
|
|
||||||
"`unfold` is deprecated and will be removed in 0.88.".into(),
|
|
||||||
Some(call.head),
|
|
||||||
Some("Please use `generate` instead.".into()),
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
let initial: Value = call.req(engine_state, stack, 0)?;
|
|
||||||
let capture_block: Spanned<Closure> = call.req(engine_state, stack, 1)?;
|
|
||||||
let block_span = capture_block.span;
|
|
||||||
let block = engine_state.get_block(capture_block.item.block_id).clone();
|
|
||||||
let ctrlc = engine_state.ctrlc.clone();
|
|
||||||
let engine_state = engine_state.clone();
|
|
||||||
let mut stack = stack.captures_to_stack(capture_block.item.captures);
|
|
||||||
let orig_env_vars = stack.env_vars.clone();
|
|
||||||
let orig_env_hidden = stack.env_hidden.clone();
|
|
||||||
let redirect_stdout = call.redirect_stdout;
|
|
||||||
let redirect_stderr = call.redirect_stderr;
|
|
||||||
|
|
||||||
// A type of Option<S> is used to represent state. Invocation
|
|
||||||
// will stop on None. Using Option<S> allows functions to output
|
|
||||||
// one final value before stopping.
|
|
||||||
let iter = unfold(Some(initial), move |state| {
|
|
||||||
let arg = match state {
|
|
||||||
Some(state) => state.clone(),
|
|
||||||
None => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// with_env() is used here to ensure that each iteration uses
|
|
||||||
// a different set of environment variables.
|
|
||||||
// Hence, a 'cd' in the first loop won't affect the next loop.
|
|
||||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
|
||||||
|
|
||||||
if let Some(var) = block.signature.get_positional(0) {
|
|
||||||
if let Some(var_id) = &var.var_id {
|
|
||||||
stack.add_var(*var_id, arg.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (output, next_input) = match eval_block_with_early_return(
|
|
||||||
&engine_state,
|
|
||||||
&mut stack,
|
|
||||||
&block,
|
|
||||||
arg.into_pipeline_data(),
|
|
||||||
redirect_stdout,
|
|
||||||
redirect_stderr,
|
|
||||||
) {
|
|
||||||
// no data -> output nothing and stop.
|
|
||||||
Ok(PipelineData::Empty) => (None, None),
|
|
||||||
|
|
||||||
Ok(PipelineData::Value(value, ..)) => {
|
|
||||||
let span = value.span();
|
|
||||||
match value {
|
|
||||||
// {out: ..., next: ...} -> output and continue
|
|
||||||
Value::Record { val, .. } => {
|
|
||||||
let iter = val.into_iter();
|
|
||||||
let mut out = None;
|
|
||||||
let mut next = None;
|
|
||||||
let mut err = None;
|
|
||||||
|
|
||||||
for (k, v) in iter {
|
|
||||||
if k.eq_ignore_ascii_case("out") {
|
|
||||||
out = Some(v);
|
|
||||||
} else if k.eq_ignore_ascii_case("next") {
|
|
||||||
next = Some(v);
|
|
||||||
} else {
|
|
||||||
let error = ShellError::GenericError(
|
|
||||||
"Invalid block return".to_string(),
|
|
||||||
format!("Unexpected record key '{}'", k),
|
|
||||||
Some(span),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
);
|
|
||||||
err = Some(Value::error(error, block_span));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err.is_some() {
|
|
||||||
(err, None)
|
|
||||||
} else {
|
|
||||||
(out, next)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// some other value -> error and stop
|
|
||||||
_ => {
|
|
||||||
let error = ShellError::GenericError(
|
|
||||||
"Invalid block return".to_string(),
|
|
||||||
format!("Expected record, found {}", value.get_type()),
|
|
||||||
Some(span),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
);
|
|
||||||
|
|
||||||
(Some(Value::error(error, block_span)), None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(other) => {
|
|
||||||
let val = other.into_value(block_span);
|
|
||||||
let error = ShellError::GenericError(
|
|
||||||
"Invalid block return".to_string(),
|
|
||||||
format!("Expected record, found {}", val.get_type()),
|
|
||||||
Some(val.span()),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
);
|
|
||||||
|
|
||||||
(Some(Value::error(error, block_span)), None)
|
|
||||||
}
|
|
||||||
|
|
||||||
// error -> error and stop
|
|
||||||
Err(error) => (Some(Value::error(error, block_span)), None),
|
|
||||||
};
|
|
||||||
|
|
||||||
// We use `state` to control when to stop, not `output`. By wrapping
|
|
||||||
// it in a `Some`, we allow the generator to output `None` as a valid output
|
|
||||||
// value.
|
|
||||||
*state = next_input;
|
|
||||||
Some(output)
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(iter.flatten().into_pipeline_data(ctrlc))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
use crate::test_examples;
|
|
||||||
|
|
||||||
test_examples(Unfold {})
|
|
||||||
}
|
|
||||||
}
|
|
@ -95,7 +95,7 @@ You can also learn more at https://www.nushell.sh/book/"#;
|
|||||||
result
|
result
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = if let Err(ShellError::CommandNotFound(_)) = result {
|
let result = if let Err(ShellError::CommandNotFound { .. }) = result {
|
||||||
help_modules(engine_state, stack, call)
|
help_modules(engine_state, stack, call)
|
||||||
} else {
|
} else {
|
||||||
result
|
result
|
||||||
|
@ -114,10 +114,9 @@ pub fn help_commands(
|
|||||||
.into_pipeline_data(),
|
.into_pipeline_data(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::CommandNotFound(span(&[
|
Err(ShellError::CommandNotFound {
|
||||||
rest[0].span,
|
span: span(&[rest[0].span, rest[rest.len() - 1].span]),
|
||||||
rest[rest.len() - 1].span,
|
})
|
||||||
])))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,10 +133,9 @@ pub fn help_externs(
|
|||||||
.into_pipeline_data(),
|
.into_pipeline_data(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::CommandNotFound(span(&[
|
Err(ShellError::CommandNotFound {
|
||||||
rest[0].span,
|
span: span(&[rest[0].span, rest[rest.len() - 1].span]),
|
||||||
rest[rest.len() - 1].span,
|
})
|
||||||
])))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ pub fn min(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn sum(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError> {
|
pub fn sum(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError> {
|
||||||
let initial_value = data.get(0);
|
let initial_value = data.first();
|
||||||
|
|
||||||
let mut acc = match initial_value {
|
let mut acc = match initial_value {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
@ -124,7 +124,7 @@ pub fn sum(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn product(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError> {
|
pub fn product(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError> {
|
||||||
let initial_value = data.get(0);
|
let initial_value = data.first();
|
||||||
|
|
||||||
let mut acc = match initial_value {
|
let mut acc = match initial_value {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
|
@ -363,38 +363,26 @@ pub fn request_add_custom_headers(
|
|||||||
|
|
||||||
fn handle_response_error(span: Span, requested_url: &str, response_err: Error) -> ShellError {
|
fn handle_response_error(span: Span, requested_url: &str, response_err: Error) -> ShellError {
|
||||||
match response_err {
|
match response_err {
|
||||||
Error::Status(301, _) => ShellError::NetworkFailure(
|
Error::Status(301, _) => ShellError::NetworkFailure { msg: format!("Resource moved permanently (301): {requested_url:?}"), span },
|
||||||
format!("Resource moved permanently (301): {requested_url:?}"),
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
Error::Status(400, _) => {
|
Error::Status(400, _) => {
|
||||||
ShellError::NetworkFailure(format!("Bad request (400) to {requested_url:?}"), span)
|
ShellError::NetworkFailure { msg: format!("Bad request (400) to {requested_url:?}"), span }
|
||||||
}
|
}
|
||||||
Error::Status(403, _) => {
|
Error::Status(403, _) => {
|
||||||
ShellError::NetworkFailure(format!("Access forbidden (403) to {requested_url:?}"), span)
|
ShellError::NetworkFailure { msg: format!("Access forbidden (403) to {requested_url:?}"), span }
|
||||||
}
|
}
|
||||||
Error::Status(404, _) => ShellError::NetworkFailure(
|
Error::Status(404, _) => ShellError::NetworkFailure { msg: format!("Requested file not found (404): {requested_url:?}"), span },
|
||||||
format!("Requested file not found (404): {requested_url:?}"),
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
Error::Status(408, _) => {
|
Error::Status(408, _) => {
|
||||||
ShellError::NetworkFailure(format!("Request timeout (408): {requested_url:?}"), span)
|
ShellError::NetworkFailure { msg: format!("Request timeout (408): {requested_url:?}"), span }
|
||||||
}
|
}
|
||||||
Error::Status(_, _) => ShellError::NetworkFailure(
|
Error::Status(_, _) => ShellError::NetworkFailure { msg: format!(
|
||||||
format!(
|
|
||||||
"Cannot make request to {:?}. Error is {:?}",
|
"Cannot make request to {:?}. Error is {:?}",
|
||||||
requested_url,
|
requested_url,
|
||||||
response_err.to_string()
|
response_err.to_string()
|
||||||
),
|
), span },
|
||||||
span,
|
|
||||||
),
|
|
||||||
|
|
||||||
Error::Transport(t) => match t {
|
Error::Transport(t) => match t {
|
||||||
t if t.kind() == ErrorKind::ConnectionFailed => ShellError::NetworkFailure(
|
t if t.kind() == ErrorKind::ConnectionFailed => ShellError::NetworkFailure { msg: format!("Cannot make request to {requested_url}, there was an error establishing a connection.",), span },
|
||||||
format!("Cannot make request to {requested_url}, there was an error establishing a connection.",),
|
t => ShellError::NetworkFailure { msg: t.to_string(), span },
|
||||||
span,
|
|
||||||
),
|
|
||||||
t => ShellError::NetworkFailure(t.to_string(), span),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ mod detect_columns;
|
|||||||
mod encode_decode;
|
mod encode_decode;
|
||||||
mod format;
|
mod format;
|
||||||
mod parse;
|
mod parse;
|
||||||
mod size;
|
|
||||||
mod split;
|
mod split;
|
||||||
mod str_;
|
mod str_;
|
||||||
|
|
||||||
@ -12,7 +11,6 @@ pub use detect_columns::*;
|
|||||||
pub use encode_decode::*;
|
pub use encode_decode::*;
|
||||||
pub use format::*;
|
pub use format::*;
|
||||||
pub use parse::*;
|
pub use parse::*;
|
||||||
pub use size::Size;
|
|
||||||
pub use split::*;
|
pub use split::*;
|
||||||
pub use str_::*;
|
pub use str_::*;
|
||||||
|
|
||||||
|
@ -1,381 +0,0 @@
|
|||||||
use fancy_regex::Regex;
|
|
||||||
use nu_protocol::ast::Call;
|
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
|
||||||
use nu_protocol::{
|
|
||||||
record, Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
|
||||||
};
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::{fmt, str};
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
|
||||||
|
|
||||||
// borrowed liberally from here https://github.com/dead10ck/uwc
|
|
||||||
pub type Counted = BTreeMap<Counter, usize>;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Size;
|
|
||||||
|
|
||||||
impl Command for Size {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"size"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("size")
|
|
||||||
.category(Category::Strings)
|
|
||||||
.input_output_types(vec![(Type::String, Type::Record(vec![]))])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Gather word count statistics on the text."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["count", "word", "character", "unicode", "wc"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
_stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
nu_protocol::report_error_new(
|
|
||||||
engine_state,
|
|
||||||
&ShellError::GenericError(
|
|
||||||
"Deprecated command".into(),
|
|
||||||
"`size` is deprecated and will be removed in 0.88.".into(),
|
|
||||||
Some(call.head),
|
|
||||||
Some("Use `str stats` instead".into()),
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
size(engine_state, call, input)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
description: "Count the number of words in a string",
|
|
||||||
example: r#""There are seven words in this sentence" | size"#,
|
|
||||||
result: Some(Value::test_record(record! {
|
|
||||||
"lines" => Value::test_int(1),
|
|
||||||
"words" => Value::test_int(7),
|
|
||||||
"bytes" => Value::test_int(38),
|
|
||||||
"chars" => Value::test_int(38),
|
|
||||||
"graphemes" => Value::test_int(38),
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Counts unicode characters",
|
|
||||||
example: r#"'今天天气真好' | size "#,
|
|
||||||
result: Some(Value::test_record(record! {
|
|
||||||
"lines" => Value::test_int(1),
|
|
||||||
"words" => Value::test_int(6),
|
|
||||||
"bytes" => Value::test_int(18),
|
|
||||||
"chars" => Value::test_int(6),
|
|
||||||
"graphemes" => Value::test_int(6),
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Counts Unicode characters correctly in a string",
|
|
||||||
example: r#""Amélie Amelie" | size"#,
|
|
||||||
result: Some(Value::test_record(record! {
|
|
||||||
"lines" => Value::test_int(1),
|
|
||||||
"words" => Value::test_int(2),
|
|
||||||
"bytes" => Value::test_int(15),
|
|
||||||
"chars" => Value::test_int(14),
|
|
||||||
"graphemes" => Value::test_int(13),
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size(
|
|
||||||
engine_state: &EngineState,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let span = call.head;
|
|
||||||
// This doesn't match explicit nulls
|
|
||||||
if matches!(input, PipelineData::Empty) {
|
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: span });
|
|
||||||
}
|
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
let value_span = v.span();
|
|
||||||
// First, obtain the span. If this fails, propagate the error that results.
|
|
||||||
if let Value::Error { error, .. } = v {
|
|
||||||
return Value::error(*error, span);
|
|
||||||
}
|
|
||||||
// Now, check if it's a string.
|
|
||||||
match v.as_string() {
|
|
||||||
Ok(s) => counter(&s, span),
|
|
||||||
Err(_) => Value::error(
|
|
||||||
ShellError::PipelineMismatch {
|
|
||||||
exp_input_type: "string".into(),
|
|
||||||
dst_span: span,
|
|
||||||
src_span: value_span,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn counter(contents: &str, span: Span) -> Value {
|
|
||||||
let counts = uwc_count(&ALL_COUNTERS[..], contents);
|
|
||||||
|
|
||||||
fn get_count(counts: &BTreeMap<Counter, usize>, counter: Counter, span: Span) -> Value {
|
|
||||||
Value::int(counts.get(&counter).copied().unwrap_or(0) as i64, span)
|
|
||||||
}
|
|
||||||
|
|
||||||
let record = record! {
|
|
||||||
"lines" => get_count(&counts, Counter::Lines, span),
|
|
||||||
"words" => get_count(&counts, Counter::Words, span),
|
|
||||||
"bytes" => get_count(&counts, Counter::Bytes, span),
|
|
||||||
"chars" => get_count(&counts, Counter::CodePoints, span),
|
|
||||||
"graphemes" => get_count(&counts, Counter::GraphemeClusters, span),
|
|
||||||
};
|
|
||||||
|
|
||||||
Value::record(record, span)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Take all the counts in `other_counts` and sum them into `accum`.
|
|
||||||
// pub fn sum_counts(accum: &mut Counted, other_counts: &Counted) {
|
|
||||||
// for (counter, count) in other_counts {
|
|
||||||
// let entry = accum.entry(*counter).or_insert(0);
|
|
||||||
// *entry += count;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// Sums all the `Counted` instances into a new one.
|
|
||||||
// pub fn sum_all_counts<'a, I>(counts: I) -> Counted
|
|
||||||
// where
|
|
||||||
// I: IntoIterator<Item = &'a Counted>,
|
|
||||||
// {
|
|
||||||
// let mut totals = BTreeMap::new();
|
|
||||||
// for counts in counts {
|
|
||||||
// sum_counts(&mut totals, counts);
|
|
||||||
// }
|
|
||||||
// totals
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// Something that counts things in `&str`s.
|
|
||||||
pub trait Count {
|
|
||||||
/// Counts something in the given `&str`.
|
|
||||||
fn count(&self, s: &str) -> usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Count for Counter {
|
|
||||||
fn count(&self, s: &str) -> usize {
|
|
||||||
match *self {
|
|
||||||
Counter::GraphemeClusters => s.graphemes(true).count(),
|
|
||||||
Counter::Bytes => s.len(),
|
|
||||||
Counter::Lines => {
|
|
||||||
const LF: &str = "\n"; // 0xe0000a
|
|
||||||
const CR: &str = "\r"; // 0xe0000d
|
|
||||||
const CRLF: &str = "\r\n"; // 0xe00d0a
|
|
||||||
const NEL: &str = "\u{0085}"; // 0x00c285
|
|
||||||
const FF: &str = "\u{000C}"; // 0x00000c
|
|
||||||
const LS: &str = "\u{2028}"; // 0xe280a8
|
|
||||||
const PS: &str = "\u{2029}"; // 0xe280a9
|
|
||||||
|
|
||||||
// use regex here because it can search for CRLF first and not duplicate the count
|
|
||||||
let line_ending_types = [CRLF, LF, CR, NEL, FF, LS, PS];
|
|
||||||
let pattern = &line_ending_types.join("|");
|
|
||||||
let newline_pattern = Regex::new(pattern).expect("Unable to create regex");
|
|
||||||
let line_endings = newline_pattern
|
|
||||||
.find_iter(s)
|
|
||||||
.map(|f| match f {
|
|
||||||
Ok(mat) => mat.as_str().to_string(),
|
|
||||||
Err(_) => "".to_string(),
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
let has_line_ending_suffix =
|
|
||||||
line_ending_types.iter().any(|&suffix| s.ends_with(suffix));
|
|
||||||
// eprintln!("suffix = {}", has_line_ending_suffix);
|
|
||||||
|
|
||||||
if has_line_ending_suffix {
|
|
||||||
line_endings.len()
|
|
||||||
} else {
|
|
||||||
line_endings.len() + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Counter::Words => s.unicode_words().count(),
|
|
||||||
Counter::CodePoints => s.chars().count(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Different types of counters.
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
|
||||||
pub enum Counter {
|
|
||||||
/// Counts lines.
|
|
||||||
Lines,
|
|
||||||
|
|
||||||
/// Counts words.
|
|
||||||
Words,
|
|
||||||
|
|
||||||
/// Counts the total number of bytes.
|
|
||||||
Bytes,
|
|
||||||
|
|
||||||
/// Counts grapheme clusters. The input is required to be valid UTF-8.
|
|
||||||
GraphemeClusters,
|
|
||||||
|
|
||||||
/// Counts unicode code points
|
|
||||||
CodePoints,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A convenience array of all counter types.
|
|
||||||
pub const ALL_COUNTERS: [Counter; 5] = [
|
|
||||||
Counter::GraphemeClusters,
|
|
||||||
Counter::Bytes,
|
|
||||||
Counter::Lines,
|
|
||||||
Counter::Words,
|
|
||||||
Counter::CodePoints,
|
|
||||||
];
|
|
||||||
|
|
||||||
impl fmt::Display for Counter {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let s = match *self {
|
|
||||||
Counter::GraphemeClusters => "graphemes",
|
|
||||||
Counter::Bytes => "bytes",
|
|
||||||
Counter::Lines => "lines",
|
|
||||||
Counter::Words => "words",
|
|
||||||
Counter::CodePoints => "codepoints",
|
|
||||||
};
|
|
||||||
|
|
||||||
write!(f, "{s}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Counts the given `Counter`s in the given `&str`.
|
|
||||||
pub fn uwc_count<'a, I>(counters: I, s: &str) -> Counted
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = &'a Counter>,
|
|
||||||
{
|
|
||||||
let mut counts: Counted = counters.into_iter().map(|c| (*c, c.count(s))).collect();
|
|
||||||
if let Some(lines) = counts.get_mut(&Counter::Lines) {
|
|
||||||
if s.is_empty() {
|
|
||||||
// If s is empty, indeed, the count is 0
|
|
||||||
*lines = 0;
|
|
||||||
} else if *lines == 0 && !s.is_empty() {
|
|
||||||
// If s is not empty and the count is 0, it means there
|
|
||||||
// is a line without a line ending, so let's make it 1
|
|
||||||
*lines = 1;
|
|
||||||
} else {
|
|
||||||
// no change, whatever the count is, is right
|
|
||||||
}
|
|
||||||
}
|
|
||||||
counts
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
use crate::test_examples;
|
|
||||||
|
|
||||||
test_examples(Size {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_one_newline() {
|
|
||||||
let s = "\n".to_string();
|
|
||||||
let counts = uwc_count(&ALL_COUNTERS[..], &s);
|
|
||||||
let mut correct_counts = BTreeMap::new();
|
|
||||||
correct_counts.insert(Counter::Lines, 1);
|
|
||||||
correct_counts.insert(Counter::Words, 0);
|
|
||||||
correct_counts.insert(Counter::GraphemeClusters, 1);
|
|
||||||
correct_counts.insert(Counter::Bytes, 1);
|
|
||||||
correct_counts.insert(Counter::CodePoints, 1);
|
|
||||||
|
|
||||||
assert_eq!(correct_counts, counts);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_count_counts_lines() {
|
|
||||||
// const LF: &str = "\n"; // 0xe0000a
|
|
||||||
// const CR: &str = "\r"; // 0xe0000d
|
|
||||||
// const CRLF: &str = "\r\n"; // 0xe00d0a
|
|
||||||
const NEL: &str = "\u{0085}"; // 0x00c285
|
|
||||||
const FF: &str = "\u{000C}"; // 0x00000c
|
|
||||||
const LS: &str = "\u{2028}"; // 0xe280a8
|
|
||||||
const PS: &str = "\u{2029}"; // 0xe280a9
|
|
||||||
|
|
||||||
// * \r\n is a single grapheme cluster
|
|
||||||
// * trailing newlines are counted
|
|
||||||
// * NEL is 2 bytes
|
|
||||||
// * FF is 1 byte
|
|
||||||
// * LS is 3 bytes
|
|
||||||
// * PS is 3 bytes
|
|
||||||
let mut s = String::from("foo\r\nbar\n\nbaz");
|
|
||||||
s += NEL;
|
|
||||||
s += "quux";
|
|
||||||
s += FF;
|
|
||||||
s += LS;
|
|
||||||
s += "xi";
|
|
||||||
s += PS;
|
|
||||||
s += "\n";
|
|
||||||
|
|
||||||
let counts = uwc_count(&ALL_COUNTERS[..], &s);
|
|
||||||
|
|
||||||
let mut correct_counts = BTreeMap::new();
|
|
||||||
correct_counts.insert(Counter::Lines, 8);
|
|
||||||
correct_counts.insert(Counter::Words, 5);
|
|
||||||
correct_counts.insert(Counter::GraphemeClusters, 23);
|
|
||||||
correct_counts.insert(Counter::Bytes, 29);
|
|
||||||
|
|
||||||
// one more than grapheme clusters because of \r\n
|
|
||||||
correct_counts.insert(Counter::CodePoints, 24);
|
|
||||||
|
|
||||||
assert_eq!(correct_counts, counts);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_count_counts_words() {
|
|
||||||
let i_can_eat_glass = "Μπορῶ νὰ φάω σπασμένα γυαλιὰ χωρὶς νὰ πάθω τίποτα.";
|
|
||||||
let s = String::from(i_can_eat_glass);
|
|
||||||
|
|
||||||
let counts = uwc_count(&ALL_COUNTERS[..], &s);
|
|
||||||
|
|
||||||
let mut correct_counts = BTreeMap::new();
|
|
||||||
correct_counts.insert(Counter::GraphemeClusters, 50);
|
|
||||||
correct_counts.insert(Counter::Lines, 1);
|
|
||||||
correct_counts.insert(Counter::Bytes, i_can_eat_glass.len());
|
|
||||||
correct_counts.insert(Counter::Words, 9);
|
|
||||||
correct_counts.insert(Counter::CodePoints, 50);
|
|
||||||
|
|
||||||
assert_eq!(correct_counts, counts);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_count_counts_codepoints() {
|
|
||||||
// these are NOT the same! One is e + ́́ , and one is é, a single codepoint
|
|
||||||
let one = "é";
|
|
||||||
let two = "é";
|
|
||||||
|
|
||||||
let counters = [Counter::CodePoints];
|
|
||||||
|
|
||||||
let counts = uwc_count(&counters[..], one);
|
|
||||||
|
|
||||||
let mut correct_counts = BTreeMap::new();
|
|
||||||
correct_counts.insert(Counter::CodePoints, 1);
|
|
||||||
|
|
||||||
assert_eq!(correct_counts, counts);
|
|
||||||
|
|
||||||
let counts = uwc_count(&counters[..], two);
|
|
||||||
|
|
||||||
let mut correct_counts = BTreeMap::new();
|
|
||||||
correct_counts.insert(Counter::CodePoints, 2);
|
|
||||||
|
|
||||||
assert_eq!(correct_counts, counts);
|
|
||||||
}
|
|
@ -14,6 +14,14 @@ use nu_protocol::{
|
|||||||
Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError, Signature,
|
Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError, Signature,
|
||||||
Type, Value,
|
Type, Value,
|
||||||
};
|
};
|
||||||
|
#[cfg(all(
|
||||||
|
unix,
|
||||||
|
not(target_os = "macos"),
|
||||||
|
not(target_os = "windows"),
|
||||||
|
not(target_os = "android"),
|
||||||
|
not(target_os = "ios")
|
||||||
|
))]
|
||||||
|
use procfs::WithCurrentSystemInfo;
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@ -127,13 +135,11 @@ fn run_ps(engine_state: &EngineState, call: &Call) -> Result<PipelineData, Shell
|
|||||||
Vec::new(),
|
Vec::new(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let proc_start = match proc_stat.starttime() {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(_) => {
|
|
||||||
// If we can't get the start time, just use the current time
|
// If we can't get the start time, just use the current time
|
||||||
chrono::Local::now()
|
let proc_start = proc_stat
|
||||||
}
|
.starttime()
|
||||||
};
|
.get()
|
||||||
|
.unwrap_or_else(|_| chrono::Local::now());
|
||||||
record.push("start_time", Value::date(proc_start.into(), span));
|
record.push("start_time", Value::date(proc_start.into(), span));
|
||||||
record.push("user_id", Value::int(proc.curr_proc.owner() as i64, span));
|
record.push("user_id", Value::int(proc.curr_proc.owner() as i64, span));
|
||||||
// These work and may be helpful, but it just seemed crowded
|
// These work and may be helpful, but it just seemed crowded
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
|
// todo: (refactoring) limit get_config() usage to 1 call
|
||||||
|
// overall reduce the redundant calls to StyleComputer etc.
|
||||||
|
// the goal is to configure it once...
|
||||||
|
|
||||||
use lscolors::{LsColors, Style};
|
use lscolors::{LsColors, Style};
|
||||||
use nu_color_config::color_from_hex;
|
use nu_color_config::color_from_hex;
|
||||||
use nu_color_config::{StyleComputer, TextStyle};
|
use nu_color_config::{StyleComputer, TextStyle};
|
||||||
use nu_engine::{env::get_config, env_to_string, CallExt};
|
use nu_engine::{env::get_config, env_to_string, CallExt};
|
||||||
use nu_protocol::record;
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Config, DataSource, Example, IntoPipelineData, ListStream, PipelineData,
|
Category, Config, DataSource, Example, IntoPipelineData, ListStream, PipelineData,
|
||||||
PipelineMetadata, RawStream, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
PipelineMetadata, RawStream, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
use nu_protocol::{record, TableMode};
|
||||||
use nu_table::common::create_nu_table_config;
|
use nu_table::common::create_nu_table_config;
|
||||||
use nu_table::{
|
use nu_table::{
|
||||||
CollapsedTable, ExpandedTable, JustTable, NuTable, NuTableCell, StringResult, TableOpts,
|
CollapsedTable, ExpandedTable, JustTable, NuTable, NuTableCell, StringResult, TableOpts,
|
||||||
@ -16,6 +20,7 @@ use nu_table::{
|
|||||||
};
|
};
|
||||||
use nu_utils::get_ls_colors;
|
use nu_utils::get_ls_colors;
|
||||||
use std::io::IsTerminal;
|
use std::io::IsTerminal;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{path::PathBuf, sync::atomic::AtomicBool};
|
use std::{path::PathBuf, sync::atomic::AtomicBool};
|
||||||
@ -60,12 +65,17 @@ impl Command for Table {
|
|||||||
.input_output_types(vec![(Type::Any, Type::Any)])
|
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||||
// TODO: make this more precise: what turns into string and what into raw stream
|
// TODO: make this more precise: what turns into string and what into raw stream
|
||||||
.named(
|
.named(
|
||||||
"start-number",
|
"theme",
|
||||||
SyntaxShape::Int,
|
SyntaxShape::String,
|
||||||
"row number to start viewing from",
|
"set a table mode/theme",
|
||||||
Some('n'),
|
Some('t'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"index",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"enable (true) or disable (false) the #/index column or set the starting index",
|
||||||
|
Some('i'),
|
||||||
)
|
)
|
||||||
.switch("list", "list available table modes/themes", Some('l'))
|
|
||||||
.named(
|
.named(
|
||||||
"width",
|
"width",
|
||||||
SyntaxShape::Int,
|
SyntaxShape::Int,
|
||||||
@ -80,7 +90,7 @@ impl Command for Table {
|
|||||||
.named(
|
.named(
|
||||||
"expand-deep",
|
"expand-deep",
|
||||||
SyntaxShape::Int,
|
SyntaxShape::Int,
|
||||||
"an expand limit of recursion which will take place",
|
"an expand limit of recursion which will take place, must be used with --expand",
|
||||||
Some('d'),
|
Some('d'),
|
||||||
)
|
)
|
||||||
.switch("flatten", "Flatten simple arrays", None)
|
.switch("flatten", "Flatten simple arrays", None)
|
||||||
@ -101,6 +111,7 @@ impl Command for Table {
|
|||||||
"abbreviate the data in the table by truncating the middle part and only showing amount provided on top and bottom",
|
"abbreviate the data in the table by truncating the middle part and only showing amount provided on top and bottom",
|
||||||
Some('a'),
|
Some('a'),
|
||||||
)
|
)
|
||||||
|
.switch("list", "list available table modes/themes", Some('l'))
|
||||||
.category(Category::Viewers)
|
.category(Category::Viewers)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,15 +123,15 @@ impl Command for Table {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let list_themes: bool = call.has_flag("list");
|
let list_themes: bool = call.has_flag("list");
|
||||||
let cfg = parse_table_config(call, engine_state, stack)?;
|
|
||||||
let input = CmdInput::new(engine_state, stack, call, input);
|
|
||||||
|
|
||||||
// if list argument is present we just need to return a list of supported table themes
|
// if list argument is present we just need to return a list of supported table themes
|
||||||
if list_themes {
|
if list_themes {
|
||||||
let val = Value::list(supported_table_modes(), Span::test_data());
|
let val = Value::list(supported_table_modes(), Span::test_data());
|
||||||
return Ok(val.into_pipeline_data());
|
return Ok(val.into_pipeline_data());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cfg = parse_table_config(call, engine_state, stack)?;
|
||||||
|
let input = CmdInput::new(engine_state, stack, call, input);
|
||||||
|
|
||||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
@ -133,8 +144,8 @@ impl Command for Table {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "List the files in current directory, with indexes starting from 1.",
|
description: "List the files in current directory, with indexes starting from 1",
|
||||||
example: r#"ls | table --start-number 1"#,
|
example: r#"ls | table --index 1"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
@ -179,30 +190,54 @@ impl Command for Table {
|
|||||||
}),
|
}),
|
||||||
])),
|
])),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Change the table theme to the specified theme for a single run",
|
||||||
|
example: r#"[[a b]; [1 2] [2 [4 4]]] | table --theme basic"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Force showing of the #/index column for a single run",
|
||||||
|
example: r#"[[a b]; [1 2] [2 [4 4]]] | table -i true"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Set the starting number of the #/index column to 100 for a single run",
|
||||||
|
example: r#"[[a b]; [1 2] [2 [4 4]]] | table -i 100"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Force hiding of the #/index column for a single run",
|
||||||
|
example: r#"[[a b]; [1 2] [2 [4 4]]] | table -i false"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct TableConfig {
|
struct TableConfig {
|
||||||
row_offset: usize,
|
index: Option<usize>,
|
||||||
table_view: TableView,
|
table_view: TableView,
|
||||||
term_width: usize,
|
term_width: usize,
|
||||||
|
theme: TableMode,
|
||||||
abbreviation: Option<usize>,
|
abbreviation: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableConfig {
|
impl TableConfig {
|
||||||
fn new(
|
fn new(
|
||||||
row_offset: usize,
|
|
||||||
table_view: TableView,
|
table_view: TableView,
|
||||||
term_width: usize,
|
term_width: usize,
|
||||||
|
theme: TableMode,
|
||||||
abbreviation: Option<usize>,
|
abbreviation: Option<usize>,
|
||||||
|
index: Option<usize>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
row_offset,
|
index,
|
||||||
table_view,
|
table_view,
|
||||||
term_width,
|
term_width,
|
||||||
abbreviation,
|
abbreviation,
|
||||||
|
theme,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,8 +247,6 @@ fn parse_table_config(
|
|||||||
state: &EngineState,
|
state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
) -> Result<TableConfig, ShellError> {
|
) -> Result<TableConfig, ShellError> {
|
||||||
let start_num: Option<i64> = call.get_flag(state, stack, "start-number")?;
|
|
||||||
let row_offset = start_num.unwrap_or_default() as usize;
|
|
||||||
let width_param: Option<i64> = call.get_flag(state, stack, "width")?;
|
let width_param: Option<i64> = call.get_flag(state, stack, "width")?;
|
||||||
let expand: bool = call.has_flag("expand");
|
let expand: bool = call.has_flag("expand");
|
||||||
let expand_limit: Option<usize> = call.get_flag(state, stack, "expand-deep")?;
|
let expand_limit: Option<usize> = call.get_flag(state, stack, "expand-deep")?;
|
||||||
@ -232,13 +265,74 @@ fn parse_table_config(
|
|||||||
flatten_separator,
|
flatten_separator,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
let theme =
|
||||||
|
get_theme_flag(call, state, stack)?.unwrap_or_else(|| get_config(state, stack).table_mode);
|
||||||
|
let index = get_index_flag(call, state, stack)?;
|
||||||
|
|
||||||
let term_width = get_width_param(width_param);
|
let term_width = get_width_param(width_param);
|
||||||
let cfg = TableConfig::new(row_offset, table_view, term_width, abbrivation);
|
let cfg = TableConfig::new(table_view, term_width, theme, abbrivation, index);
|
||||||
|
|
||||||
Ok(cfg)
|
Ok(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_index_flag(
|
||||||
|
call: &Call,
|
||||||
|
state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
) -> Result<Option<usize>, ShellError> {
|
||||||
|
let index: Option<Value> = call.get_flag(state, stack, "index")?;
|
||||||
|
let value = match index {
|
||||||
|
Some(value) => value,
|
||||||
|
None => return Ok(Some(0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Value::Bool { val, .. } => {
|
||||||
|
if val {
|
||||||
|
Ok(Some(0))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Int { val, internal_span } => {
|
||||||
|
if val < 0 {
|
||||||
|
Err(ShellError::UnsupportedInput {
|
||||||
|
msg: String::from("got a negative integer"),
|
||||||
|
input: val.to_string(),
|
||||||
|
msg_span: call.span(),
|
||||||
|
input_span: internal_span,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(Some(val as usize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Nothing { .. } => Ok(Some(0)),
|
||||||
|
_ => Err(ShellError::CantConvert {
|
||||||
|
to_type: String::from("index"),
|
||||||
|
from_type: String::new(),
|
||||||
|
span: call.span(),
|
||||||
|
help: Some(String::from("supported values: [bool, int, nothing]")),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_theme_flag(
|
||||||
|
call: &Call,
|
||||||
|
state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
) -> Result<Option<TableMode>, ShellError> {
|
||||||
|
call.get_flag(state, stack, "theme")?
|
||||||
|
.map(|theme: String| {
|
||||||
|
TableMode::from_str(&theme).map_err(|err| ShellError::CantConvert {
|
||||||
|
to_type: String::from("theme"),
|
||||||
|
from_type: String::from("string"),
|
||||||
|
span: call.span(),
|
||||||
|
help: Some(format!("{}, but found '{}'.", err, theme)),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
|
||||||
struct CmdInput<'a> {
|
struct CmdInput<'a> {
|
||||||
engine_state: &'a EngineState,
|
engine_state: &'a EngineState,
|
||||||
stack: &'a mut Stack,
|
stack: &'a mut Stack,
|
||||||
@ -366,7 +460,17 @@ fn handle_record(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let indent = (config.table_indent.left, config.table_indent.right);
|
let indent = (config.table_indent.left, config.table_indent.right);
|
||||||
let opts = TableOpts::new(&config, styles, ctrlc, span, 0, cfg.term_width, indent);
|
let opts = TableOpts::new(
|
||||||
|
&config,
|
||||||
|
styles,
|
||||||
|
ctrlc,
|
||||||
|
span,
|
||||||
|
cfg.term_width,
|
||||||
|
indent,
|
||||||
|
cfg.theme,
|
||||||
|
cfg.index.unwrap_or(0),
|
||||||
|
cfg.index.is_none(),
|
||||||
|
);
|
||||||
let result = build_table_kv(record, cfg.table_view, opts, span)?;
|
let result = build_table_kv(record, cfg.table_view, opts, span)?;
|
||||||
|
|
||||||
let result = match result {
|
let result = match result {
|
||||||
@ -581,6 +685,7 @@ struct PagingTableCreator {
|
|||||||
elements_displayed: usize,
|
elements_displayed: usize,
|
||||||
reached_end: bool,
|
reached_end: bool,
|
||||||
cfg: TableConfig,
|
cfg: TableConfig,
|
||||||
|
row_offset: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PagingTableCreator {
|
impl PagingTableCreator {
|
||||||
@ -601,6 +706,7 @@ impl PagingTableCreator {
|
|||||||
cfg,
|
cfg,
|
||||||
elements_displayed: 0,
|
elements_displayed: 0,
|
||||||
reached_end: false,
|
reached_end: false,
|
||||||
|
row_offset: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -657,9 +763,11 @@ impl PagingTableCreator {
|
|||||||
style_comp,
|
style_comp,
|
||||||
self.ctrlc.clone(),
|
self.ctrlc.clone(),
|
||||||
self.head,
|
self.head,
|
||||||
self.cfg.row_offset,
|
|
||||||
self.cfg.term_width,
|
self.cfg.term_width,
|
||||||
(cfg.table_indent.left, cfg.table_indent.right),
|
(cfg.table_indent.left, cfg.table_indent.right),
|
||||||
|
self.cfg.theme,
|
||||||
|
self.cfg.index.unwrap_or(0) + self.row_offset,
|
||||||
|
self.cfg.index.is_none(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -770,7 +878,7 @@ impl Iterator for PagingTableCreator {
|
|||||||
|
|
||||||
let table = self.build_table(batch);
|
let table = self.build_table(batch);
|
||||||
|
|
||||||
self.cfg.row_offset += idx;
|
self.row_offset += idx;
|
||||||
|
|
||||||
let config = get_config(&self.engine_state, &self.stack);
|
let config = get_config(&self.engine_state, &self.stack);
|
||||||
convert_table_to_output(table, &config, &self.ctrlc, self.cfg.term_width)
|
convert_table_to_output(table, &config, &self.ctrlc, self.cfg.term_width)
|
||||||
@ -858,7 +966,7 @@ fn create_empty_placeholder(
|
|||||||
let out = TableOutput::new(table, false, false);
|
let out = TableOutput::new(table, false, false);
|
||||||
|
|
||||||
let style_computer = &StyleComputer::from_config(engine_state, stack);
|
let style_computer = &StyleComputer::from_config(engine_state, stack);
|
||||||
let config = create_nu_table_config(&config, style_computer, &out, false);
|
let config = create_nu_table_config(&config, style_computer, &out, false, TableMode::default());
|
||||||
|
|
||||||
out.table
|
out.table
|
||||||
.draw(config, termwidth)
|
.draw(config, termwidth)
|
||||||
|
@ -269,10 +269,10 @@ fn cd_permission_denied_folder() {
|
|||||||
sandbox.mkdir("banned");
|
sandbox.mkdir("banned");
|
||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: dirs.test(),
|
cwd: dirs.test(),
|
||||||
r#"
|
r"
|
||||||
icacls banned /deny BUILTIN\Administrators:F
|
icacls banned /deny BUILTIN\Administrators:F
|
||||||
cd banned
|
cd banned
|
||||||
"#
|
"
|
||||||
);
|
);
|
||||||
assert!(actual.err.contains("Folder is not able to read"));
|
assert!(actual.err.contains("Folder is not able to read"));
|
||||||
});
|
});
|
||||||
|
@ -87,3 +87,17 @@ fn lazy_record_test_values() {
|
|||||||
);
|
);
|
||||||
assert_eq!(actual.out, "3");
|
assert_eq!(actual.out, "3");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deep_cell_path_creates_all_nested_records() {
|
||||||
|
let actual = nu!(r#"{a: {}} | insert a.b.c 0 | get a.b.c"#);
|
||||||
|
assert_eq!(actual.out, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inserts_all_rows_in_table_in_record() {
|
||||||
|
let actual = nu!(
|
||||||
|
r#"{table: [[col]; [{a: 1}], [{a: 1}]]} | insert table.col.b 2 | get table.col.b | to nuon"#
|
||||||
|
);
|
||||||
|
assert_eq!(actual.out, "[2, 2]");
|
||||||
|
}
|
||||||
|
45
crates/nu-command/tests/commands/mktemp.rs
Normal file
45
crates/nu-command/tests/commands/mktemp.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use nu_test_support::nu;
|
||||||
|
use nu_test_support::playground::Playground;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn creates_temp_file() {
|
||||||
|
Playground::setup("mktemp_test_1", |dirs, _| {
|
||||||
|
let output = nu!(
|
||||||
|
cwd: dirs.test(),
|
||||||
|
"mktemp"
|
||||||
|
);
|
||||||
|
let loc = PathBuf::from(output.out.clone());
|
||||||
|
println!("{:?}", loc);
|
||||||
|
assert!(loc.exists());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn creates_temp_file_with_suffix() {
|
||||||
|
Playground::setup("mktemp_test_2", |dirs, _| {
|
||||||
|
let output = nu!(
|
||||||
|
cwd: dirs.test(),
|
||||||
|
"mktemp --suffix .txt tempfileXXX"
|
||||||
|
);
|
||||||
|
let loc = PathBuf::from(output.out.clone());
|
||||||
|
assert!(loc.exists());
|
||||||
|
assert!(loc.is_file());
|
||||||
|
assert!(output.out.ends_with(".txt"));
|
||||||
|
assert!(output.out.starts_with(dirs.test().to_str().unwrap()));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn creates_temp_directory() {
|
||||||
|
Playground::setup("mktemp_test_3", |dirs, _| {
|
||||||
|
let output = nu!(
|
||||||
|
cwd: dirs.test(),
|
||||||
|
"mktemp -d"
|
||||||
|
);
|
||||||
|
|
||||||
|
let loc = PathBuf::from(output.out);
|
||||||
|
assert!(loc.exists());
|
||||||
|
assert!(loc.is_dir());
|
||||||
|
})
|
||||||
|
}
|
@ -57,6 +57,7 @@ mod match_;
|
|||||||
mod math;
|
mod math;
|
||||||
mod merge;
|
mod merge;
|
||||||
mod mkdir;
|
mod mkdir;
|
||||||
|
mod mktemp;
|
||||||
mod move_;
|
mod move_;
|
||||||
mod mut_;
|
mod mut_;
|
||||||
mod network;
|
mod network;
|
||||||
|
@ -5,11 +5,11 @@ use nu_test_support::{nu, pipeline};
|
|||||||
fn parses_single_path_prefix() {
|
fn parses_single_path_prefix() {
|
||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: "tests", pipeline(
|
cwd: "tests", pipeline(
|
||||||
r#"
|
r"
|
||||||
echo 'C:\users\viking\spam.txt'
|
echo 'C:\users\viking\spam.txt'
|
||||||
| path parse
|
| path parse
|
||||||
| get prefix
|
| get prefix
|
||||||
"#
|
"
|
||||||
));
|
));
|
||||||
|
|
||||||
assert_eq!(actual.out, "C:");
|
assert_eq!(actual.out, "C:");
|
||||||
|
@ -3,7 +3,7 @@ use nu_test_support::nu;
|
|||||||
#[test]
|
#[test]
|
||||||
fn generates_chars_of_specified_length() {
|
fn generates_chars_of_specified_length() {
|
||||||
let actual = nu!(r#"
|
let actual = nu!(r#"
|
||||||
random chars --length 15 | size | get chars
|
random chars --length 15 | str stats | get chars
|
||||||
"#);
|
"#);
|
||||||
|
|
||||||
let result = actual.out;
|
let result = actual.out;
|
||||||
|
@ -21,7 +21,7 @@ fn redirect_err() {
|
|||||||
Playground::setup("redirect_err_test", |dirs, _sandbox| {
|
Playground::setup("redirect_err_test", |dirs, _sandbox| {
|
||||||
let output = nu!(
|
let output = nu!(
|
||||||
cwd: dirs.test(),
|
cwd: dirs.test(),
|
||||||
"vol missingdrive err> a; (open a | size).bytes >= 16"
|
"vol missingdrive err> a; (open a | str stats).bytes >= 16"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(output.out.contains("true"));
|
assert!(output.out.contains("true"));
|
||||||
@ -50,7 +50,7 @@ fn redirect_outerr() {
|
|||||||
cwd: dirs.test(),
|
cwd: dirs.test(),
|
||||||
"vol missingdrive out+err> a"
|
"vol missingdrive out+err> a"
|
||||||
);
|
);
|
||||||
let output = nu!(cwd: dirs.test(), "(open a | size).bytes >= 16");
|
let output = nu!(cwd: dirs.test(), "(open a | str stats).bytes >= 16");
|
||||||
|
|
||||||
assert!(output.out.contains("true"));
|
assert!(output.out.contains("true"));
|
||||||
})
|
})
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1,102 +0,0 @@
|
|||||||
use nu_test_support::{nu, pipeline};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unfold_no_next_break() {
|
|
||||||
let actual =
|
|
||||||
nu!("unfold 1 {|x| if $x == 3 { {out: $x}} else { {out: $x, next: ($x + 1)} }} | to nuon");
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "[1, 2, 3]");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unfold_null_break() {
|
|
||||||
let actual = nu!("unfold 1 {|x| if $x <= 3 { {out: $x, next: ($x + 1)} }} | to nuon");
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "[1, 2, 3]");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unfold_allows_empty_output() {
|
|
||||||
let actual = nu!(pipeline(
|
|
||||||
r#"
|
|
||||||
unfold 0 {|x|
|
|
||||||
if $x == 1 {
|
|
||||||
{next: ($x + 1)}
|
|
||||||
} else if $x < 3 {
|
|
||||||
{out: $x, next: ($x + 1)}
|
|
||||||
}
|
|
||||||
} | to nuon
|
|
||||||
"#
|
|
||||||
));
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "[0, 2]");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unfold_allows_no_output() {
|
|
||||||
let actual = nu!(pipeline(
|
|
||||||
r#"
|
|
||||||
unfold 0 {|x|
|
|
||||||
if $x < 3 {
|
|
||||||
{next: ($x + 1)}
|
|
||||||
}
|
|
||||||
} | to nuon
|
|
||||||
"#
|
|
||||||
));
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "[]");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unfold_allows_null_state() {
|
|
||||||
let actual = nu!(pipeline(
|
|
||||||
r#"
|
|
||||||
unfold 0 {|x|
|
|
||||||
if $x == null {
|
|
||||||
{out: "done"}
|
|
||||||
} else if $x < 1 {
|
|
||||||
{out: "going", next: ($x + 1)}
|
|
||||||
} else {
|
|
||||||
{out: "stopping", next: null}
|
|
||||||
}
|
|
||||||
} | to nuon
|
|
||||||
"#
|
|
||||||
));
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "[going, stopping, done]");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unfold_allows_null_output() {
|
|
||||||
let actual = nu!(pipeline(
|
|
||||||
r#"
|
|
||||||
unfold 0 {|x|
|
|
||||||
if $x == 3 {
|
|
||||||
{out: "done"}
|
|
||||||
} else {
|
|
||||||
{out: null, next: ($x + 1)}
|
|
||||||
}
|
|
||||||
} | to nuon
|
|
||||||
"#
|
|
||||||
));
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "[null, null, null, done]");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unfold_disallows_extra_keys() {
|
|
||||||
let actual = nu!("unfold 0 {|x| {foo: bar, out: $x}}");
|
|
||||||
assert!(actual.err.contains("Invalid block return"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unfold_disallows_list() {
|
|
||||||
let actual = nu!("unfold 0 {|x| [$x, ($x + 1)]}");
|
|
||||||
assert!(actual.err.contains("Invalid block return"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unfold_disallows_primitive() {
|
|
||||||
let actual = nu!("unfold 0 {|x| 1}");
|
|
||||||
assert!(actual.err.contains("Invalid block return"));
|
|
||||||
}
|
|
@ -90,3 +90,17 @@ fn upsert_support_lazy_record() {
|
|||||||
nu!(r#"let x = (lazy make -c ["h"] -g {|a| $a | str upcase}); $x | upsert aa 10 | get aa"#);
|
nu!(r#"let x = (lazy make -c ["h"] -g {|a| $a | str upcase}); $x | upsert aa 10 | get aa"#);
|
||||||
assert_eq!(actual.out, "10");
|
assert_eq!(actual.out, "10");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deep_cell_path_creates_all_nested_records() {
|
||||||
|
let actual = nu!(r#"{a: {}} | insert a.b.c 0 | get a.b.c"#);
|
||||||
|
assert_eq!(actual.out, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn upserts_all_rows_in_table_in_record() {
|
||||||
|
let actual = nu!(
|
||||||
|
r#"{table: [[col]; [{a: 1}], [{a: 1}]]} | insert table.col.b 2 | get table.col.b | to nuon"#
|
||||||
|
);
|
||||||
|
assert_eq!(actual.out, "[2, 2]");
|
||||||
|
}
|
||||||
|
@ -183,6 +183,7 @@ fn from_csv_text_with_tab_separator_to_table() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[allow(clippy::needless_raw_string_hashes)]
|
||||||
fn from_csv_text_with_comments_to_table() {
|
fn from_csv_text_with_comments_to_table() {
|
||||||
Playground::setup("filter_from_csv_test_5", |dirs, sandbox| {
|
Playground::setup("filter_from_csv_test_5", |dirs, sandbox| {
|
||||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
@ -106,6 +106,7 @@ fn from_tsv_text_to_table() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[allow(clippy::needless_raw_string_hashes)]
|
||||||
fn from_tsv_text_with_comments_to_table() {
|
fn from_tsv_text_with_comments_to_table() {
|
||||||
Playground::setup("filter_from_tsv_test_2", |dirs, sandbox| {
|
Playground::setup("filter_from_tsv_test_2", |dirs, sandbox| {
|
||||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-engine"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-engine"
|
name = "nu-engine"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.87.1" }
|
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.87.2" }
|
||||||
nu-path = { path = "../nu-path", version = "0.87.1" }
|
nu-path = { path = "../nu-path", version = "0.87.2" }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.87.1" }
|
nu-glob = { path = "../nu-glob", version = "0.87.2" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.87.1" }
|
nu-utils = { path = "../nu-utils", version = "0.87.2" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = []
|
plugin = []
|
||||||
|
@ -795,10 +795,10 @@ fn eval_element_with_input(
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::CommandNotFound(*span))
|
Err(ShellError::CommandNotFound { span: *span })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Err(ShellError::CommandNotFound(*span)),
|
_ => Err(ShellError::CommandNotFound { span: *span }),
|
||||||
},
|
},
|
||||||
PipelineElement::SeparateRedirection {
|
PipelineElement::SeparateRedirection {
|
||||||
out: (out_span, out_expr),
|
out: (out_span, out_expr),
|
||||||
@ -842,14 +842,14 @@ fn eval_element_with_input(
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::CommandNotFound(*out_span))
|
Err(ShellError::CommandNotFound { span: *out_span })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(_out_other, err_other) => {
|
(_out_other, err_other) => {
|
||||||
if let Expr::String(_) = err_other {
|
if let Expr::String(_) = err_other {
|
||||||
Err(ShellError::CommandNotFound(*out_span))
|
Err(ShellError::CommandNotFound { span: *out_span })
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::CommandNotFound(*err_span))
|
Err(ShellError::CommandNotFound { span: *err_span })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -5,20 +5,20 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-explore"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-explore"
|
name = "nu-explore"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-ansi-term = "0.49.0"
|
nu-ansi-term = "0.49.0"
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.87.1" }
|
nu-parser = { path = "../nu-parser", version = "0.87.2" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.87.1" }
|
nu-color-config = { path = "../nu-color-config", version = "0.87.2" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.87.1" }
|
nu-engine = { path = "../nu-engine", version = "0.87.2" }
|
||||||
nu-table = { path = "../nu-table", version = "0.87.1" }
|
nu-table = { path = "../nu-table", version = "0.87.2" }
|
||||||
nu-json = { path = "../nu-json", version = "0.87.1" }
|
nu-json = { path = "../nu-json", version = "0.87.2" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.87.1" }
|
nu-utils = { path = "../nu-utils", version = "0.87.2" }
|
||||||
|
|
||||||
terminal_size = "0.2"
|
terminal_size = "0.2"
|
||||||
strip-ansi-escapes = "0.2.0"
|
strip-ansi-escapes = "0.2.0"
|
||||||
|
@ -15,7 +15,7 @@ pub use nu_protocol::{Config as NuConfig, Span as NuSpan};
|
|||||||
pub type NuText = (String, TextStyle);
|
pub type NuText = (String, TextStyle);
|
||||||
pub type CtrlC = Option<Arc<AtomicBool>>;
|
pub type CtrlC = Option<Arc<AtomicBool>>;
|
||||||
|
|
||||||
pub use command::{is_ignored_command, run_command_with_value, run_nu_command};
|
pub use command::run_command_with_value;
|
||||||
pub use lscolor::{create_lscolors, lscolorize};
|
pub use lscolor::{create_lscolors, lscolorize};
|
||||||
pub use string::{string_width, truncate_str};
|
pub use string::{string_width, truncate_str};
|
||||||
pub use table::try_build_table;
|
pub use table::try_build_table;
|
||||||
|
@ -38,9 +38,11 @@ fn try_build_map(
|
|||||||
style_computer,
|
style_computer,
|
||||||
ctrlc,
|
ctrlc,
|
||||||
Span::unknown(),
|
Span::unknown(),
|
||||||
0,
|
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
(config.table_indent.left, config.table_indent.right),
|
(config.table_indent.left, config.table_indent.right),
|
||||||
|
config.table_mode,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
let result = ExpandedTable::new(None, false, String::new()).build_map(&record, opts);
|
let result = ExpandedTable::new(None, false, String::new()).build_map(&record, opts);
|
||||||
match result {
|
match result {
|
||||||
@ -63,10 +65,13 @@ fn try_build_list(
|
|||||||
style_computer,
|
style_computer,
|
||||||
ctrlc,
|
ctrlc,
|
||||||
Span::unknown(),
|
Span::unknown(),
|
||||||
0,
|
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
(config.table_indent.left, config.table_indent.right),
|
(config.table_indent.left, config.table_indent.right),
|
||||||
|
config.table_mode,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
let result = ExpandedTable::new(None, false, String::new()).build_list(&vals, opts);
|
let result = ExpandedTable::new(None, false, String::new()).build_list(&vals, opts);
|
||||||
match result {
|
match result {
|
||||||
Ok(Some(out)) => out,
|
Ok(Some(out)) => out,
|
||||||
|
@ -721,7 +721,7 @@ fn build_table_as_record(v: &RecordView) -> Value {
|
|||||||
let layer = v.get_layer_last();
|
let layer = v.get_layer_last();
|
||||||
|
|
||||||
let cols = layer.columns.to_vec();
|
let cols = layer.columns.to_vec();
|
||||||
let vals = layer.records.get(0).map_or(Vec::new(), |row| row.clone());
|
let vals = layer.records.first().map_or(Vec::new(), |row| row.clone());
|
||||||
|
|
||||||
Value::record(Record::from_raw_cols_vals(cols, vals), NuSpan::unknown())
|
Value::record(Record::from_raw_cols_vals(cols, vals), NuSpan::unknown())
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nu-glob"
|
name = "nu-glob"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
authors = ["The Nushell Project Developers", "The Rust Project Developers"]
|
authors = ["The Nushell Project Developers", "The Rust Project Developers"]
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
description = """
|
description = """
|
||||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-json"
|
name = "nu-json"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -22,5 +22,5 @@ num-traits = "0.2"
|
|||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# nu-path = { path="../nu-path", version = "0.87.1" }
|
# nu-path = { path="../nu-path", version = "0.87.2" }
|
||||||
# serde_json = "1.0"
|
# serde_json = "1.0"
|
||||||
|
@ -63,7 +63,7 @@ fn main() {
|
|||||||
|
|
||||||
// Extract the array
|
// Extract the array
|
||||||
let array : &mut Vec<Value> = sample.get_mut("array").unwrap().as_array_mut().unwrap();
|
let array : &mut Vec<Value> = sample.get_mut("array").unwrap().as_array_mut().unwrap();
|
||||||
println!("first: {}", array.get(0).unwrap());
|
println!("first: {}", array.first().unwrap());
|
||||||
|
|
||||||
// Add a value
|
// Add a value
|
||||||
array.push(Value::String("tak".to_string()));
|
array.push(Value::String("tak".to_string()));
|
||||||
|
@ -3,28 +3,29 @@ authors = ["The Nushell Project Developers"]
|
|||||||
description = "Nushell's integrated LSP server"
|
description = "Nushell's integrated LSP server"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-lsp"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-lsp"
|
||||||
name = "nu-lsp"
|
name = "nu-lsp"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cli = { path = "../nu-cli", version = "0.87.1" }
|
nu-cli = { path = "../nu-cli", version = "0.87.2" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.87.1" }
|
nu-parser = { path = "../nu-parser", version = "0.87.2" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
|
||||||
|
|
||||||
reedline = { version = "0.26" }
|
reedline = { version = "0.26" }
|
||||||
|
|
||||||
|
crossbeam-channel = "0.5.8"
|
||||||
lsp-types = "0.94.1"
|
lsp-types = "0.94.1"
|
||||||
lsp-server = "0.7.4"
|
lsp-server = { version = "0.7.4", git = "https://github.com/schrieveslaach/rust-analyzer.git", branch = "cancelable-initialization" }
|
||||||
miette = "5.10"
|
miette = "5.10"
|
||||||
ropey = "1.6.1"
|
ropey = "1.6.1"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.2" }
|
||||||
nu-command = { path = "../nu-command", version = "0.87.1" }
|
nu-command = { path = "../nu-command", version = "0.87.2" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.87.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.87.2" }
|
||||||
|
|
||||||
assert-json-diff = "2.0"
|
assert-json-diff = "2.0"
|
||||||
tempfile = "3.2"
|
tempfile = "3.2"
|
||||||
|
147
crates/nu-lsp/src/diagnostics.rs
Normal file
147
crates/nu-lsp/src/diagnostics.rs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
use lsp_types::{
|
||||||
|
notification::{Notification, PublishDiagnostics},
|
||||||
|
Diagnostic, DiagnosticSeverity, PublishDiagnosticsParams, Url,
|
||||||
|
};
|
||||||
|
use miette::{IntoDiagnostic, Result};
|
||||||
|
use nu_parser::parse;
|
||||||
|
use nu_protocol::{
|
||||||
|
engine::{EngineState, StateWorkingSet},
|
||||||
|
eval_const::create_nu_constant,
|
||||||
|
Span, Value, NU_VARIABLE_ID,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::LanguageServer;
|
||||||
|
|
||||||
|
impl LanguageServer {
|
||||||
|
pub(crate) fn publish_diagnostics_for_file(
|
||||||
|
&self,
|
||||||
|
uri: Url,
|
||||||
|
engine_state: &mut EngineState,
|
||||||
|
) -> Result<()> {
|
||||||
|
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||||
|
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
|
||||||
|
|
||||||
|
let Ok(nu_const) = create_nu_constant(engine_state, Span::unknown()) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
engine_state.set_variable_const_val(NU_VARIABLE_ID, nu_const);
|
||||||
|
|
||||||
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
let Some((rope_of_file, file_path)) = self.rope(&uri) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let contents = rope_of_file.bytes().collect::<Vec<u8>>();
|
||||||
|
let offset = working_set.next_span_start();
|
||||||
|
parse(
|
||||||
|
&mut working_set,
|
||||||
|
Some(&file_path.to_string_lossy()),
|
||||||
|
&contents,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut diagnostics = PublishDiagnosticsParams {
|
||||||
|
uri,
|
||||||
|
diagnostics: Vec::new(),
|
||||||
|
version: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
for err in working_set.parse_errors.iter() {
|
||||||
|
let message = err.to_string();
|
||||||
|
|
||||||
|
diagnostics.diagnostics.push(Diagnostic {
|
||||||
|
range: Self::span_to_range(&err.span(), rope_of_file, offset),
|
||||||
|
severity: Some(DiagnosticSeverity::ERROR),
|
||||||
|
message,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.connection
|
||||||
|
.sender
|
||||||
|
.send(lsp_server::Message::Notification(
|
||||||
|
lsp_server::Notification::new(PublishDiagnostics::METHOD.to_string(), diagnostics),
|
||||||
|
))
|
||||||
|
.into_diagnostic()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use assert_json_diff::assert_json_eq;
|
||||||
|
use lsp_types::Url;
|
||||||
|
use nu_test_support::fs::fixtures;
|
||||||
|
|
||||||
|
use crate::tests::{initialize_language_server, open, update};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn publish_diagnostics_variable_does_not_exists() {
|
||||||
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
|
let mut script = fixtures();
|
||||||
|
script.push("lsp");
|
||||||
|
script.push("diagnostics");
|
||||||
|
script.push("var.nu");
|
||||||
|
let script = Url::from_file_path(script).unwrap();
|
||||||
|
|
||||||
|
let notification = open(&client_connection, script.clone());
|
||||||
|
|
||||||
|
assert_json_eq!(
|
||||||
|
notification,
|
||||||
|
serde_json::json!({
|
||||||
|
"method": "textDocument/publishDiagnostics",
|
||||||
|
"params": {
|
||||||
|
"uri": script,
|
||||||
|
"diagnostics": [{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 6 },
|
||||||
|
"end": { "line": 0, "character": 30 }
|
||||||
|
},
|
||||||
|
"message": "Variable not found.",
|
||||||
|
"severity": 1
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn publish_diagnostics_fixed_unknown_variable() {
|
||||||
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
|
let mut script = fixtures();
|
||||||
|
script.push("lsp");
|
||||||
|
script.push("diagnostics");
|
||||||
|
script.push("var.nu");
|
||||||
|
let script = Url::from_file_path(script).unwrap();
|
||||||
|
|
||||||
|
open(&client_connection, script.clone());
|
||||||
|
let notification = update(
|
||||||
|
&client_connection,
|
||||||
|
script.clone(),
|
||||||
|
String::from("$env"),
|
||||||
|
Some(lsp_types::Range {
|
||||||
|
start: lsp_types::Position {
|
||||||
|
line: 0,
|
||||||
|
character: 6,
|
||||||
|
},
|
||||||
|
end: lsp_types::Position {
|
||||||
|
line: 0,
|
||||||
|
character: 30,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_json_eq!(
|
||||||
|
notification,
|
||||||
|
serde_json::json!({
|
||||||
|
"method": "textDocument/publishDiagnostics",
|
||||||
|
"params": {
|
||||||
|
"uri": script,
|
||||||
|
"diagnostics": []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,19 @@
|
|||||||
use std::{fs::File, io::Cursor, sync::Arc};
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use lsp_server::{Connection, IoThreads, Message, Response, ResponseError};
|
use lsp_server::{Connection, IoThreads, Message, Response, ResponseError};
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
request::{Completion, GotoDefinition, HoverRequest, Request},
|
request::{Completion, GotoDefinition, HoverRequest, Request},
|
||||||
CompletionItem, CompletionParams, CompletionResponse, CompletionTextEdit, GotoDefinitionParams,
|
CompletionItem, CompletionParams, CompletionResponse, CompletionTextEdit, GotoDefinitionParams,
|
||||||
GotoDefinitionResponse, Hover, HoverContents, HoverParams, Location, MarkupContent, MarkupKind,
|
GotoDefinitionResponse, Hover, HoverContents, HoverParams, Location, MarkupContent, MarkupKind,
|
||||||
OneOf, Range, ServerCapabilities, TextEdit, Url,
|
OneOf, Range, ServerCapabilities, TextDocumentSyncKind, TextEdit, Url,
|
||||||
};
|
};
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use nu_cli::NuCompleter;
|
use nu_cli::NuCompleter;
|
||||||
@ -17,6 +25,9 @@ use nu_protocol::{
|
|||||||
use reedline::Completer;
|
use reedline::Completer;
|
||||||
use ropey::Rope;
|
use ropey::Rope;
|
||||||
|
|
||||||
|
mod diagnostics;
|
||||||
|
mod notification;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Id {
|
enum Id {
|
||||||
Variable(VarId),
|
Variable(VarId),
|
||||||
@ -27,6 +38,7 @@ enum Id {
|
|||||||
pub struct LanguageServer {
|
pub struct LanguageServer {
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
io_threads: Option<IoThreads>,
|
io_threads: Option<IoThreads>,
|
||||||
|
ropes: BTreeMap<PathBuf, Rope>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageServer {
|
impl LanguageServer {
|
||||||
@ -42,11 +54,19 @@ impl LanguageServer {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
connection,
|
connection,
|
||||||
io_threads,
|
io_threads,
|
||||||
|
ropes: BTreeMap::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serve_requests(self, engine_state: EngineState) -> Result<()> {
|
pub fn serve_requests(
|
||||||
let server_capabilities = serde_json::to_value(&ServerCapabilities {
|
mut self,
|
||||||
|
engine_state: EngineState,
|
||||||
|
ctrlc: Arc<AtomicBool>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let server_capabilities = serde_json::to_value(ServerCapabilities {
|
||||||
|
text_document_sync: Some(lsp_types::TextDocumentSyncCapability::Kind(
|
||||||
|
TextDocumentSyncKind::INCREMENTAL,
|
||||||
|
)),
|
||||||
definition_provider: Some(OneOf::Left(true)),
|
definition_provider: Some(OneOf::Left(true)),
|
||||||
hover_provider: Some(lsp_types::HoverProviderCapability::Simple(true)),
|
hover_provider: Some(lsp_types::HoverProviderCapability::Simple(true)),
|
||||||
completion_provider: Some(lsp_types::CompletionOptions::default()),
|
completion_provider: Some(lsp_types::CompletionOptions::default()),
|
||||||
@ -56,10 +76,22 @@ impl LanguageServer {
|
|||||||
|
|
||||||
let _initialization_params = self
|
let _initialization_params = self
|
||||||
.connection
|
.connection
|
||||||
.initialize(server_capabilities)
|
.initialize_while(server_capabilities, || !ctrlc.load(Ordering::SeqCst))
|
||||||
.into_diagnostic()?;
|
.into_diagnostic()?;
|
||||||
|
|
||||||
for msg in &self.connection.receiver {
|
while !ctrlc.load(Ordering::SeqCst) {
|
||||||
|
let msg = match self
|
||||||
|
.connection
|
||||||
|
.receiver
|
||||||
|
.recv_timeout(Duration::from_secs(1))
|
||||||
|
{
|
||||||
|
Ok(msg) => msg,
|
||||||
|
Err(crossbeam_channel::RecvTimeoutError::Timeout) => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(_) => break,
|
||||||
|
};
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
Message::Request(request) => {
|
Message::Request(request) => {
|
||||||
if self
|
if self
|
||||||
@ -71,25 +103,39 @@ impl LanguageServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut engine_state = engine_state.clone();
|
let mut engine_state = engine_state.clone();
|
||||||
match request.method.as_str() {
|
let resp = match request.method.as_str() {
|
||||||
GotoDefinition::METHOD => {
|
GotoDefinition::METHOD => Self::handle_lsp_request(
|
||||||
self.handle_lsp_request(
|
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
request,
|
request,
|
||||||
Self::goto_definition,
|
|engine_state, params| self.goto_definition(engine_state, params),
|
||||||
)?;
|
),
|
||||||
}
|
HoverRequest::METHOD => Self::handle_lsp_request(
|
||||||
HoverRequest::METHOD => {
|
&mut engine_state,
|
||||||
self.handle_lsp_request(&mut engine_state, request, Self::hover)?;
|
request,
|
||||||
}
|
|engine_state, params| self.hover(engine_state, params),
|
||||||
Completion::METHOD => {
|
),
|
||||||
self.handle_lsp_request(&mut engine_state, request, Self::complete)?;
|
Completion::METHOD => Self::handle_lsp_request(
|
||||||
}
|
&mut engine_state,
|
||||||
_ => {}
|
request,
|
||||||
|
|engine_state, params| self.complete(engine_state, params),
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.connection
|
||||||
|
.sender
|
||||||
|
.send(Message::Response(resp))
|
||||||
|
.into_diagnostic()?;
|
||||||
}
|
}
|
||||||
Message::Response(_) => {}
|
Message::Response(_) => {}
|
||||||
Message::Notification(_) => {}
|
Message::Notification(notification) => {
|
||||||
|
if let Some(updated_file) = self.handle_lsp_notification(notification) {
|
||||||
|
let mut engine_state = engine_state.clone();
|
||||||
|
self.publish_diagnostics_for_file(updated_file, &mut engine_state)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,22 +147,23 @@ impl LanguageServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_lsp_request<P, H, R>(
|
fn handle_lsp_request<P, H, R>(
|
||||||
&self,
|
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
req: lsp_server::Request,
|
req: lsp_server::Request,
|
||||||
param_handler: H,
|
mut param_handler: H,
|
||||||
) -> Result<()>
|
) -> Response
|
||||||
where
|
where
|
||||||
P: serde::de::DeserializeOwned,
|
P: serde::de::DeserializeOwned,
|
||||||
H: Fn(&mut EngineState, &P) -> Option<R>,
|
H: FnMut(&mut EngineState, &P) -> Option<R>,
|
||||||
R: serde::ser::Serialize,
|
R: serde::ser::Serialize,
|
||||||
{
|
{
|
||||||
let resp = {
|
|
||||||
match serde_json::from_value::<P>(req.params) {
|
match serde_json::from_value::<P>(req.params) {
|
||||||
Ok(params) => Response {
|
Ok(params) => Response {
|
||||||
id: req.id,
|
id: req.id,
|
||||||
result: param_handler(engine_state, ¶ms)
|
result: Some(
|
||||||
.and_then(|response| serde_json::to_value(response).ok()),
|
param_handler(engine_state, ¶ms)
|
||||||
|
.and_then(|response| serde_json::to_value(response).ok())
|
||||||
|
.unwrap_or(serde_json::Value::Null),
|
||||||
|
),
|
||||||
error: None,
|
error: None,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -130,12 +177,6 @@ impl LanguageServer {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
self.connection
|
|
||||||
.sender
|
|
||||||
.send(Message::Response(resp))
|
|
||||||
.into_diagnostic()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn span_to_range(span: &Span, rope_of_file: &Rope, offset: usize) -> lsp_types::Range {
|
fn span_to_range(span: &Span, rope_of_file: &Rope, offset: usize) -> lsp_types::Range {
|
||||||
@ -158,79 +199,84 @@ impl LanguageServer {
|
|||||||
lsp_types::Range { start, end }
|
lsp_types::Range { start, end }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lsp_position_to_location(position: &lsp_types::Position, rope_of_file: &Rope) -> usize {
|
pub fn lsp_position_to_location(position: &lsp_types::Position, rope_of_file: &Rope) -> usize {
|
||||||
let line_idx = rope_of_file.line_to_char(position.line as usize);
|
let line_idx = rope_of_file.line_to_char(position.line as usize);
|
||||||
line_idx + position.character as usize
|
line_idx + position.character as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_id(
|
fn find_id(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
file_path: &str,
|
path: &Path,
|
||||||
file: &[u8],
|
file: &Rope,
|
||||||
location: usize,
|
location: usize,
|
||||||
) -> Option<(Id, usize, Span)> {
|
) -> Option<(Id, usize, Span)> {
|
||||||
let file_id = working_set.add_file(file_path.to_string(), file);
|
let file_path = path.to_string_lossy();
|
||||||
let offset = working_set.get_span_for_file(file_id).start;
|
|
||||||
let block = parse(working_set, Some(file_path), file, false);
|
// TODO: think about passing down the rope into the working_set
|
||||||
|
let contents = file.bytes().collect::<Vec<u8>>();
|
||||||
|
let block = parse(working_set, Some(&file_path), &contents, false);
|
||||||
let flattened = flatten_block(working_set, &block);
|
let flattened = flatten_block(working_set, &block);
|
||||||
|
|
||||||
|
let offset = working_set.get_span_for_filename(&file_path)?.start;
|
||||||
let location = location + offset;
|
let location = location + offset;
|
||||||
for item in flattened {
|
|
||||||
if location >= item.0.start && location < item.0.end {
|
for (span, shape) in flattened {
|
||||||
match &item.1 {
|
if location >= span.start && location < span.end {
|
||||||
|
match &shape {
|
||||||
FlatShape::Variable(var_id) | FlatShape::VarDecl(var_id) => {
|
FlatShape::Variable(var_id) | FlatShape::VarDecl(var_id) => {
|
||||||
return Some((Id::Variable(*var_id), offset, item.0));
|
return Some((Id::Variable(*var_id), offset, span));
|
||||||
}
|
}
|
||||||
FlatShape::InternalCall(decl_id) => {
|
FlatShape::InternalCall(decl_id) => {
|
||||||
return Some((Id::Declaration(*decl_id), offset, item.0));
|
return Some((Id::Declaration(*decl_id), offset, span));
|
||||||
}
|
}
|
||||||
_ => return Some((Id::Value(item.1), offset, item.0)),
|
_ => return Some((Id::Value(shape), offset, span)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_in_file<'a>(
|
fn rope<'a, 'b: 'a>(&'b self, file_url: &Url) -> Option<(&'a Rope, &'a PathBuf)> {
|
||||||
engine_state: &'a mut EngineState,
|
let file_path = file_url.to_file_path().ok()?;
|
||||||
file_path: &str,
|
|
||||||
) -> Result<(Vec<u8>, StateWorkingSet<'a>)> {
|
|
||||||
let file = std::fs::read(file_path).into_diagnostic()?;
|
|
||||||
|
|
||||||
engine_state.start_in_file(Some(file_path));
|
self.ropes
|
||||||
|
.get_key_value(&file_path)
|
||||||
|
.map(|(path, rope)| (rope, path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_in_file<'a>(
|
||||||
|
&mut self,
|
||||||
|
engine_state: &'a mut EngineState,
|
||||||
|
file_url: &Url,
|
||||||
|
) -> Option<(&Rope, &PathBuf, StateWorkingSet<'a>)> {
|
||||||
|
let (file, path) = self.rope(file_url)?;
|
||||||
|
|
||||||
|
// TODO: AsPath thingy
|
||||||
|
engine_state.start_in_file(Some(&path.to_string_lossy()));
|
||||||
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
Ok((file, working_set))
|
Some((file, path, working_set))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn goto_definition(
|
fn goto_definition(
|
||||||
|
&mut self,
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
params: &GotoDefinitionParams,
|
params: &GotoDefinitionParams,
|
||||||
) -> Option<GotoDefinitionResponse> {
|
) -> Option<GotoDefinitionResponse> {
|
||||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||||
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
|
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
|
||||||
|
|
||||||
let file_path = params
|
let (file, path, mut working_set) = self.read_in_file(
|
||||||
.text_document_position_params
|
engine_state,
|
||||||
.text_document
|
¶ms.text_document_position_params.text_document.uri,
|
||||||
.uri
|
)?;
|
||||||
.to_file_path()
|
|
||||||
.ok()?;
|
|
||||||
|
|
||||||
let file_path = file_path.to_string_lossy();
|
|
||||||
|
|
||||||
let (file, mut working_set) = Self::read_in_file(engine_state, &file_path).ok()?;
|
|
||||||
let rope_of_file = Rope::from_reader(Cursor::new(&file)).ok()?;
|
|
||||||
|
|
||||||
let (id, _, _) = Self::find_id(
|
let (id, _, _) = Self::find_id(
|
||||||
&mut working_set,
|
&mut working_set,
|
||||||
&file_path,
|
path,
|
||||||
&file,
|
file,
|
||||||
Self::lsp_position_to_location(
|
Self::lsp_position_to_location(¶ms.text_document_position_params.position, file),
|
||||||
¶ms.text_document_position_params.position,
|
|
||||||
&rope_of_file,
|
|
||||||
),
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
match id {
|
match id {
|
||||||
@ -242,7 +288,7 @@ impl LanguageServer {
|
|||||||
if span.start >= *file_start && span.start < *file_end {
|
if span.start >= *file_start && span.start < *file_end {
|
||||||
return Some(GotoDefinitionResponse::Scalar(Location {
|
return Some(GotoDefinitionResponse::Scalar(Location {
|
||||||
uri: Url::from_file_path(file_path).ok()?,
|
uri: Url::from_file_path(file_path).ok()?,
|
||||||
range: Self::span_to_range(span, &rope_of_file, *file_start),
|
range: Self::span_to_range(span, file, *file_start),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -261,11 +307,7 @@ impl LanguageServer {
|
|||||||
.text_document
|
.text_document
|
||||||
.uri
|
.uri
|
||||||
.clone(),
|
.clone(),
|
||||||
range: Self::span_to_range(
|
range: Self::span_to_range(&var.declaration_span, file, *file_start),
|
||||||
&var.declaration_span,
|
|
||||||
&rope_of_file,
|
|
||||||
*file_start,
|
|
||||||
),
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -275,30 +317,20 @@ impl LanguageServer {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hover(engine_state: &mut EngineState, params: &HoverParams) -> Option<Hover> {
|
fn hover(&mut self, engine_state: &mut EngineState, params: &HoverParams) -> Option<Hover> {
|
||||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||||
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
|
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
|
||||||
|
|
||||||
let file_path = params
|
let (file, path, mut working_set) = self.read_in_file(
|
||||||
.text_document_position_params
|
engine_state,
|
||||||
.text_document
|
¶ms.text_document_position_params.text_document.uri,
|
||||||
.uri
|
)?;
|
||||||
.to_file_path()
|
|
||||||
.ok()?;
|
|
||||||
|
|
||||||
let file_path = file_path.to_string_lossy();
|
|
||||||
|
|
||||||
let (file, mut working_set) = Self::read_in_file(engine_state, &file_path).ok()?;
|
|
||||||
let rope_of_file = Rope::from_reader(Cursor::new(&file)).ok()?;
|
|
||||||
|
|
||||||
let (id, _, _) = Self::find_id(
|
let (id, _, _) = Self::find_id(
|
||||||
&mut working_set,
|
&mut working_set,
|
||||||
&file_path,
|
path,
|
||||||
&file,
|
file,
|
||||||
Self::lsp_position_to_location(
|
Self::lsp_position_to_location(¶ms.text_document_position_params.position, file),
|
||||||
¶ms.text_document_position_params.position,
|
|
||||||
&rope_of_file,
|
|
||||||
),
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
match id {
|
match id {
|
||||||
@ -489,27 +521,23 @@ impl LanguageServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete(
|
fn complete(
|
||||||
|
&mut self,
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
params: &CompletionParams,
|
params: &CompletionParams,
|
||||||
) -> Option<CompletionResponse> {
|
) -> Option<CompletionResponse> {
|
||||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||||
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
|
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
|
||||||
|
|
||||||
let file_path = params
|
let (rope_of_file, _, _) = self.read_in_file(
|
||||||
.text_document_position
|
engine_state,
|
||||||
.text_document
|
¶ms.text_document_position.text_document.uri,
|
||||||
.uri
|
)?;
|
||||||
.to_file_path()
|
|
||||||
.ok()?;
|
|
||||||
|
|
||||||
let file_path = file_path.to_string_lossy();
|
|
||||||
let rope_of_file = Rope::from_reader(File::open(file_path.as_ref()).ok()?).ok()?;
|
|
||||||
|
|
||||||
let stack = Stack::new();
|
let stack = Stack::new();
|
||||||
let mut completer = NuCompleter::new(Arc::new(engine_state.clone()), stack);
|
let mut completer = NuCompleter::new(Arc::new(engine_state.clone()), stack);
|
||||||
|
|
||||||
let location =
|
let location =
|
||||||
Self::lsp_position_to_location(¶ms.text_document_position.position, &rope_of_file);
|
Self::lsp_position_to_location(¶ms.text_document_position.position, rope_of_file);
|
||||||
let results = completer.complete(&rope_of_file.to_string(), location);
|
let results = completer.complete(&rope_of_file.to_string(), location);
|
||||||
if results.is_empty() {
|
if results.is_empty() {
|
||||||
None
|
None
|
||||||
@ -545,15 +573,18 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use assert_json_diff::assert_json_eq;
|
use assert_json_diff::assert_json_eq;
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
notification::{Exit, Initialized, Notification},
|
notification::{
|
||||||
|
DidChangeTextDocument, DidOpenTextDocument, Exit, Initialized, Notification,
|
||||||
|
},
|
||||||
request::{Completion, GotoDefinition, HoverRequest, Initialize, Request, Shutdown},
|
request::{Completion, GotoDefinition, HoverRequest, Initialize, Request, Shutdown},
|
||||||
CompletionParams, GotoDefinitionParams, InitializeParams, InitializedParams,
|
CompletionParams, DidChangeTextDocumentParams, DidOpenTextDocumentParams,
|
||||||
TextDocumentIdentifier, TextDocumentPositionParams, Url,
|
GotoDefinitionParams, InitializeParams, InitializedParams, TextDocumentContentChangeEvent,
|
||||||
|
TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, Url,
|
||||||
};
|
};
|
||||||
use nu_test_support::fs::{fixtures, root};
|
use nu_test_support::fs::{fixtures, root};
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
|
|
||||||
fn initialize_language_server() -> (Connection, Receiver<Result<()>>) {
|
pub fn initialize_language_server() -> (Connection, Receiver<Result<()>>) {
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
let (client_connection, server_connection) = Connection::memory();
|
let (client_connection, server_connection) = Connection::memory();
|
||||||
let lsp_server = LanguageServer::initialize_connection(server_connection, None).unwrap();
|
let lsp_server = LanguageServer::initialize_connection(server_connection, None).unwrap();
|
||||||
@ -562,7 +593,7 @@ mod tests {
|
|||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let engine_state = nu_cmd_lang::create_default_context();
|
let engine_state = nu_cmd_lang::create_default_context();
|
||||||
let engine_state = nu_command::add_shell_command_context(engine_state);
|
let engine_state = nu_command::add_shell_command_context(engine_state);
|
||||||
send.send(lsp_server.serve_requests(engine_state))
|
send.send(lsp_server.serve_requests(engine_state, Arc::new(AtomicBool::new(false))))
|
||||||
});
|
});
|
||||||
|
|
||||||
client_connection
|
client_connection
|
||||||
@ -651,16 +682,91 @@ mod tests {
|
|||||||
.receiver
|
.receiver
|
||||||
.recv_timeout(std::time::Duration::from_secs(2))
|
.recv_timeout(std::time::Duration::from_secs(2))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let result = if let Message::Response(response) = resp {
|
||||||
|
response.result
|
||||||
|
} else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
|
||||||
assert!(matches!(
|
assert_json_eq!(result, serde_json::json!(null));
|
||||||
resp,
|
|
||||||
Message::Response(response) if response.result.is_none()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn goto_definition(uri: Url, line: u32, character: u32) -> Message {
|
pub fn open(client_connection: &Connection, uri: Url) -> lsp_server::Notification {
|
||||||
let (client_connection, _recv) = initialize_language_server();
|
let text = std::fs::read_to_string(uri.to_file_path().unwrap()).unwrap();
|
||||||
|
|
||||||
|
client_connection
|
||||||
|
.sender
|
||||||
|
.send(Message::Notification(lsp_server::Notification {
|
||||||
|
method: DidOpenTextDocument::METHOD.to_string(),
|
||||||
|
params: serde_json::to_value(DidOpenTextDocumentParams {
|
||||||
|
text_document: TextDocumentItem {
|
||||||
|
uri,
|
||||||
|
language_id: String::from("nu"),
|
||||||
|
version: 1,
|
||||||
|
text,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let notification = client_connection
|
||||||
|
.receiver
|
||||||
|
.recv_timeout(Duration::from_secs(2))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Message::Notification(n) = notification {
|
||||||
|
n
|
||||||
|
} else {
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(
|
||||||
|
client_connection: &Connection,
|
||||||
|
uri: Url,
|
||||||
|
text: String,
|
||||||
|
range: Option<Range>,
|
||||||
|
) -> lsp_server::Notification {
|
||||||
|
client_connection
|
||||||
|
.sender
|
||||||
|
.send(lsp_server::Message::Notification(
|
||||||
|
lsp_server::Notification {
|
||||||
|
method: DidChangeTextDocument::METHOD.to_string(),
|
||||||
|
params: serde_json::to_value(DidChangeTextDocumentParams {
|
||||||
|
text_document: lsp_types::VersionedTextDocumentIdentifier {
|
||||||
|
uri,
|
||||||
|
version: 2,
|
||||||
|
},
|
||||||
|
content_changes: vec![TextDocumentContentChangeEvent {
|
||||||
|
range,
|
||||||
|
range_length: None,
|
||||||
|
text,
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let notification = client_connection
|
||||||
|
.receiver
|
||||||
|
.recv_timeout(Duration::from_secs(2))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Message::Notification(n) = notification {
|
||||||
|
n
|
||||||
|
} else {
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn goto_definition(
|
||||||
|
client_connection: &Connection,
|
||||||
|
uri: Url,
|
||||||
|
line: u32,
|
||||||
|
character: u32,
|
||||||
|
) -> Message {
|
||||||
client_connection
|
client_connection
|
||||||
.sender
|
.sender
|
||||||
.send(Message::Request(lsp_server::Request {
|
.send(Message::Request(lsp_server::Request {
|
||||||
@ -686,13 +792,17 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn goto_definition_of_variable() {
|
fn goto_definition_of_variable() {
|
||||||
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("goto");
|
script.push("goto");
|
||||||
script.push("var.nu");
|
script.push("var.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = Url::from_file_path(script).unwrap();
|
||||||
|
|
||||||
let resp = goto_definition(script.clone(), 2, 12);
|
open(&client_connection, script.clone());
|
||||||
|
|
||||||
|
let resp = goto_definition(&client_connection, script.clone(), 2, 12);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
@ -713,13 +823,17 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn goto_definition_of_command() {
|
fn goto_definition_of_command() {
|
||||||
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("goto");
|
script.push("goto");
|
||||||
script.push("command.nu");
|
script.push("command.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = Url::from_file_path(script).unwrap();
|
||||||
|
|
||||||
let resp = goto_definition(script.clone(), 4, 1);
|
open(&client_connection, script.clone());
|
||||||
|
|
||||||
|
let resp = goto_definition(&client_connection, script.clone(), 4, 1);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
@ -740,13 +854,17 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn goto_definition_of_command_parameter() {
|
fn goto_definition_of_command_parameter() {
|
||||||
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("goto");
|
script.push("goto");
|
||||||
script.push("command.nu");
|
script.push("command.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = Url::from_file_path(script).unwrap();
|
||||||
|
|
||||||
let resp = goto_definition(script.clone(), 1, 14);
|
open(&client_connection, script.clone());
|
||||||
|
|
||||||
|
let resp = goto_definition(&client_connection, script.clone(), 1, 14);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
@ -765,9 +883,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hover(uri: Url, line: u32, character: u32) -> Message {
|
pub fn hover(client_connection: &Connection, uri: Url, line: u32, character: u32) -> Message {
|
||||||
let (client_connection, _recv) = initialize_language_server();
|
|
||||||
|
|
||||||
client_connection
|
client_connection
|
||||||
.sender
|
.sender
|
||||||
.send(Message::Request(lsp_server::Request {
|
.send(Message::Request(lsp_server::Request {
|
||||||
@ -792,13 +908,17 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hover_on_variable() {
|
fn hover_on_variable() {
|
||||||
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("hover");
|
script.push("hover");
|
||||||
script.push("var.nu");
|
script.push("var.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = Url::from_file_path(script).unwrap();
|
||||||
|
|
||||||
let resp = hover(script.clone(), 2, 0);
|
open(&client_connection, script.clone());
|
||||||
|
|
||||||
|
let resp = hover(&client_connection, script.clone(), 2, 0);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
@ -815,13 +935,17 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hover_on_command() {
|
fn hover_on_command() {
|
||||||
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("hover");
|
script.push("hover");
|
||||||
script.push("command.nu");
|
script.push("command.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = Url::from_file_path(script).unwrap();
|
||||||
|
|
||||||
let resp = hover(script.clone(), 3, 0);
|
open(&client_connection, script.clone());
|
||||||
|
|
||||||
|
let resp = hover(&client_connection, script.clone(), 3, 0);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
@ -839,9 +963,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete(uri: Url, line: u32, character: u32) -> Message {
|
fn complete(client_connection: &Connection, uri: Url, line: u32, character: u32) -> Message {
|
||||||
let (client_connection, _recv) = initialize_language_server();
|
|
||||||
|
|
||||||
client_connection
|
client_connection
|
||||||
.sender
|
.sender
|
||||||
.send(Message::Request(lsp_server::Request {
|
.send(Message::Request(lsp_server::Request {
|
||||||
@ -868,13 +990,17 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn complete_on_variable() {
|
fn complete_on_variable() {
|
||||||
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("completion");
|
script.push("completion");
|
||||||
script.push("var.nu");
|
script.push("var.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = Url::from_file_path(script).unwrap();
|
||||||
|
|
||||||
let resp = complete(script, 2, 9);
|
open(&client_connection, script.clone());
|
||||||
|
|
||||||
|
let resp = complete(&client_connection, script, 2, 9);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
@ -900,13 +1026,17 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn complete_command_with_space() {
|
fn complete_command_with_space() {
|
||||||
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("completion");
|
script.push("completion");
|
||||||
script.push("command.nu");
|
script.push("command.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = Url::from_file_path(script).unwrap();
|
||||||
|
|
||||||
let resp = complete(script, 0, 8);
|
open(&client_connection, script.clone());
|
||||||
|
|
||||||
|
let resp = complete(&client_connection, script, 0, 8);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
|
185
crates/nu-lsp/src/notification.rs
Normal file
185
crates/nu-lsp/src/notification.rs
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
use lsp_types::{
|
||||||
|
notification::{
|
||||||
|
DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, Notification,
|
||||||
|
},
|
||||||
|
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, Url,
|
||||||
|
};
|
||||||
|
use ropey::Rope;
|
||||||
|
|
||||||
|
use crate::LanguageServer;
|
||||||
|
|
||||||
|
impl LanguageServer {
|
||||||
|
pub(crate) fn handle_lsp_notification(
|
||||||
|
&mut self,
|
||||||
|
notification: lsp_server::Notification,
|
||||||
|
) -> Option<Url> {
|
||||||
|
match notification.method.as_str() {
|
||||||
|
DidOpenTextDocument::METHOD => Self::handle_notification_payload::<
|
||||||
|
DidOpenTextDocumentParams,
|
||||||
|
_,
|
||||||
|
>(notification, |param| {
|
||||||
|
if let Ok(file_path) = param.text_document.uri.to_file_path() {
|
||||||
|
let rope = Rope::from_str(¶m.text_document.text);
|
||||||
|
self.ropes.insert(file_path, rope);
|
||||||
|
Some(param.text_document.uri)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
DidChangeTextDocument::METHOD => {
|
||||||
|
Self::handle_notification_payload::<DidChangeTextDocumentParams, _>(
|
||||||
|
notification,
|
||||||
|
|params| self.update_rope(params),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DidCloseTextDocument::METHOD => Self::handle_notification_payload::<
|
||||||
|
DidCloseTextDocumentParams,
|
||||||
|
_,
|
||||||
|
>(notification, |param| {
|
||||||
|
if let Ok(file_path) = param.text_document.uri.to_file_path() {
|
||||||
|
self.ropes.remove(&file_path);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_notification_payload<P, H>(
|
||||||
|
notification: lsp_server::Notification,
|
||||||
|
mut param_handler: H,
|
||||||
|
) -> Option<Url>
|
||||||
|
where
|
||||||
|
P: serde::de::DeserializeOwned,
|
||||||
|
H: FnMut(P) -> Option<Url>,
|
||||||
|
{
|
||||||
|
if let Ok(params) = serde_json::from_value::<P>(notification.params) {
|
||||||
|
param_handler(params)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_rope(&mut self, params: DidChangeTextDocumentParams) -> Option<Url> {
|
||||||
|
if let Ok(file_path) = params.text_document.uri.to_file_path() {
|
||||||
|
for content_change in params.content_changes.into_iter() {
|
||||||
|
let entry = self.ropes.entry(file_path.clone());
|
||||||
|
match (content_change.range, content_change.range) {
|
||||||
|
(Some(range), _) => {
|
||||||
|
entry.and_modify(|rope| {
|
||||||
|
let start = Self::lsp_position_to_location(&range.start, rope);
|
||||||
|
let end = Self::lsp_position_to_location(&range.end, rope);
|
||||||
|
|
||||||
|
rope.remove(start..end);
|
||||||
|
rope.insert(start, &content_change.text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
(None, None) => {
|
||||||
|
entry.and_modify(|r| *r = Rope::from_str(&content_change.text));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(params.text_document.uri)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use assert_json_diff::assert_json_eq;
|
||||||
|
use lsp_server::Message;
|
||||||
|
use lsp_types::{Range, Url};
|
||||||
|
use nu_test_support::fs::fixtures;
|
||||||
|
|
||||||
|
use crate::tests::{hover, initialize_language_server, open, update};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_on_command_after_full_content_change() {
|
||||||
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
|
let mut script = fixtures();
|
||||||
|
script.push("lsp");
|
||||||
|
script.push("hover");
|
||||||
|
script.push("command.nu");
|
||||||
|
let script = Url::from_file_path(script).unwrap();
|
||||||
|
|
||||||
|
open(&client_connection, script.clone());
|
||||||
|
update(
|
||||||
|
&client_connection,
|
||||||
|
script.clone(),
|
||||||
|
String::from(
|
||||||
|
r#"# Renders some updated greeting message
|
||||||
|
def hello [] {}
|
||||||
|
|
||||||
|
hello"#,
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let resp = hover(&client_connection, script.clone(), 3, 0);
|
||||||
|
let result = if let Message::Response(response) = resp {
|
||||||
|
response.result
|
||||||
|
} else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_json_eq!(
|
||||||
|
result,
|
||||||
|
serde_json::json!({
|
||||||
|
"contents": {
|
||||||
|
"kind": "markdown",
|
||||||
|
"value": "```\n### Signature\n```\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n### Usage\n Renders some updated greeting message\n"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_on_command_after_partial_content_change() {
|
||||||
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
|
let mut script = fixtures();
|
||||||
|
script.push("lsp");
|
||||||
|
script.push("hover");
|
||||||
|
script.push("command.nu");
|
||||||
|
let script = Url::from_file_path(script).unwrap();
|
||||||
|
|
||||||
|
open(&client_connection, script.clone());
|
||||||
|
update(
|
||||||
|
&client_connection,
|
||||||
|
script.clone(),
|
||||||
|
String::from("# Renders some updated greeting message"),
|
||||||
|
Some(Range {
|
||||||
|
start: lsp_types::Position {
|
||||||
|
line: 0,
|
||||||
|
character: 0,
|
||||||
|
},
|
||||||
|
end: lsp_types::Position {
|
||||||
|
line: 0,
|
||||||
|
character: 31,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let resp = hover(&client_connection, script.clone(), 3, 0);
|
||||||
|
let result = if let Message::Response(response) = resp {
|
||||||
|
response.result
|
||||||
|
} else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_json_eq!(
|
||||||
|
result,
|
||||||
|
serde_json::json!({
|
||||||
|
"contents": {
|
||||||
|
"kind": "markdown",
|
||||||
|
"value": "```\n### Signature\n```\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n### Usage\n Renders some updated greeting message\n"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-parser"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-parser"
|
name = "nu-parser"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
exclude = ["/fuzz"]
|
exclude = ["/fuzz"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.87.1" }
|
nu-engine = { path = "../nu-engine", version = "0.87.2" }
|
||||||
nu-path = { path = "../nu-path", version = "0.87.1" }
|
nu-path = { path = "../nu-path", version = "0.87.2" }
|
||||||
nu-plugin = { path = "../nu-plugin", optional = true, version = "0.87.1" }
|
nu-plugin = { path = "../nu-plugin", optional = true, version = "0.87.2" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
|
||||||
|
|
||||||
bytesize = "1.3"
|
bytesize = "1.3"
|
||||||
chrono = { default-features = false, features = ['std'], version = "0.4" }
|
chrono = { default-features = false, features = ['std'], version = "0.4" }
|
||||||
|
@ -47,9 +47,7 @@ pub const UNALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[
|
|||||||
b"export def",
|
b"export def",
|
||||||
b"for",
|
b"for",
|
||||||
b"extern",
|
b"extern",
|
||||||
b"extern-wrapped",
|
|
||||||
b"export extern",
|
b"export extern",
|
||||||
b"export extern-wrapped",
|
|
||||||
b"alias",
|
b"alias",
|
||||||
b"export alias",
|
b"export alias",
|
||||||
b"export-env",
|
b"export-env",
|
||||||
@ -72,13 +70,13 @@ pub const UNALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[
|
|||||||
/// Check whether spans start with a parser keyword that can be aliased
|
/// Check whether spans start with a parser keyword that can be aliased
|
||||||
pub fn is_unaliasable_parser_keyword(working_set: &StateWorkingSet, spans: &[Span]) -> bool {
|
pub fn is_unaliasable_parser_keyword(working_set: &StateWorkingSet, spans: &[Span]) -> bool {
|
||||||
// try two words
|
// try two words
|
||||||
if let (Some(span1), Some(span2)) = (spans.get(0), spans.get(1)) {
|
if let (Some(span1), Some(span2)) = (spans.first(), spans.get(1)) {
|
||||||
let cmd_name = working_set.get_span_contents(span(&[*span1, *span2]));
|
let cmd_name = working_set.get_span_contents(span(&[*span1, *span2]));
|
||||||
return UNALIASABLE_PARSER_KEYWORDS.contains(&cmd_name);
|
return UNALIASABLE_PARSER_KEYWORDS.contains(&cmd_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// try one word
|
// try one word
|
||||||
if let Some(span1) = spans.get(0) {
|
if let Some(span1) = spans.first() {
|
||||||
let cmd_name = working_set.get_span_contents(*span1);
|
let cmd_name = working_set.get_span_contents(*span1);
|
||||||
UNALIASABLE_PARSER_KEYWORDS.contains(&cmd_name)
|
UNALIASABLE_PARSER_KEYWORDS.contains(&cmd_name)
|
||||||
} else {
|
} else {
|
||||||
@ -151,11 +149,7 @@ pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if def_type_name != b"def"
|
if def_type_name != b"def" && def_type_name != b"extern" {
|
||||||
&& def_type_name != b"def-env"
|
|
||||||
&& def_type_name != b"extern"
|
|
||||||
&& def_type_name != b"extern-wrapped"
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,7 +372,7 @@ pub fn parse_def(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let def_call = working_set.get_span_contents(name_span).to_vec();
|
let def_call = working_set.get_span_contents(name_span).to_vec();
|
||||||
if def_call != b"def" && def_call != b"def-env" {
|
if def_call != b"def" {
|
||||||
working_set.error(ParseError::UnknownState(
|
working_set.error(ParseError::UnknownState(
|
||||||
"internal error: Wrong call name for def function".into(),
|
"internal error: Wrong call name for def function".into(),
|
||||||
span(spans),
|
span(spans),
|
||||||
@ -575,7 +569,7 @@ pub fn parse_def(
|
|||||||
let calls_itself = block_calls_itself(block, decl_id);
|
let calls_itself = block_calls_itself(block, decl_id);
|
||||||
block.recursive = Some(calls_itself);
|
block.recursive = Some(calls_itself);
|
||||||
block.signature = signature;
|
block.signature = signature;
|
||||||
block.redirect_env = def_call == b"def-env" || has_env;
|
block.redirect_env = has_env;
|
||||||
|
|
||||||
if block.signature.input_output_types.is_empty() {
|
if block.signature.input_output_types.is_empty() {
|
||||||
block
|
block
|
||||||
@ -635,9 +629,9 @@ pub fn parse_extern(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let extern_call = working_set.get_span_contents(name_span).to_vec();
|
let extern_call = working_set.get_span_contents(name_span).to_vec();
|
||||||
if extern_call != b"extern" && extern_call != b"extern-wrapped" {
|
if extern_call != b"extern" {
|
||||||
working_set.error(ParseError::UnknownState(
|
working_set.error(ParseError::UnknownState(
|
||||||
"internal error: Wrong call name for extern or extern-wrapped command".into(),
|
"internal error: Wrong call name for extern command".into(),
|
||||||
span(spans),
|
span(spans),
|
||||||
));
|
));
|
||||||
return garbage_pipeline(spans);
|
return garbage_pipeline(spans);
|
||||||
@ -659,7 +653,7 @@ pub fn parse_extern(
|
|||||||
|
|
||||||
let (command_spans, rest_spans) = spans.split_at(split_id);
|
let (command_spans, rest_spans) = spans.split_at(split_id);
|
||||||
|
|
||||||
if let Some(name_span) = rest_spans.get(0) {
|
if let Some(name_span) = rest_spans.first() {
|
||||||
if let Some(err) = detect_params_in_name(
|
if let Some(err) = detect_params_in_name(
|
||||||
working_set,
|
working_set,
|
||||||
*name_span,
|
*name_span,
|
||||||
@ -1055,8 +1049,9 @@ pub fn parse_export_in_block(
|
|||||||
let full_name = if lite_command.parts.len() > 1 {
|
let full_name = if lite_command.parts.len() > 1 {
|
||||||
let sub = working_set.get_span_contents(lite_command.parts[1]);
|
let sub = working_set.get_span_contents(lite_command.parts[1]);
|
||||||
match sub {
|
match sub {
|
||||||
b"alias" | b"def" | b"def-env" | b"extern" | b"extern-wrapped" | b"use" | b"module"
|
b"alias" | b"def" | b"extern" | b"use" | b"module" | b"const" => {
|
||||||
| b"const" => [b"export ", sub].concat(),
|
[b"export ", sub].concat()
|
||||||
|
}
|
||||||
_ => b"export".to_vec(),
|
_ => b"export".to_vec(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1113,7 +1108,7 @@ pub fn parse_export_in_block(
|
|||||||
|
|
||||||
match full_name.as_slice() {
|
match full_name.as_slice() {
|
||||||
b"export alias" => parse_alias(working_set, lite_command, None),
|
b"export alias" => parse_alias(working_set, lite_command, None),
|
||||||
b"export def" | b"export def-env" => parse_def(working_set, lite_command, None).0,
|
b"export def" => parse_def(working_set, lite_command, None).0,
|
||||||
b"export const" => parse_const(working_set, &lite_command.parts[1..]),
|
b"export const" => parse_const(working_set, &lite_command.parts[1..]),
|
||||||
b"export use" => {
|
b"export use" => {
|
||||||
let (pipeline, _) = parse_use(working_set, &lite_command.parts);
|
let (pipeline, _) = parse_use(working_set, &lite_command.parts);
|
||||||
@ -1121,7 +1116,6 @@ pub fn parse_export_in_block(
|
|||||||
}
|
}
|
||||||
b"export module" => parse_module(working_set, lite_command, None).0,
|
b"export module" => parse_module(working_set, lite_command, None).0,
|
||||||
b"export extern" => parse_extern(working_set, lite_command, None),
|
b"export extern" => parse_extern(working_set, lite_command, None),
|
||||||
b"export extern-wrapped" => parse_extern(working_set, lite_command, None),
|
|
||||||
_ => {
|
_ => {
|
||||||
working_set.error(ParseError::UnexpectedKeyword(
|
working_set.error(ParseError::UnexpectedKeyword(
|
||||||
String::from_utf8_lossy(&full_name).to_string(),
|
String::from_utf8_lossy(&full_name).to_string(),
|
||||||
@ -1141,7 +1135,7 @@ pub fn parse_export_in_module(
|
|||||||
) -> (Pipeline, Vec<Exportable>) {
|
) -> (Pipeline, Vec<Exportable>) {
|
||||||
let spans = &lite_command.parts[..];
|
let spans = &lite_command.parts[..];
|
||||||
|
|
||||||
let export_span = if let Some(sp) = spans.get(0) {
|
let export_span = if let Some(sp) = spans.first() {
|
||||||
if working_set.get_span_contents(*sp) != b"export" {
|
if working_set.get_span_contents(*sp) != b"export" {
|
||||||
working_set.error(ParseError::UnknownState(
|
working_set.error(ParseError::UnknownState(
|
||||||
"expected export statement".into(),
|
"expected export statement".into(),
|
||||||
@ -1215,7 +1209,7 @@ pub fn parse_export_in_module(
|
|||||||
expr: Expr::Call(ref def_call),
|
expr: Expr::Call(ref def_call),
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
)) = pipeline.elements.get(0)
|
)) = pipeline.elements.first()
|
||||||
{
|
{
|
||||||
call = def_call.clone();
|
call = def_call.clone();
|
||||||
|
|
||||||
@ -1230,67 +1224,7 @@ pub fn parse_export_in_module(
|
|||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
b"def-env" => {
|
b"extern" => {
|
||||||
let lite_command = LiteCommand {
|
|
||||||
comments: lite_command.comments.clone(),
|
|
||||||
parts: spans[1..].to_vec(),
|
|
||||||
};
|
|
||||||
let (pipeline, _) = parse_def(working_set, &lite_command, Some(module_name));
|
|
||||||
|
|
||||||
let export_def_decl_id = if let Some(id) = working_set.find_decl(b"export def-env")
|
|
||||||
{
|
|
||||||
id
|
|
||||||
} else {
|
|
||||||
working_set.error(ParseError::InternalError(
|
|
||||||
"missing 'export def-env' command".into(),
|
|
||||||
export_span,
|
|
||||||
));
|
|
||||||
return (garbage_pipeline(spans), vec![]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Trying to warp the 'def' call into the 'export def' in a very clumsy way
|
|
||||||
if let Some(PipelineElement::Expression(
|
|
||||||
_,
|
|
||||||
Expression {
|
|
||||||
expr: Expr::Call(ref def_call),
|
|
||||||
..
|
|
||||||
},
|
|
||||||
)) = pipeline.elements.get(0)
|
|
||||||
{
|
|
||||||
call = def_call.clone();
|
|
||||||
|
|
||||||
call.head = span(&spans[0..=1]);
|
|
||||||
call.decl_id = export_def_decl_id;
|
|
||||||
} else {
|
|
||||||
working_set.error(ParseError::InternalError(
|
|
||||||
"unexpected output from parsing a definition".into(),
|
|
||||||
span(&spans[1..]),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut result = vec![];
|
|
||||||
|
|
||||||
let decl_name = match spans.get(2) {
|
|
||||||
Some(span) => working_set.get_span_contents(*span),
|
|
||||||
None => &[],
|
|
||||||
};
|
|
||||||
let decl_name = trim_quotes(decl_name);
|
|
||||||
|
|
||||||
if let Some(decl_id) = working_set.find_decl(decl_name) {
|
|
||||||
result.push(Exportable::Decl {
|
|
||||||
name: decl_name.to_vec(),
|
|
||||||
id: decl_id,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
working_set.error(ParseError::InternalError(
|
|
||||||
"failed to find added declaration".into(),
|
|
||||||
span(&spans[1..]),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
b"extern" | b"extern-wrapped" => {
|
|
||||||
let lite_command = LiteCommand {
|
let lite_command = LiteCommand {
|
||||||
comments: lite_command.comments.clone(),
|
comments: lite_command.comments.clone(),
|
||||||
parts: spans[1..].to_vec(),
|
parts: spans[1..].to_vec(),
|
||||||
@ -1303,7 +1237,7 @@ pub fn parse_export_in_module(
|
|||||||
id
|
id
|
||||||
} else {
|
} else {
|
||||||
working_set.error(ParseError::InternalError(
|
working_set.error(ParseError::InternalError(
|
||||||
"missing 'export extern' or 'export extern-wrapped' command".into(),
|
"missing 'export extern' command".into(),
|
||||||
export_span,
|
export_span,
|
||||||
));
|
));
|
||||||
return (garbage_pipeline(spans), vec![]);
|
return (garbage_pipeline(spans), vec![]);
|
||||||
@ -1316,7 +1250,7 @@ pub fn parse_export_in_module(
|
|||||||
expr: Expr::Call(ref def_call),
|
expr: Expr::Call(ref def_call),
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
)) = pipeline.elements.get(0)
|
)) = pipeline.elements.first()
|
||||||
{
|
{
|
||||||
call = def_call.clone();
|
call = def_call.clone();
|
||||||
|
|
||||||
@ -1376,7 +1310,7 @@ pub fn parse_export_in_module(
|
|||||||
expr: Expr::Call(ref alias_call),
|
expr: Expr::Call(ref alias_call),
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
)) = pipeline.elements.get(0)
|
)) = pipeline.elements.first()
|
||||||
{
|
{
|
||||||
call = alias_call.clone();
|
call = alias_call.clone();
|
||||||
|
|
||||||
@ -1435,7 +1369,7 @@ pub fn parse_export_in_module(
|
|||||||
expr: Expr::Call(ref use_call),
|
expr: Expr::Call(ref use_call),
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
)) = pipeline.elements.get(0)
|
)) = pipeline.elements.first()
|
||||||
{
|
{
|
||||||
call = use_call.clone();
|
call = use_call.clone();
|
||||||
|
|
||||||
@ -1472,7 +1406,7 @@ pub fn parse_export_in_module(
|
|||||||
expr: Expr::Call(ref module_call),
|
expr: Expr::Call(ref module_call),
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
)) = pipeline.elements.get(0)
|
)) = pipeline.elements.first()
|
||||||
{
|
{
|
||||||
call = module_call.clone();
|
call = module_call.clone();
|
||||||
|
|
||||||
@ -1529,7 +1463,7 @@ pub fn parse_export_in_module(
|
|||||||
expr: Expr::Call(ref def_call),
|
expr: Expr::Call(ref def_call),
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
)) = pipeline.elements.get(0)
|
)) = pipeline.elements.first()
|
||||||
{
|
{
|
||||||
call = def_call.clone();
|
call = def_call.clone();
|
||||||
|
|
||||||
@ -1569,7 +1503,7 @@ pub fn parse_export_in_module(
|
|||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
working_set.error(ParseError::Expected(
|
working_set.error(ParseError::Expected(
|
||||||
"def, def-env, alias, use, module, const, extern or extern-wrapped keyword",
|
"def, alias, use, module, const or extern keyword",
|
||||||
spans[1],
|
spans[1],
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -1578,9 +1512,9 @@ pub fn parse_export_in_module(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
working_set.error(ParseError::MissingPositional(
|
working_set.error(ParseError::MissingPositional(
|
||||||
"def, def-env, alias, use, module, const, extern or extern-wrapped keyword".to_string(),
|
"def, alias, use, module, const or extern keyword".to_string(),
|
||||||
Span::new(export_span.end, export_span.end),
|
Span::new(export_span.end, export_span.end),
|
||||||
"def, def-env, alias, use, module, const, extern or extern-wrapped keyword".to_string(),
|
"def, alias, use, module, const or extern keyword".to_string(),
|
||||||
));
|
));
|
||||||
|
|
||||||
vec![]
|
vec![]
|
||||||
@ -1749,7 +1683,7 @@ pub fn parse_module_block(
|
|||||||
let name = working_set.get_span_contents(command.parts[0]);
|
let name = working_set.get_span_contents(command.parts[0]);
|
||||||
|
|
||||||
match name {
|
match name {
|
||||||
b"def" | b"def-env" => {
|
b"def" => {
|
||||||
block.pipelines.push(
|
block.pipelines.push(
|
||||||
parse_def(
|
parse_def(
|
||||||
working_set,
|
working_set,
|
||||||
@ -1762,11 +1696,9 @@ pub fn parse_module_block(
|
|||||||
b"const" => block
|
b"const" => block
|
||||||
.pipelines
|
.pipelines
|
||||||
.push(parse_const(working_set, &command.parts)),
|
.push(parse_const(working_set, &command.parts)),
|
||||||
b"extern" | b"extern-wrapped" => {
|
b"extern" => block
|
||||||
block
|
|
||||||
.pipelines
|
.pipelines
|
||||||
.push(parse_extern(working_set, command, None))
|
.push(parse_extern(working_set, command, None)),
|
||||||
}
|
|
||||||
b"alias" => {
|
b"alias" => {
|
||||||
block.pipelines.push(parse_alias(
|
block.pipelines.push(parse_alias(
|
||||||
working_set,
|
working_set,
|
||||||
@ -1906,7 +1838,7 @@ pub fn parse_module_block(
|
|||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
working_set.error(ParseError::ExpectedKeyword(
|
working_set.error(ParseError::ExpectedKeyword(
|
||||||
"def, const, def-env, extern, extern-wrapped, alias, use, module, export or export-env keyword".into(),
|
"def, const, extern, alias, use, module, export or export-env keyword".into(),
|
||||||
command.parts[0],
|
command.parts[0],
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -1572,10 +1572,10 @@ pub fn parse_brace_expr(
|
|||||||
let (tokens, _) = lex(bytes, span.start + 1, &[b'\r', b'\n', b'\t'], &[b':'], true);
|
let (tokens, _) = lex(bytes, span.start + 1, &[b'\r', b'\n', b'\t'], &[b':'], true);
|
||||||
|
|
||||||
let second_token = tokens
|
let second_token = tokens
|
||||||
.get(0)
|
.first()
|
||||||
.map(|token| working_set.get_span_contents(token.span));
|
.map(|token| working_set.get_span_contents(token.span));
|
||||||
|
|
||||||
let second_token_contents = tokens.get(0).map(|token| token.contents);
|
let second_token_contents = tokens.first().map(|token| token.contents);
|
||||||
|
|
||||||
let third_token = tokens
|
let third_token = tokens
|
||||||
.get(1)
|
.get(1)
|
||||||
@ -2666,7 +2666,7 @@ pub fn parse_string_strict(working_set: &mut StateWorkingSet, span: Span) -> Exp
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_import_pattern(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression {
|
pub fn parse_import_pattern(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression {
|
||||||
let Some(head_span) = spans.get(0) else {
|
let Some(head_span) = spans.first() else {
|
||||||
working_set.error(ParseError::WrongImportPattern(
|
working_set.error(ParseError::WrongImportPattern(
|
||||||
"needs at least one component of import pattern".to_string(),
|
"needs at least one component of import pattern".to_string(),
|
||||||
span(spans),
|
span(spans),
|
||||||
@ -4918,8 +4918,8 @@ pub fn parse_expression(
|
|||||||
|
|
||||||
// For now, check for special parses of certain keywords
|
// For now, check for special parses of certain keywords
|
||||||
match bytes.as_slice() {
|
match bytes.as_slice() {
|
||||||
b"def" | b"extern" | b"extern-wrapped" | b"for" | b"module" | b"use" | b"source"
|
b"def" | b"extern" | b"for" | b"module" | b"use" | b"source" | b"alias" | b"export"
|
||||||
| b"alias" | b"export" | b"hide" => {
|
| b"hide" => {
|
||||||
working_set.error(ParseError::BuiltinCommandInPipeline(
|
working_set.error(ParseError::BuiltinCommandInPipeline(
|
||||||
String::from_utf8(bytes)
|
String::from_utf8(bytes)
|
||||||
.expect("builtin commands bytes should be able to convert to string"),
|
.expect("builtin commands bytes should be able to convert to string"),
|
||||||
@ -5085,8 +5085,8 @@ pub fn parse_builtin_commands(
|
|||||||
let name = working_set.get_span_contents(lite_command.parts[0]);
|
let name = working_set.get_span_contents(lite_command.parts[0]);
|
||||||
|
|
||||||
match name {
|
match name {
|
||||||
b"def" | b"def-env" => parse_def(working_set, lite_command, None).0,
|
b"def" => parse_def(working_set, lite_command, None).0,
|
||||||
b"extern" | b"extern-wrapped" => parse_extern(working_set, lite_command, None),
|
b"extern" => parse_extern(working_set, lite_command, None),
|
||||||
b"let" => parse_let(working_set, &lite_command.parts),
|
b"let" => parse_let(working_set, &lite_command.parts),
|
||||||
b"const" => parse_const(working_set, &lite_command.parts),
|
b"const" => parse_const(working_set, &lite_command.parts),
|
||||||
b"mut" => parse_mut(working_set, &lite_command.parts),
|
b"mut" => parse_mut(working_set, &lite_command.parts),
|
||||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-path"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-path"
|
name = "nu-path"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
exclude = ["/fuzz"]
|
exclude = ["/fuzz"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-plugin"
|
name = "nu-plugin"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.87.1" }
|
nu-engine = { path = "../nu-engine", version = "0.87.2" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
|
||||||
|
|
||||||
bincode = "1.3"
|
bincode = "1.3"
|
||||||
rmp-serde = "1.1"
|
rmp-serde = "1.1"
|
||||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-pretty-hex"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-pretty-hex"
|
name = "nu-pretty-hex"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
doctest = false
|
doctest = false
|
||||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-protocol"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-protocol"
|
name = "nu-protocol"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -13,9 +13,9 @@ version = "0.87.1"
|
|||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-utils = { path = "../nu-utils", version = "0.87.1" }
|
nu-utils = { path = "../nu-utils", version = "0.87.2" }
|
||||||
nu-path = { path = "../nu-path", version = "0.87.1" }
|
nu-path = { path = "../nu-path", version = "0.87.2" }
|
||||||
nu-system = { path = "../nu-system", version = "0.87.1" }
|
nu-system = { path = "../nu-system", version = "0.87.2" }
|
||||||
|
|
||||||
byte-unit = "4.0"
|
byte-unit = "4.0"
|
||||||
chrono = { version = "0.4", features = [ "serde", "std", "unstable-locales" ], default-features = false }
|
chrono = { version = "0.4", features = [ "serde", "std", "unstable-locales" ], default-features = false }
|
||||||
@ -37,5 +37,5 @@ plugin = ["serde_json"]
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
strum = "0.25"
|
strum = "0.25"
|
||||||
strum_macros = "0.25"
|
strum_macros = "0.25"
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.87.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.87.2" }
|
||||||
rstest = "0.18"
|
rstest = "0.18"
|
||||||
|
@ -12,6 +12,28 @@ pub enum Argument {
|
|||||||
Unknown(Expression), // unknown argument used in "fall-through" signatures
|
Unknown(Expression), // unknown argument used in "fall-through" signatures
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Argument {
|
||||||
|
/// The span for an argument
|
||||||
|
pub fn span(&self) -> Span {
|
||||||
|
match self {
|
||||||
|
Argument::Positional(e) => e.span,
|
||||||
|
Argument::Named((named, short, expr)) => {
|
||||||
|
let start = named.span.start;
|
||||||
|
let end = if let Some(expr) = expr {
|
||||||
|
expr.span.end
|
||||||
|
} else if let Some(short) = short {
|
||||||
|
short.span.end
|
||||||
|
} else {
|
||||||
|
named.span.end
|
||||||
|
};
|
||||||
|
|
||||||
|
Span::new(start, end)
|
||||||
|
}
|
||||||
|
Argument::Unknown(e) => e.span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Call {
|
pub struct Call {
|
||||||
/// identifier of the declaration to call
|
/// identifier of the declaration to call
|
||||||
@ -36,6 +58,26 @@ impl Call {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The span encompassing the arguments
|
||||||
|
///
|
||||||
|
/// If there are no arguments the span covers where the first argument would exist
|
||||||
|
///
|
||||||
|
/// If there are one or more arguments the span encompasses the start of the first argument to
|
||||||
|
/// end of the last argument
|
||||||
|
pub fn arguments_span(&self) -> Span {
|
||||||
|
let past = self.head.past();
|
||||||
|
|
||||||
|
let start = self
|
||||||
|
.arguments
|
||||||
|
.first()
|
||||||
|
.map(|a| a.span())
|
||||||
|
.unwrap_or(past)
|
||||||
|
.start;
|
||||||
|
let end = self.arguments.last().map(|a| a.span()).unwrap_or(past).end;
|
||||||
|
|
||||||
|
Span::new(start, end)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn named_iter(
|
pub fn named_iter(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Iterator<Item = &(Spanned<String>, Option<Spanned<String>>, Option<Expression>)> {
|
) -> impl Iterator<Item = &(Spanned<String>, Option<Spanned<String>>, Option<Expression>)> {
|
||||||
@ -166,3 +208,64 @@ impl Call {
|
|||||||
span
|
span
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn argument_span_named() {
|
||||||
|
let named = Spanned {
|
||||||
|
item: "named".to_string(),
|
||||||
|
span: Span::new(2, 3),
|
||||||
|
};
|
||||||
|
let short = Spanned {
|
||||||
|
item: "short".to_string(),
|
||||||
|
span: Span::new(5, 7),
|
||||||
|
};
|
||||||
|
let expr = Expression::garbage(Span::new(11, 13));
|
||||||
|
|
||||||
|
let arg = Argument::Named((named.clone(), None, None));
|
||||||
|
|
||||||
|
assert_eq!(Span::new(2, 3), arg.span());
|
||||||
|
|
||||||
|
let arg = Argument::Named((named.clone(), Some(short.clone()), None));
|
||||||
|
|
||||||
|
assert_eq!(Span::new(2, 7), arg.span());
|
||||||
|
|
||||||
|
let arg = Argument::Named((named.clone(), None, Some(expr.clone())));
|
||||||
|
|
||||||
|
assert_eq!(Span::new(2, 13), arg.span());
|
||||||
|
|
||||||
|
let arg = Argument::Named((named.clone(), Some(short.clone()), Some(expr.clone())));
|
||||||
|
|
||||||
|
assert_eq!(Span::new(2, 13), arg.span());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn argument_span_positional() {
|
||||||
|
let span = Span::new(2, 3);
|
||||||
|
let expr = Expression::garbage(span);
|
||||||
|
let arg = Argument::Positional(expr);
|
||||||
|
|
||||||
|
assert_eq!(span, arg.span());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn argument_span_unknown() {
|
||||||
|
let span = Span::new(2, 3);
|
||||||
|
let expr = Expression::garbage(span);
|
||||||
|
let arg = Argument::Unknown(expr);
|
||||||
|
|
||||||
|
assert_eq!(span, arg.span());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn call_arguments_span() {
|
||||||
|
let mut call = Call::new(Span::new(0, 1));
|
||||||
|
call.add_positional(Expression::garbage(Span::new(2, 3)));
|
||||||
|
call.add_positional(Expression::garbage(Span::new(5, 7)));
|
||||||
|
|
||||||
|
assert_eq!(Span::new(2, 7), call.arguments_span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -134,8 +134,8 @@ impl Expression {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(pipeline) = block.pipelines.get(0) {
|
if let Some(pipeline) = block.pipelines.first() {
|
||||||
match pipeline.elements.get(0) {
|
match pipeline.elements.first() {
|
||||||
Some(element) => element.has_in_variable(working_set),
|
Some(element) => element.has_in_variable(working_set),
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
@ -150,8 +150,8 @@ impl Expression {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(pipeline) = block.pipelines.get(0) {
|
if let Some(pipeline) = block.pipelines.first() {
|
||||||
match pipeline.elements.get(0) {
|
match pipeline.elements.first() {
|
||||||
Some(element) => element.has_in_variable(working_set),
|
Some(element) => element.has_in_variable(working_set),
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
@ -258,8 +258,8 @@ impl Expression {
|
|||||||
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
||||||
let block = working_set.get_block(*block_id);
|
let block = working_set.get_block(*block_id);
|
||||||
|
|
||||||
if let Some(pipeline) = block.pipelines.get(0) {
|
if let Some(pipeline) = block.pipelines.first() {
|
||||||
if let Some(expr) = pipeline.elements.get(0) {
|
if let Some(expr) = pipeline.elements.first() {
|
||||||
expr.has_in_variable(working_set)
|
expr.has_in_variable(working_set)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@ -304,8 +304,8 @@ impl Expression {
|
|||||||
Expr::Block(block_id) => {
|
Expr::Block(block_id) => {
|
||||||
let block = working_set.get_block(*block_id);
|
let block = working_set.get_block(*block_id);
|
||||||
|
|
||||||
let new_expr = if let Some(pipeline) = block.pipelines.get(0) {
|
let new_expr = if let Some(pipeline) = block.pipelines.first() {
|
||||||
if let Some(element) = pipeline.elements.get(0) {
|
if let Some(element) = pipeline.elements.first() {
|
||||||
let mut new_element = element.clone();
|
let mut new_element = element.clone();
|
||||||
new_element.replace_in_variable(working_set, new_var_id);
|
new_element.replace_in_variable(working_set, new_var_id);
|
||||||
Some(new_element)
|
Some(new_element)
|
||||||
@ -335,8 +335,8 @@ impl Expression {
|
|||||||
Expr::Closure(block_id) => {
|
Expr::Closure(block_id) => {
|
||||||
let block = working_set.get_block(*block_id);
|
let block = working_set.get_block(*block_id);
|
||||||
|
|
||||||
let new_element = if let Some(pipeline) = block.pipelines.get(0) {
|
let new_element = if let Some(pipeline) = block.pipelines.first() {
|
||||||
if let Some(element) = pipeline.elements.get(0) {
|
if let Some(element) = pipeline.elements.first() {
|
||||||
let mut new_element = element.clone();
|
let mut new_element = element.clone();
|
||||||
new_element.replace_in_variable(working_set, new_var_id);
|
new_element.replace_in_variable(working_set, new_var_id);
|
||||||
Some(new_element)
|
Some(new_element)
|
||||||
@ -433,8 +433,8 @@ impl Expression {
|
|||||||
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
||||||
let block = working_set.get_block(*block_id);
|
let block = working_set.get_block(*block_id);
|
||||||
|
|
||||||
let new_element = if let Some(pipeline) = block.pipelines.get(0) {
|
let new_element = if let Some(pipeline) = block.pipelines.first() {
|
||||||
if let Some(element) = pipeline.elements.get(0) {
|
if let Some(element) = pipeline.elements.first() {
|
||||||
let mut new_element = element.clone();
|
let mut new_element = element.clone();
|
||||||
new_element.replace_in_variable(working_set, new_var_id);
|
new_element.replace_in_variable(working_set, new_var_id);
|
||||||
Some(new_element)
|
Some(new_element)
|
||||||
|
@ -3,7 +3,7 @@ use crate::{record, Config, ShellError, Span, Value};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default)]
|
||||||
pub enum TableMode {
|
pub enum TableMode {
|
||||||
Basic,
|
Basic,
|
||||||
Thin,
|
Thin,
|
||||||
@ -35,6 +35,7 @@ impl FromStr for TableMode {
|
|||||||
"compact" => Ok(Self::Compact),
|
"compact" => Ok(Self::Compact),
|
||||||
"with_love" => Ok(Self::WithLove),
|
"with_love" => Ok(Self::WithLove),
|
||||||
"compact_double" => Ok(Self::CompactDouble),
|
"compact_double" => Ok(Self::CompactDouble),
|
||||||
|
"default" => Ok(TableMode::default()),
|
||||||
"rounded" => Ok(Self::Rounded),
|
"rounded" => Ok(Self::Rounded),
|
||||||
"reinforced" => Ok(Self::Reinforced),
|
"reinforced" => Ok(Self::Reinforced),
|
||||||
"heavy" => Ok(Self::Heavy),
|
"heavy" => Ok(Self::Heavy),
|
||||||
|
@ -753,8 +753,7 @@ impl EngineState {
|
|||||||
decls_map.extend(new_decls);
|
decls_map.extend(new_decls);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut decls: Vec<(Vec<u8>, DeclId)> =
|
let mut decls: Vec<(Vec<u8>, DeclId)> = decls_map.into_iter().collect();
|
||||||
decls_map.into_iter().map(|(v, k)| (v, k)).collect();
|
|
||||||
|
|
||||||
decls.sort_by(|a, b| a.0.cmp(&b.0));
|
decls.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
decls.into_iter()
|
decls.into_iter()
|
||||||
|
@ -317,6 +317,15 @@ impl<'a> StateWorkingSet<'a> {
|
|||||||
self.num_virtual_paths() - 1
|
self.num_virtual_paths() - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_span_for_filename(&self, filename: &str) -> Option<Span> {
|
||||||
|
let (file_id, ..) = self
|
||||||
|
.files()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, (fname, _, _))| fname == filename)?;
|
||||||
|
|
||||||
|
Some(self.get_span_for_file(file_id))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_span_for_file(&self, file_id: usize) -> Span {
|
pub fn get_span_for_file(&self, file_id: usize) -> Span {
|
||||||
let result = self
|
let result = self
|
||||||
.files()
|
.files()
|
||||||
|
@ -676,7 +676,11 @@ pub enum ShellError {
|
|||||||
/// It's always DNS.
|
/// It's always DNS.
|
||||||
#[error("Network failure")]
|
#[error("Network failure")]
|
||||||
#[diagnostic(code(nu::shell::network_failure))]
|
#[diagnostic(code(nu::shell::network_failure))]
|
||||||
NetworkFailure(String, #[label("{0}")] Span),
|
NetworkFailure {
|
||||||
|
msg: String,
|
||||||
|
#[label("{msg}")]
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
|
||||||
/// Help text for this command could not be found.
|
/// Help text for this command could not be found.
|
||||||
///
|
///
|
||||||
@ -685,7 +689,10 @@ pub enum ShellError {
|
|||||||
/// Check the spelling for the requested command and try again. Are you sure it's defined and your configurations are loading correctly? Can you execute it?
|
/// Check the spelling for the requested command and try again. Are you sure it's defined and your configurations are loading correctly? Can you execute it?
|
||||||
#[error("Command not found")]
|
#[error("Command not found")]
|
||||||
#[diagnostic(code(nu::shell::command_not_found))]
|
#[diagnostic(code(nu::shell::command_not_found))]
|
||||||
CommandNotFound(#[label("command not found")] Span),
|
CommandNotFound {
|
||||||
|
#[label("command not found")]
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
|
||||||
/// This alias could not be found
|
/// This alias could not be found
|
||||||
///
|
///
|
||||||
|
@ -894,26 +894,6 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the content is empty
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Value::String { val, .. } => val.is_empty(),
|
|
||||||
Value::List { vals, .. } => vals.is_empty(),
|
|
||||||
Value::Record { val, .. } => val.is_empty(),
|
|
||||||
Value::Binary { val, .. } => val.is_empty(),
|
|
||||||
Value::Nothing { .. } => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_nothing(&self) -> bool {
|
|
||||||
matches!(self, Value::Nothing { .. })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_error(&self) -> bool {
|
|
||||||
matches!(self, Value::Error { .. })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Follow a given cell path into the value: for example accessing select elements in a stream or list
|
/// Follow a given cell path into the value: for example accessing select elements in a stream or list
|
||||||
pub fn follow_cell_path(
|
pub fn follow_cell_path(
|
||||||
self,
|
self,
|
||||||
@ -923,8 +903,6 @@ impl Value {
|
|||||||
let mut current = self;
|
let mut current = self;
|
||||||
|
|
||||||
for member in cell_path {
|
for member in cell_path {
|
||||||
// FIXME: this uses a few extra clones for simplicity, but there may be a way
|
|
||||||
// to traverse the path without them
|
|
||||||
match member {
|
match member {
|
||||||
PathMember::Int {
|
PathMember::Int {
|
||||||
val: count,
|
val: count,
|
||||||
@ -932,16 +910,22 @@ impl Value {
|
|||||||
optional,
|
optional,
|
||||||
} => {
|
} => {
|
||||||
// Treat a numeric path member as `select <val>`
|
// Treat a numeric path member as `select <val>`
|
||||||
match &mut current {
|
match current {
|
||||||
Value::List { vals: val, .. } => {
|
Value::List { mut vals, .. } => {
|
||||||
if let Some(item) = val.get(*count) {
|
if *count < vals.len() {
|
||||||
current = item.clone();
|
// `vals` is owned and will be dropped right after this,
|
||||||
|
// so we can `swap_remove` the value at index `count`
|
||||||
|
// without worrying about preserving order.
|
||||||
|
current = vals.swap_remove(*count);
|
||||||
} else if *optional {
|
} else if *optional {
|
||||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||||
} else if val.is_empty() {
|
} else if vals.is_empty() {
|
||||||
return Err(ShellError::AccessEmptyContent { span: *origin_span })
|
return Err(ShellError::AccessEmptyContent { span: *origin_span });
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::AccessBeyondEnd { max_idx:val.len()-1,span: *origin_span });
|
return Err(ShellError::AccessBeyondEnd {
|
||||||
|
max_idx: vals.len() - 1,
|
||||||
|
span: *origin_span,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Binary { val, .. } => {
|
Value::Binary { val, .. } => {
|
||||||
@ -950,19 +934,22 @@ impl Value {
|
|||||||
} else if *optional {
|
} else if *optional {
|
||||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||||
} else if val.is_empty() {
|
} else if val.is_empty() {
|
||||||
return Err(ShellError::AccessEmptyContent { span: *origin_span })
|
return Err(ShellError::AccessEmptyContent { span: *origin_span });
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::AccessBeyondEnd { max_idx:val.len()-1,span: *origin_span });
|
return Err(ShellError::AccessBeyondEnd {
|
||||||
|
max_idx: val.len() - 1,
|
||||||
|
span: *origin_span,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Range { val, .. } => {
|
Value::Range { val, .. } => {
|
||||||
if let Some(item) = val.clone().into_range_iter(None)?.nth(*count) {
|
if let Some(item) = val.into_range_iter(None)?.nth(*count) {
|
||||||
current = item.clone();
|
current = item;
|
||||||
} else if *optional {
|
} else if *optional {
|
||||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::AccessBeyondEndOfStream {
|
return Err(ShellError::AccessBeyondEndOfStream {
|
||||||
span: *origin_span
|
span: *origin_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -988,11 +975,14 @@ impl Value {
|
|||||||
return Err(ShellError::TypeMismatch {
|
return Err(ShellError::TypeMismatch {
|
||||||
err_message:"Can't access record values with a row index. Try specifying a column name instead".into(),
|
err_message:"Can't access record values with a row index. Try specifying a column name instead".into(),
|
||||||
span: *origin_span,
|
span: *origin_span,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
Value::Error { error, .. } => return Err(*error.to_owned()),
|
Value::Error { error, .. } => return Err(*error),
|
||||||
x => {
|
x => {
|
||||||
return Err(ShellError::IncompatiblePathAccess { type_name:format!("{}",x.get_type()), span: *origin_span })
|
return Err(ShellError::IncompatiblePathAccess {
|
||||||
|
type_name: format!("{}", x.get_type()),
|
||||||
|
span: *origin_span,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1003,7 +993,7 @@ impl Value {
|
|||||||
} => {
|
} => {
|
||||||
let span = current.span();
|
let span = current.span();
|
||||||
|
|
||||||
match &mut current {
|
match current {
|
||||||
Value::Record { val, .. } => {
|
Value::Record { val, .. } => {
|
||||||
// Make reverse iterate to avoid duplicate column leads to first value, actually last value is expected.
|
// Make reverse iterate to avoid duplicate column leads to first value, actually last value is expected.
|
||||||
if let Some(found) = val.iter().rev().find(|x| {
|
if let Some(found) = val.iter().rev().find(|x| {
|
||||||
@ -1013,7 +1003,7 @@ impl Value {
|
|||||||
x.0 == column_name
|
x.0 == column_name
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
current = found.1.clone();
|
current = found.1.clone(); // TODO: avoid clone here
|
||||||
} else if *optional {
|
} else if *optional {
|
||||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||||
} else if let Some(suggestion) =
|
} else if let Some(suggestion) =
|
||||||
@ -1022,7 +1012,7 @@ impl Value {
|
|||||||
return Err(ShellError::DidYouMean(suggestion, *origin_span));
|
return Err(ShellError::DidYouMean(suggestion, *origin_span));
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: column_name.to_string(),
|
col_name: column_name.clone(),
|
||||||
span: *origin_span,
|
span: *origin_span,
|
||||||
src_span: span,
|
src_span: span,
|
||||||
});
|
});
|
||||||
@ -1031,15 +1021,21 @@ impl Value {
|
|||||||
Value::LazyRecord { val, .. } => {
|
Value::LazyRecord { val, .. } => {
|
||||||
let columns = val.column_names();
|
let columns = val.column_names();
|
||||||
|
|
||||||
if columns.contains(&column_name.as_str()) {
|
if let Some(col) = columns.iter().rev().find(|&col| {
|
||||||
current = val.get_column_value(column_name)?;
|
if insensitive {
|
||||||
|
col.eq_ignore_case(column_name)
|
||||||
|
} else {
|
||||||
|
col == column_name
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
current = val.get_column_value(col)?;
|
||||||
} else if *optional {
|
} else if *optional {
|
||||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||||
} else if let Some(suggestion) = did_you_mean(&columns, column_name) {
|
} else if let Some(suggestion) = did_you_mean(&columns, column_name) {
|
||||||
return Err(ShellError::DidYouMean(suggestion, *origin_span));
|
return Err(ShellError::DidYouMean(suggestion, *origin_span));
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: column_name.to_string(),
|
col_name: column_name.clone(),
|
||||||
span: *origin_span,
|
span: *origin_span,
|
||||||
src_span: span,
|
src_span: span,
|
||||||
});
|
});
|
||||||
@ -1049,39 +1045,50 @@ impl Value {
|
|||||||
// Create a List which contains each matching value for contained
|
// Create a List which contains each matching value for contained
|
||||||
// records in the source list.
|
// records in the source list.
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
// TODO: this should stream instead of collecting
|
let list = vals
|
||||||
let mut output = vec![];
|
.into_iter()
|
||||||
for val in vals {
|
.map(|val| {
|
||||||
// only look in records; this avoids unintentionally recursing into deeply nested tables
|
let val_span = val.span();
|
||||||
if matches!(val, Value::Record { .. }) {
|
match val {
|
||||||
if let Ok(result) = val.clone().follow_cell_path(
|
Value::Record { val, .. } => {
|
||||||
&[PathMember::String {
|
if let Some(found) = val.iter().rev().find(|x| {
|
||||||
val: column_name.clone(),
|
if insensitive {
|
||||||
span: *origin_span,
|
x.0.eq_ignore_case(column_name)
|
||||||
optional: *optional,
|
|
||||||
}],
|
|
||||||
insensitive,
|
|
||||||
) {
|
|
||||||
output.push(result);
|
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::CantFindColumn {
|
x.0 == column_name
|
||||||
col_name: column_name.to_string(),
|
|
||||||
span: *origin_span,
|
|
||||||
src_span: val.span(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else if *optional && matches!(val, Value::Nothing { .. }) {
|
}) {
|
||||||
output.push(Value::nothing(*origin_span));
|
Ok(found.1.clone()) // TODO: avoid clone here
|
||||||
|
} else if *optional {
|
||||||
|
Ok(Value::nothing(*origin_span))
|
||||||
|
} else if let Some(suggestion) =
|
||||||
|
did_you_mean(val.columns(), column_name)
|
||||||
|
{
|
||||||
|
Err(ShellError::DidYouMean(
|
||||||
|
suggestion,
|
||||||
|
*origin_span,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::CantFindColumn {
|
Err(ShellError::CantFindColumn {
|
||||||
col_name: column_name.to_string(),
|
col_name: column_name.clone(),
|
||||||
span: *origin_span,
|
span: *origin_span,
|
||||||
src_span: val.span(),
|
src_span: val_span,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Value::Nothing { .. } if *optional => {
|
||||||
|
Ok(Value::nothing(*origin_span))
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::CantFindColumn {
|
||||||
|
col_name: column_name.clone(),
|
||||||
|
span: *origin_span,
|
||||||
|
src_span: val_span,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
current = Value::list(output, span);
|
current = Value::list(list, span);
|
||||||
}
|
}
|
||||||
Value::CustomValue { val, .. } => {
|
Value::CustomValue { val, .. } => {
|
||||||
current = val.follow_path_string(column_name.clone(), *origin_span)?;
|
current = val.follow_path_string(column_name.clone(), *origin_span)?;
|
||||||
@ -1089,12 +1096,12 @@ impl Value {
|
|||||||
Value::Nothing { .. } if *optional => {
|
Value::Nothing { .. } if *optional => {
|
||||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||||
}
|
}
|
||||||
Value::Error { error, .. } => return Err(*error.to_owned()),
|
Value::Error { error, .. } => return Err(*error),
|
||||||
x => {
|
x => {
|
||||||
return Err(ShellError::IncompatiblePathAccess {
|
return Err(ShellError::IncompatiblePathAccess {
|
||||||
type_name: format!("{}", x.get_type()),
|
type_name: format!("{}", x.get_type()),
|
||||||
span: *origin_span,
|
span: *origin_span,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1130,8 +1137,8 @@ impl Value {
|
|||||||
cell_path: &[PathMember],
|
cell_path: &[PathMember],
|
||||||
new_val: Value,
|
new_val: Value,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
match cell_path.first() {
|
if let Some((member, path)) = cell_path.split_first() {
|
||||||
Some(path_member) => match path_member {
|
match member {
|
||||||
PathMember::String {
|
PathMember::String {
|
||||||
val: col_name,
|
val: col_name,
|
||||||
span,
|
span,
|
||||||
@ -1141,61 +1148,43 @@ impl Value {
|
|||||||
for val in vals.iter_mut() {
|
for val in vals.iter_mut() {
|
||||||
match val {
|
match val {
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
let mut found = false;
|
if let Some(val) = record.get_mut(col_name) {
|
||||||
for (col, val) in record.iter_mut() {
|
val.upsert_data_at_cell_path(path, new_val.clone())?;
|
||||||
if col == col_name {
|
} else {
|
||||||
found = true;
|
let new_col = if path.is_empty() {
|
||||||
val.upsert_data_at_cell_path(
|
new_val.clone()
|
||||||
&cell_path[1..],
|
|
||||||
new_val.clone(),
|
|
||||||
)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
if cell_path.len() == 1 {
|
|
||||||
record.push(col_name, new_val);
|
|
||||||
break;
|
|
||||||
} else {
|
} else {
|
||||||
let mut new_col =
|
let mut new_col =
|
||||||
Value::record(Record::new(), new_val.span());
|
Value::record(Record::new(), new_val.span());
|
||||||
new_col.upsert_data_at_cell_path(
|
new_col
|
||||||
&cell_path[1..],
|
.upsert_data_at_cell_path(path, new_val.clone())?;
|
||||||
new_val,
|
new_col
|
||||||
)?;
|
};
|
||||||
vals.push(new_col);
|
record.push(col_name, new_col);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Value::Error { error, .. } => return Err(*error.clone()),
|
||||||
Value::Error { error, .. } => return Err(*error.to_owned()),
|
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.to_string(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: *span,
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
let mut found = false;
|
if let Some(val) = record.get_mut(col_name) {
|
||||||
|
val.upsert_data_at_cell_path(path, new_val)?;
|
||||||
for (col, val) in record.iter_mut() {
|
} else {
|
||||||
if col == col_name {
|
let new_col = if path.is_empty() {
|
||||||
found = true;
|
|
||||||
val.upsert_data_at_cell_path(&cell_path[1..], new_val.clone())?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
let new_col = if cell_path.len() == 1 {
|
|
||||||
new_val
|
new_val
|
||||||
} else {
|
} else {
|
||||||
let mut new_col = Value::record(Record::new(), new_val.span());
|
let mut new_col = Value::record(Record::new(), new_val.span());
|
||||||
new_col.upsert_data_at_cell_path(&cell_path[1..], new_val)?;
|
new_col.upsert_data_at_cell_path(path, new_val)?;
|
||||||
new_col
|
new_col
|
||||||
};
|
};
|
||||||
|
|
||||||
record.push(col_name, new_col);
|
record.push(col_name, new_col);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1203,15 +1192,15 @@ impl Value {
|
|||||||
// convert to Record first.
|
// convert to Record first.
|
||||||
let mut record = val.collect()?;
|
let mut record = val.collect()?;
|
||||||
record.upsert_data_at_cell_path(cell_path, new_val)?;
|
record.upsert_data_at_cell_path(cell_path, new_val)?;
|
||||||
*self = record
|
*self = record;
|
||||||
}
|
}
|
||||||
Value::Error { error, .. } => return Err(*error.to_owned()),
|
Value::Error { error, .. } => return Err(*error.clone()),
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.to_string(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: *span,
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PathMember::Int {
|
PathMember::Int {
|
||||||
@ -1219,8 +1208,8 @@ impl Value {
|
|||||||
} => match self {
|
} => match self {
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
if let Some(v) = vals.get_mut(*row_num) {
|
if let Some(v) = vals.get_mut(*row_num) {
|
||||||
v.upsert_data_at_cell_path(&cell_path[1..], new_val)?
|
v.upsert_data_at_cell_path(path, new_val)?;
|
||||||
} else if vals.len() == *row_num && cell_path.len() == 1 {
|
} else if vals.len() == *row_num && path.is_empty() {
|
||||||
// If the upsert is at 1 + the end of the list, it's OK.
|
// If the upsert is at 1 + the end of the list, it's OK.
|
||||||
// Otherwise, it's prohibited.
|
// Otherwise, it's prohibited.
|
||||||
vals.push(new_val);
|
vals.push(new_val);
|
||||||
@ -1231,19 +1220,18 @@ impl Value {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Error { error, .. } => return Err(*error.to_owned()),
|
Value::Error { error, .. } => return Err(*error.clone()),
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::NotAList {
|
return Err(ShellError::NotAList {
|
||||||
dst_span: *span,
|
dst_span: *span,
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
None => {
|
} else {
|
||||||
*self = new_val;
|
*self = new_val;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1259,7 +1247,6 @@ impl Value {
|
|||||||
|
|
||||||
match new_val {
|
match new_val {
|
||||||
Value::Error { error, .. } => Err(*error),
|
Value::Error { error, .. } => Err(*error),
|
||||||
|
|
||||||
new_val => self.update_data_at_cell_path(cell_path, new_val),
|
new_val => self.update_data_at_cell_path(cell_path, new_val),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1270,9 +1257,8 @@ impl Value {
|
|||||||
new_val: Value,
|
new_val: Value,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let v_span = self.span();
|
let v_span = self.span();
|
||||||
|
if let Some((member, path)) = cell_path.split_first() {
|
||||||
match cell_path.first() {
|
match member {
|
||||||
Some(path_member) => match path_member {
|
|
||||||
PathMember::String {
|
PathMember::String {
|
||||||
val: col_name,
|
val: col_name,
|
||||||
span,
|
span,
|
||||||
@ -1283,47 +1269,33 @@ impl Value {
|
|||||||
let v_span = val.span();
|
let v_span = val.span();
|
||||||
match val {
|
match val {
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
let mut found = false;
|
if let Some(val) = record.get_mut(col_name) {
|
||||||
for (col, val) in record.iter_mut() {
|
val.update_data_at_cell_path(path, new_val.clone())?;
|
||||||
if col == col_name {
|
} else {
|
||||||
found = true;
|
|
||||||
val.update_data_at_cell_path(
|
|
||||||
&cell_path[1..],
|
|
||||||
new_val.clone(),
|
|
||||||
)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.to_string(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: *span,
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Error { error, .. } => return Err(*error.to_owned()),
|
Value::Error { error, .. } => return Err(*error.clone()),
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.to_string(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: *span,
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
let mut found = false;
|
if let Some(val) = record.get_mut(col_name) {
|
||||||
|
val.update_data_at_cell_path(path, new_val)?;
|
||||||
for (col, val) in record.iter_mut() {
|
} else {
|
||||||
if col == col_name {
|
|
||||||
found = true;
|
|
||||||
val.update_data_at_cell_path(&cell_path[1..], new_val.clone())?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.to_string(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: *span,
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
@ -1333,15 +1305,15 @@ impl Value {
|
|||||||
// convert to Record first.
|
// convert to Record first.
|
||||||
let mut record = val.collect()?;
|
let mut record = val.collect()?;
|
||||||
record.update_data_at_cell_path(cell_path, new_val)?;
|
record.update_data_at_cell_path(cell_path, new_val)?;
|
||||||
*self = record
|
*self = record;
|
||||||
}
|
}
|
||||||
Value::Error { error, .. } => return Err(*error.to_owned()),
|
Value::Error { error, .. } => return Err(*error.clone()),
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.to_string(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: *span,
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PathMember::Int {
|
PathMember::Int {
|
||||||
@ -1349,7 +1321,7 @@ impl Value {
|
|||||||
} => match self {
|
} => match self {
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
if let Some(v) = vals.get_mut(*row_num) {
|
if let Some(v) = vals.get_mut(*row_num) {
|
||||||
v.update_data_at_cell_path(&cell_path[1..], new_val)?
|
v.update_data_at_cell_path(path, new_val)?;
|
||||||
} else if vals.is_empty() {
|
} else if vals.is_empty() {
|
||||||
return Err(ShellError::AccessEmptyContent { span: *span });
|
return Err(ShellError::AccessEmptyContent { span: *span });
|
||||||
} else {
|
} else {
|
||||||
@ -1359,29 +1331,27 @@ impl Value {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Error { error, .. } => return Err(*error.to_owned()),
|
Value::Error { error, .. } => return Err(*error.clone()),
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::NotAList {
|
return Err(ShellError::NotAList {
|
||||||
dst_span: *span,
|
dst_span: *span,
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
None => {
|
} else {
|
||||||
*self = new_val;
|
*self = new_val;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_data_at_cell_path(&mut self, cell_path: &[PathMember]) -> Result<(), ShellError> {
|
pub fn remove_data_at_cell_path(&mut self, cell_path: &[PathMember]) -> Result<(), ShellError> {
|
||||||
match cell_path.len() {
|
match cell_path {
|
||||||
0 => Ok(()),
|
[] => Ok(()),
|
||||||
1 => {
|
[member] => {
|
||||||
let path_member = cell_path.first().expect("there is a first");
|
|
||||||
let v_span = self.span();
|
let v_span = self.span();
|
||||||
match path_member {
|
match member {
|
||||||
PathMember::String {
|
PathMember::String {
|
||||||
val: col_name,
|
val: col_name,
|
||||||
span,
|
span,
|
||||||
@ -1390,12 +1360,11 @@ impl Value {
|
|||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
for val in vals.iter_mut() {
|
for val in vals.iter_mut() {
|
||||||
let v_span = val.span();
|
let v_span = val.span();
|
||||||
|
|
||||||
match val {
|
match val {
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
if record.remove(col_name).is_none() && !optional {
|
if record.remove(col_name).is_none() && !optional {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.to_string(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: *span,
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
@ -1403,10 +1372,10 @@ impl Value {
|
|||||||
}
|
}
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.to_string(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: *span,
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1415,7 +1384,7 @@ impl Value {
|
|||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
if record.remove(col_name).is_none() && !optional {
|
if record.remove(col_name).is_none() && !optional {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.to_string(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: *span,
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
@ -1430,7 +1399,7 @@ impl Value {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
v => Err(ShellError::CantFindColumn {
|
v => Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.to_string(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: *span,
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
}),
|
}),
|
||||||
@ -1462,10 +1431,9 @@ impl Value {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
[member, path @ ..] => {
|
||||||
let path_member = cell_path.first().expect("there is a first");
|
|
||||||
let v_span = self.span();
|
let v_span = self.span();
|
||||||
match path_member {
|
match member {
|
||||||
PathMember::String {
|
PathMember::String {
|
||||||
val: col_name,
|
val: col_name,
|
||||||
span,
|
span,
|
||||||
@ -1476,16 +1444,11 @@ impl Value {
|
|||||||
let v_span = val.span();
|
let v_span = val.span();
|
||||||
match val {
|
match val {
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
let mut found = false;
|
if let Some(val) = record.get_mut(col_name) {
|
||||||
for (col, val) in record.iter_mut() {
|
val.remove_data_at_cell_path(path)?;
|
||||||
if col == col_name {
|
} else if !optional {
|
||||||
found = true;
|
|
||||||
val.remove_data_at_cell_path(&cell_path[1..])?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found && !optional {
|
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.to_string(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: *span,
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
@ -1493,27 +1456,21 @@ impl Value {
|
|||||||
}
|
}
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.to_string(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: *span,
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
let mut found = false;
|
if let Some(val) = record.get_mut(col_name) {
|
||||||
|
val.remove_data_at_cell_path(path)?;
|
||||||
for (col, val) in record.iter_mut() {
|
} else if !optional {
|
||||||
if col == col_name {
|
|
||||||
found = true;
|
|
||||||
val.remove_data_at_cell_path(&cell_path[1..])?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found && !optional {
|
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.to_string(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: *span,
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
@ -1528,7 +1485,7 @@ impl Value {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
v => Err(ShellError::CantFindColumn {
|
v => Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.to_string(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: *span,
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
}),
|
}),
|
||||||
@ -1540,7 +1497,7 @@ impl Value {
|
|||||||
} => match self {
|
} => match self {
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
if let Some(v) = vals.get_mut(*row_num) {
|
if let Some(v) = vals.get_mut(*row_num) {
|
||||||
v.remove_data_at_cell_path(&cell_path[1..])
|
v.remove_data_at_cell_path(path)
|
||||||
} else if *optional {
|
} else if *optional {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else if vals.is_empty() {
|
} else if vals.is_empty() {
|
||||||
@ -1569,8 +1526,8 @@ impl Value {
|
|||||||
head_span: Span,
|
head_span: Span,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let v_span = self.span();
|
let v_span = self.span();
|
||||||
match cell_path.first() {
|
if let Some((member, path)) = cell_path.split_first() {
|
||||||
Some(path_member) => match path_member {
|
match member {
|
||||||
PathMember::String {
|
PathMember::String {
|
||||||
val: col_name,
|
val: col_name,
|
||||||
span,
|
span,
|
||||||
@ -1581,27 +1538,36 @@ impl Value {
|
|||||||
let v_span = val.span();
|
let v_span = val.span();
|
||||||
match val {
|
match val {
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
for (col, val) in record.iter_mut() {
|
if let Some(val) = record.get_mut(col_name) {
|
||||||
if col == col_name {
|
if path.is_empty() {
|
||||||
if cell_path.len() == 1 {
|
|
||||||
return Err(ShellError::ColumnAlreadyExists {
|
return Err(ShellError::ColumnAlreadyExists {
|
||||||
col_name: col_name.to_string(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: *span,
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return val.insert_data_at_cell_path(
|
val.insert_data_at_cell_path(
|
||||||
&cell_path[1..],
|
path,
|
||||||
new_val,
|
new_val.clone(),
|
||||||
head_span,
|
head_span,
|
||||||
);
|
)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let new_col = if path.is_empty() {
|
||||||
|
new_val.clone()
|
||||||
|
} else {
|
||||||
|
let mut new_col =
|
||||||
|
Value::record(Record::new(), new_val.span());
|
||||||
|
new_col.insert_data_at_cell_path(
|
||||||
|
path,
|
||||||
|
new_val.clone(),
|
||||||
|
head_span,
|
||||||
|
)?;
|
||||||
|
new_col
|
||||||
|
};
|
||||||
|
record.push(col_name, new_col);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
record.push(col_name, new_val.clone());
|
|
||||||
}
|
|
||||||
// SIGH...
|
|
||||||
Value::Error { error, .. } => return Err(*error.clone()),
|
Value::Error { error, .. } => return Err(*error.clone()),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::UnsupportedInput {
|
return Err(ShellError::UnsupportedInput {
|
||||||
@ -1609,37 +1575,42 @@ impl Value {
|
|||||||
input: format!("input type: {:?}", val.get_type()),
|
input: format!("input type: {:?}", val.get_type()),
|
||||||
msg_span: head_span,
|
msg_span: head_span,
|
||||||
input_span: *span,
|
input_span: *span,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
for (col, val) in record.iter_mut() {
|
if let Some(val) = record.get_mut(col_name) {
|
||||||
if col == col_name {
|
if path.is_empty() {
|
||||||
if cell_path.len() == 1 {
|
|
||||||
return Err(ShellError::ColumnAlreadyExists {
|
return Err(ShellError::ColumnAlreadyExists {
|
||||||
col_name: col_name.to_string(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: *span,
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return val.insert_data_at_cell_path(
|
val.insert_data_at_cell_path(path, new_val, head_span)?;
|
||||||
&cell_path[1..],
|
}
|
||||||
new_val,
|
} else {
|
||||||
|
let new_col = if path.is_empty() {
|
||||||
|
new_val.clone()
|
||||||
|
} else {
|
||||||
|
let mut new_col = Value::record(Record::new(), new_val.span());
|
||||||
|
new_col.insert_data_at_cell_path(
|
||||||
|
path,
|
||||||
|
new_val.clone(),
|
||||||
head_span,
|
head_span,
|
||||||
);
|
)?;
|
||||||
|
new_col
|
||||||
|
};
|
||||||
|
record.push(col_name, new_col);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
record.push(col_name, new_val);
|
|
||||||
}
|
|
||||||
Value::LazyRecord { val, .. } => {
|
Value::LazyRecord { val, .. } => {
|
||||||
// convert to Record first.
|
// convert to Record first.
|
||||||
let mut record = val.collect()?;
|
let mut record = val.collect()?;
|
||||||
record.insert_data_at_cell_path(cell_path, new_val, v_span)?;
|
record.insert_data_at_cell_path(cell_path, new_val, v_span)?;
|
||||||
*self = record
|
*self = record;
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedInput {
|
return Err(ShellError::UnsupportedInput {
|
||||||
@ -1647,7 +1618,7 @@ impl Value {
|
|||||||
input: format!("input type: {:?}", other.get_type()),
|
input: format!("input type: {:?}", other.get_type()),
|
||||||
msg_span: head_span,
|
msg_span: head_span,
|
||||||
input_span: *span,
|
input_span: *span,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PathMember::Int {
|
PathMember::Int {
|
||||||
@ -1655,8 +1626,8 @@ impl Value {
|
|||||||
} => match self {
|
} => match self {
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
if let Some(v) = vals.get_mut(*row_num) {
|
if let Some(v) = vals.get_mut(*row_num) {
|
||||||
v.insert_data_at_cell_path(&cell_path[1..], new_val, head_span)?
|
v.insert_data_at_cell_path(path, new_val, head_span)?;
|
||||||
} else if vals.len() == *row_num && cell_path.len() == 1 {
|
} else if vals.len() == *row_num && path.is_empty() {
|
||||||
// If the insert is at 1 + the end of the list, it's OK.
|
// If the insert is at 1 + the end of the list, it's OK.
|
||||||
// Otherwise, it's prohibited.
|
// Otherwise, it's prohibited.
|
||||||
vals.push(new_val);
|
vals.push(new_val);
|
||||||
@ -1671,17 +1642,36 @@ impl Value {
|
|||||||
return Err(ShellError::NotAList {
|
return Err(ShellError::NotAList {
|
||||||
dst_span: *span,
|
dst_span: *span,
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
None => {
|
} else {
|
||||||
*self = new_val;
|
*self = new_val;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if the content is empty
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Value::String { val, .. } => val.is_empty(),
|
||||||
|
Value::List { vals, .. } => vals.is_empty(),
|
||||||
|
Value::Record { val, .. } => val.is_empty(),
|
||||||
|
Value::Binary { val, .. } => val.is_empty(),
|
||||||
|
Value::Nothing { .. } => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_nothing(&self) -> bool {
|
||||||
|
matches!(self, Value::Nothing { .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_error(&self) -> bool {
|
||||||
|
matches!(self, Value::Error { .. })
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_true(&self) -> bool {
|
pub fn is_true(&self) -> bool {
|
||||||
matches!(self, Value::Bool { val: true, .. })
|
matches!(self, Value::Bool { val: true, .. })
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,10 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-std"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-std"
|
name = "nu-std"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
miette = { version = "5.10", features = ["fancy-no-backtrace"] }
|
miette = { version = "5.10", features = ["fancy-no-backtrace"] }
|
||||||
nu-parser = { version = "0.87.1", path = "../nu-parser" }
|
nu-parser = { version = "0.87.2", path = "../nu-parser" }
|
||||||
nu-protocol = { version = "0.87.1", path = "../nu-protocol" }
|
nu-protocol = { version = "0.87.2", path = "../nu-protocol" }
|
||||||
nu-engine = { version = "0.87.1", path = "../nu-engine" }
|
nu-engine = { version = "0.87.2", path = "../nu-engine" }
|
||||||
|
@ -335,3 +335,16 @@ export def repeat [
|
|||||||
|
|
||||||
1..$n | each { $item }
|
1..$n | each { $item }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# return a null device file.
|
||||||
|
#
|
||||||
|
# # Examples
|
||||||
|
# run a command and ignore it's stderr output
|
||||||
|
# > cat xxx.txt e> (null-device)
|
||||||
|
export def null-device []: nothing -> path {
|
||||||
|
if ($nu.os-info.name | str downcase) == "windows" {
|
||||||
|
'\\.\NUL'
|
||||||
|
} else {
|
||||||
|
"/dev/null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@ authors = ["The Nushell Project Developers", "procs creators"]
|
|||||||
description = "Nushell system querying"
|
description = "Nushell system querying"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-system"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-system"
|
||||||
name = "nu-system"
|
name = "nu-system"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ sysinfo = "0.29"
|
|||||||
nix = { version = "0.27", default-features = false, features = ["fs", "term", "process", "signal"] }
|
nix = { version = "0.27", default-features = false, features = ["fs", "term", "process", "signal"] }
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
|
||||||
procfs = "0.15"
|
procfs = "0.16"
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
libproc = "0.14"
|
libproc = "0.14"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use log::info;
|
use log::info;
|
||||||
use procfs::process::{FDInfo, Io, Process, Stat, Status};
|
use procfs::process::{FDInfo, Io, Process, Stat, Status};
|
||||||
use procfs::{ProcError, ProcessCgroup};
|
use procfs::{ProcError, ProcessCGroups, WithCurrentSystemInfo};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@ -25,7 +25,7 @@ impl ProcessTask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cgroups(&self) -> Result<Vec<ProcessCgroup>, ProcError> {
|
pub fn cgroups(&self) -> Result<ProcessCGroups, ProcError> {
|
||||||
match self {
|
match self {
|
||||||
ProcessTask::Process(x) => x.cgroups(),
|
ProcessTask::Process(x) => x.cgroups(),
|
||||||
_ => Err(ProcError::Other("not supported".to_string())),
|
_ => Err(ProcError::Other("not supported".to_string())),
|
||||||
@ -218,7 +218,7 @@ impl ProcessInfo {
|
|||||||
/// Memory size in number of bytes
|
/// Memory size in number of bytes
|
||||||
pub fn mem_size(&self) -> u64 {
|
pub fn mem_size(&self) -> u64 {
|
||||||
match self.curr_proc.stat() {
|
match self.curr_proc.stat() {
|
||||||
Ok(p) => p.rss_bytes(),
|
Ok(p) => p.rss_bytes().get(),
|
||||||
Err(_) => 0,
|
Err(_) => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -969,9 +969,9 @@ fn get_name(psid: PSID) -> Option<(String, String)> {
|
|||||||
let ret = LookupAccountSidW(
|
let ret = LookupAccountSidW(
|
||||||
ptr::null::<u16>() as *mut u16,
|
ptr::null::<u16>() as *mut u16,
|
||||||
psid,
|
psid,
|
||||||
name.as_mut_ptr() as *mut u16,
|
name.as_mut_ptr(),
|
||||||
&mut cc_name,
|
&mut cc_name,
|
||||||
domainname.as_mut_ptr() as *mut u16,
|
domainname.as_mut_ptr(),
|
||||||
&mut cc_domainname,
|
&mut cc_domainname,
|
||||||
&mut pe_use,
|
&mut pe_use,
|
||||||
);
|
);
|
||||||
|
@ -5,20 +5,20 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-table"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-table"
|
name = "nu-table"
|
||||||
version = "0.87.1"
|
version = "0.87.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.87.1" }
|
nu-utils = { path = "../nu-utils", version = "0.87.2" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.87.1" }
|
nu-engine = { path = "../nu-engine", version = "0.87.2" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.87.1" }
|
nu-color-config = { path = "../nu-color-config", version = "0.87.2" }
|
||||||
nu-ansi-term = "0.49.0"
|
nu-ansi-term = "0.49.0"
|
||||||
once_cell = "1.18"
|
once_cell = "1.18"
|
||||||
fancy-regex = "0.11"
|
fancy-regex = "0.11"
|
||||||
tabled = { version = "0.14.0", features = ["color"], default-features = false }
|
tabled = { version = "0.14.0", features = ["color"], default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# nu-test-support = { path="../nu-test-support", version = "0.87.1" }
|
# nu-test-support = { path="../nu-test-support", version = "0.87.2" }
|
||||||
|
@ -17,9 +17,10 @@ pub fn create_nu_table_config(
|
|||||||
comp: &StyleComputer,
|
comp: &StyleComputer,
|
||||||
out: &TableOutput,
|
out: &TableOutput,
|
||||||
expand: bool,
|
expand: bool,
|
||||||
|
mode: TableMode,
|
||||||
) -> NuTableConfig {
|
) -> NuTableConfig {
|
||||||
NuTableConfig {
|
NuTableConfig {
|
||||||
theme: load_theme_from_config(config),
|
theme: load_theme(mode),
|
||||||
with_footer: with_footer(config, out.with_header, out.table.count_rows()),
|
with_footer: with_footer(config, out.with_header, out.table.count_rows()),
|
||||||
with_index: out.with_index,
|
with_index: out.with_index,
|
||||||
with_header: out.with_header,
|
with_header: out.with_header,
|
||||||
@ -173,8 +174,8 @@ fn is_cfg_trim_keep_words(config: &Config) -> bool {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_theme_from_config(config: &Config) -> TableTheme {
|
pub fn load_theme(mode: TableMode) -> TableTheme {
|
||||||
match config.table_mode {
|
match mode {
|
||||||
TableMode::Basic => TableTheme::basic(),
|
TableMode::Basic => TableTheme::basic(),
|
||||||
TableMode::Thin => TableTheme::thin(),
|
TableMode::Thin => TableTheme::thin(),
|
||||||
TableMode::Light => TableTheme::light(),
|
TableMode::Light => TableTheme::light(),
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use nu_color_config::StyleComputer;
|
use nu_color_config::StyleComputer;
|
||||||
use nu_protocol::{Config, Record, Value};
|
use nu_protocol::{Config, Record, TableMode, Value};
|
||||||
|
|
||||||
use crate::UnstructuredTable;
|
use crate::UnstructuredTable;
|
||||||
|
|
||||||
use crate::common::nu_value_to_string_clean;
|
use crate::common::nu_value_to_string_clean;
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{get_index_style, load_theme_from_config},
|
common::{get_index_style, load_theme},
|
||||||
StringResult, TableOpts,
|
StringResult, TableOpts,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -13,7 +13,13 @@ pub struct CollapsedTable;
|
|||||||
|
|
||||||
impl CollapsedTable {
|
impl CollapsedTable {
|
||||||
pub fn build(value: Value, opts: TableOpts<'_>) -> StringResult {
|
pub fn build(value: Value, opts: TableOpts<'_>) -> StringResult {
|
||||||
collapsed_table(value, opts.config, opts.width, opts.style_computer)
|
collapsed_table(
|
||||||
|
value,
|
||||||
|
opts.config,
|
||||||
|
opts.width,
|
||||||
|
opts.style_computer,
|
||||||
|
opts.mode,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,10 +28,11 @@ fn collapsed_table(
|
|||||||
config: &Config,
|
config: &Config,
|
||||||
term_width: usize,
|
term_width: usize,
|
||||||
style_computer: &StyleComputer,
|
style_computer: &StyleComputer,
|
||||||
|
mode: TableMode,
|
||||||
) -> StringResult {
|
) -> StringResult {
|
||||||
colorize_value(&mut value, config, style_computer);
|
colorize_value(&mut value, config, style_computer);
|
||||||
|
|
||||||
let theme = load_theme_from_config(config);
|
let theme = load_theme(mode);
|
||||||
let mut table = UnstructuredTable::new(value, config);
|
let mut table = UnstructuredTable::new(value, config);
|
||||||
let is_empty = table.truncate(&theme, term_width);
|
let is_empty = table.truncate(&theme, term_width);
|
||||||
if is_empty {
|
if is_empty {
|
||||||
|
@ -3,17 +3,18 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use nu_color_config::{Alignment, StyleComputer, TextStyle};
|
use nu_color_config::{Alignment, StyleComputer, TextStyle};
|
||||||
use nu_engine::column::get_columns;
|
use nu_engine::column::get_columns;
|
||||||
use nu_protocol::{Config, Record, ShellError, Span, TableIndexMode, Value};
|
use nu_protocol::{Config, Record, ShellError, Span, Value};
|
||||||
use tabled::grid::config::Position;
|
use tabled::grid::config::Position;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{
|
common::{
|
||||||
create_nu_table_config, error_sign, get_header_style, get_index_style,
|
create_nu_table_config, error_sign, get_header_style, get_index_style, load_theme,
|
||||||
load_theme_from_config, nu_value_to_string, nu_value_to_string_clean,
|
nu_value_to_string, nu_value_to_string_clean, nu_value_to_string_colored, wrap_text,
|
||||||
nu_value_to_string_colored, wrap_text, NuText, StringResult, TableResult,
|
NuText, StringResult, TableResult, INDEX_COLUMN_NAME,
|
||||||
INDEX_COLUMN_NAME,
|
|
||||||
},
|
},
|
||||||
string_width, NuTable, NuTableCell, TableOpts, TableOutput,
|
string_width,
|
||||||
|
types::has_index,
|
||||||
|
NuTable, NuTableCell, TableOpts, TableOutput,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -83,11 +84,8 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
|
|
||||||
let headers = get_columns(input);
|
let headers = get_columns(input);
|
||||||
|
|
||||||
let with_index = match cfg.opts.config.table_index_mode {
|
let with_index = has_index(&cfg.opts, &headers);
|
||||||
TableIndexMode::Always => true,
|
let row_offset = cfg.opts.index_offset;
|
||||||
TableIndexMode::Never => false,
|
|
||||||
TableIndexMode::Auto => headers.iter().any(|header| header == INDEX_COLUMN_NAME),
|
|
||||||
};
|
|
||||||
|
|
||||||
// The header with the INDEX is removed from the table headers since
|
// The header with the INDEX is removed from the table headers since
|
||||||
// it is added to the natural table index
|
// it is added to the natural table index
|
||||||
@ -115,7 +113,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
return Err(*error.clone());
|
return Err(*error.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let index = row + cfg.opts.row_offset;
|
let index = row + row_offset;
|
||||||
let text = item
|
let text = item
|
||||||
.as_record()
|
.as_record()
|
||||||
.ok()
|
.ok()
|
||||||
@ -156,8 +154,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
return Err(*error.clone());
|
return Err(*error.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut inner_cfg = cfg.clone();
|
let inner_cfg = update_config(cfg.clone(), available_width);
|
||||||
inner_cfg.opts.width = available_width;
|
|
||||||
let (mut text, style) = expanded_table_entry2(item, inner_cfg);
|
let (mut text, style) = expanded_table_entry2(item, inner_cfg);
|
||||||
|
|
||||||
let value_width = string_width(&text);
|
let value_width = string_width(&text);
|
||||||
@ -244,8 +241,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
return Err(*error.clone());
|
return Err(*error.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut inner_cfg = cfg.clone();
|
let inner_cfg = update_config(cfg.clone(), available);
|
||||||
inner_cfg.opts.width = available;
|
|
||||||
let (mut text, style) = expanded_table_entry(item, header.as_str(), inner_cfg);
|
let (mut text, style) = expanded_table_entry(item, header.as_str(), inner_cfg);
|
||||||
|
|
||||||
let mut value_width = string_width(&text);
|
let mut value_width = string_width(&text);
|
||||||
@ -343,7 +339,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> StringResult {
|
fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> StringResult {
|
||||||
let theme = load_theme_from_config(cfg.opts.config);
|
let theme = load_theme(cfg.opts.mode);
|
||||||
let key_width = record
|
let key_width = record
|
||||||
.columns()
|
.columns()
|
||||||
.map(|col| string_width(col))
|
.map(|col| string_width(col))
|
||||||
@ -407,8 +403,7 @@ fn expand_table_value(
|
|||||||
let span = value.span();
|
let span = value.span();
|
||||||
match value {
|
match value {
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
let mut inner_cfg = dive_options(cfg, span);
|
let inner_cfg = update_config(dive_options(cfg, span), value_width);
|
||||||
inner_cfg.opts.width = value_width;
|
|
||||||
let table = expanded_table_list(vals, inner_cfg)?;
|
let table = expanded_table_list(vals, inner_cfg)?;
|
||||||
|
|
||||||
match table {
|
match table {
|
||||||
@ -438,8 +433,7 @@ fn expand_table_value(
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut inner_cfg = dive_options(cfg, span);
|
let inner_cfg = update_config(dive_options(cfg, span), value_width);
|
||||||
inner_cfg.opts.width = value_width;
|
|
||||||
let result = expanded_table_kv(record, inner_cfg)?;
|
let result = expanded_table_kv(record, inner_cfg)?;
|
||||||
match result {
|
match result {
|
||||||
Some(result) => Ok(Some((result, true))),
|
Some(result) => Ok(Some((result, true))),
|
||||||
@ -556,7 +550,8 @@ fn dive_options<'b>(cfg: &Cfg<'b>, span: Span) -> Cfg<'b> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn maybe_expand_table(out: TableOutput, term_width: usize, opts: &TableOpts<'_>) -> StringResult {
|
fn maybe_expand_table(out: TableOutput, term_width: usize, opts: &TableOpts<'_>) -> StringResult {
|
||||||
let mut table_config = create_nu_table_config(opts.config, opts.style_computer, &out, false);
|
let mut table_config =
|
||||||
|
create_nu_table_config(opts.config, opts.style_computer, &out, false, opts.mode);
|
||||||
let total_width = out.table.total_width(&table_config);
|
let total_width = out.table.total_width(&table_config);
|
||||||
if total_width < term_width {
|
if total_width < term_width {
|
||||||
const EXPAND_THRESHOLD: f32 = 0.80;
|
const EXPAND_THRESHOLD: f32 = 0.80;
|
||||||
@ -577,7 +572,13 @@ fn set_data_styles(table: &mut NuTable, styles: HashMap<Position, TextStyle>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_table_cfg(cfg: &Cfg<'_>, out: &TableOutput) -> crate::NuTableConfig {
|
fn create_table_cfg(cfg: &Cfg<'_>, out: &TableOutput) -> crate::NuTableConfig {
|
||||||
create_nu_table_config(cfg.opts.config, cfg.opts.style_computer, out, false)
|
create_nu_table_config(
|
||||||
|
cfg.opts.config,
|
||||||
|
cfg.opts.style_computer,
|
||||||
|
out,
|
||||||
|
false,
|
||||||
|
cfg.opts.mode,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value_to_string(value: &Value, cfg: &Cfg<'_>) -> String {
|
fn value_to_string(value: &Value, cfg: &Cfg<'_>) -> String {
|
||||||
@ -596,3 +597,10 @@ fn value_to_wrapped_string_clean(value: &Value, cfg: &Cfg<'_>, value_width: usiz
|
|||||||
let text = nu_value_to_string_colored(value, cfg.opts.config, cfg.opts.style_computer);
|
let text = nu_value_to_string_colored(value, cfg.opts.config, cfg.opts.style_computer);
|
||||||
wrap_text(&text, value_width, cfg.opts.config)
|
wrap_text(&text, value_width, cfg.opts.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_config(cfg: Cfg<'_>, width: usize) -> Cfg<'_> {
|
||||||
|
let mut inner_cfg = cfg.clone();
|
||||||
|
inner_cfg.opts.width = width;
|
||||||
|
inner_cfg.opts.index_offset = 0;
|
||||||
|
inner_cfg
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use nu_color_config::TextStyle;
|
use nu_color_config::TextStyle;
|
||||||
use nu_engine::column::get_columns;
|
use nu_engine::column::get_columns;
|
||||||
use nu_protocol::{Config, Record, ShellError, TableIndexMode, Value};
|
use nu_protocol::{Config, Record, ShellError, Value};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
clean_charset, colorize_space,
|
clean_charset, colorize_space,
|
||||||
@ -11,6 +11,8 @@ use crate::{
|
|||||||
NuTable, NuTableCell, StringResult, TableOpts, TableOutput, TableResult,
|
NuTable, NuTableCell, StringResult, TableOpts, TableOutput, TableResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::has_index;
|
||||||
|
|
||||||
pub struct JustTable;
|
pub struct JustTable;
|
||||||
|
|
||||||
impl JustTable {
|
impl JustTable {
|
||||||
@ -24,7 +26,7 @@ impl JustTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, ShellError> {
|
fn create_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, ShellError> {
|
||||||
match table(input, opts.row_offset, opts.clone())? {
|
match table(input, &opts)? {
|
||||||
Some(mut out) => {
|
Some(mut out) => {
|
||||||
let left = opts.config.table_indent.left;
|
let left = opts.config.table_indent.left;
|
||||||
let right = opts.config.table_indent.right;
|
let right = opts.config.table_indent.right;
|
||||||
@ -33,7 +35,7 @@ fn create_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>,
|
|||||||
colorize_space(out.table.get_records_mut(), opts.style_computer);
|
colorize_space(out.table.get_records_mut(), opts.style_computer);
|
||||||
|
|
||||||
let table_config =
|
let table_config =
|
||||||
create_nu_table_config(opts.config, opts.style_computer, &out, false);
|
create_nu_table_config(opts.config, opts.style_computer, &out, false, opts.mode);
|
||||||
Ok(out.table.draw(table_config, opts.width))
|
Ok(out.table.draw(table_config, opts.width))
|
||||||
}
|
}
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
@ -65,23 +67,21 @@ fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
|
|||||||
let right = opts.config.table_indent.right;
|
let right = opts.config.table_indent.right;
|
||||||
out.table.set_indent(left, right);
|
out.table.set_indent(left, right);
|
||||||
|
|
||||||
let table_config = create_nu_table_config(opts.config, opts.style_computer, &out, false);
|
let table_config =
|
||||||
|
create_nu_table_config(opts.config, opts.style_computer, &out, false, opts.mode);
|
||||||
let table = out.table.draw(table_config, opts.width);
|
let table = out.table.draw(table_config, opts.width);
|
||||||
|
|
||||||
Ok(table)
|
Ok(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table(input: &[Value], row_offset: usize, opts: TableOpts<'_>) -> TableResult {
|
fn table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
|
||||||
if input.is_empty() {
|
if input.is_empty() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut headers = get_columns(input);
|
let mut headers = get_columns(input);
|
||||||
let with_index = match opts.config.table_index_mode {
|
let with_index = has_index(opts, &headers);
|
||||||
TableIndexMode::Always => true,
|
let row_offset = opts.index_offset;
|
||||||
TableIndexMode::Never => false,
|
|
||||||
TableIndexMode::Auto => headers.iter().any(|header| header == INDEX_COLUMN_NAME),
|
|
||||||
};
|
|
||||||
|
|
||||||
let with_header = !headers.is_empty();
|
let with_header = !headers.is_empty();
|
||||||
if !with_header {
|
if !with_header {
|
||||||
@ -112,7 +112,7 @@ fn to_table_with_header(
|
|||||||
headers: Vec<String>,
|
headers: Vec<String>,
|
||||||
with_index: bool,
|
with_index: bool,
|
||||||
row_offset: usize,
|
row_offset: usize,
|
||||||
opts: TableOpts<'_>,
|
opts: &TableOpts<'_>,
|
||||||
) -> Result<Option<NuTable>, ShellError> {
|
) -> Result<Option<NuTable>, ShellError> {
|
||||||
let count_rows = input.len() + 1;
|
let count_rows = input.len() + 1;
|
||||||
let count_columns = headers.len();
|
let count_columns = headers.len();
|
||||||
@ -140,7 +140,7 @@ fn to_table_with_header(
|
|||||||
|
|
||||||
let skip_index = usize::from(with_index);
|
let skip_index = usize::from(with_index);
|
||||||
for (col, header) in headers.iter().enumerate().skip(skip_index) {
|
for (col, header) in headers.iter().enumerate().skip(skip_index) {
|
||||||
let (text, style) = get_string_value_with_header(item, header, &opts);
|
let (text, style) = get_string_value_with_header(item, header, opts);
|
||||||
|
|
||||||
table.insert((row + 1, col), text);
|
table.insert((row + 1, col), text);
|
||||||
table.insert_style((row + 1, col), style);
|
table.insert_style((row + 1, col), style);
|
||||||
@ -154,7 +154,7 @@ fn to_table_with_no_header(
|
|||||||
input: &[Value],
|
input: &[Value],
|
||||||
with_index: bool,
|
with_index: bool,
|
||||||
row_offset: usize,
|
row_offset: usize,
|
||||||
opts: TableOpts<'_>,
|
opts: &TableOpts<'_>,
|
||||||
) -> Result<Option<NuTable>, ShellError> {
|
) -> Result<Option<NuTable>, ShellError> {
|
||||||
let mut table = NuTable::new(input.len(), with_index as usize + 1);
|
let mut table = NuTable::new(input.len(), with_index as usize + 1);
|
||||||
table.set_index_style(get_index_style(opts.style_computer));
|
table.set_index_style(get_index_style(opts.style_computer));
|
||||||
@ -173,7 +173,7 @@ fn to_table_with_no_header(
|
|||||||
table.insert((row, 0), text);
|
table.insert((row, 0), text);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (text, style) = get_string_value(item, &opts);
|
let (text, style) = get_string_value(item, opts);
|
||||||
|
|
||||||
let pos = (row, with_index as usize);
|
let pos = (row, with_index as usize);
|
||||||
table.insert(pos, text);
|
table.insert(pos, text);
|
||||||
|
@ -8,9 +8,9 @@ pub use collapse::CollapsedTable;
|
|||||||
pub use expanded::ExpandedTable;
|
pub use expanded::ExpandedTable;
|
||||||
pub use general::JustTable;
|
pub use general::JustTable;
|
||||||
use nu_color_config::StyleComputer;
|
use nu_color_config::StyleComputer;
|
||||||
use nu_protocol::{Config, Span};
|
use nu_protocol::{Config, Span, TableIndexMode, TableMode};
|
||||||
|
|
||||||
use crate::NuTable;
|
use crate::{common::INDEX_COLUMN_NAME, NuTable};
|
||||||
|
|
||||||
pub struct TableOutput {
|
pub struct TableOutput {
|
||||||
pub table: NuTable,
|
pub table: NuTable,
|
||||||
@ -34,29 +34,46 @@ pub struct TableOpts<'a> {
|
|||||||
config: &'a Config,
|
config: &'a Config,
|
||||||
style_computer: &'a StyleComputer<'a>,
|
style_computer: &'a StyleComputer<'a>,
|
||||||
span: Span,
|
span: Span,
|
||||||
row_offset: usize,
|
|
||||||
width: usize,
|
width: usize,
|
||||||
indent: (usize, usize),
|
indent: (usize, usize),
|
||||||
|
mode: TableMode,
|
||||||
|
index_offset: usize,
|
||||||
|
index_remove: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TableOpts<'a> {
|
impl<'a> TableOpts<'a> {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
config: &'a Config,
|
config: &'a Config,
|
||||||
style_computer: &'a StyleComputer<'a>,
|
style_computer: &'a StyleComputer<'a>,
|
||||||
ctrlc: Option<Arc<AtomicBool>>,
|
ctrlc: Option<Arc<AtomicBool>>,
|
||||||
span: Span,
|
span: Span,
|
||||||
row_offset: usize,
|
|
||||||
width: usize,
|
width: usize,
|
||||||
indent: (usize, usize),
|
indent: (usize, usize),
|
||||||
|
mode: TableMode,
|
||||||
|
index_offset: usize,
|
||||||
|
index_remove: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ctrlc,
|
ctrlc,
|
||||||
config,
|
config,
|
||||||
style_computer,
|
style_computer,
|
||||||
span,
|
span,
|
||||||
row_offset,
|
|
||||||
indent,
|
indent,
|
||||||
width,
|
width,
|
||||||
|
mode,
|
||||||
|
index_offset,
|
||||||
|
index_remove,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_index(opts: &TableOpts<'_>, headers: &[String]) -> bool {
|
||||||
|
let with_index = match opts.config.table_index_mode {
|
||||||
|
TableIndexMode::Always => true,
|
||||||
|
TableIndexMode::Never => false,
|
||||||
|
TableIndexMode::Auto => headers.iter().any(|header| header == INDEX_COLUMN_NAME),
|
||||||
|
};
|
||||||
|
|
||||||
|
with_index && !opts.index_remove
|
||||||
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user