cuenv/providers/
codegen.rs1use std::any::Any;
6use std::path::Path;
7
8use async_trait::async_trait;
9use clap::{Arg, Command, arg};
10use cuenv_core::Result;
11use cuenv_core::manifest::{Base, Project};
12
13use crate::commands::CommandExecutor;
14use crate::commands::sync::functions;
15use crate::commands::sync::provider::{SyncMode, SyncOptions, SyncResult};
16use crate::provider::{Provider, SyncCapability};
17
18pub struct CodegenProvider;
35
36impl CodegenProvider {
37 #[must_use]
39 pub fn new() -> Self {
40 Self
41 }
42}
43
44impl Default for CodegenProvider {
45 fn default() -> Self {
46 Self::new()
47 }
48}
49
50impl Provider for CodegenProvider {
51 fn name(&self) -> &'static str {
52 "codegen"
53 }
54
55 fn description(&self) -> &'static str {
56 "Sync files from CUE codegen configurations"
57 }
58
59 fn as_any(&self) -> &dyn Any {
60 self
61 }
62
63 fn as_any_mut(&mut self) -> &mut dyn Any {
64 self
65 }
66}
67
68#[async_trait]
69impl SyncCapability for CodegenProvider {
70 fn build_sync_command(&self) -> Command {
71 Command::new(self.name())
72 .about(self.description())
73 .arg(arg!(-p --path <PATH> "Path to directory containing CUE files").default_value("."))
74 .arg(
75 Arg::new("package")
76 .long("package")
77 .help("Name of the CUE package to evaluate")
78 .default_value("cuenv"),
79 )
80 .arg(arg!(--"dry-run" "Show what would be generated without writing files"))
81 .arg(arg!(--check "Check if files are in sync without making changes"))
82 .arg(arg!(-A --all "Sync all projects in the workspace"))
83 .arg(arg!(--diff "Show diff for files that would change"))
84 }
85
86 async fn sync_path(
87 &self,
88 path: &Path,
89 package: &str,
90 options: &SyncOptions,
91 executor: &CommandExecutor,
92 ) -> Result<SyncResult> {
93 let dry_run = options.mode == SyncMode::DryRun;
94 let check = options.mode == SyncMode::Check;
95
96 let path_str = path.to_str().ok_or_else(|| {
97 cuenv_core::Error::configuration(format!(
98 "Path contains invalid UTF-8: {}",
99 path.display()
100 ))
101 })?;
102
103 let codegen_options = functions::CodegenSyncOptions {
104 dry_run: dry_run.into(),
105 check,
106 diff: options.show_diff,
107 };
108 let request = functions::CodegenSyncRequest {
109 path: path_str,
110 package,
111 options: codegen_options,
112 };
113 let output = functions::execute_sync_codegen(request, executor).await?;
114
115 Ok(SyncResult::success(output))
116 }
117
118 async fn sync_workspace(
119 &self,
120 package: &str,
121 options: &SyncOptions,
122 executor: &CommandExecutor,
123 ) -> Result<SyncResult> {
124 let dry_run = options.mode == SyncMode::DryRun;
125 let check = options.mode == SyncMode::Check;
126
127 let cwd = std::env::current_dir().map_err(|e| {
128 cuenv_core::Error::configuration(format!("Failed to get current directory: {e}"))
129 })?;
130
131 let project_paths: Vec<(std::path::PathBuf, String)> = {
133 let module = executor.get_module(&cwd)?;
134 let mut paths = Vec::new();
135 for instance in module.projects() {
136 if let Ok(manifest) = instance.deserialize::<Project>()
137 && manifest.codegen.is_some()
138 {
139 paths.push((
140 module.root.join(&instance.path),
141 instance.path.display().to_string(),
142 ));
143 }
144 }
145 paths
146 };
147
148 let mut outputs = Vec::new();
149 let mut had_error = false;
150
151 for (full_path, display_path) in project_paths {
152 let Some(path_str) = full_path.to_str() else {
153 outputs.push(format!(
154 "{}: Error: Path contains invalid UTF-8",
155 full_path.display()
156 ));
157 had_error = true;
158 continue;
159 };
160
161 let codegen_options = functions::CodegenSyncOptions {
162 dry_run: dry_run.into(),
163 check,
164 diff: options.show_diff,
165 };
166 let request = functions::CodegenSyncRequest {
167 path: path_str,
168 package,
169 options: codegen_options,
170 };
171 let result = functions::execute_sync_codegen(request, executor).await;
172
173 match result {
174 Ok(output) if !output.is_empty() => {
175 let display = if display_path.is_empty() {
176 "[root]".to_string()
177 } else {
178 display_path
179 };
180 outputs.push(format!("{display}:\n{output}"));
181 }
182 Ok(_) => {}
183 Err(e) => {
184 outputs.push(format!("{display_path}: Error: {e}"));
185 had_error = true;
186 }
187 }
188 }
189
190 if outputs.is_empty() {
191 Ok(SyncResult::success("No codegen configurations found."))
192 } else {
193 Ok(SyncResult {
194 output: outputs.join("\n\n"),
195 had_error,
196 })
197 }
198 }
199
200 fn has_config(&self, _manifest: &Base) -> bool {
201 false
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_codegen_provider_name() {
212 let provider = CodegenProvider::new();
213 assert_eq!(provider.name(), "codegen");
214 }
215
216 #[test]
217 fn test_codegen_provider_description() {
218 let provider = CodegenProvider::new();
219 assert!(!provider.description().is_empty());
220 assert!(provider.description().contains("codegen"));
221 }
222
223 #[test]
224 fn test_codegen_provider_as_any() {
225 let provider = CodegenProvider::new();
226 let any = provider.as_any();
227 assert!(any.is::<CodegenProvider>());
228 }
229
230 #[test]
231 fn test_codegen_provider_as_any_mut() {
232 let mut provider = CodegenProvider::new();
233 let any = provider.as_any_mut();
234 assert!(any.is::<CodegenProvider>());
235 }
236
237 #[test]
238 fn test_codegen_provider_command() {
239 let provider = CodegenProvider::new();
240 let cmd = provider.build_sync_command();
241 assert_eq!(cmd.get_name(), "codegen");
242 }
243
244 #[test]
245 fn test_codegen_provider_command_has_args() {
246 let provider = CodegenProvider::new();
247 let cmd = provider.build_sync_command();
248
249 let args: Vec<_> = cmd.get_arguments().map(|a| a.get_id().as_str()).collect();
250 assert!(args.contains(&"path"));
251 assert!(args.contains(&"package"));
252 assert!(args.contains(&"dry-run"));
253 assert!(args.contains(&"check"));
254 assert!(args.contains(&"all"));
255 assert!(args.contains(&"diff"));
256 }
257
258 #[test]
259 fn test_codegen_provider_default() {
260 let provider = CodegenProvider;
261 assert_eq!(provider.name(), "codegen");
262 }
263
264 #[test]
265 fn test_codegen_provider_has_config() {
266 let provider = CodegenProvider::new();
267 let base = Base::default();
268 assert!(!provider.has_config(&base));
270 }
271}