use toml_edit::{DocumentMut, Item, RawString, Table};
pub(super) struct Rescued {
at: usize,
text: String,
}
pub(super) fn rescue(dog: &Item) -> Vec<Rescued> {
let mut tables: Vec<&Table> = Vec::new();
if let Item::Table(table) = dog {
tables.push(table);
for (_, child) in table {
match child {
Item::Table(inner) if inner.is_empty() => tables.push(inner),
Item::ArrayOfTables(array) => tables.extend(array.iter().filter(|t| t.is_empty())),
_ => {}
}
}
}
let mut found: Vec<Rescued> = tables
.into_iter()
.filter_map(|table| {
let prefix = table.decor().prefix().and_then(RawString::as_str)?;
prefix.contains('#').then_some(())?;
Some(Rescued {
at: table.position()?,
text: prefix.to_owned(),
})
})
.collect();
found.sort_by_key(|rescued| rescued.at);
found
}
pub(super) fn restore(doc: &mut DocumentMut, rescued: Vec<Rescued>) {
for Rescued { at, text } in rescued.into_iter().rev() {
let next = next_position(doc.as_table(), at);
let placed = next.is_some_and(|position| prepend(doc.as_table_mut(), position, &text));
if !placed {
let trailing = doc.trailing().as_str().unwrap_or("");
let joined = format!("{text}{trailing}");
doc.set_trailing(joined);
}
}
}
fn next_position(table: &Table, at: usize) -> Option<usize> {
let own = table.position().filter(|position| *position > at);
let nested = table.iter().filter_map(|(_, child)| match child {
Item::Table(inner) => next_position(inner, at),
Item::ArrayOfTables(array) => array
.iter()
.filter_map(|inner| next_position(inner, at))
.min(),
Item::Value(_) | Item::None => None,
});
own.into_iter().chain(nested).min()
}
fn prepend(table: &mut Table, position: usize, text: &str) -> bool {
if table.position() == Some(position) {
let decor = table.decor_mut();
let joined = format!(
"{text}{}",
decor.prefix().and_then(RawString::as_str).unwrap_or("")
);
decor.set_prefix(joined);
return true;
}
table.iter_mut().any(|(_, child)| match child {
Item::Table(inner) => prepend(inner, position, text),
Item::ArrayOfTables(array) => array.iter_mut().any(|inner| prepend(inner, position, text)),
Item::Value(_) | Item::None => false,
})
}