mxp 1.2.1

Rust implementation of the MXP (Mud eXtension Protocol) standard
Documentation
macro_rules! impl_parse_enum {
    ($t:ty, $v1:ident $(, $v:ident)* $(,)?) => {
        impl std::str::FromStr for $t {
            type Err = UnrecognizedVariant<Self>;

            fn from_str(s: &str) -> Result<Self, Self::Err> {
                if s.eq_ignore_ascii_case(stringify!($v1)) {
                    Ok(Self::$v1)
                }
                $(
                    else if s.eq_ignore_ascii_case(stringify!($v)) {
                        Ok(Self::$v)
                    }
                )*
                else {
                    Err(Self::Err::new(s))
                }
            }
        }

        impl crate::parse::StringVariant for $t {
            const VARIANTS: &[&str] = &[stringify!($v1) $(, stringify!($v))*];
        }
    };
}

macro_rules! impl_display_enum {
    ($t:ty, $($i:ident),* $(,)?) => {
        impl std::fmt::Display for $t {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                match self {
                    $(
                        Self::$i => {
                            const UPPERCASE: &str = match str::from_utf8(
                                &const {
                                    let mut buf = [0; stringify!($i).len()];
                                    buf.copy_from_slice(stringify!($i).as_bytes());
                                    buf.make_ascii_uppercase();
                                    buf
                                },
                            ) {
                                Ok(s) => s,
                                Err(_) => unreachable!(),
                            };
                            f.write_str(UPPERCASE)
                        }
                    )*
                }
            }
        }
    }
}

pub const fn to_ascii_lowercase<'a>(text: &[u8], buf: &'a mut [u8]) -> Option<&'a [u8]> {
    let Some((lower_buf, _)) = buf.split_at_mut_checked(text.len()) else {
        return None;
    };
    lower_buf.copy_from_slice(text);
    lower_buf.make_ascii_lowercase();
    Some(lower_buf)
}

macro_rules! match_ci {
    (
        $s:expr,
        $l:literal $(| $lo:literal)* => $i:expr,
        $(_ => $default:expr)? $(,)?
    ) => {
        if $s.eq_ignore_ascii_case($l) $(|| $s.eq_ignore_ascii_case($lo))* {
            $i
        } $(else {
            $default
        })?
    };
    (
        $s:expr,
        $l_first:literal $(| $lo_first:literal)* => $i_first:expr,
        $($l:literal $(| $lo:literal)* => $i:expr),*
        $(, _ => $default:expr)? $(,)?
    ) => {
        if $s.eq_ignore_ascii_case($l_first) $(|| $s.eq_ignore_ascii_case($lo_first))* {
            $i_first
        } $(else if $s.eq_ignore_ascii_case($l) $(|| $s.eq_ignore_ascii_case($lo))* {
            $i
        })* $(else {
            $default
        })?
    };
}

#[cfg(test)]
mod tests {
    #[test]
    fn match_ci_is_case_insensitive() {
        let result = match_ci! {"teSt",
            " TEST" => " TEST",
            "TEst" => "TEst",
            "teSt " => "teSt ",
            _ => "unmatched"
        };
        assert_eq!(result, "TEst");
    }
}