mirror of
https://github.com/nushell/nushell.git
synced 2024-11-24 17:34:00 +01:00
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::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||
};
|
||||
@ -27,6 +27,10 @@ impl Command for Describe {
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
@ -34,39 +38,16 @@ impl Command for Describe {
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
run(call, input)
|
||||
}
|
||||
|
||||
let no_collect: bool = call.has_flag("no-collect");
|
||||
|
||||
let description = match input {
|
||||
PipelineData::ExternalStream { .. } => "raw input".into(),
|
||||
PipelineData::ListStream(_, _) => {
|
||||
if no_collect {
|
||||
"stream".into()
|
||||
} else {
|
||||
let value = input.into_value(head);
|
||||
let base_description = match value {
|
||||
Value::CustomValue { val, .. } => val.value_string(),
|
||||
_ => value.get_type().to_string(),
|
||||
};
|
||||
|
||||
format!("{base_description} (stream)")
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let value = input.into_value(head);
|
||||
match value {
|
||||
Value::CustomValue { val, .. } => val.value_string(),
|
||||
_ => value.get_type().to_string(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Value::String {
|
||||
val: description,
|
||||
span: head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
fn run_const(
|
||||
&self,
|
||||
_working_set: &StateWorkingSet,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
run(call, input)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -96,6 +77,42 @@ impl Command for Describe {
|
||||
}
|
||||
}
|
||||
|
||||
fn run(call: &Call, input: PipelineData) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
|
||||
let no_collect: bool = call.has_flag("no-collect");
|
||||
|
||||
let description = match input {
|
||||
PipelineData::ExternalStream { .. } => "raw input".into(),
|
||||
PipelineData::ListStream(_, _) => {
|
||||
if no_collect {
|
||||
"stream".into()
|
||||
} else {
|
||||
let value = input.into_value(head);
|
||||
let base_description = match value {
|
||||
Value::CustomValue { val, .. } => val.value_string(),
|
||||
_ => value.get_type().to_string(),
|
||||
};
|
||||
|
||||
format!("{base_description} (stream)")
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let value = input.into_value(head);
|
||||
match value {
|
||||
Value::CustomValue { val, .. } => val.value_string(),
|
||||
_ => value.get_type().to_string(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Value::String {
|
||||
val: description,
|
||||
span: head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
|
@ -1,6 +1,6 @@
|
||||
use nu_engine::CallExt;
|
||||
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, ListStream, PipelineData, ShellError, Signature, Span, SyntaxShape, Type,
|
||||
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."#
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
@ -38,22 +42,18 @@ little reason to use this over just writing the values as-is."#
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
call.rest(engine_state, stack, 0).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,
|
||||
),
|
||||
let args = call.rest(engine_state, stack, 0);
|
||||
run(engine_state, args, call)
|
||||
}
|
||||
|
||||
// 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> {
|
||||
@ -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)]
|
||||
mod test {
|
||||
#[test]
|
||||
|
@ -1,5 +1,5 @@
|
||||
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};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -24,6 +24,10 @@ impl Command for Ignore {
|
||||
vec!["silent", "quiet", "out-null"]
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
@ -35,6 +39,16 @@ impl Command for Ignore {
|
||||
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> {
|
||||
vec![Example {
|
||||
description: "Ignore the output of an echo command",
|
||||
|
@ -1,5 +1,5 @@
|
||||
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, IntoPipelineData, PipelineData, Record, ShellError, Signature, Type, Value,
|
||||
};
|
||||
@ -26,14 +26,27 @@ impl Command for Version {
|
||||
"Display Nu version, and its build configuration."
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
_input: PipelineData,
|
||||
) -> 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> {
|
||||
@ -45,12 +58,7 @@ impl Command for Version {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn version(
|
||||
engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
pub fn version(engine_state: &EngineState, call: &Call) -> Result<PipelineData, ShellError> {
|
||||
// Pre-allocate the arrays in the worst case (12 items):
|
||||
// - version
|
||||
// - branch
|
||||
|
@ -3,7 +3,7 @@ use std::path::Path;
|
||||
use super::PathSubcommandArguments;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||
use nu_protocol::{
|
||||
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Spanned,
|
||||
SyntaxShape, Type, Value,
|
||||
@ -45,6 +45,10 @@ impl Command for SubCommand {
|
||||
"Get the final component of a path."
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
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)]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
|
@ -2,7 +2,7 @@ use std::path::Path;
|
||||
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||
use nu_protocol::{
|
||||
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Spanned,
|
||||
SyntaxShape, Type, Value,
|
||||
@ -53,6 +53,10 @@ impl Command for SubCommand {
|
||||
"Get the parent directory of a path."
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
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)]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
|
@ -1,9 +1,9 @@
|
||||
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_protocol::ast::Call;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||
use nu_protocol::{
|
||||
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`."#
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
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)]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
|
@ -1,9 +1,9 @@
|
||||
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_protocol::ast::Call;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||
use nu_protocol::{
|
||||
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."
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
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)]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
|
@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||
use nu_protocol::{
|
||||
engine::Command, Category, Example, PipelineData, Record, ShellError, Signature, Span, Spanned,
|
||||
SyntaxShape, Type, Value,
|
||||
@ -46,6 +46,10 @@ impl Command for SubCommand {
|
||||
the output of 'path parse' and 'path split' subcommands."#
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
@ -53,29 +57,24 @@ the output of 'path parse' and 'path split' subcommands."#
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let args = Arguments {
|
||||
append: call.rest(engine_state, stack, 0)?,
|
||||
};
|
||||
|
||||
let metadata = input.metadata();
|
||||
run(call, &args, input)
|
||||
}
|
||||
|
||||
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 run_const(
|
||||
&self,
|
||||
working_set: &StateWorkingSet,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let args = Arguments {
|
||||
append: call.rest_const(working_set, 0)?,
|
||||
};
|
||||
|
||||
run(call, &args, input)
|
||||
}
|
||||
|
||||
#[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 {
|
||||
match v {
|
||||
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_protocol::ast::Call;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||
use nu_protocol::{
|
||||
engine::Command, Category, Example, PipelineData, Record, ShellError, Signature, Span, Spanned,
|
||||
SyntaxShape, Type, Value,
|
||||
@ -48,6 +48,10 @@ impl Command for SubCommand {
|
||||
On Windows, an extra 'prefix' column is added."#
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
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)]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
|
@ -3,7 +3,7 @@ use std::path::Path;
|
||||
use nu_engine::CallExt;
|
||||
use nu_path::expand_to_real_path;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||
use nu_protocol::{
|
||||
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Spanned,
|
||||
SyntaxShape, Type, Value,
|
||||
@ -52,6 +52,10 @@ absolute or both relative. The argument path needs to be a parent of the input
|
||||
path."#
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
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)]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::path::{Component, Path};
|
||||
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||
use nu_protocol::{
|
||||
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."
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
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)]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
|
@ -2,7 +2,7 @@ use std::path::Path;
|
||||
|
||||
use nu_path::expand_tilde;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||
use nu_protocol::{
|
||||
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."#
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
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> {
|
||||
vec![
|
||||
Example {
|
||||
|
@ -3,7 +3,7 @@ use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
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::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
@ -62,6 +62,10 @@ impl Command for SubCommand {
|
||||
vec!["size", "count"]
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
@ -70,11 +74,17 @@ impl Command for SubCommand {
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
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())
|
||||
run(cell_paths, engine_state, call, input)
|
||||
}
|
||||
|
||||
fn run_const(
|
||||
&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> {
|
||||
@ -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 {
|
||||
match input {
|
||||
Value::String { val, .. } => Value::int(
|
||||
|
@ -34,3 +34,9 @@ fn echo_range_handles_exclusive_down() {
|
||||
|
||||
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"]);
|
||||
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"]);
|
||||
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");
|
||||
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)]
|
||||
mod windows {
|
||||
use super::*;
|
||||
|
@ -54,3 +54,10 @@ fn returns_joined_path_when_joining_empty_path() {
|
||||
|
||||
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 type_;
|
||||
|
||||
use nu_test_support::{nu, pipeline};
|
||||
use std::path::MAIN_SEPARATOR;
|
||||
|
||||
/// 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");
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
#[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");
|
||||
}
|
||||
|
||||
#[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");
|
||||
})
|
||||
}
|
||||
|
||||
#[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::{
|
||||
ast::Call,
|
||||
engine::{EngineState, Stack},
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
eval_const::eval_constant,
|
||||
FromValue, ShellError,
|
||||
};
|
||||
|
||||
@ -14,6 +15,12 @@ pub trait CallExt {
|
||||
name: &str,
|
||||
) -> Result<Option<T>, ShellError>;
|
||||
|
||||
fn get_flag_const<T: FromValue>(
|
||||
&self,
|
||||
working_set: &StateWorkingSet,
|
||||
name: &str,
|
||||
) -> Result<Option<T>, ShellError>;
|
||||
|
||||
fn rest<T: FromValue>(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
@ -21,6 +28,12 @@ pub trait CallExt {
|
||||
starting_pos: usize,
|
||||
) -> Result<Vec<T>, ShellError>;
|
||||
|
||||
fn rest_const<T: FromValue>(
|
||||
&self,
|
||||
working_set: &StateWorkingSet,
|
||||
starting_pos: usize,
|
||||
) -> Result<Vec<T>, ShellError>;
|
||||
|
||||
fn opt<T: FromValue>(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
@ -35,6 +48,12 @@ pub trait CallExt {
|
||||
pos: usize,
|
||||
) -> Result<T, ShellError>;
|
||||
|
||||
fn req_const<T: FromValue>(
|
||||
&self,
|
||||
working_set: &StateWorkingSet,
|
||||
pos: usize,
|
||||
) -> Result<T, ShellError>;
|
||||
|
||||
fn req_parser_info<T: FromValue>(
|
||||
&self,
|
||||
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>(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
@ -74,6 +106,21 @@ impl CallExt for Call {
|
||||
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>(
|
||||
&self,
|
||||
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>(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
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_path::canonicalize_with;
|
||||
@ -159,8 +159,9 @@ pub fn env_to_strings(
|
||||
|
||||
/// Shorthand for env_to_string() for PWD with custom error
|
||||
pub fn current_dir_str(engine_state: &EngineState, stack: &Stack) -> Result<String, ShellError> {
|
||||
if let Some(pwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
match env_to_string("PWD", &pwd, engine_state, stack) {
|
||||
if let Some(pwd) = stack.get_env_var(engine_state, PWD_ENV) {
|
||||
// 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) => {
|
||||
if Path::new(&cwd).is_absolute() {
|
||||
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
|
||||
pub fn current_dir(engine_state: &EngineState, stack: &Stack) -> Result<PathBuf, ShellError> {
|
||||
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
|
||||
///
|
||||
/// On non-Windows: It will fetch PATH
|
||||
|
@ -1,5 +1,4 @@
|
||||
mod deparse;
|
||||
mod eval;
|
||||
mod flatten;
|
||||
mod known_external;
|
||||
mod lex;
|
||||
|
@ -12,6 +12,7 @@ use nu_protocol::{
|
||||
ImportPatternMember, Pipeline, PipelineElement,
|
||||
},
|
||||
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
|
||||
eval_const::{eval_constant, value_as_string},
|
||||
span, Alias, BlockId, Exportable, Module, ModuleId, ParseError, PositionalArg,
|
||||
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";
|
||||
|
||||
use crate::{
|
||||
eval::{eval_constant, value_as_string},
|
||||
is_math_expression_like,
|
||||
known_external::KnownExternal,
|
||||
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(s) => (s, expr.span),
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, call_span));
|
||||
return garbage_pipeline(&[call_span]);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, 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(s) => (s, expr.span),
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, call_span));
|
||||
return garbage_pipeline(&[call_span]);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, 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,
|
||||
}),
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, call_span));
|
||||
return garbage_pipeline(&[call_span]);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, 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(s) => (s, expr.span),
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, call_span));
|
||||
return garbage_pipeline(&[call_span]);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, 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
|
||||
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) {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, span(&spans[1..])));
|
||||
return Pipeline::from_vec(vec![Expression {
|
||||
expr: Expr::Call(call),
|
||||
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]) {
|
||||
Ok(s) => s,
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, span(&spans[1..])));
|
||||
return Pipeline::from_vec(vec![Expression {
|
||||
expr: Expr::Call(call),
|
||||
span: span(&spans[1..]),
|
||||
@ -3504,8 +3504,10 @@ pub fn parse_register(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipe
|
||||
let arguments = call
|
||||
.positional_nth(0)
|
||||
.map(|expr| {
|
||||
let val = eval_constant(working_set, expr)?;
|
||||
let filename = value_as_string(val, expr.span)?;
|
||||
let val =
|
||||
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 {
|
||||
return Err(ParseError::RegisteredFileNotFound(filename, expr.span))
|
||||
|
@ -1,5 +1,4 @@
|
||||
use crate::{
|
||||
eval::{eval_constant, value_as_string},
|
||||
lex::{lex, lex_signature},
|
||||
lite_parser::{lite_parse, LiteCommand, LiteElement, LitePipeline},
|
||||
parse_mut,
|
||||
@ -16,6 +15,7 @@ use nu_protocol::{
|
||||
Operator, PathMember, Pattern, Pipeline, PipelineElement, RangeInclusion, RangeOperator,
|
||||
},
|
||||
engine::StateWorkingSet,
|
||||
eval_const::{eval_constant, value_as_string},
|
||||
span, BlockId, DidYouMean, Flag, ParseError, PositionalArg, Signature, Span, Spanned,
|
||||
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(s) => (working_set.find_module(s.as_bytes()), s.into_bytes()),
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, span(spans)));
|
||||
return garbage(span(spans));
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, 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 super::{EngineState, Stack};
|
||||
use super::{EngineState, Stack, StateWorkingSet};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CommandType {
|
||||
@ -34,6 +34,19 @@ pub trait Command: Send + Sync + CommandClone {
|
||||
input: PipelineData,
|
||||
) -> 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> {
|
||||
Vec::new()
|
||||
}
|
||||
@ -83,6 +96,11 @@ pub trait Command: Send + Sync + CommandClone {
|
||||
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
|
||||
fn get_block_id(&self) -> Option<BlockId> {
|
||||
None
|
||||
|
@ -19,7 +19,7 @@ use std::sync::{
|
||||
Arc, Mutex,
|
||||
};
|
||||
|
||||
static PWD_ENV: &str = "PWD";
|
||||
pub static PWD_ENV: &str = "PWD";
|
||||
|
||||
/// Organizes usage messages for various primitives
|
||||
#[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) {
|
||||
self.parse_errors.push(parse_error)
|
||||
}
|
||||
|
@ -1,16 +1,70 @@
|
||||
use nu_protocol::{
|
||||
ast::{Expr, Expression},
|
||||
use crate::{
|
||||
ast::{Block, Call, Expr, Expression, PipelineElement},
|
||||
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
|
||||
///
|
||||
/// Based off eval_expression() in the engine
|
||||
pub fn eval_constant(
|
||||
working_set: &StateWorkingSet,
|
||||
expr: &Expression,
|
||||
) -> Result<Value, ParseError> {
|
||||
) -> Result<Value, ShellError> {
|
||||
match &expr.expr {
|
||||
Expr::Bool(b) => Ok(Value::bool(*b, 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() {
|
||||
Some(val) => Ok(val.clone()),
|
||||
None => Err(ParseError::NotAConstant(expr.span)),
|
||||
None => Err(ShellError::NotAConstant(expr.span)),
|
||||
},
|
||||
Expr::CellPath(cell_path) => Ok(Value::CellPath {
|
||||
val: cell_path.clone(),
|
||||
@ -37,10 +91,12 @@ pub fn eval_constant(
|
||||
match value.follow_cell_path(&cell_path.tail, false) {
|
||||
Ok(val) => Ok(val),
|
||||
// TODO: Better error conversion
|
||||
Err(shell_error) => Err(ParseError::LabeledError(
|
||||
Err(shell_error) => Err(ShellError::GenericError(
|
||||
"Error when following cell path".to_string(),
|
||||
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::ValueWithUnit(expr, unit) => {
|
||||
if let Ok(Value::Int { val, .. }) = eval_constant(working_set, expr) {
|
||||
unit.item.to_value(val, unit.span).map_err(|_| {
|
||||
ParseError::InvalidLiteral(
|
||||
"literal can not fit in unit".into(),
|
||||
"literal can not fit in unit".into(),
|
||||
unit.span,
|
||||
)
|
||||
})
|
||||
unit.item.to_value(val, unit.span)
|
||||
} 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
|
||||
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 {
|
||||
Value::String { val, .. } => Ok(val),
|
||||
_ => Err(ParseError::NotAConstant(span)),
|
||||
_ => Err(ShellError::NotAConstant(span)),
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ pub mod cli_error;
|
||||
pub mod config;
|
||||
mod did_you_mean;
|
||||
pub mod engine;
|
||||
pub mod eval_const;
|
||||
mod example;
|
||||
mod exportable;
|
||||
mod id;
|
||||
|
@ -449,18 +449,6 @@ pub enum ParseError {
|
||||
#[diagnostic(code(nu::shell::error_reading_file))]
|
||||
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>.
|
||||
#[diagnostic()]
|
||||
InvalidLiteral(String, String, #[label("{0} in {1}")] Span),
|
||||
@ -561,7 +549,6 @@ impl ParseError {
|
||||
ParseError::ShellOutErrRedirect(s) => *s,
|
||||
ParseError::UnknownOperator(_, _, s) => *s,
|
||||
ParseError::InvalidLiteral(_, _, s) => *s,
|
||||
ParseError::NotAConstant(s) => *s,
|
||||
ParseError::LabeledErrorWithHelp { span: s, .. } => *s,
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use miette::Diagnostic;
|
||||
use serde::{Deserialize, Serialize};
|
||||
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 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")]
|
||||
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 {
|
||||
|
@ -75,7 +75,7 @@ fn scope_variable() -> TestResult {
|
||||
fn scope_command_defaults(#[case] var: &str, #[case] exp_result: &str) -> TestResult {
|
||||
run_test(
|
||||
&format!(
|
||||
r#"def t1 [a:int b?:float=1.23 --flag1:string --flag2:float=4.56] {{ true }};
|
||||
r#"def t1 [a:int b?:float=1.23 --flag1:string --flag2:float=4.56] {{ true }};
|
||||
let rslt = (scope commands | where name == 't1' | get signatures.0.any | where parameter_name == '{var}' | get parameter_default.0);
|
||||
$"<($rslt)> ($rslt | describe)""#
|
||||
),
|
||||
@ -352,17 +352,14 @@ fn default_value_constant2() -> TestResult {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_value_not_constant1() -> TestResult {
|
||||
fail_test(
|
||||
r#"def foo [x = ("foo" | str length)] { $x }; foo"#,
|
||||
"expected a constant",
|
||||
)
|
||||
fn default_value_constant3() -> TestResult {
|
||||
run_test(r#"def foo [x = ("foo" | str length)] { $x }; foo"#, "3")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_value_not_constant2() -> TestResult {
|
||||
fail_test(
|
||||
r#"def foo [--x = ("foo" | str length)] { $x }; foo"#,
|
||||
r#"def foo [x = (loop { break })] { $x }; foo"#,
|
||||
"expected a constant",
|
||||
)
|
||||
}
|
||||
|
@ -108,12 +108,30 @@ fn const_nothing() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_unsupported() {
|
||||
let inp = &["const x = ('abc' | str length)"];
|
||||
fn const_subexpression_supported() {
|
||||
let inp = &["const x = ('spam')", "$x"];
|
||||
|
||||
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]
|
||||
@ -125,6 +143,12 @@ fn const_in_scope() {
|
||||
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]
|
||||
fn complex_const_export() {
|
||||
let inp = &[MODULE_SETUP, "use spam", "$spam.X"];
|
||||
@ -250,3 +274,22 @@ fn complex_const_overlay_use_hide() {
|
||||
let actual = nu!(&inp.join("; "));
|
||||
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