hyprshell_core_lib/
ini.rs1use std::collections::HashMap;
2use std::path::Path;
3
4#[derive(Debug, Default)]
5pub struct IniFile<'a> {
6 sections: HashMap<&'a str, Section<'a>>,
7}
8
9#[derive(Debug, Default)]
10pub struct Section<'a> {
11 entries: HashMap<&'a str, &'a str>,
12}
13
14impl<'a> Section<'a> {
15 pub fn insert(&mut self, key: &'a str, value: &'a str) {
16 self.entries.insert(key, value);
17 }
18
19 pub fn get(&self, key: &str) -> Option<&'a str> {
20 self.entries.get(key).copied()
21 }
22
23 pub fn get_boxed(&self, key: &str) -> Option<Box<str>> {
24 self.get(key).map(Box::from)
25 }
26
27 pub fn get_path_boxed(&self, key: &str) -> Option<Box<Path>> {
28 self.get(key).map(Path::new).map(Box::from)
29 }
30
31 pub fn get_boolean(&self, key: &str) -> Option<bool> {
32 self.get(key).map(|s| s == "true")
33 }
34
35 pub fn values(&'a self) -> impl Iterator<Item = &'a str> {
36 self.entries.values().copied()
37 }
38}
39
40impl<'a> IniFile<'a> {
41 pub fn parse(content: &'a str) -> Self {
42 let mut sections = HashMap::new();
43 let mut current_section = sections.entry("").or_insert_with(Section::default);
44
45 for line in content.lines() {
46 let line = line.trim();
47 if line.is_empty() || line.starts_with('#') || line.starts_with(';') {
48 continue;
49 }
50
51 if line.starts_with('[') && line.ends_with(']') {
52 let current_section_name = &line[1..line.len() - 1];
53 current_section = sections
54 .entry(current_section_name.trim())
55 .or_insert_with(Section::default);
56 continue;
57 }
58
59 if let Some((key, value)) = line.split_once('=') {
60 let key = key.trim();
61 let value = value.trim();
62
63 if key.contains('[') {
65 continue;
66 }
67 current_section.insert(key, value);
68 }
69 }
70
71 Self { sections }
72 }
73
74 pub fn get_section(&self, section: &str) -> Option<&Section> {
75 self.sections.get(section)
76 }
77
78 pub fn get_value(&self, section: &str, key: &str) -> Option<&'a str> {
79 self.sections.get(section).and_then(|s| s.get(key))
80 }
81
82 pub fn sections(&self) -> &HashMap<&'a str, Section> {
83 &self.sections
84 }
85
86 pub fn values(&'a self) -> impl Iterator<Item = &'a str> + 'a {
87 self.sections.values().flat_map(|section| section.values())
88 }
89}
90
91impl<'a> IntoIterator for &'a IniFile<'a> {
92 type Item = &'a str;
93 type IntoIter = Box<dyn Iterator<Item = &'a str> + 'a>;
94
95 fn into_iter(self) -> Self::IntoIter {
96 Box::new(self.values())
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_parse_ini() {
106 let content = r#"[Section1]
107key1=value1
108key2=value2
109
110[Section2]
111foo=bar
112baz=qux
113
114; Comment
115# Another comment
116[Empty Section]
117
118[Section With Spaces]
119key with spaces=value with spaces
120"#;
121
122 let ini = IniFile::parse(content);
123
124 assert_eq!(ini.get_value("Section1", "key1"), Some("value1"));
126 assert_eq!(ini.get_value("Section2", "foo"), Some("bar"));
127
128 assert!(ini.get_section("Empty Section").is_some());
130
131 assert_eq!(
133 ini.get_value("Section With Spaces", "key with spaces"),
134 Some("value with spaces")
135 );
136
137 assert_eq!(ini.get_value("NonExistent", "key"), None);
139 assert_eq!(ini.get_value("Section1", "nonexistent"), None);
140 }
141
142 #[test]
143 fn test_empty_ini() {
144 let content = "";
145 let ini = IniFile::parse(content);
146 assert_eq!(ini.sections().len(), 1);
147 }
148
149 #[test]
150 fn test_no_sections() {
151 let content = "key=value";
152 let ini = IniFile::parse(content);
153 assert_eq!(ini.get_value("", "key"), Some("value"));
154 }
155
156 #[test]
157 fn test_values_iterator() {
158 let content = r#"
159 [Section1]
160 key1=value1
161 key2=value2
162
163 [Section2]
164 foo=bar
165 "#;
166 let ini = IniFile::parse(content);
167 let values: Vec<_> = ini.values().collect();
168 assert!(values.contains(&"value1"));
169 assert!(values.contains(&"value2"));
170 assert!(values.contains(&"bar"));
171 assert_eq!(values.len(), 3);
172 }
173
174 #[test]
175 fn test_values_iterator_2() {
176 let content = r#"
177 [Section1]
178 key1=value1
179 key2=value2
180
181 [Section2]
182 foo=bar
183 "#;
184 let ini = IniFile::parse(content);
185 let mut count = 0;
186 for item in &ini {
187 assert!(!item.is_empty(), "Item should not be empty");
188 count += 1;
189 }
190 assert_eq!(count, 3, "There should be 3 items in the iterator");
191 }
192}