use std::{
fmt::{Debug, Display, Write},
str::FromStr,
};
use {
anyhow::Context,
lexopt::{Arg, Parser, ValueExt},
};
pub mod api;
pub mod backtrack;
pub mod common;
pub mod dfa;
pub mod flags;
pub mod haystack;
pub mod hybrid;
pub mod input;
pub mod lite;
pub mod meta;
pub mod onepass;
pub mod overlapping;
pub mod patterns;
pub mod pikevm;
pub mod syntax;
pub mod thompson;
pub trait Configurable: Debug {
fn configure(
&mut self,
p: &mut Parser,
arg: &mut Arg,
) -> anyhow::Result<bool>;
fn usage(&self) -> &[Usage];
}
pub fn configure(
p: &mut Parser,
usage: &str,
targets: &mut [&mut dyn Configurable],
) -> anyhow::Result<()> {
while let Some(arg) = p.next()? {
match arg {
Arg::Short('h') | Arg::Long("help") => {
let mut usages = vec![];
for t in targets.iter() {
usages.extend_from_slice(t.usage());
}
usages.sort_by_key(|u| {
u.format
.split_once(", ")
.map(|(_, long)| long)
.unwrap_or(u.format)
});
let options = if arg == Arg::Short('h') {
Usage::short(&usages)
} else {
Usage::long(&usages)
};
let usage = usage.replace("%options%", &options);
anyhow::bail!("{}", usage.trim());
}
_ => {}
}
let long_flag: Option<String> = match arg {
Arg::Long(name) => Some(name.to_string()),
_ => None,
};
let mut arg = match long_flag {
Some(ref flag) => Arg::Long(flag),
None => match arg {
Arg::Short(c) => Arg::Short(c),
Arg::Long(_) => unreachable!(),
Arg::Value(value) => Arg::Value(value),
},
};
let mut recognized = false;
for t in targets.iter_mut() {
if t.configure(p, &mut arg)? {
recognized = true;
break;
}
}
if !recognized {
return Err(arg.unexpected().into());
}
}
Ok(())
}
pub fn next_as_command(usage: &str, p: &mut Parser) -> anyhow::Result<String> {
let usage = usage.trim();
let arg = match p.next()? {
Some(arg) => arg,
None => anyhow::bail!("{usage}"),
};
let cmd = match arg {
Arg::Value(cmd) => cmd.string()?,
Arg::Short('h') | Arg::Long("help") => anyhow::bail!("{usage}"),
arg => return Err(arg.unexpected().into()),
};
Ok(cmd)
}
pub fn parse<T>(p: &mut Parser, flag_name: &'static str) -> anyhow::Result<T>
where
T: FromStr,
<T as FromStr>::Err: Display + Debug + Send + Sync + 'static,
{
let osv = p.value().context(flag_name)?;
let strv = match osv.to_str() {
Some(strv) => strv,
None => {
let err = lexopt::Error::NonUnicodeValue(osv.into());
return Err(anyhow::Error::from(err).context(flag_name));
}
};
let parsed = match strv.parse() {
Err(err) => return Err(anyhow::Error::msg(err).context(flag_name)),
Ok(parsed) => parsed,
};
Ok(parsed)
}
pub fn parse_maybe<T>(
p: &mut Parser,
flag_name: &'static str,
) -> anyhow::Result<Option<T>>
where
T: FromStr,
<T as FromStr>::Err: Display + Debug + Send + Sync + 'static,
{
let osv = p.value().context(flag_name)?;
let strv = match osv.to_str() {
Some(strv) => strv,
None => {
let err = lexopt::Error::NonUnicodeValue(osv.into());
return Err(anyhow::Error::from(err).context(flag_name));
}
};
if strv == "none" {
return Ok(None);
}
let parsed = match strv.parse() {
Err(err) => return Err(anyhow::Error::msg(err).context(flag_name)),
Ok(parsed) => parsed,
};
Ok(Some(parsed))
}
#[derive(Clone, Copy, Debug)]
pub struct Usage {
pub format: &'static str,
pub short: &'static str,
pub long: &'static str,
}
impl Usage {
pub const fn new(
format: &'static str,
short: &'static str,
long: &'static str,
) -> Usage {
Usage { format, short, long }
}
pub fn short(usages: &[Usage]) -> String {
const MIN_SPACE: usize = 2;
let mut result = String::new();
let max_len = match usages.iter().map(|u| u.format.len()).max() {
None => return result,
Some(len) => len,
};
for usage in usages.iter() {
let padlen = MIN_SPACE + (max_len - usage.format.len());
let padding = " ".repeat(padlen);
writeln!(result, " {}{}{}", usage.format, padding, usage.short)
.unwrap();
}
result
}
pub fn long(usages: &[Usage]) -> String {
let wrap_opts = textwrap::Options::new(79)
.initial_indent(" ")
.subsequent_indent(" ");
let mut result = String::new();
for (i, usage) in usages.iter().enumerate() {
if i > 0 {
writeln!(result, "").unwrap();
}
writeln!(result, " {}", usage.format).unwrap();
for (i, paragraph) in usage.long.trim().split("\n\n").enumerate() {
if i > 0 {
result.push('\n');
}
let flattened = paragraph.replace("\n", " ");
for line in textwrap::wrap(&flattened, &wrap_opts) {
result.push_str(&line);
result.push('\n');
}
}
}
result
}
}