use std::{ iter, fmt, };
#[macro_export]
macro_rules! arg {
(-- $($long:ident)-+ | - $short:ident) => {
arg!(-- $($long)-+) | arg!(- $short)
};
(- $short:ident | -- $($long:ident)-+) => {
arg!(- $short) | arg!(-- $($long)-+)
};
(-- $($a:ident)-+) => {
$crate::Argument::Long(arg!(+ $($a)-+))
};
(- $a:ident) => {
$crate::Argument::Short(stringify!($a))
};
($a:literal) => {
$crate::Argument::Bare($a)
};
(+ $first:ident) => {
stringify!($first)
};
(+ $first:ident - $($next:ident)-+) => {
concat!(stringify!($first), "-", arg!(+ $($next)-+))
};
}
#[macro_export]
macro_rules! match_arg {
($str:expr; $match:tt) => {
for __ak_arg in $str.as_arg() {
match __ak_arg $match
}
}
}
#[macro_export]
macro_rules! for_args {
($iter:expr; $match:tt) => {
while let Some(__ak_args) = $iter.next() {
$crate::match_arg!(__ak_args; $match)
}
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum Argument<'a> {
Long(&'a str),
Short(&'a str),
Bare(&'a str),
}
impl fmt::Display for Argument<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Long(long) => write!(f, "--{long}"),
Self::Short(short) => write!(f, "-{short}"),
Self::Bare(bare) => write!(f, "{bare}"),
}
}
}
impl Argument<'_> {
pub fn is_long(&self) -> bool {
matches!(self, Self::Long(_))
}
pub fn is_short(&self) -> bool {
matches!(self, Self::Short(_))
}
pub fn is_bare(&self) -> bool {
matches!(self, Self::Bare(_))
}
}
#[derive(Clone)]
pub struct ArgumentIterator<'a>(ArgumentIteratorState<'a>);
#[derive(Clone)]
enum ArgumentIteratorState<'a> {
Long(Option<&'a str>),
Short(&'a str),
Bare(Option<&'a str>),
}
impl<'a> iter::Iterator for ArgumentIterator<'a> {
type Item = Argument<'a>;
fn next(&mut self) -> Option<Self::Item> {
match self.0 {
ArgumentIteratorState::Long(ref mut long) => Some(Argument::Long(long.take()?)),
ArgumentIteratorState::Short(ref mut short) => {
let (one, rest) = short.split_at_checked(1)?;
*short = rest;
Some(Argument::Short(one))
},
ArgumentIteratorState::Bare(ref mut bare) => Some(Argument::Bare(bare.take()?)),
}
}
}
pub trait AsArgumentIterator {
fn as_arg<'a>(&'a self) -> ArgumentIterator<'a>;
}
impl AsArgumentIterator for str {
fn as_arg<'a>(&'a self) -> ArgumentIterator<'a> {
if self.starts_with("---") {
return ArgumentIterator(ArgumentIteratorState::Bare(Some(self)));
}
if let Some(long) = self.strip_prefix("--") {
if long.is_empty() {
ArgumentIterator(ArgumentIteratorState::Bare(Some(self)))
} else {
ArgumentIterator(ArgumentIteratorState::Long(Some(long)))
}
} else if let Some(short) = self.strip_prefix("-") {
if short.is_empty() {
ArgumentIterator(ArgumentIteratorState::Bare(Some(self)))
} else {
ArgumentIterator(ArgumentIteratorState::Short(short))
}
} else {
ArgumentIterator(ArgumentIteratorState::Bare(Some(self)))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn long() {
let long_arg = "--help";
let mut long_iter = long_arg.as_arg();
assert_eq!(long_iter.next(), Some(arg!(--help)));
assert_eq!(long_iter.next(), None);
}
#[test]
fn short() {
let short_arg = "-abc";
let mut short_iter = short_arg.as_arg();
assert_eq!(short_iter.next(), Some(arg!(-a)));
assert_eq!(short_iter.next(), Some(arg!(-b)));
assert_eq!(short_iter.next(), Some(arg!(-c)));
assert_eq!(short_iter.next(), None);
}
#[test]
fn long_no_ident() {
let long_arg = "--";
let mut long_iter = long_arg.as_arg();
assert_eq!(long_iter.next(), Some(arg!("--")));
assert_eq!(long_iter.next(), None);
}
#[test]
fn short_no_ident() {
let short_arg = "-";
let mut short_iter = short_arg.as_arg();
assert_eq!(short_iter.next(), Some(arg!("-")));
assert_eq!(short_iter.next(), None);
}
#[test]
fn long_with_dash() {
let long_arg = "--abc-def";
let mut long_iter = long_arg.as_arg();
assert_eq!(long_iter.next(), Some(arg!(--abc-def)));
assert_eq!(long_iter.next(), None);
}
#[test]
fn no_dashes() {
let other_arg = "yeet";
let mut other_iter = other_arg.as_arg();
assert_eq!(other_iter.next(), Some(arg!("yeet")));
assert_eq!(other_iter.next(), None);
}
#[test]
fn three_dashes() {
let other_arg = "---yeet";
let mut other_iter = other_arg.as_arg();
assert_eq!(other_iter.next(), Some(arg!("---yeet")));
assert_eq!(other_iter.next(), None);
}
#[test]
fn match_long() {
let mut state = false;
match_arg!("--hello"; {
arg!(--hello) => {
assert_eq!(state, false);
state = true;
},
_ => panic!(),
});
}
#[test]
fn match_short() {
let mut state = 0;
match_arg!("-abc"; {
arg!(-a) => {
assert_eq!(state, 0);
state = 1;
},
arg!(-b) => {
assert_eq!(state, 1);
state = 2;
},
arg!(-c) => {
assert_eq!(state, 2);
state = 3;
},
_ => panic!(),
});
}
#[test]
fn for_long_short_bare() {
let mut args = ["-a", "not_an_arg", "--blah", "-cd"].into_iter();
let mut state = 0;
for_args!(args; {
arg!(-a | --blah) => {
state = match state {
0 => 1,
2 => 3,
_ => panic!(),
}
},
arg!("not_an_arg") | arg!(-d) => {
state = match state {
1 => 2,
4 => 5,
_ => panic!(),
}
},
arg!(-c) => {
assert_eq!(state, 3);
state = 4;
},
_ => panic!(),
});
}
}