1use crate::CodegenError;
7use amalgam_core::ir::Module;
8use std::collections::HashMap;
9use std::path::PathBuf;
10
11#[derive(Debug, Clone)]
13pub struct NickelPackageConfig {
14 pub name: String,
16 pub version: String,
18 pub minimal_nickel_version: String,
20 pub description: String,
22 pub authors: Vec<String>,
24 pub license: String,
26 pub keywords: Vec<String>,
28}
29
30impl Default for NickelPackageConfig {
31 fn default() -> Self {
32 Self {
33 name: "generated-types".to_string(),
34 version: "0.1.0".to_string(),
35 minimal_nickel_version: "1.9.0".to_string(),
36 description: "Auto-generated Nickel type definitions".to_string(),
37 authors: vec!["amalgam".to_string()],
38 license: "Apache-2.0".to_string(),
39 keywords: vec![
40 "kubernetes".to_string(),
41 "crd".to_string(),
42 "types".to_string(),
43 ],
44 }
45 }
46}
47
48pub struct NickelPackageGenerator {
50 config: NickelPackageConfig,
51}
52
53impl NickelPackageGenerator {
54 pub fn new(config: NickelPackageConfig) -> Self {
55 Self { config }
56 }
57
58 pub fn generate_manifest(
60 &self,
61 _modules: &[Module],
62 dependencies: HashMap<String, PackageDependency>,
63 ) -> Result<String, CodegenError> {
64 let mut manifest = String::new();
65
66 manifest.push_str("{\n");
68
69 manifest.push_str(&format!(" name = \"{}\",\n", self.config.name));
71 manifest.push_str(&format!(
72 " description = \"{}\",\n",
73 self.config.description
74 ));
75 manifest.push_str(&format!(" version = \"{}\",\n", self.config.version));
76
77 if !self.config.authors.is_empty() {
79 manifest.push_str(" authors = [\n");
80 for author in &self.config.authors {
81 manifest.push_str(&format!(" \"{}\",\n", author));
82 }
83 manifest.push_str(" ],\n");
84 }
85
86 if !self.config.license.is_empty() {
88 manifest.push_str(&format!(" license = \"{}\",\n", self.config.license));
89 }
90
91 if !self.config.keywords.is_empty() {
93 manifest.push_str(" keywords = [\n");
94 for keyword in &self.config.keywords {
95 manifest.push_str(&format!(" \"{}\",\n", keyword));
96 }
97 manifest.push_str(" ],\n");
98 }
99
100 manifest.push_str(&format!(
102 " minimal_nickel_version = \"{}\",\n",
103 self.config.minimal_nickel_version
104 ));
105
106 if !dependencies.is_empty() {
108 manifest.push_str(" dependencies = {\n");
109 for (name, dep) in dependencies {
110 manifest.push_str(&format!(" {} = {},\n", name, dep.to_nickel_string()));
111 }
112 manifest.push_str(" },\n");
113 }
114
115 manifest.push_str("} | std.package.Manifest\n");
117
118 Ok(manifest)
119 }
120
121 pub fn generate_main_module(&self, modules: &[Module]) -> Result<String, CodegenError> {
123 let mut main = String::new();
124
125 main.push_str("# Main module for ");
126 main.push_str(&self.config.name);
127 main.push('\n');
128 main.push_str("# This file exports all generated types\n\n");
129
130 main.push_str("{\n");
131
132 let mut grouped_modules: HashMap<String, Vec<&Module>> = HashMap::new();
134 for module in modules {
135 let parts: Vec<&str> = module.name.split('.').collect();
136 if let Some(group) = parts.first() {
137 grouped_modules
138 .entry(group.to_string())
139 .or_default()
140 .push(module);
141 }
142 }
143
144 for (group, group_modules) in grouped_modules {
146 main.push_str(&format!(" {} = {{\n", sanitize_identifier(&group)));
147
148 for module in group_modules {
149 let parts: Vec<&str> = module.name.split('.').collect();
151 if parts.len() > 1 {
152 let version = parts[1];
153 main.push_str(&format!(
154 " {} = import \"./{}/{}/mod.ncl\",\n",
155 sanitize_identifier(version),
156 group,
157 version
158 ));
159 }
160 }
161
162 main.push_str(" },\n");
163 }
164
165 main.push_str("}\n");
166
167 Ok(main)
168 }
169}
170
171#[derive(Debug, Clone)]
173pub enum PackageDependency {
174 Path(PathBuf),
176 Index { package: String, version: String },
178 Git {
180 url: String,
181 branch: Option<String>,
182 tag: Option<String>,
183 rev: Option<String>,
184 },
185}
186
187impl PackageDependency {
188 pub fn to_nickel_string(&self) -> String {
190 match self {
191 PackageDependency::Path(path) => {
192 format!("'Path \"{}\"", path.display())
193 }
194 PackageDependency::Index { package, version } => {
195 format!(
196 "'Index {{ package = \"{}\", version = \"{}\" }}",
197 package, version
198 )
199 }
200 PackageDependency::Git {
201 url,
202 branch,
203 tag,
204 rev,
205 } => {
206 let mut parts = vec![format!("url = \"{}\"", url)];
207 if let Some(branch) = branch {
208 parts.push(format!("branch = \"{}\"", branch));
209 }
210 if let Some(tag) = tag {
211 parts.push(format!("tag = \"{}\"", tag));
212 }
213 if let Some(rev) = rev {
214 parts.push(format!("rev = \"{}\"", rev));
215 }
216 format!("'Git {{ {} }}", parts.join(", "))
217 }
218 }
219 }
220}
221
222fn sanitize_identifier(s: &str) -> String {
224 s.chars()
226 .map(|c| {
227 if c.is_alphanumeric() || c == '_' {
228 c
229 } else {
230 '_'
231 }
232 })
233 .collect()
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn test_generate_basic_manifest() {
242 let config = NickelPackageConfig {
243 name: "test-package".to_string(),
244 version: "1.0.0".to_string(),
245 minimal_nickel_version: "1.9.0".to_string(),
246 description: "A test package".to_string(),
247 authors: vec!["Test Author".to_string()],
248 license: "MIT".to_string(),
249 keywords: vec!["test".to_string()],
250 };
251
252 let generator = NickelPackageGenerator::new(config);
253 let manifest = generator.generate_manifest(&[], HashMap::new()).unwrap();
254
255 assert!(manifest.contains("name = \"test-package\""));
256 assert!(manifest.contains("version = \"1.0.0\""));
257 assert!(manifest.contains("| std.package.Manifest"));
258 }
259
260 #[test]
261 fn test_path_dependency() {
262 let dep = PackageDependency::Path(PathBuf::from("../k8s-types"));
263 assert_eq!(dep.to_nickel_string(), "'Path \"../k8s-types\"");
264 }
265
266 #[test]
267 fn test_index_dependency() {
268 let dep = PackageDependency::Index {
269 package: "github:nickel-lang/stdlib".to_string(),
270 version: ">=1.0.0".to_string(),
271 };
272 assert_eq!(
273 dep.to_nickel_string(),
274 "'Index { package = \"github:nickel-lang/stdlib\", version = \">=1.0.0\" }"
275 );
276 }
277}