use super::collect::{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 = super::text::trim(line);
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<String> {
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::Str(value.to_string()));
}
}
collect(&Value::Seq(values))
}
pub(crate) fn parse_error(text: &str) -> Option<String> {
ini::Ini::load_from_str_opt(&without_bare_keys(text), options())
.err()
.map(|error| format!("Invalid INI: {error}"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn values_are_extracted_and_keys_are_not() {
assert_eq!(extract("[s]\nname = John Doe"), ["John Doe"]);
}
#[test]
fn quoted_and_unquoted_values_both_read() {
assert_eq!(
extract("[s]\na = \"quoted\"\nb = plain text"),
["quoted", "plain text"]
);
}
#[test]
fn comment_lines_are_skipped() {
assert_eq!(
extract("[s]\n; c = commented\n# d = also\na = kept"),
["kept"]
);
}
#[test]
fn a_numeric_looking_value_is_a_string_here() {
assert_eq!(extract("[s]\nport = 8080"), ["8080"]);
}
#[test]
fn sections_are_walked_in_order() {
assert_eq!(
extract("[one]\na = first\n\n[two]\nb = second"),
["first", "second"]
);
}
#[test]
fn a_backslash_is_kept_verbatim() {
assert_eq!(
extract(
r"[s]
path = C:\Users\test"
),
[r"C:\Users\test"]
);
}
#[test]
fn a_bare_key_does_not_take_the_file_down_with_it() {
assert_eq!(extract("[a]\nname = kept\nbarekey"), ["kept"]);
assert_eq!(extract("[a]\nbarekey\nname = kept"), ["kept"]);
}
#[test]
fn an_indented_continuation_is_not_a_bare_key() {
assert_eq!(
extract("[a]\nmulti = one\n two\nname = kept"),
["one", "kept"]
);
assert_eq!(extract("[a]\n indented = v"), ["v"]);
}
#[test]
fn a_document_that_cannot_parse_yields_nothing_and_says_why() {
assert!(extract("[unclosed").is_empty());
assert!(parse_error("[unclosed").is_some());
assert!(parse_error("[a]\nb = c").is_none());
}
#[test]
fn an_empty_value_is_dropped() {
assert!(extract("[s]\nempty =").is_empty());
}
}