Start work on basic CLI features

+ Add basic logic for detecting conflicting configs, prompt to abort or
  continue
+ Create symlinks to configs
+ Basic usage is `kot path/to/dotfiles/`
+ Add `--backup-dir` option for setting directory for backing up configs
+ Default backup-dir to `backups/kapper` until finished testing
+ Add `--home-dir` option for setting location to install configs
+ Default home-dir setting to `dry-runs/kapper` until finished testing
This commit is contained in:
Shaun Reed 2021-09-28 18:54:37 -04:00
parent cb0e627a42
commit eadf1fd0a2
16 changed files with 490 additions and 6 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "dotfiles/dot"]
path = dotfiles/dot
url = https://gitlab.com/shaunrd0/dot

227
Cargo.lock generated
View File

@ -3,5 +3,230 @@
version = 3 version = 3
[[package]] [[package]]
name = "dotk" name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "clap"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "fs_extra"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "kot"
version = "0.1.0" version = "0.1.0"
dependencies = [
"fs_extra",
"structopt",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "structopt"
version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf9d950ef167e25e0bdb073cf1d68e9ad2795ac826f2f3f59647817cf23c0bfa"
dependencies = [
"clap",
"lazy_static",
"structopt-derive",
]
[[package]]
name = "structopt-derive"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134d838a2c9943ac3125cf6df165eda53493451b719f3255b2a26b85f772d0ba"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "1.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -1,8 +1,11 @@
[package] [package]
name = "dotk" name = "kot"
version = "0.1.0" version = "0.1.0"
edition = "2018" edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at
# https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
structopt = "0.3.23"
fs_extra = "1.2.0"

View File

@ -1,6 +1,26 @@
#### dotk #### 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. If I enjoy working on it and get good use out of it, I may rethink this later. 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.
```bash
[kapper@kubuntu ~]$./kot --help
kot 0.1.0
CLI utility for managing Linux user configurations
USAGE:
kot [OPTIONS] <config>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--backup-dir <backup-dir> The location to store backups for this user [default: backups/kapper]
--home-dir <install-dir> The location to attempt installation of user configurations [default: dry-
runs/kapper]
ARGS:
<config> Local or full path to user configurations to install
```

0
active/.gitkeep Normal file
View File

0
backups/.gitkeep Normal file
View File

1
backups/kapper Submodule

@ -0,0 +1 @@
Subproject commit 7877117d5bd413ecf35c86efb4514742d8136843

0
dotfiles/.gitkeep Normal file
View File

1
dotfiles/dot Submodule

@ -0,0 +1 @@
Subproject commit 7877117d5bd413ecf35c86efb4514742d8136843

0
dry-runs/.gitkeep Normal file
View File

1
dry-runs/kapper Submodule

@ -0,0 +1 @@
Subproject commit 7877117d5bd413ecf35c86efb4514742d8136843

41
src/kot.rs Normal file
View File

@ -0,0 +1,41 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ##
## About: Root module for Linux configuration manager kot ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
pub mod cli;
pub mod fs;
pub mod io;
// =============================================================================
// IMPLEMENTATION
// =============================================================================
// -----------------------------------------------------------------------------
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
let config_map = fs::get_target_paths(&args)?;
// At this point there are either no conflicts or the user agreed to them
for (config_path, target_path) in &config_map {
println!("Installing config: {:?}\n+ At location: {:?}\n", config_path, target_path);
match std::os::unix::fs::symlink(config_path, target_path) {
Ok(()) => (),
Err(_e) => {
match target_path.is_dir() {
true => fs_extra::dir::remove(target_path),
false => fs_extra::file::remove(target_path),
};
std::os::unix::fs::symlink(config_path, target_path)
.expect(&format!("Unable to symlink config: {:?}", config_path));
},
}
}
Ok(())
}

69
src/kot/cli.rs Normal file
View File

