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