use super::entries::{entry_line, entry_span};
use super::{BlockKey, GrammarError, block_key, 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,
})
}
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 upsert_field(
file: &'static str,
text: &str,
block: &str,
entry: &str,
name: &'static str,
value: &str,
) -> Result<String, GrammarError> {
match set_field(file, text, block, entry, name, value) {
Err(GrammarError::NoField { .. }) => insert_field(file, text, block, entry, name, value),
settled => settled,
}
}
fn span_of(
file: &'static str,
lines: &[&str],
block: &str,
entry: &str,
) -> Result<(usize, usize), GrammarError> {
let missing = || GrammarError::NoEntry {
file,
entry: entry.to_owned(),
};
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(missing()),
};
entry_span(lines, at, entry).ok_or_else(missing)
}
fn insert_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 (from, to) = span_of(file, &lines, block, entry)?;
let mut last = from;
for (offset, line) in lines.iter().enumerate().take(to).skip(from) {
if !line.trim().is_empty() {
last = offset;
}
}
let mut out: Vec<String> = lines.iter().map(|l| (*l).to_string()).collect();
out.insert(last + 1, format!(" {name}: {value}"));
Ok(join(&out))
}
pub fn remove_field(
file: &'static str,
text: &str,
block: &str,
entry: &str,
name: &str,
) -> Result<String, GrammarError> {
let lines: Vec<&str> = text.lines().collect();
let (from, _) = span_of(file, &lines, block, entry)?;
let mut out: Vec<String> = lines.iter().map(|l| (*l).to_string()).collect();
if let Some((_, i)) = field(&lines, from, name) {
out.remove(i);
}
Ok(join(&out))
}