/rust/registry/src/index.crates.io-1949cf8c6b5b557f/configparser-3.1.0/src/ini.rs
Line | Count | Source |
1 | | //!The ini module provides all the things necessary to load and parse ini-syntax files. The most important of which is the `Ini` struct. |
2 | | //!See the [implementation](https://docs.rs/configparser/*/configparser/ini/struct.Ini.html) documentation for more details. |
3 | | #[cfg(feature = "indexmap")] |
4 | | use indexmap::IndexMap as Map; |
5 | | #[cfg(not(feature = "indexmap"))] |
6 | | use std::collections::HashMap as Map; |
7 | | #[cfg(feature = "tokio")] |
8 | | use tokio::fs as async_fs; |
9 | | |
10 | | use std::collections::HashMap; |
11 | | use std::convert::AsRef; |
12 | | use std::fmt::Write; |
13 | | use std::fs; |
14 | | use std::path::Path; |
15 | | |
16 | | ///The `Ini` struct simply contains a nested hashmap of the loaded configuration, the default section header and comment symbols. |
17 | | ///## Example |
18 | | ///```rust |
19 | | ///use configparser::ini::Ini; |
20 | | /// |
21 | | ///let mut config = Ini::new(); |
22 | | ///``` |
23 | | #[derive(Debug, Clone, Eq, PartialEq, Default)] |
24 | | #[non_exhaustive] |
25 | | pub struct Ini { |
26 | | map: Map<String, Map<String, Option<String>>>, |
27 | | default_section: std::string::String, |
28 | | comment_symbols: Vec<char>, |
29 | | inline_comment_symbols: Option<Vec<char>>, |
30 | | delimiters: Vec<char>, |
31 | | boolean_values: HashMap<bool, Vec<String>>, |
32 | | case_sensitive: bool, |
33 | | multiline: bool, |
34 | | } |
35 | | |
36 | | ///The `IniDefault` struct serves as a template to create other `Ini` objects from. It can be used to store and load |
37 | | ///default properties from different `Ini` objects. |
38 | | ///## Example |
39 | | ///```rust |
40 | | ///use configparser::ini::Ini; |
41 | | /// |
42 | | ///let mut config = Ini::new(); |
43 | | ///let default = config.defaults(); |
44 | | ///let mut config2 = Ini::new_from_defaults(default); // default gets consumed |
45 | | ///``` |
46 | | #[derive(Debug, Clone, Eq, PartialEq)] |
47 | | #[non_exhaustive] |
48 | | pub struct IniDefault { |
49 | | ///Denotes the default section header name. |
50 | | ///## Example |
51 | | ///```rust |
52 | | ///use configparser::ini::Ini; |
53 | | /// |
54 | | ///let mut config = Ini::new(); |
55 | | ///let default = config.defaults(); |
56 | | ///assert_eq!(default.default_section, "default"); |
57 | | ///``` |
58 | | pub default_section: std::string::String, |
59 | | ///Denotes the set comment symbols for the object. |
60 | | ///## Example |
61 | | ///```rust |
62 | | ///use configparser::ini::Ini; |
63 | | /// |
64 | | ///let mut config = Ini::new(); |
65 | | ///let default = config.defaults(); |
66 | | ///assert_eq!(default.comment_symbols, vec![';', '#']); |
67 | | ///``` |
68 | | pub comment_symbols: Vec<char>, |
69 | | ///Denotes the set of inline comment symbols for the object. The default of |
70 | | ///`None` means to fall back to the normal comment symbols. |
71 | | ///## Example |
72 | | ///```rust |
73 | | ///use configparser::ini::Ini; |
74 | | /// |
75 | | ///let mut config = Ini::new(); |
76 | | ///let default = config.defaults(); |
77 | | ///assert_eq!(default.inline_comment_symbols, None); |
78 | | ///``` |
79 | | pub inline_comment_symbols: Option<Vec<char>>, |
80 | | ///Denotes the set delimiters for the key-value pairs. |
81 | | ///## Example |
82 | | ///```rust |
83 | | ///use configparser::ini::Ini; |
84 | | /// |
85 | | ///let mut config = Ini::new(); |
86 | | ///let default = config.defaults(); |
87 | | ///assert_eq!(default.delimiters, vec!['=', ':']); |
88 | | ///``` |
89 | | pub delimiters: Vec<char>, |
90 | | pub boolean_values: HashMap<bool, Vec<String>>, |
91 | | ///Denotes if the `Ini` object is case-sensitive. |
92 | | ///## Example |
93 | | ///```rust |
94 | | ///use configparser::ini::Ini; |
95 | | /// |
96 | | ///let mut config = Ini::new(); |
97 | | ///let default = config.defaults(); |
98 | | ///assert_eq!(default.case_sensitive, false); |
99 | | ///``` |
100 | | pub case_sensitive: bool, |
101 | | ///Denotes if the `Ini` object parses multiline strings. |
102 | | ///## Example |
103 | | ///```rust |
104 | | ///use configparser::ini::Ini; |
105 | | /// |
106 | | ///let mut config = Ini::new(); |
107 | | ///let default = config.defaults(); |
108 | | ///assert_eq!(default.multiline, false); |
109 | | ///``` |
110 | | pub multiline: bool, |
111 | | } |
112 | | |
113 | | impl Default for IniDefault { |
114 | 0 | fn default() -> Self { |
115 | | Self { |
116 | 0 | default_section: "default".to_owned(), |
117 | 0 | comment_symbols: vec![';', '#'], |
118 | 0 | inline_comment_symbols: None, |
119 | 0 | delimiters: vec!['=', ':'], |
120 | | multiline: false, |
121 | | boolean_values: [ |
122 | | ( |
123 | | true, |
124 | 0 | ["true", "yes", "t", "y", "on", "1"] |
125 | 0 | .iter() |
126 | 0 | .map(|&s| s.to_owned()) |
127 | 0 | .collect(), |
128 | | ), |
129 | | ( |
130 | | false, |
131 | 0 | ["false", "no", "f", "n", "off", "0"] |
132 | 0 | .iter() |
133 | 0 | .map(|&s| s.to_owned()) |
134 | 0 | .collect(), |
135 | | ), |
136 | | ] |
137 | 0 | .iter() |
138 | 0 | .cloned() |
139 | 0 | .collect(), |
140 | | case_sensitive: false, |
141 | | } |
142 | 0 | } |
143 | | } |
144 | | |
145 | | /// Use this struct to define formatting options for the `pretty_write` functions. |
146 | | #[derive(Debug, Clone, Eq, PartialEq)] |
147 | | #[non_exhaustive] |
148 | | pub struct WriteOptions { |
149 | | ///If true then the keys and values will be separated by " = ". In the special case where the value is empty, the |
150 | | ///line ends with " =". |
151 | | ///If false then keys and values will be separated by "=". |
152 | | ///Default is `false`. |
153 | | ///## Example |
154 | | ///```rust |
155 | | ///use configparser::ini::WriteOptions; |
156 | | /// |
157 | | ///let mut write_options = WriteOptions::default(); |
158 | | ///assert_eq!(write_options.space_around_delimiters, false); |
159 | | ///``` |
160 | | pub space_around_delimiters: bool, |
161 | | |
162 | | ///Defines the number of spaces for indentation of for multiline values. |
163 | | ///Default is 4 spaces. |
164 | | ///## Example |
165 | | ///```rust |
166 | | ///use configparser::ini::WriteOptions; |
167 | | /// |
168 | | ///let mut write_options = WriteOptions::default(); |
169 | | ///assert_eq!(write_options.multiline_line_indentation, 4); |
170 | | ///``` |
171 | | pub multiline_line_indentation: usize, |
172 | | |
173 | | ///Defines the number of blank lines between sections. |
174 | | ///Default is 0. |
175 | | ///## Example |
176 | | ///```rust |
177 | | ///use configparser::ini::WriteOptions; |
178 | | /// |
179 | | ///let mut write_options = WriteOptions::default(); |
180 | | ///assert_eq!(write_options.blank_lines_between_sections, 0); |
181 | | ///``` |
182 | | pub blank_lines_between_sections: usize, |
183 | | } |
184 | | |
185 | | impl Default for WriteOptions { |
186 | 0 | fn default() -> Self { |
187 | 0 | Self { |
188 | 0 | space_around_delimiters: false, |
189 | 0 | multiline_line_indentation: 4, |
190 | 0 | blank_lines_between_sections: 0, |
191 | 0 | } |
192 | 0 | } |
193 | | } |
194 | | |
195 | | impl WriteOptions { |
196 | | ///Creates a new `WriteOptions` object with the default values. |
197 | | ///## Example |
198 | | ///```rust |
199 | | ///use configparser::ini::WriteOptions; |
200 | | /// |
201 | | ///let write_options = WriteOptions::new(); |
202 | | ///assert_eq!(write_options.space_around_delimiters, false); |
203 | | ///assert_eq!(write_options.multiline_line_indentation, 4); |
204 | | ///assert_eq!(write_options.blank_lines_between_sections, 0); |
205 | | ///``` |
206 | | ///Returns the struct and stores it in the calling variable. |
207 | 0 | pub fn new() -> WriteOptions { |
208 | 0 | WriteOptions::default() |
209 | 0 | } |
210 | | |
211 | | ///Creates a new `WriteOptions` object with the given parameters. |
212 | | ///## Example |
213 | | ///```rust |
214 | | ///use configparser::ini::WriteOptions; |
215 | | /// |
216 | | ///let write_options = WriteOptions::new_with_params(true, 2, 1); |
217 | | ///assert_eq!(write_options.space_around_delimiters, true); |
218 | | ///assert_eq!(write_options.multiline_line_indentation, 2); |
219 | | ///assert_eq!(write_options.blank_lines_between_sections, 1); |
220 | | ///``` |
221 | | ///Returns the struct and stores it in the calling variable. |
222 | 0 | pub fn new_with_params( |
223 | 0 | space_around_delimiters: bool, |
224 | 0 | multiline_line_indentation: usize, |
225 | 0 | blank_lines_between_sections: usize, |
226 | 0 | ) -> WriteOptions { |
227 | 0 | Self { |
228 | 0 | space_around_delimiters, |
229 | 0 | multiline_line_indentation, |
230 | 0 | blank_lines_between_sections, |
231 | 0 | } |
232 | 0 | } |
233 | | } |
234 | | |
235 | | #[cfg(windows)] |
236 | | const LINE_ENDING: &str = "\r\n"; |
237 | | #[cfg(not(windows))] |
238 | | const LINE_ENDING: &str = "\n"; |
239 | | |
240 | | impl Ini { |
241 | | ///Creates a new `Map` of `Map<String, Map<String, Option<String>>>` type for the struct. |
242 | | ///All values in the Map are stored in `String` type. |
243 | | /// |
244 | | ///By default, [`std::collections::HashMap`] is used for the Map object. |
245 | | ///The `indexmap` feature can be used to use an [`indexmap::map::IndexMap`] instead, which |
246 | | ///allows keeping the insertion order for sections and keys. |
247 | | /// |
248 | | ///## Example |
249 | | ///```rust |
250 | | ///use configparser::ini::Ini; |
251 | | /// |
252 | | ///let mut config = Ini::new(); |
253 | | ///``` |
254 | | ///Returns the struct and stores it in the calling variable. |
255 | 0 | pub fn new() -> Ini { |
256 | 0 | Ini::new_from_defaults(IniDefault::default()) |
257 | 0 | } |
258 | | |
259 | | ///Creates a new **case-sensitive** `Map` of `Map<String, Map<String, Option<String>>>` type for the struct. |
260 | | ///All values in the Map are stored in `String` type. |
261 | | ///## Example |
262 | | ///```rust |
263 | | ///use configparser::ini::Ini; |
264 | | /// |
265 | | ///let mut config = Ini::new_cs(); |
266 | | ///``` |
267 | | ///Returns the struct and stores it in the calling variable. |
268 | 0 | pub fn new_cs() -> Ini { |
269 | 0 | Ini::new_from_defaults(IniDefault { |
270 | 0 | case_sensitive: true, |
271 | 0 | ..Default::default() |
272 | 0 | }) |
273 | 0 | } |
274 | | |
275 | | ///Creates a new `Ini` with the given defaults from an existing `IniDefault` object. |
276 | | ///## Example |
277 | | ///```rust |
278 | | ///use configparser::ini::Ini; |
279 | | ///use configparser::ini::IniDefault; |
280 | | /// |
281 | | ///let mut default = IniDefault::default(); |
282 | | ///default.comment_symbols = vec![';']; |
283 | | ///default.delimiters = vec!['=']; |
284 | | ///let mut config = Ini::new_from_defaults(default.clone()); |
285 | | ///// Now, load as usual with new defaults: |
286 | | ///let map = config.load("tests/test.ini").unwrap(); |
287 | | ///assert_eq!(config.defaults(), default); |
288 | | /// |
289 | | ///``` |
290 | 0 | pub fn new_from_defaults(defaults: IniDefault) -> Ini { |
291 | 0 | Ini { |
292 | 0 | map: Map::new(), |
293 | 0 | default_section: defaults.default_section, |
294 | 0 | comment_symbols: defaults.comment_symbols, |
295 | 0 | inline_comment_symbols: defaults.inline_comment_symbols, |
296 | 0 | delimiters: defaults.delimiters, |
297 | 0 | boolean_values: defaults.boolean_values, |
298 | 0 | case_sensitive: defaults.case_sensitive, |
299 | 0 | multiline: defaults.multiline, |
300 | 0 | } |
301 | 0 | } |
302 | | |
303 | | ///Fetches the defaults from the current `Ini` object and stores it as a `IniDefault` struct for usage elsewhere. |
304 | | ///## Example |
305 | | ///```rust |
306 | | ///use configparser::ini::Ini; |
307 | | /// |
308 | | ///let mut config = Ini::new(); |
309 | | ///let default = config.defaults(); |
310 | | ///``` |
311 | | ///Returns an `IniDefault` object. Keep in mind that it will get borrowed since it has non-`Copy` types. |
312 | 0 | pub fn defaults(&self) -> IniDefault { |
313 | 0 | IniDefault { |
314 | 0 | default_section: self.default_section.to_owned(), |
315 | 0 | comment_symbols: self.comment_symbols.to_owned(), |
316 | 0 | inline_comment_symbols: self.inline_comment_symbols.to_owned(), |
317 | 0 | delimiters: self.delimiters.to_owned(), |
318 | 0 | boolean_values: self.boolean_values.to_owned(), |
319 | 0 | case_sensitive: self.case_sensitive, |
320 | 0 | multiline: self.multiline, |
321 | 0 | } |
322 | 0 | } |
323 | | |
324 | | ///Takes an `IniDefault` object and stores its properties in the calling `Ini` object. This happens in-place and |
325 | | ///does not work retroactively, only future operations are affected. |
326 | | ///## Example |
327 | | ///```rust |
328 | | ///use configparser::ini::Ini; |
329 | | ///use configparser::ini::IniDefault; |
330 | | /// |
331 | | ///let mut config = Ini::new(); |
332 | | ///let mut default = IniDefault::default(); |
333 | | ///default.case_sensitive = true; |
334 | | ///// This is equivalent to ini_cs() defaults |
335 | | ///config.load_defaults(default.clone()); |
336 | | ///assert_eq!(config.defaults(), default); |
337 | | ///``` |
338 | | ///Returns nothing. |
339 | 0 | pub fn load_defaults(&mut self, defaults: IniDefault) { |
340 | 0 | self.default_section = defaults.default_section; |
341 | 0 | self.comment_symbols = defaults.comment_symbols; |
342 | 0 | self.inline_comment_symbols = defaults.inline_comment_symbols; |
343 | 0 | self.delimiters = defaults.delimiters; |
344 | 0 | self.boolean_values = defaults.boolean_values; |
345 | 0 | self.case_sensitive = defaults.case_sensitive; |
346 | 0 | } |
347 | | |
348 | | ///Sets the default section header to the defined string (the default is `default`). |
349 | | ///It must be set before `load()` or `read()` is called in order to take effect. |
350 | | ///## Example |
351 | | ///```rust |
352 | | ///use configparser::ini::Ini; |
353 | | /// |
354 | | ///let mut config = Ini::new(); |
355 | | /// |
356 | | ///config.set_default_section("topsecret"); |
357 | | ///let map = config.load("tests/test.ini").unwrap(); |
358 | | ///``` |
359 | | ///Returns nothing. |
360 | 0 | pub fn set_default_section(&mut self, section: &str) { |
361 | 0 | self.default_section = section.to_owned(); |
362 | 0 | } |
363 | | |
364 | | ///Sets the default comment symbols to the defined character slice (the defaults are `;` and `#`). |
365 | | ///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. |
366 | | ///## Example |
367 | | ///```rust |
368 | | ///use configparser::ini::Ini; |
369 | | /// |
370 | | ///let mut config = Ini::new(); |
371 | | ///config.set_comment_symbols(&['!', '#']); |
372 | | ///let map = config.load("tests/test.ini").unwrap(); |
373 | | ///``` |
374 | | ///Returns nothing. |
375 | 0 | pub fn set_comment_symbols(&mut self, symlist: &[char]) { |
376 | 0 | self.comment_symbols = symlist.to_vec(); |
377 | 0 | } |
378 | | |
379 | | ///Sets the default inline comment symbols to the defined character slice (the default is `None` which falls back to the normal comment symbols). |
380 | | ///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. |
381 | | ///## Example |
382 | | ///```rust |
383 | | ///use configparser::ini::Ini; |
384 | | /// |
385 | | ///let mut config = Ini::new(); |
386 | | ///config.set_inline_comment_symbols(Some(&['!', '#'])); |
387 | | ///let map = config.load("tests/test.ini").unwrap(); |
388 | | ///``` |
389 | | ///Returns nothing. |
390 | 0 | pub fn set_inline_comment_symbols(&mut self, symlist: Option<&[char]>) { |
391 | 0 | self.inline_comment_symbols = symlist.map(|val| val.to_vec()); |
392 | 0 | } |
393 | | |
394 | | ///Sets multiline string support. |
395 | | ///It must be set before `load()` or `read()` is called in order to take effect. |
396 | | ///## Example |
397 | | ///```rust |
398 | | ///use configparser::ini::Ini; |
399 | | /// |
400 | | ///let mut config = Ini::new(); |
401 | | ///config.set_multiline(true); |
402 | | ///let map = config.load("tests/test.ini").unwrap(); |
403 | | ///``` |
404 | | ///Returns nothing. |
405 | 0 | pub fn set_multiline(&mut self, multiline: bool) { |
406 | 0 | self.multiline = multiline; |
407 | 0 | } |
408 | | |
409 | | ///Gets all the sections of the currently-stored `Map` in a vector. |
410 | | ///## Example |
411 | | ///```rust |
412 | | ///use configparser::ini::Ini; |
413 | | /// |
414 | | ///let mut config = Ini::new(); |
415 | | ///config.load("tests/test.ini"); |
416 | | ///let sections = config.sections(); |
417 | | ///``` |
418 | | ///Returns `Vec<String>`. |
419 | 0 | pub fn sections(&self) -> Vec<String> { |
420 | 0 | self.map.keys().cloned().collect() |
421 | 0 | } |
422 | | |
423 | | ///Loads a file from a defined path, parses it and puts the hashmap into our struct. |
424 | | ///At one time, it only stores one configuration, so each call to `load()` or `read()` will clear the existing `Map`, if present. |
425 | | ///## Example |
426 | | ///```rust |
427 | | ///use configparser::ini::Ini; |
428 | | /// |
429 | | ///let mut config = Ini::new(); |
430 | | ///let map = config.load("tests/test.ini").unwrap(); // we can get a clone like this, or just store it |
431 | | /////Then, we can use standard hashmap functions like: |
432 | | ///let values = map.get("values").unwrap(); |
433 | | ///``` |
434 | | ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`. |
435 | | ///Use `get_mut_map()` if you want a mutable reference. |
436 | 0 | pub fn load<T: AsRef<Path>>( |
437 | 0 | &mut self, |
438 | 0 | path: T, |
439 | 0 | ) -> Result<Map<String, Map<String, Option<String>>>, String> { |
440 | 0 | self.map = match self.parse(match fs::read_to_string(&path) { |
441 | 0 | Err(why) => { |
442 | 0 | return Err(format!( |
443 | 0 | "couldn't read {}: {}", |
444 | 0 | &path.as_ref().display(), |
445 | 0 | why |
446 | 0 | )) |
447 | | } |
448 | 0 | Ok(s) => s, |
449 | | }) { |
450 | 0 | Err(why) => { |
451 | 0 | return Err(format!( |
452 | 0 | "couldn't read {}: {}", |
453 | 0 | &path.as_ref().display(), |
454 | 0 | why |
455 | 0 | )) |
456 | | } |
457 | 0 | Ok(map) => map, |
458 | | }; |
459 | 0 | Ok(self.map.clone()) |
460 | 0 | } |
461 | | |
462 | | ///Loads a file from a defined path, parses it and applies it to the existing hashmap in our struct. |
463 | | ///While `load()` will clear the existing `Map`, `load_and_append()` applies the new values on top of |
464 | | ///the existing hashmap, preserving previous values. |
465 | | ///## Example |
466 | | ///```rust |
467 | | ///use configparser::ini::Ini; |
468 | | /// |
469 | | ///let mut config = Ini::new(); |
470 | | ///config.load("tests/test.ini").unwrap(); |
471 | | ///config.load_and_append("tests/sys_cfg.ini").ok(); // we don't have to worry if this doesn't succeed |
472 | | ///config.load_and_append("tests/user_cfg.ini").ok(); // we don't have to worry if this doesn't succeed |
473 | | ///let map = config.get_map().unwrap(); |
474 | | /////Then, we can use standard hashmap functions like: |
475 | | ///let values = map.get("values").unwrap(); |
476 | | ///``` |
477 | | ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`. |
478 | | ///Use `get_mut_map()` if you want a mutable reference. |
479 | 0 | pub fn load_and_append<T: AsRef<Path>>( |
480 | 0 | &mut self, |
481 | 0 | path: T, |
482 | 0 | ) -> Result<Map<String, Map<String, Option<String>>>, String> { |
483 | 0 | let loaded = match self.parse(match fs::read_to_string(&path) { |
484 | 0 | Err(why) => { |
485 | 0 | return Err(format!( |
486 | 0 | "couldn't read {}: {}", |
487 | 0 | &path.as_ref().display(), |
488 | 0 | why |
489 | 0 | )) |
490 | | } |
491 | 0 | Ok(s) => s, |
492 | | }) { |
493 | 0 | Err(why) => { |
494 | 0 | return Err(format!( |
495 | 0 | "couldn't read {}: {}", |
496 | 0 | &path.as_ref().display(), |
497 | 0 | why |
498 | 0 | )) |
499 | | } |
500 | 0 | Ok(map) => map, |
501 | | }; |
502 | | |
503 | 0 | for (section, section_map) in loaded.iter() { |
504 | 0 | self.map |
505 | 0 | .entry(section.clone()) |
506 | 0 | .or_default() |
507 | 0 | .extend(section_map.clone()); |
508 | 0 | } |
509 | | |
510 | 0 | Ok(self.map.clone()) |
511 | 0 | } |
512 | | |
513 | | ///Reads an input string, parses it and puts the hashmap into our struct. |
514 | | ///At one time, it only stores one configuration, so each call to `load()` or `read()` will clear the existing `Map`, if present. |
515 | | ///## Example |
516 | | ///```rust |
517 | | ///use configparser::ini::Ini; |
518 | | /// |
519 | | ///let mut config = Ini::new(); |
520 | | ///let map = match config.read(String::from( |
521 | | /// "[2000s] |
522 | | /// 2020 = bad")) { |
523 | | /// Err(why) => panic!("{}", why), |
524 | | /// Ok(inner) => inner |
525 | | ///}; |
526 | | ///let this_year = map["2000s"]["2020"].clone().unwrap(); |
527 | | ///assert_eq!(this_year, "bad"); // value accessible! |
528 | | ///``` |
529 | | ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`. |
530 | | ///Use `get_mut_map()` if you want a mutable reference. |
531 | 0 | pub fn read( |
532 | 0 | &mut self, |
533 | 0 | input: String, |
534 | 0 | ) -> Result<Map<String, Map<String, Option<String>>>, String> { |
535 | 0 | self.map = match self.parse(input) { |
536 | 0 | Err(why) => return Err(why), |
537 | 0 | Ok(map) => map, |
538 | | }; |
539 | 0 | Ok(self.map.clone()) |
540 | 0 | } |
541 | | |
542 | | ///Reads an input string, parses it and applies it to the existing hashmap in our struct. |
543 | | ///While `read()` and `load()` will clear the existing `Map`, `read_and_append()` applies the new |
544 | | ///values on top of the existing hashmap, preserving previous values. |
545 | | ///## Example |
546 | | ///```rust |
547 | | ///use configparser::ini::Ini; |
548 | | /// |
549 | | ///let mut config = Ini::new(); |
550 | | ///if let Err(why) = config.read(String::from( |
551 | | /// "[2000s] |
552 | | /// 2020 = bad |
553 | | /// 2023 = better")) { |
554 | | /// panic!("{}", why); |
555 | | ///}; |
556 | | ///if let Err(why) = config.read_and_append(String::from( |
557 | | /// "[2000s] |
558 | | /// 2020 = terrible")) { |
559 | | /// panic!("{}", why); |
560 | | ///}; |
561 | | ///let map = config.get_map().unwrap(); |
562 | | ///let few_years_ago = map["2000s"]["2020"].clone().unwrap(); |
563 | | ///let this_year = map["2000s"]["2023"].clone().unwrap(); |
564 | | ///assert_eq!(few_years_ago, "terrible"); // value updated! |
565 | | ///assert_eq!(this_year, "better"); // keeps old values! |
566 | | ///``` |
567 | | ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`. |
568 | | ///Use `get_mut_map()` if you want a mutable reference. |
569 | 0 | pub fn read_and_append( |
570 | 0 | &mut self, |
571 | 0 | input: String, |
572 | 0 | ) -> Result<Map<String, Map<String, Option<String>>>, String> { |
573 | 0 | let loaded = match self.parse(input) { |
574 | 0 | Err(why) => return Err(why), |
575 | 0 | Ok(map) => map, |
576 | | }; |
577 | | |
578 | 0 | for (section, section_map) in loaded.iter() { |
579 | 0 | self.map |
580 | 0 | .entry(section.clone()) |
581 | 0 | .or_default() |
582 | 0 | .extend(section_map.clone()); |
583 | 0 | } |
584 | | |
585 | 0 | Ok(self.map.clone()) |
586 | 0 | } |
587 | | |
588 | | ///Writes the current configuation to the specified path using default formatting. |
589 | | ///If a file is not present then it is automatically created for you. If a file already exists then it is overwritten. |
590 | | ///## Example |
591 | | ///```rust |
592 | | ///use configparser::ini::Ini; |
593 | | /// |
594 | | ///fn main() -> std::io::Result<()> { |
595 | | /// let mut config = Ini::new(); |
596 | | /// config.read(String::from( |
597 | | /// "[2000s] |
598 | | /// 2020 = bad")); |
599 | | /// config.write("output.ini") |
600 | | ///} |
601 | | ///``` |
602 | | ///Returns a `std::io::Result<()>` type dependent on whether the write was successful or not. |
603 | 0 | pub fn write<T: AsRef<Path>>(&self, path: T) -> std::io::Result<()> { |
604 | 0 | fs::write(path.as_ref(), self.unparse(&WriteOptions::default())) |
605 | 0 | } |
606 | | |
607 | | ///Writes the current configuation to the specified path using the given formatting options. |
608 | | ///If a file is not present then it is automatically created for you. If a file already exists then it is overwritten. |
609 | | ///## Example |
610 | | ///```rust |
611 | | ///use configparser::ini::{Ini, WriteOptions}; |
612 | | /// |
613 | | ///fn main() -> std::io::Result<()> { |
614 | | /// let mut write_options = WriteOptions::default(); |
615 | | /// write_options.space_around_delimiters = true; |
616 | | /// write_options.multiline_line_indentation = 2; |
617 | | /// write_options.blank_lines_between_sections = 1; |
618 | | /// |
619 | | /// let mut config = Ini::new(); |
620 | | /// config.read(String::from( |
621 | | /// "[2000s] |
622 | | /// 2020 = bad")); |
623 | | /// config.pretty_write("output.ini", &write_options) |
624 | | ///} |
625 | | ///``` |
626 | | ///Returns a `std::io::Result<()>` type dependent on whether the write was successful or not. |
627 | 0 | pub fn pretty_write<T: AsRef<Path>>( |
628 | 0 | &self, |
629 | 0 | path: T, |
630 | 0 | write_options: &WriteOptions, |
631 | 0 | ) -> std::io::Result<()> { |
632 | 0 | fs::write(path.as_ref(), self.unparse(write_options)) |
633 | 0 | } |
634 | | |
635 | | ///Returns a string with the current configuration formatted with valid ini-syntax using default formatting. |
636 | | ///This is always safe since the configuration is validated during parsing. |
637 | | ///## Example |
638 | | ///```rust |
639 | | ///use configparser::ini::Ini; |
640 | | /// |
641 | | ///let mut config = Ini::new(); |
642 | | ///config.read(String::from( |
643 | | /// "[2000s] |
644 | | /// 2020 = bad")); |
645 | | ///let outstring = config.writes(); |
646 | | ///``` |
647 | | ///Returns a `String` type contatining the ini-syntax file. |
648 | 0 | pub fn writes(&self) -> String { |
649 | 0 | self.unparse(&WriteOptions::default()) |
650 | 0 | } |
651 | | |
652 | | ///Returns a string with the current configuration formatted with valid ini-syntax using the given formatting options. |
653 | | ///This is always safe since the configuration is validated during parsing. |
654 | | ///## Example |
655 | | ///```rust |
656 | | ///use configparser::ini::{Ini, WriteOptions}; |
657 | | /// |
658 | | ///let mut write_options = WriteOptions::default(); |
659 | | ///write_options.space_around_delimiters = true; |
660 | | ///write_options.multiline_line_indentation = 2; |
661 | | ///write_options.blank_lines_between_sections = 1; |
662 | | /// |
663 | | ///let mut config = Ini::new(); |
664 | | ///config.read(String::from( |
665 | | /// "[2000s] |
666 | | /// 2020 = bad")); |
667 | | ///let outstring = config.pretty_writes(&write_options); |
668 | | ///``` |
669 | | ///Returns a `String` type contatining the ini-syntax file. |
670 | 0 | pub fn pretty_writes(&self, write_options: &WriteOptions) -> String { |
671 | 0 | self.unparse(write_options) |
672 | 0 | } |
673 | | |
674 | | ///Private function that converts the currently stored configuration into a valid ini-syntax string. |
675 | 0 | fn unparse(&self, write_options: &WriteOptions) -> String { |
676 | | // push key/value pairs in outmap to out string. |
677 | 0 | fn unparse_key_values( |
678 | 0 | out: &mut String, |
679 | 0 | outmap: &Map<String, Option<String>>, |
680 | 0 | multiline: bool, |
681 | 0 | space_around_delimiters: bool, |
682 | 0 | indent: usize, |
683 | 0 | ) { |
684 | 0 | let delimiter = if space_around_delimiters { " = " } else { "=" }; |
685 | 0 | for (key, val) in outmap.iter() { |
686 | 0 | out.push_str(key); |
687 | | |
688 | 0 | if let Some(value) = val { |
689 | 0 | if value.is_empty() { |
690 | 0 | out.push_str(delimiter.trim_end()); |
691 | 0 | } else { |
692 | 0 | out.push_str(delimiter); |
693 | 0 | } |
694 | | |
695 | 0 | if multiline { |
696 | 0 | let mut lines = value.lines(); |
697 | | |
698 | 0 | out.push_str(lines.next().unwrap_or_default()); |
699 | | |
700 | 0 | for line in lines { |
701 | 0 | out.push_str(LINE_ENDING); |
702 | 0 | if !line.is_empty() { |
703 | 0 | out.push_str(" ".repeat(indent).as_ref()); |
704 | 0 | out.push_str(line); |
705 | 0 | } |
706 | | } |
707 | 0 | } else { |
708 | 0 | out.push_str(value); |
709 | 0 | } |
710 | 0 | } |
711 | | |
712 | 0 | out.push_str(LINE_ENDING); |
713 | | } |
714 | 0 | } |
715 | | |
716 | 0 | let line_endings = LINE_ENDING.repeat(write_options.blank_lines_between_sections); |
717 | 0 | let mut out = String::new(); |
718 | | |
719 | 0 | if let Some(defaultmap) = self.map.get(&self.default_section) { |
720 | 0 | unparse_key_values( |
721 | 0 | &mut out, |
722 | 0 | defaultmap, |
723 | 0 | self.multiline, |
724 | 0 | write_options.space_around_delimiters, |
725 | 0 | write_options.multiline_line_indentation, |
726 | 0 | ); |
727 | 0 | } |
728 | | |
729 | 0 | let mut is_first = true; |
730 | 0 | for (section, secmap) in self.map.iter() { |
731 | 0 | if !is_first { |
732 | 0 | out.push_str(line_endings.as_ref()); |
733 | 0 | } |
734 | 0 | if section != &self.default_section { |
735 | 0 | write!(out, "[{}]", section).unwrap(); |
736 | 0 | out.push_str(LINE_ENDING); |
737 | 0 | unparse_key_values( |
738 | 0 | &mut out, |
739 | 0 | secmap, |
740 | 0 | self.multiline, |
741 | 0 | write_options.space_around_delimiters, |
742 | 0 | write_options.multiline_line_indentation, |
743 | 0 | ); |
744 | 0 | } |
745 | 0 | is_first = false; |
746 | | } |
747 | 0 | out |
748 | 0 | } |
749 | | |
750 | | ///Private function that parses ini-style syntax into a Map. |
751 | 0 | fn parse(&self, input: String) -> Result<Map<String, Map<String, Option<String>>>, String> { |
752 | 0 | let inline_comment_symbols: &[char] = self |
753 | 0 | .inline_comment_symbols |
754 | 0 | .as_deref() |
755 | 0 | .unwrap_or_else(|| self.comment_symbols.as_ref()); |
756 | 0 | let mut map: Map<String, Map<String, Option<String>>> = Map::new(); |
757 | 0 | let mut section = self.default_section.clone(); |
758 | 0 | let mut current_key: Option<String> = None; |
759 | | |
760 | 0 | let caser = |val: &str| { |
761 | 0 | if self.case_sensitive { |
762 | 0 | val.to_owned() |
763 | | } else { |
764 | 0 | val.to_lowercase() |
765 | | } |
766 | 0 | }; |
767 | | |
768 | | // Track blank lines to preserve them in multiline values. |
769 | 0 | let mut blank_lines = 0usize; |
770 | | |
771 | 0 | for (num, raw_line) in input.lines().enumerate() { |
772 | 0 | let line = raw_line.trim(); |
773 | | |
774 | | // If the line is _just_ a comment, skip it entirely. |
775 | 0 | let line = match line.find(|c: char| self.comment_symbols.contains(&c)) { |
776 | 0 | Some(0) => continue, |
777 | 0 | Some(_) | None => line, |
778 | | }; |
779 | | |
780 | 0 | let line = line.trim(); |
781 | | |
782 | | // Skip empty lines, but keep track of them for multiline values. |
783 | 0 | if line.is_empty() { |
784 | 0 | blank_lines += 1; |
785 | 0 | continue; |
786 | 0 | } |
787 | | |
788 | 0 | let line = match line.find(|c: char| inline_comment_symbols.contains(&c)) { |
789 | 0 | Some(idx) => &line[..idx], |
790 | 0 | None => line, |
791 | | }; |
792 | | |
793 | 0 | let trimmed = line.trim(); |
794 | | |
795 | 0 | match (trimmed.find('['), trimmed.rfind(']')) { |
796 | 0 | (Some(0), Some(end)) => { |
797 | 0 | section = caser(trimmed[1..end].trim()); |
798 | | |
799 | 0 | map.entry(section.clone()).or_default(); |
800 | | |
801 | 0 | continue; |
802 | | } |
803 | | (Some(0), None) => { |
804 | 0 | return Err(format!( |
805 | 0 | "line {}: Found opening bracket for section name but no closing bracket", |
806 | 0 | num |
807 | 0 | )); |
808 | | } |
809 | 0 | _ => {} |
810 | | } |
811 | | |
812 | 0 | if raw_line.starts_with(char::is_whitespace) && self.multiline { |
813 | 0 | let key = match current_key.as_ref() { |
814 | 0 | Some(x) => x, |
815 | | None => { |
816 | 0 | return Err(format!( |
817 | 0 | "line {}: Started with indentation but there is no current entry", |
818 | 0 | num, |
819 | 0 | )) |
820 | | } |
821 | | }; |
822 | | |
823 | 0 | let valmap = map.entry(section.clone()).or_default(); |
824 | | |
825 | 0 | let val = valmap |
826 | 0 | .entry(key.clone()) |
827 | 0 | .or_insert_with(|| Some(String::new())); |
828 | | |
829 | 0 | match val { |
830 | 0 | Some(s) => { |
831 | 0 | for _ in 0..blank_lines { |
832 | 0 | s.push_str(LINE_ENDING); |
833 | 0 | } |
834 | 0 | s.push_str(LINE_ENDING); |
835 | 0 | s.push_str(trimmed); |
836 | | } |
837 | | None => { |
838 | 0 | let mut s = String::with_capacity( |
839 | 0 | (blank_lines + 1) * LINE_ENDING.len() + trimmed.len(), |
840 | | ); |
841 | 0 | for _ in 0..blank_lines { |
842 | 0 | s.push_str(LINE_ENDING); |
843 | 0 | } |
844 | 0 | s.push_str(LINE_ENDING); |
845 | 0 | s.push_str(trimmed); |
846 | 0 | *val = Some(s); |
847 | | } |
848 | | } |
849 | | } else { |
850 | 0 | let valmap = map.entry(section.clone()).or_default(); |
851 | | |
852 | 0 | match trimmed.find(&self.delimiters[..]) { |
853 | 0 | Some(delimiter) => { |
854 | 0 | let key = caser(trimmed[..delimiter].trim()); |
855 | | |
856 | 0 | if key.is_empty() { |
857 | 0 | return Err(format!("line {}:{}: Key cannot be empty", num, delimiter)); |
858 | 0 | } else { |
859 | 0 | current_key = Some(key.clone()); |
860 | 0 |
|
861 | 0 | let value = trimmed[delimiter + 1..].trim().to_owned(); |
862 | 0 |
|
863 | 0 | valmap.insert(key, Some(value)); |
864 | 0 | } |
865 | | } |
866 | 0 | None => { |
867 | 0 | let key = caser(trimmed); |
868 | 0 | current_key = Some(key.clone()); |
869 | 0 |
|
870 | 0 | valmap.insert(key, None); |
871 | 0 | } |
872 | | } |
873 | | } |
874 | | |
875 | 0 | blank_lines = 0; |
876 | | } |
877 | | |
878 | 0 | Ok(map) |
879 | 0 | } |
880 | | |
881 | | ///Private function that cases things automatically depending on the set variable. |
882 | 0 | fn autocase(&self, section: &str, key: &str) -> (String, String) { |
883 | 0 | if self.case_sensitive { |
884 | 0 | (section.to_owned(), key.to_owned()) |
885 | | } else { |
886 | 0 | (section.to_lowercase(), key.to_lowercase()) |
887 | | } |
888 | 0 | } |
889 | | |
890 | | ///Returns a clone of the stored value from the key stored in the defined section. |
891 | | ///Unlike accessing the map directly, `get()` can process your input to make case-insensitive access *if* the |
892 | | ///default constructor is used. |
893 | | ///All `get` functions will do this automatically under the hood. |
894 | | ///## Example |
895 | | ///```rust |
896 | | ///use configparser::ini::Ini; |
897 | | /// |
898 | | ///let mut config = Ini::new(); |
899 | | ///config.load("tests/test.ini"); |
900 | | ///let value = config.get("default", "defaultvalues").unwrap(); |
901 | | ///assert_eq!(value, String::from("defaultvalues")); |
902 | | ///``` |
903 | | ///Returns `Some(value)` of type `String` if value is found or else returns `None`. |
904 | 0 | pub fn get(&self, section: &str, key: &str) -> Option<String> { |
905 | 0 | let (section, key) = self.autocase(section, key); |
906 | 0 | self.map.get(§ion)?.get(&key)?.clone() |
907 | 0 | } |
908 | | |
909 | | ///Parses the stored value from the key stored in the defined section to a `bool`. |
910 | | ///For ease of use, the function converts the type case-insensitively (`true` == `True`). |
911 | | ///## Example |
912 | | ///```rust |
913 | | ///use configparser::ini::Ini; |
914 | | /// |
915 | | ///let mut config = Ini::new(); |
916 | | ///config.load("tests/test.ini"); |
917 | | ///let value = config.getbool("values", "bool").unwrap().unwrap(); |
918 | | ///assert!(value); // value accessible! |
919 | | ///``` |
920 | | ///Returns `Ok(Some(value))` of type `bool` if value is found or else returns `Ok(None)`. |
921 | | ///If the parsing fails, it returns an `Err(string)`. |
922 | 0 | pub fn getbool(&self, section: &str, key: &str) -> Result<Option<bool>, String> { |
923 | 0 | let (section, key) = self.autocase(section, key); |
924 | 0 | match self.map.get(§ion) { |
925 | 0 | Some(secmap) => match secmap.get(&key) { |
926 | 0 | Some(val) => match val { |
927 | 0 | Some(inner) => match inner.to_lowercase().parse::<bool>() { |
928 | 0 | Err(why) => Err(why.to_string()), |
929 | 0 | Ok(boolean) => Ok(Some(boolean)), |
930 | | }, |
931 | 0 | None => Ok(None), |
932 | | }, |
933 | 0 | None => Ok(None), |
934 | | }, |
935 | 0 | None => Ok(None), |
936 | | } |
937 | 0 | } |
938 | | |
939 | | ///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. |
940 | | ///It attempts to case-insenstively find `true`, `yes`, `t`, `y`, `1` and `on` to parse it as `True`. |
941 | | ///Similarly it attempts to case-insensitvely find `false`, `no`, `f`, `n`, `0` and `off` to parse it as `False`. |
942 | | ///## Example |
943 | | ///```rust |
944 | | ///use configparser::ini::Ini; |
945 | | /// |
946 | | ///let mut config = Ini::new(); |
947 | | ///config.load("tests/test.ini"); |
948 | | ///let value = config.getboolcoerce("values", "boolcoerce").unwrap().unwrap(); |
949 | | ///assert!(!value); // value accessible! |
950 | | ///``` |
951 | | ///Returns `Ok(Some(value))` of type `bool` if value is found or else returns `Ok(None)`. |
952 | | ///If the parsing fails, it returns an `Err(string)`. |
953 | 0 | pub fn getboolcoerce(&self, section: &str, key: &str) -> Result<Option<bool>, String> { |
954 | 0 | let (section, key) = self.autocase(section, key); |
955 | 0 | match self.map.get(§ion) { |
956 | 0 | Some(secmap) => match secmap.get(&key) { |
957 | 0 | Some(val) => match val { |
958 | 0 | Some(inner) => { |
959 | 0 | let boolval = &inner.to_lowercase()[..]; |
960 | 0 | if self |
961 | 0 | .boolean_values |
962 | 0 | .get(&true) |
963 | 0 | .unwrap() |
964 | 0 | .iter() |
965 | 0 | .any(|elem| elem == boolval) |
966 | | { |
967 | 0 | Ok(Some(true)) |
968 | 0 | } else if self |
969 | 0 | .boolean_values |
970 | 0 | .get(&false) |
971 | 0 | .unwrap() |
972 | 0 | .iter() |
973 | 0 | .any(|elem| elem == boolval) |
974 | | { |
975 | 0 | Ok(Some(false)) |
976 | | } else { |
977 | 0 | Err(format!( |
978 | 0 | "Unable to parse value into bool at {}:{}", |
979 | 0 | section, key |
980 | 0 | )) |
981 | | } |
982 | | } |
983 | 0 | None => Ok(None), |
984 | | }, |
985 | 0 | None => Ok(None), |
986 | | }, |
987 | 0 | None => Ok(None), |
988 | | } |
989 | 0 | } |
990 | | |
991 | | ///Parses the stored value from the key stored in the defined section to an `i64`. |
992 | | ///## Example |
993 | | ///```rust |
994 | | ///use configparser::ini::Ini; |
995 | | /// |
996 | | ///let mut config = Ini::new(); |
997 | | ///config.load("tests/test.ini"); |
998 | | ///let value = config.getint("values", "int").unwrap().unwrap(); |
999 | | ///assert_eq!(value, -31415); // value accessible! |
1000 | | ///``` |
1001 | | ///Returns `Ok(Some(value))` of type `i64` if value is found or else returns `Ok(None)`. |
1002 | | ///If the parsing fails, it returns an `Err(string)`. |
1003 | 0 | pub fn getint(&self, section: &str, key: &str) -> Result<Option<i64>, String> { |
1004 | 0 | let (section, key) = self.autocase(section, key); |
1005 | 0 | match self.map.get(§ion) { |
1006 | 0 | Some(secmap) => match secmap.get(&key) { |
1007 | 0 | Some(val) => match val { |
1008 | 0 | Some(inner) => match inner.parse::<i64>() { |
1009 | 0 | Err(why) => Err(why.to_string()), |
1010 | 0 | Ok(int) => Ok(Some(int)), |
1011 | | }, |
1012 | 0 | None => Ok(None), |
1013 | | }, |
1014 | 0 | None => Ok(None), |
1015 | | }, |
1016 | 0 | None => Ok(None), |
1017 | | } |
1018 | 0 | } |
1019 | | |
1020 | | ///Parses the stored value from the key stored in the defined section to a `u64`. |
1021 | | ///## Example |
1022 | | ///```rust |
1023 | | ///use configparser::ini::Ini; |
1024 | | /// |
1025 | | ///let mut config = Ini::new(); |
1026 | | ///config.load("tests/test.ini"); |
1027 | | ///let value = config.getint("values", "Uint").unwrap().unwrap(); |
1028 | | ///assert_eq!(value, 31415); // value accessible! |
1029 | | ///``` |
1030 | | ///Returns `Ok(Some(value))` of type `u64` if value is found or else returns `Ok(None)`. |
1031 | | ///If the parsing fails, it returns an `Err(string)`. |
1032 | 0 | pub fn getuint(&self, section: &str, key: &str) -> Result<Option<u64>, String> { |
1033 | 0 | let (section, key) = self.autocase(section, key); |
1034 | 0 | match self.map.get(§ion) { |
1035 | 0 | Some(secmap) => match secmap.get(&key) { |
1036 | 0 | Some(val) => match val { |
1037 | 0 | Some(inner) => match inner.parse::<u64>() { |
1038 | 0 | Err(why) => Err(why.to_string()), |
1039 | 0 | Ok(uint) => Ok(Some(uint)), |
1040 | | }, |
1041 | 0 | None => Ok(None), |
1042 | | }, |
1043 | 0 | None => Ok(None), |
1044 | | }, |
1045 | 0 | None => Ok(None), |
1046 | | } |
1047 | 0 | } |
1048 | | |
1049 | | ///Parses the stored value from the key stored in the defined section to a `f64`. |
1050 | | ///## Example |
1051 | | ///```rust |
1052 | | ///use configparser::ini::Ini; |
1053 | | /// |
1054 | | ///let mut config = Ini::new(); |
1055 | | ///config.load("tests/test.ini"); |
1056 | | ///let value = config.getfloat("values", "float").unwrap().unwrap(); |
1057 | | ///assert_eq!(value, 3.1415); // value accessible! |
1058 | | ///``` |
1059 | | ///Returns `Ok(Some(value))` of type `f64` if value is found or else returns `Ok(None)`. |
1060 | | ///If the parsing fails, it returns an `Err(string)`. |
1061 | 0 | pub fn getfloat(&self, section: &str, key: &str) -> Result<Option<f64>, String> { |
1062 | 0 | let (section, key) = self.autocase(section, key); |
1063 | 0 | match self.map.get(§ion) { |
1064 | 0 | Some(secmap) => match secmap.get(&key) { |
1065 | 0 | Some(val) => match val { |
1066 | 0 | Some(inner) => match inner.parse::<f64>() { |
1067 | 0 | Err(why) => Err(why.to_string()), |
1068 | 0 | Ok(float) => Ok(Some(float)), |
1069 | | }, |
1070 | 0 | None => Ok(None), |
1071 | | }, |
1072 | 0 | None => Ok(None), |
1073 | | }, |
1074 | 0 | None => Ok(None), |
1075 | | } |
1076 | 0 | } |
1077 | | |
1078 | | ///Returns a clone of the `Map` stored in our struct. |
1079 | | ///## Example |
1080 | | ///```rust |
1081 | | ///use configparser::ini::Ini; |
1082 | | /// |
1083 | | ///let mut config = Ini::new(); |
1084 | | ///config.read(String::from( |
1085 | | /// "[section] |
1086 | | /// key=values")); |
1087 | | ///let map = config.get_map().unwrap(); |
1088 | | ///assert_eq!(map, *config.get_map_ref()); // the cloned map is basically a snapshot that you own |
1089 | | ///``` |
1090 | | ///Returns `Some(map)` if map is non-empty or else returns `None`. |
1091 | | ///Similar to `load()` but returns an `Option` type with the currently stored `Map`. |
1092 | 0 | pub fn get_map(&self) -> Option<Map<String, Map<String, Option<String>>>> { |
1093 | 0 | if self.map.is_empty() { |
1094 | 0 | None |
1095 | | } else { |
1096 | 0 | Some(self.map.clone()) |
1097 | | } |
1098 | 0 | } |
1099 | | |
1100 | | ///Returns an immutable reference to the `Map` stored in our struct. |
1101 | | ///## Example |
1102 | | ///```rust |
1103 | | ///use configparser::ini::Ini; |
1104 | | /// |
1105 | | ///let mut config = Ini::new(); |
1106 | | ///let mapclone = config.read(String::from |
1107 | | /// ("[topsecrets] |
1108 | | /// Valueless key")).unwrap(); |
1109 | | /////Think of the clone as being a snapshot at a point of time while the reference always points to the current configuration. |
1110 | | ///assert_eq!(*config.get_map_ref(), mapclone); // same as expected. |
1111 | | ///``` |
1112 | | ///If you just need to definitely mutate the map, use `get_mut_map()` instead. Alternatively, you can generate a snapshot by getting a clone |
1113 | | ///with `get_map()` and work with that. |
1114 | 0 | pub fn get_map_ref(&self) -> &Map<String, Map<String, Option<String>>> { |
1115 | 0 | &self.map |
1116 | 0 | } |
1117 | | |
1118 | | ///Returns a mutable reference to the `Map` stored in our struct. |
1119 | | ///## Example |
1120 | | ///```rust |
1121 | | ///use configparser::ini::Ini; |
1122 | | /// |
1123 | | ///let mut config = Ini::new(); |
1124 | | ///config.read(String::from |
1125 | | /// ("[topsecrets] |
1126 | | /// Valueless key")); |
1127 | | /////We can then get the mutable map and insert a value like: |
1128 | | ///config.get_mut_map().get_mut("topsecrets").unwrap().insert(String::from("nuclear launch codes"), None); |
1129 | | ///assert_eq!(config.get("topsecrets", "nuclear launch codes"), None); // inserted successfully! |
1130 | | ///``` |
1131 | | ///If you just need to access the map without mutating, use `get_map_ref()` or make a clone with `get_map()` instead. |
1132 | 0 | pub fn get_mut_map(&mut self) -> &mut Map<String, Map<String, Option<String>>> { |
1133 | 0 | &mut self.map |
1134 | 0 | } |
1135 | | |
1136 | | ///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. |
1137 | | ///An existing value in the map will be overwritten. You can also set `None` safely. |
1138 | | ///## Example |
1139 | | ///```rust |
1140 | | ///use configparser::ini::Ini; |
1141 | | /// |
1142 | | ///let mut config = Ini::new(); |
1143 | | ///config.read(String::from( |
1144 | | /// "[section] |
1145 | | /// key=value")); |
1146 | | ///let key_value = String::from("value"); |
1147 | | ///config.set("section", "key", Some(key_value)); |
1148 | | ///config.set("section", "key", None); // also valid! |
1149 | | ///assert_eq!(config.get("section", "key"), None); // correct! |
1150 | | ///``` |
1151 | | ///Returns `None` if there is no existing value, else returns `Some(Option<String>)`, with the existing value being the wrapped `Option<String>`. |
1152 | | ///If you want to insert using a string literal, use `setstr()` instead. |
1153 | 0 | pub fn set( |
1154 | 0 | &mut self, |
1155 | 0 | section: &str, |
1156 | 0 | key: &str, |
1157 | 0 | value: Option<String>, |
1158 | 0 | ) -> Option<Option<String>> { |
1159 | 0 | let (section, key) = self.autocase(section, key); |
1160 | 0 | match self.map.get_mut(§ion) { |
1161 | 0 | Some(secmap) => secmap.insert(key, value), |
1162 | | None => { |
1163 | 0 | let mut valmap: Map<String, Option<String>> = Map::new(); |
1164 | 0 | valmap.insert(key, value); |
1165 | 0 | self.map.insert(section, valmap); |
1166 | 0 | None |
1167 | | } |
1168 | | } |
1169 | 0 | } |
1170 | | |
1171 | | ///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. |
1172 | | ///An existing value in the map will be overwritten. You can also set `None` safely. |
1173 | | ///## Example |
1174 | | ///```rust |
1175 | | ///use configparser::ini::Ini; |
1176 | | /// |
1177 | | ///let mut config = Ini::new(); |
1178 | | ///config.read(String::from( |
1179 | | /// "[section] |
1180 | | /// key=notvalue")); |
1181 | | ///config.setstr("section", "key", Some("value")); |
1182 | | ///config.setstr("section", "key", None); // also valid! |
1183 | | ///assert_eq!(config.get("section", "key"), None); // correct! |
1184 | | ///``` |
1185 | | ///Returns `None` if there is no existing value, else returns `Some(Option<String>)`, with the existing value being the wrapped `Option<String>`. |
1186 | | ///If you want to insert using a `String`, use `set()` instead. |
1187 | 0 | pub fn setstr( |
1188 | 0 | &mut self, |
1189 | 0 | section: &str, |
1190 | 0 | key: &str, |
1191 | 0 | value: Option<&str>, |
1192 | 0 | ) -> Option<Option<String>> { |
1193 | 0 | let (section, key) = self.autocase(section, key); |
1194 | 0 | self.set(§ion, &key, value.map(String::from)) |
1195 | 0 | } |
1196 | | |
1197 | | ///Clears the map, removing all sections and properties from the hashmap. It keeps the allocated memory for reuse. |
1198 | | ///## Example |
1199 | | ///```rust |
1200 | | ///use configparser::ini::Ini; |
1201 | | /// |
1202 | | ///let mut config = Ini::new(); |
1203 | | ///config.read(String::from( |
1204 | | /// "[section] |
1205 | | /// key=somevalue")); |
1206 | | ///config.clear(); |
1207 | | ///assert!(config.get_map_ref().is_empty()); // our map is empty! |
1208 | | ///``` |
1209 | | ///Returns nothing. |
1210 | 0 | pub fn clear(&mut self) { |
1211 | 0 | self.map.clear(); |
1212 | 0 | } |
1213 | | |
1214 | | ///Removes a section from the hashmap, returning the properties stored in the section if the section was previously in the map. |
1215 | | ///## Example |
1216 | | ///```rust |
1217 | | ///use configparser::ini::Ini; |
1218 | | /// |
1219 | | ///let mut config = Ini::new(); |
1220 | | ///config.read(String::from( |
1221 | | /// "[section] |
1222 | | /// updog=whatsupdog")); |
1223 | | ///config.remove_section("section"); // this will return a cloned hashmap of the stored property |
1224 | | ///assert!(config.get_map_ref().is_empty()); // with the last section removed, our map is now empty! |
1225 | | ///``` |
1226 | | ///Returns `Some(section_map)` if the section exists or else, `None`. |
1227 | 0 | pub fn remove_section(&mut self, section: &str) -> Option<Map<String, Option<String>>> { |
1228 | 0 | let section = if self.case_sensitive { |
1229 | 0 | section.to_owned() |
1230 | | } else { |
1231 | 0 | section.to_lowercase() |
1232 | | }; |
1233 | | #[cfg(not(feature = "indexmap"))] |
1234 | | { |
1235 | | self.map.remove(§ion) |
1236 | | } |
1237 | | #[cfg(feature = "indexmap")] |
1238 | | { |
1239 | 0 | self.map.swap_remove(§ion) |
1240 | | } |
1241 | 0 | } |
1242 | | |
1243 | | ///Removes a key from a section in the hashmap, returning the value attached to the key if it was previously in the map. |
1244 | | ///## Example |
1245 | | ///```rust |
1246 | | ///use configparser::ini::Ini; |
1247 | | /// |
1248 | | ///let mut config = Ini::new(); |
1249 | | ///config.read(String::from( |
1250 | | /// "[section] |
1251 | | /// updog=whatsupdog |
1252 | | /// [anothersection] |
1253 | | /// updog=differentdog")); |
1254 | | ///let val = config.remove_key("anothersection", "updog").unwrap().unwrap(); |
1255 | | ///assert_eq!(val, String::from("differentdog")); // with the last section removed, our map is now empty! |
1256 | | ///``` |
1257 | | ///Returns `Some(Option<String>)` if the value exists or else, `None`. |
1258 | 0 | pub fn remove_key(&mut self, section: &str, key: &str) -> Option<Option<String>> { |
1259 | 0 | let (section, key) = self.autocase(section, key); |
1260 | | #[cfg(not(feature = "indexmap"))] |
1261 | | { |
1262 | | self.map.get_mut(§ion)?.remove(&key) |
1263 | | } |
1264 | | #[cfg(feature = "indexmap")] |
1265 | | { |
1266 | 0 | self.map.get_mut(§ion)?.swap_remove(&key) |
1267 | | } |
1268 | 0 | } |
1269 | | } |
1270 | | |
1271 | | #[cfg(feature = "async-std")] |
1272 | | impl Ini { |
1273 | | ///Loads a file asynchronously from a defined path, parses it and puts the hashmap into our struct. |
1274 | | ///At one time, it only stores one configuration, so each call to `load()` or `read()` will clear the existing `Map`, if present. |
1275 | | /// |
1276 | | ///Usage is similar to `load`, but `.await` must be called after along with the usual async rules. |
1277 | | /// |
1278 | | ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`. |
1279 | | ///Use `get_mut_map()` if you want a mutable reference. |
1280 | | pub async fn load_async<T: AsRef<Path>>( |
1281 | | &mut self, |
1282 | | path: T, |
1283 | | ) -> Result<Map<String, Map<String, Option<String>>>, String> { |
1284 | | self.map = match self.parse(match async_fs::read_to_string(&path).await { |
1285 | | Err(why) => { |
1286 | | return Err(format!( |
1287 | | "couldn't read {}: {}", |
1288 | | &path.as_ref().display(), |
1289 | | why |
1290 | | )) |
1291 | | } |
1292 | | Ok(s) => s, |
1293 | | }) { |
1294 | | Err(why) => { |
1295 | | return Err(format!( |
1296 | | "couldn't read {}: {}", |
1297 | | &path.as_ref().display(), |
1298 | | why |
1299 | | )) |
1300 | | } |
1301 | | Ok(map) => map, |
1302 | | }; |
1303 | | Ok(self.map.clone()) |
1304 | | } |
1305 | | |
1306 | | ///Loads a file from a defined path, parses it and applies it to the existing hashmap in our struct. |
1307 | | ///While `load_async()` will clear the existing `Map`, `load_and_append_async()` applies the new values on top |
1308 | | ///of the existing hashmap, preserving previous values. |
1309 | | /// |
1310 | | ///Usage is similar to `load_and_append`, but `.await` must be called after along with the usual async rules. |
1311 | | /// |
1312 | | ///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`. |
1313 | | ///Use `get_mut_map()` if you want a mutable reference. |
1314 | | pub async fn load_and_append_async<T: AsRef<Path>>( |
1315 | | &mut self, |
1316 | | path: T, |
1317 | | ) -> Result<Map<String, Map<String, Option<String>>>, String> { |
1318 | | let loaded = match self.parse(match async_fs::read_to_string(&path).await { |
1319 | | Err(why) => { |
1320 | | return Err(format!( |
1321 | | "couldn't read {}: {}", |
1322 | | &path.as_ref().display(), |
1323 | | why |
1324 | | )) |
1325 | | } |
1326 | | Ok(s) => s, |
1327 | | }) { |
1328 | | Err(why) => { |
1329 | | return Err(format!( |
1330 | | "couldn't read {}: {}", |
1331 | | &path.as_ref().display(), |
1332 | | why |
1333 | | )) |
1334 | | } |
1335 | | Ok(map) => map, |
1336 | | }; |
1337 | | |
1338 | | for (section, section_map) in loaded.iter() { |
1339 | | self.map |
1340 | | .entry(section.clone()) |
1341 | | .or_insert_with(Map::new) |
1342 | | .extend(section_map.clone()); |
1343 | | } |
1344 | | |
1345 | | Ok(self.map.clone()) |
1346 | | } |
1347 | | |
1348 | | ///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 |
1349 | | ///exists, it is truncated and the configuration is written to it. |
1350 | | /// |
1351 | | ///Usage is the same as `write`, but `.await` must be called after along with the usual async rules. |
1352 | | /// |
1353 | | ///Returns a `std::io::Result<()>` type dependent on whether the write was successful or not. |
1354 | | pub async fn write_async<T: AsRef<Path>>(&self, path: T) -> std::io::Result<()> { |
1355 | | async_fs::write(path.as_ref(), self.unparse(&WriteOptions::default())).await |
1356 | | } |
1357 | | |
1358 | | ///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 |
1359 | | ///exists, it is truncated and the configuration is written to it. |
1360 | | /// |
1361 | | ///Usage is the same as `pretty_pretty_write`, but `.await` must be called after along with the usual async rules. |
1362 | | /// |
1363 | | ///Returns a `std::io::Result<()>` type dependent on whether the write was successful or not. |
1364 | | pub async fn pretty_write_async<T: AsRef<Path>>( |
1365 | | &self, |
1366 | | path: T, |
1367 | | write_options: &WriteOptions, |
1368 | | ) -> std::io::Result<()> { |
1369 | | async_fs::write(path.as_ref(), self.unparse(write_options)).await |
1370 | | } |
1371 | | } |