Skip to content

Commit

Permalink
feat: Initial implementation:
Browse files Browse the repository at this point in the history
The traits `FromStr`, Display`, TryFrom<String>` and `TryFrom<&str>` are
implemented.
  • Loading branch information
Yag000 committed Sep 13, 2023
1 parent c9668d7 commit db8b24b
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 20 deletions.
171 changes: 166 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,87 @@
use std::{
fmt::{Display, Formatter},
str::FromStr,
};

use proc_macro::TokenStream;
use quote::quote;

/// Derive the [`std::fmt::Display`] trait for an enum.
/// Derive [`std::fmt::Display`], [`std::str::FromStr`], [`TryFrom<&str>`] and
/// [`TryFrom<String>`] for an enum.
///
/// This macro will derive the [`std::fmt::Display`] trait for an enum. The
/// trait will print the name of the variant.
/// They simply take the name of the enum variant and convert it to a string.
///
/// # Example
///
/// ```
/// use enum_string::EnumToString;
/// use std::str::FromStr;
///
/// #[derive(EnumToString)]
/// #[derive(EnumToString, Debug, PartialEq)]
/// enum Numbers {
/// One,
/// Two,
/// }
///
/// assert_eq!(Numbers::One.to_string(), "One");
/// assert_eq!(Numbers::Two.to_string(), "Two");
///
///
/// assert_eq!(Numbers::try_from("One").unwrap(), Numbers::One);
/// assert_eq!(Numbers::try_from("Two").unwrap(), Numbers::Two);
///
/// assert!(Numbers::try_from("Three").is_err());
/// ```
///
/// # Details
///
/// The implementations of the above traits corresponds to this:
///
/// ```rust, no_run
/// enum Numbers {
/// One,
/// Two,
/// }
///
/// impl std::fmt::Display for Numbers {
/// fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
/// match self {
/// Self::One => write!(f, "One"),
/// Self::Two => write!(f, "Two"),
/// }
/// }
/// }
///
/// impl TryFrom<&str> for Numbers {
/// type Error = ();
///
/// fn try_from(s: &str) -> Result<Self, Self::Error> {
/// match s {
/// "One" => Ok(Self::One),
/// "Two" => Ok(Self::Two),
/// _ => Err(()),
/// }
/// }
/// }
///
/// impl TryFrom<String> for Numbers {
/// type Error = ();
///
/// fn try_from(s: String) -> Result<Self, Self::Error> {
/// s.as_str().try_into()
/// }
/// }
///
/// impl std::str::FromStr for Numbers {
/// type Err = ();
///
/// fn from_str(s: &str) -> Result<Self, Self::Err> {
/// s.try_into()
/// }
/// }
/// ```
///
///
#[proc_macro_derive(EnumToString)]
pub fn enum_to_string(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
Expand All @@ -34,8 +95,17 @@ fn impl_enum_to_string(ast: &syn::DeriveInput) -> TokenStream {
_ => panic!("EnumToString only works with Enums"),
};

let names = variants.iter().map(|v| &v.ident);
let names = variants.iter().map(|v| &v.ident).collect::<Vec<_>>();

let mut gen = impl_display(&name, &names);
gen.extend(impl_from_str(&name, &names));
gen.extend(impl_from_string(&name));
gen.extend(impl_from_str_trait(&name));

gen.into()
}

fn impl_display(name: &syn::Ident, names: &Vec<&syn::Ident>) -> TokenStream {
let gen = quote! {
impl std::fmt::Display for #name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
Expand All @@ -48,3 +118,94 @@ fn impl_enum_to_string(ast: &syn::DeriveInput) -> TokenStream {

gen.into()
}

fn impl_from_str(name: &syn::Ident, names: &Vec<&syn::Ident>) -> TokenStream {
let gen = quote! {
impl TryFrom<&str> for #name {
type Error = ();

fn try_from(s: &str) -> Result<Self, Self::Error> {
match s {
#(stringify!(#names) => Ok(Self::#names),)*
_ => Err(()),
}
}
}
};

gen.into()
}

fn impl_from_string(name: &syn::Ident) -> TokenStream {
let gen = quote! {
impl TryFrom<String> for #name {
type Error = ();

fn try_from(s: String) -> Result<Self, Self::Error> {
s.as_str().try_into()
}
}
};

gen.into()
}

fn impl_from_str_trait(name: &syn::Ident) -> TokenStream {
let gen = quote! {
impl std::str::FromStr for #name {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
s.try_into()
}
}
};

gen.into()
}

enum Number {
One,
Two,
Three,
}

impl Display for Number {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::One => write!(f, "One"),
Self::Two => write!(f, "Two"),
Self::Three => write!(f, "Three"),
}
}
}

impl FromStr for Number {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"One" => Ok(Self::One),
"Two" => Ok(Self::Two),
"Three" => Ok(Self::Three),
_ => Err(()),
}
}
}

impl From<&str> for Number {
fn from(s: &str) -> Self {
match s {
"One" => Self::One,
"Two" => Self::Two,
"Three" => Self::Three,
_ => panic!("Invalid string"),
}
}
}

impl From<String> for Number {
fn from(s: String) -> Self {
s.as_str().into()
}
}
44 changes: 29 additions & 15 deletions tests/basic.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,45 @@
#[derive(enum_string::EnumToString)]
use std::str::FromStr;

#[derive(enum_string::EnumToString, Debug, PartialEq)]
enum Numbers {
One,
Two,
Three,
}

#[test]
fn test_numbers() {
fn test_numbers_to_string() {
assert_eq!(Numbers::One.to_string(), "One");
assert_eq!(Numbers::Two.to_string(), "Two");
assert_eq!(Numbers::Three.to_string(), "Three");
}

#[derive(enum_string::EnumToString, Debug, PartialEq)]
#[allow(non_camel_case_types)]
enum Command {
/// Ignore this comment macro :)
HelpMe,
/// And this one too!
yay,
/// This is a help Command
_wow,
#[test]
fn test_from_str() {
assert_eq!(Numbers::try_from("One").unwrap(), Numbers::One);
assert_eq!(Numbers::try_from("Two").unwrap(), Numbers::Two);
assert_eq!(Numbers::try_from("Three").unwrap(), Numbers::Three);

assert!(Numbers::try_from("Four").is_err());
}

#[test]
fn test_command() {
assert_eq!(Command::HelpMe.to_string(), "HelpMe");
assert_eq!(Command::yay.to_string(), "yay");
assert_eq!(Command::_wow.to_string(), "_wow");
fn test_from_string() {
assert_eq!(Numbers::try_from("One".to_string()).unwrap(), Numbers::One);
assert_eq!(Numbers::try_from("Two".to_string()).unwrap(), Numbers::Two);
assert_eq!(
Numbers::try_from("Three".to_string()).unwrap(),
Numbers::Three
);

assert!(Numbers::try_from("Four".to_string()).is_err());
}

#[test]
fn test_from_str_trait() {
assert_eq!(Numbers::from_str("One").unwrap(), Numbers::One);
assert_eq!(Numbers::from_str("Two").unwrap(), Numbers::Two);
assert_eq!(Numbers::from_str("Three").unwrap(), Numbers::Three);

assert!(Numbers::from_str("Four").is_err());
}

0 comments on commit db8b24b

Please sign in to comment.