Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/Arian8j2/strum into Arian…
Browse files Browse the repository at this point in the history
…8j2-master
  • Loading branch information
Peter Glotfelty committed Jan 21, 2024
2 parents df478ed + ead1654 commit 73f5db2
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 4 deletions.
7 changes: 7 additions & 0 deletions strum_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,10 @@ pub fn to_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
/// },
/// Blue(usize),
/// Yellow,
/// #[strum(to_string = "purple with {sat} saturation")]
/// Purple {
/// sat: usize,
/// },
/// }
///
/// // uses the serialize string for Display
Expand All @@ -331,6 +335,9 @@ pub fn to_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
/// Color::Blue(10),
/// Color::Green { range: 42 }
/// );
/// // you can also use named fields in message
/// let purple = Color::Purple { sat: 10 };
/// assert_eq!(String::from("purple with 10 saturation"), purple.to_string());
/// ```
#[proc_macro_derive(Display, attributes(strum))]
pub fn display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
Expand Down
89 changes: 85 additions & 4 deletions strum_macros/src/macros/strings/display.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use proc_macro2::TokenStream;
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::{Data, DeriveInput, Fields, LitStr};
use syn::{punctuated::Punctuated, Data, DeriveInput, Fields, LitStr, Token};

use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};

Expand Down Expand Up @@ -32,7 +32,19 @@ pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let params = match variant.fields {
Fields::Unit => quote! {},
Fields::Unnamed(..) => quote! { (..) },
Fields::Named(..) => quote! { {..} },
Fields::Named(ref field_names) => {
// Transform named params '{ name: String, age: u8 }' to '{ ref name, ref age }'
let names: Punctuated<TokenStream, Token!(,)> = field_names
.named
.iter()
.map(|field| {
let ident = field.ident.as_ref().unwrap();
quote! { ref #ident }
})
.collect();

quote! { {#names} }
}
};

if variant_properties.to_string.is_none() && variant_properties.default.is_some() {
Expand All @@ -48,7 +60,36 @@ pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
}
}
} else {
arms.push(quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) } );
let arm = if let Fields::Named(ref field_names) = variant.fields {
let used_vars = capture_format_string_idents(&output)?;
if used_vars.is_empty() {
quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) }
} else {
// Create args like 'name = name, age = age' for format macro
let args: Punctuated<_, Token!(,)> = field_names
.named
.iter()
.filter_map(|field| {
let ident = field.ident.as_ref().unwrap();
// Only contain variables that are used in format string
if !used_vars.contains(ident) {
None
} else {
Some(quote! { #ident = #ident })
}
})
.collect();

quote! {
#[allow(unused_variables)]
#name::#ident #params => f.pad(&format!(#output, #args))
}
}
} else {
quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) }
};

arms.push(arm);
}
}

Expand All @@ -66,3 +107,43 @@ pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
}
})
}

fn capture_format_string_idents(string_literal: &LitStr) -> syn::Result<Vec<Ident>> {
// Remove escaped brackets
let format_str = string_literal.value().replace("{{", "").replace("}}", "");

let mut new_var_start_index: Option<usize> = None;
let mut var_used: Vec<Ident> = Vec::new();

for (i, chr) in format_str.bytes().enumerate() {
if chr == b'{' {
if new_var_start_index.is_some() {
return Err(syn::Error::new_spanned(
string_literal,
"Bracket opened without closing previous bracket",
));
}
new_var_start_index = Some(i);
continue;
}

if chr == b'}' {
let start_index = new_var_start_index.take().ok_or(syn::Error::new_spanned(
string_literal,
"Bracket closed without previous opened bracket",
))?;

let inside_brackets = &format_str[start_index + 1..i];
let ident_str = inside_brackets.split(":").next().unwrap();
let ident = syn::parse_str::<Ident>(ident_str).map_err(|_| {
syn::Error::new_spanned(
string_literal,
"Invalid identifier inside format string bracket",
)
})?;
var_used.push(ident);
}
}

Ok(var_used)
}
10 changes: 10 additions & 0 deletions strum_tests/tests/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ enum Color {
Blue { hue: usize },
#[strum(serialize = "y", serialize = "yellow")]
Yellow,
#[strum(to_string = "saturation is {sat}")]
Purple { sat: usize },
#[strum(default)]
Green(String),
}
Expand Down Expand Up @@ -41,6 +43,14 @@ fn to_yellow_string() {
assert_eq!(String::from("yellow"), format!("{}", Color::Yellow));
}

#[test]
fn to_purple_string() {
assert_eq!(
String::from("saturation is 10"),
(Color::Purple { sat: 10 }).to_string().as_ref()
);
}

#[test]
fn to_red_string() {
assert_eq!(String::from("RedRed"), format!("{}", Color::Red));
Expand Down

0 comments on commit 73f5db2

Please sign in to comment.