use crate::ArgDef;
use crate::args::Args;
use crate::defs::ArgName;
use crate::defs::Argument;
use crate::defs::ParseArgError;
use std::collections::HashMap;
use std::iter::Peekable;
pub struct ParserContext<'a> {
defs: Vec<ArgDef<'a>>,
short_map: HashMap<char, usize>,
long_map: HashMap<&'a str, usize>,
}
impl<'a> ParserContext<'a> {
pub fn new() -> Self {
Self {
defs: Vec::new(),
short_map: HashMap::new(),
long_map: HashMap::new(),
}
}
pub fn from(defs: Vec<ArgDef<'a>>) -> Self {
let mut ctx = Self::new();
defs.iter().for_each(|def| {
ctx.register(*def).unwrap();
});
ctx
}
pub fn register(&mut self, arg: ArgDef<'a>) -> Result<&Self, String> {
match &arg {
ArgDef::Short(s) => {
if self.short_map.contains_key(s) {
return Err(format!("Short argument -{} already defined", s));
}
}
ArgDef::Long(l) => {
if self.long_map.contains_key(l) {
return Err(format!("Long argument --{} already defined", l));
}
}
ArgDef::ShortAndLong { short, long } => {
if self.short_map.contains_key(short) {
return Err(format!("Short argument -{} already defined", short));
}
if self.long_map.contains_key(long) {
return Err(format!("Long argument --{} already defined", long));
}
}
}
let index = self.defs.len();
match &arg {
ArgDef::Short(s) => {
self.short_map.insert(*s, index);
}
ArgDef::Long(l) => {
self.long_map.insert(l, index);
}
ArgDef::ShortAndLong { short, long } => {
self.short_map.insert(*short, index);
self.long_map.insert(long, index);
}
}
self.defs.push(arg);
Ok(self)
}
}
pub fn parse_with_ctx<'a>(
args: &'a [&str],
ctx: &ParserContext,
) -> Result<Args<'a>, ParseArgError<'a>> {
let mut result = Vec::new();
let mut args = args.iter().peekable();
let mut positional = false;
while let Some(&arg) = args.next() {
if positional {
result.push(parse_positional(arg));
continue;
}
if arg == "--" {
positional = true;
continue;
}
if arg.starts_with("--") {
let parsed = parse_long(arg, &mut args)?;
let name = match parsed.name() {
Some(ArgName::Long(name)) => name,
Some(_) => unreachable!("parse_long should never return a short name"),
None => {
result.push(parsed);
return Ok(Args(result)); }
};
if !ctx.long_map.contains_key(name) {
return Err(ParseArgError::UnknownLong(name.into()));
}
result.push(parsed);
} else if arg.starts_with("-") && arg.len() > 1 {
let mut parsed_args = parse_short(arg, &mut args)?;
for short_arg in &parsed_args {
if let Some(name) = short_arg.name() {
match name {
ArgName::Short(name) => {
if !ctx.short_map.contains_key(&name) {
return Err(ParseArgError::UnknownShort(name.into()));
}
}
_ => unreachable!(
"parse_short should never return an argument with a short name"
),
}
}
}
result.append(&mut parsed_args);
} else {
result.push(parse_positional(arg));
}
}
Ok(Args(result))
}
pub fn parse<'a>(args: &'a [&str]) -> Result<Args<'a>, ParseArgError<'a>> {
let mut result = Vec::new();
let mut args = args.iter().peekable();
let mut positional = false;
while let Some(&arg) = args.next() {
if positional {
result.push(parse_positional(arg));
continue;
}
if arg == "--" {
positional = true;
continue;
}
if arg.starts_with("--") {
result.push(parse_long(arg, &mut args)?);
} else if arg.starts_with("-") {
result.append(&mut parse_short(arg, &mut args)?);
} else {
result.push(parse_positional(arg));
}
}
Ok(Args(result))
}
fn parse_positional(arg: &str) -> Argument {
Argument::Positional { value: arg.into() }
}
fn parse_long<'a, I>(
arg: &'a str,
input: &mut Peekable<I>,
) -> Result<Argument<'a>, ParseArgError<'a>>
where
I: Iterator<Item = &'a &'a str>,
{
if let Some((name, value)) = arg.split_once("=") {
Ok(Argument::Option {
name: ArgName::Long(name),
value,
})
} else if let Some(long_name) = arg.strip_prefix("--") {
if let Some(&next) = input.peek() {
if next.starts_with("-") {
Ok(Argument::Flag {
name: ArgName::Long(long_name),
})
} else {
let value = input.next().unwrap();
Ok(Argument::Option {
name: ArgName::Long(long_name),
value,
})
}
} else {
Ok(Argument::Flag {
name: ArgName::Long(long_name),
})
}
} else {
Err(ParseArgError::MalformedArg(
"Malformed argument at position {index}",
))
}
}
fn parse_short<'a, I>(
arg: &'a str,
input: &mut Peekable<I>,
) -> Result<Vec<Argument<'a>>, ParseArgError<'a>>
where
I: Iterator<Item = &'a &'a str>,
{
if arg.len() < 2 {
return Err(ParseArgError::MalformedArg(
"Malformed argument at position {index}".into(),
));
}
if let Some((names, value)) = arg.split_once('=') {
return Ok(names
.chars()
.skip(1)
.map(|short| Argument::Option {
name: ArgName::Short(short),
value,
})
.collect());
}
let chars: Vec<char> = arg.chars().skip(1).collect();
if let Some(&next) = input.peek() {
if !next.starts_with('-') {
let value = input.next().unwrap(); return Ok(chars
.into_iter()
.map(|short| Argument::Option {
name: ArgName::Short(short),
value,
})
.collect());
}
}
Ok(chars
.into_iter()
.map(|short| Argument::Flag {
name: ArgName::Short(short),
})
.collect())
}