use self::error::{BadSystemLabelSomewhere, MissingProperty, SystemDirNotFound};
use crate::system::System;
use std::{io, path::Path};
use yaml_rust::Yaml;
pub mod error;
#[derive(Debug)]
pub struct ConfigFile<'a> {
archive_root: &'a Path,
contents: Yaml,
}
impl<'a> ConfigFile<'a> {
pub fn from_archive(archive_root: &'a Path) -> io::Result<Self> {
if !archive_root.exists() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("archive root {archive_root:?} does not exist. (expected a directory)"),
));
}
if !archive_root.is_dir() {
return Err(io::Error::new(
io::ErrorKind::NotADirectory,
format!("archive root {archive_root:?} is not a valid directory."),
));
}
Ok(Self {
archive_root,
contents: {
let yaml_path = archive_root.join("config.yaml");
let raw_contents = std::fs::read_to_string(yaml_path)?;
match yaml_rust::YamlLoader::load_from_str(&raw_contents) {
Ok(y) => y[0].clone(),
Err(scan_err) => {
return Err(io::Error::new(io::ErrorKind::InvalidData, scan_err))
}
}
},
})
}
#[must_use]
pub const fn contents(&self) -> &Yaml {
&self.contents
}
pub fn systems(&self) -> Result<Vec<System>, Box<dyn std::error::Error>> {
macro_rules! return_err {
( $err: expr ) => {
return Err(Box::new($err))
};
}
let mut systems: Vec<System> = Vec::new();
for (sys_label, properties) in self.contents["systems"]
.as_hash()
.expect("`systems` contains a single value, expected a collection of labels")
{
let Some(label) = sys_label.as_str() else {
return_err!(BadSystemLabelSomewhere {})
};
let sys_error_msg = |msg: &str| -> String {
format!("archive config error: system labeled `{label}`: {msg}")
};
macro_rules! extract_property {
( $property_name: expr, $converter: ident ) => {{
let Some(x) = properties[$property_name].$converter() else {
return_err!(MissingProperty::new(
label.to_owned(),
$property_name.to_owned()
))
};
x
}};
}
let display_name = extract_property!("display_name", as_str);
let color = extract_property!("color", as_vec);
let path = extract_property!("path", as_str);
let games_are_dirs = extract_property!("games_are_directories", as_bool);
let system_path = self.archive_root.join(path);
if !system_path.exists() || !system_path.is_dir() {
return_err!(SystemDirNotFound {
sys_label: label.to_owned(),
dir: system_path.to_string_lossy().into_owned(),
});
}
let color_sys_error_msg: &str =
&sys_error_msg("unexpected `color` value. Expected: `[u8, u8, u8]`");
let nth_color = |n: usize| -> u8 {
u8::try_from(
color
.get(n)
.unwrap_or_else(|| panic!("{color_sys_error_msg}"))
.as_i64()
.unwrap_or_else(|| panic!("{color_sys_error_msg}")),
)
.unwrap_or_else(|_| panic!("{color_sys_error_msg}"))
};
let rgb = [nth_color(0), nth_color(1), nth_color(2)];
systems.push(System::new(label, display_name, rgb, path, games_are_dirs));
}
Ok(systems)
}
}
impl std::ops::Index<&str> for ConfigFile<'_> {
type Output = Yaml;
fn index(&self, index: &str) -> &Self::Output {
&self.contents[0][index]
}
}