Skip to content

Commit e1418c6

Browse files
SwivelgamesQEDK
authored andcommitted
feat: added load_more to accumulate multiple configs
1 parent 16fa334 commit e1418c6

File tree

3 files changed

+194
-0
lines changed

3 files changed

+194
-0
lines changed

src/ini.rs

+139
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,57 @@ impl Ini {
339339
Ok(self.map.clone())
340340
}
341341

342+
///Loads a file from a defined path, parses it and applies it to the existing hashmap in our struct.
343+
///While `load()` will clear the existing `Map`, `load_and_append()` applies the new values on top of
344+
///the existing hashmap, preserving previous values.
345+
///## Example
346+
///```rust
347+
///use configparser::ini::Ini;
348+
///
349+
///let mut config = Ini::new();
350+
///config.load("tests/test.ini").unwrap();
351+
///config.load_and_append("tests/sys_cfg.ini").ok(); // we don't have to worry if this doesn't succeed
352+
///config.load_and_append("tests/user_cfg.ini").ok(); // we don't have to worry if this doesn't succeed
353+
///let map = config.get_map().unwrap();
354+
/////Then, we can use standard hashmap functions like:
355+
///let values = map.get("values").unwrap();
356+
///```
357+
///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`.
358+
///Use `get_mut_map()` if you want a mutable reference.
359+
pub fn load_and_append<T: AsRef<Path>>(
360+
&mut self,
361+
path: T,
362+
) -> Result<Map<String, Map<String, Option<String>>>, String> {
363+
let loaded = match self.parse(match fs::read_to_string(&path) {
364+
Err(why) => {
365+
return Err(format!(
366+
"couldn't read {}: {}",
367+
&path.as_ref().display(),
368+
why
369+
))
370+
}
371+
Ok(s) => s,
372+
}) {
373+
Err(why) => {
374+
return Err(format!(
375+
"couldn't read {}: {}",
376+
&path.as_ref().display(),
377+
why
378+
))
379+
}
380+
Ok(map) => map,
381+
};
382+
383+
for (section, section_map) in loaded.iter() {
384+
self.map
385+
.entry(section.clone())
386+
.or_insert_with(Map::new)
387+
.extend(section_map.clone());
388+
}
389+
390+
Ok(self.map.clone())
391+
}
392+
342393
///Reads an input string, parses it and puts the hashmap into our struct.
343394
///At one time, it only stores one configuration, so each call to `load()` or `read()` will clear the existing `Map`, if present.
344395
///## Example
@@ -368,6 +419,52 @@ impl Ini {
368419
Ok(self.map.clone())
369420
}
370421

422+
///Reads an input string, parses it and applies it to the existing hashmap in our struct.
423+
///While `read()` and `load()` will clear the existing `Map`, `read_and_append()` applies the new
424+
///values on top of the existing hashmap, preserving previous values.
425+
///## Example
426+
///```rust
427+
///use configparser::ini::Ini;
428+
///
429+
///let mut config = Ini::new();
430+
///if let Err(why) = config.read(String::from(
431+
/// "[2000s]
432+
/// 2020 = bad
433+
/// 2023 = better")) {
434+
/// panic!("{}", why);
435+
///};
436+
///if let Err(why) = config.read_and_append(String::from(
437+
/// "[2000s]
438+
/// 2020 = terrible")) {
439+
/// panic!("{}", why);
440+
///};
441+
///let map = config.get_map().unwrap();
442+
///let few_years_ago = map["2000s"]["2020"].clone().unwrap();
443+
///let this_year = map["2000s"]["2023"].clone().unwrap();
444+
///assert_eq!(few_years_ago, "terrible"); // value updated!
445+
///assert_eq!(this_year, "better"); // keeps old values!
446+
///```
447+
///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`.
448+
///Use `get_mut_map()` if you want a mutable reference.
449+
pub fn read_and_append(
450+
&mut self,
451+
input: String,
452+
) -> Result<Map<String, Map<String, Option<String>>>, String> {
453+
let loaded = match self.parse(input) {
454+
Err(why) => return Err(why),
455+
Ok(map) => map,
456+
};
457+
458+
for (section, section_map) in loaded.iter() {
459+
self.map
460+
.entry(section.clone())
461+
.or_insert_with(Map::new)
462+
.extend(section_map.clone());
463+
}
464+
465+
Ok(self.map.clone())
466+
}
467+
371468
///Writes the current configuation to the specified path. If a file is not present, it is automatically created for you, if a file already
372469
///exists, it is truncated and the configuration is written to it.
373470
///## Example
@@ -963,6 +1060,48 @@ impl Ini {
9631060
Ok(self.map.clone())
9641061
}
9651062

