use super::declaration::{Positional, Tail, Verb, WrappedCommand};
use super::error::WrappedError;
const DYNAMIC: &str = "<dynamic>";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Known {
Literal,
Opaque,
OpaqueValue,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Word {
pub(crate) text: String,
pub(crate) known: Known,
}
impl Word {
pub(crate) fn literal(text: impl Into<String>) -> Self {
Self {
text: text.into(),
known: Known::Literal,
}
}
pub(crate) fn from_validation_text(text: impl Into<String>) -> Self {
let text = text.into();
let known = if text == DYNAMIC {
Known::Opaque
} else if text.starts_with("--") && text.ends_with(&format!("={DYNAMIC}")) {
Known::OpaqueValue
} else {
Known::Literal
};
Self { text, known }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct FlagUse {
pub(crate) flag_index: usize,
pub(crate) value: Option<String>,
pub(crate) value_known: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Item {
Positional {
slot: usize,
value: String,
known: bool,
},
Flag(usize),
DashDash,
Undeclared(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Call {
pub(crate) verb_index: Option<usize>,
pub(crate) flags: Vec<FlagUse>,
pub(crate) items: Vec<Item>,
pub(crate) uncertain: bool,
}
impl Call {
fn unjudgeable() -> Self {
Self {
verb_index: None,
flags: Vec::new(),
items: Vec::new(),
uncertain: true,
}
}
pub(crate) fn verb<'d>(&self, declaration: &'d WrappedCommand) -> Option<&'d Verb> {
match self.verb_index {
Some(index) => declaration.verbs.get(index),
None => declaration.root.as_ref(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ParseError {
pub(crate) error: WrappedError,
pub(crate) uncertain: bool,
}
pub(crate) fn parse(declaration: &WrappedCommand, words: &[Word]) -> Result<Call, ParseError> {
let Some((verb_index, mut index)) = select_verb(declaration, words)? else {
return Ok(Call::unjudgeable());
};
let Some(verb) = (match verb_index {
Some(i) => declaration.verbs.get(i),
None => declaration.root.as_ref(),
}) else {
return Ok(Call::unjudgeable());
};
let scope = declaration.scope_of(verb);
let mut call = Call {
verb_index,
flags: Vec::new(),
items: Vec::new(),
uncertain: false,
};
let mut past_dash_dash = false;
let mut slot = 0usize;
macro_rules! refuse {
($result:expr) => {
if let Err(error) = $result {
return Err(ParseError {
error,
uncertain: call.uncertain,
});
}
};
}
while index < words.len() {
let word = &words[index];
index += 1;
if past_dash_dash {
refuse!(fill_positional(
declaration,
verb,
&scope,
&mut call,
&mut slot,
&word.text,
word.known,
true,
));
continue;
}
match word.known {
Known::Opaque => {
call.uncertain = true;
refuse!(fill_positional(
declaration,
verb,
&scope,
&mut call,
&mut slot,
&word.text,
word.known,
false,
));
continue;
}
Known::Literal | Known::OpaqueValue => {}
}
if word.text == "--" {
past_dash_dash = true;
call.items.push(Item::DashDash);
continue;
}
if word.text.starts_with('-') {
refuse!(bind_flag(
declaration,
verb,
&scope,
&mut call,
words,
&mut index,
word,
));
continue;
}
refuse!(fill_positional(
declaration,
verb,
&scope,
&mut call,
&mut slot,
&word.text,
word.known,
false,
));
}
Ok(call)
}
fn select_verb(
declaration: &WrappedCommand,
words: &[Word],
) -> Result<Option<(Option<usize>, usize)>, ParseError> {
if declaration.verbs.is_empty() {
return Ok(Some((None, 0)));
}
let allowed = allowed_verbs(declaration);
let missing = |uncertain: bool| ParseError {
error: WrappedError::MissingVerb {
command: declaration.name.clone(),
allowed: allowed.clone(),
},
uncertain,
};
let Some(first) = words.first() else {
return match declaration.root {
Some(_) => Ok(Some((None, 0))),
None => Err(missing(false)),
};
};
match first.known {
Known::Literal if !first.text.starts_with('-') => {
match declaration
.verbs
.iter()
.position(|v| v.name_or_root() == first.text)
{
Some(index) => Ok(Some((Some(index), 1))),
None if declaration.root.is_some() => Ok(Some((None, 0))),
None => Err(ParseError {
error: WrappedError::UnknownVerb {
command: declaration.name.clone(),
word: first.text.clone(),
allowed,
},
uncertain: false,
}),
}
}
Known::Literal => match declaration.root {
Some(_) => Ok(Some((None, 0))),
None => Err(missing(false)),
},
Known::Opaque | Known::OpaqueValue => match declaration.root {
Some(_) => Ok(Some((None, 0))),
None => Ok(None),
},
}
}
fn bind_flag(
declaration: &WrappedCommand,
verb: &Verb,
scope: &str,
call: &mut Call,
words: &[Word],
index: &mut usize,
word: &Word,
) -> Result<(), WrappedError> {
let (head, inline) = match word.text.strip_prefix("--").and_then(|rest| {
rest.find('=')
.map(|eq| (&word.text[..eq + 2], word.text[eq + 3..].to_string()))
}) {
Some((head, value)) => (head.to_string(), Some(value)),
None => (word.text.clone(), None),
};
let Some(flag_index) = verb
.flags
.iter()
.position(|flag| flag.matches(&head))
else {
if verb.tail == Tail::Forward {
call.items.push(Item::Undeclared(word.text.clone()));
return Ok(());
}
return Err(unknown_flag(declaration, verb, scope, &word.text, &head));
};
let flag = &verb.flags[flag_index];
if !flag.repeatable && call.flags.iter().any(|use_| use_.flag_index == flag_index) {
return Err(WrappedError::RepeatedFlag {
command: declaration.name.clone(),
scope: scope.to_string(),
flag: flag.written_name(),
});
}
if !flag.takes_value {
if inline.is_some() {
return Err(WrappedError::UnexpectedFlagValue {
command: declaration.name.clone(),
scope: scope.to_string(),
flag: head,
});
}
call.items.push(Item::Flag(call.flags.len()));
call.flags.push(FlagUse {
flag_index,
value: None,
value_known: true,
});
return Ok(());
}
let (value, value_known) = match inline {
Some(value) => (value, word.known == Known::Literal),
None => {
let Some(next) = words.get(*index) else {
return Err(WrappedError::MissingFlagValue {
command: declaration.name.clone(),
scope: scope.to_string(),
flag: head,
});
};
*index += 1;
(next.text.clone(), next.known == Known::Literal)
}
};
call.items.push(Item::Flag(call.flags.len()));
call.flags.push(FlagUse {
flag_index,
value: Some(value),
value_known,
});
Ok(())
}
fn unknown_flag(
declaration: &WrappedCommand,
verb: &Verb,
scope: &str,
word: &str,
head: &str,
) -> WrappedError {
if let Some(separated) = clustered_shorts(verb, word) {
return WrappedError::ClusteredShort {
command: declaration.name.clone(),
scope: scope.to_string(),
word: word.to_string(),
separated,
};
}
if let Some(separated) = glued_short_value(verb, word) {
return WrappedError::GluedShortValue {
command: declaration.name.clone(),
scope: scope.to_string(),
word: word.to_string(),
separated,
};
}
WrappedError::UnknownFlag {
command: declaration.name.clone(),
scope: scope.to_string(),
word: head.to_string(),
allowed: allowed_flags(verb),
}
}
fn clustered_shorts(verb: &Verb, word: &str) -> Option<String> {
let rest = word.strip_prefix('-')?;
if rest.starts_with('-') || rest.chars().count() < 2 {
return None;
}
let mut separated = Vec::new();
for ch in rest.chars() {
let short = format!("-{ch}");
let flag = verb
.flags
.iter()
.find(|flag| flag.matches(&short))?;
if flag.takes_value {
return None;
}
separated.push(short);
}
Some(separated.join(" "))
}
fn glued_short_value(verb: &Verb, word: &str) -> Option<String> {
let rest = word.strip_prefix('-')?;
if rest.starts_with('-') || rest.chars().count() < 2 {
return None;
}
let mut chars = rest.chars();
let first = chars.next()?;
let short = format!("-{first}");
let flag = verb
.flags
.iter()
.find(|flag| flag.matches(&short))?;
if !flag.takes_value {
return None;
}
Some(format!("{short} {}", chars.as_str()))
}
#[allow(clippy::too_many_arguments)]
fn fill_positional(
declaration: &WrappedCommand,
verb: &Verb,
scope: &str,
call: &mut Call,
slot: &mut usize,
text: &str,
known: Known,
past_dash_dash: bool,
) -> Result<(), WrappedError> {
if let Some(Positional { many, .. }) = verb.positionals.get(*slot) {
call.items.push(Item::Positional {
slot: *slot,
value: text.to_string(),
known: known == Known::Literal,
});
if !many {
*slot += 1;
}
return Ok(());
}
match (verb.tail, past_dash_dash) {
(Tail::Forward, _) | (Tail::AfterDashDash, true) => {
call.items.push(Item::Undeclared(text.to_string()));
Ok(())
}
(Tail::AfterDashDash, false) => Err(WrappedError::UndeclaredPositional {
command: declaration.name.clone(),
scope: scope.to_string(),
word: text.to_string(),
}),
(Tail::Deny, _) => Err(WrappedError::UnexpectedArgument {
command: declaration.name.clone(),
word: text.to_string(),
}),
}
}
pub(crate) fn allowed_verbs(declaration: &WrappedCommand) -> Vec<String> {
let mut allowed: Vec<String> = declaration
.verbs
.iter()
.map(|verb| verb.name_or_root().to_string())
.collect();
allowed.sort();
allowed
}
pub(crate) fn allowed_flags(verb: &Verb) -> Vec<String> {
let mut flags: Vec<&super::declaration::Flag> = verb.flags.iter().collect();
flags.sort_by(|a, b| a.name.cmp(&b.name));
flags.iter().map(|flag| flag.allowed_spelling()).collect()
}