1use std::collections::{HashMap, HashSet};
7use std::fs::File;
8use std::io::{self, Read};
9use std::path::Path;
10
11#[derive(Debug)]
13pub enum IniReaderError {
14 Empty,
15 Duplicate,
16 OutOfBound,
17 NotExist,
18 NotParsed,
19 IoError(io::Error),
20 None,
21}
22
23impl std::fmt::Display for IniReaderError {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 match self {
26 IniReaderError::Empty => write!(f, "Empty document"),
27 IniReaderError::Duplicate => write!(f, "Duplicate section"),
28 IniReaderError::OutOfBound => write!(f, "Item exists outside of any section"),
29 IniReaderError::NotExist => write!(f, "Target does not exist"),
30 IniReaderError::NotParsed => write!(f, "Parse error"),
31 IniReaderError::IoError(e) => write!(f, "IO error: {}", e),
32 IniReaderError::None => write!(f, "No error"),
33 }
34 }
35}
36
37impl From<io::Error> for IniReaderError {
38 fn from(error: io::Error) -> Self {
39 IniReaderError::IoError(error)
40 }
41}
42
43pub struct IniReader {
45 content: HashMap<String, Vec<(String, String)>>,
47 parsed: bool,
49 current_section: String,
51 exclude_sections: HashSet<String>,
53 include_sections: HashSet<String>,
55 direct_save_sections: HashSet<String>,
57 section_order: Vec<String>,
59 last_error: IniReaderError,
61 pub store_any_line: bool,
63 pub allow_dup_section_titles: bool,
65 pub keep_empty_section: bool,
67 isolated_items_section: String,
69 pub store_isolated_line: bool,
71}
72
73impl Default for IniReader {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79impl IniReader {
80 pub fn new() -> Self {
82 IniReader {
83 content: HashMap::new(),
84 parsed: false,
85 current_section: String::new(),
86 exclude_sections: HashSet::new(),
87 include_sections: HashSet::new(),
88 direct_save_sections: HashSet::new(),
89 section_order: Vec::new(),
90 last_error: IniReaderError::None,
91 store_any_line: false,
92 allow_dup_section_titles: false,
93 keep_empty_section: true,
94 isolated_items_section: String::new(),
95 store_isolated_line: false,
96 }
97 }
98
99 pub fn exclude_section(&mut self, section: &str) {
101 self.exclude_sections.insert(section.to_string());
102 }
103
104 pub fn include_section(&mut self, section: &str) {
106 self.include_sections.insert(section.to_string());
107 }
108
109 pub fn add_direct_save_section(&mut self, section: &str) {
111 self.direct_save_sections.insert(section.to_string());
112 }
113
114 pub fn set_isolated_items_section(&mut self, section: &str) {
116 self.isolated_items_section = section.to_string();
117 }
118
119 pub fn erase_section(&mut self) {
121 if self.current_section.is_empty() {
122 return;
123 }
124
125 if let Some(section_vec) = self.content.get_mut(&self.current_section) {
126 section_vec.clear();
127 }
128 }
129
130 pub fn erase_section_by_name(&mut self, section: &str) {
132 if !self.section_exist(section) {
133 return;
134 }
135
136 if let Some(section_vec) = self.content.get_mut(section) {
137 section_vec.clear();
138 }
139 }
140
141 fn should_ignore_section(&self, section: &str) -> bool {
143 let excluded = self.exclude_sections.contains(section);
144 let included = if self.include_sections.is_empty() {
145 true
146 } else {
147 self.include_sections.contains(section)
148 };
149
150 excluded || !included
151 }
152
153 fn should_direct_save(&self, section: &str) -> bool {
155 self.direct_save_sections.contains(section)
156 }
157
158 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, IniReaderError> {
160 let mut reader = IniReader::new();
161 reader.parse_file(path)?;
162 Ok(reader)
163 }
164
165 pub fn get_last_error(&self) -> String {
167 self.last_error.to_string()
168 }
169
170 fn trim_whitespace(s: &str) -> String {
172 s.trim().to_string()
173 }
174
175 fn process_escape_char(s: &mut String) {
177 *s = s
179 .replace("\\n", "\n")
180 .replace("\\r", "\r")
181 .replace("\\t", "\t");
182 }
183
184 fn process_escape_char_reverse(s: &mut String) {
186 *s = s
188 .replace('\n', "\\n")
189 .replace('\r', "\\r")
190 .replace('\t', "\\t");
191 }
192
193 pub fn erase_all(&mut self) {
195 self.content.clear();
196 self.section_order.clear();
197 self.current_section.clear();
198 self.parsed = false;
199 }
200
201 pub fn is_parsed(&self) -> bool {
203 self.parsed
204 }
205
206 pub fn parse(&mut self, content: &str) -> Result<(), IniReaderError> {
208 self.erase_all();
210
211 if content.is_empty() {
212 self.last_error = IniReaderError::Empty;
213 return Err(IniReaderError::Empty);
214 }
215
216 let content = if content.starts_with("\u{FEFF}") {
218 &content[3..]
219 } else {
220 content
221 };
222
223 let mut in_excluded_section = false;
224 let mut in_direct_save_section = false;
225 let mut in_isolated_section = false;
226
227 let mut cur_section = String::new();
228 let mut item_group = Vec::new();
229 let mut read_sections = Vec::new();
230
231 if self.store_isolated_line && !self.isolated_items_section.is_empty() {
233 cur_section = self.isolated_items_section.clone();
234 in_excluded_section = self.should_ignore_section(&cur_section);
235 in_direct_save_section = self.should_direct_save(&cur_section);
236 in_isolated_section = true;
237 }
238
239 for line in content.lines() {
241 let line = Self::trim_whitespace(line);
242
243 if line.is_empty()
245 || line.starts_with(';')
246 || line.starts_with('#')
247 || line.starts_with("//")
248 {
249 continue;
250 }
251
252 let mut line = line.to_string();
253
254 Self::process_escape_char(&mut line);
256
257 if line.starts_with('[') && line.ends_with(']') && line.len() >= 3 {
259 let this_section = line[1..line.len() - 1].to_string();
260 in_excluded_section = self.should_ignore_section(&this_section);
261 in_direct_save_section = self.should_direct_save(&this_section);
262
263 if !cur_section.is_empty() && (self.keep_empty_section || !item_group.is_empty()) {
265 if self.content.contains_key(&cur_section) {
266 if self.allow_dup_section_titles
268 || self.content.get(&cur_section).unwrap().is_empty()
269 {
270 if let Some(existing_items) = self.content.get_mut(&cur_section) {
272 existing_items.extend(item_group.drain(..));
273 }
274 } else {
275 self.last_error = IniReaderError::Duplicate;
276 return Err(IniReaderError::Duplicate);
277 }
278 } else if !in_isolated_section || self.isolated_items_section != this_section {
279 if !item_group.is_empty() {
280 read_sections.push(cur_section.clone());
281 }
282
283 if !self.section_order.contains(&cur_section) {
284 self.section_order.push(cur_section.clone());
285 }
286
287 self.content.insert(cur_section.clone(), item_group);
288 }
289 }
290
291 in_isolated_section = false;
292 cur_section = this_section;
293 item_group = Vec::new();
294 }
295 else if !in_excluded_section && !cur_section.is_empty() {
297 let pos_equal = line.find('=');
298
299 if (self.store_any_line && pos_equal.is_none()) || in_direct_save_section {
301 item_group.push(("{NONAME}".to_string(), line));
302 }
303 else if let Some(pos) = pos_equal {
305 let item_name = line[0..pos].trim().to_string();
306 let item_value = if pos + 1 < line.len() {
307 line[pos + 1..].trim().to_string()
308 } else {
309 String::new()
310 };
311
312 item_group.push((item_name, item_value));
313 }
314 } else if cur_section.is_empty() {
315 self.last_error = IniReaderError::OutOfBound;
317 return Err(IniReaderError::OutOfBound);
318 }
319
320 if !self.include_sections.is_empty()
322 && read_sections
323 .iter()
324 .all(|s| self.include_sections.contains(s))
325 {
326 break;
327 }
328 }
329
330 if !cur_section.is_empty() && (self.keep_empty_section || !item_group.is_empty()) {
332 if self.content.contains_key(&cur_section) {
333 if self.allow_dup_section_titles || in_isolated_section {
334 if let Some(existing_items) = self.content.get_mut(&cur_section) {
336 existing_items.extend(item_group.drain(..));
337 }
338 } else if !self.content.get(&cur_section).unwrap().is_empty() {
339 self.last_error = IniReaderError::Duplicate;
340 return Err(IniReaderError::Duplicate);
341 }
342 } else if !in_isolated_section || self.isolated_items_section != cur_section {
343 if !item_group.is_empty() {
344 read_sections.push(cur_section.clone());
345 }
346
347 if !self.section_order.contains(&cur_section) {
348 self.section_order.push(cur_section.clone());
349 }
350
351 self.content.insert(cur_section, item_group);
352 }
353 }
354
355 self.parsed = true;
356 self.last_error = IniReaderError::None;
357 Ok(())
358 }
359
360 pub fn parse_file<P: AsRef<Path>>(&mut self, path: P) -> Result<(), IniReaderError> {
362 if !path.as_ref().exists() {
364 self.last_error = IniReaderError::NotExist;
365 return Err(IniReaderError::NotExist);
366 }
367
368 let mut file = File::open(path)?;
370 let mut content = String::new();
371 file.read_to_string(&mut content)?;
372
373 self.parse(&content)
375 }
376
377 pub fn section_exist(&self, section: &str) -> bool {
379 self.content.contains_key(section)
380 }
381
382 pub fn section_count(&self) -> usize {
384 self.content.len()
385 }
386
387 pub fn get_section_names(&self) -> &[String] {
389 &self.section_order
390 }
391
392 pub fn set_current_section(&mut self, section: &str) {
394 self.current_section = section.to_string();
395 }
396
397 pub fn enter_section(&mut self, section: &str) -> Result<(), IniReaderError> {
399 if !self.section_exist(section) {
400 self.last_error = IniReaderError::NotExist;
401 return Err(IniReaderError::NotExist);
402 }
403
404 self.current_section = section.to_string();
405 self.last_error = IniReaderError::None;
406 Ok(())
407 }
408
409 pub fn item_exist(&self, section: &str, item_name: &str) -> bool {
411 if !self.section_exist(section) {
412 return false;
413 }
414
415 self.content
416 .get(section)
417 .map(|items| items.iter().any(|(key, _)| key == item_name))
418 .unwrap_or(false)
419 }
420
421 pub fn item_exist_current(&self, item_name: &str) -> bool {
423 if self.current_section.is_empty() {
424 return false;
425 }
426
427 self.item_exist(&self.current_section, item_name)
428 }
429
430 pub fn item_prefix_exists(&self, section: &str, prefix: &str) -> bool {
432 if !self.section_exist(section) {
433 return false;
434 }
435
436 if let Some(items) = self.content.get(section) {
437 return items.iter().any(|(key, _)| key.starts_with(prefix));
438 }
439
440 false
441 }
442
443 pub fn item_prefix_exist(&self, prefix: &str) -> bool {
445 if self.current_section.is_empty() {
446 return false;
447 }
448
449 self.item_prefix_exists(&self.current_section, prefix)
450 }
451
452 pub fn get_items(&self, section: &str) -> Result<Vec<(String, String)>, IniReaderError> {
454 if !self.parsed {
455 return Err(IniReaderError::NotParsed);
456 }
457
458 if !self.section_exist(section) {
459 return Err(IniReaderError::NotExist);
460 }
461
462 Ok(self.content.get(section).cloned().unwrap_or_default())
463 }
464
465 pub fn get_all(&self, section: &str, item_name: &str) -> Result<Vec<String>, IniReaderError> {
467 if !self.parsed {
468 return Err(IniReaderError::NotParsed);
469 }
470
471 if !self.section_exist(section) {
472 return Err(IniReaderError::NotExist);
473 }
474
475 let mut results = Vec::new();
476
477 if let Some(items) = self.content.get(section) {
478 for (key, value) in items {
479 if key.starts_with(item_name) {
480 results.push(value.clone());
481 }
482 }
483 }
484
485 Ok(results)
486 }
487
488 pub fn get_all_current(&self, item_name: &str) -> Result<Vec<String>, IniReaderError> {
490 if self.current_section.is_empty() {
491 return Err(IniReaderError::NotExist);
492 }
493
494 self.get_all(&self.current_section, item_name)
495 }
496
497 pub fn get(&self, section: &str, item_name: &str) -> String {
499 if !self.parsed || !self.section_exist(section) {
500 return String::new();
501 }
502
503 self.content
504 .get(section)
505 .and_then(|items| items.iter().find(|(key, _)| key == item_name))
506 .map(|(_, value)| value.clone())
507 .unwrap_or_default()
508 }
509
510 pub fn get_current(&self, item_name: &str) -> String {
512 if self.current_section.is_empty() {
513 return String::new();
514 }
515
516 self.get(&self.current_section, item_name)
517 }
518
519 pub fn get_bool(&self, section: &str, item_name: &str) -> bool {
521 self.get(section, item_name) == "true"
522 }
523
524 pub fn get_bool_current(&self, item_name: &str) -> bool {
526 self.get_current(item_name) == "true"
527 }
528
529 pub fn get_int(&self, section: &str, item_name: &str) -> i32 {
531 self.get(section, item_name).parse::<i32>().unwrap_or(0)
532 }
533
534 pub fn get_int_current(&self, item_name: &str) -> i32 {
536 self.get_current(item_name).parse::<i32>().unwrap_or(0)
537 }
538
539 pub fn set(
541 &mut self,
542 section: &str,
543 item_name: &str,
544 item_val: &str,
545 ) -> Result<(), IniReaderError> {
546 if section.is_empty() {
547 self.last_error = IniReaderError::NotExist;
548 return Err(IniReaderError::NotExist);
549 }
550
551 if !self.parsed {
552 self.parsed = true;
553 }
554
555 let real_section = if section == "{NONAME}" {
557 if self.current_section.is_empty() {
558 self.last_error = IniReaderError::NotExist;
559 return Err(IniReaderError::NotExist);
560 }
561 &self.current_section
562 } else {
563 section
564 };
565
566 if !self.section_exist(real_section) {
568 self.section_order.push(real_section.to_string());
569 self.content.insert(real_section.to_string(), Vec::new());
570 }
571
572 if let Some(section_vec) = self.content.get_mut(real_section) {
574 section_vec.push((item_name.to_string(), item_val.to_string()));
575 }
576
577 self.last_error = IniReaderError::None;
578 Ok(())
579 }
580
581 pub fn set_current(&mut self, item_name: &str, item_val: &str) -> Result<(), IniReaderError> {
583 if self.current_section.is_empty() {
584 self.last_error = IniReaderError::NotExist;
585 return Err(IniReaderError::NotExist);
586 }
587
588 if item_name == "{NONAME}" {
590 return self.set_current_with_noname(item_val);
591 }
592
593 self.set(&self.current_section.clone(), item_name, item_val)
594 }
595
596 pub fn set_bool(
598 &mut self,
599 section: &str,
600 item_name: &str,
601 item_val: bool,
602 ) -> Result<(), IniReaderError> {
603 self.set(section, item_name, if item_val { "true" } else { "false" })
604 }
605
606 pub fn set_bool_current(
608 &mut self,
609 item_name: &str,
610 item_val: bool,
611 ) -> Result<(), IniReaderError> {
612 if self.current_section.is_empty() {
613 self.last_error = IniReaderError::NotExist;
614 return Err(IniReaderError::NotExist);
615 }
616
617 let value = if item_val { "true" } else { "false" };
618 self.set(&self.current_section.clone(), item_name, value)
619 }
620
621 pub fn set_int(
623 &mut self,
624 section: &str,
625 item_name: &str,
626 item_val: i32,
627 ) -> Result<(), IniReaderError> {
628 self.set(section, item_name, &item_val.to_string())
629 }
630
631 pub fn set_int_current(
633 &mut self,
634 item_name: &str,
635 item_val: i32,
636 ) -> Result<(), IniReaderError> {
637 if self.current_section.is_empty() {
638 self.last_error = IniReaderError::NotExist;
639 return Err(IniReaderError::NotExist);
640 }
641
642 self.set(
643 &self.current_section.clone(),
644 item_name,
645 &item_val.to_string(),
646 )
647 }
648
649 pub fn to_string(&self) -> String {
651 if !self.parsed {
652 return String::new();
653 }
654
655 let mut result = String::new();
656
657 for section_name in &self.section_order {
658 result.push_str(&format!("[{}]\n", section_name));
660
661 if let Some(section) = self.content.get(section_name) {
662 if section.is_empty() {
663 result.push('\n');
664 continue;
665 }
666
667 for (key, value) in section {
669 let mut value = value.clone();
670 Self::process_escape_char_reverse(&mut value);
671
672 if key != "{NONAME}" {
673 result.push_str(&format!("{}={}\n", key, value));
674 } else {
675 result.push_str(&format!("{}\n", value));
676 }
677 }
678
679 result.push('\n');
681 }
682 }
683
684 result
685 }
686
687 pub fn to_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
689 if !self.parsed {
690 return Err(io::Error::new(io::ErrorKind::InvalidData, "INI not parsed"));
691 }
692
693 let content = self.to_string();
694 std::fs::write(path, content)?;
695 Ok(())
696 }
697
698 pub fn set_current_with_noname(&mut self, item_val: &str) -> Result<(), IniReaderError> {
701 if self.current_section.is_empty() {
702 self.last_error = IniReaderError::NotExist;
703 return Err(IniReaderError::NotExist);
704 }
705
706 self.set(&self.current_section.clone(), "{NONAME}", item_val)
707 }
708}