ini/
lib.rs

1// The MIT License (MIT)
2
3// Copyright (c) 2014 Y. T. CHUNG
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy of
6// this software and associated documentation files (the "Software"), to deal in
7// the Software without restriction, including without limitation the rights to
8// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9// the Software, and to permit persons to whom the Software is furnished to do so,
10// subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22//! Ini parser for Rust
23//!
24//! ```no_run
25//! use ini::Ini;
26//!
27//! let mut conf = Ini::new();
28//! conf.with_section(Some("User"))
29//!     .set("name", "Raspberry树莓")
30//!     .set("value", "Pi");
31//! conf.with_section(Some("Library"))
32//!     .set("name", "Sun Yat-sen U")
33//!     .set("location", "Guangzhou=world");
34//! conf.write_to_file("conf.ini").unwrap();
35//!
36//! let i = Ini::load_from_file("conf.ini").unwrap();
37//! for (sec, prop) in i.iter() {
38//!     println!("Section: {:?}", sec);
39//!     for (k, v) in prop.iter() {
40//!         println!("{}:{}", k, v);
41//!     }
42//! }
43//! ```
44
45use 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/// Policies for escaping logic
66#[derive(Debug, PartialEq, Copy, Clone)]
67pub enum EscapePolicy {
68    /// Escape absolutely nothing (dangerous)
69    Nothing,
70    /// Only escape the most necessary things.
71    /// This means backslashes, control characters (codepoints U+0000 to U+001F), and delete (U+007F).
72    /// Quotes (single or double) are not escaped.
73    Basics,
74    /// Escape basics and non-ASCII characters in the [Basic Multilingual Plane](https://www.compart.com/en/unicode/plane)
75    /// (i.e. between U+007F - U+FFFF)
76    /// Codepoints above U+FFFF, e.g. '🐱' U+1F431 "CAT FACE" will *not* be escaped!
77    BasicsUnicode,
78    /// Escape basics and all non-ASCII characters, including codepoints above U+FFFF.
79    /// This will escape emoji - if you want them to remain raw, use BasicsUnicode instead.
80    BasicsUnicodeExtended,
81    /// Escape reserved symbols.
82    /// This includes everything in EscapePolicy::Basics, plus the comment characters ';' and '#' and the key/value-separating characters '=' and ':'.
83    Reserved,
84    /// Escape reserved symbols and non-ASCII characters in the BMP.
85    /// Codepoints above U+FFFF, e.g. '🐱' U+1F431 "CAT FACE" will *not* be escaped!
86    ReservedUnicode,
87    /// Escape reserved symbols and all non-ASCII characters, including codepoints above U+FFFF.
88    ReservedUnicodeExtended,
89    /// Escape everything that some INI implementations assume
90    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    /// Given a character this returns true if it should be escaped as
127    /// per this policy or false if not.
128    pub fn should_escape(self, c: char) -> bool {
129        match c {
130            // A single backslash, must be escaped
131            // ASCII control characters, U+0000 NUL..= U+001F UNIT SEPARATOR, or U+007F DELETE. The same as char::is_ascii_control()
132            '\\' | '\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
141// Escape non-INI characters
142//
143// Common escape sequences: https://en.wikipedia.org/wiki/INI_file#Escape_characters
144//
145// * `\\` \ (a single backslash, escaping the escape character)
146// * `\0` Null character
147// * `\a` Bell/Alert/Audible
148// * `\b` Backspace, Bell character for some applications
149// * `\t` Tab character
150// * `\r` Carriage return
151// * `\n` Line feed
152// * `\;` Semicolon
153// * `\#` Number sign
154// * `\=` Equals sign
155// * `\:` Colon
156// * `\x????` Unicode character with hexadecimal code point corresponding to ????
157fn 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 we know this is not something to escape as per policy, we just
161        // write it and continue.
162        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            // Longer escapes.
182            '\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
193/// Parsing configuration
194pub struct ParseOption {
195    /// Allow quote (`"` or `'`) in value
196    /// For example
197    /// ```ini
198    /// [Section]
199    /// Key1="Quoted value"
200    /// Key2='Single Quote' with extra value
201    /// ```
202    ///
203    /// In this example, Value of `Key1` is `Quoted value`,
204    /// and value of `Key2` is `Single Quote with extra value`
205    /// if `enabled_quote` is set to `true`.
206    pub enabled_quote: bool,
207
208    /// Interpret `\` as an escape character
209    /// For example
210    /// ```ini
211    /// [Section]
212    /// Key1=C:\Windows
213    /// ```
214    ///
215    /// If `enabled_escape` is true, then the value of `Key` will become `C:Windows` (`\W` equals to `W`).
216    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/// Newline style
229#[derive(Debug, Copy, Clone, Eq, PartialEq)]
230pub enum LineSeparator {
231    /// System-dependent line separator
232    ///
233    /// On UNIX system, uses "\n"
234    /// On Windows system, uses "\r\n"
235    SystemDefault,
236
237    /// Uses "\n" as new line separator
238    CR,
239
240    /// Uses "\r\n" as new line separator
241    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    /// String representation
260    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/// Writing configuration
270#[derive(Debug, Clone)]
271pub struct WriteOption {
272    /// Policies about how to escape characters
273    pub escape_policy: EscapePolicy,
274
275    /// Newline style
276    pub line_separator: LineSeparator,
277
278    /// Key value separator
279    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        /// Internal storage of section's key
295        pub type SectionKey = Option<UniCase<String>>;
296        /// Internal storage of property's key
297        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        /// Internal storage of section's key
319        pub type SectionKey = Option<String>;
320        /// Internal storage of property's key
321        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
343/// A setter which could be used to set key-value pair in a specified section
344pub 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    /// Set (replace) key-value pair in this section (all with the same name)
355    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    /// Add (append) key-value pair in this section
370    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    /// Delete the first entry in this section with `key`
385    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    /// Get the entry in this section with `key`
398    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/// Properties type (key-value pairs)
407#[derive(Clone, Default, Debug, PartialEq)]
408pub struct Properties {
409    data: ListOrderedMultimap<PropertyKey, String>,
410}
411
412impl Properties {
413    /// Create an instance
414    pub fn new() -> Properties {
415        Default::default()
416    }
417
418    /// Get the number of the properties
419    pub fn len(&self) -> usize {
420        self.data.keys_len()
421    }
422
423    /// Check if properties has 0 elements
424    pub fn is_empty(&self) -> bool {
425        self.data.is_empty()
426    }
427
428    /// Get an iterator of the properties
429    pub fn iter(&self) -> PropertyIter {
430        PropertyIter {
431            inner: self.data.iter(),
432        }
433    }
434
435    /// Get a mutable iterator of the properties
436    pub fn iter_mut(&mut self) -> PropertyIterMut {
437        PropertyIterMut {
438            inner: self.data.iter_mut(),
439        }
440    }
441
442    /// Return true if property exist
443    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    /// Insert (key, value) pair by replace
448    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    /// Append key with (key, value) pair
457    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    /// Get the first value associate with the key
466    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    /// Get all values associate with the key
471    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    /// Remove the property with the first value of the key
476    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    /// Remove the property with all values with the same key
481    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
524/// Iterator for traversing sections
525pub 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
600/// A view into a vacant entry in a `Ini`
601pub struct SectionVacantEntry<'a> {
602    inner: VacantEntry<'a, SectionKey, Properties>,
603}
604
605impl<'a> SectionVacantEntry<'a> {
606    /// Insert one new section
607    pub fn insert(self, value: Properties) -> &'a mut Properties {
608        self.inner.insert(value)
609    }
610}
611
612/// A view into a occupied entry in a `Ini`
613pub struct SectionOccupiedEntry<'a> {
614    inner: OccupiedEntry<'a, SectionKey, Properties>,
615}
616
617impl<'a> SectionOccupiedEntry<'a> {
618    /// Into the first internal mutable properties
619    pub fn into_mut(self) -> &'a mut Properties {
620        self.inner.into_mut()
621    }
622
623    /// Append a new section
624    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
636/// A view into an `Ini`, which may either be vacant or occupied.
637pub enum SectionEntry<'a> {
638    Vacant(SectionVacantEntry<'a>),
639    Occupied(SectionOccupiedEntry<'a>),
640}
641
642impl<'a> SectionEntry<'a> {
643    /// Ensures a value is in the entry by inserting the default if empty, and returns a mutable reference to the value in the entry.
644    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    /// Ensures a value is in the entry by inserting the result of the default function if empty, and returns a mutable reference to the value in the entry.
652    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/// Ini struct
670#[derive(Debug, Clone)]
671pub struct Ini {
672    sections: ListOrderedMultimap<SectionKey, Properties>,
673}
674
675impl Ini {
676    /// Create an instance
677    pub fn new() -> Ini {
678        Default::default()
679    }
680
681    /// Set with a specified section, `None` is for the general section
682    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    /// Set with general section, a simple wrapper of `with_section(None::<String>)`
690    pub fn with_general_section(&mut self) -> SectionSetter {
691        self.with_section(None::<String>)
692    }
693
694    /// Get the immutable general section
695    pub fn general_section(&self) -> &Properties {
696        self.section(None::<String>)
697            .expect("There is no general section in this Ini")
698    }
699
700    /// Get the mutable general section
701    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    /// Get a immutable section
707    pub fn section<S>(&self, name: Option<S>) -> Option<&Properties>
708    where
709        S: Into<String>,
710    {
711        self.sections.get(&section_key!(name))
712    }
713
714    /// Get a mutable section
715    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(&section_key!(name))
720    }
721
722    /// Get all sections immutable with the same key
723    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(&section_key!(name))
728    }
729
730    /// Get all sections mutable with the same key
731    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(&section_key!(name))
736    }
737
738    /// Get the entry
739    #[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    /// Get the entry
745    #[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    /// Clear all entries
751    pub fn clear(&mut self) {
752        self.sections.clear()
753    }
754
755    /// Iterate with sections
756    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    /// Set key-value to a section
761    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    /// Get the first value from the sections with key
769    ///
770    /// Example:
771    ///
772    /// ```
773    /// use ini::Ini;
774    /// let input = "[sec]\nabc = def\n";
775    /// let ini = Ini::load_from_str(input).unwrap();
776    /// assert_eq!(ini.get_from(Some("sec"), "abc"), Some("def"));
777    /// ```
778    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(&section_key!(section)).and_then(|prop| prop.get(key))
783    }
784
785    /// Get the first value from the sections with key, return the default value if it does not exist
786    ///
787    /// Example:
788    ///
789    /// ```
790    /// use ini::Ini;
791    /// let input = "[sec]\n";
792    /// let ini = Ini::load_from_str(input).unwrap();
793    /// assert_eq!(ini.get_from_or(Some("sec"), "key", "default"), "default");
794    /// ```
795    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    /// Get the first mutable value from the sections with key
803    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(&section_key!(section))
809            .and_then(|prop| prop.get_mut(key))
810    }
811
812    /// Delete the first section with key, return the properties if it exists
813    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    /// Delete the key from the section, return the value if key exists or None
822    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    /// Total sections count
830    pub fn len(&self) -> usize {
831        self.sections.keys_len()
832    }
833
834    /// Check if object contains no section
835    pub fn is_empty(&self) -> bool {
836        self.sections.is_empty()
837    }
838}
839
840impl Default for Ini {
841    /// Creates an ini instance with an empty general section. This allows [Ini::general_section]
842    /// and [Ini::with_general_section] to be called without panicking.
843    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    /// Write to a file
896    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    /// Write to a file
901    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    /// Write to a file with options
911    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    /// Write to a writer
921    pub fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
922        self.write_to_opt(writer, Default::default())
923    }
924
925    /// Write to a writer
926    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    /// Write to a writer with options
937    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                    // Write an empty line between sections
946                    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(&section[..], 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    /// Load from a string
970    pub fn load_from_str(buf: &str) -> Result<Ini, ParseError> {
971        Ini::load_from_str_opt(buf, ParseOption::default())
972    }
973
974    /// Load from a string, but do not interpret '\' as an escape character
975    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    /// Load from a string with options
986    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    /// Load from a reader
992    pub fn read_from<R: Read>(reader: &mut R) -> Result<Ini, Error> {
993        Ini::read_from_opt(reader, ParseOption::default())
994    }
995
996    /// Load from a reader, but do not interpret '\' as an escape character
997    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    /// Load from a reader with options
1008    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    /// Load from a file
1019    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    /// Load from a file, but do not interpret '\' as an escape character
1024    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    /// Load from a file with options
1035    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        // Check if file starts with a BOM marker
1046        // UTF-8: EF BB BF
1047        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            // Reset file pointer
1054            reader.seek(SeekFrom::Start(0))?;
1055        }
1056
1057        Ini::read_from_opt(&mut reader, opt)
1058    }
1059}
1060
1061/// Iterator for traversing sections
1062pub 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
1084/// Iterator for traversing sections
1085pub 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
1107/// Iterator for traversing sections
1108pub 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    /// Immutable iterate though sections
1132    pub fn iter(&'a self) -> SectionIter<'a> {
1133        SectionIter {
1134            inner: self.sections.iter(),
1135        }
1136    }
1137
1138    /// Mutable iterate though sections
1139    #[deprecated(note = "Use `iter_mut` instead!")]
1140    pub fn mut_iter(&'a mut self) -> SectionIterMut<'a> {
1141        self.iter_mut()
1142    }
1143
1144    /// Mutable iterate though sections
1145    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
1181// Ini parser
1182struct Parser<'a> {
1183    ch: Option<char>,
1184    rdr: Chars<'a>,
1185    line: usize,
1186    col: usize,
1187    opt: ParseOption,
1188}
1189
1190#[derive(Debug)]
1191/// Parse error
1192pub 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/// Error while parsing an INI document
1207#[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    // Create a parser
1239    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    /// Consume all the white space until the end of the line or a tab
1288    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    /// Consume all the white space except line break
1298    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    /// Parse the whole INI input
1308    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                        // Inline comments is not supported, so comments must starts from a new line
1319                        //
1320                        // https://en.wikipedia.org/wiki/INI_file#Comments
1321                        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                                    // cursec must be None (the General Section)
1353                                    let mut prop = Properties::new();
1354                                    prop.insert(curkey, mval);
1355                                    v.insert(prop);
1356                                }
1357                                SectionEntry::Occupied(mut o) => {
1358                                    // Insert into the last (current) section
1359                                    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                            // [space]#, [space]; starts an inline comment
1403                            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                            // Unicode 4 character
1426                            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                // Skip [
1467                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                // Deal with inline comment
1475                #[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                // Skip [
1489                self.bump();
1490                let sec = self.parse_str_until(&[Some(']')], false)?;
1491                if let Some(']') = self.ch {
1492                    self.bump();
1493                }
1494
1495                // Deal with inline comment
1496                #[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        // Issue #35: Allow empty value
1513        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(); // Eats the last "
1521                                 // Parse until EOL
1522                    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(); // Eats the last '
1530                                 // Parse until EOL
1531                    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// ------------------------------------------------------------------------------
1553
1554#[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        // there is always a general section
1657        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        // there is always a general section
1681        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        // for _ in ini {}
1794    }
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        // Test different line endings in Windows and Unix
2298        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        // https://github.com/zonyitoo/rust-ini/issues/49
2314
2315        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        // there is always an empty general section
2334        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        // Test duplicate properties in a section
2356        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        // Test add with unique keys
2364        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        // Test string representation
2372        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        // create a new configuration
2401        let mut conf = Ini::new();
2402        conf.with_section(Some(section)).set(key, value);
2403
2404        // assert the value is the one expected
2405        let v = conf.get_from(Some(section), key).unwrap();
2406        assert_eq!(v, value);
2407
2408        // update the section/key with a new value
2409        conf.set_to(Some(section), key.to_string(), new_value.to_string());
2410
2411        // assert the new value was set
2412        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        // This policy should never escape anything.
2502        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        // Test reserved characters.
2540        let test_reserved = ":=;#";
2541        // And characters which are *not* reserved, but look like they might be.
2542        let test_punctuation = "!@$%^&*()-_+/?.>,<[]{}``";
2543
2544        // These policies should *not* escape reserved characters.
2545        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        // These should.
2556        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        // Test unicode escapes.
2570        // The first are Basic Multilingual Plane (BMP) characters - i.e. <= U+FFFF
2571        // Emoji are above U+FFFF (e.g. in the 1F???? range), and the CJK characters are in the U+20???? range.
2572        // The last one is for codepoints at the edge of Rust's char type.
2573        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        // The "Unicode" policies should escape standard BMP unicode, but should *not* escape emoji or supplementary CJK codepoints.
2584        // The Basics/Reserved policies should behave identically in this regard.
2585        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        // UnicodeExtended policies should escape both BMP and supplementary plane characters.
2593        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        // fix issue #134
2643
2644        let mut ini = Ini::new();
2645        let mut section_setter = ini.with_section(Some("section"));
2646
2647        // chained set() calls work
2648        section_setter.set("a", "1").set("b", "2");
2649        // separate set() calls work
2650        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        // overwrite values
2657        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        // delete entries
2665        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}