use super::policy::{Coercion, Literal, Value, collect};
fn options() -> ini::ParseOption {
ini::ParseOption {
enabled_escape: false,
..ini::ParseOption::default()
}
}
fn without_bare_keys(text: &str) -> String {
text.lines()
.filter(|line| {
let trimmed = line.trim();
trimmed.is_empty()
|| line.starts_with(char::is_whitespace)
|| trimmed.starts_with(';')
|| trimmed.starts_with('#')
|| trimmed.starts_with('[')
|| trimmed.contains('=')
|| trimmed.contains(':')
})
.collect::<Vec<_>>()
.join("\n")
}
pub(crate) fn extract(text: &str) -> Vec<Literal> {
let Ok(parsed) = ini::Ini::load_from_str_opt(&without_bare_keys(text), options()) else {
return Vec::new();
};
let mut values = Vec::new();
for (_, properties) in &parsed {
for (_, value) in properties {
values.push(Value::Text(value.to_string()));
}
}
collect(&Value::Seq(values), Coercion::Untyped)
}
pub(crate) fn parse_error(text: &str) -> Option<String> {
ini::Ini::load_from_str_opt(&without_bare_keys(text), options())
.err()
.map(|error| format!("Failed to parse INI: {error}"))
}
#[cfg(test)]
mod tests {
use super::*;
fn values(text: &str) -> Vec<f64> {
extract(text)
.into_iter()
.map(|literal| literal.value)
.collect()
}
#[test]
fn numeric_looking_values_are_numbers_here() {
assert_eq!(values("[s]\nport = 8080\nrate = 0.0825"), [8080.0, 0.0825]);
}
#[test]
fn an_exponent_is_read() {
assert_eq!(values("[s]\na = -1.5e3"), [-1500.0]);
}
#[test]
fn partly_numeric_values_are_rejected() {
assert!(values("[s]\na = 12abc\nb = 1.2.3\nc = hello").is_empty());
}
#[test]
fn hex_and_underscored_values_are_not_numbers_here() {
assert!(values("[s]\na = 0x1A\nb = 1_000").is_empty());
}
#[test]
fn comment_lines_are_skipped() {
assert_eq!(values("[s]\n; n = 1\n# m = 2\na = 3"), [3.0]);
}
#[test]
fn sections_are_walked_in_order() {
assert_eq!(values("[one]\na = 1\n\n[two]\nb = 2"), [1.0, 2.0]);
}
#[test]
fn an_indented_continuation_is_not_a_bare_key() {
assert_eq!(values("[s]\nmulti = 1\n 2\nport = 8080"), [1.0, 8080.0]);
}
#[test]
fn a_document_that_cannot_parse_yields_nothing_and_says_why() {
assert!(values("[unclosed").is_empty());
assert!(parse_error("[unclosed").is_some());
assert!(parse_error("[s]\na = 1").is_none());
}
#[test]
fn a_bare_key_does_not_take_the_file_down_with_it() {
assert_eq!(values("[s]\nport = 8080\nbarekey"), [8080.0]);
}
}