use super::locate::{KeySpan, lines, value_length};
const COMMENT: &[u8] = b";#";
pub(crate) fn key_spans(text: &str) -> Vec<KeySpan> {
let mut section = String::new();
let mut spans = Vec::new();
for (offset, line) in lines(text) {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with(';') || trimmed.starts_with('#') {
continue;
}
if let Some(name) = trimmed
.strip_prefix('[')
.and_then(|rest| rest.strip_suffix(']'))
{
section = name.trim().to_string();
continue;
}
let Some(separator) = separator(line) else {
continue;
};
let key = line[..separator].trim();
if key.is_empty() {
continue;
}
spans.push(KeySpan {
start: offset + separator + 1,
end: offset + separator + 1 + value_length(&line[separator + 1..], COMMENT),
path: if section.is_empty() {
key.to_string()
} else {
format!("{section}.{key}")
},
});
}
spans
}
fn separator(line: &str) -> Option<usize> {
line.find('=').or_else(|| line.find(':'))
}
#[cfg(test)]
mod tests {
use super::*;
fn keyed(text: &str, expected: &[(&str, &str)]) {
let spans = key_spans(text);
let found: Vec<(&str, &str)> = spans
.iter()
.map(|span| (text[span.start..span.end].trim(), span.path.as_str()))
.collect();
assert_eq!(found, expected, "{text}");
}
#[test]
fn a_value_is_named_by_its_section_and_key() {
keyed("[service]\nid = abc\n", &[("abc", "service.id")]);
}
#[test]
fn a_key_before_any_section_is_named_alone() {
keyed("id = abc\n", &[("abc", "id")]);
}
#[test]
fn a_later_section_replaces_the_earlier_one() {
keyed(
"[a]\nid = 1\n[b]\nid = 2\n",
&[("1", "a.id"), ("2", "b.id")],
);
}
#[test]
fn both_comment_characters_are_honoured() {
keyed("; id = 1\n# id = 2\nid = 3\n", &[("3", "id")]);
}
#[test]
fn an_equals_is_preferred_over_a_colon() {
keyed(
"url = https://example.com\n",
&[("https://example.com", "url")],
);
keyed(
"url:https://example.com\n",
&[("https://example.com", "url")],
);
}
#[test]
fn a_line_with_no_separator_is_not_a_value() {
keyed("[a]\njustaword\n", &[]);
}
#[test]
fn a_trailing_comment_is_outside_the_value() {
keyed("id = 1 # 6a7bb780a1b2c3d4e5f60718\n", &[("1", "id")]);
keyed("id = 1 ; 6a7bb780a1b2c3d4e5f60718\n", &[("1", "id")]);
}
#[test]
fn a_comment_character_inside_a_string_belongs_to_the_value() {
keyed("id = \"a # b\"\n", &[("\"a # b\"", "id")]);
keyed("id = \"a ; b\"\n", &[("\"a ; b\"", "id")]);
}
}