Browse Source

Add comments and TODOs

+ Resolve all build warnings
master
Shaun Reed 9 months ago
parent
commit
34ff8f54ab
  1. 57
      README.md
  2. 1
      backups/kapper/README.md
  3. 12
      src/kot.rs
  4. 18
      src/kot/cli.rs
  5. 31
      src/kot/fs.rs
  6. 3
      src/kot/io.rs
  7. 13
      src/main.rs

57
README.md

@ -1,11 +1,17 @@ @@ -1,11 +1,17 @@
#### 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.
Follow [Rustup instructions](https://rustup.rs/) to setup the Rust toolchain
Then to build and run `kot`, run the following commands
```bash
[kapper@kubuntu ~]$./kot --help
git clone https://gitlab.com/shaunrd0/kot && cd kot
cargo build
./target/debug/kot --help
kot 0.1.0
CLI utility for managing Linux user configurations
@ -27,22 +33,41 @@ ARGS: @@ -27,22 +33,41 @@ ARGS:
To store dotfiles, this repository uses submodules. To update surface-level submodules, we can run the following commands
```bash
git submodule init
git submodule update
git submodule update --init
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
```bash
git submodule init
git submodule update --recursive
Submodule path 'dot': checked out '7877117d5bd413ecf35c86efb4514742d8136843'
Submodule path 'dot/.vim/bundle/Colorizer': checked out '826d5691ac7d36589591314621047b1b9d89ed34'
Submodule path 'dot/.vim/bundle/ale': checked out '3ea887d2f4d43dd55d81213517344226f6399ed6'
Submodule path 'dot/.vim/bundle/clang_complete': checked out '293a1062274a06be61797612034bd8d87851406e'
Submodule path 'dot/.vim/bundle/supertab': checked out 'd80e8e2c1fa08607fa34c0ca5f1b66d8a906c5ef'
Submodule path 'dot/.vim/bundle/unicode.vim': checked out 'afb8db4f81580771c39967e89bc5772e72b9018e'
Submodule path 'dot/.vim/bundle/vim-airline': checked out 'cb1bc19064d3762e4e08103afb37a246b797d902'
Submodule path 'dot/.vim/bundle/vim-airline-themes': checked out 'd148d42d9caf331ff08b6cae683d5b210003cde7'
Submodule path 'dot/.vim/bundle/vim-signify': checked out 'b2a0450e23c63b75bbeabf4f0c28f9b4b2480689'
git submodule update --init --recursive
Submodule 'dotfiles/dot' (https://gitlab.com/shaunrd0/dot) registered for path 'dotfiles/dot'
Cloning into '/home/kapper/Code/kotd/dotfiles/dot'...
warning: redirecting to https://gitlab.com/shaunrd0/dot.git/
Submodule path 'dotfiles/dot': checked out '7877117d5bd413ecf35c86efb4514742d8136843'
Submodule '.vim/bundle/Colorizer' (https://github.com/chrisbra/Colorizer) registered for path 'dotfiles/dot/.vim/bundle/Colorizer'
Submodule '.vim/bundle/ale' (https://github.com/dense-analysis/ale) registered for path 'dotfiles/dot/.vim/bundle/ale'
Submodule '.vim/bundle/clang_complete' (https://github.com/xavierd/clang_complete) registered for path 'dotfiles/dot/.vim/bundle/clang_complete'
Submodule '.vim/bundle/supertab' (https://github.com/ervandew/supertab) registered for path 'dotfiles/dot/.vim/bundle/supertab'
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'
```

1
backups/kapper/README.md

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

12
src/kot.rs

@ -16,6 +16,8 @@ pub mod io; @@ -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<()> { @@ -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));
},

18
src/kot/cli.rs

@ -24,13 +24,14 @@ pub struct Cli { @@ -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 { @@ -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 { @@ -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
}

31
src/kot/fs.rs

@ -19,20 +19,27 @@ use fs_extra::dir; @@ -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 @@ -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)
}

3
src/kot/io.rs

@ -17,6 +17,8 @@ use std::io; @@ -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 { @@ -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()),
}
}

13
src/main.rs

@ -15,12 +15,21 @@ mod kot; @@ -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(()) => (),
}
}

Loading…
Cancel
Save