#![no_std]
#[cfg(feature = "std")]
extern crate std;
mod arg;
mod populated_slice;
use ::core::fmt::Debug;
use populated_slice::PopulatedSlice;
pub use crate::arg::Arg;
pub trait Visitor<'arg> {
type Value;
fn visit_positional(self, argument: &'arg Arg) -> Self::Value;
fn visit_long_argument(self, flag: &'arg Arg, argument: &'arg Arg) -> Self::Value;
fn visit_long(self, flag: &'arg Arg, arg: impl ArgAccess<'arg>) -> Self::Value;
fn visit_short(self, flag: u8, arg: impl ArgAccess<'arg>) -> Self::Value;
}
pub trait ArgAccess<'arg>: Sized {
fn take(self) -> Option<&'arg Arg>;
}
#[derive(Debug, Clone)]
enum State<'arg> {
Ready,
PositionalOnly,
ShortInProgress(&'arg PopulatedSlice<u8>),
}
#[derive(Debug, Clone)]
pub struct ArgumentsParser<'arg, I> {
state: State<'arg>,
args: I,
}
pub fn parser<A: AsRef<[u8]>>(args: &[A]) -> ArgumentsParser<'_, impl Iterator<Item = &[u8]>> {
ArgumentsParser::new(args.iter().map(|arg| arg.as_ref()))
}
impl<'arg, I> ArgumentsParser<'arg, I>
where
I: Iterator<Item = &'arg [u8]>,
{
#[inline]
#[must_use]
pub fn new(args: impl IntoIterator<IntoIter = I>) -> Self {
Self {
state: State::Ready,
args: args.into_iter(),
}
}
#[inline]
#[must_use]
pub fn new_from_slice(
slice: &[impl AsBytes],
) -> ArgumentsParser<'_, impl Iterator<Item = &'_ [u8]>> {
ArgumentsParser::new(slice.iter().map(|arg| arg.as_bytes()))
}
#[inline]
fn positional_only_arg<V>(&mut self, visitor: V) -> Option<V::Value>
where
V: Visitor<'arg>,
{
debug_assert!(!matches!(self.state, State::ShortInProgress(_)));
self.state = State::PositionalOnly;
self.args
.next()
.map(Arg::new)
.map(|arg| visitor.visit_positional(arg))
}
#[inline]
fn standard_arg(&mut self) -> StandardArgAccess<'_, 'arg, I> {
debug_assert!(!matches!(self.state, State::PositionalOnly));
self.state = State::Ready;
StandardArgAccess { parent: self }
}
#[inline]
fn short_arg(&mut self, short: &'arg PopulatedSlice<u8>) -> ShortArgAccess<'_, 'arg> {
debug_assert!(!matches!(self.state, State::PositionalOnly));
self.state = State::ShortInProgress(short);
ShortArgAccess {
short: short.get(),
state: &mut self.state,
}
}
#[inline]
fn handle_short_argument<V>(&mut self, short: &'arg PopulatedSlice<u8>, visitor: V) -> V::Value
where
V: Visitor<'arg>,
{
let (&flag, short) = short.split_first();
match PopulatedSlice::new(short) {
None => visitor.visit_short(flag, self.standard_arg()),
Some(short) => visitor.visit_short(flag, self.short_arg(short)),
}
}
pub fn next_arg<V>(&mut self, visitor: V) -> Option<V::Value>
where
V: Visitor<'arg>,
{
match self.state {
State::Ready => match self.args.next()? {
b"--" => self.positional_only_arg(visitor),
argument => Some(match argument {
[b'-', b'-', flag @ ..] => match split_once(flag, b'=') {
Some((flag, argument)) => {
visitor.visit_long_argument(Arg::new(flag), Arg::new(argument))
}
None => visitor.visit_long(Arg::new(flag), self.standard_arg()),
},
[b'-', short @ ..] => match PopulatedSlice::new(short) {
None => visitor.visit_positional(Arg::new(b"-")),
Some(short) => self.handle_short_argument(short, visitor),
},
positional => visitor.visit_positional(Arg::new(positional)),
}),
},
State::PositionalOnly => self.positional_only_arg(visitor),
State::ShortInProgress(short) => Some(self.handle_short_argument(short, visitor)),
}
}
}
struct StandardArgAccess<'a, 'arg, I> {
parent: &'a mut ArgumentsParser<'arg, I>,
}
impl<'arg, I> ArgAccess<'arg> for StandardArgAccess<'_, 'arg, I>
where
I: Iterator<Item = &'arg [u8]>,
{
fn take(self) -> Option<&'arg Arg> {
match self.parent.args.next()? {
b"--" if !matches!(self.parent.state, State::PositionalOnly) => {
self.parent.state = State::PositionalOnly;
None
}
arg => Some(Arg::new(arg)),
}
}
}
struct ShortArgAccess<'a, 'arg> {
short: &'arg [u8],
state: &'a mut State<'arg>,
}
impl<'arg> ArgAccess<'arg> for ShortArgAccess<'_, 'arg> {
fn take(self) -> Option<&'arg Arg> {
debug_assert!(
matches!(*self.state, State::ShortInProgress(short) if short.get() == self.short)
);
*self.state = State::Ready;
Some(Arg::new(self.short))
}
}
fn split_once(input: &[u8], delimiter: u8) -> Option<(&[u8], &[u8])> {
memchr::memchr(delimiter, input).map(|i| (&input[..i], &input[i + 1..]))
}
pub trait AsBytes {
fn as_bytes(&self) -> &[u8];
}
impl AsBytes for [u8] {
fn as_bytes(&self) -> &[u8] {
self
}
}
impl AsBytes for str {
fn as_bytes(&self) -> &[u8] {
self.as_bytes()
}
}
impl<T: AsBytes> AsBytes for &T {
fn as_bytes(&self) -> &[u8] {
T::as_bytes(*self)
}
}
#[cfg(feature = "std")]
mod std_impls {
use super::*;
use std::{
ffi::{OsStr, OsString},
string::String,
vec::Vec,
};
impl AsBytes for Vec<u8> {
fn as_bytes(&self) -> &[u8] {
self.as_slice()
}
}
impl AsBytes for String {
fn as_bytes(&self) -> &[u8] {
self.as_bytes()
}
}
impl AsBytes for OsString {
fn as_bytes(&self) -> &[u8] {
self.as_encoded_bytes()
}
}
impl AsBytes for OsStr {
fn as_bytes(&self) -> &[u8] {
self.as_encoded_bytes()
}
}
}