gesha_rust_types/
identifier.rs1use crate::ModuleName;
2use heck::{ToSnakeCase, ToUpperCamelCase};
3use std::fmt::{Display, Formatter};
4use syn::Ident;
5use syn::parse_str;
6
7#[derive(Clone, Debug, Hash, Eq, PartialEq)]
8pub struct TypeIdentifier(String);
9
10impl TypeIdentifier {
11 pub fn parse<A: AsRef<str>>(a: A) -> Self {
12 let a = a.as_ref();
13 let converted = a.to_upper_camel_case();
14 let result = parse_str::<Ident>(&converted);
15 if result.is_ok() {
16 return Self(converted);
17 }
18 let init: Vec<String> = vec!["".to_string()];
19 let mut converted = a
20 .chars()
21 .fold(init, replace_symbol_with_name)
22 .join("_")
23 .to_upper_camel_case();
24
25 if converted.starts_with(char::is_numeric) {
26 converted = "_".to_string() + &converted;
27 }
28 Self(converted)
30 }
31
32 pub fn to_mod_name(&self) -> ModuleName {
33 ModuleName::new(self.0.to_snake_case())
34 }
35}
36
37fn replace_symbol_with_name(mut acc: Vec<String>, c: char) -> Vec<String> {
38 match ascii_symbol_to_name(c) {
39 Some(converted) => {
40 acc.push(converted.into());
41 acc.push("".to_string());
42 }
43 _ => {
44 let last = acc.len() - 1;
45 acc[last].push(c);
46 }
47 };
48 acc
49}
50
51impl Display for TypeIdentifier {
52 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53 Display::fmt(&self.0, f)
54 }
55}
56
57impl AsRef<str> for TypeIdentifier {
58 fn as_ref(&self) -> &str {
59 &self.0
60 }
61}
62
63impl From<TypeIdentifier> for String {
64 fn from(this: TypeIdentifier) -> Self {
65 this.0
66 }
67}
68
69impl PartialEq<&str> for TypeIdentifier {
70 fn eq(&self, other: &&str) -> bool {
71 self.0 == *other
72 }
73}
74
75fn ascii_symbol_to_name(c: char) -> Option<&'static str> {
76 let str = match c {
77 ' ' => "space",
78 '!' => "exclamation",
79 '"' => "double_quote",
80 '#' => "hash",
81 '$' => "dollar",
82 '%' => "percent",
83 '&' => "ampersand",
84 '\'' => "apostrophe",
85 '(' => "left_parenthesis",
86 ')' => "right_parenthesis",
87 '*' => "asterisk",
88 '+' => "plus",
89 ',' => "comma",
90 '-' => "minus",
91 '.' => "period",
92 '/' => "slash",
93 ':' => "colon",
94 ';' => "semicolon",
95 '<' => "less_than",
96 '=' => "equals",
97 '>' => "greater_than",
98 '?' => "question",
99 '@' => "at",
100 '[' => "left_bracket",
101 '\\' => "backslash",
102 ']' => "right_bracket",
103 '^' => "caret",
104 '_' => "underscore",
105 '`' => "backtick",
106 '{' => "left_brace",
107 '|' => "pipe",
108 '}' => "right_brace",
109 '~' => "tilde",
110 _ => {
111 return None;
113 }
114 };
115 Some(str)
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn ok_as_it_is() {
124 let actual = TypeIdentifier::parse("hello_world");
125 assert_eq!(actual, "HelloWorld");
126 }
127
128 #[test]
129 fn ok_only_symbol() {
130 let actual = TypeIdentifier::parse("*+-/");
131 let expected = "AsteriskPlusMinusSlash";
132 assert_eq!(actual, expected);
133 }
134
135 #[test]
136 fn ok_starts_with_numeric() {
137 let actual = TypeIdentifier::parse("123foo");
138 let expected = "_123foo";
139 assert_eq!(actual, expected);
140 }
141
142 #[test]
143 fn ok_with_numeric_and_symbol() {
144 let actual = TypeIdentifier::parse("1+foo=345%bar");
145 let expected = "_1PlusFooEquals345PercentBar";
146 assert_eq!(actual, expected);
147 }
148
149 #[test]
150 fn ok_with_minus() {
151 let actual = TypeIdentifier::parse("-42");
152 let expected = "Minus42";
153 assert_eq!(actual, expected);
154 }
155
156 #[test]
157 fn ok_with_numeric_and_symbol_as_it_is() {
158 let actual = TypeIdentifier::parse("_42");
159 let expected = "Underscore42";
160 assert_eq!(actual, expected);
161 }
162
163 #[test]
164 fn ok_with_symbol_and_numeric() {
165 let actual = TypeIdentifier::parse("%_42");
166 let expected = "PercentUnderscore42";
167 assert_eq!(actual, expected);
168 }
169}