pub mod collect; pub mod errors; pub mod header; pub mod install; use clap::Parser; /// Defines the arguments passed to the program. /// /// # Examples /// /// ``` /// use clap::Parser; /// /// let args = Args::parse(); /// assert_eq!("config.json", args.config_file_path); /// ``` #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] pub struct Args { /// The file path to the config file #[arg(short, long, default_value = "config.json")] pub config_file_path: String, /// The path to the directory to do work in #[arg(short, long, default_value = "~/dotfiles")] pub destination_dir_path: String, /// Whether to treat the user's home folder as their username or "$USER" #[arg(short, long, default_value_t = false)] pub use_username: bool, } /// The root of a Linux System const ROOT: &str = "/"; /// Defines the Configuration for the progam pub struct Config { /// The Config as a JSON Object pub config: serde_json::Value, /// The paths currently stored for this configuration pub paths: Vec, /// The root path of all work done pub root: String, /// Whether to treat the user's home folder as their username or "$USER" pub use_username: bool, /// The path to the directory to do work in pub destination_path: String, } impl Config { /// Returns a new instance of Config /// /// # Arguments /// /// * `config_file_path` - The file path to the config file /// /// # Examples /// /// ``` /// let config = Config::new("config.json"); /// assert_eq!(ROOT, config.root); /// ``` pub fn new(args: Args) -> Self { let root = "/"; let paths = vec![root.to_string()]; let config = parse_config(&args.config_file_path); Self { paths, config, root: root.to_string(), use_username: args.use_username, destination_path: args.destination_dir_path, } } /// Initializes the Config instance /// /// # Examples /// /// ``` /// let config = Config::new("config.json"); /// config.run(); /// assert!(config.paths.len() > 1); /// ``` pub fn run(&mut self) { let mut config_root = self.root.clone(); let mut config_tree = self.config.clone(); self.generate_paths(&mut config_tree, &mut config_root) } /// Recursively generates file paths from a JSON Object /// /// # Arguments /// /// * `json_file_tree` - The file_tree of paths to generate as a JSON Object /// * `current_root` - The current operating directory path fn generate_paths(&mut self, json_file_tree: &serde_json::Value, current_root: &mut String) { match json_file_tree { serde_json::Value::Object(dir) => self.parse_dir(current_root.clone() ,dir), serde_json::Value::Array(dir_list) => self.parse_list(dir_list, current_root), serde_json::Value::String(file) => self.parse_file(file, current_root.clone()), _ => (), } } /// Parses a directory from a JSON Object /// /// # Arguments /// /// * `current_root` - The current operating directory path /// * `dir` - The JSON Object to be parsed as a Map fn parse_dir(&mut self, mut current_root: String, dir: &serde_json::Map) { let dirname = match dir.keys().next() { Some(dirname) => convert_if_user_folder(dirname, self.use_username), None => "".to_string(), }; current_root.push_str(&dirname); current_root.push_str(ROOT); self.paths.push(current_root.to_string()); let json_file_tree = match dir.values().next() { Some(value) => value, None => errors::json_parsing_error(), }; self.generate_paths(json_file_tree, &mut current_root) } /// Parses a vector of directories from JSON Objects /// /// # Arguments /// ///* 'dir_list' - The vector to be parsed /// * `current_root` - The current operating directory path fn parse_list(&mut self, dir_list: &Vec, current_root: &mut String) { let mut current_root = current_root.clone(); dir_list.iter().for_each(|dir| self.generate_paths(dir, &mut current_root) ); } /// Parses a file from a JSON String /// /// # Arguments /// /// * `file` - The string to be parsed /// * `current_root` - The current operating directory path fn parse_file(&mut self, file: &str, mut current_root: String) { current_root.push_str(file); self.paths.push(current_root.to_string()) } } /// Parses the JSON file at file_path and returns a serde_json::Value. /// Exits program upon error. /// /// # Arguments /// /// * `file_path` - The file path of the config to be parsed /// /// # Examples /// /// ``` /// let config = parse_config("config.json"); /// println!("{:?}", config); /// ``` pub fn parse_config(file_path: &str) -> serde_json::Value { let json_string = read_file_from_path(file_path); let value = match serde_json::from_str(&json_string) { Ok(config) => config, Err(_) => errors::json_parsing_error(), }; value } /// Reads a file to a String. /// Exits program upon error. /// /// # Arguments /// /// * `file_path` - The file path of the file to be read /// /// # Examples /// /// ``` /// let config = read_file_from_path("config.json"); /// println!("{}", config); /// ``` fn read_file_from_path(file_path: &str) -> String { use std::fs; match fs::read_to_string(file_path) { Ok(config) => config, Err(_) => { eprintln!("Error reading file. Check that the file exists and/or the supplied path is correct."); std::process::exit(127) }, } } fn convert_if_user_folder(dirname: &str, want_username: bool) -> String { use std::env; let env_user = env::var("USER").unwrap_or("\n".to_string()); match dirname { name if !want_username & (name == env_user) => "$USER".to_string(), "$USER" if want_username => env_user, _ => dirname.to_string(), } }