dampen_cli/commands/
build.rs1#![allow(clippy::print_stderr, clippy::print_stdout)]
2
3use dampen_core::{generate_application, parse, HandlerSignature};
6use std::fs;
7use std::path::{Path, PathBuf};
8
9#[derive(clap::Args)]
11pub struct BuildArgs {
12 #[arg(short, long, default_value = "ui")]
14 input: String,
15
16 #[arg(short, long, default_value = "src/ui_generated.rs")]
18 output: String,
19
20 #[arg(long, default_value = "Model")]
22 model: String,
23
24 #[arg(long, default_value = "Message")]
26 message: String,
27
28 #[arg(short, long)]
30 verbose: bool,
31
32 #[arg(long)]
34 prod: bool,
35
36 #[arg(long)]
38 release: bool,
39}
40
41pub fn execute(args: &BuildArgs) -> Result<(), String> {
43 if args.prod || args.release {
44 return execute_production_build(args);
45 }
46
47 if args.verbose {
48 eprintln!("Building from {} to {}", args.input, args.output);
49 }
50
51 let input_dir = PathBuf::from(&args.input);
53 if !input_dir.exists() {
54 return Err(format!("Input directory '{}' does not exist", args.input));
55 }
56
57 let dampen_files = find_dampen_files(&input_dir);
58 if dampen_files.is_empty() {
59 return Err(format!("No .dampen files found in '{}'", args.input));
60 }
61
62 if args.verbose {
63 eprintln!("Found {} .dampen files", dampen_files.len());
64 }
65
66 let generated_code =
68 generate_code_from_files(&dampen_files, &args.model, &args.message, args.verbose)?;
69
70 let output_path = PathBuf::from(&args.output);
72 if let Some(parent) = output_path.parent() {
73 fs::create_dir_all(parent)
74 .map_err(|e| format!("Failed to create output directory: {}", e))?;
75 }
76
77 fs::write(&output_path, generated_code)
78 .map_err(|e| format!("Failed to write output: {}", e))?;
79
80 if args.verbose {
81 eprintln!("Generated code written to {}", output_path.display());
82 }
83
84 eprintln!("Build successful!");
85 Ok(())
86}
87
88fn find_dampen_files(dir: &Path) -> Vec<PathBuf> {
90 let mut files = Vec::new();
91
92 if let Ok(entries) = fs::read_dir(dir) {
93 for entry in entries.filter_map(|e| e.ok()) {
94 let path = entry.path();
95
96 if path.is_dir() {
97 files.extend(find_dampen_files(&path));
98 } else if path.extension().is_some_and(|ext| ext == "dampen") {
99 files.push(path);
100 }
101 }
102 }
103
104 files
105}
106
107fn generate_code_from_files(
109 files: &[PathBuf],
110 model_name: &str,
111 message_name: &str,
112 verbose: bool,
113) -> Result<String, String> {
114 let mut code = String::new();
115
116 code.push_str("//! Auto-generated UI code\n");
118 code.push_str("//! DO NOT EDIT - Generated by `dampen build`\n\n");
119 code.push_str("use dampen_core::{parse, generate_application, HandlerSignature};\n\n");
120
121 if files.len() > 1 {
124 eprintln!("Warning: Multiple .dampen files found. Using first file for generation.");
125 }
126
127 let main_file = &files[0];
129 let content = fs::read_to_string(main_file)
130 .map_err(|e| format!("Failed to read {}: {}", main_file.display(), e))?;
131
132 if verbose {
133 eprintln!("Parsing {}", main_file.display());
134 }
135
136 let doc = parse(&content).map_err(|e| format!("Parse error: {}", e))?;
138
139 let handlers: Vec<HandlerSignature> = vec![];
142
143 let output = generate_application(&doc, model_name, message_name, &handlers)
147 .map_err(|e| format!("Code generation error: {}", e))?;
148
149 code.push_str(&output.code);
150
151 if !output.warnings.is_empty() {
153 for warning in &output.warnings {
154 eprintln!("Warning: {}", warning);
155 }
156 }
157
158 Ok(code)
159}
160
161fn execute_production_build(args: &BuildArgs) -> Result<(), String> {
163 use std::process::Command;
164
165 if args.verbose {
166 eprintln!("Running production build...");
167 eprintln!("Mode: {}", if args.release { "release" } else { "debug" });
168 }
169
170 if !Path::new("build.rs").exists() {
172 return Err(
173 "build.rs not found. This project may not be configured for production builds."
174 .to_string(),
175 );
176 }
177
178 if !Path::new("Cargo.toml").exists() {
180 return Err("Cargo.toml not found. Are you in a Rust project directory?".to_string());
181 }
182
183 let mut cmd = Command::new("cargo");
185 cmd.arg("build");
186
187 if args.release {
188 cmd.arg("--release");
189 }
190
191 if args.verbose {
192 cmd.arg("--verbose");
193 }
194
195 if args.verbose {
197 eprintln!(
198 "Executing: cargo build{}",
199 if args.release { " --release" } else { "" }
200 );
201 }
202
203 let status = cmd
204 .status()
205 .map_err(|e| format!("Failed to execute cargo: {}", e))?;
206
207 if !status.success() {
208 return Err("Build failed".to_string());
209 }
210
211 if args.verbose {
212 let binary_path = if args.release {
213 "target/release"
214 } else {
215 "target/debug"
216 };
217 eprintln!("Build successful! Binary is in {}/", binary_path);
218 }
219
220 eprintln!("Production build completed successfully!");
221 Ok(())
222}