use super::{KeySpan, dotted, lines};
fn strip_comment(line: &str) -> &str {
super::strip_comment(line, &[';', '#'], true)
}
pub(crate) fn keys(text: &str) -> Vec<KeySpan> {
let mut section = String::new();
let mut spans = Vec::new();
for (offset, line) in lines(text) {
let content = strip_comment(line);
let trimmed = content.trim();
if trimmed.is_empty() {
continue;
}
if let Some(name) = trimmed.strip_prefix('[').and_then(|r| r.strip_suffix(']')) {
section = name.trim().to_string();
continue;
}
if let Some(span) = span(offset, content, §ion) {
spans.push(span);
}
}
spans
}
fn span(offset: usize, content: &str, section: &str) -> Option<KeySpan> {
let separator = content.find(['=', ':'])?;
let key = content[..separator].trim();
if key.is_empty() {
return None;
}
Some(KeySpan {
start: offset + separator + 1,
end: offset + content.len(),
path: dotted(section, key),
})
}
#[cfg(test)]
mod tests {
use super::*;
fn at(text: &str, needle: &str) -> Option<String> {
let at = text.find(needle).expect("the needle is in the text");
keys(text)
.into_iter()
.find(|span| span.start <= at && at < span.end)
.map(|span| span.path)
}
#[test]
fn a_section_prefixes_its_keys() {
let text = "[server]\nbind = 10.0.0.5\n";
assert_eq!(at(text, "10.0.0.5").as_deref(), Some("server.bind"));
}
#[test]
fn a_key_outside_a_section_stands_alone() {
assert_eq!(at("bind = 10.0.0.5\n", "10.0.0.5").as_deref(), Some("bind"));
}
#[test]
fn a_colon_separates_as_well_as_an_equals() {
assert_eq!(at("host: 10.0.0.5\n", "10.0.0.5").as_deref(), Some("host"));
}
#[test]
fn an_ipv6_value_does_not_split_its_own_key() {
let text = "[dns]\nupstream = 2001:db8::1\n";
assert_eq!(at(text, "2001:db8::1").as_deref(), Some("dns.upstream"));
}
#[test]
fn comments_in_both_dialects_are_skipped() {
assert!(keys("; a = 10.0.0.1\n# b = 10.0.0.2\n").is_empty());
}
#[test]
fn a_section_header_is_not_a_value() {
assert_eq!(at("[10.0.0.1]\na = b\n", "10.0.0.1"), None);
}
#[test]
fn a_line_that_names_no_key_yields_no_span() {
assert!(keys("just some prose 10.0.0.1\n").is_empty());
assert!(keys("= 10.0.0.1\n").is_empty());
assert!(keys(" : 10.0.0.1\n").is_empty());
}
}