refactoring to use quit crate

This commit is contained in:
Bryson Steck 2025-03-16 14:48:31 -06:00
parent 8d1d3e514b
commit 6d3ff502a9
Signed by: bryson
SSH key fingerprint: SHA256:XpKABw/nP4z8UVaH+weLaBnEOD86+cVwif+QjuYLGT4
6 changed files with 140 additions and 59 deletions

26
Cargo.lock generated
View file

@ -146,6 +146,15 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "colored"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
dependencies = [
"windows-sys",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
@ -539,6 +548,21 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "quit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c090c608233d81bd6b90e718cf34506c60a10e633dff2292c3d1029e798d669b"
dependencies = [
"quit_macros",
]
[[package]]
name = "quit_macros"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d4b27a0dd5d08ad7af2d17952fb360ec9c30eeade0b32df7a3c9b099ff37564"
[[package]]
name = "quote"
version = "1.0.40"
@ -553,9 +577,11 @@ name = "refractr"
version = "0.3.1"
dependencies = [
"clap",
"colored",
"ctrlc",
"git2",
"hex",
"quit",
"serde",
"serde_derive",
"sha2",

View file

@ -5,9 +5,11 @@ edition = "2021"
[dependencies]
clap = { version = "4.5.29", features = ["derive"] }
colored = "3.0.0"
ctrlc = "3.4.5"
git2 = "0.20.0"
hex = "0.4.3"
quit = "2.0.0"
serde = "1.0.217"
serde_derive = "1.0.217"
sha2 = "0.10.8"
@ -16,4 +18,4 @@ toml = "0.8.20"
username = "0.2.0"
[target.'cfg(target_family = "unix")'.dependencies]
users = "0.11.0"
users = "0.11.0"

View file

@ -1,13 +1,25 @@
use colored::Colorize;
#[macro_export]
macro_rules! freak_out {
($msg:expr) => {
return Err($msg)
};
pub enum ExitCode {
ParseError = 2,
FilesystemError = 3,
RepositoryError = 4,
RemoteError = 5,
PushError = 6
}
pub struct ReturnData {
pub code: ExitCode,
pub msg: String,
}
pub fn error(msg: String, code: ExitCode) {
eprintln!("{} {}", "error:".red().bold(), msg);
quit::with_code(code as u8)
}
pub fn warning(msg: String) {
eprintln!("warning: {}", msg)
eprintln!("{} {}", "warning:".yellow().bold(), msg)
}
pub fn verbose(level: u8, msg_lvl: u8, msg: String) {
@ -18,5 +30,5 @@ pub fn verbose(level: u8, msg_lvl: u8, msg: String) {
}
prefix += "> ";
eprintln!("{}{}", prefix, msg);
eprintln!("{}{}", prefix.purple().bold(), msg);
}

View file

@ -107,24 +107,32 @@ pub fn read_config(paths: Vec<PathBuf>, refractr: &Refractr) -> Result<Vec<Confi
common::verbose(refractr.verbose, 1, format!("Reading config file: \"{}\"", String::from(path.to_string_lossy())));
let mut data = String::new();
let mut file = match File::open(path.as_path()) {
Err(e) => return Err(format!("refractr: unable to open {}: {}", path.as_path().display(), e)),
Err(e) => return Err(format!("unable to open {}: {}", path.as_path().display(), e)),
Ok(file) => file
};
if let Err(e) = file.read_to_string(&mut data) {
return Err(format!("refractr: unable to read {}: {}", path.as_path().display(), e))
return Err(format!("unable to read {}: {}", path.as_path().display(), e))
}
let config: Config = match toml::from_str(&data) {
Ok(c) => c,
Err(e) => return Err(format!("issues parsing toml file {}: {}", path.as_path().display(), e))
};
let config_file = ConfigFile {
path: match fs::canonicalize(&path) {
Err(_) => return Err(format!("refractr: cannot get absolute path of config file: {}", path.as_path().display())),
Err(_) => return Err(format!("cannot get absolute path of config file: {}", path.as_path().display())),
Ok(abs) => abs.to_string_lossy().to_string()
},
file: match fs::metadata(&path) {
Err(_) => return Err(format!("refractr: cannot obtain metadata for config file: {}", path.as_path().display())),
Err(_) => return Err(format!("cannot obtain metadata for config file: {}", path.as_path().display())),
Ok(metadata) => metadata
},
config: verify_config(toml::from_str(&data).unwrap())
config: match verify_config(&config) {
Err(e) => return Err(format!("invalid config {}: {}", path.as_path().display(), e)),
Ok(_) => config
}
};
let mut dup = false;
@ -146,25 +154,37 @@ pub fn read_config(paths: Vec<PathBuf>, refractr: &Refractr) -> Result<Vec<Confi
return Ok(config_files);
}
fn verify_config(config: Config) -> Config {
fn verify_config(config: &Config) -> Result<(), String> {
if config.schedule.enabled {
assert_ne!(config.schedule.interval, None);
assert!(config.schedule.interval.unwrap() >= 60);
match config.schedule.interval {
Some(i) => {
if i < 60 {
return Err(format!("schedule is enabled, but less than 60"))
}
},
None => return Err(format!("schedule is enabled, but no interval was defined"))
}
}
assert_ne!("", match &config.work_dir {
match &config.work_dir {
Some(path) => format!("{}", path),
None => {
if cfg!(windows) {
match env::var("TEMP") {
Ok(val) => val,
Err(_) => format!("")
Err(_) => return Err(format!("cannot determine the default temp dir"))
}
} else {
format!("/tmp/refractr")
}
},
});
};
return config;
if !&config.from.starts_with("ssh://")
&& !&config.from.starts_with("https://")
&& !&config.from.starts_with("http://") {
return Err(format!("'from' value does not use a supported protocol"))
}
Ok(())
}

View file

@ -44,6 +44,7 @@ fn get_config_default() -> &'static str {
return "/usr/local/etc/refractr/config.toml";
}
#[quit::main]
fn main() -> Result<(), String> {
let args = Args::parse();
let refractr = Refractr {
@ -77,29 +78,35 @@ fn main() -> Result<(), String> {
Err(_) => common::warning(format!("failed to get process username")),
}
common::verbose(refractr.verbose, 1, format!("Level {} verbosity enabled", refractr.verbose.to_string()));
common::verbose(refractr.verbose, 1, format!("refractr started with level {} verbosity enabled", refractr.verbose.to_string()));
common::verbose(refractr.verbose, 3, format!("Process ID: {}", refractr.pid));
common::verbose(refractr.verbose, 3, format!("Running in Docker: {}", refractr.docker));
common::verbose(refractr.verbose, 3, format!("System is UNIX(-like): {}", refractr.unix));
common::verbose(refractr.verbose, 2, format!("Checking for create flag"));
if args.create {
common::verbose(refractr.verbose, 3, format!("Printing sample config"));
let example = include_str!("example/config.toml");
println!("{}", example);
Ok(())
} else {
let cfgs = match config::read_config(args.config, &refractr) {
Ok(cfgs) => cfgs,
Err(e) => freak_out!(e)
};
if refractr.verbose >= 2 {
// no need to loop over configs if verbose is not at the correct level
for i in &cfgs {
common::verbose(refractr.verbose, 2, format!("{}", i));
}
}
common::verbose(refractr.verbose, 1, format!("Config file(s) read successfully"));
refractr.run(cfgs)
return Ok(())
}
let cfgs = match config::read_config(args.config, &refractr) {
Ok(cfgs) => cfgs,
Err(e) => return Err(e)
};
if refractr.verbose >= 2 {
// no need to loop over configs if verbose is not at the correct level
for i in &cfgs {
common::verbose(refractr.verbose, 2, format!("{}", i));
}
}
common::verbose(refractr.verbose, 1, format!("Config file(s) read successfully"));
match refractr.run(cfgs) {
Ok(_) => (),
Err(e) => common::error(format!("{}", e.msg), e.code)
};
Ok(())
}

View file

@ -1,10 +1,9 @@
use crate::freak_out;
use crate::common;
use crate::common::{self, ReturnData, ExitCode};
use crate::config::{Config, ConfigFile};
use git2::{FetchOptions, CertificateCheckStatus, Cred, PushOptions, RemoteCallbacks, Repository};
use git2::{Error, ErrorCode};
use git2::build::{RepoBuilder, CheckoutBuilder};
use git2::build::RepoBuilder;
use hex;
use sha2::{Sha256, Digest};
use std::env;
@ -33,7 +32,7 @@ struct OpenedRepository {
impl Refractr {
fn set_up_work_dir(&self, work_dir: PathBuf) -> Result<String, String> {
if let Err(e) = fs::create_dir_all(&work_dir) {
freak_out!(format!("could not create working directory: {}: {}", work_dir.to_string_lossy().to_string(), e))
return Err(format!("could not create working directory: {}: {}", work_dir.to_string_lossy().to_string(), e))
}
Ok(work_dir.to_string_lossy().to_string())
}
@ -121,7 +120,7 @@ impl Refractr {
common::warning(format!("remote {} already exists, skipping", remote_id));
remote_list.push(remote_id)
} else {
freak_out!(format!("failed to create remote: {}", e));
return Err(format!("failed to create remote: {}", e));
}
}
}
@ -180,7 +179,7 @@ impl Refractr {
Ok(())
}
fn looper(&self, repos: Vec<OpenedRepository>) -> Result<(), String> {
fn looper(&self, repos: Vec<OpenedRepository>) -> Result<(), ReturnData> {
let mut current_ints = Vec::new();
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
@ -224,8 +223,8 @@ impl Refractr {
&repos[i].cfg,
&repos[i].repo,
&repos[i].remotes) {
return Err(e)
}
common::error(e, ExitCode::PushError)
};
}
}
do_break = false;
@ -238,7 +237,7 @@ impl Refractr {
Ok(())
}
pub fn run(&self, cfgs: Vec<ConfigFile>) -> Result<(), String> {
pub fn run(&self, cfgs: Vec<ConfigFile>) -> Result<(), ReturnData> {
common::verbose(self.verbose, 3, format!("Starting main refractr loop"));
let mut loop_repos = Vec::new();
@ -248,7 +247,7 @@ impl Refractr {
let work_dir = self.set_up_work_dir(match &cfg.config.work_dir {
None => {
if cfg!(windows) {
PathBuf::from(format!("{}\\refractr\"", env::var("TEMP").unwrap()))
PathBuf::from(format!("{}\\refractr", env::var("TEMP").unwrap()))
} else {
PathBuf::from("/tmp/refractr")
}
@ -257,7 +256,10 @@ impl Refractr {
});
let path_str = match work_dir {
Ok(p) => p,
Err(e) => return Err(e)
Err(e) => return Err(ReturnData {
code: ExitCode::FilesystemError,
msg: e
})
};
common::verbose(
self.verbose,
@ -265,19 +267,17 @@ impl Refractr {
format!("Created working directory: {}", &path_str));
let repo_name = match &cfg.config.from.split("/").last() {
Some(split) => split.to_string(),
None => freak_out!(format!("failed to parse repository name"))
None => return Err(ReturnData {
code: ExitCode::ParseError,
msg: format!("failed to parse repository name")
})
};
// make initial clone
common::verbose(
self.verbose,
1,
format!("Cloning repository: {}", &cfg.config.from));
let mut builder = RepoBuilder::new();
let mut cb = RemoteCallbacks::new();
let mut fo = FetchOptions::new();
// make initial clone
if cfg.config.from.starts_with("ssh://") {
let key_string = cfg.config.git.ssh_identity_file.clone();
cb.credentials(move |_,_,_| Cred::ssh_key(
@ -304,17 +304,28 @@ impl Refractr {
builder.fetch_options(fo);
let repo_dir = format!("{}{}{}", &path_str, MAIN_SEPARATOR_STR, repo_name);
common::verbose(
self.verbose,
1,
format!("Cloning repository {} to {}", &cfg.config.from, &repo_dir));
let repo = match builder.clone(&cfg.config.from, Path::new(&repo_dir)) {
Ok(repo) => repo,
Err(_) => {
Err(e) => {
println!("{}", e);
common::warning(format!("found existing repo at {}, attempting to use", repo_dir));
match self.fast_forward(&repo_dir, &cfg.config.branches) {
Ok(_) => if let Ok(repo) = Repository::open(Path::new(&repo_dir)) {
repo
} else {
freak_out!(format!("failed to obtain existing repo"))
return Err(ReturnData {
code: ExitCode::RepositoryError,
msg: format!("failed to obtain existing repo")
})
},
Err(e) => freak_out!(format!("failed to obtain existing repo: {}", e))
Err(e) => return Err(ReturnData {
code: ExitCode::RepositoryError,
msg: format!("failed to obtain existing repo: {}", e)
})
}
}
};
@ -324,10 +335,13 @@ impl Refractr {
let remotes = match self.make_remotes(&repo, &cfg) {
Ok(v) => v,
Err(e) => return Err(e)
Err(e) => return Err(ReturnData {
code: ExitCode::RemoteError,
msg: e
})
};
if let Err(e) = self.push_remotes(&cfg.config, &repo, &remotes) {
return Err(e)
common::error(e, ExitCode::PushError);
}
if cfg.config.schedule.enabled {