1use std::collections::HashMap;
17use std::io::{self, BufRead, Write};
18use std::path::{Path, PathBuf};
19
20pub struct Config {
22 path: PathBuf,
23 data: HashMap<String, String>,
24}
25
26impl Config {
27 pub fn load(game_dir: &str, mod_id: &str) -> Self {
32 let safe = mod_id.replace([':', '/'], "_");
33 let dir = Path::new(game_dir).join("yog-config");
34 let path = dir.join(format!("{safe}.cfg"));
35 let data = Self::parse(&path).unwrap_or_default();
36 Self { path, data }
37 }
38
39 fn parse(path: &Path) -> io::Result<HashMap<String, String>> {
40 let file = std::fs::File::open(path)?;
41 let mut map = HashMap::new();
42 for line in io::BufReader::new(file).lines() {
43 let line = line?;
44 let t = line.trim();
45 if t.is_empty() || t.starts_with('#') { continue; }
46 if let Some((k, v)) = t.split_once('=') {
47 map.insert(k.trim().to_string(), v.trim().to_string());
48 }
49 }
50 Ok(map)
51 }
52
53 pub fn get(&self, key: &str) -> Option<&str> {
55 self.data.get(key).map(String::as_str)
56 }
57
58 pub fn get_or<'a>(&'a self, key: &str, default: &'a str) -> &'a str {
60 self.get(key).unwrap_or(default)
61 }
62
63 pub fn get_int(&self, key: &str) -> Option<i64> {
65 self.get(key)?.trim().parse().ok()
66 }
67
68 pub fn get_int_or(&self, key: &str, default: i64) -> i64 {
70 self.get_int(key).unwrap_or(default)
71 }
72
73 pub fn get_float(&self, key: &str) -> Option<f64> {
75 self.get(key)?.trim().parse().ok()
76 }
77
78 pub fn get_float_or(&self, key: &str, default: f64) -> f64 {
80 self.get_float(key).unwrap_or(default)
81 }
82
83 pub fn get_bool(&self, key: &str) -> Option<bool> {
87 match self.get(key)?.trim().to_lowercase().as_str() {
88 "true" | "yes" | "1" | "on" => Some(true),
89 "false"| "no" | "0" | "off" => Some(false),
90 _ => None,
91 }
92 }
93
94 pub fn get_bool_or(&self, key: &str, default: bool) -> bool {
96 self.get_bool(key).unwrap_or(default)
97 }
98
99 pub fn set(&mut self, key: impl Into<String>, value: impl ToString) {
101 self.data.insert(key.into(), value.to_string());
102 }
103
104 pub fn remove(&mut self, key: &str) -> Option<String> {
106 self.data.remove(key)
107 }
108
109 pub fn contains(&self, key: &str) -> bool {
111 self.data.contains_key(key)
112 }
113
114 pub fn save(&self) -> io::Result<()> {
119 if let Some(p) = self.path.parent() {
120 std::fs::create_dir_all(p)?;
121 }
122 let mut file = std::fs::File::create(&self.path)?;
123 writeln!(file, "# Yog mod configuration — auto-generated")?;
124 let mut keys: Vec<_> = self.data.keys().collect();
125 keys.sort();
126 for k in keys {
127 writeln!(file, "{} = {}", k, self.data[k])?;
128 }
129 Ok(())
130 }
131
132 pub fn save_defaults(&self) -> io::Result<()> {
134 if !self.path.exists() { self.save() } else { Ok(()) }
135 }
136
137 pub fn len(&self) -> usize { self.data.len() }
139
140 pub fn is_empty(&self) -> bool { self.data.is_empty() }
141
142 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
143 self.data.iter().map(|(k, v)| (k.as_str(), v.as_str()))
144 }
145}