1use crate::ConfigContent;
7use crate::error::{ConfigError, ConfigResult};
8use crate::key::{Key, KeySegment};
9use crate::value::Value;
10use core::fmt;
11use core::str::FromStr;
12use std::collections::HashMap;
13
14#[derive(Debug, Clone)]
16pub struct IniContent {
17 data: HashMap<String, HashMap<String, Option<String>>>,
18}
19
20impl PartialEq for IniContent {
21 fn eq(&self, other: &Self) -> bool {
22 self.data == other.data
24 }
25}
26
27impl IniContent {
28 pub fn from_map(data: HashMap<String, HashMap<String, Option<String>>>) -> Self {
30 Self { data }
31 }
32
33 pub fn as_map(&self) -> &HashMap<String, HashMap<String, Option<String>>> {
35 &self.data
36 }
37
38 pub fn into_map(self) -> HashMap<String, HashMap<String, Option<String>>> {
40 self.data
41 }
42
43 fn parse_key(key: &Key) -> ConfigResult<(Option<String>, String)> {
44 let segments = key.segments();
45 if segments.is_empty() {
46 return Err(ConfigError::InvalidKey("Empty key".to_string()));
47 }
48
49 if segments.len() == 1 {
50 if let KeySegment::Key(s) = &segments[0] {
51 return Ok((None, s.clone()));
52 }
53 }
54
55 if segments.len() == 2 {
56 if let (KeySegment::Key(section), KeySegment::Key(k)) = (&segments[0], &segments[1]) {
57 return Ok((Some(section.clone()), k.clone()));
58 }
59 }
60
61 Err(ConfigError::InvalidKey(
62 "INI key must be .key or .section.key".to_string(),
63 ))
64 }
65
66 fn get_value_at_key(&self, key: &Key) -> ConfigResult<String> {
67 let (section, key_name) = Self::parse_key(key)?;
68 match section {
69 Some(sec) => self
70 .data
71 .get(&sec)
72 .and_then(|s| s.get(&key_name))
73 .cloned()
74 .ok_or_else(|| ConfigError::KeyNotFound(key.to_key_string()))
75 .and_then(|v| v.ok_or_else(|| ConfigError::KeyNotFound(key.to_key_string()))),
76 None => self
77 .data
78 .get("")
79 .and_then(|s| s.get(&key_name))
80 .cloned()
81 .ok_or_else(|| ConfigError::KeyNotFound(key.to_key_string()))
82 .and_then(|v| v.ok_or_else(|| ConfigError::KeyNotFound(key.to_key_string()))),
83 }
84 }
85
86 fn contains_key_at(&self, key: &Key) -> bool {
87 self.get_value_at_key(key).is_ok()
88 }
89
90 fn keys_at(&self) -> Vec<Key> {
91 let mut keys = Vec::new();
92
93 if let Some(section) = self.data.get("") {
95 for k in section.keys() {
96 if let Ok(key) = Key::from_str(&format!(".{k}")) {
97 keys.push(key);
98 }
99 }
100 }
101
102 for (section_name, section) in &self.data {
104 if section_name.is_empty() {
105 continue;
106 }
107 for k in section.keys() {
108 if let Ok(key) = Key::from_str(&format!(".{section_name}.{k}")) {
109 keys.push(key);
110 }
111 }
112 }
113
114 keys
115 }
116
117 #[allow(dead_code)]
118 fn len_at(&self) -> usize {
119 self.keys_at().len()
120 }
121
122 fn delete_at(&mut self, key: &Key) -> ConfigResult<()> {
123 let (section, key_name) = Self::parse_key(key)?;
124 let section_key = section.unwrap_or_default();
125 if let Some(properties) = self.data.get_mut(§ion_key) {
126 if properties.remove(&key_name).is_some() {
127 return Ok(());
128 }
129 }
130 Err(ConfigError::KeyNotFound(key.to_key_string()))
131 }
132
133 fn upsert_at(&mut self, key: &Key, value: &Value) -> ConfigResult<()> {
134 let (section, key_name) = Self::parse_key(key)?;
135 let value_str = value.to_string_repr();
136
137 let section_name = section.unwrap_or_default();
138 let section_data = self.data.entry(section_name).or_default();
139 drop(section_data.insert(key_name, Some(value_str)));
140 Ok(())
141 }
142}
143
144impl fmt::Display for IniContent {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 let mut output = String::new();
147
148 if let Some(section) = self.data.get("") {
150 for (key, value) in section {
151 output.push_str(key);
152 output.push('=');
153 if let Some(v) = value {
154 output.push_str(v);
155 }
156 output.push('\n');
157 }
158 if !section.is_empty() {
159 output.push('\n');
160 }
161 }
162
163 for (section_name, section) in &self.data {
164 if section_name.is_empty() {
165 continue;
166 }
167 if section.is_empty() {
168 continue;
169 }
170
171 output.push('[');
172 output.push_str(section_name);
173 output.push_str("]\n");
174
175 for (key, value) in section {
176 output.push_str(key);
177 output.push('=');
178 if let Some(v) = value {
179 output.push_str(v);
180 }
181 output.push('\n');
182 }
183
184 output.push('\n');
185 }
186
187 write!(f, "{}", output.trim_end())
188 }
189}
190
191fn strip_inline_comment(value: &str) -> &str {
194 let mut in_single_quote = false;
195 let mut in_double_quote = false;
196
197 for (i, c) in value.char_indices() {
198 match c {
199 '\'' if !in_double_quote => in_single_quote = !in_single_quote,
200 '"' if !in_single_quote => in_double_quote = !in_double_quote,
201 '#' | ';' if !in_single_quote && !in_double_quote => {
202 return value[..i].trim();
203 }
204 _ => {}
205 }
206 }
207 value
208}
209
210fn parse_ini_content(input: &str) -> HashMap<String, HashMap<String, Option<String>>> {
211 let estimated_sections = input
213 .lines()
214 .filter(|l| l.trim().starts_with('['))
215 .count()
216 .saturating_add(1); let _estimated_keys = input
218 .lines()
219 .filter(|l| {
220 let t = l.trim();
221 !t.is_empty() && !t.starts_with('[') && !t.starts_with('#') && !t.starts_with(';')
222 })
223 .count();
224
225 let mut data: HashMap<String, HashMap<String, Option<String>>> =
226 HashMap::with_capacity(estimated_sections);
227 let mut current_section = String::new();
228
229 let _ = data.entry(String::new()).or_default();
231
232 for line in input.lines() {
233 let line = line.trim();
234
235 if line.is_empty() {
237 continue;
238 }
239
240 if line.starts_with('#') || line.starts_with(';') || line.starts_with("//") {
242 continue;
243 }
244
245 if line.starts_with('[') {
247 if let Some(end) = line.find(']') {
248 let section = line[1..end].trim().to_string();
249 if !section.is_empty() {
250 current_section = section;
251 let _ = data.entry(current_section.clone()).or_default();
252 }
253 continue;
254 }
255 }
256
257 if let Some(eq_pos) = line.find('=') {
259 let key = line[..eq_pos].trim().to_string();
260 let value = line[eq_pos + 1..].trim();
261
262 let value = strip_inline_comment(value);
264
265 if !key.is_empty() {
266 let section_data = data.entry(current_section.clone()).or_default();
267 drop(section_data.insert(key, Some(value.to_string())));
268 }
269 }
270 }
271
272 if let Some(default_section) = data.get("") {
274 if default_section.is_empty() {
275 drop(data.remove(""));
276 }
277 }
278
279 data
280}
281
282impl FromStr for IniContent {
283 type Err = ConfigError;
284
285 fn from_str(s: &str) -> Result<Self, Self::Err> {
286 let data = parse_ini_content(s);
287 Ok(Self { data })
288 }
289}
290
291impl ConfigContent for IniContent {
292 fn select(&self, key: &Key) -> ConfigResult<Value> {
293 self.get_value_at_key(key).map(Value::string)
294 }
295
296 fn insert(&mut self, key: &Key, value: &Value) -> ConfigResult<()> {
297 if self.contains_key_at(key) {
298 return Err(ConfigError::KeyAlreadyExists(key.to_key_string()));
299 }
300 self.upsert_at(key, value)
301 }
302
303 fn update(&mut self, key: &Key, value: &Value) -> ConfigResult<()> {
304 if !self.contains_key_at(key) {
305 return Err(ConfigError::KeyDoesNotExist(key.to_key_string()));
306 }
307 self.upsert_at(key, value)
308 }
309
310 fn delete(&mut self, key: &Key) -> ConfigResult<()> {
311 self.delete_at(key)
312 }
313
314 fn upsert(&mut self, key: &Key, value: &Value) -> ConfigResult<()> {
315 self.upsert_at(key, value)
316 }
317
318 fn keys(&self) -> Vec<Key> {
319 self.keys_at()
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 #[test]
328 fn test_ini_from_str() {
329 let content = IniContent::from_str("key=value").expect("test should succeed");
330 assert!(
331 content
332 .select(&Key::from_str(".key").expect("test should succeed"))
333 .is_ok()
334 );
335 }
336
337 #[test]
338 fn test_ini_select() {
339 let content = IniContent::from_str("name=test").expect("test should succeed");
340 let value = content
341 .select(&Key::from_str(".name").expect("test should succeed"))
342 .expect("test should succeed");
343 assert_eq!(value.as_str(), Some("test"));
344 }
345
346 #[test]
347 fn test_ini_section() {
348 let content =
349 IniContent::from_str("[database]\nurl=localhost").expect("test should succeed");
350 let value = content
351 .select(&Key::from_str(".database.url").expect("test should succeed"))
352 .expect("test should succeed");
353 assert_eq!(value.as_str(), Some("localhost"));
354 }
355
356 #[test]
357 fn test_ini_insert() {
358 let mut content = IniContent::from_str("").expect("test should succeed");
359 content
360 .insert(
361 &Key::from_str(".name").expect("test should succeed"),
362 &Value::string("test"),
363 )
364 .expect("test should succeed");
365 let value = content
366 .select(&Key::from_str(".name").expect("test should succeed"))
367 .expect("test should succeed");
368 assert_eq!(value.as_str(), Some("test"));
369 }
370
371 #[test]
372 fn test_ini_update() {
373 let mut content = IniContent::from_str("name=old").expect("test should succeed");
374 content
375 .update(
376 &Key::from_str(".name").expect("test should succeed"),
377 &Value::string("new"),
378 )
379 .expect("test should succeed");
380 let value = content
381 .select(&Key::from_str(".name").expect("test should succeed"))
382 .expect("test should succeed");
383 assert_eq!(value.as_str(), Some("new"));
384 }
385
386 #[test]
387 fn test_ini_delete() {
388 let mut content = IniContent::from_str("name=test").expect("test should succeed");
389 content
390 .delete(&Key::from_str(".name").expect("test should succeed"))
391 .expect("test should succeed");
392 assert!(
393 content
394 .select(&Key::from_str(".name").expect("test should succeed"))
395 .is_err()
396 );
397 }
398
399 #[test]
400 fn test_ini_upsert() {
401 let mut content = IniContent::from_str("").expect("test should succeed");
402 content
403 .upsert(
404 &Key::from_str(".name").expect("test should succeed"),
405 &Value::string("test"),
406 )
407 .expect("test should succeed");
408 let value = content
409 .select(&Key::from_str(".name").expect("test should succeed"))
410 .expect("test should succeed");
411 assert_eq!(value.as_str(), Some("test"));
412 }
413
414 #[test]
415 fn test_ini_keys() {
416 let content = IniContent::from_str("a=1\nb=2").expect("test should succeed");
417 let keys = content.keys();
418 assert_eq!(keys.len(), 2);
419 }
420
421 #[test]
422 fn test_ini_section_nested() {
423 let content =
424 IniContent::from_str("[database]\nhost=localhost").expect("test should succeed");
425 let value = content
426 .select(&Key::from_str(".database.host").expect("test should succeed"))
427 .expect("test should succeed");
428 assert_eq!(value.as_str(), Some("localhost"));
429 }
430
431 #[test]
432 fn test_ini_multiple_sections() {
433 let content = IniContent::from_str("[database]\nhost=localhost\n[server]\nport=8080")
434 .expect("test should succeed");
435 assert!(
436 content
437 .select(&Key::from_str(".database.host").expect("test should succeed"))
438 .is_ok()
439 );
440 assert!(
441 content
442 .select(&Key::from_str(".server.port").expect("test should succeed"))
443 .is_ok()
444 );
445 }
446
447 #[test]
448 fn test_ini_comment() {
449 let content =
450 IniContent::from_str("# This is a comment\nkey=value").expect("test should succeed");
451 let value = content
452 .select(&Key::from_str(".key").expect("test should succeed"))
453 .expect("test should succeed");
454 assert_eq!(value.as_str(), Some("value"));
455 }
456
457 #[test]
458 fn test_ini_inline_comment() {
459 let content = IniContent::from_str("key=value # comment").expect("test should succeed");
460 let value = content
461 .select(&Key::from_str(".key").expect("test should succeed"))
462 .expect("test should succeed");
463 assert_eq!(value.as_str(), Some("value"));
464 }
465
466 #[test]
467 fn test_ini_empty_value() {
468 let content = IniContent::from_str("key=").expect("test should succeed");
469 let value = content
470 .select(&Key::from_str(".key").expect("test should succeed"))
471 .expect("test should succeed");
472 assert_eq!(value.as_str(), Some(""));
473 }
474
475 #[test]
476 fn test_ini_update_section_value() {
477 let mut content =
478 IniContent::from_str("[database]\nhost=old").expect("test should succeed");
479 content
480 .update(
481 &Key::from_str(".database.host").expect("test should succeed"),
482 &Value::string("new"),
483 )
484 .expect("test should succeed");
485 let value = content
486 .select(&Key::from_str(".database.host").expect("test should succeed"))
487 .expect("test should succeed");
488 assert_eq!(value.as_str(), Some("new"));
489 }
490
491 #[test]
492 fn test_ini_delete_section_value() {
493 let mut content = IniContent::from_str("[database]\nhost=localhost\nport=5432")
494 .expect("test should succeed");
495 content
496 .delete(&Key::from_str(".database.port").expect("test should succeed"))
497 .expect("test should succeed");
498 assert!(
499 content
500 .select(&Key::from_str(".database.port").expect("test should succeed"))
501 .is_err()
502 );
503 }
504}