pub use clap;
pub use once_cell;
use clap::{CommandFactory, FromArgMatches, Parser};
use once_cell::sync::OnceCell;
use serde::Serialize;
use strum_macros::{AsRefStr, Display, EnumString};
pub type DateTime = chrono::DateTime<chrono::Utc>;
static PROCESS_INFO: OnceCell<ProcessInfo> = OnceCell::new();
static DEBUG: OnceCell<bool> = OnceCell::new();
#[derive(Debug, Parser)]
pub struct BaseCli {
#[arg(short, long, env = "ATB_CLI_ENV", default_value = "dev")]
pub env: Environment,
#[arg(short, long, env = "ATB_CLI_DEBUG")]
pub debug: bool,
}
pub trait AtbCli: Sized {
fn executable_name() -> String {
std::env::current_exe()
.ok()
.and_then(|e| e.file_name().map(|s| s.to_os_string()))
.and_then(|w| w.into_string().ok())
.unwrap_or_else(|| Self::name())
}
fn name() -> String {
"unknown".to_owned()
}
fn version() -> String {
"unknown".to_owned()
}
fn authors() -> Vec<String> {
vec!["unknown".to_owned()]
}
fn description() -> String {
"unknown".to_owned()
}
fn repository() -> String {
"unknown".to_owned()
}
fn impl_version() -> String;
fn commit() -> String {
"unknown".to_owned()
}
fn branch() -> String {
"unknown".to_owned()
}
fn platform() -> String {
"unknown".to_owned()
}
fn rustc_info() -> String {
"unknown".to_owned()
}
fn client_id() -> String {
format!("{}/v{}", Self::name(), Self::impl_version())
}
fn globals_from(&self) -> Option<&BaseCli> {
None
}
fn parse() -> Self
where
Self: Parser + Sized,
{
<Self as AtbCli>::from_iter(std::env::args_os())
}
fn set_globals(base: &BaseCli) {
let _ = PROCESS_INFO.set(ProcessInfo {
name: Self::name(),
version: Self::version(),
branch: Self::branch(),
commit: Self::commit(),
platform: Self::platform(),
rustc: Self::rustc_info(),
start_time: chrono::Utc::now(),
environment: base.env.clone(),
});
let _ = DEBUG.set(base.debug);
}
fn from_iter<I>(iter: I) -> Self
where
Self: Parser + Sized,
I: IntoIterator,
I::Item: Into<std::ffi::OsString> + Clone,
{
let app = <Self as CommandFactory>::command();
let mut full_version = Self::impl_version();
full_version.push('\n');
let name = Self::executable_name();
let authors = ["authors [", &Self::authors().join(","), "]"].concat();
let about = Self::description();
let app = app
.name(name)
.author(authors)
.about(about)
.version(full_version)
.propagate_version(true)
.args_conflicts_with_subcommands(true)
.subcommand_negates_reqs(true);
let matches = app.try_get_matches_from(iter).unwrap_or_else(|e| e.exit());
let parsed: Self =
<Self as FromArgMatches>::from_arg_matches(&matches).unwrap_or_else(|e| e.exit());
if let Some(base) = parsed.globals_from() {
<Self as AtbCli>::set_globals(base);
}
parsed
}
}
pub fn process_info() -> &'static ProcessInfo {
PROCESS_INFO
.get()
.expect("static PROCESS_INFO has not been set.")
}
pub fn debug() -> bool {
*DEBUG.get().expect("static DEBUG has not been set.")
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProcessInfo {
environment: Environment,
name: String,
version: String,
branch: String,
commit: String,
platform: String,
rustc: String,
start_time: DateTime,
}
impl ProcessInfo {
pub fn env(&self) -> &Environment {
&self.environment
}
}
#[derive(Clone, Debug, PartialEq, Serialize, EnumString, Display, AsRefStr)]
pub enum Environment {
#[strum(serialize = "prod", serialize = "production")]
Production,
#[strum(serialize = "dev", serialize = "develop")]
Develop,
#[strum(serialize = "stag", serialize = "staging")]
Staging,
}
impl Environment {
pub fn prod(&self) -> bool {
matches!(self, Environment::Production)
}
pub fn dev(&self) -> bool {
matches!(self, Environment::Develop)
}
pub fn staging(&self) -> bool {
matches!(self, Environment::Staging)
}
}