//! Configuration file that handles (de-)serializing other components use std::{fs::{self, DirEntry}, io::Write, path::{Path, PathBuf}}; use serde::{Deserialize, Serialize}; use crate::{SquadObject, account::Account, error::Error, quest::Quest}; /// Struct for containing paths to other (de-)serializable things #[derive(Serialize, Deserialize)] #[serde(default)] pub struct Config { /// Path to config directory #[serde(skip)] pub path: PathBuf, /// Path to serialized [quests][`crate::quest::Quest`] folder pub quests_path: PathBuf, /// Path to serialized [accounts][`crate::account::Account`] folder pub accounts_path: PathBuf, /// Path to serialized [map][`crate::map::Map`] file pub map: PathBuf, /// If true, print to std{out/err} pub verbose: bool, } impl Default for Config { fn default() -> Self { Config { path: ".".into(), quests_path: "quests".into(), accounts_path: "accounts".into(), map: "map.toml".into(), verbose: true, } } } fn handle_quest_entry(quest_entry: DirEntry) -> Result{ let filetype = quest_entry.file_type(); if let Err(error) = filetype { return Err(Error::IoError(error)); } let path = quest_entry.path(); let filetype = filetype.unwrap(); if !filetype.is_file() { return Err(Error::IsNotAFile(path)); } Quest::load(path) } fn handle_account_entry(account_entry: DirEntry) -> Result{ let filetype = account_entry.file_type(); if let Err(error) = filetype { return Err(Error::IoError(error)); } let path = account_entry.path(); let filetype = filetype.unwrap(); if !filetype.is_file() { return Err(Error::IsNotAFile(path)); } Account::load(path) } impl Config { /// Deserialize config from TOML. /// /// This function wraps [try_load][Config::try_load]. /// /// Logs all errors if `config.verbose == true`. /// Returns default config on error. /// /// # Examples /// ```rust /// use squad_quest::config::Config; /// /// let path = "cfg/config.toml".into(); /// let config = Config::load(path); /// ``` pub fn load(path: PathBuf) -> Self { let dir = path.parent() .unwrap_or(Path::new(".")) .to_owned(); match Self::try_load(path) { Ok(conf) => { if conf.verbose { println!("Successfully loaded config"); } conf }, Err(error) => { let conf = Config { path: dir, ..Default::default() }; if conf.verbose { println!("Error while loading config: {error}"); } conf } } } /// Serialize config into TOML. /// Config will be saved as `path/config.toml` /// /// # Examples /// ```rust /// use squad_quest::config::Config; /// /// let path = "cfg".into(); /// /// let config = Config::default(); /// /// if let Err(error) = config.save(path) { /// // handle error /// } /// ``` pub fn save(&self, path: PathBuf) -> Result<(), Error> { let mut path = path; path.push("config.toml"); let str = match toml::to_string_pretty(&self) { Ok(string) => string, Err(error) => return Err(Error::TomlSerializeError(error)), }; let mut file = match fs::File::create(path) { Ok(f) => f, Err(error) => return Err(Error::IoError(error)), }; if let Err(error) = file.write_all(str.as_bytes()) { return Err(Error::IoError(error)); } Ok(()) } /// Deserialize config from TOML /// /// # Examples /// ```rust /// use squad_quest::{config::Config,error::Error}; /// # fn main() { /// # let _ = wrapper(); /// # } /// # fn wrapper() -> Result<(), Error> { /// let path = "cfg/config.toml".into(); /// let config = Config::try_load(path)?; /// # Ok(()) /// # } /// ``` pub fn try_load(path: PathBuf) -> Result { let dir = path.parent() .unwrap_or(Path::new(".")) .to_owned(); match fs::read_to_string(path) { Ok(string) => { match toml::from_str::(&string) { Ok(mut conf) => { conf.path = dir; Ok(conf) }, Err(error) => { Err(Error::TomlDeserializeError(error)) } } }, Err(error) => { Err(Error::IoError(error)) } } } /// Returns full path to quests folder /// This path will be relative to $PWD, not to config. /// /// # Examples /// ```rust /// use squad_quest::config::Config; /// /// let path = "cfg/config.toml".into(); /// let config = Config::load(path); /// /// let quests_path = config.full_quests_path(); /// ``` pub fn full_quests_path(&self) -> PathBuf { let mut path = self.path.clone(); path.push(self.quests_path.clone()); path } /// Load [Vec]<[Quest]> from quests folder. /// Also logs errors and counts successfully loaded quests. /// /// # Examples /// ```rust /// use squad_quest::{config::Config, quest::Quest}; /// /// /// let path = "cfg/config.toml".into(); /// let config = Config::load(path); /// let quests = config.load_quests(); /// /// for quest in quests { /// println!("Quest #{} {}", quest.id, quest.name); /// } /// ``` pub fn load_quests(&self) -> Vec { let mut out_vec = Vec::new(); let path = self.full_quests_path(); match fs::read_dir(path) { Ok(iter) => { for entry in iter { match entry { Ok(quest_entry) => { match handle_quest_entry(quest_entry) { Ok(quest) => out_vec.push(quest), Err(error) if self.verbose => { eprintln!("Error on loading single quest: {error}"); }, _ => {}, } }, Err(error) if self.verbose => { eprintln!("Error on loading single quest: {error}"); }, _ => {}, } } }, Err(error) if self.verbose => { eprintln!("Error on loading quests: {error}"); }, _ => {}, } if self.verbose { println!("Loaded {} quests successfully", out_vec.len()); } out_vec } /// Returns full path to accounts folder /// This path will be relative to $PWD, not to config. /// /// # Examples /// ```rust /// use squad_quest::config::Config; /// /// let path = "cfg/config.toml".into(); /// let config = Config::load(path); /// /// let accounts_path = config.full_accounts_path(); /// ``` pub fn full_accounts_path(&self) -> PathBuf { let mut path = self.path.clone(); path.push(self.accounts_path.clone()); path } /// Load [Vec]<[Account]> from accounts folder. /// Also logs errors and counts successfully loaded quests. /// /// # Examples /// ```rust /// use squad_quest::{config::Config, account::Account}; /// /// /// let path = "cfg/config.toml".into(); /// let config = Config::load(path); /// let accounts = config.load_accounts(); /// /// for account in accounts { /// println!("Account {}", account.id); /// } /// ``` pub fn load_accounts(&self) -> Vec { let mut out_vec = Vec::new(); let path = self.full_accounts_path(); match fs::read_dir(path) { Ok(iter) => { for entry in iter { match entry { Ok(acc_entry) => { match handle_account_entry(acc_entry) { Ok(quest) => out_vec.push(quest), Err(error) if self.verbose => { eprintln!("Error on loading single account: {error}"); }, _ => {}, } }, Err(error) if self.verbose => { eprintln!("Error on loading single account: {error}"); }, _ => {}, } } }, Err(error) if self.verbose => { eprintln!("Error on loading accounts: {error}"); }, _ => {}, } if self.verbose { println!("Loaded {} accounts successfully", out_vec.len()); } out_vec } /// Returns full path to map.toml /// This path will be relative to $PWD, not to config. /// /// # Examples /// ```rust /// use squad_quest::{config::Config,error::Error,map::Map,SquadObject}; /// # fn main() { /// # let _ = wrapper(); /// # } /// # fn wrapper() -> Result<(),Error> { /// /// let path = "cfg/config.toml".into(); /// let config = Config::load(path); /// /// let map_path = config.full_map_path(); /// let map = Map::load(map_path)?; /// # Ok(()) /// # } /// ``` pub fn full_map_path(&self) -> PathBuf { let mut path = self.path.clone(); path.push(self.map.clone()); path } }