Skip to main content

serializer/llm/
section_names.rs

1//! Section name dictionary for Human V3 format conversion
2//!
3//! This module provides bidirectional mappings between full section names
4//! (used in Human V3 format) and abbreviated section IDs (used in LLM format).
5
6use std::collections::HashMap;
7
8/// Bidirectional section name dictionary
9///
10/// Provides mappings between full section names (e.g., "forge", "style")
11/// and abbreviated section IDs (e.g., "f", "y").
12#[derive(Debug, Clone)]
13pub struct SectionNameDict {
14    /// Full name → Section ID (for compression)
15    name_to_id: HashMap<&'static str, &'static str>,
16    /// Section ID → Full name (for expansion)
17    id_to_name: HashMap<&'static str, &'static str>,
18}
19
20impl SectionNameDict {
21    /// Create dictionary with all standard section mappings
22    pub fn new() -> Self {
23        let mut name_to_id = HashMap::new();
24        let mut id_to_name = HashMap::new();
25
26        // Helper to add bidirectional mapping
27        let mut add = |name: &'static str, id: &'static str| {
28            name_to_id.insert(name, id);
29            id_to_name.insert(id, name);
30        };
31
32        // Section mappings from TypeScript SECTION_NAMES
33        add("config", "c");
34        add("forge", "f");
35        add("stack", "k");
36        add("style", "y");
37        add("ui", "u");
38        add("media", "m");
39        add("i18n", "i");
40        add("icon", "o");
41        add("font", "t");
42        add("driven", "d");
43        add("generator", "g");
44        add("scripts", "s");
45        add("dependencies", "x");
46        add("js", "j");
47        add("python", "p");
48        add("rust", "r");
49
50        Self {
51            name_to_id,
52            id_to_name,
53        }
54    }
55
56    /// Convert full section name to abbreviated ID
57    ///
58    /// Returns the original name if no mapping exists.
59    pub fn name_to_id(&self, name: &str) -> String {
60        self.name_to_id
61            .get(name)
62            .map(|s| s.to_string())
63            .unwrap_or_else(|| name.to_string())
64    }
65
66    /// Convert abbreviated ID to full section name
67    ///
68    /// Returns the original ID if no mapping exists.
69    pub fn id_to_name(&self, id: &str) -> String {
70        self.id_to_name
71            .get(id)
72            .map(|s| s.to_string())
73            .unwrap_or_else(|| id.to_string())
74    }
75
76    /// Check if a section name exists in the dictionary
77    pub fn has_name(&self, name: &str) -> bool {
78        self.name_to_id.contains_key(name)
79    }
80
81    /// Check if a section ID exists in the dictionary
82    pub fn has_id(&self, id: &str) -> bool {
83        self.id_to_name.contains_key(id)
84    }
85
86    /// Get the number of mappings
87    pub fn len(&self) -> usize {
88        self.name_to_id.len()
89    }
90
91    /// Check if the dictionary is empty
92    pub fn is_empty(&self) -> bool {
93        self.name_to_id.is_empty()
94    }
95}
96
97impl Default for SectionNameDict {
98    fn default() -> Self {
99        Self::new()
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_section_name_dict_has_all_mappings() {
109        let dict = SectionNameDict::new();
110        assert_eq!(dict.len(), 16, "Should have 16 section mappings");
111    }
112
113    #[test]
114    fn test_name_to_id() {
115        let dict = SectionNameDict::new();
116
117        assert_eq!(dict.name_to_id("config"), "c");
118        assert_eq!(dict.name_to_id("forge"), "f");
119        assert_eq!(dict.name_to_id("stack"), "k");
120        assert_eq!(dict.name_to_id("style"), "y");
121        assert_eq!(dict.name_to_id("ui"), "u");
122        assert_eq!(dict.name_to_id("media"), "m");
123        assert_eq!(dict.name_to_id("i18n"), "i");
124        assert_eq!(dict.name_to_id("icon"), "o");
125        assert_eq!(dict.name_to_id("font"), "t");
126        assert_eq!(dict.name_to_id("driven"), "d");
127        assert_eq!(dict.name_to_id("generator"), "g");
128        assert_eq!(dict.name_to_id("scripts"), "s");
129        assert_eq!(dict.name_to_id("dependencies"), "x");
130        assert_eq!(dict.name_to_id("js"), "j");
131        assert_eq!(dict.name_to_id("python"), "p");
132        assert_eq!(dict.name_to_id("rust"), "r");
133    }
134
135    #[test]
136    fn test_id_to_name() {
137        let dict = SectionNameDict::new();
138
139        assert_eq!(dict.id_to_name("c"), "config");
140        assert_eq!(dict.id_to_name("f"), "forge");
141        assert_eq!(dict.id_to_name("k"), "stack");
142        assert_eq!(dict.id_to_name("y"), "style");
143        assert_eq!(dict.id_to_name("u"), "ui");
144        assert_eq!(dict.id_to_name("m"), "media");
145        assert_eq!(dict.id_to_name("i"), "i18n");
146        assert_eq!(dict.id_to_name("o"), "icon");
147        assert_eq!(dict.id_to_name("t"), "font");
148        assert_eq!(dict.id_to_name("d"), "driven");
149        assert_eq!(dict.id_to_name("g"), "generator");
150        assert_eq!(dict.id_to_name("s"), "scripts");
151        assert_eq!(dict.id_to_name("x"), "dependencies");
152        assert_eq!(dict.id_to_name("j"), "js");
153        assert_eq!(dict.id_to_name("p"), "python");
154        assert_eq!(dict.id_to_name("r"), "rust");
155    }
156
157    #[test]
158    fn test_unknown_passthrough() {
159        let dict = SectionNameDict::new();
160
161        // Unknown names pass through unchanged
162        assert_eq!(dict.name_to_id("unknown"), "unknown");
163        assert_eq!(dict.name_to_id("custom_section"), "custom_section");
164
165        // Unknown IDs pass through unchanged
166        assert_eq!(dict.id_to_name("z"), "z");
167        assert_eq!(dict.id_to_name("custom"), "custom");
168    }
169
170    #[test]
171    fn test_round_trip() {
172        let dict = SectionNameDict::new();
173
174        // All known names should round-trip correctly
175        let names = [
176            "config",
177            "forge",
178            "stack",
179            "style",
180            "ui",
181            "media",
182            "i18n",
183            "icon",
184            "font",
185            "driven",
186            "generator",
187            "scripts",
188            "dependencies",
189            "js",
190            "python",
191            "rust",
192        ];
193
194        for name in names {
195            let id = dict.name_to_id(name);
196            let back = dict.id_to_name(&id);
197            assert_eq!(back, name, "Round-trip failed for {}", name);
198        }
199    }
200
201    #[test]
202    fn test_has_name_and_id() {
203        let dict = SectionNameDict::new();
204
205        assert!(dict.has_name("forge"));
206        assert!(dict.has_name("style"));
207        assert!(!dict.has_name("unknown"));
208
209        assert!(dict.has_id("f"));
210        assert!(dict.has_id("y"));
211        assert!(!dict.has_id("z"));
212    }
213}