use anyhow::{Context, Result};
use serde::de::DeserializeOwned;
use serde_json::Value;
use std::collections::HashMap;
pub mod config;
pub mod models;
mod parser;
pub mod schemas;
pub use config::*;
pub use models::*;
pub use parser::{parse_ccl, CclValue};
pub use schemas::*;
pub fn parse_to_hashmap(ccl_content: &str) -> Result<HashMap<String, Value>> {
let model = sickle::load(ccl_content).context("Failed to parse CCL with sickle")?;
model_to_hashmap(&model)
}
fn model_to_hashmap(model: &sickle::CclObject) -> Result<HashMap<String, Value>> {
let mut result = HashMap::new();
for (key, value) in model.iter() {
result.insert(key.clone(), model_to_value(value)?);
}
Ok(result)
}
fn model_to_value(model: &sickle::CclObject) -> Result<Value> {
if let Ok(empty_key_values) = model.get_all("") {
if !empty_key_values.is_empty() {
let all_simple_strings = empty_key_values
.iter()
.all(|v| v.len() == 1 && v.values().all(|child| child.is_empty()));
if all_simple_strings {
let values: Vec<Value> = empty_key_values
.iter()
.filter_map(|v| v.keys().next().cloned())
.map(Value::String)
.collect();
return Ok(Value::Array(values));
} else {
let values: Vec<Value> = empty_key_values
.iter()
.map(model_to_value)
.collect::<Result<Vec<_>>>()?;
return Ok(Value::Array(values));
}
}
}
if model.len() == 1 {
let (key, value) = model.iter().next().unwrap();
if value.is_empty() {
return Ok(Value::String(key.clone()));
}
}
if model.len() > 1 && model.values().all(|v| v.is_empty()) {
let values: Vec<Value> = model.keys().map(|k| Value::String(k.clone())).collect();
return Ok(Value::Array(values));
}
let mut obj = serde_json::Map::new();
for (k, v) in model.iter() {
obj.insert(k.clone(), model_to_value(v)?);
}
Ok(Value::Object(obj))
}
pub fn parse_ccl_to<T: DeserializeOwned>(ccl_content: &str) -> Result<T> {
let options =
sickle::ParserOptions::default().with_crlf(sickle::options::CrlfBehavior::NormalizeToLf);
sickle::from_str_with_options(ccl_content, &options).context("Failed to deserialize parsed CCL")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_array() {
let ccl = r#"
test_pkg =
= brew
= scoop
= pacman
"#;
let result = parse_to_hashmap(ccl).unwrap();
assert!(result.contains_key("test_pkg"));
let value = &result["test_pkg"];
println!("DEBUG test_pkg value: {:#?}", value);
assert!(value.is_array());
let arr = value.as_array().unwrap();
assert_eq!(arr.len(), 3);
assert_eq!(arr[0].as_str().unwrap(), "brew");
assert_eq!(arr[1].as_str().unwrap(), "scoop");
assert_eq!(arr[2].as_str().unwrap(), "pacman");
}
#[test]
fn test_parse_complex_object() {
let ccl = r#"
test_pkg =
_sources =
= brew
= scoop
brew = gh
"#;
let result = parse_to_hashmap(ccl).unwrap();
assert!(result.contains_key("test_pkg"));
let value = &result["test_pkg"];
println!("Parsed value: {:#?}", value);
assert!(value.is_object());
let obj = value.as_object().unwrap();
println!("Object keys: {:?}", obj.keys().collect::<Vec<_>>());
assert!(obj.contains_key("_sources"));
assert!(obj.contains_key("brew"));
let sources_value = &obj["_sources"];
println!("_sources value: {:#?}", sources_value);
let sources = sources_value.as_array().unwrap();
assert_eq!(sources.len(), 2);
let brew_override = obj["brew"].as_str().unwrap();
assert_eq!(brew_override, "gh");
}
#[test]
fn test_parse_multiple_packages() {
let ccl = r#"
simple =
= brew
= scoop
complex =
_sources =
= pacman
_platforms =
= linux
"#;
let result = parse_to_hashmap(ccl).unwrap();
assert_eq!(result.len(), 2);
assert!(result["simple"].is_array());
assert!(result["complex"].is_object());
}
}