use std::ffi::OsString;
use super::{Args, Error, Item, OptionParser, Parser, Rc};
use crate::{
args::{Arg, Word},
info::{ItemKind, Meta},
};
#[derive(Clone, Debug)]
pub struct Named {
short: Vec<char>,
long: Vec<&'static str>,
help: Option<String>,
}
#[must_use]
pub fn short(short: char) -> Named {
Named {
short: vec![short],
long: Vec::new(),
help: None,
}
}
#[must_use]
pub fn long(long: &'static str) -> Named {
Named {
short: Vec::new(),
long: vec![long],
help: None,
}
}
impl Named {
#[must_use]
pub fn short(mut self, short: char) -> Self {
self.short.push(short);
self
}
#[must_use]
pub fn long(mut self, long: &'static str) -> Self {
self.long.push(long);
self
}
#[must_use]
pub fn help<M>(mut self, help: M) -> Self
where
M: Into<String>,
{
self.help = Some(help.into());
self
}
#[must_use]
pub fn switch(self) -> Parser<bool> {
build_flag_parser(true, Some(false), self.short, self.long, self.help)
}
#[must_use]
pub fn flag<T>(self, present: T, absent: T) -> Parser<T>
where
T: Clone + 'static,
{
build_flag_parser(present, Some(absent), self.short, self.long, self.help)
}
#[must_use]
pub fn req_flag<T>(self, present: T) -> Parser<T>
where
T: Clone + 'static,
{
build_flag_parser(present, None, self.short, self.long, self.help)
}
#[must_use]
pub fn argument(self, metavar: &'static str) -> Parser<String> {
build_argument(self.short, self.long, self.help, metavar)
.parse(|x| x.utf8.ok_or("not utf8")) }
#[must_use]
pub fn argument_os(self, metavar: &'static str) -> Parser<OsString> {
build_argument(self.short, self.long, self.help, metavar).map(|x| x.os)
}
}
#[must_use]
pub fn positional(metavar: &'static str) -> Parser<String> {
build_positional(metavar).parse(|x| x.utf8.ok_or("not utf8")) }
pub fn positional_if<F>(metavar: &'static str, check: F) -> Parser<Option<String>>
where
F: Fn(&str) -> bool + 'static,
{
let check = move |w: &Word| match &w.utf8 {
Some(s) => check(s),
None => false,
};
build_positional_if(metavar, check).parse(|x| match x {
Some(Word { utf8: Some(w), .. }) => Ok(Some(w)),
Some(_) => Err("not utf8"),
None => Ok(None),
})
}
#[must_use]
pub fn positional_os(metavar: &'static str) -> Parser<OsString> {
build_positional(metavar).map(|x| x.os)
}
#[must_use]
pub fn command<T, M>(name: &'static str, help: Option<M>, subparser: OptionParser<T>) -> Parser<T>
where
T: 'static,
M: Into<String>,
{
let meta = Meta::from(Item {
short: None,
long: Some(name),
metavar: None,
help: help.map(Into::into),
kind: ItemKind::Command,
});
let meta2 = meta.clone();
let parse = move |mut args: Args| {
if args.take_cmd(name) {
(subparser.parse)(args)
} else {
Err(Error::Missing(vec![meta2.clone()]))
}
};
Parser {
parse: Rc::new(parse),
meta,
}
}
fn short_or_long_flag(arg: &Arg, shorts: &[char], longs: &[&str]) -> bool {
shorts.iter().any(|&c| arg.is_short(c)) || longs.iter().any(|s| arg.is_long(s))
}
fn build_flag_parser<T>(
present: T,
absent: Option<T>,
shorts: Vec<char>,
longs: Vec<&'static str>,
help: Option<String>,
) -> Parser<T>
where
T: Clone + 'static,
{
let item = Item {
short: shorts.first().copied(),
long: longs.first().copied(),
metavar: None,
help,
kind: ItemKind::Flag,
};
let required = absent.is_none();
let meta = item.required(required);
let missing = if required {
Error::Missing(vec![meta.clone()])
} else {
Error::Stdout(String::new())
};
let parse = move |mut args: Args| {
if args.take_flag(|arg| short_or_long_flag(arg, &shorts, &longs)) {
Ok((present.clone(), args))
} else {
Ok((
absent.as_ref().ok_or_else(|| missing.clone())?.clone(),
args,
))
}
};
Parser {
parse: Rc::new(parse),
meta,
}
}
fn build_argument(
shorts: Vec<char>,
longs: Vec<&'static str>,
help: Option<String>,
metavar: &'static str,
) -> Parser<Word> {
let item = Item {
kind: ItemKind::Flag,
short: shorts.first().copied(),
long: longs.first().copied(),
metavar: Some(metavar),
help,
};
let meta = item.required(true);
let meta2 = meta.clone();
let parse = move |mut args: Args| {
#[allow(clippy::option_if_let_else)]
if let Some(w) = args.take_arg(|arg| short_or_long_flag(arg, &shorts, &longs))? {
Ok((w, args))
} else {
Err(Error::Missing(vec![meta2.clone()]))
}
};
Parser {
parse: Rc::new(parse),
meta,
}
}
fn build_positional(metavar: &'static str) -> Parser<Word> {
let item = Item {
short: None,
long: None,
metavar: Some(metavar),
help: None,
kind: ItemKind::Positional,
};
let meta = item.required(true);
let meta2 = meta.clone();
let parse = move |mut args: Args| match args.take_positional_word()? {
Some(word) => Ok((word, args)),
None => Err(Error::Missing(vec![meta2.clone()])),
};
Parser {
parse: Rc::new(parse),
meta,
}
}
fn build_positional_if<F>(metavar: &'static str, check: F) -> Parser<Option<Word>>
where
F: Fn(&Word) -> bool + 'static,
{
let item = Item {
short: None,
long: None,
metavar: Some(metavar),
help: None,
kind: ItemKind::Positional,
};
let meta = item.required(false);
let meta2 = meta.clone();
let parse = move |mut args: Args| match args.peek() {
Some(Arg::Word(w_ref)) => {
if check(w_ref) {
let w_owned = args
.take_positional_word()?
.expect("We just confirmed it's there");
Ok((Some(w_owned), args))
} else {
Ok((None, args))
}
}
Some(_) => Err(Error::Missing(vec![meta2.clone()])),
None => Ok((None, args)),
};
Parser {
parse: Rc::new(parse),
meta,
}
}