pub mod cell;
pub(crate) mod exit;
mod name;
mod state;
pub use self::{cell::AppCell, exit::fatal_error, name::Name, state::State};
use crate::{
FrameworkError,
FrameworkErrorKind::*,
command::Command,
component::Component,
config::{self, Config, Configurable},
path::{AbsPathBuf, ExePath, RootPath},
runnable::Runnable,
shutdown::Shutdown,
terminal::{ColorChoice, component::Terminal},
trace::{self, Tracing},
};
use std::{env, ffi::OsString, path::Path, process, vec};
#[allow(unused_variables)]
pub trait Application: Default + Sized + 'static {
type Cmd: Command + Configurable<Self::Cfg> + clap::Parser;
type Cfg: Config;
type Paths: Default + ExePath + RootPath;
fn run<I, T>(app_cell: &'static AppCell<Self>, args: I)
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let command = Self::Cmd::parse_args(args);
let mut app = Self::default();
app.init(&command).unwrap_or_else(|e| fatal_error(&app, &e));
app_cell.set_once(app);
command.run();
app_cell.shutdown(Shutdown::Graceful);
}
fn config(&self) -> config::Reader<Self::Cfg>;
fn state(&self) -> &State<Self>;
fn register_components(&mut self, command: &Self::Cmd) -> Result<(), FrameworkError>;
fn after_config(&mut self, config: Self::Cfg) -> Result<(), FrameworkError>;
fn init(&mut self, command: &Self::Cmd) -> Result<(), FrameworkError> {
self.register_components(command)?;
let config = command
.config_path()
.map(|path| self.load_config(&path))
.transpose()?
.unwrap_or_default();
self.after_config(command.process_config(config)?)?;
Ok(())
}
fn framework_components(
&mut self,
command: &Self::Cmd,
) -> Result<Vec<Box<dyn Component<Self>>>, FrameworkError> {
let terminal = Terminal::new(self.term_colors(command));
let tracing = Tracing::new(self.tracing_config(command), self.term_colors(command))
.expect("tracing subsystem failed to initialize");
Ok(vec![Box::new(terminal), Box::new(tracing)])
}
fn load_config(&mut self, path: &Path) -> Result<Self::Cfg, FrameworkError> {
let canonical_path = AbsPathBuf::canonicalize(path).map_err(|e| {
let path_error = PathError {
name: Some(path.into()),
};
FrameworkError::from(ConfigError.context(path_error))
})?;
Self::Cfg::load_toml_file(canonical_path)
}
fn name(&self) -> &'static str {
Self::Cmd::name()
}
fn description(&self) -> &'static str {
Self::Cmd::description()
}
fn authors(&self) -> Vec<String> {
Self::Cmd::authors().split(':').map(str::to_owned).collect()
}
fn term_colors(&self, command: &Self::Cmd) -> ColorChoice {
ColorChoice::Auto
}
fn tracing_config(&self, command: &Self::Cmd) -> trace::Config {
trace::Config::default()
}
fn shutdown(&self, shutdown: Shutdown) -> ! {
let components = self.state().components();
if let Err(e) = components.shutdown(self, shutdown) {
fatal_error(self, &e)
}
process::exit(0);
}
fn shutdown_with_exitcode(&self, shutdown: Shutdown, exit_code: i32) -> ! {
let components = self.state().components();
if let Err(e) = components.shutdown(self, shutdown) {
fatal_error(self, &e)
}
process::exit(exit_code);
}
}
pub fn boot<A: Application>(app_cell: &'static AppCell<A>) -> ! {
let args = env::args_os();
boot_with_args(app_cell, args)
}
pub fn boot_with_args<A, I, T>(app_cell: &'static AppCell<A>, args: I) -> !
where
A: Application,
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
A::run(app_cell, args);
process::exit(0);
}