use anyhow::{Ok, Result};
use clap::{Parser, Subcommand};
use simplelog::{ColorChoice, CombinedLogger, Config, LevelFilter, TermLogger, TerminalMode};
use std::path::PathBuf;
use url::Url;
#[derive(Debug, Parser)]
#[clap(
name = env!("CARGO_PKG_NAME"),
version = env!("CARGO_PKG_VERSION"),
author = env!("CARGO_PKG_AUTHORS"),
about = env!("CARGO_PKG_DESCRIPTION"),
arg_required_else_help = true,
)]
struct Args {
#[command(flatten)]
log: LogArgs,
#[clap(required = false, short, long)]
config: Option<PathBuf>,
#[clap(subcommand)]
subcommand: SubCommands,
}
#[derive(clap::Args, Debug)]
#[group(required = false, multiple = false)]
struct LogArgs {
#[clap(short, long, default_value_t = false)]
verbose: bool,
#[clap(short, long, default_value_t = false)]
debug: bool,
#[clap(short, long, default_value_t = false)]
trace: bool,
#[clap(short, long, default_value_t = false)]
quiet: bool,
}
#[derive(Debug, Subcommand)]
enum SubCommands {
Init {
#[clap(required = false, ignore_case = true)]
url: Option<Url>,
},
Status {
#[clap(required = false, ignore_case = true)]
repo: Option<String>,
},
Deploy {
#[clap(required = false, ignore_case = true)]
repo: Option<String>,
#[clap(short, long, default_value_t = false)]
force: bool,
},
Undeploy {
#[clap(required = false, ignore_case = true)]
repo: Option<String>,
},
Update {
#[clap(required = false, ignore_case = true)]
repo: Option<String>,
#[clap(short, long, default_value_t = false)]
force: bool,
},
}
fn main() {
(|| {
let args = Args::parse();
init_logger(&args.log)?;
let config = if let Some(ref path) = args.config {
path.clone()
} else {
dirs::config_dir()
.ok_or_else(|| anyhow::anyhow!("config directory not found"))?
.join("rrcm")
.join("config.yaml")
};
match args.subcommand {
SubCommands::Init { url } => {
if let Some(url) = url {
rrcm::config::download_app_config(&config, url)?;
} else {
rrcm::config::init_app_config(&config)?;
}
}
SubCommands::Status { ref repo } => {
let app_config = rrcm::config::load_app_config(&config)?;
rrcm::status(&app_config, repo)?;
}
SubCommands::Deploy { ref repo, force } => {
let app_config = rrcm::config::load_app_config(&config)?;
rrcm::deploy(&app_config, repo, args.log.quiet, force)?;
}
SubCommands::Undeploy { ref repo } => {
let app_config = rrcm::config::load_app_config(&config)?;
rrcm::undeploy(&app_config, repo, args.log.quiet)?;
}
SubCommands::Update { ref repo, force } => {
let app_config = rrcm::config::load_app_config(&config)?;
rrcm::update(
&app_config,
repo,
args.log.quiet,
args.log.verbose || args.log.debug || args.log.trace,
force,
)?;
}
}
Ok(())
})()
.unwrap_or_else(|e| {
log::error!("{:?}", e);
std::process::exit(1);
});
}
fn get_log_level(log: &LogArgs) -> LevelFilter {
if log.quiet {
LevelFilter::Error
} else if log.trace {
LevelFilter::Trace
} else if log.debug {
LevelFilter::Debug
} else if log.verbose {
LevelFilter::Info
} else {
LevelFilter::Error
}
}
fn init_logger(log: &LogArgs) -> Result<()> {
CombinedLogger::init(vec![TermLogger::new(
get_log_level(log),
Config::default(),
TerminalMode::Mixed,
ColorChoice::Auto,
)])?;
Ok(())
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case(false, false, false, false, LevelFilter::Error)]
#[case(false, false, false, true, LevelFilter::Info)]
#[case(false, false, true, false, LevelFilter::Debug)]
#[case(false, true, false, false, LevelFilter::Trace)]
#[case(true, false, false, false, LevelFilter::Error)]
fn test_get_log_level(
#[case] quiet: bool,
#[case] trace: bool,
#[case] debug: bool,
#[case] verbose: bool,
#[case] expected: LevelFilter,
) {
let log = LogArgs {
quiet,
trace,
debug,
verbose,
};
assert_eq!(get_log_level(&log), expected);
}
}