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