Skip to main content

ini/
lib.rs

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