diff --git a/cli/src/cli/complete_word.rs b/cli/src/cli/complete_word.rs index 827d3e6..e595ff7 100644 --- a/cli/src/cli/complete_word.rs +++ b/cli/src/cli/complete_word.rs @@ -72,7 +72,7 @@ impl CompleteWord { ctx.insert("PREV", &(cword - 1)); } - let parsed = usage::parse::parse(spec, &words)?; + let parsed = usage::parse::parse_partial(spec, &words)?; debug!("parsed cmd: {}", parsed.cmd.full_cmd.join(" ")); let choices = if ctoken == "-" { let shorts = self.complete_short_flag_names(&parsed.available_flags, ""); diff --git a/lib/src/parse.rs b/lib/src/parse.rs index a2af1c6..82de4f3 100644 --- a/lib/src/parse.rs +++ b/lib/src/parse.rs @@ -15,6 +15,7 @@ pub struct ParseOutput { pub flags: IndexMap, pub available_flags: BTreeMap, pub flag_awaiting_value: Option, + pub errors: Vec, } #[derive(Debug, EnumTryAs)] @@ -26,6 +27,14 @@ pub enum ParseValue { } pub fn parse(spec: &Spec, input: &[String]) -> Result { + let out = parse_partial(spec, input)?; + if !out.errors.is_empty() { + bail!("{}", out.errors.join("\n")); + } + Ok(out) +} + +pub fn parse_partial(spec: &Spec, input: &[String]) -> Result { let mut input = input.iter().cloned().collect::>(); input.pop_front(); @@ -54,6 +63,7 @@ pub fn parse(spec: &Spec, input: &[String]) -> Result Result", arg.name)); + } + } + + for flag in out.available_flags.values() { + if flag.required && !out.flags.contains_key(flag) { + out.errors.push(format!( + "missing required option --{} <{}>", + flag.name, flag.name + )); + } + } + Ok(out) } diff --git a/lib/tests/parse.rs b/lib/tests/parse.rs index 3bf5621..09f6fe6 100644 --- a/lib/tests/parse.rs +++ b/lib/tests/parse.rs @@ -20,6 +20,16 @@ macro_rules! tests { } tests! { + required_arg: + spec=r#"arg """#, + args="", + expected=r#"missing required arg "#, + + required_option: + spec=r#"flag "--name " required=true"#, + args="", + expected=r#"missing required option --name "#, + negate: spec=r#"flag "--force" negate="--no-force""#, args="--no-force",