configparser-2.0.0/.cargo_vcs_info.json0000644000000001120000000000000135060ustar { "git": { "sha1": "c35c58b7874e73b26569d867897ba6bcb08af48a" } } configparser-2.0.0/.gitignore000075500000000000000000000000760000000000000142600ustar 00000000000000/target .DS_Store Cargo.lock output.ini output2.ini test2.ini configparser-2.0.0/.travis.yml000075500000000000000000000004060000000000000143760ustar 00000000000000dist: bionic language: rust rust: - stable - beta - nightly jobs: allow_failures: - rust: nightly fast_finish: true before_script: - rustup component add rustfmt script: - cargo build --verbose - cargo test --verbose - cargo fmt -- --check configparser-2.0.0/CHANGELOG.md000075500000000000000000000053200000000000000140760ustar 00000000000000## Changelog - 0.1.0 (yanked) - First experimental version with only a public-facing load() function. - 0.1.1 - `configparser` module renamed to `ini`. - 0.2.1 - `Ini` struct is added along with file-loading, parsing and hashmap functions. Documentation is added. - 0.2.2 - Fixed docs. - 0.3.0 - Added `get()` for getting values from the map directly. Docs expanded as well. - Mark `ini::load()` for deprecation. - 0.3.1 - Updated docs. - All parameters now trimmed before insertion. - Converted `ini::load()` into a wrapper around `Ini`. - 0.4.0 - Changed `Ini::load()` to return an `Ok(map)` with a clone of the stored `HashMap`. - 0.4.1 - Fixed and added docs. - 0.5.0 (**BETA**) (yanked) - Changelog added. - Support for value-less keys. - `HashMap` values are now `Option` instead of `String` to denote empty values vs. no values. - Documentation greatly improved. - Syntax docs provided. - `new()` and `get()` methods are simplified. - 0.5.1 - Fixed erroneous docs - 0.6.0 (**BETA 2**) - Tests added - `get_map_ref()` and `get_mut_map()` are now added to allow direct `HashMap` index access making things greatly easier. - 0.6.1 (yanked) - Fixed tests - 0.6.2 - Fixed accidental binary delivery increasing crate size by ~20x - 0.7.0 (**BETA 3**) - Handy getter functions introduced such as `getint()`, `getuint()`, `getfloat()`, `getbool()` - Fixed docs - Fixed tests - 0.7.1 - Enable `Eq` and `PartialEq` traits - Improve docs - 0.8.0 (**BETA 4**) - Added feature to set default headers. - Added feature to parse from a `String`. - 0.8.1 - Added support for comments - Improved docs - 0.9.0 (**BETA 5**) - Comment customization is here! (**note:** defaults are now changed to `#` and `;`) - Fixed some docs - Make more docs pass tests - 0.9.1 - Hotfix to change getters to return `Ok(None)` instead of failing parsing for `None` values - 0.9.2 - Added `getboolcoerce()` function to parse more `bool`-like values. - Convert some snippets to doctests. - 0.10.0 (**BETA 6**) - Added `set()` and `setstr()` methods to add section, key, values to the configuration. - Added more test, minor doc fixes. - 0.11.0 (**BETA 7**) - Writing to file is here! (`write()`). - More doctests and docs fixed, next release is planned to be stable. - 0.11.1 - Hotfix to remove hardcoded default section and use set default section. - Enabled auto-trait implementation of `Default` for empty inits. - Added the `sections()` method to get a vector of sections. - 0.12.0 (**BETA 8**) - New function added, `writes()` to support writing configuration to a string. - More doctests passed. Older changelogs are preserved here, current changelog is present in [README.md](README.md). configparser-2.0.0/Cargo.toml0000644000000024400000000000000115120ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "configparser" version = "2.0.0" authors = ["QEDK "] description = "A simple configuration parsing utility with no dependencies that allows you to parse INI and ini-style syntax. You can use this to write Rust programs which can be customized by end users easily." homepage = "https://github.com/mexili/configparser-rs" documentation = "https://docs.rs/configparser" readme = "README.md" keywords = ["config", "ini", "settings", "configuration", "parser"] categories = ["config", "encoding", "parser-implementations"] license = "MIT OR LGPL-3.0-or-later" repository = "https://github.com/mexili/configparser-rs" [badges.maintenance] status = "actively-developed" [badges.travis-ci] branch = "master" repository = "QEDK/configparser" configparser-2.0.0/Cargo.toml.orig000075500000000000000000000015710000000000000151600ustar 00000000000000[package] name = "configparser" version = "2.0.0" authors = ["QEDK "] edition = "2018" description = "A simple configuration parsing utility with no dependencies that allows you to parse INI and ini-style syntax. You can use this to write Rust programs which can be customized by end users easily." homepage = "https://github.com/mexili/configparser-rs" repository = "https://github.com/mexili/configparser-rs" documentation = "https://docs.rs/configparser" readme = "README.md" license = "MIT OR LGPL-3.0-or-later" keywords = ["config", "ini", "settings", "configuration", "parser"] categories = ["config", "encoding", "parser-implementations"] [badges] travis-ci = { repository = "QEDK/configparser", branch = "master" } maintenance = { status = "actively-developed" } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html configparser-2.0.0/LICENSE-LGPL000064400000000000000000000167420000000000000140350ustar 00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.configparser-2.0.0/LICENSE-MIT000064400000000000000000000020440000000000000137160ustar 00000000000000MIT License Copyright (c) 2020 QEDK Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.configparser-2.0.0/README.md000075500000000000000000000214750000000000000135550ustar 00000000000000# configparser [![Build Status](https://travis-ci.com/mexili/configparser-rs.svg?branch=master)](https://travis-ci.com/mexili/configparser-rs) [![Crates.io](https://img.shields.io/crates/l/configparser?color=black)](LICENSE-MIT) [![Crates.io](https://img.shields.io/crates/v/configparser?color=black)](https://crates.io/crates/configparser) [![Released API docs](https://docs.rs/configparser/badge.svg)](https://docs.rs/configparser) [![Maintenance](https://img.shields.io/maintenance/yes/2021)](https://github.com/QEDK/configparser-rs) This crate provides the `Ini` struct which implements a basic configuration language which provides a structure similar to what’s found in Windows' `ini` files. You can use this to write Rust programs which can be customized by end users easily. This is a simple configuration parsing utility with no dependencies built on Rust. It is inspired by Python's `configparser`. The current release is stable and changes will take place at a slower pace. We'll be keeping semver in mind for future releases as well. ## πŸš€ Quick Start A basic `ini`-syntax file (we say ini-syntax files because the files don't need to be necessarily `*.ini`) looks like this: ```INI [DEFAULT] key1 = value1 pizzatime = yes cost = 9 [topsecrets] nuclear launch codes = topsecret [github.com] User = QEDK ``` Essentially, the syntax consists of sections, each of which can which contains keys with values. The `Ini` struct can read and write such values to strings as well as files. ### 🧰 Installation You can install this easily via `cargo` by including it in your `Cargo.toml` file like: ```TOML [dependencies] configparser = "2.0.0" ``` ## βž• Supported datatypes `configparser` does not guess the datatype of values in configuration files and stores everything as strings. However, some datatypes are so common that it's a safe bet that some values need to be parsed in other types. For this, the `Ini` struct provides easy functions like `getint()`, `getuint()`, `getfloat()` and `getbool()`. The only bit of extra magic involved is that the `getbool()` function will treat boolean values case-insensitively (so `true` is the same as `True` just like `TRUE`). The crate also provides a stronger `getboolcoerce()` function that parses more values (such as `T`, `yes` and `0`, all case-insensitively), the function's documentation will give you the exact details. ```rust use configparser::ini::Ini; let mut config = Ini::new(); config.read(String::from( "[somesection] someintvalue = 5")); let my_value = config.getint("somesection", "someintvalue").unwrap().unwrap(); assert_eq!(my_value, 5); // value accessible! //You can ofcourse just choose to parse the values yourself: let my_string = String::from("1984"); let my_int = my_string.parse::().unwrap(); ``` ## πŸ“ Supported `ini` file structure A configuration file can consist of sections, each led by a `[section-name]` header, followed by key-value entries separated by a delimiter (`=` and `:`). By default, section names and key names are case-insensitive. Case-sensitivity can be enabled using the `Ini::new_cs()` constructor. All leading and trailing whitespace is removed from stored keys, values and section names. Key values can be omitted, in which case the key-value delimiter may also be left out (but this is different from putting a delimiter, we'll explain it later). You can use comment symbols (`;` and `#` to denote comments). This can be configured with the `set_comment_symbols()` method in the API. Keep in mind that key-value pairs or section headers cannot span multiple lines. Owing to how ini files usually are, this means that `[`, `]`, `=`, `:`, `;` and `#` are special symbols by default (this crate will allow you to use `]` sparingly). Let's take for example: ```INI [section headers are case-insensitive by default] [ section headers are case-insensitive by default ] are the section headers above same? = yes sectionheaders_and_keysarestored_in_lowercase? = yes keys_are_also_case_insensitive = Values are case sensitive Case-sensitive_keys_and_sections = using a special constructor you can also use colons : instead of the equal symbol ;anything after a comment symbol is ignored #this is also a comment spaces in keys=allowed ;and everything before this is still valid! spaces in values=allowed as well spaces around the delimiter = also OK [All values are strings] values like this= 0000 or this= 0.999 are they treated as numbers? = no integers, floats and booleans are held as= strings [value-less?] a_valueless_key_has_None this key has an empty string value has Some("") = [indented sections] can_values_be_as_well = True purpose = formatting for readability is_this_same = yes is_this_same=yes ``` An important thing to note is that values with the same keys will get updated, this means that the last inserted key (whether that's a section header or property key) is the one that remains in the `HashMap`. The only bit of magic the API does is the section-less properties are put in a section called "default". You can configure this variable via the API. Keep in mind that a section named "default" is also treated as sectionless so the output files remains consistent with no section header. ## πŸ›  Usage Let's take another simple `ini` file and talk about working with it: ```INI [topsecret] KFC = the secret herb is orega- [values] Uint = 31415 ``` If you read the above sections carefully, you'll know that 1) all the keys are stored in lowercase, 2) `get()` can make access in a case-insensitive manner and 3) we can use `getuint()` to parse the `Uint` value into an `u64`. Let's see that in action. ```rust use configparser::ini::Ini; use std::error::Error; fn main() -> Result<(), Box> { let mut config = Ini::new(); // You can easily load a file to get a clone of the map: let map = config.load("tests/test.ini")?; println!("{:?}", map); // You can also safely not store the reference and access it later with get_map_ref() or get a clone with get_map() // If you want to access the value, then you can simply do: let val = config.get("TOPSECRET", "KFC").unwrap(); // Notice how get() can access indexes case-insensitively. assert_eq!(val, "the secret herb is orega-"); // value accessible! // What if you want remove KFC's secret recipe? Just use set(): config.set("topsecret", "kfc", None); assert_eq!(config.get("TOPSECRET", "KFC"), None); // as expected! // What if you want to get an unsigned integer? let my_number = config.getuint("values", "Uint")?.unwrap(); assert_eq!(my_number, 31415); // and we got it! // The Ini struct provides more getters for primitive datatypes. // You can also access it like a normal hashmap: let innermap = map["topsecret"].clone(); // Remember that all indexes are stored in lowercase! // You can easily write the currently stored configuration to a file like: config.write("output.ini"); // If you want to simply mutate the stored hashmap, you can use get_mut_map() let map = config.get_mut_map(); // You can then use normal HashMap functions on this map at your convenience. // Remember that functions which rely on standard formatting might stop working // if it's mutated differently. // If you want a case-sensitive map, just do: let mut config = Ini::new_cs(); // This automatically changes the behaviour of every function and parses the file as case-sensitive. Ok(()) } ``` The `Ini` struct offers great support for type conversion and type setting safely, as well as map accesses. See the API for more verbose documentation. ## πŸ“œ License Licensed under either of * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) * Lesser General Public license v3.0 or later ([LICENSE-LGPL](LICENSE-LGPL) or https://www.gnu.org/licenses/lgpl-3.0.html) at your option. ### ✏ Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the LGPL-3.0 license, shall be dual licensed as above, without any additional terms or conditions. ## πŸ†• Changelog Old changelogs are in [CHANGELOG.md](CHANGELOG.md). - 0.13.0 (**BETA 9**) - New functions added, `clear()` and `remove_section()` to make handling similar to hashmaps. - Docs fixed. On track to stable. - 0.13.1 (yanked) - New function added, `remove_key()` to remove a key from a section - All doctests passing! - 0.13.2 (**FINAL BETA**) - Erroneous docs fixed. - Final release before stable. - 1.0.0 (**STABLE**) - Dropped support for `ini::load()` - Updated tests - 2.0.0 - **BREAKING** Added Python-esque support for `:` as a delimiter. - :new: Add support for case-sensitive maps with automatic handling under the hood. - :hammer: Fixed buggy setters which went uncaught, to preserve case-insensitive nature. ### πŸ”œ Future plans - Support for appending sections, coercing them as well. configparser-2.0.0/src/ini.rs000075500000000000000000000647160000000000000142170ustar 00000000000000//!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. use std::collections::HashMap; use std::fs; use std::fs::File; use std::io::prelude::*; 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)] pub struct Ini { map: HashMap>>, default_section: std::string::String, comment_symbols: Vec, delimiters: Vec, case_sensitive: bool, } impl Ini { ///Creates a new `HashMap` of `HashMap>>` type for the struct. ///All values in the HashMap are stored in `String` type. ///## 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 { map: HashMap::new(), default_section: "default".to_owned(), comment_symbols: vec![';', '#'], delimiters: vec!['=', ':'], case_sensitive: false, } } ///Creates a new **case-sensitive** `HashMap` of `HashMap>>` type for the struct. ///All values in the HashMap 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 { map: HashMap::new(), default_section: "default".to_owned(), comment_symbols: vec![';', '#'], delimiters: vec!['=', ':'], case_sensitive: true, } } ///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(); } ///Gets all the sections of the currently-stored `HashMap` 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`. pub fn sections(&self) -> Vec { 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 `HashMap`, 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 `HashMap` if no errors are thrown or else `Err(error_string)`. ///Use `get_mut_map()` if you want a mutable reference. pub fn load( &mut self, path: &str, ) -> Result>>, String> { let path = Path::new(path); let display = path.display(); let mut file = match File::open(&path) { Err(why) => return Err(format!("couldn't open {}: {}", display, why)), Ok(file) => file, }; let mut s = String::new(); self.map = match file.read_to_string(&mut s) { Err(why) => return Err(format!("couldn't read {}: {}", display, why)), Ok(_) => match self.parse(s) { Err(why) => return Err(why), Ok(map) => map, }, }; 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 `HashMap`, 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 `HashMap` 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>>, String> { self.map = match self.parse(input) { Err(why) => return Err(why), Ok(map) => map, }; Ok(self.map.clone()) } ///Writes the current configuation to the specified path. 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. ///## 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(&self, path: &str) -> std::io::Result<()> { fs::write(path, self.unparse()) } ///Returns a string with the current configuration formatted with valid ini-syntax. 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() } ///Private function that converts the currently stored configuration into a valid ini-syntax string. fn unparse(&self) -> String { let mut out = String::new(); let mut cloned = self.map.clone(); if let Some(defaultmap) = cloned.get(&self.default_section) { for (key, val) in defaultmap.iter() { out.push_str(&key); if let Some(value) = val { out.push_str("="); out.push_str(&value); } out.push_str("\n"); } cloned.remove(&self.default_section); } for (section, secmap) in cloned.iter() { out.push_str(&format!("[{}]", section)); out.push_str("\n"); for (key, val) in secmap.iter() { out.push_str(&key); if let Some(value) = val { out.push_str("="); out.push_str(&value); } out.push_str("\n"); } } out } ///Private function that parses ini-style syntax into a HashMap. fn parse( &self, input: String, ) -> Result>>, String> { let mut map: HashMap>> = HashMap::new(); let mut section = self.default_section.clone(); let caser = |val: &str| { if self.case_sensitive { val.to_owned() } else { val.to_lowercase() } }; for (num, lines) in input.lines().enumerate() { let trimmed = match lines.find(|c: char| self.comment_symbols.contains(&c)) { Some(idx) => lines[..idx].trim(), None => lines.trim(), }; if trimmed.is_empty() { continue; } match trimmed.find('[') { Some(start) => match trimmed.rfind(']') { Some(end) => { section = caser(trimmed[start + 1..end].trim()); } None => { return Err(format!( "line {}:{}: Found opening bracket for section name but no closing bracket", num, start )); } }, None => match trimmed.find(&self.delimiters[..]) { Some(delimiter) => match map.get_mut(§ion) { Some(valmap) => { let key = caser(trimmed[..delimiter].trim()); let value = trimmed[delimiter + 1..].trim().to_owned(); if key.is_empty() { return Err(format!( "line {}:{}: Key cannot be empty", num, delimiter )); } else { valmap.insert(key, Some(value)); } } None => { let mut valmap: HashMap> = HashMap::new(); let key = caser(trimmed[..delimiter].trim()); let value = trimmed[delimiter + 1..].trim().to_owned(); if key.is_empty() { return Err(format!( "line {}:{}: Key cannot be empty", num, delimiter )); } else { valmap.insert(key, Some(value)); } map.insert(section.clone(), valmap); } }, None => match map.get_mut(§ion) { Some(valmap) => { let key = caser(trimmed); valmap.insert(key, None); } None => { let mut valmap: HashMap> = HashMap::new(); let key = caser(trimmed); valmap.insert(key, None); map.insert(section.clone(), valmap); } }, }, } } 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 { 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, 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::() { 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` and `1` to parse it as `True`. ///Similarly it attempts to case-insensitvely find `false`, `no`, `f`, `n` and `0` 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, 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 ["true", "yes", "t", "y", "1"].contains(&boolval) { Ok(Some(true)) } else if ["false", "no", "f", "n", "0"].contains(&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, 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::() { 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, 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::() { 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, 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::() { 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 `HashMap` 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 `HashMap`. pub fn get_map(&self) -> Option>>> { if self.map.is_empty() { None } else { Some(self.map.clone()) } } ///Returns an immutable reference to the `HashMap` 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) -> &HashMap>> { &self.map } ///Returns a mutable reference to the `HashMap` 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 HashMap>> { &mut self.map } ///Sets an `Option` in the `HashMap` 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)`, with the existing value being the wrapped `Option`. ///If you want to insert using a string literal, use `setstr()` instead. pub fn set( &mut self, section: &str, key: &str, value: Option, ) -> Option> { let (section, key) = self.autocase(section, key); match self.map.get_mut(§ion) { Some(secmap) => secmap.insert(key, value), None => { let mut valmap: HashMap> = HashMap::new(); valmap.insert(key, value); self.map.insert(section, valmap); None } } } ///Sets an `Option<&str>` in the `HashMap` 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)`, with the existing value being the wrapped `Option`. ///If you want to insert using a `String`, use `set()` instead. pub fn setstr( &mut self, section: &str, key: &str, value: Option<&str>, ) -> Option> { 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. ///```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>> { 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. ///```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)` if the value exists or else, `None`. pub fn remove_key(&mut self, section: &str, key: &str) -> Option> { let (section, key) = self.autocase(section, key); self.map.get_mut(§ion)?.remove(&key) } } configparser-2.0.0/src/lib.rs000075500000000000000000000152560000000000000142010ustar 00000000000000/*! This crate provides the `Ini` struct which implements a basic configuration language which provides a structure similar to what’s found in Windows' `ini` files. You can use this to write Rust programs which can be customized by end users easily. This is a simple configuration parsing utility with no dependencies built on Rust. It is inspired by Python's `configparser`. The current release is stable and changes will take place at a slower pace. We'll be keeping semver in mind for future releases as well. ## πŸš€ Quick Start A basic `ini`-syntax file (we say ini-syntax files because the files don't need to be necessarily `*.ini`) looks like this: ```INI [DEFAULT] key1 = value1 pizzatime = yes cost = 9 [topsecrets] nuclear launch codes = topsecret [github.com] User = QEDK ``` Essentially, the syntax consists of sections, each of which can which contains keys with values. The `Ini` struct can read and write such values to strings as well as files. ## βž• Supported datatypes `configparser` does not guess the datatype of values in configuration files and stores everything as strings. However, some datatypes are so common that it's a safe bet that some values need to be parsed in other types. For this, the `Ini` struct provides easy functions like `getint()`, `getuint()`, `getfloat()` and `getbool()`. The only bit of extra magic involved is that the `getbool()` function will treat boolean values case-insensitively (so `true` is the same as `True` just like `TRUE`). The crate also provides a stronger `getboolcoerce()` function that parses more values (such as `T`, `yes` and `0`, all case-insensitively), the function's documentation will give you the exact details. ```rust use configparser::ini::Ini; let mut config = Ini::new(); config.read(String::from( "[somesection] someintvalue = 5")); let my_value = config.getint("somesection", "someintvalue").unwrap().unwrap(); assert_eq!(my_value, 5); // value accessible! //You can ofcourse just choose to parse the values yourself: let my_string = String::from("1984"); let my_int = my_string.parse::().unwrap(); ``` ## πŸ“ Supported `ini` file structure A configuration file can consist of sections, each led by a `[section-name]` header, followed by key-value entries separated by a delimiter (`=` and `:`). By default, section names and key names are case-insensitive. Case-sensitivity can be enabled using the `Ini::new_cs()` constructor. All leading and trailing whitespace is removed from stored keys, values and section names. Key values can be omitted, in which case the key-value delimiter may also be left out (but this is different from putting a delimiter, we'll explain it later). You can use comment symbols (`;` and `#` to denote comments). This can be configured with the `set_comment_symbols()` method in the API. Keep in mind that key-value pairs or section headers cannot span multiple lines. Owing to how ini files usually are, this means that `[`, `]`, `=`, `:`, `;` and `#` are special symbols by default (this crate will allow you to use `]` sparingly). Let's take for example: ```INI [section headers are case-insensitive by default] [ section headers are case-insensitive by default ] are the section headers above same? = yes sectionheaders_and_keysarestored_in_lowercase? = yes keys_are_also_case_insensitive = Values are case sensitive Case-sensitive_keys_and_sections = using a special constructor you can also use colons : instead of the equal symbol ;anything after a comment symbol is ignored #this is also a comment spaces in keys=allowed ;and everything before this is still valid! spaces in values=allowed as well spaces around the delimiter = also OK [All values are strings] values like this= 0000 or this= 0.999 are they treated as numbers? = no integers, floats and booleans are held as= strings [value-less?] a_valueless_key_has_None this key has an empty string value has Some("") = [indented sections] can_values_be_as_well = True purpose = formatting for readability is_this_same = yes is_this_same=yes ``` An important thing to note is that values with the same keys will get updated, this means that the last inserted key (whether that's a section header or property key) is the one that remains in the `HashMap`. The only bit of magic the API does is the section-less properties are put in a section called "default". You can configure this variable via the API. Keep in mind that a section named "default" is also treated as sectionless so the output files remains consistent with no section header. ## Usage Let's take another simple `ini` file and talk about working with it: ```INI [topsecret] KFC = the secret herb is orega- [values] Uint = 31415 ``` If you read the above sections carefully, you'll know that 1) all the keys are stored in lowercase, 2) `get()` can make access in a case-insensitive manner and 3) we can use `getint()` to parse the `Int` value into an `i64`. Let's see that in action. ```rust use configparser::ini::Ini; use std::error::Error; fn main() -> Result<(), Box> { let mut config = Ini::new(); // You can easily load a file to get a clone of the map: let map = config.load("tests/test.ini")?; println!("{:?}", map); // You can also safely not store the reference and access it later with get_map_ref() or get a clone with get_map() // If you want to access the value, then you can simply do: let val = config.get("TOPSECRET", "KFC").unwrap(); // Notice how get() can access indexes case-insensitively. assert_eq!(val, "the secret herb is orega-"); // value accessible! // What if you want remove KFC's secret recipe? Just use set(): config.set("topsecret", "kfc", None); assert_eq!(config.get("TOPSECRET", "KFC"), None); // as expected! // What if you want to get an unsigned integer? let my_number = config.getuint("values", "Uint")?.unwrap(); assert_eq!(my_number, 31415); // and we got it! // The Ini struct provides more getters for primitive datatypes. // You can also access it like a normal hashmap: let innermap = map["topsecret"].clone(); // Remember that all indexes are stored in lowercase! // You can easily write the currently stored configuration to a file like: config.write("output.ini"); // If you want to simply mutate the stored hashmap, you can use get_mut_map() let map = config.get_mut_map(); // You can then use normal HashMap functions on this map at your convenience. // Remember that functions which rely on standard formatting might stop working // if it's mutated differently. // If you want a case-sensitive map, just do: let mut config = Ini::new_cs(); // This automatically changes the behaviour of every function and parses the file as case-sensitive. Ok(()) } ``` */ pub mod ini; configparser-2.0.0/tests/test.ini000064400000000000000000000004100000000000000150770ustar 00000000000000defaultvalues=defaultvalues [topsecret] KFC = the secret herb is orega- colon:value after colon Empty string = None string [ spacing ] indented=indented not indented = not indented [values] Bool = True Boolcoerce = 0 Int = -31415 Uint = 31415 Float = 3.1415 configparser-2.0.0/tests/test.rs000075500000000000000000000160360000000000000147620ustar 00000000000000use configparser::ini::Ini; use std::error::Error; #[test] fn non_cs() -> Result<(), Box> { let mut config = Ini::new(); let map = config.load("tests/test.ini")?; config.set_comment_symbols(&[';', '#', '!']); let inpstring = config.read( "defaultvalues=defaultvalues [topsecret] KFC = the secret herb is orega- colon:value after colon Empty string = None string [ spacing ] indented=indented not indented = not indented ;testcomment !modified comment [values]#another comment Bool = True Boolcoerce = 0 Int = -31415 Uint = 31415 Float = 3.1415" .to_owned(), )?; assert_eq!(map, inpstring); config.set("DEFAULT", "defaultvalues", Some("notdefault".to_owned())); assert_eq!( config.get("DEFAULT", "defaultvalues").unwrap(), "notdefault" ); config.setstr("DEFAULT", "defaultvalues", Some("defaultvalues")); assert_eq!( config.get("DEFAULT", "defaultvalues").unwrap(), "defaultvalues" ); config.setstr("DEFAULT", "defaultvalues", None); config.write("output.ini")?; let map2 = config.clone().load("output.ini")?; assert_eq!(map2, *config.get_map_ref()); let map3 = config.clone().read(config.writes())?; assert_eq!(map2, map3); assert_eq!(config.sections().len(), 4); assert_eq!(config.get("DEFAULT", "defaultvalues"), None); assert_eq!( config.get("topsecret", "KFC").unwrap(), "the secret herb is orega-" ); assert_eq!(config.get("topsecret", "Empty string").unwrap(), ""); assert_eq!(config.get("topsecret", "None string"), None); assert_eq!(config.get("spacing", "indented").unwrap(), "indented"); assert_eq!( config.get("spacing", "not indented").unwrap(), "not indented" ); assert_eq!( config.get("topsecret", "colon").unwrap(), "value after colon" ); assert_eq!(config.getbool("values", "Bool")?.unwrap(), true); assert_eq!( config.getboolcoerce("values", "Boolcoerce")?.unwrap(), false ); assert_eq!(config.getint("values", "Int")?.unwrap(), -31415); assert_eq!(config.getuint("values", "Uint")?.unwrap(), 31415); assert_eq!(config.getfloat("values", "Float")?.unwrap(), 3.1415); assert_eq!(config.getfloat("topsecret", "None string"), Ok(None)); assert_eq!( map["default"]["defaultvalues"].clone().unwrap(), "defaultvalues" ); assert_eq!( map["topsecret"]["kfc"].clone().unwrap(), "the secret herb is orega-" ); assert_eq!(map["topsecret"]["empty string"].clone().unwrap(), ""); assert_eq!(map["topsecret"]["none string"], None); assert_eq!(map["spacing"]["indented"].clone().unwrap(), "indented"); assert_eq!( map["spacing"]["not indented"].clone().unwrap(), "not indented" ); let mut config2 = config.clone(); let val = config2.remove_key("default", "defaultvalues"); assert_eq!(val, Some(None)); assert_eq!(config2.get("default", "defaultvalues"), None); config2.remove_section("default"); assert_eq!(config2.get("default", "nope"), None); let mut_map = config.get_mut_map(); mut_map.get_mut("topsecret").unwrap().insert( String::from("none string"), Some(String::from("None string")), ); assert_eq!( mut_map["topsecret"]["none string"].clone().unwrap(), "None string" ); mut_map.clear(); config2.clear(); assert_eq!(config.get_map_ref(), config2.get_map_ref()); Ok(()) } #[test] fn cs() -> Result<(), Box> { let mut config = Ini::new_cs(); let map = config.load("tests/test.ini")?; config.set_comment_symbols(&[';', '#', '!']); let inpstring = config.read( "defaultvalues=defaultvalues [topsecret] KFC = the secret herb is orega- colon:value after colon Empty string = None string [ spacing ] indented=indented not indented = not indented ;testcomment !modified comment [values]#another comment Bool = True Boolcoerce = 0 Int = -31415 Uint = 31415 Float = 3.1415" .to_owned(), )?; assert_eq!(map, inpstring); config.set("default", "defaultvalues", Some("notdefault".to_owned())); assert_eq!( config.get("default", "defaultvalues").unwrap(), "notdefault" ); config.setstr("default", "defaultvalues", Some("defaultvalues")); assert_eq!( config.get("default", "defaultvalues").unwrap(), "defaultvalues" ); config.setstr("default", "defaultvalues", None); config.write("output2.ini")?; let map2 = config.clone().load("output2.ini")?; assert_eq!(map2, *config.get_map_ref()); let map3 = config.clone().read(config.writes())?; assert_eq!(map2, map3); assert_eq!(config.sections().len(), 4); assert_eq!(config.get("default", "defaultvalues"), None); assert_eq!( config.get("topsecret", "KFC").unwrap(), "the secret herb is orega-" ); assert_eq!(config.get("topsecret", "Empty string").unwrap(), ""); assert_eq!(config.get("topsecret", "None string"), None); assert_eq!(config.get("spacing", "indented").unwrap(), "indented"); assert_eq!( config.get("spacing", "not indented").unwrap(), "not indented" ); assert_eq!( config.get("topsecret", "colon").unwrap(), "value after colon" ); assert_eq!(config.getbool("values", "Bool")?.unwrap(), true); assert_eq!( config.getboolcoerce("values", "Boolcoerce")?.unwrap(), false ); assert_eq!(config.getint("values", "Int")?.unwrap(), -31415); assert_eq!(config.getuint("values", "Uint")?.unwrap(), 31415); assert_eq!(config.getfloat("values", "Float")?.unwrap(), 3.1415); assert_eq!(config.getfloat("topsecret", "None string"), Ok(None)); assert_eq!( map["default"]["defaultvalues"].clone().unwrap(), "defaultvalues" ); assert_eq!( map["topsecret"]["KFC"].clone().unwrap(), "the secret herb is orega-" ); assert_eq!(map["topsecret"]["Empty string"].clone().unwrap(), ""); assert_eq!(map["topsecret"]["None string"], None); assert_eq!(map["spacing"]["indented"].clone().unwrap(), "indented"); assert_eq!( map["spacing"]["not indented"].clone().unwrap(), "not indented" ); let mut config2 = config.clone(); let val = config2.remove_key("default", "defaultvalues"); assert_eq!(val, Some(None)); assert_eq!(config2.get("default", "defaultvalues"), None); config2.remove_section("default"); assert_eq!(config2.get("default", "nope"), None); let mut_map = config.get_mut_map(); mut_map.get_mut("topsecret").unwrap().insert( String::from("none string"), Some(String::from("None string")), ); assert_eq!( mut_map["topsecret"]["none string"].clone().unwrap(), "None string" ); mut_map.clear(); config2.clear(); assert_eq!(config.get_map_ref(), config2.get_map_ref()); Ok(()) }