1use crate::cube::{Cube, FileMode};
9use crate::formatter::Formatter;
10use crate::{CodegenError, Result};
11use std::path::{Path, PathBuf};
12
13#[derive(Debug, Clone)]
15pub struct GeneratedFile {
16 pub path: PathBuf,
18 pub content: String,
20 pub mode: FileMode,
22 pub language: String,
24}
25
26#[derive(Debug, Clone)]
28pub struct GenerateOptions {
29 pub output_dir: PathBuf,
31 pub check: bool,
33 pub diff: bool,
35}
36
37impl Default for GenerateOptions {
38 fn default() -> Self {
39 Self {
40 output_dir: PathBuf::from("."),
41 check: false,
42 diff: false,
43 }
44 }
45}
46
47#[derive(Debug)]
49pub struct Generator {
50 cube: Cube,
51 formatter: Formatter,
52}
53
54impl Generator {
55 #[must_use]
57 pub fn new(cube: Cube) -> Self {
58 Self {
59 cube,
60 formatter: Formatter::new(),
61 }
62 }
63
64 pub fn generate(&self, options: &GenerateOptions) -> Result<Vec<GeneratedFile>> {
70 let mut generated_files = Vec::new();
71
72 for (file_path, file_def) in self.cube.files() {
73 let output_path = options.output_dir.join(file_path);
74
75 let formatted_content =
77 self.formatter
78 .format(&file_def.content, &file_def.language, &file_def.format)?;
79
80 let generated = GeneratedFile {
81 path: output_path.clone(),
82 content: formatted_content.clone(),
83 mode: file_def.mode,
84 language: file_def.language.clone(),
85 };
86
87 match file_def.mode {
89 FileMode::Managed => {
90 if options.check {
91 self.check_file(&output_path, &formatted_content)?;
92 } else {
93 self.write_file(&output_path, &formatted_content)?;
94 }
95 }
96 FileMode::Scaffold => {
97 if output_path.exists() {
98 tracing::info!("Skipping {} (scaffold mode, file exists)", file_path);
99 } else if options.check {
100 return Err(CodegenError::Generation(format!(
101 "Missing scaffold file: {file_path}"
102 )));
103 } else {
104 self.write_file(&output_path, &formatted_content)?;
105 }
106 }
107 }
108
109 generated_files.push(generated);
110 }
111
112 Ok(generated_files)
113 }
114
115 #[allow(clippy::unused_self)] fn write_file(&self, path: &Path, content: &str) -> Result<()> {
118 if let Some(parent) = path.parent() {
120 std::fs::create_dir_all(parent)?;
121 }
122
123 std::fs::write(path, content)?;
124 tracing::info!("Generated: {}", path.display());
125
126 Ok(())
127 }
128
129 #[allow(clippy::unused_self)] fn check_file(&self, path: &Path, expected_content: &str) -> Result<()> {
132 if !path.exists() {
133 return Err(CodegenError::Generation(format!(
134 "Missing managed file: {}",
135 path.display()
136 )));
137 }
138
139 let actual_content = std::fs::read_to_string(path)?;
140
141 if actual_content != expected_content {
142 return Err(CodegenError::Generation(format!(
143 "File would be modified: {}",
144 path.display()
145 )));
146 }
147
148 Ok(())
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::cube::{CubeData, FormatConfig, ProjectFileDefinition};
156 use std::collections::HashMap;
157 use tempfile::TempDir;
158
159 fn create_test_cube() -> Cube {
160 let mut files = HashMap::new();
161 files.insert(
162 "test.json".to_string(),
163 ProjectFileDefinition {
164 content: r#"{"name":"test"}"#.to_string(),
165 language: "json".to_string(),
166 mode: FileMode::Managed,
167 format: FormatConfig::default(),
168 gitignore: false,
169 },
170 );
171
172 let data = CubeData {
173 files,
174 context: serde_json::Value::Null,
175 };
176
177 Cube {
178 data,
179 source_path: PathBuf::from("test.cue"),
180 }
181 }
182
183 #[test]
184 fn test_generator_new() {
185 let cube = create_test_cube();
186 let generator = Generator::new(cube);
187 assert!(generator.cube.files().contains_key("test.json"));
188 }
189
190 #[test]
191 fn test_generate_managed_file() {
192 let cube = create_test_cube();
193 let generator = Generator::new(cube);
194
195 let temp_dir = TempDir::new().unwrap();
196 let options = GenerateOptions {
197 output_dir: temp_dir.path().to_path_buf(),
198 check: false,
199 diff: false,
200 };
201
202 let result = generator.generate(&options);
203 assert!(result.is_ok());
204
205 let generated = result.unwrap();
206 assert_eq!(generated.len(), 1);
207 assert_eq!(generated[0].mode, FileMode::Managed);
208
209 let file_path = temp_dir.path().join("test.json");
210 assert!(file_path.exists());
211 }
212}