1063+
///Loads a file from a defined path, parses it and applies it to the existing hashmap in our struct.
1064+
///While `load_async()` will clear the existing `Map`, `load_and_append_async()` applies the new values on top
1065+
///of the existing hashmap, preserving previous values.
1066+
///
1067+
///Usage is similar to `load_and_append`, but `.await` must be called after along with the usual async rules.
1068+
///
1069+
///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`.
1070+
///Use `get_mut_map()` if you want a mutable reference.
1071+
pub async fn load_and_append_async<T: AsRef<AsyncPath>>(
1072+
&mut self,
1073+
path: T,
1074+
) -> Result<Map<String, Map<String, Option<String>>>, String> {
1075+
let loaded = match self.parse(match async_fs::read_to_string(&path).await {
1076+
Err(why) => {
1077+
return Err(format!(
1078+
"couldn't read {}: {}",
1079+
&path.as_ref().display(),
1080+
why
1081+
))
1082+
}
1083+
Ok(s) => s,
1084+
}) {
1085+
Err(why) => {
1086+
return Err(format!(
1087+
"couldn't read {}: {}",
1088+
&path.as_ref().display(),
1089+
why
1090+
))
1091+
}
1092+
Ok(map) => map,
1093+
};
1094+
1095+
for (section, section_map) in loaded.iter() {
1096+
self.map
1097+
.entry(section.clone())
1098+
.or_insert_with(Map::new)
1099+
.extend(section_map.clone());
1100+
}
1101+
1102+
Ok(self.map.clone())
1103+
}
1104+
9661105
///Writes the current configuation to the specified path asynchronously. If a file is not present, it is automatically created for you, if a file already
9671106
///exists, it is truncated and the configuration is written to it.
9681107
///

tests/test.rs

+47
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,32 @@ fn non_cs() -> Result<(), Box<dyn Error>> {
100100
mut_map.clear();
101101
config2.clear();
102102
assert_eq!(config.get_map_ref(), config2.get_map_ref());
103+
104+
config.load("tests/test.ini")?;
105+
config.read_and_append("defaultvalues=somenewvalue".to_owned())?;
106+
assert_eq!(
107+
config.get("default", "defaultvalues").unwrap(),
108+
"somenewvalue"
109+
);
110+
assert_eq!(
111+
config.get("topsecret", "KFC").unwrap(),
112+
"the secret herb is orega-"
113+
);
114+
115+
let mut config3 = config.clone();
116+
let mut_map = config3.get_mut_map();
117+
mut_map.clear();
118+
config3.load("tests/test.ini")?;
119+
config3.load_and_append("tests/test_more.ini")?;
120+
assert_eq!(
121+
config3.get("default", "defaultvalues").unwrap(),
122+
"overwritten"
123+
);
124+
assert_eq!(config3.get("topsecret", "KFC").unwrap(), "redacted");
125+
// spacing -> indented exists in tests/test.ini, but not tests/test_more.ini
126+
assert_eq!(config3.get("spacing", "indented").unwrap(), "indented");
127+
assert_eq!(config3.getbool("values", "Bool")?.unwrap(), false);
128+
103129
Ok(())
104130
}
105131

@@ -284,6 +310,27 @@ fn async_load_write() -> Result<(), Box<dyn Error>> {
284310
Ok(())
285311
}
286312

313+
#[test]
314+
#[cfg(feature = "async-std")]
315+
fn async_load_and_append() -> Result<(), Box<dyn Error>> {
316+
let mut sync_content = Ini::new();
317+
sync_content.load("tests/test.ini")?;
318+
sync_content.load_and_append("tests/test_more.ini")?;
319+
320+
let async_content = async_std::task::block_on::<_, Result<_, String>>(async {
321+
let mut async_content = Ini::new();
322+
async_content.load_async("tests/test.ini").await?;
323+
async_content
324+
.load_and_append_async("tests/test_more.ini")
325+
.await?;
326+
Ok(async_content)
327+
})?;
328+
329+
assert_eq!(sync_content, async_content);
330+
331+
Ok(())
332+
}
333+
287334
#[test]
288335
#[cfg(feature = "indexmap")]
289336
fn multiline_off() -> Result<(), Box<dyn Error>> {

tests/test_more.ini

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
defaultvalues=overwritten
2+
3+
[topsecret]
4+
KFC = redacted
5+
6+
[values]
7+
Bool = False
8+

0 commit comments

Comments
 (0)