use std::ffi::OsString;
use std::path::PathBuf;
use winnow::stream::BStr;
use crate::error::BoxError;
pub trait FromArg: Sized {
const CHOICES: &'static [&'static str] = &[];
fn from_arg(value: &BStr) -> Result<Self, BoxError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChoiceError {
pub choices: &'static [&'static str],
}
impl std::fmt::Display for ChoiceError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "expected one of {}", self.choices.join(", "))
}
}
impl std::error::Error for ChoiceError {}
pub fn to_str(value: &BStr) -> Result<&str, std::str::Utf8Error> {
std::str::from_utf8(value)
}
impl FromArg for String {
fn from_arg(value: &BStr) -> Result<Self, BoxError> {
Ok(to_str(value)?.to_owned())
}
}
impl FromArg for Vec<u8> {
fn from_arg(value: &BStr) -> Result<Self, BoxError> {
Ok(value.to_vec())
}
}
impl FromArg for OsString {
#[cfg(unix)]
fn from_arg(value: &BStr) -> Result<Self, BoxError> {
use std::os::unix::ffi::OsStrExt as _;
Ok(std::ffi::OsStr::from_bytes(value).to_owned())
}
#[cfg(not(unix))]
fn from_arg(value: &BStr) -> Result<Self, BoxError> {
Ok(to_str(value)?.into())
}
}
impl FromArg for PathBuf {
fn from_arg(value: &BStr) -> Result<Self, BoxError> {
OsString::from_arg(value).map(PathBuf::from)
}
}
macro_rules! from_str {
($($ty:ty),* $(,)?) => {$(
impl FromArg for $ty {
fn from_arg(value: &BStr) -> Result<Self, BoxError> {
Ok(to_str(value)?.parse::<$ty>()?)
}
}
)*};
}
from_str!(
bool,
char,
u8,
u16,
u32,
u64,
u128,
usize,
i8,
i16,
i32,
i64,
i128,
isize,
f32,
f64,
std::net::IpAddr,
std::net::Ipv4Addr,
std::net::Ipv6Addr,
std::net::SocketAddr,
);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub struct Parsed<T>(pub T);
impl<T> FromArg for Parsed<T>
where
T: std::str::FromStr,
T::Err: Into<BoxError>,
{
fn from_arg(value: &BStr) -> Result<Self, BoxError> {
to_str(value)?.parse().map(Parsed).map_err(Into::into)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Spanned<T> {
pub value: T,
pub offset: usize,
pub attached: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct CInt<T>(pub T);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CIntError(&'static str);
impl std::fmt::Display for CIntError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0)
}
}
impl std::error::Error for CIntError {}
fn c_digits(text: &str) -> Result<(bool, u32, &str), CIntError> {
let (negative, rest) = match text.strip_prefix('-') {
Some(rest) => (true, rest),
None => (false, text.strip_prefix('+').unwrap_or(text)),
};
let (radix, digits) =
if let Some(hex) = rest.strip_prefix("0x").or_else(|| rest.strip_prefix("0X")) {
(16, hex)
} else if rest.len() > 1 && rest.starts_with('0') {
(8, &rest[1..])
} else {
(10, rest)
};
if digits.is_empty() || !digits.chars().all(|c| c.is_digit(radix)) {
return Err(CIntError("expected a number: decimal, 0x hex or 0 octal"));
}
Ok((negative, radix, digits))
}
macro_rules! c_int {
($($ty:ty),* $(,)?) => {$(
impl FromArg for CInt<$ty> {
fn from_arg(value: &BStr) -> Result<Self, BoxError> {
let (negative, radix, digits) = c_digits(to_str(value)?)?;
let magnitude = u128::from_str_radix(digits, radix)?;
let n = if negative {
0i128.checked_sub_unsigned(magnitude).and_then(|n| <$ty>::try_from(n).ok())
} else {
<$ty>::try_from(magnitude).ok()
};
n.map(CInt).ok_or_else(|| CIntError("number out of range").into())
}
}
)*};
}
c_int!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct KeyValue<K, V> {
pub key: K,
pub value: V,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MissingEquals;
impl std::fmt::Display for MissingEquals {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("expected `key=value`")
}
}
impl std::error::Error for MissingEquals {}
impl<K: FromArg, V: FromArg> FromArg for KeyValue<K, V> {
fn from_arg(value: &BStr) -> Result<Self, BoxError> {
let eq = value.iter().position(|&b| b == b'=').ok_or(MissingEquals)?;
Ok(KeyValue {
key: K::from_arg(BStr::new(&value[..eq]))?,
value: V::from_arg(BStr::new(&value[eq + 1..]))?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn c<T>(text: &str) -> Result<T, String>
where
CInt<T>: FromArg,
{
CInt::<T>::from_arg(BStr::new(text))
.map(|CInt(n)| n)
.map_err(|e| e.to_string())
}
#[test]
fn c_integers() {
assert_eq!(c::<u64>("0x400000"), Ok(0x40_0000));
assert_eq!(c::<u64>("0X1f"), Ok(31));
assert_eq!(c::<u64>("010"), Ok(8));
assert_eq!(c::<u64>("0"), Ok(0));
assert_eq!(c::<u64>("4096"), Ok(4096));
assert_eq!(c::<i64>("-0x10"), Ok(-16));
assert_eq!(c::<i8>("-128"), Ok(-128));
assert!(c::<u8>("256").is_err());
assert!(c::<u64>("-1").is_err());
assert!(c::<u64>("08").is_err());
assert!(c::<u64>("0x").is_err());
assert!(c::<u64>("").is_err());
}
#[test]
fn key_values() {
let kv = KeyValue::<String, String>::from_arg(BStr::new("sym=a=b")).unwrap();
assert_eq!((kv.key.as_str(), kv.value.as_str()), ("sym", "a=b"));
let kv = KeyValue::<String, CInt<u64>>::from_arg(BStr::new(".text=0x1000")).unwrap();
assert_eq!((kv.key.as_str(), kv.value.0), (".text", 0x1000));
assert!(KeyValue::<String, String>::from_arg(BStr::new("nothing")).is_err());
}
}