tyrx 0.1.2

Typed, ergonomic regular expression library
Documentation
//! [`RegexPattern`] and [`FromMatch`] impls for standard types

use std::str::FromStr;
use std::borrow::Cow;
use std::num::NonZero;
use std::fmt::{self, Formatter};

use regex::{Match, Captures};
use crate::{RegexPattern, FromMatch, ErasedLifetime, Error};


impl<'h> FromMatch<'h> for &'h str {
    fn from_match(_name: &'static str, m: Match<'h>, _captures: &Captures<'h>) -> Result<Self, Error> {
        Ok(m.as_str())
    }
}

impl<'h> FromMatch<'h> for &'h [u8] {
    fn from_match(_name: &'static str, m: Match<'h>, _captures: &Captures<'h>) -> Result<Self, Error> {
        Ok(m.as_str().as_bytes())
    }
}

impl<'h> FromMatch<'h> for Vec<u8> {
    fn from_match(_name: &'static str, m: Match<'h>, _captures: &Captures<'h>) -> Result<Self, Error> {
        Ok(m.as_str().as_bytes().to_vec())
    }
}

impl<'h, T> FromMatch<'h> for Option<T>
where
    T: FromMatch<'h>
{
    fn from_match(name: &'static str, m: Match<'h>, captures: &Captures<'h>) -> Result<Self, Error> {
        if m.is_empty() {
            Ok(None)
        } else {
            T::from_match(name, m, captures).map(Some)
        }
    }
}

impl<'h, T> FromMatch<'h> for Cow<'h, T>
where
    T: ?Sized + ToOwned,
    &'h T: FromMatch<'h>,
{
    fn from_match(name: &'static str, m: Match<'h>, captures: &Captures<'h>) -> Result<Self, Error> {
        <&T>::from_match(name, m, captures).map(Cow::Borrowed)
    }
}

impl<'h, T> FromMatch<'h> for Box<T>
where
    T: FromMatch<'h>,
{
    fn from_match(name: &'static str, m: Match<'h>, captures: &Captures<'h>) -> Result<Self, Error> {
        T::from_match(name, m, captures).map(Box::new)
    }
}

impl<'h> FromMatch<'h> for &'h std::path::Path {
    fn from_match(_name: &'static str, m: Match<'h>, _captures: &Captures<'h>) -> Result<Self, Error> {
        Ok(std::path::Path::new(m.as_str()))
    }
}

impl<'h> FromMatch<'h> for &'h std::ffi::OsStr {
    fn from_match(_name: &'static str, m: Match<'h>, _captures: &Captures<'h>) -> Result<Self, Error> {
        Ok(std::ffi::OsStr::new(m.as_str()))
    }
}

impl<'h> FromMatch<'h> for &'h std::ffi::CStr {
    fn from_match(name: &'static str, m: Match<'h>, _captures: &Captures<'h>) -> Result<Self, Error> {
        std::ffi::CStr::from_bytes_with_nul(m.as_str().as_bytes()).map_err(|error| {
            Error::group_from_str(name, m.range(), error)
        })
    }
}

macro_rules! impl_from_match_for_from_str {
    ($($ty:ty)+) => {$(
        impl<'h> FromMatch<'h> for $ty {
            fn from_match(name: &'static str, m: Match<'h>, _captures: &Captures<'h>) -> Result<Self, Error> {
                Self::from_str(m.as_str()).map_err(|error| {
                    Error::group_from_str(name, m.range(), error)
                })
            }
        }
    )+}
}

impl_from_match_for_from_str! {
    bool char

    u8 u16 u32 u64 u128 usize
    i8 i16 i32 i64 i128 isize

    NonZero<u8> NonZero<u16> NonZero<u32> NonZero<u64> NonZero<u128> NonZero<usize>
    NonZero<i8> NonZero<i16> NonZero<i32> NonZero<i64> NonZero<i128> NonZero<isize>

    f32 f64

    String
    std::ffi::OsString
    std::ffi::CString
    std::path::PathBuf

    std::net::IpAddr
    std::net::Ipv4Addr
    std::net::Ipv6Addr

    std::net::SocketAddr
    std::net::SocketAddrV4
    std::net::SocketAddrV6
}

