mirror of
https://github.com/nushell/nushell.git
synced 2024-11-22 00:13:21 +01:00
Merge branch 'main' into path_insensitive
This commit is contained in:
commit
31d3e37e3d
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -3333,7 +3333,7 @@ dependencies = [
|
|||||||
"sysinfo 0.32.0",
|
"sysinfo 0.32.0",
|
||||||
"tabled",
|
"tabled",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"terminal_size 0.3.0",
|
"terminal_size 0.4.0",
|
||||||
"titlecase",
|
"titlecase",
|
||||||
"toml 0.8.19",
|
"toml 0.8.19",
|
||||||
"trash",
|
"trash",
|
||||||
@ -3378,7 +3378,7 @@ dependencies = [
|
|||||||
"nu-path",
|
"nu-path",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
"nu-utils",
|
"nu-utils",
|
||||||
"terminal_size 0.3.0",
|
"terminal_size 0.4.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3402,7 +3402,7 @@ dependencies = [
|
|||||||
"nu-utils",
|
"nu-utils",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"strip-ansi-escapes",
|
"strip-ansi-escapes",
|
||||||
"terminal_size 0.3.0",
|
"terminal_size 0.4.0",
|
||||||
"unicode-width 0.1.11",
|
"unicode-width 0.1.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3485,7 +3485,7 @@ dependencies = [
|
|||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
"nu-utils",
|
"nu-utils",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 1.0.69",
|
"thiserror 2.0.3",
|
||||||
"typetag",
|
"typetag",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3591,7 +3591,7 @@ dependencies = [
|
|||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 1.0.69",
|
"thiserror 2.0.3",
|
||||||
"typetag",
|
"typetag",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
@ -3635,7 +3635,7 @@ dependencies = [
|
|||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
"nu-utils",
|
"nu-utils",
|
||||||
"tabled",
|
"tabled",
|
||||||
"terminal_size 0.3.0",
|
"terminal_size 0.4.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5893,9 +5893,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shadow-rs"
|
name = "shadow-rs"
|
||||||
version = "0.35.2"
|
version = "0.36.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f1b2328fb3ec0d5302f95915e7e77cfc2ff943714d9970bc4b66e9eacf318687"
|
checksum = "58cfcd0643497a9f780502063aecbcc4a3212cbe4948fd25ee8fd179c2cf9a18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const_format",
|
"const_format",
|
||||||
"is_debug",
|
"is_debug",
|
||||||
|
@ -156,7 +156,7 @@ syn = "2.0"
|
|||||||
sysinfo = "0.32"
|
sysinfo = "0.32"
|
||||||
tabled = { version = "0.16.0", default-features = false }
|
tabled = { version = "0.16.0", default-features = false }
|
||||||
tempfile = "3.14"
|
tempfile = "3.14"
|
||||||
terminal_size = "0.3"
|
terminal_size = "0.4"
|
||||||
titlecase = "2.0"
|
titlecase = "2.0"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
trash = "5.2"
|
trash = "5.2"
|
||||||
|
@ -74,7 +74,7 @@ pub fn evaluate_commands(
|
|||||||
|
|
||||||
if let Some(err) = working_set.compile_errors.first() {
|
if let Some(err) = working_set.compile_errors.first() {
|
||||||
report_compile_error(&working_set, err);
|
report_compile_error(&working_set, err);
|
||||||
// Not a fatal error, for now
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
(output, working_set.render())
|
(output, working_set.render())
|
||||||
|
@ -89,7 +89,7 @@ pub fn evaluate_file(
|
|||||||
|
|
||||||
if let Some(err) = working_set.compile_errors.first() {
|
if let Some(err) = working_set.compile_errors.first() {
|
||||||
report_compile_error(&working_set, err);
|
report_compile_error(&working_set, err);
|
||||||
// Not a fatal error, for now
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for blocks whose name starts with "main" and replace it with the filename.
|
// Look for blocks whose name starts with "main" and replace it with the filename.
|
||||||
|
@ -296,7 +296,7 @@ fn evaluate_source(
|
|||||||
|
|
||||||
if let Some(err) = working_set.compile_errors.first() {
|
if let Some(err) = working_set.compile_errors.first() {
|
||||||
report_compile_error(&working_set, err);
|
report_compile_error(&working_set, err);
|
||||||
// Not a fatal error, for now
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
(output, working_set.render())
|
(output, working_set.render())
|
||||||
|
@ -21,10 +21,10 @@ nu-protocol = { path = "../nu-protocol", version = "0.100.1" }
|
|||||||
nu-utils = { path = "../nu-utils", version = "0.100.1" }
|
nu-utils = { path = "../nu-utils", version = "0.100.1" }
|
||||||
|
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
shadow-rs = { version = "0.35", default-features = false }
|
shadow-rs = { version = "0.36", default-features = false }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = { version = "0.35", default-features = false }
|
shadow-rs = { version = "0.36", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
mimalloc = []
|
mimalloc = []
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_parser::parse;
|
use nu_parser::{flatten_block, parse};
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
use nu_protocol::{engine::StateWorkingSet, record};
|
||||||
|
use serde_json::{json, Value as JsonValue};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Ast;
|
pub struct Ast;
|
||||||
@ -16,109 +17,23 @@ impl Command for Ast {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("ast")
|
Signature::build("ast")
|
||||||
.input_output_types(vec![(Type::String, Type::record())])
|
.input_output_types(vec![
|
||||||
|
(Type::Nothing, Type::table()),
|
||||||
|
(Type::Nothing, Type::record()),
|
||||||
|
(Type::Nothing, Type::String),
|
||||||
|
])
|
||||||
.required(
|
.required(
|
||||||
"pipeline",
|
"pipeline",
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
"The pipeline to print the ast for.",
|
"The pipeline to print the ast for.",
|
||||||
)
|
)
|
||||||
.switch("json", "serialize to json", Some('j'))
|
.switch("json", "Serialize to json", Some('j'))
|
||||||
.switch("minify", "minify the nuon or json output", Some('m'))
|
.switch("minify", "Minify the nuon or json output", Some('m'))
|
||||||
|
.switch("flatten", "An easier to read version of the ast", Some('f'))
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.category(Category::Debug)
|
.category(Category::Debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let pipeline: Spanned<String> = call.req(engine_state, stack, 0)?;
|
|
||||||
let to_json = call.has_flag(engine_state, stack, "json")?;
|
|
||||||
let minify = call.has_flag(engine_state, stack, "minify")?;
|
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
|
||||||
let block_output = parse(&mut working_set, None, pipeline.item.as_bytes(), false);
|
|
||||||
let error_output = working_set.parse_errors.first();
|
|
||||||
let block_span = match &block_output.span {
|
|
||||||
Some(span) => span,
|
|
||||||
None => &pipeline.span,
|
|
||||||
};
|
|
||||||
if to_json {
|
|
||||||
// Get the block as json
|
|
||||||
let serde_block_str = if minify {
|
|
||||||
serde_json::to_string(&*block_output)
|
|
||||||
} else {
|
|
||||||
serde_json::to_string_pretty(&*block_output)
|
|
||||||
};
|
|
||||||
let block_json = match serde_block_str {
|
|
||||||
Ok(json) => json,
|
|
||||||
Err(e) => Err(ShellError::CantConvert {
|
|
||||||
to_type: "string".to_string(),
|
|
||||||
from_type: "block".to_string(),
|
|
||||||
span: *block_span,
|
|
||||||
help: Some(format!(
|
|
||||||
"Error: {e}\nCan't convert {block_output:?} to string"
|
|
||||||
)),
|
|
||||||
})?,
|
|
||||||
};
|
|
||||||
// Get the error as json
|
|
||||||
let serde_error_str = if minify {
|
|
||||||
serde_json::to_string(&error_output)
|
|
||||||
} else {
|
|
||||||
serde_json::to_string_pretty(&error_output)
|
|
||||||
};
|
|
||||||
|
|
||||||
let error_json = match serde_error_str {
|
|
||||||
Ok(json) => json,
|
|
||||||
Err(e) => Err(ShellError::CantConvert {
|
|
||||||
to_type: "string".to_string(),
|
|
||||||
from_type: "error".to_string(),
|
|
||||||
span: *block_span,
|
|
||||||
help: Some(format!(
|
|
||||||
"Error: {e}\nCan't convert {error_output:?} to string"
|
|
||||||
)),
|
|
||||||
})?,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a new output record, merging the block and error
|
|
||||||
let output_record = Value::record(
|
|
||||||
record! {
|
|
||||||
"block" => Value::string(block_json, *block_span),
|
|
||||||
"error" => Value::string(error_json, Span::test_data()),
|
|
||||||
},
|
|
||||||
pipeline.span,
|
|
||||||
);
|
|
||||||
Ok(output_record.into_pipeline_data())
|
|
||||||
} else {
|
|
||||||
let block_value = Value::string(
|
|
||||||
if minify {
|
|
||||||
format!("{block_output:?}")
|
|
||||||
} else {
|
|
||||||
format!("{block_output:#?}")
|
|
||||||
},
|
|
||||||
pipeline.span,
|
|
||||||
);
|
|
||||||
let error_value = Value::string(
|
|
||||||
if minify {
|
|
||||||
format!("{error_output:?}")
|
|
||||||
} else {
|
|
||||||
format!("{error_output:#?}")
|
|
||||||
},
|
|
||||||
pipeline.span,
|
|
||||||
);
|
|
||||||
let output_record = Value::record(
|
|
||||||
record! {
|
|
||||||
"block" => block_value,
|
|
||||||
"error" => error_value
|
|
||||||
},
|
|
||||||
pipeline.span,
|
|
||||||
);
|
|
||||||
Ok(output_record.into_pipeline_data())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
@ -147,8 +62,247 @@ impl Command for Ast {
|
|||||||
example: "ast 'for x in 1..10 { echo $x ' --json --minify",
|
example: "ast 'for x in 1..10 { echo $x ' --json --minify",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Print the ast of a string flattened",
|
||||||
|
example: r#"ast "'hello'" --flatten"#,
|
||||||
|
result: Some(Value::test_list(vec![Value::test_record(record! {
|
||||||
|
"content" => Value::test_string("'hello'"),
|
||||||
|
"shape" => Value::test_string("shape_string"),
|
||||||
|
"span" => Value::test_record(record! {
|
||||||
|
"start" => Value::test_int(0),
|
||||||
|
"end" => Value::test_int(7),}),
|
||||||
|
})])),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Print the ast of a string flattened, as json, minified",
|
||||||
|
example: r#"ast "'hello'" --flatten --json --minify"#,
|
||||||
|
result: Some(Value::test_string(
|
||||||
|
r#"[{"content":"'hello'","shape":"shape_string","span":{"start":0,"end":7}}]"#,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Print the ast of a pipeline flattened",
|
||||||
|
example: r#"ast 'ls | sort-by type name -i' --flatten"#,
|
||||||
|
result: Some(Value::test_list(vec![
|
||||||
|
Value::test_record(record! {
|
||||||
|
"content" => Value::test_string("ls"),
|
||||||
|
"shape" => Value::test_string("shape_external"),
|
||||||
|
"span" => Value::test_record(record! {
|
||||||
|
"start" => Value::test_int(0),
|
||||||
|
"end" => Value::test_int(2),}),
|
||||||
|
}),
|
||||||
|
Value::test_record(record! {
|
||||||
|
"content" => Value::test_string("|"),
|
||||||
|
"shape" => Value::test_string("shape_pipe"),
|
||||||
|
"span" => Value::test_record(record! {
|
||||||
|
"start" => Value::test_int(3),
|
||||||
|
"end" => Value::test_int(4),}),
|
||||||
|
}),
|
||||||
|
Value::test_record(record! {
|
||||||
|
"content" => Value::test_string("sort-by"),
|
||||||
|
"shape" => Value::test_string("shape_internalcall"),
|
||||||
|
"span" => Value::test_record(record! {
|
||||||
|
"start" => Value::test_int(5),
|
||||||
|
"end" => Value::test_int(12),}),
|
||||||
|
}),
|
||||||
|
Value::test_record(record! {
|
||||||
|
"content" => Value::test_string("type"),
|
||||||
|
"shape" => Value::test_string("shape_string"),
|
||||||
|
"span" => Value::test_record(record! {
|
||||||
|
"start" => Value::test_int(13),
|
||||||
|
"end" => Value::test_int(17),}),
|
||||||
|
}),
|
||||||
|
Value::test_record(record! {
|
||||||
|
"content" => Value::test_string("name"),
|
||||||
|
"shape" => Value::test_string("shape_string"),
|
||||||
|
"span" => Value::test_record(record! {
|
||||||
|
"start" => Value::test_int(18),
|
||||||
|
"end" => Value::test_int(22),}),
|
||||||
|
}),
|
||||||
|
Value::test_record(record! {
|
||||||
|
"content" => Value::test_string("-i"),
|
||||||
|
"shape" => Value::test_string("shape_flag"),
|
||||||
|
"span" => Value::test_record(record! {
|
||||||
|
"start" => Value::test_int(23),
|
||||||
|
"end" => Value::test_int(25),}),
|
||||||
|
}),
|
||||||
|
])),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let pipeline: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||||
|
let to_json = call.has_flag(engine_state, stack, "json")?;
|
||||||
|
let minify = call.has_flag(engine_state, stack, "minify")?;
|
||||||
|
let flatten = call.has_flag(engine_state, stack, "flatten")?;
|
||||||
|
|
||||||
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
let offset = working_set.next_span_start();
|
||||||
|
let parsed_block = parse(&mut working_set, None, pipeline.item.as_bytes(), false);
|
||||||
|
|
||||||
|
if flatten {
|
||||||
|
let flat = flatten_block(&working_set, &parsed_block);
|
||||||
|
if to_json {
|
||||||
|
let mut json_val: JsonValue = json!([]);
|
||||||
|
for (span, shape) in flat {
|
||||||
|
let content =
|
||||||
|
String::from_utf8_lossy(working_set.get_span_contents(span)).to_string();
|
||||||
|
|
||||||
|
let json = json!(
|
||||||
|
{
|
||||||
|
"content": content,
|
||||||
|
"shape": shape.to_string(),
|
||||||
|
"span": {
|
||||||
|
"start": span.start.checked_sub(offset),
|
||||||
|
"end": span.end.checked_sub(offset),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
json_merge(&mut json_val, &json);
|
||||||
|
}
|
||||||
|
let json_string = if minify {
|
||||||
|
if let Ok(json_str) = serde_json::to_string(&json_val) {
|
||||||
|
json_str
|
||||||
|
} else {
|
||||||
|
"{}".to_string()
|
||||||
|
}
|
||||||
|
} else if let Ok(json_str) = serde_json::to_string_pretty(&json_val) {
|
||||||
|
json_str
|
||||||
|
} else {
|
||||||
|
"{}".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Value::string(json_string, pipeline.span).into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
// let mut rec: Record = Record::new();
|
||||||
|
let mut rec = vec![];
|
||||||
|
for (span, shape) in flat {
|
||||||
|
let content =
|
||||||
|
String::from_utf8_lossy(working_set.get_span_contents(span)).to_string();
|
||||||
|
let each_rec = record! {
|
||||||
|
"content" => Value::test_string(content),
|
||||||
|
"shape" => Value::test_string(shape.to_string()),
|
||||||
|
"span" => Value::test_record(record!{
|
||||||
|
"start" => Value::test_int(match span.start.checked_sub(offset) {
|
||||||
|
Some(start) => start as i64,
|
||||||
|
None => 0
|
||||||
|
}),
|
||||||
|
"end" => Value::test_int(match span.end.checked_sub(offset) {
|
||||||
|
Some(end) => end as i64,
|
||||||
|
None => 0
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
rec.push(Value::test_record(each_rec));
|
||||||
|
}
|
||||||
|
Ok(Value::list(rec, pipeline.span).into_pipeline_data())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let error_output = working_set.parse_errors.first();
|
||||||
|
let block_span = match &parsed_block.span {
|
||||||
|
Some(span) => span,
|
||||||
|
None => &pipeline.span,
|
||||||
|
};
|
||||||
|
if to_json {
|
||||||
|
// Get the block as json
|
||||||
|
let serde_block_str = if minify {
|
||||||
|
serde_json::to_string(&*parsed_block)
|
||||||
|
} else {
|
||||||
|
serde_json::to_string_pretty(&*parsed_block)
|
||||||
|
};
|
||||||
|
let block_json = match serde_block_str {
|
||||||
|
Ok(json) => json,
|
||||||
|
Err(e) => Err(ShellError::CantConvert {
|
||||||
|
to_type: "string".to_string(),
|
||||||
|
from_type: "block".to_string(),
|
||||||
|
span: *block_span,
|
||||||
|
help: Some(format!(
|
||||||
|
"Error: {e}\nCan't convert {parsed_block:?} to string"
|
||||||
|
)),
|
||||||
|
})?,
|
||||||
|
};
|
||||||
|
// Get the error as json
|
||||||
|
let serde_error_str = if minify {
|
||||||
|
serde_json::to_string(&error_output)
|
||||||
|
} else {
|
||||||
|
serde_json::to_string_pretty(&error_output)
|
||||||
|
};
|
||||||
|
|
||||||
|
let error_json = match serde_error_str {
|
||||||
|
Ok(json) => json,
|
||||||
|
Err(e) => Err(ShellError::CantConvert {
|
||||||
|
to_type: "string".to_string(),
|
||||||
|
from_type: "error".to_string(),
|
||||||
|
span: *block_span,
|
||||||
|
help: Some(format!(
|
||||||
|
"Error: {e}\nCan't convert {error_output:?} to string"
|
||||||
|
)),
|
||||||
|
})?,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a new output record, merging the block and error
|
||||||
|
let output_record = Value::record(
|
||||||
|
record! {
|
||||||
|
"block" => Value::string(block_json, *block_span),
|
||||||
|
"error" => Value::string(error_json, Span::test_data()),
|
||||||
|
},
|
||||||
|
pipeline.span,
|
||||||
|
);
|
||||||
|
Ok(output_record.into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
let block_value = Value::string(
|
||||||
|
if minify {
|
||||||
|
format!("{parsed_block:?}")
|
||||||
|
} else {
|
||||||
|
format!("{parsed_block:#?}")
|
||||||
|
},
|
||||||
|
pipeline.span,
|
||||||
|
);
|
||||||
|
let error_value = Value::string(
|
||||||
|
if minify {
|
||||||
|
format!("{error_output:?}")
|
||||||
|
} else {
|
||||||
|
format!("{error_output:#?}")
|
||||||
|
},
|
||||||
|
pipeline.span,
|
||||||
|
);
|
||||||
|
let output_record = Value::record(
|
||||||
|
record! {
|
||||||
|
"block" => block_value,
|
||||||
|
"error" => error_value
|
||||||
|
},
|
||||||
|
pipeline.span,
|
||||||
|
);
|
||||||
|
Ok(output_record.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_merge(a: &mut JsonValue, b: &JsonValue) {
|
||||||
|
match (a, b) {
|
||||||
|
(JsonValue::Object(ref mut a), JsonValue::Object(b)) => {
|
||||||
|
for (k, v) in b {
|
||||||
|
json_merge(a.entry(k).or_insert(JsonValue::Null), v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(JsonValue::Array(ref mut a), JsonValue::Array(b)) => {
|
||||||
|
a.extend(b.clone());
|
||||||
|
}
|
||||||
|
(JsonValue::Array(ref mut a), JsonValue::Object(b)) => {
|
||||||
|
a.extend([JsonValue::Object(b.clone())]);
|
||||||
|
}
|
||||||
|
(a, b) => {
|
||||||
|
*a = b.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -5,6 +5,8 @@ use nu_protocol::{did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDe
|
|||||||
use nu_system::ForegroundChild;
|
use nu_system::ForegroundChild;
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
use pathdiff::diff_paths;
|
use pathdiff::diff_paths;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use std::os::windows::process::CommandExt;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
ffi::{OsStr, OsString},
|
ffi::{OsStr, OsString},
|
||||||
@ -91,6 +93,22 @@ impl Command for External {
|
|||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// let's make sure it's a .ps1 script, but only on Windows
|
||||||
|
let potential_powershell_script = if cfg!(windows) {
|
||||||
|
if let Some(executable) = which(&expanded_name, "", cwd.as_ref()) {
|
||||||
|
let ext = executable
|
||||||
|
.extension()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_uppercase();
|
||||||
|
ext == "PS1"
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
// Find the absolute path to the executable. On Windows, set the
|
// Find the absolute path to the executable. On Windows, set the
|
||||||
// executable to "cmd.exe" if it's a CMD internal command. If the
|
// executable to "cmd.exe" if it's a CMD internal command. If the
|
||||||
// command is not found, display a helpful error message.
|
// command is not found, display a helpful error message.
|
||||||
@ -98,11 +116,16 @@ impl Command for External {
|
|||||||
&& (is_cmd_internal_command(&name_str) || potential_nuscript_in_windows)
|
&& (is_cmd_internal_command(&name_str) || potential_nuscript_in_windows)
|
||||||
{
|
{
|
||||||
PathBuf::from("cmd.exe")
|
PathBuf::from("cmd.exe")
|
||||||
|
} else if cfg!(windows) && potential_powershell_script {
|
||||||
|
// If we're on Windows and we're trying to run a PowerShell script, we'll use
|
||||||
|
// `powershell.exe` to run it. We shouldn't have to check for powershell.exe because
|
||||||
|
// it's automatically installed on all modern windows systems.
|
||||||
|
PathBuf::from("powershell.exe")
|
||||||
} else {
|
} else {
|
||||||
// Determine the PATH to be used and then use `which` to find it - though this has no
|
// Determine the PATH to be used and then use `which` to find it - though this has no
|
||||||
// effect if it's an absolute path already
|
// effect if it's an absolute path already
|
||||||
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
|
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
|
||||||
let Some(executable) = which(expanded_name, &paths, cwd.as_ref()) else {
|
let Some(executable) = which(&expanded_name, &paths, cwd.as_ref()) else {
|
||||||
return Err(command_not_found(&name_str, call.head, engine_state, stack));
|
return Err(command_not_found(&name_str, call.head, engine_state, stack));
|
||||||
};
|
};
|
||||||
executable
|
executable
|
||||||
@ -123,15 +146,29 @@ impl Command for External {
|
|||||||
let args = eval_arguments_from_call(engine_state, stack, call)?;
|
let args = eval_arguments_from_call(engine_state, stack, call)?;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
if is_cmd_internal_command(&name_str) || potential_nuscript_in_windows {
|
if is_cmd_internal_command(&name_str) || potential_nuscript_in_windows {
|
||||||
use std::os::windows::process::CommandExt;
|
|
||||||
|
|
||||||
// The /D flag disables execution of AutoRun commands from registry.
|
// The /D flag disables execution of AutoRun commands from registry.
|
||||||
// The /C flag followed by a command name instructs CMD to execute
|
// The /C flag followed by a command name instructs CMD to execute
|
||||||
// that command and quit.
|
// that command and quit.
|
||||||
command.args(["/D", "/C", &name_str]);
|
command.args(["/D", "/C", &expanded_name.to_string_lossy()]);
|
||||||
for arg in &args {
|
for arg in &args {
|
||||||
command.raw_arg(escape_cmd_argument(arg)?);
|
command.raw_arg(escape_cmd_argument(arg)?);
|
||||||
}
|
}
|
||||||
|
} else if potential_powershell_script {
|
||||||
|
use nu_path::canonicalize_with;
|
||||||
|
|
||||||
|
// canonicalize the path to the script so that tests pass
|
||||||
|
let canon_path = if let Ok(cwd) = engine_state.cwd_as_string(None) {
|
||||||
|
canonicalize_with(&expanded_name, cwd)?
|
||||||
|
} else {
|
||||||
|
// If we can't get the current working directory, just provide the expanded name
|
||||||
|
expanded_name
|
||||||
|
};
|
||||||
|
// The -Command flag followed by a script name instructs PowerShell to
|
||||||
|
// execute that script and quit.
|
||||||
|
command.args(["-Command", &canon_path.to_string_lossy()]);
|
||||||
|
for arg in &args {
|
||||||
|
command.raw_arg(arg.item.clone());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
command.args(args.into_iter().map(|s| s.item));
|
command.args(args.into_iter().map(|s| s.item));
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,29 @@ fn net(span: Span) -> Value {
|
|||||||
let networks = Networks::new_with_refreshed_list()
|
let networks = Networks::new_with_refreshed_list()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(iface, data)| {
|
.map(|(iface, data)| {
|
||||||
|
let ip_addresses = data
|
||||||
|
.ip_networks()
|
||||||
|
.iter()
|
||||||
|
.map(|ip| {
|
||||||
|
let protocol = match ip.addr {
|
||||||
|
std::net::IpAddr::V4(_) => "ipv4",
|
||||||
|
std::net::IpAddr::V6(_) => "ipv6",
|
||||||
|
};
|
||||||
|
Value::record(
|
||||||
|
record! {
|
||||||
|
"address" => Value::string(ip.addr.to_string(), span),
|
||||||
|
"protocol" => Value::string(protocol, span),
|
||||||
|
"loop" => Value::bool(ip.addr.is_loopback(), span),
|
||||||
|
"multicast" => Value::bool(ip.addr.is_multicast(), span),
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
let record = record! {
|
let record = record! {
|
||||||
"name" => Value::string(trim_cstyle_null(iface), span),
|
"name" => Value::string(trim_cstyle_null(iface), span),
|
||||||
|
"mac" => Value::string(data.mac_address().to_string(), span),
|
||||||
|
"ip" => Value::list(ip_addresses, span),
|
||||||
"sent" => Value::filesize(data.total_transmitted() as i64, span),
|
"sent" => Value::filesize(data.total_transmitted() as i64, span),
|
||||||
"recv" => Value::filesize(data.total_received() as i64, span),
|
"recv" => Value::filesize(data.total_received() as i64, span),
|
||||||
};
|
};
|
||||||
|
@ -1088,7 +1088,7 @@ fn create_empty_placeholder(
|
|||||||
let data = vec![vec![cell]];
|
let data = vec![vec![cell]];
|
||||||
let mut table = NuTable::from(data);
|
let mut table = NuTable::from(data);
|
||||||
table.set_data_style(TextStyle::default().dimmed());
|
table.set_data_style(TextStyle::default().dimmed());
|
||||||
let out = TableOutput::new(table, false, false, false);
|
let out = TableOutput::new(table, false, false, 1);
|
||||||
|
|
||||||
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, TableMode::default());
|
let config = create_nu_table_config(&config, style_computer, &out, false, TableMode::default());
|
||||||
|
@ -355,9 +355,9 @@ fn external_command_receives_raw_binary_data() {
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
#[test]
|
#[test]
|
||||||
fn can_run_batch_files() {
|
fn can_run_cmd_files() {
|
||||||
use nu_test_support::fs::Stub::FileWithContent;
|
use nu_test_support::fs::Stub::FileWithContent;
|
||||||
Playground::setup("run a Windows batch file", |dirs, sandbox| {
|
Playground::setup("run a Windows cmd file", |dirs, sandbox| {
|
||||||
sandbox.with_files(&[FileWithContent(
|
sandbox.with_files(&[FileWithContent(
|
||||||
"foo.cmd",
|
"foo.cmd",
|
||||||
r#"
|
r#"
|
||||||
@ -371,12 +371,30 @@ fn can_run_batch_files() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[test]
|
||||||
|
fn can_run_batch_files() {
|
||||||
|
use nu_test_support::fs::Stub::FileWithContent;
|
||||||
|
Playground::setup("run a Windows batch file", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(&[FileWithContent(
|
||||||
|
"foo.bat",
|
||||||
|
r#"
|
||||||
|
@echo off
|
||||||
|
echo Hello World
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline("foo.bat"));
|
||||||
|
assert!(actual.out.contains("Hello World"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
#[test]
|
#[test]
|
||||||
fn can_run_batch_files_without_cmd_extension() {
|
fn can_run_batch_files_without_cmd_extension() {
|
||||||
use nu_test_support::fs::Stub::FileWithContent;
|
use nu_test_support::fs::Stub::FileWithContent;
|
||||||
Playground::setup(
|
Playground::setup(
|
||||||
"run a Windows batch file without specifying the extension",
|
"run a Windows cmd file without specifying the extension",
|
||||||
|dirs, sandbox| {
|
|dirs, sandbox| {
|
||||||
sandbox.with_files(&[FileWithContent(
|
sandbox.with_files(&[FileWithContent(
|
||||||
"foo.cmd",
|
"foo.cmd",
|
||||||
@ -440,3 +458,20 @@ fn redirect_combine() {
|
|||||||
assert_eq!(actual.out, "FooBar");
|
assert_eq!(actual.out, "FooBar");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[test]
|
||||||
|
fn can_run_ps1_files() {
|
||||||
|
use nu_test_support::fs::Stub::FileWithContent;
|
||||||
|
Playground::setup("run_a_windows_ps_file", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(&[FileWithContent(
|
||||||
|
"foo.ps1",
|
||||||
|
r#"
|
||||||
|
Write-Host Hello World
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline("foo.ps1"));
|
||||||
|
assert!(actual.out.contains("Hello World"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -2941,3 +2941,123 @@ fn table_footer_inheritance() {
|
|||||||
assert_eq!(actual.out.match_indices("x2").count(), 1);
|
assert_eq!(actual.out.match_indices("x2").count(), 1);
|
||||||
assert_eq!(actual.out.match_indices("x3").count(), 1);
|
assert_eq!(actual.out.match_indices("x3").count(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn table_footer_inheritance_kv_rows() {
|
||||||
|
let actual = nu!(
|
||||||
|
concat!(
|
||||||
|
"$env.config.table.footer_inheritance = true;",
|
||||||
|
"$env.config.footer_mode = 7;",
|
||||||
|
"[[a b]; ['kv' {0: 0, 1: 1, 2: 2, 3: 3, 4: 4} ], ['data' 0], ['data' 0] ] | table --expand --width=80",
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
actual.out,
|
||||||
|
"╭───┬──────┬───────────╮\
|
||||||
|
│ # │ a │ b │\
|
||||||
|
├───┼──────┼───────────┤\
|
||||||
|
│ 0 │ kv │ ╭───┬───╮ │\
|
||||||
|
│ │ │ │ 0 │ 0 │ │\
|
||||||
|
│ │ │ │ 1 │ 1 │ │\
|
||||||
|
│ │ │ │ 2 │ 2 │ │\
|
||||||
|
│ │ │ │ 3 │ 3 │ │\
|
||||||
|
│ │ │ │ 4 │ 4 │ │\
|
||||||
|
│ │ │ ╰───┴───╯ │\
|
||||||
|
│ 1 │ data │ 0 │\
|
||||||
|
│ 2 │ data │ 0 │\
|
||||||
|
╰───┴──────┴───────────╯"
|
||||||
|
);
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
concat!(
|
||||||
|
"$env.config.table.footer_inheritance = true;",
|
||||||
|
"$env.config.footer_mode = 7;",
|
||||||
|
"[[a b]; ['kv' {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5} ], ['data' 0], ['data' 0] ] | table --expand --width=80",
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
actual.out,
|
||||||
|
"╭───┬──────┬───────────╮\
|
||||||
|
│ # │ a │ b │\
|
||||||
|
├───┼──────┼───────────┤\
|
||||||
|
│ 0 │ kv │ ╭───┬───╮ │\
|
||||||
|
│ │ │ │ 0 │ 0 │ │\
|
||||||
|
│ │ │ │ 1 │ 1 │ │\
|
||||||
|
│ │ │ │ 2 │ 2 │ │\
|
||||||
|
│ │ │ │ 3 │ 3 │ │\
|
||||||
|
│ │ │ │ 4 │ 4 │ │\
|
||||||
|
│ │ │ │ 5 │ 5 │ │\
|
||||||
|
│ │ │ ╰───┴───╯ │\
|
||||||
|
│ 1 │ data │ 0 │\
|
||||||
|
│ 2 │ data │ 0 │\
|
||||||
|
├───┼──────┼───────────┤\
|
||||||
|
│ # │ a │ b │\
|
||||||
|
╰───┴──────┴───────────╯"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn table_footer_inheritance_list_rows() {
|
||||||
|
let actual = nu!(
|
||||||
|
concat!(
|
||||||
|
"$env.config.table.footer_inheritance = true;",
|
||||||
|
"$env.config.footer_mode = 7;",
|
||||||
|
"[[a b]; ['kv' {0: [[field]; [0] [1] [2] [3] [4]]} ], ['data' 0], ['data' 0] ] | table --expand --width=80",
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
actual.out,
|
||||||
|
"╭───┬──────┬───────────────────────╮\
|
||||||
|
│ # │ a │ b │\
|
||||||
|
├───┼──────┼───────────────────────┤\
|
||||||
|
│ 0 │ kv │ ╭───┬───────────────╮ │\
|
||||||
|
│ │ │ │ │ ╭───┬───────╮ │ │\
|
||||||
|
│ │ │ │ 0 │ │ # │ field │ │ │\
|
||||||
|
│ │ │ │ │ ├───┼───────┤ │ │\
|
||||||
|
│ │ │ │ │ │ 0 │ 0 │ │ │\
|
||||||
|
│ │ │ │ │ │ 1 │ 1 │ │ │\
|
||||||
|
│ │ │ │ │ │ 2 │ 2 │ │ │\
|
||||||
|
│ │ │ │ │ │ 3 │ 3 │ │ │\
|
||||||
|
│ │ │ │ │ │ 4 │ 4 │ │ │\
|
||||||
|
│ │ │ │ │ ╰───┴───────╯ │ │\
|
||||||
|
│ │ │ ╰───┴───────────────╯ │\
|
||||||
|
│ 1 │ data │ 0 │\
|
||||||
|
│ 2 │ data │ 0 │\
|
||||||
|
╰───┴──────┴───────────────────────╯"
|
||||||
|
);
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
concat!(
|
||||||
|
"$env.config.table.footer_inheritance = true;",
|
||||||
|
"$env.config.footer_mode = 7;",
|
||||||
|
"[[a b]; ['kv' {0: [[field]; [0] [1] [2] [3] [4] [5]]} ], ['data' 0], ['data' 0] ] | table --expand --width=80",
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
actual.out,
|
||||||
|
"╭───┬──────┬───────────────────────╮\
|
||||||
|
│ # │ a │ b │\
|
||||||
|
├───┼──────┼───────────────────────┤\
|
||||||
|
│ 0 │ kv │ ╭───┬───────────────╮ │\
|
||||||
|
│ │ │ │ │ ╭───┬───────╮ │ │\
|
||||||
|
│ │ │ │ 0 │ │ # │ field │ │ │\
|
||||||
|
│ │ │ │ │ ├───┼───────┤ │ │\
|
||||||
|
│ │ │ │ │ │ 0 │ 0 │ │ │\
|
||||||
|
│ │ │ │ │ │ 1 │ 1 │ │ │\
|
||||||
|
│ │ │ │ │ │ 2 │ 2 │ │ │\
|
||||||
|
│ │ │ │ │ │ 3 │ 3 │ │ │\
|
||||||
|
│ │ │ │ │ │ 4 │ 4 │ │ │\
|
||||||
|
│ │ │ │ │ │ 5 │ 5 │ │ │\
|
||||||
|
│ │ │ │ │ ╰───┴───────╯ │ │\
|
||||||
|
│ │ │ ╰───┴───────────────╯ │\
|
||||||
|
│ 1 │ data │ 0 │\
|
||||||
|
│ 2 │ data │ 0 │\
|
||||||
|
├───┼──────┼───────────────────────┤\
|
||||||
|
│ # │ a │ b │\
|
||||||
|
╰───┴──────┴───────────────────────╯"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -21,7 +21,7 @@ nu-plugin-core = { path = "../nu-plugin-core", version = "0.100.1", default-feat
|
|||||||
nu-utils = { path = "../nu-utils", version = "0.100.1" }
|
nu-utils = { path = "../nu-utils", version = "0.100.1" }
|
||||||
|
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
thiserror = "1.0"
|
thiserror = "2.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
@ -36,7 +36,7 @@ num-format = { workspace = true }
|
|||||||
rmp-serde = { workspace = true, optional = true }
|
rmp-serde = { workspace = true, optional = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
thiserror = "1.0"
|
thiserror = "2.0"
|
||||||
typetag = "0.2"
|
typetag = "0.2"
|
||||||
os_pipe = { workspace = true, features = ["io_safety"] }
|
os_pipe = { workspace = true, features = ["io_safety"] }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
|
@ -18,8 +18,12 @@ pub fn create_nu_table_config(
|
|||||||
expand: bool,
|
expand: bool,
|
||||||
mode: TableMode,
|
mode: TableMode,
|
||||||
) -> NuTableConfig {
|
) -> NuTableConfig {
|
||||||
let with_footer = (config.table.footer_inheritance && out.with_footer)
|
let mut count_rows = out.table.count_rows();
|
||||||
|| with_footer(config, out.with_header, out.table.count_rows());
|
if config.table.footer_inheritance {
|
||||||
|
count_rows = out.count_rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
let with_footer = with_footer(config, out.with_header, count_rows);
|
||||||
|
|
||||||
NuTableConfig {
|
NuTableConfig {
|
||||||
theme: load_theme(mode),
|
theme: load_theme(mode),
|
||||||
|
@ -615,12 +615,15 @@ fn load_theme(
|
|||||||
if let Some(style) = sep_color {
|
if let Some(style) = sep_color {
|
||||||
let color = convert_style(style);
|
let color = convert_style(style);
|
||||||
let color = ANSIBuf::from(color);
|
let color = ANSIBuf::from(color);
|
||||||
|
// todo: use .modify(Segment::all(), color) --> it has this optimization
|
||||||
table.get_config_mut().set_border_color_default(color);
|
table.get_config_mut().set_border_color_default(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !with_header {
|
if !with_header {
|
||||||
|
// todo: remove and use theme.remove_horizontal_lines();
|
||||||
table.with(RemoveHorizontalLine);
|
table.with(RemoveHorizontalLine);
|
||||||
} else if with_footer {
|
} else if with_footer {
|
||||||
|
// todo: remove and set it on theme rather then here...
|
||||||
table.with(CopyFirstHorizontalLineAtLast);
|
table.with(CopyFirstHorizontalLineAtLast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1257,6 +1260,7 @@ fn remove_row(recs: &mut NuRecords, row: usize) -> Vec<String> {
|
|||||||
columns
|
columns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo; use Format?
|
||||||
struct StripColorFromRow(usize);
|
struct StripColorFromRow(usize);
|
||||||
|
|
||||||
impl TableOption<NuRecords, ColoredConfig, CompleteDimensionVecRecords<'_>> for StripColorFromRow {
|
impl TableOption<NuRecords, ColoredConfig, CompleteDimensionVecRecords<'_>> for StripColorFromRow {
|
||||||
|
@ -5,7 +5,7 @@ use crate::{
|
|||||||
NuText, StringResult, TableResult, INDEX_COLUMN_NAME,
|
NuText, StringResult, TableResult, INDEX_COLUMN_NAME,
|
||||||
},
|
},
|
||||||
string_width,
|
string_width,
|
||||||
types::{has_footer, has_index},
|
types::has_index,
|
||||||
NuTable, NuTableCell, TableOpts, TableOutput,
|
NuTable, NuTableCell, TableOpts, TableOutput,
|
||||||
};
|
};
|
||||||
use nu_color_config::{Alignment, StyleComputer, TextStyle};
|
use nu_color_config::{Alignment, StyleComputer, TextStyle};
|
||||||
@ -63,22 +63,22 @@ struct Cfg<'a> {
|
|||||||
struct CellOutput {
|
struct CellOutput {
|
||||||
text: String,
|
text: String,
|
||||||
style: TextStyle,
|
style: TextStyle,
|
||||||
is_big: bool,
|
size: usize,
|
||||||
is_expanded: bool,
|
is_expanded: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CellOutput {
|
impl CellOutput {
|
||||||
fn new(text: String, style: TextStyle, is_big: bool, is_expanded: bool) -> Self {
|
fn new(text: String, style: TextStyle, size: usize, is_expanded: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
text,
|
text,
|
||||||
style,
|
style,
|
||||||
is_big,
|
size,
|
||||||
is_expanded,
|
is_expanded,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clean(text: String, is_big: bool, is_expanded: bool) -> Self {
|
fn clean(text: String, size: usize, is_expanded: bool) -> Self {
|
||||||
Self::new(text, Default::default(), is_big, is_expanded)
|
Self::new(text, Default::default(), size, is_expanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text(text: String) -> Self {
|
fn text(text: String) -> Self {
|
||||||
@ -86,7 +86,7 @@ impl CellOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn styled(text: NuText) -> Self {
|
fn styled(text: NuText) -> Self {
|
||||||
Self::new(text.0, text.1, false, false)
|
Self::new(text.0, text.1, 1, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
|
|
||||||
let with_index = has_index(&cfg.opts, &headers);
|
let with_index = has_index(&cfg.opts, &headers);
|
||||||
let row_offset = cfg.opts.index_offset;
|
let row_offset = cfg.opts.index_offset;
|
||||||
let mut is_footer_used = false;
|
let mut rows_count = 0usize;
|
||||||
|
|
||||||
// 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
|
||||||
@ -199,9 +199,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
data[row].push(value);
|
data[row].push(value);
|
||||||
data_styles.insert((row, with_index as usize), cell.style);
|
data_styles.insert((row, with_index as usize), cell.style);
|
||||||
|
|
||||||
if cell.is_big {
|
rows_count = rows_count.saturating_add(cell.size);
|
||||||
is_footer_used = cell.is_big;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut table = NuTable::from(data);
|
let mut table = NuTable::from(data);
|
||||||
@ -209,12 +207,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
table.set_index_style(get_index_style(cfg.opts.style_computer));
|
table.set_index_style(get_index_style(cfg.opts.style_computer));
|
||||||
set_data_styles(&mut table, data_styles);
|
set_data_styles(&mut table, data_styles);
|
||||||
|
|
||||||
return Ok(Some(TableOutput::new(
|
return Ok(Some(TableOutput::new(table, false, with_index, rows_count)));
|
||||||
table,
|
|
||||||
false,
|
|
||||||
with_index,
|
|
||||||
is_footer_used,
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !headers.is_empty() {
|
if !headers.is_empty() {
|
||||||
@ -269,6 +262,8 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut column_rows = 0usize;
|
||||||
|
|
||||||
for (row, item) in input.iter().enumerate() {
|
for (row, item) in input.iter().enumerate() {
|
||||||
cfg.opts.signals.check(cfg.opts.span)?;
|
cfg.opts.signals.check(cfg.opts.span)?;
|
||||||
|
|
||||||
@ -294,9 +289,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
data[row + 1].push(value);
|
data[row + 1].push(value);
|
||||||
data_styles.insert((row + 1, col + with_index as usize), cell.style);
|
data_styles.insert((row + 1, col + with_index as usize), cell.style);
|
||||||
|
|
||||||
if cell.is_big {
|
column_rows = column_rows.saturating_add(cell.size);
|
||||||
is_footer_used = cell.is_big;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let head_cell = NuTableCell::new(header);
|
let head_cell = NuTableCell::new(header);
|
||||||
@ -316,6 +309,8 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
|
|
||||||
available_width -= pad_space + column_width;
|
available_width -= pad_space + column_width;
|
||||||
rendered_column += 1;
|
rendered_column += 1;
|
||||||
|
|
||||||
|
rows_count = std::cmp::max(rows_count, column_rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
if truncate && rendered_column == 0 {
|
if truncate && rendered_column == 0 {
|
||||||
@ -374,9 +369,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
table.set_indent(cfg.opts.indent.0, cfg.opts.indent.1);
|
table.set_indent(cfg.opts.indent.0, cfg.opts.indent.1);
|
||||||
set_data_styles(&mut table, data_styles);
|
set_data_styles(&mut table, data_styles);
|
||||||
|
|
||||||
let has_footer = is_footer_used || has_footer(&cfg.opts, table.count_rows() as u64);
|
Ok(Some(TableOutput::new(table, true, with_index, rows_count)))
|
||||||
|
|
||||||
Ok(Some(TableOutput::new(table, true, with_index, has_footer)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
||||||
@ -395,7 +388,7 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
|||||||
|
|
||||||
let value_width = cfg.opts.width - key_width - count_borders - padding - padding;
|
let value_width = cfg.opts.width - key_width - count_borders - padding - padding;
|
||||||
|
|
||||||
let mut with_footer = false;
|
let mut count_rows = 0usize;
|
||||||
|
|
||||||
let mut data = Vec::with_capacity(record.len());
|
let mut data = Vec::with_capacity(record.len());
|
||||||
for (key, value) in record {
|
for (key, value) in record {
|
||||||
@ -420,19 +413,17 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
|||||||
|
|
||||||
data.push(row);
|
data.push(row);
|
||||||
|
|
||||||
if cell.is_big {
|
count_rows = count_rows.saturating_add(cell.size);
|
||||||
with_footer = cell.is_big;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut table = NuTable::from(data);
|
let mut table = NuTable::from(data);
|
||||||
table.set_index_style(get_key_style(&cfg));
|
table.set_index_style(get_key_style(&cfg));
|
||||||
table.set_indent(cfg.opts.indent.0, cfg.opts.indent.1);
|
table.set_indent(cfg.opts.indent.0, cfg.opts.indent.1);
|
||||||
|
|
||||||
let out = TableOutput::new(table, false, true, with_footer);
|
let out = TableOutput::new(table, false, true, count_rows);
|
||||||
|
|
||||||
maybe_expand_table(out, cfg.opts.width, &cfg.opts)
|
maybe_expand_table(out, cfg.opts.width, &cfg.opts)
|
||||||
.map(|value| value.map(|value| CellOutput::clean(value, with_footer, false)))
|
.map(|value| value.map(|value| CellOutput::clean(value, count_rows, false)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// the flag is used as an optimization to not do `value.lines().count()` search.
|
// the flag is used as an optimization to not do `value.lines().count()` search.
|
||||||
@ -441,7 +432,7 @@ fn expand_table_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellR
|
|||||||
if is_limited {
|
if is_limited {
|
||||||
return Ok(Some(CellOutput::clean(
|
return Ok(Some(CellOutput::clean(
|
||||||
value_to_string_clean(value, cfg),
|
value_to_string_clean(value, cfg),
|
||||||
false,
|
1,
|
||||||
false,
|
false,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
@ -457,7 +448,7 @@ fn expand_table_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellR
|
|||||||
let cfg = create_table_cfg(cfg, &out);
|
let cfg = create_table_cfg(cfg, &out);
|
||||||
let value = out.table.draw(cfg, value_width);
|
let value = out.table.draw(cfg, value_width);
|
||||||
match value {
|
match value {
|
||||||
Some(value) => Ok(Some(CellOutput::clean(value, out.with_footer, true))),
|
Some(value) => Ok(Some(CellOutput::clean(value, out.count_rows, true))),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -484,7 +475,7 @@ fn expand_table_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellR
|
|||||||
let inner_cfg = update_config(dive_options(cfg, span), value_width);
|
let inner_cfg = update_config(dive_options(cfg, span), 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(CellOutput::clean(result.text, result.is_big, true))),
|
Some(result) => Ok(Some(CellOutput::clean(result.text, result.size, true))),
|
||||||
None => Ok(Some(CellOutput::text(value_to_wrapped_string(
|
None => Ok(Some(CellOutput::text(value_to_wrapped_string(
|
||||||
value,
|
value,
|
||||||
cfg,
|
cfg,
|
||||||
@ -575,7 +566,7 @@ fn expanded_table_entry2(item: &Value, cfg: Cfg<'_>) -> CellOutput {
|
|||||||
let table_config = create_table_cfg(&cfg, &out);
|
let table_config = create_table_cfg(&cfg, &out);
|
||||||
let table = out.table.draw(table_config, usize::MAX);
|
let table = out.table.draw(table_config, usize::MAX);
|
||||||
match table {
|
match table {
|
||||||
Some(table) => CellOutput::clean(table, out.with_footer, false),
|
Some(table) => CellOutput::clean(table, out.count_rows, false),
|
||||||
None => CellOutput::styled(nu_value_to_string(
|
None => CellOutput::styled(nu_value_to_string(
|
||||||
item,
|
item,
|
||||||
cfg.opts.config,
|
cfg.opts.config,
|
||||||
|
@ -56,8 +56,9 @@ fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
|
|||||||
|
|
||||||
let mut table = NuTable::from(data);
|
let mut table = NuTable::from(data);
|
||||||
table.set_index_style(TextStyle::default_field());
|
table.set_index_style(TextStyle::default_field());
|
||||||
|
let count_rows = table.count_rows();
|
||||||
|
|
||||||
let mut out = TableOutput::new(table, false, true, false);
|
let mut out = TableOutput::new(table, false, true, count_rows);
|
||||||
|
|
||||||
let left = opts.config.table.padding.left;
|
let left = opts.config.table.padding.left;
|
||||||
let right = opts.config.table.padding.right;
|
let right = opts.config.table.padding.right;
|
||||||
@ -82,7 +83,10 @@ fn table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
|
|||||||
let with_header = !headers.is_empty();
|
let with_header = !headers.is_empty();
|
||||||
if !with_header {
|
if !with_header {
|
||||||
let table = to_table_with_no_header(input, with_index, row_offset, opts)?;
|
let table = to_table_with_no_header(input, with_index, row_offset, opts)?;
|
||||||
let table = table.map(|table| TableOutput::new(table, false, with_index, false));
|
let table = table.map(|table| {
|
||||||
|
let count_rows = table.count_rows();
|
||||||
|
TableOutput::new(table, false, with_index, count_rows)
|
||||||
|
});
|
||||||
return Ok(table);
|
return Ok(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +102,10 @@ fn table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let table = to_table_with_header(input, &headers, with_index, row_offset, opts)?;
|
let table = to_table_with_header(input, &headers, with_index, row_offset, opts)?;
|
||||||
let table = table.map(|table| TableOutput::new(table, true, with_index, false));
|
let table = table.map(|table| {
|
||||||
|
let count_rows = table.count_rows();
|
||||||
|
TableOutput::new(table, true, with_index, count_rows)
|
||||||
|
});
|
||||||
|
|
||||||
Ok(table)
|
Ok(table)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use terminal_size::{terminal_size, Height, Width};
|
use nu_color_config::StyleComputer;
|
||||||
|
use nu_protocol::{Config, Signals, Span, TableIndexMode, TableMode};
|
||||||
|
|
||||||
use crate::{common::INDEX_COLUMN_NAME, NuTable};
|
use crate::{common::INDEX_COLUMN_NAME, NuTable};
|
||||||
use nu_color_config::StyleComputer;
|
|
||||||
use nu_protocol::{Config, FooterMode, Signals, Span, TableIndexMode, TableMode};
|
|
||||||
|
|
||||||
mod collapse;
|
mod collapse;
|
||||||
mod expanded;
|
mod expanded;
|
||||||
@ -16,16 +15,16 @@ pub struct TableOutput {
|
|||||||
pub table: NuTable,
|
pub table: NuTable,
|
||||||
pub with_header: bool,
|
pub with_header: bool,
|
||||||
pub with_index: bool,
|
pub with_index: bool,
|
||||||
pub with_footer: bool,
|
pub count_rows: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableOutput {
|
impl TableOutput {
|
||||||
pub fn new(table: NuTable, with_header: bool, with_index: bool, with_footer: bool) -> Self {
|
pub fn new(table: NuTable, with_header: bool, with_index: bool, count_rows: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
table,
|
table,
|
||||||
with_header,
|
with_header,
|
||||||
with_index,
|
with_index,
|
||||||
with_footer,
|
count_rows,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,23 +78,3 @@ fn has_index(opts: &TableOpts<'_>, headers: &[String]) -> bool {
|
|||||||
|
|
||||||
with_index && !opts.index_remove
|
with_index && !opts.index_remove
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_footer(opts: &TableOpts<'_>, count_records: u64) -> bool {
|
|
||||||
match opts.config.footer_mode {
|
|
||||||
// Only show the footer if there are more than RowCount rows
|
|
||||||
FooterMode::RowCount(limit) => count_records > limit,
|
|
||||||
// Always show the footer
|
|
||||||
FooterMode::Always => true,
|
|
||||||
// Never show the footer
|
|
||||||
FooterMode::Never => false,
|
|
||||||
// Calculate the screen height and row count, if screen height is larger than row count, don't show footer
|
|
||||||
FooterMode::Auto => {
|
|
||||||
let (_width, height) = match terminal_size() {
|
|
||||||
Some((w, h)) => (Width(w.0).0 as u64, Height(h.0).0 as u64),
|
|
||||||
None => (Width(0).0 as u64, Height(0).0 as u64),
|
|
||||||
};
|
|
||||||
|
|
||||||
height <= count_records
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user