Merge remote-tracking branch 'origin/main' into plugin-ctrlc
This commit is contained in:
commit
acef856916
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -3344,6 +3344,8 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-humanize",
|
"chrono-humanize",
|
||||||
"convert_case",
|
"convert_case",
|
||||||
|
"dirs",
|
||||||
|
"dirs-sys",
|
||||||
"fancy-regex",
|
"fancy-regex",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"log",
|
"log",
|
||||||
|
@ -3367,6 +3369,7 @@ dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"typetag",
|
"typetag",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -84,6 +84,7 @@ deunicode = "1.6.0"
|
||||||
dialoguer = { default-features = false, version = "0.11" }
|
dialoguer = { default-features = false, version = "0.11" }
|
||||||
digest = { default-features = false, version = "0.10" }
|
digest = { default-features = false, version = "0.10" }
|
||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
|
dirs-sys = "0.4"
|
||||||
dtparse = "2.0"
|
dtparse = "2.0"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
fancy-regex = "0.13"
|
fancy-regex = "0.13"
|
||||||
|
@ -178,6 +179,7 @@ v_htmlescape = "0.15.0"
|
||||||
wax = "0.6"
|
wax = "0.6"
|
||||||
which = "6.0.0"
|
which = "6.0.0"
|
||||||
windows = "0.54"
|
windows = "0.54"
|
||||||
|
windows-sys = "0.48"
|
||||||
winreg = "0.52"
|
winreg = "0.52"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
@ -429,6 +429,14 @@ fn find_matching_block_end_in_expr(
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
Expr::Collect(_, expr) => find_matching_block_end_in_expr(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
expr,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
),
|
||||||
|
|
||||||
Expr::Block(block_id)
|
Expr::Block(block_id)
|
||||||
| Expr::Closure(block_id)
|
| Expr::Closure(block_id)
|
||||||
| Expr::RowCondition(block_id)
|
| Expr::RowCondition(block_id)
|
||||||
|
|
|
@ -833,7 +833,7 @@ fn variables_completions() {
|
||||||
"plugin-path".into(),
|
"plugin-path".into(),
|
||||||
"startup-time".into(),
|
"startup-time".into(),
|
||||||
"temp-path".into(),
|
"temp-path".into(),
|
||||||
"vendor-autoload-dir".into(),
|
"vendor-autoload-dirs".into(),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Match results
|
// Match results
|
||||||
|
|
|
@ -21,7 +21,9 @@ impl Command for Break {
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html
|
||||||
|
|
||||||
|
break can only be used in while, loop, and for loops. It can not be used with each or other filter commands"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn command_type(&self) -> CommandType {
|
fn command_type(&self) -> CommandType {
|
||||||
|
|
|
@ -21,7 +21,9 @@ impl Command for Continue {
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html
|
||||||
|
|
||||||
|
continue can only be used in while, loop, and for loops. It can not be used with each or other filter commands"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn command_type(&self) -> CommandType {
|
fn command_type(&self) -> CommandType {
|
||||||
|
|
|
@ -4,7 +4,7 @@ use nu_protocol::{
|
||||||
ast::Block,
|
ast::Block,
|
||||||
debugger::WithoutDebug,
|
debugger::WithoutDebug,
|
||||||
engine::{StateDelta, StateWorkingSet},
|
engine::{StateDelta, StateWorkingSet},
|
||||||
Range,
|
report_error_new, Range,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -124,7 +124,10 @@ pub fn eval_block(
|
||||||
|
|
||||||
nu_engine::eval_block::<WithoutDebug>(engine_state, &mut stack, &block, input)
|
nu_engine::eval_block::<WithoutDebug>(engine_state, &mut stack, &block, input)
|
||||||
.and_then(|data| data.into_value(Span::test_data()))
|
.and_then(|data| data.into_value(Span::test_data()))
|
||||||
.unwrap_or_else(|err| panic!("test eval error in `{}`: {:?}", "TODO", err))
|
.unwrap_or_else(|err| {
|
||||||
|
report_error_new(engine_state, &err);
|
||||||
|
panic!("test eval error in `{}`: {:?}", "TODO", err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_example_evaluates_to_expected_output(
|
pub fn check_example_evaluates_to_expected_output(
|
||||||
|
|
|
@ -42,32 +42,32 @@ impl Command for MetadataSet {
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
mut input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let ds_fp: Option<String> = call.get_flag(engine_state, stack, "datasource-filepath")?;
|
let ds_fp: Option<String> = call.get_flag(engine_state, stack, "datasource-filepath")?;
|
||||||
let ds_ls = call.has_flag(engine_state, stack, "datasource-ls")?;
|
let ds_ls = call.has_flag(engine_state, stack, "datasource-ls")?;
|
||||||
let content_type: Option<String> = call.get_flag(engine_state, stack, "content-type")?;
|
let content_type: Option<String> = call.get_flag(engine_state, stack, "content-type")?;
|
||||||
let signals = engine_state.signals().clone();
|
|
||||||
let metadata = input
|
let mut metadata = match &mut input {
|
||||||
.metadata()
|
PipelineData::Value(_, metadata)
|
||||||
.clone()
|
| PipelineData::ListStream(_, metadata)
|
||||||
.unwrap_or_default()
|
| PipelineData::ByteStream(_, metadata) => metadata.take().unwrap_or_default(),
|
||||||
.with_content_type(content_type);
|
PipelineData::Empty => return Err(ShellError::PipelineEmpty { dst_span: head }),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(content_type) = content_type {
|
||||||
|
metadata.content_type = Some(content_type);
|
||||||
|
}
|
||||||
|
|
||||||
match (ds_fp, ds_ls) {
|
match (ds_fp, ds_ls) {
|
||||||
(Some(path), false) => Ok(input.into_pipeline_data_with_metadata(
|
(Some(path), false) => metadata.data_source = DataSource::FilePath(path.into()),
|
||||||
head,
|
(None, true) => metadata.data_source = DataSource::Ls,
|
||||||
signals,
|
(Some(_), true) => (), // TODO: error here
|
||||||
metadata.with_data_source(DataSource::FilePath(path.into())),
|
(None, false) => (),
|
||||||
)),
|
|
||||||
(None, true) => Ok(input.into_pipeline_data_with_metadata(
|
|
||||||
head,
|
|
||||||
signals,
|
|
||||||
metadata.with_data_source(DataSource::Ls),
|
|
||||||
)),
|
|
||||||
_ => Ok(input.into_pipeline_data_with_metadata(head, signals, metadata)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(input.set_metadata(Some(metadata)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
@ -85,7 +85,9 @@ impl Command for MetadataSet {
|
||||||
Example {
|
Example {
|
||||||
description: "Set the metadata of a file path",
|
description: "Set the metadata of a file path",
|
||||||
example: "'crates' | metadata set --content-type text/plain | metadata",
|
example: "'crates' | metadata set --content-type text/plain | metadata",
|
||||||
result: Some(Value::record(record!("content_type" => Value::string("text/plain", Span::test_data())), Span::test_data())),
|
result: Some(Value::test_record(record! {
|
||||||
|
"content_type" => Value::test_string("text/plain"),
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ mod test_examples {
|
||||||
check_example_evaluates_to_expected_output,
|
check_example_evaluates_to_expected_output,
|
||||||
check_example_input_and_output_types_match_command_signature,
|
check_example_input_and_output_types_match_command_signature,
|
||||||
};
|
};
|
||||||
use nu_cmd_lang::{Break, Echo, If, Let, Mut};
|
use nu_cmd_lang::{Break, Describe, Echo, If, Let, Mut};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{Command, EngineState, StateWorkingSet},
|
engine::{Command, EngineState, StateWorkingSet},
|
||||||
Type,
|
Type,
|
||||||
|
@ -81,6 +81,7 @@ mod test_examples {
|
||||||
working_set.add_decl(Box::new(Break));
|
working_set.add_decl(Box::new(Break));
|
||||||
working_set.add_decl(Box::new(Date));
|
working_set.add_decl(Box::new(Date));
|
||||||
working_set.add_decl(Box::new(Default));
|
working_set.add_decl(Box::new(Default));
|
||||||
|
working_set.add_decl(Box::new(Describe));
|
||||||
working_set.add_decl(Box::new(Each));
|
working_set.add_decl(Box::new(Each));
|
||||||
working_set.add_decl(Box::new(Echo));
|
working_set.add_decl(Box::new(Echo));
|
||||||
working_set.add_decl(Box::new(Enumerate));
|
working_set.add_decl(Box::new(Enumerate));
|
||||||
|
|
|
@ -61,6 +61,7 @@ impl Command for Watch {
|
||||||
"Watch all directories under `<path>` recursively. Will be ignored if `<path>` is a file (default: true)",
|
"Watch all directories under `<path>` recursively. Will be ignored if `<path>` is a file (default: true)",
|
||||||
Some('r'),
|
Some('r'),
|
||||||
)
|
)
|
||||||
|
.switch("quiet", "Hide the initial status message (default: false)", Some('q'))
|
||||||
.switch("verbose", "Operate in verbose mode (default: false)", Some('v'))
|
.switch("verbose", "Operate in verbose mode (default: false)", Some('v'))
|
||||||
.category(Category::FileSystem)
|
.category(Category::FileSystem)
|
||||||
}
|
}
|
||||||
|
@ -94,6 +95,8 @@ impl Command for Watch {
|
||||||
|
|
||||||
let verbose = call.has_flag(engine_state, stack, "verbose")?;
|
let verbose = call.has_flag(engine_state, stack, "verbose")?;
|
||||||
|
|
||||||
|
let quiet = call.has_flag(engine_state, stack, "quiet")?;
|
||||||
|
|
||||||
let debounce_duration_flag: Option<Spanned<i64>> =
|
let debounce_duration_flag: Option<Spanned<i64>> =
|
||||||
call.get_flag(engine_state, stack, "debounce-ms")?;
|
call.get_flag(engine_state, stack, "debounce-ms")?;
|
||||||
let debounce_duration = match debounce_duration_flag {
|
let debounce_duration = match debounce_duration_flag {
|
||||||
|
@ -161,7 +164,9 @@ impl Command for Watch {
|
||||||
// need to cache to make sure that rename event works.
|
// need to cache to make sure that rename event works.
|
||||||
debouncer.cache().add_root(&path, recursive_mode);
|
debouncer.cache().add_root(&path, recursive_mode);
|
||||||
|
|
||||||
eprintln!("Now watching files at {path:?}. Press ctrl+c to abort.");
|
if !quiet {
|
||||||
|
eprintln!("Now watching files at {path:?}. Press ctrl+c to abort.");
|
||||||
|
}
|
||||||
|
|
||||||
let mut closure = ClosureEval::new(engine_state, stack, closure);
|
let mut closure = ClosureEval::new(engine_state, stack, closure);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::ListStream;
|
use nu_protocol::ListStream;
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Chunks;
|
pub struct Chunks;
|
||||||
|
@ -89,26 +90,33 @@ impl Command for Chunks {
|
||||||
span: chunk_size.span(),
|
span: chunk_size.span(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if size == 0 {
|
let size = NonZeroUsize::try_from(size).map_err(|_| ShellError::IncorrectValue {
|
||||||
return Err(ShellError::IncorrectValue {
|
msg: "`chunk_size` cannot be zero".into(),
|
||||||
msg: "`chunk_size` cannot be zero".into(),
|
val_span: chunk_size.span(),
|
||||||
val_span: chunk_size.span(),
|
call_span: head,
|
||||||
call_span: head,
|
})?;
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
match input {
|
chunks(engine_state, input, size, head)
|
||||||
PipelineData::Value(Value::List { vals, .. }, metadata) => {
|
}
|
||||||
let chunks = ChunksIter::new(vals, size, head);
|
}
|
||||||
let stream = ListStream::new(chunks, head, engine_state.signals().clone());
|
|
||||||
Ok(PipelineData::ListStream(stream, metadata))
|
pub fn chunks(
|
||||||
}
|
engine_state: &EngineState,
|
||||||
PipelineData::ListStream(stream, metadata) => {
|
input: PipelineData,
|
||||||
let stream = stream.modify(|iter| ChunksIter::new(iter, size, head));
|
chunk_size: NonZeroUsize,
|
||||||
Ok(PipelineData::ListStream(stream, metadata))
|
span: Span,
|
||||||
}
|
) -> Result<PipelineData, ShellError> {
|
||||||
input => Err(input.unsupported_input_error("list", head)),
|
match input {
|
||||||
|
PipelineData::Value(Value::List { vals, .. }, metadata) => {
|
||||||
|
let chunks = ChunksIter::new(vals, chunk_size, span);
|
||||||
|
let stream = ListStream::new(chunks, span, engine_state.signals().clone());
|
||||||
|
Ok(PipelineData::ListStream(stream, metadata))
|
||||||
}
|
}
|
||||||
|
PipelineData::ListStream(stream, metadata) => {
|
||||||
|
let stream = stream.modify(|iter| ChunksIter::new(iter, chunk_size, span));
|
||||||
|
Ok(PipelineData::ListStream(stream, metadata))
|
||||||
|
}
|
||||||
|
input => Err(input.unsupported_input_error("list", span)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,10 +127,10 @@ struct ChunksIter<I: Iterator<Item = Value>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: Iterator<Item = Value>> ChunksIter<I> {
|
impl<I: Iterator<Item = Value>> ChunksIter<I> {
|
||||||
fn new(iter: impl IntoIterator<IntoIter = I>, size: usize, span: Span) -> Self {
|
fn new(iter: impl IntoIterator<IntoIter = I>, size: NonZeroUsize, span: Span) -> Self {
|
||||||
Self {
|
Self {
|
||||||
iter: iter.into_iter(),
|
iter: iter.into_iter(),
|
||||||
size,
|
size: size.into(),
|
||||||
span,
|
span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,8 +132,6 @@ with 'transpose' first."#
|
||||||
Ok(data) => Some(data.into_value(head).unwrap_or_else(|err| {
|
Ok(data) => Some(data.into_value(head).unwrap_or_else(|err| {
|
||||||
Value::error(chain_error_with_input(err, is_error, span), span)
|
Value::error(chain_error_with_input(err, is_error, span), span)
|
||||||
})),
|
})),
|
||||||
Err(ShellError::Continue { span }) => Some(Value::nothing(span)),
|
|
||||||
Err(ShellError::Break { .. }) => None,
|
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
let error = chain_error_with_input(error, is_error, span);
|
let error = chain_error_with_input(error, is_error, span);
|
||||||
Some(Value::error(error, span))
|
Some(Value::error(error, span))
|
||||||
|
@ -149,10 +147,6 @@ with 'transpose' first."#
|
||||||
.map_while(move |value| {
|
.map_while(move |value| {
|
||||||
let value = match value {
|
let value = match value {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(ShellError::Continue { span }) => {
|
|
||||||
return Some(Value::nothing(span))
|
|
||||||
}
|
|
||||||
Err(ShellError::Break { .. }) => return None,
|
|
||||||
Err(err) => return Some(Value::error(err, head)),
|
Err(err) => return Some(Value::error(err, head)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -163,8 +157,6 @@ with 'transpose' first."#
|
||||||
.and_then(|data| data.into_value(head))
|
.and_then(|data| data.into_value(head))
|
||||||
{
|
{
|
||||||
Ok(value) => Some(value),
|
Ok(value) => Some(value),
|
||||||
Err(ShellError::Continue { span }) => Some(Value::nothing(span)),
|
|
||||||
Err(ShellError::Break { .. }) => None,
|
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
let error = chain_error_with_input(error, is_error, span);
|
let error = chain_error_with_input(error, is_error, span);
|
||||||
Some(Value::error(error, span))
|
Some(Value::error(error, span))
|
||||||
|
|
|
@ -60,7 +60,6 @@ impl Command for Items {
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(value) => Some(value),
|
Ok(value) => Some(value),
|
||||||
Err(ShellError::Break { .. }) => None,
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let err = chain_error_with_input(err, false, span);
|
let err = chain_error_with_input(err, false, span);
|
||||||
Some(Value::error(err, head))
|
Some(Value::error(err, head))
|
||||||
|
|
|
@ -36,7 +36,7 @@ impl Command for Reduce {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Aggregate a list to a single value using an accumulator closure."
|
"Aggregate a list (starting from the left) to a single value using an accumulator closure."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
@ -50,6 +50,11 @@ impl Command for Reduce {
|
||||||
description: "Sum values of a list (same as 'math sum')",
|
description: "Sum values of a list (same as 'math sum')",
|
||||||
result: Some(Value::test_int(10)),
|
result: Some(Value::test_int(10)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
example: "[ 1 2 3 4 ] | reduce {|it, acc| $acc - $it }",
|
||||||
|
description: r#"`reduce` accumulates value from left to right, equivalent to (((1 - 2) - 3) - 4)."#,
|
||||||
|
result: Some(Value::test_int(-8)),
|
||||||
|
},
|
||||||
Example {
|
Example {
|
||||||
example:
|
example:
|
||||||
"[ 8 7 6 ] | enumerate | reduce --fold 0 {|it, acc| $acc + $it.item + $it.index }",
|
"[ 8 7 6 ] | enumerate | reduce --fold 0 {|it, acc| $acc + $it.item + $it.index }",
|
||||||
|
@ -61,6 +66,11 @@ impl Command for Reduce {
|
||||||
description: "Sum values with a starting value (fold)",
|
description: "Sum values with a starting value (fold)",
|
||||||
result: Some(Value::test_int(20)),
|
result: Some(Value::test_int(20)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
example: r#"[[foo baz] [baz quux]] | reduce --fold "foobar" {|it, acc| $acc | str replace $it.0 $it.1}"#,
|
||||||
|
description: "Iteratively perform string replace (from left to right): 'foobar' -> 'bazbar' -> 'quuxbar'",
|
||||||
|
result: Some(Value::test_string("quuxbar")),
|
||||||
|
},
|
||||||
Example {
|
Example {
|
||||||
example: r#"[ i o t ] | reduce --fold "Arthur, King of the Britons" {|it, acc| $acc | str replace --all $it "X" }"#,
|
example: r#"[ i o t ] | reduce --fold "Arthur, King of the Britons" {|it, acc| $acc | str replace --all $it "X" }"#,
|
||||||
description: "Replace selected characters in a string with 'X'",
|
description: "Replace selected characters in a string with 'X'",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::ValueIterator;
|
use nu_protocol::ListStream;
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Window;
|
pub struct Window;
|
||||||
|
@ -12,8 +13,8 @@ impl Command for Window {
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("window")
|
Signature::build("window")
|
||||||
.input_output_types(vec![(
|
.input_output_types(vec![(
|
||||||
Type::List(Box::new(Type::Any)),
|
Type::list(Type::Any),
|
||||||
Type::List(Box::new(Type::List(Box::new(Type::Any)))),
|
Type::list(Type::list(Type::Any)),
|
||||||
)])
|
)])
|
||||||
.required("window_size", SyntaxShape::Int, "The size of each window.")
|
.required("window_size", SyntaxShape::Int, "The size of each window.")
|
||||||
.named(
|
.named(
|
||||||
|
@ -34,72 +35,41 @@ impl Command for Window {
|
||||||
"Creates a sliding window of `window_size` that slide by n rows/elements across input."
|
"Creates a sliding window of `window_size` that slide by n rows/elements across input."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
"This command will error if `window_size` or `stride` are negative or zero."
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
let stream_test_1 = vec![
|
|
||||||
Value::list(
|
|
||||||
vec![Value::test_int(1), Value::test_int(2)],
|
|
||||||
Span::test_data(),
|
|
||||||
),
|
|
||||||
Value::list(
|
|
||||||
vec![Value::test_int(2), Value::test_int(3)],
|
|
||||||
Span::test_data(),
|
|
||||||
),
|
|
||||||
Value::list(
|
|
||||||
vec![Value::test_int(3), Value::test_int(4)],
|
|
||||||
Span::test_data(),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
let stream_test_2 = vec![
|
|
||||||
Value::list(
|
|
||||||
vec![Value::test_int(1), Value::test_int(2)],
|
|
||||||
Span::test_data(),
|
|
||||||
),
|
|
||||||
Value::list(
|
|
||||||
vec![Value::test_int(4), Value::test_int(5)],
|
|
||||||
Span::test_data(),
|
|
||||||
),
|
|
||||||
Value::list(
|
|
||||||
vec![Value::test_int(7), Value::test_int(8)],
|
|
||||||
Span::test_data(),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
let stream_test_3 = vec![
|
|
||||||
Value::list(
|
|
||||||
vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)],
|
|
||||||
Span::test_data(),
|
|
||||||
),
|
|
||||||
Value::list(
|
|
||||||
vec![Value::test_int(4), Value::test_int(5)],
|
|
||||||
Span::test_data(),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
example: "[1 2 3 4] | window 2",
|
example: "[1 2 3 4] | window 2",
|
||||||
description: "A sliding window of two elements",
|
description: "A sliding window of two elements",
|
||||||
result: Some(Value::list(
|
result: Some(Value::test_list(vec![
|
||||||
stream_test_1,
|
Value::test_list(vec![Value::test_int(1), Value::test_int(2)]),
|
||||||
Span::test_data(),
|
Value::test_list(vec![Value::test_int(2), Value::test_int(3)]),
|
||||||
)),
|
Value::test_list(vec![Value::test_int(3), Value::test_int(4)]),
|
||||||
|
])),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
example: "[1, 2, 3, 4, 5, 6, 7, 8] | window 2 --stride 3",
|
example: "[1, 2, 3, 4, 5, 6, 7, 8] | window 2 --stride 3",
|
||||||
description: "A sliding window of two elements, with a stride of 3",
|
description: "A sliding window of two elements, with a stride of 3",
|
||||||
result: Some(Value::list(
|
result: Some(Value::test_list(vec![
|
||||||
stream_test_2,
|
Value::test_list(vec![Value::test_int(1), Value::test_int(2)]),
|
||||||
Span::test_data(),
|
Value::test_list(vec![Value::test_int(4), Value::test_int(5)]),
|
||||||
)),
|
Value::test_list(vec![Value::test_int(7), Value::test_int(8)]),
|
||||||
|
])),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
example: "[1, 2, 3, 4, 5] | window 3 --stride 3 --remainder",
|
example: "[1, 2, 3, 4, 5] | window 3 --stride 3 --remainder",
|
||||||
description: "A sliding window of equal stride that includes remainder. Equivalent to chunking",
|
description: "A sliding window of equal stride that includes remainder. Equivalent to chunking",
|
||||||
result: Some(Value::list(
|
result: Some(Value::test_list(vec![
|
||||||
stream_test_3,
|
Value::test_list(vec![
|
||||||
Span::test_data(),
|
Value::test_int(1),
|
||||||
)),
|
Value::test_int(2),
|
||||||
|
Value::test_int(3),
|
||||||
|
]),
|
||||||
|
Value::test_list(vec![Value::test_int(4), Value::test_int(5)]),
|
||||||
|
])),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -112,116 +82,169 @@ impl Command for Window {
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let group_size: Spanned<usize> = call.req(engine_state, stack, 0)?;
|
let window_size: Value = call.req(engine_state, stack, 0)?;
|
||||||
let metadata = input.metadata();
|
let stride: Option<Value> = call.get_flag(engine_state, stack, "stride")?;
|
||||||
let stride: Option<usize> = call.get_flag(engine_state, stack, "stride")?;
|
|
||||||
let remainder = call.has_flag(engine_state, stack, "remainder")?;
|
let remainder = call.has_flag(engine_state, stack, "remainder")?;
|
||||||
|
|
||||||
let stride = stride.unwrap_or(1);
|
let size =
|
||||||
|
usize::try_from(window_size.as_int()?).map_err(|_| ShellError::NeedsPositiveValue {
|
||||||
|
span: window_size.span(),
|
||||||
|
})?;
|
||||||
|
|
||||||
//FIXME: add in support for external redirection when engine-q supports it generally
|
let size = NonZeroUsize::try_from(size).map_err(|_| ShellError::IncorrectValue {
|
||||||
|
msg: "`window_size` cannot be zero".into(),
|
||||||
|
val_span: window_size.span(),
|
||||||
|
call_span: head,
|
||||||
|
})?;
|
||||||
|
|
||||||
let each_group_iterator = EachWindowIterator {
|
let stride = if let Some(stride_val) = stride {
|
||||||
group_size: group_size.item,
|
let stride = usize::try_from(stride_val.as_int()?).map_err(|_| {
|
||||||
input: Box::new(input.into_iter()),
|
ShellError::NeedsPositiveValue {
|
||||||
span: head,
|
span: stride_val.span(),
|
||||||
previous: None,
|
}
|
||||||
stride,
|
})?;
|
||||||
remainder,
|
|
||||||
|
NonZeroUsize::try_from(stride).map_err(|_| ShellError::IncorrectValue {
|
||||||
|
msg: "`stride` cannot be zero".into(),
|
||||||
|
val_span: stride_val.span(),
|
||||||
|
call_span: head,
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
NonZeroUsize::MIN
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(each_group_iterator.into_pipeline_data_with_metadata(
|
if remainder && size == stride {
|
||||||
head,
|
super::chunks::chunks(engine_state, input, size, head)
|
||||||
engine_state.signals().clone(),
|
} else if stride >= size {
|
||||||
metadata,
|
match input {
|
||||||
))
|
PipelineData::Value(Value::List { vals, .. }, metadata) => {
|
||||||
|
let chunks = WindowGapIter::new(vals, size, stride, remainder, head);
|
||||||
|
let stream = ListStream::new(chunks, head, engine_state.signals().clone());
|
||||||
|
Ok(PipelineData::ListStream(stream, metadata))
|
||||||
|
}
|
||||||
|
PipelineData::ListStream(stream, metadata) => {
|
||||||
|
let stream = stream
|
||||||
|
.modify(|iter| WindowGapIter::new(iter, size, stride, remainder, head));
|
||||||
|
Ok(PipelineData::ListStream(stream, metadata))
|
||||||
|
}
|
||||||
|
input => Err(input.unsupported_input_error("list", head)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match input {
|
||||||
|
PipelineData::Value(Value::List { vals, .. }, metadata) => {
|
||||||
|
let chunks = WindowOverlapIter::new(vals, size, stride, remainder, head);
|
||||||
|
let stream = ListStream::new(chunks, head, engine_state.signals().clone());
|
||||||
|
Ok(PipelineData::ListStream(stream, metadata))
|
||||||
|
}
|
||||||
|
PipelineData::ListStream(stream, metadata) => {
|
||||||
|
let stream = stream
|
||||||
|
.modify(|iter| WindowOverlapIter::new(iter, size, stride, remainder, head));
|
||||||
|
Ok(PipelineData::ListStream(stream, metadata))
|
||||||
|
}
|
||||||
|
input => Err(input.unsupported_input_error("list", head)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EachWindowIterator {
|
struct WindowOverlapIter<I: Iterator<Item = Value>> {
|
||||||
group_size: usize,
|
iter: I,
|
||||||
input: ValueIterator,
|
window: Vec<Value>,
|
||||||
span: Span,
|
|
||||||
previous: Option<Vec<Value>>,
|
|
||||||
stride: usize,
|
stride: usize,
|
||||||
remainder: bool,
|
remainder: bool,
|
||||||
|
span: Span,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for EachWindowIterator {
|
impl<I: Iterator<Item = Value>> WindowOverlapIter<I> {
|
||||||
|
fn new(
|
||||||
|
iter: impl IntoIterator<IntoIter = I>,
|
||||||
|
size: NonZeroUsize,
|
||||||
|
stride: NonZeroUsize,
|
||||||
|
remainder: bool,
|
||||||
|
span: Span,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
iter: iter.into_iter(),
|
||||||
|
window: Vec::with_capacity(size.into()),
|
||||||
|
stride: stride.into(),
|
||||||
|
remainder,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: Iterator<Item = Value>> Iterator for WindowOverlapIter<I> {
|
||||||
type Item = Value;
|
type Item = Value;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let mut group = self.previous.take().unwrap_or_else(|| {
|
let len = if self.window.is_empty() {
|
||||||
let mut vec = Vec::new();
|
self.window.capacity()
|
||||||
|
|
||||||
// We default to a Vec of capacity size + stride as striding pushes n extra elements to the end
|
|
||||||
vec.try_reserve(self.group_size + self.stride).ok();
|
|
||||||
|
|
||||||
vec
|
|
||||||
});
|
|
||||||
let mut current_count = 0;
|
|
||||||
|
|
||||||
if group.is_empty() {
|
|
||||||
loop {
|
|
||||||
let item = self.input.next();
|
|
||||||
|
|
||||||
match item {
|
|
||||||
Some(v) => {
|
|
||||||
group.push(v);
|
|
||||||
|
|
||||||
current_count += 1;
|
|
||||||
if current_count >= self.group_size {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
if self.remainder {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// our historic buffer is already full, so stride instead
|
self.stride
|
||||||
|
};
|
||||||
|
|
||||||
loop {
|
self.window.extend((&mut self.iter).take(len));
|
||||||
let item = self.input.next();
|
|
||||||
|
|
||||||
match item {
|
if self.window.len() == self.window.capacity()
|
||||||
Some(v) => {
|
|| (self.remainder && !self.window.is_empty())
|
||||||
group.push(v);
|
{
|
||||||
|
let mut next = Vec::with_capacity(self.window.len());
|
||||||
current_count += 1;
|
next.extend(self.window.iter().skip(self.stride).cloned());
|
||||||
if current_count >= self.stride {
|
let window = std::mem::replace(&mut self.window, next);
|
||||||
break;
|
Some(Value::list(window, self.span))
|
||||||
}
|
} else {
|
||||||
}
|
None
|
||||||
None => {
|
|
||||||
if self.remainder {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We now have elements + stride in our group, and need to
|
|
||||||
// drop the skipped elements. Drain to preserve allocation and capacity
|
|
||||||
// Dropping this iterator consumes it.
|
|
||||||
group.drain(..self.stride.min(group.len()));
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if group.is_empty() {
|
struct WindowGapIter<I: Iterator<Item = Value>> {
|
||||||
return None;
|
iter: I,
|
||||||
|
size: usize,
|
||||||
|
skip: usize,
|
||||||
|
first: bool,
|
||||||
|
remainder: bool,
|
||||||
|
span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: Iterator<Item = Value>> WindowGapIter<I> {
|
||||||
|
fn new(
|
||||||
|
iter: impl IntoIterator<IntoIter = I>,
|
||||||
|
size: NonZeroUsize,
|
||||||
|
stride: NonZeroUsize,
|
||||||
|
remainder: bool,
|
||||||
|
span: Span,
|
||||||
|
) -> Self {
|
||||||
|
let size = size.into();
|
||||||
|
Self {
|
||||||
|
iter: iter.into_iter(),
|
||||||
|
size,
|
||||||
|
skip: stride.get() - size,
|
||||||
|
first: true,
|
||||||
|
remainder,
|
||||||
|
span,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let return_group = group.clone();
|
impl<I: Iterator<Item = Value>> Iterator for WindowGapIter<I> {
|
||||||
self.previous = Some(group);
|
type Item = Value;
|
||||||
|
|
||||||
Some(Value::list(return_group, self.span))
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let mut window = Vec::with_capacity(self.size);
|
||||||
|
window.extend(
|
||||||
|
(&mut self.iter)
|
||||||
|
.skip(if self.first { 0 } else { self.skip })
|
||||||
|
.take(self.size),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.first = false;
|
||||||
|
|
||||||
|
if window.len() == self.size || (self.remainder && !window.is_empty()) {
|
||||||
|
Some(Value::list(window, self.span))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,12 @@ impl Command for Generate {
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("generate")
|
Signature::build("generate")
|
||||||
.input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::Any)))])
|
.input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::Any)))])
|
||||||
.required("initial", SyntaxShape::Any, "Initial value.")
|
|
||||||
.required(
|
.required(
|
||||||
"closure",
|
"closure",
|
||||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||||
"Generator function.",
|
"Generator function.",
|
||||||
)
|
)
|
||||||
|
.optional("initial", SyntaxShape::Any, "Initial value.")
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.category(Category::Generators)
|
.category(Category::Generators)
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ used as the next argument to the closure, otherwise generation stops.
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
example: "generate 0 {|i| if $i <= 10 { {out: $i, next: ($i + 2)} }}",
|
example: "generate {|i| if $i <= 10 { {out: $i, next: ($i + 2)} }} 0",
|
||||||
description: "Generate a sequence of numbers",
|
description: "Generate a sequence of numbers",
|
||||||
result: Some(Value::list(
|
result: Some(Value::list(
|
||||||
vec![
|
vec![
|
||||||
|
@ -57,10 +57,17 @@ used as the next argument to the closure, otherwise generation stops.
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
example:
|
example:
|
||||||
"generate [0, 1] {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} }",
|
"generate {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} } [0, 1]",
|
||||||
description: "Generate a continuous stream of Fibonacci numbers",
|
description: "Generate a continuous stream of Fibonacci numbers",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
example:
|
||||||
|
"generate {|fib=[0, 1]| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} }",
|
||||||
|
description:
|
||||||
|
"Generate a continuous stream of Fibonacci numbers, using default parameters",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,15 +79,15 @@ used as the next argument to the closure, otherwise generation stops.
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let initial: Value = call.req(engine_state, stack, 0)?;
|
let closure: Closure = call.req(engine_state, stack, 0)?;
|
||||||
let closure: Closure = call.req(engine_state, stack, 1)?;
|
let initial: Option<Value> = call.opt(engine_state, stack, 1)?;
|
||||||
|
let block = engine_state.get_block(closure.block_id);
|
||||||
let mut closure = ClosureEval::new(engine_state, stack, closure);
|
let mut closure = ClosureEval::new(engine_state, stack, closure);
|
||||||
|
|
||||||
// A type of Option<S> is used to represent state. Invocation
|
// A type of Option<S> is used to represent state. Invocation
|
||||||
// will stop on None. Using Option<S> allows functions to output
|
// will stop on None. Using Option<S> allows functions to output
|
||||||
// one final value before stopping.
|
// one final value before stopping.
|
||||||
let mut state = Some(initial);
|
let mut state = Some(get_initial_state(initial, &block.signature, call.head)?);
|
||||||
let iter = std::iter::from_fn(move || {
|
let iter = std::iter::from_fn(move || {
|
||||||
let arg = state.take()?;
|
let arg = state.take()?;
|
||||||
|
|
||||||
|
@ -170,6 +177,38 @@ used as the next argument to the closure, otherwise generation stops.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_initial_state(
|
||||||
|
initial: Option<Value>,
|
||||||
|
signature: &Signature,
|
||||||
|
span: Span,
|
||||||
|
) -> Result<Value, ShellError> {
|
||||||
|
match initial {
|
||||||
|
Some(v) => Ok(v),
|
||||||
|
None => {
|
||||||
|
// the initial state should be referred from signature
|
||||||
|
if !signature.optional_positional.is_empty()
|
||||||
|
&& signature.optional_positional[0].default_value.is_some()
|
||||||
|
{
|
||||||
|
Ok(signature.optional_positional[0]
|
||||||
|
.default_value
|
||||||
|
.clone()
|
||||||
|
.expect("Already checked default value"))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::GenericError {
|
||||||
|
error: "The initial value is missing".to_string(),
|
||||||
|
msg: "Missing initial value".to_string(),
|
||||||
|
span: Some(span),
|
||||||
|
help: Some(
|
||||||
|
"Provide an <initial> value as an argument to generate, or assign a default value to the closure parameter"
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
inner: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -3,6 +3,7 @@ use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
use nu_protocol::Signals;
|
use nu_protocol::Signals;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
// Character used to separate directories in a Path Environment variable on windows is ";"
|
// Character used to separate directories in a Path Environment variable on windows is ";"
|
||||||
#[cfg(target_family = "windows")]
|
#[cfg(target_family = "windows")]
|
||||||
|
@ -149,6 +150,24 @@ static CHAR_MAP: Lazy<IndexMap<&'static str, String>> = Lazy::new(|| {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
static NO_OUTPUT_CHARS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
|
||||||
|
[
|
||||||
|
// If the character is in the this set, we don't output it to prevent
|
||||||
|
// the broken of `char --list` command table format and alignment.
|
||||||
|
"newline",
|
||||||
|
"enter",
|
||||||
|
"nl",
|
||||||
|
"line_feed",
|
||||||
|
"lf",
|
||||||
|
"cr",
|
||||||
|
"crlf",
|
||||||
|
"bel",
|
||||||
|
"backspace",
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
|
||||||
impl Command for Char {
|
impl Command for Char {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"char"
|
"char"
|
||||||
|
@ -297,6 +316,11 @@ fn generate_character_list(signals: Signals, call_span: Span) -> PipelineData {
|
||||||
CHAR_MAP
|
CHAR_MAP
|
||||||
.iter()
|
.iter()
|
||||||
.map(move |(name, s)| {
|
.map(move |(name, s)| {
|
||||||
|
let character = if NO_OUTPUT_CHARS.contains(name) {
|
||||||
|
Value::string("", call_span)
|
||||||
|
} else {
|
||||||
|
Value::string(s, call_span)
|
||||||
|
};
|
||||||
let unicode = Value::string(
|
let unicode = Value::string(
|
||||||
s.chars()
|
s.chars()
|
||||||
.map(|c| format!("{:x}", c as u32))
|
.map(|c| format!("{:x}", c as u32))
|
||||||
|
@ -306,7 +330,7 @@ fn generate_character_list(signals: Signals, call_span: Span) -> PipelineData {
|
||||||
);
|
);
|
||||||
let record = record! {
|
let record = record! {
|
||||||
"name" => Value::string(*name, call_span),
|
"name" => Value::string(*name, call_span),
|
||||||
"character" => Value::string(s, call_span),
|
"character" => character,
|
||||||
"unicode" => unicode,
|
"unicode" => unicode,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,3 @@ fn break_while_loop() {
|
||||||
|
|
||||||
assert_eq!(actual.out, "hello");
|
assert_eq!(actual.out, "hello");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn break_each() {
|
|
||||||
let actual = nu!("
|
|
||||||
[1, 2, 3, 4, 5] | each {|x| if $x > 3 { break }; $x} | math sum
|
|
||||||
");
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "6");
|
|
||||||
}
|
|
||||||
|
|
|
@ -58,22 +58,6 @@ fn each_while_uses_enumerate_index() {
|
||||||
assert_eq!(actual.out, "[0, 1, 2, 3]");
|
assert_eq!(actual.out, "[0, 1, 2, 3]");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn each_element_continue_command() {
|
|
||||||
let actual =
|
|
||||||
nu!("[1,2,3,4,6,7] | each { |x| if ($x mod 2 == 0) {continue} else { $x }} | to nuon");
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "[1, 3, 7]");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn each_element_break_command() {
|
|
||||||
let actual =
|
|
||||||
nu!("[1,2,5,4,6,7] | each { |x| if ($x mod 3 == 0) {break} else { $x }} | to nuon");
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "[1, 2, 5, 4]");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn errors_in_nested_each_show() {
|
fn errors_in_nested_each_show() {
|
||||||
let actual = nu!("[[1,2]] | each {|x| $x | each {|y| error make {msg: \"oh noes\"} } }");
|
let actual = nu!("[[1,2]] | each {|x| $x | each {|y| error make {msg: \"oh noes\"} } }");
|
||||||
|
|
|
@ -3,7 +3,7 @@ use nu_test_support::{nu, pipeline};
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_no_next_break() {
|
fn generate_no_next_break() {
|
||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
"generate 1 {|x| if $x == 3 { {out: $x}} else { {out: $x, next: ($x + 1)} }} | to nuon"
|
"generate {|x| if $x == 3 { {out: $x}} else { {out: $x, next: ($x + 1)} }} 1 | to nuon"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(actual.out, "[1, 2, 3]");
|
assert_eq!(actual.out, "[1, 2, 3]");
|
||||||
|
@ -11,7 +11,7 @@ fn generate_no_next_break() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_null_break() {
|
fn generate_null_break() {
|
||||||
let actual = nu!("generate 1 {|x| if $x <= 3 { {out: $x, next: ($x + 1)} }} | to nuon");
|
let actual = nu!("generate {|x| if $x <= 3 { {out: $x, next: ($x + 1)} }} 1 | to nuon");
|
||||||
|
|
||||||
assert_eq!(actual.out, "[1, 2, 3]");
|
assert_eq!(actual.out, "[1, 2, 3]");
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,13 @@ fn generate_null_break() {
|
||||||
fn generate_allows_empty_output() {
|
fn generate_allows_empty_output() {
|
||||||
let actual = nu!(pipeline(
|
let actual = nu!(pipeline(
|
||||||
r#"
|
r#"
|
||||||
generate 0 {|x|
|
generate {|x|
|
||||||
if $x == 1 {
|
if $x == 1 {
|
||||||
{next: ($x + 1)}
|
{next: ($x + 1)}
|
||||||
} else if $x < 3 {
|
} else if $x < 3 {
|
||||||
{out: $x, next: ($x + 1)}
|
{out: $x, next: ($x + 1)}
|
||||||
}
|
}
|
||||||
} | to nuon
|
} 0 | to nuon
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -37,11 +37,11 @@ fn generate_allows_empty_output() {
|
||||||
fn generate_allows_no_output() {
|
fn generate_allows_no_output() {
|
||||||
let actual = nu!(pipeline(
|
let actual = nu!(pipeline(
|
||||||
r#"
|
r#"
|
||||||
generate 0 {|x|
|
generate {|x|
|
||||||
if $x < 3 {
|
if $x < 3 {
|
||||||
{next: ($x + 1)}
|
{next: ($x + 1)}
|
||||||
}
|
}
|
||||||
} | to nuon
|
} 0 | to nuon
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ fn generate_allows_no_output() {
|
||||||
fn generate_allows_null_state() {
|
fn generate_allows_null_state() {
|
||||||
let actual = nu!(pipeline(
|
let actual = nu!(pipeline(
|
||||||
r#"
|
r#"
|
||||||
generate 0 {|x|
|
generate {|x|
|
||||||
if $x == null {
|
if $x == null {
|
||||||
{out: "done"}
|
{out: "done"}
|
||||||
} else if $x < 1 {
|
} else if $x < 1 {
|
||||||
|
@ -60,7 +60,7 @@ fn generate_allows_null_state() {
|
||||||
} else {
|
} else {
|
||||||
{out: "stopping", next: null}
|
{out: "stopping", next: null}
|
||||||
}
|
}
|
||||||
} | to nuon
|
} 0 | to nuon
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -71,7 +71,42 @@ fn generate_allows_null_state() {
|
||||||
fn generate_allows_null_output() {
|
fn generate_allows_null_output() {
|
||||||
let actual = nu!(pipeline(
|
let actual = nu!(pipeline(
|
||||||
r#"
|
r#"
|
||||||
generate 0 {|x|
|
generate {|x|
|
||||||
|
if $x == 3 {
|
||||||
|
{out: "done"}
|
||||||
|
} else {
|
||||||
|
{out: null, next: ($x + 1)}
|
||||||
|
}
|
||||||
|
} 0 | to nuon
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "[null, null, null, done]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_disallows_extra_keys() {
|
||||||
|
let actual = nu!("generate {|x| {foo: bar, out: $x}} 0 ");
|
||||||
|
assert!(actual.err.contains("Invalid block return"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_disallows_list() {
|
||||||
|
let actual = nu!("generate {|x| [$x, ($x + 1)]} 0 ");
|
||||||
|
assert!(actual.err.contains("Invalid block return"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_disallows_primitive() {
|
||||||
|
let actual = nu!("generate {|x| 1} 0");
|
||||||
|
assert!(actual.err.contains("Invalid block return"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_allow_default_parameter() {
|
||||||
|
let actual = nu!(pipeline(
|
||||||
|
r#"
|
||||||
|
generate {|x = 0|
|
||||||
if $x == 3 {
|
if $x == 3 {
|
||||||
{out: "done"}
|
{out: "done"}
|
||||||
} else {
|
} else {
|
||||||
|
@ -82,22 +117,34 @@ fn generate_allows_null_output() {
|
||||||
));
|
));
|
||||||
|
|
||||||
assert_eq!(actual.out, "[null, null, null, done]");
|
assert_eq!(actual.out, "[null, null, null, done]");
|
||||||
|
|
||||||
|
// if initial is given, use initial value
|
||||||
|
let actual = nu!(pipeline(
|
||||||
|
r#"
|
||||||
|
generate {|x = 0|
|
||||||
|
if $x == 3 {
|
||||||
|
{out: "done"}
|
||||||
|
} else {
|
||||||
|
{out: null, next: ($x + 1)}
|
||||||
|
}
|
||||||
|
} 1 | to nuon
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
assert_eq!(actual.out, "[null, null, done]");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_disallows_extra_keys() {
|
fn generate_raise_error_on_no_default_parameter_closure_and_init_val() {
|
||||||
let actual = nu!("generate 0 {|x| {foo: bar, out: $x}}");
|
let actual = nu!(pipeline(
|
||||||
assert!(actual.err.contains("Invalid block return"));
|
r#"
|
||||||
}
|
generate {|x|
|
||||||
|
if $x == 3 {
|
||||||
#[test]
|
{out: "done"}
|
||||||
fn generate_disallows_list() {
|
} else {
|
||||||
let actual = nu!("generate 0 {|x| [$x, ($x + 1)]}");
|
{out: null, next: ($x + 1)}
|
||||||
assert!(actual.err.contains("Invalid block return"));
|
}
|
||||||
}
|
} | to nuon
|
||||||
|
"#
|
||||||
#[test]
|
));
|
||||||
fn generate_disallows_primitive() {
|
assert!(actual.err.contains("The initial value is missing"));
|
||||||
let actual = nu!("generate 0 {|x| 1}");
|
|
||||||
assert!(actual.err.contains("Invalid block return"));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,6 +115,7 @@ mod try_;
|
||||||
mod ucp;
|
mod ucp;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
mod ulimit;
|
mod ulimit;
|
||||||
|
mod window;
|
||||||
|
|
||||||
mod debug;
|
mod debug;
|
||||||
mod umkdir;
|
mod umkdir;
|
||||||
|
|
|
@ -439,3 +439,37 @@ fn no_duplicate_redirection() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest::rstest]
|
||||||
|
#[case("let", "out>")]
|
||||||
|
#[case("let", "err>")]
|
||||||
|
#[case("let", "out+err>")]
|
||||||
|
#[case("mut", "out>")]
|
||||||
|
#[case("mut", "err>")]
|
||||||
|
#[case("mut", "out+err>")]
|
||||||
|
fn file_redirection_in_let_and_mut(#[case] keyword: &str, #[case] redir: &str) {
|
||||||
|
Playground::setup("file redirection in let and mut", |dirs, _| {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(),
|
||||||
|
format!("$env.BAZ = 'foo'; {keyword} v = nu --testbin echo_env_mixed out-err BAZ BAZ {redir} result.txt")
|
||||||
|
);
|
||||||
|
assert!(actual.status.success());
|
||||||
|
assert!(file_contents(dirs.test().join("result.txt")).contains("foo"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest::rstest]
|
||||||
|
#[case("let", "err>|", "foo3")]
|
||||||
|
#[case("let", "out+err>|", "7")]
|
||||||
|
#[case("mut", "err>|", "foo3")]
|
||||||
|
#[case("mut", "out+err>|", "7")]
|
||||||
|
fn pipe_redirection_in_let_and_mut(
|
||||||
|
#[case] keyword: &str,
|
||||||
|
#[case] redir: &str,
|
||||||
|
#[case] output: &str,
|
||||||
|
) {
|
||||||
|
let actual = nu!(
|
||||||
|
format!("$env.BAZ = 'foo'; {keyword} v = nu --testbin echo_env_mixed out-err BAZ BAZ {redir} str length; $v")
|
||||||
|
);
|
||||||
|
assert_eq!(actual.out, output);
|
||||||
|
}
|
||||||
|
|
103
crates/nu-command/tests/commands/window.rs
Normal file
103
crates/nu-command/tests/commands/window.rs
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
use nu_test_support::nu;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn window_size_negative() {
|
||||||
|
let actual = nu!("[0 1 2] | window -1");
|
||||||
|
assert!(actual.err.contains("positive"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn window_size_zero() {
|
||||||
|
let actual = nu!("[0 1 2] | window 0");
|
||||||
|
assert!(actual.err.contains("zero"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn window_size_not_int() {
|
||||||
|
let actual = nu!("[0 1 2] | window (if true { 1sec })");
|
||||||
|
assert!(actual.err.contains("can't convert"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stride_negative() {
|
||||||
|
let actual = nu!("[0 1 2] | window 1 -s -1");
|
||||||
|
assert!(actual.err.contains("positive"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stride_zero() {
|
||||||
|
let actual = nu!("[0 1 2] | window 1 -s 0");
|
||||||
|
assert!(actual.err.contains("zero"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stride_not_int() {
|
||||||
|
let actual = nu!("[0 1 2] | window 1 -s (if true { 1sec })");
|
||||||
|
assert!(actual.err.contains("can't convert"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty() {
|
||||||
|
let actual = nu!("[] | window 2 | is-empty");
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_stream() {
|
||||||
|
let actual = nu!("([0 1 2] | every 1 | window 2) == ([0 1 2] | window 2)");
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn table_stream() {
|
||||||
|
let actual = nu!("([[foo bar]; [0 1] [2 3] [4 5]] | every 1 | window 2) == ([[foo bar]; [0 1] [2 3] [4 5]] | window 2)");
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_empty_chunks() {
|
||||||
|
let actual = nu!("([0 1 2 3 4 5] | window 3 -s 3 -r | length) == 2");
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn same_as_chunks() {
|
||||||
|
let actual = nu!("([0 1 2 3 4] | window 2 -s 2 -r) == ([0 1 2 3 4 ] | chunks 2)");
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stride_equal_to_window_size() {
|
||||||
|
let actual = nu!("([0 1 2 3] | window 2 -s 2 | flatten) == [0 1 2 3]");
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stride_greater_than_window_size() {
|
||||||
|
let actual = nu!("([0 1 2 3 4] | window 2 -s 3 | flatten) == [0 1 3 4]");
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stride_less_than_window_size() {
|
||||||
|
let actual = nu!("([0 1 2 3 4 5] | window 3 -s 2 | length) == 2");
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stride_equal_to_window_size_remainder() {
|
||||||
|
let actual = nu!("([0 1 2 3 4] | window 2 -s 2 -r | flatten) == [0 1 2 3 4]");
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stride_greater_than_window_size_remainder() {
|
||||||
|
let actual = nu!("([0 1 2 3 4] | window 2 -s 3 -r | flatten) == [0 1 3 4]");
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stride_less_than_window_size_remainder() {
|
||||||
|
let actual = nu!("([0 1 2 3 4 5] | window 3 -s 2 -r | length) == 3");
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
}
|
|
@ -204,6 +204,7 @@ impl BlockBuilder {
|
||||||
Instruction::Drain { src } => allocate(&[*src], &[]),
|
Instruction::Drain { src } => allocate(&[*src], &[]),
|
||||||
Instruction::LoadVariable { dst, var_id: _ } => allocate(&[], &[*dst]),
|
Instruction::LoadVariable { dst, var_id: _ } => allocate(&[], &[*dst]),
|
||||||
Instruction::StoreVariable { var_id: _, src } => allocate(&[*src], &[]),
|
Instruction::StoreVariable { var_id: _, src } => allocate(&[*src], &[]),
|
||||||
|
Instruction::DropVariable { var_id: _ } => Ok(()),
|
||||||
Instruction::LoadEnv { dst, key: _ } => allocate(&[], &[*dst]),
|
Instruction::LoadEnv { dst, key: _ } => allocate(&[], &[*dst]),
|
||||||
Instruction::LoadEnvOpt { dst, key: _ } => allocate(&[], &[*dst]),
|
Instruction::LoadEnvOpt { dst, key: _ } => allocate(&[], &[*dst]),
|
||||||
Instruction::StoreEnv { key: _, src } => allocate(&[*src], &[]),
|
Instruction::StoreEnv { key: _, src } => allocate(&[*src], &[]),
|
||||||
|
|
|
@ -171,6 +171,27 @@ pub(crate) fn compile_expression(
|
||||||
Err(CompileError::UnsupportedOperatorExpression { span: op.span })
|
Err(CompileError::UnsupportedOperatorExpression { span: op.span })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expr::Collect(var_id, expr) => {
|
||||||
|
let store_reg = if let Some(in_reg) = in_reg {
|
||||||
|
// Collect, clone, store
|
||||||
|
builder.push(Instruction::Collect { src_dst: in_reg }.into_spanned(expr.span))?;
|
||||||
|
builder.clone_reg(in_reg, expr.span)?
|
||||||
|
} else {
|
||||||
|
// Just store nothing in the variable
|
||||||
|
builder.literal(Literal::Nothing.into_spanned(Span::unknown()))?
|
||||||
|
};
|
||||||
|
builder.push(
|
||||||
|
Instruction::StoreVariable {
|
||||||
|
var_id: *var_id,
|
||||||
|
src: store_reg,
|
||||||
|
}
|
||||||
|
.into_spanned(expr.span),
|
||||||
|
)?;
|
||||||
|
compile_expression(working_set, builder, expr, redirect_modes, in_reg, out_reg)?;
|
||||||
|
// Clean it up afterward
|
||||||
|
builder.push(Instruction::DropVariable { var_id: *var_id }.into_spanned(expr.span))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Expr::Subexpression(block_id) => {
|
Expr::Subexpression(block_id) => {
|
||||||
let block = working_set.get_block(*block_id);
|
let block = working_set.get_block(*block_id);
|
||||||
compile_block(working_set, builder, block, redirect_modes, in_reg, out_reg)
|
compile_block(working_set, builder, block, redirect_modes, in_reg, out_reg)
|
||||||
|
|
|
@ -10,8 +10,8 @@ use nu_protocol::{
|
||||||
debugger::DebugContext,
|
debugger::DebugContext,
|
||||||
engine::{Closure, EngineState, Redirection, Stack, StateWorkingSet},
|
engine::{Closure, EngineState, Redirection, Stack, StateWorkingSet},
|
||||||
eval_base::Eval,
|
eval_base::Eval,
|
||||||
ByteStreamSource, Config, FromValue, IntoPipelineData, OutDest, PipelineData, ShellError, Span,
|
ByteStreamSource, Config, DataSource, FromValue, IntoPipelineData, OutDest, PipelineData,
|
||||||
Spanned, Type, Value, VarId, ENV_VARIABLE_ID,
|
PipelineMetadata, ShellError, Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID,
|
||||||
};
|
};
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
use std::{fs::OpenOptions, path::PathBuf, sync::Arc};
|
use std::{fs::OpenOptions, path::PathBuf, sync::Arc};
|
||||||
|
@ -259,6 +259,10 @@ pub fn eval_expression_with_input<D: DebugContext>(
|
||||||
input = eval_external(engine_state, stack, head, args, input)?;
|
input = eval_external(engine_state, stack, head, args, input)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expr::Collect(var_id, expr) => {
|
||||||
|
input = eval_collect::<D>(engine_state, stack, *var_id, expr, input)?;
|
||||||
|
}
|
||||||
|
|
||||||
Expr::Subexpression(block_id) => {
|
Expr::Subexpression(block_id) => {
|
||||||
let block = engine_state.get_block(*block_id);
|
let block = engine_state.get_block(*block_id);
|
||||||
// FIXME: protect this collect with ctrl-c
|
// FIXME: protect this collect with ctrl-c
|
||||||
|
@ -605,6 +609,44 @@ pub fn eval_block<D: DebugContext>(
|
||||||
Ok(input)
|
Ok(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn eval_collect<D: DebugContext>(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
var_id: VarId,
|
||||||
|
expr: &Expression,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
// Evaluate the expression with the variable set to the collected input
|
||||||
|
let span = input.span().unwrap_or(Span::unknown());
|
||||||
|
|
||||||
|
let metadata = match input.metadata() {
|
||||||
|
// Remove the `FilePath` metadata, because after `collect` it's no longer necessary to
|
||||||
|
// check where some input came from.
|
||||||
|
Some(PipelineMetadata {
|
||||||
|
data_source: DataSource::FilePath(_),
|
||||||
|
content_type: None,
|
||||||
|
}) => None,
|
||||||
|
other => other,
|
||||||
|
};
|
||||||
|
|
||||||
|
let input = input.into_value(span)?;
|
||||||
|
|
||||||
|
stack.add_var(var_id, input.clone());
|
||||||
|
|
||||||
|
let result = eval_expression_with_input::<D>(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
expr,
|
||||||
|
// We still have to pass it as input
|
||||||
|
input.into_pipeline_data_with_metadata(metadata),
|
||||||
|
)
|
||||||
|
.map(|(result, _failed)| result);
|
||||||
|
|
||||||
|
stack.remove_var(var_id);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
pub fn eval_subexpression<D: DebugContext>(
|
pub fn eval_subexpression<D: DebugContext>(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
|
@ -729,6 +771,18 @@ impl Eval for EvalRuntime {
|
||||||
eval_external(engine_state, stack, head, args, PipelineData::empty())?.into_value(span)
|
eval_external(engine_state, stack, head, args, PipelineData::empty())?.into_value(span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn eval_collect<D: DebugContext>(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
var_id: VarId,
|
||||||
|
expr: &Expression,
|
||||||
|
) -> Result<Value, ShellError> {
|
||||||
|
// It's a little bizarre, but the expression can still have some kind of result even with
|
||||||
|
// nothing input
|
||||||
|
eval_collect::<D>(engine_state, stack, var_id, expr, PipelineData::empty())?
|
||||||
|
.into_value(expr.span)
|
||||||
|
}
|
||||||
|
|
||||||
fn eval_subexpression<D: DebugContext>(
|
fn eval_subexpression<D: DebugContext>(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
|
|
|
@ -6,9 +6,9 @@ use nu_protocol::{
|
||||||
debugger::DebugContext,
|
debugger::DebugContext,
|
||||||
engine::{Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack},
|
engine::{Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack},
|
||||||
ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode},
|
ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode},
|
||||||
record, ByteStreamSource, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned, ListStream,
|
record, ByteStreamSource, DataSource, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned,
|
||||||
OutDest, PipelineData, PositionalArg, Range, Record, RegId, ShellError, Signals, Signature,
|
ListStream, OutDest, PipelineData, PipelineMetadata, PositionalArg, Range, Record, RegId,
|
||||||
Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID,
|
ShellError, Signals, Signature, Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID,
|
||||||
};
|
};
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
|
|
||||||
|
@ -345,6 +345,10 @@ fn eval_instruction<D: DebugContext>(
|
||||||
ctx.stack.add_var(*var_id, value);
|
ctx.stack.add_var(*var_id, value);
|
||||||
Ok(Continue)
|
Ok(Continue)
|
||||||
}
|
}
|
||||||
|
Instruction::DropVariable { var_id } => {
|
||||||
|
ctx.stack.remove_var(*var_id);
|
||||||
|
Ok(Continue)
|
||||||
|
}
|
||||||
Instruction::LoadEnv { dst, key } => {
|
Instruction::LoadEnv { dst, key } => {
|
||||||
let key = ctx.get_str(*key, *span)?;
|
let key = ctx.get_str(*key, *span)?;
|
||||||
if let Some(value) = get_env_var_case_insensitive(ctx, key) {
|
if let Some(value) = get_env_var_case_insensitive(ctx, key) {
|
||||||
|
@ -1341,9 +1345,19 @@ fn get_env_var_name_case_insensitive<'a>(ctx: &mut EvalContext<'_>, key: &'a str
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to collect values into [`PipelineData`], preserving original span and metadata
|
/// Helper to collect values into [`PipelineData`], preserving original span and metadata
|
||||||
|
///
|
||||||
|
/// The metadata is removed if it is the file data source, as that's just meant to mark streams.
|
||||||
fn collect(data: PipelineData, fallback_span: Span) -> Result<PipelineData, ShellError> {
|
fn collect(data: PipelineData, fallback_span: Span) -> Result<PipelineData, ShellError> {
|
||||||
let span = data.span().unwrap_or(fallback_span);
|
let span = data.span().unwrap_or(fallback_span);
|
||||||
let metadata = data.metadata();
|
let metadata = match data.metadata() {
|
||||||
|
// Remove the `FilePath` metadata, because after `collect` it's no longer necessary to
|
||||||
|
// check where some input came from.
|
||||||
|
Some(PipelineMetadata {
|
||||||
|
data_source: DataSource::FilePath(_),
|
||||||
|
content_type: None,
|
||||||
|
}) => None,
|
||||||
|
other => other,
|
||||||
|
};
|
||||||
let value = data.into_value(span)?;
|
let value = data.into_value(span)?;
|
||||||
Ok(PipelineData::Value(value, metadata))
|
Ok(PipelineData::Value(value, metadata))
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use nu_protocol::{
|
||||||
RecordItem,
|
RecordItem,
|
||||||
},
|
},
|
||||||
engine::StateWorkingSet,
|
engine::StateWorkingSet,
|
||||||
DeclId, Span, VarId,
|
DeclId, Span, SyntaxShape, VarId,
|
||||||
};
|
};
|
||||||
use std::fmt::{Display, Formatter, Result};
|
use std::fmt::{Display, Formatter, Result};
|
||||||
|
|
||||||
|
@ -166,6 +166,22 @@ fn flatten_pipeline_element_into(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flatten_positional_arg_into(
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
positional: &Expression,
|
||||||
|
shape: &SyntaxShape,
|
||||||
|
output: &mut Vec<(Span, FlatShape)>,
|
||||||
|
) {
|
||||||
|
if matches!(shape, SyntaxShape::ExternalArgument)
|
||||||
|
&& matches!(positional.expr, Expr::String(..) | Expr::GlobPattern(..))
|
||||||
|
{
|
||||||
|
// Make known external arguments look more like external arguments
|
||||||
|
output.push((positional.span, FlatShape::ExternalArg));
|
||||||
|
} else {
|
||||||
|
flatten_expression_into(working_set, positional, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn flatten_expression_into(
|
fn flatten_expression_into(
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
expr: &Expression,
|
expr: &Expression,
|
||||||
|
@ -189,6 +205,9 @@ fn flatten_expression_into(
|
||||||
));
|
));
|
||||||
flatten_expression_into(working_set, not, output);
|
flatten_expression_into(working_set, not, output);
|
||||||
}
|
}
|
||||||
|
Expr::Collect(_, expr) => {
|
||||||
|
flatten_expression_into(working_set, expr, output);
|
||||||
|
}
|
||||||
Expr::Closure(block_id) => {
|
Expr::Closure(block_id) => {
|
||||||
let outer_span = expr.span;
|
let outer_span = expr.span;
|
||||||
|
|
||||||
|
@ -246,16 +265,40 @@ fn flatten_expression_into(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Call(call) => {
|
Expr::Call(call) => {
|
||||||
|
let decl = working_set.get_decl(call.decl_id);
|
||||||
|
|
||||||
if call.head.end != 0 {
|
if call.head.end != 0 {
|
||||||
// Make sure we don't push synthetic calls
|
// Make sure we don't push synthetic calls
|
||||||
output.push((call.head, FlatShape::InternalCall(call.decl_id)));
|
output.push((call.head, FlatShape::InternalCall(call.decl_id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Follow positional arguments from the signature.
|
||||||
|
let signature = decl.signature();
|
||||||
|
let mut positional_args = signature
|
||||||
|
.required_positional
|
||||||
|
.iter()
|
||||||
|
.chain(&signature.optional_positional);
|
||||||
|
|
||||||
let arg_start = output.len();
|
let arg_start = output.len();
|
||||||
for arg in &call.arguments {
|
for arg in &call.arguments {
|
||||||
match arg {
|
match arg {
|
||||||
Argument::Positional(positional) | Argument::Unknown(positional) => {
|
Argument::Positional(positional) => {
|
||||||
flatten_expression_into(working_set, positional, output)
|
let positional_arg = positional_args.next();
|
||||||
|
let shape = positional_arg
|
||||||
|
.or(signature.rest_positional.as_ref())
|
||||||
|
.map(|arg| &arg.shape)
|
||||||
|
.unwrap_or(&SyntaxShape::Any);
|
||||||
|
|
||||||
|
flatten_positional_arg_into(working_set, positional, shape, output)
|
||||||
|
}
|
||||||
|
Argument::Unknown(positional) => {
|
||||||
|
let shape = signature
|
||||||
|
.rest_positional
|
||||||
|
.as_ref()
|
||||||
|
.map(|arg| &arg.shape)
|
||||||
|
.unwrap_or(&SyntaxShape::Any);
|
||||||
|
|
||||||
|
flatten_positional_arg_into(working_set, positional, shape, output)
|
||||||
}
|
}
|
||||||
Argument::Named(named) => {
|
Argument::Named(named) => {
|
||||||
if named.0.span.end != 0 {
|
if named.0.span.end != 0 {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
//! can be parsed.
|
//! can be parsed.
|
||||||
|
|
||||||
use crate::{Token, TokenContents};
|
use crate::{Token, TokenContents};
|
||||||
|
use itertools::{Either, Itertools};
|
||||||
use nu_protocol::{ast::RedirectionSource, ParseError, Span};
|
use nu_protocol::{ast::RedirectionSource, ParseError, Span};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
|
@ -24,6 +25,15 @@ impl LiteRedirectionTarget {
|
||||||
| LiteRedirectionTarget::Pipe { connector } => *connector,
|
| LiteRedirectionTarget::Pipe { connector } => *connector,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn spans(&self) -> impl Iterator<Item = Span> {
|
||||||
|
match *self {
|
||||||
|
LiteRedirectionTarget::File {
|
||||||
|
connector, file, ..
|
||||||
|
} => Either::Left([connector, file].into_iter()),
|
||||||
|
LiteRedirectionTarget::Pipe { connector } => Either::Right(std::iter::once(connector)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -38,6 +48,17 @@ pub enum LiteRedirection {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LiteRedirection {
|
||||||
|
pub fn spans(&self) -> impl Iterator<Item = Span> {
|
||||||
|
match self {
|
||||||
|
LiteRedirection::Single { target, .. } => Either::Left(target.spans()),
|
||||||
|
LiteRedirection::Separate { out, err } => {
|
||||||
|
Either::Right(out.spans().chain(err.spans()).sorted())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct LiteCommand {
|
pub struct LiteCommand {
|
||||||
pub pipe: Option<Span>,
|
pub pipe: Option<Span>,
|
||||||
|
@ -113,6 +134,14 @@ impl LiteCommand {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parts_including_redirection(&self) -> impl Iterator<Item = Span> + '_ {
|
||||||
|
self.parts.iter().copied().chain(
|
||||||
|
self.redirection
|
||||||
|
.iter()
|
||||||
|
.flat_map(|redirection| redirection.spans()),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
|
|
@ -783,6 +783,16 @@ pub fn parse_extern(
|
||||||
working_set.get_block_mut(block_id).signature = signature;
|
working_set.get_block_mut(block_id).signature = signature;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if signature.rest_positional.is_none() {
|
||||||
|
// Make sure that a known external takes rest args with ExternalArgument
|
||||||
|
// shape
|
||||||
|
*signature = signature.rest(
|
||||||
|
"args",
|
||||||
|
SyntaxShape::ExternalArgument,
|
||||||
|
"all other arguments to the command",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let decl = KnownExternal {
|
let decl = KnownExternal {
|
||||||
name: external_name,
|
name: external_name,
|
||||||
usage,
|
usage,
|
||||||
|
|
|
@ -221,6 +221,22 @@ pub(crate) fn check_call(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses an unknown argument for the given signature. This handles the parsing as appropriate to
|
||||||
|
/// the rest type of the command.
|
||||||
|
fn parse_unknown_arg(
|
||||||
|
working_set: &mut StateWorkingSet,
|
||||||
|
span: Span,
|
||||||
|
signature: &Signature,
|
||||||
|
) -> Expression {
|
||||||
|
let shape = signature
|
||||||
|
.rest_positional
|
||||||
|
.as_ref()
|
||||||
|
.map(|arg| arg.shape.clone())
|
||||||
|
.unwrap_or(SyntaxShape::Any);
|
||||||
|
|
||||||
|
parse_value(working_set, span, &shape)
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses a string in the arg or head position of an external call.
|
/// Parses a string in the arg or head position of an external call.
|
||||||
///
|
///
|
||||||
/// If the string begins with `r#`, it is parsed as a raw string. If it doesn't contain any quotes
|
/// If the string begins with `r#`, it is parsed as a raw string. If it doesn't contain any quotes
|
||||||
|
@ -427,11 +443,7 @@ fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expre
|
||||||
fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> ExternalArgument {
|
fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> ExternalArgument {
|
||||||
let contents = working_set.get_span_contents(span);
|
let contents = working_set.get_span_contents(span);
|
||||||
|
|
||||||
if contents.starts_with(b"$") || contents.starts_with(b"(") {
|
if contents.len() > 3
|
||||||
ExternalArgument::Regular(parse_dollar_expr(working_set, span))
|
|
||||||
} else if contents.starts_with(b"[") {
|
|
||||||
ExternalArgument::Regular(parse_list_expression(working_set, span, &SyntaxShape::Any))
|
|
||||||
} else if contents.len() > 3
|
|
||||||
&& contents.starts_with(b"...")
|
&& contents.starts_with(b"...")
|
||||||
&& (contents[3] == b'$' || contents[3] == b'[' || contents[3] == b'(')
|
&& (contents[3] == b'$' || contents[3] == b'[' || contents[3] == b'(')
|
||||||
{
|
{
|
||||||
|
@ -441,7 +453,19 @@ fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> External
|
||||||
&SyntaxShape::List(Box::new(SyntaxShape::Any)),
|
&SyntaxShape::List(Box::new(SyntaxShape::Any)),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
ExternalArgument::Regular(parse_external_string(working_set, span))
|
ExternalArgument::Regular(parse_regular_external_arg(working_set, span))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_regular_external_arg(working_set: &mut StateWorkingSet, span: Span) -> Expression {
|
||||||
|
let contents = working_set.get_span_contents(span);
|
||||||
|
|
||||||
|
if contents.starts_with(b"$") || contents.starts_with(b"(") {
|
||||||
|
parse_dollar_expr(working_set, span)
|
||||||
|
} else if contents.starts_with(b"[") {
|
||||||
|
parse_list_expression(working_set, span, &SyntaxShape::Any)
|
||||||
|
} else {
|
||||||
|
parse_external_string(working_set, span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -998,7 +1022,7 @@ pub fn parse_internal_call(
|
||||||
&& signature.allows_unknown_args
|
&& signature.allows_unknown_args
|
||||||
{
|
{
|
||||||
working_set.parse_errors.truncate(starting_error_count);
|
working_set.parse_errors.truncate(starting_error_count);
|
||||||
let arg = parse_value(working_set, arg_span, &SyntaxShape::Any);
|
let arg = parse_unknown_arg(working_set, arg_span, &signature);
|
||||||
|
|
||||||
call.add_unknown(arg);
|
call.add_unknown(arg);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1040,7 +1064,7 @@ pub fn parse_internal_call(
|
||||||
&& signature.allows_unknown_args
|
&& signature.allows_unknown_args
|
||||||
{
|
{
|
||||||
working_set.parse_errors.truncate(starting_error_count);
|
working_set.parse_errors.truncate(starting_error_count);
|
||||||
let arg = parse_value(working_set, arg_span, &SyntaxShape::Any);
|
let arg = parse_unknown_arg(working_set, arg_span, &signature);
|
||||||
|
|
||||||
call.add_unknown(arg);
|
call.add_unknown(arg);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1135,6 +1159,10 @@ pub fn parse_internal_call(
|
||||||
call.add_positional(Expression::garbage(working_set, arg_span));
|
call.add_positional(Expression::garbage(working_set, arg_span));
|
||||||
} else {
|
} else {
|
||||||
let rest_shape = match &signature.rest_positional {
|
let rest_shape = match &signature.rest_positional {
|
||||||
|
Some(arg) if matches!(arg.shape, SyntaxShape::ExternalArgument) => {
|
||||||
|
// External args aren't parsed inside lists in spread position.
|
||||||
|
SyntaxShape::Any
|
||||||
|
}
|
||||||
Some(arg) => arg.shape.clone(),
|
Some(arg) => arg.shape.clone(),
|
||||||
None => SyntaxShape::Any,
|
None => SyntaxShape::Any,
|
||||||
};
|
};
|
||||||
|
@ -1196,7 +1224,7 @@ pub fn parse_internal_call(
|
||||||
call.add_positional(arg);
|
call.add_positional(arg);
|
||||||
positional_idx += 1;
|
positional_idx += 1;
|
||||||
} else if signature.allows_unknown_args {
|
} else if signature.allows_unknown_args {
|
||||||
let arg = parse_value(working_set, arg_span, &SyntaxShape::Any);
|
let arg = parse_unknown_arg(working_set, arg_span, &signature);
|
||||||
|
|
||||||
call.add_unknown(arg);
|
call.add_unknown(arg);
|
||||||
} else {
|
} else {
|
||||||
|
@ -4670,7 +4698,8 @@ pub fn parse_value(
|
||||||
| SyntaxShape::Signature
|
| SyntaxShape::Signature
|
||||||
| SyntaxShape::Filepath
|
| SyntaxShape::Filepath
|
||||||
| SyntaxShape::String
|
| SyntaxShape::String
|
||||||
| SyntaxShape::GlobPattern => {}
|
| SyntaxShape::GlobPattern
|
||||||
|
| SyntaxShape::ExternalArgument => {}
|
||||||
_ => {
|
_ => {
|
||||||
working_set.error(ParseError::Expected("non-[] value", span));
|
working_set.error(ParseError::Expected("non-[] value", span));
|
||||||
return Expression::garbage(working_set, span);
|
return Expression::garbage(working_set, span);
|
||||||
|
@ -4747,6 +4776,8 @@ pub fn parse_value(
|
||||||
Expression::garbage(working_set, span)
|
Expression::garbage(working_set, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SyntaxShape::ExternalArgument => parse_regular_external_arg(working_set, span),
|
||||||
|
|
||||||
SyntaxShape::Any => {
|
SyntaxShape::Any => {
|
||||||
if bytes.starts_with(b"[") {
|
if bytes.starts_with(b"[") {
|
||||||
//parse_value(working_set, span, &SyntaxShape::Table)
|
//parse_value(working_set, span, &SyntaxShape::Table)
|
||||||
|
@ -5299,6 +5330,7 @@ pub fn parse_expression(working_set: &mut StateWorkingSet, spans: &[Span]) -> Ex
|
||||||
let mut block = Block::default();
|
let mut block = Block::default();
|
||||||
let ty = output.ty.clone();
|
let ty = output.ty.clone();
|
||||||
block.pipelines = vec![Pipeline::from_vec(vec![output])];
|
block.pipelines = vec![Pipeline::from_vec(vec![output])];
|
||||||
|
block.span = Some(Span::concat(spans));
|
||||||
|
|
||||||
compile_block(working_set, &mut block);
|
compile_block(working_set, &mut block);
|
||||||
|
|
||||||
|
@ -5393,9 +5425,19 @@ pub fn parse_builtin_commands(
|
||||||
match name {
|
match name {
|
||||||
b"def" => parse_def(working_set, lite_command, None).0,
|
b"def" => parse_def(working_set, lite_command, None).0,
|
||||||
b"extern" => 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_including_redirection()
|
||||||
|
.collect::<Vec<Span>>(),
|
||||||
|
),
|
||||||
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_including_redirection()
|
||||||
|
.collect::<Vec<Span>>(),
|
||||||
|
),
|
||||||
b"for" => {
|
b"for" => {
|
||||||
let expr = parse_for(working_set, lite_command);
|
let expr = parse_for(working_set, lite_command);
|
||||||
Pipeline::from_vec(vec![expr])
|
Pipeline::from_vec(vec![expr])
|
||||||
|
@ -5647,169 +5689,73 @@ pub(crate) fn redirecting_builtin_error(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_pipeline(
|
pub fn parse_pipeline(working_set: &mut StateWorkingSet, pipeline: &LitePipeline) -> Pipeline {
|
||||||
working_set: &mut StateWorkingSet,
|
let first_command = pipeline.commands.first();
|
||||||
pipeline: &LitePipeline,
|
let first_command_name = first_command
|
||||||
is_subexpression: bool,
|
.and_then(|command| command.parts.first())
|
||||||
pipeline_index: usize,
|
.map(|span| working_set.get_span_contents(*span));
|
||||||
) -> Pipeline {
|
|
||||||
if pipeline.commands.len() > 1 {
|
if pipeline.commands.len() > 1 {
|
||||||
// Special case: allow `let` and `mut` to consume the whole pipeline, eg) `let abc = "foo" | str length`
|
// Special case: allow "let" or "mut" to consume the whole pipeline, if this is a pipeline
|
||||||
if let Some(&first) = pipeline.commands[0].parts.first() {
|
// with multiple commands
|
||||||
let first = working_set.get_span_contents(first);
|
if matches!(first_command_name, Some(b"let" | b"mut")) {
|
||||||
if first == b"let" || first == b"mut" {
|
// Merge the pipeline into one command
|
||||||
let name = if first == b"let" { "let" } else { "mut" };
|
let first_command = first_command.expect("must be Some");
|
||||||
let mut new_command = LiteCommand {
|
|
||||||
comments: vec![],
|
|
||||||
parts: pipeline.commands[0].parts.clone(),
|
|
||||||
pipe: None,
|
|
||||||
redirection: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(redirection) = pipeline.commands[0].redirection.as_ref() {
|
let remainder_span = first_command
|
||||||
working_set.error(redirecting_builtin_error(name, redirection));
|
.parts_including_redirection()
|
||||||
}
|
.skip(3)
|
||||||
|
.chain(
|
||||||
|
pipeline.commands[1..]
|
||||||
|
.iter()
|
||||||
|
.flat_map(|command| command.parts_including_redirection()),
|
||||||
|
)
|
||||||
|
.reduce(Span::append);
|
||||||
|
|
||||||
for element in &pipeline.commands[1..] {
|
let parts = first_command
|
||||||
if let Some(redirection) = pipeline.commands[0].redirection.as_ref() {
|
.parts
|
||||||
working_set.error(redirecting_builtin_error(name, redirection));
|
.iter()
|
||||||
} else {
|
.take(3) // the let/mut start itself
|
||||||
new_command.parts.push(element.pipe.expect("pipe span"));
|
.copied()
|
||||||
new_command.comments.extend_from_slice(&element.comments);
|
.chain(remainder_span) // everything else
|
||||||
new_command.parts.extend_from_slice(&element.parts);
|
.collect();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the 'let' is complete enough, use it, if not, fall through for now
|
let comments = pipeline
|
||||||
if new_command.parts.len() > 3 {
|
.commands
|
||||||
let rhs_span = Span::concat(&new_command.parts[3..]);
|
.iter()
|
||||||
|
.flat_map(|command| command.comments.iter())
|
||||||
|
.copied()
|
||||||
|
.collect();
|
||||||
|
|
||||||
new_command.parts.truncate(3);
|
let new_command = LiteCommand {
|
||||||
new_command.parts.push(rhs_span);
|
pipe: None,
|
||||||
|
comments,
|
||||||
let mut pipeline = parse_builtin_commands(working_set, &new_command);
|
parts,
|
||||||
|
redirection: None,
|
||||||
if pipeline_index == 0 {
|
};
|
||||||
let let_decl_id = working_set.find_decl(b"let");
|
parse_builtin_commands(working_set, &new_command)
|
||||||
let mut_decl_id = working_set.find_decl(b"mut");
|
|
||||||
for element in pipeline.elements.iter_mut() {
|
|
||||||
if let Expr::Call(call) = &element.expr.expr {
|
|
||||||
if Some(call.decl_id) == let_decl_id
|
|
||||||
|| Some(call.decl_id) == mut_decl_id
|
|
||||||
{
|
|
||||||
// Do an expansion
|
|
||||||
if let Some(Expression {
|
|
||||||
expr: Expr::Block(block_id),
|
|
||||||
..
|
|
||||||
}) = call.positional_iter().nth(1)
|
|
||||||
{
|
|
||||||
let block = working_set.get_block(*block_id);
|
|
||||||
|
|
||||||
if let Some(element) = block
|
|
||||||
.pipelines
|
|
||||||
.first()
|
|
||||||
.and_then(|p| p.elements.first())
|
|
||||||
.cloned()
|
|
||||||
{
|
|
||||||
if element.has_in_variable(working_set) {
|
|
||||||
let element = wrap_element_with_collect(
|
|
||||||
working_set,
|
|
||||||
&element,
|
|
||||||
);
|
|
||||||
let block = working_set.get_block_mut(*block_id);
|
|
||||||
block.pipelines[0].elements[0] = element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
} else if element.has_in_variable(working_set) && !is_subexpression
|
|
||||||
{
|
|
||||||
*element = wrap_element_with_collect(working_set, element);
|
|
||||||
}
|
|
||||||
} else if element.has_in_variable(working_set) && !is_subexpression {
|
|
||||||
*element = wrap_element_with_collect(working_set, element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pipeline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut elements = pipeline
|
|
||||||
.commands
|
|
||||||
.iter()
|
|
||||||
.map(|element| parse_pipeline_element(working_set, element))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if is_subexpression {
|
|
||||||
for element in elements.iter_mut().skip(1) {
|
|
||||||
if element.has_in_variable(working_set) {
|
|
||||||
*element = wrap_element_with_collect(working_set, element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
for element in elements.iter_mut() {
|
// Parse a normal multi command pipeline
|
||||||
if element.has_in_variable(working_set) {
|
let elements: Vec<_> = pipeline
|
||||||
*element = wrap_element_with_collect(working_set, element);
|
.commands
|
||||||
}
|
.iter()
|
||||||
}
|
.enumerate()
|
||||||
}
|
.map(|(index, element)| {
|
||||||
|
let element = parse_pipeline_element(working_set, element);
|
||||||
Pipeline { elements }
|
// Handle $in for pipeline elements beyond the first one
|
||||||
} else {
|
if index > 0 && element.has_in_variable(working_set) {
|
||||||
if let Some(&first) = pipeline.commands[0].parts.first() {
|
wrap_element_with_collect(working_set, element.clone())
|
||||||
let first = working_set.get_span_contents(first);
|
} else {
|
||||||
if first == b"let" || first == b"mut" {
|
element
|
||||||
if let Some(redirection) = pipeline.commands[0].redirection.as_ref() {
|
|
||||||
let name = if first == b"let" { "let" } else { "mut" };
|
|
||||||
working_set.error(redirecting_builtin_error(name, redirection));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut pipeline = parse_builtin_commands(working_set, &pipeline.commands[0]);
|
|
||||||
|
|
||||||
let let_decl_id = working_set.find_decl(b"let");
|
|
||||||
let mut_decl_id = working_set.find_decl(b"mut");
|
|
||||||
|
|
||||||
if pipeline_index == 0 {
|
|
||||||
for element in pipeline.elements.iter_mut() {
|
|
||||||
if let Expr::Call(call) = &element.expr.expr {
|
|
||||||
if Some(call.decl_id) == let_decl_id || Some(call.decl_id) == mut_decl_id {
|
|
||||||
// Do an expansion
|
|
||||||
if let Some(Expression {
|
|
||||||
expr: Expr::Block(block_id),
|
|
||||||
..
|
|
||||||
}) = call.positional_iter().nth(1)
|
|
||||||
{
|
|
||||||
let block = working_set.get_block(*block_id);
|
|
||||||
|
|
||||||
if let Some(element) = block
|
|
||||||
.pipelines
|
|
||||||
.first()
|
|
||||||
.and_then(|p| p.elements.first())
|
|
||||||
.cloned()
|
|
||||||
{
|
|
||||||
if element.has_in_variable(working_set) {
|
|
||||||
let element = wrap_element_with_collect(working_set, &element);
|
|
||||||
let block = working_set.get_block_mut(*block_id);
|
|
||||||
block.pipelines[0].elements[0] = element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
} else if element.has_in_variable(working_set) && !is_subexpression {
|
|
||||||
*element = wrap_element_with_collect(working_set, element);
|
|
||||||
}
|
}
|
||||||
} else if element.has_in_variable(working_set) && !is_subexpression {
|
})
|
||||||
*element = wrap_element_with_collect(working_set, element);
|
.collect();
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pipeline
|
Pipeline { elements }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If there's only one command in the pipeline, this could be a builtin command
|
||||||
|
parse_builtin_commands(working_set, &pipeline.commands[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5840,18 +5786,45 @@ pub fn parse_block(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut block = Block::new_with_capacity(lite_block.block.len());
|
let mut block = Block::new_with_capacity(lite_block.block.len());
|
||||||
|
block.span = Some(span);
|
||||||
|
|
||||||
for (idx, lite_pipeline) in lite_block.block.iter().enumerate() {
|
for lite_pipeline in &lite_block.block {
|
||||||
let pipeline = parse_pipeline(working_set, lite_pipeline, is_subexpression, idx);
|
let pipeline = parse_pipeline(working_set, lite_pipeline);
|
||||||
block.pipelines.push(pipeline);
|
block.pipelines.push(pipeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this is not a subexpression and there are any pipelines where the first element has $in,
|
||||||
|
// we can wrap the whole block in collect so that they all reference the same $in
|
||||||
|
if !is_subexpression
|
||||||
|
&& block
|
||||||
|
.pipelines
|
||||||
|
.iter()
|
||||||
|
.flat_map(|pipeline| pipeline.elements.first())
|
||||||
|
.any(|element| element.has_in_variable(working_set))
|
||||||
|
{
|
||||||
|
// Move the block out to prepare it to become a subexpression
|
||||||
|
let inner_block = std::mem::take(&mut block);
|
||||||
|
block.span = inner_block.span;
|
||||||
|
let ty = inner_block.output_type();
|
||||||
|
let block_id = working_set.add_block(Arc::new(inner_block));
|
||||||
|
|
||||||
|
// Now wrap it in a Collect expression, and put it in the block as the only pipeline
|
||||||
|
let subexpression = Expression::new(working_set, Expr::Subexpression(block_id), span, ty);
|
||||||
|
let collect = wrap_expr_with_collect(working_set, subexpression);
|
||||||
|
|
||||||
|
block.pipelines.push(Pipeline {
|
||||||
|
elements: vec![PipelineElement {
|
||||||
|
pipe: None,
|
||||||
|
expr: collect,
|
||||||
|
redirection: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if scoped {
|
if scoped {
|
||||||
working_set.exit_scope();
|
working_set.exit_scope();
|
||||||
}
|
}
|
||||||
|
|
||||||
block.span = Some(span);
|
|
||||||
|
|
||||||
let errors = type_check::check_block_input_output(working_set, &block);
|
let errors = type_check::check_block_input_output(working_set, &block);
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
working_set.parse_errors.extend_from_slice(&errors);
|
working_set.parse_errors.extend_from_slice(&errors);
|
||||||
|
@ -6220,6 +6193,10 @@ pub fn discover_captures_in_expr(
|
||||||
discover_captures_in_expr(working_set, &match_.1, seen, seen_blocks, output)?;
|
discover_captures_in_expr(working_set, &match_.1, seen, seen_blocks, output)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expr::Collect(var_id, expr) => {
|
||||||
|
seen.push(*var_id);
|
||||||
|
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?
|
||||||
|
}
|
||||||
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);
|
||||||
|
|
||||||
|
@ -6270,28 +6247,28 @@ pub fn discover_captures_in_expr(
|
||||||
|
|
||||||
fn wrap_redirection_with_collect(
|
fn wrap_redirection_with_collect(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
target: &RedirectionTarget,
|
target: RedirectionTarget,
|
||||||
) -> RedirectionTarget {
|
) -> RedirectionTarget {
|
||||||
match target {
|
match target {
|
||||||
RedirectionTarget::File { expr, append, span } => RedirectionTarget::File {
|
RedirectionTarget::File { expr, append, span } => RedirectionTarget::File {
|
||||||
expr: wrap_expr_with_collect(working_set, expr),
|
expr: wrap_expr_with_collect(working_set, expr),
|
||||||
span: *span,
|
span,
|
||||||
append: *append,
|
append,
|
||||||
},
|
},
|
||||||
RedirectionTarget::Pipe { span } => RedirectionTarget::Pipe { span: *span },
|
RedirectionTarget::Pipe { span } => RedirectionTarget::Pipe { span },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_element_with_collect(
|
fn wrap_element_with_collect(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
element: &PipelineElement,
|
element: PipelineElement,
|
||||||
) -> PipelineElement {
|
) -> PipelineElement {
|
||||||
PipelineElement {
|
PipelineElement {
|
||||||
pipe: element.pipe,
|
pipe: element.pipe,
|
||||||
expr: wrap_expr_with_collect(working_set, &element.expr),
|
expr: wrap_expr_with_collect(working_set, element.expr),
|
||||||
redirection: element.redirection.as_ref().map(|r| match r {
|
redirection: element.redirection.map(|r| match r {
|
||||||
PipelineRedirection::Single { source, target } => PipelineRedirection::Single {
|
PipelineRedirection::Single { source, target } => PipelineRedirection::Single {
|
||||||
source: *source,
|
source,
|
||||||
target: wrap_redirection_with_collect(working_set, target),
|
target: wrap_redirection_with_collect(working_set, target),
|
||||||
},
|
},
|
||||||
PipelineRedirection::Separate { out, err } => PipelineRedirection::Separate {
|
PipelineRedirection::Separate { out, err } => PipelineRedirection::Separate {
|
||||||
|
@ -6302,65 +6279,24 @@ fn wrap_element_with_collect(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) -> Expression {
|
fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: Expression) -> Expression {
|
||||||
let span = expr.span;
|
let span = expr.span;
|
||||||
|
|
||||||
if let Some(decl_id) = working_set.find_decl(b"collect") {
|
// IN_VARIABLE_ID should get replaced with a unique variable, so that we don't have to
|
||||||
let mut output = vec![];
|
// execute as a closure
|
||||||
|
let var_id = working_set.add_variable(b"$in".into(), expr.span, Type::Any, false);
|
||||||
|
let mut expr = expr.clone();
|
||||||
|
expr.replace_in_variable(working_set, var_id);
|
||||||
|
|
||||||
let var_id = IN_VARIABLE_ID;
|
// Bind the custom `$in` variable for that particular expression
|
||||||
let mut signature = Signature::new("");
|
let ty = expr.ty.clone();
|
||||||
signature.required_positional.push(PositionalArg {
|
Expression::new(
|
||||||
var_id: Some(var_id),
|
working_set,
|
||||||
name: "$in".into(),
|
Expr::Collect(var_id, Box::new(expr)),
|
||||||
desc: String::new(),
|
span,
|
||||||
shape: SyntaxShape::Any,
|
// We can expect it to have the same result type
|
||||||
default_value: None,
|
ty,
|
||||||
});
|
)
|
||||||
|
|
||||||
let mut block = Block {
|
|
||||||
pipelines: vec![Pipeline::from_vec(vec![expr.clone()])],
|
|
||||||
signature: Box::new(signature),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
compile_block(working_set, &mut block);
|
|
||||||
|
|
||||||
let block_id = working_set.add_block(Arc::new(block));
|
|
||||||
|
|
||||||
output.push(Argument::Positional(Expression::new(
|
|
||||||
working_set,
|
|
||||||
Expr::Closure(block_id),
|
|
||||||
span,
|
|
||||||
Type::Any,
|
|
||||||
)));
|
|
||||||
|
|
||||||
output.push(Argument::Named((
|
|
||||||
Spanned {
|
|
||||||
item: "keep-env".to_string(),
|
|
||||||
span: Span::new(0, 0),
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)));
|
|
||||||
|
|
||||||
// The containing, synthetic call to `collect`.
|
|
||||||
// We don't want to have a real span as it will confuse flattening
|
|
||||||
// The args are where we'll get the real info
|
|
||||||
Expression::new(
|
|
||||||
working_set,
|
|
||||||
Expr::Call(Box::new(Call {
|
|
||||||
head: Span::new(0, 0),
|
|
||||||
arguments: output,
|
|
||||||
decl_id,
|
|
||||||
parser_info: HashMap::new(),
|
|
||||||
})),
|
|
||||||
span,
|
|
||||||
Type::Any,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Expression::garbage(working_set, span)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses a vector of u8 to create an AST Block. If a file name is given, then
|
// Parses a vector of u8 to create an AST Block. If a file name is given, then
|
||||||
|
|
|
@ -41,6 +41,41 @@ impl Command for Let {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Mut;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl Command for Mut {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"mut"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Mock mut command."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("mut")
|
||||||
|
.required("var_name", SyntaxShape::VarWithOptType, "variable name")
|
||||||
|
.required(
|
||||||
|
"initial_value",
|
||||||
|
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
|
||||||
|
"equals sign followed by value",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
_call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn test_int(
|
fn test_int(
|
||||||
test_tag: &str, // name of sub-test
|
test_tag: &str, // name of sub-test
|
||||||
test: &[u8], // input expression
|
test: &[u8], // input expression
|
||||||
|
@ -1149,11 +1184,29 @@ fn test_nothing_comparison_eq() {
|
||||||
fn test_redirection_with_letmut(#[case] phase: &[u8]) {
|
fn test_redirection_with_letmut(#[case] phase: &[u8]) {
|
||||||
let engine_state = EngineState::new();
|
let engine_state = EngineState::new();
|
||||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
let _block = parse(&mut working_set, None, phase, true);
|
working_set.add_decl(Box::new(Let));
|
||||||
assert!(matches!(
|
working_set.add_decl(Box::new(Mut));
|
||||||
working_set.parse_errors.first(),
|
|
||||||
Some(ParseError::RedirectingBuiltinCommand(_, _, _))
|
let block = parse(&mut working_set, None, phase, true);
|
||||||
));
|
assert!(
|
||||||
|
working_set.parse_errors.is_empty(),
|
||||||
|
"parse errors: {:?}",
|
||||||
|
working_set.parse_errors
|
||||||
|
);
|
||||||
|
assert_eq!(1, block.pipelines[0].elements.len());
|
||||||
|
|
||||||
|
let element = &block.pipelines[0].elements[0];
|
||||||
|
assert!(element.redirection.is_none()); // it should be in the let block, not here
|
||||||
|
|
||||||
|
if let Expr::Call(call) = &element.expr.expr {
|
||||||
|
let arg = call.positional_nth(1).expect("no positional args");
|
||||||
|
let block_id = arg.as_block().expect("arg 1 is not a block");
|
||||||
|
let block = working_set.get_block(block_id);
|
||||||
|
let inner_element = &block.pipelines[0].elements[0];
|
||||||
|
assert!(inner_element.redirection.is_some());
|
||||||
|
} else {
|
||||||
|
panic!("expected Call: {:?}", block.pipelines[0].elements[0])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use crate::util::MutableCow;
|
use crate::util::MutableCow;
|
||||||
use nu_engine::{get_eval_block_with_early_return, get_full_help, ClosureEvalOnce};
|
use nu_engine::{get_eval_block_with_early_return, get_full_help, ClosureEvalOnce};
|
||||||
|
use nu_plugin_protocol::EvaluatedCall;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{Call, Closure, EngineState, Redirection, Stack},
|
engine::{Call, Closure, EngineState, Redirection, Stack},
|
||||||
Config, IntoSpanned, OutDest, PipelineData, PluginIdentity, ShellError, Signals, Span, Spanned,
|
ir, Config, DeclId, IntoSpanned, OutDest, PipelineData, PluginIdentity, ShellError, Signals,
|
||||||
Value,
|
Span, Spanned, Value,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
@ -44,6 +45,17 @@ pub trait PluginExecutionContext: Send + Sync {
|
||||||
redirect_stdout: bool,
|
redirect_stdout: bool,
|
||||||
redirect_stderr: bool,
|
redirect_stderr: bool,
|
||||||
) -> Result<PipelineData, ShellError>;
|
) -> Result<PipelineData, ShellError>;
|
||||||
|
/// Find a declaration by name
|
||||||
|
fn find_decl(&self, name: &str) -> Result<Option<DeclId>, ShellError>;
|
||||||
|
/// Call a declaration with arguments and input
|
||||||
|
fn call_decl(
|
||||||
|
&mut self,
|
||||||
|
decl_id: DeclId,
|
||||||
|
call: EvaluatedCall,
|
||||||
|
input: PipelineData,
|
||||||
|
redirect_stdout: bool,
|
||||||
|
redirect_stderr: bool,
|
||||||
|
) -> Result<PipelineData, ShellError>;
|
||||||
/// Create an owned version of the context with `'static` lifetime
|
/// Create an owned version of the context with `'static` lifetime
|
||||||
fn boxed(&self) -> Box<dyn PluginExecutionContext>;
|
fn boxed(&self) -> Box<dyn PluginExecutionContext>;
|
||||||
}
|
}
|
||||||
|
@ -177,19 +189,10 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> {
|
||||||
.captures_to_stack(closure.item.captures)
|
.captures_to_stack(closure.item.captures)
|
||||||
.reset_pipes();
|
.reset_pipes();
|
||||||
|
|
||||||
let stdout = if redirect_stdout {
|
let stack = &mut stack.push_redirection(
|
||||||
Some(Redirection::Pipe(OutDest::Capture))
|
redirect_stdout.then_some(Redirection::Pipe(OutDest::Capture)),
|
||||||
} else {
|
redirect_stderr.then_some(Redirection::Pipe(OutDest::Capture)),
|
||||||
None
|
);
|
||||||
};
|
|
||||||
|
|
||||||
let stderr = if redirect_stderr {
|
|
||||||
Some(Redirection::Pipe(OutDest::Capture))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let stack = &mut stack.push_redirection(stdout, stderr);
|
|
||||||
|
|
||||||
// Set up the positional arguments
|
// Set up the positional arguments
|
||||||
for (idx, value) in positional.into_iter().enumerate() {
|
for (idx, value) in positional.into_iter().enumerate() {
|
||||||
|
@ -211,6 +214,57 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> {
|
||||||
eval_block_with_early_return(&self.engine_state, stack, block, input)
|
eval_block_with_early_return(&self.engine_state, stack, block, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_decl(&self, name: &str) -> Result<Option<DeclId>, ShellError> {
|
||||||
|
Ok(self.engine_state.find_decl(name.as_bytes(), &[]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_decl(
|
||||||
|
&mut self,
|
||||||
|
decl_id: DeclId,
|
||||||
|
call: EvaluatedCall,
|
||||||
|
input: PipelineData,
|
||||||
|
redirect_stdout: bool,
|
||||||
|
redirect_stderr: bool,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
if decl_id >= self.engine_state.num_decls() {
|
||||||
|
return Err(ShellError::GenericError {
|
||||||
|
error: "Plugin misbehaving".into(),
|
||||||
|
msg: format!("Tried to call unknown decl id: {}", decl_id),
|
||||||
|
span: Some(call.head),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let decl = self.engine_state.get_decl(decl_id);
|
||||||
|
|
||||||
|
let stack = &mut self.stack.push_redirection(
|
||||||
|
redirect_stdout.then_some(Redirection::Pipe(OutDest::Capture)),
|
||||||
|
redirect_stderr.then_some(Redirection::Pipe(OutDest::Capture)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut call_builder = ir::Call::build(decl_id, call.head);
|
||||||
|
|
||||||
|
for positional in call.positional {
|
||||||
|
call_builder.add_positional(stack, positional.span(), positional);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (name, value) in call.named {
|
||||||
|
if let Some(value) = value {
|
||||||
|
call_builder.add_named(stack, &name.item, "", name.span, value);
|
||||||
|
} else {
|
||||||
|
call_builder.add_flag(stack, &name.item, "", name.span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decl.run(
|
||||||
|
&self.engine_state,
|
||||||
|
stack,
|
||||||
|
&(&call_builder.finish()).into(),
|
||||||
|
input,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn boxed(&self) -> Box<dyn PluginExecutionContext + 'static> {
|
fn boxed(&self) -> Box<dyn PluginExecutionContext + 'static> {
|
||||||
Box::new(PluginExecutionCommandContext {
|
Box::new(PluginExecutionCommandContext {
|
||||||
identity: self.identity.clone(),
|
identity: self.identity.clone(),
|
||||||
|
@ -298,6 +352,25 @@ impl PluginExecutionContext for PluginExecutionBogusContext {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_decl(&self, _name: &str) -> Result<Option<DeclId>, ShellError> {
|
||||||
|
Err(ShellError::NushellFailed {
|
||||||
|
msg: "find_decl not implemented on bogus".into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_decl(
|
||||||
|
&mut self,
|
||||||
|
_decl_id: DeclId,
|
||||||
|
_call: EvaluatedCall,
|
||||||
|
_input: PipelineData,
|
||||||
|
_redirect_stdout: bool,
|
||||||
|
_redirect_stderr: bool,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
Err(ShellError::NushellFailed {
|
||||||
|
msg: "call_decl not implemented on bogus".into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn boxed(&self) -> Box<dyn PluginExecutionContext + 'static> {
|
fn boxed(&self) -> Box<dyn PluginExecutionContext + 'static> {
|
||||||
Box::new(PluginExecutionBogusContext)
|
Box::new(PluginExecutionBogusContext)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1322,6 +1322,22 @@ pub(crate) fn handle_engine_call(
|
||||||
} => context
|
} => context
|
||||||
.eval_closure(closure, positional, input, redirect_stdout, redirect_stderr)
|
.eval_closure(closure, positional, input, redirect_stdout, redirect_stderr)
|
||||||
.map(EngineCallResponse::PipelineData),
|
.map(EngineCallResponse::PipelineData),
|
||||||
|
EngineCall::FindDecl(name) => context.find_decl(&name).map(|decl_id| {
|
||||||
|
if let Some(decl_id) = decl_id {
|
||||||
|
EngineCallResponse::Identifier(decl_id)
|
||||||
|
} else {
|
||||||
|
EngineCallResponse::empty()
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
EngineCall::CallDecl {
|
||||||
|
decl_id,
|
||||||
|
call,
|
||||||
|
input,
|
||||||
|
redirect_stdout,
|
||||||
|
redirect_stderr,
|
||||||
|
} => context
|
||||||
|
.call_decl(decl_id, call, input, redirect_stdout, redirect_stderr)
|
||||||
|
.map(EngineCallResponse::PipelineData),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,82 @@ pub struct EvaluatedCall {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EvaluatedCall {
|
impl EvaluatedCall {
|
||||||
|
/// Create a new [`EvaluatedCall`] with the given head span.
|
||||||
|
pub fn new(head: Span) -> EvaluatedCall {
|
||||||
|
EvaluatedCall {
|
||||||
|
head,
|
||||||
|
positional: vec![],
|
||||||
|
named: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a positional argument to an [`EvaluatedCall`].
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use nu_protocol::{Value, Span, IntoSpanned};
|
||||||
|
/// # use nu_plugin_protocol::EvaluatedCall;
|
||||||
|
/// # let head = Span::test_data();
|
||||||
|
/// let mut call = EvaluatedCall::new(head);
|
||||||
|
/// call.add_positional(Value::test_int(1337));
|
||||||
|
/// ```
|
||||||
|
pub fn add_positional(&mut self, value: Value) -> &mut Self {
|
||||||
|
self.positional.push(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a named argument to an [`EvaluatedCall`].
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use nu_protocol::{Value, Span, IntoSpanned};
|
||||||
|
/// # use nu_plugin_protocol::EvaluatedCall;
|
||||||
|
/// # let head = Span::test_data();
|
||||||
|
/// let mut call = EvaluatedCall::new(head);
|
||||||
|
/// call.add_named("foo".into_spanned(head), Value::test_string("bar"));
|
||||||
|
/// ```
|
||||||
|
pub fn add_named(&mut self, name: Spanned<impl Into<String>>, value: Value) -> &mut Self {
|
||||||
|
self.named.push((name.map(Into::into), Some(value)));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a flag argument to an [`EvaluatedCall`]. A flag argument is a named argument with no
|
||||||
|
/// value.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use nu_protocol::{Value, Span, IntoSpanned};
|
||||||
|
/// # use nu_plugin_protocol::EvaluatedCall;
|
||||||
|
/// # let head = Span::test_data();
|
||||||
|
/// let mut call = EvaluatedCall::new(head);
|
||||||
|
/// call.add_flag("pretty".into_spanned(head));
|
||||||
|
/// ```
|
||||||
|
pub fn add_flag(&mut self, name: Spanned<impl Into<String>>) -> &mut Self {
|
||||||
|
self.named.push((name.map(Into::into), None));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builder variant of [`.add_positional()`].
|
||||||
|
pub fn with_positional(mut self, value: Value) -> Self {
|
||||||
|
self.add_positional(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builder variant of [`.add_named()`].
|
||||||
|
pub fn with_named(mut self, name: Spanned<impl Into<String>>, value: Value) -> Self {
|
||||||
|
self.add_named(name, value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builder variant of [`.add_flag()`].
|
||||||
|
pub fn with_flag(mut self, name: Spanned<impl Into<String>>) -> Self {
|
||||||
|
self.add_flag(name);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Try to create an [`EvaluatedCall`] from a command `Call`.
|
/// Try to create an [`EvaluatedCall`] from a command `Call`.
|
||||||
pub fn try_from_call(
|
pub fn try_from_call(
|
||||||
call: &Call,
|
call: &Call,
|
||||||
|
@ -192,6 +268,16 @@ impl EvaluatedCall {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the [`Span`] of the name of an optional named argument.
|
||||||
|
///
|
||||||
|
/// This can be used in errors for named arguments that don't take values.
|
||||||
|
pub fn get_flag_span(&self, flag_name: &str) -> Option<Span> {
|
||||||
|
self.named
|
||||||
|
.iter()
|
||||||
|
.find(|(name, _)| name.item == flag_name)
|
||||||
|
.map(|(name, _)| name.span)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the [`Value`] of an optional named argument
|
/// Returns the [`Value`] of an optional named argument
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
|
|
@ -22,7 +22,7 @@ mod tests;
|
||||||
pub mod test_util;
|
pub mod test_util;
|
||||||
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Operator, engine::Closure, ByteStreamType, Config, LabeledError, PipelineData,
|
ast::Operator, engine::Closure, ByteStreamType, Config, DeclId, LabeledError, PipelineData,
|
||||||
PluginMetadata, PluginSignature, ShellError, Span, Spanned, Value,
|
PluginMetadata, PluginSignature, ShellError, Span, Spanned, Value,
|
||||||
};
|
};
|
||||||
use nu_utils::SharedCow;
|
use nu_utils::SharedCow;
|
||||||
|
@ -496,6 +496,21 @@ pub enum EngineCall<D> {
|
||||||
/// Whether to redirect stderr from external commands
|
/// Whether to redirect stderr from external commands
|
||||||
redirect_stderr: bool,
|
redirect_stderr: bool,
|
||||||
},
|
},
|
||||||
|
/// Find a declaration by name
|
||||||
|
FindDecl(String),
|
||||||
|
/// Call a declaration with args
|
||||||
|
CallDecl {
|
||||||
|
/// The id of the declaration to be called (can be found with `FindDecl`)
|
||||||
|
decl_id: DeclId,
|
||||||
|
/// Information about the call (head span, arguments, etc.)
|
||||||
|
call: EvaluatedCall,
|
||||||
|
/// Pipeline input to the call
|
||||||
|
input: D,
|
||||||
|
/// Whether to redirect stdout from external commands
|
||||||
|
redirect_stdout: bool,
|
||||||
|
/// Whether to redirect stderr from external commands
|
||||||
|
redirect_stderr: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> EngineCall<D> {
|
impl<D> EngineCall<D> {
|
||||||
|
@ -513,6 +528,8 @@ impl<D> EngineCall<D> {
|
||||||
EngineCall::LeaveForeground => "LeaveForeground",
|
EngineCall::LeaveForeground => "LeaveForeground",
|
||||||
EngineCall::GetSpanContents(_) => "GetSpanContents",
|
EngineCall::GetSpanContents(_) => "GetSpanContents",
|
||||||
EngineCall::EvalClosure { .. } => "EvalClosure",
|
EngineCall::EvalClosure { .. } => "EvalClosure",
|
||||||
|
EngineCall::FindDecl(_) => "FindDecl",
|
||||||
|
EngineCall::CallDecl { .. } => "CallDecl",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -546,6 +563,20 @@ impl<D> EngineCall<D> {
|
||||||
redirect_stdout,
|
redirect_stdout,
|
||||||
redirect_stderr,
|
redirect_stderr,
|
||||||
},
|
},
|
||||||
|
EngineCall::FindDecl(name) => EngineCall::FindDecl(name),
|
||||||
|
EngineCall::CallDecl {
|
||||||
|
decl_id,
|
||||||
|
call,
|
||||||
|
input,
|
||||||
|
redirect_stdout,
|
||||||
|
redirect_stderr,
|
||||||
|
} => EngineCall::CallDecl {
|
||||||
|
decl_id,
|
||||||
|
call,
|
||||||
|
input: f(input)?,
|
||||||
|
redirect_stdout,
|
||||||
|
redirect_stderr,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -558,6 +589,7 @@ pub enum EngineCallResponse<D> {
|
||||||
PipelineData(D),
|
PipelineData(D),
|
||||||
Config(SharedCow<Config>),
|
Config(SharedCow<Config>),
|
||||||
ValueMap(HashMap<String, Value>),
|
ValueMap(HashMap<String, Value>),
|
||||||
|
Identifier(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> EngineCallResponse<D> {
|
impl<D> EngineCallResponse<D> {
|
||||||
|
@ -572,6 +604,7 @@ impl<D> EngineCallResponse<D> {
|
||||||
EngineCallResponse::PipelineData(data) => EngineCallResponse::PipelineData(f(data)?),
|
EngineCallResponse::PipelineData(data) => EngineCallResponse::PipelineData(f(data)?),
|
||||||
EngineCallResponse::Config(config) => EngineCallResponse::Config(config),
|
EngineCallResponse::Config(config) => EngineCallResponse::Config(config),
|
||||||
EngineCallResponse::ValueMap(map) => EngineCallResponse::ValueMap(map),
|
EngineCallResponse::ValueMap(map) => EngineCallResponse::ValueMap(map),
|
||||||
|
EngineCallResponse::Identifier(id) => EngineCallResponse::Identifier(id),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,14 @@ use nu_plugin_core::{
|
||||||
StreamManagerHandle,
|
StreamManagerHandle,
|
||||||
};
|
};
|
||||||
use nu_plugin_protocol::{
|
use nu_plugin_protocol::{
|
||||||
CallInfo, CustomValueOp, EngineCall, EngineCallId, EngineCallResponse, Ordering, PluginCall,
|
CallInfo, CustomValueOp, EngineCall, EngineCallId, EngineCallResponse, EvaluatedCall, Ordering,
|
||||||
PluginCallId, PluginCallResponse, PluginCustomValue, PluginInput, PluginOption, PluginOutput,
|
PluginCall, PluginCallId, PluginCallResponse, PluginCustomValue, PluginInput, PluginOption,
|
||||||
ProtocolInfo,
|
PluginOutput, ProtocolInfo,
|
||||||
};
|
};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{ctrlc, Closure, Sequence},
|
engine::{ctrlc, Closure, Sequence},
|
||||||
Config, LabeledError, PipelineData, PluginMetadata, PluginSignature, ShellError, Signals, Span,
|
Config, DeclId, LabeledError, PipelineData, PluginMetadata, PluginSignature, ShellError,
|
||||||
Spanned, Value,
|
Signals, Span, Spanned, Value,
|
||||||
};
|
};
|
||||||
use nu_utils::SharedCow;
|
use nu_utils::SharedCow;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -889,6 +889,71 @@ impl EngineInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ask the engine for the identifier for a declaration. If found, the result can then be passed
|
||||||
|
/// to [`.call_decl()`] to call other internal commands.
|
||||||
|
///
|
||||||
|
/// See [`.call_decl()`] for an example.
|
||||||
|
pub fn find_decl(&self, name: impl Into<String>) -> Result<Option<DeclId>, ShellError> {
|
||||||
|
let call = EngineCall::FindDecl(name.into());
|
||||||
|
|
||||||
|
match self.engine_call(call)? {
|
||||||
|
EngineCallResponse::Error(err) => Err(err),
|
||||||
|
EngineCallResponse::Identifier(id) => Ok(Some(id)),
|
||||||
|
EngineCallResponse::PipelineData(PipelineData::Empty) => Ok(None),
|
||||||
|
_ => Err(ShellError::PluginFailedToDecode {
|
||||||
|
msg: "Received unexpected response type for EngineCall::FindDecl".into(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ask the engine to call an internal command, using the declaration ID previously looked up
|
||||||
|
/// with [`.find_decl()`].
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// # use nu_protocol::{Value, ShellError, PipelineData};
|
||||||
|
/// # use nu_plugin::{EngineInterface, EvaluatedCall};
|
||||||
|
/// # fn example(engine: &EngineInterface, call: &EvaluatedCall) -> Result<Value, ShellError> {
|
||||||
|
/// if let Some(decl_id) = engine.find_decl("scope commands")? {
|
||||||
|
/// let commands = engine.call_decl(
|
||||||
|
/// decl_id,
|
||||||
|
/// EvaluatedCall::new(call.head),
|
||||||
|
/// PipelineData::Empty,
|
||||||
|
/// true,
|
||||||
|
/// false,
|
||||||
|
/// )?;
|
||||||
|
/// commands.into_value(call.head)
|
||||||
|
/// } else {
|
||||||
|
/// Ok(Value::list(vec![], call.head))
|
||||||
|
/// }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn call_decl(
|
||||||
|
&self,
|
||||||
|
decl_id: DeclId,
|
||||||
|
call: EvaluatedCall,
|
||||||
|
input: PipelineData,
|
||||||
|
redirect_stdout: bool,
|
||||||
|
redirect_stderr: bool,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let call = EngineCall::CallDecl {
|
||||||
|
decl_id,
|
||||||
|
call,
|
||||||
|
input,
|
||||||
|
redirect_stdout,
|
||||||
|
redirect_stderr,
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.engine_call(call)? {
|
||||||
|
EngineCallResponse::Error(err) => Err(err),
|
||||||
|
EngineCallResponse::PipelineData(data) => Ok(data),
|
||||||
|
_ => Err(ShellError::PluginFailedToDecode {
|
||||||
|
msg: "Received unexpected response type for EngineCall::CallDecl".into(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Tell the engine whether to disable garbage collection for this plugin.
|
/// Tell the engine whether to disable garbage collection for this plugin.
|
||||||
///
|
///
|
||||||
/// The garbage collector is enabled by default, but plugins can turn it off (ideally
|
/// The garbage collector is enabled by default, but plugins can turn it off (ideally
|
||||||
|
|
|
@ -23,6 +23,7 @@ byte-unit = { version = "5.1", features = [ "serde" ] }
|
||||||
chrono = { workspace = true, features = [ "serde", "std", "unstable-locales" ], default-features = false }
|
chrono = { workspace = true, features = [ "serde", "std", "unstable-locales" ], default-features = false }
|
||||||
chrono-humanize = { workspace = true }
|
chrono-humanize = { workspace = true }
|
||||||
convert_case = { workspace = true }
|
convert_case = { workspace = true }
|
||||||
|
dirs = { workspace = true }
|
||||||
fancy-regex = { workspace = true }
|
fancy-regex = { workspace = true }
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
lru = { workspace = true }
|
lru = { workspace = true }
|
||||||
|
@ -38,6 +39,10 @@ log = { workspace = true }
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
nix = { workspace = true, default-features = false, features = ["signal"] }
|
nix = { workspace = true, default-features = false, features = ["signal"] }
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
dirs-sys = { workspace = true }
|
||||||
|
windows-sys = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = [
|
plugin = [
|
||||||
"brotli",
|
"brotli",
|
||||||
|
|
|
@ -78,6 +78,19 @@ impl Block {
|
||||||
Type::Nothing
|
Type::Nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replace any `$in` variables in the initial element of pipelines within the block
|
||||||
|
pub fn replace_in_variable(
|
||||||
|
&mut self,
|
||||||
|
working_set: &mut StateWorkingSet<'_>,
|
||||||
|
new_var_id: VarId,
|
||||||
|
) {
|
||||||
|
for pipeline in self.pipelines.iter_mut() {
|
||||||
|
if let Some(element) = pipeline.elements.first_mut() {
|
||||||
|
element.replace_in_variable(working_set, new_var_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<T> for Block
|
impl<T> From<T> for Block
|
||||||
|
|
|
@ -110,17 +110,11 @@ impl Call {
|
||||||
/// If there are one or more arguments the span encompasses the start of the first argument to
|
/// If there are one or more arguments the span encompasses the start of the first argument to
|
||||||
/// end of the last argument
|
/// end of the last argument
|
||||||
pub fn arguments_span(&self) -> Span {
|
pub fn arguments_span(&self) -> Span {
|
||||||
let past = self.head.past();
|
if self.arguments.is_empty() {
|
||||||
|
self.head.past()
|
||||||
let start = self
|
} else {
|
||||||
.arguments
|
Span::merge_many(self.arguments.iter().map(|a| a.span()))
|
||||||
.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(
|
||||||
|
@ -341,27 +335,7 @@ impl Call {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn span(&self) -> Span {
|
pub fn span(&self) -> Span {
|
||||||
let mut span = self.head;
|
self.head.merge(self.arguments_span())
|
||||||
|
|
||||||
for positional in self.positional_iter() {
|
|
||||||
if positional.span.end > span.end {
|
|
||||||
span.end = positional.span.end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (named, _, val) in self.named_iter() {
|
|
||||||
if named.span.end > span.end {
|
|
||||||
span.end = named.span.end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(val) = &val {
|
|
||||||
if val.span.end > span.end {
|
|
||||||
span.end = val.span.end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
span
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ pub enum Expr {
|
||||||
RowCondition(BlockId),
|
RowCondition(BlockId),
|
||||||
UnaryNot(Box<Expression>),
|
UnaryNot(Box<Expression>),
|
||||||
BinaryOp(Box<Expression>, Box<Expression>, Box<Expression>), //lhs, op, rhs
|
BinaryOp(Box<Expression>, Box<Expression>, Box<Expression>), //lhs, op, rhs
|
||||||
|
Collect(VarId, Box<Expression>),
|
||||||
Subexpression(BlockId),
|
Subexpression(BlockId),
|
||||||
Block(BlockId),
|
Block(BlockId),
|
||||||
Closure(BlockId),
|
Closure(BlockId),
|
||||||
|
@ -65,11 +66,13 @@ impl Expr {
|
||||||
&self,
|
&self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
) -> (Option<OutDest>, Option<OutDest>) {
|
) -> (Option<OutDest>, Option<OutDest>) {
|
||||||
// Usages of `$in` will be wrapped by a `collect` call by the parser,
|
|
||||||
// so we do not have to worry about that when considering
|
|
||||||
// which of the expressions below may consume pipeline output.
|
|
||||||
match self {
|
match self {
|
||||||
Expr::Call(call) => working_set.get_decl(call.decl_id).pipe_redirection(),
|
Expr::Call(call) => working_set.get_decl(call.decl_id).pipe_redirection(),
|
||||||
|
Expr::Collect(_, _) => {
|
||||||
|
// A collect expression always has default redirection, it's just going to collect
|
||||||
|
// stdout unless another type of redirection is specified
|
||||||
|
(None, None)
|
||||||
|
},
|
||||||
Expr::Subexpression(block_id) | Expr::Block(block_id) => working_set
|
Expr::Subexpression(block_id) | Expr::Block(block_id) => working_set
|
||||||
.get_block(*block_id)
|
.get_block(*block_id)
|
||||||
.pipe_redirection(working_set),
|
.pipe_redirection(working_set),
|
||||||
|
|
|
@ -6,6 +6,8 @@ use crate::{
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::ListItem;
|
||||||
|
|
||||||
/// Wrapper around [`Expr`]
|
/// Wrapper around [`Expr`]
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Expression {
|
pub struct Expression {
|
||||||
|
@ -106,37 +108,14 @@ impl Expression {
|
||||||
left.has_in_variable(working_set) || right.has_in_variable(working_set)
|
left.has_in_variable(working_set) || right.has_in_variable(working_set)
|
||||||
}
|
}
|
||||||
Expr::UnaryNot(expr) => expr.has_in_variable(working_set),
|
Expr::UnaryNot(expr) => expr.has_in_variable(working_set),
|
||||||
Expr::Block(block_id) => {
|
Expr::Block(block_id) | Expr::Closure(block_id) => {
|
||||||
let block = working_set.get_block(*block_id);
|
let block = working_set.get_block(*block_id);
|
||||||
|
block.captures.contains(&IN_VARIABLE_ID)
|
||||||
if block.captures.contains(&IN_VARIABLE_ID) {
|
|| block
|
||||||
return true;
|
.pipelines
|
||||||
}
|
.iter()
|
||||||
|
.flat_map(|pipeline| pipeline.elements.first())
|
||||||
if let Some(pipeline) = block.pipelines.first() {
|
.any(|element| element.has_in_variable(working_set))
|
||||||
match pipeline.elements.first() {
|
|
||||||
Some(element) => element.has_in_variable(working_set),
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Expr::Closure(block_id) => {
|
|
||||||
let block = working_set.get_block(*block_id);
|
|
||||||
|
|
||||||
if block.captures.contains(&IN_VARIABLE_ID) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(pipeline) = block.pipelines.first() {
|
|
||||||
match pipeline.elements.first() {
|
|
||||||
Some(element) => element.has_in_variable(working_set),
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Expr::Binary(_) => false,
|
Expr::Binary(_) => false,
|
||||||
Expr::Bool(_) => false,
|
Expr::Bool(_) => false,
|
||||||
|
@ -251,6 +230,9 @@ impl Expression {
|
||||||
Expr::Signature(_) => false,
|
Expr::Signature(_) => false,
|
||||||
Expr::String(_) => false,
|
Expr::String(_) => false,
|
||||||
Expr::RawString(_) => false,
|
Expr::RawString(_) => false,
|
||||||
|
// A `$in` variable found within a `Collect` is local, as it's already been wrapped
|
||||||
|
// This is probably unlikely to happen anyway - the expressions are wrapped depth-first
|
||||||
|
Expr::Collect(_, _) => false,
|
||||||
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);
|
||||||
|
|
||||||
|
@ -414,6 +396,7 @@ impl Expression {
|
||||||
i.replace_span(working_set, replaced, new_span)
|
i.replace_span(working_set, replaced, new_span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expr::Collect(_, expr) => expr.replace_span(working_set, replaced, new_span),
|
||||||
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
||||||
let mut block = (**working_set.get_block(*block_id)).clone();
|
let mut block = (**working_set.get_block(*block_id)).clone();
|
||||||
|
|
||||||
|
@ -443,6 +426,129 @@ impl Expression {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn replace_in_variable(&mut self, working_set: &mut StateWorkingSet, new_var_id: VarId) {
|
||||||
|
match &mut self.expr {
|
||||||
|
Expr::Bool(_) => {}
|
||||||
|
Expr::Int(_) => {}
|
||||||
|
Expr::Float(_) => {}
|
||||||
|
Expr::Binary(_) => {}
|
||||||
|
Expr::Range(_) => {}
|
||||||
|
Expr::Var(var_id) | Expr::VarDecl(var_id) => {
|
||||||
|
if *var_id == IN_VARIABLE_ID {
|
||||||
|
*var_id = new_var_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Call(call) => {
|
||||||
|
for arg in call.arguments.iter_mut() {
|
||||||
|
match arg {
|
||||||
|
Argument::Positional(expr)
|
||||||
|
| Argument::Unknown(expr)
|
||||||
|
| Argument::Named((_, _, Some(expr)))
|
||||||
|
| Argument::Spread(expr) => {
|
||||||
|
expr.replace_in_variable(working_set, new_var_id)
|
||||||
|
}
|
||||||
|
Argument::Named((_, _, None)) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for expr in call.parser_info.values_mut() {
|
||||||
|
expr.replace_in_variable(working_set, new_var_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::ExternalCall(head, args) => {
|
||||||
|
head.replace_in_variable(working_set, new_var_id);
|
||||||
|
for arg in args.iter_mut() {
|
||||||
|
match arg {
|
||||||
|
ExternalArgument::Regular(expr) | ExternalArgument::Spread(expr) => {
|
||||||
|
expr.replace_in_variable(working_set, new_var_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Operator(_) => {}
|
||||||
|
// `$in` in `Collect` has already been handled, so we don't need to check further
|
||||||
|
Expr::Collect(_, _) => {}
|
||||||
|
Expr::Block(block_id)
|
||||||
|
| Expr::Closure(block_id)
|
||||||
|
| Expr::RowCondition(block_id)
|
||||||
|
| Expr::Subexpression(block_id) => {
|
||||||
|
let mut block = Block::clone(working_set.get_block(*block_id));
|
||||||
|
block.replace_in_variable(working_set, new_var_id);
|
||||||
|
*working_set.get_block_mut(*block_id) = block;
|
||||||
|
}
|
||||||
|
Expr::UnaryNot(expr) => {
|
||||||
|
expr.replace_in_variable(working_set, new_var_id);
|
||||||
|
}
|
||||||
|
Expr::BinaryOp(lhs, op, rhs) => {
|
||||||
|
for expr in [lhs, op, rhs] {
|
||||||
|
expr.replace_in_variable(working_set, new_var_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::MatchBlock(match_patterns) => {
|
||||||
|
for (_, expr) in match_patterns.iter_mut() {
|
||||||
|
expr.replace_in_variable(working_set, new_var_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::List(items) => {
|
||||||
|
for item in items.iter_mut() {
|
||||||
|
match item {
|
||||||
|
ListItem::Item(expr) | ListItem::Spread(_, expr) => {
|
||||||
|
expr.replace_in_variable(working_set, new_var_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Table(table) => {
|
||||||
|
for col_expr in table.columns.iter_mut() {
|
||||||
|
col_expr.replace_in_variable(working_set, new_var_id);
|
||||||
|
}
|
||||||
|
for row in table.rows.iter_mut() {
|
||||||
|
for row_expr in row.iter_mut() {
|
||||||
|
row_expr.replace_in_variable(working_set, new_var_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Record(items) => {
|
||||||
|
for item in items.iter_mut() {
|
||||||
|
match item {
|
||||||
|
RecordItem::Pair(key, val) => {
|
||||||
|
key.replace_in_variable(working_set, new_var_id);
|
||||||
|
val.replace_in_variable(working_set, new_var_id);
|
||||||
|
}
|
||||||
|
RecordItem::Spread(_, expr) => {
|
||||||
|
expr.replace_in_variable(working_set, new_var_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Keyword(kw) => kw.expr.replace_in_variable(working_set, new_var_id),
|
||||||
|
Expr::ValueWithUnit(value_with_unit) => value_with_unit
|
||||||
|
.expr
|
||||||
|
.replace_in_variable(working_set, new_var_id),
|
||||||
|
Expr::DateTime(_) => {}
|
||||||
|
Expr::Filepath(_, _) => {}
|
||||||
|
Expr::Directory(_, _) => {}
|
||||||
|
Expr::GlobPattern(_, _) => {}
|
||||||
|
Expr::String(_) => {}
|
||||||
|
Expr::RawString(_) => {}
|
||||||
|
Expr::CellPath(_) => {}
|
||||||
|
Expr::FullCellPath(full_cell_path) => {
|
||||||
|
full_cell_path
|
||||||
|
.head
|
||||||
|
.replace_in_variable(working_set, new_var_id);
|
||||||
|
}
|
||||||
|
Expr::ImportPattern(_) => {}
|
||||||
|
Expr::Overlay(_) => {}
|
||||||
|
Expr::Signature(_) => {}
|
||||||
|
Expr::StringInterpolation(exprs) | Expr::GlobInterpolation(exprs, _) => {
|
||||||
|
for expr in exprs.iter_mut() {
|
||||||
|
expr.replace_in_variable(working_set, new_var_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Nothing => {}
|
||||||
|
Expr::Garbage => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(working_set: &mut StateWorkingSet, expr: Expr, span: Span, ty: Type) -> Expression {
|
pub fn new(working_set: &mut StateWorkingSet, expr: Expr, span: Span, ty: Type) -> Expression {
|
||||||
let span_id = working_set.add_span(span);
|
let span_id = working_set.add_span(span);
|
||||||
Expression {
|
Expression {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{ast::Expression, engine::StateWorkingSet, OutDest, Span};
|
use crate::{ast::Expression, engine::StateWorkingSet, OutDest, Span, VarId};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
@ -62,6 +62,19 @@ impl RedirectionTarget {
|
||||||
RedirectionTarget::Pipe { .. } => {}
|
RedirectionTarget::Pipe { .. } => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn replace_in_variable(
|
||||||
|
&mut self,
|
||||||
|
working_set: &mut StateWorkingSet<'_>,
|
||||||
|
new_var_id: VarId,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
RedirectionTarget::File { expr, .. } => {
|
||||||
|
expr.replace_in_variable(working_set, new_var_id)
|
||||||
|
}
|
||||||
|
RedirectionTarget::Pipe { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
@ -75,6 +88,23 @@ pub enum PipelineRedirection {
|
||||||
err: RedirectionTarget,
|
err: RedirectionTarget,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
impl PipelineRedirection {
|
||||||
|
pub fn replace_in_variable(
|
||||||
|
&mut self,
|
||||||
|
working_set: &mut StateWorkingSet<'_>,
|
||||||
|
new_var_id: VarId,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
PipelineRedirection::Single { source: _, target } => {
|
||||||
|
target.replace_in_variable(working_set, new_var_id)
|
||||||
|
}
|
||||||
|
PipelineRedirection::Separate { out, err } => {
|
||||||
|
out.replace_in_variable(working_set, new_var_id);
|
||||||
|
err.replace_in_variable(working_set, new_var_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct PipelineElement {
|
pub struct PipelineElement {
|
||||||
|
@ -120,6 +150,17 @@ impl PipelineElement {
|
||||||
) -> (Option<OutDest>, Option<OutDest>) {
|
) -> (Option<OutDest>, Option<OutDest>) {
|
||||||
self.expr.expr.pipe_redirection(working_set)
|
self.expr.expr.pipe_redirection(working_set)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn replace_in_variable(
|
||||||
|
&mut self,
|
||||||
|
working_set: &mut StateWorkingSet<'_>,
|
||||||
|
new_var_id: VarId,
|
||||||
|
) {
|
||||||
|
self.expr.replace_in_variable(working_set, new_var_id);
|
||||||
|
if let Some(redirection) = &mut self.redirection {
|
||||||
|
redirection.replace_in_variable(working_set, new_var_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|
|
@ -343,6 +343,7 @@ fn expr_to_string(engine_state: &EngineState, expr: &Expr) -> String {
|
||||||
Expr::String(_) | Expr::RawString(_) => "string".to_string(),
|
Expr::String(_) | Expr::RawString(_) => "string".to_string(),
|
||||||
Expr::StringInterpolation(_) => "string interpolation".to_string(),
|
Expr::StringInterpolation(_) => "string interpolation".to_string(),
|
||||||
Expr::GlobInterpolation(_, _) => "glob interpolation".to_string(),
|
Expr::GlobInterpolation(_, _) => "glob interpolation".to_string(),
|
||||||
|
Expr::Collect(_, _) => "collect".to_string(),
|
||||||
Expr::Subexpression(_) => "subexpression".to_string(),
|
Expr::Subexpression(_) => "subexpression".to_string(),
|
||||||
Expr::Table(_) => "table".to_string(),
|
Expr::Table(_) => "table".to_string(),
|
||||||
Expr::UnaryNot(_) => "unary not".to_string(),
|
Expr::UnaryNot(_) => "unary not".to_string(),
|
||||||
|
|
|
@ -159,6 +159,9 @@ pub trait Eval {
|
||||||
Expr::ExternalCall(head, args) => {
|
Expr::ExternalCall(head, args) => {
|
||||||
Self::eval_external_call(state, mut_state, head, args, expr_span)
|
Self::eval_external_call(state, mut_state, head, args, expr_span)
|
||||||
}
|
}
|
||||||
|
Expr::Collect(var_id, expr) => {
|
||||||
|
Self::eval_collect::<D>(state, mut_state, *var_id, expr)
|
||||||
|
}
|
||||||
Expr::Subexpression(block_id) => {
|
Expr::Subexpression(block_id) => {
|
||||||
Self::eval_subexpression::<D>(state, mut_state, *block_id, expr_span)
|
Self::eval_subexpression::<D>(state, mut_state, *block_id, expr_span)
|
||||||
}
|
}
|
||||||
|
@ -356,6 +359,13 @@ pub trait Eval {
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Result<Value, ShellError>;
|
) -> Result<Value, ShellError>;
|
||||||
|
|
||||||
|
fn eval_collect<D: DebugContext>(
|
||||||
|
state: Self::State<'_>,
|
||||||
|
mut_state: &mut Self::MutState,
|
||||||
|
var_id: VarId,
|
||||||
|
expr: &Expression,
|
||||||
|
) -> Result<Value, ShellError>;
|
||||||
|
|
||||||
fn eval_subexpression<D: DebugContext>(
|
fn eval_subexpression<D: DebugContext>(
|
||||||
state: Self::State<'_>,
|
state: Self::State<'_>,
|
||||||
mut_state: &mut Self::MutState,
|
mut_state: &mut Self::MutState,
|
||||||
|
|
|
@ -185,24 +185,15 @@ pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Valu
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a system level directory for nushell scripts, modules, completions, etc
|
|
||||||
// that can be changed by setting the NU_VENDOR_AUTOLOAD_DIR env var on any platform
|
|
||||||
// before nushell is compiled OR if NU_VENDOR_AUTOLOAD_DIR is not set for non-windows
|
|
||||||
// systems, the PREFIX env var can be set before compile and used as PREFIX/nushell/vendor/autoload
|
|
||||||
record.push(
|
record.push(
|
||||||
"vendor-autoload-dir",
|
"vendor-autoload-dirs",
|
||||||
// pseudo code
|
Value::list(
|
||||||
// if env var NU_VENDOR_AUTOLOAD_DIR is set, in any platform, use it
|
get_vendor_autoload_dirs(engine_state)
|
||||||
// if not, if windows, use ALLUSERPROFILE\nushell\vendor\autoload
|
.iter()
|
||||||
// if not, if non-windows, if env var PREFIX is set, use PREFIX/share/nushell/vendor/autoload
|
.map(|path| Value::string(path.to_string_lossy(), span))
|
||||||
// if not, use the default /usr/share/nushell/vendor/autoload
|
.collect(),
|
||||||
|
span,
|
||||||
// check to see if NU_VENDOR_AUTOLOAD_DIR env var is set, if not, use the default
|
),
|
||||||
if let Some(path) = get_vendor_autoload_dir(engine_state) {
|
|
||||||
Value::string(path.to_string_lossy(), span)
|
|
||||||
} else {
|
|
||||||
Value::error(ShellError::ConfigDirNotFound { span: Some(span) }, span)
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
record.push("temp-path", {
|
record.push("temp-path", {
|
||||||
|
@ -259,39 +250,95 @@ pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Valu
|
||||||
Value::record(record, span)
|
Value::record(record, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_vendor_autoload_dir(engine_state: &EngineState) -> Option<PathBuf> {
|
pub fn get_vendor_autoload_dirs(_engine_state: &EngineState) -> Vec<PathBuf> {
|
||||||
// pseudo code
|
// load order for autoload dirs
|
||||||
// if env var NU_VENDOR_AUTOLOAD_DIR is set, in any platform, use it
|
// /Library/Application Support/nushell/vendor/autoload on macOS
|
||||||
// if not, if windows, use ALLUSERPROFILE\nushell\vendor\autoload
|
// <dir>/nushell/vendor/autoload for every dir in XDG_DATA_DIRS in reverse order on platforms other than windows. If XDG_DATA_DIRS is not set, it falls back to <PREFIX>/share if PREFIX ends in local, or <PREFIX>/local/share:<PREFIX>/share otherwise. If PREFIX is not set, fall back to /usr/local/share:/usr/share.
|
||||||
// if not, if non-windows, if env var PREFIX is set, use PREFIX/share/nushell/vendor/autoload
|
// %ProgramData%\nushell\vendor\autoload on windows
|
||||||
// if not, use the default /usr/share/nushell/vendor/autoload
|
// NU_VENDOR_AUTOLOAD_DIR from compile time, if env var is set at compile time
|
||||||
|
// if on macOS, additionally check XDG_DATA_HOME, which `dirs` is only doing on Linux
|
||||||
|
// <data_dir>/nushell/vendor/autoload of the current user according to the `dirs` crate
|
||||||
|
// NU_VENDOR_AUTOLOAD_DIR at runtime, if env var is set
|
||||||
|
|
||||||
// check to see if NU_VENDOR_AUTOLOAD_DIR env var is set, if not, use the default
|
let into_autoload_path_fn = |mut path: PathBuf| {
|
||||||
Some(
|
path.push("nushell");
|
||||||
option_env!("NU_VENDOR_AUTOLOAD_DIR")
|
path.push("vendor");
|
||||||
.map(String::from)
|
path.push("autoload");
|
||||||
.unwrap_or_else(|| {
|
path
|
||||||
if cfg!(windows) {
|
};
|
||||||
let all_user_profile = match engine_state.get_env_var("ALLUSERPROFILE") {
|
|
||||||
Some(v) => format!(
|
let mut dirs = Vec::new();
|
||||||
"{}\\nushell\\vendor\\autoload",
|
|
||||||
v.coerce_string().unwrap_or("C:\\ProgramData".into())
|
let mut append_fn = |path: PathBuf| {
|
||||||
),
|
if !dirs.contains(&path) {
|
||||||
None => "C:\\ProgramData\\nushell\\vendor\\autoload".into(),
|
dirs.push(path)
|
||||||
};
|
}
|
||||||
all_user_profile
|
};
|
||||||
} else {
|
|
||||||
// In non-Windows environments, if NU_VENDOR_AUTOLOAD_DIR is not set
|
#[cfg(target_os = "macos")]
|
||||||
// check to see if PREFIX env var is set, and use it as PREFIX/nushell/vendor/autoload
|
std::iter::once("/Library/Application Support")
|
||||||
// otherwise default to /usr/share/nushell/vendor/autoload
|
.map(PathBuf::from)
|
||||||
option_env!("PREFIX").map(String::from).map_or_else(
|
.map(into_autoload_path_fn)
|
||||||
|| "/usr/local/share/nushell/vendor/autoload".into(),
|
.for_each(&mut append_fn);
|
||||||
|prefix| format!("{}/share/nushell/vendor/autoload", prefix),
|
#[cfg(unix)]
|
||||||
)
|
{
|
||||||
}
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
|
||||||
|
std::env::var_os("XDG_DATA_DIRS")
|
||||||
|
.or_else(|| {
|
||||||
|
option_env!("PREFIX").map(|prefix| {
|
||||||
|
if prefix.ends_with("local") {
|
||||||
|
std::ffi::OsString::from(format!("{prefix}/share"))
|
||||||
|
} else {
|
||||||
|
std::ffi::OsString::from(format!("{prefix}/local/share:{prefix}/share"))
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.into(),
|
.unwrap_or_else(|| std::ffi::OsString::from("/usr/local/share/:/usr/share/"))
|
||||||
)
|
.as_encoded_bytes()
|
||||||
|
.split(|b| *b == b':')
|
||||||
|
.map(|split| into_autoload_path_fn(PathBuf::from(std::ffi::OsStr::from_bytes(split))))
|
||||||
|
.rev()
|
||||||
|
.for_each(&mut append_fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
dirs_sys::known_folder(windows_sys::Win32::UI::Shell::FOLDERID_ProgramData)
|
||||||
|
.into_iter()
|
||||||
|
.map(into_autoload_path_fn)
|
||||||
|
.for_each(&mut append_fn);
|
||||||
|
|
||||||
|
option_env!("NU_VENDOR_AUTOLOAD_DIR")
|
||||||
|
.into_iter()
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.for_each(&mut append_fn);
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
std::env::var("XDG_DATA_HOME")
|
||||||
|
.ok()
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.or_else(|| {
|
||||||
|
dirs::home_dir().map(|mut home| {
|
||||||
|
home.push(".local");
|
||||||
|
home.push("share");
|
||||||
|
home
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map(into_autoload_path_fn)
|
||||||
|
.into_iter()
|
||||||
|
.for_each(&mut append_fn);
|
||||||
|
|
||||||
|
dirs::data_dir()
|
||||||
|
.into_iter()
|
||||||
|
.map(into_autoload_path_fn)
|
||||||
|
.for_each(&mut append_fn);
|
||||||
|
|
||||||
|
std::env::var_os("NU_VENDOR_AUTOLOAD_DIR")
|
||||||
|
.into_iter()
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.for_each(&mut append_fn);
|
||||||
|
|
||||||
|
dirs
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_const_call(
|
fn eval_const_call(
|
||||||
|
@ -422,6 +469,15 @@ impl Eval for EvalConst {
|
||||||
Err(ShellError::NotAConstant { span })
|
Err(ShellError::NotAConstant { span })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn eval_collect<D: DebugContext>(
|
||||||
|
_: &StateWorkingSet,
|
||||||
|
_: &mut (),
|
||||||
|
_var_id: VarId,
|
||||||
|
expr: &Expression,
|
||||||
|
) -> Result<Value, ShellError> {
|
||||||
|
Err(ShellError::NotAConstant { span: expr.span })
|
||||||
|
}
|
||||||
|
|
||||||
fn eval_subexpression<D: DebugContext>(
|
fn eval_subexpression<D: DebugContext>(
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
_: &mut (),
|
_: &mut (),
|
||||||
|
|
|
@ -239,7 +239,7 @@ impl CallBuilder {
|
||||||
}
|
}
|
||||||
self.inner.args_len += 1;
|
self.inner.args_len += 1;
|
||||||
if let Some(span) = argument.span() {
|
if let Some(span) = argument.span() {
|
||||||
self.inner.span = self.inner.span.append(span);
|
self.inner.span = self.inner.span.merge(span);
|
||||||
}
|
}
|
||||||
stack.arguments.push(argument);
|
stack.arguments.push(argument);
|
||||||
self
|
self
|
||||||
|
|
|
@ -102,6 +102,10 @@ impl<'a> fmt::Display for FmtInstruction<'a> {
|
||||||
let var = FmtVar::new(self.engine_state, *var_id);
|
let var = FmtVar::new(self.engine_state, *var_id);
|
||||||
write!(f, "{:WIDTH$} {var}, {src}", "store-variable")
|
write!(f, "{:WIDTH$} {var}, {src}", "store-variable")
|
||||||
}
|
}
|
||||||
|
Instruction::DropVariable { var_id } => {
|
||||||
|
let var = FmtVar::new(self.engine_state, *var_id);
|
||||||
|
write!(f, "{:WIDTH$} {var}", "drop-variable")
|
||||||
|
}
|
||||||
Instruction::LoadEnv { dst, key } => {
|
Instruction::LoadEnv { dst, key } => {
|
||||||
let key = FmtData(self.data, *key);
|
let key = FmtData(self.data, *key);
|
||||||
write!(f, "{:WIDTH$} {dst}, {key}", "load-env")
|
write!(f, "{:WIDTH$} {dst}, {key}", "load-env")
|
||||||
|
|
|
@ -127,6 +127,8 @@ pub enum Instruction {
|
||||||
LoadVariable { dst: RegId, var_id: VarId },
|
LoadVariable { dst: RegId, var_id: VarId },
|
||||||
/// Store the value of a variable from the `src` register
|
/// Store the value of a variable from the `src` register
|
||||||
StoreVariable { var_id: VarId, src: RegId },
|
StoreVariable { var_id: VarId, src: RegId },
|
||||||
|
/// Remove a variable from the stack, freeing up whatever resources were associated with it
|
||||||
|
DropVariable { var_id: VarId },
|
||||||
/// Load the value of an environment variable into the `dst` register
|
/// Load the value of an environment variable into the `dst` register
|
||||||
LoadEnv { dst: RegId, key: DataSlice },
|
LoadEnv { dst: RegId, key: DataSlice },
|
||||||
/// Load the value of an environment variable into the `dst` register, or `Nothing` if it
|
/// Load the value of an environment variable into the `dst` register, or `Nothing` if it
|
||||||
|
@ -290,6 +292,7 @@ impl Instruction {
|
||||||
Instruction::Drain { .. } => None,
|
Instruction::Drain { .. } => None,
|
||||||
Instruction::LoadVariable { dst, .. } => Some(dst),
|
Instruction::LoadVariable { dst, .. } => Some(dst),
|
||||||
Instruction::StoreVariable { .. } => None,
|
Instruction::StoreVariable { .. } => None,
|
||||||
|
Instruction::DropVariable { .. } => None,
|
||||||
Instruction::LoadEnv { dst, .. } => Some(dst),
|
Instruction::LoadEnv { dst, .. } => Some(dst),
|
||||||
Instruction::LoadEnvOpt { dst, .. } => Some(dst),
|
Instruction::LoadEnvOpt { dst, .. } => Some(dst),
|
||||||
Instruction::StoreEnv { .. } => None,
|
Instruction::StoreEnv { .. } => None,
|
||||||
|
|
|
@ -47,6 +47,12 @@ pub enum SyntaxShape {
|
||||||
/// A general expression, eg `1 + 2` or `foo --bar`
|
/// A general expression, eg `1 + 2` or `foo --bar`
|
||||||
Expression,
|
Expression,
|
||||||
|
|
||||||
|
/// A (typically) string argument that follows external command argument parsing rules.
|
||||||
|
///
|
||||||
|
/// Filepaths are expanded if unquoted, globs are allowed, and quotes embedded within unknown
|
||||||
|
/// args are unquoted.
|
||||||
|
ExternalArgument,
|
||||||
|
|
||||||
/// A filepath is allowed
|
/// A filepath is allowed
|
||||||
Filepath,
|
Filepath,
|
||||||
|
|
||||||
|
@ -145,6 +151,7 @@ impl SyntaxShape {
|
||||||
SyntaxShape::DateTime => Type::Date,
|
SyntaxShape::DateTime => Type::Date,
|
||||||
SyntaxShape::Duration => Type::Duration,
|
SyntaxShape::Duration => Type::Duration,
|
||||||
SyntaxShape::Expression => Type::Any,
|
SyntaxShape::Expression => Type::Any,
|
||||||
|
SyntaxShape::ExternalArgument => Type::Any,
|
||||||
SyntaxShape::Filepath => Type::String,
|
SyntaxShape::Filepath => Type::String,
|
||||||
SyntaxShape::Directory => Type::String,
|
SyntaxShape::Directory => Type::String,
|
||||||
SyntaxShape::Float => Type::Float,
|
SyntaxShape::Float => Type::Float,
|
||||||
|
@ -238,6 +245,7 @@ impl Display for SyntaxShape {
|
||||||
SyntaxShape::Signature => write!(f, "signature"),
|
SyntaxShape::Signature => write!(f, "signature"),
|
||||||
SyntaxShape::MatchBlock => write!(f, "match-block"),
|
SyntaxShape::MatchBlock => write!(f, "match-block"),
|
||||||
SyntaxShape::Expression => write!(f, "expression"),
|
SyntaxShape::Expression => write!(f, "expression"),
|
||||||
|
SyntaxShape::ExternalArgument => write!(f, "external-argument"),
|
||||||
SyntaxShape::Boolean => write!(f, "bool"),
|
SyntaxShape::Boolean => write!(f, "bool"),
|
||||||
SyntaxShape::Error => write!(f, "error"),
|
SyntaxShape::Error => write!(f, "error"),
|
||||||
SyntaxShape::CompleterWrapper(x, _) => write!(f, "completable<{x}>"),
|
SyntaxShape::CompleterWrapper(x, _) => write!(f, "completable<{x}>"),
|
||||||
|
|
|
@ -47,7 +47,7 @@ let dark_theme = {
|
||||||
shape_flag: blue_bold
|
shape_flag: blue_bold
|
||||||
shape_float: purple_bold
|
shape_float: purple_bold
|
||||||
# shapes are used to change the cli syntax highlighting
|
# shapes are used to change the cli syntax highlighting
|
||||||
shape_garbage: { fg: white bg: red attr: b}
|
shape_garbage: { fg: white bg: red attr: b }
|
||||||
shape_glob_interpolation: cyan_bold
|
shape_glob_interpolation: cyan_bold
|
||||||
shape_globpattern: cyan_bold
|
shape_globpattern: cyan_bold
|
||||||
shape_int: purple_bold
|
shape_int: purple_bold
|
||||||
|
@ -114,7 +114,8 @@ let light_theme = {
|
||||||
shape_flag: blue_bold
|
shape_flag: blue_bold
|
||||||
shape_float: purple_bold
|
shape_float: purple_bold
|
||||||
# shapes are used to change the cli syntax highlighting
|
# shapes are used to change the cli syntax highlighting
|
||||||
shape_garbage: { fg: white bg: red attr: b}
|
shape_garbage: { fg: white bg: red attr: b }
|
||||||
|
shape_glob_interpolation: cyan_bold
|
||||||
shape_globpattern: cyan_bold
|
shape_globpattern: cyan_bold
|
||||||
shape_int: purple_bold
|
shape_int: purple_bold
|
||||||
shape_internalcall: cyan_bold
|
shape_internalcall: cyan_bold
|
||||||
|
@ -226,9 +227,9 @@ $env.config = {
|
||||||
|
|
||||||
color_config: $dark_theme # if you want a more interesting theme, you can replace the empty record with `$dark_theme`, `$light_theme` or another custom record
|
color_config: $dark_theme # if you want a more interesting theme, you can replace the empty record with `$dark_theme`, `$light_theme` or another custom record
|
||||||
use_grid_icons: true
|
use_grid_icons: true
|
||||||
footer_mode: "25" # always, never, number_of_rows, auto
|
footer_mode: 25 # always, never, number_of_rows, auto
|
||||||
float_precision: 2 # the precision for displaying floats in tables
|
float_precision: 2 # the precision for displaying floats in tables
|
||||||
buffer_editor: "" # command that will be used to edit the current line buffer with ctrl+o, if unset fallback to $env.EDITOR and $env.VISUAL
|
buffer_editor: null # command that will be used to edit the current line buffer with ctrl+o, if unset fallback to $env.EDITOR and $env.VISUAL
|
||||||
use_ansi_coloring: true
|
use_ansi_coloring: true
|
||||||
bracketed_paste: true # enable bracketed paste, currently useless on windows
|
bracketed_paste: true # enable bracketed paste, currently useless on windows
|
||||||
edit_mode: emacs # emacs, vi
|
edit_mode: emacs # emacs, vi
|
||||||
|
@ -888,4 +889,4 @@ $env.config = {
|
||||||
event: { edit: selectall }
|
event: { edit: selectall }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
78
crates/nu_plugin_example/src/commands/call_decl.rs
Normal file
78
crates/nu_plugin_example/src/commands/call_decl.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
|
||||||
|
use nu_protocol::{
|
||||||
|
IntoSpanned, LabeledError, PipelineData, Record, Signature, Spanned, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::ExamplePlugin;
|
||||||
|
|
||||||
|
pub struct CallDecl;
|
||||||
|
|
||||||
|
impl PluginCommand for CallDecl {
|
||||||
|
type Plugin = ExamplePlugin;
|
||||||
|
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"example call-decl"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.required(
|
||||||
|
"name",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the name of the command to call",
|
||||||
|
)
|
||||||
|
.optional(
|
||||||
|
"named_args",
|
||||||
|
SyntaxShape::Record(vec![]),
|
||||||
|
"named arguments to pass to the command",
|
||||||
|
)
|
||||||
|
.rest(
|
||||||
|
"positional_args",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"positional arguments to pass to the command",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Demonstrates calling other commands from plugins using `call_decl()`."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
"
|
||||||
|
The arguments will not be typechecked at parse time. This command is for
|
||||||
|
demonstration only, and should not be used for anything real.
|
||||||
|
"
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_plugin: &ExamplePlugin,
|
||||||
|
engine: &EngineInterface,
|
||||||
|
call: &EvaluatedCall,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, LabeledError> {
|
||||||
|
let name: Spanned<String> = call.req(0)?;
|
||||||
|
let named_args: Option<Record> = call.opt(1)?;
|
||||||
|
let positional_args: Vec<Value> = call.rest(2)?;
|
||||||
|
|
||||||
|
let decl_id = engine.find_decl(&name.item)?.ok_or_else(|| {
|
||||||
|
LabeledError::new(format!("Can't find `{}`", name.item))
|
||||||
|
.with_label("not in scope", name.span)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut new_call = EvaluatedCall::new(call.head);
|
||||||
|
|
||||||
|
for (key, val) in named_args.into_iter().flatten() {
|
||||||
|
new_call.add_named(key.into_spanned(val.span()), val);
|
||||||
|
}
|
||||||
|
|
||||||
|
for val in positional_args {
|
||||||
|
new_call.add_positional(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = engine.call_decl(decl_id, new_call, input, true, false)?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,12 +13,14 @@ pub use three::Three;
|
||||||
pub use two::Two;
|
pub use two::Two;
|
||||||
|
|
||||||
// Engine interface demos
|
// Engine interface demos
|
||||||
|
mod call_decl;
|
||||||
mod config;
|
mod config;
|
||||||
mod ctrlc;
|
mod ctrlc;
|
||||||
mod disable_gc;
|
mod disable_gc;
|
||||||
mod env;
|
mod env;
|
||||||
mod view_span;
|
mod view_span;
|
||||||
|
|
||||||
|
pub use call_decl::CallDecl;
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
pub use ctrlc::Ctrlc;
|
pub use ctrlc::Ctrlc;
|
||||||
pub use disable_gc::DisableGc;
|
pub use disable_gc::DisableGc;
|
||||||
|
|
|
@ -28,6 +28,7 @@ impl Plugin for ExamplePlugin {
|
||||||
Box::new(ViewSpan),
|
Box::new(ViewSpan),
|
||||||
Box::new(DisableGc),
|
Box::new(DisableGc),
|
||||||
Box::new(Ctrlc),
|
Box::new(Ctrlc),
|
||||||
|
Box::new(CallDecl),
|
||||||
// Stream demos
|
// Stream demos
|
||||||
Box::new(CollectBytes),
|
Box::new(CollectBytes),
|
||||||
Box::new(Echo),
|
Box::new(Echo),
|
||||||
|
|
|
@ -76,7 +76,7 @@ pub fn web_examples() -> Vec<Example<'static>> {
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
example: "http get https://en.wikipedia.org/wiki/List_of_cities_in_India_by_population |
|
example: "http get https://en.wikipedia.org/wiki/List_of_cities_in_India_by_population |
|
||||||
query web --as-table [City 'Population(2011)[3]' 'Population(2001)[3][a]' 'State or unionterritory' 'Ref']",
|
query web --as-table [City 'Population(2011)[3]' 'Population(2001)[3][a]' 'State or unionterritory' 'Reference']",
|
||||||
description: "Retrieve a html table from Wikipedia and parse it into a nushell table using table headers as guides",
|
description: "Retrieve a html table from Wikipedia and parse it into a nushell table using table headers as guides",
|
||||||
result: None
|
result: None
|
||||||
},
|
},
|
||||||
|
|
|
@ -329,6 +329,12 @@ fn convert_to_value(
|
||||||
msg: "glob interpolation not supported in nuon".into(),
|
msg: "glob interpolation not supported in nuon".into(),
|
||||||
span: expr.span,
|
span: expr.span,
|
||||||
}),
|
}),
|
||||||
|
Expr::Collect(..) => Err(ShellError::OutsideSpannedLabeledError {
|
||||||
|
src: original_text.to_string(),
|
||||||
|
error: "Error when loading".into(),
|
||||||
|
msg: "`$in` not supported in nuon".into(),
|
||||||
|
span: expr.span,
|
||||||
|
}),
|
||||||
Expr::Subexpression(..) => Err(ShellError::OutsideSpannedLabeledError {
|
Expr::Subexpression(..) => Err(ShellError::OutsideSpannedLabeledError {
|
||||||
src: original_text.to_string(),
|
src: original_text.to_string(),
|
||||||
error: "Error when loading".into(),
|
error: "Error when loading".into(),
|
||||||
|
|
|
@ -200,8 +200,7 @@ pub(crate) fn read_vendor_autoload_files(engine_state: &mut EngineState, stack:
|
||||||
column!()
|
column!()
|
||||||
);
|
);
|
||||||
|
|
||||||
// read and source vendor_autoload_files file if exists
|
for autoload_dir in nu_protocol::eval_const::get_vendor_autoload_dirs(engine_state) {
|
||||||
if let Some(autoload_dir) = nu_protocol::eval_const::get_vendor_autoload_dir(engine_state) {
|
|
||||||
warn!("read_vendor_autoload_files: {}", autoload_dir.display());
|
warn!("read_vendor_autoload_files: {}", autoload_dir.display());
|
||||||
|
|
||||||
if autoload_dir.exists() {
|
if autoload_dir.exists() {
|
||||||
|
|
42
tests/plugins/call_decl.rs
Normal file
42
tests/plugins/call_decl.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use nu_test_support::nu_with_plugins;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn call_to_json() {
|
||||||
|
let result = nu_with_plugins!(
|
||||||
|
cwd: ".",
|
||||||
|
plugin: ("nu_plugin_example"),
|
||||||
|
r#"
|
||||||
|
[42] | example call-decl 'to json' {indent: 4}
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
assert!(result.status.success());
|
||||||
|
// newlines are removed from test output
|
||||||
|
assert_eq!("[ 42]", result.out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn call_reduce() {
|
||||||
|
let result = nu_with_plugins!(
|
||||||
|
cwd: ".",
|
||||||
|
plugin: ("nu_plugin_example"),
|
||||||
|
r#"
|
||||||
|
[1 2 3] | example call-decl 'reduce' {fold: 10} { |it, acc| $it + $acc }
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
assert!(result.status.success());
|
||||||
|
assert_eq!("16", result.out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn call_scope_variables() {
|
||||||
|
let result = nu_with_plugins!(
|
||||||
|
cwd: ".",
|
||||||
|
plugin: ("nu_plugin_example"),
|
||||||
|
r#"
|
||||||
|
let test_var = 10
|
||||||
|
example call-decl 'scope variables' | where name == '$test_var' | length
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
assert!(result.status.success());
|
||||||
|
assert_eq!("1", result.out);
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod call_decl;
|
||||||
mod config;
|
mod config;
|
||||||
mod core_inc;
|
mod core_inc;
|
||||||
mod custom_values;
|
mod custom_values;
|
||||||
|
|
|
@ -186,8 +186,12 @@ fn help_present_in_def() -> TestResult {
|
||||||
#[test]
|
#[test]
|
||||||
fn help_not_present_in_extern() -> TestResult {
|
fn help_not_present_in_extern() -> TestResult {
|
||||||
run_test(
|
run_test(
|
||||||
"module test {export extern \"git fetch\" []}; use test `git fetch`; help git fetch | ansi strip",
|
r#"
|
||||||
"Usage:\n > git fetch",
|
module test {export extern "git fetch" []};
|
||||||
|
use test `git fetch`;
|
||||||
|
help git fetch | find help | to text | ansi strip
|
||||||
|
"#,
|
||||||
|
"",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,29 @@ fn in_and_if_else() -> TestResult {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn in_with_closure() -> TestResult {
|
||||||
|
// Can use $in twice
|
||||||
|
run_test(r#"3 | do { let x = $in; let y = $in; $x + $y }"#, "6")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn in_with_custom_command() -> TestResult {
|
||||||
|
// Can use $in twice
|
||||||
|
run_test(
|
||||||
|
r#"def foo [] { let x = $in; let y = $in; $x + $y }; 3 | foo"#,
|
||||||
|
"6",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn in_used_twice_and_also_in_pipeline() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"3 | do { let x = $in; let y = $in; $x + $y | $in * 4 }"#,
|
||||||
|
"24",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn help_works_with_missing_requirements() -> TestResult {
|
fn help_works_with_missing_requirements() -> TestResult {
|
||||||
run_test(r#"each --help | lines | length"#, "72")
|
run_test(r#"each --help | lines | length"#, "72")
|
||||||
|
|
|
@ -137,3 +137,39 @@ fn known_external_aliased_subcommand_from_module() -> TestResult {
|
||||||
String::from_utf8(output.stdout)?.trim(),
|
String::from_utf8(output.stdout)?.trim(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn known_external_arg_expansion() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"
|
||||||
|
extern echo [];
|
||||||
|
echo ~/foo
|
||||||
|
"#,
|
||||||
|
&dirs::home_dir()
|
||||||
|
.expect("can't find home dir")
|
||||||
|
.join("foo")
|
||||||
|
.to_string_lossy(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn known_external_arg_quoted_no_expand() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"
|
||||||
|
extern echo [];
|
||||||
|
echo "~/foo"
|
||||||
|
"#,
|
||||||
|
"~/foo",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn known_external_arg_internally_quoted_options() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"
|
||||||
|
extern echo [];
|
||||||
|
echo --option="test"
|
||||||
|
"#,
|
||||||
|
"--option=test",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -129,3 +129,16 @@ fn transpose_into_load_env() -> TestResult {
|
||||||
"10",
|
"10",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn in_variable_expression_correct_output_type() -> TestResult {
|
||||||
|
run_test(r#"def foo []: nothing -> string { 'foo' | $"($in)" }"#, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn in_variable_expression_wrong_output_type() -> TestResult {
|
||||||
|
fail_test(
|
||||||
|
r#"def foo []: nothing -> int { 'foo' | $"($in)" }"#,
|
||||||
|
"expected int",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user