mirror of
https://github.com/nushell/nushell
synced 2025-01-13 21:55:07 +00:00
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:
parent
6f9b9914cf
commit
44493dac51
3 changed files with 71 additions and 41 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue