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)]) .input_output_types(vec![(Type::Nothing, Type::Nothing)])
.required("def_name", SyntaxShape::String, "definition name") .required("def_name", SyntaxShape::String, "definition name")
.required("params", SyntaxShape::Signature, "parameters") .required("params", SyntaxShape::Signature, "parameters")
.optional("body", SyntaxShape::Block, "wrapper function block")
.category(Category::Core) .category(Category::Core)
} }

View file

@ -141,3 +141,11 @@ fn def_with_paren_params() {
assert_eq!(actual.out, "3"); 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])); 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_expr = parse_string(working_set, spans[1]);
let name = name_expr.as_string(); let name = name_expr.as_string();
@ -430,40 +430,7 @@ pub fn parse_def(
*declaration = signature.clone().into_block_command(block_id); *declaration = signature.clone().into_block_command(block_id);
let mut block = working_set.get_block_mut(block_id); let mut block = working_set.get_block_mut(block_id);
let calls_itself = block.pipelines.iter().any(|pipeline| { let calls_itself = block_calls_itself(block, decl_id);
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,
})
});
block.recursive = Some(calls_itself); block.recursive = Some(calls_itself);
block.signature = signature; block.signature = signature;
block.redirect_env = def_call == b"def-env"; block.redirect_env = def_call == b"def-env";
@ -543,6 +510,7 @@ pub fn parse_extern(
}; };
let name_expr = call.positional_nth(0); let name_expr = call.positional_nth(0);
let sig = call.positional_nth(1); 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_expr), Some(sig)) = (name_expr, sig) {
if let (Some(name), Some(mut signature)) = (&name_expr.as_string(), sig.as_signature()) { 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.extra_usage = extra_usage.clone();
signature.allows_unknown_args = true; signature.allows_unknown_args = true;
let decl = KnownExternal { if let Some(block_id) = body.and_then(|x| x.as_block()) {
name: external_name, if signature.rest_positional.is_none() {
usage: [usage, extra_usage].join("\n"), working_set.error(ParseError::InternalError(
signature, "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 { } else {
working_set.error(ParseError::InternalError( working_set.error(ParseError::InternalError(
"Predeclaration failed to add declaration".into(), "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( pub fn parse_alias(
working_set: &mut StateWorkingSet, working_set: &mut StateWorkingSet,
lite_command: &LiteCommand, lite_command: &LiteCommand,