1use cadi_core::{CadiError, CadiResult};
4use std::collections::HashMap;
5use std::process::Stdio;
6use tokio::process::Command;
7use std::path::Path;
8
9#[derive(Debug, Clone)]
11pub enum TransformType {
12 Parse { language: String },
14 Compile { target: String },
16 Link { format: String },
18 Bundle { format: String },
20 Containerize { base: String },
22 Custom { name: String, args: HashMap<String, String> },
24}
25
26#[derive(Debug, Clone)]
28pub struct TransformInput {
29 pub chunk_id: String,
31 pub data: Option<Vec<u8>>,
33 pub role: String,
35 pub path: Option<String>,
37}
38
39pub struct Transformer;
41
42impl Transformer {
43 pub fn new() -> Self {
45 Self
46 }
47
48 pub async fn transform(
50 &self,
51 transform: &TransformType,
52 inputs: &[TransformInput],
53 ) -> CadiResult<Vec<u8>> {
54 match transform {
55 TransformType::Parse { language } => {
56 self.execute_parse(language, inputs).await
57 }
58 TransformType::Compile { target } => {
59 self.execute_compile(target, inputs).await
60 }
61 TransformType::Link { format } => {
62 self.execute_link(format, inputs).await
63 }
64 TransformType::Bundle { format } => {
65 self.execute_bundle(format, inputs).await
66 }
67 TransformType::Containerize { base } => {
68 self.execute_containerize(base, inputs).await
69 }
70 TransformType::Custom { name, args } => {
71 self.execute_custom(name, args, inputs).await
72 }
73 }
74 }
75
76 async fn execute_parse(&self, language: &str, inputs: &[TransformInput]) -> CadiResult<Vec<u8>> {
77 tracing::info!("Parsing {} source", language);
78 let input = inputs.first()
79 .ok_or_else(|| CadiError::TransformFailed("No input provided".to_string()))?;
80
81 let mock_ir = serde_json::json!({
84 "type": "ir",
85 "language": language,
86 "source_chunk": input.chunk_id,
87 "timestamp": chrono::Utc::now().to_rfc3339(),
88 "status": "parsed"
89 });
90
91 Ok(serde_json::to_vec(&mock_ir)?)
92 }
93
94 async fn execute_compile(&self, target: &str, inputs: &[TransformInput]) -> CadiResult<Vec<u8>> {
95 tracing::info!("Compiling to {}", target);
96 let input = inputs.first()
97 .ok_or_else(|| CadiError::TransformFailed("No input provided".to_string()))?;
98
99 if let Some(mut path) = input.path.clone() {
100 if path.starts_with("file://") {
101 path = path.replace("file://", "");
102 }
103 println!("DEBUG: execute_compile path='{}' target='{}'", path, target);
104
105 if target.contains("linux") || target.contains("darwin") || target == "any" {
106 if path.ends_with(".c") {
108 let output_path = format!("{}.out", path);
109 let status = Command::new("gcc")
110 .arg("-o")
111 .arg(&output_path)
112 .arg(path)
113 .stdout(Stdio::inherit())
114 .stderr(Stdio::inherit())
115 .status()
116 .await?;
117
118 if !status.success() {
119 return Err(CadiError::TransformFailed(format!("gcc failed with status {}", status)));
120 }
121
122 return Ok(std::fs::read(&output_path)?);
123 }
124
125 if path.ends_with("Cargo.toml") {
127 let dir = Path::new(&path).parent().unwrap_or(Path::new("."));
128 let status = Command::new("cargo")
129 .arg("build")
130 .current_dir(dir)
131 .stdout(Stdio::inherit())
132 .stderr(Stdio::inherit())
133 .status()
134 .await?;
135
136 if !status.success() {
137 return Err(CadiError::TransformFailed(format!("cargo build failed in {}", dir.display())));
138 }
139
140 let bin_name = path.split('/').rev().nth(1).unwrap_or("app");
144 let bin_path = dir.join("target").join("debug").join(bin_name);
145 if bin_path.exists() {
146 return Ok(std::fs::read(bin_path)?);
147 }
148 }
149 }
150 }
151
152 let mock_blob = serde_json::json!({
154 "type": "blob",
155 "target": target,
156 "source_chunk": input.chunk_id,
157 "timestamp": chrono::Utc::now().to_rfc3339(),
158 "size_bytes": 12345,
159 "format": "elf64",
160 });
161
162 Ok(serde_json::to_vec(&mock_blob)?)
163 }
164
165 async fn execute_link(&self, format: &str, inputs: &[TransformInput]) -> CadiResult<Vec<u8>> {
166 tracing::info!("Linking {} objects to {}", inputs.len(), format);
167
168 let input_ids: Vec<&str> = inputs.iter()
169 .map(|i| i.chunk_id.as_str())
170 .collect();
171
172 let mock_linked = serde_json::json!({
173 "type": "linked",
174 "format": format,
175 "inputs": input_ids,
176 "timestamp": chrono::Utc::now().to_rfc3339(),
177 "entry_point": "main",
178 });
179
180 Ok(serde_json::to_vec(&mock_linked)?)
181 }
182
183 async fn execute_bundle(&self, format: &str, inputs: &[TransformInput]) -> CadiResult<Vec<u8>> {
184 tracing::info!("Creating {} bundle from {} inputs", format, inputs.len());
185
186 for input in inputs {
189 if let Some(mut path) = input.path.clone() {
190 if path.starts_with("file://") {
191 path = path.replace("file://", "");
192 }
193 if path.ends_with("package.json") {
194 let dir = Path::new(&path).parent().unwrap_or(Path::new("."));
195 let status = Command::new("npm")
196 .arg("run")
197 .arg("build")
198 .current_dir(dir)
199 .status()
200 .await?;
201
202 if status.success() {
203 let dist = dir.join("dist").join("bundle.js");
205 if dist.exists() {
206 return Ok(std::fs::read(dist)?);
207 }
208 }
209 }
210
211 if path.ends_with("index.html") {
213 return Ok(std::fs::read(path)?);
214 }
215 }
216 }
217
218 let input_ids: Vec<&str> = inputs.iter()
219 .map(|i| i.chunk_id.as_str())
220 .collect();
221
222 let mock_bundle = serde_json::json!({
223 "type": "bundle",
224 "format": format,
225 "inputs": input_ids,
226 "timestamp": chrono::Utc::now().to_rfc3339(),
227 "entry": "index.js",
228 });
229
230 Ok(serde_json::to_vec(&mock_bundle)?)
231 }
232
233 async fn execute_containerize(&self, base: &str, inputs: &[TransformInput]) -> CadiResult<Vec<u8>> {
234 tracing::info!("Creating container from base {}", base);
235
236 for input in inputs {
238 if let Some(path) = &input.path {
239 if path.ends_with("Dockerfile") {
240 let dir = Path::new(path).parent().unwrap_or(Path::new("."));
241 let tag = format!("cadi-build-{}", input.chunk_id.replace(":", "-"));
242 let status = Command::new("docker")
243 .arg("build")
244 .arg("-t")
245 .arg(&tag)
246 .arg(".")
247 .current_dir(dir)
248 .status()
249 .await?;
250
251 if status.success() {
252 return Ok(tag.into_bytes());
253 }
254 }
255 }
256 }
257
258 let mock_container = serde_json::json!({
259 "type": "container",
260 "base": base,
261 "layers": inputs.len(),
262 "timestamp": chrono::Utc::now().to_rfc3339(),
263 });
264
265 Ok(serde_json::to_vec(&mock_container)?)
266 }
267
268 async fn execute_custom(&self, name: &str, args: &HashMap<String, String>, inputs: &[TransformInput]) -> CadiResult<Vec<u8>> {
269 tracing::info!("Running custom transform: {} with {} args", name, args.len());
270
271 let mock_custom = serde_json::json!({
272 "type": "custom",
273 "transform": name,
274 "args": args,
275 "inputs": inputs.len(),
276 "timestamp": chrono::Utc::now().to_rfc3339(),
277 });
278
279 Ok(serde_json::to_vec(&mock_custom)?)
280 }
281}
282
283impl Default for Transformer {
284 fn default() -> Self {
285 Self::new()
286 }
287}
288
289mod chrono {
291 pub struct Utc;
292 impl Utc {
293 pub fn now() -> DateTime { DateTime }
294 }
295 pub struct DateTime;
296 impl DateTime {
297 pub fn to_rfc3339(&self) -> String {
298 "2026-01-11T14:12:00Z".to_string()
300 }
301 }
302}