| //!The ini module provides all the things necessary to load and parse ini-syntax files. The most important of which is the `Ini` struct. |
| //!See the [implementation](https://docs.rs/configparser/*/configparser/ini/struct.Ini.html) documentation for more details. |
| #[cfg(feature = "indexmap")] |
| use indexmap::IndexMap as Map; |
| #[cfg(not(feature = "indexmap"))] |
| use std::collections::HashMap as Map; |
| |
| #[deprecated( |
| since = "3.0.4", |
| note = "async-std runtime has been replaced with tokio" |
| )] |
| #[cfg(feature = "async-std")] |
| #[cfg(feature = "tokio")] |
| use tokio::fs as async_fs; |
| |
| use std::collections::HashMap; |
| use std::convert::AsRef; |
| use std::fmt::Write; |
| use std::fs; |
| use std::path::Path; |
| |
| ///The `Ini` struct simply contains a nested hashmap of the loaded configuration, the default section header and comment symbols. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///``` |
| #[derive(Debug, Clone, Eq, PartialEq, Default)] |
| #[non_exhaustive] |
| pub struct Ini { |
| map: Map<String, Map<String, Option<String>>>, |
| default_section: std::string::String, |
| comment_symbols: Vec<char>, |
| delimiters: Vec<char>, |
| boolean_values: HashMap<bool, Vec<String>>, |
| case_sensitive: bool, |
| multiline: bool, |
| } |
| |
| ///The `IniDefault` struct serves as a template to create other `Ini` objects from. It can be used to store and load |
| ///default properties from different `Ini` objects. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///let default = config.defaults(); |
| ///let mut config2 = Ini::new_from_defaults(default); // default gets consumed |
| ///``` |
| #[derive(Debug, Clone, Eq, PartialEq)] |
| #[non_exhaustive] |
| pub struct IniDefault { |
| ///Denotes the default section header name. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///let default = config.defaults(); |
| ///assert_eq!(default.default_section, "default"); |
| ///``` |
| pub default_section: std::string::String, |
| ///Denotes the set comment symbols for the object. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///let default = config.defaults(); |
| ///assert_eq!(default.comment_symbols, vec![';', '#']); |
| ///``` |
| pub comment_symbols: Vec<char>, |
| ///Denotes the set delimiters for the key-value pairs. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///let default = config.defaults(); |
| ///assert_eq!(default.delimiters, vec!['=', ':']); |
| ///``` |
| pub delimiters: Vec<char>, |
| pub boolean_values: HashMap<bool, Vec<String>>, |
| ///Denotes if the `Ini` object is case-sensitive. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///let default = config.defaults(); |
| ///assert_eq!(default.case_sensitive, false); |
| ///``` |
| pub case_sensitive: bool, |
| ///Denotes if the `Ini` object parses multiline strings. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///let default = config.defaults(); |
| ///assert_eq!(default.multiline, false); |
| ///``` |
| pub multiline: bool, |
| } |
| |
| impl Default for IniDefault { |
| fn default() -> Self { |
| Self { |
| default_section: "default".to_owned(), |
| comment_symbols: vec![';', '#'], |
| delimiters: vec!['=', ':'], |
| multiline: false, |
| boolean_values: [ |
| ( |
| true, |
| ["true", "yes", "t", "y", "on", "1"] |
| .iter() |
| .map(|&s| s.to_owned()) |
| .collect(), |
| ), |
| ( |
| false, |
| ["false", "no", "f", "n", "off", "0"] |
| .iter() |
| .map(|&s| s.to_owned()) |
| .collect(), |
| ), |
| ] |
| .iter() |
| .cloned() |
| .collect(), |
| case_sensitive: false, |
| } |
| } |
| } |
| |
| /// Use this struct to define formatting options for the `pretty_write` functions. |
| #[derive(Debug, Clone, Eq, PartialEq)] |
| #[non_exhaustive] |
| pub struct WriteOptions { |
| ///If true then the keys and values will be separated by " = ". In the special case where the value is empty, the |
| ///line ends with " =". |
| ///If false then keys and values will be separated by "=". |
| ///Default is `false`. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::WriteOptions; |
| /// |
| ///let mut write_options = WriteOptions::default(); |
| ///assert_eq!(write_options.space_around_delimiters, false); |
| ///``` |
| pub space_around_delimiters: bool, |
| |
| ///Defines the number of spaces for indentation of for multiline values. |
| ///Default is 4 spaces. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::WriteOptions; |
| /// |
| ///let mut write_options = WriteOptions::default(); |
| ///assert_eq!(write_options.multiline_line_indentation, 4); |
| ///``` |
| pub multiline_line_indentation: usize, |
| |
| ///Defines the number of blank lines between sections. |
| ///Default is 0. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::WriteOptions; |
| /// |
| ///let mut write_options = WriteOptions::default(); |
| ///assert_eq!(write_options.blank_lines_between_sections, 0); |
| ///``` |
| pub blank_lines_between_sections: usize, |
| } |
| |
| impl Default for WriteOptions { |
| fn default() -> Self { |
| Self { |
| space_around_delimiters: false, |
| multiline_line_indentation: 4, |
| blank_lines_between_sections: 0, |
| } |
| } |
| } |
| |
| impl WriteOptions { |
| ///Creates a new `WriteOptions` object with the default values. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::WriteOptions; |
| /// |
| ///let write_options = WriteOptions::new(); |
| ///assert_eq!(write_options.space_around_delimiters, false); |
| ///assert_eq!(write_options.multiline_line_indentation, 4); |
| ///assert_eq!(write_options.blank_lines_between_sections, 0); |
| ///``` |
| ///Returns the struct and stores it in the calling variable. |
| pub fn new() -> WriteOptions { |
| WriteOptions::default() |
| } |
| |
| ///Creates a new `WriteOptions` object with the given parameters. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::WriteOptions; |
| /// |
| ///let write_options = WriteOptions::new_with_params(true, 2, 1); |
| ///assert_eq!(write_options.space_around_delimiters, true); |
| ///assert_eq!(write_options.multiline_line_indentation, 2); |
| ///assert_eq!(write_options.blank_lines_between_sections, 1); |
| ///``` |
| ///Returns the struct and stores it in the calling variable. |
| pub fn new_with_params( |
| space_around_delimiters: bool, |
| multiline_line_indentation: usize, |
| blank_lines_between_sections: usize, |
| ) -> WriteOptions { |
| Self { |
| space_around_delimiters, |
| multiline_line_indentation, |
| blank_lines_between_sections, |
| } |
| } |
| } |
| |
| #[cfg(windows)] |
| const LINE_ENDING: &str = "\r\n"; |
| #[cfg(not(windows))] |
| const LINE_ENDING: &str = "\n"; |
| |
| impl Ini { |
| ///Creates a new `Map` of `Map<String, Map<String, Option<String>>>` type for the struct. |
| ///All values in the Map are stored in `String` type. |
| /// |
| ///By default, [`std::collections::HashMap`] is used for the Map object. |
| ///The `indexmap` feature can be used to use an [`indexmap::map::IndexMap`] instead, which |
| ///allows keeping the insertion order for sections and keys. |
| /// |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///``` |
| ///Returns the struct and stores it in the calling variable. |
| pub fn new() -> Ini { |
| Ini::new_from_defaults(IniDefault::default()) |
| } |
| |
| ///Creates a new **case-sensitive** `Map` of `Map<String, Map<String, Option<String>>>` type for the struct. |
| ///All values in the Map are stored in `String` type. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new_cs(); |
| ///``` |
| ///Returns the struct and stores it in the calling variable. |
| pub fn new_cs() -> Ini { |
| Ini::new_from_defaults(IniDefault { |
| case_sensitive: true, |
| ..Default::default() |
| }) |
| } |
| |
| ///Creates a new `Ini` with the given defaults from an existing `IniDefault` object. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| ///use configparser::ini::IniDefault; |
| /// |
| ///let mut default = IniDefault::default(); |
| ///default.comment_symbols = vec![';']; |
| ///default.delimiters = vec!['=']; |
| ///let mut config = Ini::new_from_defaults(default.clone()); |
| ///// Now, load as usual with new defaults: |
| ///let map = config.load("tests/test.ini").unwrap(); |
| ///assert_eq!(config.defaults(), default); |
| /// |
| ///``` |
| pub fn new_from_defaults(defaults: IniDefault) -> Ini { |
| Ini { |
| map: Map::new(), |
| default_section: defaults.default_section, |
| comment_symbols: defaults.comment_symbols, |
| delimiters: defaults.delimiters, |
| boolean_values: defaults.boolean_values, |
| case_sensitive: defaults.case_sensitive, |
| multiline: defaults.multiline, |
| } |
| } |
| |
| ///Fetches the defaults from the current `Ini` object and stores it as a `IniDefault` struct for usage elsewhere. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///let default = config.defaults(); |
| ///``` |
| ///Returns an `IniDefault` object. Keep in mind that it will get borrowed since it has non-`Copy` types. |
| pub fn defaults(&self) -> IniDefault { |
| IniDefault { |
| default_section: self.default_section.to_owned(), |
| comment_symbols: self.comment_symbols.to_owned(), |
| delimiters: self.delimiters.to_owned(), |
| boolean_values: self.boolean_values.to_owned(), |
| case_sensitive: self.case_sensitive, |
| multiline: self.multiline, |
| } |
| } |
| |
| ///Takes an `IniDefault` object and stores its properties in the calling `Ini` object. This happens in-place and |
| ///does not work retroactively, only future operations are affected. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| ///use configparser::ini::IniDefault; |
| /// |
| ///let mut config = Ini::new(); |
| ///let mut default = IniDefault::default(); |
| ///default.case_sensitive = true; |
| ///// This is equivalent to ini_cs() defaults |
| ///config.load_defaults(default.clone()); |
| ///assert_eq!(config.defaults(), default); |
| ///``` |
| ///Returns nothing. |
| pub fn load_defaults(&mut self, defaults: IniDefault) { |
| self.default_section = defaults.default_section; |
| self.comment_symbols = defaults.comment_symbols; |
| self.delimiters = defaults.delimiters; |
| self.boolean_values = defaults.boolean_values; |
| self.case_sensitive = defaults.case_sensitive; |
| } |
| |
| ///Sets the default section header to the defined string (the default is `default`). |
| ///It must be set before `load()` or `read()` is called in order to take effect. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| /// |
| ///config.set_default_section("topsecret"); |
| ///let map = config.load("tests/test.ini").unwrap(); |
| ///``` |
| ///Returns nothing. |
| pub fn set_default_section(&mut self, section: &str) { |
| self.default_section = section.to_owned(); |
| } |
| |
| ///Sets the default comment symbols to the defined character slice (the defaults are `;` and `#`). |
| ///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. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.set_comment_symbols(&['!', '#']); |
| ///let map = config.load("tests/test.ini").unwrap(); |
| ///``` |
| ///Returns nothing. |
| pub fn set_comment_symbols(&mut self, symlist: &[char]) { |
| self.comment_symbols = symlist.to_vec(); |
| } |
| |
| ///Sets multiline string support. |
| ///It must be set before `load()` or `read()` is called in order to take effect. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.set_multiline(true); |
| ///let map = config.load("tests/test.ini").unwrap(); |
| ///``` |
| ///Returns nothing. |
| pub fn set_multiline(&mut self, multiline: bool) { |
| self.multiline = multiline; |
| } |
| |
| ///Gets all the sections of the currently-stored `Map` in a vector. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.load("tests/test.ini"); |
| ///let sections = config.sections(); |
| ///``` |
| ///Returns `Vec<String>`. |
| pub fn sections(&self) -> Vec<String> { |
| self.map.keys().cloned().collect() |
| } |
| |
| ///Loads a file from a defined path, parses it and puts the hashmap into our struct. |
| ///At one time, it only stores one configuration, so each call to `load()` or `read()` will clear the existing `Map`, if present. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///let map = config.load("tests/test.ini").unwrap(); // we can get a clone like this, or just store it |
| /////Then, we can use standard hashmap functions like: |
| ///let values = map.get("values").unwrap(); |
| ///``` |
| ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`. |
| ///Use `get_mut_map()` if you want a mutable reference. |
| pub fn load<T: AsRef<Path>>( |
| &mut self, |
| path: T, |
| ) -> Result<Map<String, Map<String, Option<String>>>, String> { |
| self.map = match self.parse(match fs::read_to_string(&path) { |
| Err(why) => { |
| return Err(format!( |
| "couldn't read {}: {}", |
| &path.as_ref().display(), |
| why |
| )) |
| } |
| Ok(s) => s, |
| }) { |
| Err(why) => { |
| return Err(format!( |
| "couldn't read {}: {}", |
| &path.as_ref().display(), |
| why |
| )) |
| } |
| Ok(map) => map, |
| }; |
| Ok(self.map.clone()) |
| } |
| |
| ///Loads a file from a defined path, parses it and applies it to the existing hashmap in our struct. |
| ///While `load()` will clear the existing `Map`, `load_and_append()` applies the new values on top of |
| ///the existing hashmap, preserving previous values. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.load("tests/test.ini").unwrap(); |
| ///config.load_and_append("tests/sys_cfg.ini").ok(); // we don't have to worry if this doesn't succeed |
| ///config.load_and_append("tests/user_cfg.ini").ok(); // we don't have to worry if this doesn't succeed |
| ///let map = config.get_map().unwrap(); |
| /////Then, we can use standard hashmap functions like: |
| ///let values = map.get("values").unwrap(); |
| ///``` |
| ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`. |
| ///Use `get_mut_map()` if you want a mutable reference. |
| pub fn load_and_append<T: AsRef<Path>>( |
| &mut self, |
| path: T, |
| ) -> Result<Map<String, Map<String, Option<String>>>, String> { |
| let loaded = match self.parse(match fs::read_to_string(&path) { |
| Err(why) => { |
| return Err(format!( |
| "couldn't read {}: {}", |
| &path.as_ref().display(), |
| why |
| )) |
| } |
| Ok(s) => s, |
| }) { |
| Err(why) => { |
| return Err(format!( |
| "couldn't read {}: {}", |
| &path.as_ref().display(), |
| why |
| )) |
| } |
| Ok(map) => map, |
| }; |
| |
| for (section, section_map) in loaded.iter() { |
| self.map |
| .entry(section.clone()) |
| .or_default() |
| .extend(section_map.clone()); |
| } |
| |
| Ok(self.map.clone()) |
| } |
| |
| ///Reads an input string, parses it and puts the hashmap into our struct. |
| ///At one time, it only stores one configuration, so each call to `load()` or `read()` will clear the existing `Map`, if present. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///let map = match config.read(String::from( |
| /// "[2000s] |
| /// 2020 = bad")) { |
| /// Err(why) => panic!("{}", why), |
| /// Ok(inner) => inner |
| ///}; |
| ///let this_year = map["2000s"]["2020"].clone().unwrap(); |
| ///assert_eq!(this_year, "bad"); // value accessible! |
| ///``` |
| ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`. |
| ///Use `get_mut_map()` if you want a mutable reference. |
| pub fn read( |
| &mut self, |
| input: String, |
| ) -> Result<Map<String, Map<String, Option<String>>>, String> { |
| self.map = match self.parse(input) { |
| Err(why) => return Err(why), |
| Ok(map) => map, |
| }; |
| Ok(self.map.clone()) |
| } |
| |
| ///Reads an input string, parses it and applies it to the existing hashmap in our struct. |
| ///While `read()` and `load()` will clear the existing `Map`, `read_and_append()` applies the new |
| ///values on top of the existing hashmap, preserving previous values. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///if let Err(why) = config.read(String::from( |
| /// "[2000s] |
| /// 2020 = bad |
| /// 2023 = better")) { |
| /// panic!("{}", why); |
| ///}; |
| ///if let Err(why) = config.read_and_append(String::from( |
| /// "[2000s] |
| /// 2020 = terrible")) { |
| /// panic!("{}", why); |
| ///}; |
| ///let map = config.get_map().unwrap(); |
| ///let few_years_ago = map["2000s"]["2020"].clone().unwrap(); |
| ///let this_year = map["2000s"]["2023"].clone().unwrap(); |
| ///assert_eq!(few_years_ago, "terrible"); // value updated! |
| ///assert_eq!(this_year, "better"); // keeps old values! |
| ///``` |
| ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`. |
| ///Use `get_mut_map()` if you want a mutable reference. |
| pub fn read_and_append( |
| &mut self, |
| input: String, |
| ) -> Result<Map<String, Map<String, Option<String>>>, String> { |
| let loaded = match self.parse(input) { |
| Err(why) => return Err(why), |
| Ok(map) => map, |
| }; |
| |
| for (section, section_map) in loaded.iter() { |
| self.map |
| .entry(section.clone()) |
| .or_default() |
| .extend(section_map.clone()); |
| } |
| |
| Ok(self.map.clone()) |
| } |
| |
| ///Writes the current configuation to the specified path using default formatting. |
| ///If a file is not present then it is automatically created for you. If a file already exists then it is overwritten. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///fn main() -> std::io::Result<()> { |
| /// let mut config = Ini::new(); |
| /// config.read(String::from( |
| /// "[2000s] |
| /// 2020 = bad")); |
| /// config.write("output.ini") |
| ///} |
| ///``` |
| ///Returns a `std::io::Result<()>` type dependent on whether the write was successful or not. |
| pub fn write<T: AsRef<Path>>(&self, path: T) -> std::io::Result<()> { |
| fs::write(path.as_ref(), self.unparse(&WriteOptions::default())) |
| } |
| |
| ///Writes the current configuation to the specified path using the given formatting options. |
| ///If a file is not present then it is automatically created for you. If a file already exists then it is overwritten. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::{Ini, WriteOptions}; |
| /// |
| ///fn main() -> std::io::Result<()> { |
| /// let mut write_options = WriteOptions::default(); |
| /// write_options.space_around_delimiters = true; |
| /// write_options.multiline_line_indentation = 2; |
| /// write_options.blank_lines_between_sections = 1; |
| /// |
| /// let mut config = Ini::new(); |
| /// config.read(String::from( |
| /// "[2000s] |
| /// 2020 = bad")); |
| /// config.pretty_write("output.ini", &write_options) |
| ///} |
| ///``` |
| ///Returns a `std::io::Result<()>` type dependent on whether the write was successful or not. |
| pub fn pretty_write<T: AsRef<Path>>( |
| &self, |
| path: T, |
| write_options: &WriteOptions, |
| ) -> std::io::Result<()> { |
| fs::write(path.as_ref(), self.unparse(write_options)) |
| } |
| |
| ///Returns a string with the current configuration formatted with valid ini-syntax using default formatting. |
| ///This is always safe since the configuration is validated during parsing. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.read(String::from( |
| /// "[2000s] |
| /// 2020 = bad")); |
| ///let outstring = config.writes(); |
| ///``` |
| ///Returns a `String` type contatining the ini-syntax file. |
| pub fn writes(&self) -> String { |
| self.unparse(&WriteOptions::default()) |
| } |
| |
| ///Returns a string with the current configuration formatted with valid ini-syntax using the given formatting options. |
| ///This is always safe since the configuration is validated during parsing. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::{Ini, WriteOptions}; |
| /// |
| ///let mut write_options = WriteOptions::default(); |
| ///write_options.space_around_delimiters = true; |
| ///write_options.multiline_line_indentation = 2; |
| ///write_options.blank_lines_between_sections = 1; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.read(String::from( |
| /// "[2000s] |
| /// 2020 = bad")); |
| ///let outstring = config.pretty_writes(&write_options); |
| ///``` |
| ///Returns a `String` type contatining the ini-syntax file. |
| pub fn pretty_writes(&self, write_options: &WriteOptions) -> String { |
| self.unparse(write_options) |
| } |
| |
| ///Private function that converts the currently stored configuration into a valid ini-syntax string. |
| fn unparse(&self, write_options: &WriteOptions) -> String { |
| // push key/value pairs in outmap to out string. |
| fn unparse_key_values( |
| out: &mut String, |
| outmap: &Map<String, Option<String>>, |
| multiline: bool, |
| space_around_delimiters: bool, |
| indent: usize, |
| ) { |
| let delimiter = if space_around_delimiters { " = " } else { "=" }; |
| for (key, val) in outmap.iter() { |
| out.push_str(key); |
| |
| if let Some(value) = val { |
| if value.is_empty() { |
| out.push_str(delimiter.trim_end()); |
| } else { |
| out.push_str(delimiter); |
| } |
| |
| if multiline { |
| let mut lines = value.lines(); |
| |
| out.push_str(lines.next().unwrap_or_default()); |
| |
| for line in lines { |
| out.push_str(LINE_ENDING); |
| out.push_str(" ".repeat(indent).as_ref()); |
| out.push_str(line); |
| } |
| } else { |
| out.push_str(value); |
| } |
| } |
| |
| out.push_str(LINE_ENDING); |
| } |
| } |
| |
| let line_endings = LINE_ENDING.repeat(write_options.blank_lines_between_sections); |
| let mut out = String::new(); |
| |
| if let Some(defaultmap) = self.map.get(&self.default_section) { |
| unparse_key_values( |
| &mut out, |
| defaultmap, |
| self.multiline, |
| write_options.space_around_delimiters, |
| write_options.multiline_line_indentation, |
| ); |
| } |
| |
| let mut is_first = true; |
| for (section, secmap) in self.map.iter() { |
| if !is_first { |
| out.push_str(line_endings.as_ref()); |
| } |
| if section != &self.default_section { |
| write!(out, "[{}]", section).unwrap(); |
| out.push_str(LINE_ENDING); |
| unparse_key_values( |
| &mut out, |
| secmap, |
| self.multiline, |
| write_options.space_around_delimiters, |
| write_options.multiline_line_indentation, |
| ); |
| } |
| is_first = false; |
| } |
| out |
| } |
| |
| ///Private function that parses ini-style syntax into a Map. |
| fn parse(&self, input: String) -> Result<Map<String, Map<String, Option<String>>>, String> { |
| let mut map: Map<String, Map<String, Option<String>>> = Map::new(); |
| let mut section = self.default_section.clone(); |
| let mut current_key: Option<String> = None; |
| |
| let caser = |val: &str| { |
| if self.case_sensitive { |
| val.to_owned() |
| } else { |
| val.to_lowercase() |
| } |
| }; |
| |
| for (num, raw_line) in input.lines().enumerate() { |
| let line = match raw_line.find(|c: char| self.comment_symbols.contains(&c)) { |
| Some(idx) => &raw_line[..idx], |
| None => raw_line, |
| }; |
| |
| let trimmed = line.trim(); |
| |
| if trimmed.is_empty() { |
| continue; |
| } |
| |
| match (trimmed.find('['), trimmed.rfind(']')) { |
| (Some(0), Some(end)) => { |
| section = caser(trimmed[1..end].trim()); |
| |
| continue; |
| } |
| (Some(0), None) => { |
| return Err(format!( |
| "line {}: Found opening bracket for section name but no closing bracket", |
| num |
| )); |
| } |
| _ => {} |
| } |
| |
| if line.starts_with(char::is_whitespace) && self.multiline { |
| let key = match current_key.as_ref() { |
| Some(x) => x, |
| None => { |
| return Err(format!( |
| "line {}: Started with indentation but there is no current entry", |
| num, |
| )) |
| } |
| }; |
| |
| let valmap = map.entry(section.clone()).or_default(); |
| |
| let val = valmap |
| .entry(key.clone()) |
| .or_insert_with(|| Some(String::new())); |
| |
| match val { |
| Some(x) => { |
| x.push_str(LINE_ENDING); |
| x.push_str(trimmed); |
| } |
| None => { |
| *val = Some(format!("{}{}", LINE_ENDING, trimmed)); |
| } |
| } |
| |
| continue; |
| } |
| |
| let valmap = map.entry(section.clone()).or_default(); |
| |
| match trimmed.find(&self.delimiters[..]) { |
| Some(delimiter) => { |
| let key = caser(trimmed[..delimiter].trim()); |
| |
| if key.is_empty() { |
| return Err(format!("line {}:{}: Key cannot be empty", num, delimiter)); |
| } else { |
| current_key = Some(key.clone()); |
| |
| let value = trimmed[delimiter + 1..].trim().to_owned(); |
| |
| valmap.insert(key, Some(value)); |
| } |
| } |
| None => { |
| let key = caser(trimmed); |
| current_key = Some(key.clone()); |
| |
| valmap.insert(key, None); |
| } |
| } |
| } |
| |
| Ok(map) |
| } |
| |
| ///Private function that cases things automatically depending on the set variable. |
| fn autocase(&self, section: &str, key: &str) -> (String, String) { |
| if self.case_sensitive { |
| (section.to_owned(), key.to_owned()) |
| } else { |
| (section.to_lowercase(), key.to_lowercase()) |
| } |
| } |
| |
| ///Returns a clone of the stored value from the key stored in the defined section. |
| ///Unlike accessing the map directly, `get()` can process your input to make case-insensitive access *if* the |
| ///default constructor is used. |
| ///All `get` functions will do this automatically under the hood. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.load("tests/test.ini"); |
| ///let value = config.get("default", "defaultvalues").unwrap(); |
| ///assert_eq!(value, String::from("defaultvalues")); |
| ///``` |
| ///Returns `Some(value)` of type `String` if value is found or else returns `None`. |
| pub fn get(&self, section: &str, key: &str) -> Option<String> { |
| let (section, key) = self.autocase(section, key); |
| self.map.get(§ion)?.get(&key)?.clone() |
| } |
| |
| ///Parses the stored value from the key stored in the defined section to a `bool`. |
| ///For ease of use, the function converts the type case-insensitively (`true` == `True`). |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.load("tests/test.ini"); |
| ///let value = config.getbool("values", "bool").unwrap().unwrap(); |
| ///assert!(value); // value accessible! |
| ///``` |
| ///Returns `Ok(Some(value))` of type `bool` if value is found or else returns `Ok(None)`. |
| ///If the parsing fails, it returns an `Err(string)`. |
| pub fn getbool(&self, section: &str, key: &str) -> Result<Option<bool>, String> { |
| let (section, key) = self.autocase(section, key); |
| match self.map.get(§ion) { |
| Some(secmap) => match secmap.get(&key) { |
| Some(val) => match val { |
| Some(inner) => match inner.to_lowercase().parse::<bool>() { |
| Err(why) => Err(why.to_string()), |
| Ok(boolean) => Ok(Some(boolean)), |
| }, |
| None => Ok(None), |
| }, |
| None => Ok(None), |
| }, |
| None => Ok(None), |
| } |
| } |
| |
| ///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. |
| ///It attempts to case-insenstively find `true`, `yes`, `t`, `y`, `1` and `on` to parse it as `True`. |
| ///Similarly it attempts to case-insensitvely find `false`, `no`, `f`, `n`, `0` and `off` to parse it as `False`. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.load("tests/test.ini"); |
| ///let value = config.getboolcoerce("values", "boolcoerce").unwrap().unwrap(); |
| ///assert!(!value); // value accessible! |
| ///``` |
| ///Returns `Ok(Some(value))` of type `bool` if value is found or else returns `Ok(None)`. |
| ///If the parsing fails, it returns an `Err(string)`. |
| pub fn getboolcoerce(&self, section: &str, key: &str) -> Result<Option<bool>, String> { |
| let (section, key) = self.autocase(section, key); |
| match self.map.get(§ion) { |
| Some(secmap) => match secmap.get(&key) { |
| Some(val) => match val { |
| Some(inner) => { |
| let boolval = &inner.to_lowercase()[..]; |
| if self |
| .boolean_values |
| .get(&true) |
| .unwrap() |
| .iter() |
| .any(|elem| elem == boolval) |
| { |
| Ok(Some(true)) |
| } else if self |
| .boolean_values |
| .get(&false) |
| .unwrap() |
| .iter() |
| .any(|elem| elem == boolval) |
| { |
| Ok(Some(false)) |
| } else { |
| Err(format!( |
| "Unable to parse value into bool at {}:{}", |
| section, key |
| )) |
| } |
| } |
| None => Ok(None), |
| }, |
| None => Ok(None), |
| }, |
| None => Ok(None), |
| } |
| } |
| |
| ///Parses the stored value from the key stored in the defined section to an `i64`. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.load("tests/test.ini"); |
| ///let value = config.getint("values", "int").unwrap().unwrap(); |
| ///assert_eq!(value, -31415); // value accessible! |
| ///``` |
| ///Returns `Ok(Some(value))` of type `i64` if value is found or else returns `Ok(None)`. |
| ///If the parsing fails, it returns an `Err(string)`. |
| pub fn getint(&self, section: &str, key: &str) -> Result<Option<i64>, String> { |
| let (section, key) = self.autocase(section, key); |
| match self.map.get(§ion) { |
| Some(secmap) => match secmap.get(&key) { |
| Some(val) => match val { |
| Some(inner) => match inner.parse::<i64>() { |
| Err(why) => Err(why.to_string()), |
| Ok(int) => Ok(Some(int)), |
| }, |
| None => Ok(None), |
| }, |
| None => Ok(None), |
| }, |
| None => Ok(None), |
| } |
| } |
| |
| ///Parses the stored value from the key stored in the defined section to a `u64`. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.load("tests/test.ini"); |
| ///let value = config.getint("values", "Uint").unwrap().unwrap(); |
| ///assert_eq!(value, 31415); // value accessible! |
| ///``` |
| ///Returns `Ok(Some(value))` of type `u64` if value is found or else returns `Ok(None)`. |
| ///If the parsing fails, it returns an `Err(string)`. |
| pub fn getuint(&self, section: &str, key: &str) -> Result<Option<u64>, String> { |
| let (section, key) = self.autocase(section, key); |
| match self.map.get(§ion) { |
| Some(secmap) => match secmap.get(&key) { |
| Some(val) => match val { |
| Some(inner) => match inner.parse::<u64>() { |
| Err(why) => Err(why.to_string()), |
| Ok(uint) => Ok(Some(uint)), |
| }, |
| None => Ok(None), |
| }, |
| None => Ok(None), |
| }, |
| None => Ok(None), |
| } |
| } |
| |
| ///Parses the stored value from the key stored in the defined section to a `f64`. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.load("tests/test.ini"); |
| ///let value = config.getfloat("values", "float").unwrap().unwrap(); |
| ///assert_eq!(value, 3.1415); // value accessible! |
| ///``` |
| ///Returns `Ok(Some(value))` of type `f64` if value is found or else returns `Ok(None)`. |
| ///If the parsing fails, it returns an `Err(string)`. |
| pub fn getfloat(&self, section: &str, key: &str) -> Result<Option<f64>, String> { |
| let (section, key) = self.autocase(section, key); |
| match self.map.get(§ion) { |
| Some(secmap) => match secmap.get(&key) { |
| Some(val) => match val { |
| Some(inner) => match inner.parse::<f64>() { |
| Err(why) => Err(why.to_string()), |
| Ok(float) => Ok(Some(float)), |
| }, |
| None => Ok(None), |
| }, |
| None => Ok(None), |
| }, |
| None => Ok(None), |
| } |
| } |
| |
| ///Returns a clone of the `Map` stored in our struct. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.read(String::from( |
| /// "[section] |
| /// key=values")); |
| ///let map = config.get_map().unwrap(); |
| ///assert_eq!(map, *config.get_map_ref()); // the cloned map is basically a snapshot that you own |
| ///``` |
| ///Returns `Some(map)` if map is non-empty or else returns `None`. |
| ///Similar to `load()` but returns an `Option` type with the currently stored `Map`. |
| pub fn get_map(&self) -> Option<Map<String, Map<String, Option<String>>>> { |
| if self.map.is_empty() { |
| None |
| } else { |
| Some(self.map.clone()) |
| } |
| } |
| |
| ///Returns an immutable reference to the `Map` stored in our struct. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///let mapclone = config.read(String::from |
| /// ("[topsecrets] |
| /// Valueless key")).unwrap(); |
| /////Think of the clone as being a snapshot at a point of time while the reference always points to the current configuration. |
| ///assert_eq!(*config.get_map_ref(), mapclone); // same as expected. |
| ///``` |
| ///If you just need to definitely mutate the map, use `get_mut_map()` instead. Alternatively, you can generate a snapshot by getting a clone |
| ///with `get_map()` and work with that. |
| pub fn get_map_ref(&self) -> &Map<String, Map<String, Option<String>>> { |
| &self.map |
| } |
| |
| ///Returns a mutable reference to the `Map` stored in our struct. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.read(String::from |
| /// ("[topsecrets] |
| /// Valueless key")); |
| /////We can then get the mutable map and insert a value like: |
| ///config.get_mut_map().get_mut("topsecrets").unwrap().insert(String::from("nuclear launch codes"), None); |
| ///assert_eq!(config.get("topsecrets", "nuclear launch codes"), None); // inserted successfully! |
| ///``` |
| ///If you just need to access the map without mutating, use `get_map_ref()` or make a clone with `get_map()` instead. |
| pub fn get_mut_map(&mut self) -> &mut Map<String, Map<String, Option<String>>> { |
| &mut self.map |
| } |
| |
| ///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. |
| ///An existing value in the map will be overwritten. You can also set `None` safely. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.read(String::from( |
| /// "[section] |
| /// key=value")); |
| ///let key_value = String::from("value"); |
| ///config.set("section", "key", Some(key_value)); |
| ///config.set("section", "key", None); // also valid! |
| ///assert_eq!(config.get("section", "key"), None); // correct! |
| ///``` |
| ///Returns `None` if there is no existing value, else returns `Some(Option<String>)`, with the existing value being the wrapped `Option<String>`. |
| ///If you want to insert using a string literal, use `setstr()` instead. |
| pub fn set( |
| &mut self, |
| section: &str, |
| key: &str, |
| value: Option<String>, |
| ) -> Option<Option<String>> { |
| let (section, key) = self.autocase(section, key); |
| match self.map.get_mut(§ion) { |
| Some(secmap) => secmap.insert(key, value), |
| None => { |
| let mut valmap: Map<String, Option<String>> = Map::new(); |
| valmap.insert(key, value); |
| self.map.insert(section, valmap); |
| None |
| } |
| } |
| } |
| |
| ///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. |
| ///An existing value in the map will be overwritten. You can also set `None` safely. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.read(String::from( |
| /// "[section] |
| /// key=notvalue")); |
| ///config.setstr("section", "key", Some("value")); |
| ///config.setstr("section", "key", None); // also valid! |
| ///assert_eq!(config.get("section", "key"), None); // correct! |
| ///``` |
| ///Returns `None` if there is no existing value, else returns `Some(Option<String>)`, with the existing value being the wrapped `Option<String>`. |
| ///If you want to insert using a `String`, use `set()` instead. |
| pub fn setstr( |
| &mut self, |
| section: &str, |
| key: &str, |
| value: Option<&str>, |
| ) -> Option<Option<String>> { |
| let (section, key) = self.autocase(section, key); |
| self.set(§ion, &key, value.map(String::from)) |
| } |
| |
| ///Clears the map, removing all sections and properties from the hashmap. It keeps the allocated memory for reuse. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.read(String::from( |
| /// "[section] |
| /// key=somevalue")); |
| ///config.clear(); |
| ///assert!(config.get_map_ref().is_empty()); // our map is empty! |
| ///``` |
| ///Returns nothing. |
| pub fn clear(&mut self) { |
| self.map.clear(); |
| } |
| |
| ///Removes a section from the hashmap, returning the properties stored in the section if the section was previously in the map. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.read(String::from( |
| /// "[section] |
| /// updog=whatsupdog")); |
| ///config.remove_section("section"); // this will return a cloned hashmap of the stored property |
| ///assert!(config.get_map_ref().is_empty()); // with the last section removed, our map is now empty! |
| ///``` |
| ///Returns `Some(section_map)` if the section exists or else, `None`. |
| pub fn remove_section(&mut self, section: &str) -> Option<Map<String, Option<String>>> { |
| let section = if self.case_sensitive { |
| section.to_owned() |
| } else { |
| section.to_lowercase() |
| }; |
| self.map.remove(§ion) |
| } |
| |
| ///Removes a key from a section in the hashmap, returning the value attached to the key if it was previously in the map. |
| ///## Example |
| ///```rust |
| ///use configparser::ini::Ini; |
| /// |
| ///let mut config = Ini::new(); |
| ///config.read(String::from( |
| /// "[section] |
| /// updog=whatsupdog |
| /// [anothersection] |
| /// updog=differentdog")); |
| ///let val = config.remove_key("anothersection", "updog").unwrap().unwrap(); |
| ///assert_eq!(val, String::from("differentdog")); // with the last section removed, our map is now empty! |
| ///``` |
| ///Returns `Some(Option<String>)` if the value exists or else, `None`. |
| pub fn remove_key(&mut self, section: &str, key: &str) -> Option<Option<String>> { |
| let (section, key) = self.autocase(section, key); |
| self.map.get_mut(§ion)?.remove(&key) |
| } |
| } |
| |
| #[cfg(feature = "async-std")] |
| impl Ini { |
| ///Loads a file asynchronously from a defined path, parses it and puts the hashmap into our struct. |
| ///At one time, it only stores one configuration, so each call to `load()` or `read()` will clear the existing `Map`, if present. |
| /// |
| ///Usage is similar to `load`, but `.await` must be called after along with the usual async rules. |
| /// |
| ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`. |
| ///Use `get_mut_map()` if you want a mutable reference. |
| pub async fn load_async<T: AsRef<Path>>( |
| &mut self, |
| path: T, |
| ) -> Result<Map<String, Map<String, Option<String>>>, String> { |
| self.map = match self.parse(match async_fs::read_to_string(&path).await { |
| Err(why) => { |
| return Err(format!( |
| "couldn't read {}: {}", |
| &path.as_ref().display(), |
| why |
| )) |
| } |
| Ok(s) => s, |
| }) { |
| Err(why) => { |
| return Err(format!( |
| "couldn't read {}: {}", |
| &path.as_ref().display(), |
| why |
| )) |
| } |
| Ok(map) => map, |
| }; |
| Ok(self.map.clone()) |
| } |
| |
| ///Loads a file from a defined path, parses it and applies it to the existing hashmap in our struct. |
| ///While `load_async()` will clear the existing `Map`, `load_and_append_async()` applies the new values on top |
| ///of the existing hashmap, preserving previous values. |
| /// |
| ///Usage is similar to `load_and_append`, but `.await` must be called after along with the usual async rules. |
| /// |
| ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`. |
| ///Use `get_mut_map()` if you want a mutable reference. |
| pub async fn load_and_append_async<T: AsRef<Path>>( |
| &mut self, |
| path: T, |
| ) -> Result<Map<String, Map<String, Option<String>>>, String> { |
| let loaded = match self.parse(match async_fs::read_to_string(&path).await { |
| Err(why) => { |
| return Err(format!( |
| "couldn't read {}: {}", |
| &path.as_ref().display(), |
| why |
| )) |
| } |
| Ok(s) => s, |
| }) { |
| Err(why) => { |
| return Err(format!( |
| "couldn't read {}: {}", |
| &path.as_ref().display(), |
| why |
| )) |
| } |
| Ok(map) => map, |
| }; |
| |
| for (section, section_map) in loaded.iter() { |
| self.map |
| .entry(section.clone()) |
| .or_insert_with(Map::new) |
| .extend(section_map.clone()); |
| } |
| |
| Ok(self.map.clone()) |
| } |
| |
| ///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 |
| ///exists, it is truncated and the configuration is written to it. |
| /// |
| ///Usage is the same as `write`, but `.await` must be called after along with the usual async rules. |
| /// |
| ///Returns a `std::io::Result<()>` type dependent on whether the write was successful or not. |
| pub async fn write_async<T: AsRef<Path>>(&self, path: T) -> std::io::Result<()> { |
| async_fs::write(path.as_ref(), self.unparse(&WriteOptions::default())).await |
| } |
| |
| ///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 |
| ///exists, it is truncated and the configuration is written to it. |
| /// |
| ///Usage is the same as `pretty_pretty_write`, but `.await` must be called after along with the usual async rules. |
| /// |
| ///Returns a `std::io::Result<()>` type dependent on whether the write was successful or not. |
| pub async fn pretty_write_async<T: AsRef<Path>>( |
| &self, |
| path: T, |
| write_options: &WriteOptions, |
| ) -> std::io::Result<()> { |
| async_fs::write(path.as_ref(), self.unparse(write_options)).await |
| } |
| } |