pagetop/src/app.rs

153 lines
5 KiB
Rust

//! Prepara y ejecuta una aplicación creada con `Pagetop`.
mod figfont;
use crate::core::{extension, extension::ExtensionRef};
use crate::{global, locale, service, trace};
use substring::Substring;
use std::io::Error;
use std::sync::LazyLock;
/// Punto de entrada de una aplicación `PageTop`.
///
/// No almacena datos, pero **encapsula** el ciclo completo de configuración y puesta en marcha.
/// Para instanciarla se puede usar [`new`](Application::new) o [`prepare`](Application::prepare).
/// Después sólo hay que llamar a [`run`](Application::run) (o a [`test`](Application::test) si se
/// está preparando un entorno de pruebas).
pub struct Application;
impl Default for Application {
fn default() -> Self {
Self::new()
}
}
impl Application {
/// Crea una instancia de la aplicación.
pub fn new() -> Self {
Self::internal_prepare(None)
}
/// Prepara una instancia de la aplicación a partir de una extensión raíz.
///
/// Esa extensión suele declarar:
///
/// - Sus propias dependencias (que se habilitarán automáticamente).
/// - Una lista de extensiones que deben deshabilitarse si estuvieran activadas.
///
/// Esto simplifica el arranque en escenarios complejos.
pub fn prepare(root_extension: ExtensionRef) -> Self {
Self::internal_prepare(Some(root_extension))
}
// Método interno para preparar la aplicación, opcionalmente con una extensión.
fn internal_prepare(root_extension: Option<ExtensionRef>) -> Self {
// Al arrancar muestra una cabecera para la aplicación.
Self::show_banner();
// Inicia gestión de trazas y registro de eventos (logging).
LazyLock::force(&trace::TRACING);
// Valida el identificador de idioma por defecto.
LazyLock::force(&locale::DEFAULT_LANGID);
// Registra las extensiones de la aplicación.
extension::all::register_extensions(root_extension);
// Registra las acciones de las extensiones.
extension::all::register_actions();
// Inicializa las extensiones.
extension::all::initialize_extensions();
Self
}
// Muestra una cabecera para la aplicación basada en la configuración.
fn show_banner() {
use colored::Colorize;
use terminal_size::{terminal_size, Width};
if global::SETTINGS.app.startup_banner.to_lowercase() != "off" {
// Nombre de la aplicación, ajustado al ancho del terminal si es necesario.
let mut app_ff = String::new();
let app_name = &global::SETTINGS.app.name;
if let Some((Width(term_width), _)) = terminal_size() {
if term_width >= 80 {
let maxlen: usize = ((term_width / 10) - 2).into();
let mut app = app_name.substring(0, maxlen).to_owned();
if app_name.len() > maxlen {
app = format!("{app}...");
}
if let Some(ff) = figfont::FIGFONT.convert(&app) {
app_ff = ff.to_string();
}
}
}
if app_ff.is_empty() {
println!("\n{app_name}");
} else {
print!("\n{app_ff}");
}
// Descripción de la aplicación.
if !global::SETTINGS.app.description.is_empty() {
println!("{}", global::SETTINGS.app.description.cyan());
};
// Versión de PageTop.
println!(
"{} {}\n",
"Powered by PageTop".yellow(),
env!("CARGO_PKG_VERSION").yellow()
);
}
}
/// Arranca el servidor web de la aplicación.
///
/// Devuelve [`std::io::Error`] si el *socket* no puede enlazarse (por puerto en uso, permisos,
/// etc.).
pub fn run(self) -> Result<service::Server, Error> {
// Prepara el servidor web.
Ok(service::HttpServer::new(move || {
Self::service_app().wrap(tracing_actix_web::TracingLogger::default())
})
.bind(format!(
"{}:{}",
&global::SETTINGS.server.bind_address,
&global::SETTINGS.server.bind_port
))?
.run())
}
/// Prepara el servidor web de la aplicación para pruebas.
pub fn test(
self,
) -> service::App<
impl service::Factory<
service::Request,
Config = (),
Response = service::Response<service::BoxBody>,
Error = service::Error,
InitError = (),
>,
> {
Self::service_app()
}
// Configura el servicio web de la aplicación.
fn service_app() -> service::App<
impl service::Factory<
service::Request,
Config = (),
Response = service::Response<service::BoxBody>,
Error = service::Error,
InitError = (),
>,
> {
service::App::new().configure(extension::all::configure_services)
}
}