pub(crate) mod exit;
pub mod lock;
mod name;
mod state;
pub use self::{exit::fatal_error, lock::Lock, name::Name, state::State};
#[cfg(all(feature = "signals", unix))]
use crate::signal::Signal;
use crate::{
command::Command,
component::Component,
config::{Config, Configurable},
error::{FrameworkError, FrameworkErrorKind::*},
logging::{self, LoggingComponent},
path::{AbsPathBuf, ExePath, RootPath},
runnable::Runnable,
shutdown::Shutdown,
terminal::{component::TerminalComponent, ColorChoice},
Version,
};
use std::{env, path::Path, process, vec};
#[allow(unused_variables)]
pub trait Application: Default + Sized {
type Cmd: Command + Configurable<Self::Cfg>;
type Cfg: Config;
type Paths: Default + ExePath + RootPath;
fn run<I>(app_lock: &'static Lock<Self>, args: I)
where
I: IntoIterator<Item = String>,
{
let command = Self::Cmd::from_args(args);
let mut app = Self::default();
app.init(&command).unwrap_or_else(|e| fatal_error!(&app, e));
app_lock.set(app);
command.run();
let mut app = app_lock.write();
app.shutdown(Shutdown::Graceful);
}
fn config(&self) -> &Self::Cfg;
fn state(&self) -> &State<Self>;
fn state_mut(&mut self) -> &mut 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 = TerminalComponent::new(self.term_colors(command));
let logging = LoggingComponent::new(self.logging_config(command))
.expect("logging subsystem failed to initialize");
Ok(vec![Box::new(terminal), Box::new(logging)])
}
fn load_config(&mut self, path: &Path) -> Result<Self::Cfg, FrameworkError> {
let canonical_path = AbsPathBuf::canonicalize(path)
.map_err(|e| err!(ConfigError, "invalid path '{}': {}", path.display(), e))?;
Self::Cfg::load_toml_file(&canonical_path).map_err(|e| {
err!(
ConfigError,
"error loading config from '{}': {}",
canonical_path.display(),
e
)
})
}
fn name(&self) -> &'static str {
Self::Cmd::name()
}
fn description(&self) -> &'static str {
Self::Cmd::description()
}
fn version(&self) -> Version {
Self::Cmd::version()
.parse::<Version>()
.unwrap_or_else(|e| fatal_error!(self, e))
}
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 logging_config(&self, command: &Self::Cmd) -> logging::Config {
logging::Config::default()
}
#[cfg(all(feature = "signals", unix))]
fn handle_signal(&mut self, signal: Signal) -> Result<(), FrameworkError> {
info!("received signal: {} - shutting down", signal.number());
self.shutdown(Shutdown::Graceful)
}
fn shutdown(&mut self, shutdown: Shutdown) -> ! {
match self.state().components.shutdown(self, shutdown) {
Ok(()) => process::exit(0),
Err(e) => fatal_error(self, &e.into()),
}
}
}
pub fn boot<A: Application>(app_lock: &'static Lock<A>) -> ! {
let mut args = env::args();
assert!(args.next().is_some(), "expected one argument but got zero");
A::run(app_lock, args);
process::exit(0);
}