gesha_rust_types/
identifier.rs

1use 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        // TODO: return error if incompatible chars found
29        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            // non-ascii character
112            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}