From d4e49ab43ca7c5cefd6509be0652aba3a3b6a2c4 Mon Sep 17 00:00:00 2001 From: Ethan Simmons Date: Sat, 3 Feb 2024 00:06:45 -0600 Subject: [PATCH] Reworked config parsing and added error handling --- src/config/mod.rs | 132 +++++++++++++++++++++++++++++++++++++--------- src/lib.rs | 17 +++--- src/main.rs | 8 +-- 3 files changed, 122 insertions(+), 35 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 7e0a0b8..0e8a5ec 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,7 +1,8 @@ use std::fs; -use std::str; use std::path::PathBuf; use std::error::Error; +use std::fmt; + use toml::Table; use crate::dotfile::Dotfile; @@ -9,60 +10,139 @@ use crate::dotfile::Dotfile; pub struct Config { pub manager_dir: PathBuf, - pub dotfiles: Vec, + pub dotfiles: Vec>>, } impl Config { - pub fn parse(config: PathBuf) -> Result> { + pub fn parse(path: PathBuf) -> Result> { - let config_file: Table = str::from_utf8(&fs::read(config)?)?.parse()?; + let config_file = Config::read_config(path)?; - let read_dotfiles = config_file.get("dotfiles").expect("No dotfiles section in config"); + let dotfiles = Config::get_dotfiles(&config_file)?; - let dotfile_array = read_dotfiles.as_array().expect("Invalid config file format").iter(); + let manager_dir = Config::get_manager_dir(&config_file); - let dotfiles = dotfile_array.map(|dotfile| { + Ok(Config{manager_dir, dotfiles}) + } + + + fn read_config(path: PathBuf) -> Result> { + + let file = fs::read(path)?; + + let read_file = String::from_utf8(file)?; + + let config: Table = read_file.parse()?; + + Ok(config) + + } + + + fn get_dotfiles(config: &Table) -> Result>>, Box> { + + let read_dotfiles = config.get("dotfiles"); + + let dotfiles = match read_dotfiles { + Some(dotfiles) => dotfiles, + None => return Err(Config::produce_error(1)), + }; + + let dotfile_iter = match dotfiles.as_array() { + Some(dotfiles) => dotfiles.iter(), + None => return Err(Config::produce_error(2)), + }; + + + let dotfiles = dotfile_iter.map(|dotfile| { let dotfile_table = dotfile.as_table().unwrap(); let manager_path = PathBuf::from( match dotfile_table.get("manager_path") { Some(path) => path.as_str().expect("Invalid character in dotfile path"), - None => return None, + None => return Err(Config::produce_error(3)), } ); let system_path = PathBuf::from( match dotfile_table.get("system_path") { Some(path) => path.as_str().expect("Invalid character in dotfile path"), - None => return None, + None => return Err(Config::produce_error(3)), } ); - match Dotfile::new(manager_path, system_path) { - Ok(dotfile) => Some(dotfile), - Err(e) => { - println!("Failed to read dotfile: {}", e); - None - } - } + Dotfile::new(manager_path, system_path) }); - let valid_dotfiles: Vec = dotfiles.filter_map(|dotfile| match dotfile { - Some(dotfile) => Some(dotfile), - None => { - println!("Failed to parse config"); - None - }, - }).collect(); + Ok(dotfiles.collect()) - let manager_dir = if config_file.contains_key("manager_directory") { - PathBuf::from(config_file.get("manager_directory").unwrap().as_str().unwrap()) + } + + + fn get_manager_dir(config: &Table) -> PathBuf { + + let manager_dir = if config.contains_key("manager_directory") { + PathBuf::from(config.get("manager_directory").unwrap().as_str().unwrap()) } else { PathBuf::from("$HOME/.dotfiles") }; - Ok(Config{manager_dir, dotfiles: valid_dotfiles}) + manager_dir + } + + + fn produce_error(code: usize) -> Box { + let error = match code { + 1 => ConfigParseError { + code: 1, + message: String::from("No dotfiles section in config"), + }, + 2 => ConfigParseError { + code: 2, + message: String::from("Dotfiles is not a valid config"), + }, + 3 => ConfigParseError { + code: 3, + message: String::from("A dotfile section in config is not valid"), + }, + _ => ConfigParseError { + code: 99, + message: String::from("Error parsing config"), + } + }; + + Box::new(error) + } +} + +struct ConfigParseError { + code: usize, + message: String, +} + +impl Error for ConfigParseError {} + +impl fmt::Display for ConfigParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let err = match self.code{ + 1 => "No dotfiles section in config", + 2 => "Dotfiles section in config is not a valid array, Hint: Use [[dotfiles]]", + 3 => "A dotfile section in config is not valid", + _ => "Error parsing config", + }; + + write!(f, "{}", err) + } +} + +impl fmt::Debug for ConfigParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "ConfigReadError {{ code: {}, message: {} }}", + self.code, self.message + ) } } diff --git a/src/lib.rs b/src/lib.rs index 5ced9ba..a6bfc1b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,16 +47,21 @@ fn copy_directory(dir_path: &PathBuf, dest_path: &PathBuf) -> Result<(), Box Result<(), Box> { - let dotfiles = config.dotfiles; - let copy_to_sys = args.get_flag("from-git"); - let copy_results = dotfiles.iter().map(|dotfile| (dotfile.copy_dotfile(copy_to_sys), dotfile)); + let dotfiles = config.dotfiles; + + let valid_dotfiles: Vec<_> = dotfiles.iter().filter_map(|dotfile| match dotfile { + Ok(dotfile) => Some(dotfile), + Err(e) => { + println!("Failed to read a dotfile: {}", e); + None + }, + }).collect(); + + let copy_results = valid_dotfiles.iter().map(|dotfile| (dotfile.copy_dotfile(copy_to_sys), dotfile)); copy_results.for_each(|result| { if let Err(e) = result.0 { diff --git a/src/main.rs b/src/main.rs index b582aee..bc0f042 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,19 @@ use std::path::PathBuf; +use std::error::Error; use dotfiles_manager::args; use dotfiles_manager::config::Config; -fn main() { +fn main() -> Result<(), Box> { let args = args::parse_args(); - let program_config = Config::parse(PathBuf::from("/home/eesim/.config/dotfiles/config")); + let program_config = Config::parse(PathBuf::from("/home/eesim/.config/dotfiles/config"))?; - if let Err(e) = dotfiles_manager::run(args, program_config.unwrap()) { + if let Err(e) = dotfiles_manager::run(args, program_config) { panic!("Error: {}", e) }; + Ok(()) }