Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prefix suffix #1

Merged
merged 4 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ license = "MIT OR Apache-2.0"
homepage = "https://github.com/Yag000/enum_stringify"
repository = "https://github.com/Yag000/enum_stringify"
documentation = "https://docs.rs/enum_stringify"
keywords = ["enum", "string", "derive", "macro"]


# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand All @@ -19,3 +20,7 @@ proc-macro = true
[dependencies]
quote = "1.0.33"
syn = "2.0.32"

[dev-dependencies]
serde = { version = "1.0.130", features = ["derive"] }

20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,26 @@ fn main() {
}
```

### Custom string representation

You can customize the string representation of the enum by adding rpefixes or/and suffixes to the
variants.

```rust
use enum_stringify::EnumStringify;

#[derive(EnumStringify)]
#[enum_stringify(prefix = "MyPrefix", suffix = "MySuffix")]
enum MyEnum {
Variant1,
Variant2,
Variant3,
}
```

In this case the string representation of `MyEnum::Variant1` will be `MyPrefixVariant1MySuffix`(and
so on for the other variants).

## Documentation and installation

See [docs.rs](https://docs.rs/enum-stringify) for documentation.
Expand Down
100 changes: 100 additions & 0 deletions src/attributes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use syn::{DeriveInput, Meta};

pub(crate) enum Case {
Camel,
Snake,
None,
}

pub(crate) struct Attributes {
pub(crate) case: Option<Case>,
pub(crate) prefix: Option<String>,
pub(crate) suffix: Option<String>,
}

impl Attributes {
pub(crate) fn new(ast: &DeriveInput) -> Self {
let mut new = Self {
case: None,
prefix: None,
suffix: None,
};

ast.attrs.iter().for_each(|attr| match &attr.meta {
Meta::Path(_) => {
panic!("Unexpected argument");
}
Meta::List(list) => {
let path = list
.path
.segments
.iter()
.map(|s| s.ident.to_string())
.collect::<Vec<_>>();

if path == vec!["enum_stringify"] {
let mut tokens = list.tokens.clone().into_iter();

while let Some(attribute_type) = tokens.next() {
let attribute_type = attribute_type.to_string();

if tokens.next().expect("type must be specified").to_string() != "=" {
panic!("too many arguments");
}
let value = tokens.next().expect("value must be specified").to_string();

match attribute_type.as_str() {
"case" => {
let case = match value.as_str() {
"camel" => Case::Camel,
"snake" => Case::Snake,
_ => Case::None,
};
new.case = Some(case);
}
"prefix" => {
new.prefix = Some(value);
}
"suffix" => {
new.suffix = Some(value);
}
_ => {
panic!("Attribute not supported");
}
}

if let Some(comma_separator) = tokens.next() {
if comma_separator.to_string() != "," {
panic!("Expected a commaseparated attribute list");
}
}
}
}
}
Meta::NameValue(_) => {
panic!("Unexpected argument");
}
});

new
}

pub(crate) fn apply(&self, names: &Vec<&syn::Ident>) -> Vec<syn::Ident> {
let mut new_names = Vec::new();
for name in names {
let mut new_name = String::new();
if let Some(prefix) = &self.prefix {
new_name.push_str(prefix);
}

// Add here case logic
new_name.push_str(&name.to_string());

if let Some(suffix) = &self.suffix {
new_name.push_str(suffix);
}
new_names.push(syn::Ident::new(&new_name, name.span()));
}
new_names
}
}
56 changes: 47 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
//! Derive [`std::fmt::Display`], [`std::str::FromStr`], [`TryFrom<&str>`] and
//! [`TryFrom<String>`] with a simple derive macro: [`EnumStringify`].

use attributes::Attributes;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

mod attributes;

/// Derive [`std::fmt::Display`], [`std::str::FromStr`], [`TryFrom<&str>`] and
/// [`TryFrom<String>`] for an enum.
Expand Down Expand Up @@ -33,6 +37,29 @@ use quote::quote;
/// assert!(Numbers::try_from("Three").is_err());
/// ```
///
/// # Prefix and suffix
///
/// You can add a prefix and/or a suffix to the string representation of the
/// enum variants.
///
/// ```
/// use enum_stringify::EnumStringify;
/// use std::str::FromStr;
///
/// #[derive(EnumStringify, Debug, PartialEq)]
/// #[enum_stringify(prefix = MyPrefix, suffix = MySuffix)]
/// enum Numbers {
/// One,
/// Two,
/// }
///
/// assert_eq!(Numbers::One.to_string(), "MyPrefixOneMySuffix");
/// assert_eq!(Numbers::Two.to_string(), "MyPrefixTwoMySuffix");
///
/// assert_eq!(Numbers::try_from("MyPrefixOneMySuffix").unwrap(), Numbers::One);
/// assert_eq!(Numbers::try_from("MyPrefixTwoMySuffix").unwrap(), Numbers::Two);
/// ```
///
/// # Details
///
/// The implementations of the above traits corresponds to this:
Expand Down Expand Up @@ -80,36 +107,43 @@ use quote::quote;
/// }
/// }
/// ```
#[proc_macro_derive(EnumStringify)]
#[proc_macro_derive(EnumStringify, attributes(enum_stringify))]
pub fn enum_stringify(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
let ast = parse_macro_input!(input as DeriveInput);

impl_enum_to_string(&ast)
}

