#![doc = include_str!("docs/flag.md")]
#![doc = include_str!("docs/req_flag.md")]
#![doc = include_str!("docs/switch.md")]
#![doc = include_str!("docs/argument.md")]
#![doc = include_str!("docs/positional.md")]
#![doc = include_str!("docs/any.md")]
#![doc = include_str!("docs/command.md")]
use std::{ffi::OsString, marker::PhantomData, str::FromStr};
use super::{Args, Error, OptionParser, Parser};
use crate::{args::Arg, from_os_str::parse_os_str, item::ShortLong, Item, Meta};
#[derive(Clone, Debug)]
pub struct NamedArg {
pub(crate) short: Vec<char>,
pub(crate) long: Vec<&'static str>,
env: Vec<&'static str>,
pub(crate) help: Option<String>,
}
impl NamedArg {
pub(crate) fn flag_item(&self) -> Item {
Item::Flag {
name: ShortLong::from(self),
help: self.help.clone(),
shorts: self.short.clone(),
}
}
}
#[doc = include_str!("docs/short_long_env.md")]
#[must_use]
pub fn short(short: char) -> NamedArg {
NamedArg {
short: vec![short],
env: Vec::new(),
long: Vec::new(),
help: None,
}
}
#[doc = include_str!("docs/short_long_env.md")]
#[must_use]
pub fn long(long: &'static str) -> NamedArg {
NamedArg {
short: Vec::new(),
long: vec![long],
env: Vec::new(),
help: None,
}
}
#[doc = include_str!("docs/short_long_env.md")]
#[must_use]
pub fn env(variable: &'static str) -> NamedArg {
NamedArg {
short: Vec::new(),
long: Vec::new(),
help: None,
env: vec![variable],
}
}
impl NamedArg {
#[doc = include_str!("docs/short_long_env.md")]
#[must_use]
pub fn short(mut self, short: char) -> Self {
self.short.push(short);
self
}
#[doc = include_str!("docs/short_long_env.md")]
#[must_use]
pub fn long(mut self, long: &'static str) -> Self {
self.long.push(long);
self
}
#[doc = include_str!("docs/short_long_env.md")]
#[must_use]
pub fn env(mut self, variable: &'static str) -> Self {
self.env.push(variable);
self
}
#[must_use]
pub fn help<M>(mut self, help: M) -> Self
where
M: Into<String>,
{
self.help = Some(help.into());
self
}
#[doc = include_str!("docs/switch.md")]
#[must_use]
pub fn switch(self) -> impl Parser<bool> {
build_flag_parser(true, Some(false), self)
}
#[doc = include_str!("docs/flag.md")]
#[must_use]
pub fn flag<T>(self, present: T, absent: T) -> impl Parser<T>
where
T: Clone + 'static,
{
build_flag_parser(present, Some(absent), self)
}
#[doc = include_str!("docs/req_flag.md")]
#[must_use]
pub fn req_flag<T>(self, present: T) -> impl Parser<T>
where
T: Clone + 'static,
{
build_flag_parser(present, None, self)
}
#[doc = include_str!("docs/argument.md")]
#[must_use]
pub fn argument<T>(self, metavar: &'static str) -> ParseArgument<T>
where
T: FromStr + 'static,
{
build_argument(self, metavar)
}
#[track_caller]
pub(crate) fn matches_arg(&self, arg: &Arg, adjacent: bool) -> bool {
match arg {
Arg::Short(s, is_adj, _) => self.short.contains(s) && (!adjacent || *is_adj),
Arg::Long(l, is_adj, _) => self.long.contains(&l.as_str()) && (!adjacent || *is_adj),
Arg::Word(_) | Arg::PosWord(_) | Arg::Ambiguity(..) => false,
}
}
}
#[doc = include_str!("docs/positional.md")]
#[must_use]
pub fn positional<T>(metavar: &'static str) -> ParsePositional<T> {
build_positional(metavar)
}
#[doc = include_str!("docs/command.md")]
#[must_use]
pub fn command<T>(name: &'static str, subparser: OptionParser<T>) -> ParseCommand<T>
where
T: 'static,
{
ParseCommand {
longs: vec![name],
shorts: Vec::new(),
help: subparser.short_descr().map(Into::into),
subparser,
}
}
pub struct ParseCommand<T> {
longs: Vec<&'static str>,
shorts: Vec<char>,
help: Option<String>,
subparser: OptionParser<T>,
}
impl<P> ParseCommand<P> {
#[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 short(mut self, short: char) -> Self {
self.shorts.push(short);
self
}
#[must_use]
pub fn long(mut self, long: &'static str) -> Self {
self.longs.push(long);
self
}
}
impl<T> Parser<T> for ParseCommand<T> {
fn eval(&self, args: &mut Args) -> Result<T, Error> {
let mut tmp = String::new();
if self.longs.iter().any(|long| args.take_cmd(long))
|| self.shorts.iter().any(|s| {
tmp.clear();
tmp.push(*s);
args.take_cmd(&tmp)
})
{
#[cfg(feature = "autocomplete")]
if args.touching_last_remove() {
args.clear_comps();
args.push_command(self.longs[0], self.shorts.first().copied(), &self.help);
return Err(Error::Missing(Vec::new()));
}
args.head = usize::MAX;
args.depth += 1;
#[allow(clippy::let_and_return)]
let res = self.subparser.run_subparser(args);
res
} else {
#[cfg(feature = "autocomplete")]
args.push_command(self.longs[0], self.shorts.first().copied(), &self.help);
Err(Error::Missing(vec![self.item()]))
}
}
fn meta(&self) -> Meta {
Meta::Item(self.item())
}
}
impl<T> ParseCommand<T> {
fn item(&self) -> Item {
Item::Command {
name: self.longs[0],
short: self.shorts.first().copied(),
help: self.help.clone(),
meta: Box::new(self.subparser.inner.meta()),
info: Box::new(self.subparser.info.clone()),
}
}
}
fn build_flag_parser<T>(present: T, absent: Option<T>, named: NamedArg) -> ParseFlag<T>
where
T: Clone + 'static,
{
ParseFlag {
present,
absent,
named,
}
}
#[derive(Clone)]
struct ParseFlag<T> {
present: T,
absent: Option<T>,
named: NamedArg,
}
impl<T: Clone + 'static> Parser<T> for ParseFlag<T> {
fn eval(&self, args: &mut Args) -> Result<T, Error> {
if args.take_flag(&self.named) || self.named.env.iter().find_map(std::env::var_os).is_some()
{
#[cfg(feature = "autocomplete")]
if args.touching_last_remove() {
args.push_flag(&self.named);
}
Ok(self.present.clone())
} else {
#[cfg(feature = "autocomplete")]
args.push_flag(&self.named);
match &self.absent {
Some(ok) => Ok(ok.clone()),
None => Err(Error::Missing(vec![self.named.flag_item()])),
}
}
}
fn meta(&self) -> Meta {
self.named.flag_item().required(self.absent.is_none())
}
}
fn build_argument<T>(named: NamedArg, metavar: &'static str) -> ParseArgument<T> {
if !named.env.is_empty() {
assert!(
!(named.short.is_empty() && named.long.is_empty()),
"env fallback can only be used if name is present"
);
}
ParseArgument {
named,
metavar,
ty: PhantomData,
adjacent: false,
}
}
#[derive(Clone)]
pub struct ParseArgument<T> {
ty: PhantomData<T>,
named: NamedArg,
metavar: &'static str,
adjacent: bool,
}
impl<T> ParseArgument<T> {
#[doc = include_str!("docs/pos_adjacent.md")]
#[must_use]
pub fn adjacent(mut self) -> Self {
self.adjacent = true;
self
}
fn item(&self) -> Item {
Item::Argument {
name: ShortLong::from(&self.named),
metavar: self.metavar,
env: self.named.env.first().copied(),
help: self.named.help.clone(),
shorts: self.named.short.clone(),
}
}
fn take_argument(&self, args: &mut Args) -> Result<OsString, Error> {
match args.take_arg(&self.named, self.adjacent) {
Ok(Some(w)) => {
#[cfg(feature = "autocomplete")]
if args.touching_last_remove() {
args.push_metadata(self.metavar, &self.named.help, true);
}
Ok(w)
}
Err(err) => {
#[cfg(feature = "autocomplete")]
args.push_argument(&self.named, self.metavar);
Err(err)
}
_ => {
#[cfg(feature = "autocomplete")]
args.push_argument(&self.named, self.metavar);
if let Some(val) = self.named.env.iter().find_map(std::env::var_os) {
args.current = None;
Ok(val)
} else {
Err(Error::Missing(vec![self.item()]))
}
}
}
}
}
impl<T> Parser<T> for ParseArgument<T>
where
T: FromStr + 'static,
<T as std::str::FromStr>::Err: std::fmt::Display,
{
fn eval(&self, args: &mut Args) -> Result<T, Error> {
let os = self.take_argument(args)?;
match parse_os_str::<T>(os) {
Ok(ok) => Ok(ok),
Err(err) => Err(args.word_parse_error(&err)),
}
}
fn meta(&self) -> Meta {
Meta::Item(self.item())
}
}
fn build_positional<T>(metavar: &'static str) -> ParsePositional<T> {
ParsePositional {
metavar,
help: None,
result_type: PhantomData,
strict: false,
}
}
#[derive(Clone)]
pub struct ParsePositional<T> {
metavar: &'static str,
help: Option<String>,
result_type: PhantomData<T>,
strict: bool,
}
impl<T> ParsePositional<T> {
#[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 strict(mut self) -> Self {
self.strict = true;
self
}
fn meta(&self) -> Meta {
Meta::Item({
Item::Positional {
metavar: self.metavar,
help: self.help.clone(),
strict: self.strict,
}
})
}
}
fn parse_word(
args: &mut Args,
strict: bool,
metavar: &'static str,
help: &Option<String>,
) -> Result<OsString, Error> {
if let Some((is_strict, word)) = args.take_positional_word()? {
if strict && !is_strict {
#[cfg(feature = "autocomplete")]
args.push_value("--", &Some("-- Positional only items".to_owned()), false);
return Err(Error::Stderr(format!(
"Expected <{}> to be on the right side of --",
metavar,
)));
}
#[cfg(feature = "autocomplete")]
if args.touching_last_remove() && !args.no_pos_ahead {
args.push_metadata(metavar, help, false);
args.no_pos_ahead = true;
}
Ok(word)
} else {
#[cfg(feature = "autocomplete")]
if !args.no_pos_ahead {
args.push_metadata(metavar, help, false);
args.no_pos_ahead = true;
}
let item = Item::Positional {
metavar,
help: help.clone(),
strict,
};
Err(Error::Missing(vec![item]))
}
}
impl<T> Parser<T> for ParsePositional<T>
where
T: FromStr + 'static,
<T as std::str::FromStr>::Err: std::fmt::Display,
{
fn eval(&self, args: &mut Args) -> Result<T, Error> {
let os = parse_word(args, self.strict, self.metavar, &self.help)?;
match parse_os_str::<T>(os) {
Ok(ok) => Ok(ok),
Err(err) => Err(args.word_parse_error(&err)),
}
}
fn meta(&self) -> Meta {
self.meta()
}
}
pub struct ParseAny<T> {
ty: PhantomData<T>,
metavar: &'static str,
strict: bool,
help: Option<String>,
}
#[doc = include_str!("docs/any.md")]
#[must_use]
pub fn any<T>(metavar: &'static str) -> ParseAny<T> {
ParseAny {
ty: PhantomData,
metavar,
strict: false,
help: None,
}
}
impl<T> ParseAny<T> {
#[doc = include_str!("docs/any.md")]
#[must_use]
pub fn help<M: Into<String>>(mut self, help: M) -> Self {
self.help = Some(help.into());
self
}
fn meta(&self) -> Meta {
Meta::Item(self.item())
}
fn item(&self) -> Item {
Item::Positional {
metavar: self.metavar,
strict: self.strict,
help: self.help.clone(),
}
}
fn next_os_string(&self, args: &mut Args) -> Result<OsString, Error> {
let (ix, item) = match args.items_iter().next() {
Some(item_with_index) => item_with_index,
None => return Err(Error::Missing(vec![self.item()])),
};
match item {
Arg::Ambiguity(_, s) => {
let os = s.clone();
args.remove(ix);
Ok(os)
}
Arg::Short(_, part, s) | Arg::Long(_, part, s) => {
let os = s.clone();
if *part {
args.remove(ix + 1);
}
args.remove(ix);
Ok(os)
}
Arg::Word(w) | Arg::PosWord(w) => {
let os = w.clone();
args.remove(ix);
Ok(os)
}
}
}
}
impl<T> Parser<T> for ParseAny<T>
where
T: FromStr + 'static,
<T as std::str::FromStr>::Err: std::fmt::Display,
{
fn eval(&self, args: &mut Args) -> Result<T, Error> {
let os = self.next_os_string(args)?;
match parse_os_str::<T>(os) {
Ok(ok) => Ok(ok),
Err(err) => Err(args.word_parse_error(&err)), }
}
fn meta(&self) -> Meta {
self.meta()
}
}