Merge branch 'nushell:main' into feature-PWD-per-drive

This commit is contained in:
PegasusPlusUS
2024-11-21 21:01:58 -08:00
committed by GitHub
55 changed files with 2925 additions and 1821 deletions

View File

@ -1,6 +1,7 @@
use nu_engine::command_prelude::*;
use nu_parser::parse;
use nu_protocol::engine::StateWorkingSet;
use nu_parser::{flatten_block, parse};
use nu_protocol::{engine::StateWorkingSet, record};
use serde_json::{json, Value as JsonValue};
#[derive(Clone)]
pub struct Ast;
@ -16,109 +17,23 @@ impl Command for Ast {
fn signature(&self) -> Signature {
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(
"pipeline",
SyntaxShape::String,
"The pipeline to print the ast for.",
)
.switch("json", "serialize to json", Some('j'))
.switch("minify", "minify the nuon or json output", Some('m'))
.switch("json", "Serialize to json", Some('j'))
.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)
.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> {
vec![
Example {
@ -147,8 +62,247 @@ impl Command for Ast {
example: "ast 'for x in 1..10 { echo $x ' --json --minify",
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)]

View File

@ -15,7 +15,16 @@ impl Command for ConfigEnv {
Signature::build(self.name())
.category(Category::Env)
.input_output_types(vec![(Type::Nothing, Type::Any)])
.switch("default", "Print default `env.nu` file instead.", Some('d'))
.switch(
"default",
"Print the internal default `env.nu` file instead.",
Some('d'),
)
.switch(
"sample",
"Print a commented, sample `env.nu` file instead.",
Some('s'),
)
// TODO: Signature narrower than what run actually supports theoretically
}
@ -26,18 +35,18 @@ impl Command for ConfigEnv {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "allow user to open and update nu env",
description: "open user's env.nu in the default editor",
example: "config env",
result: None,
},
Example {
description: "allow user to print default `env.nu` file",
example: "config env --default,",
description: "pretty-print a commented, sample `env.nu` that explains common settings",
example: "config env --sample | nu-highlight,",
result: None,
},
Example {
description: "allow saving the default `env.nu` locally",
example: "config env --default | save -f ~/.config/nushell/default_env.nu",
description: "pretty-print the internal `env.nu` file which is loaded before the user's environment",
example: "config env --default | nu-highlight,",
result: None,
},
]
@ -50,12 +59,28 @@ impl Command for ConfigEnv {
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let default_flag = call.has_flag(engine_state, stack, "default")?;
let sample_flag = call.has_flag(engine_state, stack, "sample")?;
if default_flag && sample_flag {
return Err(ShellError::IncompatibleParameters {
left_message: "can't use `--default` at the same time".into(),
left_span: call.get_flag_span(stack, "default").expect("has flag"),
right_message: "because of `--sample`".into(),
right_span: call.get_flag_span(stack, "sample").expect("has flag"),
});
}
// `--default` flag handling
if call.has_flag(engine_state, stack, "default")? {
let head = call.head;
return Ok(Value::string(nu_utils::get_default_env(), head).into_pipeline_data());
}
// `--sample` flag handling
if sample_flag {
let head = call.head;
return Ok(Value::string(nu_utils::get_sample_env(), head).into_pipeline_data());
}
// Find the editor executable.
let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?;
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;

View File

@ -17,9 +17,14 @@ impl Command for ConfigNu {
.input_output_types(vec![(Type::Nothing, Type::Any)])
.switch(
"default",
"Print default `config.nu` file instead.",
"Print the internal default `config.nu` file instead.",
Some('d'),
)
.switch(
"sample",
"Print a commented, sample `config.nu` file instead.",
Some('s'),
)
// TODO: Signature narrower than what run actually supports theoretically
}
@ -30,18 +35,19 @@ impl Command for ConfigNu {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "allow user to open and update nu config",
description: "open user's config.nu in the default editor",
example: "config nu",
result: None,
},
Example {
description: "allow user to print default `config.nu` file",
example: "config nu --default,",
description: "pretty-print a commented, sample `config.nu` that explains common settings",
example: "config nu --sample | nu-highlight",
result: None,
},
Example {
description: "allow saving the default `config.nu` locally",
example: "config nu --default | save -f ~/.config/nushell/default_config.nu",
description:
"pretty-print the internal `config.nu` file which is loaded before user's config",
example: "config nu --default | nu-highlight",
result: None,
},
]
@ -54,12 +60,29 @@ impl Command for ConfigNu {
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let default_flag = call.has_flag(engine_state, stack, "default")?;
let sample_flag = call.has_flag(engine_state, stack, "sample")?;
if default_flag && sample_flag {
return Err(ShellError::IncompatibleParameters {
left_message: "can't use `--default` at the same time".into(),
left_span: call.get_flag_span(stack, "default").expect("has flag"),
right_message: "because of `--sample`".into(),
right_span: call.get_flag_span(stack, "sample").expect("has flag"),
});
}
// `--default` flag handling
if call.has_flag(engine_state, stack, "default")? {
if default_flag {
let head = call.head;
return Ok(Value::string(nu_utils::get_default_config(), head).into_pipeline_data());
}
// `--sample` flag handling
if sample_flag {
let head = call.head;
return Ok(Value::string(nu_utils::get_sample_config(), head).into_pipeline_data());
}
// Find the editor executable.
let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?;
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;

View File

@ -194,7 +194,7 @@ impl Command for Watch {
match result {
Ok(val) => {
val.print(engine_state, stack, false, false)?;
val.print_table(engine_state, stack, false, false)?;
}
Err(err) => {
let working_set = StateWorkingSet::new(engine_state);

View File

@ -1,5 +1,6 @@
use indexmap::IndexMap;
use nu_engine::command_prelude::*;
use nu_protocol::report_shell_warning;
#[derive(Clone)]
pub struct SplitBy;
@ -27,6 +28,15 @@ impl Command for SplitBy {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
report_shell_warning(
engine_state,
&ShellError::Deprecated {
deprecated: "The `split_by` command",
suggestion: "Please use the `group-by` command instead.",
span: call.head,
help: None,
},
);
split_by(engine_state, stack, call, input)
}

View File

@ -11,7 +11,10 @@ impl Command for FromCsv {
fn signature(&self) -> Signature {
Signature::build("from csv")
.input_output_types(vec![(Type::String, Type::table())])
.input_output_types(vec![
(Type::String, Type::table()),
(Type::String, Type::list(Type::Any)),
])
.named(
"separator",
SyntaxShape::String,
@ -82,6 +85,26 @@ impl Command for FromCsv {
})],
))
},
Example {
description: "Convert comma-separated data to a table, allowing variable number of columns per row",
example: "\"ColA,ColB\n1,2\n3,4,5\n6\" | from csv --flexible",
result: Some(Value::test_list (
vec![
Value::test_record(record! {
"ColA" => Value::test_int(1),
"ColB" => Value::test_int(2),
}),
Value::test_record(record! {
"ColA" => Value::test_int(3),
"ColB" => Value::test_int(4),
"column2" => Value::test_int(5),
}),
Value::test_record(record! {
"ColA" => Value::test_int(6),
}),
],
))
},
Example {
description: "Convert comma-separated data to a table, ignoring headers",
example: "open data.txt | from csv --noheaders",

View File

@ -39,12 +39,7 @@ fn from_delimited_stream(
.from_reader(input_reader);
let headers = if noheaders {
(0..reader
.headers()
.map_err(|err| from_csv_error(err, span))?
.len())
.map(|i| format!("column{i}"))
.collect::<Vec<String>>()
vec![]
} else {
reader
.headers()
@ -54,32 +49,28 @@ fn from_delimited_stream(
.collect()
};
let n = headers.len();
let columns = headers
.into_iter()
.chain((n..).map(|i| format!("column{i}")));
let iter = reader.into_records().map(move |row| {
let row = match row {
Ok(row) => row,
Err(err) => return Value::error(from_csv_error(err, span), span),
};
let columns = headers.iter().cloned();
let values = row
.into_iter()
.map(|s| {
if no_infer {
Value::string(s, span)
} else if let Ok(i) = s.parse() {
Value::int(i, span)
} else if let Ok(f) = s.parse() {
Value::float(f, span)
} else {
Value::string(s, span)
}
})
.chain(std::iter::repeat(Value::nothing(span)));
let columns = columns.clone();
let values = row.into_iter().map(|s| {
if no_infer {
Value::string(s, span)
} else if let Ok(i) = s.parse() {
Value::int(i, span)
} else if let Ok(f) = s.parse() {
Value::float(f, span)
} else {
Value::string(s, span)
}
});
// If there are more values than the number of headers,
// then the remaining values are ignored.
//
// Otherwise, if there are less values than headers,
// then `Value::nothing(span)` is used to fill the remaining columns.
Value::record(columns.zip(values).collect(), span)
});

View File

@ -11,7 +11,10 @@ impl Command for FromTsv {
fn signature(&self) -> Signature {
Signature::build("from tsv")
.input_output_types(vec![(Type::String, Type::table())])
.input_output_types(vec![
(Type::String, Type::table()),
(Type::String, Type::list(Type::Any)),
])
.named(
"comment",
SyntaxShape::String,
@ -76,6 +79,21 @@ impl Command for FromTsv {
})],
))
},
Example {
description: "Convert comma-separated data to a table, allowing variable number of columns per row and ignoring headers",
example: "\"value 1\nvalue 2\tdescription 2\" | from tsv --flexible --noheaders",
result: Some(Value::test_list (
vec![
Value::test_record(record! {
"column0" => Value::test_string("value 1"),
}),
Value::test_record(record! {
"column0" => Value::test_string("value 2"),
"column1" => Value::test_string("description 2"),
}),
],
))
},
Example {
description: "Create a tsv file with header columns and open it",
example: r#"$'c1(char tab)c2(char tab)c3(char nl)1(char tab)2(char tab)3' | save tsv-data | open tsv-data | from tsv"#,

View File

@ -4,7 +4,7 @@ use nu_engine::command_prelude::*;
use quick_xml::{
escape,
events::{BytesEnd, BytesStart, BytesText, Event},
events::{BytesEnd, BytesPI, BytesStart, BytesText, Event},
};
use std::{borrow::Cow, io::Cursor};
@ -406,7 +406,7 @@ impl Job {
let content_text = format!("{} {}", tag, content);
// PI content must NOT be escaped
// https://www.w3.org/TR/xml/#sec-pi
let pi_content = BytesText::from_escaped(content_text.as_str());
let pi_content = BytesPI::new(content_text.as_str());
self.writer
.write_event(Event::PI(pi_content))

View File

@ -43,6 +43,12 @@ impl Command for Input {
"number of characters to read; suppresses output",
Some('n'),
)
.named(
"default",
SyntaxShape::String,
"default value if no input is provided",
Some('d'),
)
.switch("suppress-output", "don't print keystroke values", Some('s'))
.category(Category::Platform)
}
@ -72,8 +78,12 @@ impl Command for Input {
});
}
let default_val: Option<String> = call.get_flag(engine_state, stack, "default")?;
if let Some(prompt) = &prompt {
print!("{prompt}");
match &default_val {
None => print!("{prompt}"),
Some(val) => print!("{prompt} (default: {val})"),
}
let _ = std::io::stdout().flush();
}
@ -149,7 +159,10 @@ impl Command for Input {
if !suppress_output {
std::io::stdout().write_all(b"\n")?;
}
Ok(Value::string(buf, call.head).into_pipeline_data())
match default_val {
Some(val) if buf.is_empty() => Ok(Value::string(val, call.head).into_pipeline_data()),
_ => Ok(Value::string(buf, call.head).into_pipeline_data()),
}
}
fn examples(&self) -> Vec<Example> {
@ -164,6 +177,11 @@ impl Command for Input {
example: "let user_input = (input --numchar 2)",
result: None,
},
Example {
description: "Get input from the user with default value, and assign to a variable",
example: "let user_input = (input --default 10)",
result: None,
},
]
}
}

View File

@ -77,20 +77,6 @@ impl Command for SubCommand {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
if call.has_flag_const(working_set, "not")? {
nu_protocol::report_shell_error(
working_set.permanent(),
&ShellError::GenericError {
error: "Deprecated option".into(),
msg: "`str contains --not {string}` is deprecated and will be removed in 0.95."
.into(),
span: Some(call.head),
help: Some("Please use the `not` operator instead.".into()),
inner: vec![],
},
);
}
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let args = Arguments {

View File

@ -5,6 +5,8 @@ use nu_protocol::{did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDe
use nu_system::ForegroundChild;
use nu_utils::IgnoreCaseExt;
use pathdiff::diff_paths;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
use std::{
borrow::Cow,
ffi::{OsStr, OsString},
@ -91,6 +93,22 @@ impl Command for External {
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
// executable to "cmd.exe" if it's a CMD internal command. If the
// 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)
{
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 {
// 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
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));
};
executable
@ -123,15 +146,29 @@ impl Command for External {
let args = eval_arguments_from_call(engine_state, stack, call)?;
#[cfg(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 /C flag followed by a command name instructs CMD to execute
// that command and quit.
command.args(["/D", "/C", &name_str]);
command.args(["/D", "/C", &expanded_name.to_string_lossy()]);
for arg in &args {
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 {
command.args(args.into_iter().map(|s| s.item));
}

View File

@ -44,8 +44,29 @@ fn net(span: Span) -> Value {
let networks = Networks::new_with_refreshed_list()
.iter()
.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! {
"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),
"recv" => Value::filesize(data.total_received() as i64, span),
};

View File

@ -1088,7 +1088,7 @@ fn create_empty_placeholder(
let data = vec![vec![cell]];
let mut table = NuTable::from(data);
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 config = create_nu_table_config(&config, style_computer, &out, false, TableMode::default());

View File

@ -355,9 +355,9 @@ fn external_command_receives_raw_binary_data() {
#[cfg(windows)]
#[test]
fn can_run_batch_files() {
fn can_run_cmd_files() {
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(
"foo.cmd",
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)]
#[test]
fn can_run_batch_files_without_cmd_extension() {
use nu_test_support::fs::Stub::FileWithContent;
Playground::setup(
"run a Windows batch file without specifying the extension",
"run a Windows cmd file without specifying the extension",
|dirs, sandbox| {
sandbox.with_files(&[FileWithContent(
"foo.cmd",
@ -440,3 +458,20 @@ fn redirect_combine() {
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"));
});
}

View File

@ -2941,3 +2941,123 @@ fn table_footer_inheritance() {
assert_eq!(actual.out.match_indices("x2").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 │\
╰───┴──────┴───────────────────────╯"
);
}

View File

@ -469,7 +469,7 @@ fn from_csv_test_flexible_extra_vals() {
echo "a,b\n1,2,3" | from csv --flexible | first | values | to nuon
"#
));
assert_eq!(actual.out, "[1, 2]");
assert_eq!(actual.out, "[1, 2, 3]");
}
#[test]
@ -479,5 +479,5 @@ fn from_csv_test_flexible_missing_vals() {
echo "a,b\n1" | from csv --flexible | first | values | to nuon
"#
));
assert_eq!(actual.out, "[1, null]");
assert_eq!(actual.out, "[1]");
}

View File

@ -29,3 +29,20 @@ fn from_ods_file_to_table_select_sheet() {
assert_eq!(actual.out, "SalesOrders");
}
#[test]
fn from_ods_file_to_table_select_sheet_with_annotations() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
open sample_data_with_annotation.ods --raw
| from ods --sheets ["SalesOrders"]
| get SalesOrders
| get column4
| get 0
"#
));
// The Units column in the sheet SalesOrders has an annotation and should be ignored.
assert_eq!(actual.out, "Units");
}