ini/lib.rs
1/*!
2This 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.
3
4This 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.
5
6## Quick Start
7
8A basic `ini`-syntax file (we say ini-syntax files because the files don't need to be necessarily `*.ini`) looks like this:
9```INI
10[DEFAULT]
11key1 = value1
12pizzatime = yes
13cost = 9
14
15[topsecrets]
16nuclear launch codes = topsecret
17
18[github.com]
19User = QEDK
20```
21Essentially, the syntax consists of sections, each of which can which contains keys with values.
22
23### The `ini!` macro
24The `ini!` macro allows you to simply get a hashmap of type `HashMap<String, HashMap<String, Option<String>>>` for a list of files.
25It is planned to provide shell expansion and file-writing in the future:
26```ignore,rust
27#[macro_use]
28extern crate ini;
29
30fn main() {
31 let map = ini!("...path/to/file");
32 // Proceed to use normal HashMap functions on the map:
33 let val = map["section"]["key"].clone().unwrap();
34
35 // To load multiple files, just do:
36 let (map1, map2, map3) = ini!("path/to/file1", "path/to/file2", "path/to/file3");
37 // Each map is a cloned hashmap with no relation to other ones
38}
39```
40If 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
41gracefully, it's recommended you use the `safe` metavariable instead. This will make sure your code does not panic and instead exists as a
42`Result<HashMap, String>` type and let you deal with errors gracefully.
43```ignore,rust
44let map = ini!(safe "...path/to/file");
45// Proceed to use normal HashMap functions on the map:
46let val = map.unwrap()["section"]["key"].clone().unwrap();
47// Note the extra unwrap here, which is required because our HashMap is inside a Result type.
48```
49
50## Supported datatypes
51`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.
52```ignore,rust
53let my_string = map["section"]["key"].clone().unwrap();
54let my_int = my_string.parse::<i32>().unwrap();
55```
56
57## Supported `ini` file structure
58A 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.
59Key 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
60explain it later). You can use comment symbols (`;` and `#` to denote comments). If you want to select custom symbols, use the `configparser` crate.
61Keep in mind that key-value pairs or section headers cannot span multiple lines.
62Owing to how ini files usually are, this means that `[`, `]`, `=`, ';' and `#` are special symbols (this crate will allow you to use `]` sparingly).
63
64Let's take for example:
65```INI
66[section headers are case-insensitive]
67[ section headers are case-insensitive ]
68are the section headers above same? = yes
69sectionheaders_and_keysarestored_in_lowercase? = yes
70keys_are_also_case_insensitive = Values are case sensitive
71;anything after a comment symbol is ignored
72#this is also a comment
73spaces in keys=allowed ;and everything before this is still valid!
74spaces in values=allowed as well
75spaces around the delimiter = also OK
76
77
78[All values are strings]
79values like this= 0000
80or this= 0.999
81are they treated as numbers? = no
82integers, floats and booleans are held as= strings
83
84[value-less?]
85a_valueless_key_has_None
86this key has an empty string value has Some("") =
87
88 [indented sections]
89 can_values_be_as_well = True
90 purpose = formatting for readability
91 is_this_same = yes
92 is_this_same=yes
93```
94An 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
95or property key) is the one that remains in the `HashMap`.
96The only bit of magic the API does is the section-less properties are put in a section called "default".
97*/
98pub use configparser;
99use std::collections::HashMap;
100
101///The `ini!` macro allows you to simply get a hashmap of type `HashMap<String, HashMap<String, Option<String>>>` for a list of files.
102///```ignore,rust
103///#[macro_use]
104///extern crate ini;
105///
106///fn main() {
107/// let map = ini!("...path/to/file");
108/// // Proceed to use normal HashMap functions on the map:
109/// let val = map["section"]["key"].clone().unwrap();
110/// // The type of the map is HashMap<String, HashMap<String, Option<String>>>
111///
112/// // To load multiple files, just do:
113/// let (map1, map2, map3) = ini!("path/to/file1", "path/to/file2", "path/to/file3");
114/// // Each map is a cloned hashmap with no relation to other ones
115///}
116///```
117///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
118///gracefully, it's recommended you use the `safe` metavariable instead. This will make sure your code does not panic and instead exists as a
119///`Result<HashMap, String>` type and let you deal with errors gracefully.
120///```ignore,rust
121///let map = ini!(safe "...path/to/file");
122/// // Proceed to use normal HashMap functions on the map:
123///let val = map.unwrap()["section"]["key"].clone().unwrap();
124/// // Note the extra unwrap here, which is required because our HashMap is inside a Result type.
125///```
126#[macro_export]
127macro_rules! ini {
128 {$($path: expr),+} => {{
129 ($($crate::macro_load($path)),+)
130 }};
131 {safe $($path: expr),+} => {{
132 ($($crate::macro_safe_load($path)),+)
133 }};
134}
135///The `inistr!` macro allows you to simply get a hashmap of type `HashMap<String, HashMap<String, Option<String>>>` for a list of strings.
136///```rust
137///#[macro_use]
138///extern crate ini;
139///
140///fn main() {
141/// let configstring = "[section]
142/// key = value
143/// top = secret";
144/// let map = inistr!(configstring);
145/// // Proceed to use normal HashMap functions on the map:
146/// let val = map["section"]["top"].clone().unwrap();
147/// // The type of the map is HashMap<String, HashMap<String, Option<String>>>
148/// assert_eq!(val, "secret"); // value accessible!
149///
150/// // To load multiple string, just do:
151/// let (map1, map2, map3) = inistr!(&String::from(configstring), configstring, "[section]
152/// key = value
153/// top = secret");
154/// // Each map is a cloned hashmap with no relation to other ones
155///}
156///```
157///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
158///gracefully, it's recommended you use the `safe` metavariable instead. This will make sure your code does not panic and instead exists as a
159///`Result<HashMap, String>` type and let you deal with errors gracefully.
160///```ignore,rust
161///let map = inistr!(safe strvariable_or_strliteral);
162/// // Proceed to use normal HashMap functions on the map:
163///let val = map.unwrap()["section"]["key"].clone().unwrap();
164/// // Note the extra unwrap here, which is required because our HashMap is inside a Result type.
165///```
166#[macro_export]
167macro_rules! inistr {
168 {$($instring: expr),+} => {{
169 ($($crate::macro_read($instring)),+)
170 }};
171 {safe $($instring: expr),+} => {{
172 ($($crate::macro_safe_read($instring)),+)
173 }};
174}
175
176pub fn macro_load(path: &str) -> HashMap<String, HashMap<String, Option<String>>> {
177 let mut config = configparser::ini::Ini::new();
178 match config.load(path) {
179 Err(why) => panic!("{}", why),
180 Ok(map) => map
181 }
182}
183
184pub fn macro_safe_load(path: &str) -> Result<HashMap<String, HashMap<String, Option<String>>>, String> {
185 let mut config = configparser::ini::Ini::new();
186 config.load(path)
187}
188
189pub fn macro_read(instring: &str) -> HashMap<String, HashMap<String, Option<String>>> {
190 let mut config = configparser::ini::Ini::new();
191 match config.read(String::from(instring)) {
192 Err(why) => panic!("{}", why),
193 Ok(map) => map
194 }
195}
196
197pub fn macro_safe_read(instring: &str) -> Result<HashMap<String, HashMap<String, Option<String>>>, String> {
198 let mut config = configparser::ini::Ini::new();
199 config.read(String::from(instring))
200}