#[cfg(feature = "std")]
use std::ffi::OsStr;
#[derive(Debug, PartialEq)]
pub struct NotAscii;
pub trait Argument: Copy {
type LongOpt;
#[must_use]
fn is_empty(self) -> bool;
#[must_use]
fn parse_long_option(self) -> Option<(Self::LongOpt, Option<Self>)>;
#[must_use]
fn parse_short_option_group(self) -> Option<Self>;
#[must_use]
fn split_short_option(self) -> Option<Result<(u8, Self), NotAscii>>;
}
impl Argument for &[u8] {
type LongOpt = Self;
#[inline]
fn is_empty(self) -> bool {
self.is_empty()
}
#[inline]
fn parse_long_option(self) -> Option<(Self::LongOpt, Option<Self>)> {
match self {
[b'-', b'-', rest @ ..] if !rest.is_empty() => {
let mut split = rest.splitn(2, |&ch| ch == b'=');
Some((split.next().unwrap(), split.next()))
}
_ => None,
}
}
#[inline]
fn parse_short_option_group(self) -> Option<Self> {
match self {
[b'-', rest @ ..] if !matches!(rest, [] | [b'-', ..]) => Some(rest),
_ => None,
}
}
#[inline]
fn split_short_option(self) -> Option<Result<(u8, Self), NotAscii>> {
self.split_first().map(|(&first, rest)| {
if first.is_ascii() {
Ok((first, rest))
} else {
Err(NotAscii)
}
})
}
}
impl Argument for &[u16] {
type LongOpt = Self;
#[inline]
fn is_empty(self) -> bool {
self.is_empty()
}
#[inline]
fn parse_long_option(self) -> Option<(Self::LongOpt, Option<Self>)> {
const DASH: u16 = b'-' as u16;
match self {
[DASH, DASH, rest @ ..] if !rest.is_empty() => {
let mut split = rest.splitn(2, |&ch| ch == u16::from(b'='));
Some((split.next().unwrap(), split.next()))
}
_ => None,
}
}
#[inline]
fn parse_short_option_group(self) -> Option<Self> {
const DASH: u16 = b'-' as u16;
match self {
[DASH, rest @ ..] if !matches!(rest, [] | [DASH, ..]) => Some(rest),
_ => None,
}
}
#[inline]
fn split_short_option(self) -> Option<Result<(u8, Self), NotAscii>> {
self.split_first().map(|(&first, rest)| {
if let Some(first) = u8::try_from(first).ok().filter(u8::is_ascii) {
Ok((first, rest))
} else {
Err(NotAscii)
}
})
}
}
#[cfg(feature = "std")]
impl<'a> Argument for &'a OsStr {
type LongOpt = &'a [u8];
#[inline]
fn is_empty(self) -> bool {
self.is_empty()
}
#[inline]
fn parse_long_option(self) -> Option<(Self::LongOpt, Option<Self>)> {
match self.as_encoded_bytes() {
[b'-', b'-', rest @ ..] if !rest.is_empty() => {
let mut split = rest.splitn(2, |&ch| ch == b'=');
let opt = split.next().unwrap();
let value = split.next();
Some((
opt,
value.map(|s| unsafe { OsStr::from_encoded_bytes_unchecked(s) }),
))
}
_ => None,
}
}
#[inline]
fn parse_short_option_group(self) -> Option<Self> {
match self.as_encoded_bytes() {
[b'-', rest @ ..] if !matches!(rest, [] | [b'-', ..]) => {
Some(unsafe { OsStr::from_encoded_bytes_unchecked(rest) })
}
_ => None,
}
}
#[inline]
fn split_short_option(self) -> Option<Result<(u8, Self), NotAscii>> {
self.as_encoded_bytes().split_first().map(|(&first, rest)| {
if first.is_ascii() {
Ok((first, unsafe { OsStr::from_encoded_bytes_unchecked(rest) }))
} else {
Err(NotAscii)
}
})
}
}
#[cfg(feature = "str")]
impl Argument for &str {
type LongOpt = Self;
#[inline]
fn is_empty(self) -> bool {
self.is_empty()
}
#[inline]
fn parse_long_option(self) -> Option<(Self::LongOpt, Option<Self>)> {
self.strip_prefix("--")
.filter(|rest| !rest.is_empty())
.map(|rest| match rest.split_once('=') {
Some((opt, value)) => (opt, Some(value)),
None => (rest, None),
})
}
#[inline]
fn parse_short_option_group(self) -> Option<Self> {
self.strip_prefix('-')
.filter(|rest| !rest.is_empty() && !rest.starts_with('-'))
}
#[inline]
fn split_short_option(self) -> Option<Result<(u8, Self), NotAscii>> {
let mut chars = self.chars();
chars.next().map(|first| {
if first.is_ascii() {
Ok((first as u8, chars.as_str()))
} else {
Err(NotAscii)
}
})
}
}