mirror of
https://github.com/nushell/nushell.git
synced 2024-12-23 07:30:13 +01:00
Add "fall-through" signatures (#7527)
Fixes https://github.com/nushell/nushell/issues/4659 Fixes https://github.com/nushell/nushell/issues/5294 Fixes https://github.com/nushell/nushell/issues/6124 fix https://github.com/nushell/nushell/issues/5103
This commit is contained in:
parent
440feaf74a
commit
757d7479af
@ -352,6 +352,7 @@ fn find_matching_block_end_in_expr(
|
||||
let opt_expr = match arg {
|
||||
Argument::Named((_, _, opt_expr)) => opt_expr.as_ref(),
|
||||
Argument::Positional(inner_expr) => Some(inner_expr),
|
||||
Argument::Unknown(inner_expr) => Some(inner_expr),
|
||||
};
|
||||
|
||||
if let Some(inner_expr) = opt_expr {
|
||||
|
@ -34,6 +34,26 @@ fn completer_strings() -> NuCompleter {
|
||||
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn extern_completer() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = r#"
|
||||
def animals [] { [ "cat", "dog", "eel" ] }
|
||||
extern spam [
|
||||
animal: string@animals
|
||||
--foo (-f): string@animals
|
||||
-b: string@animals
|
||||
]
|
||||
"#;
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instantiate a new completer
|
||||
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variables_dollar_sign_with_varialblecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
@ -750,3 +770,45 @@ fn filecompletions_triggers_after_cursor() {
|
||||
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_positional(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam ", 5);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_long_flag_1(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam --foo=", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_long_flag_2(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam --foo ", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_long_flag_short(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam -f ", 8);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_short_flag(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam -b ", 8);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_complete_flags(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam -", 6);
|
||||
let expected: Vec<String> = vec!["--foo".into(), "-b".into(), "-f".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use super::run_external::ExternalCommand;
|
||||
use nu_engine::{current_dir, env_to_strings, CallExt};
|
||||
use super::run_external::create_external_command;
|
||||
use nu_engine::{current_dir, CallExt};
|
||||
use nu_protocol::{
|
||||
ast::{Call, Expr},
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type,
|
||||
};
|
||||
@ -19,11 +19,7 @@ impl Command for Exec {
|
||||
Signature::build("exec")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Any)])
|
||||
.required("command", SyntaxShape::String, "the command to execute")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::String,
|
||||
"any additional arguments for the command",
|
||||
)
|
||||
.allows_unknown_args()
|
||||
.category(Category::System)
|
||||
}
|
||||
|
||||
@ -69,36 +65,22 @@ fn exec(
|
||||
let name: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
let name_span = name.span;
|
||||
|
||||
let args: Vec<Spanned<String>> = call.rest(engine_state, stack, 1)?;
|
||||
let args_expr: Vec<nu_protocol::ast::Expression> =
|
||||
call.positional_iter().skip(1).cloned().collect();
|
||||
let mut arg_keep_raw = vec![];
|
||||
for one_arg_expr in args_expr {
|
||||
match one_arg_expr.expr {
|
||||
// refer to `parse_dollar_expr` function
|
||||
// the expression type of $variable_name, $"($variable_name)"
|
||||
// will be Expr::StringInterpolation, Expr::FullCellPath
|
||||
Expr::StringInterpolation(_) | Expr::FullCellPath(_) => arg_keep_raw.push(true),
|
||||
_ => arg_keep_raw.push(false),
|
||||
}
|
||||
}
|
||||
let redirect_stdout = call.has_flag("redirect-stdout");
|
||||
let redirect_stderr = call.has_flag("redirect-stderr");
|
||||
let trim_end_newline = call.has_flag("trim-end-newline");
|
||||
|
||||
let external_command = create_external_command(
|
||||
engine_state,
|
||||
stack,
|
||||
call,
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
trim_end_newline,
|
||||
)?;
|
||||
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let env_vars = env_to_strings(engine_state, stack)?;
|
||||
let current_dir = current_dir(engine_state, stack)?;
|
||||
|
||||
let external_command = ExternalCommand {
|
||||
name,
|
||||
args,
|
||||
arg_keep_raw,
|
||||
env_vars,
|
||||
redirect_stdout: true,
|
||||
redirect_stderr: false,
|
||||
trim_end_newline: false,
|
||||
};
|
||||
|
||||
let mut command = external_command.spawn_simple_command(&cwd.to_string_lossy())?;
|
||||
command.current_dir(current_dir);
|
||||
command.current_dir(cwd);
|
||||
|
||||
let err = command.exec(); // this replaces our process, should not return
|
||||
|
||||
|
@ -52,70 +52,19 @@ impl Command for External {
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let name: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
let args: Vec<Value> = call.rest(engine_state, stack, 1)?;
|
||||
let redirect_stdout = call.has_flag("redirect-stdout");
|
||||
let redirect_stderr = call.has_flag("redirect-stderr");
|
||||
let trim_end_newline = call.has_flag("trim-end-newline");
|
||||
|
||||
// Translate environment variables from Values to Strings
|
||||
let env_vars_str = env_to_strings(engine_state, stack)?;
|
||||
|
||||
fn value_as_spanned(value: Value) -> Result<Spanned<String>, ShellError> {
|
||||
let span = value.span()?;
|
||||
|
||||
value
|
||||
.as_string()
|
||||
.map(|item| Spanned { item, span })
|
||||
.map_err(|_| {
|
||||
ShellError::ExternalCommand(
|
||||
format!("Cannot convert {} to a string", value.get_type()),
|
||||
"All arguments to an external command need to be string-compatible".into(),
|
||||
span,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
let mut spanned_args = vec![];
|
||||
let args_expr: Vec<Expression> = call.positional_iter().skip(1).cloned().collect();
|
||||
let mut arg_keep_raw = vec![];
|
||||
for (one_arg, one_arg_expr) in args.into_iter().zip(args_expr) {
|
||||
match one_arg {
|
||||
Value::List { vals, .. } => {
|
||||
// turn all the strings in the array into params.
|
||||
// Example: one_arg may be something like ["ls" "-a"]
|
||||
// convert it to "ls" "-a"
|
||||
for v in vals {
|
||||
spanned_args.push(value_as_spanned(v)?);
|
||||
// for arguments in list, it's always treated as a whole arguments
|
||||
arg_keep_raw.push(true);
|
||||
}
|
||||
}
|
||||
val => {
|
||||
spanned_args.push(value_as_spanned(val)?);
|
||||
match one_arg_expr.expr {
|
||||
// refer to `parse_dollar_expr` function
|
||||
// the expression type of $variable_name, $"($variable_name)"
|
||||
// will be Expr::StringInterpolation, Expr::FullCellPath
|
||||
Expr::StringInterpolation(_) | Expr::FullCellPath(_) => {
|
||||
arg_keep_raw.push(true)
|
||||
}
|
||||
_ => arg_keep_raw.push(false),
|
||||
}
|
||||
{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let command = ExternalCommand {
|
||||
name,
|
||||
args: spanned_args,
|
||||
arg_keep_raw,
|
||||
let command = create_external_command(
|
||||
engine_state,
|
||||
stack,
|
||||
call,
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
env_vars: env_vars_str,
|
||||
trim_end_newline,
|
||||
};
|
||||
)?;
|
||||
|
||||
command.run_with_input(engine_state, stack, input, false)
|
||||
}
|
||||
|
||||
@ -135,6 +84,76 @@ impl Command for External {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates ExternalCommand from a call
|
||||
pub fn create_external_command(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
redirect_stdout: bool,
|
||||
redirect_stderr: bool,
|
||||
trim_end_newline: bool,
|
||||
) -> Result<ExternalCommand, ShellError> {
|
||||
let name: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
let args: Vec<Value> = call.rest(engine_state, stack, 1)?;
|
||||
|
||||
// Translate environment variables from Values to Strings
|
||||
let env_vars_str = env_to_strings(engine_state, stack)?;
|
||||
|
||||
fn value_as_spanned(value: Value) -> Result<Spanned<String>, ShellError> {
|
||||
let span = value.span()?;
|
||||
|
||||
value
|
||||
.as_string()
|
||||
.map(|item| Spanned { item, span })
|
||||
.map_err(|_| {
|
||||
ShellError::ExternalCommand(
|
||||
format!("Cannot convert {} to a string", value.get_type()),
|
||||
"All arguments to an external command need to be string-compatible".into(),
|
||||
span,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
let mut spanned_args = vec![];
|
||||
let args_expr: Vec<Expression> = call.positional_iter().skip(1).cloned().collect();
|
||||
let mut arg_keep_raw = vec![];
|
||||
for (one_arg, one_arg_expr) in args.into_iter().zip(args_expr) {
|
||||
match one_arg {
|
||||
Value::List { vals, .. } => {
|
||||
// turn all the strings in the array into params.
|
||||
// Example: one_arg may be something like ["ls" "-a"]
|
||||
// convert it to "ls" "-a"
|
||||
for v in vals {
|
||||
spanned_args.push(value_as_spanned(v)?);
|
||||
// for arguments in list, it's always treated as a whole arguments
|
||||
arg_keep_raw.push(true);
|
||||
}
|
||||
}
|
||||
val => {
|
||||
spanned_args.push(value_as_spanned(val)?);
|
||||
match one_arg_expr.expr {
|
||||
// refer to `parse_dollar_expr` function
|
||||
// the expression type of $variable_name, $"($variable_name)"
|
||||
// will be Expr::StringInterpolation, Expr::FullCellPath
|
||||
Expr::StringInterpolation(_) | Expr::FullCellPath(_) => arg_keep_raw.push(true),
|
||||
_ => arg_keep_raw.push(false),
|
||||
}
|
||||
{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ExternalCommand {
|
||||
name,
|
||||
args: spanned_args,
|
||||
arg_keep_raw,
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
env_vars: env_vars_str,
|
||||
trim_end_newline,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExternalCommand {
|
||||
pub name: Spanned<String>,
|
||||
|
58
crates/nu-command/tests/commands/exec.rs
Normal file
58
crates/nu-command/tests/commands/exec.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn basic_exec() {
|
||||
Playground::setup("test_exec_1", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
nu -c 'exec nu --testbin cococo a b c'
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "a b c");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_complex_args() {
|
||||
Playground::setup("test_exec_2", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
nu -c 'exec nu --testbin cococo b --bar=2 -sab --arwr - -DTEEE=aasd-290 -90 --'
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "b --bar=2 -sab --arwr - -DTEEE=aasd-290 -90 --");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_fail_batched_short_args() {
|
||||
Playground::setup("test_exec_3", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
nu -c 'exec nu --testbin cococo -ab 10'
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_misc_values() {
|
||||
Playground::setup("test_exec_4", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
nu -c 'let x = "abc"; exec nu --testbin cococo $x [ a b c ]'
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "abc a b c");
|
||||
})
|
||||
}
|
@ -20,6 +20,8 @@ mod empty;
|
||||
mod enter;
|
||||
mod error_make;
|
||||
mod every;
|
||||
#[cfg(not(windows))]
|
||||
mod exec;
|
||||
mod export_def;
|
||||
mod find;
|
||||
mod first;
|
||||
|
@ -96,6 +96,7 @@ impl Command for KnownExternal {
|
||||
extern_call.add_positional(arg.clone());
|
||||
}
|
||||
}
|
||||
Argument::Unknown(unknown) => extern_call.add_unknown(unknown.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -499,6 +499,7 @@ pub fn parse_extern(
|
||||
|
||||
signature.name = name.clone();
|
||||
signature.usage = usage.clone();
|
||||
signature.allows_unknown_args = true;
|
||||
|
||||
let decl = KnownExternal {
|
||||
name: name.to_string(),
|
||||
|
@ -827,10 +827,26 @@ pub fn parse_internal_call(
|
||||
&signature,
|
||||
expand_aliases_denylist,
|
||||
);
|
||||
|
||||
if let Some(long_name) = long_name {
|
||||
// We found a long flag, like --bar
|
||||
error = error.or(err);
|
||||
call.add_named((long_name, None, arg));
|
||||
if matches!(err, Some(ParseError::UnknownFlag(_, _, _, _)))
|
||||
&& signature.allows_unknown_args
|
||||
{
|
||||
let (arg, arg_err) = parse_value(
|
||||
working_set,
|
||||
arg_span,
|
||||
&SyntaxShape::Any,
|
||||
expand_aliases_denylist,
|
||||
);
|
||||
|
||||
error = error.or(arg_err);
|
||||
call.add_unknown(arg);
|
||||
} else {
|
||||
error = error.or(err);
|
||||
call.add_named((long_name, None, arg));
|
||||
}
|
||||
|
||||
spans_idx += 1;
|
||||
continue;
|
||||
}
|
||||
@ -846,6 +862,7 @@ pub fn parse_internal_call(
|
||||
|
||||
if let Some(mut short_flags) = short_flags {
|
||||
if short_flags.is_empty() {
|
||||
// workaround for completions (PR #6067)
|
||||
short_flags.push(Flag {
|
||||
long: "".to_string(),
|
||||
short: Some('a'),
|
||||
@ -856,72 +873,88 @@ pub fn parse_internal_call(
|
||||
default_value: None,
|
||||
})
|
||||
}
|
||||
error = error.or(err);
|
||||
for flag in short_flags {
|
||||
if let Some(arg_shape) = flag.arg {
|
||||
if let Some(arg) = spans.get(spans_idx + 1) {
|
||||
let (arg, err) =
|
||||
parse_value(working_set, *arg, &arg_shape, expand_aliases_denylist);
|
||||
error = error.or(err);
|
||||
|
||||
if flag.long.is_empty() {
|
||||
if let Some(short) = flag.short {
|
||||
if matches!(err, Some(ParseError::UnknownFlag(_, _, _, _)))
|
||||
&& signature.allows_unknown_args
|
||||
{
|
||||
let (arg, arg_err) = parse_value(
|
||||
working_set,
|
||||
arg_span,
|
||||
&SyntaxShape::Any,
|
||||
expand_aliases_denylist,
|
||||
);
|
||||
|
||||
call.add_unknown(arg);
|
||||
error = error.or(arg_err);
|
||||
} else {
|
||||
error = error.or(err);
|
||||
for flag in short_flags {
|
||||
if let Some(arg_shape) = flag.arg {
|
||||
if let Some(arg) = spans.get(spans_idx + 1) {
|
||||
let (arg, err) =
|
||||
parse_value(working_set, *arg, &arg_shape, expand_aliases_denylist);
|
||||
error = error.or(err);
|
||||
|
||||
if flag.long.is_empty() {
|
||||
if let Some(short) = flag.short {
|
||||
call.add_named((
|
||||
Spanned {
|
||||
item: String::new(),
|
||||
span: spans[spans_idx],
|
||||
},
|
||||
Some(Spanned {
|
||||
item: short.to_string(),
|
||||
span: spans[spans_idx],
|
||||
}),
|
||||
Some(arg),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
call.add_named((
|
||||
Spanned {
|
||||
item: String::new(),
|
||||
item: flag.long.clone(),
|
||||
span: spans[spans_idx],
|
||||
},
|
||||
Some(Spanned {
|
||||
item: short.to_string(),
|
||||
span: spans[spans_idx],
|
||||
}),
|
||||
None,
|
||||
Some(arg),
|
||||
));
|
||||
}
|
||||
spans_idx += 1;
|
||||
} else {
|
||||
error = error.or_else(|| {
|
||||
Some(ParseError::MissingFlagParam(
|
||||
arg_shape.to_string(),
|
||||
arg_span,
|
||||
))
|
||||
})
|
||||
}
|
||||
} else if flag.long.is_empty() {
|
||||
if let Some(short) = flag.short {
|
||||
call.add_named((
|
||||
Spanned {
|
||||
item: flag.long.clone(),
|
||||
item: String::new(),
|
||||
span: spans[spans_idx],
|
||||
},
|
||||
Some(Spanned {
|
||||
item: short.to_string(),
|
||||
span: spans[spans_idx],
|
||||
}),
|
||||
None,
|
||||
Some(arg),
|
||||
));
|
||||
}
|
||||
spans_idx += 1;
|
||||
} else {
|
||||
error = error.or_else(|| {
|
||||
Some(ParseError::MissingFlagParam(
|
||||
arg_shape.to_string(),
|
||||
arg_span,
|
||||
))
|
||||
})
|
||||
}
|
||||
} else if flag.long.is_empty() {
|
||||
if let Some(short) = flag.short {
|
||||
call.add_named((
|
||||
Spanned {
|
||||
item: String::new(),
|
||||
item: flag.long.clone(),
|
||||
span: spans[spans_idx],
|
||||
},
|
||||
Some(Spanned {
|
||||
item: short.to_string(),
|
||||
span: spans[spans_idx],
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
call.add_named((
|
||||
Spanned {
|
||||
item: flag.long.clone(),
|
||||
span: spans[spans_idx],
|
||||
},
|
||||
None,
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
spans_idx += 1;
|
||||
continue;
|
||||
}
|
||||
@ -973,6 +1006,16 @@ pub fn parse_internal_call(
|
||||
};
|
||||
call.add_positional(arg);
|
||||
positional_idx += 1;
|
||||
} else if signature.allows_unknown_args {
|
||||
let (arg, arg_err) = parse_value(
|
||||
working_set,
|
||||
arg_span,
|
||||
&SyntaxShape::Any,
|
||||
expand_aliases_denylist,
|
||||
);
|
||||
|
||||
call.add_unknown(arg);
|
||||
error = error.or(arg_err);
|
||||
} else {
|
||||
call.add_positional(Expression::garbage(arg_span));
|
||||
error = error.or_else(|| {
|
||||
|
@ -7,6 +7,7 @@ use crate::{DeclId, Span, Spanned};
|
||||
pub enum Argument {
|
||||
Positional(Expression),
|
||||
Named((Spanned<String>, Option<Spanned<String>>, Option<Expression>)),
|
||||
Unknown(Expression), // unknown argument used in "fall-through" signatures
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
@ -36,6 +37,7 @@ impl Call {
|
||||
self.arguments.iter().filter_map(|arg| match arg {
|
||||
Argument::Named(named) => Some(named),
|
||||
Argument::Positional(_) => None,
|
||||
Argument::Unknown(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
@ -46,6 +48,7 @@ impl Call {
|
||||
self.arguments.iter_mut().filter_map(|arg| match arg {
|
||||
Argument::Named(named) => Some(named),
|
||||
Argument::Positional(_) => None,
|
||||
Argument::Unknown(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
@ -64,10 +67,15 @@ impl Call {
|
||||
self.arguments.push(Argument::Positional(positional));
|
||||
}
|
||||
|
||||
pub fn add_unknown(&mut self, unknown: Expression) {
|
||||
self.arguments.push(Argument::Unknown(unknown));
|
||||
}
|
||||
|
||||
pub fn positional_iter(&self) -> impl Iterator<Item = &Expression> {
|
||||
self.arguments.iter().filter_map(|arg| match arg {
|
||||
Argument::Named(_) => None,
|
||||
Argument::Positional(positional) => Some(positional),
|
||||
Argument::Unknown(unknown) => Some(unknown),
|
||||
})
|
||||
}
|
||||
|
||||
@ -75,6 +83,7 @@ impl Call {
|
||||
self.arguments.iter_mut().filter_map(|arg| match arg {
|
||||
Argument::Named(_) => None,
|
||||
Argument::Positional(positional) => Some(positional),
|
||||
Argument::Unknown(unknown) => Some(unknown),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -118,6 +118,7 @@ pub struct Signature {
|
||||
pub allow_variants_without_examples: bool,
|
||||
pub is_filter: bool,
|
||||
pub creates_scope: bool,
|
||||
pub allows_unknown_args: bool,
|
||||
// Signature category used to classify commands stored in the list of declarations
|
||||
pub category: Category,
|
||||
}
|
||||
@ -220,6 +221,7 @@ impl Signature {
|
||||
is_filter: false,
|
||||
creates_scope: false,
|
||||
category: Category::Default,
|
||||
allows_unknown_args: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,6 +276,12 @@ impl Signature {
|
||||
self
|
||||
}
|
||||
|
||||
/// Allow unknown signature parameters
|
||||
pub fn allows_unknown_args(mut self) -> Signature {
|
||||
self.allows_unknown_args = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a required positional argument to the signature
|
||||
pub fn required(
|
||||
mut self,
|
||||
|
@ -101,7 +101,6 @@ module completions {
|
||||
--dry-run(-n) # dry run
|
||||
--exec: string # receive pack program
|
||||
--follow-tags # push missing but relevant tags
|
||||
--force-with-lease # require old value of ref to be at this value
|
||||
--force(-f) # force updates
|
||||
--ipv4(-4) # use IPv4 addresses only
|
||||
--ipv6(-6) # use IPv6 addresses only
|
||||
@ -292,7 +291,7 @@ let light_theme = {
|
||||
}
|
||||
|
||||
# External completer example
|
||||
# let carapace_completer = {|spans|
|
||||
# let carapace_completer = {|spans|
|
||||
# carapace $spans.0 nushell $spans | from json
|
||||
# }
|
||||
|
||||
@ -325,31 +324,31 @@ let-env config = {
|
||||
|
||||
command_bar_text: '#C4C9C6'
|
||||
# command_bar: {fg: '#C4C9C6' bg: '#223311' }
|
||||
|
||||
|
||||
status_bar_background: {fg: '#1D1F21' bg: '#C4C9C6' }
|
||||
# status_bar_text: {fg: '#C4C9C6' bg: '#223311' }
|
||||
|
||||
highlight: {bg: 'yellow' fg: 'black' }
|
||||
|
||||
status: {
|
||||
# warn: {bg: 'yellow', fg: 'blue'}
|
||||
# error: {bg: 'yellow', fg: 'blue'}
|
||||
# warn: {bg: 'yellow', fg: 'blue'}
|
||||
# error: {bg: 'yellow', fg: 'blue'}
|
||||
# info: {bg: 'yellow', fg: 'blue'}
|
||||
}
|
||||
|
||||
try: {
|
||||
# border_color: 'red'
|
||||
# border_color: 'red'
|
||||
# highlighted_color: 'blue'
|
||||
|
||||
# reactive: false
|
||||
}
|
||||
|
||||
table: {
|
||||
split_line: '#404040'
|
||||
split_line: '#404040'
|
||||
|
||||
cursor: true
|
||||
|
||||
line_index: true
|
||||
line_index: true
|
||||
line_shift: true
|
||||
line_head_top: true
|
||||
line_head_bottom: true
|
||||
@ -357,14 +356,14 @@ let-env config = {
|
||||
show_head: true
|
||||
show_index: true
|
||||
|
||||
# selected_cell: {fg: 'white', bg: '#777777'}
|
||||
# selected_row: {fg: 'yellow', bg: '#C1C2A3'}
|
||||
# selected_cell: {fg: 'white', bg: '#777777'}
|
||||
# selected_row: {fg: 'yellow', bg: '#C1C2A3'}
|
||||
# selected_column: blue
|
||||
|
||||
# padding_column_right: 2
|
||||
# padding_column_right: 2
|
||||
# padding_column_left: 2
|
||||
|
||||
# padding_index_left: 2
|
||||
# padding_index_left: 2
|
||||
# padding_index_right: 1
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::tests::{fail_test, run_test_contains, TestResult};
|
||||
use crate::tests::{fail_test, run_test, run_test_contains, TestResult};
|
||||
|
||||
// cargo version prints a string of the form:
|
||||
// cargo 1.60.0 (d1fd9fe2c 2022-03-01)
|
||||
@ -10,10 +10,7 @@ fn known_external_runs() -> TestResult {
|
||||
|
||||
#[test]
|
||||
fn known_external_unknown_flag() -> TestResult {
|
||||
fail_test(
|
||||
r#"extern "cargo version" []; cargo version --no-such-flag"#,
|
||||
"command doesn't have flag",
|
||||
)
|
||||
run_test_contains(r#"extern "cargo" []; cargo --version"#, "cargo")
|
||||
}
|
||||
|
||||
/// GitHub issues #5179, #4618
|
||||
@ -33,3 +30,49 @@ fn known_external_subcommand_alias() -> TestResult {
|
||||
"cargo",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn known_external_complex_unknown_args() -> TestResult {
|
||||
run_test_contains(
|
||||
"extern echo []; echo foo -b -as -9 --abc -- -Dxmy=AKOO - bar",
|
||||
"foo -b -as -9 --abc -- -Dxmy=AKOO - bar",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn known_external_batched_short_flag_arg_disallowed() -> TestResult {
|
||||
fail_test(
|
||||
"extern echo [-a, -b: int]; echo -ab 10",
|
||||
"short flag batches",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn known_external_missing_positional() -> TestResult {
|
||||
fail_test("extern echo [a]; echo", "missing_positional")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn known_external_type_mismatch() -> TestResult {
|
||||
fail_test("extern echo [a: int]; echo 1.234", "mismatch")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn known_external_missing_flag_param() -> TestResult {
|
||||
fail_test(
|
||||
"extern echo [--foo: string]; echo --foo",
|
||||
"missing_flag_param",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn known_external_misc_values() -> TestResult {
|
||||
run_test(
|
||||
r#"
|
||||
let x = 'abc'
|
||||
extern echo []
|
||||
echo $x [ a b c ]
|
||||
"#,
|
||||
"abc a b c",
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user