easy_configuration_format/
file.rs1use crate::*;
2use std::{collections::{HashMap, HashSet}, ops::{Deref, DerefMut}};
3
4
5
6
7
8#[derive(Debug, Clone, PartialEq)]
10pub struct File {
11 pub values: HashMap<String, Value>,
13 pub layout: Vec<LayoutEntry>,
15 pub version: usize,
17}
18
19impl Deref for File {
20 type Target = HashMap<String, Value>;
21 fn deref(&self) -> &Self::Target {
22 &self.values
23 }
24}
25
26impl DerefMut for File {
27 fn deref_mut(&mut self) -> &mut Self::Target {
28 &mut self.values
29 }
30}
31
32
33
34impl File {
35
36
37
38 pub fn from_str<T>(contents: impl AsRef<str>, updater_fns: &[fn(&mut HashMap<String, Value>, &mut T)], args: &mut T) -> (Self, DidRunUpdaters, Vec<ParseEntryError>) {
42 let mut layout = vec!();
43 let mut values = HashMap::new();
44 let mut errors = vec!();
45
46 let lines = contents.as_ref().split('\n').collect::<Vec<_>>();
47 let version = get_file_version(lines[0].trim());
48 let mut line_i = 1;
49 loop {
50 let result = parse_line(&lines, &mut line_i, &mut layout, &mut values);
51 if let Err(err) = result {
52 layout.push(LayoutEntry::Comment (lines[line_i].to_string()));
53 errors.push(err);
54 }
55 line_i += 1;
56 if line_i >= lines.len() {break;}
57 }
58
59 let did_run_updaters = if let Some(version) = version {
60 let fns_to_run = &updater_fns[version - 1 ..];
61 for updater_fn in fns_to_run {
62 (updater_fn)(&mut values, args);
63 }
64 !fns_to_run.is_empty()
65 } else {
66 errors.push(ParseEntryError::new(0, "Could not find version, assuming version is latest"));
67 false
68 };
69
70 (
71 Self {
72 values,
73 layout,
74 version: updater_fns.len() + 1,
75 },
76 did_run_updaters,
77 errors,
78 )
79 }
80
81
82
83 pub fn to_str(&self) -> (String, Vec<FormatEntryError>) {
85 let mut output = format!("format {}\n", self.version);
86 if self.layout.is_empty() {return (output, vec!());}
87 let mut errors = vec!();
88 let mut printed_keys = HashSet::new();
89 for entry in &self.layout {
90 match entry {
91 LayoutEntry::Empty => {}
92 LayoutEntry::Comment (comment) => {
93 if comment.contains('\n') {
94 output += "##\n";
95 output += comment;
96 output += "\n##";
97 } else {
98 output.push('#');
99 output += comment;
100 }
101 }
102 LayoutEntry::Key (key) => {
103 output += key;
104 output += ": ";
105 let value = self.get(key);
106 if let Some(value) = value {
107 output += &value.format();
108 } else {
109 errors.push(FormatEntryError::new(key));
110 continue;
111 };
112 printed_keys.insert(key.to_string());
113 }
114 }
115 output.push('\n');
116 }
117 for (key, value) in &self.values {
118 if printed_keys.contains(key) {continue;}
119 output += key;
120 output += ": ";
121 output += &value.format();
122 output.push('\n');
123 }
124 output.pop();
125 (output, errors)
126 }
127
128
129
130 pub fn get_empty(&self, key: impl AsRef<str>) -> Result<(), RetrieveSettingError> {
132 let key = key.as_ref();
133 match self.get(key) {
134 None => Err(RetrieveSettingError::new_missing(key)),
135 Some(Value::Empty) => Ok(()),
136 Some(value) => Err(RetrieveSettingError::new_wrong_singular_type(key.to_string(), "empty", value.type_as_string())),
137 }
138 }
139
140 pub fn get_int(&self, key: impl AsRef<str>) -> Result<i64, RetrieveSettingError> {
142 let key = key.as_ref();
143 match self.get(key) {
144 None => Err(RetrieveSettingError::new_missing(key)),
145 Some(Value::I64 (v)) => Ok(*v),
146 Some(value) => Err(RetrieveSettingError::new_wrong_singular_type(key.to_string(), "Int", value.type_as_string())),
147 }
148 }
149
150 pub fn get_int_mut(&mut self, key: impl AsRef<str>) -> Result<&mut i64, RetrieveSettingError> {
152 let key = key.as_ref();
153 match self.get_mut(key) {
154 None => Err(RetrieveSettingError::new_missing(key)),
155 Some(Value::I64 (v)) => Ok(v),
156 Some(value) => Err(RetrieveSettingError::new_wrong_singular_type(key.to_string(), "Int", value.type_as_string())),
157 }
158 }
159
160 pub fn get_float(&self, key: impl AsRef<str>) -> Result<f64, RetrieveSettingError> {
162 let key = key.as_ref();
163 match self.get(key) {
164 None => Err(RetrieveSettingError::new_missing(key)),
165 Some(Value::F64 (v)) => Ok(*v),
166 Some(value) => Err(RetrieveSettingError::new_wrong_singular_type(key.to_string(), "Float", value.type_as_string())),
167 }
168 }
169
170 pub fn get_float_mut(&mut self, key: impl AsRef<str>) -> Result<&mut f64, RetrieveSettingError> {
172 let key = key.as_ref();
173 match self.get_mut(key) {
174 None => Err(RetrieveSettingError::new_missing(key)),
175 Some(Value::F64 (v)) => Ok(v),
176 Some(value) => Err(RetrieveSettingError::new_wrong_singular_type(key.to_string(), "Float", value.type_as_string())),
177 }
178 }
179
180 pub fn get_number(&self, key: impl AsRef<str>) -> Result<f64, RetrieveSettingError> {
184 let key = key.as_ref();
185 match self.get(key) {
186 None => Err(RetrieveSettingError::new_missing(key)),
187 Some(Value::I64 (v)) => Ok(*v as f64),
188 Some(Value::F64 (v)) => Ok(*v),
189 Some(value) => Err(RetrieveSettingError::new_wrong_multiple_type(key.to_string(), vec!(String::from("Int"), String::from("Float")), value.type_as_string())),
190 }
191 }
192
193 pub fn get_bool(&self, key: impl AsRef<str>) -> Result<bool, RetrieveSettingError> {
195 let key = key.as_ref();
196 match self.get(key) {
197 None => Err(RetrieveSettingError::new_missing(key)),
198 Some(Value::Bool (v)) => Ok(*v),
199 Some(value) => Err(RetrieveSettingError::new_wrong_singular_type(key.to_string(), "Bool", value.type_as_string())),
200 }
201 }
202
203 pub fn get_bool_mut(&mut self, key: impl AsRef<str>) -> Result<&mut bool, RetrieveSettingError> {
205 let key = key.as_ref();
206 match self.get_mut(key) {
207 None => Err(RetrieveSettingError::new_missing(key)),
208 Some(Value::Bool (v)) => Ok(v),
209 Some(value) => Err(RetrieveSettingError::new_wrong_singular_type(key.to_string(), "Bool", value.type_as_string())),
210 }
211 }
212
213 pub fn get_str(&self, key: impl AsRef<str>) -> Result<&str, RetrieveSettingError> {
215 let key = key.as_ref();
216 match self.get(key) {
217 None => Err(RetrieveSettingError::new_missing(key)),
218 Some(Value::String (v)) => Ok(v),
219 Some(value) => Err(RetrieveSettingError::new_wrong_singular_type(key.to_string(), "String", value.type_as_string())),
220 }
221 }
222
223 pub fn get_string_mut(&mut self, key: impl AsRef<str>) -> Result<&mut String, RetrieveSettingError> {
225 let key = key.as_ref();
226 match self.get_mut(key) {
227 None => Err(RetrieveSettingError::new_missing(key)),
228 Some(Value::String (v)) => Ok(v),
229 Some(value) => Err(RetrieveSettingError::new_wrong_singular_type(key.to_string(), "String", value.type_as_string())),
230 }
231 }
232
233
234
235 pub fn add_missing_values(&mut self, defaults: impl IntoIterator<Item = (&str, Value)>) {
237 for (key, value) in defaults {
238 if self.contains_key(key) {continue;}
239 self.insert(key.to_string(), value);
240 }
241 }
242
243
244
245}
246
247
248
249
250
251fn get_file_version(first_line: &str) -> Option<usize> {
252 let format_str = first_line.strip_prefix("format ")?;
253 format_str.parse::<usize>().ok()
254}
255
256
257
258fn parse_line(
259 lines: &[&str],
260 line_i: &mut usize,
261 layout: &mut Vec<LayoutEntry>,
262 values: &mut HashMap<String, Value>,
263) -> Result<(), ParseEntryError> {
264
265 let line_trimmed = lines[*line_i].trim();
266 if line_trimmed.is_empty() {
267 layout.push(LayoutEntry::Empty);
268 return Ok(());
269 }
270
271 if line_trimmed == "##" {
272 layout.push(parse_multiline_comment(lines, line_i)?);
273 return Ok(());
274 }
275 if let Some(comment) = line_trimmed.strip_prefix("#") {
276 layout.push(LayoutEntry::Comment (comment.to_string()));
277 return Ok(());
278 }
279
280 let colon_index = line_trimmed.find(':');
281 let Some(colon_index) = colon_index else {return Err(ParseEntryError::new(*line_i, "No colon was found, either add a colon after the key or mark this as a comment."));};
282 if colon_index == 0 {return Err(ParseEntryError::new(*line_i, "Lines cannot start with a colon."));}
283 let key = line_trimmed[..colon_index].trim_end();
284 if values.contains_key(key) {return Err(ParseEntryError::new(*line_i, format!("Key \"{key}\" is already defined.")));}
285 let value = parse_value(lines, line_i, colon_index)?;
286 layout.push(LayoutEntry::Key (key.to_string()));
287 values.insert(key.to_string(), value);
288
289 Ok(())
290}
291
292
293
294fn parse_multiline_comment(
295 lines: &[&str],
296 line_i: &mut usize,
297) -> Result<LayoutEntry, ParseEntryError> {
298
299 let start_line_i = *line_i;
300 let mut output = String::new();
301 *line_i += 1;
302 while lines[*line_i].trim() != "##" {
303 output += lines[*line_i];
304 output.push('\n');
305 *line_i += 1;
306 if *line_i == lines.len() {
307 *line_i = start_line_i;
308 return Err(ParseEntryError::new(start_line_i, "Could not find an end of this multiline comment. To end a multiline comment, its last line should be nothing but '##'."));
309 }
310 }
311 output.pop();
312 Ok(LayoutEntry::Comment (output))
313}
314
315
316
317fn parse_value(lines: &[&str], line_i: &mut usize, colon_index: usize) -> Result<Value, ParseEntryError> {
318 let line_trimmed = lines[*line_i].trim();
319
320 let value_start_i =
321 line_trimmed.char_indices()
322 .skip(colon_index + 1)
323 .find(|(_i, c)| !c.is_whitespace());
324 let Some((value_start_i, _c)) = value_start_i else {return Err(ParseEntryError::new(*line_i, "No value was found for this key (if this is meant to be empty, please set the value as 'empty')."));};
325
326 let value = &line_trimmed[value_start_i..];
327 match &*value.to_lowercase() {
328 "empty" => return Ok(Value::Empty),
329 "true" => return Ok(Value::Bool (true)),
330 "false" => return Ok(Value::Bool (false)),
331 "\"" => return parse_multiline_string(lines, line_i),
332 _ => {}
333 }
334 let first_char = value.chars().next().unwrap(); if first_char.is_ascii_digit() {
336 if let Ok(i64_value) = value.parse::<i64>() {return Ok(Value::I64 (i64_value));}
337 if let Ok(f64_value) = value.parse::<f64>() {return Ok(Value::F64 (f64_value));}
338 }
339 if first_char == '"' {
340 let last_char = value.chars().last().unwrap(); if last_char != '"' {return Err(ParseEntryError::new(*line_i, "Invalid string, no ending quote found. If this is a single-line string, no characters are allowed after the final quotation mark. If this is meant to be a multi-line string, no characters are allowed after the first quotation mark."))}
342 return Ok(Value::String (value[1 .. value.len()-1].to_string()));
343 }
344
345 Err(ParseEntryError::new(*line_i, "Invalid value, must be 'empty', 'true', 'false', a valid integer, a valid decimal number, a string enclosed in quotes, or a multiline quote starting with a single '\"' character."))
346}
347
348
349
350fn parse_multiline_string(lines: &[&str], line_i: &mut usize) -> Result<Value, ParseEntryError> {
351 let mut output = String::new();
352 let start_i = *line_i;
353 *line_i += 1;
354 let mut curr_line = lines[*line_i].trim_start();
355 while curr_line.starts_with('"') {
356 output += &curr_line[1..];
357 output.push('\n');
358 *line_i += 1;
359 if *line_i == lines.len() {break;}
360 curr_line = lines[*line_i].trim_start();
361 }
362 *line_i -= 1;
363 output.pop();
364 if *line_i == start_i {
365 return Err(ParseEntryError::new(start_i, String::from("Invalid value, multiline strings cannot be empty")));
366 }
367 Ok(Value::String (output))
368}