1use std::{
46 borrow::Cow,
47 char, error,
48 fmt::{self, Display},
49 fs::{File, OpenOptions},
50 io::{self, Read, Seek, SeekFrom, Write},
51 ops::{Index, IndexMut},
52 path::Path,
53 str::Chars,
54};
55
56use cfg_if::cfg_if;
57use ordered_multimap::{
58 list_ordered_multimap::{Entry, IntoIter, Iter, IterMut, OccupiedEntry, VacantEntry},
59 ListOrderedMultimap,
60};
61use trim_in_place::TrimInPlace;
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
219impl Default for ParseOption {
220 fn default() -> ParseOption {
221 ParseOption {
222 enabled_quote: true,
223 enabled_escape: true,
224 }
225 }
226}
227
228#[derive(Debug, Copy, Clone, Eq, PartialEq)]
230pub enum LineSeparator {
231 SystemDefault,
236
237 CR,
239
240 CRLF,
242}
243
244#[cfg(not(windows))]
245static DEFAULT_LINE_SEPARATOR: &str = "\n";
246
247#[cfg(windows)]
248static DEFAULT_LINE_SEPARATOR: &str = "\r\n";
249
250static DEFAULT_KV_SEPARATOR: &str = "=";
251
252impl fmt::Display for LineSeparator {
253 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
254 f.write_str(self.as_str())
255 }
256}
257
258impl LineSeparator {
259 pub fn as_str(self) -> &'static str {
261 match self {
262 LineSeparator::SystemDefault => DEFAULT_LINE_SEPARATOR,
263 LineSeparator::CR => "\n",
264 LineSeparator::CRLF => "\r\n",
265 }
266 }
267}
268
269#[derive(Debug, Clone)]
271pub struct WriteOption {
272 pub escape_policy: EscapePolicy,
274
275 pub line_separator: LineSeparator,
277
278 pub kv_separator: &'static str,
280}
281
282impl Default for WriteOption {
283 fn default() -> WriteOption {
284 WriteOption {
285 escape_policy: EscapePolicy::Basics,
286 line_separator: LineSeparator::SystemDefault,
287 kv_separator: DEFAULT_KV_SEPARATOR,
288 }
289 }
290}
291
292cfg_if! {
293 if #[cfg(feature = "case-insensitive")] {
294 pub type SectionKey = Option<UniCase<String>>;
296 pub type PropertyKey = UniCase<String>;
298
299 macro_rules! property_get_key {
300 ($s:expr) => {
301 &UniCase::from($s)
302 };
303 }
304
305 macro_rules! property_insert_key {
306 ($s:expr) => {
307 UniCase::from($s)
308 };
309 }
310
311 macro_rules! section_key {
312 ($s:expr) => {
313 $s.map(|s| UniCase::from(s.into()))
314 };
315 }
316
317 } else {
318 pub type SectionKey = Option<String>;
320 pub type PropertyKey = String;
322
323 macro_rules! property_get_key {
324 ($s:expr) => {
325 $s
326 };
327 }
328
329 macro_rules! property_insert_key {
330 ($s:expr) => {
331 $s
332 };
333 }
334
335 macro_rules! section_key {
336 ($s:expr) => {
337 $s.map(Into::into)
338 };
339 }
340 }
341}
342
343pub struct SectionSetter<'a> {
345 ini: &'a mut Ini,
346 section_name: Option<String>,
347}
348
349impl<'a> SectionSetter<'a> {
350 fn new(ini: &'a mut Ini, section_name: Option<String>) -> SectionSetter<'a> {
351 SectionSetter { ini, section_name }
352 }
353
354 pub fn set<'b, K, V>(&'b mut self, key: K, value: V) -> &'b mut SectionSetter<'a>
356 where
357 K: Into<String>,
358 V: Into<String>,
359 'a: 'b,
360 {
361 self.ini
362 .entry(self.section_name.clone())
363 .or_insert_with(Default::default)
364 .insert(key, value);
365
366 self
367 }
368
369 pub fn add<'b, K, V>(&'b mut self, key: K, value: V) -> &'b mut SectionSetter<'a>
371 where
372 K: Into<String>,
373 V: Into<String>,
374 'a: 'b,
375 {
376 self.ini
377 .entry(self.section_name.clone())
378 .or_insert_with(Default::default)
379 .append(key, value);
380
381 self
382 }
383
384 pub fn delete<'b, K>(&'b mut self, key: &K) -> &'b mut SectionSetter<'a>
386 where
387 K: AsRef<str>,
388 'a: 'b,
389 {
390 for prop in self.ini.section_all_mut(self.section_name.as_ref()) {
391 prop.remove(key);
392 }
393
394 self
395 }
396
397 pub fn get<K: AsRef<str>>(&'a self, key: K) -> Option<&'a str> {
399 self.ini
400 .section(self.section_name.as_ref())
401 .and_then(|prop| prop.get(key))
402 .map(AsRef::as_ref)
403 }
404}
405
406#[derive(Clone, Default, Debug, PartialEq)]
408pub struct Properties {
409 data: ListOrderedMultimap<PropertyKey, String>,
410}
411
412impl Properties {
413 pub fn new() -> Properties {
415 Default::default()
416 }
417
418 pub fn len(&self) -> usize {
420 self.data.keys_len()
421 }
422
423 pub fn is_empty(&self) -> bool {
425 self.data.is_empty()
426 }
427
428 pub fn iter(&self) -> PropertyIter {
430 PropertyIter {
431 inner: self.data.iter(),
432 }
433 }
434
435 pub fn iter_mut(&mut self) -> PropertyIterMut {
437 PropertyIterMut {
438 inner: self.data.iter_mut(),
439 }
440 }
441
442 pub fn contains_key<S: AsRef<str>>(&self, s: S) -> bool {
444 self.data.contains_key(property_get_key!(s.as_ref()))
445 }
446
447 pub fn insert<K, V>(&mut self, k: K, v: V)
449 where
450 K: Into<String>,
451 V: Into<String>,
452 {
453 self.data.insert(property_insert_key!(k.into()), v.into());
454 }
455
456 pub fn append<K, V>(&mut self, k: K, v: V)
458 where
459 K: Into<String>,
460 V: Into<String>,
461 {
462 self.data.append(property_insert_key!(k.into()), v.into());
463 }
464
465 pub fn get<S: AsRef<str>>(&self, s: S) -> Option<&str> {
467 self.data.get(property_get_key!(s.as_ref())).map(|v| v.as_str())
468 }
469
470 pub fn get_all<S: AsRef<str>>(&self, s: S) -> impl DoubleEndedIterator<Item = &str> {
472 self.data.get_all(property_get_key!(s.as_ref())).map(|v| v.as_str())
473 }
474
475 pub fn remove<S: AsRef<str>>(&mut self, s: S) -> Option<String> {
477 self.data.remove(property_get_key!(s.as_ref()))
478 }
479
480 pub fn remove_all<S: AsRef<str>>(&mut self, s: S) -> impl DoubleEndedIterator<Item = String> + '_ {
482 self.data.remove_all(property_get_key!(s.as_ref()))
483 }
484
485 fn get_mut<S: AsRef<str>>(&mut self, s: S) -> Option<&mut str> {
486 self.data.get_mut(property_get_key!(s.as_ref())).map(|v| v.as_mut_str())
487 }
488}
489
490impl<S: AsRef<str>> Index<S> for Properties {
491 type Output = str;
492
493 fn index(&self, index: S) -> &str {
494 let s = index.as_ref();
495 match self.get(s) {
496 Some(p) => p,
497 None => panic!("Key `{}` does not exist", s),
498 }
499 }
500}
501
502pub struct PropertyIter<'a> {
503 inner: Iter<'a, PropertyKey, String>,
504}
505
506impl<'a> Iterator for PropertyIter<'a> {
507 type Item = (&'a str, &'a str);
508
509 fn next(&mut self) -> Option<Self::Item> {
510 self.inner.next().map(|(k, v)| (k.as_ref(), v.as_ref()))
511 }
512
513 fn size_hint(&self) -> (usize, Option<usize>) {
514 self.inner.size_hint()
515 }
516}
517
518impl DoubleEndedIterator for PropertyIter<'_> {
519 fn next_back(&mut self) -> Option<Self::Item> {
520 self.inner.next_back().map(|(k, v)| (k.as_ref(), v.as_ref()))
521 }
522}
523
524pub struct PropertyIterMut<'a> {
526 inner: IterMut<'a, PropertyKey, String>,
527}
528
529impl<'a> Iterator for PropertyIterMut<'a> {
530 type Item = (&'a str, &'a mut String);
531
532 fn next(&mut self) -> Option<Self::Item> {
533 self.inner.next().map(|(k, v)| (k.as_ref(), v))
534 }
535
536 fn size_hint(&self) -> (usize, Option<usize>) {
537 self.inner.size_hint()
538 }
539}
540
541impl DoubleEndedIterator for PropertyIterMut<'_> {
542 fn next_back(&mut self) -> Option<Self::Item> {
543 self.inner.next_back().map(|(k, v)| (k.as_ref(), v))
544 }
545}
546
547pub struct PropertiesIntoIter {
548 inner: IntoIter<PropertyKey, String>,
549}
550
551impl Iterator for PropertiesIntoIter {
552 type Item = (String, String);
553
554 #[cfg_attr(not(feature = "case-insensitive"), allow(clippy::useless_conversion))]
555 fn next(&mut self) -> Option<Self::Item> {
556 self.inner.next().map(|(k, v)| (k.into(), v))
557 }
558
559 fn size_hint(&self) -> (usize, Option<usize>) {
560 self.inner.size_hint()
561 }
562}
563
564impl DoubleEndedIterator for PropertiesIntoIter {
565 #[cfg_attr(not(feature = "case-insensitive"), allow(clippy::useless_conversion))]
566 fn next_back(&mut self) -> Option<Self::Item> {
567 self.inner.next_back().map(|(k, v)| (k.into(), v))
568 }
569}
570
571impl<'a> IntoIterator for &'a Properties {
572 type IntoIter = PropertyIter<'a>;
573 type Item = (&'a str, &'a str);
574
575 fn into_iter(self) -> Self::IntoIter {
576 self.iter()
577 }
578}
579
580impl<'a> IntoIterator for &'a mut Properties {
581 type IntoIter = PropertyIterMut<'a>;
582 type Item = (&'a str, &'a mut String);
583
584 fn into_iter(self) -> Self::IntoIter {
585 self.iter_mut()
586 }
587}
588
589impl IntoIterator for Properties {
590 type IntoIter = PropertiesIntoIter;
591 type Item = (String, String);
592
593 fn into_iter(self) -> Self::IntoIter {
594 PropertiesIntoIter {
595 inner: self.data.into_iter(),
596 }
597 }
598}
599
600pub struct SectionVacantEntry<'a> {
602 inner: VacantEntry<'a, SectionKey, Properties>,
603}
604
605impl<'a> SectionVacantEntry<'a> {
606 pub fn insert(self, value: Properties) -> &'a mut Properties {
608 self.inner.insert(value)
609 }
610}
611
612pub struct SectionOccupiedEntry<'a> {
614 inner: OccupiedEntry<'a, SectionKey, Properties>,
615}
616
617impl<'a> SectionOccupiedEntry<'a> {
618 pub fn into_mut(self) -> &'a mut Properties {
620 self.inner.into_mut()
621 }
622
623 pub fn append(&mut self, prop: Properties) {
625 self.inner.append(prop);
626 }
627
628 fn last_mut(&'a mut self) -> &'a mut Properties {
629 self.inner
630 .iter_mut()
631 .next_back()
632 .expect("occupied section shouldn't have 0 property")
633 }
634}
635
636pub enum SectionEntry<'a> {
638 Vacant(SectionVacantEntry<'a>),
639 Occupied(SectionOccupiedEntry<'a>),
640}
641
642impl<'a> SectionEntry<'a> {
643 pub fn or_insert(self, properties: Properties) -> &'a mut Properties {
645 match self {
646 SectionEntry::Occupied(e) => e.into_mut(),
647 SectionEntry::Vacant(e) => e.insert(properties),
648 }
649 }
650
651 pub fn or_insert_with<F: FnOnce() -> Properties>(self, default: F) -> &'a mut Properties {
653 match self {
654 SectionEntry::Occupied(e) => e.into_mut(),
655 SectionEntry::Vacant(e) => e.insert(default()),
656 }
657 }
658}
659
660impl<'a> From<Entry<'a, SectionKey, Properties>> for SectionEntry<'a> {
661 fn from(e: Entry<'a, SectionKey, Properties>) -> SectionEntry<'a> {
662 match e {
663 Entry::Occupied(inner) => SectionEntry::Occupied(SectionOccupiedEntry { inner }),
664 Entry::Vacant(inner) => SectionEntry::Vacant(SectionVacantEntry { inner }),
665 }
666 }
667}
668
669#[derive(Debug, Clone)]
671pub struct Ini {
672 sections: ListOrderedMultimap<SectionKey, Properties>,
673}
674
675impl Ini {
676 pub fn new() -> Ini {
678 Default::default()
679 }
680
681 pub fn with_section<S>(&mut self, section: Option<S>) -> SectionSetter
683 where
684 S: Into<String>,
685 {
686 SectionSetter::new(self, section.map(Into::into))
687 }
688
689 pub fn with_general_section(&mut self) -> SectionSetter {
691 self.with_section(None::<String>)
692 }
693
694 pub fn general_section(&self) -> &Properties {
696 self.section(None::<String>)
697 .expect("There is no general section in this Ini")
698 }
699
700 pub fn general_section_mut(&mut self) -> &mut Properties {
702 self.section_mut(None::<String>)
703 .expect("There is no general section in this Ini")
704 }
705
706 pub fn section<S>(&self, name: Option<S>) -> Option<&Properties>
708 where
709 S: Into<String>,
710 {
711 self.sections.get(§ion_key!(name))
712 }
713
714 pub fn section_mut<S>(&mut self, name: Option<S>) -> Option<&mut Properties>
716 where
717 S: Into<String>,
718 {
719 self.sections.get_mut(§ion_key!(name))
720 }
721
722 pub fn section_all<S>(&self, name: Option<S>) -> impl DoubleEndedIterator<Item = &Properties>
724 where
725 S: Into<String>,
726 {
727 self.sections.get_all(§ion_key!(name))
728 }
729
730 pub fn section_all_mut<S>(&mut self, name: Option<S>) -> impl DoubleEndedIterator<Item = &mut Properties>
732 where
733 S: Into<String>,
734 {
735 self.sections.get_all_mut(§ion_key!(name))
736 }
737
738 #[cfg(not(feature = "case-insensitive"))]
740 pub fn entry(&mut self, name: Option<String>) -> SectionEntry<'_> {
741 SectionEntry::from(self.sections.entry(name))
742 }
743
744 #[cfg(feature = "case-insensitive")]
746 pub fn entry(&mut self, name: Option<String>) -> SectionEntry<'_> {
747 SectionEntry::from(self.sections.entry(name.map(UniCase::from)))
748 }
749
750 pub fn clear(&mut self) {
752 self.sections.clear()
753 }
754
755 pub fn sections(&self) -> impl DoubleEndedIterator<Item = Option<&str>> {
757 self.sections.keys().map(|s| s.as_ref().map(AsRef::as_ref))
758 }
759
760 pub fn set_to<S>(&mut self, section: Option<S>, key: String, value: String)
762 where
763 S: Into<String>,
764 {
765 self.with_section(section).set(key, value);
766 }
767
768 pub fn get_from<'a, S>(&'a self, section: Option<S>, key: &str) -> Option<&'a str>
779 where
780 S: Into<String>,
781 {
782 self.sections.get(§ion_key!(section)).and_then(|prop| prop.get(key))
783 }
784
785 pub fn get_from_or<'a, S>(&'a self, section: Option<S>, key: &str, default: &'a str) -> &'a str
796 where
797 S: Into<String>,
798 {
799 self.get_from(section, key).unwrap_or(default)
800 }
801
802 pub fn get_from_mut<'a, S>(&'a mut self, section: Option<S>, key: &str) -> Option<&'a mut str>
804 where
805 S: Into<String>,
806 {
807 self.sections
808 .get_mut(§ion_key!(section))
809 .and_then(|prop| prop.get_mut(key))
810 }
811
812 pub fn delete<S>(&mut self, section: Option<S>) -> Option<Properties>
814 where
815 S: Into<String>,
816 {
817 let key = section_key!(section);
818 self.sections.remove(&key)
819 }
820
821 pub fn delete_from<S>(&mut self, section: Option<S>, key: &str) -> Option<String>
823 where
824 S: Into<String>,
825 {
826 self.section_mut(section).and_then(|prop| prop.remove(key))
827 }
828
829 pub fn len(&self) -> usize {
831 self.sections.keys_len()
832 }
833
834 pub fn is_empty(&self) -> bool {
836 self.sections.is_empty()
837 }
838}
839
840impl Default for Ini {
841 fn default() -> Self {
844 let mut result = Ini {
845 sections: Default::default(),
846 };
847
848 result.sections.insert(None, Default::default());
849
850 result
851 }
852}
853
854impl<S: Into<String>> Index<Option<S>> for Ini {
855 type Output = Properties;
856
857 fn index(&self, index: Option<S>) -> &Properties {
858 match self.section(index) {
859 Some(p) => p,
860 None => panic!("Section does not exist"),
861 }
862 }
863}
864
865impl<S: Into<String>> IndexMut<Option<S>> for Ini {
866 fn index_mut(&mut self, index: Option<S>) -> &mut Properties {
867 match self.section_mut(index) {
868 Some(p) => p,
869 None => panic!("Section does not exist"),
870 }
871 }
872}
873
874impl<'q> Index<&'q str> for Ini {
875 type Output = Properties;
876
877 fn index<'a>(&'a self, index: &'q str) -> &'a Properties {
878 match self.section(Some(index)) {
879 Some(p) => p,
880 None => panic!("Section `{}` does not exist", index),
881 }
882 }
883}
884
885impl<'q> IndexMut<&'q str> for Ini {
886 fn index_mut<'a>(&'a mut self, index: &'q str) -> &'a mut Properties {
887 match self.section_mut(Some(index)) {
888 Some(p) => p,
889 None => panic!("Section `{}` does not exist", index),
890 }
891 }
892}
893
894impl Ini {
895 pub fn write_to_file<P: AsRef<Path>>(&self, filename: P) -> io::Result<()> {
897 self.write_to_file_policy(filename, EscapePolicy::Basics)
898 }
899
900 pub fn write_to_file_policy<P: AsRef<Path>>(&self, filename: P, policy: EscapePolicy) -> io::Result<()> {
902 let mut file = OpenOptions::new()
903 .write(true)
904 .truncate(true)
905 .create(true)
906 .open(filename.as_ref())?;
907 self.write_to_policy(&mut file, policy)
908 }
909
910 pub fn write_to_file_opt<P: AsRef<Path>>(&self, filename: P, opt: WriteOption) -> io::Result<()> {
912 let mut file = OpenOptions::new()
913 .write(true)
914 .truncate(true)
915 .create(true)
916 .open(filename.as_ref())?;
917 self.write_to_opt(&mut file, opt)
918 }
919
920 pub fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
922 self.write_to_opt(writer, Default::default())
923 }
924
925 pub fn write_to_policy<W: Write>(&self, writer: &mut W, policy: EscapePolicy) -> io::Result<()> {
927 self.write_to_opt(
928 writer,
929 WriteOption {
930 escape_policy: policy,
931 ..Default::default()
932 },
933 )
934 }
935
936 pub fn write_to_opt<W: Write>(&self, writer: &mut W, opt: WriteOption) -> io::Result<()> {
938 let mut firstline = true;
939
940 for (section, props) in &self.sections {
941 if !props.data.is_empty() {
942 if firstline {
943 firstline = false;
944 } else {
945 writer.write_all(opt.line_separator.as_str().as_bytes())?;
947 }
948 }
949
950 if let Some(ref section) = *section {
951 write!(
952 writer,
953 "[{}]{}",
954 escape_str(§ion[..], opt.escape_policy),
955 opt.line_separator
956 )?;
957 }
958 for (k, v) in props.iter() {
959 let k_str = escape_str(k, opt.escape_policy);
960 let v_str = escape_str(v, opt.escape_policy);
961 write!(writer, "{}{}{}{}", k_str, opt.kv_separator, v_str, opt.line_separator)?;
962 }
963 }
964 Ok(())
965 }
966}
967
968impl Ini {
969 pub fn load_from_str(buf: &str) -> Result<Ini, ParseError> {
971 Ini::load_from_str_opt(buf, ParseOption::default())
972 }
973
974 pub fn load_from_str_noescape(buf: &str) -> Result<Ini, ParseError> {
976 Ini::load_from_str_opt(
977 buf,
978 ParseOption {
979 enabled_escape: false,
980 ..ParseOption::default()
981 },
982 )
983 }
984
985 pub fn load_from_str_opt(buf: &str, opt: ParseOption) -> Result<Ini, ParseError> {
987 let mut parser = Parser::new(buf.chars(), opt);
988 parser.parse()
989 }
990
991 pub fn read_from<R: Read>(reader: &mut R) -> Result<Ini, Error> {
993 Ini::read_from_opt(reader, ParseOption::default())
994 }
995
996 pub fn read_from_noescape<R: Read>(reader: &mut R) -> Result<Ini, Error> {
998 Ini::read_from_opt(
999 reader,
1000 ParseOption {
1001 enabled_escape: false,
1002 ..ParseOption::default()
1003 },
1004 )
1005 }
1006
1007 pub fn read_from_opt<R: Read>(reader: &mut R, opt: ParseOption) -> Result<Ini, Error> {
1009 let mut s = String::new();
1010 reader.read_to_string(&mut s).map_err(Error::Io)?;
1011 let mut parser = Parser::new(s.chars(), opt);
1012 match parser.parse() {
1013 Err(e) => Err(Error::Parse(e)),
1014 Ok(success) => Ok(success),
1015 }
1016 }
1017
1018 pub fn load_from_file<P: AsRef<Path>>(filename: P) -> Result<Ini, Error> {
1020 Ini::load_from_file_opt(filename, ParseOption::default())
1021 }
1022
1023 pub fn load_from_file_noescape<P: AsRef<Path>>(filename: P) -> Result<Ini, Error> {
1025 Ini::load_from_file_opt(
1026 filename,
1027 ParseOption {
1028 enabled_escape: false,
1029 ..ParseOption::default()
1030 },
1031 )
1032 }
1033
1034 pub fn load_from_file_opt<P: AsRef<Path>>(filename: P, opt: ParseOption) -> Result<Ini, Error> {
1036 let mut reader = match File::open(filename.as_ref()) {
1037 Err(e) => {
1038 return Err(Error::Io(e));
1039 }
1040 Ok(r) => r,
1041 };
1042
1043 let mut with_bom = false;
1044
1045 let mut bom = [0u8; 3];
1048 if reader.read_exact(&mut bom).is_ok() && &bom == b"\xEF\xBB\xBF" {
1049 with_bom = true;
1050 }
1051
1052 if !with_bom {
1053 reader.seek(SeekFrom::Start(0))?;
1055 }
1056
1057 Ini::read_from_opt(&mut reader, opt)
1058 }
1059}
1060
1061pub struct SectionIter<'a> {
1063 inner: Iter<'a, SectionKey, Properties>,
1064}
1065
1066impl<'a> Iterator for SectionIter<'a> {
1067 type Item = (Option<&'a str>, &'a Properties);
1068
1069 fn next(&mut self) -> Option<Self::Item> {
1070 self.inner.next().map(|(k, v)| (k.as_ref().map(|s| s.as_str()), v))
1071 }
1072
1073 fn size_hint(&self) -> (usize, Option<usize>) {
1074 self.inner.size_hint()
1075 }
1076}
1077
1078impl DoubleEndedIterator for SectionIter<'_> {
1079 fn next_back(&mut self) -> Option<Self::Item> {
1080 self.inner.next_back().map(|(k, v)| (k.as_ref().map(|s| s.as_str()), v))
1081 }
1082}
1083
1084pub struct SectionIterMut<'a> {
1086 inner: IterMut<'a, SectionKey, Properties>,
1087}
1088
1089impl<'a> Iterator for SectionIterMut<'a> {
1090 type Item = (Option<&'a str>, &'a mut Properties);
1091
1092 fn next(&mut self) -> Option<Self::Item> {
1093 self.inner.next().map(|(k, v)| (k.as_ref().map(|s| s.as_str()), v))
1094 }
1095
1096 fn size_hint(&self) -> (usize, Option<usize>) {
1097 self.inner.size_hint()
1098 }
1099}
1100
1101impl DoubleEndedIterator for SectionIterMut<'_> {
1102 fn next_back(&mut self) -> Option<Self::Item> {
1103 self.inner.next_back().map(|(k, v)| (k.as_ref().map(|s| s.as_str()), v))
1104 }
1105}
1106
1107pub struct SectionIntoIter {
1109 inner: IntoIter<SectionKey, Properties>,
1110}
1111
1112impl Iterator for SectionIntoIter {
1113 type Item = (SectionKey, Properties);
1114
1115 fn next(&mut self) -> Option<Self::Item> {
1116 self.inner.next()
1117 }
1118
1119 fn size_hint(&self) -> (usize, Option<usize>) {
1120 self.inner.size_hint()
1121 }
1122}
1123
1124impl DoubleEndedIterator for SectionIntoIter {
1125 fn next_back(&mut self) -> Option<Self::Item> {
1126 self.inner.next_back()
1127 }
1128}
1129
1130impl<'a> Ini {
1131 pub fn iter(&'a self) -> SectionIter<'a> {
1133 SectionIter {
1134 inner: self.sections.iter(),
1135 }
1136 }
1137
1138 #[deprecated(note = "Use `iter_mut` instead!")]
1140 pub fn mut_iter(&'a mut self) -> SectionIterMut<'a> {
1141 self.iter_mut()
1142 }
1143
1144 pub fn iter_mut(&'a mut self) -> SectionIterMut<'a> {
1146 SectionIterMut {
1147 inner: self.sections.iter_mut(),
1148 }
1149 }
1150}
1151
1152impl<'a> IntoIterator for &'a Ini {
1153 type IntoIter = SectionIter<'a>;
1154 type Item = (Option<&'a str>, &'a Properties);
1155
1156 fn into_iter(self) -> Self::IntoIter {
1157 self.iter()
1158 }
1159}
1160
1161impl<'a> IntoIterator for &'a mut Ini {
1162 type IntoIter = SectionIterMut<'a>;
1163 type Item = (Option<&'a str>, &'a mut Properties);
1164
1165 fn into_iter(self) -> Self::IntoIter {
1166 self.iter_mut()
1167 }
1168}
1169
1170impl IntoIterator for Ini {
1171 type IntoIter = SectionIntoIter;
1172 type Item = (SectionKey, Properties);
1173
1174 fn into_iter(self) -> Self::IntoIter {
1175 SectionIntoIter {
1176 inner: self.sections.into_iter(),
1177 }
1178 }
1179}
1180
1181struct Parser<'a> {
1183 ch: Option<char>,
1184 rdr: Chars<'a>,
1185 line: usize,
1186 col: usize,
1187 opt: ParseOption,
1188}
1189
1190#[derive(Debug)]
1191pub struct ParseError {
1193 pub line: usize,
1194 pub col: usize,
1195 pub msg: Cow<'static, str>,
1196}
1197
1198impl Display for ParseError {
1199 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1200 write!(f, "{}:{} {}", self.line, self.col, self.msg)
1201 }
1202}
1203
1204impl error::Error for ParseError {}
1205
1206#[derive(Debug)]
1208pub enum Error {
1209 Io(io::Error),
1210 Parse(ParseError),
1211}
1212
1213impl Display for Error {
1214 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1215 match *self {
1216 Error::Io(ref err) => err.fmt(f),
1217 Error::Parse(ref err) => err.fmt(f),
1218 }
1219 }
1220}
1221
1222impl error::Error for Error {
1223 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
1224 match *self {
1225 Error::Io(ref err) => err.source(),
1226 Error::Parse(ref err) => err.source(),
1227 }
1228 }
1229}
1230
1231impl From<io::Error> for Error {
1232 fn from(err: io::Error) -> Self {
1233 Error::Io(err)
1234 }
1235}
1236
1237impl<'a> Parser<'a> {
1238 pub fn new(rdr: Chars<'a>, opt: ParseOption) -> Parser<'a> {
1240 let mut p = Parser {
1241 ch: None,
1242 line: 0,
1243 col: 0,
1244 rdr,
1245 opt,
1246 };
1247 p.bump();
1248 p
1249 }
1250
1251 fn bump(&mut self) {
1252 self.ch = self.rdr.next();
1253 match self.ch {
1254 Some('\n') => {
1255 self.line += 1;
1256 self.col = 0;
1257 }
1258 Some(..) => {
1259 self.col += 1;
1260 }
1261 None => {}
1262 }
1263 }
1264
1265 #[cold]
1266 #[inline(never)]
1267 fn error<U, M: Into<Cow<'static, str>>>(&self, msg: M) -> Result<U, ParseError> {
1268 Err(ParseError {
1269 line: self.line + 1,
1270 col: self.col + 1,
1271 msg: msg.into(),
1272 })
1273 }
1274
1275 #[cold]
1276 fn eof_error(&self, expecting: &[Option<char>]) -> Result<char, ParseError> {
1277 self.error(format!("expecting \"{:?}\" but found EOF.", expecting))
1278 }
1279
1280 fn char_or_eof(&self, expecting: &[Option<char>]) -> Result<char, ParseError> {
1281 match self.ch {
1282 Some(ch) => Ok(ch),
1283 None => self.eof_error(expecting),
1284 }
1285 }
1286
1287 fn parse_whitespace(&mut self) {
1289 while let Some(c) = self.ch {
1290 if !c.is_whitespace() && c != '\n' && c != '\t' && c != '\r' {
1291 break;
1292 }
1293 self.bump();
1294 }
1295 }
1296
1297 fn parse_whitespace_except_line_break(&mut self) {
1299 while let Some(c) = self.ch {
1300 if (c == '\n' || c == '\r' || !c.is_whitespace()) && c != '\t' {
1301 break;
1302 }
1303 self.bump();
1304 }
1305 }
1306
1307 pub fn parse(&mut self) -> Result<Ini, ParseError> {
1309 let mut result = Ini::new();
1310 let mut curkey: String = "".into();
1311 let mut cursec: Option<String> = None;
1312
1313 self.parse_whitespace();
1314 while let Some(cur_ch) = self.ch {
1315 match cur_ch {
1316 ';' | '#' => {
1317 if cfg!(not(feature = "inline-comment")) {
1318 if self.col > 1 {
1322 return self.error("doesn't support inline comment");
1323 }
1324 }
1325
1326 self.parse_comment();
1327 }
1328 '[' => match self.parse_section() {
1329 Ok(mut sec) => {
1330 sec.trim_in_place();
1331 cursec = Some(sec);
1332 match result.entry(cursec.clone()) {
1333 SectionEntry::Vacant(v) => {
1334 v.insert(Default::default());
1335 }
1336 SectionEntry::Occupied(mut o) => {
1337 o.append(Default::default());
1338 }
1339 }
1340 }
1341 Err(e) => return Err(e),
1342 },
1343 '=' | ':' => {
1344 if (curkey[..]).is_empty() {
1345 return self.error("missing key");
1346 }
1347 match self.parse_val() {
1348 Ok(mut mval) => {
1349 mval.trim_in_place();
1350 match result.entry(cursec.clone()) {
1351 SectionEntry::Vacant(v) => {
1352 let mut prop = Properties::new();
1354 prop.insert(curkey, mval);
1355 v.insert(prop);
1356 }
1357 SectionEntry::Occupied(mut o) => {
1358 o.last_mut().append(curkey, mval);
1360 }
1361 }
1362 curkey = "".into();
1363 }
1364 Err(e) => return Err(e),
1365 }
1366 }
1367 _ => match self.parse_key() {
1368 Ok(mut mkey) => {
1369 mkey.trim_in_place();
1370 curkey = mkey;
1371 }
1372 Err(e) => return Err(e),
1373 },
1374 }
1375
1376 self.parse_whitespace();
1377 }
1378
1379 Ok(result)
1380 }
1381
1382 fn parse_comment(&mut self) {
1383 while let Some(c) = self.ch {
1384 self.bump();
1385 if c == '\n' {
1386 break;
1387 }
1388 }
1389 }
1390
1391 fn parse_str_until(&mut self, endpoint: &[Option<char>], check_inline_comment: bool) -> Result<String, ParseError> {
1392 let mut result: String = String::new();
1393
1394 while !endpoint.contains(&self.ch) {
1395 match self.char_or_eof(endpoint)? {
1396 #[cfg(feature = "inline-comment")]
1397 space if check_inline_comment && (space == ' ' || space == '\t') => {
1398 self.bump();
1399
1400 match self.ch {
1401 Some('#') | Some(';') => {
1402 break;
1404 }
1405 Some(_) => {
1406 result.push(space);
1407 continue;
1408 }
1409 None => {
1410 result.push(space);
1411 }
1412 }
1413 }
1414 '\\' if self.opt.enabled_escape => {
1415 self.bump();
1416 match self.char_or_eof(endpoint)? {
1417 '0' => result.push('\0'),
1418 'a' => result.push('\x07'),
1419 'b' => result.push('\x08'),
1420 't' => result.push('\t'),
1421 'r' => result.push('\r'),
1422 'n' => result.push('\n'),
1423 '\n' => (),
1424 'x' => {
1425 let mut code: String = String::with_capacity(4);
1427 for _ in 0..4 {
1428 self.bump();
1429 let ch = self.char_or_eof(endpoint)?;
1430 if ch == '\\' {
1431 self.bump();
1432 if self.ch != Some('\n') {
1433 return self.error(format!(
1434 "expecting \"\\\\n\" but \
1435 found \"{:?}\".",
1436 self.ch
1437 ));
1438 }
1439 }
1440
1441 code.push(ch);
1442 }
1443 let r = u32::from_str_radix(&code[..], 16);
1444 match r.ok().and_then(char::from_u32) {
1445 Some(ch) => result.push(ch),
1446 None => return self.error("unknown character in \\xHH form"),
1447 }
1448 }
1449 c => result.push(c),
1450 }
1451 }
1452 ch => {
1453 result.push(ch);
1454 }
1455 }
1456 self.bump();
1457 }
1458
1459 let _ = check_inline_comment;
1460 Ok(result)
1461 }
1462
1463 fn parse_section(&mut self) -> Result<String, ParseError> {
1464 cfg_if! {
1465 if #[cfg(feature = "brackets-in-section-names")] {
1466 self.bump();
1468
1469 let mut s = match self.parse_str_until(&[Some('\r'), Some('\n')], cfg!(feature = "inline-comment")) {
1470 Ok(r) => r,
1471 Err(err) => return Err(err)
1472 };
1473
1474 #[cfg(feature = "inline-comment")]
1476 if matches!(self.ch, Some('#') | Some(';')) {
1477 self.parse_comment();
1478 }
1479
1480 let tr = s.trim_end_matches(|c| c == ' ' || c == '\t');
1481 if !tr.ends_with(']') {
1482 return self.error("section must be ended with ']'");
1483 }
1484
1485 s.truncate(tr.len() - 1);
1486 Ok(s)
1487 } else {
1488 self.bump();
1490 let sec = self.parse_str_until(&[Some(']')], false)?;
1491 if let Some(']') = self.ch {
1492 self.bump();
1493 }
1494
1495 #[cfg(feature = "inline-comment")]
1497 if matches!(self.ch, Some('#') | Some(';')) {
1498 self.parse_comment();
1499 }
1500
1501 Ok(sec)
1502 }
1503 }
1504 }
1505
1506 fn parse_key(&mut self) -> Result<String, ParseError> {
1507 self.parse_str_until(&[Some('='), Some(':')], false)
1508 }
1509
1510 fn parse_val(&mut self) -> Result<String, ParseError> {
1511 self.bump();
1512 self.parse_whitespace_except_line_break();
1514
1515 match self.ch {
1516 None => Ok(String::new()),
1517 Some('"') if self.opt.enabled_quote => {
1518 self.bump();
1519 self.parse_str_until(&[Some('"')], false).and_then(|s| {
1520 self.bump(); self.parse_str_until_eol(cfg!(feature = "inline-comment"))
1523 .map(|x| s + &x)
1524 })
1525 }
1526 Some('\'') if self.opt.enabled_quote => {
1527 self.bump();
1528 self.parse_str_until(&[Some('\'')], false).and_then(|s| {
1529 self.bump(); self.parse_str_until_eol(cfg!(feature = "inline-comment"))
1532 .map(|x| s + &x)
1533 })
1534 }
1535 _ => self.parse_str_until_eol(cfg!(feature = "inline-comment")),
1536 }
1537 }
1538
1539 #[inline]
1540 fn parse_str_until_eol(&mut self, check_inline_comment: bool) -> Result<String, ParseError> {
1541 let r = self.parse_str_until(&[Some('\n'), Some('\r'), None], check_inline_comment)?;
1542
1543 #[cfg(feature = "inline-comment")]
1544 if check_inline_comment && matches!(self.ch, Some('#') | Some(';')) {
1545 self.parse_comment();
1546 }
1547
1548 Ok(r)
1549 }
1550}
1551
1552#[cfg(test)]
1555mod test {
1556 use std::env::temp_dir;
1557
1558 use super::*;
1559
1560 #[test]
1561 fn property_replace() {
1562 let mut props = Properties::new();
1563 props.insert("k1", "v1");
1564
1565 assert_eq!(Some("v1"), props.get("k1"));
1566 let res = props.get_all("k1").collect::<Vec<&str>>();
1567 assert_eq!(res, vec!["v1"]);
1568
1569 props.insert("k1", "v2");
1570 assert_eq!(Some("v2"), props.get("k1"));
1571
1572 let res = props.get_all("k1").collect::<Vec<&str>>();
1573 assert_eq!(res, vec!["v2"]);
1574 }
1575
1576 #[test]
1577 fn property_get_vec() {
1578 let mut props = Properties::new();
1579 props.append("k1", "v1");
1580
1581 assert_eq!(Some("v1"), props.get("k1"));
1582
1583 props.append("k1", "v2");
1584
1585 assert_eq!(Some("v1"), props.get("k1"));
1586
1587 let res = props.get_all("k1").collect::<Vec<&str>>();
1588 assert_eq!(res, vec!["v1", "v2"]);
1589
1590 let res = props.get_all("k2").collect::<Vec<&str>>();
1591 assert!(res.is_empty());
1592 }
1593
1594 #[test]
1595 fn property_remove() {
1596 let mut props = Properties::new();
1597 props.append("k1", "v1");
1598 props.append("k1", "v2");
1599
1600 let res = props.remove_all("k1").collect::<Vec<String>>();
1601 assert_eq!(res, vec!["v1", "v2"]);
1602 assert!(!props.contains_key("k1"));
1603 }
1604
1605 #[test]
1606 fn load_from_str_with_empty_general_section() {
1607 let input = "[sec1]\nkey1=val1\n";
1608 let opt = Ini::load_from_str(input);
1609 assert!(opt.is_ok());
1610
1611 let mut output = opt.unwrap();
1612 assert_eq!(output.len(), 2);
1613
1614 assert!(output.general_section().is_empty());
1615 assert!(output.general_section_mut().is_empty());
1616
1617 let props1 = output.section(None::<String>).unwrap();
1618 assert!(props1.is_empty());
1619 let props2 = output.section(Some("sec1")).unwrap();
1620 assert_eq!(props2.len(), 1);
1621 assert_eq!(props2.get("key1"), Some("val1"));
1622 }
1623
1624 #[test]
1625 fn load_from_str_with_empty_input() {
1626 let input = "";
1627 let opt = Ini::load_from_str(input);
1628 assert!(opt.is_ok());
1629
1630 let mut output = opt.unwrap();
1631 assert!(output.general_section().is_empty());
1632 assert!(output.general_section_mut().is_empty());
1633 assert_eq!(output.len(), 1);
1634 }
1635
1636 #[test]
1637 fn load_from_str_with_empty_lines() {
1638 let input = "\n\n\n";
1639 let opt = Ini::load_from_str(input);
1640 assert!(opt.is_ok());
1641
1642 let mut output = opt.unwrap();
1643 assert!(output.general_section().is_empty());
1644 assert!(output.general_section_mut().is_empty());
1645 assert_eq!(output.len(), 1);
1646 }
1647
1648 #[test]
1649 #[cfg(not(feature = "brackets-in-section-names"))]
1650 fn load_from_str_with_valid_input() {
1651 let input = "[sec1]\nkey1=val1\nkey2=377\n[sec2]foo=bar\n";
1652 let opt = Ini::load_from_str(input);
1653 assert!(opt.is_ok());
1654
1655 let output = opt.unwrap();
1656 assert_eq!(output.len(), 3);
1658 assert!(output.section(Some("sec1")).is_some());
1659
1660 let sec1 = output.section(Some("sec1")).unwrap();
1661 assert_eq!(sec1.len(), 2);
1662 let key1: String = "key1".into();
1663 assert!(sec1.contains_key(&key1));
1664 let key2: String = "key2".into();
1665 assert!(sec1.contains_key(&key2));
1666 let val1: String = "val1".into();
1667 assert_eq!(sec1[&key1], val1);
1668 let val2: String = "377".into();
1669 assert_eq!(sec1[&key2], val2);
1670 }
1671
1672 #[test]
1673 #[cfg(feature = "brackets-in-section-names")]
1674 fn load_from_str_with_valid_input() {
1675 let input = "[sec1]\nkey1=val1\nkey2=377\n[sec2]\nfoo=bar\n";
1676 let opt = Ini::load_from_str(input);
1677 assert!(opt.is_ok());
1678
1679 let output = opt.unwrap();
1680 assert_eq!(output.len(), 3);
1682 assert!(output.section(Some("sec1")).is_some());
1683
1684 let sec1 = output.section(Some("sec1")).unwrap();
1685 assert_eq!(sec1.len(), 2);
1686 let key1: String = "key1".into();
1687 assert!(sec1.contains_key(&key1));
1688 let key2: String = "key2".into();
1689 assert!(sec1.contains_key(&key2));
1690 let val1: String = "val1".into();
1691 assert_eq!(sec1[&key1], val1);
1692 let val2: String = "377".into();
1693 assert_eq!(sec1[&key2], val2);
1694 }
1695
1696 #[test]
1697 #[cfg(not(feature = "brackets-in-section-names"))]
1698 fn load_from_str_without_ending_newline() {
1699 let input = "[sec1]\nkey1=val1\nkey2=377\n[sec2]foo=bar";
1700 let opt = Ini::load_from_str(input);
1701 assert!(opt.is_ok());
1702 }
1703
1704 #[test]
1705 #[cfg(feature = "brackets-in-section-names")]
1706 fn load_from_str_without_ending_newline() {
1707 let input = "[sec1]\nkey1=val1\nkey2=377\n[sec2]\nfoo=bar";
1708 let opt = Ini::load_from_str(input);
1709 assert!(opt.is_ok());
1710 }
1711
1712 #[test]
1713 fn parse_error_numbers() {
1714 let invalid_input = "\n\\x";
1715 let ini = Ini::load_from_str_opt(
1716 invalid_input,
1717 ParseOption {
1718 enabled_escape: true,
1719 ..Default::default()
1720 },
1721 );
1722 assert!(ini.is_err());
1723
1724 let err = ini.unwrap_err();
1725 assert_eq!(err.line, 2);
1726 assert_eq!(err.col, 3);
1727 }
1728
1729 #[test]
1730 fn parse_comment() {
1731 let input = "; abcdefghijklmn\n";
1732 let opt = Ini::load_from_str(input);
1733 assert!(opt.is_ok());
1734 }
1735
1736 #[cfg(not(feature = "inline-comment"))]
1737 #[test]
1738 fn inline_comment_not_supported() {
1739 let input = "
1740[section name]
1741name = hello # abcdefg
1742gender = mail ; abdddd
1743";
1744 let ini = Ini::load_from_str(input).unwrap();
1745 assert_eq!(ini.get_from(Some("section name"), "name").unwrap(), "hello # abcdefg");
1746 assert_eq!(ini.get_from(Some("section name"), "gender").unwrap(), "mail ; abdddd");
1747 }
1748
1749 #[test]
1750 #[cfg_attr(not(feature = "inline-comment"), should_panic)]
1751 fn inline_comment() {
1752 let input = "
1753[section name] # comment in section line
1754name = hello # abcdefg
1755gender = mail ; abdddd
1756address = web#url ;# eeeeee
1757phone = 01234 # tab before comment
1758phone2 = 56789 # tab + space before comment
1759phone3 = 43210 # space + tab before comment
1760";
1761 let ini = Ini::load_from_str(input).unwrap();
1762 println!("{:?}", ini.section(Some("section name")));
1763 assert_eq!(ini.get_from(Some("section name"), "name").unwrap(), "hello");
1764 assert_eq!(ini.get_from(Some("section name"), "gender").unwrap(), "mail");
1765 assert_eq!(ini.get_from(Some("section name"), "address").unwrap(), "web#url");
1766 assert_eq!(ini.get_from(Some("section name"), "phone").unwrap(), "01234");
1767 assert_eq!(ini.get_from(Some("section name"), "phone2").unwrap(), "56789");
1768 assert_eq!(ini.get_from(Some("section name"), "phone3").unwrap(), "43210");
1769 }
1770
1771 #[test]
1772 fn sharp_comment() {
1773 let input = "
1774[section name]
1775name = hello
1776# abcdefg
1777";
1778 let ini = Ini::load_from_str(input).unwrap();
1779 assert_eq!(ini.get_from(Some("section name"), "name").unwrap(), "hello");
1780 }
1781
1782 #[test]
1783 fn iter() {
1784 let input = "
1785[section name]
1786name = hello # abcdefg
1787gender = mail ; abdddd
1788";
1789 let mut ini = Ini::load_from_str(input).unwrap();
1790
1791 for _ in &mut ini {}
1792 for _ in &ini {}
1793 }
1795
1796 #[test]
1797 fn colon() {
1798 let input = "
1799[section name]
1800name: hello
1801gender : mail
1802";
1803 let ini = Ini::load_from_str(input).unwrap();
1804 assert_eq!(ini.get_from(Some("section name"), "name").unwrap(), "hello");
1805 assert_eq!(ini.get_from(Some("section name"), "gender").unwrap(), "mail");
1806 }
1807
1808 #[test]
1809 fn string() {
1810 let input = "
1811[section name]
1812# This is a comment
1813Key = \"Value\"
1814";
1815 let ini = Ini::load_from_str(input).unwrap();
1816 assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value");
1817 }
1818
1819 #[test]
1820 fn string_multiline() {
1821 let input = "
1822[section name]
1823# This is a comment
1824Key = \"Value
1825Otherline\"
1826";
1827 let ini = Ini::load_from_str(input).unwrap();
1828 assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value\nOtherline");
1829 }
1830
1831 #[test]
1832 fn string_comment() {
1833 let input = "
1834[section name]
1835# This is a comment
1836Key = \"Value # This is not a comment ; at all\"
1837Stuff = Other
1838";
1839 let ini = Ini::load_from_str(input).unwrap();
1840 assert_eq!(
1841 ini.get_from(Some("section name"), "Key").unwrap(),
1842 "Value # This is not a comment ; at all"
1843 );
1844 }
1845
1846 #[test]
1847 fn string_single() {
1848 let input = "
1849[section name]
1850# This is a comment
1851Key = 'Value'
1852Stuff = Other
1853";
1854 let ini = Ini::load_from_str(input).unwrap();
1855 assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value");
1856 }
1857
1858 #[test]
1859 fn string_includes_quote() {
1860 let input = "
1861[Test]
1862Comment[tr]=İnternet'e erişin
1863Comment[uk]=Доступ до Інтернету
1864";
1865 let ini = Ini::load_from_str(input).unwrap();
1866 assert_eq!(ini.get_from(Some("Test"), "Comment[tr]").unwrap(), "İnternet'e erişin");
1867 }
1868
1869 #[test]
1870 fn string_single_multiline() {
1871 let input = "
1872[section name]
1873# This is a comment
1874Key = 'Value
1875Otherline'
1876Stuff = Other
1877";
1878 let ini = Ini::load_from_str(input).unwrap();
1879 assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value\nOtherline");
1880 }
1881
1882 #[test]
1883 fn string_single_comment() {
1884 let input = "
1885[section name]
1886# This is a comment
1887Key = 'Value # This is not a comment ; at all'
1888";
1889 let ini = Ini::load_from_str(input).unwrap();
1890 assert_eq!(
1891 ini.get_from(Some("section name"), "Key").unwrap(),
1892 "Value # This is not a comment ; at all"
1893 );
1894 }
1895
1896 #[test]
1897 fn load_from_str_with_valid_empty_input() {
1898 let input = "key1=\nkey2=val2\n";
1899 let opt = Ini::load_from_str(input);
1900 assert!(opt.is_ok());
1901
1902 let output = opt.unwrap();
1903 assert_eq!(output.len(), 1);
1904 assert!(output.section(None::<String>).is_some());
1905
1906 let sec1 = output.section(None::<String>).unwrap();
1907 assert_eq!(sec1.len(), 2);
1908 let key1: String = "key1".into();
1909 assert!(sec1.contains_key(&key1));
1910 let key2: String = "key2".into();
1911 assert!(sec1.contains_key(&key2));
1912 let val1: String = "".into();
1913 assert_eq!(sec1[&key1], val1);
1914 let val2: String = "val2".into();
1915 assert_eq!(sec1[&key2], val2);
1916 }
1917
1918 #[test]
1919 fn load_from_str_with_crlf() {
1920 let input = "key1=val1\r\nkey2=val2\r\n";
1921 let opt = Ini::load_from_str(input);
1922 assert!(opt.is_ok());
1923
1924 let output = opt.unwrap();
1925 assert_eq!(output.len(), 1);
1926 assert!(output.section(None::<String>).is_some());
1927 let sec1 = output.section(None::<String>).unwrap();
1928 assert_eq!(sec1.len(), 2);
1929 let key1: String = "key1".into();
1930 assert!(sec1.contains_key(&key1));
1931 let key2: String = "key2".into();
1932 assert!(sec1.contains_key(&key2));
1933 let val1: String = "val1".into();
1934 assert_eq!(sec1[&key1], val1);
1935 let val2: String = "val2".into();
1936 assert_eq!(sec1[&key2], val2);
1937 }
1938
1939 #[test]
1940 fn load_from_str_with_cr() {
1941 let input = "key1=val1\rkey2=val2\r";
1942 let opt = Ini::load_from_str(input);
1943 assert!(opt.is_ok());
1944
1945 let output = opt.unwrap();
1946 assert_eq!(output.len(), 1);
1947 assert!(output.section(None::<String>).is_some());
1948 let sec1 = output.section(None::<String>).unwrap();
1949 assert_eq!(sec1.len(), 2);
1950 let key1: String = "key1".into();
1951 assert!(sec1.contains_key(&key1));
1952 let key2: String = "key2".into();
1953 assert!(sec1.contains_key(&key2));
1954 let val1: String = "val1".into();
1955 assert_eq!(sec1[&key1], val1);
1956 let val2: String = "val2".into();
1957 assert_eq!(sec1[&key2], val2);
1958 }
1959
1960 #[test]
1961 #[cfg(not(feature = "brackets-in-section-names"))]
1962 fn load_from_file_with_bom() {
1963 let file_name = temp_dir().join("rust_ini_load_from_file_with_bom");
1964
1965 let file_content = b"\xEF\xBB\xBF[Test]Key=Value\n";
1966
1967 {
1968 let mut file = File::create(&file_name).expect("create");
1969 file.write_all(file_content).expect("write");
1970 }
1971
1972 let ini = Ini::load_from_file(&file_name).unwrap();
1973 assert_eq!(ini.get_from(Some("Test"), "Key"), Some("Value"));
1974 }
1975
1976 #[test]
1977 #[cfg(feature = "brackets-in-section-names")]
1978 fn load_from_file_with_bom() {
1979 let file_name = temp_dir().join("rust_ini_load_from_file_with_bom");
1980
1981 let file_content = b"\xEF\xBB\xBF[Test]\nKey=Value\n";
1982
1983 {
1984 let mut file = File::create(&file_name).expect("create");
1985 file.write_all(file_content).expect("write");
1986 }
1987
1988 let ini = Ini::load_from_file(&file_name).unwrap();
1989 assert_eq!(ini.get_from(Some("Test"), "Key"), Some("Value"));
1990 }
1991
1992 #[test]
1993 #[cfg(not(feature = "brackets-in-section-names"))]
1994 fn load_from_file_without_bom() {
1995 let file_name = temp_dir().join("rust_ini_load_from_file_without_bom");
1996
1997 let file_content = b"[Test]Key=Value\n";
1998
1999 {
2000 let mut file = File::create(&file_name).expect("create");
2001 file.write_all(file_content).expect("write");
2002 }
2003
2004 let ini = Ini::load_from_file(&file_name).unwrap();
2005 assert_eq!(ini.get_from(Some("Test"), "Key"), Some("Value"));
2006 }
2007
2008 #[test]
2009 #[cfg(feature = "brackets-in-section-names")]
2010 fn load_from_file_without_bom() {
2011 let file_name = temp_dir().join("rust_ini_load_from_file_without_bom");
2012
2013 let file_content = b"[Test]\nKey=Value\n";
2014
2015 {
2016 let mut file = File::create(&file_name).expect("create");
2017 file.write_all(file_content).expect("write");
2018 }
2019
2020 let ini = Ini::load_from_file(&file_name).unwrap();
2021 assert_eq!(ini.get_from(Some("Test"), "Key"), Some("Value"));
2022 }
2023
2024 #[test]
2025 fn get_with_non_static_key() {
2026 let input = "key1=val1\nkey2=val2\n";
2027 let opt = Ini::load_from_str(input).unwrap();
2028
2029 let sec1 = opt.section(None::<String>).unwrap();
2030
2031 let key = "key1".to_owned();
2032 sec1.get(&key).unwrap();
2033 }
2034
2035 #[test]
2036 fn load_from_str_noescape() {
2037 let input = "path=C:\\Windows\\Some\\Folder\\";
2038 let opt = Ini::load_from_str_noescape(input);
2039 assert!(opt.is_ok());
2040
2041 let output = opt.unwrap();
2042 assert_eq!(output.len(), 1);
2043 let sec = output.section(None::<String>).unwrap();
2044 assert_eq!(sec.len(), 1);
2045 assert!(sec.contains_key("path"));
2046 assert_eq!(&sec["path"], "C:\\Windows\\Some\\Folder\\");
2047 }
2048
2049 #[test]
2050 fn partial_quoting_double() {
2051 let input = "
2052[Section]
2053A=\"quote\" arg0
2054B=b";
2055
2056 let opt = Ini::load_from_str(input).unwrap();
2057 let sec = opt.section(Some("Section")).unwrap();
2058 assert_eq!(&sec["A"], "quote arg0");
2059 assert_eq!(&sec["B"], "b");
2060 }
2061
2062 #[test]
2063 fn partial_quoting_single() {
2064 let input = "
2065[Section]
2066A='quote' arg0
2067B=b";
2068
2069 let opt = Ini::load_from_str(input).unwrap();
2070 let sec = opt.section(Some("Section")).unwrap();
2071 assert_eq!(&sec["A"], "quote arg0");
2072 assert_eq!(&sec["B"], "b");
2073 }
2074
2075 #[test]
2076 fn parse_without_quote() {
2077 let input = "
2078[Desktop Entry]
2079Exec = \"/path/to/exe with space\" arg
2080";
2081
2082 let opt = Ini::load_from_str_opt(
2083 input,
2084 ParseOption {
2085 enabled_quote: false,
2086 ..ParseOption::default()
2087 },
2088 )
2089 .unwrap();
2090 let sec = opt.section(Some("Desktop Entry")).unwrap();
2091 assert_eq!(&sec["Exec"], "\"/path/to/exe with space\" arg");
2092 }
2093
2094 #[test]
2095 #[cfg(feature = "case-insensitive")]
2096 fn case_insensitive() {
2097 let input = "
2098[SecTION]
2099KeY=value
2100";
2101
2102 let ini = Ini::load_from_str(input).unwrap();
2103 let section = ini.section(Some("section")).unwrap();
2104 let val = section.get("key").unwrap();
2105 assert_eq!("value", val);
2106 }
2107
2108 #[test]
2109 fn preserve_order_section() {
2110 let input = r"
2111none2 = n2
2112[SB]
2113p2 = 2
2114[SA]
2115x2 = 2
2116[SC]
2117cd1 = x
2118[xC]
2119xd = x
2120 ";
2121
2122 let data = Ini::load_from_str(input).unwrap();
2123 let keys: Vec<Option<&str>> = data.iter().map(|(k, _)| k).collect();
2124
2125 assert_eq!(keys.len(), 5);
2126 assert_eq!(keys[0], None);
2127 assert_eq!(keys[1], Some("SB"));
2128 assert_eq!(keys[2], Some("SA"));
2129 assert_eq!(keys[3], Some("SC"));
2130 assert_eq!(keys[4], Some("xC"));
2131 }
2132
2133 #[test]
2134 fn preserve_order_property() {
2135 let input = r"
2136x2 = n2
2137x1 = n2
2138x3 = n2
2139";
2140 let data = Ini::load_from_str(input).unwrap();
2141 let section = data.general_section();
2142 let keys: Vec<&str> = section.iter().map(|(k, _)| k).collect();
2143 assert_eq!(keys, vec!["x2", "x1", "x3"]);
2144 }
2145
2146 #[test]
2147 fn preserve_order_property_in_section() {
2148 let input = r"
2149[s]
2150x2 = n2
2151xb = n2
2152a3 = n3
2153";
2154 let data = Ini::load_from_str(input).unwrap();
2155 let section = data.section(Some("s")).unwrap();
2156 let keys: Vec<&str> = section.iter().map(|(k, _)| k).collect();
2157 assert_eq!(keys, vec!["x2", "xb", "a3"])
2158 }
2159
2160 #[test]
2161 fn preserve_order_write() {
2162 let input = r"
2163x2 = n2
2164x1 = n2
2165x3 = n2
2166[s]
2167x2 = n2
2168xb = n2
2169a3 = n3
2170";
2171 let data = Ini::load_from_str(input).unwrap();
2172 let mut buf = vec![];
2173 data.write_to(&mut buf).unwrap();
2174 let new_data = Ini::load_from_str(&String::from_utf8(buf).unwrap()).unwrap();
2175
2176 let sec0 = new_data.general_section();
2177 let keys0: Vec<&str> = sec0.iter().map(|(k, _)| k).collect();
2178 assert_eq!(keys0, vec!["x2", "x1", "x3"]);
2179
2180 let sec1 = new_data.section(Some("s")).unwrap();
2181 let keys1: Vec<&str> = sec1.iter().map(|(k, _)| k).collect();
2182 assert_eq!(keys1, vec!["x2", "xb", "a3"]);
2183 }
2184
2185 #[test]
2186 fn write_new() {
2187 use std::str;
2188
2189 let ini = Ini::new();
2190
2191 let opt = WriteOption {
2192 line_separator: LineSeparator::CR,
2193 ..Default::default()
2194 };
2195 let mut buf = Vec::new();
2196 ini.write_to_opt(&mut buf, opt).unwrap();
2197
2198 assert_eq!("", str::from_utf8(&buf).unwrap());
2199 }
2200
2201 #[test]
2202 fn write_line_separator() {
2203 use std::str;
2204
2205 let mut ini = Ini::new();
2206 ini.with_section(Some("Section1"))
2207 .set("Key1", "Value")
2208 .set("Key2", "Value");
2209 ini.with_section(Some("Section2"))
2210 .set("Key1", "Value")
2211 .set("Key2", "Value");
2212
2213 {
2214 let mut buf = Vec::new();
2215 ini.write_to_opt(
2216 &mut buf,
2217 WriteOption {
2218 line_separator: LineSeparator::CR,
2219 ..Default::default()
2220 },
2221 )
2222 .unwrap();
2223
2224 assert_eq!(
2225 "[Section1]\nKey1=Value\nKey2=Value\n\n[Section2]\nKey1=Value\nKey2=Value\n",
2226 str::from_utf8(&buf).unwrap()
2227 );
2228 }
2229
2230 {
2231 let mut buf = Vec::new();
2232 ini.write_to_opt(
2233 &mut buf,
2234 WriteOption {
2235 line_separator: LineSeparator::CRLF,
2236 ..Default::default()
2237 },
2238 )
2239 .unwrap();
2240
2241 assert_eq!(
2242 "[Section1]\r\nKey1=Value\r\nKey2=Value\r\n\r\n[Section2]\r\nKey1=Value\r\nKey2=Value\r\n",
2243 str::from_utf8(&buf).unwrap()
2244 );
2245 }
2246
2247 {
2248 let mut buf = Vec::new();
2249 ini.write_to_opt(
2250 &mut buf,
2251 WriteOption {
2252 line_separator: LineSeparator::SystemDefault,
2253 ..Default::default()
2254 },
2255 )
2256 .unwrap();
2257
2258 if cfg!(windows) {
2259 assert_eq!(
2260 "[Section1]\r\nKey1=Value\r\nKey2=Value\r\n\r\n[Section2]\r\nKey1=Value\r\nKey2=Value\r\n",
2261 str::from_utf8(&buf).unwrap()
2262 );
2263 } else {
2264 assert_eq!(
2265 "[Section1]\nKey1=Value\nKey2=Value\n\n[Section2]\nKey1=Value\nKey2=Value\n",
2266 str::from_utf8(&buf).unwrap()
2267 );
2268 }
2269 }
2270 }
2271
2272 #[test]
2273 fn write_kv_separator() {
2274 use std::str;
2275
2276 let mut ini = Ini::new();
2277 ini.with_section(None::<String>)
2278 .set("Key1", "Value")
2279 .set("Key2", "Value");
2280 ini.with_section(Some("Section1"))
2281 .set("Key1", "Value")
2282 .set("Key2", "Value");
2283 ini.with_section(Some("Section2"))
2284 .set("Key1", "Value")
2285 .set("Key2", "Value");
2286
2287 let mut buf = Vec::new();
2288 ini.write_to_opt(
2289 &mut buf,
2290 WriteOption {
2291 kv_separator: " = ",
2292 ..Default::default()
2293 },
2294 )
2295 .unwrap();
2296
2297 if cfg!(windows) {
2299 assert_eq!(
2300 "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",
2301 str::from_utf8(&buf).unwrap()
2302 );
2303 } else {
2304 assert_eq!(
2305 "Key1 = Value\nKey2 = Value\n\n[Section1]\nKey1 = Value\nKey2 = Value\n\n[Section2]\nKey1 = Value\nKey2 = Value\n",
2306 str::from_utf8(&buf).unwrap()
2307 );
2308 }
2309 }
2310
2311 #[test]
2312 fn duplicate_sections() {
2313 let input = r"
2316[Peer]
2317foo = a
2318bar = b
2319
2320[Peer]
2321foo = c
2322bar = d
2323
2324[Peer]
2325foo = e
2326bar = f
2327";
2328
2329 let ini = Ini::load_from_str(input).unwrap();
2330 assert_eq!(3, ini.section_all(Some("Peer")).count());
2331
2332 let mut iter = ini.iter();
2333 let (k0, p0) = iter.next().unwrap();
2335 assert_eq!(None, k0);
2336 assert!(p0.is_empty());
2337 let (k1, p1) = iter.next().unwrap();
2338 assert_eq!(Some("Peer"), k1);
2339 assert_eq!(Some("a"), p1.get("foo"));
2340 assert_eq!(Some("b"), p1.get("bar"));
2341 let (k2, p2) = iter.next().unwrap();
2342 assert_eq!(Some("Peer"), k2);
2343 assert_eq!(Some("c"), p2.get("foo"));
2344 assert_eq!(Some("d"), p2.get("bar"));
2345 let (k3, p3) = iter.next().unwrap();
2346 assert_eq!(Some("Peer"), k3);
2347 assert_eq!(Some("e"), p3.get("foo"));
2348 assert_eq!(Some("f"), p3.get("bar"));
2349
2350 assert_eq!(None, iter.next());
2351 }
2352
2353 #[test]
2354 fn add_properties_api() {
2355 let mut ini = Ini::new();
2357 ini.with_section(Some("foo")).add("a", "1").add("a", "2");
2358
2359 let sec = ini.section(Some("foo")).unwrap();
2360 assert_eq!(sec.get("a"), Some("1"));
2361 assert_eq!(sec.get_all("a").collect::<Vec<&str>>(), vec!["1", "2"]);
2362
2363 let mut ini = Ini::new();
2365 ini.with_section(Some("foo")).add("a", "1").add("b", "2");
2366
2367 let sec = ini.section(Some("foo")).unwrap();
2368 assert_eq!(sec.get("a"), Some("1"));
2369 assert_eq!(sec.get("b"), Some("2"));
2370
2371 let mut ini = Ini::new();
2373 ini.with_section(Some("foo")).add("a", "1").add("a", "2");
2374 let mut buf = Vec::new();
2375 ini.write_to(&mut buf).unwrap();
2376 let ini_str = String::from_utf8(buf).unwrap();
2377 if cfg!(windows) {
2378 assert_eq!(ini_str, "[foo]\r\na=1\r\na=2\r\n");
2379 } else {
2380 assert_eq!(ini_str, "[foo]\na=1\na=2\n");
2381 }
2382 }
2383
2384 #[test]
2385 fn new_has_empty_general_section() {
2386 let mut ini = Ini::new();
2387
2388 assert!(ini.general_section().is_empty());
2389 assert!(ini.general_section_mut().is_empty());
2390 assert_eq!(ini.len(), 1);
2391 }
2392
2393 #[test]
2394 fn fix_issue63() {
2395 let section = "PHP";
2396 let key = "engine";
2397 let value = "On";
2398 let new_value = "Off";
2399
2400 let mut conf = Ini::new();
2402 conf.with_section(Some(section)).set(key, value);
2403
2404 let v = conf.get_from(Some(section), key).unwrap();
2406 assert_eq!(v, value);
2407
2408 conf.set_to(Some(section), key.to_string(), new_value.to_string());
2410
2411 let v = conf.get_from(Some(section), key).unwrap();
2413 assert_eq!(v, new_value);
2414 }
2415
2416 #[test]
2417 fn fix_issue64() {
2418 let input = format!("some-key=åäö{}", super::DEFAULT_LINE_SEPARATOR);
2419
2420 let conf = Ini::load_from_str(&input).unwrap();
2421
2422 let mut output = Vec::new();
2423 conf.write_to_policy(&mut output, EscapePolicy::Basics).unwrap();
2424
2425 assert_eq!(input, String::from_utf8(output).unwrap());
2426 }
2427
2428 #[test]
2429 fn invalid_codepoint() {
2430 use std::io::Cursor;
2431
2432 let d = vec![
2433 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,
2434 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,
2435 120, 46, 36, 91, 91, 1, 0, 0, 16, 0, 0, 0, 0, 0, 0,
2436 ];
2437 let mut file = Cursor::new(d);
2438 assert!(Ini::read_from(&mut file).is_err());
2439 }
2440
2441 #[test]
2442 #[cfg(feature = "brackets-in-section-names")]
2443 fn fix_issue84() {
2444 let input = "
2445[[*]]
2446a = b
2447c = d
2448";
2449 let ini = Ini::load_from_str(input).unwrap();
2450 let sect = ini.section(Some("[*]"));
2451 assert!(sect.is_some());
2452 assert!(sect.unwrap().contains_key("a"));
2453 assert!(sect.unwrap().contains_key("c"));
2454 }
2455
2456 #[test]
2457 #[cfg(feature = "brackets-in-section-names")]
2458 fn fix_issue84_brackets_inside() {
2459 let input = "
2460[a[b]c]
2461a = b
2462c = d
2463";
2464 let ini = Ini::load_from_str(input).unwrap();
2465 let sect = ini.section(Some("a[b]c"));
2466 assert!(sect.is_some());
2467 assert!(sect.unwrap().contains_key("a"));
2468 assert!(sect.unwrap().contains_key("c"));
2469 }
2470
2471 #[test]
2472 #[cfg(feature = "brackets-in-section-names")]
2473 fn fix_issue84_whitespaces_after_bracket() {
2474 let input = "
2475[[*]]\t\t
2476a = b
2477c = d
2478";
2479 let ini = Ini::load_from_str(input).unwrap();
2480 let sect = ini.section(Some("[*]"));
2481 assert!(sect.is_some());
2482 assert!(sect.unwrap().contains_key("a"));
2483 assert!(sect.unwrap().contains_key("c"));
2484 }
2485
2486 #[test]
2487 #[cfg(feature = "brackets-in-section-names")]
2488 fn fix_issue84_not_whitespaces_after_bracket() {
2489 let input = "
2490[[*]]xx
2491a = b
2492c = d
2493";
2494 let ini = Ini::load_from_str(input);
2495 assert!(ini.is_err());
2496 }
2497
2498 #[test]
2499 fn escape_str_nothing_policy() {
2500 let test_str = "\0\x07\n字'\"✨🍉杓";
2501 let policy = EscapePolicy::Nothing;
2503 assert_eq!(escape_str(test_str, policy), test_str);
2504 }
2505
2506 #[test]
2507 fn escape_str_basics() {
2508 let test_backslash = r"\backslashes\";
2509 let test_nul = "string with \x00nulls\x00 in it";
2510 let test_controls = "|\x07| bell, |\x08| backspace, |\x7f| delete, |\x1b| escape";
2511 let test_whitespace = "\t \r\n";
2512
2513 assert_eq!(escape_str(test_backslash, EscapePolicy::Nothing), test_backslash);
2514 assert_eq!(escape_str(test_nul, EscapePolicy::Nothing), test_nul);
2515 assert_eq!(escape_str(test_controls, EscapePolicy::Nothing), test_controls);
2516 assert_eq!(escape_str(test_whitespace, EscapePolicy::Nothing), test_whitespace);
2517
2518 for policy in [
2519 EscapePolicy::Basics,
2520 EscapePolicy::BasicsUnicode,
2521 EscapePolicy::BasicsUnicodeExtended,
2522 EscapePolicy::Reserved,
2523 EscapePolicy::ReservedUnicode,
2524 EscapePolicy::ReservedUnicodeExtended,
2525 EscapePolicy::Everything,
2526 ] {
2527 assert_eq!(escape_str(test_backslash, policy), r"\\backslashes\\");
2528 assert_eq!(escape_str(test_nul, policy), r"string with \0nulls\0 in it");
2529 assert_eq!(
2530 escape_str(test_controls, policy),
2531 r"|\a| bell, |\b| backspace, |\x007f| delete, |\x001b| escape"
2532 );
2533 assert_eq!(escape_str(test_whitespace, policy), r"\t \r\n");
2534 }
2535 }
2536
2537 #[test]
2538 fn escape_str_reserved() {
2539 let test_reserved = ":=;#";
2541 let test_punctuation = "!@$%^&*()-_+/?.>,<[]{}``";
2543
2544 for policy in [
2546 EscapePolicy::Nothing,
2547 EscapePolicy::Basics,
2548 EscapePolicy::BasicsUnicode,
2549 EscapePolicy::BasicsUnicodeExtended,
2550 ] {
2551 assert_eq!(escape_str(test_reserved, policy), ":=;#");
2552 assert_eq!(escape_str(test_punctuation, policy), test_punctuation);
2553 }
2554
2555 for policy in [
2557 EscapePolicy::Reserved,
2558 EscapePolicy::ReservedUnicodeExtended,
2559 EscapePolicy::ReservedUnicode,
2560 EscapePolicy::Everything,
2561 ] {
2562 assert_eq!(escape_str(test_reserved, policy), r"\:\=\;\#");
2563 assert_eq!(escape_str(test_punctuation, policy), "!@$%^&*()-_+/?.>,<[]{}``");
2564 }
2565 }
2566
2567 #[test]
2568 fn escape_str_unicode() {
2569 let test_unicode = r"é£∳字✨";
2574 let test_emoji = r"🐱😉";
2575 let test_cjk = r"𠈌𠕇";
2576 let test_high_points = "\u{10ABCD}\u{10FFFF}";
2577
2578 let policy = EscapePolicy::Nothing;
2579 assert_eq!(escape_str(test_unicode, policy), test_unicode);
2580 assert_eq!(escape_str(test_emoji, policy), test_emoji);
2581 assert_eq!(escape_str(test_high_points, policy), test_high_points);
2582
2583 for policy in [EscapePolicy::BasicsUnicode, EscapePolicy::ReservedUnicode] {
2586 assert_eq!(escape_str(test_unicode, policy), r"\x00e9\x00a3\x2233\x5b57\x2728");
2587 assert_eq!(escape_str(test_emoji, policy), test_emoji);
2588 assert_eq!(escape_str(test_cjk, policy), test_cjk);
2589 assert_eq!(escape_str(test_high_points, policy), test_high_points);
2590 }
2591
2592 for policy in [
2594 EscapePolicy::BasicsUnicodeExtended,
2595 EscapePolicy::ReservedUnicodeExtended,
2596 ] {
2597 assert_eq!(escape_str(test_unicode, policy), r"\x00e9\x00a3\x2233\x5b57\x2728");
2598 assert_eq!(escape_str(test_emoji, policy), r"\x1f431\x1f609");
2599 assert_eq!(escape_str(test_cjk, policy), r"\x2020c\x20547");
2600 assert_eq!(escape_str(test_high_points, policy), r"\x10abcd\x10ffff");
2601 }
2602 }
2603
2604 #[test]
2605 fn iter_mut_preserve_order_in_section() {
2606 let input = r"
2607x2 = nc
2608x1 = na
2609x3 = nb
2610";
2611 let mut data = Ini::load_from_str(input).unwrap();
2612 let section = data.general_section_mut();
2613 section.iter_mut().enumerate().for_each(|(i, (_, v))| {
2614 v.push_str(&i.to_string());
2615 });
2616 let props: Vec<_> = section.iter().collect();
2617 assert_eq!(props, vec![("x2", "nc0"), ("x1", "na1"), ("x3", "nb2")]);
2618 }
2619
2620 #[test]
2621 fn preserve_order_properties_into_iter() {
2622 let input = r"
2623x2 = nc
2624x1 = na
2625x3 = nb
2626";
2627 let data = Ini::load_from_str(input).unwrap();
2628 let (_, section) = data.into_iter().next().unwrap();
2629 let props: Vec<_> = section.into_iter().collect();
2630 assert_eq!(
2631 props,
2632 vec![
2633 ("x2".to_owned(), "nc".to_owned()),
2634 ("x1".to_owned(), "na".to_owned()),
2635 ("x3".to_owned(), "nb".to_owned())
2636 ]
2637 );
2638 }
2639
2640 #[test]
2641 fn section_setter_chain() {
2642 let mut ini = Ini::new();
2645 let mut section_setter = ini.with_section(Some("section"));
2646
2647 section_setter.set("a", "1").set("b", "2");
2649 section_setter.set("c", "3");
2651
2652 assert_eq!("1", section_setter.get("a").unwrap());
2653 assert_eq!("2", section_setter.get("b").unwrap());
2654 assert_eq!("3", section_setter.get("c").unwrap());
2655
2656 section_setter.set("a", "4").set("b", "5");
2658 section_setter.set("c", "6");
2659
2660 assert_eq!("4", section_setter.get("a").unwrap());
2661 assert_eq!("5", section_setter.get("b").unwrap());
2662 assert_eq!("6", section_setter.get("c").unwrap());
2663
2664 section_setter.delete(&"a").delete(&"b");
2666 section_setter.delete(&"c");
2667
2668 assert!(section_setter.get("a").is_none());
2669 assert!(section_setter.get("b").is_none());
2670 assert!(section_setter.get("c").is_none());
2671 }
2672}