Upstream QEDK/configparser-rs state of the touched files before c14b35b3b5
This commit is contained in:
1 parent
71e7f66418
commit
98b4971587
2 files changed
+195
-260
No files matched your search
+133
-258
@@ -11,6 +11,7 @@ use std::collections::HashMap as Map;
|
|||||||
#[cfg(feature = "tokio")]
|
#[cfg(feature = "tokio")]
|
||||||
use tokio::fs as async_fs;
|
use tokio::fs as async_fs;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::AsRef;
|
use std::convert::AsRef;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
@@ -24,7 +25,7 @@ use std::path::Path;
|
|||||||
///
|
///
|
||||||
///let mut config = Ini::new();
|
///let mut config = Ini::new();
|
||||||
///```
|
///```
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct Ini {
|
pub struct Ini {
|
||||||
map: Map<String, Map<String, Option<String>>>,
|
map: Map<String, Map<String, Option<String>>>,
|
||||||
@@ -39,6 +40,12 @@ pub struct Ini {
|
|||||||
cascade_defaults: bool,
|
cascade_defaults: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Ini {
|
||||||
|
fn default() -> Self {
|
||||||
|
Ini::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "serde", not(feature = "indexmap")))]
|
#[cfg(all(feature = "serde", not(feature = "indexmap")))]
|
||||||
impl Serialize for Ini {
|
impl Serialize for Ini {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
@@ -197,25 +204,20 @@ impl Default for IniDefault {
|
|||||||
inline_comment_symbols: None,
|
inline_comment_symbols: None,
|
||||||
delimiters: vec!['=', ':'],
|
delimiters: vec!['=', ':'],
|
||||||
multiline: false,
|
multiline: false,
|
||||||
boolean_values: [
|
boolean_values: HashMap::from([
|
||||||
(
|
(
|
||||||
true,
|
true,
|
||||||
["true", "yes", "t", "y", "on", "1"]
|
["true", "yes", "t", "y", "on", "1"]
|
||||||
.iter()
|
.map(String::from)
|
||||||
.map(|&s| s.to_owned())
|
.into(),
|
||||||
.collect(),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
false,
|
false,
|
||||||
["false", "no", "f", "n", "off", "0"]
|
["false", "no", "f", "n", "off", "0"]
|
||||||
.iter()
|
.map(String::from)
|
||||||
.map(|&s| s.to_owned())
|
.into(),
|
||||||
.collect(),
|
|
||||||
),
|
),
|
||||||
]
|
]),
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.collect(),
|
|
||||||
case_sensitive: false,
|
case_sensitive: false,
|
||||||
enable_inline_comments: true, // retain compatibility with previous versions
|
enable_inline_comments: true, // retain compatibility with previous versions
|
||||||
cascade_defaults: false, // retain backwards compatibility
|
cascade_defaults: false, // retain backwards compatibility
|
||||||
@@ -318,6 +320,10 @@ const LINE_ENDING: &str = "\r\n";
|
|||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
const LINE_ENDING: &str = "\n";
|
const LINE_ENDING: &str = "\n";
|
||||||
|
|
||||||
|
fn read_error(path: &Path, why: impl std::fmt::Display) -> String {
|
||||||
|
format!("couldn't read {}: {}", path.display(), why)
|
||||||
|
}
|
||||||
|
|
||||||
impl Ini {
|
impl Ini {
|
||||||
///Creates a new `Map` of `Map<String, Map<String, Option<String>>>` type for the struct.
|
///Creates a new `Map` of `Map<String, Map<String, Option<String>>>` type for the struct.
|
||||||
///All values in the Map are stored in `String` type.
|
///All values in the Map are stored in `String` type.
|
||||||
@@ -428,6 +434,9 @@ impl Ini {
|
|||||||
self.delimiters = defaults.delimiters;
|
self.delimiters = defaults.delimiters;
|
||||||
self.boolean_values = defaults.boolean_values;
|
self.boolean_values = defaults.boolean_values;
|
||||||
self.case_sensitive = defaults.case_sensitive;
|
self.case_sensitive = defaults.case_sensitive;
|
||||||
|
self.multiline = defaults.multiline;
|
||||||
|
self.enable_inline_comments = defaults.enable_inline_comments;
|
||||||
|
self.cascade_defaults = defaults.cascade_defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
///Sets the default section header to the defined string (the default is `default`).
|
///Sets the default section header to the defined string (the default is `default`).
|
||||||
@@ -476,7 +485,8 @@ impl Ini {
|
|||||||
self.inline_comment_symbols = symlist.map(|val| val.to_vec());
|
self.inline_comment_symbols = symlist.map(|val| val.to_vec());
|
||||||
}
|
}
|
||||||
|
|
||||||
///Sets multiline string support.
|
///Sets multiline string support. A line continues the previous key's value when it is
|
||||||
|
///indented deeper than that key's line; a section header ends the value.
|
||||||
///It must be set before `load()` or `read()` is called in order to take effect.
|
///It must be set before `load()` or `read()` is called in order to take effect.
|
||||||
///## Example
|
///## Example
|
||||||
///```rust
|
///```rust
|
||||||
@@ -541,28 +551,21 @@ impl Ini {
|
|||||||
&mut self,
|
&mut self,
|
||||||
path: T,
|
path: T,
|
||||||
) -> Result<Map<String, Map<String, Option<String>>>, String> {
|
) -> Result<Map<String, Map<String, Option<String>>>, String> {
|
||||||
self.map = match self.parse(match fs::read_to_string(&path) {
|
self.map = self.parse_file(path.as_ref())?;
|
||||||
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,
|
|
||||||
};
|
|
||||||
Ok(self.map.clone())
|
Ok(self.map.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_file(&self, path: &Path) -> Result<Map<String, Map<String, Option<String>>>, String> {
|
||||||
|
let input = fs::read_to_string(path).map_err(|why| read_error(path, why))?;
|
||||||
|
self.parse(input).map_err(|why| read_error(path, why))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append(&mut self, loaded: Map<String, Map<String, Option<String>>>) {
|
||||||
|
for (section, section_map) in loaded {
|
||||||
|
self.map.entry(section).or_default().extend(section_map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Loads configuration data from any stream implementing `std::io::Read`, parses it, and applies it to the internal map.
|
/// Loads configuration data from any stream implementing `std::io::Read`, parses it, and applies it to the internal map.
|
||||||
///
|
///
|
||||||
/// This function reads the entire stream into a string, parses it as an INI file, and updates the internal map.
|
/// This function reads the entire stream into a string, parses it as an INI file, and updates the internal map.
|
||||||
@@ -623,30 +626,8 @@ impl Ini {
|
|||||||
&mut self,
|
&mut self,
|
||||||
path: T,
|
path: T,
|
||||||
) -> Result<Map<String, Map<String, Option<String>>>, String> {
|
) -> Result<Map<String, Map<String, Option<String>>>, String> {
|
||||||
let loaded = match self.parse(match fs::read_to_string(&path) {
|
let loaded = self.parse_file(path.as_ref())?;
|
||||||
Err(why) => {
|
self.append(loaded);
|
||||||
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 {
|
|
||||||
self.map.entry(section).or_default().extend(section_map);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self.map.clone())
|
Ok(self.map.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -708,11 +689,7 @@ impl Ini {
|
|||||||
input: String,
|
input: String,
|
||||||
) -> Result<Map<String, Map<String, Option<String>>>, String> {
|
) -> Result<Map<String, Map<String, Option<String>>>, String> {
|
||||||
let loaded = self.parse(input)?;
|
let loaded = self.parse(input)?;
|
||||||
|
self.append(loaded);
|
||||||
for (section, section_map) in loaded {
|
|
||||||
self.map.entry(section).or_default().extend(section_map);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self.map.clone())
|
Ok(self.map.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -895,42 +872,25 @@ impl Ini {
|
|||||||
.unwrap_or_else(|| self.comment_symbols.as_ref());
|
.unwrap_or_else(|| self.comment_symbols.as_ref());
|
||||||
let mut map: Map<String, Map<String, Option<String>>> = Map::new();
|
let mut map: Map<String, Map<String, Option<String>>> = Map::new();
|
||||||
let mut section = self.default_section.clone();
|
let mut section = self.default_section.clone();
|
||||||
// Tracks whether `section` is already present in `map`. Section headers
|
// only the default section is created lazily
|
||||||
// always insert their section, so the only section that can still be
|
|
||||||
// missing is the (lazily created) default section.
|
|
||||||
let mut section_exists = false;
|
let mut section_exists = false;
|
||||||
|
// a line continues `current_key` only if indented deeper than the line that set it
|
||||||
let mut current_key: Option<String> = None;
|
let mut current_key: Option<String> = None;
|
||||||
|
let mut current_indent = 0usize;
|
||||||
let caser = |val: &str| {
|
|
||||||
if self.case_sensitive {
|
|
||||||
val.to_owned()
|
|
||||||
} else {
|
|
||||||
val.to_lowercase()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Track blank lines to preserve them in multiline values.
|
|
||||||
let mut blank_lines = 0usize;
|
let mut blank_lines = 0usize;
|
||||||
|
|
||||||
for (num, raw_line) in input.lines().enumerate() {
|
for (num, raw_line) in (1..).zip(input.lines()) {
|
||||||
let line = raw_line.trim();
|
let line = raw_line.trim();
|
||||||
|
|
||||||
// If the line is _just_ a comment, skip it entirely. Only the first
|
|
||||||
// character can make this a full-line comment, so there's no need to
|
|
||||||
// scan the whole line.
|
|
||||||
if line.starts_with(|c: char| self.comment_symbols.contains(&c)) {
|
if line.starts_with(|c: char| self.comment_symbols.contains(&c)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip empty lines, but keep track of them for multiline values.
|
|
||||||
if line.is_empty() {
|
if line.is_empty() {
|
||||||
blank_lines += 1;
|
blank_lines += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strip a trailing inline comment if enabled. `line` is already
|
|
||||||
// trimmed on both ends, so only the comment-stripped slice needs to
|
|
||||||
// be re-trimmed.
|
|
||||||
let trimmed = if self.enable_inline_comments {
|
let trimmed = if self.enable_inline_comments {
|
||||||
match line.find(|c: char| inline_comment_symbols.contains(&c)) {
|
match line.find(|c: char| inline_comment_symbols.contains(&c)) {
|
||||||
Some(idx) => line[..idx].trim(),
|
Some(idx) => line[..idx].trim(),
|
||||||
@@ -941,103 +901,59 @@ impl Ini {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if trimmed.starts_with('[') {
|
if trimmed.starts_with('[') {
|
||||||
match trimmed.rfind(']') {
|
let end = trimmed.rfind(']').ok_or_else(|| {
|
||||||
Some(end) => {
|
format!(
|
||||||
section = caser(trimmed[1..end].trim());
|
"line {}: Found opening bracket for section name but no closing bracket",
|
||||||
|
num
|
||||||
map.entry(section.clone()).or_default();
|
)
|
||||||
section_exists = true;
|
})?;
|
||||||
|
section = self.casefold(trimmed[1..end].trim()).into_owned();
|
||||||
continue;
|
map.entry(section.clone()).or_default();
|
||||||
}
|
section_exists = true;
|
||||||
None => {
|
current_key = None;
|
||||||
return Err(format!(
|
blank_lines = 0;
|
||||||
"line {}: Found opening bracket for section name but no closing bracket",
|
continue;
|
||||||
num
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the current section exists, then take a mutable handle to
|
|
||||||
// it. Using `get_mut` on the common path avoids cloning the section
|
|
||||||
// name on every single line.
|
|
||||||
if !section_exists {
|
if !section_exists {
|
||||||
map.entry(section.clone()).or_default();
|
map.entry(section.clone()).or_default();
|
||||||
section_exists = true;
|
section_exists = true;
|
||||||
}
|
}
|
||||||
let valmap = map.get_mut(§ion).unwrap();
|
let valmap = map.get_mut(§ion).unwrap();
|
||||||
|
|
||||||
if raw_line.starts_with(char::is_whitespace) && self.multiline {
|
let indent = raw_line.len() - raw_line.trim_start().len();
|
||||||
let key = match current_key.as_ref() {
|
let continued = current_key
|
||||||
Some(x) => x,
|
.as_deref()
|
||||||
None => {
|
.filter(|_| self.multiline && indent > current_indent);
|
||||||
return Err(format!(
|
|
||||||
"line {}: Started with indentation but there is no current entry",
|
|
||||||
num,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
if let Some(key) = continued {
|
||||||
let val = valmap
|
let val = valmap
|
||||||
.entry(key.clone())
|
.get_mut(key)
|
||||||
.or_insert_with(|| Some(String::new()));
|
.expect("multiline key missing from its section");
|
||||||
|
let s = val.get_or_insert_with(String::new);
|
||||||
match val {
|
for _ in 0..=blank_lines {
|
||||||
Some(s) => {
|
s.push_str(LINE_ENDING);
|
||||||
for _ in 0..blank_lines {
|
|
||||||
s.push_str(LINE_ENDING);
|
|
||||||
}
|
|
||||||
s.push_str(LINE_ENDING);
|
|
||||||
s.push_str(trimmed);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let mut s = String::with_capacity(
|
|
||||||
(blank_lines + 1) * LINE_ENDING.len() + trimmed.len(),
|
|
||||||
);
|
|
||||||
for _ in 0..blank_lines {
|
|
||||||
s.push_str(LINE_ENDING);
|
|
||||||
}
|
|
||||||
s.push_str(LINE_ENDING);
|
|
||||||
s.push_str(trimmed);
|
|
||||||
*val = Some(s);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
s.push_str(trimmed);
|
||||||
} else {
|
} else {
|
||||||
match trimmed.find(&self.delimiters[..]) {
|
let (key, value) = match trimmed.find(&self.delimiters[..]) {
|
||||||
Some(delimiter) => {
|
Some(idx) => {
|
||||||
let key = caser(trimmed[..delimiter].trim());
|
let key = trimmed[..idx].trim();
|
||||||
|
|
||||||
if key.is_empty() {
|
if key.is_empty() {
|
||||||
return Err(format!("line {}:{}: Key cannot be empty", num, delimiter));
|
return Err(format!("line {}:{}: Key cannot be empty", num, idx));
|
||||||
} else {
|
|
||||||
// `current_key` is only read when stitching together
|
|
||||||
// multiline values, so only clone the key then.
|
|
||||||
if self.multiline {
|
|
||||||
current_key = Some(key.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
// `delimiter` is a byte offset, and the delimiter
|
|
||||||
// itself may be a multi-byte character, so advance
|
|
||||||
// by its UTF-8 length rather than assuming one byte.
|
|
||||||
let delimiter_len = trimmed[delimiter..]
|
|
||||||
.chars()
|
|
||||||
.next()
|
|
||||||
.map_or(1, char::len_utf8);
|
|
||||||
let value = trimmed[delimiter + delimiter_len..].trim().to_owned();
|
|
||||||
|
|
||||||
valmap.insert(key, Some(value));
|
|
||||||
}
|
}
|
||||||
|
// delimiter may be multi-byte
|
||||||
|
let delimiter_len = trimmed[idx..].chars().next().map_or(1, char::len_utf8);
|
||||||
|
(key, Some(trimmed[idx + delimiter_len..].trim().to_owned()))
|
||||||
}
|
}
|
||||||
None => {
|
None => (trimmed, None),
|
||||||
let key = caser(trimmed);
|
};
|
||||||
if self.multiline {
|
let key = self.casefold(key).into_owned();
|
||||||
current_key = Some(key.clone());
|
if self.multiline {
|
||||||
}
|
current_key = Some(key.clone());
|
||||||
|
current_indent = indent;
|
||||||
valmap.insert(key, None);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
valmap.insert(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
blank_lines = 0;
|
blank_lines = 0;
|
||||||
@@ -1046,15 +962,19 @@ impl Ini {
|
|||||||
Ok(map)
|
Ok(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
///Private function that cases things automatically depending on the set variable.
|
///Lowercases `val` unless case-sensitive. Borrows already-lowercase ASCII input.
|
||||||
fn autocase(&self, section: &str, key: &str) -> (String, String) {
|
fn casefold<'a>(&self, val: &'a str) -> Cow<'a, str> {
|
||||||
if self.case_sensitive {
|
if self.case_sensitive || val.bytes().all(|b| b.is_ascii() && !b.is_ascii_uppercase()) {
|
||||||
(section.to_owned(), key.to_owned())
|
Cow::Borrowed(val)
|
||||||
} else {
|
} else {
|
||||||
(section.to_lowercase(), key.to_lowercase())
|
Cow::Owned(val.to_lowercase())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn autocase<'a>(&self, section: &'a str, key: &'a str) -> (Cow<'a, str>, Cow<'a, str>) {
|
||||||
|
(self.casefold(section), self.casefold(key))
|
||||||
|
}
|
||||||
|
|
||||||
///Returns a clone of the stored value from the key stored in the defined section.
|
///Returns a clone of the stored value from the key stored in the defined section.
|
||||||
///Unlike accessing the map directly, `get()` can process your input to make case-insensitive access *if* the
|
///Unlike accessing the map directly, `get()` can process your input to make case-insensitive access *if* the
|
||||||
///default constructor is used.
|
///default constructor is used.
|
||||||
@@ -1071,16 +991,14 @@ impl Ini {
|
|||||||
///Returns `Some(value)` of type `String` if value is found or else returns `None`.
|
///Returns `Some(value)` of type `String` if value is found or else returns `None`.
|
||||||
pub fn get(&self, section: &str, key: &str) -> Option<String> {
|
pub fn get(&self, section: &str, key: &str) -> Option<String> {
|
||||||
let (section, key) = self.autocase(section, key);
|
let (section, key) = self.autocase(section, key);
|
||||||
let val = match self.map.get(§ion) {
|
let val = self
|
||||||
Some(secmap) => match secmap.get(&key) {
|
.map
|
||||||
Some(val) => val.clone(),
|
.get(&*section)
|
||||||
None => None,
|
.and_then(|secmap| secmap.get(&*key))
|
||||||
},
|
.and_then(Clone::clone);
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if val.is_none() && self.cascade_defaults {
|
if val.is_none() && self.cascade_defaults {
|
||||||
return self.map.get(&self.default_section)?.get(&key)?.clone();
|
return self.map.get(&self.default_section)?.get(&*key)?.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
val
|
val
|
||||||
@@ -1101,11 +1019,10 @@ impl Ini {
|
|||||||
///If the parsing fails, it returns an `Err(string)`.
|
///If the parsing fails, it returns an `Err(string)`.
|
||||||
pub fn getbool(&self, section: &str, key: &str) -> Result<Option<bool>, String> {
|
pub fn getbool(&self, section: &str, key: &str) -> Result<Option<bool>, String> {
|
||||||
let (section, key) = self.autocase(section, key);
|
let (section, key) = self.autocase(section, key);
|
||||||
match self.map.get(§ion).and_then(|secmap| secmap.get(&key)) {
|
match self.map.get(&*section).and_then(|secmap| secmap.get(&*key)) {
|
||||||
Some(Some(inner)) => match inner.to_lowercase().parse::<bool>() {
|
Some(Some(inner)) if inner.eq_ignore_ascii_case("true") => Ok(Some(true)),
|
||||||
Err(why) => Err(why.to_string()),
|
Some(Some(inner)) if inner.eq_ignore_ascii_case("false") => Ok(Some(false)),
|
||||||
Ok(boolean) => Ok(Some(boolean)),
|
Some(Some(_)) => Err("provided string was not `true` or `false`".to_owned()),
|
||||||
},
|
|
||||||
_ => {
|
_ => {
|
||||||
if self.cascade_defaults && section != self.default_section {
|
if self.cascade_defaults && section != self.default_section {
|
||||||
return self.getbool(&self.default_section, &key);
|
return self.getbool(&self.default_section, &key);
|
||||||
@@ -1131,24 +1048,17 @@ impl Ini {
|
|||||||
///If the parsing fails, it returns an `Err(string)`.
|
///If the parsing fails, it returns an `Err(string)`.
|
||||||
pub fn getboolcoerce(&self, section: &str, key: &str) -> Result<Option<bool>, String> {
|
pub fn getboolcoerce(&self, section: &str, key: &str) -> Result<Option<bool>, String> {
|
||||||
let (section, key) = self.autocase(section, key);
|
let (section, key) = self.autocase(section, key);
|
||||||
match self.map.get(§ion).and_then(|secmap| secmap.get(&key)) {
|
match self.map.get(&*section).and_then(|secmap| secmap.get(&*key)) {
|
||||||
Some(Some(inner)) => {
|
Some(Some(inner)) => {
|
||||||
let boolval = &inner.to_lowercase()[..];
|
let boolval = inner.to_lowercase();
|
||||||
if self
|
let matches = |truth: bool| {
|
||||||
.boolean_values
|
self.boolean_values
|
||||||
.get(&true)
|
.get(&truth)
|
||||||
.unwrap()
|
.is_some_and(|values| values.contains(&boolval))
|
||||||
.iter()
|
};
|
||||||
.any(|elem| elem == boolval)
|
if matches(true) {
|
||||||
{
|
|
||||||
Ok(Some(true))
|
Ok(Some(true))
|
||||||
} else if self
|
} else if matches(false) {
|
||||||
.boolean_values
|
|
||||||
.get(&false)
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
|
||||||
.any(|elem| elem == boolval)
|
|
||||||
{
|
|
||||||
Ok(Some(false))
|
Ok(Some(false))
|
||||||
} else {
|
} else {
|
||||||
Err(format!(
|
Err(format!(
|
||||||
@@ -1180,7 +1090,7 @@ impl Ini {
|
|||||||
///If the parsing fails, it returns an `Err(string)`.
|
///If the parsing fails, it returns an `Err(string)`.
|
||||||
pub fn getint(&self, section: &str, key: &str) -> Result<Option<i64>, String> {
|
pub fn getint(&self, section: &str, key: &str) -> Result<Option<i64>, String> {
|
||||||
let (section, key) = self.autocase(section, key);
|
let (section, key) = self.autocase(section, key);
|
||||||
match self.map.get(§ion).and_then(|secmap| secmap.get(&key)) {
|
match self.map.get(&*section).and_then(|secmap| secmap.get(&*key)) {
|
||||||
Some(Some(inner)) => match inner.parse::<i64>() {
|
Some(Some(inner)) => match inner.parse::<i64>() {
|
||||||
Err(why) => Err(why.to_string()),
|
Err(why) => Err(why.to_string()),
|
||||||
Ok(int) => Ok(Some(int)),
|
Ok(int) => Ok(Some(int)),
|
||||||
@@ -1208,7 +1118,7 @@ impl Ini {
|
|||||||
///If the parsing fails, it returns an `Err(string)`.
|
///If the parsing fails, it returns an `Err(string)`.
|
||||||
pub fn getuint(&self, section: &str, key: &str) -> Result<Option<u64>, String> {
|
pub fn getuint(&self, section: &str, key: &str) -> Result<Option<u64>, String> {
|
||||||
let (section, key) = self.autocase(section, key);
|
let (section, key) = self.autocase(section, key);
|
||||||
match self.map.get(§ion).and_then(|secmap| secmap.get(&key)) {
|
match self.map.get(&*section).and_then(|secmap| secmap.get(&*key)) {
|
||||||
Some(Some(inner)) => match inner.parse::<u64>() {
|
Some(Some(inner)) => match inner.parse::<u64>() {
|
||||||
Err(why) => Err(why.to_string()),
|
Err(why) => Err(why.to_string()),
|
||||||
Ok(uint) => Ok(Some(uint)),
|
Ok(uint) => Ok(Some(uint)),
|
||||||
@@ -1236,7 +1146,7 @@ impl Ini {
|
|||||||
///If the parsing fails, it returns an `Err(string)`.
|
///If the parsing fails, it returns an `Err(string)`.
|
||||||
pub fn getfloat(&self, section: &str, key: &str) -> Result<Option<f64>, String> {
|
pub fn getfloat(&self, section: &str, key: &str) -> Result<Option<f64>, String> {
|
||||||
let (section, key) = self.autocase(section, key);
|
let (section, key) = self.autocase(section, key);
|
||||||
match self.map.get(§ion).and_then(|secmap| secmap.get(&key)) {
|
match self.map.get(&*section).and_then(|secmap| secmap.get(&*key)) {
|
||||||
Some(Some(inner)) => match inner.parse::<f64>() {
|
Some(Some(inner)) => match inner.parse::<f64>() {
|
||||||
Err(why) => Err(why.to_string()),
|
Err(why) => Err(why.to_string()),
|
||||||
Ok(float) => Ok(Some(float)),
|
Ok(float) => Ok(Some(float)),
|
||||||
@@ -1332,12 +1242,12 @@ impl Ini {
|
|||||||
value: Option<String>,
|
value: Option<String>,
|
||||||
) -> Option<Option<String>> {
|
) -> Option<Option<String>> {
|
||||||
let (section, key) = self.autocase(section, key);
|
let (section, key) = self.autocase(section, key);
|
||||||
match self.map.get_mut(§ion) {
|
match self.map.get_mut(&*section) {
|
||||||
Some(secmap) => secmap.insert(key, value),
|
Some(secmap) => secmap.insert(key.into_owned(), value),
|
||||||
None => {
|
None => {
|
||||||
let mut valmap: Map<String, Option<String>> = Map::new();
|
let mut valmap: Map<String, Option<String>> = Map::new();
|
||||||
valmap.insert(key, value);
|
valmap.insert(key.into_owned(), value);
|
||||||
self.map.insert(section, valmap);
|
self.map.insert(section.into_owned(), valmap);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1365,8 +1275,7 @@ impl Ini {
|
|||||||
key: &str,
|
key: &str,
|
||||||
value: Option<&str>,
|
value: Option<&str>,
|
||||||
) -> Option<Option<String>> {
|
) -> Option<Option<String>> {
|
||||||
let (section, key) = self.autocase(section, key);
|
self.set(section, key, value.map(String::from))
|
||||||
self.set(§ion, &key, value.map(String::from))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///Clears the map, removing all sections and properties from the hashmap. It keeps the allocated memory for reuse.
|
///Clears the map, removing all sections and properties from the hashmap. It keeps the allocated memory for reuse.
|
||||||
@@ -1400,18 +1309,14 @@ impl Ini {
|
|||||||
///```
|
///```
|
||||||
///Returns `Some(section_map)` if the section exists or else, `None`.
|
///Returns `Some(section_map)` if the section exists or else, `None`.
|
||||||
pub fn remove_section(&mut self, section: &str) -> Option<Map<String, Option<String>>> {
|
pub fn remove_section(&mut self, section: &str) -> Option<Map<String, Option<String>>> {
|
||||||
let section = if self.case_sensitive {
|
let section = self.casefold(section);
|
||||||
section.to_owned()
|
|
||||||
} else {
|
|
||||||
section.to_lowercase()
|
|
||||||
};
|
|
||||||
#[cfg(not(feature = "indexmap"))]
|
#[cfg(not(feature = "indexmap"))]
|
||||||
{
|
{
|
||||||
self.map.remove(§ion)
|
self.map.remove(&*section)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "indexmap")]
|
#[cfg(feature = "indexmap")]
|
||||||
{
|
{
|
||||||
self.map.swap_remove(§ion)
|
self.map.swap_remove(&*section)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1434,11 +1339,11 @@ impl Ini {
|
|||||||
let (section, key) = self.autocase(section, key);
|
let (section, key) = self.autocase(section, key);
|
||||||
#[cfg(not(feature = "indexmap"))]
|
#[cfg(not(feature = "indexmap"))]
|
||||||
{
|
{
|
||||||
self.map.get_mut(§ion)?.remove(&key)
|
self.map.get_mut(&*section)?.remove(&*key)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "indexmap")]
|
#[cfg(feature = "indexmap")]
|
||||||
{
|
{
|
||||||
self.map.get_mut(§ion)?.swap_remove(&key)
|
self.map.get_mut(&*section)?.swap_remove(&*key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1456,28 +1361,20 @@ impl Ini {
|
|||||||
&mut self,
|
&mut self,
|
||||||
path: T,
|
path: T,
|
||||||
) -> Result<Map<String, Map<String, Option<String>>>, String> {
|
) -> Result<Map<String, Map<String, Option<String>>>, String> {
|
||||||
self.map = match self.parse(match async_fs::read_to_string(&path).await {
|
self.map = self.parse_file_async(path.as_ref()).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,
|
|
||||||
};
|
|
||||||
Ok(self.map.clone())
|
Ok(self.map.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn parse_file_async(
|
||||||
|
&self,
|
||||||
|
path: &Path,
|
||||||
|
) -> Result<Map<String, Map<String, Option<String>>>, String> {
|
||||||
|
let input = async_fs::read_to_string(path)
|
||||||
|
.await
|
||||||
|
.map_err(|why| read_error(path, why))?;
|
||||||
|
self.parse(input).map_err(|why| read_error(path, why))
|
||||||
|
}
|
||||||
|
|
||||||
///Loads a file from a defined path, parses it and applies it to the existing hashmap in our struct.
|
///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_and_append_async()` applies the new values on top
|
///While `load_async()` will clear the existing `Map`, `load_and_append_async()` applies the new values on top
|
||||||
///of the existing hashmap, preserving previous values.
|
///of the existing hashmap, preserving previous values.
|
||||||
@@ -1490,30 +1387,8 @@ impl Ini {
|
|||||||
&mut self,
|
&mut self,
|
||||||
path: T,
|
path: T,
|
||||||
) -> Result<Map<String, Map<String, Option<String>>>, String> {
|
) -> Result<Map<String, Map<String, Option<String>>>, String> {
|
||||||
let loaded = match self.parse(match async_fs::read_to_string(&path).await {
|
let loaded = self.parse_file_async(path.as_ref()).await?;
|
||||||
Err(why) => {
|
self.append(loaded);
|
||||||
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 {
|
|
||||||
self.map.entry(section).or_default().extend(section_map);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self.map.clone())
|
Ok(self.map.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+62
-2
@@ -978,8 +978,7 @@ fn serde_case_sensitive_roundtrip() -> Result<(), Box<dyn Error>> {
|
|||||||
fn multibyte_delimiter_does_not_panic() -> Result<(), Box<dyn Error>> {
|
fn multibyte_delimiter_does_not_panic() -> Result<(), Box<dyn Error>> {
|
||||||
use configparser::ini::IniDefault;
|
use configparser::ini::IniDefault;
|
||||||
|
|
||||||
// A multi-byte delimiter must not cause a panic when splitting the
|
// A multi-byte delimiter must not split on a non-char boundary.
|
||||||
// key from the value on a non-char-boundary byte offset.
|
|
||||||
let mut defaults = IniDefault::default();
|
let mut defaults = IniDefault::default();
|
||||||
defaults.delimiters = vec!['\u{00a7}']; // section sign, 2 bytes in UTF-8
|
defaults.delimiters = vec!['\u{00a7}']; // section sign, 2 bytes in UTF-8
|
||||||
|
|
||||||
@@ -989,3 +988,64 @@ fn multibyte_delimiter_does_not_panic() -> Result<(), Box<dyn Error>> {
|
|||||||
assert_eq!(config.get("s", "key"), Some(String::from("value")));
|
assert_eq!(config.get("s", "key"), Some(String::from("value")));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_ini_parses_like_new() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut config = Ini::default();
|
||||||
|
config.read(String::from("[S]\nKey = value ; note"))?;
|
||||||
|
assert_eq!(config.get("s", "key"), Some(String::from("value")));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_defaults_applies_every_field() {
|
||||||
|
use configparser::ini::IniDefault;
|
||||||
|
|
||||||
|
let mut defaults = IniDefault::default();
|
||||||
|
defaults.multiline = true;
|
||||||
|
defaults.enable_inline_comments = false;
|
||||||
|
defaults.cascade_defaults = true;
|
||||||
|
|
||||||
|
let mut config = Ini::new();
|
||||||
|
config.load_defaults(defaults.clone());
|
||||||
|
assert_eq!(config.defaults(), defaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiline_continuation_rules() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut config = Ini::new();
|
||||||
|
config.set_multiline(true);
|
||||||
|
config.read(String::from(
|
||||||
|
"[a]
|
||||||
|
key = line one
|
||||||
|
line two
|
||||||
|
next = value
|
||||||
|
|
||||||
|
[b]
|
||||||
|
other = value",
|
||||||
|
))?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
config.get("a", "key"),
|
||||||
|
Some(String::from("line one\nline two"))
|
||||||
|
);
|
||||||
|
assert_eq!(config.get("a", "next"), Some(String::from("value")));
|
||||||
|
assert_eq!(config.get("b", "other"), Some(String::from("value")));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bool_getters_reject_unknown_values() -> Result<(), Box<dyn Error>> {
|
||||||
|
use configparser::ini::IniDefault;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
let mut defaults = IniDefault::default();
|
||||||
|
defaults.boolean_values = HashMap::from([(true, vec![String::from("aye")])]);
|
||||||
|
|
||||||
|
let mut config = Ini::new_from_defaults(defaults);
|
||||||
|
config.read(String::from("[s]\nyes = AYE\nno = nay"))?;
|
||||||
|
assert_eq!(config.getboolcoerce("s", "yes")?, Some(true));
|
||||||
|
assert!(config.getboolcoerce("s", "no").is_err());
|
||||||
|
assert!(config.getbool("s", "yes").is_err());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in new issue
Block a user