1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
/*! This crate provides the `ini!` macro 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 macro utility built on top of `configparser` with no other dependencies built on Rust. For more advanced functions, you should use the [configparser](https://crates.io/crates/configparser) crate. ## 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!` macro The `ini!` macro allows you to simply get a hashmap of type `HashMap<String, HashMap<String, Option<String>>>` for a list of files. It is planned to provide shell expansion and file-writing in the future: ```ignore,rust #[macro_use] extern crate ini; fn main() { let map = ini!("...path/to/file"); // Proceed to use normal HashMap functions on the map: let val = map["section"]["key"].clone().unwrap(); // To load multiple files, just do: let (map1, map2, map3) = ini!("path/to/file1", "path/to/file2", "path/to/file3"); // Each map is a cloned hashmap with no relation to other ones } ``` If loading a file fails or the parser is unable to parse the file, the code will `panic` with an appropriate error. In case, you want to handle this gracefully, it's recommended you use the `safe` metavariable instead. This will make sure your code does not panic and instead exists as a `Result<HashMap, String>` type and let you deal with errors gracefully. ```ignore,rust let map = ini!(safe "...path/to/file"); // Proceed to use normal HashMap functions on the map: let val = map.unwrap()["section"]["key"].clone().unwrap(); // Note the extra unwrap here, which is required because our HashMap is inside a Result type. ``` ## Supported datatypes `configparser` does not guess the datatype of values in configuration files and stores everything as strings, same applies to `ini`. If you need getters that parse the values for you, you might want to use `configparser`. You can ofcourse just choose to parse the string values yourself. ```ignore,rust let my_string = map["section"]["key"].clone().unwrap(); let my_int = my_string.parse::<i32>().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 `=`. By default, section names and key names are case-insensitive. 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). If you want to select custom symbols, use the `configparser` crate. 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 (this crate will allow you to use `]` sparingly). Let's take for example: ```INI [section headers are case-insensitive] [ section headers are case-insensitive ] are the section headers above same? = yes sectionheaders_and_keysarestored_in_lowercase? = yes keys_are_also_case_insensitive = Values are case sensitive ;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". */ pub use configparser; use std::collections::HashMap; ///The `ini!` macro allows you to simply get a hashmap of type `HashMap<String, HashMap<String, Option<String>>>` for a list of files. ///```ignore,rust ///#[macro_use] ///extern crate ini; /// ///fn main() { /// let map = ini!("...path/to/file"); /// // Proceed to use normal HashMap functions on the map: /// let val = map["section"]["key"].clone().unwrap(); /// // The type of the map is HashMap<String, HashMap<String, Option<String>>> /// /// // To load multiple files, just do: /// let (map1, map2, map3) = ini!("path/to/file1", "path/to/file2", "path/to/file3"); /// // Each map is a cloned hashmap with no relation to other ones ///} ///``` ///If loading a file fails or the parser is unable to parse the file, the code will `panic` with an appropriate error. In case, you want to handle this ///gracefully, it's recommended you use the `safe` metavariable instead. This will make sure your code does not panic and instead exists as a ///`Result<HashMap, String>` type and let you deal with errors gracefully. ///```ignore,rust ///let map = ini!(safe "...path/to/file"); /// // Proceed to use normal HashMap functions on the map: ///let val = map.unwrap()["section"]["key"].clone().unwrap(); /// // Note the extra unwrap here, which is required because our HashMap is inside a Result type. ///``` #[macro_export] macro_rules! ini { {$($path: expr),+} => {{ ($($crate::macro_load($path)),+) }}; {safe $($path: expr),+} => {{ ($($crate::macro_safe_load($path)),+) }}; } ///The `inistr!` macro allows you to simply get a hashmap of type `HashMap<String, HashMap<String, Option<String>>>` for a list of strings. ///```rust ///#[macro_use] ///extern crate ini; /// ///fn main() { /// let configstring = "[section] /// key = value /// top = secret"; /// let map = inistr!(configstring); /// // Proceed to use normal HashMap functions on the map: /// let val = map["section"]["top"].clone().unwrap(); /// // The type of the map is HashMap<String, HashMap<String, Option<String>>> /// assert_eq!(val, "secret"); // value accessible! /// /// // To load multiple string, just do: /// let (map1, map2, map3) = inistr!(&String::from(configstring), configstring, "[section] /// key = value /// top = secret"); /// // Each map is a cloned hashmap with no relation to other ones ///} ///``` ///If loading a file fails or the parser is unable to parse the file, the code will `panic` with an appropriate error. In case, you want to handle this ///gracefully, it's recommended you use the `safe` metavariable instead. This will make sure your code does not panic and instead exists as a ///`Result<HashMap, String>` type and let you deal with errors gracefully. ///```ignore,rust ///let map = inistr!(safe strvariable_or_strliteral); /// // Proceed to use normal HashMap functions on the map: ///let val = map.unwrap()["section"]["key"].clone().unwrap(); /// // Note the extra unwrap here, which is required because our HashMap is inside a Result type. ///``` #[macro_export] macro_rules! inistr { {$($instring: expr),+} => {{ ($($crate::macro_read($instring)),+) }}; {safe $($instring: expr),+} => {{ ($($crate::macro_safe_read($instring)),+) }}; } pub fn macro_load(path: &str) -> HashMap<String, HashMap<String, Option<String>>> { let mut config = configparser::ini::Ini::new(); match config.load(path) { Err(why) => panic!("{}", why), Ok(map) => map } } pub fn macro_safe_load(path: &str) -> Result<HashMap<String, HashMap<String, Option<String>>>, String> { let mut config = configparser::ini::Ini::new(); config.load(path) } pub fn macro_read(instring: &str) -> HashMap<String, HashMap<String, Option<String>>> { let mut config = configparser::ini::Ini::new(); match config.read(String::from(instring)) { Err(why) => panic!("{}", why), Ok(map) => map } } pub fn macro_safe_read(instring: &str) -> Result<HashMap<String, HashMap<String, Option<String>>>, String> { let mut config = configparser::ini::Ini::new(); config.read(String::from(instring)) }