#![cfg_attr(not(doctest), doc = include_str!("docs2/flag.md"))]
#![cfg_attr(not(doctest), doc = include_str!("docs2/req_flag.md"))]
#![cfg_attr(not(doctest), doc = include_str!("docs2/switch.md"))]
#![cfg_attr(not(doctest), doc = include_str!("docs2/argument.md"))]
#![cfg_attr(not(doctest), doc = include_str!("docs2/positional.md"))]
#![cfg_attr(not(doctest), doc = include_str!("docs2/any_simple.md"))]
#![cfg_attr(not(doctest), doc = include_str!("docs2/any_literal.md"))]
#![cfg_attr(not(doctest), doc = include_str!("docs2/command.md"))]
use std::{ffi::OsString, marker::PhantomData, str::FromStr};
use crate::{
args::{Arg, State},
error::{Message, MissingItem},
from_os_str::parse_os_str,
item::ShortLong,
meta_help::Metavar,
Doc, Error, Item, Meta, OptionParser, Parser,
};
#[cfg(doc)]
use crate::{any, command, env, long, positional, short};
#[cfg_attr(not(doctest), doc = include_str!("docs2/named_arg_combine.md"))]
#[cfg_attr(not(doctest), doc = include_str!("docs2/named_arg_derive.md"))]
#[derive(Clone, Debug)]
pub struct NamedArg {
pub(crate) short: Vec<char>,
pub(crate) long: Vec<&'static str>,
pub(crate) env: Vec<&'static str>,
pub(crate) help: Option<Doc>,
}
impl NamedArg {
pub(crate) fn flag_item(&self) -> Option<Item> {
Some(Item::Flag {
name: ShortLong::try_from(self).ok()?,
help: self.help.clone(),
env: self.env.first().copied(),
shorts: self.short.clone(),
})
}
}
impl NamedArg {
#[cfg_attr(not(doctest), doc = include_str!("docs2/short_long_env.md"))]
#[must_use]
pub fn short(mut self, short: char) -> Self {
self.short.push(short);
self
}
#[cfg_attr(not(doctest), doc = include_str!("docs2/short_long_env.md"))]
#[must_use]
pub fn long(mut self, long: &'static str) -> Self {
self.long.push(long);
self
}
#[cfg_attr(not(doctest), doc = include_str!("docs2/short_long_env.md"))]
#[must_use]
pub fn env(mut self, variable: &'static str) -> Self {
self.env.push(variable);
self
}
#[cfg_attr(not(doctest), doc = include_str!("docs2/switch_help.md"))]
#[must_use]
pub fn help<M>(mut self, help: M) -> Self
where
M: Into<Doc>,
{
self.help = Some(help.into());
self
}
#[cfg_attr(not(doctest), doc = include_str!("docs2/switch.md"))]
#[must_use]
pub fn switch(self) -> ParseFlag<bool> {
build_flag_parser(true, Some(false), self)
}
#[cfg_attr(not(doctest), doc = include_str!("docs2/flag.md"))]
#[must_use]
pub fn flag<T>(self, present: T, absent: T) -> ParseFlag<T>
where
T: Clone + 'static,
{
build_flag_parser(present, Some(absent), self)
}
#[cfg_attr(not(doctest), doc = include_str!("docs2/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)
}
#[cfg_attr(not(doctest), doc = include_str!("docs2/argument.md"))]
#[must_use]
pub fn argument<T>(self, metavar: &'static str) -> ParseArgument<T>
where
T: FromStr + 'static,
{
build_argument(self, metavar)
}
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::ArgWord(_) | Arg::Word(_) | Arg::PosWord(_) => false,
}
}
}
impl<T> OptionParser<T> {
#[cfg_attr(not(doctest), doc = include_str!("docs2/command.md"))]
#[cfg_attr(not(doctest), doc = include_str!("docs2/command_enum.md"))]
#[must_use]
pub fn command(self, name: &'static str) -> ParseCommand<T>
where
T: 'static,
{
ParseCommand {
longs: vec![name],
shorts: Vec::new(),
help: self.short_descr(),
subparser: self,
adjacent: false,
}
}
}
pub struct ParseCommand<T> {
pub(crate) longs: Vec<&'static str>,
pub(crate) shorts: Vec<char>,
pub(crate) help: Option<Doc>,
pub(crate) subparser: OptionParser<T>,
pub(crate) adjacent: bool,
}
impl<P> ParseCommand<P> {
#[must_use]
pub fn help<M>(mut self, help: M) -> Self
where
M: Into<Doc>,
{
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
}
#[cfg_attr(not(doctest), doc = include_str!("docs2/adjacent_command.md"))]
#[must_use]
pub fn adjacent(mut self) -> Self {
self.adjacent = true;
self
}
}
impl<T> Parser<T> for ParseCommand<T> {
fn eval(&self, args: &mut State) -> 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(Message::Missing(Vec::new())));
}
if let Some(cur) = args.current {
args.set_scope(cur..args.scope().end);
}
args.path.push(self.longs[0].to_string());
if self.adjacent {
let mut orig_args = args.clone();
args.set_scope(args.adjacently_available_from(args.scope().start + 1));
match self
.subparser
.run_subparser(args)
.map_err(Message::ParseFailure)
{
Ok(ok) => {
args.set_scope(orig_args.scope());
Ok(ok)
}
Err(err) => {
let orig_scope = args.scope();
if let Some(narrow_scope) = args.adjacent_scope(&orig_args) {
orig_args.set_scope(narrow_scope);
if let Ok(res) = self.subparser.run_subparser(&mut orig_args) {
orig_args.set_scope(orig_scope);
std::mem::swap(&mut orig_args, args);
return Ok(res);
}
}
Err(Error(err))
}
}
} else {
self.subparser
.run_subparser(args)
.map_err(|e| Error(Message::ParseFailure(e)))
}
} else {
#[cfg(feature = "autocomplete")]
args.push_command(self.longs[0], self.shorts.first().copied(), &self.help);
let missing = MissingItem {
item: self.item(),
position: args.scope().start,
scope: args.scope(),
};
Err(Error(Message::Missing(vec![missing])))
}
}
fn meta(&self) -> Meta {
Meta::from(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)]
pub struct ParseFlag<T> {
present: T,
absent: Option<T>,
named: NamedArg,
}
impl<T: Clone + 'static> Parser<T> for ParseFlag<T> {
fn eval(&self, args: &mut State) -> 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 => {
if let Some(item) = self.named.flag_item() {
let missing = MissingItem {
item,
position: args.scope().start,
scope: args.scope(),
};
Err(Error(Message::Missing(vec![missing])))
} else if let Some(name) = self.named.env.first() {
Err(Error(Message::NoEnv(name)))
} else {
todo!("no key!")
}
}
}
}
}
fn meta(&self) -> Meta {
if let Some(item) = self.named.flag_item() {
item.required(self.absent.is_none())
} else {
Meta::Skip
}
}
}
impl<T> ParseFlag<T> {
#[must_use]
pub fn help<M>(mut self, help: M) -> Self
where
M: Into<Doc>,
{
self.named.help = Some(help.into());
self
}
}
impl<T> ParseArgument<T> {
#[must_use]
pub fn help<M>(mut self, help: M) -> Self
where
M: Into<Doc>,
{
self.named.help = Some(help.into());
self
}
}
fn build_argument<T>(named: NamedArg, metavar: &'static str) -> ParseArgument<T> {
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> {
#[cfg_attr(not(doctest), doc = include_str!("docs2/adjacent_argument.md"))]
#[must_use]
pub fn adjacent(mut self) -> Self {
self.adjacent = true;
self
}
fn item(&self) -> Option<Item> {
Some(Item::Argument {
name: ShortLong::try_from(&self.named).ok()?,
metavar: 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 State) -> Result<OsString, Error> {
match args.take_arg(&self.named, self.adjacent, Metavar(self.metavar)) {
Ok(Some(w)) => {
#[cfg(feature = "autocomplete")]
if args.touching_last_remove() {
args.push_metavar(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;
return Ok(val);
}
if let Some(item) = self.item() {
let missing = MissingItem {
item,
position: args.scope().start,
scope: args.scope(),
};
Err(Error(Message::Missing(vec![missing])))
} else if let Some(name) = self.named.env.first() {
Err(Error(Message::NoEnv(name)))
} else {
unreachable!()
}
}
}
}
}
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 State) -> Result<T, Error> {
let os = self.take_argument(args)?;
match parse_os_str::<T>(os) {
Ok(ok) => Ok(ok),
Err(err) => Err(Error(Message::ParseFailed(args.current, err))),
}
}
fn meta(&self) -> Meta {
if let Some(item) = self.item() {
Meta::from(item)
} else {
Meta::Skip
}
}
}
pub(crate) fn build_positional<T>(metavar: &'static str) -> ParsePositional<T> {
ParsePositional {
metavar,
help: None,
position: Position::Unrestricted,
ty: PhantomData,
}
}
#[derive(Clone)]
pub struct ParsePositional<T> {
metavar: &'static str,
help: Option<Doc>,
position: Position,
ty: PhantomData<T>,
}
#[derive(Copy, Clone, PartialEq, Eq)]
enum Position {
Unrestricted,
Strict,
NonStrict,
}
impl<T> ParsePositional<T> {
#[cfg_attr(not(doctest), doc = include_str!("docs2/positional.md"))]
#[must_use]
pub fn help<M>(mut self, help: M) -> Self
where
M: Into<Doc>,
{
self.help = Some(help.into());
self
}
#[cfg_attr(not(doctest), doc = include_str!("docs2/positional_strict.md"))]
#[must_use]
#[inline(always)]
pub fn strict(mut self) -> ParsePositional<T> {
self.position = Position::Strict;
self
}
#[must_use]
#[inline(always)]
pub fn non_strict(mut self) -> Self {
self.position = Position::NonStrict;
self
}
#[inline(always)]
fn meta(&self) -> Meta {
let meta = Meta::from(Item::Positional {
metavar: Metavar(self.metavar),
help: self.help.clone(),
});
match self.position {
Position::Strict => Meta::Strict(Box::new(meta)),
_ => meta,
}
}
}
#[allow(unused_variables)] fn parse_pos_word(
args: &mut State,
metavar: Metavar,
help: &Option<Doc>,
position: Position,
) -> Result<OsString, Error> {
match args.take_positional_word(metavar) {
Ok((ix, is_strict, word)) => {
match position {
Position::Strict => {
if !is_strict {
#[cfg(feature = "autocomplete")]
args.push_pos_sep();
return Err(Error(Message::StrictPos(ix, metavar)));
}
}
Position::NonStrict => {
if is_strict {
return Err(Error(Message::NonStrictPos(ix, metavar)));
}
}
Position::Unrestricted => {}
}
#[cfg(feature = "autocomplete")]
if args.touching_last_remove() && !args.check_no_pos_ahead() {
args.push_metavar(metavar.0, help, false);
args.set_no_pos_ahead();
}
Ok(word)
}
Err(err) => {
#[cfg(feature = "autocomplete")]
if !args.check_no_pos_ahead() {
args.push_metavar(metavar.0, help, false);
args.set_no_pos_ahead();
}
Err(err)
}
}
}
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 State) -> Result<T, Error> {
let os = parse_pos_word(args, Metavar(self.metavar), &self.help, self.position)?;
match parse_os_str::<T>(os) {
Ok(ok) => Ok(ok),
Err(err) => Err(Error(Message::ParseFailed(args.current, err))),
}
}
#[inline(always)]
fn meta(&self) -> Meta {
self.meta()
}
}
pub struct ParseAny<T> {
pub(crate) metavar: Doc,
pub(crate) help: Option<Doc>,
pub(crate) check: Box<dyn Fn(OsString) -> Option<T>>,
pub(crate) anywhere: bool,
}
impl<T> ParseAny<T> {
pub(crate) fn item(&self) -> Item {
Item::Any {
metavar: self.metavar.clone(),
help: self.help.clone(),
anywhere: self.anywhere,
}
}
#[must_use]
pub fn help<M: Into<Doc>>(mut self, help: M) -> Self {
self.help = Some(help.into());
self
}
#[must_use]
pub fn metavar<M: Into<Doc>>(mut self, metavar: M) -> Self {
self.metavar = metavar.into();
self
}
#[must_use]
pub fn anywhere(mut self) -> Self {
self.anywhere = true;
self
}
}
impl<T> Parser<T> for ParseAny<T> {
fn eval(&self, args: &mut State) -> Result<T, Error> {
for (ix, x) in args.items_iter() {
let (os, next) = match x {
Arg::Short(_, next, os) | Arg::Long(_, next, os) => (os, *next),
Arg::ArgWord(os) | Arg::Word(os) | Arg::PosWord(os) => (os, false),
};
if let Some(i) = (self.check)(os.clone()) {
args.remove(ix);
if next {
args.remove(ix + 1);
}
return Ok(i);
}
if !self.anywhere {
break;
}
}
let missing_item = MissingItem {
item: self.item(),
position: args.scope().start,
scope: args.scope(),
};
Err(Error(Message::Missing(vec![missing_item])))
}
fn meta(&self) -> Meta {
Meta::Item(Box::new(self.item()))
}
}