1 //!The ini module provides all the things necessary to load and parse ini-syntax files. The most important of which is the `Ini` struct.
2 //!See the [implementation](https://docs.rs/configparser/*/configparser/ini/struct.Ini.html) documentation for more details.
3 #[cfg(feature = "indexmap")]
4 use indexmap::IndexMap as Map;
5 #[cfg(not(feature = "indexmap"))]
6 use std::collections::HashMap as Map;
7 
8 #[deprecated(
9     since = "3.0.4",
10     note = "async-std runtime has been replaced with tokio"
11 )]
12 #[cfg(feature = "async-std")]
13 #[cfg(feature = "tokio")]
14 use tokio::fs as async_fs;
15 
16 use std::collections::HashMap;
17 use std::convert::AsRef;
18 use std::fmt::Write;
19 use std::fs;
20 use std::path::Path;
21 
22 ///The `Ini` struct simply contains a nested hashmap of the loaded configuration, the default section header and comment symbols.
23 ///## Example
24 ///```rust
25 ///use configparser::ini::Ini;
26 ///
27 ///let mut config = Ini::new();
28 ///```
29 #[derive(Debug, Clone, Eq, PartialEq, Default)]
30 #[non_exhaustive]
31 pub struct Ini {
32     map: Map<String, Map<String, Option<String>>>,
33     default_section: std::string::String,
34     comment_symbols: Vec<char>,
35     delimiters: Vec<char>,
36     boolean_values: HashMap<bool, Vec<String>>,
37     case_sensitive: bool,
38     multiline: bool,
39 }
40 
41 ///The `IniDefault` struct serves as a template to create other `Ini` objects from. It can be used to store and load
42 ///default properties from different `Ini` objects.
43 ///## Example
44 ///```rust
45 ///use configparser::ini::Ini;
46 ///
47 ///let mut config = Ini::new();
48 ///let default = config.defaults();
49 ///let mut config2 = Ini::new_from_defaults(default); // default gets consumed
50 ///```
51 #[derive(Debug, Clone, Eq, PartialEq)]
52 #[non_exhaustive]
53 pub struct IniDefault {
54     ///Denotes the default section header name.
55     ///## Example
56     ///```rust
57     ///use configparser::ini::Ini;
58     ///
59     ///let mut config = Ini::new();
60     ///let default = config.defaults();
61     ///assert_eq!(default.default_section, "default");
62     ///```
63     pub default_section: std::string::String,
64     ///Denotes the set comment symbols for the object.
65     ///## Example
66     ///```rust
67     ///use configparser::ini::Ini;
68     ///
69     ///let mut config = Ini::new();
70     ///let default = config.defaults();
71     ///assert_eq!(default.comment_symbols, vec![';', '#']);
72     ///```
73     pub comment_symbols: Vec<char>,
74     ///Denotes the set delimiters for the key-value pairs.
75     ///## Example
76     ///```rust
77     ///use configparser::ini::Ini;
78     ///
79     ///let mut config = Ini::new();
80     ///let default = config.defaults();
81     ///assert_eq!(default.delimiters, vec!['=', ':']);
82     ///```
83     pub delimiters: Vec<char>,
84     pub boolean_values: HashMap<bool, Vec<String>>,
85     ///Denotes if the `Ini` object is case-sensitive.
86     ///## Example
87     ///```rust
88     ///use configparser::ini::Ini;
89     ///
90     ///let mut config = Ini::new();
91     ///let default = config.defaults();
92     ///assert_eq!(default.case_sensitive, false);
93     ///```
94     pub case_sensitive: bool,
95     ///Denotes if the `Ini` object parses multiline strings.
96     ///## Example
97     ///```rust
98     ///use configparser::ini::Ini;
99     ///
100     ///let mut config = Ini::new();
101     ///let default = config.defaults();
102     ///assert_eq!(default.multiline, false);
103     ///```
104     pub multiline: bool,
105 }
106 
107 impl Default for IniDefault {
default() -> Self108     fn default() -> Self {
109         Self {
110             default_section: "default".to_owned(),
111             comment_symbols: vec![';', '#'],
112             delimiters: vec!['=', ':'],
113             multiline: false,
114             boolean_values: [
115                 (
116                     true,
117                     ["true", "yes", "t", "y", "on", "1"]
118                         .iter()
119                         .map(|&s| s.to_owned())
120                         .collect(),
121                 ),
122                 (
123                     false,
124                     ["false", "no", "f", "n", "off", "0"]
125                         .iter()
126                         .map(|&s| s.to_owned())
127                         .collect(),
128                 ),
129             ]
130             .iter()
131             .cloned()
132             .collect(),
133             case_sensitive: false,
134         }
135     }
136 }
137 
138 /// Use this struct to define formatting options for the `pretty_write` functions.
139 #[derive(Debug, Clone, Eq, PartialEq)]
140 #[non_exhaustive]
141 pub struct WriteOptions {
142     ///If true then the keys and values will be separated by " = ". In the special case where the value is empty, the
143     ///line ends with " =".
144     ///If false then keys and values will be separated by "=".
145     ///Default is `false`.
146     ///## Example
147     ///```rust
148     ///use configparser::ini::WriteOptions;
149     ///
150     ///let mut write_options = WriteOptions::default();
151     ///assert_eq!(write_options.space_around_delimiters, false);
152     ///```
153     pub space_around_delimiters: bool,
154 
155     ///Defines the number of spaces for indentation of for multiline values.
156     ///Default is 4 spaces.
157     ///## Example
158     ///```rust
159     ///use configparser::ini::WriteOptions;
160     ///
161     ///let mut write_options = WriteOptions::default();
162     ///assert_eq!(write_options.multiline_line_indentation, 4);
163     ///```
164     pub multiline_line_indentation: usize,
165 
166     ///Defines the number of blank lines between sections.
167     ///Default is 0.
168     ///## Example
169     ///```rust
170     ///use configparser::ini::WriteOptions;
171     ///
172     ///let mut write_options = WriteOptions::default();
173     ///assert_eq!(write_options.blank_lines_between_sections, 0);
174     ///```
175     pub blank_lines_between_sections: usize,
176 }
177 
178 impl Default for WriteOptions {
default() -> Self179     fn default() -> Self {
180         Self {
181             space_around_delimiters: false,
182             multiline_line_indentation: 4,
183             blank_lines_between_sections: 0,
184         }
185     }
186 }
187 
188 impl WriteOptions {
189     ///Creates a new `WriteOptions` object with the default values.
190     ///## Example
191     ///```rust
192     ///use configparser::ini::WriteOptions;
193     ///
194     ///let write_options = WriteOptions::new();
195     ///assert_eq!(write_options.space_around_delimiters, false);
196     ///assert_eq!(write_options.multiline_line_indentation, 4);
197     ///assert_eq!(write_options.blank_lines_between_sections, 0);
198     ///```
199     ///Returns the struct and stores it in the calling variable.
new() -> WriteOptions200     pub fn new() -> WriteOptions {
201         WriteOptions::default()
202     }
203 
204     ///Creates a new `WriteOptions` object with the given parameters.
205     ///## Example
206     ///```rust
207     ///use configparser::ini::WriteOptions;
208     ///
209     ///let write_options = WriteOptions::new_with_params(true, 2, 1);
210     ///assert_eq!(write_options.space_around_delimiters, true);
211     ///assert_eq!(write_options.multiline_line_indentation, 2);
212     ///assert_eq!(write_options.blank_lines_between_sections, 1);
213     ///```
214     ///Returns the struct and stores it in the calling variable.
new_with_params( space_around_delimiters: bool, multiline_line_indentation: usize, blank_lines_between_sections: usize, ) -> WriteOptions215     pub fn new_with_params(
216         space_around_delimiters: bool,
217         multiline_line_indentation: usize,
218         blank_lines_between_sections: usize,
219     ) -> WriteOptions {
220         Self {
221             space_around_delimiters,
222             multiline_line_indentation,
223             blank_lines_between_sections,
224         }
225     }
226 }
227 
228 #[cfg(windows)]
229 const LINE_ENDING: &str = "\r\n";
230 #[cfg(not(windows))]
231 const LINE_ENDING: &str = "\n";
232 
233 impl Ini {
234     ///Creates a new `Map` of `Map<String, Map<String, Option<String>>>` type for the struct.
235     ///All values in the Map are stored in `String` type.
236     ///
237     ///By default, [`std::collections::HashMap`] is used for the Map object.
238     ///The `indexmap` feature can be used to use an [`indexmap::map::IndexMap`] instead, which
239     ///allows keeping the insertion order for sections and keys.
240     ///
241     ///## Example
242     ///```rust
243     ///use configparser::ini::Ini;
244     ///
245     ///let mut config = Ini::new();
246     ///```
247     ///Returns the struct and stores it in the calling variable.
new() -> Ini248     pub fn new() -> Ini {
249         Ini::new_from_defaults(IniDefault::default())
250     }
251 
252     ///Creates a new **case-sensitive** `Map` of `Map<String, Map<String, Option<String>>>` type for the struct.
253     ///All values in the Map are stored in `String` type.
254     ///## Example
255     ///```rust
256     ///use configparser::ini::Ini;
257     ///
258     ///let mut config = Ini::new_cs();
259     ///```
260     ///Returns the struct and stores it in the calling variable.
new_cs() -> Ini261     pub fn new_cs() -> Ini {
262         Ini::new_from_defaults(IniDefault {
263             case_sensitive: true,
264             ..Default::default()
265         })
266     }
267 
268     ///Creates a new `Ini` with the given defaults from an existing `IniDefault` object.
269     ///## Example
270     ///```rust
271     ///use configparser::ini::Ini;
272     ///use configparser::ini::IniDefault;
273     ///
274     ///let mut default = IniDefault::default();
275     ///default.comment_symbols = vec![';'];
276     ///default.delimiters = vec!['='];
277     ///let mut config = Ini::new_from_defaults(default.clone());
278     ///// Now, load as usual with new defaults:
279     ///let map = config.load("tests/test.ini").unwrap();
280     ///assert_eq!(config.defaults(), default);
281     ///
282     ///```
new_from_defaults(defaults: IniDefault) -> Ini283     pub fn new_from_defaults(defaults: IniDefault) -> Ini {
284         Ini {
285             map: Map::new(),
286             default_section: defaults.default_section,
287             comment_symbols: defaults.comment_symbols,
288             delimiters: defaults.delimiters,
289             boolean_values: defaults.boolean_values,
290             case_sensitive: defaults.case_sensitive,
291             multiline: defaults.multiline,
292         }
293     }
294 
295     ///Fetches the defaults from the current `Ini` object and stores it as a `IniDefault` struct for usage elsewhere.
296     ///## Example
297     ///```rust
298     ///use configparser::ini::Ini;
299     ///
300     ///let mut config = Ini::new();
301     ///let default = config.defaults();
302     ///```
303     ///Returns an `IniDefault` object. Keep in mind that it will get borrowed since it has non-`Copy` types.
defaults(&self) -> IniDefault304     pub fn defaults(&self) -> IniDefault {
305         IniDefault {
306             default_section: self.default_section.to_owned(),
307             comment_symbols: self.comment_symbols.to_owned(),
308             delimiters: self.delimiters.to_owned(),
309             boolean_values: self.boolean_values.to_owned(),
310             case_sensitive: self.case_sensitive,
311             multiline: self.multiline,
312         }
313     }
314 
315     ///Takes an `IniDefault` object and stores its properties in the calling `Ini` object. This happens in-place and
316     ///does not work retroactively, only future operations are affected.
317     ///## Example
318     ///```rust
319     ///use configparser::ini::Ini;
320     ///use configparser::ini::IniDefault;
321     ///
322     ///let mut config = Ini::new();
323     ///let mut default = IniDefault::default();
324     ///default.case_sensitive = true;
325     ///// This is equivalent to ini_cs() defaults
326     ///config.load_defaults(default.clone());
327     ///assert_eq!(config.defaults(), default);
328     ///```
329     ///Returns nothing.
load_defaults(&mut self, defaults: IniDefault)330     pub fn load_defaults(&mut self, defaults: IniDefault) {
331         self.default_section = defaults.default_section;
332         self.comment_symbols = defaults.comment_symbols;
333         self.delimiters = defaults.delimiters;
334         self.boolean_values = defaults.boolean_values;
335         self.case_sensitive = defaults.case_sensitive;
336     }
337 
338     ///Sets the default section header to the defined string (the default is `default`).
339     ///It must be set before `load()` or `read()` is called in order to take effect.
340     ///## Example
341     ///```rust
342     ///use configparser::ini::Ini;
343     ///
344     ///let mut config = Ini::new();
345     ///
346     ///config.set_default_section("topsecret");
347     ///let map = config.load("tests/test.ini").unwrap();
348     ///```
349     ///Returns nothing.
set_default_section(&mut self, section: &str)350     pub fn set_default_section(&mut self, section: &str) {
351         self.default_section = section.to_owned();
352     }
353 
354     ///Sets the default comment symbols to the defined character slice (the defaults are `;` and `#`).
355     ///Keep in mind that this will remove the default symbols. It must be set before `load()` or `read()` is called in order to take effect.
356     ///## Example
357     ///```rust
358     ///use configparser::ini::Ini;
359     ///
360     ///let mut config = Ini::new();
361     ///config.set_comment_symbols(&['!', '#']);
362     ///let map = config.load("tests/test.ini").unwrap();
363     ///```
364     ///Returns nothing.
set_comment_symbols(&mut self, symlist: &[char])365     pub fn set_comment_symbols(&mut self, symlist: &[char]) {
366         self.comment_symbols = symlist.to_vec();
367     }
368 
369     ///Sets multiline string support.
370     ///It must be set before `load()` or `read()` is called in order to take effect.
371     ///## Example
372     ///```rust
373     ///use configparser::ini::Ini;
374     ///
375     ///let mut config = Ini::new();
376     ///config.set_multiline(true);
377     ///let map = config.load("tests/test.ini").unwrap();
378     ///```
379     ///Returns nothing.
set_multiline(&mut self, multiline: bool)380     pub fn set_multiline(&mut self, multiline: bool) {
381         self.multiline = multiline;
382     }
383 
384     ///Gets all the sections of the currently-stored `Map` in a vector.
385     ///## Example
386     ///```rust
387     ///use configparser::ini::Ini;
388     ///
389     ///let mut config = Ini::new();
390     ///config.load("tests/test.ini");
391     ///let sections = config.sections();
392     ///```
393     ///Returns `Vec<String>`.
sections(&self) -> Vec<String>394     pub fn sections(&self) -> Vec<String> {
395         self.map.keys().cloned().collect()
396     }
397 
398     ///Loads a file from a defined path, parses it and puts the hashmap into our struct.
399     ///At one time, it only stores one configuration, so each call to `load()` or `read()` will clear the existing `Map`, if present.
400     ///## Example
401     ///```rust
402     ///use configparser::ini::Ini;
403     ///
404     ///let mut config = Ini::new();
405     ///let map = config.load("tests/test.ini").unwrap();  // we can get a clone like this, or just store it
406     /////Then, we can use standard hashmap functions like:
407     ///let values = map.get("values").unwrap();
408     ///```
409     ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`.
410     ///Use `get_mut_map()` if you want a mutable reference.
load<T: AsRef<Path>>( &mut self, path: T, ) -> Result<Map<String, Map<String, Option<String>>>, String>411     pub fn load<T: AsRef<Path>>(
412         &mut self,
413         path: T,
414     ) -> Result<Map<String, Map<String, Option<String>>>, String> {
415         self.map = match self.parse(match fs::read_to_string(&path) {
416             Err(why) => {
417                 return Err(format!(
418                     "couldn't read {}: {}",
419                     &path.as_ref().display(),
420                     why
421                 ))
422             }
423             Ok(s) => s,
424         }) {
425             Err(why) => {
426                 return Err(format!(
427                     "couldn't read {}: {}",
428                     &path.as_ref().display(),
429                     why
430                 ))
431             }
432             Ok(map) => map,
433         };
434         Ok(self.map.clone())
435     }
436 
437     ///Loads a file from a defined path, parses it and applies it to the existing hashmap in our struct.
438     ///While `load()` will clear the existing `Map`, `load_and_append()` applies the new values on top of
439     ///the existing hashmap, preserving previous values.
440     ///## Example
441     ///```rust
442     ///use configparser::ini::Ini;
443     ///
444     ///let mut config = Ini::new();
445     ///config.load("tests/test.ini").unwrap();
446     ///config.load_and_append("tests/sys_cfg.ini").ok();  // we don't have to worry if this doesn't succeed
447     ///config.load_and_append("tests/user_cfg.ini").ok();  // we don't have to worry if this doesn't succeed
448     ///let map = config.get_map().unwrap();
449     /////Then, we can use standard hashmap functions like:
450     ///let values = map.get("values").unwrap();
451     ///```
452     ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`.
453     ///Use `get_mut_map()` if you want a mutable reference.
load_and_append<T: AsRef<Path>>( &mut self, path: T, ) -> Result<Map<String, Map<String, Option<String>>>, String>454     pub fn load_and_append<T: AsRef<Path>>(
455         &mut self,
456         path: T,
457     ) -> Result<Map<String, Map<String, Option<String>>>, String> {
458         let loaded = match self.parse(match fs::read_to_string(&path) {
459             Err(why) => {
460                 return Err(format!(
461                     "couldn't read {}: {}",
462                     &path.as_ref().display(),
463                     why
464                 ))
465             }
466             Ok(s) => s,
467         }) {
468             Err(why) => {
469                 return Err(format!(
470                     "couldn't read {}: {}",
471                     &path.as_ref().display(),
472                     why
473                 ))
474             }
475             Ok(map) => map,
476         };
477 
478         for (section, section_map) in loaded.iter() {
479             self.map
480                 .entry(section.clone())
481                 .or_default()
482                 .extend(section_map.clone());
483         }
484 
485         Ok(self.map.clone())
486     }
487 
488     ///Reads an input string, parses it and puts the hashmap into our struct.
489     ///At one time, it only stores one configuration, so each call to `load()` or `read()` will clear the existing `Map`, if present.
490     ///## Example
491     ///```rust
492     ///use configparser::ini::Ini;
493     ///
494     ///let mut config = Ini::new();
495     ///let map = match config.read(String::from(
496     ///    "[2000s]
497     ///    2020 = bad")) {
498     ///    Err(why) => panic!("{}", why),
499     ///    Ok(inner) => inner
500     ///};
501     ///let this_year = map["2000s"]["2020"].clone().unwrap();
502     ///assert_eq!(this_year, "bad"); // value accessible!
503     ///```
504     ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`.
505     ///Use `get_mut_map()` if you want a mutable reference.
read( &mut self, input: String, ) -> Result<Map<String, Map<String, Option<String>>>, String>506     pub fn read(
507         &mut self,
508         input: String,
509     ) -> Result<Map<String, Map<String, Option<String>>>, String> {
510         self.map = match self.parse(input) {
511             Err(why) => return Err(why),
512             Ok(map) => map,
513         };
514         Ok(self.map.clone())
515     }
516 
517     ///Reads an input string, parses it and applies it to the existing hashmap in our struct.
518     ///While `read()` and `load()` will clear the existing `Map`, `read_and_append()` applies the new
519     ///values on top of the existing hashmap, preserving previous values.
520     ///## Example
521     ///```rust
522     ///use configparser::ini::Ini;
523     ///
524     ///let mut config = Ini::new();
525     ///if let Err(why) = config.read(String::from(
526     ///    "[2000s]
527     ///    2020 = bad
528     ///    2023 = better")) {
529     ///    panic!("{}", why);
530     ///};
531     ///if let Err(why) = config.read_and_append(String::from(
532     ///    "[2000s]
533     ///    2020 = terrible")) {
534     ///    panic!("{}", why);
535     ///};
536     ///let map = config.get_map().unwrap();
537     ///let few_years_ago = map["2000s"]["2020"].clone().unwrap();
538     ///let this_year = map["2000s"]["2023"].clone().unwrap();
539     ///assert_eq!(few_years_ago, "terrible"); // value updated!
540     ///assert_eq!(this_year, "better"); // keeps old values!
541     ///```
542     ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`.
543     ///Use `get_mut_map()` if you want a mutable reference.
read_and_append( &mut self, input: String, ) -> Result<Map<String, Map<String, Option<String>>>, String>544     pub fn read_and_append(
545         &mut self,
546         input: String,
547     ) -> Result<Map<String, Map<String, Option<String>>>, String> {
548         let loaded = match self.parse(input) {
549             Err(why) => return Err(why),
550             Ok(map) => map,
551         };
552 
553         for (section, section_map) in loaded.iter() {
554             self.map
555                 .entry(section.clone())
556                 .or_default()
557                 .extend(section_map.clone());
558         }
559 
560         Ok(self.map.clone())
561     }
562 
563     ///Writes the current configuation to the specified path using default formatting.
564     ///If a file is not present then it is automatically created for you. If a file already exists then it is overwritten.
565     ///## Example
566     ///```rust
567     ///use configparser::ini::Ini;
568     ///
569     ///fn main() -> std::io::Result<()> {
570     ///  let mut config = Ini::new();
571     ///  config.read(String::from(
572     ///    "[2000s]
573     ///    2020 = bad"));
574     ///  config.write("output.ini")
575     ///}
576     ///```
577     ///Returns a `std::io::Result<()>` type dependent on whether the write was successful or not.
write<T: AsRef<Path>>(&self, path: T) -> std::io::Result<()>578     pub fn write<T: AsRef<Path>>(&self, path: T) -> std::io::Result<()> {
579         fs::write(path.as_ref(), self.unparse(&WriteOptions::default()))
580     }
581 
582     ///Writes the current configuation to the specified path using the given formatting options.
583     ///If a file is not present then it is automatically created for you. If a file already exists then it is overwritten.
584     ///## Example
585     ///```rust
586     ///use configparser::ini::{Ini, WriteOptions};
587     ///
588     ///fn main() -> std::io::Result<()> {
589     ///  let mut write_options = WriteOptions::default();
590     ///  write_options.space_around_delimiters = true;
591     ///  write_options.multiline_line_indentation = 2;
592     ///  write_options.blank_lines_between_sections = 1;
593     ///
594     ///  let mut config = Ini::new();
595     ///  config.read(String::from(
596     ///    "[2000s]
597     ///    2020 = bad"));
598     ///  config.pretty_write("output.ini", &write_options)
599     ///}
600     ///```
601     ///Returns a `std::io::Result<()>` type dependent on whether the write was successful or not.
pretty_write<T: AsRef<Path>>( &self, path: T, write_options: &WriteOptions, ) -> std::io::Result<()>602     pub fn pretty_write<T: AsRef<Path>>(
603         &self,
604         path: T,
605         write_options: &WriteOptions,
606     ) -> std::io::Result<()> {
607         fs::write(path.as_ref(), self.unparse(write_options))
608     }
609 
610     ///Returns a string with the current configuration formatted with valid ini-syntax using default formatting.
611     ///This is always safe since the configuration is validated during parsing.
612     ///## Example
613     ///```rust
614     ///use configparser::ini::Ini;
615     ///
616     ///let mut config = Ini::new();
617     ///config.read(String::from(
618     ///  "[2000s]
619     ///  2020 = bad"));
620     ///let outstring = config.writes();
621     ///```
622     ///Returns a `String` type contatining the ini-syntax file.
writes(&self) -> String623     pub fn writes(&self) -> String {
624         self.unparse(&WriteOptions::default())
625     }
626 
627     ///Returns a string with the current configuration formatted with valid ini-syntax using the given formatting options.
628     ///This is always safe since the configuration is validated during parsing.
629     ///## Example
630     ///```rust
631     ///use configparser::ini::{Ini, WriteOptions};
632     ///
633     ///let mut write_options = WriteOptions::default();
634     ///write_options.space_around_delimiters = true;
635     ///write_options.multiline_line_indentation = 2;
636     ///write_options.blank_lines_between_sections = 1;
637     ///
638     ///let mut config = Ini::new();
639     ///config.read(String::from(
640     ///  "[2000s]
641     ///  2020 = bad"));
642     ///let outstring = config.pretty_writes(&write_options);
643     ///```
644     ///Returns a `String` type contatining the ini-syntax file.
pretty_writes(&self, write_options: &WriteOptions) -> String645     pub fn pretty_writes(&self, write_options: &WriteOptions) -> String {
646         self.unparse(write_options)
647     }
648 
649     ///Private function that converts the currently stored configuration into a valid ini-syntax string.
unparse(&self, write_options: &WriteOptions) -> String650     fn unparse(&self, write_options: &WriteOptions) -> String {
651         // push key/value pairs in outmap to out string.
652         fn unparse_key_values(
653             out: &mut String,
654             outmap: &Map<String, Option<String>>,
655             multiline: bool,
656             space_around_delimiters: bool,
657             indent: usize,
658         ) {
659             let delimiter = if space_around_delimiters { " = " } else { "=" };
660             for (key, val) in outmap.iter() {
661                 out.push_str(key);
662 
663                 if let Some(value) = val {
664                     if value.is_empty() {
665                         out.push_str(delimiter.trim_end());
666                     } else {
667                         out.push_str(delimiter);
668                     }
669 
670                     if multiline {
671                         let mut lines = value.lines();
672 
673                         out.push_str(lines.next().unwrap_or_default());
674 
675                         for line in lines {
676                             out.push_str(LINE_ENDING);
677                             out.push_str(" ".repeat(indent).as_ref());
678                             out.push_str(line);
679                         }
680                     } else {
681                         out.push_str(value);
682                     }
683                 }
684 
685                 out.push_str(LINE_ENDING);
686             }
687         }
688 
689         let line_endings = LINE_ENDING.repeat(write_options.blank_lines_between_sections);
690         let mut out = String::new();
691 
692         if let Some(defaultmap) = self.map.get(&self.default_section) {
693             unparse_key_values(
694                 &mut out,
695                 defaultmap,
696                 self.multiline,
697                 write_options.space_around_delimiters,
698                 write_options.multiline_line_indentation,
699             );
700         }
701 
702         let mut is_first = true;
703         for (section, secmap) in self.map.iter() {
704             if !is_first {
705                 out.push_str(line_endings.as_ref());
706             }
707             if section != &self.default_section {
708                 write!(out, "[{}]", section).unwrap();
709                 out.push_str(LINE_ENDING);
710                 unparse_key_values(
711                     &mut out,
712                     secmap,
713                     self.multiline,
714                     write_options.space_around_delimiters,
715                     write_options.multiline_line_indentation,
716                 );
717             }
718             is_first = false;
719         }
720         out
721     }
722 
723     ///Private function that parses ini-style syntax into a Map.
parse(&self, input: String) -> Result<Map<String, Map<String, Option<String>>>, String>724     fn parse(&self, input: String) -> Result<Map<String, Map<String, Option<String>>>, String> {
725         let mut map: Map<String, Map<String, Option<String>>> = Map::new();
726         let mut section = self.default_section.clone();
727         let mut current_key: Option<String> = None;
728 
729         let caser = |val: &str| {
730             if self.case_sensitive {
731                 val.to_owned()
732             } else {
733                 val.to_lowercase()
734             }
735         };
736 
737         for (num, raw_line) in input.lines().enumerate() {
738             let line = match raw_line.find(|c: char| self.comment_symbols.contains(&c)) {
739                 Some(idx) => &raw_line[..idx],
740                 None => raw_line,
741             };
742 
743             let trimmed = line.trim();
744 
745             if trimmed.is_empty() {
746                 continue;
747             }
748 
749             match (trimmed.find('['), trimmed.rfind(']')) {
750                 (Some(0), Some(end)) => {
751                     section = caser(trimmed[1..end].trim());
752 
753                     continue;
754                 }
755                 (Some(0), None) => {
756                     return Err(format!(
757                         "line {}: Found opening bracket for section name but no closing bracket",
758                         num
759                     ));
760                 }
761                 _ => {}
762             }
763 
764             if line.starts_with(char::is_whitespace) && self.multiline {
765                 let key = match current_key.as_ref() {
766                     Some(x) => x,
767                     None => {
768                         return Err(format!(
769                             "line {}: Started with indentation but there is no current entry",
770                             num,
771                         ))
772                     }
773                 };
774 
775                 let valmap = map.entry(section.clone()).or_default();
776 
777                 let val = valmap
778                     .entry(key.clone())
779                     .or_insert_with(|| Some(String::new()));
780 
781                 match val {
782                     Some(x) => {
783                         x.push_str(LINE_ENDING);
784                         x.push_str(trimmed);
785                     }
786                     None => {
787                         *val = Some(format!("{}{}", LINE_ENDING, trimmed));
788                     }
789                 }
790 
791                 continue;
792             }
793 
794             let valmap = map.entry(section.clone()).or_default();
795 
796             match trimmed.find(&self.delimiters[..]) {
797                 Some(delimiter) => {
798                     let key = caser(trimmed[..delimiter].trim());
799 
800                     if key.is_empty() {
801                         return Err(format!("line {}:{}: Key cannot be empty", num, delimiter));
802                     } else {
803                         current_key = Some(key.clone());
804 
805                         let value = trimmed[delimiter + 1..].trim().to_owned();
806 
807                         valmap.insert(key, Some(value));
808                     }
809                 }
810                 None => {
811                     let key = caser(trimmed);
812                     current_key = Some(key.clone());
813 
814                     valmap.insert(key, None);
815                 }
816             }
817         }
818 
819         Ok(map)
820     }
821 
822     ///Private function that cases things automatically depending on the set variable.
autocase(&self, section: &str, key: &str) -> (String, String)823     fn autocase(&self, section: &str, key: &str) -> (String, String) {
824         if self.case_sensitive {
825             (section.to_owned(), key.to_owned())
826         } else {
827             (section.to_lowercase(), key.to_lowercase())
828         }
829     }
830 
831     ///Returns a clone of the stored value from the key stored in the defined section.
832     ///Unlike accessing the map directly, `get()` can process your input to make case-insensitive access *if* the
833     ///default constructor is used.
834     ///All `get` functions will do this automatically under the hood.
835     ///## Example
836     ///```rust
837     ///use configparser::ini::Ini;
838     ///
839     ///let mut config = Ini::new();
840     ///config.load("tests/test.ini");
841     ///let value = config.get("default", "defaultvalues").unwrap();
842     ///assert_eq!(value, String::from("defaultvalues"));
843     ///```
844     ///Returns `Some(value)` of type `String` if value is found or else returns `None`.
get(&self, section: &str, key: &str) -> Option<String>845     pub fn get(&self, section: &str, key: &str) -> Option<String> {
846         let (section, key) = self.autocase(section, key);
847         self.map.get(&section)?.get(&key)?.clone()
848     }
849 
850     ///Parses the stored value from the key stored in the defined section to a `bool`.
851     ///For ease of use, the function converts the type case-insensitively (`true` == `True`).
852     ///## Example
853     ///```rust
854     ///use configparser::ini::Ini;
855     ///
856     ///let mut config = Ini::new();
857     ///config.load("tests/test.ini");
858     ///let value = config.getbool("values", "bool").unwrap().unwrap();
859     ///assert!(value);  // value accessible!
860     ///```
861     ///Returns `Ok(Some(value))` of type `bool` if value is found or else returns `Ok(None)`.
862     ///If the parsing fails, it returns an `Err(string)`.
getbool(&self, section: &str, key: &str) -> Result<Option<bool>, String>863     pub fn getbool(&self, section: &str, key: &str) -> Result<Option<bool>, String> {
864         let (section, key) = self.autocase(section, key);
865         match self.map.get(&section) {
866             Some(secmap) => match secmap.get(&key) {
867                 Some(val) => match val {
868                     Some(inner) => match inner.to_lowercase().parse::<bool>() {
869                         Err(why) => Err(why.to_string()),
870                         Ok(boolean) => Ok(Some(boolean)),
871                     },
872                     None => Ok(None),
873                 },
874                 None => Ok(None),
875             },
876             None => Ok(None),
877         }
878     }
879 
880     ///Parses the stored value from the key stored in the defined section to a `bool`. For ease of use, the function converts the type coerces a match.
881     ///It attempts to case-insenstively find `true`, `yes`, `t`, `y`, `1` and `on` to parse it as `True`.
882     ///Similarly it attempts to case-insensitvely find `false`, `no`, `f`, `n`, `0` and `off` to parse it as `False`.
883     ///## Example
884     ///```rust
885     ///use configparser::ini::Ini;
886     ///
887     ///let mut config = Ini::new();
888     ///config.load("tests/test.ini");
889     ///let value = config.getboolcoerce("values", "boolcoerce").unwrap().unwrap();
890     ///assert!(!value);  // value accessible!
891     ///```
892     ///Returns `Ok(Some(value))` of type `bool` if value is found or else returns `Ok(None)`.
893     ///If the parsing fails, it returns an `Err(string)`.
getboolcoerce(&self, section: &str, key: &str) -> Result<Option<bool>, String>894     pub fn getboolcoerce(&self, section: &str, key: &str) -> Result<Option<bool>, String> {
895         let (section, key) = self.autocase(section, key);
896         match self.map.get(&section) {
897             Some(secmap) => match secmap.get(&key) {
898                 Some(val) => match val {
899                     Some(inner) => {
900                         let boolval = &inner.to_lowercase()[..];
901                         if self
902                             .boolean_values
903                             .get(&true)
904                             .unwrap()
905                             .iter()
906                             .any(|elem| elem == boolval)
907                         {
908                             Ok(Some(true))
909                         } else if self
910                             .boolean_values
911                             .get(&false)
912                             .unwrap()
913                             .iter()
914                             .any(|elem| elem == boolval)
915                         {
916                             Ok(Some(false))
917                         } else {
918                             Err(format!(
919                                 "Unable to parse value into bool at {}:{}",
920                                 section, key
921                             ))
922                         }
923                     }
924                     None => Ok(None),
925                 },
926                 None => Ok(None),
927             },
928             None => Ok(None),
929         }
930     }
931 
932     ///Parses the stored value from the key stored in the defined section to an `i64`.
933     ///## Example
934     ///```rust
935     ///use configparser::ini::Ini;
936     ///
937     ///let mut config = Ini::new();
938     ///config.load("tests/test.ini");
939     ///let value = config.getint("values", "int").unwrap().unwrap();
940     ///assert_eq!(value, -31415);  // value accessible!
941     ///```
942     ///Returns `Ok(Some(value))` of type `i64` if value is found or else returns `Ok(None)`.
943     ///If the parsing fails, it returns an `Err(string)`.
getint(&self, section: &str, key: &str) -> Result<Option<i64>, String>944     pub fn getint(&self, section: &str, key: &str) -> Result<Option<i64>, String> {
945         let (section, key) = self.autocase(section, key);
946         match self.map.get(&section) {
947             Some(secmap) => match secmap.get(&key) {
948                 Some(val) => match val {
949                     Some(inner) => match inner.parse::<i64>() {
950                         Err(why) => Err(why.to_string()),
951                         Ok(int) => Ok(Some(int)),
952                     },
953                     None => Ok(None),
954                 },
955                 None => Ok(None),
956             },
957             None => Ok(None),
958         }
959     }
960 
961     ///Parses the stored value from the key stored in the defined section to a `u64`.
962     ///## Example
963     ///```rust
964     ///use configparser::ini::Ini;
965     ///
966     ///let mut config = Ini::new();
967     ///config.load("tests/test.ini");
968     ///let value = config.getint("values", "Uint").unwrap().unwrap();
969     ///assert_eq!(value, 31415);  // value accessible!
970     ///```
971     ///Returns `Ok(Some(value))` of type `u64` if value is found or else returns `Ok(None)`.
972     ///If the parsing fails, it returns an `Err(string)`.
getuint(&self, section: &str, key: &str) -> Result<Option<u64>, String>973     pub fn getuint(&self, section: &str, key: &str) -> Result<Option<u64>, String> {
974         let (section, key) = self.autocase(section, key);
975         match self.map.get(&section) {
976             Some(secmap) => match secmap.get(&key) {
977                 Some(val) => match val {
978                     Some(inner) => match inner.parse::<u64>() {
979                         Err(why) => Err(why.to_string()),
980                         Ok(uint) => Ok(Some(uint)),
981                     },
982                     None => Ok(None),
983                 },
984                 None => Ok(None),
985             },
986             None => Ok(None),
987         }
988     }
989 
990     ///Parses the stored value from the key stored in the defined section to a `f64`.
991     ///## Example
992     ///```rust
993     ///use configparser::ini::Ini;
994     ///
995     ///let mut config = Ini::new();
996     ///config.load("tests/test.ini");
997     ///let value = config.getfloat("values", "float").unwrap().unwrap();
998     ///assert_eq!(value, 3.1415);  // value accessible!
999     ///```
1000     ///Returns `Ok(Some(value))` of type `f64` if value is found or else returns `Ok(None)`.
1001     ///If the parsing fails, it returns an `Err(string)`.
getfloat(&self, section: &str, key: &str) -> Result<Option<f64>, String>1002     pub fn getfloat(&self, section: &str, key: &str) -> Result<Option<f64>, String> {
1003         let (section, key) = self.autocase(section, key);
1004         match self.map.get(&section) {
1005             Some(secmap) => match secmap.get(&key) {
1006                 Some(val) => match val {
1007                     Some(inner) => match inner.parse::<f64>() {
1008                         Err(why) => Err(why.to_string()),
1009                         Ok(float) => Ok(Some(float)),
1010                     },
1011                     None => Ok(None),
1012                 },
1013                 None => Ok(None),
1014             },
1015             None => Ok(None),
1016         }
1017     }
1018 
1019     ///Returns a clone of the `Map` stored in our struct.
1020     ///## Example
1021     ///```rust
1022     ///use configparser::ini::Ini;
1023     ///
1024     ///let mut config = Ini::new();
1025     ///config.read(String::from(
1026     ///  "[section]
1027     ///  key=values"));
1028     ///let map = config.get_map().unwrap();
1029     ///assert_eq!(map, *config.get_map_ref());  // the cloned map is basically a snapshot that you own
1030     ///```
1031     ///Returns `Some(map)` if map is non-empty or else returns `None`.
1032     ///Similar to `load()` but returns an `Option` type with the currently stored `Map`.
get_map(&self) -> Option<Map<String, Map<String, Option<String>>>>1033     pub fn get_map(&self) -> Option<Map<String, Map<String, Option<String>>>> {
1034         if self.map.is_empty() {
1035             None
1036         } else {
1037             Some(self.map.clone())
1038         }
1039     }
1040 
1041     ///Returns an immutable reference to the `Map` stored in our struct.
1042     ///## Example
1043     ///```rust
1044     ///use configparser::ini::Ini;
1045     ///
1046     ///let mut config = Ini::new();
1047     ///let mapclone = config.read(String::from
1048     ///  ("[topsecrets]
1049     ///  Valueless key")).unwrap();
1050     /////Think of the clone as being a snapshot at a point of time while the reference always points to the current configuration.
1051     ///assert_eq!(*config.get_map_ref(), mapclone);  // same as expected.
1052     ///```
1053     ///If you just need to definitely mutate the map, use `get_mut_map()` instead. Alternatively, you can generate a snapshot by getting a clone
1054     ///with `get_map()` and work with that.
get_map_ref(&self) -> &Map<String, Map<String, Option<String>>>1055     pub fn get_map_ref(&self) -> &Map<String, Map<String, Option<String>>> {
1056         &self.map
1057     }
1058 
1059     ///Returns a mutable reference to the `Map` stored in our struct.
1060     ///## Example
1061     ///```rust
1062     ///use configparser::ini::Ini;
1063     ///
1064     ///let mut config = Ini::new();
1065     ///config.read(String::from
1066     ///  ("[topsecrets]
1067     ///  Valueless key"));
1068     /////We can then get the mutable map and insert a value like:
1069     ///config.get_mut_map().get_mut("topsecrets").unwrap().insert(String::from("nuclear launch codes"), None);
1070     ///assert_eq!(config.get("topsecrets", "nuclear launch codes"), None);  // inserted successfully!
1071     ///```
1072     ///If you just need to access the map without mutating, use `get_map_ref()` or make a clone with `get_map()` instead.
get_mut_map(&mut self) -> &mut Map<String, Map<String, Option<String>>>1073     pub fn get_mut_map(&mut self) -> &mut Map<String, Map<String, Option<String>>> {
1074         &mut self.map
1075     }
1076 
1077     ///Sets an `Option<String>` in the `Map` stored in our struct. If a particular section or key does not exist, it will be automatically created.
1078     ///An existing value in the map  will be overwritten. You can also set `None` safely.
1079     ///## Example
1080     ///```rust
1081     ///use configparser::ini::Ini;
1082     ///
1083     ///let mut config = Ini::new();
1084     ///config.read(String::from(
1085     ///  "[section]
1086     ///  key=value"));
1087     ///let key_value = String::from("value");
1088     ///config.set("section", "key", Some(key_value));
1089     ///config.set("section", "key", None);  // also valid!
1090     ///assert_eq!(config.get("section", "key"), None);  // correct!
1091     ///```
1092     ///Returns `None` if there is no existing value, else returns `Some(Option<String>)`, with the existing value being the wrapped `Option<String>`.
1093     ///If you want to insert using a string literal, use `setstr()` instead.
set( &mut self, section: &str, key: &str, value: Option<String>, ) -> Option<Option<String>>1094     pub fn set(
1095         &mut self,
1096         section: &str,
1097         key: &str,
1098         value: Option<String>,
1099     ) -> Option<Option<String>> {
1100         let (section, key) = self.autocase(section, key);
1101         match self.map.get_mut(&section) {
1102             Some(secmap) => secmap.insert(key, value),
1103             None => {
1104                 let mut valmap: Map<String, Option<String>> = Map::new();
1105                 valmap.insert(key, value);
1106                 self.map.insert(section, valmap);
1107                 None
1108             }
1109         }
1110     }
1111 
1112     ///Sets an `Option<&str>` in the `Map` stored in our struct. If a particular section or key does not exist, it will be automatically created.
1113     ///An existing value in the map  will be overwritten. You can also set `None` safely.
1114     ///## Example
1115     ///```rust
1116     ///use configparser::ini::Ini;
1117     ///
1118     ///let mut config = Ini::new();
1119     ///config.read(String::from(
1120     ///  "[section]
1121     ///  key=notvalue"));
1122     ///config.setstr("section", "key", Some("value"));
1123     ///config.setstr("section", "key", None);  // also valid!
1124     ///assert_eq!(config.get("section", "key"), None);  // correct!
1125     ///```
1126     ///Returns `None` if there is no existing value, else returns `Some(Option<String>)`, with the existing value being the wrapped `Option<String>`.
1127     ///If you want to insert using a `String`, use `set()` instead.
setstr( &mut self, section: &str, key: &str, value: Option<&str>, ) -> Option<Option<String>>1128     pub fn setstr(
1129         &mut self,
1130         section: &str,
1131         key: &str,
1132         value: Option<&str>,
1133     ) -> Option<Option<String>> {
1134         let (section, key) = self.autocase(section, key);
1135         self.set(&section, &key, value.map(String::from))
1136     }
1137 
1138     ///Clears the map, removing all sections and properties from the hashmap. It keeps the allocated memory for reuse.
1139     ///## Example
1140     ///```rust
1141     ///use configparser::ini::Ini;
1142     ///
1143     ///let mut config = Ini::new();
1144     ///config.read(String::from(
1145     ///  "[section]
1146     ///  key=somevalue"));
1147     ///config.clear();
1148     ///assert!(config.get_map_ref().is_empty());  // our map is empty!
1149     ///```
1150     ///Returns nothing.
clear(&mut self)1151     pub fn clear(&mut self) {
1152         self.map.clear();
1153     }
1154 
1155     ///Removes a section from the hashmap, returning the properties stored in the section if the section was previously in the map.
1156     ///## Example
1157     ///```rust
1158     ///use configparser::ini::Ini;
1159     ///
1160     ///let mut config = Ini::new();
1161     ///config.read(String::from(
1162     ///  "[section]
1163     ///  updog=whatsupdog"));
1164     ///config.remove_section("section");  // this will return a cloned hashmap of the stored property
1165     ///assert!(config.get_map_ref().is_empty());  // with the last section removed, our map is now empty!
1166     ///```
1167     ///Returns `Some(section_map)` if the section exists or else, `None`.
remove_section(&mut self, section: &str) -> Option<Map<String, Option<String>>>1168     pub fn remove_section(&mut self, section: &str) -> Option<Map<String, Option<String>>> {
1169         let section = if self.case_sensitive {
1170             section.to_owned()
1171         } else {
1172             section.to_lowercase()
1173         };
1174         self.map.remove(&section)
1175     }
1176 
1177     ///Removes a key from a section in the hashmap, returning the value attached to the key if it was previously in the map.
1178     ///## Example
1179     ///```rust
1180     ///use configparser::ini::Ini;
1181     ///
1182     ///let mut config = Ini::new();
1183     ///config.read(String::from(
1184     ///  "[section]
1185     ///  updog=whatsupdog
1186     ///  [anothersection]
1187     ///  updog=differentdog"));
1188     ///let val = config.remove_key("anothersection", "updog").unwrap().unwrap();
1189     ///assert_eq!(val, String::from("differentdog"));  // with the last section removed, our map is now empty!
1190     ///```
1191     ///Returns `Some(Option<String>)` if the value exists or else, `None`.
remove_key(&mut self, section: &str, key: &str) -> Option<Option<String>>1192     pub fn remove_key(&mut self, section: &str, key: &str) -> Option<Option<String>> {
1193         let (section, key) = self.autocase(section, key);
1194         self.map.get_mut(&section)?.remove(&key)
1195     }
1196 }
1197 
1198 #[cfg(feature = "async-std")]
1199 impl Ini {
1200     ///Loads a file asynchronously from a defined path, parses it and puts the hashmap into our struct.
1201     ///At one time, it only stores one configuration, so each call to `load()` or `read()` will clear the existing `Map`, if present.
1202     ///
1203     ///Usage is similar to `load`, but `.await` must be called after along with the usual async rules.
1204     ///
1205     ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`.
1206     ///Use `get_mut_map()` if you want a mutable reference.
load_async<T: AsRef<Path>>( &mut self, path: T, ) -> Result<Map<String, Map<String, Option<String>>>, String>1207     pub async fn load_async<T: AsRef<Path>>(
1208         &mut self,
1209         path: T,
1210     ) -> Result<Map<String, Map<String, Option<String>>>, String> {
1211         self.map = match self.parse(match async_fs::read_to_string(&path).await {
1212             Err(why) => {
1213                 return Err(format!(
1214                     "couldn't read {}: {}",
1215                     &path.as_ref().display(),
1216                     why
1217                 ))
1218             }
1219             Ok(s) => s,
1220         }) {
1221             Err(why) => {
1222                 return Err(format!(
1223                     "couldn't read {}: {}",
1224                     &path.as_ref().display(),
1225                     why
1226                 ))
1227             }
1228             Ok(map) => map,
1229         };
1230         Ok(self.map.clone())
1231     }
1232 
1233     ///Loads a file from a defined path, parses it and applies it to the existing hashmap in our struct.
1234     ///While `load_async()` will clear the existing `Map`, `load_and_append_async()` applies the new values on top
1235     ///of the existing hashmap, preserving previous values.
1236     ///
1237     ///Usage is similar to `load_and_append`, but `.await` must be called after along with the usual async rules.
1238     ///
1239     ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`.
1240     ///Use `get_mut_map()` if you want a mutable reference.
load_and_append_async<T: AsRef<Path>>( &mut self, path: T, ) -> Result<Map<String, Map<String, Option<String>>>, String>1241     pub async fn load_and_append_async<T: AsRef<Path>>(
1242         &mut self,
1243         path: T,
1244     ) -> Result<Map<String, Map<String, Option<String>>>, String> {
1245         let loaded = match self.parse(match async_fs::read_to_string(&path).await {
1246             Err(why) => {
1247                 return Err(format!(
1248                     "couldn't read {}: {}",
1249                     &path.as_ref().display(),
1250                     why
1251                 ))
1252             }
1253             Ok(s) => s,
1254         }) {
1255             Err(why) => {
1256                 return Err(format!(
1257                     "couldn't read {}: {}",
1258                     &path.as_ref().display(),
1259                     why
1260                 ))
1261             }
1262             Ok(map) => map,
1263         };
1264 
1265         for (section, section_map) in loaded.iter() {
1266             self.map
1267                 .entry(section.clone())
1268                 .or_insert_with(Map::new)
1269                 .extend(section_map.clone());
1270         }
1271 
1272         Ok(self.map.clone())
1273     }
1274 
1275     ///Writes the current configuation to the specified path asynchronously using default formatting. If a file is not present, it is automatically created for you, if a file already
1276     ///exists, it is truncated and the configuration is written to it.
1277     ///
1278     ///Usage is the same as `write`, but `.await` must be called after along with the usual async rules.
1279     ///
1280     ///Returns a `std::io::Result<()>` type dependent on whether the write was successful or not.
write_async<T: AsRef<Path>>(&self, path: T) -> std::io::Result<()>1281     pub async fn write_async<T: AsRef<Path>>(&self, path: T) -> std::io::Result<()> {
1282         async_fs::write(path.as_ref(), self.unparse(&WriteOptions::default())).await
1283     }
1284 
1285     ///Writes the current configuation to the specified path asynchronously using the given formatting options. If a file is not present, it is automatically created for you, if a file already
1286     ///exists, it is truncated and the configuration is written to it.
1287     ///
1288     ///Usage is the same as `pretty_pretty_write`, but `.await` must be called after along with the usual async rules.
1289     ///
1290     ///Returns a `std::io::Result<()>` type dependent on whether the write was successful or not.
pretty_write_async<T: AsRef<Path>>( &self, path: T, write_options: &WriteOptions, ) -> std::io::Result<()>1291     pub async fn pretty_write_async<T: AsRef<Path>>(
1292         &self,
1293         path: T,
1294         write_options: &WriteOptions,
1295     ) -> std::io::Result<()> {
1296         async_fs::write(path.as_ref(), self.unparse(write_options)).await
1297     }
1298 }
1299