easy_configuration_format/
file.rs

1use crate::*;
2use std::{collections::{HashMap, HashSet}, ops::{Deref, DerefMut}};
3
4
5
6
7
8/// Holds data for a file's contents, layout, and version
9#[derive(Debug, Clone, PartialEq)]
10pub struct File {
11	/// Contents of file
12	pub values: HashMap<String, Value>,
13	/// Layout of file
14	pub layout: Vec<LayoutEntry>,
15	/// Version of file (strongly recommended to hold the latest version of settings that your application supports)
16	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	/// Converts a settings file into a layout + values, opposite of `format_settings()`
39	/// 
40	/// The generic `T` is for passing generic data to the updater functions
41	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	/// Converts a layout plus values into a formatted settings file, opposite of `from_str()`
84	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	/// Basically an assert for a setting key existing but being left empty
131	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	/// Gets the value of a setting as an int (or returns an error if the setting is missing or if it's holding the wrong type)
141	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	/// Gets the value of a setting as an int as mut (or returns an error if the setting is missing or if it's holding the wrong type)
151	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	/// Gets the value of a setting as a float (or returns an error if the setting is missing or if it's holding the wrong type)
161	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	/// Gets the value of a setting as a float as mut (or returns an error if the setting is missing or if it's holding the wrong type)
171	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	/// Gets the value of a setting as a float, but also allowing ints (or returns an error if the setting is missing or if it's holding the wrong type)
181	/// 
182	/// This does'n't' have a `get_number_mut()` because there's no return type that would make sense
183	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	/// Gets the value of a setting as a bool (or returns an error if the setting is missing or if it's holding the wrong type)
194	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	/// Gets the value of a setting as a bool as mut (or returns an error if the setting is missing or if it's holding the wrong type)
204	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	/// Gets the value of a setting as a string (or returns an error if the setting is missing or if it's holding the wrong type)
214	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	/// Gets the value of a setting as a string as mut (or returns an error if the setting is missing or if it's holding the wrong type)
224	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	/// Add key-value pairs to the `values` hashmap for keys that aren't set
236	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(); // safety: value cannot be empty because it has to have non-whitespace char(s)
335	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(); // safety: value is already assumed to have a first char, therefore it also has a last char
341		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}