sdf_common/
render.rs

1use std::{fs::File, io::Write, path::Path, sync::OnceLock};
2
3use anyhow::Result;
4use check_keyword::CheckKeyword;
5use convert_case::{Boundary, Case, Casing};
6use regex::Regex;
7
8static RE: OnceLock<Regex> = OnceLock::new();
9
10fn dash_or_underscore_digit_regex() -> &'static Regex {
11    RE.get_or_init(|| Regex::new(r"[-_](\d+)").unwrap())
12}
13
14pub fn rust_type_case(value: &str) -> String {
15    if ["u8", "u16", "u32", "u64", "f32", "f64", "bool"].contains(&value) {
16        return value.to_owned();
17    }
18
19    if ["s8", "s16", "s32", "s64"].contains(&value) {
20        return value.replace('s', "i");
21    }
22
23    if ["f32", "f64"].contains(&value) {
24        return value.replace("float", "f");
25    }
26
27    value.to_case(Case::Pascal)
28}
29
30pub fn rust_name_case(value: &str) -> String {
31    let case = if value.is_case(Case::Kebab) {
32        Case::Kebab
33    } else if value.is_case(Case::UpperKebab) {
34        Case::UpperKebab
35    } else if value.is_case(Case::Train) {
36        Case::Train
37    } else if value.is_case(Case::Ada) {
38        Case::Ada
39    } else if value.is_case(Case::UpperSnake) {
40        Case::UpperSnake
41    } else if value.is_case(Case::UpperCamel) {
42        Case::UpperCamel
43    } else if value.is_case(Case::Pascal) {
44        Case::Pascal
45    } else if value.is_case(Case::Camel) {
46        Case::Camel
47    } else if value.is_case(Case::Snake) {
48        Case::Snake
49    } else {
50        Case::Kebab
51    };
52
53    let value = value
54        .from_case(case)
55        .without_boundaries(&Boundary::letter_digit())
56        .to_case(Case::Snake);
57
58    let value = dash_or_underscore_digit_regex()
59        .replace_all(&value, "$1")
60        .to_string();
61
62    if value.is_keyword() {
63        format!("{}_", value)
64    } else {
65        value
66    }
67}
68
69pub fn upper_snake(value: &str) -> String {
70    value.to_case(Case::UpperSnake)
71}
72
73pub fn wit_name_case(name: &str) -> String {
74    let case = if name.is_case(Case::UpperKebab) {
75        Case::UpperKebab
76    } else if name.is_case(Case::Train) {
77        Case::Train
78    } else if name.is_case(Case::Ada) {
79        Case::Ada
80    } else if name.is_case(Case::UpperSnake) {
81        Case::UpperSnake
82    } else if name.is_case(Case::UpperCamel) {
83        Case::UpperCamel
84    } else if name.is_case(Case::Pascal) {
85        Case::Pascal
86    } else if name.is_case(Case::Camel) {
87        Case::Camel
88    } else if name.is_case(Case::Kebab) {
89        Case::Kebab
90    } else {
91        Case::Snake
92    };
93
94    let kebab_case_name = name
95        .from_case(case)
96        .without_boundaries(&Boundary::letter_digit())
97        .to_case(Case::Kebab);
98
99    dash_or_underscore_digit_regex()
100        .replace_all(&kebab_case_name, "$1")
101        .to_string()
102}
103
104pub fn upper_camel_case(value: &str) -> String {
105    value.to_case(Case::UpperCamel)
106}
107
108pub fn is_wit_type_or_keyword(value: &str) -> bool {
109    [
110        "bool", "string", "s8", "s16", "s32", "s64", "u8", "u16", "u32", "u64", "f32", "f64",
111        "char", "list", "option", "result", "tuple",
112    ]
113    .contains(&value)
114        || is_wit_keyword(value)
115}
116
117fn is_wit_keyword(value: &str) -> bool {
118    [
119        "record",
120        "variant",
121        "enum",
122        "resource",
123        "type",
124        "world",
125        "interface",
126        "use",
127        "package",
128    ]
129    .contains(&value)
130}
131
132pub fn map_wit_keyword(value: &str) -> String {
133    if is_wit_keyword(value) {
134        format!("%{}", value)
135    } else {
136        value.to_owned()
137    }
138}
139
140pub fn create_sdf_gitignore(path: impl AsRef<Path>) -> Result<()> {
141    let path = path.as_ref();
142    let gitignore = path.join(".gitignore");
143    if !gitignore.exists() {
144        let mut file = File::create(&gitignore)?;
145        file.write_all(b"*\n")?;
146        file.flush()?;
147    }
148    Ok(())
149}
150
151#[cfg(test)]
152mod tests {
153
154    use super::*;
155
156    #[test]
157    fn test_rust_type_case() {
158        assert_eq!(rust_type_case("string"), "String");
159        assert_eq!(rust_type_case("bool"), "bool");
160        assert_eq!(rust_type_case("s8"), "i8");
161        assert_eq!(rust_type_case("s16"), "i16");
162        assert_eq!(rust_type_case("s32"), "i32");
163        assert_eq!(rust_type_case("s64"), "i64");
164        assert_eq!(rust_type_case("u8"), "u8");
165        assert_eq!(rust_type_case("u16"), "u16");
166        assert_eq!(rust_type_case("u32"), "u32");
167        assert_eq!(rust_type_case("u64"), "u64");
168        assert_eq!(rust_type_case("f32"), "f32");
169        assert_eq!(rust_type_case("f64"), "f64");
170        assert_eq!(rust_type_case("my-type"), "MyType");
171        assert_eq!(rust_type_case("my-type-2"), "MyType2");
172    }
173
174    #[test]
175    fn test_rust_case_name() {
176        assert_eq!(rust_name_case("MyType"), "my_type");
177        assert_eq!(rust_name_case("MyType2"), "my_type2");
178        assert_eq!(rust_name_case("my-type"), "my_type");
179        assert_eq!(rust_name_case("My_Type"), "my_type");
180        assert_eq!(rust_name_case("myType"), "my_type");
181        assert_eq!(rust_name_case("type"), "type_");
182        assert_eq!(wit_name_case("line0"), "line0");
183    }
184
185    #[test]
186    fn test_wit_name_case() {
187        assert_eq!(wit_name_case("MyType0"), "my-type0");
188        assert_eq!(wit_name_case("MyType"), "my-type");
189        assert_eq!(wit_name_case("myType0"), "my-type0");
190        assert_eq!(wit_name_case("myType"), "my-type");
191        assert_eq!(wit_name_case("My-Type"), "my-type");
192        assert_eq!(wit_name_case("My_Type"), "my-type");
193        assert_eq!(wit_name_case("my_type"), "my-type");
194        assert_eq!(wit_name_case("my-type"), "my-type");
195        assert_eq!(wit_name_case("my-type0"), "my-type0");
196        assert_eq!(wit_name_case("line0"), "line0");
197        assert_eq!(wit_name_case("line-0"), "line0");
198    }
199
200    #[test]
201    fn test_upper_snake() {
202        assert_eq!(upper_snake("my_type"), "MY_TYPE");
203        assert_eq!(upper_snake("my-type"), "MY_TYPE");
204        assert_eq!(upper_snake("my-type0"), "MY_TYPE_0");
205    }
206}