fn impl_enum_to_string(ast: &syn::DeriveInput) -> TokenStream {
let attributes = Attributes::new(ast);
let name = &ast.ident;
let variants = match ast.data {
syn::Data::Enum(ref e) => &e.variants,
_ => panic!("EnumToString only works with Enums"),
};

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

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

gen
}

/// Implementation of [`std::fmt::Display`].
fn impl_display(name: &syn::Ident, names: &Vec<&syn::Ident>) -> TokenStream {
fn impl_display(
name: &syn::Ident,
identifiers: &Vec<&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 {
match self {
#(Self::#names => write!(f, stringify!(#names))),*
#(Self::#identifiers=> write!(f, stringify!(#names))),*
}
}
}
Expand All @@ -119,14 +153,18 @@ fn impl_display(name: &syn::Ident, names: &Vec<&syn::Ident>) -> TokenStream {
}

/// Implementation of [`TryFrom<&str>`].
fn impl_from_str(name: &syn::Ident, names: &Vec<&syn::Ident>) -> TokenStream {
fn impl_from_str(
name: &syn::Ident,
identifiers: &Vec<&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),)*
#(stringify!(#names) => Ok(Self::#identifiers),)*
_ => Err(()),
}
}
Expand Down
90 changes: 90 additions & 0 deletions tests/attributes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use std::str::FromStr;

#[derive(Debug, PartialEq, enum_stringify::EnumStringify)]
#[enum_stringify(suffix = Suff)]
enum Number1 {
Zero,
One,
Two,
}

#[test]
fn test_suffix_to_string() {
assert_eq!(Number1::Zero.to_string(), "ZeroSuff");
assert_eq!(Number1::One.to_string(), "OneSuff");
assert_eq!(Number1::Two.to_string(), "TwoSuff");
}
#[test]
fn test_suffix_from_str() {
assert_eq!(Number1::from_str("ZeroSuff"), Ok(Number1::Zero));
assert_eq!(Number1::from_str("OneSuff"), Ok(Number1::One));
assert_eq!(Number1::from_str("TwoSuff"), Ok(Number1::Two));
}

#[derive(Debug, PartialEq, enum_stringify::EnumStringify)]
#[enum_stringify(prefix = Pref)]
enum Number2 {
Zero,
One,
Two,
}

#[test]
fn test_prefix_to_string() {
assert_eq!(Number2::Zero.to_string(), "PrefZero");
assert_eq!(Number2::One.to_string(), "PrefOne");
assert_eq!(Number2::Two.to_string(), "PrefTwo");
}

#[test]
fn test_prefix_from_str() {
assert_eq!(Number2::from_str("PrefZero"), Ok(Number2::Zero));
assert_eq!(Number2::from_str("PrefOne"), Ok(Number2::One));
assert_eq!(Number2::from_str("PrefTwo"), Ok(Number2::Two));
}

#[derive(Debug, PartialEq, enum_stringify::EnumStringify)]
#[enum_stringify(prefix = Pref, suffix = Suff)]
enum Number3 {
Zero,
One,
Two,
}

#[test]
fn test_prefix_suffix_to_string() {
assert_eq!(Number3::Zero.to_string(), "PrefZeroSuff");
assert_eq!(Number3::One.to_string(), "PrefOneSuff");
assert_eq!(Number3::Two.to_string(), "PrefTwoSuff");
}

#[test]
fn test_prefix_suffix_from_str() {
assert_eq!(Number3::from_str("PrefZeroSuff"), Ok(Number3::Zero));
assert_eq!(Number3::from_str("PrefOneSuff"), Ok(Number3::One));
assert_eq!(Number3::from_str("PrefTwoSuff"), Ok(Number3::Two));
}

// Testing commutativity of prefix and suffix

#[derive(Debug, PartialEq, enum_stringify::EnumStringify)]
#[enum_stringify(suffix = Suff, prefix = Pref)]
enum Number4 {
Zero,
One,
Two,
}

#[test]
fn test_suffix_prefix_to_string() {
assert_eq!(Number4::Zero.to_string(), "PrefZeroSuff");
assert_eq!(Number4::One.to_string(), "PrefOneSuff");
assert_eq!(Number4::Two.to_string(), "PrefTwoSuff");
}

#[test]
fn test_suffix_prefix_from_str() {
assert_eq!(Number4::from_str("PrefZeroSuff"), Ok(Number4::Zero));
assert_eq!(Number4::from_str("PrefOneSuff"), Ok(Number4::One));
assert_eq!(Number4::from_str("PrefTwoSuff"), Ok(Number4::Two));
}
Loading