Add comments and TODOs

+ Resolve all build warnings
This commit is contained in:
2021-12-26 19:08:58 -05:00
parent 94473ca8da
commit 34ff8f54ab
7 changed files with 104 additions and 31 deletions

View File

@@ -16,6 +16,8 @@ pub mod io;
// -----------------------------------------------------------------------------
// Creates symbolic links to the configurations we're installing
// TODO: On error, revert to last good state
pub fn install_configs(args: & cli::Cli) -> std::io::Result<()> {
// Get the configurations and their target installation paths
// + Checks for conflicts and prompts user to abort or continue
@@ -26,12 +28,16 @@ pub fn install_configs(args: & cli::Cli) -> std::io::Result<()> {
println!("Installing config: {:?}\n+ At location: {:?}\n", config_path, target_path);
match std::os::unix::fs::symlink(config_path, target_path) {
Ok(()) => (),
Ok(()) => (), // Configuration installed successfully
Err(_e) => {
// Attempt to remove the file or directory, and then symlink the new config
match target_path.is_dir() {
true => fs_extra::dir::remove(target_path),
false => fs_extra::file::remove(target_path),
true => fs_extra::dir::remove(target_path)
.expect(&format!("Error: Unable to remove directory: {:?}", target_path)),
false => fs_extra::file::remove(target_path)
.expect(&format!("Error: Unable to remove file: {:?}", target_path)),
};
// Try to symlink the config again, if failure exit with error
std::os::unix::fs::symlink(config_path, target_path)
.expect(&format!("Unable to symlink config: {:?}", config_path));
},

View File

@@ -24,13 +24,14 @@ pub struct Cli {
help="Local or full path to user configurations to install",
parse(from_os_str)
)]
pub configs_dir: std::path::PathBuf,
pub dotfiles_dir: std::path::PathBuf,
#[structopt(
help="The location to attempt installation of user configurations",
default_value="dry-runs/kapper", // TODO: Remove temp default value after tests
// env = "HOME", // Default value to env variable $HOME
long="home-dir",
name="install-dir",
short, long,
parse(from_os_str)
)]
pub install_dir: std::path::PathBuf,
@@ -38,7 +39,8 @@ pub struct Cli {
#[structopt(
help="The location to store backups for this user",
default_value="backups/kapper",
long="backup-dir",
name="backup-dir",
short, long,
parse(from_os_str)
)]
pub backup_dir: std::path::PathBuf,
@@ -61,8 +63,16 @@ pub fn from_args() -> Cli {
impl Cli {
// Helper function to normalize arguments passed to program
pub fn normalize(mut self) -> Self {
self.configs_dir = self.configs_dir.canonicalize().unwrap();
// If the path to the dotfiles doesn't exist, exit with error
if !&self.dotfiles_dir.exists() {
panic!("Error: Dotfiles configuration at {:?} does not exist", self.dotfiles_dir);
}
self.dotfiles_dir = self.dotfiles_dir.canonicalize().unwrap();
// If either the install or backup dir don't exist, create them
std::fs::create_dir_all(&self.install_dir).ok();
self.install_dir = self.install_dir.canonicalize().unwrap();
std::fs::create_dir_all(&self.backup_dir).ok();
self.backup_dir = self.backup_dir.canonicalize().unwrap();
self
}

View File

@@ -19,20 +19,27 @@ use fs_extra::dir;
// -----------------------------------------------------------------------------
// Creates a backup of configurations that conflict
// + Backup directory location is specified by CLI --backup-dir
// TODO: Automatically create backup directory
// TODO: .kotignore in dotfiles repo to specify files to not install / backup
// TODO: .kotrc in dotfiles repo or home dir to set backup-dir and install-dir?
fn backup_config(config_path: & PathBuf, backup_dir: & PathBuf) -> super::io::Result<()> {
let mut backup_path = backup_dir.to_owned();
backup_path.push(config_path.file_name().unwrap());
match config_path.is_dir() {
true => {
// Copy directory with recursion using fs_extra::dir::move_dir
let mut options = dir::CopyOptions::new();
options.copy_inside = true;
dir::move_dir(config_path, backup_path, &options)
},
false => {
// Copy single configuration file
let options = fs_extra::file::CopyOptions::new();
fs_extra::file::move_file(config_path, backup_path, &options)
},
};
}.expect(&format!("Error: Unable to backup config: {:?}", config_path));
Ok(())
}
@@ -41,27 +48,41 @@ fn backup_config(config_path: & PathBuf, backup_dir: & PathBuf) -> super::io::Re
pub fn get_target_paths(args: & super::cli::Cli) -> super::io::Result<HashMap<PathBuf, PathBuf>> {
let mut config_map = HashMap::new();
// Local variable for the installation directory as an absolute path
let mut config_target = args.install_dir.to_owned();
for config_entry in fs::read_dir(&args.configs_dir)? {
// For each file or directory within the dotfiles we're installing
for config_entry in fs::read_dir(&args.dotfiles_dir)? {
// Match result from reading each item in dotfiles, return error if any
match config_entry {
Err(err) => return Err(err),
Ok(entry) => {
// Create full path to target config file (or directory) by push onto install path
config_target.push(entry.file_name());
// If the target configuration file or directory already exists
if config_target.exists() {
match super::io::prompt(format!("Configuration already exists: {:?}\nAbort? Enter y/n or Y/N: ", config_target)) {
true => return Err(std::io::Error::from(std::io::ErrorKind::AlreadyExists)),//panic!("User abort"),
false => backup_config(&config_target, &args.backup_dir).ok(), // TODO: Backup colliding configs
// Ask client if they would like to abort given the config collision
let msg = format!("Configuration already exists: {:?}\
\nAbort? Enter y/n or Y/N: ", config_target);
// If we abort, exit; If we continue, back up the configs
match super::io::prompt(msg) {
true => return Err(std::io::Error::from(std::io::ErrorKind::AlreadyExists)),
false => backup_config(&config_target, &args.backup_dir).ok(),
};
};
// If the entry doesn't already exist, insert it into the config_map
// TODO: If the entry does exist, should there be an exception?
config_map.entry(entry.path().to_owned())
.or_insert(config_target.to_owned());
// Reset config_target to be equal to requested install_dir
config_target.pop();
},
}
}
Ok(config_map)
}

View File

@@ -17,6 +17,8 @@ use std::io;
// -----------------------------------------------------------------------------
// Asks user for y/n Y/N input, returns true/false respectively
// + Prompt output defined by msg parameter String
pub fn prompt(msg: String) -> bool {
println!("{}", msg);
let mut reply = String::new();
@@ -25,6 +27,7 @@ pub fn prompt(msg: String) -> bool {
match reply.trim() {
"y" | "Y" => true,
"n" | "N" => false,
// Handle garbage input
_ => prompt("Please enter y/n or Y/N\n".to_owned()),
}
}

View File

@@ -15,12 +15,21 @@ mod kot;
// -----------------------------------------------------------------------------
fn main() {
// Call augmented kot::cli::from_args() to parse CLI arguments
let args = kot::cli::from_args();
// At this point all paths exist and have been converted to absolute paths
println!("args: {:?}\n", args);
// Attempt to install the configurations, checking for collisions
match kot::install_configs(&args) {
Err(e) => println!("Error: {:?}\n+ Configs used: {:?}\n+ Install directory: {:?}\n",
e.kind(), args.configs_dir, args.install_dir),
Err(e) => {
// If there was an error, show the error type and run settings
println!(
"Error: {:?}\n+ Configs used: {:?}\n+ Install directory: {:?}\n",
e.kind(), args.dotfiles_dir, args.install_dir
)
},
// Configurations installed successfully
Ok(()) => (),
}
}