use std::fs;
use ini::Ini;
use serde_json::{Map, Value};
use crate::{Error, Job, Result};
pub fn ini_to_json(job: &Job) -> Result<()> {
let raw = fs::read_to_string(job.input)?;
let ini = Ini::load_from_str(&raw)
.map_err(|e| Error::other(format!("invalid INI: {e}")))?;
let mut out = Map::new();
for (section, props) in &ini {
let mut section_map = Map::new();
for (k, v) in props.iter() {
section_map.insert(k.to_string(), Value::String(v.to_string()));
}
let key = section.map(|s| s.to_string()).unwrap_or_default();
out.insert(key, Value::Object(section_map));
}
let json = serde_json::to_string_pretty(&Value::Object(out))
.map_err(|e| Error::other(format!("json serialize: {e}")))?;
fs::write(job.output, json + "\n")?;
Ok(())
}
pub fn json_to_ini(job: &Job) -> Result<()> {
let raw = fs::read_to_string(job.input)?;
let value: Value = serde_json::from_str(&raw)
.map_err(|e| Error::other(format!("invalid JSON: {e}")))?;
let Value::Object(map) = value else {
return Err(Error::other(
"json → ini: root must be an object of sections".to_string(),
));
};
let mut ini = Ini::new();
let mut defaults: Vec<(String, String)> = Vec::new();
for (section, body) in map {
match body {
Value::Object(props) => {
let section_name = if section.is_empty() {
None
} else {
Some(section.as_str())
};
let mut setter = ini.with_section(section_name);
for (k, v) in &props {
setter.set(k, scalar_to_ini(v));
}
}
other => {
defaults.push((section, scalar_to_ini(&other)));
}
}
}
if !defaults.is_empty() {
let mut setter = ini.with_general_section();
for (k, v) in &defaults {
setter.set(k, v);
}
}
let mut buf: Vec<u8> = Vec::new();
ini
.write_to(&mut buf)
.map_err(|e| Error::other(format!("ini write: {e}")))?;
fs::write(job.output, buf)?;
Ok(())
}
fn scalar_to_ini(v: &Value) -> String {
match v {
Value::String(s) => s.clone(),
Value::Null => String::new(),
Value::Bool(b) => b.to_string(),
Value::Number(n) => n.to_string(),
other => other.to_string(),
}
}