forked from extern/nushell
Allow parse-time evaluation of calls, pipelines and subexpressions (#9499)
Co-authored-by: Antoine Stevan <44101798+amtoine@users.noreply.github.com>
This commit is contained in:
parent
3d73287ea4
commit
5ac5b90aed
@ -1,5 +1,5 @@
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||||
};
|
};
|
||||||
@ -27,6 +27,10 @@ impl Command for Describe {
|
|||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
_engine_state: &EngineState,
|
_engine_state: &EngineState,
|
||||||
@ -34,6 +38,46 @@ impl Command for Describe {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
run(call, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
_working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
run(call, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Describe the type of a string",
|
||||||
|
example: "'hello' | describe",
|
||||||
|
result: Some(Value::test_string("string")),
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
Example {
|
||||||
|
description: "Describe a stream of data, collecting it first",
|
||||||
|
example: "[1 2 3] | each {|i| $i} | describe",
|
||||||
|
result: Some(Value::test_string("list<int> (stream)")),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Describe the input but do not collect streams",
|
||||||
|
example: "[1 2 3] | each {|i| $i} | describe --no-collect",
|
||||||
|
result: Some(Value::test_string("stream")),
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["type", "typeof", "info", "structure"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(call: &Call, input: PipelineData) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
|
|
||||||
let no_collect: bool = call.has_flag("no-collect");
|
let no_collect: bool = call.has_flag("no-collect");
|
||||||
@ -67,33 +111,6 @@ impl Command for Describe {
|
|||||||
span: head,
|
span: head,
|
||||||
}
|
}
|
||||||
.into_pipeline_data())
|
.into_pipeline_data())
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
description: "Describe the type of a string",
|
|
||||||
example: "'hello' | describe",
|
|
||||||
result: Some(Value::test_string("string")),
|
|
||||||
},
|
|
||||||
/*
|
|
||||||
Example {
|
|
||||||
description: "Describe a stream of data, collecting it first",
|
|
||||||
example: "[1 2 3] | each {|i| $i} | describe",
|
|
||||||
result: Some(Value::test_string("list<int> (stream)")),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Describe the input but do not collect streams",
|
|
||||||
example: "[1 2 3] | each {|i| $i} | describe --no-collect",
|
|
||||||
result: Some(Value::test_string("stream")),
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["type", "typeof", "info", "structure"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, ListStream, PipelineData, ShellError, Signature, Span, SyntaxShape, Type,
|
Category, Example, ListStream, PipelineData, ShellError, Signature, Span, SyntaxShape, Type,
|
||||||
Value,
|
Value,
|
||||||
@ -31,6 +31,10 @@ it returns it. Otherwise, it returns a list of the arguments. There is usually
|
|||||||
little reason to use this over just writing the values as-is."#
|
little reason to use this over just writing the values as-is."#
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -38,22 +42,18 @@ little reason to use this over just writing the values as-is."#
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
call.rest(engine_state, stack, 0).map(|to_be_echoed| {
|
let args = call.rest(engine_state, stack, 0);
|
||||||
let n = to_be_echoed.len();
|
run(engine_state, args, call)
|
||||||
match n.cmp(&1usize) {
|
|
||||||
// More than one value is converted in a stream of values
|
|
||||||
std::cmp::Ordering::Greater => PipelineData::ListStream(
|
|
||||||
ListStream::from_stream(to_be_echoed.into_iter(), engine_state.ctrlc.clone()),
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
|
|
||||||
// But a single value can be forwarded as it is
|
|
||||||
std::cmp::Ordering::Equal => PipelineData::Value(to_be_echoed[0].clone(), None),
|
|
||||||
|
|
||||||
// When there are no elements, we echo the empty string
|
|
||||||
std::cmp::Ordering::Less => PipelineData::Value(Value::string("", call.head), None),
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let args = call.rest_const(working_set, 0);
|
||||||
|
run(working_set.permanent(), args, call)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -76,6 +76,29 @@ little reason to use this over just writing the values as-is."#
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
args: Result<Vec<Value>, ShellError>,
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
args.map(|to_be_echoed| {
|
||||||
|
let n = to_be_echoed.len();
|
||||||
|
match n.cmp(&1usize) {
|
||||||
|
// More than one value is converted in a stream of values
|
||||||
|
std::cmp::Ordering::Greater => PipelineData::ListStream(
|
||||||
|
ListStream::from_stream(to_be_echoed.into_iter(), engine_state.ctrlc.clone()),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
|
||||||
|
// But a single value can be forwarded as it is
|
||||||
|
std::cmp::Ordering::Equal => PipelineData::Value(to_be_echoed[0].clone(), None),
|
||||||
|
|
||||||
|
// When there are no elements, we echo the empty string
|
||||||
|
std::cmp::Ordering::Less => PipelineData::Value(Value::string("", call.head), None),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
|
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -24,6 +24,10 @@ impl Command for Ignore {
|
|||||||
vec!["silent", "quiet", "out-null"]
|
vec!["silent", "quiet", "out-null"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
_engine_state: &EngineState,
|
_engine_state: &EngineState,
|
||||||
@ -35,6 +39,16 @@ impl Command for Ignore {
|
|||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
_working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
input.into_value(call.head);
|
||||||
|
Ok(PipelineData::empty())
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Ignore the output of an echo command",
|
description: "Ignore the output of an echo command",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Type, Value,
|
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Type, Value,
|
||||||
};
|
};
|
||||||
@ -26,14 +26,27 @@ impl Command for Version {
|
|||||||
"Display Nu version, and its build configuration."
|
"Display Nu version, and its build configuration."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
version(engine_state, stack, call, input)
|
version(engine_state, call)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
version(working_set.permanent(), call)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -45,12 +58,7 @@ impl Command for Version {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn version(
|
pub fn version(engine_state: &EngineState, call: &Call) -> Result<PipelineData, ShellError> {
|
||||||
engine_state: &EngineState,
|
|
||||||
_stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
// Pre-allocate the arrays in the worst case (12 items):
|
// Pre-allocate the arrays in the worst case (12 items):
|
||||||
// - version
|
// - version
|
||||||
// - branch
|
// - branch
|
||||||
|
@ -3,7 +3,7 @@ use std::path::Path;
|
|||||||
use super::PathSubcommandArguments;
|
use super::PathSubcommandArguments;
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{EngineState, Stack};
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Spanned,
|
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Spanned,
|
||||||
SyntaxShape, Type, Value,
|
SyntaxShape, Type, Value,
|
||||||
@ -45,6 +45,10 @@ impl Command for SubCommand {
|
|||||||
"Get the final component of a path."
|
"Get the final component of a path."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -67,6 +71,27 @@ impl Command for SubCommand {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let args = Arguments {
|
||||||
|
replace: call.get_flag_const(working_set, "replace")?,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |value| super::operate(&get_basename, &args, value, head),
|
||||||
|
working_set.permanent().ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
|
@ -2,7 +2,7 @@ use std::path::Path;
|
|||||||
|
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{EngineState, Stack};
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Spanned,
|
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Spanned,
|
||||||
SyntaxShape, Type, Value,
|
SyntaxShape, Type, Value,
|
||||||
@ -53,6 +53,10 @@ impl Command for SubCommand {
|
|||||||
"Get the parent directory of a path."
|
"Get the parent directory of a path."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -76,6 +80,28 @@ impl Command for SubCommand {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let args = Arguments {
|
||||||
|
replace: call.get_flag_const(working_set, "replace")?,
|
||||||
|
num_levels: call.get_flag_const(working_set, "num-levels")?,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |value| super::operate(&get_dirname, &args, value, head),
|
||||||
|
working_set.permanent().ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use nu_engine::current_dir;
|
use nu_engine::{current_dir, current_dir_const};
|
||||||
use nu_path::expand_path_with;
|
use nu_path::expand_path_with;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{EngineState, Stack};
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||||
};
|
};
|
||||||
@ -45,6 +45,10 @@ impl Command for SubCommand {
|
|||||||
If you need to distinguish dirs and files, please use `path type`."#
|
If you need to distinguish dirs and files, please use `path type`."#
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -66,6 +70,26 @@ If you need to distinguish dirs and files, please use `path type`."#
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let args = Arguments {
|
||||||
|
pwd: current_dir_const(working_set)?,
|
||||||
|
};
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |value| super::operate(&exists, &args, value, head),
|
||||||
|
working_set.permanent().ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use nu_engine::env::current_dir_str;
|
use nu_engine::env::{current_dir_str, current_dir_str_const};
|
||||||
use nu_path::{canonicalize_with, expand_path_with};
|
use nu_path::{canonicalize_with, expand_path_with};
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{EngineState, Stack};
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||||
};
|
};
|
||||||
@ -48,6 +48,10 @@ impl Command for SubCommand {
|
|||||||
"Try to expand a path to its absolute form."
|
"Try to expand a path to its absolute form."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -71,6 +75,28 @@ impl Command for SubCommand {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let args = Arguments {
|
||||||
|
strict: call.has_flag("strict"),
|
||||||
|
cwd: current_dir_str_const(working_set)?,
|
||||||
|
not_follow_symlink: call.has_flag("no-symlink"),
|
||||||
|
};
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |value| super::operate(&expand, &args, value, head),
|
||||||
|
working_set.permanent().ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
|
@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{EngineState, Stack};
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::Command, Category, Example, PipelineData, Record, ShellError, Signature, Span, Spanned,
|
engine::Command, Category, Example, PipelineData, Record, ShellError, Signature, Span, Spanned,
|
||||||
SyntaxShape, Type, Value,
|
SyntaxShape, Type, Value,
|
||||||
@ -46,6 +46,10 @@ impl Command for SubCommand {
|
|||||||
the output of 'path parse' and 'path split' subcommands."#
|
the output of 'path parse' and 'path split' subcommands."#
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -53,29 +57,24 @@ the output of 'path parse' and 'path split' subcommands."#
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
|
||||||
let args = Arguments {
|
let args = Arguments {
|
||||||
append: call.rest(engine_state, stack, 0)?,
|
append: call.rest(engine_state, stack, 0)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let metadata = input.metadata();
|
run(call, &args, input)
|
||||||
|
}
|
||||||
|
|
||||||
match input {
|
fn run_const(
|
||||||
PipelineData::Value(val, md) => {
|
&self,
|
||||||
Ok(PipelineData::Value(handle_value(val, &args, head), md))
|
working_set: &StateWorkingSet,
|
||||||
}
|
call: &Call,
|
||||||
PipelineData::ListStream(..) => Ok(PipelineData::Value(
|
input: PipelineData,
|
||||||
handle_value(input.into_value(head), &args, head),
|
) -> Result<PipelineData, ShellError> {
|
||||||
metadata,
|
let args = Arguments {
|
||||||
)),
|
append: call.rest_const(working_set, 0)?,
|
||||||
PipelineData::Empty { .. } => Err(ShellError::PipelineEmpty { dst_span: head }),
|
};
|
||||||
_ => Err(ShellError::UnsupportedInput(
|
|
||||||
"Input value cannot be joined".to_string(),
|
run(call, &args, input)
|
||||||
"value originates from here".into(),
|
|
||||||
head,
|
|
||||||
input.span().unwrap_or(call.head),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@ -147,6 +146,27 @@ the output of 'path parse' and 'path split' subcommands."#
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run(call: &Call, args: &Arguments, input: PipelineData) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
|
||||||
|
let metadata = input.metadata();
|
||||||
|
|
||||||
|
match input {
|
||||||
|
PipelineData::Value(val, md) => Ok(PipelineData::Value(handle_value(val, args, head), md)),
|
||||||
|
PipelineData::ListStream(..) => Ok(PipelineData::Value(
|
||||||
|
handle_value(input.into_value(head), args, head),
|
||||||
|
metadata,
|
||||||
|
)),
|
||||||
|
PipelineData::Empty { .. } => Err(ShellError::PipelineEmpty { dst_span: head }),
|
||||||
|
_ => Err(ShellError::UnsupportedInput(
|
||||||
|
"Input value cannot be joined".to_string(),
|
||||||
|
"value originates from here".into(),
|
||||||
|
head,
|
||||||
|
input.span().unwrap_or(call.head),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_value(v: Value, args: &Arguments, head: Span) -> Value {
|
fn handle_value(v: Value, args: &Arguments, head: Span) -> Value {
|
||||||
match v {
|
match v {
|
||||||
Value::String { ref val, .. } => join_single(Path::new(val), head, args),
|
Value::String { ref val, .. } => join_single(Path::new(val), head, args),
|
||||||
|
@ -2,7 +2,7 @@ use std::path::Path;
|
|||||||
|
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{EngineState, Stack};
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::Command, Category, Example, PipelineData, Record, ShellError, Signature, Span, Spanned,
|
engine::Command, Category, Example, PipelineData, Record, ShellError, Signature, Span, Spanned,
|
||||||
SyntaxShape, Type, Value,
|
SyntaxShape, Type, Value,
|
||||||
@ -48,6 +48,10 @@ impl Command for SubCommand {
|
|||||||
On Windows, an extra 'prefix' column is added."#
|
On Windows, an extra 'prefix' column is added."#
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -70,6 +74,27 @@ On Windows, an extra 'prefix' column is added."#
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let args = Arguments {
|
||||||
|
extension: call.get_flag_const(working_set, "extension")?,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |value| super::operate(&parse, &args, value, head),
|
||||||
|
working_set.permanent().ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
|
@ -3,7 +3,7 @@ use std::path::Path;
|
|||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_path::expand_to_real_path;
|
use nu_path::expand_to_real_path;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{EngineState, Stack};
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Spanned,
|
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Spanned,
|
||||||
SyntaxShape, Type, Value,
|
SyntaxShape, Type, Value,
|
||||||
@ -52,6 +52,10 @@ absolute or both relative. The argument path needs to be a parent of the input
|
|||||||
path."#
|
path."#
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -74,6 +78,27 @@ path."#
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let args = Arguments {
|
||||||
|
path: call.req_const(working_set, 0)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |value| super::operate(&relative_to, &args, value, head),
|
||||||
|
working_set.permanent().ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::path::{Component, Path};
|
use std::path::{Component, Path};
|
||||||
|
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{EngineState, Stack};
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||||
};
|
};
|
||||||
@ -36,6 +36,10 @@ impl Command for SubCommand {
|
|||||||
"Split a path into a list based on the system's path separator."
|
"Split a path into a list based on the system's path separator."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -56,6 +60,25 @@ impl Command for SubCommand {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let args = Arguments;
|
||||||
|
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |value| super::operate(&split, &args, value, head),
|
||||||
|
working_set.permanent().ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
|
@ -2,7 +2,7 @@ use std::path::Path;
|
|||||||
|
|
||||||
use nu_path::expand_tilde;
|
use nu_path::expand_tilde;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{EngineState, Stack};
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||||
};
|
};
|
||||||
@ -43,6 +43,10 @@ impl Command for SubCommand {
|
|||||||
If nothing is found, an empty string will be returned."#
|
If nothing is found, an empty string will be returned."#
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -63,6 +67,25 @@ If nothing is found, an empty string will be returned."#
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let args = Arguments;
|
||||||
|
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |value| super::operate(&r#type, &args, value, head),
|
||||||
|
working_set.permanent().ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
|
@ -3,7 +3,7 @@ use nu_cmd_base::input_handler::{operate, CmdArgument};
|
|||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::Category;
|
use nu_protocol::Category;
|
||||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
@ -62,6 +62,10 @@ impl Command for SubCommand {
|
|||||||
vec!["size", "count"]
|
vec!["size", "count"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -70,11 +74,17 @@ impl Command for SubCommand {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
let args = Arguments {
|
run(cell_paths, engine_state, call, input)
|
||||||
cell_paths: (!cell_paths.is_empty()).then_some(cell_paths),
|
}
|
||||||
graphemes: grapheme_flags(call)?,
|
|
||||||
};
|
fn run_const(
|
||||||
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 0)?;
|
||||||
|
run(cell_paths, working_set.permanent(), call, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -101,6 +111,19 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
cell_paths: Vec<CellPath>,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let args = Arguments {
|
||||||
|
cell_paths: (!cell_paths.is_empty()).then_some(cell_paths),
|
||||||
|
graphemes: grapheme_flags(call)?,
|
||||||
|
};
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
fn action(input: &Value, arg: &Arguments, head: Span) -> Value {
|
fn action(input: &Value, arg: &Arguments, head: Span) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::String { val, .. } => Value::int(
|
Value::String { val, .. } => Value::int(
|
||||||
|
@ -34,3 +34,9 @@ fn echo_range_handles_exclusive_down() {
|
|||||||
|
|
||||||
assert_eq!(actual.out, "[3,2]");
|
assert_eq!(actual.out, "[3,2]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn echo_const() {
|
||||||
|
let actual = nu!("const x = (echo spam); $x");
|
||||||
|
assert_eq!(actual.out, "spam");
|
||||||
|
}
|
||||||
|
@ -81,3 +81,9 @@ fn replaces_basename_of_path_ending_with_double_dot() {
|
|||||||
let expected = join_path_sep(&["some/file.txt/..", "eggs"]);
|
let expected = join_path_sep(&["some/file.txt/..", "eggs"]);
|
||||||
assert_eq!(actual.out, expected);
|
assert_eq!(actual.out, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_path_basename() {
|
||||||
|
let actual = nu!("const name = ('spam/eggs.txt' | path basename); $name");
|
||||||
|
assert_eq!(actual.out, "eggs.txt");
|
||||||
|
}
|
||||||
|
@ -135,3 +135,9 @@ fn replaces_dirname_of_way_too_many_levels() {
|
|||||||
let expected = join_path_sep(&["eggs", "some/dir/with/spam.txt"]);
|
let expected = join_path_sep(&["eggs", "some/dir/with/spam.txt"]);
|
||||||
assert_eq!(actual.out, expected);
|
assert_eq!(actual.out, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_path_dirname() {
|
||||||
|
let actual = nu!("const name = ('spam/eggs.txt' | path dirname); $name");
|
||||||
|
assert_eq!(actual.out, "spam");
|
||||||
|
}
|
||||||
|
@ -57,3 +57,9 @@ fn checks_tilde_relative_path_exists() {
|
|||||||
let actual = nu!("'~' | path exists");
|
let actual = nu!("'~' | path exists");
|
||||||
assert_eq!(actual.out, "true");
|
assert_eq!(actual.out, "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_path_exists() {
|
||||||
|
let actual = nu!("const exists = ('~' | path exists); $exists");
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
}
|
||||||
|
@ -66,6 +66,26 @@ fn expands_path_with_double_dot() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_path_expand() {
|
||||||
|
Playground::setup("const_path_expand", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.within("menu")
|
||||||
|
.with_files(vec![EmptyFile("spam.txt")]);
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(), pipeline(
|
||||||
|
r#"
|
||||||
|
const result = ("menu/./spam.txt" | path expand);
|
||||||
|
$result
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
let expected = dirs.test.join("menu").join("spam.txt");
|
||||||
|
assert_eq!(PathBuf::from(actual.out), expected);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
mod windows {
|
mod windows {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -54,3 +54,10 @@ fn returns_joined_path_when_joining_empty_path() {
|
|||||||
|
|
||||||
assert_eq!(actual.out, "foo.txt");
|
assert_eq!(actual.out, "foo.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_path_join() {
|
||||||
|
let actual = nu!("const name = ('spam' | path join 'eggs.txt'); $name");
|
||||||
|
let expected = join_path_sep(&["spam", "eggs.txt"]);
|
||||||
|
assert_eq!(actual.out, expected);
|
||||||
|
}
|
||||||
|
@ -7,6 +7,7 @@ mod parse;
|
|||||||
mod split;
|
mod split;
|
||||||
mod type_;
|
mod type_;
|
||||||
|
|
||||||
|
use nu_test_support::{nu, pipeline};
|
||||||
use std::path::MAIN_SEPARATOR;
|
use std::path::MAIN_SEPARATOR;
|
||||||
|
|
||||||
/// Helper function that joins string literals with '/' or '\', based on host OS
|
/// Helper function that joins string literals with '/' or '\', based on host OS
|
||||||
@ -32,3 +33,9 @@ fn joins_path_on_other_than_windows() {
|
|||||||
|
|
||||||
assert_eq!(&actual, "sausage/bacon/spam");
|
assert_eq!(&actual, "sausage/bacon/spam");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_path_relative_to() {
|
||||||
|
let actual = nu!("'/home/viking' | path relative-to '/home'");
|
||||||
|
assert_eq!(actual.out, "viking");
|
||||||
|
}
|
||||||
|
@ -119,3 +119,15 @@ fn parses_into_correct_number_of_columns() {
|
|||||||
|
|
||||||
assert_eq!(actual.out, expected);
|
assert_eq!(actual.out, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_path_parse() {
|
||||||
|
let actual = nu!("const name = ('spam/eggs.txt' | path parse); $name.parent");
|
||||||
|
assert_eq!(actual.out, "spam");
|
||||||
|
|
||||||
|
let actual = nu!("const name = ('spam/eggs.txt' | path parse); $name.stem");
|
||||||
|
assert_eq!(actual.out, "eggs");
|
||||||
|
|
||||||
|
let actual = nu!("const name = ('spam/eggs.txt' | path parse); $name.extension");
|
||||||
|
assert_eq!(actual.out, "txt");
|
||||||
|
}
|
||||||
|
@ -25,3 +25,13 @@ fn splits_correctly_single_path() {
|
|||||||
|
|
||||||
assert_eq!(actual.out, "spam.txt");
|
assert_eq!(actual.out, "spam.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn splits_correctly_single_path_const() {
|
||||||
|
let actual = nu!(r#"
|
||||||
|
const result = ('home/viking/spam.txt' | path split);
|
||||||
|
$result | last
|
||||||
|
"#);
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "spam.txt");
|
||||||
|
}
|
||||||
|
@ -61,3 +61,22 @@ fn returns_type_of_existing_directory() {
|
|||||||
assert_eq!(actual.out, "dir");
|
assert_eq!(actual.out, "dir");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn returns_type_of_existing_file_const() {
|
||||||
|
Playground::setup("path_type_const", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.within("menu")
|
||||||
|
.with_files(vec![EmptyFile("spam.txt")]);
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(), pipeline(
|
||||||
|
r#"
|
||||||
|
const ty = ("menu" | path type);
|
||||||
|
$ty
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "dir");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
|
eval_const::eval_constant,
|
||||||
FromValue, ShellError,
|
FromValue, ShellError,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -14,6 +15,12 @@ pub trait CallExt {
|
|||||||
name: &str,
|
name: &str,
|
||||||
) -> Result<Option<T>, ShellError>;
|
) -> Result<Option<T>, ShellError>;
|
||||||
|
|
||||||
|
fn get_flag_const<T: FromValue>(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<Option<T>, ShellError>;
|
||||||
|
|
||||||
fn rest<T: FromValue>(
|
fn rest<T: FromValue>(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -21,6 +28,12 @@ pub trait CallExt {
|
|||||||
starting_pos: usize,
|
starting_pos: usize,
|
||||||
) -> Result<Vec<T>, ShellError>;
|
) -> Result<Vec<T>, ShellError>;
|
||||||
|
|
||||||
|
fn rest_const<T: FromValue>(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
starting_pos: usize,
|
||||||
|
) -> Result<Vec<T>, ShellError>;
|
||||||
|
|
||||||
fn opt<T: FromValue>(
|
fn opt<T: FromValue>(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -35,6 +48,12 @@ pub trait CallExt {
|
|||||||
pos: usize,
|
pos: usize,
|
||||||
) -> Result<T, ShellError>;
|
) -> Result<T, ShellError>;
|
||||||
|
|
||||||
|
fn req_const<T: FromValue>(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
pos: usize,
|
||||||
|
) -> Result<T, ShellError>;
|
||||||
|
|
||||||
fn req_parser_info<T: FromValue>(
|
fn req_parser_info<T: FromValue>(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -58,6 +77,19 @@ impl CallExt for Call {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_flag_const<T: FromValue>(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<Option<T>, ShellError> {
|
||||||
|
if let Some(expr) = self.get_flag_expr(name) {
|
||||||
|
let result = eval_constant(working_set, &expr)?;
|
||||||
|
FromValue::from_value(&result).map(Some)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn rest<T: FromValue>(
|
fn rest<T: FromValue>(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -74,6 +106,21 @@ impl CallExt for Call {
|
|||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rest_const<T: FromValue>(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
starting_pos: usize,
|
||||||
|
) -> Result<Vec<T>, ShellError> {
|
||||||
|
let mut output = vec![];
|
||||||
|
|
||||||
|
for expr in self.positional_iter().skip(starting_pos) {
|
||||||
|
let result = eval_constant(working_set, expr)?;
|
||||||
|
output.push(FromValue::from_value(&result)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
fn opt<T: FromValue>(
|
fn opt<T: FromValue>(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -107,6 +154,24 @@ impl CallExt for Call {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn req_const<T: FromValue>(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
pos: usize,
|
||||||
|
) -> Result<T, ShellError> {
|
||||||
|
if let Some(expr) = self.positional_nth(pos) {
|
||||||
|
let result = eval_constant(working_set, expr)?;
|
||||||
|
FromValue::from_value(&result)
|
||||||
|
} else if self.positional_len() == 0 {
|
||||||
|
Err(ShellError::AccessEmptyContent { span: self.head })
|
||||||
|
} else {
|
||||||
|
Err(ShellError::AccessBeyondEnd {
|
||||||
|
max_idx: self.positional_len() - 1,
|
||||||
|
span: self.head,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn req_parser_info<T: FromValue>(
|
fn req_parser_info<T: FromValue>(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use nu_protocol::ast::{Call, Expr, PathMember};
|
use nu_protocol::ast::{Call, Expr, PathMember};
|
||||||
use nu_protocol::engine::{EngineState, Stack};
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet, PWD_ENV};
|
||||||
use nu_protocol::{Config, PipelineData, ShellError, Span, Value, VarId};
|
use nu_protocol::{Config, PipelineData, ShellError, Span, Value, VarId};
|
||||||
|
|
||||||
use nu_path::canonicalize_with;
|
use nu_path::canonicalize_with;
|
||||||
@ -159,8 +159,9 @@ pub fn env_to_strings(
|
|||||||
|
|
||||||
/// Shorthand for env_to_string() for PWD with custom error
|
/// Shorthand for env_to_string() for PWD with custom error
|
||||||
pub fn current_dir_str(engine_state: &EngineState, stack: &Stack) -> Result<String, ShellError> {
|
pub fn current_dir_str(engine_state: &EngineState, stack: &Stack) -> Result<String, ShellError> {
|
||||||
if let Some(pwd) = stack.get_env_var(engine_state, "PWD") {
|
if let Some(pwd) = stack.get_env_var(engine_state, PWD_ENV) {
|
||||||
match env_to_string("PWD", &pwd, engine_state, stack) {
|
// TODO: PWD should be string by default, we don't need to run ENV_CONVERSIONS on it
|
||||||
|
match env_to_string(PWD_ENV, &pwd, engine_state, stack) {
|
||||||
Ok(cwd) => {
|
Ok(cwd) => {
|
||||||
if Path::new(&cwd).is_absolute() {
|
if Path::new(&cwd).is_absolute() {
|
||||||
Ok(cwd)
|
Ok(cwd)
|
||||||
@ -187,11 +188,55 @@ pub fn current_dir_str(engine_state: &EngineState, stack: &Stack) -> Result<Stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Simplified version of current_dir_str() for constant evaluation
|
||||||
|
pub fn current_dir_str_const(working_set: &StateWorkingSet) -> Result<String, ShellError> {
|
||||||
|
if let Some(pwd) = working_set.get_env_var(PWD_ENV) {
|
||||||
|
match pwd {
|
||||||
|
Value::String { val, span } => {
|
||||||
|
if Path::new(val).is_absolute() {
|
||||||
|
Ok(val.clone())
|
||||||
|
} else {
|
||||||
|
Err(ShellError::GenericError(
|
||||||
|
"Invalid current directory".to_string(),
|
||||||
|
format!("The 'PWD' environment variable must be set to an absolute path. Found: '{val}'"),
|
||||||
|
Some(*span),
|
||||||
|
None,
|
||||||
|
Vec::new()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::GenericError(
|
||||||
|
"PWD is not a string".to_string(),
|
||||||
|
"".to_string(),
|
||||||
|
None,
|
||||||
|
Some(
|
||||||
|
"Cusrrent working directory environment variable 'PWD' must be a string."
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
Vec::new(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ShellError::GenericError(
|
||||||
|
"Current directory not found".to_string(),
|
||||||
|
"".to_string(),
|
||||||
|
None,
|
||||||
|
Some("The environment variable 'PWD' was not found. It is required to define the current directory.".to_string()),
|
||||||
|
Vec::new(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Calls current_dir_str() and returns the current directory as a PathBuf
|
/// Calls current_dir_str() and returns the current directory as a PathBuf
|
||||||
pub fn current_dir(engine_state: &EngineState, stack: &Stack) -> Result<PathBuf, ShellError> {
|
pub fn current_dir(engine_state: &EngineState, stack: &Stack) -> Result<PathBuf, ShellError> {
|
||||||
current_dir_str(engine_state, stack).map(PathBuf::from)
|
current_dir_str(engine_state, stack).map(PathBuf::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Version of current_dir() for constant evaluation
|
||||||
|
pub fn current_dir_const(working_set: &StateWorkingSet) -> Result<PathBuf, ShellError> {
|
||||||
|
current_dir_str_const(working_set).map(PathBuf::from)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the contents of path environment variable as a list of strings
|
/// Get the contents of path environment variable as a list of strings
|
||||||
///
|
///
|
||||||
/// On non-Windows: It will fetch PATH
|
/// On non-Windows: It will fetch PATH
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
mod deparse;
|
mod deparse;
|
||||||
mod eval;
|
|
||||||
mod flatten;
|
mod flatten;
|
||||||
mod known_external;
|
mod known_external;
|
||||||
mod lex;
|
mod lex;
|
||||||
|
@ -12,6 +12,7 @@ use nu_protocol::{
|
|||||||
ImportPatternMember, Pipeline, PipelineElement,
|
ImportPatternMember, Pipeline, PipelineElement,
|
||||||
},
|
},
|
||||||
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
|
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
|
||||||
|
eval_const::{eval_constant, value_as_string},
|
||||||
span, Alias, BlockId, Exportable, Module, ModuleId, ParseError, PositionalArg,
|
span, Alias, BlockId, Exportable, Module, ModuleId, ParseError, PositionalArg,
|
||||||
ResolvedImportPattern, Span, Spanned, SyntaxShape, Type, VarId,
|
ResolvedImportPattern, Span, Spanned, SyntaxShape, Type, VarId,
|
||||||
};
|
};
|
||||||
@ -24,7 +25,6 @@ pub const LIB_DIRS_VAR: &str = "NU_LIB_DIRS";
|
|||||||
pub const PLUGIN_DIRS_VAR: &str = "NU_PLUGIN_DIRS";
|
pub const PLUGIN_DIRS_VAR: &str = "NU_PLUGIN_DIRS";
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
eval::{eval_constant, value_as_string},
|
|
||||||
is_math_expression_like,
|
is_math_expression_like,
|
||||||
known_external::KnownExternal,
|
known_external::KnownExternal,
|
||||||
lex,
|
lex,
|
||||||
@ -2585,12 +2585,12 @@ pub fn parse_overlay_new(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
|||||||
Ok(val) => match value_as_string(val, expr.span) {
|
Ok(val) => match value_as_string(val, expr.span) {
|
||||||
Ok(s) => (s, expr.span),
|
Ok(s) => (s, expr.span),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err);
|
working_set.error(err.wrap(working_set, call_span));
|
||||||
return garbage_pipeline(&[call_span]);
|
return garbage_pipeline(&[call_span]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err);
|
working_set.error(err.wrap(working_set, call_span));
|
||||||
return garbage_pipeline(&[call_span]);
|
return garbage_pipeline(&[call_span]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2634,12 +2634,12 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
|||||||
Ok(val) => match value_as_string(val, expr.span) {
|
Ok(val) => match value_as_string(val, expr.span) {
|
||||||
Ok(s) => (s, expr.span),
|
Ok(s) => (s, expr.span),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err);
|
working_set.error(err.wrap(working_set, call_span));
|
||||||
return garbage_pipeline(&[call_span]);
|
return garbage_pipeline(&[call_span]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err);
|
working_set.error(err.wrap(working_set, call_span));
|
||||||
return garbage_pipeline(&[call_span]);
|
return garbage_pipeline(&[call_span]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2660,12 +2660,12 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
|||||||
span: new_name_expression.span,
|
span: new_name_expression.span,
|
||||||
}),
|
}),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err);
|
working_set.error(err.wrap(working_set, call_span));
|
||||||
return garbage_pipeline(&[call_span]);
|
return garbage_pipeline(&[call_span]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err);
|
working_set.error(err.wrap(working_set, call_span));
|
||||||
return garbage_pipeline(&[call_span]);
|
return garbage_pipeline(&[call_span]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2851,12 +2851,12 @@ pub fn parse_overlay_hide(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
|||||||
Ok(val) => match value_as_string(val, expr.span) {
|
Ok(val) => match value_as_string(val, expr.span) {
|
||||||
Ok(s) => (s, expr.span),
|
Ok(s) => (s, expr.span),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err);
|
working_set.error(err.wrap(working_set, call_span));
|
||||||
return garbage_pipeline(&[call_span]);
|
return garbage_pipeline(&[call_span]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err);
|
working_set.error(err.wrap(working_set, call_span));
|
||||||
return garbage_pipeline(&[call_span]);
|
return garbage_pipeline(&[call_span]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3107,7 +3107,7 @@ pub fn parse_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipelin
|
|||||||
// Assign the constant value to the variable
|
// Assign the constant value to the variable
|
||||||
working_set.set_variable_const_val(var_id, val);
|
working_set.set_variable_const_val(var_id, val);
|
||||||
}
|
}
|
||||||
Err(err) => working_set.error(err),
|
Err(err) => working_set.error(err.wrap(working_set, rvalue.span)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3300,7 +3300,7 @@ pub fn parse_source(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeli
|
|||||||
let val = match eval_constant(working_set, &expr) {
|
let val = match eval_constant(working_set, &expr) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err);
|
working_set.error(err.wrap(working_set, span(&spans[1..])));
|
||||||
return Pipeline::from_vec(vec![Expression {
|
return Pipeline::from_vec(vec![Expression {
|
||||||
expr: Expr::Call(call),
|
expr: Expr::Call(call),
|
||||||
span: span(&spans[1..]),
|
span: span(&spans[1..]),
|
||||||
@ -3313,7 +3313,7 @@ pub fn parse_source(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeli
|
|||||||
let filename = match value_as_string(val, spans[1]) {
|
let filename = match value_as_string(val, spans[1]) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err);
|
working_set.error(err.wrap(working_set, span(&spans[1..])));
|
||||||
return Pipeline::from_vec(vec![Expression {
|
return Pipeline::from_vec(vec![Expression {
|
||||||
expr: Expr::Call(call),
|
expr: Expr::Call(call),
|
||||||
span: span(&spans[1..]),
|
span: span(&spans[1..]),
|
||||||
@ -3504,8 +3504,10 @@ pub fn parse_register(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipe
|
|||||||
let arguments = call
|
let arguments = call
|
||||||
.positional_nth(0)
|
.positional_nth(0)
|
||||||
.map(|expr| {
|
.map(|expr| {
|
||||||
let val = eval_constant(working_set, expr)?;
|
let val =
|
||||||
let filename = value_as_string(val, expr.span)?;
|
eval_constant(working_set, expr).map_err(|err| err.wrap(working_set, call.head))?;
|
||||||
|
let filename =
|
||||||
|
value_as_string(val, expr.span).map_err(|err| err.wrap(working_set, call.head))?;
|
||||||
|
|
||||||
let Some(path) = find_in_dirs(&filename, working_set, &cwd, PLUGIN_DIRS_VAR) else {
|
let Some(path) = find_in_dirs(&filename, working_set, &cwd, PLUGIN_DIRS_VAR) else {
|
||||||
return Err(ParseError::RegisteredFileNotFound(filename, expr.span))
|
return Err(ParseError::RegisteredFileNotFound(filename, expr.span))
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
eval::{eval_constant, value_as_string},
|
|
||||||
lex::{lex, lex_signature},
|
lex::{lex, lex_signature},
|
||||||
lite_parser::{lite_parse, LiteCommand, LiteElement, LitePipeline},
|
lite_parser::{lite_parse, LiteCommand, LiteElement, LitePipeline},
|
||||||
parse_mut,
|
parse_mut,
|
||||||
@ -16,6 +15,7 @@ use nu_protocol::{
|
|||||||
Operator, PathMember, Pattern, Pipeline, PipelineElement, RangeInclusion, RangeOperator,
|
Operator, PathMember, Pattern, Pipeline, PipelineElement, RangeInclusion, RangeOperator,
|
||||||
},
|
},
|
||||||
engine::StateWorkingSet,
|
engine::StateWorkingSet,
|
||||||
|
eval_const::{eval_constant, value_as_string},
|
||||||
span, BlockId, DidYouMean, Flag, ParseError, PositionalArg, Signature, Span, Spanned,
|
span, BlockId, DidYouMean, Flag, ParseError, PositionalArg, Signature, Span, Spanned,
|
||||||
SyntaxShape, Type, Unit, VarId, ENV_VARIABLE_ID, IN_VARIABLE_ID,
|
SyntaxShape, Type, Unit, VarId, ENV_VARIABLE_ID, IN_VARIABLE_ID,
|
||||||
};
|
};
|
||||||
@ -2959,12 +2959,12 @@ pub fn parse_import_pattern(working_set: &mut StateWorkingSet, spans: &[Span]) -
|
|||||||
Ok(val) => match value_as_string(val, head_expr.span) {
|
Ok(val) => match value_as_string(val, head_expr.span) {
|
||||||
Ok(s) => (working_set.find_module(s.as_bytes()), s.into_bytes()),
|
Ok(s) => (working_set.find_module(s.as_bytes()), s.into_bytes()),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err);
|
working_set.error(err.wrap(working_set, span(spans)));
|
||||||
return garbage(span(spans));
|
return garbage(span(spans));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err);
|
working_set.error(err.wrap(working_set, span(spans)));
|
||||||
return garbage(span(spans));
|
return garbage(span(spans));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use crate::{ast::Call, Alias, BlockId, Example, PipelineData, ShellError, Signature};
|
use crate::{ast::Call, Alias, BlockId, Example, PipelineData, ShellError, Signature};
|
||||||
|
|
||||||
use super::{EngineState, Stack};
|
use super::{EngineState, Stack, StateWorkingSet};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum CommandType {
|
pub enum CommandType {
|
||||||
@ -34,6 +34,19 @@ pub trait Command: Send + Sync + CommandClone {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError>;
|
) -> Result<PipelineData, ShellError>;
|
||||||
|
|
||||||
|
/// Used by the parser to run command at parse time
|
||||||
|
///
|
||||||
|
/// If a command has `is_const()` set to true, it must also implement this method.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
Err(ShellError::MissingConstEvalImpl { span: call.head })
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
@ -83,6 +96,11 @@ pub trait Command: Send + Sync + CommandClone {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whether can run in const evaluation in the parser
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
// If command is a block i.e. def blah [] { }, get the block id
|
// If command is a block i.e. def blah [] { }, get the block id
|
||||||
fn get_block_id(&self) -> Option<BlockId> {
|
fn get_block_id(&self) -> Option<BlockId> {
|
||||||
None
|
None
|
||||||
|
@ -19,7 +19,7 @@ use std::sync::{
|
|||||||
Arc, Mutex,
|
Arc, Mutex,
|
||||||
};
|
};
|
||||||
|
|
||||||
static PWD_ENV: &str = "PWD";
|
pub static PWD_ENV: &str = "PWD";
|
||||||
|
|
||||||
/// Organizes usage messages for various primitives
|
/// Organizes usage messages for various primitives
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -1090,6 +1090,10 @@ impl<'a> StateWorkingSet<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn permanent(&self) -> &EngineState {
|
||||||
|
self.permanent_state
|
||||||
|
}
|
||||||
|
|
||||||
pub fn error(&mut self, parse_error: ParseError) {
|
pub fn error(&mut self, parse_error: ParseError) {
|
||||||
self.parse_errors.push(parse_error)
|
self.parse_errors.push(parse_error)
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,70 @@
|
|||||||
use nu_protocol::{
|
use crate::{
|
||||||
ast::{Expr, Expression},
|
ast::{Block, Call, Expr, Expression, PipelineElement},
|
||||||
engine::StateWorkingSet,
|
engine::StateWorkingSet,
|
||||||
ParseError, Record, Span, Value,
|
PipelineData, Record, ShellError, Span, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn eval_const_call(
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let decl = working_set.get_decl(call.decl_id);
|
||||||
|
|
||||||
|
if !decl.is_const() {
|
||||||
|
return Err(ShellError::NotAConstCommand(call.head));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !decl.is_known_external() && call.named_iter().any(|(flag, _, _)| flag.item == "help") {
|
||||||
|
// It would require re-implementing get_full_help() for const evaluation. Assuming that
|
||||||
|
// getting help messages at parse-time is rare enough, we can simply disallow it.
|
||||||
|
return Err(ShellError::NotAConstHelp(call.head));
|
||||||
|
}
|
||||||
|
|
||||||
|
decl.run_const(working_set, call, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_const_subexpression(
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
expr: &Expression,
|
||||||
|
block: &Block,
|
||||||
|
mut input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
for pipeline in block.pipelines.iter() {
|
||||||
|
for element in pipeline.elements.iter() {
|
||||||
|
let PipelineElement::Expression(_, expr) = element else {
|
||||||
|
return Err(ShellError::NotAConstant(expr.span));
|
||||||
|
};
|
||||||
|
|
||||||
|
input = eval_constant_with_input(working_set, expr, input)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_constant_with_input(
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
expr: &Expression,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
match &expr.expr {
|
||||||
|
Expr::Call(call) => eval_const_call(working_set, call, input),
|
||||||
|
Expr::Subexpression(block_id) => {
|
||||||
|
let block = working_set.get_block(*block_id);
|
||||||
|
eval_const_subexpression(working_set, expr, block, input)
|
||||||
|
}
|
||||||
|
_ => eval_constant(working_set, expr).map(|v| PipelineData::Value(v, None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Evaluate a constant value at parse time
|
/// Evaluate a constant value at parse time
|
||||||
///
|
///
|
||||||
/// Based off eval_expression() in the engine
|
/// Based off eval_expression() in the engine
|
||||||
pub fn eval_constant(
|
pub fn eval_constant(
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
expr: &Expression,
|
expr: &Expression,
|
||||||
) -> Result<Value, ParseError> {
|
) -> Result<Value, ShellError> {
|
||||||
match &expr.expr {
|
match &expr.expr {
|
||||||
Expr::Bool(b) => Ok(Value::bool(*b, expr.span)),
|
Expr::Bool(b) => Ok(Value::bool(*b, expr.span)),
|
||||||
Expr::Int(i) => Ok(Value::int(*i, expr.span)),
|
Expr::Int(i) => Ok(Value::int(*i, expr.span)),
|
||||||
@ -25,7 +79,7 @@ pub fn eval_constant(
|
|||||||
}),
|
}),
|
||||||
Expr::Var(var_id) => match working_set.get_variable(*var_id).const_val.as_ref() {
|
Expr::Var(var_id) => match working_set.get_variable(*var_id).const_val.as_ref() {
|
||||||
Some(val) => Ok(val.clone()),
|
Some(val) => Ok(val.clone()),
|
||||||
None => Err(ParseError::NotAConstant(expr.span)),
|
None => Err(ShellError::NotAConstant(expr.span)),
|
||||||
},
|
},
|
||||||
Expr::CellPath(cell_path) => Ok(Value::CellPath {
|
Expr::CellPath(cell_path) => Ok(Value::CellPath {
|
||||||
val: cell_path.clone(),
|
val: cell_path.clone(),
|
||||||
@ -37,10 +91,12 @@ pub fn eval_constant(
|
|||||||
match value.follow_cell_path(&cell_path.tail, false) {
|
match value.follow_cell_path(&cell_path.tail, false) {
|
||||||
Ok(val) => Ok(val),
|
Ok(val) => Ok(val),
|
||||||
// TODO: Better error conversion
|
// TODO: Better error conversion
|
||||||
Err(shell_error) => Err(ParseError::LabeledError(
|
Err(shell_error) => Err(ShellError::GenericError(
|
||||||
"Error when following cell path".to_string(),
|
"Error when following cell path".to_string(),
|
||||||
format!("{shell_error:?}"),
|
format!("{shell_error:?}"),
|
||||||
expr.span,
|
Some(expr.span),
|
||||||
|
None,
|
||||||
|
vec![],
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,25 +168,29 @@ pub fn eval_constant(
|
|||||||
Expr::Nothing => Ok(Value::Nothing { span: expr.span }),
|
Expr::Nothing => Ok(Value::Nothing { span: expr.span }),
|
||||||
Expr::ValueWithUnit(expr, unit) => {
|
Expr::ValueWithUnit(expr, unit) => {
|
||||||
if let Ok(Value::Int { val, .. }) = eval_constant(working_set, expr) {
|
if let Ok(Value::Int { val, .. }) = eval_constant(working_set, expr) {
|
||||||
unit.item.to_value(val, unit.span).map_err(|_| {
|
unit.item.to_value(val, unit.span)
|
||||||
ParseError::InvalidLiteral(
|
|
||||||
"literal can not fit in unit".into(),
|
|
||||||
"literal can not fit in unit".into(),
|
|
||||||
unit.span,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
Err(ParseError::NotAConstant(expr.span))
|
Err(ShellError::NotAConstant(expr.span))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Err(ParseError::NotAConstant(expr.span)),
|
Expr::Call(call) => {
|
||||||
|
Ok(eval_const_call(working_set, call, PipelineData::empty())?.into_value(expr.span))
|
||||||
|
}
|
||||||
|
Expr::Subexpression(block_id) => {
|
||||||
|
let block = working_set.get_block(*block_id);
|
||||||
|
Ok(
|
||||||
|
eval_const_subexpression(working_set, expr, block, PipelineData::empty())?
|
||||||
|
.into_value(expr.span),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::NotAConstant(expr.span)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value as a string
|
/// Get the value as a string
|
||||||
pub fn value_as_string(value: Value, span: Span) -> Result<String, ParseError> {
|
pub fn value_as_string(value: Value, span: Span) -> Result<String, ShellError> {
|
||||||
match value {
|
match value {
|
||||||
Value::String { val, .. } => Ok(val),
|
Value::String { val, .. } => Ok(val),
|
||||||
_ => Err(ParseError::NotAConstant(span)),
|
_ => Err(ShellError::NotAConstant(span)),
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,6 +4,7 @@ pub mod cli_error;
|
|||||||
pub mod config;
|
pub mod config;
|
||||||
mod did_you_mean;
|
mod did_you_mean;
|
||||||
pub mod engine;
|
pub mod engine;
|
||||||
|
pub mod eval_const;
|
||||||
mod example;
|
mod example;
|
||||||
mod exportable;
|
mod exportable;
|
||||||
mod id;
|
mod id;
|
||||||
|
@ -449,18 +449,6 @@ pub enum ParseError {
|
|||||||
#[diagnostic(code(nu::shell::error_reading_file))]
|
#[diagnostic(code(nu::shell::error_reading_file))]
|
||||||
ReadingFile(String, #[label("{0}")] Span),
|
ReadingFile(String, #[label("{0}")] Span),
|
||||||
|
|
||||||
/// Tried assigning non-constant value to a constant
|
|
||||||
///
|
|
||||||
/// ## Resolution
|
|
||||||
///
|
|
||||||
/// Only a subset of expressions are allowed to be assigned as a constant during parsing.
|
|
||||||
#[error("Not a constant.")]
|
|
||||||
#[diagnostic(
|
|
||||||
code(nu::parser::not_a_constant),
|
|
||||||
help("Only a subset of expressions are allowed constants during parsing. Try using the 'const' command or typing the value literally.")
|
|
||||||
)]
|
|
||||||
NotAConstant(#[label = "Value is not a parse-time constant"] Span),
|
|
||||||
|
|
||||||
#[error("Invalid literal")] // <problem> in <entity>.
|
#[error("Invalid literal")] // <problem> in <entity>.
|
||||||
#[diagnostic()]
|
#[diagnostic()]
|
||||||
InvalidLiteral(String, String, #[label("{0} in {1}")] Span),
|
InvalidLiteral(String, String, #[label("{0} in {1}")] Span),
|
||||||
@ -561,7 +549,6 @@ impl ParseError {
|
|||||||
ParseError::ShellOutErrRedirect(s) => *s,
|
ParseError::ShellOutErrRedirect(s) => *s,
|
||||||
ParseError::UnknownOperator(_, _, s) => *s,
|
ParseError::UnknownOperator(_, _, s) => *s,
|
||||||
ParseError::InvalidLiteral(_, _, s) => *s,
|
ParseError::InvalidLiteral(_, _, s) => *s,
|
||||||
ParseError::NotAConstant(s) => *s,
|
|
||||||
ParseError::LabeledErrorWithHelp { span: s, .. } => *s,
|
ParseError::LabeledErrorWithHelp { span: s, .. } => *s,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use miette::Diagnostic;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{ast::Operator, Span, Value};
|
use crate::{ast::Operator, engine::StateWorkingSet, format_error, ParseError, Span, Value};
|
||||||
|
|
||||||
/// The fundamental error type for the evaluation engine. These cases represent different kinds of errors
|
/// The fundamental error type for the evaluation engine. These cases represent different kinds of errors
|
||||||
/// the evaluator might face, along with helpful spans to label. An error renderer will take this error value
|
/// the evaluator might face, along with helpful spans to label. An error renderer will take this error value
|
||||||
@ -1074,6 +1074,72 @@ pub enum ShellError {
|
|||||||
#[label("not a boolean expression")]
|
#[label("not a boolean expression")]
|
||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// An attempt to run a command marked for constant evaluation lacking the const. eval.
|
||||||
|
/// implementation.
|
||||||
|
///
|
||||||
|
/// This is an internal Nushell error, please file an issue.
|
||||||
|
#[error("Missing const eval implementation")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(nu::shell::missing_const_eval_implementation),
|
||||||
|
help(
|
||||||
|
"The command lacks an implementation for constant evaluation. \
|
||||||
|
This is an internal Nushell error, please file an issue https://github.com/nushell/nushell/issues."
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
MissingConstEvalImpl {
|
||||||
|
#[label("command lacks constant implementation")]
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Tried assigning non-constant value to a constant
|
||||||
|
///
|
||||||
|
/// ## Resolution
|
||||||
|
///
|
||||||
|
/// Only a subset of expressions are allowed to be assigned as a constant during parsing.
|
||||||
|
#[error("Not a constant.")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(nu::shell::not_a_constant),
|
||||||
|
help("Only a subset of expressions are allowed constants during parsing. Try using the 'const' command or typing the value literally.")
|
||||||
|
)]
|
||||||
|
NotAConstant(#[label = "Value is not a parse-time constant"] Span),
|
||||||
|
|
||||||
|
/// Tried running a command that is not const-compatible
|
||||||
|
///
|
||||||
|
/// ## Resolution
|
||||||
|
///
|
||||||
|
/// Only a subset of builtin commands, and custom commands built only from those commands, can
|
||||||
|
/// run at parse time.
|
||||||
|
#[error("Not a const command.")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(nu::shell::not_a_const_command),
|
||||||
|
help("Only a subset of builtin commands, and custom commands built only from those commands, can run at parse time.")
|
||||||
|
)]
|
||||||
|
NotAConstCommand(#[label = "This command cannot run at parse time."] Span),
|
||||||
|
|
||||||
|
/// Tried getting a help message at parse time.
|
||||||
|
///
|
||||||
|
/// ## Resolution
|
||||||
|
///
|
||||||
|
/// Help messages are not supported at parse time.
|
||||||
|
#[error("Help message not a constant.")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(nu::shell::not_a_const_help),
|
||||||
|
help("Help messages are currently not supported to be constants.")
|
||||||
|
)]
|
||||||
|
NotAConstHelp(#[label = "Cannot get help message at parse time."] Span),
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement as From trait
|
||||||
|
impl ShellError {
|
||||||
|
pub fn wrap(self, working_set: &StateWorkingSet, span: Span) -> ParseError {
|
||||||
|
let msg = format_error(working_set, &self);
|
||||||
|
ParseError::LabeledError(
|
||||||
|
msg,
|
||||||
|
"Encountered error during parse-time evaluation".into(),
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::io::Error> for ShellError {
|
impl From<std::io::Error> for ShellError {
|
||||||
|
@ -352,17 +352,14 @@ fn default_value_constant2() -> TestResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_value_not_constant1() -> TestResult {
|
fn default_value_constant3() -> TestResult {
|
||||||
fail_test(
|
run_test(r#"def foo [x = ("foo" | str length)] { $x }; foo"#, "3")
|
||||||
r#"def foo [x = ("foo" | str length)] { $x }; foo"#,
|
|
||||||
"expected a constant",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_value_not_constant2() -> TestResult {
|
fn default_value_not_constant2() -> TestResult {
|
||||||
fail_test(
|
fail_test(
|
||||||
r#"def foo [--x = ("foo" | str length)] { $x }; foo"#,
|
r#"def foo [x = (loop { break })] { $x }; foo"#,
|
||||||
"expected a constant",
|
"expected a constant",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -108,12 +108,30 @@ fn const_nothing() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn const_unsupported() {
|
fn const_subexpression_supported() {
|
||||||
let inp = &["const x = ('abc' | str length)"];
|
let inp = &["const x = ('spam')", "$x"];
|
||||||
|
|
||||||
let actual = nu!(&inp.join("; "));
|
let actual = nu!(&inp.join("; "));
|
||||||
|
|
||||||
assert!(actual.err.contains("not_a_constant"));
|
assert_eq!(actual.out, "spam");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_command_supported() {
|
||||||
|
let inp = &["const x = ('spam' | str length)", "$x"];
|
||||||
|
|
||||||
|
let actual = nu!(&inp.join("; "));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "4");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_command_unsupported() {
|
||||||
|
let inp = &["const x = (loop { break })"];
|
||||||
|
|
||||||
|
let actual = nu!(&inp.join("; "));
|
||||||
|
|
||||||
|
assert!(actual.err.contains("not_a_const_command"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -125,6 +143,12 @@ fn const_in_scope() {
|
|||||||
assert_eq!(actual.out, "x");
|
assert_eq!(actual.out, "x");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn not_a_const_help() {
|
||||||
|
let actual = nu!("const x = ('abc' | str length -h)");
|
||||||
|
assert!(actual.err.contains("not_a_const_help"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn complex_const_export() {
|
fn complex_const_export() {
|
||||||
let inp = &[MODULE_SETUP, "use spam", "$spam.X"];
|
let inp = &[MODULE_SETUP, "use spam", "$spam.X"];
|
||||||
@ -250,3 +274,22 @@ fn complex_const_overlay_use_hide() {
|
|||||||
let actual = nu!(&inp.join("; "));
|
let actual = nu!(&inp.join("; "));
|
||||||
assert!(actual.err.contains("nu::parser::variable_not_found"));
|
assert!(actual.err.contains("nu::parser::variable_not_found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const implementations of commands without dedicated tests
|
||||||
|
#[test]
|
||||||
|
fn describe_const() {
|
||||||
|
let actual = nu!("const x = ('abc' | describe); $x");
|
||||||
|
assert_eq!(actual.out, "string");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ignore_const() {
|
||||||
|
let actual = nu!("const x = (echo spam | ignore); $x == null");
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_const() {
|
||||||
|
let actual = nu!("const x = (version); $x");
|
||||||
|
assert!(actual.err.is_empty());
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user