impl FromMatch<'_> for Box<str> {
    fn from_match(_name: &'static str, m: Match<'_>, _captures: &Captures<'_>) -> Result<Self, Error> {
        Ok(m.as_str().into())
    }
}

impl FromMatch<'_> for Box<std::path::Path> {
    fn from_match(_name: &'static str, m: Match<'_>, _captures: &Captures<'_>) -> Result<Self, Error> {
        Ok(std::path::Path::new(m.as_str()).into())
    }
}

impl FromMatch<'_> for Box<std::ffi::OsStr> {
    fn from_match(_name: &'static str, m: Match<'_>, _captures: &Captures<'_>) -> Result<Self, Error> {
        Ok(std::ffi::OsStr::new(m.as_str()).into())
    }
}

impl FromMatch<'_> for Box<std::ffi::CStr> {
    fn from_match(name: &'static str, m: Match<'_>, _captures: &Captures<'_>) -> Result<Self, Error> {
        std::ffi::CStr::from_bytes_with_nul(m.as_str().as_bytes())
            .map(Box::from)
            .map_err(|error| Error::group_from_str(name, m.range(), error))
    }
}

impl<T: ?Sized + RegexPattern> RegexPattern for &T {
    fn fmt_pattern(f: &mut Formatter<'_>) -> fmt::Result {
        T::fmt_pattern(f)
    }
}

impl<T: ?Sized + RegexPattern> RegexPattern for &mut T {
    fn fmt_pattern(f: &mut Formatter<'_>) -> fmt::Result {
        T::fmt_pattern(f)
    }
}

impl<T: ?Sized + RegexPattern> RegexPattern for Box<T> {
    fn fmt_pattern(f: &mut Formatter<'_>) -> fmt::Result {
        T::fmt_pattern(f)
    }
}

impl<T: ?Sized + ToOwned + RegexPattern> RegexPattern for Cow<'_, T> {
    fn fmt_pattern(f: &mut Formatter<'_>) -> fmt::Result {
        T::fmt_pattern(f)
    }
}

impl<T: RegexPattern> RegexPattern for Option<T> {
    fn fmt_pattern(f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "(?:{})?", T::pattern_display())
    }
}

macro_rules! impl_regex_pattern {
    ($($($ty:ty),+ => $pat:expr;)+) => {$(
        $(
            impl RegexPattern for $ty {
                fn fmt_pattern(f: &mut Formatter<'_>) -> fmt::Result {
                    f.write_str($pat)
                }
            }
        )+
    )+}
}

macro_rules! ipv4_byte_decimal {
    () => { "0|1[0-9]{0,2}|2[0-4][0-9]|25[0-5]|2[0-9]?|[3-9][0-9]?" }
}

macro_rules! ipv4_address_pattern {
    () => { concat!("(?:(?:", ipv4_byte_decimal!(), r")\.){3}(?:", ipv4_byte_decimal!(), ")") }
}

macro_rules! ipv6_address_pattern {
    () => { "[0-9a-fA-F:]{2,39}" } // TODO(H2CO3): more sophisticated IPv6 address pattern
}

