cuenv_ci/emitter/
registry.rs1use std::collections::HashMap;
7use std::sync::Arc;
8
9use super::{Emitter, EmitterError, EmitterResult};
10use crate::ir::IntermediateRepresentation;
11
12#[derive(Default)]
32pub struct EmitterRegistry {
33 emitters: HashMap<&'static str, Arc<dyn Emitter>>,
34}
35
36impl EmitterRegistry {
37 #[must_use]
39 pub fn new() -> Self {
40 Self {
41 emitters: HashMap::new(),
42 }
43 }
44
45 pub fn register(&mut self, emitter: impl Emitter + 'static) {
50 let name = emitter.format_name();
51 self.emitters.insert(name, Arc::new(emitter));
52 }
53
54 pub fn register_arc(&mut self, emitter: Arc<dyn Emitter>) {
58 let name = emitter.format_name();
59 self.emitters.insert(name, emitter);
60 }
61
62 #[must_use]
64 pub fn get(&self, name: &str) -> Option<Arc<dyn Emitter>> {
65 self.emitters.get(name).cloned()
66 }
67
68 #[must_use]
70 pub fn has(&self, name: &str) -> bool {
71 self.emitters.contains_key(name)
72 }
73
74 #[must_use]
76 pub fn formats(&self) -> Vec<&'static str> {
77 let mut names: Vec<_> = self.emitters.keys().copied().collect();
78 names.sort_unstable();
79 names
80 }
81
82 #[must_use]
84 pub fn all(&self) -> Vec<Arc<dyn Emitter>> {
85 self.emitters.values().cloned().collect()
86 }
87
88 #[must_use]
90 pub fn len(&self) -> usize {
91 self.emitters.len()
92 }
93
94 #[must_use]
96 pub fn is_empty(&self) -> bool {
97 self.emitters.is_empty()
98 }
99
100 pub fn emit(&self, format: &str, ir: &IntermediateRepresentation) -> EmitterResult<String> {
105 let emitter = self.get(format).ok_or_else(|| {
106 EmitterError::InvalidIR(format!(
107 "Unknown format '{}'. Available: {}",
108 format,
109 self.formats().join(", ")
110 ))
111 })?;
112
113 emitter.emit(ir)
114 }
115}
116
117#[derive(Debug, Clone)]
119pub struct EmitterInfo {
120 pub format: &'static str,
122 pub extension: &'static str,
124 pub description: &'static str,
126}
127
128impl EmitterInfo {
129 #[must_use]
131 pub fn from_emitter(emitter: &dyn Emitter) -> Self {
132 Self {
133 format: emitter.format_name(),
134 extension: emitter.file_extension(),
135 description: emitter.description(),
136 }
137 }
138}
139
140impl EmitterRegistry {
141 #[must_use]
143 pub fn info(&self) -> Vec<EmitterInfo> {
144 let mut infos: Vec<_> = self
145 .emitters
146 .values()
147 .map(|e| EmitterInfo::from_emitter(e.as_ref()))
148 .collect();
149 infos.sort_by_key(|i| i.format);
150 infos
151 }
152}
153
154#[derive(Default)]
156pub struct EmitterRegistryBuilder {
157 registry: EmitterRegistry,
158}
159
160impl EmitterRegistryBuilder {
161 #[must_use]
163 pub fn new() -> Self {
164 Self::default()
165 }
166
167 #[must_use]
169 pub fn with_emitter(mut self, emitter: impl Emitter + 'static) -> Self {
170 self.registry.register(emitter);
171 self
172 }
173
174 #[must_use]
176 pub fn build(self) -> EmitterRegistry {
177 self.registry
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use cuenv_core::ci::PipelineMode;
185
186 struct TestEmitter {
187 name: &'static str,
188 }
189
190 impl Emitter for TestEmitter {
191 fn emit_thin(&self, ir: &IntermediateRepresentation) -> EmitterResult<String> {
192 Ok(format!("# {} thin - {}", self.name, ir.pipeline.name))
193 }
194
195 fn emit_expanded(&self, ir: &IntermediateRepresentation) -> EmitterResult<String> {
196 Ok(format!("# {} expanded - {}", self.name, ir.pipeline.name))
197 }
198
199 fn format_name(&self) -> &'static str {
200 self.name
201 }
202
203 fn file_extension(&self) -> &'static str {
204 "yml"
205 }
206
207 fn description(&self) -> &'static str {
208 "Test emitter"
209 }
210 }
211
212 #[test]
213 fn test_registry_new() {
214 let registry = EmitterRegistry::new();
215 assert!(registry.is_empty());
216 assert_eq!(registry.len(), 0);
217 }
218
219 #[test]
220 fn test_registry_register() {
221 let mut registry = EmitterRegistry::new();
222 registry.register(TestEmitter { name: "test" });
223
224 assert!(!registry.is_empty());
225 assert_eq!(registry.len(), 1);
226 assert!(registry.has("test"));
227 }
228
229 #[test]
230 fn test_registry_get() {
231 let mut registry = EmitterRegistry::new();
232 registry.register(TestEmitter { name: "test" });
233
234 let emitter = registry.get("test");
235 assert!(emitter.is_some());
236 assert_eq!(emitter.unwrap().format_name(), "test");
237
238 assert!(registry.get("nonexistent").is_none());
239 }
240
241 #[test]
242 fn test_registry_formats() {
243 let mut registry = EmitterRegistry::new();
244 registry.register(TestEmitter { name: "buildkite" });
245 registry.register(TestEmitter { name: "gitlab" });
246 registry.register(TestEmitter { name: "circleci" });
247
248 let formats = registry.formats();
249 assert_eq!(formats, vec!["buildkite", "circleci", "gitlab"]);
250 }
251
252 #[test]
253 fn test_registry_emit() {
254 let mut registry = EmitterRegistry::new();
255 registry.register(TestEmitter { name: "test" });
256
257 let ir = IntermediateRepresentation {
259 version: "1.5".to_string(),
260 pipeline: crate::ir::PipelineMetadata {
261 name: "my-pipeline".to_string(),
262 mode: PipelineMode::default(),
263 environment: None,
264 requires_onepassword: false,
265 project_name: None,
266 trigger: None,
267 pipeline_tasks: vec![],
268 pipeline_task_defs: vec![],
269 },
270 runtimes: vec![],
271 tasks: vec![],
272 };
273
274 let output = registry.emit("test", &ir).unwrap();
275 assert_eq!(output, "# test thin - my-pipeline");
276 }
277
278 #[test]
279 fn test_registry_emit_unknown_format() {
280 let registry = EmitterRegistry::new();
281 let ir = IntermediateRepresentation {
282 version: "1.5".to_string(),
283 pipeline: crate::ir::PipelineMetadata {
284 name: "test".to_string(),
285 mode: PipelineMode::default(),
286 environment: None,
287 requires_onepassword: false,
288 project_name: None,
289 trigger: None,
290 pipeline_tasks: vec![],
291 pipeline_task_defs: vec![],
292 },
293 runtimes: vec![],
294 tasks: vec![],
295 };
296
297 let result = registry.emit("unknown", &ir);
298 assert!(result.is_err());
299 assert!(result.unwrap_err().to_string().contains("Unknown format"));
300 }
301
302 #[test]
303 fn test_registry_info() {
304 let mut registry = EmitterRegistry::new();
305 registry.register(TestEmitter { name: "test" });
306
307 let infos = registry.info();
308 assert_eq!(infos.len(), 1);
309 assert_eq!(infos[0].format, "test");
310 assert_eq!(infos[0].extension, "yml");
311 assert_eq!(infos[0].description, "Test emitter");
312 }
313
314 #[test]
315 fn test_registry_register_replaces() {
316 let mut registry = EmitterRegistry::new();
317 registry.register(TestEmitter { name: "test" });
318 registry.register(TestEmitter { name: "test" });
319
320 assert_eq!(registry.len(), 1);
321 }
322}