Skip to content

Commit

Permalink
feat: added load_more to accumulate multiple configs
Browse files Browse the repository at this point in the history
  • Loading branch information
Swivelgames committed Aug 26, 2023
1 parent 16fa334 commit 4217659
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 0 deletions.
93 changes: 93 additions & 0 deletions src/ini.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,57 @@ impl Ini {
Ok(self.map.clone())
}

///Loads a file from a defined path, parses it and applies it to the existing hashmap in our struct.
///While `load()` will clear the existing `Map`, `load_more()` applies the new values on top of
///the existing hashmap, preserving previous values.
///## Example
///```rust
///use configparser::ini::Ini;
///
///let mut config = Ini::new();
///config.load("tests/test.ini").unwrap();
///config.load_more("tests/sys_cfg.ini").ok(); // we don't have to worry if this doesn't succeed
///config.load_more("tests/user_cfg.ini").ok(); // we don't have to worry if this doesn't succeed
///let map = config.get_map().unwrap();
/////Then, we can use standard hashmap functions like:
///let values = map.get("values").unwrap();
///```
///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`.
///Use `get_mut_map()` if you want a mutable reference.
pub fn load_more<T: AsRef<Path>>(
&mut self,
path: T,
) -> Result<Map<String, Map<String, Option<String>>>, String> {
let loaded = match self.parse(match fs::read_to_string(&path) {
Err(why) => {
return Err(format!(
"couldn't read {}: {}",
&path.as_ref().display(),
why
))
}
Ok(s) => s,
}) {
Err(why) => {
return Err(format!(
"couldn't read {}: {}",
&path.as_ref().display(),
why
))
}
Ok(map) => map,
};

for (section, section_map) in loaded.iter() {
self.map
.entry(section.clone())
.or_insert_with(Map::new)
.extend(section_map.clone());
}

Ok(self.map.clone())
}

///Reads an input string, parses it and puts the hashmap into our struct.
///At one time, it only stores one configuration, so each call to `load()` or `read()` will clear the existing `Map`, if present.
///## Example
Expand Down Expand Up @@ -963,6 +1014,48 @@ impl Ini {
Ok(self.map.clone())
}

///Loads a file from a defined path, parses it and applies it to the existing hashmap in our struct.
///While `load_async()` will clear the existing `Map`, `load_more_async()` applies the new values on top
///of the existing hashmap, preserving previous values.
///
///Usage is similar to `load_more`, but `.await` must be called after along with the usual async rules.
///
///Returns `Ok(map)` with a clone of the stored `Map` if no errors are thrown or else `Err(error_string)`.
///Use `get_mut_map()` if you want a mutable reference.
pub async fn load_more_async<T: AsRef<AsyncPath>>(
&mut self,
path: T,
) -> Result<Map<String, Map<String, Option<String>>>, String> {
let loaded = match self.parse(match async_fs::read_to_string(&path).await {
Err(why) => {
return Err(format!(
"couldn't read {}: {}",
&path.as_ref().display(),
why
))
}
Ok(s) => s,
}) {
Err(why) => {
return Err(format!(
"couldn't read {}: {}",
&path.as_ref().display(),
why
))
}
Ok(map) => map,
};

for (section, section_map) in loaded.iter() {
self.map
.entry(section.clone())
.or_insert_with(Map::new)
.extend(section_map.clone());
}

Ok(self.map.clone())
}

///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
///exists, it is truncated and the configuration is written to it.
///
Expand Down
40 changes: 40 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,25 @@ fn non_cs() -> Result<(), Box<dyn Error>> {
mut_map.clear();
config2.clear();
assert_eq!(config.get_map_ref(), config2.get_map_ref());

let mut config3 = config.clone();
let mut_map = config3.get_mut_map();
mut_map.clear();
config3.load("tests/test.ini")?;
config3.load_more("tests/test_more.ini")?;
config3.load_more("tests/i_dont_exist.ini").ok();
assert_eq!(
config3.get("default", "defaultvalues").unwrap(),
"overwritten"
);
assert_eq!(
config3.get("topsecret", "KFC").unwrap(),
"redacted"
);
// spacing -> indented exists in tests/test.ini, but not tests/test_more.ini
assert_eq!(config3.get("spacing", "indented").unwrap(), "indented");
assert_eq!(config3.getbool("values", "Bool")?.unwrap(), false);

Ok(())
}

Expand Down Expand Up @@ -284,6 +303,27 @@ fn async_load_write() -> Result<(), Box<dyn Error>> {
Ok(())
}

#[test]
#[cfg(feature = "async-std")]
fn async_load_more() -> Result<(), Box<dyn Error>> {
let mut sync_content = Ini::new();
sync_content.load("tests/test.ini")?;
sync_content.load_more("tests/test_more.ini")?;
sync_content.load_more("tests/i_dont_exist.ini").ok();

let async_content = async_std::task::block_on::<_, Result<_, String>>(async {
let mut async_content = Ini::new();
async_content.load_async("tests/test.ini").await?;
async_content.load_more_async("tests/test_more.ini").await?;
async_content.load_more_async("tests/i_dont_exist.ini").await.ok();
Ok(async_content)
})?;

assert_eq!(sync_content, async_content);

Ok(())
}

#[test]
#[cfg(feature = "indexmap")]
fn multiline_off() -> Result<(), Box<dyn Error>> {
Expand Down
8 changes: 8 additions & 0 deletions tests/test_more.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
defaultvalues=overwritten

[topsecret]
KFC = redacted

[values]
Bool = False

0 comments on commit 4217659

Please sign in to comment.