ini/
lib.rs

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