dampen_core/codegen/
config.rs

1//! Configuration for code generation behavior
2//!
3//! This module provides configuration structures for controlling how
4//! Dampen generates Rust code from XML UI definitions.
5
6use std::path::PathBuf;
7
8/// Configuration for code generation behavior
9#[derive(Debug, Clone)]
10pub struct CodegenConfig {
11    /// Output directory for generated code
12    pub output_dir: PathBuf,
13
14    /// Whether to format generated code with prettyplease
15    pub format_output: bool,
16
17    /// Whether to validate generated code syntax
18    pub validate_syntax: bool,
19
20    /// Model type name (e.g., "MyModel")
21    pub model_type: String,
22
23    /// Message enum name (e.g., "Message")
24    pub message_type: String,
25}
26
27impl CodegenConfig {
28    /// Create a new CodegenConfig with the given output directory
29    ///
30    /// # Arguments
31    /// * `output_dir` - Directory where generated code will be written
32    ///
33    /// # Returns
34    /// A new CodegenConfig with default settings
35    pub fn new(output_dir: PathBuf) -> Self {
36        Self {
37            output_dir,
38            format_output: true,
39            validate_syntax: true,
40            model_type: "Model".to_string(),
41            message_type: "Message".to_string(),
42        }
43    }
44
45    /// Set the model type name
46    pub fn with_model_type(mut self, model_type: impl Into<String>) -> Self {
47        self.model_type = model_type.into();
48        self
49    }
50
51    /// Set the message type name
52    pub fn with_message_type(mut self, message_type: impl Into<String>) -> Self {
53        self.message_type = message_type.into();
54        self
55    }
56
57    /// Enable or disable code formatting
58    pub fn with_formatting(mut self, format_output: bool) -> Self {
59        self.format_output = format_output;
60        self
61    }
62
63    /// Enable or disable syntax validation
64    pub fn with_validation(mut self, validate_syntax: bool) -> Self {
65        self.validate_syntax = validate_syntax;
66        self
67    }
68
69    /// Validate the configuration
70    ///
71    /// # Returns
72    /// Ok if configuration is valid, Err with message otherwise
73    pub fn validate(&self) -> Result<(), String> {
74        // Check if output_dir path is valid (we can't check writability at compile time)
75        if self.output_dir.as_os_str().is_empty() {
76            return Err("Output directory cannot be empty".to_string());
77        }
78
79        // Validate model type is a valid Rust identifier
80        if !is_valid_identifier(&self.model_type) {
81            return Err(format!(
82                "Model type '{}' is not a valid Rust identifier",
83                self.model_type
84            ));
85        }
86
87        // Validate message type is a valid Rust identifier
88        if !is_valid_identifier(&self.message_type) {
89            return Err(format!(
90                "Message type '{}' is not a valid Rust identifier",
91                self.message_type
92            ));
93        }
94
95        Ok(())
96    }
97}
98
99impl Default for CodegenConfig {
100    fn default() -> Self {
101        Self::new(PathBuf::from("target/generated"))
102    }
103}
104
105/// Check if a string is a valid Rust identifier
106///
107/// Valid identifiers must:
108/// - Start with uppercase letter (for types)
109/// - Contain only alphanumeric characters and underscores
110fn is_valid_identifier(s: &str) -> bool {
111    if s.is_empty() {
112        return false;
113    }
114
115    let mut chars = s.chars();
116
117    // First character must be uppercase letter
118    if let Some(first) = chars.next() {
119        if !first.is_uppercase() {
120            return false;
121        }
122    } else {
123        return false;
124    }
125
126    // Remaining characters must be alphanumeric or underscore
127    chars.all(|c| c.is_alphanumeric() || c == '_')
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_valid_identifiers() {
136        assert!(is_valid_identifier("Model"));
137        assert!(is_valid_identifier("MyModel"));
138        assert!(is_valid_identifier("Model123"));
139        assert!(is_valid_identifier("My_Model"));
140        assert!(is_valid_identifier("M"));
141    }
142
143    #[test]
144    fn test_invalid_identifiers() {
145        assert!(!is_valid_identifier(""));
146        assert!(!is_valid_identifier("model")); // lowercase
147        assert!(!is_valid_identifier("123Model")); // starts with number
148        assert!(!is_valid_identifier("My-Model")); // contains hyphen
149        assert!(!is_valid_identifier("My Model")); // contains space
150        assert!(!is_valid_identifier("_Model")); // starts with underscore
151    }
152
153    #[test]
154    fn test_config_validation() {
155        let config = CodegenConfig::default();
156        assert!(config.validate().is_ok());
157
158        let config = CodegenConfig::new(PathBuf::from("")).with_model_type("Model");
159        assert!(config.validate().is_err());
160
161        let config = CodegenConfig::new(PathBuf::from("target")).with_model_type("invalid");
162        assert!(config.validate().is_err());
163
164        let config = CodegenConfig::new(PathBuf::from("target")).with_message_type("invalid");
165        assert!(config.validate().is_err());
166    }
167}