use std::fs;
use std::path::Path;
use toml_edit::{DocumentMut, TableLike};
use crate::error::ForgeError;
use crate::ui::{self, StatusKind};
pub(super) fn is_cargo_config(path: &Path) -> bool {
if !matches!(
path.file_name().and_then(|name| name.to_str()),
Some("config" | "config.toml")
) {
return false;
}
let Some(parent) = path.parent() else {
return false;
};
if parent.file_name().is_some_and(|name| name == ".cargo") {
return true;
}
std::env::var_os("CARGO_HOME")
.filter(|home| !home.is_empty())
.and_then(|home| std::path::absolute(home).ok())
.zip(std::path::absolute(parent).ok())
.is_some_and(|(home, parent)| super::paths_equal(&home, &parent))
}
pub(super) fn merge(label: &str, path: &Path, lines: &[String]) -> Result<(), ForgeError> {
let existing = match fs::read_to_string(path) {
Ok(text) => text,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => String::new(),
Err(source) => {
return Err(ForgeError::Io {
path: path.to_owned(),
source,
});
}
};
let parse = |text: &str| {
text.parse::<DocumentMut>().map_err(|error| {
ForgeError::Config(format!(
"cannot merge Cargo configuration {}: {error}",
path.display()
))
})
};
let mut document = parse(&existing)?;
let fragment = parse(&lines.join("\n"))?;
merge_tables(document.as_table_mut(), fragment.as_table());
let merged = document.to_string();
let values = |text: &str| {
toml::from_str::<toml::Value>(text).map_err(|error| {
ForgeError::Config(format!(
"invalid Cargo configuration {}: {error}",
path.display()
))
})
};
if values(&existing)? == values(&merged)? {
ui::stdout_status(
StatusKind::Info,
&format!(
"{label} already contains bot-forge configuration · {}",
path.display()
),
);
return Ok(());
}
super::write_text(path, &merged)?;
ui::stdout_status(
StatusKind::Success,
&format!("{label} updated · {}", path.display()),
);
Ok(())
}
fn merge_tables(target: &mut dyn TableLike, fragment: &dyn TableLike) {
for (key, incoming) in fragment.iter() {
if let Some(current) = target.get_mut(key) {
if let (Some(target_table), Some(source_table)) =
(current.as_table_like_mut(), incoming.as_table_like())
{
merge_tables(target_table, source_table);
continue;
}
let mut replacement = incoming.clone();
if let (Some(old), Some(new)) = (current.as_value(), replacement.as_value_mut()) {
*new.decor_mut() = old.decor().clone();
}
*current = replacement;
} else {
target.insert(key, incoming.clone());
}
}
}