strum-lite 0.2.1

Lightweight declarative macro for sets of strings.
Documentation
//! Lightweight declarative macro for sets of strings.
//!
//! ```
//! strum_lite::strum! {
//!     pub enum Casing {
//!         Kebab = "kebab-case",
//!         ScreamingSnake = "SCREAMING_SNAKE",
//!     }
//! }
//! ```
//!
//! # Features
//! - Implements [`FromStr`](core::str::FromStr) and [`Display`](core::fmt::Display).
//! - Attributes (docs, `#[derive(..)]`s) are passed through to the definition and variants.
//! - Aliases are supported.
//! - Custom enum discriminants are passed through.
//! - `#![no_std]`.
//! - The generated [`FromStr::Err`](core::str::FromStr) provides a helpful error message.
//! - You may ask for a `const` slice of all the variants.
//! - You may ask for a custom zero-sized error type rather than using this crate's [`ParseError`].

#![no_std]

use core::fmt;

/// Give the passed-in enum a [`FromStr`](core::str::FromStr) and [`Display`](core::fmt::Display)
/// implementation.
///
/// ```
/// strum_lite::strum! {
///     #[derive(Default)]
///     pub enum Casing {
///         Kebab = "kebab-case" | "kebab" = 100,
///         #[default]
///         ScreamingSnake = "SCREAMING_SNAKE",
///     }
///     pub const ALL_VARIANTS; // optional
///     throws #[derive(Clone)] ParseCasingError; // optional
/// }
///
/// let derives_are_passed_through = Casing::default();
/// let implements_display = Casing::Kebab.to_string();
/// let implements_from_str = "kebab".parse::<Casing>().unwrap();
///
/// assert_eq!(Casing::Kebab as i32, 100);     // discriminants are passed through
/// assert_eq!(Casing::ALL_VARIANTS.len(), 2); // generated constant
/// ```
#[macro_export]
macro_rules! strum {
    // Entry point.
    (
        $(#[$enum_meta:meta])*
        $enum_vis:vis enum $enum_name:ident {
            $(
                $(#[$variant_meta:meta])*
                $variant_name:ident = $string:literal $(| $alias:literal)* $(= $discriminant:expr)?
            ),* $(,)?
        }
        $($rest:tt)*
    ) => {
        $crate::__strum! {@tail
            {
                [$(#[$enum_meta])*]
                [$enum_vis]
                $enum_name
                [$([$(#[$variant_meta])*] $variant_name [$string $(| $alias)*] [$($discriminant)?])*]
                [$($string)*]
            }
            $($rest)*
        }
    };

}

#[macro_export]
#[doc(hidden)]
macro_rules! __strum {
    // Dispatch on the optional trailing clauses.
    (@tail
        { $metas:tt $vis:tt $enum_name:ident $variants:tt [$($string:literal)*] }
    ) => {
        $crate::__strum! {@define $metas $vis $enum_name $variants []
            [$crate::ParseError]
            [$crate::ParseError({
                const ALL: &'static [&'static ::core::primitive::str] = &[$($string),*];
                &ALL
            })]
        }
    };
    (@tail
        { $metas:tt $vis:tt $enum_name:ident $variants:tt [$($string:literal)*] }
        $(#[$const_meta:meta])* $const_vis:vis const $const_name:ident;
    ) => {
        $crate::__strum! {@define $metas $vis $enum_name $variants
            [[$(#[$const_meta])*] $const_vis const $const_name]
            [$crate::ParseError]
            [$crate::ParseError({
                const ALL: &'static [&'static ::core::primitive::str] = &[$($string),*];
                &ALL
            })]
        }
    };
    (@tail
        { $metas:tt $vis:tt $enum_name:ident $variants:tt [$($string:literal)*] }
        throws $(#[$error_meta:meta])* $error_name:ident $(;)?
    ) => {
        $crate::__strum! {@define $metas $vis $enum_name $variants []
            [$error_name]
            [$error_name]
        }
        $crate::__strum! {@error [$(#[$error_meta])*] $vis $enum_name $error_name [$($string)*]}
    };
    (@tail
        { $metas:tt $vis:tt $enum_name:ident $variants:tt [$($string:literal)*] }
        $(#[$const_meta:meta])* $const_vis:vis const $const_name:ident;
        throws $(#[$error_meta:meta])* $error_name:ident $(;)?
    ) => {
        $crate::__strum! {@define $metas $vis $enum_name $variants
            [[$(#[$const_meta])*] $const_vis const $const_name]
            [$error_name]
            [$error_name]
        }
        $crate::__strum! {@error [$(#[$error_meta])*] $vis $enum_name $error_name [$($string)*]}
    };
    // The enum itself, its optional const of variants, and its impls.
    (@define
        [$(#[$enum_meta:meta])*]
        [$enum_vis:vis]
        $enum_name:ident
        [$(
            [$(#[$variant_meta:meta])*]
            $variant_name:ident
            [$string:literal $(| $alias:literal)*]
            [$($discriminant:expr)?]
        )*]
        $konst:tt
        [$error_ty:ty]
        [$error_new:expr]
    ) => {
        $(#[$enum_meta])*
        $enum_vis enum $enum_name {
            $(
                $(#[$variant_meta])*
                #[doc = ::core::concat!(" String representation: `", $string, "`")]
                $variant_name $(= $discriminant)?,
            )*
        }
        $crate::__strum! {@konst $enum_name $konst [$($variant_name)*]}
        const _: () = {
            use ::core;
            impl core::fmt::Display for $enum_name {
                fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                    fn as_str(e: &$enum_name) -> &core::primitive::str {
                        match *e {
                            $($enum_name::$variant_name => $string),*
                        }
                    }
                    core::fmt::Formatter::write_str(f, as_str(self))
                }
            }
            impl core::str::FromStr for $enum_name {
                type Err = $error_ty;
                fn from_str(s: &core::primitive::str) -> core::result::Result<Self, Self::Err> {
                    match s {
                        $(
                            $string $(| $alias )* => core::result::Result::Ok(Self::$variant_name),
                        )*
                        _ => core::result::Result::Err($error_new)
                    }
                }
            }
        };
    };
    (@konst $enum_name:ident [] $variant_names:tt) => {};
    (@konst $enum_name:ident [[] $vis:vis const $konst:ident] $variant_names:tt) => {
        $crate::__strum! {@konst $enum_name
            [[#[doc = " Every variant of this enum, in declaration order."]] $vis const $konst]
            $variant_names
        }
    };
    (@konst $enum_name:ident [[$(#[$const_meta:meta])+] $vis:vis const $konst:ident] [$($variant_name:ident)*]) => {
        impl $enum_name {
            $(#[$const_meta])+
            $vis const $konst: [Self; <[Self]>::len(&[$(Self::$variant_name),*])] =
                [$(Self::$variant_name),*];
        }
    };
    // A zero-sized error struct whose messages list the expected strings.
    (@error
        [$(#[$error_meta:meta])*]
        [$vis:vis]
        $enum_name:ident
        $error_name:ident
        [$($string:literal)*]
    ) => {
        $(#[$error_meta])*
        #[doc = ::core::concat!(" Error returned when parsing [`", ::core::stringify!($enum_name), "`] from a string.")]
        $vis struct $error_name;
        const _: () = {
            use ::core;
            impl core::fmt::Display for $error_name {
                fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                    const ALL: &'static [&'static core::primitive::str] = &[$($string),*];
                    core::fmt::Display::fmt(&$crate::ParseError(&ALL), f)
                }
            }
            impl core::fmt::Debug for $error_name {
                fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                    let mut f = core::fmt::Formatter::debug_tuple(f, core::stringify!($error_name));
                    core::fmt::DebugTuple::field(&mut f, &core::format_args!("{}", self));
                    core::fmt::DebugTuple::finish(&mut f)
                }
            }
            impl core::error::Error for $error_name {}
        };
    };
}

/// Pointer-wide shared error type for [`strum!`].
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ParseError(#[doc(hidden)] pub &'static &'static [&'static str]);

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.0 {
            [] => f.write_str("Uninhabited type is impossible to parse"),
            [first] => f.write_fmt(format_args!("Expected string `{first}`")),
            [first, second] => f.write_fmt(format_args!("Expected `{first}` or `{second}`")),
            [first, rest @ .., last] => {
                f.write_fmt(format_args!("Expected one of `{first}`"))?;
                for it in rest {
                    f.write_fmt(format_args!(", `{it}`"))?
                }
                f.write_fmt(format_args!(", or `{last}`"))
            }
        }
    }
}

impl fmt::Debug for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_tuple("ParseError")
            .field(&format_args!("{self}"))
            .finish()
    }
}

impl core::error::Error for ParseError {}