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