| 1 | |
|
| 2 | |
|
| 3 | |
|
| 4 | | use std::collections::BTreeMap; |
| 5 | |
|
| 6 | | pub type Sections = BTreeMap<String, BTreeMap<String, String>>; |
| 7 | |
|
| 8 | 3× | pub fn parse(text: &str) -> Sections { |
| 9 | 3× | let mut out = Sections::new(); |
| 10 | 3× | let mut current: Option<String> = None; |
| 11 | 13× | for raw in text.lines() { |
| 12 | 13× | let line = raw.trim(); |
| 13 | 13× | if line.is_empty() || line.starts_with('#') || line.starts_with(';') { |
| 14 | 2× | continue; |
| 15 | 11× | } |
| 16 | 11× | if let Some(name) = line.strip_prefix('[').and_then(|l| l.strip_suffix(']')) { |
| 17 | 5× | current = Some(name.trim().to_string()); |
| 18 | 5× | out.entry(name.trim().to_string()).or_default(); |
| 19 | 5× | continue; |
| 20 | 6× | } |
| 21 | 6× | if let (Some(section), Some((k, v))) = (¤t, line.split_once('=')) { |
| 22 | 6× | out.entry(section.clone()) |
| 23 | 6× | .or_default() |
| 24 | 6× | .insert(k.trim().to_string(), v.trim().to_string()); |
| 25 | 6× | } |
| 26 | | } |
| 27 | 3× | out |
| 28 | 3× | } |
| 29 | |
|
| 30 | | #[cfg(test)] |
| 31 | | mod tests { |
| 32 | | use super::parse; |
| 33 | |
|
| 34 | | #[test] |
| 35 | 1× | fn sections_and_keys() { |
| 36 | 1× | let s = parse("[default]\nregion = eu-west-2\n\n# a comment\n[profile work]\nregion=us-east-1\nsso_start_url = https://x\n"); |
| 37 | 1× | assert_eq!(s["default"]["region"], "eu-west-2"); |
| 38 | 1× | assert_eq!(s["profile work"]["region"], "us-east-1"); |
| 39 | 1× | assert_eq!(s["profile work"]["sso_start_url"], "https://x"); |
| 40 | 1× | assert!(parse("").is_empty()); |
| 41 | 1× | } |
| 42 | | } |