Skip to content

Commit

Permalink
feat: distinct multi-occurs and unlimited multi-args (#255)
Browse files Browse the repository at this point in the history
  • Loading branch information
sigoden authored Oct 11, 2023
1 parent cf2cb38 commit 44310b5
Show file tree
Hide file tree
Showing 19 changed files with 754 additions and 166 deletions.
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ Define a positional argument.
### @option

```
@option [short] <long>[modifier|default|modifier+choices] [value-notation]... [help-string]
@option [short] <long>[modifier|default|modifier+choices] [value-notations] [help-string]
```

Define a option.
Expand All @@ -143,20 +143,21 @@ Define a option.
# @option -b --ob short
# @option -c short only
# @option --oc! required
# @option --od* multiple
# @option --oe+ required + multiple
# @option --od* multi-occurs
# @option --oe+ required + multi-occurs
# @option --ona <PATH> value notation
# @option --onb <NAME> <FILE> multiple value notations
# @option --onb <CMD> <FILE> two-args value notations
# @option --onc <CMD> <FILE+> unlimited-args value notations
# @option --oda=a default
# @option --odb=`_default_fn` default from fn
# @option --oca[a|b] choice
# @option --ocb[=a|b] choice + default
# @option --occ*[a|b] multiple + choice
# @option --ocd+[a|b] required + multiple + choice
# @option --occ*[a|b] multi-occurs + choice
# @option --ocd+[a|b] required + multi-occurs + choice
# @option --ofa[`_choice_fn`] choice from fn
# @option --ofb[?`_choice_fn`] choice from fn + no validation
# @option --ofc*[`_choice_fn`] multiple + choice from fn
# @option --ox~ capture all remaing args
# @option --ofc*[`_choice_fn`] multi-occurs + choice from fn
# @option --oxa~ capture all remaing args
```

### @flag
Expand All @@ -172,8 +173,8 @@ Define a flag. A flag is an option of boolean type, and is always false by defau
# @flag --fa
# @flag -b --fb shoft
# @flag -c shoft only
# @flag --fd* multiple
# @flag -e --fe* short + multiple
# @flag --fd* multi-occurs
# @flag -e --fe* short + multi-occurs
```

### @alias
Expand Down
19 changes: 10 additions & 9 deletions examples/options.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@
# @option -b --ob short
# @option -c short only
# @option --oc! required
# @option --od* multiple
# @option --oe+ required + multiple
# @option --od* multi-occurs
# @option --oe+ required + multi-occurs
# @option --ona <PATH> value notation
# @option --onb <NAME> <FILE> multiple value notations
# @option --onb <CMD> <FILE> two-args value notations
# @option --onc <CMD> <FILE+> unlimited-args value notations
# @option --oda=a default
# @option --odb=`_default_fn` default from fn
# @option --oca[a|b] choice
# @option --ocb[=a|b] choice + default
# @option --occ*[a|b] multiple + choice
# @option --ocd+[a|b] required + multiple + choice
# @option --occ*[a|b] multi-occurs + choice
# @option --ocd+[a|b] required + multi-occurs + choice
# @option --ofa[`_choice_fn`] choice from fn
# @option --ofb[?`_choice_fn`] choice from fn + no validation
# @option --ofc*[`_choice_fn`] multiple + choice from fn
# @option --ox~ capture all remaing args
# @option --ofc*[`_choice_fn`] multi-occurs + choice from fn
# @option --oxa~ capture all remaing args
options() {
:;
}
Expand All @@ -27,8 +28,8 @@ options() {
# @flag --fa
# @flag -b --fb shoft
# @flag -c shoft only
# @flag --fd* multiple
# @flag -e --fe* short + multiple
# @flag --fd* multi-occurs
# @flag -e --fe* short + multi-occurs
flags() {
:;
}
Expand Down
14 changes: 7 additions & 7 deletions src/bin/argc/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ fn run() -> Result<i32> {
"--argc-completions" => {
let shell: Shell = match args.get(2) {
Some(v) => v.parse()?,
None => bail!("Usage: argc --argc-completions <SHELL> [CMDS...]"),
None => bail!("Usage: argc --argc-completions <SHELL> [CMDS]..."),
};
let script = crate::completions::generate(shell, &args[3..])?;
println!("{}", script);
}
"--argc-parallel" => {
if args.len() <= 3 {
bail!("Usage: argc --argc-parallel <SCRIPT> <ARGS...>");
bail!("Usage: argc --argc-parallel <SCRIPT> <ARGS>...");
}
let shell = get_shell_path().ok_or_else(|| anyhow!("Shell not found"))?;
let (source, cmd_args) = parse_script_args(&args[2..])?;
Expand Down Expand Up @@ -222,12 +222,12 @@ fn get_argc_help() -> String {
r###"{about}
USAGE:
argc --argc-eval <SCRIPT> [ARGS...] Use `eval "$(argc --argc-eval "$0" "$@")"`
argc --argc-create [TASKS...] Create a boilerplate argcfile
argc --argc-completions <SHELL> [CMDS...] Generate completion scripts for bash,elvish,fish,nushell,powershell,xsh,zsh
argc --argc-compgen <SHELL> <SCRIPT> <ARGS...> Generate dynamic completion word
argc --argc-eval <SCRIPT> [ARGS]... Use `eval "$(argc --argc-eval "$0" "$@")"`
argc --argc-create [TASKS]... Create a boilerplate argcfile
argc --argc-completions <SHELL> [CMDS]... Generate completion scripts for bash,elvish,fish,nushell,powershell,xsh,zsh
argc --argc-compgen <SHELL> <SCRIPT> <ARGS>... Generate dynamic completion word
argc --argc-export <SCRIPT> Export command line definitions as json
argc --argc-parallel <SCRIPT> <ARGS...> Execute argc functions in parallel
argc --argc-parallel <SCRIPT> <ARGS>... Execute argc functions in parallel
argc --argc-script-path Print current argcfile path
argc --argc-help Print help information
argc --argc-version Print version information
Expand Down
9 changes: 3 additions & 6 deletions src/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ impl<'a, 'b> Matcher<'a, 'b> {
if let Some(param) = cmd.flag_option_params.iter().find(|v| v.name() == name) {
let values_list: Vec<&[&str]> =
indexes.iter().map(|v| args[*v].1.as_slice()).collect();
if !param.multiple() && values_list.len() > 1 {
if !param.multi_occurs() && values_list.len() > 1 {
return Some(MatchError::NotMultipleArgument(level, param.render_name()));
}
for values in values_list.iter() {
Expand All @@ -610,10 +610,7 @@ impl<'a, 'b> Matcher<'a, 'b> {
param.render_name(),
values[0].to_string(),
));
} else if (param.ellipsis()
&& values.len() + 1 < param.arg_value_names.len())
|| (!param.ellipsis() && values.len() != param.arg_value_names.len())
{
} else if !param.validate_args_len(values.len()) {
return Some(MatchError::MismatchValues(
level,
param.render_name_values(),
Expand Down Expand Up @@ -968,7 +965,7 @@ fn match_flag_option<'a, 'b>(
flag_option_args.push((arg, value_args, Some(param.name())));
} else {
let mut values_len = param.arg_value_names.len();
if param.ellipsis() {
if param.unlimited_args() {
values_len = usize::MAX / 2;
}
let args_len = args.len();
Expand Down
124 changes: 84 additions & 40 deletions src/param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,17 @@ impl FlagOptionParam {
) -> Self {
let name = param.name.clone();
let value_names: Vec<String> = value_names.iter().map(|v| v.to_string()).collect();
let arg_value_names = if flag {
let mut arg_value_names = if flag {
vec![]
} else if value_names.is_empty() {
vec![to_cobol_case(&name)]
} else {
value_names.iter().map(|v| to_cobol_case(v)).collect()
};
if param.terminated() {
let last_arg = arg_value_names.last_mut().unwrap();
last_arg.push('~')
}
Self {
describe: describe.to_string(),
short,
Expand Down Expand Up @@ -69,7 +73,44 @@ impl FlagOptionParam {
}

pub(crate) fn multiple(&self) -> bool {
self.data.multiple()
self.multi_occurs() || self.multi_values()
}

pub(crate) fn multi_occurs(&self) -> bool {
self.data.multi_occurs()
}

pub(crate) fn multi_values(&self) -> bool {
self.unlimited_args() || self.arg_value_names.len() > 1
}

pub(crate) fn unlimited_args(&self) -> bool {
self.terminated() || !self.notation_modifer().is_none()
}

pub(crate) fn validate_args_len(&self, num: usize) -> bool {
let len = self.arg_value_names.len();
if self.unlimited_args() {
let min = if len > 1 && self.notation_modifer() == NotationModifier::Asterisk {
len - 1
} else {
len
};
num >= min
} else {
num == len
}
}

pub(crate) fn notation_modifer(&self) -> NotationModifier {
if let Some(notation) = self.arg_value_names.last() {
if notation.ends_with('*') {
return NotationModifier::Asterisk;
} else if notation.ends_with('+') {
return NotationModifier::Plus;
}
}
NotationModifier::None
}

pub(crate) fn required(&self) -> bool {
Expand Down Expand Up @@ -130,7 +171,10 @@ impl FlagOptionParam {

pub(crate) fn render_name_values(&self) -> String {
let mut output = self.render_name();
output.push_str(&self.render_arg_values());
if !self.is_flag() {
output.push(' ');
output.push_str(&self.render_arg_values());
}
output
}

Expand All @@ -153,10 +197,11 @@ impl FlagOptionParam {
}

if self.is_flag() {
if self.multiple() {
if self.multi_occurs() {
output.push_str("...")
}
} else {
output.push(' ');
output.push_str(&self.render_arg_values());
}
output
Expand All @@ -166,31 +211,24 @@ impl FlagOptionParam {
if self.is_flag() {
return String::new();
}
let mut output = " ".to_string();
if self.arg_value_names.len() == 1 {
let name: &String = &self.arg_value_names[0];
let value = match (self.required(), self.multiple()) {
(true, true) => format!("<{name}>..."),
(false, true) => format!("[{name}]..."),
(_, false) => format!("<{name}>"),
};
output.push_str(&value);
let output = self
.arg_value_names
.iter()
.map(|v| {
if self.multi_occurs() {
v.to_string()
} else {
format!("<{v}>")
}
})
.collect::<Vec<String>>()
.join(" ");

if self.multi_occurs() {
format!("[{output}]...")
} else {
let values = self
.arg_value_names
.iter()
.enumerate()
.map(|(i, v)| {
if self.ellipsis() && i == self.arg_value_names.len() - 1 {
format!("<{v}>...")
} else {
format!("<{v}>")
}
})
.collect::<Vec<String>>();
output.push_str(&values.join(" "));
output
}
output
}

pub(crate) fn render_describe(&self) -> String {
Expand Down Expand Up @@ -273,14 +311,6 @@ impl FlagOptionParam {
output
}

pub(crate) fn multi_occurs(&self) -> bool {
self.multiple() && (self.flag || self.arg_value_names.len() == 1)
}

pub(crate) fn ellipsis(&self) -> bool {
self.terminated() || (self.multiple() && self.arg_value_names.len() > 1)
}

pub(crate) fn describe_head(&self) -> &str {
match self.describe.split_once('\n') {
Some((v, _)) => v,
Expand Down Expand Up @@ -334,7 +364,7 @@ impl PositionalParam {
}

pub(crate) fn multiple(&self) -> bool {
self.data.multiple()
self.data.multi_occurs() || self.terminated()
}

pub(crate) fn required(&self) -> bool {
Expand Down Expand Up @@ -451,8 +481,8 @@ impl ParamData {
}
}

pub(crate) fn multiple(&self) -> bool {
self.modifer.multiple()
pub(crate) fn multi_occurs(&self) -> bool {
self.modifer.multi_occurs()
}

pub(crate) fn required(&self) -> bool {
Expand Down Expand Up @@ -564,6 +594,20 @@ impl ParamData {
}
}

#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
#[serde(tag = "type", content = "value")]
pub(crate) enum NotationModifier {
None,
Plus,
Asterisk,
}

impl NotationModifier {
pub(crate) fn is_none(&self) -> bool {
self == &NotationModifier::None
}
}

#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
#[serde(tag = "type", content = "value")]
pub(crate) enum Modifier {
Expand All @@ -579,15 +623,15 @@ pub(crate) enum Modifier {
}

impl Modifier {
pub(crate) fn multiple(&self) -> bool {
pub(crate) fn multi_occurs(&self) -> bool {
match self {
Self::Optional => false,
Self::Required => false,
Self::MultipleOptional => true,
Self::MultipleRequired => true,
Self::MultiCharOptional(_) => true,
Self::MultiCharRequired(_) => true,
Self::Terminated => true,
Self::Terminated => false,
Self::Prefixed => false,
Self::MultiPrefixed => true,
}
Expand Down
2 changes: 1 addition & 1 deletion src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ fn parse_zero_or_one_value_notation(input: &str) -> nom::IResult<&str, Option<&s
// Parse '<FOO>'
fn parse_value_notation(input: &str) -> nom::IResult<&str, &str> {
preceded(
one_of(" "),
char(' '),
delimited(char('<'), parse_notation_text, char('>')),
)(input)
}
Expand Down
Loading

0 comments on commit 44310b5

Please sign in to comment.