use std::collections::HashMap;
#[derive(Debug, Default)]
pub struct Args {
pub positionals: Vec<String>,
opts: HashMap<String, Vec<String>>,
}
impl Args {
pub fn parse<I: IntoIterator<Item = String>>(it: I) -> Args {
let v: Vec<String> = it.into_iter().collect();
let mut a = Args::default();
let mut i = 0;
while i < v.len() {
if let Some(k) = v[i].strip_prefix("--") {
let (k, inline) = match k.split_once('=') {
Some((k, val)) => (k, Some(val.to_string())),
None => (k, None),
};
let val = inline.or_else(|| {
v.get(i + 1).filter(|n| !n.starts_with("--")).map(|n| {
i += 1;
n.clone()
})
});
a.opts.entry(k.to_string()).or_default().extend(val);
} else {
a.positionals.push(v[i].clone());
}
i += 1;
}
a
}
pub fn has(&self, k: &str) -> bool {
self.opts.contains_key(k)
}
pub fn opt(&self, k: &str) -> Option<&str> {
self.opts.get(k).and_then(|v| v.last()).map(|s| s.as_str())
}
pub fn opt_or(&self, k: &str) -> String {
self.opt(k).unwrap_or_default().to_string()
}
pub fn list(&self, k: &str) -> Vec<String> {
self.opts.get(k).cloned().unwrap_or_default()
}
pub fn positional(&self, i: usize) -> Option<&str> {
self.positionals.get(i).map(|s| s.as_str())
}
pub fn unknown(&self, permitidas: &[&str]) -> Vec<&str> {
let mut v: Vec<&str> = self
.opts
.keys()
.map(|k| k.as_str())
.filter(|k| !permitidas.contains(k))
.collect();
v.sort_unstable();
v
}
}
#[cfg(test)]
mod tests {
use super::*;
fn p(s: &str) -> Args {
Args::parse(s.split_whitespace().map(String::from))
}
#[test]
fn positionals_and_options() {
let a = p("title --why reason --blocks --ref one --ref two");
assert_eq!(a.positional(0), Some("title"));
assert_eq!(a.opt("why"), Some("reason"));
assert!(a.has("blocks"));
assert_eq!(a.list("ref"), vec!["one", "two"]);
}
#[test]
fn a_flag_next_to_another_flag() {
let a = p("--blocks --why x");
assert!(a.has("blocks"));
assert_eq!(a.opt("blocks"), None);
assert_eq!(a.opt("why"), Some("x"));
}
#[test]
fn an_option_that_does_not_exist_does_not_pass_in_silence() {
let a = p("x --kind finding --type task");
assert_eq!(a.unknown(&["type", "why"]), vec!["kind"]);
assert!(a.unknown(&["type", "kind"]).is_empty());
}
#[test]
fn equals_sign() {
let a = p("--type=decision");
assert_eq!(a.opt("type"), Some("decision"));
}
#[test]
fn a_flag_that_is_not_english_is_unknown_rather_than_ignored() {
let a = p("t --padre 3");
assert_eq!(a.opt("parent"), None);
assert_eq!(a.unknown(&["why", "parent"]), vec!["padre"]);
}
#[test]
fn an_unknown_flag_is_quoted_back_verbatim() {
assert_eq!(p("--nonesuch 1").unknown(&["why"]), vec!["nonesuch"]);
}
}