1use anyhow::{Context, Result};
10use serde::de::DeserializeOwned;
11use serde_json::Value;
12use std::collections::HashMap;
13
14pub mod config;
15pub mod models;
16mod parser;
17pub mod schemas;
18
19pub use config::*;
20pub use models::*;
21pub use parser::{parse_ccl, CclValue};
22pub use schemas::*;
23
24pub fn parse_to_hashmap(ccl_content: &str) -> Result<HashMap<String, Value>> {
50 let model = sickle::load(ccl_content).context("Failed to parse CCL with sickle")?;
52
53 model_to_hashmap(&model)
55}
56
57fn model_to_hashmap(model: &sickle::Model) -> Result<HashMap<String, Value>> {
59 let mut result = HashMap::new();
60
61 for (key, value) in model.iter() {
62 result.insert(key.clone(), model_to_value(value)?);
63 }
64
65 Ok(result)
66}
67
68fn model_to_value(model: &sickle::Model) -> Result<Value> {
70 if model.len() == 1 {
72 let (key, value) = model.iter().next().unwrap();
73
74 if key.is_empty() && !value.is_empty() && value.values().all(|v| v.is_empty()) {
78 let values: Vec<Value> = value.keys().map(|k| Value::String(k.clone())).collect();
80 return Ok(Value::Array(values));
81 }
82
83 if value.is_empty() {
85 return Ok(Value::String(key.clone()));
86 }
87 }
88
89 if model.len() > 1 && model.values().all(|v| v.is_empty()) {
91 let values: Vec<Value> = model.keys().map(|k| Value::String(k.clone())).collect();
93 return Ok(Value::Array(values));
94 }
95
96 let empty_key_count = model.keys().filter(|k| k.is_empty()).count();
98 if empty_key_count > 1 {
99 let mut values = Vec::new();
101 for (k, v) in model.iter() {
102 if k.is_empty() {
103 values.push(model_to_value(v)?);
104 }
105 }
106 return Ok(Value::Array(values));
107 }
108
109 let mut obj = serde_json::Map::new();
111 for (k, v) in model.iter() {
112 obj.insert(k.clone(), model_to_value(v)?);
113 }
114 Ok(Value::Object(obj))
115}
116
117pub fn parse_ccl_to<T: DeserializeOwned>(ccl_content: &str) -> Result<T> {
143 sickle::from_str(ccl_content).context("Failed to deserialize parsed CCL")
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_parse_simple_array() {
153 let ccl = r#"
154test_pkg =
155 = brew
156 = scoop
157 = pacman
158"#;
159 let result = parse_to_hashmap(ccl).unwrap();
160
161 assert!(result.contains_key("test_pkg"));
162 let value = &result["test_pkg"];
163 println!("DEBUG test_pkg value: {:#?}", value);
164 assert!(value.is_array());
165
166 let arr = value.as_array().unwrap();
167 assert_eq!(arr.len(), 3);
168 assert_eq!(arr[0].as_str().unwrap(), "brew");
169 assert_eq!(arr[1].as_str().unwrap(), "scoop");
170 assert_eq!(arr[2].as_str().unwrap(), "pacman");
171 }
172
173 #[test]
174 fn test_parse_complex_object() {
175 let ccl = r#"
176test_pkg =
177 _sources =
178 = brew
179 = scoop
180 brew = gh
181"#;
182 let result = parse_to_hashmap(ccl).unwrap();
183
184 assert!(result.contains_key("test_pkg"));
185 let value = &result["test_pkg"];
186 println!("Parsed value: {:#?}", value);
187 assert!(value.is_object());
188
189 let obj = value.as_object().unwrap();
190 println!("Object keys: {:?}", obj.keys().collect::<Vec<_>>());
191 assert!(obj.contains_key("_sources"));
192 assert!(obj.contains_key("brew"));
193
194 let sources_value = &obj["_sources"];
195 println!("_sources value: {:#?}", sources_value);
196 let sources = sources_value.as_array().unwrap();
197 assert_eq!(sources.len(), 2);
198
199 let brew_override = obj["brew"].as_str().unwrap();
200 assert_eq!(brew_override, "gh");
201 }
202
203 #[test]
204 fn test_parse_multiple_packages() {
205 let ccl = r#"
206simple =
207 = brew
208 = scoop
209
210complex =
211 _sources =
212 = pacman
213 _platforms =
214 = linux
215"#;
216 let result = parse_to_hashmap(ccl).unwrap();
217
218 assert_eq!(result.len(), 2);
219 assert!(result["simple"].is_array());
220 assert!(result["complex"].is_object());
221 }
222}