Repository: Peternator7/strum
Pull request #296 (2023-09-15 09:04:35)

Add the `prefix` attribute
The idea here was to allow the addition of the `prefix` attribute such that when displaying the enum variant that said variant will have the prefix. This should be exempt from other formatting. Example usage:

```
use strum::{Display, EnumString};

#[derive(Debug, EnumString, Display)]
#[strum(prefix = "prefix")]
enum Thing {
    Test,
}

fn main() {
    println!("{}", Thing::Test);  // prints "prefixTest"
}
```

Could possibly add some kind of skipping mechanism for individual variants.


Patch:
diff --git a/strum_macros/src/helpers/case_style.rs b/strum_macros/src/helpers/case_style.rs
index 86a8583..bcea788 100644
--- a/strum_macros/src/helpers/case_style.rs
+++ b/strum_macros/src/helpers/case_style.rs
@@ -1,5 +1,6 @@
 use heck::{
-    ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase, ToUpperCamelCase, ToTrainCase,
+    ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase, ToTrainCase,
+    ToUpperCamelCase,
 };
 use std::str::FromStr;
 use syn::{
diff --git a/strum_macros/src/helpers/metadata.rs b/strum_macros/src/helpers/metadata.rs
index d638ae3..19eb927 100644
--- a/strum_macros/src/helpers/metadata.rs
+++ b/strum_macros/src/helpers/metadata.rs
@@ -17,6 +17,7 @@ pub mod kw {
     // enum metadata
     custom_keyword!(serialize_all);
     custom_keyword!(use_phf);
+    custom_keyword!(prefix);
 
     // enum discriminant metadata
     custom_keyword!(derive);
@@ -45,6 +46,10 @@ pub enum EnumMeta {
         crate_module_path: Path,
     },
     UsePhf(kw::use_phf),
+    Prefix {
+        kw: kw::prefix,
+        prefix: LitStr,
+    },
 }
 
 impl Parse for EnumMeta {
@@ -69,6 +74,11 @@ impl Parse for EnumMeta {
             Ok(EnumMeta::AsciiCaseInsensitive(input.parse()?))
         } else if lookahead.peek(kw::use_phf) {
             Ok(EnumMeta::UsePhf(input.parse()?))
+        } else if lookahead.peek(kw::prefix) {
+            let kw = input.parse::<kw::prefix>()?;
+            input.parse::<Token![=]>()?;
+            let prefix = input.parse()?;
+            Ok(EnumMeta::Prefix { kw, prefix })
         } else {
             Err(lookahead.error())
         }
diff --git a/strum_macros/src/helpers/mod.rs b/strum_macros/src/helpers/mod.rs
index 142ea0b..724fce2 100644
--- a/strum_macros/src/helpers/mod.rs
+++ b/strum_macros/src/helpers/mod.rs
@@ -1,4 +1,4 @@
-pub use self::case_style::{CaseStyleHelpers, snakify};
+pub use self::case_style::{snakify, CaseStyleHelpers};
 pub use self::type_props::HasTypeProperties;
 pub use self::variant_props::HasStrumVariantProperties;
 
diff --git a/strum_macros/src/helpers/type_props.rs b/strum_macros/src/helpers/type_props.rs
index 17d84c9..7302853 100644
--- a/strum_macros/src/helpers/type_props.rs
+++ b/strum_macros/src/helpers/type_props.rs
@@ -1,7 +1,7 @@
 use proc_macro2::TokenStream;
 use quote::quote;
 use std::default::Default;
-use syn::{parse_quote, DeriveInput, Ident, Path, Visibility};
+use syn::{parse_quote, DeriveInput, Ident, LitStr, Path, Visibility};
 
 use super::case_style::CaseStyle;
 use super::metadata::{DeriveInputExt, EnumDiscriminantsMeta, EnumMeta};
@@ -21,6 +21,7 @@ pub struct StrumTypeProperties {
     pub discriminant_others: Vec<TokenStream>,
     pub discriminant_vis: Option<Visibility>,
     pub use_phf: bool,
+    pub prefix: Option<LitStr>,
     pub enum_repr: Option<TokenStream>,
 }
 
@@ -35,6 +36,7 @@ impl HasTypeProperties for DeriveInput {
         let mut ascii_case_insensitive_kw = None;
         let mut use_phf_kw = None;
         let mut crate_module_path_kw = None;
+        let mut prefix_kw = None;
         for meta in strum_meta {
             match meta {
                 EnumMeta::SerializeAll { case_style, kw } => {
@@ -72,6 +74,14 @@ impl HasTypeProperties for DeriveInput {
                     crate_module_path_kw = Some(kw);
                     output.crate_module_path = Some(crate_module_path);
                 }
+                EnumMeta::Prefix { prefix, kw } => {
+                    if let Some(fst_kw) = prefix_kw {
+                        return Err(occurrence_error(fst_kw, kw, "prefix"));
+                    }
+
+                    prefix_kw = Some(kw);
+                    output.prefix = Some(prefix);
+                }
             }
         }
 
diff --git a/strum_macros/src/macros/enum_messages.rs b/strum_macros/src/macros/enum_messages.rs
index c056108..2dad411 100644
--- a/strum_macros/src/macros/enum_messages.rs
+++ b/strum_macros/src/macros/enum_messages.rs
@@ -74,14 +74,17 @@ pub fn enum_message_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
         if !documentation.is_empty() {
             let params = params.clone();
             // Strip a single leading space from each documentation line.
-            let documentation: Vec<LitStr> = documentation.iter().map(|lit_str| {
-                let line = lit_str.value();
-                if line.starts_with(' ') {
-                    LitStr::new(&line.as_str()[1..], lit_str.span())
-                } else {
-                    lit_str.clone()
-                }
-            }).collect();
+            let documentation: Vec<LitStr> = documentation
+                .iter()
+                .map(|lit_str| {
+                    let line = lit_str.value();
+                    if line.starts_with(' ') {
+                        LitStr::new(&line.as_str()[1..], lit_str.span())
+                    } else {
+                        lit_str.clone()
+                    }
+                })
+                .collect();
             if documentation.len() == 1 {
                 let text = &documentation[0];
                 documentation_arms
diff --git a/strum_macros/src/macros/enum_variant_names.rs b/strum_macros/src/macros/enum_variant_names.rs
index c54d45d..f53b0af 100644
--- a/strum_macros/src/macros/enum_variant_names.rs
+++ b/strum_macros/src/macros/enum_variant_names.rs
@@ -1,6 +1,6 @@
 use proc_macro2::TokenStream;
 use quote::quote;
-use syn::{Data, DeriveInput};
+use syn::{Data, DeriveInput, LitStr};
 
 use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
 
@@ -24,7 +24,7 @@ pub fn enum_variant_names_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
             let props = v.get_variant_properties()?;
             Ok(props.get_preferred_name(type_properties.case_style))
         })
-        .collect::<syn::Result<Vec<_>>>()?;
+        .collect::<syn::Result<Vec<LitStr>>>()?;
 
     Ok(quote! {
         impl #impl_generics #strum_module_path::VariantNames for #name #ty_generics #where_clause {
diff --git a/strum_macros/src/macros/strings/display.rs b/strum_macros/src/macros/strings/display.rs
index 77f8afa..e6a2459 100644
--- a/strum_macros/src/macros/strings/display.rs
+++ b/strum_macros/src/macros/strings/display.rs
@@ -1,6 +1,6 @@
 use proc_macro2::TokenStream;
 use quote::quote;
-use syn::{Data, DeriveInput, Fields};
+use syn::{Data, DeriveInput, Fields, LitStr};
 
 use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
 
@@ -24,7 +24,10 @@ pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
         }
 
         // Look at all the serialize attributes.
-        let output = variant_properties.get_preferred_name(type_properties.case_style);
+        let mut output = variant_properties.get_preferred_name(type_properties.case_style);
+        if let Some(prefix) = &type_properties.prefix {
+            output = LitStr::new(&(prefix.value() + &output.value()), output.span());
+        }
 
         let params = match variant.fields {
             Fields::Unit => quote! {},
diff --git a/strum_macros/src/macros/strings/from_string.rs b/strum_macros/src/macros/strings/from_string.rs
index 2d25591..4b9a721 100644
--- a/strum_macros/src/macros/strings/from_string.rs
+++ b/strum_macros/src/macros/strings/from_string.rs
@@ -79,7 +79,7 @@ pub fn from_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
                 phf_exact_match_arms.push(quote! { #serialization => #name::#ident #params, });
 
                 if is_ascii_case_insensitive {
-                    // Store the lowercase and UPPERCASE variants in the phf map to capture 
+                    // Store the lowercase and UPPERCASE variants in the phf map to capture
                     let ser_string = serialization.value();
 
                     let lower =


Test patch:
diff --git a/strum_tests/tests/enum_is.rs b/strum_tests/tests/enum_is.rs
index 4e997fe..3226ebb 100644
--- a/strum_tests/tests/enum_is.rs
+++ b/strum_tests/tests/enum_is.rs
@@ -11,8 +11,13 @@ enum LifeTimeTest<'a>{
 enum Foo {
     Unit,
     Named0 {},
-    Named1 { _a: char },
-    Named2 { _a: u32, _b: String },
+    Named1 {
+        _a: char,
+    },
+    Named2 {
+        _a: u32,
+        _b: String,
+    },
     Unnamed0(),
     Unnamed1(Option<u128>),
     Unnamed2(bool, u8),
diff --git a/strum_tests/tests/enum_message.rs b/strum_tests/tests/enum_message.rs
index b1c8600..e7ff303 100644
--- a/strum_tests/tests/enum_message.rs
+++ b/strum_tests/tests/enum_message.rs
@@ -49,7 +49,10 @@ fn only_detailed_message() {
 
 #[test]
 fn documentation() {
-    assert_eq!("I eat birds.\n\nAnd fish.\n", (Pets::Cat).get_documentation().unwrap());
+    assert_eq!(
+        "I eat birds.\n\nAnd fish.\n",
+        (Pets::Cat).get_documentation().unwrap()
+    );
     assert_eq!("I'm a fish.", (Pets::Fish).get_documentation().unwrap());
     assert_eq!("I'm a bird.", (Pets::Bird).get_documentation().unwrap());
 }
diff --git a/strum_tests/tests/enum_try_as.rs b/strum_tests/tests/enum_try_as.rs
index aa54205..6cbf81e 100644
--- a/strum_tests/tests/enum_try_as.rs
+++ b/strum_tests/tests/enum_try_as.rs
@@ -11,7 +11,10 @@ enum Foo {
     #[allow(dead_code)]
     Unit,
     #[allow(dead_code)]
-    Named { _a: u32, _b: String },
+    Named {
+        _a: u32,
+        _b: String,
+    },
 }
 
 #[test]
diff --git a/strum_tests/tests/prefix.rs b/strum_tests/tests/prefix.rs
new file mode 100644
index 0000000..806b5d5
--- /dev/null
+++ b/strum_tests/tests/prefix.rs
@@ -0,0 +1,20 @@
+use strum::{Display, EnumString};
+
+#[allow(dead_code)]
+#[derive(Debug, EnumString, Display)]
+#[strum(prefix = "colour/")]
+enum Color {
+    #[strum(to_string = "RedRed")]
+    Red,
+    #[strum(serialize = "b", to_string = "blue")]
+    Blue { hue: usize },
+    #[strum(serialize = "y", serialize = "yellow")]
+    Yellow,
+    #[strum(default)]
+    Green(String),
+}
+
+#[test]
+fn prefix_redred() {
+    assert_eq!(String::from("colour/RedRed"), (Color::Red).to_string());
+}