mirror of
https://github.com/nushell/nushell
synced 2024-12-28 14:03:09 +00:00
parser/add rest args to def (#2961)
* Add rest arg to def This commit applied adds the ability to define the rest parameter of a def command. It does not implement the functionality to expand the rest argument in a user defined def function. The rest argument has to be exactly worded "...rest". Example after this PR is applied: file test.nu ```shell def my_command [ ...rest:int # My rest arg ] { echo 1 2 3 } ``` ```shell > source test.nu > my_command -h Usage: > my_command ...args {flags} Parameters: ...args: My rest arg Flags: -h, --help: Display this help message ``` * Fix space in help on wrong side
This commit is contained in:
parent
64553ddcb7
commit
71b99edd48
3 changed files with 152 additions and 5 deletions
|
@ -160,7 +160,7 @@ pub fn get_documentation(
|
||||||
}
|
}
|
||||||
|
|
||||||
if signature.rest_positional.is_some() {
|
if signature.rest_positional.is_some() {
|
||||||
one_liner.push_str(" ...args");
|
one_liner.push_str("...args ");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !subcommands.is_empty() {
|
if !subcommands.is_empty() {
|
||||||
|
|
|
@ -55,6 +55,7 @@ pub fn parse_signature(
|
||||||
|
|
||||||
let mut parameters = vec![];
|
let mut parameters = vec![];
|
||||||
let mut flags = vec![];
|
let mut flags = vec![];
|
||||||
|
let mut rest = None;
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
|
||||||
while i < tokens.len() {
|
while i < tokens.len() {
|
||||||
|
@ -66,6 +67,11 @@ pub fn parse_signature(
|
||||||
err = err.or(error);
|
err = err.or(error);
|
||||||
i += advanced_by;
|
i += advanced_by;
|
||||||
flags.push(flag);
|
flags.push(flag);
|
||||||
|
} else if is_rest(&tokens[i]) {
|
||||||
|
let (rest_, advanced_by, error) = parse_rest(&tokens[i..], signature_vec);
|
||||||
|
err = err.or(error);
|
||||||
|
i += advanced_by;
|
||||||
|
rest = rest_;
|
||||||
} else {
|
} else {
|
||||||
let (parameter, advanced_by, error) = parse_parameter(&tokens[i..], signature_vec);
|
let (parameter, advanced_by, error) = parse_parameter(&tokens[i..], signature_vec);
|
||||||
err = err.or(error);
|
err = err.or(error);
|
||||||
|
@ -74,7 +80,7 @@ pub fn parse_signature(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let signature = to_signature(name, parameters, flags);
|
let signature = to_signature(name, parameters, flags, rest);
|
||||||
debug!("Signature: {:?}", signature);
|
debug!("Signature: {:?}", signature);
|
||||||
|
|
||||||
(signature, err)
|
(signature, err)
|
||||||
|
@ -168,6 +174,68 @@ fn parse_flag(
|
||||||
(flag, i, err)
|
(flag, i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_rest(
|
||||||
|
tokens: &[Token],
|
||||||
|
tokens_as_str: &Spanned<String>,
|
||||||
|
) -> (
|
||||||
|
Option<(SyntaxShape, Description)>,
|
||||||
|
usize,
|
||||||
|
Option<ParseError>,
|
||||||
|
) {
|
||||||
|
if tokens.is_empty() {
|
||||||
|
return (
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
Some(ParseError::unexpected_eof(
|
||||||
|
"rest argument",
|
||||||
|
tokens_as_str.span,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut err = None;
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
let error = parse_rest_name(&tokens[i]);
|
||||||
|
err = err.or(error);
|
||||||
|
i += 1;
|
||||||
|
|
||||||
|
let (type_, advanced_by, error) = parse_optional_type(&tokens[i..]);
|
||||||
|
err = err.or(error);
|
||||||
|
i += advanced_by;
|
||||||
|
let type_ = type_.unwrap_or(SyntaxShape::Any);
|
||||||
|
|
||||||
|
let (comment, advanced_by) = parse_optional_comment(&tokens[i..]);
|
||||||
|
i += advanced_by;
|
||||||
|
let comment = comment.unwrap_or_else(|| "".to_string());
|
||||||
|
|
||||||
|
return (Some((type_, comment)), i, err);
|
||||||
|
|
||||||
|
fn parse_rest_name(name_token: &Token) -> Option<ParseError> {
|
||||||
|
return if let TokenContents::Baseline(name) = &name_token.contents {
|
||||||
|
if !name.starts_with("...") {
|
||||||
|
parse_rest_name_err(name_token)
|
||||||
|
} else if !name.starts_with("...rest") {
|
||||||
|
Some(ParseError::mismatch(
|
||||||
|
"rest argument name to be 'rest'",
|
||||||
|
token_to_spanned_string(name_token),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parse_rest_name_err(name_token)
|
||||||
|
};
|
||||||
|
|
||||||
|
fn parse_rest_name_err(token: &Token) -> Option<ParseError> {
|
||||||
|
Some(ParseError::mismatch(
|
||||||
|
"...rest",
|
||||||
|
token_to_spanned_string(token),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_type(type_: &Spanned<String>) -> (SyntaxShape, Option<ParseError>) {
|
fn parse_type(type_: &Spanned<String>) -> (SyntaxShape, Option<ParseError>) {
|
||||||
debug!("Parsing type {:?}", type_);
|
debug!("Parsing type {:?}", type_);
|
||||||
match type_.item.as_str() {
|
match type_.item.as_str() {
|
||||||
|
@ -396,6 +464,14 @@ fn parse_comma(tokens: &[Token]) -> (bool, usize) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///Returns true if token potentially represents rest argument
|
||||||
|
fn is_rest(token: &Token) -> bool {
|
||||||
|
match &token.contents {
|
||||||
|
TokenContents::Baseline(item) => item.starts_with("..."),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///True for short or longform flags. False otherwise
|
///True for short or longform flags. False otherwise
|
||||||
fn is_flag(token: &Token) -> bool {
|
fn is_flag(token: &Token) -> bool {
|
||||||
match &token.contents {
|
match &token.contents {
|
||||||
|
@ -404,7 +480,12 @@ fn is_flag(token: &Token) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_signature(name: &str, params: Vec<Parameter>, flags: Vec<Flag>) -> Signature {
|
fn to_signature(
|
||||||
|
name: &str,
|
||||||
|
params: Vec<Parameter>,
|
||||||
|
flags: Vec<Flag>,
|
||||||
|
rest: Option<(SyntaxShape, Description)>,
|
||||||
|
) -> Signature {
|
||||||
let mut sign = Signature::new(name);
|
let mut sign = Signature::new(name);
|
||||||
|
|
||||||
for param in params.into_iter() {
|
for param in params.into_iter() {
|
||||||
|
@ -420,6 +501,8 @@ fn to_signature(name: &str, params: Vec<Parameter>, flags: Vec<Flag>) -> Signatu
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sign.rest_positional = rest;
|
||||||
|
|
||||||
sign
|
sign
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -881,4 +964,64 @@ mod tests {
|
||||||
"The all powerful x flag",
|
"The all powerful x flag",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_def_with_rest_arg() {
|
||||||
|
let name = "my_func";
|
||||||
|
let sign = "[ ...rest]";
|
||||||
|
let (sign, err) = parse_signature(name, &sign.to_string().spanned_unknown());
|
||||||
|
assert!(err.is_none());
|
||||||
|
assert_eq!(
|
||||||
|
sign.rest_positional,
|
||||||
|
Some((SyntaxShape::Any, "".to_string()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_def_with_rest_arg_with_type_and_comment() {
|
||||||
|
let name = "my_func";
|
||||||
|
let sign = "[ ...rest:path # My super cool rest arg]";
|
||||||
|
let (sign, err) = parse_signature(name, &sign.to_string().spanned_unknown());
|
||||||
|
assert!(err.is_none());
|
||||||
|
assert_eq!(
|
||||||
|
sign.rest_positional,
|
||||||
|
Some((SyntaxShape::FilePath, "My super cool rest arg".to_string()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_def_with_param_flag_and_rest() {
|
||||||
|
let name = "my_func";
|
||||||
|
let sign = "[
|
||||||
|
d:string # The required d parameter
|
||||||
|
--xxx(-x) # The all powerful x flag
|
||||||
|
--yyy (-y):int # The accompanying y flag
|
||||||
|
...rest:table # Another rest
|
||||||
|
]";
|
||||||
|
let (sign, err) = parse_signature(name, &sign.to_string().spanned_unknown());
|
||||||
|
assert!(err.is_none());
|
||||||
|
assert_signature_has_flag(
|
||||||
|
&sign,
|
||||||
|
"xxx",
|
||||||
|
NamedType::Optional(Some('x'), SyntaxShape::Any),
|
||||||
|
"The all powerful x flag",
|
||||||
|
);
|
||||||
|
assert_signature_has_flag(
|
||||||
|
&sign,
|
||||||
|
"yyy",
|
||||||
|
NamedType::Optional(Some('y'), SyntaxShape::Int),
|
||||||
|
"The accompanying y flag",
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
sign.positional,
|
||||||
|
vec![(
|
||||||
|
PositionalType::Mandatory("d".into(), SyntaxShape::String),
|
||||||
|
"The required d parameter".into()
|
||||||
|
)]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
sign.rest_positional,
|
||||||
|
Some((SyntaxShape::Table, "Another rest".to_string()))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,14 +48,18 @@ impl Spanned<String> {
|
||||||
) -> impl Iterator<Item = &'a str> {
|
) -> impl Iterator<Item = &'a str> {
|
||||||
items.map(|item| &item.item[..])
|
items.map(|item| &item.item[..])
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Spanned<String> {
|
|
||||||
/// Borrows the contained String
|
/// Borrows the contained String
|
||||||
pub fn borrow_spanned(&self) -> Spanned<&str> {
|
pub fn borrow_spanned(&self) -> Spanned<&str> {
|
||||||
let span = self.span;
|
let span = self.span;
|
||||||
self.item[..].spanned(span)
|
self.item[..].spanned(span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn slice_spanned(&self, span: impl Into<Span>) -> Spanned<&str> {
|
||||||
|
let span = span.into();
|
||||||
|
let item = &self.item[span.start()..span.end()];
|
||||||
|
item.spanned(span)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SpannedItem: Sized {
|
pub trait SpannedItem: Sized {
|
||||||
|
|
Loading…
Reference in a new issue