use super::{BlockKey, GrammarError, block_end, block_key, entries, field, join};
pub(super) fn locate(
file: &'static str,
lines: &[&str],
block: &str,
entry: &str,
name: &'static str,
) -> Result<(String, usize), GrammarError> {
let at = match block_key(lines, block) {
BlockKey::At(at) => at,
BlockKey::Inline => {
return Err(GrammarError::Inline {
file,
key: block.to_owned(),
});
}
BlockKey::Absent => {
return Err(GrammarError::NoEntry {
file,
entry: entry.to_owned(),
});
}
};
let start = entry_line(lines, at, entry).ok_or_else(|| GrammarError::NoEntry {
file,
entry: entry.to_owned(),
})?;
field(lines, start, name).ok_or_else(|| GrammarError::NoField {
file,
entry: entry.to_owned(),
field: name,
})
}
fn entry_line(lines: &[&str], at: usize, entry: &str) -> Option<usize> {
entries(lines, at)
.into_iter()
.find(|(name, _)| name == entry)
.map(|(_, i)| i)
}
pub fn entry_names(text: &str, block: &str) -> Vec<String> {
let lines: Vec<&str> = text.lines().collect();
let BlockKey::At(at) = block_key(&lines, block) else {
return Vec::new();
};
entries(&lines, at)
.into_iter()
.map(|(name, _)| name)
.collect()
}
pub fn entry_field(text: &str, block: &str, entry: &str, name: &str) -> Option<String> {
let lines: Vec<&str> = text.lines().collect();
let BlockKey::At(at) = block_key(&lines, block) else {
return None;
};
let start = entry_line(&lines, at, entry)?;
Some(field(&lines, start, name)?.0)
}
pub fn set_field(
file: &'static str,
text: &str,
block: &str,
entry: &str,
name: &'static str,
value: &str,
) -> Result<String, GrammarError> {
let lines: Vec<&str> = text.lines().collect();
let (_, i) = locate(file, &lines, block, entry, name)?;
let mut out: Vec<String> = lines.iter().map(|l| (*l).to_string()).collect();
if let Some(slot) = out.get_mut(i) {
*slot = format!(" {name}: {value}");
}
Ok(join(&out))
}
pub fn set_entry(
text: &str,
block: &str,
entry: &str,
fields: &[(&str, String)],
) -> Option<String> {
let lines: Vec<&str> = text.lines().collect();
let mut out: Vec<String> = lines.iter().map(|l| (*l).to_string()).collect();
let body = std::iter::once(format!(" {entry}:"))
.chain(fields.iter().map(|(k, v)| format!(" {k}: {v}")));
match block_key(&lines, block) {
BlockKey::Inline => return None,
BlockKey::Absent => {
out.retain(|line| !line.trim().is_empty());
out.push(format!("{block}:"));
out.extend(body);
}
BlockKey::At(at) => {
let span = entry_span(&lines, at, entry);
if let Some((from, to)) = span {
out.splice(from..to, body);
} else {
out.splice((at + 1)..=at, body);
}
}
}
Some(join(&out))
}
pub fn remove_entry(text: &str, block: &str, entry: &str) -> String {
let lines: Vec<&str> = text.lines().collect();
let mut out: Vec<String> = lines.iter().map(|l| (*l).to_string()).collect();
if let BlockKey::At(at) = block_key(&lines, block)
&& let Some((from, to)) = entry_span(&lines, at, entry)
{
out.drain(from..to);
}
join(&out)
}
fn entry_span(lines: &[&str], at: usize, entry: &str) -> Option<(usize, usize)> {
let found = entries(lines, at);
let from = found.iter().find(|(name, _)| name == entry)?.1;
let to = found
.iter()
.map(|(_, i)| *i)
.find(|i| *i > from)
.unwrap_or_else(|| block_end(lines, from));
Some((from, to))
}