#![warn(missing_docs)]
#![warn(rustdoc::missing_doc_code_examples)]
#![doc = include_str!("../README.md")]
use std::rc::Rc;
use std::str::FromStr;
pub mod params;
mod args;
#[doc(hidden)]
pub mod info;
use crate::{args::Word, info::Error, info::Item};
#[cfg(test)]
mod tests;
#[doc(inline)]
pub use crate::args::Args;
#[doc(inline)]
pub use crate::info::{Info, Meta, OptionParser};
#[doc(inline)]
pub use crate::params::*;
#[cfg(feature = "bpaf_derive")]
pub use bpaf_derive::Bpaf;
#[macro_export]
macro_rules! construct {
($con:ident { $($tokens:tt)* }) => {{ construct!(@prepare [named [$con]] [] $($tokens)*) }};
($con:ident ( $($tokens:tt)* )) => {{ construct!(@prepare [pos [$con]] [] $($tokens)*) }};
($ns:ident :: $con:ident { $($tokens:tt)* }) => {{ construct!(@prepare [named [$ns $con]] [] $($tokens)*) }};
($ns:ident :: $con:ident ( $($tokens:tt)* )) => {{ construct!(@prepare [pos [$ns $con]] [] $($tokens)*) }};
($first:ident $($tokens:tt)*) => {{ construct!(@prepare [pos] [] $first $($tokens)*) }};
([$first:ident $($tokens:tt)*]) => {{ construct!(@prepare [alt] [] $first $($tokens)*) }};
(@prepare $ty:tt [$($fields:tt)*] $field:ident (), $($rest:tt)*) => {{
let $field = $field();
construct!(@prepare $ty [$($fields)* $field] $($rest)*)
}};
(@prepare $ty:tt [$($fields:tt)*] $field:ident () $($rest:tt)*) => {{
let $field = $field();
construct!(@prepare $ty [$($fields)* $field] $($rest)*)
}};
(@prepare $ty:tt [$($fields:tt)*] $field:ident, $($rest:tt)*) => {{
construct!(@prepare $ty [$($fields)* $field] $($rest)*)
}};
(@prepare $ty:tt [$($fields:tt)*] $field:ident $($rest:tt)*) => {{
construct!(@prepare $ty [$($fields)* $field] $($rest)*)
}};
(@prepare [alt] [$first:ident $($fields:ident)*]) => { $first $(.or_else($fields))* };
(@prepare $ty:tt [$($fields:tt)*]) => {{
$crate::Parser {
parse: ::std::rc::Rc::new(move |args| {
$(let ($fields , args) = ($fields . parse)(args)?;)*
Ok((construct!(@make $ty [$($fields)*]), args))
}),
meta: $crate::Meta::And(vec![ $($fields.meta),* ])
}
}};
(@make [named [$con:ident]] [$($fields:ident)*]) => { $con { $($fields),* } };
(@make [pos [$con:ident]] [$($fields:ident)*]) => { $con ( $($fields),* ) };
(@make [named [$ns:ident $con:ident]] [$($fields:ident)*]) => { $ns :: $con { $($fields),* } };
(@make [pos [$ns:ident $con:ident]] [$($fields:ident)*]) => { $ns :: $con ( $($fields),* ) };
(@make [pos] [$($fields:ident)*]) => { ( $($fields),* ) };
}
#[doc(hidden)]
pub type DynParse<T> = dyn Fn(Args) -> Result<(T, Args), Error>;
#[derive(Clone)]
pub struct Parser<T> {
#[doc(hidden)]
pub parse: Rc<DynParse<T>>,
#[doc(hidden)]
pub meta: Meta,
}
impl<T> Parser<T> {
#[must_use]
pub fn pure(val: T) -> Parser<T>
where
T: 'static + Clone,
{
let parse = move |i| Ok((val.clone(), i));
Parser {
parse: Rc::new(parse),
meta: Meta::Id,
}
}
#[doc(hidden)]
#[must_use]
pub fn ap<A, B>(self, other: Parser<A>) -> Parser<B>
where
T: Fn(A) -> B + 'static,
A: 'static,
{
let parse = move |i| {
let (t, rest) = (self.parse)(i)?;
let (a, rest) = (other.parse)(rest)?;
Ok((t(a), rest))
};
Parser {
parse: Rc::new(parse),
meta: self.meta.clone().and(other.meta.clone()),
}
}
#[must_use]
pub fn or_else(self, other: Parser<T>) -> Parser<T>
where
T: 'static,
{
let parse = move |mut i: Args| -> Result<(T, Args), Error> {
i.head = usize::MAX;
let (res, new_args) = match ((self.parse)(i.clone()), (other.parse)(i)) {
(Ok((r1, a1)), Ok((r2, a2))) => {
if a1.head < a2.head {
Ok((r1, a1))
} else {
Ok((r2, a2))
}
}
(Ok(ok), Err(_)) | (Err(_), Ok(ok)) => Ok(ok),
(Err(e1), Err(e2)) => Err(e1.combine_with(e2)),
}?;
Ok((res, new_args))
};
Parser {
parse: Rc::new(parse),
meta: self.meta.or(other.meta),
}
}
#[must_use]
pub fn fail<M>(msg: M) -> Parser<T>
where
String: From<M>,
M: Clone + 'static,
{
Parser {
meta: Meta::Empty,
parse: Rc::new(move |_| Err(Error::Stderr(String::from(msg.clone())))),
}
}
#[must_use]
pub fn many(self) -> Parser<Vec<T>>
where
T: 'static,
{
let parse = move |mut i: Args| {
let mut res = Vec::new();
let mut size = i.len();
while let Ok((elt, new_i)) = (self.parse)(i.clone()) {
let new_size = new_i.len();
#[allow(clippy::panic)]
if new_size < size {
size = new_size;
} else {
panic!("many can't be used with non failing parser")
}
i = new_i;
res.push(elt);
}
Ok((res, i))
};
Parser {
parse: Rc::new(parse),
meta: self.meta.many(),
}
}
#[must_use]
pub fn guard<F>(self, m: F, message: &'static str) -> Parser<T>
where
F: Fn(&T) -> bool + 'static,
T: 'static,
{
let parse = move |i: Args| match (self.parse)(i) {
Ok((ok, i)) if m(&ok) => Ok((ok, i)),
Ok(_) => Err(Error::Stderr(message.to_owned())), Err(err) => Err(err),
};
Parser {
parse: Rc::new(parse),
meta: self.meta,
}
}
#[must_use]
pub fn some(self, msg: &'static str) -> Parser<Vec<T>>
where
T: 'static,
{
self.many().guard(|x| !x.is_empty(), msg)
}
pub fn optional(self) -> Parser<Option<T>>
where
T: 'static + Clone,
{
self.map(Some).fallback(None)
}
pub fn map<F, B>(self, map: F) -> Parser<B>
where
F: Fn(T) -> B + 'static,
T: 'static,
{
let parse = move |args: Args| {
let (t, args) = (self.parse)(args)?;
Ok((map(t), args))
};
Parser {
parse: Rc::new(parse),
meta: self.meta,
}
}
pub fn parse<F, B, E>(self, map: F) -> Parser<B>
where
F: Fn(T) -> Result<B, E> + 'static,
T: 'static,
E: ToString,
{
let parse = move |args: Args| {
let (t, args) = (self.parse)(args)?;
match map(t) {
Ok(ok) => Ok((ok, args)),
Err(e) => Err(Error::Stderr(
if let Some(Word { utf8: Some(w), .. }) = args.current {
format!("Couldn't parse {:?}: {}", w, e.to_string())
} else {
format!("Couldn't parse: {}", e.to_string())
},
)),
}
};
Parser {
parse: Rc::new(parse),
meta: self.meta,
}
}
#[must_use]
pub fn fallback(self, val: T) -> Parser<T>
where
T: Clone + 'static,
{
let parse = move |i: Args| match (self.parse)(i.clone()) {
Ok(ok) => Ok(ok),
e @ Err(Error::Stderr(_)) => e,
Err(_) => Ok((val.clone(), i)),
};
Parser {
parse: Rc::new(parse),
meta: Meta::optional(self.meta),
}
}
#[must_use]
pub fn fallback_with<F, E>(self, val: F) -> Parser<T>
where
F: Fn() -> Result<T, E> + Clone + 'static,
E: ToString,
T: Clone + 'static,
{
let parse = move |i: Args| match (self.parse)(i.clone()) {
Ok(ok) => Ok(ok),
e @ Err(Error::Stderr(_)) => e,
Err(_) => match val() {
Ok(ok) => Ok((ok, i)),
Err(e) => Err(Error::Stderr(e.to_string())),
},
};
Parser {
parse: Rc::new(parse),
meta: Meta::optional(self.meta),
}
}
#[must_use]
pub fn default(self) -> Parser<T>
where
T: Default + 'static + Clone,
{
self.fallback(T::default())
}
#[must_use]
pub fn group_help(self, msg: &'static str) -> Parser<T> {
Self {
parse: self.parse,
meta: Meta::decorate(self.meta, msg),
}
}
#[must_use]
pub fn hide(self) -> Parser<T>
where
T: 'static,
{
Self {
parse: Rc::new(move |args: Args| {
(self.parse)(args).map_err(|_| Error::Missing(Vec::new()))
}),
meta: Meta::Id,
}
}
}
impl Parser<String> {
#[must_use]
pub fn from_str<T>(self) -> Parser<T>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Display,
{
self.parse(|s| T::from_str(&s))
}
}
#[derive(Clone, Debug)]
pub enum ParseFailure {
Stdout(String),
Stderr(String),
}
impl ParseFailure {
#[allow(clippy::must_use_candidate)]
pub fn unwrap_stderr(self) -> String {
match self {
Self::Stderr(err) => err,
Self::Stdout(_) => {
panic!("not an stderr: {:?}", self)
}
}
}
#[allow(clippy::must_use_candidate)]
pub fn unwrap_stdout(self) -> String {
match self {
Self::Stdout(err) => err,
Self::Stderr(_) => {
panic!("not an stdout: {:?}", self)
}
}
}
}
impl<T> OptionParser<T> {
#[must_use]
pub fn run(self) -> T {
let mut pos_only = false;
let mut vec = Vec::new();
for arg in std::env::args_os().skip(1) {
args::push_vec(&mut vec, arg, &mut pos_only);
}
match self.run_inner(Args::from(vec)) {
Ok(t) => t,
Err(ParseFailure::Stdout(msg)) => {
println!("{}", msg);
std::process::exit(0);
}
Err(ParseFailure::Stderr(msg)) => {
eprintln!("{}", msg);
std::process::exit(1);
}
}
}
pub fn run_inner(self, args: Args) -> Result<T, ParseFailure> {
match (self.parse)(args) {
Ok((t, rest)) if rest.is_empty() => Ok(t),
Ok((_, rest)) => Err(ParseFailure::Stderr(format!("unexpected {:?}", rest))),
Err(Error::Missing(metas)) => Err(ParseFailure::Stderr(format!(
"Expected {}, pass --help for usage information",
Meta::Or(metas)
))),
Err(Error::Stdout(stdout)) => Err(ParseFailure::Stdout(stdout)),
Err(Error::Stderr(stderr)) => Err(ParseFailure::Stderr(stderr)),
}
}
}
#[must_use]
pub fn cargo_helper<T>(cmd: &'static str, parser: Parser<T>) -> Parser<T>
where
T: 'static,
{
let skip = positional_if("", move |s| cmd == s).optional().hide();
construct!(skip, parser).map(|x| x.1)
}