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(&mut self) {
1314 while let Some(c) = self.ch {
1315 if !c.is_whitespace() && c != '\n' && c != '\t' && c != '\r' {
1316 break;
1317 }
1318 self.bump();
1319 }
1320 }
1321
1322 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 self.parse_whitespace();
1378 while let Some(cur_ch) = self.ch {
1379 match cur_ch {
1380 ';' | '#' => {
1381 if cfg!(not(feature = "inline-comment")) {
1382 if self.col > 1 {
1386 return self.error("doesn't support inline comment");
1387 }
1388 }
1389
1390 self.parse_comment();
1391 }
1392 '[' => match self.parse_section() {
1393 Ok(mut sec) => {
1394 trim_in_place(&mut sec);
1395 cursec = Some(sec);
1396 match result.entry(cursec.clone()) {
1397 SectionEntry::Vacant(v) => {
1398 v.insert(Default::default());
1399 }
1400 SectionEntry::Occupied(mut o) => {
1401 o.append(Default::default());
1402 }
1403 }
1404 }
1405 Err(e) => return Err(e),
1406 },
1407 '=' | ':' => {
1408 if (curkey[..]).is_empty() {
1409 return self.error("missing key");
1410 }
1411 match self.parse_val() {
1412 Ok(mval) => {
1413 match result.entry(cursec.clone()) {
1414 SectionEntry::Vacant(v) => {
1415 let mut prop = Properties::new();
1417 prop.insert(curkey, mval);
1418 v.insert(prop);
1419 }
1420 SectionEntry::Occupied(mut o) => {
1421 o.last_mut().append(curkey, mval);
1423 }
1424 }
1425 curkey = "".into();
1426 }
1427 Err(e) => return Err(e),
1428 }
1429 }
1430 ' ' | '\t' => {
1431 match self.parse_key_with_leading_whitespace() {
1434 Ok(mut mkey) => {
1435 if self.opt.enabled_preserve_key_leading_whitespace {
1437 trim_end_in_place(&mut mkey);
1438 } else {
1439 trim_in_place(&mut mkey);
1440 }
1441 curkey = mkey;
1442 }
1443 Err(_) => {
1444 self.bump();
1447 }
1448 }
1449 }
1450 _ => match self.parse_key() {
1451 Ok(mut mkey) => {
1452 if self.opt.enabled_preserve_key_leading_whitespace {
1455 trim_end_in_place(&mut mkey);
1456 } else {
1457 trim_in_place(&mut mkey);
1458 }
1459 curkey = mkey;
1460 }
1461 Err(e) => return Err(e),
1462 },
1463 }
1464
1465 self.parse_whitespace_preserve_line_leading();
1468 }
1469
1470 Ok(result)
1471 }
1472
1473 fn parse_comment(&mut self) {
1474 while let Some(c) = self.ch {
1475 self.bump();
1476 if c == '\n' {
1477 break;
1478 }
1479 }
1480 }
1481
1482 fn parse_str_until(&mut self, endpoint: &[Option<char>], check_inline_comment: bool) -> Result<String, ParseError> {
1483 let mut result: String = String::new();
1484
1485 let mut in_line_continuation = false;
1486
1487 while !endpoint.contains(&self.ch) {
1488 match self.char_or_eof(endpoint)? {
1489 #[cfg(feature = "inline-comment")]
1490 ch if check_inline_comment && (ch == ' ' || ch == '\t') => {
1491 self.bump();
1492
1493 match self.ch {
1494 Some('#') | Some(';') => {
1495 self.parse_comment();
1497 if in_line_continuation {
1498 result.push(ch);
1499 continue;
1500 } else {
1501 break;
1502 }
1503 }
1504 Some(_) => {
1505 result.push(ch);
1506 continue;
1507 }
1508 None => {
1509 result.push(ch);
1510 }
1511 }
1512 }
1513 #[cfg(feature = "inline-comment")]
1514 ch if check_inline_comment && in_line_continuation && (ch == '#' || ch == ';') => {
1515 self.parse_comment();
1516 continue;
1517 }
1518 '\\' => {
1519 self.bump();
1520 let Some(ch) = self.ch else {
1521 result.push('\\');
1522 continue;
1523 };
1524
1525 if matches!(ch, '\n') {
1526 in_line_continuation = true;
1527 } else if self.opt.enabled_escape {
1528 match ch {
1529 '0' => result.push('\0'),
1530 'a' => result.push('\x07'),
1531 'b' => result.push('\x08'),
1532 't' => result.push('\t'),
1533 'r' => result.push('\r'),
1534 'n' => result.push('\n'),
1535 '\n' => self.bump(),
1536 'x' => {
1537 let mut code: String = String::with_capacity(4);
1539 for _ in 0..4 {
1540 self.bump();
1541 let ch = self.char_or_eof(endpoint)?;
1542 if ch == '\\' {
1543 self.bump();
1544 if self.ch != Some('\n') {
1545 return self.error(format!(
1546 "expecting \"\\\\n\" but \
1547 found \"{:?}\".",
1548 self.ch
1549 ));
1550 }
1551 }
1552
1553 code.push(ch);
1554 }
1555 let r = u32::from_str_radix(&code[..], 16);
1556 match r.ok().and_then(char::from_u32) {
1557 Some(ch) => result.push(ch),
1558 None => return self.error("unknown character in \\xHH form"),
1559 }
1560 }
1561 c => result.push(c),
1562 }
1563 } else {
1564 result.push('\\');
1565 result.push(ch);
1566 }
1567 }
1568 ch => result.push(ch),
1569 }
1570 self.bump();
1571 }
1572
1573 let _ = check_inline_comment;
1574 let _ = in_line_continuation;
1575
1576 Ok(result)
1577 }
1578
1579 fn parse_section(&mut self) -> Result<String, ParseError> {
1580 cfg_if! {
1581 if #[cfg(feature = "brackets-in-section-names")] {
1582 self.bump();
1584
1585 let mut s = self.parse_str_until(&[Some('\r'), Some('\n')], cfg!(feature = "inline-comment"))?;
1586
1587 #[cfg(feature = "inline-comment")]
1589 if matches!(self.ch, Some('#') | Some(';')) {
1590 self.parse_comment();
1591 }
1592
1593 let tr = s.trim_end_matches([' ', '\t']);
1594 if !tr.ends_with(']') {
1595 return self.error("section must be ended with ']'");
1596 }
1597
1598 s.truncate(tr.len() - 1);
1599 Ok(s)
1600 } else {
1601 self.bump();
1603 let sec = self.parse_str_until(&[Some(']')], false)?;
1604 if let Some(']') = self.ch {
1605 self.bump();
1606 }
1607
1608 #[cfg(feature = "inline-comment")]
1610 if matches!(self.ch, Some('#') | Some(';')) {
1611 self.parse_comment();
1612 }
1613
1614 Ok(sec)
1615 }
1616 }
1617 }
1618
1619 fn parse_key(&mut self) -> Result<String, ParseError> {
1624 self.parse_str_until(&[Some('='), Some(':')], false)
1625 }
1626
1627 fn parse_key_with_leading_whitespace(&mut self) -> Result<String, ParseError> {
1640 let mut leading_whitespace = String::new();
1642 while let Some(c) = self.ch {
1643 if c == ' ' || c == '\t' {
1644 leading_whitespace.push(c);
1645 self.bump();
1646 } else {
1647 break;
1648 }
1649 }
1650
1651 let key_part = self.parse_str_until(&[Some('='), Some(':')], false)?;
1653
1654 Ok(leading_whitespace + &key_part)
1656 }
1657
1658 fn parse_val(&mut self) -> Result<String, ParseError> {
1659 self.bump();
1660 self.parse_whitespace_except_line_break();
1662
1663 let mut val = String::new();
1664 let mut val_first_part = true;
1665 'parse_value_line_loop: loop {
1667 match self.ch {
1668 None => break,
1670
1671 Some('"') if self.opt.enabled_quote => {
1673 self.bump();
1675 let quoted_val = self.parse_str_until(&[Some('"')], false)?;
1677 val.push_str("ed_val);
1678
1679 self.bump();
1681
1682 val_first_part = false;
1684 continue;
1685 }
1686
1687 Some('\'') if self.opt.enabled_quote => {
1689 self.bump();
1691 let quoted_val = self.parse_str_until(&[Some('\'')], false)?;
1693 val.push_str("ed_val);
1694
1695 self.bump();
1697
1698 val_first_part = false;
1700 continue;
1701 }
1702
1703 _ => {
1705 let standard_val = self.parse_str_until_eol(cfg!(feature = "inline-comment"))?;
1707
1708 let trimmed_value = if val_first_part {
1709 standard_val.trim()
1711 } else {
1712 standard_val.trim_end()
1714 };
1715 val_first_part = false;
1716
1717 val.push_str(trimmed_value);
1718
1719 if self.opt.enabled_indented_mutiline_value {
1720 self.bump();
1722
1723 loop {
1724 match self.ch {
1725 Some(' ') | Some('\t') => {
1726 self.parse_whitespace_except_line_break();
1729 val.push('\n');
1731 continue 'parse_value_line_loop;
1733 }
1734
1735 Some('\r') => {
1736 self.bump();
1738 if self.ch == Some('\n') {
1739 self.bump();
1740 val.push('\n');
1741 } else {
1742 return self.error("\\r is not followed by \\n");
1744 }
1745 }
1746
1747 Some('\n') => {
1748 self.bump();
1750 val.push('\n');
1751 }
1752
1753 _ => break 'parse_value_line_loop,
1755 }
1756 }
1757 } else {
1758 break;
1759 }
1760 }
1761 }
1762 }
1763
1764 if self.opt.enabled_indented_mutiline_value {
1765 trim_line_feeds(&mut val);
1767 }
1768
1769 Ok(val)
1770 }
1771
1772 #[inline]
1773 fn parse_str_until_eol(&mut self, check_inline_comment: bool) -> Result<String, ParseError> {
1774 self.parse_str_until(&[Some('\n'), Some('\r'), None], check_inline_comment)
1775 }
1776}
1777
1778fn trim_in_place(string: &mut String) {
1779 string.truncate(string.trim_end().len());
1780 string.drain(..(string.len() - string.trim_start().len()));
1781}
1782
1783fn trim_end_in_place(string: &mut String) {
1784 string.truncate(string.trim_end().len());
1785}
1786
1787fn trim_line_feeds(string: &mut String) {
1788 const LF: char = '\n';
1789 string.truncate(string.trim_end_matches(LF).len());
1790 string.drain(..(string.len() - string.trim_start_matches(LF).len()));
1791}
1792
1793#[cfg(test)]
1796mod test {
1797 use std::env::temp_dir;
1798
1799 use super::*;
1800
1801 #[test]
1802 fn property_replace() {
1803 let mut props = Properties::new();
1804 props.insert("k1", "v1");
1805
1806 assert_eq!(Some("v1"), props.get("k1"));
1807 let res = props.get_all("k1").collect::<Vec<&str>>();
1808 assert_eq!(res, vec!["v1"]);
1809
1810 props.insert("k1", "v2");
1811 assert_eq!(Some("v2"), props.get("k1"));
1812
1813 let res = props.get_all("k1").collect::<Vec<&str>>();
1814 assert_eq!(res, vec!["v2"]);
1815 }
1816
1817 #[test]
1818 fn property_get_vec() {
1819 let mut props = Properties::new();
1820 props.append("k1", "v1");
1821
1822 assert_eq!(Some("v1"), props.get("k1"));
1823
1824 props.append("k1", "v2");
1825
1826 assert_eq!(Some("v1"), props.get("k1"));
1827
1828 let res = props.get_all("k1").collect::<Vec<&str>>();
1829 assert_eq!(res, vec!["v1", "v2"]);
1830
1831 let res = props.get_all("k2").collect::<Vec<&str>>();
1832 assert!(res.is_empty());
1833 }
1834
1835 #[test]
1836 fn property_remove() {
1837 let mut props = Properties::new();
1838 props.append("k1", "v1");
1839 props.append("k1", "v2");
1840
1841 let res = props.remove_all("k1").collect::<Vec<String>>();
1842 assert_eq!(res, vec!["v1", "v2"]);
1843 assert!(!props.contains_key("k1"));
1844 }
1845
1846 #[test]
1847 fn load_from_str_with_empty_general_section() {
1848 let input = "[sec1]\nkey1=val1\n";
1849 let opt = Ini::load_from_str(input);
1850 assert!(opt.is_ok());
1851
1852 let mut output = opt.unwrap();
1853 assert_eq!(output.len(), 2);
1854
1855 assert!(output.general_section().is_empty());
1856 assert!(output.general_section_mut().is_empty());
1857
1858 let props1 = output.section(None::<String>).unwrap();
1859 assert!(props1.is_empty());
1860 let props2 = output.section(Some("sec1")).unwrap();
1861 assert_eq!(props2.len(), 1);
1862 assert_eq!(props2.get("key1"), Some("val1"));
1863 }
1864
1865 #[test]
1866 fn load_from_str_with_empty_input() {
1867 let input = "";
1868 let opt = Ini::load_from_str(input);
1869 assert!(opt.is_ok());
1870
1871 let mut output = opt.unwrap();
1872 assert!(output.general_section().is_empty());
1873 assert!(output.general_section_mut().is_empty());
1874 assert_eq!(output.len(), 1);
1875 }
1876
1877 #[test]
1878 fn load_from_str_with_empty_lines() {
1879 let input = "\n\n\n";
1880 let opt = Ini::load_from_str(input);
1881 assert!(opt.is_ok());
1882
1883 let mut output = opt.unwrap();
1884 assert!(output.general_section().is_empty());
1885 assert!(output.general_section_mut().is_empty());
1886 assert_eq!(output.len(), 1);
1887 }
1888
1889 #[test]
1890 #[cfg(not(feature = "brackets-in-section-names"))]
1891 fn load_from_str_with_valid_input() {
1892 let input = "[sec1]\nkey1=val1\nkey2=377\n[sec2]foo=bar\n";
1893 let opt = Ini::load_from_str(input);
1894 assert!(opt.is_ok());
1895
1896 let output = opt.unwrap();
1897 assert_eq!(output.len(), 3);
1899 assert!(output.section(Some("sec1")).is_some());
1900
1901 let sec1 = output.section(Some("sec1")).unwrap();
1902 assert_eq!(sec1.len(), 2);
1903 let key1: String = "key1".into();
1904 assert!(sec1.contains_key(&key1));
1905 let key2: String = "key2".into();
1906 assert!(sec1.contains_key(&key2));
1907 let val1: String = "val1".into();
1908 assert_eq!(sec1[&key1], val1);
1909 let val2: String = "377".into();
1910 assert_eq!(sec1[&key2], val2);
1911 }
1912
1913 #[test]
1914 #[cfg(feature = "brackets-in-section-names")]
1915 fn load_from_str_with_valid_input() {
1916 let input = "[sec1]\nkey1=val1\nkey2=377\n[sec2]\nfoo=bar\n";
1917 let opt = Ini::load_from_str(input);
1918 assert!(opt.is_ok());
1919
1920 let output = opt.unwrap();
1921 assert_eq!(output.len(), 3);
1923 assert!(output.section(Some("sec1")).is_some());
1924
1925 let sec1 = output.section(Some("sec1")).unwrap();
1926 assert_eq!(sec1.len(), 2);
1927 let key1: String = "key1".into();
1928 assert!(sec1.contains_key(&key1));
1929 let key2: String = "key2".into();
1930 assert!(sec1.contains_key(&key2));
1931 let val1: String = "val1".into();
1932 assert_eq!(sec1[&key1], val1);
1933 let val2: String = "377".into();
1934 assert_eq!(sec1[&key2], val2);
1935 }
1936
1937 #[test]
1938 #[cfg(not(feature = "brackets-in-section-names"))]
1939 fn load_from_str_without_ending_newline() {
1940 let input = "[sec1]\nkey1=val1\nkey2=377\n[sec2]foo=bar";
1941 let opt = Ini::load_from_str(input);
1942 assert!(opt.is_ok());
1943 }
1944
1945 #[test]
1946 #[cfg(feature = "brackets-in-section-names")]
1947 fn load_from_str_without_ending_newline() {
1948 let input = "[sec1]\nkey1=val1\nkey2=377\n[sec2]\nfoo=bar";
1949 let opt = Ini::load_from_str(input);
1950 assert!(opt.is_ok());
1951 }
1952
1953 #[test]
1954 fn parse_error_numbers() {
1955 let invalid_input = "\n\\x";
1956 let ini = Ini::load_from_str_opt(
1957 invalid_input,
1958 ParseOption {
1959 enabled_escape: true,
1960 ..Default::default()
1961 },
1962 );
1963 assert!(ini.is_err());
1964
1965 let err = ini.unwrap_err();
1966 assert_eq!(err.line, 2);
1967 assert_eq!(err.col, 3);
1968 }
1969
1970 #[test]
1971 fn parse_comment() {
1972 let input = "; abcdefghijklmn\n";
1973 let opt = Ini::load_from_str(input);
1974 assert!(opt.is_ok());
1975 }
1976
1977 #[cfg(not(feature = "inline-comment"))]
1978 #[test]
1979 fn inline_comment_not_supported() {
1980 let input = "
1981[section name]
1982name = hello # abcdefg
1983gender = mail ; abdddd
1984";
1985 let ini = Ini::load_from_str(input).unwrap();
1986 assert_eq!(ini.get_from(Some("section name"), "name").unwrap(), "hello # abcdefg");
1987 assert_eq!(ini.get_from(Some("section name"), "gender").unwrap(), "mail ; abdddd");
1988 }
1989
1990 #[test]
1991 #[cfg_attr(not(feature = "inline-comment"), should_panic)]
1992 fn inline_comment() {
1993 let input = "
1994[section name] # comment in section line
1995name = hello # abcdefg
1996gender = mail ; abdddd
1997address = web#url ;# eeeeee
1998phone = 01234 # tab before comment
1999phone2 = 56789 # tab + space before comment
2000phone3 = 43210 # space + tab before comment
2001";
2002 let ini = Ini::load_from_str(input).unwrap();
2003 println!("{:?}", ini.section(Some("section name")));
2004 assert_eq!(ini.get_from(Some("section name"), "name").unwrap(), "hello");
2005 assert_eq!(ini.get_from(Some("section name"), "gender").unwrap(), "mail");
2006 assert_eq!(ini.get_from(Some("section name"), "address").unwrap(), "web#url");
2007 assert_eq!(ini.get_from(Some("section name"), "phone").unwrap(), "01234");
2008 assert_eq!(ini.get_from(Some("section name"), "phone2").unwrap(), "56789");
2009 assert_eq!(ini.get_from(Some("section name"), "phone3").unwrap(), "43210");
2010 }
2011
2012 #[test]
2013 fn sharp_comment() {
2014 let input = "
2015[section name]
2016name = hello
2017# abcdefg
2018";
2019 let ini = Ini::load_from_str(input).unwrap();
2020 assert_eq!(ini.get_from(Some("section name"), "name").unwrap(), "hello");
2021 }
2022
2023 #[test]
2024 fn iter() {
2025 let input = "
2026[section name]
2027name = hello # abcdefg
2028gender = mail ; abdddd
2029";
2030 let mut ini = Ini::load_from_str(input).unwrap();
2031
2032 for _ in &mut ini {}
2033 for _ in &ini {}
2034 }
2036
2037 #[test]
2038 fn colon() {
2039 let input = "
2040[section name]
2041name: hello
2042gender : mail
2043";
2044 let ini = Ini::load_from_str(input).unwrap();
2045 assert_eq!(ini.get_from(Some("section name"), "name").unwrap(), "hello");
2046 assert_eq!(ini.get_from(Some("section name"), "gender").unwrap(), "mail");
2047 }
2048
2049 #[test]
2050 fn string() {
2051 let input = "
2052[section name]
2053# This is a comment
2054Key = \"Value\"
2055";
2056 let ini = Ini::load_from_str(input).unwrap();
2057 assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value");
2058 }
2059
2060 #[test]
2061 fn string_multiline() {
2062 let input = "
2063[section name]
2064# This is a comment
2065Key = \"Value
2066Otherline\"
2067";
2068 let ini = Ini::load_from_str(input).unwrap();
2069 assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value\nOtherline");
2070 }
2071
2072 #[test]
2073 fn string_multiline_escape() {
2074 let input = r"
2075[section name]
2076# This is a comment
2077Key = Value \
2078Otherline
2079";
2080 let ini = Ini::load_from_str_opt(
2081 input,
2082 ParseOption {
2083 enabled_escape: false,
2084 ..Default::default()
2085 },
2086 )
2087 .unwrap();
2088 assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value Otherline");
2089 }
2090
2091 #[cfg(feature = "inline-comment")]
2092 #[test]
2093 fn string_multiline_inline_comment() {
2094 let input = r"
2095[section name]
2096# This is a comment
2097Key = Value \
2098# This is also a comment
2099; This is also a comment
2100 # This is also a comment
2101Otherline
2102";
2103 let ini = Ini::load_from_str_opt(
2104 input,
2105 ParseOption {
2106 enabled_escape: false,
2107 ..Default::default()
2108 },
2109 )
2110 .unwrap();
2111 assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value Otherline");
2112 }
2113
2114 #[test]
2115 fn string_comment() {
2116 let input = "
2117[section name]
2118# This is a comment
2119Key = \"Value # This is not a comment ; at all\"
2120Stuff = Other
2121";
2122 let ini = Ini::load_from_str(input).unwrap();
2123 assert_eq!(
2124 ini.get_from(Some("section name"), "Key").unwrap(),
2125 "Value # This is not a comment ; at all"
2126 );
2127 }
2128
2129 #[test]
2130 fn string_single() {
2131 let input = "
2132[section name]
2133# This is a comment
2134Key = 'Value'
2135Stuff = Other
2136";
2137 let ini = Ini::load_from_str(input).unwrap();
2138 assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value");
2139 }
2140
2141 #[test]
2142 fn string_includes_quote() {
2143 let input = "
2144[Test]
2145Comment[tr]=İnternet'e erişin
2146Comment[uk]=Доступ до Інтернету
2147";
2148 let ini = Ini::load_from_str(input).unwrap();
2149 assert_eq!(ini.get_from(Some("Test"), "Comment[tr]").unwrap(), "İnternet'e erişin");
2150 }
2151
2152 #[test]
2153 fn string_single_multiline() {
2154 let input = "
2155[section name]
2156# This is a comment
2157Key = 'Value
2158Otherline'
2159Stuff = Other
2160";
2161 let ini = Ini::load_from_str(input).unwrap();
2162 assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value\nOtherline");
2163 }
2164
2165 #[test]
2166 fn string_single_comment() {
2167 let input = "
2168[section name]
2169# This is a comment
2170Key = 'Value # This is not a comment ; at all'
2171";
2172 let ini = Ini::load_from_str(input).unwrap();
2173 assert_eq!(
2174 ini.get_from(Some("section name"), "Key").unwrap(),
2175 "Value # This is not a comment ; at all"
2176 );
2177 }
2178
2179 #[test]
2180 fn load_from_str_with_valid_empty_input() {
2181 let input = "key1=\nkey2=val2\n";
2182 let opt = Ini::load_from_str(input);
2183 assert!(opt.is_ok());
2184
2185 let output = opt.unwrap();
2186 assert_eq!(output.len(), 1);
2187 assert!(output.section(None::<String>).is_some());
2188
2189 let sec1 = output.section(None::<String>).unwrap();
2190 assert_eq!(sec1.len(), 2);
2191 let key1: String = "key1".into();
2192 assert!(sec1.contains_key(&key1));
2193 let key2: String = "key2".into();
2194 assert!(sec1.contains_key(&key2));
2195 let val1: String = "".into();
2196 assert_eq!(sec1[&key1], val1);
2197 let val2: String = "val2".into();
2198 assert_eq!(sec1[&key2], val2);
2199 }
2200
2201 #[test]
2202 fn load_from_str_with_crlf() {
2203 let input = "key1=val1\r\nkey2=val2\r\n";
2204 let opt = Ini::load_from_str(input);
2205 assert!(opt.is_ok());
2206
2207 let output = opt.unwrap();
2208 assert_eq!(output.len(), 1);
2209 assert!(output.section(None::<String>).is_some());
2210 let sec1 = output.section(None::<String>).unwrap();
2211 assert_eq!(sec1.len(), 2);
2212 let key1: String = "key1".into();
2213 assert!(sec1.contains_key(&key1));
2214 let key2: String = "key2".into();
2215 assert!(sec1.contains_key(&key2));
2216 let val1: String = "val1".into();
2217 assert_eq!(sec1[&key1], val1);
2218 let val2: String = "val2".into();
2219 assert_eq!(sec1[&key2], val2);
2220 }
2221
2222 #[test]
2223 fn load_from_str_with_cr() {
2224 let input = "key1=val1\rkey2=val2\r";
2225 let opt = Ini::load_from_str(input);
2226 assert!(opt.is_ok());
2227
2228 let output = opt.unwrap();
2229 assert_eq!(output.len(), 1);
2230 assert!(output.section(None::<String>).is_some());
2231 let sec1 = output.section(None::<String>).unwrap();
2232 assert_eq!(sec1.len(), 2);
2233 let key1: String = "key1".into();
2234 assert!(sec1.contains_key(&key1));
2235 let key2: String = "key2".into();
2236 assert!(sec1.contains_key(&key2));
2237 let val1: String = "val1".into();
2238 assert_eq!(sec1[&key1], val1);
2239 let val2: String = "val2".into();
2240 assert_eq!(sec1[&key2], val2);
2241 }
2242
2243 #[test]
2244 #[cfg(not(feature = "brackets-in-section-names"))]
2245 fn load_from_file_with_bom() {
2246 let file_name = temp_dir().join("rust_ini_load_from_file_with_bom");
2247
2248 let file_content = b"\xEF\xBB\xBF[Test]Key=Value\n";
2249
2250 {
2251 let mut file = File::create(&file_name).expect("create");
2252 file.write_all(file_content).expect("write");
2253 }
2254
2255 let ini = Ini::load_from_file(&file_name).unwrap();
2256 assert_eq!(ini.get_from(Some("Test"), "Key"), Some("Value"));
2257 }
2258
2259 #[test]
2260 #[cfg(feature = "brackets-in-section-names")]
2261 fn load_from_file_with_bom() {
2262 let file_name = temp_dir().join("rust_ini_load_from_file_with_bom");
2263
2264 let file_content = b"\xEF\xBB\xBF[Test]\nKey=Value\n";
2265
2266 {
2267 let mut file = File::create(&file_name).expect("create");
2268 file.write_all(file_content).expect("write");
2269 }
2270
2271 let ini = Ini::load_from_file(&file_name).unwrap();
2272 assert_eq!(ini.get_from(Some("Test"), "Key"), Some("Value"));
2273 }
2274
2275 #[test]
2276 #[cfg(not(feature = "brackets-in-section-names"))]
2277 fn load_from_file_without_bom() {
2278 let file_name = temp_dir().join("rust_ini_load_from_file_without_bom");
2279
2280 let file_content = b"[Test]Key=Value\n";
2281
2282 {
2283 let mut file = File::create(&file_name).expect("create");
2284 file.write_all(file_content).expect("write");
2285 }
2286
2287 let ini = Ini::load_from_file(&file_name).unwrap();
2288 assert_eq!(ini.get_from(Some("Test"), "Key"), Some("Value"));
2289 }
2290
2291 #[test]
2292 #[cfg(feature = "brackets-in-section-names")]
2293 fn load_from_file_without_bom() {
2294 let file_name = temp_dir().join("rust_ini_load_from_file_without_bom");
2295
2296 let file_content = b"[Test]\nKey=Value\n";
2297
2298 {
2299 let mut file = File::create(&file_name).expect("create");
2300 file.write_all(file_content).expect("write");
2301 }
2302
2303 let ini = Ini::load_from_file(&file_name).unwrap();
2304 assert_eq!(ini.get_from(Some("Test"), "Key"), Some("Value"));
2305 }
2306
2307 #[test]
2308 fn get_with_non_static_key() {
2309 let input = "key1=val1\nkey2=val2\n";
2310 let opt = Ini::load_from_str(input).unwrap();
2311
2312 let sec1 = opt.section(None::<String>).unwrap();
2313
2314 let key = "key1".to_owned();
2315 sec1.get(&key).unwrap();
2316 }
2317
2318 #[test]
2319 fn load_from_str_noescape() {
2320 let input = "path=C:\\Windows\\Some\\Folder\\";
2321 let output = Ini::load_from_str_noescape(input).unwrap();
2322 assert_eq!(output.len(), 1);
2323 let sec = output.section(None::<String>).unwrap();
2324 assert_eq!(sec.len(), 1);
2325 assert!(sec.contains_key("path"));
2326 assert_eq!(&sec["path"], "C:\\Windows\\Some\\Folder\\");
2327 }
2328
2329 #[test]
2330 fn partial_quoting_double() {
2331 let input = "
2332[Section]
2333A=\"quote\" arg0
2334B=b";
2335
2336 let opt = Ini::load_from_str(input).unwrap();
2337 let sec = opt.section(Some("Section")).unwrap();
2338 assert_eq!(&sec["A"], "quote arg0");
2339 assert_eq!(&sec["B"], "b");
2340 }
2341
2342 #[test]
2343 fn partial_quoting_single() {
2344 let input = "
2345[Section]
2346A='quote' arg0
2347B=b";
2348
2349 let opt = Ini::load_from_str(input).unwrap();
2350 let sec = opt.section(Some("Section")).unwrap();
2351 assert_eq!(&sec["A"], "quote arg0");
2352 assert_eq!(&sec["B"], "b");
2353 }
2354
2355 #[test]
2356 fn parse_without_quote() {
2357 let input = "
2358[Desktop Entry]
2359Exec = \"/path/to/exe with space\" arg
2360";
2361
2362 let opt = Ini::load_from_str_opt(
2363 input,
2364 ParseOption {
2365 enabled_quote: false,
2366 ..ParseOption::default()
2367 },
2368 )
2369 .unwrap();
2370 let sec = opt.section(Some("Desktop Entry")).unwrap();
2371 assert_eq!(&sec["Exec"], "\"/path/to/exe with space\" arg");
2372 }
2373
2374 #[test]
2375 #[cfg(feature = "case-insensitive")]
2376 fn case_insensitive() {
2377 let input = "
2378[SecTION]
2379KeY=value
2380";
2381
2382 let ini = Ini::load_from_str(input).unwrap();
2383 let section = ini.section(Some("section")).unwrap();
2384 let val = section.get("key").unwrap();
2385 assert_eq!("value", val);
2386 }
2387
2388 #[test]
2389 fn preserve_order_section() {
2390 let input = r"
2391none2 = n2
2392[SB]
2393p2 = 2
2394[SA]
2395x2 = 2
2396[SC]
2397cd1 = x
2398[xC]
2399xd = x
2400 ";
2401
2402 let data = Ini::load_from_str(input).unwrap();
2403 let keys: Vec<Option<&str>> = data.iter().map(|(k, _)| k).collect();
2404
2405 assert_eq!(keys.len(), 5);
2406 assert_eq!(keys[0], None);
2407 assert_eq!(keys[1], Some("SB"));
2408 assert_eq!(keys[2], Some("SA"));
2409 assert_eq!(keys[3], Some("SC"));
2410 assert_eq!(keys[4], Some("xC"));
2411 }
2412
2413 #[test]
2414 fn preserve_order_property() {
2415 let input = r"
2416x2 = n2
2417x1 = n2
2418x3 = n2
2419";
2420 let data = Ini::load_from_str(input).unwrap();
2421 let section = data.general_section();
2422 let keys: Vec<&str> = section.iter().map(|(k, _)| k).collect();
2423 assert_eq!(keys, vec!["x2", "x1", "x3"]);
2424 }
2425
2426 #[test]
2427 fn preserve_order_property_in_section() {
2428 let input = r"
2429[s]
2430x2 = n2
2431xb = n2
2432a3 = n3
2433";
2434 let data = Ini::load_from_str(input).unwrap();
2435 let section = data.section(Some("s")).unwrap();
2436 let keys: Vec<&str> = section.iter().map(|(k, _)| k).collect();
2437 assert_eq!(keys, vec!["x2", "xb", "a3"])
2438 }
2439
2440 #[test]
2441 fn preserve_order_write() {
2442 let input = r"
2443x2 = n2
2444x1 = n2
2445x3 = n2
2446[s]
2447x2 = n2
2448xb = n2
2449a3 = n3
2450";
2451 let data = Ini::load_from_str(input).unwrap();
2452 let mut buf = vec![];
2453 data.write_to(&mut buf).unwrap();
2454 let new_data = Ini::load_from_str(&String::from_utf8(buf).unwrap()).unwrap();
2455
2456 let sec0 = new_data.general_section();
2457 let keys0: Vec<&str> = sec0.iter().map(|(k, _)| k).collect();
2458 assert_eq!(keys0, vec!["x2", "x1", "x3"]);
2459
2460 let sec1 = new_data.section(Some("s")).unwrap();
2461 let keys1: Vec<&str> = sec1.iter().map(|(k, _)| k).collect();
2462 assert_eq!(keys1, vec!["x2", "xb", "a3"]);
2463 }
2464
2465 #[test]
2466 fn write_new() {
2467 use std::str;
2468
2469 let ini = Ini::new();
2470
2471 let opt = WriteOption {
2472 line_separator: LineSeparator::CR,
2473 ..Default::default()
2474 };
2475 let mut buf = Vec::new();
2476 ini.write_to_opt(&mut buf, opt).unwrap();
2477
2478 assert_eq!("", str::from_utf8(&buf).unwrap());
2479 }
2480
2481 #[test]
2482 fn write_line_separator() {
2483 use std::str;
2484
2485 let mut ini = Ini::new();
2486 ini.with_section(Some("Section1"))
2487 .set("Key1", "Value")
2488 .set("Key2", "Value");
2489 ini.with_section(Some("Section2"))
2490 .set("Key1", "Value")
2491 .set("Key2", "Value");
2492
2493 {
2494 let mut buf = Vec::new();
2495 ini.write_to_opt(
2496 &mut buf,
2497 WriteOption {
2498 line_separator: LineSeparator::CR,
2499 ..Default::default()
2500 },
2501 )
2502 .unwrap();
2503
2504 assert_eq!(
2505 "[Section1]\nKey1=Value\nKey2=Value\n\n[Section2]\nKey1=Value\nKey2=Value\n",
2506 str::from_utf8(&buf).unwrap()
2507 );
2508 }
2509
2510 {
2511 let mut buf = Vec::new();
2512 ini.write_to_opt(
2513 &mut buf,
2514 WriteOption {
2515 line_separator: LineSeparator::CRLF,
2516 ..Default::default()
2517 },
2518 )
2519 .unwrap();
2520
2521 assert_eq!(
2522 "[Section1]\r\nKey1=Value\r\nKey2=Value\r\n\r\n[Section2]\r\nKey1=Value\r\nKey2=Value\r\n",
2523 str::from_utf8(&buf).unwrap()
2524 );
2525 }
2526
2527 {
2528 let mut buf = Vec::new();
2529 ini.write_to_opt(
2530 &mut buf,
2531 WriteOption {
2532 line_separator: LineSeparator::SystemDefault,
2533 ..Default::default()
2534 },
2535 )
2536 .unwrap();
2537
2538 if cfg!(windows) {
2539 assert_eq!(
2540 "[Section1]\r\nKey1=Value\r\nKey2=Value\r\n\r\n[Section2]\r\nKey1=Value\r\nKey2=Value\r\n",
2541 str::from_utf8(&buf).unwrap()
2542 );
2543 } else {
2544 assert_eq!(
2545 "[Section1]\nKey1=Value\nKey2=Value\n\n[Section2]\nKey1=Value\nKey2=Value\n",
2546 str::from_utf8(&buf).unwrap()
2547 );
2548 }
2549 }
2550 }
2551
2552 #[test]
2553 fn write_kv_separator() {
2554 use std::str;
2555
2556 let mut ini = Ini::new();
2557 ini.with_section(None::<String>)
2558 .set("Key1", "Value")
2559 .set("Key2", "Value");
2560 ini.with_section(Some("Section1"))
2561 .set("Key1", "Value")
2562 .set("Key2", "Value");
2563 ini.with_section(Some("Section2"))
2564 .set("Key1", "Value")
2565 .set("Key2", "Value");
2566
2567 let mut buf = Vec::new();
2568 ini.write_to_opt(
2569 &mut buf,
2570 WriteOption {
2571 kv_separator: " = ",
2572 ..Default::default()
2573 },
2574 )
2575 .unwrap();
2576
2577 if cfg!(windows) {
2579 assert_eq!(
2580 "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",
2581 str::from_utf8(&buf).unwrap()
2582 );
2583 } else {
2584 assert_eq!(
2585 "Key1 = Value\nKey2 = Value\n\n[Section1]\nKey1 = Value\nKey2 = Value\n\n[Section2]\nKey1 = Value\nKey2 = Value\n",
2586 str::from_utf8(&buf).unwrap()
2587 );
2588 }
2589 }
2590
2591 #[test]
2592 fn duplicate_sections() {
2593 let input = r"
2596[Peer]
2597foo = a
2598bar = b
2599
2600[Peer]
2601foo = c
2602bar = d
2603
2604[Peer]
2605foo = e
2606bar = f
2607";
2608
2609 let ini = Ini::load_from_str(input).unwrap();
2610 assert_eq!(3, ini.section_all(Some("Peer")).count());
2611
2612 let mut iter = ini.iter();
2613 let (k0, p0) = iter.next().unwrap();
2615 assert_eq!(None, k0);
2616 assert!(p0.is_empty());
2617 let (k1, p1) = iter.next().unwrap();
2618 assert_eq!(Some("Peer"), k1);
2619 assert_eq!(Some("a"), p1.get("foo"));
2620 assert_eq!(Some("b"), p1.get("bar"));
2621 let (k2, p2) = iter.next().unwrap();
2622 assert_eq!(Some("Peer"), k2);
2623 assert_eq!(Some("c"), p2.get("foo"));
2624 assert_eq!(Some("d"), p2.get("bar"));
2625 let (k3, p3) = iter.next().unwrap();
2626 assert_eq!(Some("Peer"), k3);
2627 assert_eq!(Some("e"), p3.get("foo"));
2628 assert_eq!(Some("f"), p3.get("bar"));
2629
2630 assert_eq!(None, iter.next());
2631 }
2632
2633 #[test]
2634 fn add_properties_api() {
2635 let mut ini = Ini::new();
2637 ini.with_section(Some("foo")).add("a", "1").add("a", "2");
2638
2639 let sec = ini.section(Some("foo")).unwrap();
2640 assert_eq!(sec.get("a"), Some("1"));
2641 assert_eq!(sec.get_all("a").collect::<Vec<&str>>(), vec!["1", "2"]);
2642
2643 let mut ini = Ini::new();
2645 ini.with_section(Some("foo")).add("a", "1").add("b", "2");
2646
2647 let sec = ini.section(Some("foo")).unwrap();
2648 assert_eq!(sec.get("a"), Some("1"));
2649 assert_eq!(sec.get("b"), Some("2"));
2650
2651 let mut ini = Ini::new();
2653 ini.with_section(Some("foo")).add("a", "1").add("a", "2");
2654 let mut buf = Vec::new();
2655 ini.write_to(&mut buf).unwrap();
2656 let ini_str = String::from_utf8(buf).unwrap();
2657 if cfg!(windows) {
2658 assert_eq!(ini_str, "[foo]\r\na=1\r\na=2\r\n");
2659 } else {
2660 assert_eq!(ini_str, "[foo]\na=1\na=2\n");
2661 }
2662 }
2663
2664 #[test]
2665 fn new_has_empty_general_section() {
2666 let mut ini = Ini::new();
2667
2668 assert!(ini.general_section().is_empty());
2669 assert!(ini.general_section_mut().is_empty());
2670 assert_eq!(ini.len(), 1);
2671 }
2672
2673 #[test]
2674 fn fix_issue63() {
2675 let section = "PHP";
2676 let key = "engine";
2677 let value = "On";
2678 let new_value = "Off";
2679
2680 let mut conf = Ini::new();
2682 conf.with_section(Some(section)).set(key, value);
2683
2684 let v = conf.get_from(Some(section), key).unwrap();
2686 assert_eq!(v, value);
2687
2688 conf.set_to(Some(section), key.to_string(), new_value.to_string());
2690
2691 let v = conf.get_from(Some(section), key).unwrap();
2693 assert_eq!(v, new_value);
2694 }
2695
2696 #[test]
2697 fn fix_issue64() {
2698 let input = format!("some-key=åäö{}", super::DEFAULT_LINE_SEPARATOR);
2699
2700 let conf = Ini::load_from_str(&input).unwrap();
2701
2702 let mut output = Vec::new();
2703 conf.write_to_policy(&mut output, EscapePolicy::Basics).unwrap();
2704
2705 assert_eq!(input, String::from_utf8(output).unwrap());
2706 }
2707
2708 #[test]
2709 fn invalid_codepoint() {
2710 use std::io::Cursor;
2711
2712 let d = vec![
2713 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,
2714 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,
2715 120, 46, 36, 91, 91, 1, 0, 0, 16, 0, 0, 0, 0, 0, 0,
2716 ];
2717 let mut file = Cursor::new(d);
2718 assert!(Ini::read_from(&mut file).is_err());
2719 }
2720
2721 #[test]
2722 #[cfg(feature = "brackets-in-section-names")]
2723 fn fix_issue84() {
2724 let input = "
2725[[*]]
2726a = b
2727c = d
2728";
2729 let ini = Ini::load_from_str(input).unwrap();
2730 let sect = ini.section(Some("[*]"));
2731 assert!(sect.is_some());
2732 assert!(sect.unwrap().contains_key("a"));
2733 assert!(sect.unwrap().contains_key("c"));
2734 }
2735
2736 #[test]
2737 #[cfg(feature = "brackets-in-section-names")]
2738 fn fix_issue84_brackets_inside() {
2739 let input = "
2740[a[b]c]
2741a = b
2742c = d
2743";
2744 let ini = Ini::load_from_str(input).unwrap();
2745 let sect = ini.section(Some("a[b]c"));
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_whitespaces_after_bracket() {
2754 let input = "
2755[[*]]\t\t
2756a = b
2757c = d
2758";
2759 let ini = Ini::load_from_str(input).unwrap();
2760 let sect = ini.section(Some("[*]"));
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_not_whitespaces_after_bracket() {
2769 let input = "
2770[[*]]xx
2771a = b
2772c = d
2773";
2774 let ini = Ini::load_from_str(input);
2775 assert!(ini.is_err());
2776 }
2777
2778 #[test]
2779 fn escape_str_nothing_policy() {
2780 let test_str = "\0\x07\n字'\"✨🍉杓";
2781 let policy = EscapePolicy::Nothing;
2783 assert_eq!(escape_str(test_str, policy), test_str);
2784 }
2785
2786 #[test]
2787 fn escape_str_basics() {
2788 let test_backslash = r"\backslashes\";
2789 let test_nul = "string with \x00nulls\x00 in it";
2790 let test_controls = "|\x07| bell, |\x08| backspace, |\x7f| delete, |\x1b| escape";
2791 let test_whitespace = "\t \r\n";
2792
2793 assert_eq!(escape_str(test_backslash, EscapePolicy::Nothing), test_backslash);
2794 assert_eq!(escape_str(test_nul, EscapePolicy::Nothing), test_nul);
2795 assert_eq!(escape_str(test_controls, EscapePolicy::Nothing), test_controls);
2796 assert_eq!(escape_str(test_whitespace, EscapePolicy::Nothing), test_whitespace);
2797
2798 for policy in [
2799 EscapePolicy::Basics,
2800 EscapePolicy::BasicsUnicode,
2801 EscapePolicy::BasicsUnicodeExtended,
2802 EscapePolicy::Reserved,
2803 EscapePolicy::ReservedUnicode,
2804 EscapePolicy::ReservedUnicodeExtended,
2805 EscapePolicy::Everything,
2806 ] {
2807 assert_eq!(escape_str(test_backslash, policy), r"\\backslashes\\");
2808 assert_eq!(escape_str(test_nul, policy), r"string with \0nulls\0 in it");
2809 assert_eq!(
2810 escape_str(test_controls, policy),
2811 r"|\a| bell, |\b| backspace, |\x007f| delete, |\x001b| escape"
2812 );
2813 assert_eq!(escape_str(test_whitespace, policy), r"\t \r\n");
2814 }
2815 }
2816
2817 #[test]
2818 fn escape_str_reserved() {
2819 let test_reserved = ":=;#";
2821 let test_punctuation = "!@$%^&*()-_+/?.>,<[]{}``";
2823
2824 for policy in [
2826 EscapePolicy::Nothing,
2827 EscapePolicy::Basics,
2828 EscapePolicy::BasicsUnicode,
2829 EscapePolicy::BasicsUnicodeExtended,
2830 ] {
2831 assert_eq!(escape_str(test_reserved, policy), ":=;#");
2832 assert_eq!(escape_str(test_punctuation, policy), test_punctuation);
2833 }
2834
2835 for policy in [
2837 EscapePolicy::Reserved,
2838 EscapePolicy::ReservedUnicodeExtended,
2839 EscapePolicy::ReservedUnicode,
2840 EscapePolicy::Everything,
2841 ] {
2842 assert_eq!(escape_str(test_reserved, policy), r"\:\=\;\#");
2843 assert_eq!(escape_str(test_punctuation, policy), "!@$%^&*()-_+/?.>,<[]{}``");
2844 }
2845 }
2846
2847 #[test]
2848 fn escape_str_unicode() {
2849 let test_unicode = r"é£∳字✨";
2854 let test_emoji = r"🐱😉";
2855 let test_cjk = r"𠈌𠕇";
2856 let test_high_points = "\u{10ABCD}\u{10FFFF}";
2857
2858 let policy = EscapePolicy::Nothing;
2859 assert_eq!(escape_str(test_unicode, policy), test_unicode);
2860 assert_eq!(escape_str(test_emoji, policy), test_emoji);
2861 assert_eq!(escape_str(test_high_points, policy), test_high_points);
2862
2863 for policy in [EscapePolicy::BasicsUnicode, EscapePolicy::ReservedUnicode] {
2866 assert_eq!(escape_str(test_unicode, policy), r"\x00e9\x00a3\x2233\x5b57\x2728");
2867 assert_eq!(escape_str(test_emoji, policy), test_emoji);
2868 assert_eq!(escape_str(test_cjk, policy), test_cjk);
2869 assert_eq!(escape_str(test_high_points, policy), test_high_points);
2870 }
2871
2872 for policy in [
2874 EscapePolicy::BasicsUnicodeExtended,
2875 EscapePolicy::ReservedUnicodeExtended,
2876 ] {
2877 assert_eq!(escape_str(test_unicode, policy), r"\x00e9\x00a3\x2233\x5b57\x2728");
2878 assert_eq!(escape_str(test_emoji, policy), r"\x1f431\x1f609");
2879 assert_eq!(escape_str(test_cjk, policy), r"\x2020c\x20547");
2880 assert_eq!(escape_str(test_high_points, policy), r"\x10abcd\x10ffff");
2881 }
2882 }
2883
2884 #[test]
2885 fn iter_mut_preserve_order_in_section() {
2886 let input = r"
2887x2 = nc
2888x1 = na
2889x3 = nb
2890";
2891 let mut data = Ini::load_from_str(input).unwrap();
2892 let section = data.general_section_mut();
2893 section.iter_mut().enumerate().for_each(|(i, (_, v))| {
2894 v.push_str(&i.to_string());
2895 });
2896 let props: Vec<_> = section.iter().collect();
2897 assert_eq!(props, vec![("x2", "nc0"), ("x1", "na1"), ("x3", "nb2")]);
2898 }
2899
2900 #[test]
2901 fn preserve_order_properties_into_iter() {
2902 let input = r"
2903x2 = nc
2904x1 = na
2905x3 = nb
2906";
2907 let data = Ini::load_from_str(input).unwrap();
2908 let (_, section) = data.into_iter().next().unwrap();
2909 let props: Vec<_> = section.into_iter().collect();
2910 assert_eq!(
2911 props,
2912 vec![
2913 ("x2".to_owned(), "nc".to_owned()),
2914 ("x1".to_owned(), "na".to_owned()),
2915 ("x3".to_owned(), "nb".to_owned())
2916 ]
2917 );
2918 }
2919
2920 #[test]
2921 fn section_setter_chain() {
2922 let mut ini = Ini::new();
2925 let mut section_setter = ini.with_section(Some("section"));
2926
2927 section_setter.set("a", "1").set("b", "2");
2929 section_setter.set("c", "3");
2931
2932 assert_eq!("1", section_setter.get("a").unwrap());
2933 assert_eq!("2", section_setter.get("b").unwrap());
2934 assert_eq!("3", section_setter.get("c").unwrap());
2935
2936 section_setter.set("a", "4").set("b", "5");
2938 section_setter.set("c", "6");
2939
2940 assert_eq!("4", section_setter.get("a").unwrap());
2941 assert_eq!("5", section_setter.get("b").unwrap());
2942 assert_eq!("6", section_setter.get("c").unwrap());
2943
2944 section_setter.delete(&"a").delete(&"b");
2946 section_setter.delete(&"c");
2947
2948 assert!(section_setter.get("a").is_none());
2949 assert!(section_setter.get("b").is_none());
2950 assert!(section_setter.get("c").is_none());
2951 }
2952
2953 #[test]
2954 fn parse_enabled_indented_mutiline_value() {
2955 let input = "
2956[Foo]
2957bar =
2958 u
2959 v
2960
2961baz = w
2962 x # intentional trailing whitespace below
2963 y
2964
2965 z #2
2966bla = a
2967";
2968
2969 let opt = Ini::load_from_str_opt(
2970 input,
2971 ParseOption {
2972 enabled_indented_mutiline_value: true,
2973 ..ParseOption::default()
2974 },
2975 )
2976 .unwrap();
2977 let sec = opt.section(Some("Foo")).unwrap();
2978 let mut iterator = sec.iter();
2979 let bar = iterator.next().unwrap().1;
2980 let baz = iterator.next().unwrap().1;
2981 let bla = iterator.next().unwrap().1;
2982 assert!(iterator.next().is_none());
2983 assert_eq!(bar, "u\nv");
2984 if cfg!(feature = "inline-comment") {
2985 assert_eq!(baz, "w\nx\ny\n\nz");
2986 } else {
2987 assert_eq!(baz, "w\nx # intentional trailing whitespace below\ny\n\nz #2");
2988 }
2989 assert_eq!(bla, "a");
2990 }
2991
2992 #[test]
2993 fn whitespace_inside_quoted_value_should_not_be_trimed() {
2994 let input = r#"
2995[Foo]
2996Key= " quoted with whitespace "
2997 "#;
2998
2999 let opt = Ini::load_from_str_opt(
3000 input,
3001 ParseOption {
3002 enabled_quote: true,
3003 ..ParseOption::default()
3004 },
3005 )
3006 .unwrap();
3007
3008 assert_eq!(" quoted with whitespace ", opt.get_from(Some("Foo"), "Key").unwrap());
3009 }
3010
3011 #[test]
3012 fn preserve_leading_whitespace_in_keys() {
3013 let input = r"[profile dev]
3016services=my-services
3017
3018[services my-services]
3019dynamodb=
3020 endpoint_url=http://localhost:8000
3021";
3022
3023 let mut opts = ParseOption::default();
3024 opts.enabled_preserve_key_leading_whitespace = true;
3025
3026 let data = Ini::load_from_str_opt(input, opts).unwrap();
3027 let mut w = Vec::new();
3028 data.write_to(&mut w).ok();
3029 let output = String::from_utf8(w).ok().unwrap();
3030
3031 let normalized_input = input.replace('\r', "");
3033 let normalized_output = output.replace('\r', "");
3034 assert_eq!(normalized_input, normalized_output);
3035 }
3036
3037 #[test]
3038 fn preserve_leading_whitespace_mixed_indentation() {
3039 let input = r"[section]
3040key1=value1
3041 key2=value2
3042 key3=value3
3043";
3044 let mut opts = ParseOption::default();
3045 opts.enabled_preserve_key_leading_whitespace = true;
3046
3047 let data = Ini::load_from_str_opt(input, opts).unwrap();
3048 let section = data.section(Some("section")).unwrap();
3049
3050 assert!(section.contains_key("key1"));
3052 assert!(section.contains_key(" key2"));
3053 assert!(section.contains_key(" key3"));
3054
3055 let mut w = Vec::new();
3057 data.write_to(&mut w).ok();
3058 let output = String::from_utf8(w).ok().unwrap();
3059 let normalized_input = input.replace('\r', "");
3060 let normalized_output = output.replace('\r', "");
3061 assert_eq!(normalized_input, normalized_output);
3062 }
3063
3064 #[test]
3065 fn preserve_leading_whitespace_tabs_get_escaped() {
3066 let input = r"[section]
3068 key1=value1
3069";
3070 let mut opts = ParseOption::default();
3071 opts.enabled_preserve_key_leading_whitespace = true;
3072
3073 let data = Ini::load_from_str_opt(input, opts).unwrap();
3074 let section = data.section(Some("section")).unwrap();
3075
3076 assert!(section.contains_key("\tkey1"));
3078 assert_eq!(section.get("\tkey1"), Some("value1"));
3079
3080 let mut w = Vec::new();
3082 data.write_to(&mut w).ok();
3083 let output = String::from_utf8(w).ok().unwrap();
3084
3085 let normalized_output = output.replace('\r', "");
3087 let expected = "[section]\n\\tkey1=value1\n";
3088 assert_eq!(normalized_output, expected);
3089 }
3090
3091 #[test]
3092 fn preserve_leading_whitespace_with_trailing_spaces() {
3093 let input = r"[section]
3094 key1 =value1
3095 key2 =value2
3096";
3097 let mut opts = ParseOption::default();
3098 opts.enabled_preserve_key_leading_whitespace = true;
3099
3100 let data = Ini::load_from_str_opt(input, opts).unwrap();
3101 let section = data.section(Some("section")).unwrap();
3102
3103 assert!(section.contains_key(" key1"));
3105 assert!(section.contains_key(" key2"));
3106 assert_eq!(section.get(" key1"), Some("value1"));
3107 assert_eq!(section.get(" key2"), Some("value2"));
3108 }
3109}