Add extern def which allows raw arguments (#8956)

# Description

Extends the `extern` syntax to allow commands that accept raw arguments.
This is mainly added to allow wrapper type scripts for external
commands.

This is an example on how this can be used:

```nushell
extern foo [...rest] { 
  print ($rest | str join ',' ) 
}
foo --bar baz -- -q -u -x
# => --bar,baz,--,-q,-u,-x
```

(It's only possible to accept a single ...varargs argument in the
signature)

# User-Facing Changes

No breaking changes, just extra possibilities.

# Tests + Formatting

Added a test for this new behaviour and ran the toolkit pr checker

# After Submitting

This is advanced functionality but it should be documented, I will open
a new PR on the book for that

Co-authored-by: Jelle Besseling <jelle@bigbridge.nl>
This commit is contained in:
Jelle Besseling 2023-04-28 09:06:43 +02:00 committed by GitHub
parent 6f9b9914cf
commit 44493dac51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 71 additions and 41 deletions

View file

@ -19,6 +19,7 @@ impl Command for Extern {
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.required("def_name", SyntaxShape::String, "definition name")
.required("params", SyntaxShape::Signature, "parameters")
.optional("body", SyntaxShape::Block, "wrapper function block")
.category(Category::Core)
}

View file

@ -141,3 +141,11 @@ fn def_with_paren_params() {
assert_eq!(actual.out, "3");
}
#[test]
fn extern_with_block() {
let actual =
nu!("extern foo [...rest] { print ($rest | str join ',' ) }; foo --bar baz -- -q -u -x");
assert_eq!(actual.out, "--bar,baz,--,-q,-u,-x");
}

View file

@ -159,7 +159,7 @@ pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) {
working_set.error(ParseError::DuplicateCommandDef(spans[1]));
}
}
} else if name == b"extern" && spans.len() == 3 {
} else if name == b"extern" && spans.len() >= 3 {
let name_expr = parse_string(working_set, spans[1]);
let name = name_expr.as_string();
@ -430,40 +430,7 @@ pub fn parse_def(
*declaration = signature.clone().into_block_command(block_id);
let mut block = working_set.get_block_mut(block_id);
let calls_itself = block.pipelines.iter().any(|pipeline| {
pipeline
.elements
.iter()
.any(|pipe_element| match pipe_element {
PipelineElement::Expression(
_,
Expression {
expr: Expr::Call(call_expr),
..
},
) => {
if call_expr.decl_id == decl_id {
return true;
}
call_expr.arguments.iter().any(|arg| match arg {
Argument::Positional(Expression { expr, .. }) => match expr {
Expr::Keyword(.., expr) => {
let expr = expr.as_ref();
let Expression { expr, .. } = expr;
match expr {
Expr::Call(call_expr2) => call_expr2.decl_id == decl_id,
_ => false,
}
}
Expr::Call(call_expr2) => call_expr2.decl_id == decl_id,
_ => false,
},
_ => false,
})
}
_ => false,
})
});
let calls_itself = block_calls_itself(block, decl_id);
block.recursive = Some(calls_itself);
block.signature = signature;
block.redirect_env = def_call == b"def-env";
@ -543,6 +510,7 @@ pub fn parse_extern(
};
let name_expr = call.positional_nth(0);
let sig = call.positional_nth(1);
let body = call.positional_nth(2);
if let (Some(name_expr), Some(sig)) = (name_expr, sig) {
if let (Some(name), Some(mut signature)) = (&name_expr.as_string(), sig.as_signature()) {
@ -581,13 +549,29 @@ pub fn parse_extern(
signature.extra_usage = extra_usage.clone();
signature.allows_unknown_args = true;
let decl = KnownExternal {
name: external_name,
usage: [usage, extra_usage].join("\n"),
signature,
};
if let Some(block_id) = body.and_then(|x| x.as_block()) {
if signature.rest_positional.is_none() {
working_set.error(ParseError::InternalError(
"Extern block must have a rest positional argument".into(),
name_expr.span,
));
} else {
*declaration = signature.clone().into_block_command(block_id);
*declaration = Box::new(decl);
let block = working_set.get_block_mut(block_id);
let calls_itself = block_calls_itself(block, decl_id);
block.recursive = Some(calls_itself);
block.signature = signature;
}
} else {
let decl = KnownExternal {
name: external_name,
usage: [usage, extra_usage].join("\n"),
signature,
};
*declaration = Box::new(decl);
}
} else {
working_set.error(ParseError::InternalError(
"Predeclaration failed to add declaration".into(),
@ -614,6 +598,43 @@ pub fn parse_extern(
}])
}
fn block_calls_itself(block: &Block, decl_id: usize) -> bool {
block.pipelines.iter().any(|pipeline| {
pipeline
.elements
.iter()
.any(|pipe_element| match pipe_element {
PipelineElement::Expression(
_,
Expression {
expr: Expr::Call(call_expr),
..
},
) => {
if call_expr.decl_id == decl_id {
return true;
}
call_expr.arguments.iter().any(|arg| match arg {
Argument::Positional(Expression { expr, .. }) => match expr {
Expr::Keyword(.., expr) => {
let expr = expr.as_ref();
let Expression { expr, .. } = expr;
match expr {
Expr::Call(call_expr2) => call_expr2.decl_id == decl_id,
_ => false,
}
}
Expr::Call(call_expr2) => call_expr2.decl_id == decl_id,
_ => false,
},
_ => false,
})
}
_ => false,
})
})
}
pub fn parse_alias(
working_set: &mut StateWorkingSet,
lite_command: &LiteCommand,