use std::collections::HashSet;
use std::path::{Path, PathBuf};
use clap::{Arg, Command};
use semver::Version;
use thiserror::Error;
use uninode::{loaders::UniNodeLoadError, UniNode};
#[derive(Debug, Error)]
pub enum AppError {
#[error(transparent)]
SemVer(#[from] semver::Error),
#[error(transparent)]
Load(#[from] UniNodeLoadError),
}
pub trait Application {
fn new(name: &str, version: &str, summary: &str) -> Result<Self, AppError>
where
Self: Sized;
fn name(&self) -> &str;
fn version(&self) -> &Version;
fn summary(&self) -> &str;
fn add_default_config<P: AsRef<Path>>(&mut self, path: P);
fn config(&self) -> &UniNode;
}
type ApplicationBootstrap = Box<dyn FnOnce(&UniNode) -> anyhow::Result<()> + 'static>;
pub struct DangoApplication {
name: String,
summary: String,
version: Version,
default_configs: Vec<PathBuf>,
config: UniNode,
bootstraps: Vec<ApplicationBootstrap>,
}
impl Application for DangoApplication {
fn new(name: &str, version: &str, summary: &str) -> Result<Self, AppError> {
Ok(Self {
name: name.to_string(),
summary: summary.to_string(),
version: Version::parse(version)?,
default_configs: Vec::new(),
config: UniNode::Null,
bootstraps: Vec::new(),
})
}
fn name(&self) -> &str {
&self.name
}
fn version(&self) -> &Version {
&self.version
}
fn summary(&self) -> &str {
&self.summary
}
fn config(&self) -> &UniNode {
&self.config
}
fn add_default_config<P: AsRef<Path>>(&mut self, path: P) {
self.default_configs.push(path.as_ref().to_path_buf());
}
}
impl DangoApplication {
pub fn register_bootstrap<B>(&mut self, bootstrap: B)
where
B: FnOnce(&UniNode) -> anyhow::Result<()> + 'static,
{
self.bootstraps.push(Box::new(bootstrap));
}
pub fn start(&mut self) -> anyhow::Result<()> {
let version = self.version().to_string();
let app = Command::new(self.name())
.version(version.as_ref())
.about(self.summary())
.arg(
Arg::new("config")
.short('c')
.long("config")
.value_name("FILE")
.help("Sets a custom config file")
.multiple_values(true)
.forbid_empty_values(true)
.takes_value(true),
);
let matches = app.get_matches();
fn load_configs(config_files: &[PathBuf]) -> Result<UniNode, UniNodeLoadError> {
let config_files: HashSet<&PathBuf> = config_files.iter().collect();
let mut config = UniNode::empty_object();
for c_file in config_files {
config.merge(UniNode::load(c_file)?);
}
Ok(config)
}
self.config = if matches.occurrences_of("config") == 0 {
load_configs(&self.default_configs)?
} else {
let opt_files = matches
.values_of("config")
.unwrap()
.map(PathBuf::from)
.collect::<Vec<_>>();
load_configs(&opt_files)?
};
if let Some(logger_cfg) = self.config.find("logger") {
super::logging::logger_init(logger_cfg).unwrap();
}
for bootstrap in self.bootstraps.drain(..) {
(bootstrap)(&self.config)?;
}
Ok(())
}
}