@ -0,0 +1,69 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ##
## About: Wrapper for StructOpt crate functionality used by kot ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
use structopt::StructOpt;
// =============================================================================
// STRUCTS
// =============================================================================
// -----------------------------------------------------------------------------
#[derive(Debug, StructOpt)]
#[structopt(
name="kot",
about="CLI for managing Linux user configurations"
)]
pub struct Cli {
#[structopt(
help="Local or full path to user configurations to install",
parse(from_os_str)
)]
pub configs_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",
parse(from_os_str)
)]
pub install_dir: std::path::PathBuf,
#[structopt(
help="The location to store backups for this user",
default_value="backups/kapper",
long="backup-dir",
parse(from_os_str)
)]
pub backup_dir: std::path::PathBuf,
}
// =============================================================================
// IMPLEMENTATION
// =============================================================================
// -----------------------------------------------------------------------------
// Augment implementation of from_args to limit scope of StructOpt
// + Also enforces use of Cli::normalize()
// https://docs.rs/structopt/0.3.23/src/structopt/lib.rs.html#1121-1126
pub fn from_args() -> Cli {
let s = Cli::from_clap(&Cli::clap().get_matches());
s.normalize()
}
impl Cli {
// Helper function to normalize arguments passed to program
pub fn normalize(mut self) -> Self {
self.configs_dir = self.configs_dir.canonicalize().unwrap();
self.install_dir = self.install_dir.canonicalize().unwrap();
self.backup_dir = self.backup_dir.canonicalize().unwrap();
self
}
}

67
src/kot/fs.rs Normal file
View File

@ -0,0 +1,67 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ##
## About: Wrapper for std::fs functionality used by kot ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
// Allow the use of kot::fs::Path and kot::fs::PathBuf from std::path::
pub use std::path::{Path, PathBuf};
pub use std::collections::HashMap;
use std::fs;
use fs_extra::dir;
// =============================================================================
// IMPLEMENTATION
// =============================================================================
// -----------------------------------------------------------------------------
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 => {
let mut options = dir::CopyOptions::new();
options.copy_inside = true;
dir::move_dir(config_path, backup_path, &options)
},
false => {
let options = fs_extra::file::CopyOptions::new();
fs_extra::file::move_file(config_path, backup_path, &options)
},
};
Ok(())
}
// Initialize and return a HashMap<config_dir, config_install_location>
// Later used to check each install location for conflicts before installing
pub fn get_target_paths(args: & super::cli::Cli) -> super::io::Result<HashMap<PathBuf, PathBuf>> {
let mut config_map = HashMap::new();
let mut config_target = args.install_dir.to_owned();
for config_entry in fs::read_dir(&args.configs_dir)? {
match config_entry {
Err(err) => return Err(err),
Ok(entry) => {
config_target.push(entry.file_name());
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
};
};
config_map.entry(entry.path().to_owned())
.or_insert(config_target.to_owned());
config_target.pop();
},
}
}
Ok(config_map)
}

30
src/kot/io.rs Normal file
View File

@ -0,0 +1,30 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ##
## About: Wrapper for std::io functionality used by kot ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
// Allow use of kot::io::Result
pub use std::io::Result;
use std::io;
// =============================================================================
// IMPLEMENTATION
// =============================================================================
// -----------------------------------------------------------------------------
pub fn prompt(msg: String) -> bool {
println!("{}", msg);
let mut reply = String::new();
io::stdin().read_line(&mut reply)
.expect("Failed to read user input");
match reply.trim() {
"y" | "Y" => true,
"n" | "N" => false,
_ => prompt("Please enter y/n or Y/N\n".to_owned()),
}
}

View File

@ -1,3 +1,26 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ##
## About: Main entry point for Linux configuration manager kot ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
mod kot;
// =============================================================================
// MAIN ENTRY-POINT
// =============================================================================
// -----------------------------------------------------------------------------
fn main() { fn main() {
println!("Hello, world!"); let args = kot::cli::from_args();
println!("args: {:?}\n", args);
match kot::install_configs(&args) {
Err(e) => println!("Error: {:?}\n+ Configs used: {:?}\n+ Install directory: {:?}\n",
e.kind(), args.configs_dir, args.install_dir),
Ok(()) => (),
}
} }