Make parsing for unknown args in known externals like normal external calls
This commit is contained in:
parent
dbd60ed4f4
commit
993c7c09d4
|
@ -5,7 +5,7 @@ use nu_protocol::{
|
||||||
RecordItem,
|
RecordItem,
|
||||||
},
|
},
|
||||||
engine::StateWorkingSet,
|
engine::StateWorkingSet,
|
||||||
DeclId, Span, VarId,
|
DeclId, Span, SyntaxShape, VarId,
|
||||||
};
|
};
|
||||||
use std::fmt::{Display, Formatter, Result};
|
use std::fmt::{Display, Formatter, Result};
|
||||||
|
|
||||||
|
@ -166,6 +166,22 @@ fn flatten_pipeline_element_into(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flatten_positional_arg_into(
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
positional: &Expression,
|
||||||
|
shape: &SyntaxShape,
|
||||||
|
output: &mut Vec<(Span, FlatShape)>,
|
||||||
|
) {
|
||||||
|
if matches!(shape, SyntaxShape::ExternalArgument)
|
||||||
|
&& matches!(positional.expr, Expr::String(..) | Expr::GlobPattern(..))
|
||||||
|
{
|
||||||
|
// Make known external arguments look more like external arguments
|
||||||
|
output.push((positional.span, FlatShape::ExternalArg));
|
||||||
|
} else {
|
||||||
|
flatten_expression_into(working_set, positional, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn flatten_expression_into(
|
fn flatten_expression_into(
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
expr: &Expression,
|
expr: &Expression,
|
||||||
|
@ -249,16 +265,40 @@ fn flatten_expression_into(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Call(call) => {
|
Expr::Call(call) => {
|
||||||
|
let decl = working_set.get_decl(call.decl_id);
|
||||||
|
|
||||||
if call.head.end != 0 {
|
if call.head.end != 0 {
|
||||||
// Make sure we don't push synthetic calls
|
// Make sure we don't push synthetic calls
|
||||||
output.push((call.head, FlatShape::InternalCall(call.decl_id)));
|
output.push((call.head, FlatShape::InternalCall(call.decl_id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Follow positional arguments from the signature.
|
||||||
|
let signature = decl.signature();
|
||||||
|
let mut positional_args = signature
|
||||||
|
.required_positional
|
||||||
|
.iter()
|
||||||
|
.chain(&signature.optional_positional);
|
||||||
|
|
||||||
let arg_start = output.len();
|
let arg_start = output.len();
|
||||||
for arg in &call.arguments {
|
for arg in &call.arguments {
|
||||||
match arg {
|
match arg {
|
||||||
Argument::Positional(positional) | Argument::Unknown(positional) => {
|
Argument::Positional(positional) => {
|
||||||
flatten_expression_into(working_set, positional, output)
|
let positional_arg = positional_args.next();
|
||||||
|
let shape = positional_arg
|
||||||
|
.or(signature.rest_positional.as_ref())
|
||||||
|
.map(|arg| &arg.shape)
|
||||||
|
.unwrap_or(&SyntaxShape::Any);
|
||||||
|
|
||||||
|
flatten_positional_arg_into(working_set, positional, shape, output)
|
||||||
|
}
|
||||||
|
Argument::Unknown(positional) => {
|
||||||
|
let shape = signature
|
||||||
|
.rest_positional
|
||||||
|
.as_ref()
|
||||||
|
.map(|arg| &arg.shape)
|
||||||
|
.unwrap_or(&SyntaxShape::Any);
|
||||||
|
|
||||||
|
flatten_positional_arg_into(working_set, positional, shape, output)
|
||||||
}
|
}
|
||||||
Argument::Named(named) => {
|
Argument::Named(named) => {
|
||||||
if named.0.span.end != 0 {
|
if named.0.span.end != 0 {
|
||||||
|
|
|
@ -783,6 +783,16 @@ pub fn parse_extern(
|
||||||
working_set.get_block_mut(block_id).signature = signature;
|
working_set.get_block_mut(block_id).signature = signature;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if signature.rest_positional.is_none() {
|
||||||
|
// Make sure that a known external takes rest args with ExternalArgument
|
||||||
|
// shape
|
||||||
|
*signature = signature.rest(
|
||||||
|
"args",
|
||||||
|
SyntaxShape::ExternalArgument,
|
||||||
|
"all other arguments to the command",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let decl = KnownExternal {
|
let decl = KnownExternal {
|
||||||
name: external_name,
|
name: external_name,
|
||||||
usage,
|
usage,
|
||||||
|
|
|
@ -221,6 +221,22 @@ pub(crate) fn check_call(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses an unknown argument for the given signature. This handles the parsing as appropriate to
|
||||||
|
/// the rest type of the command.
|
||||||
|
fn parse_unknown_arg(
|
||||||
|
working_set: &mut StateWorkingSet,
|
||||||
|
span: Span,
|
||||||
|
signature: &Signature,
|
||||||
|
) -> Expression {
|
||||||
|
let shape = signature
|
||||||
|
.rest_positional
|
||||||
|
.as_ref()
|
||||||
|
.map(|arg| arg.shape.clone())
|
||||||
|
.unwrap_or(SyntaxShape::Any);
|
||||||
|
|
||||||
|
parse_value(working_set, span, &shape)
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses a string in the arg or head position of an external call.
|
/// Parses a string in the arg or head position of an external call.
|
||||||
///
|
///
|
||||||
/// If the string begins with `r#`, it is parsed as a raw string. If it doesn't contain any quotes
|
/// If the string begins with `r#`, it is parsed as a raw string. If it doesn't contain any quotes
|
||||||
|
@ -427,11 +443,7 @@ fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expre
|
||||||
fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> ExternalArgument {
|
fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> ExternalArgument {
|
||||||
let contents = working_set.get_span_contents(span);
|
let contents = working_set.get_span_contents(span);
|
||||||
|
|
||||||
if contents.starts_with(b"$") || contents.starts_with(b"(") {
|
if contents.len() > 3
|
||||||
ExternalArgument::Regular(parse_dollar_expr(working_set, span))
|
|
||||||
} else if contents.starts_with(b"[") {
|
|
||||||
ExternalArgument::Regular(parse_list_expression(working_set, span, &SyntaxShape::Any))
|
|
||||||
} else if contents.len() > 3
|
|
||||||
&& contents.starts_with(b"...")
|
&& contents.starts_with(b"...")
|
||||||
&& (contents[3] == b'$' || contents[3] == b'[' || contents[3] == b'(')
|
&& (contents[3] == b'$' || contents[3] == b'[' || contents[3] == b'(')
|
||||||
{
|
{
|
||||||
|
@ -441,7 +453,19 @@ fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> External
|
||||||
&SyntaxShape::List(Box::new(SyntaxShape::Any)),
|
&SyntaxShape::List(Box::new(SyntaxShape::Any)),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
ExternalArgument::Regular(parse_external_string(working_set, span))
|
ExternalArgument::Regular(parse_regular_external_arg(working_set, span))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_regular_external_arg(working_set: &mut StateWorkingSet, span: Span) -> Expression {
|
||||||
|
let contents = working_set.get_span_contents(span);
|
||||||
|
|
||||||
|
if contents.starts_with(b"$") || contents.starts_with(b"(") {
|
||||||
|
parse_dollar_expr(working_set, span)
|
||||||
|
} else if contents.starts_with(b"[") {
|
||||||
|
parse_list_expression(working_set, span, &SyntaxShape::Any)
|
||||||
|
} else {
|
||||||
|
parse_external_string(working_set, span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -998,7 +1022,7 @@ pub fn parse_internal_call(
|
||||||
&& signature.allows_unknown_args
|
&& signature.allows_unknown_args
|
||||||
{
|
{
|
||||||
working_set.parse_errors.truncate(starting_error_count);
|
working_set.parse_errors.truncate(starting_error_count);
|
||||||
let arg = parse_value(working_set, arg_span, &SyntaxShape::Any);
|
let arg = parse_unknown_arg(working_set, arg_span, &signature);
|
||||||
|
|
||||||
call.add_unknown(arg);
|
call.add_unknown(arg);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1040,7 +1064,7 @@ pub fn parse_internal_call(
|
||||||
&& signature.allows_unknown_args
|
&& signature.allows_unknown_args
|
||||||
{
|
{
|
||||||
working_set.parse_errors.truncate(starting_error_count);
|
working_set.parse_errors.truncate(starting_error_count);
|
||||||
let arg = parse_value(working_set, arg_span, &SyntaxShape::Any);
|
let arg = parse_unknown_arg(working_set, arg_span, &signature);
|
||||||
|
|
||||||
call.add_unknown(arg);
|
call.add_unknown(arg);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1196,7 +1220,7 @@ pub fn parse_internal_call(
|
||||||
call.add_positional(arg);
|
call.add_positional(arg);
|
||||||
positional_idx += 1;
|
positional_idx += 1;
|
||||||
} else if signature.allows_unknown_args {
|
} else if signature.allows_unknown_args {
|
||||||
let arg = parse_value(working_set, arg_span, &SyntaxShape::Any);
|
let arg = parse_unknown_arg(working_set, arg_span, &signature);
|
||||||
|
|
||||||
call.add_unknown(arg);
|
call.add_unknown(arg);
|
||||||
} else {
|
} else {
|
||||||
|
@ -4670,7 +4694,8 @@ pub fn parse_value(
|
||||||
| SyntaxShape::Signature
|
| SyntaxShape::Signature
|
||||||
| SyntaxShape::Filepath
|
| SyntaxShape::Filepath
|
||||||
| SyntaxShape::String
|
| SyntaxShape::String
|
||||||
| SyntaxShape::GlobPattern => {}
|
| SyntaxShape::GlobPattern
|
||||||
|
| SyntaxShape::ExternalArgument => {}
|
||||||
_ => {
|
_ => {
|
||||||
working_set.error(ParseError::Expected("non-[] value", span));
|
working_set.error(ParseError::Expected("non-[] value", span));
|
||||||
return Expression::garbage(working_set, span);
|
return Expression::garbage(working_set, span);
|
||||||
|
@ -4747,6 +4772,8 @@ pub fn parse_value(
|
||||||
Expression::garbage(working_set, span)
|
Expression::garbage(working_set, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SyntaxShape::ExternalArgument => parse_regular_external_arg(working_set, span),
|
||||||
|
|
||||||
SyntaxShape::Any => {
|
SyntaxShape::Any => {
|
||||||
if bytes.starts_with(b"[") {
|
if bytes.starts_with(b"[") {
|
||||||
//parse_value(working_set, span, &SyntaxShape::Table)
|
//parse_value(working_set, span, &SyntaxShape::Table)
|
||||||
|
|
|
@ -47,6 +47,12 @@ pub enum SyntaxShape {
|
||||||
/// A general expression, eg `1 + 2` or `foo --bar`
|
/// A general expression, eg `1 + 2` or `foo --bar`
|
||||||
Expression,
|
Expression,
|
||||||
|
|
||||||
|
/// A (typically) string argument that follows external command argument parsing rules.
|
||||||
|
///
|
||||||
|
/// Filepaths are expanded if unquoted, globs are allowed, and quotes embedded within unknown
|
||||||
|
/// args are unquoted.
|
||||||
|
ExternalArgument,
|
||||||
|
|
||||||
/// A filepath is allowed
|
/// A filepath is allowed
|
||||||
Filepath,
|
Filepath,
|
||||||
|
|
||||||
|
@ -145,6 +151,7 @@ impl SyntaxShape {
|
||||||
SyntaxShape::DateTime => Type::Date,
|
SyntaxShape::DateTime => Type::Date,
|
||||||
SyntaxShape::Duration => Type::Duration,
|
SyntaxShape::Duration => Type::Duration,
|
||||||
SyntaxShape::Expression => Type::Any,
|
SyntaxShape::Expression => Type::Any,
|
||||||
|
SyntaxShape::ExternalArgument => Type::Any,
|
||||||
SyntaxShape::Filepath => Type::String,
|
SyntaxShape::Filepath => Type::String,
|
||||||
SyntaxShape::Directory => Type::String,
|
SyntaxShape::Directory => Type::String,
|
||||||
SyntaxShape::Float => Type::Float,
|
SyntaxShape::Float => Type::Float,
|
||||||
|
@ -238,6 +245,7 @@ impl Display for SyntaxShape {
|
||||||
SyntaxShape::Signature => write!(f, "signature"),
|
SyntaxShape::Signature => write!(f, "signature"),
|
||||||
SyntaxShape::MatchBlock => write!(f, "match-block"),
|
SyntaxShape::MatchBlock => write!(f, "match-block"),
|
||||||
SyntaxShape::Expression => write!(f, "expression"),
|
SyntaxShape::Expression => write!(f, "expression"),
|
||||||
|
SyntaxShape::ExternalArgument => write!(f, "external-argument"),
|
||||||
SyntaxShape::Boolean => write!(f, "bool"),
|
SyntaxShape::Boolean => write!(f, "bool"),
|
||||||
SyntaxShape::Error => write!(f, "error"),
|
SyntaxShape::Error => write!(f, "error"),
|
||||||
SyntaxShape::CompleterWrapper(x, _) => write!(f, "completable<{x}>"),
|
SyntaxShape::CompleterWrapper(x, _) => write!(f, "completable<{x}>"),
|
||||||
|
|
|
@ -186,8 +186,12 @@ fn help_present_in_def() -> TestResult {
|
||||||
#[test]
|
#[test]
|
||||||
fn help_not_present_in_extern() -> TestResult {
|
fn help_not_present_in_extern() -> TestResult {
|
||||||
run_test(
|
run_test(
|
||||||
"module test {export extern \"git fetch\" []}; use test `git fetch`; help git fetch | ansi strip",
|
r#"
|
||||||
"Usage:\n > git fetch",
|
module test {export extern "git fetch" []};
|
||||||
|
use test `git fetch`;
|
||||||
|
help git fetch | find help | to text | ansi strip
|
||||||
|
"#,
|
||||||
|
"",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -137,3 +137,39 @@ fn known_external_aliased_subcommand_from_module() -> TestResult {
|
||||||
String::from_utf8(output.stdout)?.trim(),
|
String::from_utf8(output.stdout)?.trim(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn known_external_arg_expansion() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"
|
||||||
|
extern echo [];
|
||||||
|
echo ~/foo
|
||||||
|
"#,
|
||||||
|
&dirs::home_dir()
|
||||||
|
.expect("can't find home dir")
|
||||||
|
.join("foo")
|
||||||
|
.to_string_lossy(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn known_external_arg_quoted_no_expand() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"
|
||||||
|
extern echo [];
|
||||||
|
echo "~/foo"
|
||||||
|
"#,
|
||||||
|
"~/foo",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn known_external_arg_internally_quoted_options() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"
|
||||||
|
extern echo [];
|
||||||
|
echo --option="test"
|
||||||
|
"#,
|
||||||
|
"--option=test",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user