Add comments and TODOs

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

View File

@ -1,11 +1,17 @@
#### kot #### kot
Learning to program in Rust by making myself a Linux CLI tool to help manage dotfiles and configurations. Learning to program in Rust by making myself a Linux CLI tool to help manage dotfiles and configurations.
There are many other tools to manage dotfiles that work just fine. For now, this is intended to be just for my own learning / use and not a general dotfiles management utility. There are many other tools to manage dotfiles that work just fine. For now, this is intended to be just for my own learning / use and not a general dotfiles management utility.
Follow [Rustup instructions](https://rustup.rs/) to setup the Rust toolchain
Then to build and run `kot`, run the following commands
```bash ```bash
[kapper@kubuntu ~]$./kot --help git clone https://gitlab.com/shaunrd0/kot && cd kot
cargo build
./target/debug/kot --help
kot 0.1.0 kot 0.1.0
CLI utility for managing Linux user configurations CLI utility for managing Linux user configurations
@ -27,22 +33,41 @@ ARGS:
To store dotfiles, this repository uses submodules. To update surface-level submodules, we can run the following commands To store dotfiles, this repository uses submodules. To update surface-level submodules, we can run the following commands
```bash ```bash
git submodule init git submodule update --init
git submodule update
Submodule path 'dot': checked out '7877117d5bd413ecf35c86efb4514742d8136843' Submodule path 'dot': checked out '7877117d5bd413ecf35c86efb4514742d8136843'
``` ```
But in the case of my dotfiles repository, [shaunrd0/dot](https://gitlab.com/shaunrd0/dot), I use submodules to clone repositories for vim plugins. To update all submodules *and their nested submodules*, we can run the following commands But in the case of my dotfiles repository, [shaunrd0/dot](https://gitlab.com/shaunrd0/dot), I use submodules to clone repositories for vim plugins. To update all submodules *and their nested submodules*, we can run the following commands
```bash ```bash
git submodule init git submodule update --init --recursive
git submodule update --recursive
Submodule path 'dot': checked out '7877117d5bd413ecf35c86efb4514742d8136843' Submodule 'dotfiles/dot' (https://gitlab.com/shaunrd0/dot) registered for path 'dotfiles/dot'
Submodule path 'dot/.vim/bundle/Colorizer': checked out '826d5691ac7d36589591314621047b1b9d89ed34' Cloning into '/home/kapper/Code/kotd/dotfiles/dot'...
Submodule path 'dot/.vim/bundle/ale': checked out '3ea887d2f4d43dd55d81213517344226f6399ed6' warning: redirecting to https://gitlab.com/shaunrd0/dot.git/
Submodule path 'dot/.vim/bundle/clang_complete': checked out '293a1062274a06be61797612034bd8d87851406e' Submodule path 'dotfiles/dot': checked out '7877117d5bd413ecf35c86efb4514742d8136843'
Submodule path 'dot/.vim/bundle/supertab': checked out 'd80e8e2c1fa08607fa34c0ca5f1b66d8a906c5ef' Submodule '.vim/bundle/Colorizer' (https://github.com/chrisbra/Colorizer) registered for path 'dotfiles/dot/.vim/bundle/Colorizer'
Submodule path 'dot/.vim/bundle/unicode.vim': checked out 'afb8db4f81580771c39967e89bc5772e72b9018e' Submodule '.vim/bundle/ale' (https://github.com/dense-analysis/ale) registered for path 'dotfiles/dot/.vim/bundle/ale'
Submodule path 'dot/.vim/bundle/vim-airline': checked out 'cb1bc19064d3762e4e08103afb37a246b797d902' Submodule '.vim/bundle/clang_complete' (https://github.com/xavierd/clang_complete) registered for path 'dotfiles/dot/.vim/bundle/clang_complete'
Submodule path 'dot/.vim/bundle/vim-airline-themes': checked out 'd148d42d9caf331ff08b6cae683d5b210003cde7' Submodule '.vim/bundle/supertab' (https://github.com/ervandew/supertab) registered for path 'dotfiles/dot/.vim/bundle/supertab'
Submodule path 'dot/.vim/bundle/vim-signify': checked out 'b2a0450e23c63b75bbeabf4f0c28f9b4b2480689' Submodule '.vim/bundle/unicode.vim' (https://github.com/chrisbra/unicode.vim) registered for path 'dotfiles/dot/.vim/bundle/unicode.vim'
Submodule '.vim/bundle/vim-airline' (https://github.com/vim-airline/vim-airline) registered for path 'dotfiles/dot/.vim/bundle/vim-airline'
Submodule '.vim/bundle/vim-airline-themes' (https://github.com/vim-airline/vim-airline-themes) registered for path 'dotfiles/dot/.vim/bundle/vim-airline-themes'
Submodule '.vim/bundle/vim-signify' (https://github.com/mhinz/vim-signify) registered for path 'dotfiles/dot/.vim/bundle/vim-signify'
Cloning into '/home/kapper/Code/kotd/dotfiles/dot/.vim/bundle/Colorizer'...
Cloning into '/home/kapper/Code/kotd/dotfiles/dot/.vim/bundle/ale'...
Cloning into '/home/kapper/Code/kotd/dotfiles/dot/.vim/bundle/clang_complete'...
Cloning into '/home/kapper/Code/kotd/dotfiles/dot/.vim/bundle/supertab'...
Cloning into '/home/kapper/Code/kotd/dotfiles/dot/.vim/bundle/unicode.vim'...
Cloning into '/home/kapper/Code/kotd/dotfiles/dot/.vim/bundle/vim-airline'...
Cloning into '/home/kapper/Code/kotd/dotfiles/dot/.vim/bundle/vim-airline-themes'...
Cloning into '/home/kapper/Code/kotd/dotfiles/dot/.vim/bundle/vim-signify'...
Submodule path 'dotfiles/dot/.vim/bundle/Colorizer': checked out '826d5691ac7d36589591314621047b1b9d89ed34'
Submodule path 'dotfiles/dot/.vim/bundle/ale': checked out '3ea887d2f4d43dd55d81213517344226f6399ed6'
Submodule path 'dotfiles/dot/.vim/bundle/clang_complete': checked out '293a1062274a06be61797612034bd8d87851406e'
Submodule path 'dotfiles/dot/.vim/bundle/supertab': checked out 'd80e8e2c1fa08607fa34c0ca5f1b66d8a906c5ef'
Submodule path 'dotfiles/dot/.vim/bundle/unicode.vim': checked out 'afb8db4f81580771c39967e89bc5772e72b9018e'
Submodule path 'dotfiles/dot/.vim/bundle/vim-airline': checked out 'cb1bc19064d3762e4e08103afb37a246b797d902'
Submodule path 'dotfiles/dot/.vim/bundle/vim-airline-themes': checked out 'd148d42d9caf331ff08b6cae683d5b210003cde7'
Submodule path 'dotfiles/dot/.vim/bundle/vim-signify': checked out 'b2a0450e23c63b75bbeabf4f0c28f9b4b2480689'
``` ```

View File

@ -1 +0,0 @@
This directory is for testing the backup process of user configurations that conflict with configurations we're attempting to install.

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<()> { pub fn install_configs(args: & cli::Cli) -> std::io::Result<()> {
// Get the configurations and their target installation paths // Get the configurations and their target installation paths
// + Checks for conflicts and prompts user to abort or continue // + 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); println!("Installing config: {:?}\n+ At location: {:?}\n", config_path, target_path);
match std::os::unix::fs::symlink(config_path, target_path) { match std::os::unix::fs::symlink(config_path, target_path) {
Ok(()) => (), Ok(()) => (), // Configuration installed successfully
Err(_e) => { Err(_e) => {
// Attempt to remove the file or directory, and then symlink the new config
match target_path.is_dir() { match target_path.is_dir() {
true => fs_extra::dir::remove(target_path), true => fs_extra::dir::remove(target_path)
false => fs_extra::file::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) std::os::unix::fs::symlink(config_path, target_path)
.expect(&format!("Unable to symlink config: {:?}", config_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", help="Local or full path to user configurations to install",
parse(from_os_str) parse(from_os_str)
)] )]
pub configs_dir: std::path::PathBuf, pub dotfiles_dir: std::path::PathBuf,
#[structopt( #[structopt(
help="The location to attempt installation of user configurations", help="The location to attempt installation of user configurations",
default_value="dry-runs/kapper", // TODO: Remove temp default value after tests default_value="dry-runs/kapper", // TODO: Remove temp default value after tests
// env = "HOME", // Default value to env variable $HOME // env = "HOME", // Default value to env variable $HOME
long="home-dir", name="install-dir",
short, long,
parse(from_os_str) parse(from_os_str)
)] )]
pub install_dir: std::path::PathBuf, pub install_dir: std::path::PathBuf,
@ -38,7 +39,8 @@ pub struct Cli {
#[structopt( #[structopt(
help="The location to store backups for this user", help="The location to store backups for this user",
default_value="backups/kapper", default_value="backups/kapper",
long="backup-dir", name="backup-dir",
short, long,
parse(from_os_str) parse(from_os_str)
)] )]
pub backup_dir: std::path::PathBuf, pub backup_dir: std::path::PathBuf,
@ -61,8 +63,16 @@ pub fn from_args() -> Cli {
impl Cli { impl Cli {
// Helper function to normalize arguments passed to program // Helper function to normalize arguments passed to program
pub fn normalize(mut self) -> Self { 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(); 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.backup_dir = self.backup_dir.canonicalize().unwrap();
self 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<()> { fn backup_config(config_path: & PathBuf, backup_dir: & PathBuf) -> super::io::Result<()> {
let mut backup_path = backup_dir.to_owned(); let mut backup_path = backup_dir.to_owned();
backup_path.push(config_path.file_name().unwrap()); backup_path.push(config_path.file_name().unwrap());
match config_path.is_dir() { match config_path.is_dir() {
true => { true => {
// Copy directory with recursion using fs_extra::dir::move_dir
let mut options = dir::CopyOptions::new(); let mut options = dir::CopyOptions::new();
options.copy_inside = true; options.copy_inside = true;
dir::move_dir(config_path, backup_path, &options) dir::move_dir(config_path, backup_path, &options)
}, },
false => { false => {
// Copy single configuration file
let options = fs_extra::file::CopyOptions::new(); let options = fs_extra::file::CopyOptions::new();
fs_extra::file::move_file(config_path, backup_path, &options) fs_extra::file::move_file(config_path, backup_path, &options)
}, },
}; }.expect(&format!("Error: Unable to backup config: {:?}", config_path));
Ok(()) 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>> { pub fn get_target_paths(args: & super::cli::Cli) -> super::io::Result<HashMap<PathBuf, PathBuf>> {
let mut config_map = HashMap::new(); 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(); 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 { match config_entry {
Err(err) => return Err(err), Err(err) => return Err(err),
Ok(entry) => { Ok(entry) => {
// Create full path to target config file (or directory) by push onto install path
config_target.push(entry.file_name()); config_target.push(entry.file_name());
// If the target configuration file or directory already exists
if config_target.exists() { if config_target.exists() {
match super::io::prompt(format!("Configuration already exists: {:?}\nAbort? Enter y/n or Y/N: ", config_target)) { // Ask client if they would like to abort given the config collision
true => return Err(std::io::Error::from(std::io::ErrorKind::AlreadyExists)),//panic!("User abort"), let msg = format!("Configuration already exists: {:?}\
false => backup_config(&config_target, &args.backup_dir).ok(), // TODO: Backup colliding configs \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()) config_map.entry(entry.path().to_owned())
.or_insert(config_target.to_owned()); .or_insert(config_target.to_owned());
// Reset config_target to be equal to requested install_dir
config_target.pop(); config_target.pop();
}, },
} }
} }
Ok(config_map) 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 { pub fn prompt(msg: String) -> bool {
println!("{}", msg); println!("{}", msg);
let mut reply = String::new(); let mut reply = String::new();
@ -25,6 +27,7 @@ pub fn prompt(msg: String) -> bool {
match reply.trim() { match reply.trim() {
"y" | "Y" => true, "y" | "Y" => true,
"n" | "N" => false, "n" | "N" => false,
// Handle garbage input
_ => prompt("Please enter y/n or Y/N\n".to_owned()), _ => prompt("Please enter y/n or Y/N\n".to_owned()),
} }
} }

View File

@ -15,12 +15,21 @@ mod kot;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
fn main() { fn main() {
// Call augmented kot::cli::from_args() to parse CLI arguments
let args = kot::cli::from_args(); let args = kot::cli::from_args();
// At this point all paths exist and have been converted to absolute paths
println!("args: {:?}\n", args); println!("args: {:?}\n", args);
// Attempt to install the configurations, checking for collisions
match kot::install_configs(&args) { match kot::install_configs(&args) {
Err(e) => println!("Error: {:?}\n+ Configs used: {:?}\n+ Install directory: {:?}\n", Err(e) => {
e.kind(), args.configs_dir, args.install_dir), // 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(()) => (), Ok(()) => (),
} }
} }