gesha_rust_types/
identifier.rs

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