Browse Source

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
master
Shaun Reed 1 year ago
parent
commit
eadf1fd0a2
  1. 3
      .gitmodules
  2. 227
      Cargo.lock
  3. 7
      Cargo.toml
  4. 24
      README.md
  5. 0
      active/.gitkeep
  6. 0
      backups/.gitkeep
  7. 1
      backups/kapper
  8. 0
      dotfiles/.gitkeep
  9. 1
      dotfiles/dot
  10. 0
      dry-runs/.gitkeep
  11. 1
      dry-runs/kapper
  12. 41
      src/kot.rs
  13. 69
      src/kot/cli.rs
  14. 67
      src/kot/fs.rs
  15. 30
      src/kot/io.rs
  16. 25
      src/main.rs

3
.gitmodules vendored

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

227
Cargo.lock generated

@ -3,5 +3,230 @@ @@ -3,5 +3,230 @@
version = 3
[[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"
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"

7
Cargo.toml

@ -1,8 +1,11 @@ @@ -1,8 +1,11 @@
[package]
name = "dotk"
name = "kot"
version = "0.1.0"
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]
structopt = "0.3.23"
fs_extra = "1.2.0"

24
README.md

@ -1,6 +1,26 @@ @@ -1,6 +1,26 @@
#### dotk
#### kot
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

0
backups/.gitkeep

1
backups/kapper

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

0
dotfiles/.gitkeep

1
dotfiles/dot

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

0
dry-runs/.gitkeep

1
dry-runs/kapper

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

41
src/kot.rs

@ -0,0 +1,41 @@ @@ -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

@ -0,0 +1,69 @@ @@ -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

@ -0,0 +1,67 @@ @@ -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

@ -0,0 +1,30 @@ @@ -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()),
}
}

25
src/main.rs

@ -1,3 +1,26 @@ @@ -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() {
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(()) => (),
}
}

Loading…
Cancel
Save