use std::any::type_name;
use std::fmt::{Debug, Display};
use std::marker::PhantomData;
#[derive(Debug, PartialEq, Eq)]
pub struct UnknownEnumVariant<E>(pub String, pub PhantomData<E>);
impl<E> Display for UnknownEnumVariant<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let type_name = type_name::<E>();
let type_name = type_name.rsplit("::").next().unwrap_or(type_name);
write!(f, "unknown {} {:?}", type_name, self.0)
}
}
impl<E: Debug> std::error::Error for UnknownEnumVariant<E> {}
#[macro_export]
macro_rules! define_str_enum {
(
$(#![$macro_attr:ident])?
$(#[$emeta:meta])*
$vis:vis enum $enum:ident {
$(
$(#[$varmeta:meta])*
$variant:ident = $display:literal,
)+
}
) => {
$(#[$emeta])*
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
$vis enum $enum {
$(
$(#[$varmeta])*
$variant,
)+
}
#[allow(dead_code)]
impl $enum {
$vis const fn as_str(&self) -> &'static str {
match self {
$(
Self::$variant => $display,
)+
}
}
$vis const fn as_cstr(&self) -> &'static ::std::ffi::CStr {
match self {
$(
Self::$variant => unsafe {
::std::ffi::CStr::from_bytes_with_nul_unchecked(
::std::concat!($display, "\0").as_bytes()
)
}
)+
}
}
#[inline(always)]
$vis const fn values() -> &'static [&'static str] {
&[ $( $display, )+ ]
}
}
impl ::std::convert::AsRef<str> for $enum {
#[inline(always)]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl ::std::ops::Deref for $enum {
type Target = str;
#[inline(always)]
fn deref(&self) -> &str {
self.as_str()
}
}
impl ::std::convert::From<$enum> for ::std::string::String {
#[inline(always)]
fn from(e: $enum) -> Self {
e.as_str().into()
}
}
impl ::std::convert::From<$enum> for &'static str {
#[inline(always)]
fn from(e: $enum) -> Self {
e.as_str()
}
}
impl ::std::str::FromStr for $enum {
type Err = $crate::define_str_enum::UnknownEnumVariant<$enum>;
fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
use ::std::marker::PhantomData;
use $crate::define_str_enum::UnknownEnumVariant;
use ::std::result::Result::{Ok, Err};
$($crate::define_str_enum! { @attr $macro_attr
let s = s.trim();
let s = s.to_lowercase();
let s = s.as_str();
})?
match s {
$(
$display => Ok(Self::$variant),
)+
_ => Err(UnknownEnumVariant(s.into(), PhantomData)),
}
}
}
impl ::std::fmt::Display for $enum {
#[inline(always)]
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
f.write_str(self.as_str())
}
}
impl serde::Serialize for $enum {
#[inline(always)]
fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> serde::Deserialize<'de> for $enum {
#[inline]
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use ::std::result::Result::Ok;
use serde::de::Error;
let tmp = <&str>::deserialize(deserializer)?;
let res = tmp.parse().map_err(|_| {
Error::unknown_variant(tmp, Self::values())
})?;
Ok(res)
}
}
impl<L: $crate::tlua::AsLua> $crate::tlua::Push<L> for $enum {
type Err = $crate::tlua::Void;
#[inline(always)]
fn push_to_lua(&self, lua: L) -> $crate::tlua::PushResult<L, Self> {
$crate::tlua::PushInto::push_into_lua(self.as_str(), lua)
}
}
impl<L: $crate::tlua::AsLua> $crate::tlua::PushOne<L> for $enum {}
impl<L: $crate::tlua::AsLua> $crate::tlua::PushInto<L> for $enum {
type Err = $crate::tlua::Void;
#[inline(always)]
fn push_into_lua(self, lua: L) -> $crate::tlua::PushIntoResult<L, Self> {
$crate::tlua::PushInto::push_into_lua(self.as_str(), lua)
}
}
impl<L: $crate::tlua::AsLua> $crate::tlua::PushOneInto<L> for $enum {}
impl<L: $crate::tlua::AsLua> $crate::tlua::LuaRead<L> for $enum {
#[inline]
fn lua_read_at_position(
lua: L,
index: ::std::num::NonZeroI32
) -> $crate::tlua::ReadResult<Self, L> {
let s = $crate::tlua::StringInLua::lua_read_at_position(lua, index)?;
match s.parse() {
Ok(v) => Ok(v),
Err(_) => {
let e = $crate::tlua::WrongType::info("reading string enum")
.expected(format!("one of {:?}", Self::values()))
.actual(format!("string '{}'", &*s));
Err((s.into_inner(), e))
}
}
}
}
};
(@attr coerce_from_str $($then:tt)*) => {
$($then)*
};
(@attr $other:ident $($then:tt)*) => {
compile_error!(
concat!("unknown attribute: ", stringify!($other))
)
};
}