1use std::{
46 borrow::Cow,
47 char,
48 error,
49 fmt::{self, Display},
50 fs::{File, OpenOptions},
51 io::{self, Read, Seek, SeekFrom, Write},
52 ops::{Index, IndexMut},
53 path::Path,
54 str::Chars,
55};
56
57use cfg_if::cfg_if;
58use ordered_multimap::{
59 list_ordered_multimap::{Entry, IntoIter, Iter, IterMut, OccupiedEntry, VacantEntry},
60 ListOrderedMultimap,
61};
62#[cfg(feature = "case-insensitive")]
63use unicase::UniCase;
64
65#[derive(Debug, PartialEq, Copy, Clone)]
67pub enum EscapePolicy {
68 Nothing,
70 Basics,
74 BasicsUnicode,
78 BasicsUnicodeExtended,
81 Reserved,
84 ReservedUnicode,
87 ReservedUnicodeExtended,
89 Everything,
91}
92
93impl EscapePolicy {
94 fn escape_basics(self) -> bool {
95 self != EscapePolicy::Nothing
96 }
97
98 fn escape_reserved(self) -> bool {
99 matches!(
100 self,
101 EscapePolicy::Reserved
102 | EscapePolicy::ReservedUnicode
103 | EscapePolicy::ReservedUnicodeExtended
104 | EscapePolicy::Everything
105 )
106 }
107
108 fn escape_unicode(self) -> bool {
109 matches!(
110 self,
111 EscapePolicy::BasicsUnicode
112 | EscapePolicy::BasicsUnicodeExtended
113 | EscapePolicy::ReservedUnicode
114 | EscapePolicy::ReservedUnicodeExtended
115 | EscapePolicy::Everything
116 )
117 }
118
119 fn escape_unicode_extended(self) -> bool {
120 matches!(
121 self,
122 EscapePolicy::BasicsUnicodeExtended | EscapePolicy::ReservedUnicodeExtended | EscapePolicy::Everything
123 )
124 }
125
126 pub fn should_escape(self, c: char) -> bool {
129 match c {
130 '\\' | '\x00'..='\x1f' | '\x7f' => self.escape_basics(),
133 ';' | '#' | '=' | ':' => self.escape_reserved(),
134 '\u{0080}'..='\u{FFFF}' => self.escape_unicode(),
135 '\u{10000}'..='\u{10FFFF}' => self.escape_unicode_extended(),
136 _ => false,
137 }
138 }
139}
140
141fn escape_str(s: &str, policy: EscapePolicy) -> String {
158 let mut escaped: String = String::with_capacity(s.len());
159 for c in s.chars() {
160 if !policy.should_escape(c) {
163 escaped.push(c);
164 continue;
165 }
166
167 match c {
168 '\\' => escaped.push_str("\\\\"),
169 '\0' => escaped.push_str("\\0"),
170 '\x01'..='\x06' | '\x0e'..='\x1f' | '\x7f'..='\u{00ff}' => {
171 escaped.push_str(&format!("\\x{:04x}", c as isize)[..])
172 }
173 '\x07' => escaped.push_str("\\a"),
174 '\x08' => escaped.push_str("\\b"),
175 '\x0c' => escaped.push_str("\\f"),
176 '\x0b' => escaped.push_str("\\v"),
177 '\n' => escaped.push_str("\\n"),
178 '\t' => escaped.push_str("\\t"),
179 '\r' => escaped.push_str("\\r"),
180 '\u{0080}'..='\u{FFFF}' => escaped.push_str(&format!("\\x{:04x}", c as isize)[..]),
181 '\u{10000}'..='\u{FFFFF}' => escaped.push_str(&format!("\\x{:05x}", c as isize)[..]),
183 '\u{100000}'..='\u{10FFFF}' => escaped.push_str(&format!("\\x{:06x}", c as isize)[..]),
184 _ => {
185 escaped.push('\\');
186 escaped.push(c);
187 }
188 }
189 }
190 escaped
191}
192
193pub struct ParseOption {
195 pub enabled_quote: bool,
207
208 pub enabled_escape: bool,
217
218 pub enabled_indented_mutiline_value: bool,
226
227 pub enabled_preserve_key_leading_whitespace: bool,
237}
238
239impl Default for ParseOption {
240 fn default() -> ParseOption {
241 ParseOption {
242 enabled_quote: true,
243 enabled_escape: true,
244 enabled_indented_mutiline_value: false,
245 enabled_preserve_key_leading_whitespace: false,
246 }
247 }
248}
249
250#[derive(Debug, Copy, Clone, Eq, PartialEq)]
252pub enum LineSeparator {
253 SystemDefault,
258
259 CR,
261
262 CRLF,
264}
265
266#[cfg(not(windows))]
267static DEFAULT_LINE_SEPARATOR: &str = "\n";
268
269#[cfg(windows)]
270static DEFAULT_LINE_SEPARATOR: &str = "\r\n";
271
272static DEFAULT_KV_SEPARATOR: &str = "=";
273
274impl fmt::Display for LineSeparator {
275 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
276 f.write_str(self.as_str())
277 }
278}
279
280impl LineSeparator {
281 pub fn as_str(self) -> &'static str {
283 match self {
284 LineSeparator::SystemDefault => DEFAULT_LINE_SEPARATOR,
285 LineSeparator::CR => "\n",
286 LineSeparator::CRLF => "\r\n",
287 }
288 }
289}
290
291#[derive(Debug, Clone)]
293pub struct WriteOption {
294 pub escape_policy: EscapePolicy,
296
297 pub line_separator: LineSeparator,
299
300 pub kv_separator: &'static str,
302}
303
304impl Default for WriteOption {
305 fn default() -> WriteOption {
306 WriteOption {
307 escape_policy: EscapePolicy::Basics,
308 line_separator: LineSeparator::SystemDefault,
309 kv_separator: DEFAULT_KV_SEPARATOR,
310 }
311 }
312}
313
314cfg_if! {
315 if #[cfg(feature = "case-insensitive")] {
316 pub type SectionKey = Option<UniCase<String>>;
318 pub type PropertyKey = UniCase<String>;
320
321 macro_rules! property_get_key {
322 ($s:expr) => {
323 &UniCase::from($s)
324 };
325 }
326
327 macro_rules! property_insert_key {
328 ($s:expr) => {
329 UniCase::from($s)
330 };
331 }
332
333 macro_rules! section_key {
334 ($s:expr) => {
335 $s.map(|s| UniCase::from(s.into()))
336 };
337 }
338
339 } else {
340 pub type SectionKey = Option<String>;
342 pub type PropertyKey = String;
344
345 macro_rules! property_get_key {
346 ($s:expr) => {
347 $s
348 };
349 }
350
351 macro_rules! property_insert_key {
352 ($s:expr) => {
353 $s
354 };
355 }
356
357 macro_rules! section_key {
358 ($s:expr) => {
359 $s.map(Into::into)
360 };
361 }
362 }
363}
364
365pub struct SectionSetter<'a> {
367 ini: &'a mut Ini,
368 section_name: Option<String>,
369}
370
371impl<'a> SectionSetter<'a> {
372 fn new(ini: &'a mut Ini, section_name: Option<String>) -> SectionSetter<'a> {
373 SectionSetter { ini, section_name }
374 }
375
376 pub fn set<'b, K, V>(&'b mut self, key: K, value: V) -> &'b mut SectionSetter<'a>
378 where
379 K: Into<String>,
380 V: Into<String>,
381 'a: 'b,
382 {
383 self.ini
384 .entry(self.section_name.clone())
385 .or_insert_with(Default::default)
386 .insert(key, value);
387
388 self
389 }
390
391 pub fn add<'b, K, V>(&'b mut self, key: K, value: V) -> &'b mut SectionSetter<'a>
393 where
394 K: Into<String>,
395 V: Into<String>,
396 'a: 'b,
397 {
398 self.ini
399 .entry(self.section_name.clone())
400 .or_insert_with(Default::default)
401 .append(key, value);
402
403 self
404 }
405
406 pub fn delete<'b, K>(&'b mut self, key: &K) -> &'b mut SectionSetter<'a>
408 where
409 K: AsRef<str>,
410 'a: 'b,
411 {
412 for prop in self.ini.section_all_mut(self.section_name.as_ref()) {
413 prop.remove(key);
414 }
415
416 self
417 }
418
419 pub fn get<K: AsRef<str>>(&'a self, key: K) -> Option<&'a str> {
421 self.ini
422 .section(self.section_name.as_ref())
423 .and_then(|prop| prop.get(key))
424 .map(AsRef::as_ref)
425 }
426}
427
428#[derive(Clone, Default, Debug, PartialEq)]
430pub struct Properties {
431 data: ListOrderedMultimap<PropertyKey, String>,
432}
433
434impl Properties {
435 pub fn new() -> Properties {
437 Default::default()
438 }
439
440 pub fn len(&self) -> usize {
442 self.data.keys_len()
443 }
444
445 pub fn is_empty(&self) -> bool {
447 self.data.is_empty()
448 }
449
450 pub fn iter(&self) -> PropertyIter<'_> {
452 PropertyIter {
453 inner: self.data.iter(),
454 }
455 }
456
457 pub fn iter_mut(&mut self) -> PropertyIterMut<'_> {
459 PropertyIterMut {
460 inner: self.data.iter_mut(),
461 }
462 }
463
464 pub fn contains_key<S: AsRef<str>>(&self, s: S) -> bool {
466 self.data.contains_key(property_get_key!(s.as_ref()))
467 }
468
469 pub fn insert<K, V>(&mut self, k: K, v: V)
471 where
472 K: Into<String>,
473 V: Into<String>,
474 {
475 self.data.insert(property_insert_key!(k.into()), v.into());
476 }
477
478 pub fn append<K, V>(&mut self, k: K, v: V)
480 where
481 K: Into<String>,
482 V: Into<String>,
483 {
484 self.data.append(property_insert_key!(k.into()), v.into());
485 }
486
487 pub fn get<S: AsRef<str>>(&self, s: S) -> Option<&str> {
489 self.data.get(property_get_key!(s.as_ref())).map(|v| v.as_str())
490 }
491
492 pub fn get_all<S: AsRef<str>>(&self, s: S) -> impl DoubleEndedIterator<Item = &str> {
494 self.data.get_all(property_get_key!(s.as_ref())).map(|v| v.as_str())
495 }
496
497 pub fn remove<S: AsRef<str>>(&mut self, s: S) -> Option<String> {
499 self.data.remove(property_get_key!(s.as_ref()))
500 }
501
502 pub fn remove_all<S: AsRef<str>>(&mut self, s: S) -> impl DoubleEndedIterator<Item = String> + '_ {
504 self.data.remove_all(property_get_key!(s.as_ref()))
505 }
506
507 fn get_mut<S: AsRef<str>>(&mut self, s: S) -> Option<&mut str> {
508 self.data.get_mut(property_get_key!(s.as_ref())).map(|v| v.as_mut_str())
509 }
510}
511
512impl<S: AsRef<str>> Index<S> for Properties {
513 type Output = str;
514
515 fn index(&self, index: S) -> &str {
516 let s = index.as_ref();
517 match self.get(s) {
518 Some(p) => p,
519 None => panic!("Key `{}` does not exist", s),
520 }
521 }
522}
523
524pub struct PropertyIter<'a> {
525 inner: Iter<'a, PropertyKey, String>,
526}
527
528impl<'a> Iterator for PropertyIter<'a> {
529 type Item = (&'a str, &'a str);
530
531 fn next(&mut self) -> Option<Self::Item> {
532 self.inner.next().map(|(k, v)| (k.as_ref(), v.as_ref()))
533 }
534
535 fn size_hint(&self) -> (usize, Option<usize>) {
536 self.inner.size_hint()
537 }
538}
539
540impl DoubleEndedIterator for PropertyIter<'_> {
541 fn next_back(&mut self) -> Option<Self::Item> {
542 self.inner.next_back().map(|(k, v)| (k.as_ref(), v.as_ref()))
543 }
544}
545
546pub struct PropertyIterMut<'a> {
548 inner: IterMut<'a, PropertyKey, String>,
549}
550
551impl<'a> Iterator for PropertyIterMut<'a> {
552 type Item = (&'a str, &'a mut String);
553
554 fn next(&mut self) -> Option<Self::Item> {
555 self.inner.next().map(|(k, v)| (k.as_ref(), v))
556 }
557
558 fn size_hint(&self) -> (usize, Option<usize>) {
559 self.inner.size_hint()
560 }
561}
562
563impl DoubleEndedIterator for PropertyIterMut<'_> {
564 fn next_back(&mut self) -> Option<Self::Item> {
565 self.inner.next_back().map(|(k, v)| (k.as_ref(), v))
566 }
567}
568
569pub struct PropertiesIntoIter {
570 inner: IntoIter<PropertyKey, String>,
571}
572
573impl Iterator for PropertiesIntoIter {
574 type Item = (String, String);
575
576 #[cfg_attr(not(feature = "case-insensitive"), allow(clippy::useless_conversion))]
577 fn next(&mut self) -> Option<Self::Item> {
578 self.inner.next().map(|(k, v)| (k.into(), v))
579 }
580
581 fn size_hint(&self) -> (usize, Option<usize>) {
582 self.inner.size_hint()
583 }
584}
585
586impl DoubleEndedIterator for PropertiesIntoIter {
587 #[cfg_attr(not(feature = "case-insensitive"), allow(clippy::useless_conversion))]
588 fn next_back(&mut self) -> Option<Self::Item> {
589 self.inner.next_back().map(|(k, v)| (k.into(), v))
590 }
591}
592
593impl<'a> IntoIterator for &'a Properties {
594 type IntoIter = PropertyIter<'a>;
595 type Item = (&'a str, &'a str);
596
597 fn into_iter(self) -> Self::IntoIter {
598 self.iter()
599 }
600}
601
602impl<'a> IntoIterator for &'a mut Properties {
603 type IntoIter = PropertyIterMut<'a>;
604 type Item = (&'a str, &'a mut String);
605
606 fn into_iter(self) -> Self::IntoIter {
607 self.iter_mut()
608 }
609}
610
611impl IntoIterator for Properties {
612 type IntoIter = PropertiesIntoIter;
613 type Item = (String, String);
614
615 fn into_iter(self) -> Self::IntoIter {
616 PropertiesIntoIter {
617 inner: self.data.into_iter(),
618 }
619 }
620}
621
622pub struct SectionVacantEntry<'a> {
624 inner: VacantEntry<'a, SectionKey, Properties>,
625}
626
627impl<'a> SectionVacantEntry<'a> {
628 pub fn insert(self, value: Properties) -> &'a mut Properties {
630 self.inner.insert(value)
631 }
632}
633
634pub struct SectionOccupiedEntry<'a> {
636 inner: OccupiedEntry<'a, SectionKey, Properties>,
637}
638
639impl<'a> SectionOccupiedEntry<'a> {
640 pub fn into_mut(self) -> &'a mut Properties {
642 self.inner.into_mut()
643 }
644
645 pub fn append(&mut self, prop: Properties) {
647 self.inner.append(prop);
648 }
649
650 fn last_mut(&'a mut self) -> &'a mut Properties {
651 self.inner
652 .iter_mut()
653 .next_back()
654 .expect("occupied section shouldn't have 0 property")
655 }
656}
657
658pub enum SectionEntry<'a> {
660 Vacant(SectionVacantEntry<'a>),
661 Occupied(SectionOccupiedEntry<'a>),
662}
663
664impl<'a> SectionEntry<'a> {
665 pub fn or_insert(self, properties: Properties) -> &'a mut Properties {
667 match self {
668 SectionEntry::Occupied(e) => e.into_mut(),
669 SectionEntry::Vacant(e) => e.insert(properties),
670 }
671 }
672
673 pub fn or_insert_with<F: FnOnce() -> Properties>(self, default: F) -> &'a mut Properties {
675 match self {
676 SectionEntry::Occupied(e) => e.into_mut(),
677 SectionEntry::Vacant(e) => e.insert(default()),
678 }
679 }
680}
681
682impl<'a> From<Entry<'a, SectionKey, Properties>> for SectionEntry<'a> {
683 fn from(e: Entry<'a, SectionKey, Properties>) -> SectionEntry<'a> {
684 match e {
685 Entry::Occupied(inner) => SectionEntry::Occupied(SectionOccupiedEntry { inner }),
686 Entry::Vacant(inner) => SectionEntry::Vacant(SectionVacantEntry { inner }),
687 }
688 }
689}
690
691#[derive(Debug, Clone)]
693pub struct Ini {
694 sections: ListOrderedMultimap<SectionKey, Properties>,
695}
696
697impl Ini {
698 pub fn new() -> Ini {
700 Default::default()
701 }
702
703 pub fn with_section<S>(&mut self, section: Option<S>) -> SectionSetter<'_>
705 where
706 S: Into<String>,
707 {
708 SectionSetter::new(self, section.map(Into::into))
709 }
710
711 pub fn with_general_section(&mut self) -> SectionSetter<'_> {
713 self.with_section(None::<String>)
714 }
715
716 pub fn general_section(&self) -> &Properties {
718 self.section(None::<String>)
719 .expect("There is no general section in this Ini")
720 }
721
722 pub fn general_section_mut(&mut self) -> &mut Properties {
724 self.section_mut(None::<String>)
725 .expect("There is no general section in this Ini")
726 }
727
728 pub fn section<S>(&self, name: Option<S>) -> Option<&Properties>
730 where
731 S: Into<String>,
732 {
733 self.sections.get(§ion_key!(name))
734 }
735
736 pub fn section_mut<S>(&mut self, name: Option<S>) -> Option<&mut Properties>
738 where
739 S: Into<String>,
740 {
741 self.sections.get_mut(§ion_key!(name))
742 }
743
744 pub fn section_all<S>(&self, name: Option<S>) -> impl DoubleEndedIterator<Item = &Properties>
746 where
747 S: Into<String>,
748 {
749 self.sections.get_all(§ion_key!(name))
750 }
751
752 pub fn section_all_mut<S>(&mut self, name: Option<S>) -> impl DoubleEndedIterator<Item = &mut Properties>
754 where
755 S: Into<String>,
756 {
757 self.sections.get_all_mut(§ion_key!(name))
758 }
759
760 #[cfg(not(feature = "case-insensitive"))]
762 pub fn entry(&mut self, name: Option<String>) -> SectionEntry<'_> {
763 SectionEntry::from(self.sections.entry(name))
764 }
765
766 #[cfg(feature = "case-insensitive")]
768 pub fn entry(&mut self, name: Option<String>) -> SectionEntry<'_> {
769 SectionEntry::from(self.sections.entry(name.map(UniCase::from)))
770 }
771
772 pub fn clear(&mut self) {
774 self.sections.clear()
775 }
776
777 pub fn sections(&self) -> impl DoubleEndedIterator<Item = Option<&str>> {
779 self.sections.keys().map(|s| s.as_ref().map(AsRef::as_ref))
780 }
781
782 pub fn set_to<S>(&mut self, section: Option<S>, key: String, value: String)
784 where
785 S: Into<String>,
786 {
787 self.with_section(section).set(key, value);
788 }
789
790 pub fn get_from<'a, S>(&'a self, section: Option<S>, key: &str) -> Option<&'a str>
801 where
802 S: Into<String>,
803 {
804 self.sections.get(§ion_key!(section)).and_then(|prop| prop.get(key))
805 }
806
807 pub fn get_from_or<'a, S>(&'a self, section: Option<S>, key: &str, default: &'a str) -> &'a str
818 where
819 S: Into<String>,
820 {
821 self.get_from(section, key).unwrap_or(default)
822 }
823
824 pub fn get_from_mut<'a, S>(&'a mut self, section: Option<S>, key: &str) -> Option<&'a mut str>
826 where
827 S: Into<String>,
828 {
829 self.sections
830 .get_mut(§ion_key!(section))
831 .and_then(|prop| prop.get_mut(key))
832 }
833
834 pub fn delete<S>(&mut self, section: Option<S>) -> Option<Properties>
836 where
837 S: Into<String>,
838 {
839 let key = section_key!(section);
840 self.sections.remove(&key)
841 }
842
843 pub fn delete_from<S>(&mut self, section: Option<S>, key: &str) -> Option<String>
845 where
846 S: Into<String>,
847 {
848 self.section_mut(section).and_then(|prop| prop.remove(key))
849 }
850
851 pub fn len(&self) -> usize {
853 self.sections.keys_len()
854 }
855
856 pub fn is_empty(&self) -> bool {
858 self.sections.is_empty()
859 }
860}
861
862impl Default for Ini {
863 fn default() -> Self {
866 let mut result = Ini {
867 sections: Default::default(),
868 };
869
870 result.sections.insert(None, Default::default());
871
872 result
873 }
874}
875
876impl<S: Into<String>> Index<Option<S>> for Ini {
877 type Output = Properties;
878
879 fn index(&self, index: Option<S>) -> &Properties {
880 match self.section(index) {
881 Some(p) => p,
882 None => panic!("Section does not exist"),
883 }
884 }
885}
886
887impl<S: Into<String>> IndexMut<Option<S>> for Ini {
888 fn index_mut(&mut self, index: Option<S>) -> &mut Properties {
889 match self.section_mut(index) {
890 Some(p) => p,
891 None => panic!("Section does not exist"),
892 }
893 }
894}
895
896impl<'q> Index<&'q str> for Ini {
897 type Output = Properties;
898
899 fn index<'a>(&'a self, index: &'q str) -> &'a Properties {
900 match self.section(Some(index)) {
901 Some(p) => p,
902 None => panic!("Section `{}` does not exist", index),
903 }
904 }
905}
906
907impl<'q> IndexMut<&'q str> for Ini {
908 fn index_mut<'a>(&'a mut self, index: &'q str) -> &'a mut Properties {
909 match self.section_mut(Some(index)) {
910 Some(p) => p,
911 None => panic!("Section `{}` does not exist", index),
912 }
913 }
914}
915
916impl Ini {
917 pub fn write_to_file<P: AsRef<Path>>(&self, filename: P) -> io::Result<()> {
919 self.write_to_file_policy(filename, EscapePolicy::Basics)
920 }
921
922 pub fn write_to_file_policy<P: AsRef<Path>>(&self, filename: P, policy: EscapePolicy) -> io::Result<()> {
924 let mut file = OpenOptions::new()
925 .write(true)
926 .truncate(true)
927 .create(true)
928 .open(filename.as_ref())?;
929 self.write_to_policy(&mut file, policy)
930 }
931
932 pub fn write_to_file_opt<P: AsRef<Path>>(&self, filename: P, opt: WriteOption) -> io::Result<()> {
934 let mut file = OpenOptions::new()
935 .write(true)
936 .truncate(true)
937 .create(true)
938 .open(filename.as_ref())?;
939 self.write_to_opt(&mut file, opt)
940 }
941
942 pub fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
944 self.write_to_opt(writer, Default::default())
945 }
946
947 pub fn write_to_policy<W: Write>(&self, writer: &mut W, policy: EscapePolicy) -> io::Result<()> {
949 self.write_to_opt(
950 writer,
951 WriteOption {
952 escape_policy: policy,
953 ..Default::default()
954 },
955 )
956 }
957
958 pub fn write_to_opt<W: Write>(&self, writer: &mut W, opt: WriteOption) -> io::Result<()> {
960 let mut firstline = true;
961
962 for (section, props) in &self.sections {
963 if !props.data.is_empty() {
964 if firstline {
965 firstline = false;
966 } else {
967 writer.write_all(opt.line_separator.as_str().as_bytes())?;
969 }
970 }
971
972 if let Some(ref section) = *section {
973 write!(
974 writer,
975 "[{}]{}",
976 escape_str(§ion[..], opt.escape_policy),
977 opt.line_separator
978 )?;
979 }
980 for (k, v) in props.iter() {
981 let k_str = escape_str(k, opt.escape_policy);
982 let v_str = escape_str(v, opt.escape_policy);
983 write!(writer, "{}{}{}{}", k_str, opt.kv_separator, v_str, opt.line_separator)?;
984 }
985 }
986 Ok(())
987 }
988}
989
990impl Ini {
991 pub fn load_from_str(buf: &str) -> Result<Ini, ParseError> {
993 Ini::load_from_str_opt(buf, ParseOption::default())
994 }
995
996 pub fn load_from_str_noescape(buf: &str) -> Result<Ini, ParseError> {
998 Ini::load_from_str_opt(
999 buf,
1000 ParseOption {
1001 enabled_escape: false,
1002 ..ParseOption::default()
1003 },
1004 )
1005 }
1006
1007 pub fn load_from_str_opt(buf: &str, opt: ParseOption) -> Result<Ini, ParseError> {
1009 let mut parser = Parser::new(buf.chars(), opt);
1010 parser.parse()
1011 }
1012
1013 pub fn read_from<R: Read>(reader: &mut R) -> Result<Ini, Error> {
1015 Ini::read_from_opt(reader, ParseOption::default())
1016 }
1017
1018 pub fn read_from_noescape<R: Read>(reader: &mut R) -> Result<Ini, Error> {
1020 Ini::read_from_opt(
1021 reader,
1022 ParseOption {
1023 enabled_escape: false,
1024 ..ParseOption::default()
1025 },
1026 )
1027 }
1028
1029 pub fn read_from_opt<R: Read>(reader: &mut R, opt: ParseOption) -> Result<Ini, Error> {
1031 let mut s = String::new();
1032 reader.read_to_string(&mut s).map_err(Error::Io)?;
1033 let mut parser = Parser::new(s.chars(), opt);
1034 match parser.parse() {
1035 Err(e) => Err(Error::Parse(e)),
1036 Ok(success) => Ok(success),
1037 }
1038 }
1039
1040 pub fn load_from_file<P: AsRef<Path>>(filename: P) -> Result<Ini, Error> {
1042 Ini::load_from_file_opt(filename, ParseOption::default())
1043 }
1044
1045 pub fn load_from_file_noescape<P: AsRef<Path>>(filename: P) -> Result<Ini, Error> {
1047 Ini::load_from_file_opt(
1048 filename,
1049 ParseOption {
1050 enabled_escape: false,
1051 ..ParseOption::default()
1052 },
1053 )
1054 }
1055
1056 pub fn load_from_file_opt<P: AsRef<Path>>(filename: P, opt: ParseOption) -> Result<Ini, Error> {
1058 let mut reader = match File::open(filename.as_ref()) {
1059 Err(e) => {
1060 return Err(Error::Io(e));
1061 }
1062 Ok(r) => r,
1063 };
1064
1065 let mut with_bom = false;
1066
1067 let mut bom = [0u8; 3];
1070 if reader.read_exact(&mut bom).is_ok() && &bom == b"\xEF\xBB\xBF" {
1071 with_bom = true;
1072 }
1073
1074 if !with_bom {
1075 reader.seek(SeekFrom::Start(0))?;
1077 }
1078
1079 Ini::read_from_opt(&mut reader, opt)
1080 }
1081}
1082
1083pub struct SectionIter<'a> {
1085 inner: Iter<'a, SectionKey, Properties>,
1086}
1087
1088impl<'a> Iterator for SectionIter<'a> {
1089 type Item = (Option<&'a str>, &'a Properties);
1090
1091 fn next(&mut self) -> Option<Self::Item> {
1092 self.inner.next().map(|(k, v)| (k.as_ref().map(|s| s.as_str()), v))
1093 }
1094
1095 fn size_hint(&self) -> (usize, Option<usize>) {
1096 self.inner.size_hint()
1097 }
1098}
1099
1100impl DoubleEndedIterator for SectionIter<'_> {
1101 fn next_back(&mut self) -> Option<Self::Item> {
1102 self.inner.next_back().map(|(k, v)| (k.as_ref().map(|s| s.as_str()), v))
1103 }
1104}
1105
1106pub struct SectionIterMut<'a> {
1108 inner: IterMut<'a, SectionKey, Properties>,
1109}
1110
1111impl<'a> Iterator for SectionIterMut<'a> {
1112 type Item = (Option<&'a str>, &'a mut Properties);
1113
1114 fn next(&mut self) -> Option<Self::Item> {
1115 self.inner.next().map(|(k, v)| (k.as_ref().map(|s| s.as_str()), v))
1116 }
1117
1118 fn size_hint(&self) -> (usize, Option<usize>) {
1119 self.inner.size_hint()
1120 }
1121}
1122
1123impl DoubleEndedIterator for SectionIterMut<'_> {
1124 fn next_back(&mut self) -> Option<Self::Item> {
1125 self.inner.next_back().map(|(k, v)| (k.as_ref().map(|s| s.as_str()), v))
1126 }
1127}
1128
1129pub struct SectionIntoIter {
1131 inner: IntoIter<SectionKey, Properties>,
1132}
1133
1134impl Iterator for SectionIntoIter {
1135 type Item = (SectionKey, Properties);
1136
1137 fn next(&mut self) -> Option<Self::Item> {
1138 self.inner.next()
1139 }
1140
1141 fn size_hint(&self) -> (usize, Option<usize>) {
1142 self.inner.size_hint()
1143 }
1144}
1145
1146impl DoubleEndedIterator for SectionIntoIter {
1147 fn next_back(&mut self) -> Option<Self::Item> {
1148 self.inner.next_back()
1149 }
1150}
1151
1152impl<'a> Ini {
1153 pub fn iter(&'a self) -> SectionIter<'a> {
1155 SectionIter {
1156 inner: self.sections.iter(),
1157 }
1158 }
1159
1160 #[deprecated(note = "Use `iter_mut` instead!")]
1162 pub fn mut_iter(&'a mut self) -> SectionIterMut<'a> {
1163 self.iter_mut()
1164 }
1165
1166 pub fn iter_mut(&'a mut self) -> SectionIterMut<'a> {
1168 SectionIterMut {
1169 inner: self.sections.iter_mut(),
1170 }
1171 }
1172}
1173
1174impl<'a> IntoIterator for &'a Ini {
1175 type IntoIter = SectionIter<'a>;
1176 type Item = (Option<&'a str>, &'a Properties);
1177
1178 fn into_iter(self) -> Self::IntoIter {
1179 self.iter()
1180 }
1181}
1182
1183impl<'a> IntoIterator for &'a mut Ini {
1184 type IntoIter = SectionIterMut<'a>;
1185 type Item = (Option<&'a str>, &'a mut Properties);
1186
1187 fn into_iter(self) -> Self::IntoIter {
1188 self.iter_mut()
1189 }
1190}
1191
1192impl IntoIterator for Ini {
1193 type IntoIter = SectionIntoIter;
1194 type Item = (SectionKey, Properties);
1195
1196 fn into_iter(self) -> Self::IntoIter {
1197 SectionIntoIter {
1198 inner: self.sections.into_iter(),
1199 }
1200 }
1201}
1202
1203struct Parser<'a> {
1205 ch: Option<char>,
1206 rdr: Chars<'a>,
1207 line: usize,
1208 col: usize,
1209 opt: ParseOption,
1210}
1211
1212#[derive(Debug)]
1213pub struct ParseError {
1215 pub line: usize,
1216 pub col: usize,
1217 pub msg: Cow<'static, str>,
1218}
1219
1220impl Display for ParseError {
1221 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1222 write!(f, "{}:{} {}", self.line, self.col, self.msg)
1223 }
1224}
1225
1226impl error::Error for ParseError {}
1227
1228#[derive(Debug)]
1230pub enum Error {
1231 Io(io::Error),
1232 Parse(ParseError),
1233}
1234
1235impl Display for Error {
1236 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1237 match *self {
1238 Error::Io(ref err) => err.fmt(f),
1239 Error::Parse(ref err) => err.fmt(f),
1240 }
1241 }
1242}
1243
1244impl error::Error for Error {
1245 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
1246 match *self {
1247 Error::Io(ref err) => err.source(),
1248 Error::Parse(ref err) => err.source(),
1249 }
1250 }
1251}
1252
1253impl From<io::Error> for Error {
1254 fn from(err: io::Error) -> Self {
1255 Error::Io(err)
1256 }
1257}
1258
1259impl<'a> Parser<'a> {
1260 pub fn new(rdr: Chars<'a>, opt: ParseOption) -> Parser<'a> {
1262 let mut p = Parser {
1263 ch: None,
1264 line: 0,
1265 col: 0,
1266 rdr,
1267 opt,
1268 };
1269 p.bump();
1270 p
1271 }
1272
1273 fn bump(&mut self) {
1274 self.ch = self.rdr.next();
1275 match self.ch {
1276 Some('\n') => {
1277 self.line += 1;
1278 self.col = 0;
1279 }
1280 Some(..) => {
1281 self.col += 1;
1282 }
1283 None => {}
1284 }
1285 }
1286
1287 #[cold]
1288 #[inline(never)]
1289 fn error<U, M: Into<Cow<'static, str>>>(&self, msg: M) -> Result<U, ParseError> {
1290 Err(ParseError {
1291 line: self.line + 1,
1292 col: self.col + 1,
1293 msg: msg.into(),
1294 })
1295 }
1296
1297 #[cold]
1298 fn eof_error(&self, expecting: &[Option<char>]) -> Result<char, ParseError> {
1299 self.error(format!("expecting \"{:?}\" but found EOF.", expecting))
1300 }
1301
1302 fn char_or_eof(&self, expecting: &[Option<char>]) -> Result<char, ParseError> {
1303 match self.ch {
1304 Some(ch) => Ok(ch),
1305 None => self.eof_error(expecting),
1306 }
1307 }
1308
1309 fn parse_whitespace_preserve_line_leading(&mut self) {
1329 while let Some(c) = self.ch {
1330 match c {
1331 ' ' | '\t' => {
1333 self.bump();
1334 }
1338 '\n' | '\r' => {
1339 self.bump();
1341 if matches!(self.ch, Some(' ') | Some('\t')) {
1343 break;
1345 }
1346 }
1348 c if c.is_whitespace() => {
1349 self.bump();
1351 }
1352 _ => break,
1353 }
1354 }
1355 }
1356
1357 fn parse_whitespace_except_line_break(&mut self) {
1363 while let Some(c) = self.ch {
1364 if (c == '\n' || c == '\r' || !c.is_whitespace()) && c != '\t' {
1365 break;
1366 }
1367 self.bump();
1368 }
1369 }
1370
1371 pub fn parse(&mut self) -> Result<Ini, ParseError> {
1373 let mut result = Ini::new();
1374 let mut curkey: String = "".into();
1375 let mut cursec: Option<String> = None;
1376
1377 while let Some(cur_ch) = self.ch {
1378 match cur_ch {
1379 ';' | '#' => {
1380 if cfg!(not(feature = "inline-comment")) {
1381 if self.col > 1 {
1385 return self.error("doesn't support inline comment");
1386 }
1387 }
1388
1389 self.parse_comment();
1390 }
1391 '[' => match self.parse_section() {
1392 Ok(mut sec) => {
1393 trim_in_place(&mut sec);
1394 cursec = Some(sec);
1395 match result.entry(cursec.clone()) {
1396 SectionEntry::Vacant(v) => {
1397 v.insert(Default::default());
1398 }
1399 SectionEntry::Occupied(mut o) => {
1400 o.append(Default::default());
1401 }
1402 }
1403 }
1404 Err(e) => return Err(e),
1405 },
1406 '=' | ':' => {
1407 if (curkey[..]).is_empty() {
1408 return self.error("missing key");
1409 }
1410 match self.parse_val() {
1411 Ok(mval) => {
1412 match result.entry(cursec.clone()) {
1413 SectionEntry::Vacant(v) => {
1414 let mut prop = Properties::new();
1416 prop.insert(curkey, mval);
1417 v.insert(prop);
1418 }
1419 SectionEntry::Occupied(mut o) => {
1420 o.last_mut().append(curkey, mval);
1422 }
1423 }
1424 curkey = "".into();
1425 }
1426 Err(e) => return Err(e),
1427 }
1428 }
1429 ' ' | '\t' => {
1430 let mut consumed_whitespace = String::new();
1432 while let Some(c) = self.ch {
1433 if c == ' ' || c == '\t' {
1434 consumed_whitespace.push(c);
1435 self.bump();
1436 } else {
1437 break;
1438 }
1439 }
1440
1441 match self.ch {
1443 Some('[') => {
1444 match self.parse_section() {
1446 Ok(mut sec) => {
1447 trim_in_place(&mut sec);
1448 cursec = Some(sec);
1449 match result.entry(cursec.clone()) {
1450 SectionEntry::Vacant(v) => {
1451 v.insert(Default::default());
1452 }
1453 SectionEntry::Occupied(mut o) => {
1454 o.append(Default::default());
1455 }
1456 }
1457 }
1458 Err(e) => return Err(e),
1459 }
1460 }
1461 Some('\n') | Some('\r') => {
1462 self.bump(); continue;
1465 }
1466 _ => {
1467 match self.parse_str_until(&[Some('='), Some(':')], false) {
1469 Ok(key_part) => {
1470 let mut mkey = if self.opt.enabled_preserve_key_leading_whitespace {
1471 consumed_whitespace + &key_part
1472 } else {
1473 key_part
1474 };
1475
1476 if self.opt.enabled_preserve_key_leading_whitespace {
1478 trim_end_in_place(&mut mkey);
1479 } else {
1480 trim_in_place(&mut mkey);
1481 }
1482 curkey = mkey;
1483 }
1484 Err(_) => {
1485 }
1488 }
1489 }
1490 }
1491 }
1492 '\n' | '\r' => {
1493 self.bump();
1495 }
1496 _ => match self.parse_key() {
1497 Ok(mut mkey) => {
1498 if self.opt.enabled_preserve_key_leading_whitespace {
1501 trim_end_in_place(&mut mkey);
1502 } else {
1503 trim_in_place(&mut mkey);
1504 }
1505 curkey = mkey;
1506 }
1507 Err(e) => return Err(e),
1508 },
1509 }
1510
1511 self.parse_whitespace_preserve_line_leading();
1514 }
1515
1516 Ok(result)
1517 }
1518
1519 fn parse_comment(&mut self) {
1520 while let Some(c) = self.ch {
1521 self.bump();
1522 if c == '\n' {
1523 break;
1524 }
1525 }
1526 }
1527
1528 fn parse_str_until(&mut self, endpoint: &[Option<char>], check_inline_comment: bool) -> Result<String, ParseError> {
1529 let mut result: String = String::new();
1530
1531 let mut in_line_continuation = false;
1532
1533 while !endpoint.contains(&self.ch) {
1534 match self.char_or_eof(endpoint)? {
1535 #[cfg(feature = "inline-comment")]
1536 ch if check_inline_comment && (ch == ' ' || ch == '\t') => {
1537 self.bump();
1538
1539 match self.ch {
1540 Some('#') | Some(';') => {
1541 self.parse_comment();
1543 if in_line_continuation {
1544 result.push(ch);
1545 continue;
1546 } else {
1547 break;
1548 }
1549 }
1550 Some(_) => {
1551 result.push(ch);
1552 continue;
1553 }
1554 None => {
1555 result.push(ch);
1556 }
1557 }
1558 }
1559 #[cfg(feature = "inline-comment")]
1560 ch if check_inline_comment && in_line_continuation && (ch == '#' || ch == ';') => {
1561 self.parse_comment();
1562 continue;
1563 }
1564 '\\' => {
1565 self.bump();
1566 let Some(ch) = self.ch else {
1567 result.push('\\');
1568 continue;
1569 };
1570
1571 if matches!(ch, '\n') {
1572 in_line_continuation = true;
1573 } else if self.opt.enabled_escape {
1574 match ch {
1575 '0' => result.push('\0'),
1576 'a' => result.push('\x07'),
1577 'b' => result.push('\x08'),
1578 't' => result.push('\t'),
1579 'r' => result.push('\r'),
1580 'n' => result.push('\n'),
1581 '\n' => self.bump(),
1582 'x' => {
1583 let mut code: String = String::with_capacity(4);
1585 for _ in 0..4 {
1586 self.bump();
1587 let ch = self.char_or_eof(endpoint)?;
1588 if ch == '\\' {
1589 self.bump();
1590 if self.ch != Some('\n') {
1591 return self.error(format!(
1592 "expecting \"\\\\n\" but \
1593 found \"{:?}\".",
1594 self.ch
1595 ));
1596 }
1597 }
1598
1599 code.push(ch);
1600 }
1601 let r = u32::from_str_radix(&code[..], 16);
1602 match r.ok().and_then(char::from_u32) {
1603 Some(ch) => result.push(ch),
1604 None => return self.error("unknown character in \\xHH form"),
1605 }
1606 }
1607 c => result.push(c),
1608 }
1609 } else {
1610 result.push('\\');
1611 result.push(ch);
1612 }
1613 }
1614 ch => result.push(ch),
1615 }
1616 self.bump();
1617 }
1618
1619 let _ = check_inline_comment;
1620 let _ = in_line_continuation;
1621
1622 Ok(result)
1623 }
1624
1625 fn parse_section(&mut self) -> Result<String, ParseError> {
1626 cfg_if! {
1627 if #[cfg(feature = "brackets-in-section-names")] {
1628 self.bump();
1630
1631 let mut s = self.parse_str_until(&[Some('\r'), Some('\n')], cfg!(feature = "inline-comment"))?;
1632
1633 #[cfg(feature = "inline-comment")]
1635 if matches!(self.ch, Some('#') | Some(';')) {
1636 self.parse_comment();
1637 }
1638
1639 let tr = s.trim_end_matches([' ', '\t']);
1640 if !tr.ends_with(']') {
1641 return self.error("section must be ended with ']'");
1642 }
1643
1644 s.truncate(tr.len() - 1);
1645 Ok(s)
1646 } else {
1647 self.bump();
1649 let sec = self.parse_str_until(&[Some(']')], false)?;
1650 if let Some(']') = self.ch {
1651 self.bump();
1652 }
1653
1654 #[cfg(feature = "inline-comment")]
1656 if matches!(self.ch, Some('#') | Some(';')) {
1657 self.parse_comment();
1658 }
1659
1660 Ok(sec)
1661 }
1662 }
1663 }
1664
1665 fn parse_key(&mut self) -> Result<String, ParseError> {
1670 self.parse_str_until(&[Some('='), Some(':')], false)
1671 }
1672
1673 fn parse_val(&mut self) -> Result<String, ParseError> {
1674 self.bump();
1675 self.parse_whitespace_except_line_break();
1677
1678 let mut val = String::new();
1679 let mut val_first_part = true;
1680 'parse_value_line_loop: loop {
1682 match self.ch {
1683 None => break,
1685
1686 Some('"') if self.opt.enabled_quote => {
1688 self.bump();
1690 let quoted_val = self.parse_str_until(&[Some('"')], false)?;
1692 val.push_str("ed_val);
1693
1694 self.bump();
1696
1697 val_first_part = false;
1699 continue;
1700 }
1701
1702 Some('\'') if self.opt.enabled_quote => {
1704 self.bump();
1706 let quoted_val = self.parse_str_until(&[Some('\'')], false)?;
1708 val.push_str("ed_val);
1709
1710 self.bump();
1712
1713 val_first_part = false;
1715 continue;
1716 }
1717
1718 _ => {
1720 let standard_val = self.parse_str_until_eol(cfg!(feature = "inline-comment"))?;
1722
1723 let trimmed_value = if val_first_part {
1724 standard_val.trim()
1726 } else {
1727 standard_val.trim_end()
1729 };
1730 val_first_part = false;
1731
1732 val.push_str(trimmed_value);
1733
1734 if self.opt.enabled_indented_mutiline_value {
1735 self.bump();
1737
1738 loop {
1739 match self.ch {
1740 Some(' ') | Some('\t') => {
1741 self.parse_whitespace_except_line_break();
1744 val.push('\n');
1746 continue 'parse_value_line_loop;
1748 }
1749
1750 Some('\r') => {
1751 self.bump();
1753 if self.ch == Some('\n') {
1754 self.bump();
1755 val.push('\n');
1756 } else {
1757 return self.error("\\r is not followed by \\n");
1759 }
1760 }
1761
1762 Some('\n') => {
1763 self.bump();
1765 val.push('\n');
1766 }
1767
1768 _ => break 'parse_value_line_loop,
1770 }
1771 }
1772 } else {
1773 break;
1774 }
1775 }
1776 }
1777 }
1778
1779 if self.opt.enabled_indented_mutiline_value {
1780 trim_line_feeds(&mut val);
1782 }
1783
1784 Ok(val)
1785 }
1786
1787 #[inline]
1788 fn parse_str_until_eol(&mut self, check_inline_comment: bool) -> Result<String, ParseError> {
1789 self.parse_str_until(&[Some('\n'), Some('\r'), None], check_inline_comment)
1790 }
1791}
1792
1793fn trim_in_place(string: &mut String) {
1794 string.truncate(string.trim_end().len());
1795 string.drain(..(string.len() - string.trim_start().len()));
1796}
1797
1798fn trim_end_in_place(string: &mut String) {
1799 string.truncate(string.trim_end().len());
1800}
1801
1802fn trim_line_feeds(string: &mut String) {
1803 const LF: char = '\n';
1804 string.truncate(string.trim_end_matches(LF).len());
1805 string.drain(..(string.len() - string.trim_start_matches(LF).len()));
1806}
1807
1808#[cfg(test)]
1811mod test {
1812 use std::env::temp_dir;
1813
1814 use super::*;
1815
1816 #[test]
1817 fn property_replace() {
1818 let mut props = Properties::new();
1819 props.insert("k1", "v1");
1820
1821 assert_eq!(Some("v1"), props.get("k1"));
1822 let res = props.get_all("k1").collect::<Vec<&str>>();
1823 assert_eq!(res, vec!["v1"]);
1824
1825 props.insert("k1", "v2");
1826 assert_eq!(Some("v2"), props.get("k1"));
1827
1828 let res = props.get_all("k1").collect::<Vec<&str>>();
1829 assert_eq!(res, vec!["v2"]);
1830 }
1831
1832 #[test]
1833 fn property_get_vec() {
1834 let mut props = Properties::new();
1835 props.append("k1", "v1");
1836
1837 assert_eq!(Some("v1"), props.get("k1"));
1838
1839 props.append("k1", "v2");
1840
1841 assert_eq!(Some("v1"), props.get("k1"));
1842
1843 let res = props.get_all("k1").collect::<Vec<&str>>();
1844 assert_eq!(res, vec!["v1", "v2"]);
1845
1846 let res = props.get_all("k2").collect::<Vec<&str>>();
1847 assert!(res.is_empty());
1848 }
1849
1850 #[test]
1851 fn property_remove() {
1852 let mut props = Properties::new();
1853 props.append("k1", "v1");
1854 props.append("k1", "v2");
1855
1856 let res = props.remove_all("k1").collect::<Vec<String>>();
1857 assert_eq!(res, vec!["v1", "v2"]);
1858 assert!(!props.contains_key("k1"));
1859 }
1860
1861 #[test]
1862 fn load_from_str_with_empty_general_section() {
1863 let input = "[sec1]\nkey1=val1\n";
1864 let opt = Ini::load_from_str(input);
1865 assert!(opt.is_ok());
1866
1867 let mut output = opt.unwrap();
1868 assert_eq!(output.len(), 2);
1869
1870 assert!(output.general_section().is_empty());
1871 assert!(output.general_section_mut().is_empty());
1872
1873 let props1 = output.section(None::<String>).unwrap();
1874 assert!(props1.is_empty());
1875 let props2 = output.section(Some("sec1")).unwrap();
1876 assert_eq!(props2.len(), 1);
1877 assert_eq!(props2.get("key1"), Some("val1"));
1878 }
1879
1880 #[test]
1881 fn load_from_str_with_empty_input() {
1882 let input = "";
1883 let opt = Ini::load_from_str(input);
1884 assert!(opt.is_ok());
1885
1886 let mut output = opt.unwrap();
1887 assert!(output.general_section().is_empty());
1888 assert!(output.general_section_mut().is_empty());
1889 assert_eq!(output.len(), 1);
1890 }
1891
1892 #[test]
1893 fn load_from_str_with_empty_lines() {
1894 let input = "\n\n\n";
1895 let opt = Ini::load_from_str(input);
1896 assert!(opt.is_ok());
1897
1898 let mut output = opt.unwrap();
1899 assert!(output.general_section().is_empty());
1900 assert!(output.general_section_mut().is_empty());
1901 assert_eq!(output.len(), 1);
1902 }
1903
1904 #[test]
1905 #[cfg(not(feature = "brackets-in-section-names"))]
1906 fn load_from_str_with_valid_input() {
1907 let input = "[sec1]\nkey1=val1\nkey2=377\n[sec2]foo=bar\n";
1908 let opt = Ini::load_from_str(input);
1909 assert!(opt.is_ok());
1910
1911 let output = opt.unwrap();
1912 assert_eq!(output.len(), 3);
1914 assert!(output.section(Some("sec1")).is_some());
1915
1916 let sec1 = output.section(Some("sec1")).unwrap();
1917 assert_eq!(sec1.len(), 2);
1918 let key1: String = "key1".into();
1919 assert!(sec1.contains_key(&key1));
1920 let key2: String = "key2".into();
1921 assert!(sec1.contains_key(&key2));
1922 let val1: String = "val1".into();
1923 assert_eq!(sec1[&key1], val1);
1924 let val2: String = "377".into();
1925 assert_eq!(sec1[&key2], val2);
1926 }
1927
1928 #[test]
1929 #[cfg(feature = "brackets-in-section-names")]
1930 fn load_from_str_with_valid_input() {
1931 let input = "[sec1]\nkey1=val1\nkey2=377\n[sec2]\nfoo=bar\n";
1932 let opt = Ini::load_from_str(input);
1933 assert!(opt.is_ok());
1934
1935 let output = opt.unwrap();
1936 assert_eq!(output.len(), 3);
1938 assert!(output.section(Some("sec1")).is_some());
1939
1940 let sec1 = output.section(Some("sec1")).unwrap();
1941 assert_eq!(sec1.len(), 2);
1942 let key1: String = "key1".into();
1943 assert!(sec1.contains_key(&key1));
1944 let key2: String = "key2".into();
1945 assert!(sec1.contains_key(&key2));
1946 let val1: String = "val1".into();
1947 assert_eq!(sec1[&key1], val1);
1948 let val2: String = "377".into();
1949 assert_eq!(sec1[&key2], val2);
1950 }
1951
1952 #[test]
1953 #[cfg(not(feature = "brackets-in-section-names"))]
1954 fn load_from_str_without_ending_newline() {
1955 let input = "[sec1]\nkey1=val1\nkey2=377\n[sec2]foo=bar";
1956 let opt = Ini::load_from_str(input);
1957 assert!(opt.is_ok());
1958 }
1959
1960 #[test]
1961 #[cfg(feature = "brackets-in-section-names")]
1962 fn load_from_str_without_ending_newline() {
1963 let input = "[sec1]\nkey1=val1\nkey2=377\n[sec2]\nfoo=bar";
1964 let opt = Ini::load_from_str(input);
1965 assert!(opt.is_ok());
1966 }
1967
1968 #[test]
1969 fn parse_error_numbers() {
1970 let invalid_input = "\n\\x";
1971 let ini = Ini::load_from_str_opt(
1972 invalid_input,
1973 ParseOption {
1974 enabled_escape: true,
1975 ..Default::default()
1976 },
1977 );
1978 assert!(ini.is_err());
1979
1980 let err = ini.unwrap_err();
1981 assert_eq!(err.line, 2);
1982 assert_eq!(err.col, 3);
1983 }
1984
1985 #[test]
1986 fn parse_comment() {
1987 let input = "; abcdefghijklmn\n";
1988 let opt = Ini::load_from_str(input);
1989 assert!(opt.is_ok());
1990 }
1991
1992 #[cfg(not(feature = "inline-comment"))]
1993 #[test]
1994 fn inline_comment_not_supported() {
1995 let input = "
1996[section name]
1997name = hello # abcdefg
1998gender = mail ; abdddd
1999";
2000 let ini = Ini::load_from_str(input).unwrap();
2001 assert_eq!(ini.get_from(Some("section name"), "name").unwrap(), "hello # abcdefg");
2002 assert_eq!(ini.get_from(Some("section name"), "gender").unwrap(), "mail ; abdddd");
2003 }
2004
2005 #[test]
2006 #[cfg_attr(not(feature = "inline-comment"), should_panic)]
2007 fn inline_comment() {
2008 let input = "
2009[section name] # comment in section line
2010name = hello # abcdefg
2011gender = mail ; abdddd
2012address = web#url ;# eeeeee
2013phone = 01234 # tab before comment
2014phone2 = 56789 # tab + space before comment
2015phone3 = 43210 # space + tab before comment
2016";
2017 let ini = Ini::load_from_str(input).unwrap();
2018 println!("{:?}", ini.section(Some("section name")));
2019 assert_eq!(ini.get_from(Some("section name"), "name").unwrap(), "hello");
2020 assert_eq!(ini.get_from(Some("section name"), "gender").unwrap(), "mail");
2021 assert_eq!(ini.get_from(Some("section name"), "address").unwrap(), "web#url");
2022 assert_eq!(ini.get_from(Some("section name"), "phone").unwrap(), "01234");
2023 assert_eq!(ini.get_from(Some("section name"), "phone2").unwrap(), "56789");
2024 assert_eq!(ini.get_from(Some("section name"), "phone3").unwrap(), "43210");
2025 }
2026
2027 #[test]
2028 fn sharp_comment() {
2029 let input = "
2030[section name]
2031name = hello
2032# abcdefg
2033";
2034 let ini = Ini::load_from_str(input).unwrap();
2035 assert_eq!(ini.get_from(Some("section name"), "name").unwrap(), "hello");
2036 }
2037
2038 #[test]
2039 fn iter() {
2040 let input = "
2041[section name]
2042name = hello # abcdefg
2043gender = mail ; abdddd
2044";
2045 let mut ini = Ini::load_from_str(input).unwrap();
2046
2047 for _ in &mut ini {}
2048 for _ in &ini {}
2049 }
2051
2052 #[test]
2053 fn colon() {
2054 let input = "
2055[section name]
2056name: hello
2057gender : mail
2058";
2059 let ini = Ini::load_from_str(input).unwrap();
2060 assert_eq!(ini.get_from(Some("section name"), "name").unwrap(), "hello");
2061 assert_eq!(ini.get_from(Some("section name"), "gender").unwrap(), "mail");
2062 }
2063
2064 #[test]
2065 fn string() {
2066 let input = "
2067[section name]
2068# This is a comment
2069Key = \"Value\"
2070";
2071 let ini = Ini::load_from_str(input).unwrap();
2072 assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value");
2073 }
2074
2075 #[test]
2076 fn string_multiline() {
2077 let input = "
2078[section name]
2079# This is a comment
2080Key = \"Value
2081Otherline\"
2082";
2083 let ini = Ini::load_from_str(input).unwrap();
2084 assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value\nOtherline");
2085 }
2086
2087 #[test]
2088 fn string_multiline_escape() {
2089 let input = r"
2090[section name]
2091# This is a comment
2092Key = Value \
2093Otherline
2094";
2095 let ini = Ini::load_from_str_opt(
2096 input,
2097 ParseOption {
2098 enabled_escape: false,
2099 ..Default::default()
2100 },
2101 )
2102 .unwrap();
2103 assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value Otherline");
2104 }
2105
2106 #[cfg(feature = "inline-comment")]
2107 #[test]
2108 fn string_multiline_inline_comment() {
2109 let input = r"
2110[section name]
2111# This is a comment
2112Key = Value \
2113# This is also a comment
2114; This is also a comment
2115 # This is also a comment
2116Otherline
2117";
2118 let ini = Ini::load_from_str_opt(
2119 input,
2120 ParseOption {
2121 enabled_escape: false,
2122 ..Default::default()
2123 },
2124 )
2125 .unwrap();
2126 assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value Otherline");
2127 }
2128
2129 #[test]
2130 fn string_comment() {
2131 let input = "
2132[section name]
2133# This is a comment
2134Key = \"Value # This is not a comment ; at all\"
2135Stuff = Other
2136";
2137 let ini = Ini::load_from_str(input).unwrap();
2138 assert_eq!(
2139 ini.get_from(Some("section name"), "Key").unwrap(),
2140 "Value # This is not a comment ; at all"
2141 );
2142 }
2143
2144 #[test]
2145 fn string_single() {
2146 let input = "
2147[section name]
2148# This is a comment
2149Key = 'Value'
2150Stuff = Other
2151";
2152 let ini = Ini::load_from_str(input).unwrap();
2153 assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value");
2154 }
2155
2156 #[test]
2157 fn string_includes_quote() {
2158 let input = "
2159[Test]
2160Comment[tr]=İnternet'e erişin
2161Comment[uk]=Доступ до Інтернету
2162";
2163 let ini = Ini::load_from_str(input).unwrap();
2164 assert_eq!(ini.get_from(Some("Test"), "Comment[tr]").unwrap(), "İnternet'e erişin");
2165 }
2166
2167 #[test]
2168 fn string_single_multiline() {
2169 let input = "
2170[section name]
2171# This is a comment
2172Key = 'Value
2173Otherline'
2174Stuff = Other
2175";
2176 let ini = Ini::load_from_str(input).unwrap();
2177 assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value\nOtherline");
2178 }
2179
2180 #[test]
2181 fn string_single_comment() {
2182 let input = "
2183[section name]
2184# This is a comment
2185Key = 'Value # This is not a comment ; at all'
2186";
2187 let ini = Ini::load_from_str(input).unwrap();
2188 assert_eq!(
2189 ini.get_from(Some("section name"), "Key").unwrap(),
2190 "Value # This is not a comment ; at all"
2191 );
2192 }
2193
2194 #[test]
2195 fn load_from_str_with_valid_empty_input() {
2196 let input = "key1=\nkey2=val2\n";
2197 let opt = Ini::load_from_str(input);
2198 assert!(opt.is_ok());
2199
2200 let output = opt.unwrap();
2201 assert_eq!(output.len(), 1);
2202 assert!(output.section(None::<String>).is_some());
2203
2204 let sec1 = output.section(None::<String>).unwrap();
2205 assert_eq!(sec1.len(), 2);
2206 let key1: String = "key1".into();
2207 assert!(sec1.contains_key(&key1));
2208 let key2: String = "key2".into();
2209 assert!(sec1.contains_key(&key2));
2210 let val1: String = "".into();
2211 assert_eq!(sec1[&key1], val1);
2212 let val2: String = "val2".into();
2213 assert_eq!(sec1[&key2], val2);
2214 }
2215
2216 #[test]
2217 fn load_from_str_with_crlf() {
2218 let input = "key1=val1\r\nkey2=val2\r\n";
2219 let opt = Ini::load_from_str(input);
2220 assert!(opt.is_ok());
2221
2222 let output = opt.unwrap();
2223 assert_eq!(output.len(), 1);
2224 assert!(output.section(None::<String>).is_some());
2225 let sec1 = output.section(None::<String>).unwrap();
2226 assert_eq!(sec1.len(), 2);
2227 let key1: String = "key1".into();
2228 assert!(sec1.contains_key(&key1));
2229 let key2: String = "key2".into();
2230 assert!(sec1.contains_key(&key2));
2231 let val1: String = "val1".into();
2232 assert_eq!(sec1[&key1], val1);
2233 let val2: String = "val2".into();
2234 assert_eq!(sec1[&key2], val2);
2235 }
2236
2237 #[test]
2238 fn load_from_str_with_cr() {
2239 let input = "key1=val1\rkey2=val2\r";
2240 let opt = Ini::load_from_str(input);
2241 assert!(opt.is_ok());
2242
2243 let output = opt.unwrap();
2244 assert_eq!(output.len(), 1);
2245 assert!(output.section(None::<String>).is_some());
2246 let sec1 = output.section(None::<String>).unwrap();
2247 assert_eq!(sec1.len(), 2);
2248 let key1: String = "key1".into();
2249 assert!(sec1.contains_key(&key1));
2250 let key2: String = "key2".into();
2251 assert!(sec1.contains_key(&key2));
2252 let val1: String = "val1".into();
2253 assert_eq!(sec1[&key1], val1);
2254 let val2: String = "val2".into();
2255 assert_eq!(sec1[&key2], val2);
2256 }
2257
2258 #[test]
2259 #[cfg(not(feature = "brackets-in-section-names"))]
2260 fn load_from_file_with_bom() {
2261 let file_name = temp_dir().join("rust_ini_load_from_file_with_bom");
2262
2263 let file_content = b"\xEF\xBB\xBF[Test]Key=Value\n";
2264
2265 {
2266 let mut file = File::create(&file_name).expect("create");
2267 file.write_all(file_content).expect("write");
2268 }
2269
2270 let ini = Ini::load_from_file(&file_name).unwrap();
2271 assert_eq!(ini.get_from(Some("Test"), "Key"), Some("Value"));
2272 }
2273
2274 #[test]
2275 #[cfg(feature = "brackets-in-section-names")]
2276 fn load_from_file_with_bom() {
2277 let file_name = temp_dir().join("rust_ini_load_from_file_with_bom");
2278
2279 let file_content = b"\xEF\xBB\xBF[Test]\nKey=Value\n";
2280
2281 {
2282 let mut file = File::create(&file_name).expect("create");
2283 file.write_all(file_content).expect("write");
2284 }
2285
2286 let ini = Ini::load_from_file(&file_name).unwrap();
2287 assert_eq!(ini.get_from(Some("Test"), "Key"), Some("Value"));
2288 }
2289
2290 #[test]
2291 #[cfg(not(feature = "brackets-in-section-names"))]
2292 fn load_from_file_without_bom() {
2293 let file_name = temp_dir().join("rust_ini_load_from_file_without_bom");
2294
2295 let file_content = b"[Test]Key=Value\n";
2296
2297 {
2298 let mut file = File::create(&file_name).expect("create");
2299 file.write_all(file_content).expect("write");
2300 }
2301
2302 let ini = Ini::load_from_file(&file_name).unwrap();
2303 assert_eq!(ini.get_from(Some("Test"), "Key"), Some("Value"));
2304 }
2305
2306 #[test]
2307 #[cfg(feature = "brackets-in-section-names")]
2308 fn load_from_file_without_bom() {
2309 let file_name = temp_dir().join("rust_ini_load_from_file_without_bom");
2310
2311 let file_content = b"[Test]\nKey=Value\n";
2312
2313 {
2314 let mut file = File::create(&file_name).expect("create");
2315 file.write_all(file_content).expect("write");
2316 }
2317
2318 let ini = Ini::load_from_file(&file_name).unwrap();
2319 assert_eq!(ini.get_from(Some("Test"), "Key"), Some("Value"));
2320 }
2321
2322 #[test]
2323 fn get_with_non_static_key() {
2324 let input = "key1=val1\nkey2=val2\n";
2325 let opt = Ini::load_from_str(input).unwrap();
2326
2327 let sec1 = opt.section(None::<String>).unwrap();
2328
2329 let key = "key1".to_owned();
2330 sec1.get(&key).unwrap();
2331 }
2332
2333 #[test]
2334 fn load_from_str_noescape() {
2335 let input = "path=C:\\Windows\\Some\\Folder\\";
2336 let output = Ini::load_from_str_noescape(input).unwrap();
2337 assert_eq!(output.len(), 1);
2338 let sec = output.section(None::<String>).unwrap();
2339 assert_eq!(sec.len(), 1);
2340 assert!(sec.contains_key("path"));
2341 assert_eq!(&sec["path"], "C:\\Windows\\Some\\Folder\\");
2342 }
2343
2344 #[test]
2345 fn partial_quoting_double() {
2346 let input = "
2347[Section]
2348A=\"quote\" arg0
2349B=b";
2350
2351 let opt = Ini::load_from_str(input).unwrap();
2352 let sec = opt.section(Some("Section")).unwrap();
2353 assert_eq!(&sec["A"], "quote arg0");
2354 assert_eq!(&sec["B"], "b");
2355 }
2356
2357 #[test]
2358 fn partial_quoting_single() {
2359 let input = "
2360[Section]
2361A='quote' arg0
2362B=b";
2363
2364 let opt = Ini::load_from_str(input).unwrap();
2365 let sec = opt.section(Some("Section")).unwrap();
2366 assert_eq!(&sec["A"], "quote arg0");
2367 assert_eq!(&sec["B"], "b");
2368 }
2369
2370 #[test]
2371 fn parse_without_quote() {
2372 let input = "
2373[Desktop Entry]
2374Exec = \"/path/to/exe with space\" arg
2375";
2376
2377 let opt = Ini::load_from_str_opt(
2378 input,
2379 ParseOption {
2380 enabled_quote: false,
2381 ..ParseOption::default()
2382 },
2383 )
2384 .unwrap();
2385 let sec = opt.section(Some("Desktop Entry")).unwrap();
2386 assert_eq!(&sec["Exec"], "\"/path/to/exe with space\" arg");
2387 }
2388
2389 #[test]
2390 #[cfg(feature = "case-insensitive")]
2391 fn case_insensitive() {
2392 let input = "
2393[SecTION]
2394KeY=value
2395";
2396
2397 let ini = Ini::load_from_str(input).unwrap();
2398 let section = ini.section(Some("section")).unwrap();
2399 let val = section.get("key").unwrap();
2400 assert_eq!("value", val);
2401 }
2402
2403 #[test]
2404 fn preserve_order_section() {
2405 let input = r"
2406none2 = n2
2407[SB]
2408p2 = 2
2409[SA]
2410x2 = 2
2411[SC]
2412cd1 = x
2413[xC]
2414xd = x
2415 ";
2416
2417 let data = Ini::load_from_str(input).unwrap();
2418 let keys: Vec<Option<&str>> = data.iter().map(|(k, _)| k).collect();
2419
2420 assert_eq!(keys.len(), 5);
2421 assert_eq!(keys[0], None);
2422 assert_eq!(keys[1], Some("SB"));
2423 assert_eq!(keys[2], Some("SA"));
2424 assert_eq!(keys[3], Some("SC"));
2425 assert_eq!(keys[4], Some("xC"));
2426 }
2427
2428 #[test]
2429 fn preserve_order_property() {
2430 let input = r"
2431x2 = n2
2432x1 = n2
2433x3 = n2
2434";
2435 let data = Ini::load_from_str(input).unwrap();
2436 let section = data.general_section();
2437 let keys: Vec<&str> = section.iter().map(|(k, _)| k).collect();
2438 assert_eq!(keys, vec!["x2", "x1", "x3"]);
2439 }
2440
2441 #[test]
2442 fn preserve_order_property_in_section() {
2443 let input = r"
2444[s]
2445x2 = n2
2446xb = n2
2447a3 = n3
2448";
2449 let data = Ini::load_from_str(input).unwrap();
2450 let section = data.section(Some("s")).unwrap();
2451 let keys: Vec<&str> = section.iter().map(|(k, _)| k).collect();
2452 assert_eq!(keys, vec!["x2", "xb", "a3"])
2453 }
2454
2455 #[test]
2456 fn preserve_order_write() {
2457 let input = r"
2458x2 = n2
2459x1 = n2
2460x3 = n2
2461[s]
2462x2 = n2
2463xb = n2
2464a3 = n3
2465";
2466 let data = Ini::load_from_str(input).unwrap();
2467 let mut buf = vec![];
2468 data.write_to(&mut buf).unwrap();
2469 let new_data = Ini::load_from_str(&String::from_utf8(buf).unwrap()).unwrap();
2470
2471 let sec0 = new_data.general_section();
2472 let keys0: Vec<&str> = sec0.iter().map(|(k, _)| k).collect();
2473 assert_eq!(keys0, vec!["x2", "x1", "x3"]);
2474
2475 let sec1 = new_data.section(Some("s")).unwrap();
2476 let keys1: Vec<&str> = sec1.iter().map(|(k, _)| k).collect();
2477 assert_eq!(keys1, vec!["x2", "xb", "a3"]);
2478 }
2479
2480 #[test]
2481 fn write_new() {
2482 use std::str;
2483
2484 let ini = Ini::new();
2485
2486 let opt = WriteOption {
2487 line_separator: LineSeparator::CR,
2488 ..Default::default()
2489 };
2490 let mut buf = Vec::new();
2491 ini.write_to_opt(&mut buf, opt).unwrap();
2492
2493 assert_eq!("", str::from_utf8(&buf).unwrap());
2494 }
2495
2496 #[test]
2497 fn write_line_separator() {
2498 use std::str;
2499
2500 let mut ini = Ini::new();
2501 ini.with_section(Some("Section1"))
2502 .set("Key1", "Value")
2503 .set("Key2", "Value");
2504 ini.with_section(Some("Section2"))
2505 .set("Key1", "Value")
2506 .set("Key2", "Value");
2507
2508 {
2509 let mut buf = Vec::new();
2510 ini.write_to_opt(
2511 &mut buf,
2512 WriteOption {
2513 line_separator: LineSeparator::CR,
2514 ..Default::default()
2515 },
2516 )
2517 .unwrap();
2518
2519 assert_eq!(
2520 "[Section1]\nKey1=Value\nKey2=Value\n\n[Section2]\nKey1=Value\nKey2=Value\n",
2521 str::from_utf8(&buf).unwrap()
2522 );
2523 }
2524
2525 {
2526 let mut buf = Vec::new();
2527 ini.write_to_opt(
2528 &mut buf,
2529 WriteOption {
2530 line_separator: LineSeparator::CRLF,
2531 ..Default::default()
2532 },
2533 )
2534 .unwrap();
2535
2536 assert_eq!(
2537 "[Section1]\r\nKey1=Value\r\nKey2=Value\r\n\r\n[Section2]\r\nKey1=Value\r\nKey2=Value\r\n",
2538 str::from_utf8(&buf).unwrap()
2539 );
2540 }
2541
2542 {
2543 let mut buf = Vec::new();
2544 ini.write_to_opt(
2545 &mut buf,
2546 WriteOption {
2547 line_separator: LineSeparator::SystemDefault,
2548 ..Default::default()
2549 },
2550 )
2551 .unwrap();
2552
2553 if cfg!(windows) {
2554 assert_eq!(
2555 "[Section1]\r\nKey1=Value\r\nKey2=Value\r\n\r\n[Section2]\r\nKey1=Value\r\nKey2=Value\r\n",
2556 str::from_utf8(&buf).unwrap()
2557 );
2558 } else {
2559 assert_eq!(
2560 "[Section1]\nKey1=Value\nKey2=Value\n\n[Section2]\nKey1=Value\nKey2=Value\n",
2561 str::from_utf8(&buf).unwrap()
2562 );
2563 }
2564 }
2565 }
2566
2567 #[test]
2568 fn write_kv_separator() {
2569 use std::str;
2570
2571 let mut ini = Ini::new();
2572 ini.with_section(None::<String>)
2573 .set("Key1", "Value")
2574 .set("Key2", "Value");
2575 ini.with_section(Some("Section1"))
2576 .set("Key1", "Value")
2577 .set("Key2", "Value");
2578 ini.with_section(Some("Section2"))
2579 .set("Key1", "Value")
2580 .set("Key2", "Value");
2581
2582 let mut buf = Vec::new();
2583 ini.write_to_opt(
2584 &mut buf,
2585 WriteOption {
2586 kv_separator: " = ",
2587 ..Default::default()
2588 },
2589 )
2590 .unwrap();
2591
2592 if cfg!(windows) {
2594 assert_eq!(
2595 "Key1 = Value\r\nKey2 = Value\r\n\r\n[Section1]\r\nKey1 = Value\r\nKey2 = Value\r\n\r\n[Section2]\r\nKey1 = Value\r\nKey2 = Value\r\n",
2596 str::from_utf8(&buf).unwrap()
2597 );
2598 } else {
2599 assert_eq!(
2600 "Key1 = Value\nKey2 = Value\n\n[Section1]\nKey1 = Value\nKey2 = Value\n\n[Section2]\nKey1 = Value\nKey2 = Value\n",
2601 str::from_utf8(&buf).unwrap()
2602 );
2603 }
2604 }
2605
2606 #[test]
2607 fn duplicate_sections() {
2608 let input = r"
2611[Peer]
2612foo = a
2613bar = b
2614
2615[Peer]
2616foo = c
2617bar = d
2618
2619[Peer]
2620foo = e
2621bar = f
2622";
2623
2624 let ini = Ini::load_from_str(input).unwrap();
2625 assert_eq!(3, ini.section_all(Some("Peer")).count());
2626
2627 let mut iter = ini.iter();
2628 let (k0, p0) = iter.next().unwrap();
2630 assert_eq!(None, k0);
2631 assert!(p0.is_empty());
2632 let (k1, p1) = iter.next().unwrap();
2633 assert_eq!(Some("Peer"), k1);
2634 assert_eq!(Some("a"), p1.get("foo"));
2635 assert_eq!(Some("b"), p1.get("bar"));
2636 let (k2, p2) = iter.next().unwrap();
2637 assert_eq!(Some("Peer"), k2);
2638 assert_eq!(Some("c"), p2.get("foo"));
2639 assert_eq!(Some("d"), p2.get("bar"));
2640 let (k3, p3) = iter.next().unwrap();
2641 assert_eq!(Some("Peer"), k3);
2642 assert_eq!(Some("e"), p3.get("foo"));
2643 assert_eq!(Some("f"), p3.get("bar"));
2644
2645 assert_eq!(None, iter.next());
2646 }
2647
2648 #[test]
2649 fn add_properties_api() {
2650 let mut ini = Ini::new();
2652 ini.with_section(Some("foo")).add("a", "1").add("a", "2");
2653
2654 let sec = ini.section(Some("foo")).unwrap();
2655 assert_eq!(sec.get("a"), Some("1"));
2656 assert_eq!(sec.get_all("a").collect::<Vec<&str>>(), vec!["1", "2"]);
2657
2658 let mut ini = Ini::new();
2660 ini.with_section(Some("foo")).add("a", "1").add("b", "2");
2661
2662 let sec = ini.section(Some("foo")).unwrap();
2663 assert_eq!(sec.get("a"), Some("1"));
2664 assert_eq!(sec.get("b"), Some("2"));
2665
2666 let mut ini = Ini::new();
2668 ini.with_section(Some("foo")).add("a", "1").add("a", "2");
2669 let mut buf = Vec::new();
2670 ini.write_to(&mut buf).unwrap();
2671 let ini_str = String::from_utf8(buf).unwrap();
2672 if cfg!(windows) {
2673 assert_eq!(ini_str, "[foo]\r\na=1\r\na=2\r\n");
2674 } else {
2675 assert_eq!(ini_str, "[foo]\na=1\na=2\n");
2676 }
2677 }
2678
2679 #[test]
2680 fn new_has_empty_general_section() {
2681 let mut ini = Ini::new();
2682
2683 assert!(ini.general_section().is_empty());
2684 assert!(ini.general_section_mut().is_empty());
2685 assert_eq!(ini.len(), 1);
2686 }
2687
2688 #[test]
2689 fn fix_issue63() {
2690 let section = "PHP";
2691 let key = "engine";
2692 let value = "On";
2693 let new_value = "Off";
2694
2695 let mut conf = Ini::new();
2697 conf.with_section(Some(section)).set(key, value);
2698
2699 let v = conf.get_from(Some(section), key).unwrap();
2701 assert_eq!(v, value);
2702
2703 conf.set_to(Some(section), key.to_string(), new_value.to_string());
2705
2706 let v = conf.get_from(Some(section), key).unwrap();
2708 assert_eq!(v, new_value);
2709 }
2710
2711 #[test]
2712 fn fix_issue64() {
2713 let input = format!("some-key=åäö{}", super::DEFAULT_LINE_SEPARATOR);
2714
2715 let conf = Ini::load_from_str(&input).unwrap();
2716
2717 let mut output = Vec::new();
2718 conf.write_to_policy(&mut output, EscapePolicy::Basics).unwrap();
2719
2720 assert_eq!(input, String::from_utf8(output).unwrap());
2721 }
2722
2723 #[test]
2724 fn invalid_codepoint() {
2725 use std::io::Cursor;
2726
2727 let d = vec![
2728 10, 8, 68, 8, 61, 10, 126, 126, 61, 49, 10, 62, 8, 8, 61, 10, 91, 93, 93, 36, 91, 61, 10, 75, 91, 10, 10,
2729 10, 61, 92, 120, 68, 70, 70, 70, 70, 70, 126, 61, 10, 0, 0, 61, 10, 38, 46, 49, 61, 0, 39, 0, 0, 46, 92,
2730 120, 46, 36, 91, 91, 1, 0, 0, 16, 0, 0, 0, 0, 0, 0,
2731 ];
2732 let mut file = Cursor::new(d);
2733 assert!(Ini::read_from(&mut file).is_err());
2734 }
2735
2736 #[test]
2737 #[cfg(feature = "brackets-in-section-names")]
2738 fn fix_issue84() {
2739 let input = "
2740[[*]]
2741a = b
2742c = d
2743";
2744 let ini = Ini::load_from_str(input).unwrap();
2745 let sect = ini.section(Some("[*]"));
2746 assert!(sect.is_some());
2747 assert!(sect.unwrap().contains_key("a"));
2748 assert!(sect.unwrap().contains_key("c"));
2749 }
2750
2751 #[test]
2752 #[cfg(feature = "brackets-in-section-names")]
2753 fn fix_issue84_brackets_inside() {
2754 let input = "
2755[a[b]c]
2756a = b
2757c = d
2758";
2759 let ini = Ini::load_from_str(input).unwrap();
2760 let sect = ini.section(Some("a[b]c"));
2761 assert!(sect.is_some());
2762 assert!(sect.unwrap().contains_key("a"));
2763 assert!(sect.unwrap().contains_key("c"));
2764 }
2765
2766 #[test]
2767 #[cfg(feature = "brackets-in-section-names")]
2768 fn fix_issue84_whitespaces_after_bracket() {
2769 let input = "
2770[[*]]\t\t
2771a = b
2772c = d
2773";
2774 let ini = Ini::load_from_str(input).unwrap();
2775 let sect = ini.section(Some("[*]"));
2776 assert!(sect.is_some());
2777 assert!(sect.unwrap().contains_key("a"));
2778 assert!(sect.unwrap().contains_key("c"));
2779 }
2780
2781 #[test]
2782 #[cfg(feature = "brackets-in-section-names")]
2783 fn fix_issue84_not_whitespaces_after_bracket() {
2784 let input = "
2785[[*]]xx
2786a = b
2787c = d
2788";
2789 let ini = Ini::load_from_str(input);
2790 assert!(ini.is_err());
2791 }
2792
2793 #[test]
2794 fn escape_str_nothing_policy() {
2795 let test_str = "\0\x07\n字'\"✨🍉杓";
2796 let policy = EscapePolicy::Nothing;
2798 assert_eq!(escape_str(test_str, policy), test_str);
2799 }
2800
2801 #[test]
2802 fn escape_str_basics() {
2803 let test_backslash = r"\backslashes\";
2804 let test_nul = "string with \x00nulls\x00 in it";
2805 let test_controls = "|\x07| bell, |\x08| backspace, |\x7f| delete, |\x1b| escape";
2806 let test_whitespace = "\t \r\n";
2807
2808 assert_eq!(escape_str(test_backslash, EscapePolicy::Nothing), test_backslash);
2809 assert_eq!(escape_str(test_nul, EscapePolicy::Nothing), test_nul);
2810 assert_eq!(escape_str(test_controls, EscapePolicy::Nothing), test_controls);
2811 assert_eq!(escape_str(test_whitespace, EscapePolicy::Nothing), test_whitespace);
2812
2813 for policy in [
2814 EscapePolicy::Basics,
2815 EscapePolicy::BasicsUnicode,
2816 EscapePolicy::BasicsUnicodeExtended,
2817 EscapePolicy::Reserved,
2818 EscapePolicy::ReservedUnicode,
2819 EscapePolicy::ReservedUnicodeExtended,
2820 EscapePolicy::Everything,
2821 ] {
2822 assert_eq!(escape_str(test_backslash, policy), r"\\backslashes\\");
2823 assert_eq!(escape_str(test_nul, policy), r"string with \0nulls\0 in it");
2824 assert_eq!(
2825 escape_str(test_controls, policy),
2826 r"|\a| bell, |\b| backspace, |\x007f| delete, |\x001b| escape"
2827 );
2828 assert_eq!(escape_str(test_whitespace, policy), r"\t \r\n");
2829 }
2830 }
2831
2832 #[test]
2833 fn escape_str_reserved() {
2834 let test_reserved = ":=;#";
2836 let test_punctuation = "!@$%^&*()-_+/?.>,<[]{}``";
2838
2839 for policy in [
2841 EscapePolicy::Nothing,
2842 EscapePolicy::Basics,
2843 EscapePolicy::BasicsUnicode,
2844 EscapePolicy::BasicsUnicodeExtended,
2845 ] {
2846 assert_eq!(escape_str(test_reserved, policy), ":=;#");
2847 assert_eq!(escape_str(test_punctuation, policy), test_punctuation);
2848 }
2849
2850 for policy in [
2852 EscapePolicy::Reserved,
2853 EscapePolicy::ReservedUnicodeExtended,
2854 EscapePolicy::ReservedUnicode,
2855 EscapePolicy::Everything,
2856 ] {
2857 assert_eq!(escape_str(test_reserved, policy), r"\:\=\;\#");
2858 assert_eq!(escape_str(test_punctuation, policy), "!@$%^&*()-_+/?.>,<[]{}``");
2859 }
2860 }
2861
2862 #[test]
2863 fn escape_str_unicode() {
2864 let test_unicode = r"é£∳字✨";
2869 let test_emoji = r"🐱😉";
2870 let test_cjk = r"𠈌𠕇";
2871 let test_high_points = "\u{10ABCD}\u{10FFFF}";
2872
2873 let policy = EscapePolicy::Nothing;
2874 assert_eq!(escape_str(test_unicode, policy), test_unicode);
2875 assert_eq!(escape_str(test_emoji, policy), test_emoji);
2876 assert_eq!(escape_str(test_high_points, policy), test_high_points);
2877
2878 for policy in [EscapePolicy::BasicsUnicode, EscapePolicy::ReservedUnicode] {
2881 assert_eq!(escape_str(test_unicode, policy), r"\x00e9\x00a3\x2233\x5b57\x2728");
2882 assert_eq!(escape_str(test_emoji, policy), test_emoji);
2883 assert_eq!(escape_str(test_cjk, policy), test_cjk);
2884 assert_eq!(escape_str(test_high_points, policy), test_high_points);
2885 }
2886
2887 for policy in [
2889 EscapePolicy::BasicsUnicodeExtended,
2890 EscapePolicy::ReservedUnicodeExtended,
2891 ] {
2892 assert_eq!(escape_str(test_unicode, policy), r"\x00e9\x00a3\x2233\x5b57\x2728");
2893 assert_eq!(escape_str(test_emoji, policy), r"\x1f431\x1f609");
2894 assert_eq!(escape_str(test_cjk, policy), r"\x2020c\x20547");
2895 assert_eq!(escape_str(test_high_points, policy), r"\x10abcd\x10ffff");
2896 }
2897 }
2898
2899 #[test]
2900 fn iter_mut_preserve_order_in_section() {
2901 let input = r"
2902x2 = nc
2903x1 = na
2904x3 = nb
2905";
2906 let mut data = Ini::load_from_str(input).unwrap();
2907 let section = data.general_section_mut();
2908 section.iter_mut().enumerate().for_each(|(i, (_, v))| {
2909 v.push_str(&i.to_string());
2910 });
2911 let props: Vec<_> = section.iter().collect();
2912 assert_eq!(props, vec![("x2", "nc0"), ("x1", "na1"), ("x3", "nb2")]);
2913 }
2914
2915 #[test]
2916 fn preserve_order_properties_into_iter() {
2917 let input = r"
2918x2 = nc
2919x1 = na
2920x3 = nb
2921";
2922 let data = Ini::load_from_str(input).unwrap();
2923 let (_, section) = data.into_iter().next().unwrap();
2924 let props: Vec<_> = section.into_iter().collect();
2925 assert_eq!(
2926 props,
2927 vec![
2928 ("x2".to_owned(), "nc".to_owned()),
2929 ("x1".to_owned(), "na".to_owned()),
2930 ("x3".to_owned(), "nb".to_owned())
2931 ]
2932 );
2933 }
2934
2935 #[test]
2936 fn section_setter_chain() {
2937 let mut ini = Ini::new();
2940 let mut section_setter = ini.with_section(Some("section"));
2941
2942 section_setter.set("a", "1").set("b", "2");
2944 section_setter.set("c", "3");
2946
2947 assert_eq!("1", section_setter.get("a").unwrap());
2948 assert_eq!("2", section_setter.get("b").unwrap());
2949 assert_eq!("3", section_setter.get("c").unwrap());
2950
2951 section_setter.set("a", "4").set("b", "5");
2953 section_setter.set("c", "6");
2954
2955 assert_eq!("4", section_setter.get("a").unwrap());
2956 assert_eq!("5", section_setter.get("b").unwrap());
2957 assert_eq!("6", section_setter.get("c").unwrap());
2958
2959 section_setter.delete(&"a").delete(&"b");
2961 section_setter.delete(&"c");
2962
2963 assert!(section_setter.get("a").is_none());
2964 assert!(section_setter.get("b").is_none());
2965 assert!(section_setter.get("c").is_none());
2966 }
2967
2968 #[test]
2969 fn parse_enabled_indented_mutiline_value() {
2970 let input = "
2971[Foo]
2972bar =
2973 u
2974 v
2975
2976baz = w
2977 x # intentional trailing whitespace below
2978 y
2979
2980 z #2
2981bla = a
2982";
2983
2984 let opt = Ini::load_from_str_opt(
2985 input,
2986 ParseOption {
2987 enabled_indented_mutiline_value: true,
2988 ..ParseOption::default()
2989 },
2990 )
2991 .unwrap();
2992 let sec = opt.section(Some("Foo")).unwrap();
2993 let mut iterator = sec.iter();
2994 let bar = iterator.next().unwrap().1;
2995 let baz = iterator.next().unwrap().1;
2996 let bla = iterator.next().unwrap().1;
2997 assert!(iterator.next().is_none());
2998 assert_eq!(bar, "u\nv");
2999 if cfg!(feature = "inline-comment") {
3000 assert_eq!(baz, "w\nx\ny\n\nz");
3001 } else {
3002 assert_eq!(baz, "w\nx # intentional trailing whitespace below\ny\n\nz #2");
3003 }
3004 assert_eq!(bla, "a");
3005 }
3006
3007 #[test]
3008 fn whitespace_inside_quoted_value_should_not_be_trimed() {
3009 let input = r#"
3010[Foo]
3011Key= " quoted with whitespace "
3012 "#;
3013
3014 let opt = Ini::load_from_str_opt(
3015 input,
3016 ParseOption {
3017 enabled_quote: true,
3018 ..ParseOption::default()
3019 },
3020 )
3021 .unwrap();
3022
3023 assert_eq!(" quoted with whitespace ", opt.get_from(Some("Foo"), "Key").unwrap());
3024 }
3025
3026 #[test]
3027 fn preserve_leading_whitespace_in_keys() {
3028 let input = r"[profile dev]
3031services=my-services
3032
3033[services my-services]
3034dynamodb=
3035 endpoint_url=http://localhost:8000
3036";
3037
3038 let mut opts = ParseOption::default();
3039 opts.enabled_preserve_key_leading_whitespace = true;
3040
3041 let data = Ini::load_from_str_opt(input, opts).unwrap();
3042 let mut w = Vec::new();
3043 data.write_to(&mut w).ok();
3044 let output = String::from_utf8(w).ok().unwrap();
3045
3046 let normalized_input = input.replace('\r', "");
3048 let normalized_output = output.replace('\r', "");
3049 assert_eq!(normalized_input, normalized_output);
3050 }
3051
3052 #[test]
3053 fn preserve_leading_whitespace_mixed_indentation() {
3054 let input = r"[section]
3055key1=value1
3056 key2=value2
3057 key3=value3
3058";
3059 let mut opts = ParseOption::default();
3060 opts.enabled_preserve_key_leading_whitespace = true;
3061
3062 let data = Ini::load_from_str_opt(input, opts).unwrap();
3063 let section = data.section(Some("section")).unwrap();
3064
3065 assert!(section.contains_key("key1"));
3067 assert!(section.contains_key(" key2"));
3068 assert!(section.contains_key(" key3"));
3069
3070 let mut w = Vec::new();
3072 data.write_to(&mut w).ok();
3073 let output = String::from_utf8(w).ok().unwrap();
3074 let normalized_input = input.replace('\r', "");
3075 let normalized_output = output.replace('\r', "");
3076 assert_eq!(normalized_input, normalized_output);
3077 }
3078
3079 #[test]
3080 fn preserve_leading_whitespace_tabs_get_escaped() {
3081 let input = r"[section]
3083 key1=value1
3084";
3085 let mut opts = ParseOption::default();
3086 opts.enabled_preserve_key_leading_whitespace = true;
3087
3088 let data = Ini::load_from_str_opt(input, opts).unwrap();
3089 let section = data.section(Some("section")).unwrap();
3090
3091 assert!(section.contains_key("\tkey1"));
3093 assert_eq!(section.get("\tkey1"), Some("value1"));
3094
3095 let mut w = Vec::new();
3097 data.write_to(&mut w).ok();
3098 let output = String::from_utf8(w).ok().unwrap();
3099
3100 let normalized_output = output.replace('\r', "");
3102 let expected = "[section]\n\\tkey1=value1\n";
3103 assert_eq!(normalized_output, expected);
3104 }
3105
3106 #[test]
3107 fn preserve_leading_whitespace_with_trailing_spaces() {
3108 let input = r"[section]
3109 key1 =value1
3110 key2 =value2
3111";
3112 let mut opts = ParseOption::default();
3113 opts.enabled_preserve_key_leading_whitespace = true;
3114
3115 let data = Ini::load_from_str_opt(input, opts).unwrap();
3116 let section = data.section(Some("section")).unwrap();
3117
3118 assert!(section.contains_key(" key1"));
3120 assert!(section.contains_key(" key2"));
3121 assert_eq!(section.get(" key1"), Some("value1"));
3122 assert_eq!(section.get(" key2"), Some("value2"));
3123 }
3124
3125 #[test]
3126 fn section_after_whitespace_bug_reproduction() {
3127 let input = "[SectionA]\nKey1=Value1\n\n [SectionB]\n Key2=Value2";
3128
3129 let data_default = Ini::load_from_str(input).unwrap();
3131
3132 assert!(data_default.section(Some("SectionA")).is_some());
3134 assert!(data_default.section(Some("SectionB")).is_some());
3135
3136 let section_a = data_default.section(Some("SectionA")).unwrap();
3137 let section_b = data_default.section(Some("SectionB")).unwrap();
3138
3139 assert_eq!(section_a.get("Key1"), Some("Value1"));
3140 assert_eq!(section_b.get("Key2"), Some("Value2"));
3141
3142 let mut opts = ParseOption::default();
3144 opts.enabled_preserve_key_leading_whitespace = true;
3145 let data_preserve = Ini::load_from_str_opt(input, opts).unwrap();
3146
3147 assert!(data_preserve.section(Some("SectionA")).is_some());
3149 assert!(data_preserve.section(Some("SectionB")).is_some());
3150
3151 let section_a_preserve = data_preserve.section(Some("SectionA")).unwrap();
3152 let section_b_preserve = data_preserve.section(Some("SectionB")).unwrap();
3153
3154 assert_eq!(section_a_preserve.get("Key1"), Some("Value1"));
3155 assert_eq!(section_b_preserve.get(" Key2"), Some("Value2"));
3157 }
3158
3159 #[test]
3160 fn section_after_tabs_and_spaces() {
3161 let input = "[SectionA]\nKey1=Value1\n\n\t [SectionB]\n\t Key2=Value2";
3163
3164 let data_default = Ini::load_from_str(input).unwrap();
3165
3166 assert!(data_default.section(Some("SectionA")).is_some());
3167 assert!(data_default.section(Some("SectionB")).is_some());
3168
3169 let section_a = data_default.section(Some("SectionA")).unwrap();
3170 let section_b = data_default.section(Some("SectionB")).unwrap();
3171
3172 assert_eq!(section_a.get("Key1"), Some("Value1"));
3173 assert_eq!(section_b.get("Key2"), Some("Value2"));
3174 }
3175
3176 #[test]
3177 fn multiple_sections_with_whitespace() {
3178 let input = "[SectionA]\nKey1=Value1\n\n [SectionB]\n Key2=Value2\n\n [SectionC]\n Key3=Value3";
3180
3181 let data = Ini::load_from_str(input).unwrap();
3182
3183 assert!(data.section(Some("SectionA")).is_some());
3184 assert!(data.section(Some("SectionB")).is_some());
3185 assert!(data.section(Some("SectionC")).is_some());
3186
3187 assert_eq!(data.section(Some("SectionA")).unwrap().get("Key1"), Some("Value1"));
3188 assert_eq!(data.section(Some("SectionB")).unwrap().get("Key2"), Some("Value2"));
3189 assert_eq!(data.section(Some("SectionC")).unwrap().get("Key3"), Some("Value3"));
3190 }
3191
3192 #[test]
3193 fn section_after_whitespace_bug_reproduction_preserve_enabled() {
3194 let input = "[SectionA]\nKey1=Value1\n\n [SectionB]\n Key2=Value2";
3195
3196 let mut opts = ParseOption::default();
3197 opts.enabled_preserve_key_leading_whitespace = true;
3198 let data = Ini::load_from_str_opt(input, opts).unwrap();
3199
3200 assert!(data.section(Some("SectionA")).is_some());
3201 assert!(data.section(Some("SectionB")).is_some());
3202
3203 let section_a = data.section(Some("SectionA")).unwrap();
3204 let section_b = data.section(Some("SectionB")).unwrap();
3205
3206 assert_eq!(section_a.get("Key1"), Some("Value1"));
3207 assert_eq!(section_b.get(" Key2"), Some("Value2"));
3208 }
3209
3210 #[test]
3211 fn section_after_tabs_and_spaces_preserve_enabled() {
3212 let input = "[SectionA]\nKey1=Value1\n\n\t [SectionB]\n\t Key2=Value2";
3213
3214 let mut opts = ParseOption::default();
3215 opts.enabled_preserve_key_leading_whitespace = true;
3216 let data = Ini::load_from_str_opt(input, opts).unwrap();
3217
3218 assert!(data.section(Some("SectionA")).is_some());
3219 assert!(data.section(Some("SectionB")).is_some());
3220
3221 let section_a = data.section(Some("SectionA")).unwrap();
3222 let section_b = data.section(Some("SectionB")).unwrap();
3223
3224 assert_eq!(section_a.get("Key1"), Some("Value1"));
3225 assert_eq!(section_b.get("\t Key2"), Some("Value2"));
3226 }
3227
3228 #[test]
3229 fn multiple_sections_with_whitespace_preserve_enabled() {
3230 let input = "[SectionA]\nKey1=Value1\n\n [SectionB]\n Key2=Value2\n\n [SectionC]\n Key3=Value3";
3231
3232 let mut opts = ParseOption::default();
3233 opts.enabled_preserve_key_leading_whitespace = true;
3234 let data = Ini::load_from_str_opt(input, opts).unwrap();
3235
3236 assert!(data.section(Some("SectionA")).is_some());
3237 assert!(data.section(Some("SectionB")).is_some());
3238 assert!(data.section(Some("SectionC")).is_some());
3239
3240 assert_eq!(data.section(Some("SectionA")).unwrap().get("Key1"), Some("Value1"));
3241 assert_eq!(data.section(Some("SectionB")).unwrap().get(" Key2"), Some("Value2"));
3242 assert_eq!(data.section(Some("SectionC")).unwrap().get(" Key3"), Some("Value3"));
3243 }
3244
3245 #[test]
3246 fn general_section_with_key_leading_whitespace() {
3247 let input = "\n\n\n\r\n key1=value1\n\tkey2=value2\nkey3=value3\n[Section]\nkeyA=valueA";
3248 let mut opts = ParseOption::default();
3249 opts.enabled_preserve_key_leading_whitespace = true;
3250 let data = Ini::load_from_str_opt(input, opts).unwrap();
3251 let general = data.general_section();
3252 assert_eq!(general.get(" key1"), Some("value1"));
3253 assert_eq!(general.get("\tkey2"), Some("value2"));
3254 assert_eq!(general.get("key3"), Some("value3"));
3255 let section = data.section(Some("Section")).unwrap();
3256 assert_eq!(section.get("keyA"), Some("valueA"));
3257 }
3258}