use regex::RegexSet;
use crate::{
case::Case,
error::Error,
meta::{Meta, Name, Options},
parse::{
Any, At, Default, Environment, Indices, Many, Map, Node, Parse, Parser, Require, Value,
With,
},
scope::{self, Scope},
stack::Stack,
style, AUTHOR, BREAK, HELP, LICENSE, MAXIMUM, SHIFT, VERSION,
};
use core::{any::TypeId, default, fmt, marker::PhantomData, num::NonZeroUsize, str::FromStr};
use std::{any, borrow::Cow, collections::hash_map::Entry, convert::Infallible, mem::replace};
pub struct Builder<S, P = At<()>> {
case: Case,
tag: Cow<'static, str>,
short: Cow<'static, str>,
long: Cow<'static, str>,
buffer: String,
parse: Result<P, Error>,
scope: S,
style: Box<dyn style::Style>,
position: usize,
}
pub struct Unit;
pub trait Flag {}
impl Flag for Option<bool> {}
impl Flag for bool {}
impl FromStr for Unit {
type Err = Infallible;
fn from_str(_: &str) -> Result<Self, Self::Err> {
Ok(Unit)
}
}
impl default::Default for Builder<scope::Root> {
fn default() -> Self {
Self::new()
}
}
impl<S, P> Builder<S, P> {
pub fn pipe<Q>(self, pipe: impl FnOnce(Self) -> Builder<S, Q>) -> Builder<S, Q> {
pipe(self)
}
pub fn map<T, F: Fn(P::Value) -> T>(self, map: F) -> Builder<S, impl Parse<Value = T>>
where
P: Parse,
{
self.try_map(move |value| Ok(map(value)))
}
pub fn try_map<T, F: Fn(P::Value) -> Result<T, Error>>(self, map: F) -> Builder<S, Map<P, F>>
where
P: Parse,
{
self.map_parse(|parse| Map(parse, map))
}
pub fn filter(
self,
filter: impl Fn(&P::Value) -> bool,
) -> Builder<S, impl Parse<Value = Option<P::Value>>>
where
P: Parse,
{
self.map(move |value| if filter(&value) { Some(value) } else { None })
}
pub fn any<T>(self) -> Builder<S, impl Parse<Value = Option<T>>>
where
P: Parse,
P::Value: Any<T>,
{
self.map(Any::any)
}
pub fn or<T>(self, error: impl Into<Error>) -> Builder<S, impl Parse<Value = T>>
where
P: Parse<Value = Option<T>>,
{
let error = error.into();
self.try_map(move |value| value.ok_or(error.clone()))
}
pub fn boxed(self) -> Builder<S, Box<dyn Parse<Value = P::Value, State = P::State>>>
where
P: Parse + 'static,
{
self.map_parse(|parse| Box::new(parse) as _)
}
fn map_parse<Q>(self, map: impl FnOnce(P) -> Q) -> Builder<S, Q> {
self.map_both(|scope| scope, map)
}
fn try_map_parse<Q>(self, map: impl FnOnce(P) -> Result<Q, Error>) -> Builder<S, Q> {
self.try_map_both(|scope| scope, map)
}
fn map_both<T, Q>(
self,
scope: impl FnOnce(S) -> T,
parse: impl FnOnce(P) -> Q,
) -> Builder<T, Q> {
self.try_map_both(scope, |old| Ok(parse(old)))
}
fn try_map_both<T, Q>(
self,
scope: impl FnOnce(S) -> T,
parse: impl FnOnce(P) -> Result<Q, Error>,
) -> Builder<T, Q> {
Builder {
case: self.case,
tag: self.tag,
short: self.short,
long: self.long,
buffer: self.buffer,
style: self.style,
scope: scope(self.scope),
parse: self.parse.and_then(parse),
position: self.position,
}
}
fn swap_scope<T>(self, scope: T) -> (S, Builder<T, P>) {
(
self.scope,
Builder {
case: self.case,
tag: self.tag,
short: self.short,
long: self.long,
buffer: self.buffer,
style: self.style,
scope,
parse: self.parse,
position: self.position,
},
)
}
fn swap_both<T, Q>(self, scope: T, parse: Q) -> (S, Result<P, Error>, Builder<T, Q>) {
(
self.scope,
self.parse,
Builder {
case: self.case,
tag: self.tag,
short: self.short,
long: self.long,
buffer: self.buffer,
style: self.style,
scope,
parse: Ok(parse),
position: self.position,
},
)
}
fn descend(&mut self, meta: &mut Meta, options: bool) -> Result<(Indices, RegexSet), Error> {
let mut indices = Indices::default();
let mut patterns = Vec::new();
if let Meta::Option(metas) | Meta::Verb(metas) | Meta::Group(metas) = meta {
let (version, help) = self.descend_node(
metas,
(0, 0),
&mut true,
&mut indices,
&mut patterns,
options,
)?;
if options {
if let Some(true) = version {
metas.extend(self.insert_version(&mut indices, true, true)?);
}
if let Some(true) = help {
metas.extend(self.insert_help(&mut indices, true, true)?);
}
if version.is_some() || help.is_some() {
Self::insert_key(self.long.clone(), &mut indices, BREAK)?;
}
}
};
Ok((indices, RegexSet::new(patterns)?))
}
fn descend_node(
&mut self,
metas: &mut [Meta],
(mask, shift): (usize, u32),
show: &mut bool,
indices: &mut Indices,
patterns: &mut Vec<String>,
options: bool,
) -> Result<(Option<bool>, Option<bool>), Error> {
let mut index = 0;
let mut version = None;
let mut help = None;
for i in 0..metas.len() {
let value = (index << shift) | mask;
let Some(meta) = metas.get_mut(i) else {
break;
};
match meta {
Meta::Version(_) if *show => version = version.or(Some(true)),
Meta::Valid(value) => patterns.push(format!("^{value}$")),
Meta::Help(_) | Meta::Usage(_) | Meta::Note(_) if *show => {
help = help.or(Some(true))
}
Meta::Hide => *show = false,
Meta::Show => *show = true,
Meta::Option(metas) => {
self.descend_option(metas, indices, value)?;
index += 1;
if *show {
help = help.or(Some(true))
}
}
Meta::Verb(metas) => {
Self::descend_verb(metas, indices, value)?;
index += 1;
if *show {
help = help.or(Some(true))
}
}
Meta::Group(_) if shift > MAXIMUM => return Err(Error::GroupNestingLimitOverflow),
Meta::Group(metas) => {
let mut show = *show;
let tuple = self.descend_node(
metas,
(value, shift + SHIFT),
&mut show,
indices,
patterns,
options,
)?;
version = merge(version, tuple.0, |left, right| left && right);
help = merge(help, tuple.1, |left, right| left && right);
index += 1;
}
Meta::Options(option) if options => {
let option = match *option {
Options::Help { short, long } => self.insert_help(indices, short, long)?,
Options::Version { short, long } => {
self.insert_version(indices, short, long)?
}
Options::License { short, long } => {
self.insert_license(indices, short, long)?
}
Options::Author { short, long } => {
self.insert_author(indices, short, long)?
}
};
if let Some(option) = option {
*meta = option;
}
help = Some(false);
version = Some(false);
}
_ => {}
}
}
Ok((version, help))
}
fn descend_verb(metas: &[Meta], indices: &mut Indices, index: usize) -> Result<(), Error> {
let mut has = false;
for i in 0..metas.len() {
match metas.get(i) {
Some(Meta::Name(_, value)) => {
Self::insert_key(value.clone(), indices, index)?;
has = true;
}
None => break,
_ => {}
};
}
if has {
Ok(())
} else {
Err(Error::MissingVerbName)
}
}
fn descend_option(
&mut self,
metas: &[Meta],
indices: &mut Indices,
index: usize,
) -> Result<(), Error> {
let mut has = false;
let mut shorts = Vec::new();
let mut swizzle = false;
for i in 0..metas.len() {
match metas.get(i) {
Some(Meta::Name(name, value)) => {
Self::insert_key(value.clone(), indices, index)?;
has = true;
if let Name::Short = name {
shorts.extend(value.chars().nth(self.short.len()));
}
}
Some(Meta::Swizzle) => swizzle = true,
Some(Meta::Position(_)) => {
indices.positions.push(index);
has = true;
}
None => break,
_ => {}
};
}
if swizzle {
if shorts.is_empty() {
return Err(Error::MissingShortOptionNameForSwizzling);
} else {
indices.swizzles.extend(shorts);
}
}
if has {
Ok(())
} else {
Err(Error::MissingOptionNameOrPosition)
}
}
fn insert_version(
&mut self,
indices: &mut Indices,
short: bool,
long: bool,
) -> Result<Option<Meta>, Error> {
self.insert_option(
indices,
if short { Some("v") } else { None },
if long { Some("version") } else { None },
"Displays version information.",
VERSION,
)
}
fn insert_license(
&mut self,
indices: &mut Indices,
short: bool,
long: bool,
) -> Result<Option<Meta>, Error> {
self.insert_option(
indices,
if short { Some("l") } else { None },
if long { Some("license") } else { None },
"Displays license information.",
LICENSE,
)
}
fn insert_author(
&mut self,
indices: &mut Indices,
short: bool,
long: bool,
) -> Result<Option<Meta>, Error> {
self.insert_option(
indices,
if short { Some("a") } else { None },
if long { Some("author") } else { None },
"Displays author information.",
AUTHOR,
)
}
fn insert_help(
&mut self,
indices: &mut Indices,
short: bool,
long: bool,
) -> Result<Option<Meta>, Error> {
self.insert_option(
indices,
if short { Some("h") } else { None },
if long { Some("help") } else { None },
"Displays this help message.",
HELP,
)
}
fn insert_option(
&mut self,
indices: &mut Indices,
short: Option<&'static str>,
long: Option<&'static str>,
help: &'static str,
index: usize,
) -> Result<Option<Meta>, Error> {
let mut option = vec![Meta::Help(Cow::Borrowed(help))];
if let Some(short) = short {
let (name, value) = self.option_name(short)?;
if Self::insert_key(value.clone(), indices, index).is_ok() {
option.push(Meta::Name(name, value));
}
}
if let Some(long) = long {
let (name, value) = self.option_name(long)?;
if Self::insert_key(value.clone(), indices, index).is_ok() {
option.push(Meta::Name(name, value));
}
}
if option.len() > 1 {
Ok(Some(Meta::Option(option)))
} else {
Ok(None)
}
}
fn insert_key(
key: Cow<'static, str>,
indices: &mut Indices,
index: usize,
) -> Result<(), Error> {
match indices.indices.entry(key) {
Entry::Occupied(entry) => {
let _ = 1;
Err(Error::DuplicateName(entry.key().to_string()))
}
Entry::Vacant(entry) => {
entry.insert(index);
Ok(())
}
}
}
fn extend_name(&mut self, name: &str, prefix: bool) -> Option<Name> {
self.buffer.clear();
if name.len() == 1 {
if prefix {
self.buffer.push_str(&self.short);
}
self.extend_letters(name.chars())
} else {
if prefix {
self.buffer.push_str(&self.long);
}
self.extend_letters(self.case.convert(name))
}
}
fn extend_letters(&mut self, letters: impl IntoIterator<Item = char>) -> Option<Name> {
let start = self.buffer.len();
for letter in letters {
if letter.is_whitespace() || !letter.is_ascii() {
return None;
} else {
self.buffer.push(letter);
}
}
match self.buffer.len() - start {
0 => None,
1 => Some(Name::Short),
_ => Some(Name::Long),
}
}
fn option_name(
&mut self,
name: impl Into<Cow<'static, str>>,
) -> Result<(Name, Cow<'static, str>), Error> {
let mut outer = name.into();
let Some(name) = self.extend_name(&outer, true) else {
return Err(Error::InvalidOptionName(outer));
};
let inner = outer.to_mut();
inner.clear();
inner.push_str(&self.buffer);
Ok((name, outer))
}
fn verb_name(
&mut self,
name: impl Into<Cow<'static, str>>,
) -> Result<(Name, Cow<'static, str>), Error> {
let mut outer = name.into();
let Some(name) = self.extend_name(&outer, false) else {
return Err(Error::InvalidVerbName(outer));
};
let inner = outer.to_mut();
inner.clear();
inner.push_str(&self.buffer);
Ok((name, outer))
}
fn convert<'a>(&mut self, format: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
let mut format = format.into();
self.buffer.clear();
self.buffer.push_str(&format);
let inner = format.to_mut();
inner.clear();
inner.extend(self.case.convert(&self.buffer));
format
}
}
impl<S: Scope, P> Builder<S, P> {
pub fn default<T: Clone + fmt::Debug>(
self,
default: impl Into<T>,
) -> Builder<S, Default<P, impl Fn() -> T>>
where
P: Parse<Value = Option<T>>,
{
let default = default.into();
let format = format!("{default:?}");
self.default_with(move || default.clone(), format)
}
pub fn default_with<T, F: Fn() -> T>(
mut self,
default: F,
format: impl Into<Cow<'static, str>>,
) -> Builder<S, Default<P, F>>
where
P: Parse<Value = Option<T>>,
{
let format = self.convert(format);
self.meta(Meta::Default(format))
.map_parse(|parse| Default(parse, default))
}
pub fn environment<T: FromStr>(
self,
variable: impl Into<Cow<'static, str>>,
) -> Builder<S, Environment<P>>
where
P: Parse<Value = Option<T>>,
{
let variable = variable.into();
self.meta(Meta::Environment(variable.clone()))
.map_parse(|inner| Environment(inner, variable))
}
pub fn many<T, I: default::Default + Extend<T>>(
self,
) -> Builder<S, Many<P, I, impl Fn() -> I, impl Fn(&mut I, T)>>
where
P: Parse<Value = Option<T>>,
{
self.many_with(Some(NonZeroUsize::MIN), I::default, |items, item| {
items.extend([item])
})
}
pub fn many_with<T, I, N: Fn() -> I, F: Fn(&mut I, T)>(
self,
per: Option<NonZeroUsize>,
new: N,
add: F,
) -> Builder<S, Many<P, I, N, F>>
where
P: Parse<Value = Option<T>>,
{
self.meta(Meta::Many(per)).map_parse(|parse| Many {
parse,
per,
new,
add,
_marker: PhantomData,
})
}
pub fn require<T: 'static>(self) -> Builder<S, Require<P>>
where
P: Parse<Value = Option<T>>,
{
self.require_with(type_name::<T>())
}
pub fn require_with<T>(mut self, format: impl Into<Cow<'static, str>>) -> Builder<S, Require<P>>
where
P: Parse<Value = Option<T>>,
{
let format = self.convert(format);
self.meta(Meta::Require(format))
.map_parse(|parse| Require(parse))
}
pub fn help(self, help: impl Into<Cow<'static, str>>) -> Self {
let help = help.into();
if help.chars().all(char::is_whitespace) {
self
} else {
self.meta(Meta::Help(help))
}
}
pub fn line(self) -> Self {
self.meta(Meta::Line)
}
pub fn note(self, note: impl Into<Cow<'static, str>>) -> Self {
let note = note.into();
if note.chars().all(char::is_whitespace) {
self
} else {
self.meta(Meta::Note(note))
}
}
pub fn hide(self) -> Self {
self.meta(Meta::Hide)
}
pub fn show(self) -> Self {
self.meta(Meta::Show)
}
fn try_meta(mut self, meta: Result<Meta, Error>) -> Self {
match meta {
Ok(meta) => {
self.scope.push(meta);
self
}
Err(error) => {
self.parse = Err(error);
self
}
}
}
fn meta(self, meta: Meta) -> Self {
self.try_meta(Ok(meta))
}
}
impl<S: scope::Node, P> Builder<S, P> {
pub fn usage(self, usage: impl Into<Cow<'static, str>>) -> Self {
let usage = usage.into();
if usage.chars().all(char::is_whitespace) {
self
} else {
self.meta(Meta::Usage(usage))
}
}
pub fn group<Q>(
self,
build: impl FnOnce(Builder<scope::Group, At>) -> Builder<scope::Group, Q>,
) -> Builder<S, P::Push<With<Q>>>
where
P: Stack,
{
let (scope, old, builder) = self.swap_both(scope::Group::new(), At(()));
let (group, mut builder) = build(builder).swap_scope(scope);
let meta = Meta::from(group);
builder.scope.push(meta.clone(usize::MAX));
builder.try_map_parse(|new| {
Ok(old?.push(With {
parse: new,
set: RegexSet::empty(),
meta,
}))
})
}
pub fn verb<Q>(
self,
build: impl FnOnce(Builder<scope::Verb, At>) -> Builder<scope::Verb, Q>,
) -> Builder<S, P::Push<With<Node<Q>>>>
where
P: Stack,
{
let (scope, old, mut builder) = self.swap_both(scope::Verb::new(), At(()));
let position = replace(&mut builder.position, 0);
let (verb, mut builder) = build(builder).swap_scope(scope);
let mut meta = Meta::from(verb);
let pair = builder.descend(&mut meta, true);
builder.position = position;
builder.scope.push(meta.clone(1));
builder.try_map_parse(|new| {
let (indices, set) = pair?;
Ok(old?.push(With {
parse: Node {
parse: new,
indices,
},
meta,
set,
}))
})
}
pub fn option<T: FromStr + 'static, Q>(
self,
build: impl FnOnce(Builder<scope::Option, Value<T>>) -> Builder<scope::Option, Q>,
) -> Builder<S, P::Push<With<Q>>>
where
P: Stack,
{
let tag = self.tag.clone();
let (scope, old, builder) = self.swap_both(
scope::Option::new(),
Value {
tag: if TypeId::of::<T>() == TypeId::of::<bool>() {
Some(tag)
} else {
None
},
_marker: PhantomData,
},
);
let (option, mut builder) = build(builder.parse::<T>()).swap_scope(scope);
let mut meta = Meta::from(option);
let pair = builder.descend(&mut meta, false);
builder.scope.push(meta.clone(1));
builder.try_map_parse(|new| {
let (_, set) = pair?;
Ok(old?.push(With {
parse: new,
set,
meta,
}))
})
}
pub fn options(self, options: impl IntoIterator<Item = Options>) -> Self {
options
.into_iter()
.map(Meta::Options)
.fold(self, Builder::meta)
}
}
impl<S: scope::Version, P> Builder<S, P> {
pub fn version(self, version: impl Into<Cow<'static, str>>) -> Self {
self.meta(Meta::Version(version.into()))
}
pub fn summary(self, summary: impl Into<Cow<'static, str>>) -> Self {
let summary = summary.into();
if summary.chars().all(char::is_whitespace) {
self
} else {
self.meta(Meta::Summary(summary))
}
}
}
impl Parser<()> {
pub fn builder() -> Builder<scope::Root> {
Builder::new()
}
}
impl Builder<scope::Root> {
fn new() -> Self {
let case = Case::Kebab { upper: false };
Self {
case,
tag: case.convert("true").collect(),
short: Cow::Borrowed("-"),
long: Cow::Borrowed("--"),
buffer: String::new(),
parse: Ok(At(())),
scope: scope::Root::new(),
style: Box::new(style::Termion),
position: 0,
}
}
pub fn case(mut self, case: Case) -> Self {
self.case = case;
self.tag = case.convert("true").collect();
self
}
pub fn prefix(
mut self,
short: impl Into<Cow<'static, str>>,
long: impl Into<Cow<'static, str>>,
) -> Self {
let short = short.into();
let long = long.into();
if short == long
|| short.is_empty()
|| short.chars().any(|letter| letter.is_ascii_alphanumeric())
|| long.is_empty()
|| long.chars().any(|letter| letter.is_ascii_alphanumeric())
{
self.try_map_parse(|_| Err(Error::InvalidPrefix(short, long)))
} else {
self.short = short;
self.long = long;
self
}
}
}
impl<P> Builder<scope::Root, P> {
pub fn build(self) -> Result<Parser<With<Node<P>>>, Error> {
let (root, mut builder) = self.swap_scope(());
let mut meta = Meta::from(root);
let (indices, set) = builder.descend(&mut meta, true)?;
Ok(Parser {
short: builder.short,
long: builder.long,
style: builder.style,
parse: With {
parse: Node {
indices,
parse: builder.parse?,
},
meta,
set,
},
})
}
pub fn style<S: style::Style + 'static>(mut self, style: S) -> Self {
self.style = Box::new(style);
self
}
pub fn name(self, name: impl Into<Cow<'static, str>>) -> Self {
let name = name.into();
if name.chars().all(char::is_whitespace) {
self
} else {
self.meta(Meta::Name(Name::Plain, name))
}
}
pub fn license(
self,
name: impl Into<Cow<'static, str>>,
file: impl Into<Cow<'static, str>>,
) -> Self {
let name = name.into();
let content = file.into();
if name.chars().all(char::is_whitespace) && content.chars().all(char::is_whitespace) {
self
} else {
self.meta(Meta::License(name, content))
}
}
pub fn author(self, author: impl Into<Cow<'static, str>>) -> Self {
let author = author.into();
if author.chars().all(char::is_whitespace) {
self
} else {
self.meta(Meta::Author(author))
}
}
pub fn repository(self, repository: impl Into<Cow<'static, str>>) -> Self {
let repository = repository.into();
if repository.chars().all(char::is_whitespace) {
self
} else {
self.meta(Meta::Repository(repository))
}
}
pub fn home(self, home: impl Into<Cow<'static, str>>) -> Self {
let home = home.into();
if home.chars().all(char::is_whitespace) {
self
} else {
self.meta(Meta::Home(home))
}
}
}
impl<P> Builder<scope::Group, P> {
pub fn name(self, name: impl Into<Cow<'static, str>>) -> Self {
let name = name.into();
if name.chars().all(char::is_whitespace) {
self
} else {
self.meta(Meta::Name(Name::Plain, name))
}
}
}
impl<P> Builder<scope::Verb, P> {
pub fn name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
let name = name.into();
let meta = self.verb_name(name).map(|pair| Meta::Name(pair.0, pair.1));
self.try_meta(meta)
}
}
impl Builder<scope::Option, Value<Unit>> {
pub fn parse<T: FromStr + 'static>(self) -> Builder<scope::Option, Value<T>> {
self.parse_with(
if TypeId::of::<T>() == TypeId::of::<bool>() {
Some("true")
} else {
None
},
type_name::<T>(),
)
}
pub fn parse_with<T: FromStr>(
mut self,
tag: Option<impl Into<Cow<'static, str>>>,
format: impl Into<Cow<'static, str>>,
) -> Builder<scope::Option, Value<T>> {
let format = self.convert(format);
self.meta(Meta::Type(format)).map_parse(|_| Value {
tag: tag.map(Into::into),
_marker: PhantomData,
})
}
}
impl<P> Builder<scope::Option, P> {
pub fn name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
let name = name.into();
let meta = self
.option_name(name)
.map(|pair| Meta::Name(pair.0, pair.1));
self.try_meta(meta)
}
pub fn position(mut self) -> Self {
let position = self.position;
self.position += 1;
self.meta(Meta::Position(position))
}
pub fn swizzle(self) -> Self
where
P: Parse,
<P as Parse>::Value: Flag,
{
self.meta(Meta::Swizzle)
}
pub fn valid(self, pattern: impl Into<Cow<'static, str>>) -> Self {
self.meta(Meta::Valid(pattern.into()))
}
}
fn type_name<T: 'static>() -> &'static str {
macro_rules! is {
($left: expr $(, $rights: ident)+) => {
$($left == TypeId::of::<$rights>() || $left == TypeId::of::<Option<$rights>>() ||)+ false
};
}
let identifier = TypeId::of::<T>();
if is!(identifier, bool) {
"boolean"
} else if is!(identifier, u8, u16, u32, u64, u128, usize) {
"natural number"
} else if is!(identifier, i8, i16, i32, i64, i128, isize) {
"integer number"
} else if is!(identifier, f32, f64) {
"rational number"
} else {
let mut name = any::type_name::<T>();
if let Some(split) = name.split('<').next() {
name = split;
}
if let Some(split) = name.split(':').last() {
name = split;
}
name
}
}
fn merge<T>(left: Option<T>, right: Option<T>, merge: impl FnOnce(T, T) -> T) -> Option<T> {
match (left, right) {
(None, None) => None,
(None, Some(right)) => Some(right),
(Some(left), None) => Some(left),
(Some(left), Some(right)) => Some(merge(left, right)),
}
}