amalgam_codegen/
package_mode.rs1use amalgam_core::dependency_analyzer::DependencyAnalyzer;
7use amalgam_core::types::Type;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::path::PathBuf;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct PackageDependency {
15 pub package_id: String,
17 pub version: String,
19}
20
21#[derive(Debug, Clone, Default)]
23pub enum PackageMode {
24 #[default]
26 Relative,
27
28 Package {
30 dependencies: HashMap<String, PackageDependency>,
32 analyzer: DependencyAnalyzer,
34 },
35
36 LocalDevelopment {
38 local_paths: HashMap<String, PathBuf>,
40 },
41}
42
43impl PackageMode {
44 pub fn new_with_analyzer(manifest_path: Option<&PathBuf>) -> Self {
46 let mut analyzer = DependencyAnalyzer::new();
47
48 if let Some(path) = manifest_path {
50 let _ = analyzer.register_from_manifest(path);
51 }
52
53 PackageMode::Package {
54 dependencies: HashMap::new(),
55 analyzer,
56 }
57 }
58
59 pub fn analyze_and_update_dependencies(&mut self, types: &[Type], current_package: &str) {
61 if let PackageMode::Package {
62 analyzer,
63 dependencies,
64 } = self
65 {
66 analyzer.set_current_package(current_package);
67
68 let mut all_refs = std::collections::HashSet::new();
70 for ty in types {
71 let refs = analyzer.analyze_type(ty, current_package);
72 all_refs.extend(refs);
73 }
74
75 let detected_deps = analyzer.determine_dependencies(&all_refs);
77
78 for dep in detected_deps {
80 if !dependencies.contains_key(&dep.package_name) {
81 let base = std::env::var("NICKEL_PACKAGE_BASE")
83 .unwrap_or_else(|_| "github:seryl/nickel-pkgs".to_string());
84 let package_id = format!("{}/{}", base, &dep.package_name);
85
86 let version = if dep.is_core_type {
87 ">=1.31.0".to_string()
88 } else {
89 ">=0.1.0".to_string()
90 };
91
92 dependencies.insert(
93 dep.package_name.clone(),
94 PackageDependency {
95 package_id,
96 version,
97 },
98 );
99 }
100 }
101 }
102 }
103
104 pub fn convert_import(&self, import_path: &str) -> String {
106 match self {
107 PackageMode::Relative => {
108 import_path.to_string()
110 }
111 PackageMode::Package { .. } => {
112 if let Some(package_name) = self.detect_package_from_path(import_path) {
114 format!("\"{}\"", package_name)
116 } else {
117 import_path.to_string()
119 }
120 }
121 PackageMode::LocalDevelopment { local_paths } => {
122 for (package_name, local_path) in local_paths {
124 if import_path.contains(package_name) {
125 return local_path.to_string_lossy().to_string();
126 }
127 }
128 import_path.to_string()
129 }
130 }
131 }
132
133 fn detect_package_from_path(&self, import_path: &str) -> Option<String> {
135 if import_path.starts_with("../") {
140 let parts: Vec<&str> = import_path.split('/').collect();
141 for part in parts {
143 if part != ".." && part != "." && !part.ends_with(".ncl") {
144 if let PackageMode::Package { dependencies, .. } = self {
147 if dependencies.contains_key(part) {
148 return Some(part.to_string());
149 }
150 let normalized = part.replace('_', "-");
152 if dependencies.contains_key(&normalized) {
153 return Some(normalized);
154 }
155 }
156 break;
157 }
158 }
159 }
160
161 None
162 }
163
164 pub fn generate_imports(&self, types: &[Type], current_package: &str) -> Vec<String> {
166 match self {
167 PackageMode::Package { analyzer, .. } => {
168 let mut analyzer = analyzer.clone();
170 analyzer.set_current_package(current_package);
171
172 let mut all_refs = std::collections::HashSet::new();
173 for ty in types {
174 let refs = analyzer.analyze_type(ty, current_package);
175 all_refs.extend(refs);
176 }
177
178 let deps = analyzer.determine_dependencies(&all_refs);
179 analyzer.generate_imports(&deps, true)
180 }
181 _ => Vec::new(),
182 }
183 }
184
185 pub fn add_to_manifest(&self, content: &str, _package_name: &str) -> String {
187 if let PackageMode::Package { dependencies, .. } = self {
188 if !dependencies.is_empty() {
189 let mut deps_str = String::from(" dependencies = {\n");
190 for (dep_name, dep_info) in dependencies {
191 deps_str.push_str(&format!(
192 " \"{}\" = \"{}\",\n",
193 dep_name, dep_info.version
194 ));
195 }
196 deps_str.push_str(" },\n");
197
198 if content.contains("dependencies = {}") {
200 content.replace("dependencies = {}", deps_str.trim_end())
201 } else {
202 content.to_string()
203 }
204 } else {
205 content.to_string()
206 }
207 } else {
208 content.to_string()
209 }
210 }
211
212 pub fn get_dependencies(&self) -> Option<&HashMap<String, PackageDependency>> {
214 match self {
215 PackageMode::Package { dependencies, .. } => Some(dependencies),
216 _ => None,
217 }
218 }
219}
220
221pub fn create_package_manifest(
223 name: &str,
224 version: &str,
225 description: &str,
226 keywords: Vec<String>,
227 dependencies: HashMap<String, String>,
228) -> String {
229 let deps = if dependencies.is_empty() {
230 "{}".to_string()
231 } else {
232 let entries: Vec<String> = dependencies
233 .iter()
234 .map(|(k, v)| format!(" \"{}\" = \"{}\"", k, v))
235 .collect();
236 format!("{{\n{}\n }}", entries.join(",\n"))
237 };
238
239 format!(
240 r#"{{
241 name = "{}",
242 version = "{}",
243 description = "{}",
244
245 keywords = [{}],
246
247 dependencies = {},
248
249 # Auto-generated by amalgam
250 minimal_nickel_version = "1.9.0",
251}} | std.package.Manifest
252"#,
253 name,
254 version,
255 description,
256 keywords
257 .iter()
258 .map(|k| format!("\"{}\"", k))
259 .collect::<Vec<_>>()
260 .join(", "),
261 deps
262 )
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 #[test]
270 fn test_import_conversion_with_analyzer() {
271 let mode = PackageMode::new_with_analyzer(None);
272
273 let import = "../../../k8s_io/v1/objectmeta.ncl";
275 let converted = mode.convert_import(import);
276
277 assert_eq!(converted, import);
279 }
280
281 #[test]
282 fn test_package_manifest_generation() {
283 let manifest = create_package_manifest(
284 "test-package",
285 "1.0.0",
286 "Test package",
287 vec!["test".to_string()],
288 HashMap::new(),
289 );
290
291 assert!(manifest.contains("name = \"test-package\""));
292 assert!(manifest.contains("version = \"1.0.0\""));
293 assert!(manifest.contains("dependencies = {}"));
294 }
295}