sillyecs/
lib.rs

1mod archetype;
2mod code;
3mod component;
4mod ecs;
5mod state;
6mod system;
7mod system_scheduler;
8mod world;
9
10pub use crate::code::EcsCode;
11use serde::Serialize;
12use std::fmt::{Display, Formatter};
13
14#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
15pub struct Name {
16    #[serde(rename = "type")]
17    pub type_name: String,
18    #[serde(rename = "raw")]
19    pub type_name_raw: String,
20    #[serde(rename = "field")]
21    pub field_name: String,
22    #[serde(rename = "fields")]
23    pub field_name_plural: String,
24}
25
26impl Name {
27    pub fn new(type_name: String, type_suffix: &str) -> Self {
28        let field_name = pascal_to_snake(&type_name);
29        let field_name_plural = pluralize_name(field_name.clone());
30        let adjusted_type_name = if type_name.ends_with(type_suffix) {
31            type_name.clone()
32        } else {
33            format!("{type_name}{type_suffix}")
34        };
35        Self {
36            type_name: adjusted_type_name,
37            type_name_raw: type_name,
38            field_name,
39            field_name_plural,
40        }
41    }
42}
43
44impl Display for Name {
45    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
46        f.write_str(&self.type_name)
47    }
48}
49
50fn pluralize_name(field_name: String) -> String {
51    let field_name = if let Some(prefix) = field_name.strip_suffix('y') {
52        format!("{prefix}ies")
53    } else if !field_name.ends_with('s') {
54        format!("{field_name}s")
55    } else {
56        field_name
57    };
58    field_name
59}
60
61fn snake_case_filter(value: String) -> String {
62    pascal_to_snake(value.trim())
63}
64
65fn pascal_to_snake(type_name: &str) -> String {
66    let field_name = type_name
67        .chars()
68        .flat_map(|c| {
69            if c.is_uppercase() {
70                vec!['_', c.to_ascii_lowercase()]
71            } else {
72                vec![c]
73            }
74        })
75        .skip_while(|&c| c == '_')
76        .collect::<String>();
77    field_name
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_pascal_to_snake() {
86        let cases = vec![
87            ("PascalCase", "pascal_case"),
88            ("SnakeCase", "snake_case"),
89            ("HTTPServer", "h_t_t_p_server"),
90            ("", ""),
91            ("lowercase", "lowercase"),
92            ("UPPERCASE", "u_p_p_e_r_c_a_s_e"),
93            ("Mixed123Case", "mixed123_case"),
94        ];
95
96        for (input, expected) in cases {
97            assert_eq!(pascal_to_snake(&input.to_string()), expected);
98        }
99    }
100}