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}