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}