Reworked config parsing and added error handling

This commit is contained in:
2024-02-03 00:06:45 -06:00
parent d0afcd5de5
commit d4e49ab43c
3 changed files with 122 additions and 35 deletions

View File

@@ -1,7 +1,8 @@
use std::fs; use std::fs;
use std::str;
use std::path::PathBuf; use std::path::PathBuf;
use std::error::Error; use std::error::Error;
use std::fmt;
use toml::Table; use toml::Table;
use crate::dotfile::Dotfile; use crate::dotfile::Dotfile;
@@ -9,60 +10,139 @@ use crate::dotfile::Dotfile;
pub struct Config { pub struct Config {
pub manager_dir: PathBuf, pub manager_dir: PathBuf,
pub dotfiles: Vec<Dotfile>, pub dotfiles: Vec<Result<Dotfile, Box<dyn Error>>>,
} }
impl Config { impl Config {
pub fn parse(config: PathBuf) -> Result<Self, Box<dyn Error>> { pub fn parse(path: PathBuf) -> Result<Self, Box<dyn Error>> {
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<Table, Box<dyn Error>> {
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<Vec<Result<Dotfile, Box<dyn Error>>>, Box<dyn Error>> {
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 dotfile_table = dotfile.as_table().unwrap();
let manager_path = PathBuf::from( let manager_path = PathBuf::from(
match dotfile_table.get("manager_path") { match dotfile_table.get("manager_path") {
Some(path) => path.as_str().expect("Invalid character in dotfile 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( let system_path = PathBuf::from(
match dotfile_table.get("system_path") { match dotfile_table.get("system_path") {
Some(path) => path.as_str().expect("Invalid character in dotfile 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) { Dotfile::new(manager_path, system_path)
Ok(dotfile) => Some(dotfile),
Err(e) => {
println!("Failed to read dotfile: {}", e);
None
}
}
}); });
let valid_dotfiles: Vec<Dotfile> = dotfiles.filter_map(|dotfile| match dotfile { Ok(dotfiles.collect())
Some(dotfile) => Some(dotfile),
None => {
println!("Failed to parse config");
None
},
}).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 { } else {
PathBuf::from("$HOME/.dotfiles") PathBuf::from("$HOME/.dotfiles")
}; };
Ok(Config{manager_dir, dotfiles: valid_dotfiles}) manager_dir
}
fn produce_error(code: usize) -> Box<dyn Error> {
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
)
} }
} }

View File

@@ -47,16 +47,21 @@ fn copy_directory(dir_path: &PathBuf, dest_path: &PathBuf) -> Result<(), Box<dyn
} }
pub fn run(args: ArgMatches, config: Config) -> Result<(), Box<dyn Error>> { pub fn run(args: ArgMatches, config: Config) -> Result<(), Box<dyn Error>> {
let dotfiles = config.dotfiles;
let copy_to_sys = args.get_flag("from-git"); 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| { copy_results.for_each(|result| {
if let Err(e) = result.0 { if let Err(e) = result.0 {

View File

@@ -1,17 +1,19 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::error::Error;
use dotfiles_manager::args; use dotfiles_manager::args;
use dotfiles_manager::config::Config; use dotfiles_manager::config::Config;
fn main() { fn main() -> Result<(), Box<dyn Error>> {
let args = args::parse_args(); 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) panic!("Error: {}", e)
}; };
Ok(())
} }