forked from extern/nushell
Fix incorrect handling of boolean flags for builtin commands (#11492)
# Description
Possible fix of #11456
This PR fixes a bug where builtin commands did not respect the logic of
dynamically passed boolean flags. The reason is
[has_flag](6f59abaf43/crates/nu-protocol/src/ast/call.rs (L204C5-L212C6)
)
method did not evaluate and take into consideration expression used with
flag.
To address this issue a solution is proposed:
1. `has_flag` method is moved to `CallExt` and new logic to evaluate
expression and check if it is a boolean value is added
2. `has_flag_const` method is added to `CallExt` which is a constant
version of `has_flag`
3. `has_named` method is added to `Call` which is basically the old
logic of `has_flag`
4. All usages of `has_flag` in code are updated, mostly to pass
`engine_state` and `stack` to new `has_flag`. In `run_const` commands it
is replaced with `has_flag_const`. And in a few select places: parser,
`to nuon` and `into string` old logic via `has_named` is used.
# User-Facing Changes
Explicit values of boolean flags are now respected in builtin commands.
Before:

After:

Another example:
Before:

After:

# Tests + Formatting
Added test reproducing some variants of original issue.
This commit is contained in:
@ -67,7 +67,7 @@ impl Command for Collect {
|
||||
)
|
||||
.map(|x| x.set_metadata(metadata));
|
||||
|
||||
if call.has_flag("keep-env") {
|
||||
if call.has_flag(engine_state, stack, "keep-env")? {
|
||||
redirect_env(engine_state, stack, &stack_captures);
|
||||
// for when we support `data | let x = $in;`
|
||||
// remove the variables added earlier
|
||||
|
@ -1,3 +1,4 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Closure, Command, EngineState, Stack, StateWorkingSet},
|
||||
@ -44,20 +45,30 @@ impl Command for Describe {
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
run(Some(engine_state), call, input)
|
||||
let options = Options {
|
||||
no_collect: call.has_flag(engine_state, stack, "no-collect")?,
|
||||
detailed: call.has_flag(engine_state, stack, "detailed")?,
|
||||
collect_lazyrecords: call.has_flag(engine_state, stack, "collect-lazyrecords")?,
|
||||
};
|
||||
run(Some(engine_state), call, input, options)
|
||||
}
|
||||
|
||||
fn run_const(
|
||||
&self,
|
||||
_working_set: &StateWorkingSet,
|
||||
working_set: &StateWorkingSet,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
run(None, call, input)
|
||||
let options = Options {
|
||||
no_collect: call.has_flag_const(working_set, "no-collect")?,
|
||||
detailed: call.has_flag_const(working_set, "detailed")?,
|
||||
collect_lazyrecords: call.has_flag_const(working_set, "collect-lazyrecords")?,
|
||||
};
|
||||
run(None, call, input, options)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -148,15 +159,21 @@ impl Command for Describe {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Options {
|
||||
no_collect: bool,
|
||||
detailed: bool,
|
||||
collect_lazyrecords: bool,
|
||||
}
|
||||
|
||||
fn run(
|
||||
engine_state: Option<&EngineState>,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
options: Options,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let metadata = input.metadata().clone().map(Box::new);
|
||||
let head = call.head;
|
||||
let no_collect: bool = call.has_flag("no-collect");
|
||||
let detailed = call.has_flag("detailed");
|
||||
|
||||
let description: Value = match input {
|
||||
PipelineData::ExternalStream {
|
||||
@ -165,7 +182,7 @@ fn run(
|
||||
ref exit_code,
|
||||
..
|
||||
} => {
|
||||
if detailed {
|
||||
if options.detailed {
|
||||
Value::record(
|
||||
record!(
|
||||
"type" => Value::string("stream", head),
|
||||
@ -212,23 +229,23 @@ fn run(
|
||||
}
|
||||
}
|
||||
PipelineData::ListStream(_, _) => {
|
||||
if detailed {
|
||||
if options.detailed {
|
||||
Value::record(
|
||||
record!(
|
||||
"type" => Value::string("stream", head),
|
||||
"origin" => Value::string("nushell", head),
|
||||
"subtype" => {
|
||||
if no_collect {
|
||||
if options.no_collect {
|
||||
Value::string("any", head)
|
||||
} else {
|
||||
describe_value(input.into_value(head), head, engine_state, call)?
|
||||
describe_value(input.into_value(head), head, engine_state, call, options)?
|
||||
}
|
||||
},
|
||||
"metadata" => metadata_to_value(metadata, head),
|
||||
),
|
||||
head,
|
||||
)
|
||||
} else if no_collect {
|
||||
} else if options.no_collect {
|
||||
Value::string("stream", head)
|
||||
} else {
|
||||
let value = input.into_value(head);
|
||||
@ -242,13 +259,13 @@ fn run(
|
||||
}
|
||||
_ => {
|
||||
let value = input.into_value(head);
|
||||
if !detailed {
|
||||
if !options.detailed {
|
||||
match value {
|
||||
Value::CustomValue { val, .. } => Value::string(val.value_string(), head),
|
||||
_ => Value::string(value.get_type().to_string(), head),
|
||||
}
|
||||
} else {
|
||||
describe_value(value, head, engine_state, call)?
|
||||
describe_value(value, head, engine_state, call, options)?
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -273,12 +290,13 @@ fn describe_value(
|
||||
head: nu_protocol::Span,
|
||||
engine_state: Option<&EngineState>,
|
||||
call: &Call,
|
||||
options: Options,
|
||||
) -> Result<Value, ShellError> {
|
||||
Ok(match value {
|
||||
Value::CustomValue { val, internal_span } => Value::record(
|
||||
record!(
|
||||
"type" => Value::string("custom", head),
|
||||
"subtype" => run(engine_state,call, val.to_base_value(internal_span)?.into_pipeline_data())?.into_value(head),
|
||||
"subtype" => run(engine_state,call, val.to_base_value(internal_span)?.into_pipeline_data(), options)?.into_value(head),
|
||||
),
|
||||
head,
|
||||
),
|
||||
@ -303,6 +321,7 @@ fn describe_value(
|
||||
head,
|
||||
engine_state,
|
||||
call,
|
||||
options,
|
||||
)?);
|
||||
}
|
||||
|
||||
@ -321,7 +340,7 @@ fn describe_value(
|
||||
"length" => Value::int(vals.len() as i64, head),
|
||||
"values" => Value::list(vals.into_iter().map(|v|
|
||||
Ok(compact_primitive_description(
|
||||
describe_value(v, head, engine_state, call)?
|
||||
describe_value(v, head, engine_state, call, options)?
|
||||
))
|
||||
)
|
||||
.collect::<Result<Vec<Value>, ShellError>>()?, head),
|
||||
@ -381,16 +400,15 @@ fn describe_value(
|
||||
head,
|
||||
),
|
||||
Value::LazyRecord { val, .. } => {
|
||||
let collect_lazyrecords: bool = call.has_flag("collect-lazyrecords");
|
||||
let mut record = Record::new();
|
||||
|
||||
record.push("type", Value::string("record", head));
|
||||
record.push("lazy", Value::bool(true, head));
|
||||
|
||||
if collect_lazyrecords {
|
||||
if options.collect_lazyrecords {
|
||||
let collected = val.collect()?;
|
||||
if let Value::Record { mut val, .. } =
|
||||
describe_value(collected, head, engine_state, call)?
|
||||
describe_value(collected, head, engine_state, call, options)?
|
||||
{
|
||||
record.push("length", Value::int(val.len() as i64, head));
|
||||
for (_k, v) in val.iter_mut() {
|
||||
@ -399,6 +417,7 @@ fn describe_value(
|
||||
head,
|
||||
engine_state,
|
||||
call,
|
||||
options,
|
||||
)?);
|
||||
}
|
||||
|
||||
|
@ -70,11 +70,13 @@ impl Command for Do {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let block: Closure = call.req(engine_state, caller_stack, 0)?;
|
||||
let rest: Vec<Value> = call.rest(engine_state, caller_stack, 1)?;
|
||||
let ignore_all_errors = call.has_flag("ignore-errors");
|
||||
let ignore_shell_errors = ignore_all_errors || call.has_flag("ignore-shell-errors");
|
||||
let ignore_program_errors = ignore_all_errors || call.has_flag("ignore-program-errors");
|
||||
let capture_errors = call.has_flag("capture-errors");
|
||||
let has_env = call.has_flag("env");
|
||||
let ignore_all_errors = call.has_flag(engine_state, caller_stack, "ignore-errors")?;
|
||||
let ignore_shell_errors = ignore_all_errors
|
||||
|| call.has_flag(engine_state, caller_stack, "ignore-shell-errors")?;
|
||||
let ignore_program_errors = ignore_all_errors
|
||||
|| call.has_flag(engine_state, caller_stack, "ignore-program-errors")?;
|
||||
let capture_errors = call.has_flag(engine_state, caller_stack, "capture-errors")?;
|
||||
let has_env = call.has_flag(engine_state, caller_stack, "env")?;
|
||||
|
||||
let mut callee_stack = caller_stack.captures_to_stack(block.captures);
|
||||
let block = engine_state.get_block(block.block_id);
|
||||
|
@ -46,7 +46,7 @@ impl Command for ErrorMake {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let arg: Value = call.req(engine_state, stack, 0)?;
|
||||
|
||||
let throw_span = if call.has_flag("unspanned") {
|
||||
let throw_span = if call.has_flag(engine_state, stack, "unspanned")? {
|
||||
None
|
||||
} else {
|
||||
Some(call.head)
|
||||
|
@ -74,7 +74,7 @@ impl Command for For {
|
||||
|
||||
let block: Block = call.req(engine_state, stack, 2)?;
|
||||
|
||||
let numbered = call.has_flag("numbered");
|
||||
let numbered = call.has_flag(engine_state, stack, "numbered")?;
|
||||
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let engine_state = engine_state.clone();
|
||||
|
@ -42,7 +42,7 @@ impl Command for HideEnv {
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let env_var_names: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||
let ignore_errors = call.has_flag("ignore-errors");
|
||||
let ignore_errors = call.has_flag(engine_state, stack, "ignore-errors")?;
|
||||
|
||||
for name in env_var_names {
|
||||
if !stack.remove_env_var(engine_state, &name.item) && !ignore_errors {
|
||||
|
Reference in New Issue
Block a user