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(§ion)?.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(§ion) { 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(§ion) { 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(§ion) { 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(§ion) { 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(§ion) { 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(§ion) { 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(§ion, &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(§ion) 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(§ion)?.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