impl_regex_pattern! {
    bool => "false|true";

    u8, u16, u32, u64, u128, usize => r"\+?[0-9]+";
    i8, i16, i32, i64, i128, isize => r"[-\+]?[0-9]+";

    NonZero<u8>, NonZero<u16>, NonZero<u32>, NonZero<u64>, NonZero<u128>, NonZero<usize> => r"\+?[1-9][0-9]*";
    NonZero<i8>, NonZero<i16>, NonZero<i32>, NonZero<i64>, NonZero<i128>, NonZero<isize> => r"[-\+]?[1-9][0-9]*";

    f32, f64 => r"[-\+]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)";

    char => ".";
    str, String => ".*";

    std::ffi::OsStr, std::ffi::OsString, std::ffi::CStr, std::ffi::CString => ".*";

    std::path::Path, std::path::PathBuf => ".*";

    std::net::Ipv4Addr => ipv4_address_pattern!();
    std::net::Ipv6Addr => ipv6_address_pattern!();
    std::net::IpAddr => concat!("(?:", ipv4_address_pattern!(), ")|(?:", ipv6_address_pattern!(), ")");

    std::net::SocketAddrV4 => concat!(ipv4_address_pattern!(), ":[0-9]+");
    std::net::SocketAddrV6 => concat!(r"\[", ipv6_address_pattern!(), r"(?:%[0-9]+)?\]:[0-9]+");
    std::net::SocketAddr => concat!("(?:(?:", ipv4_address_pattern!(), r")|(?:\[", ipv6_address_pattern!(), r"(?:%[0-9]+)?\])):[0-9]+");
}

// `ErasedLifetime` impls are provided for primitive and std types,
// so that they implement `TyRx`, allowing for quick-and-dirty
// parsing jobs as an extra convenience feature.

macro_rules! impl_erased_lifetime_self {
    ($($ty:ty)+) => {$(
        impl ErasedLifetime for $ty {
            type Erased = Self;
        }
    )+}
}

impl_erased_lifetime_self!{
    bool char

    u8 u16 u32 u64 u128 usize
    i8 i16 i32 i64 i128 isize

    NonZero<u8> NonZero<u16> NonZero<u32> NonZero<u64> NonZero<u128> NonZero<usize>
    NonZero<i8> NonZero<i16> NonZero<i32> NonZero<i64> NonZero<i128> NonZero<isize>

    f32 f64

    str String
    std::ffi::OsStr std::ffi::OsString
    std::ffi::CStr std::ffi::CString
    std::path::Path std::path::PathBuf

    std::net::IpAddr std::net::Ipv4Addr std::net::Ipv6Addr
    std::net::SocketAddr std::net::SocketAddrV4 std::net::SocketAddrV6
}

impl<T> ErasedLifetime for &T
where
    T: ?Sized + ErasedLifetime
{
    type Erased = &'static T::Erased;
}

impl<T> ErasedLifetime for &mut T
where
    T: ?Sized + ErasedLifetime
{
    type Erased = &'static mut T::Erased;
}

impl<T> ErasedLifetime for Box<T>
where
    T: ?Sized + ErasedLifetime
{
    type Erased = Box<T::Erased>;
}

impl<T> ErasedLifetime for Option<T>
where
    T: ErasedLifetime,
    T::Erased: Sized,
{
    type Erased = Option<T::Erased>;
}

impl<T> ErasedLifetime for Cow<'_, T>
where
    T: ?Sized + ToOwned + ErasedLifetime,
    T::Erased: ToOwned,
{
    type Erased = Cow<'static, T::Erased>;
}

impl ErasedLifetime for () {
    type Erased = Self;
}

macro_rules! impl_erased_lifetime_tuple {
    ($tail:ident, $($head:ident),+) => {
        impl<$tail, $($head,)+> ErasedLifetime for ($($head,)+ $tail)
        where
            $(
                $head: ErasedLifetime,
                $head::Erased: Sized,
            )*
            $tail: ?Sized + ErasedLifetime, // allow last field and its erased counterpart to be ?Sized
        {
            type Erased = ($($head::Erased,)+ $tail::Erased);
        }
        impl_erased_lifetime_tuple!($($head),+);
    };
    ($ty:ident) => {
        impl<$ty: ?Sized + ErasedLifetime> ErasedLifetime for ($ty,) {
            type Erased = ($ty::Erased,);
        }
    };
}

impl_erased_lifetime_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z);