1 use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase, TitleCase};
2 use std::str::FromStr;
3 use syn::{
4     parse::{Parse, ParseStream},
5     Ident, LitStr,
6 };
7 
8 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9 pub enum CaseStyle {
10     CamelCase,
11     KebabCase,
12     MixedCase,
13     ShoutySnakeCase,
14     SnakeCase,
15     TitleCase,
16     UpperCase,
17     LowerCase,
18     ScreamingKebabCase,
19     PascalCase,
20 }
21 
22 const VALID_CASE_STYLES: &[&str] = &[
23     "camelCase",
24     "PascalCase",
25     "kebab-case",
26     "snake_case",
27     "SCREAMING_SNAKE_CASE",
28     "SCREAMING-KEBAB-CASE",
29     "lowercase",
30     "UPPERCASE",
31     "title_case",
32     "mixed_case",
33 ];
34 
35 impl Parse for CaseStyle {
parse(input: ParseStream) -> syn::Result<Self>36     fn parse(input: ParseStream) -> syn::Result<Self> {
37         let text = input.parse::<LitStr>()?;
38         let val = text.value();
39 
40         val.as_str().parse().map_err(|_| {
41             syn::Error::new_spanned(
42                 &text,
43                 format!(
44                     "Unexpected case style for serialize_all: `{}`. Valid values are: `{:?}`",
45                     val, VALID_CASE_STYLES
46                 ),
47             )
48         })
49     }
50 }
51 
52 impl FromStr for CaseStyle {
53     type Err = ();
54 
from_str(text: &str) -> Result<CaseStyle, ()>55     fn from_str(text: &str) -> Result<CaseStyle, ()> {
56         Ok(match text {
57             "camel_case" | "PascalCase" => CaseStyle::PascalCase,
58             "camelCase" => CaseStyle::CamelCase,
59             "snake_case" | "snek_case" => CaseStyle::SnakeCase,
60             "kebab_case" | "kebab-case" => CaseStyle::KebabCase,
61             "SCREAMING-KEBAB-CASE" => CaseStyle::ScreamingKebabCase,
62             "shouty_snake_case" | "shouty_snek_case" | "SCREAMING_SNAKE_CASE" => {
63                 CaseStyle::ShoutySnakeCase
64             }
65             "title_case" => CaseStyle::TitleCase,
66             "mixed_case" => CaseStyle::MixedCase,
67             "lowercase" => CaseStyle::LowerCase,
68             "UPPERCASE" => CaseStyle::UpperCase,
69             _ => return Err(()),
70         })
71     }
72 }
73 
74 pub trait CaseStyleHelpers {
convert_case(&self, case_style: Option<CaseStyle>) -> String75     fn convert_case(&self, case_style: Option<CaseStyle>) -> String;
76 }
77 
78 impl CaseStyleHelpers for Ident {
convert_case(&self, case_style: Option<CaseStyle>) -> String79     fn convert_case(&self, case_style: Option<CaseStyle>) -> String {
80         let ident_string = self.to_string();
81         if let Some(case_style) = case_style {
82             match case_style {
83                 CaseStyle::PascalCase => ident_string.to_camel_case(),
84                 CaseStyle::KebabCase => ident_string.to_kebab_case(),
85                 CaseStyle::MixedCase => ident_string.to_mixed_case(),
86                 CaseStyle::ShoutySnakeCase => ident_string.to_shouty_snake_case(),
87                 CaseStyle::SnakeCase => ident_string.to_snake_case(),
88                 CaseStyle::TitleCase => ident_string.to_title_case(),
89                 CaseStyle::UpperCase => ident_string.to_uppercase(),
90                 CaseStyle::LowerCase => ident_string.to_lowercase(),
91                 CaseStyle::ScreamingKebabCase => ident_string.to_kebab_case().to_uppercase(),
92                 CaseStyle::CamelCase => {
93                     let camel_case = ident_string.to_camel_case();
94                     let mut pascal = String::with_capacity(camel_case.len());
95                     let mut it = camel_case.chars();
96                     if let Some(ch) = it.next() {
97                         pascal.extend(ch.to_lowercase());
98                     }
99                     pascal.extend(it);
100                     pascal
101                 }
102             }
103         } else {
104             ident_string
105         }
106     }
107 }
108 
109 #[test]
test_convert_case()110 fn test_convert_case() {
111     let id = Ident::new("test_me", proc_macro2::Span::call_site());
112     assert_eq!("testMe", id.convert_case(Some(CaseStyle::CamelCase)));
113     assert_eq!("TestMe", id.convert_case(Some(CaseStyle::PascalCase)));
114 }
115