1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
use std::{collections::HashMap, fmt};
type ConfigSection = HashMap<String, String>;
/// An INI-like config file parser.
/// *
/// ```ini
/// ; This is a comment
/// foo = some value
/// [my section]
/// password = " quotes are optional, and useful if you want to preserve surrounding spaces "
/// ```
pub struct Config {
pub main_section: ConfigSection,
pub sections: HashMap<String, ConfigSection>,
}
// to Serialize back to an INI file. use fmt::Display
impl Config {
pub fn new() -> Self {
Self {
main_section: ConfigSection::new(),
sections: HashMap::new(),
}
}
/// Parse the contents of an INI file.
// static
pub fn parse(text: String) -> Self {
// let config = Config::new();
// let commentPattern = ~/^\s*;/;
// let sectionPattern = ~/^\s*\[\s*([^\]]*)\s*\]/;
// let pair_pattern = ~/^\s*([\w\.\-_]+)\s*=\s*(.*)/;
// let currentSection = config.mainSection;
// for line in ~/\r\n|\r|\n/g.split(text) {
// if (commentPattern.match(line)) {
// // Ignore this line
// } else if (sectionPattern.match(line)) {
// let name = sectionPattern.matched(1);
// if (config.sections.contains_key(&name)) {
// // Handle duplicate sections, should this be allowed?
// currentSection = config.sections.get(name);
// } else {
// currentSection = ConfigSection::new();
// config.sections.set(name, currentSection);
// }
// } else if (pair_pattern.match(line)) {
// let key = pair_pattern.matched(1);
// let value = pair_pattern.matched(2);
// let quote = value.chars.nth(0).unwrap();
// if ((quote == '"'.to_digit(10) || quote == '\''.to_digit(10)) &&
// value.chars.last() == quote) {
// // Trim off quotes
// value = value[1..value.len()-1];
// }
// currentSection.set(key, value
// // Unescape certain characters
// .replace("\\n", "\n")
// .replace("\\r", "\r")
// .replace("\\t", "\t")
// .replace("\\'", "\'")
// .replace("\\\"", "\"")
// .replace("\\\\", "\\")
// );
// }
// }
// return config;
unimplemented!()
}
/// Shorthand for sections.get(name).
#[inline]
pub fn section(&self, name: String) -> Option<&ConfigSection> {
self.sections.get(&name)
}
/// Searches for a value with a full path. A path is a section and key name separated by a dot. A
/// path without a dot is assumed to be in the main section.
/// *
/// Eg: get("foo.bar") is the same as section("foo").get("bar");
pub fn get(&self, path: &String) -> Option<&String> {
match path.find(".") {
Some(idx) => {
if let Some(section) = self.sections.get(&path[..idx]) {
// section.get(&path[idx + 1..].to_string())
todo!("should deal with it");
} else {
None
}
}
None => self.main_section.get(path),
}
}
}
impl fmt::Display for Config {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut out = String::new();
for (key, value) in self.main_section.iter() {
out.push_str(format!("{} = \"{}\"\n", key, value).as_str());
}
for name in self.sections.keys() {
out.push_str(format!("[{}]\n", name).as_str());
if let Some(section) = self.sections.get(name) {
for (key, value) in section.iter() {
out.push_str(format!("{} =\"{}\"\n", key, value).as_str());
}
}
}
write!(f, "{}", out)
}
}