diff --git a/src/config.rs b/src/config.rs index a5baab60..07924057 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,63 +1,73 @@ -//! Retrieve and apply settings values from configuration files. +//! Load configuration settings. //! -//! Carga la configuración de la aplicación en forma de pares `clave = valor` recogidos en archivos -//! [TOML](https://toml.io). +//! These settings are loaded from [TOML](https://toml.io) files as `key = value` pairs and mapped +//! into type-safe structures with predefined values. //! -//! La metodología [The Twelve-Factor App](https://12factor.net/es/) define **la configuración de -//! una aplicación como todo lo que puede variar entre despliegues**, diferenciando entre entornos -//! de desarrollo, pre-producción, producción, etc. -//! -//! A veces las aplicaciones guardan configuraciones como constantes en el código, lo que implica -//! una violación de esta metodología. `PageTop` recomienda una **estricta separación entre código y -//! configuración**. La configuración variará en cada tipo de despliegue, y el código no. +//! Following the [Twelve-Factor App](https://12factor.net/config) methodology, `PageTop` separates +//! code from configuration. This approach allows configurations to vary across deployments, such as +//! development, staging, or production, without changing the codebase. //! //! -//! # Cómo cargar los ajustes de configuración +//! # Loading configuration settings //! -//! Si tu aplicación requiere archivos de configuración debes crear un directorio *config* al mismo -//! nivel del archivo *Cargo.toml* de tu proyecto (o del ejecutable binario de la aplicación). +//! If your application requires configuration files, create a `config` directory in the root of +//! your project, at the same level as the *Cargo.toml* file or the application's binary. //! -//! `PageTop` se encargará de cargar todos los ajustes de configuración de tu aplicación leyendo los -//! siguientes archivos TOML en este orden (todos los archivos son opcionales): +//! `PageTop` automatically loads configuration settings by reading the following TOML files in +//! order (all files are optional): //! -//! 1. **config/common.toml**, útil para los ajustes comunes a cualquier entorno. Estos valores -//! podrán ser sobrescritos al fusionar los archivos de configuración restantes. +//! 1. **config/common.toml**, for settings shared across all environments. This approach simplifies +//! maintenance by centralizing common configuration values. //! -//! 2. **config/{file}.toml**, donde *{file}* se define con la variable de entorno +//! 2. **config/{rm}.toml**, where `{rm}` corresponds to the environment variable //! `PAGETOP_RUN_MODE`: //! -//! * Si no está definida se asumirá *default* por defecto y `PageTop` intentará cargar el -//! archivo *config/default.toml* si existe. +//! * If `PAGETOP_RUN_MODE` is not set, it defaults to `default`, and `PageTop` attempts to load +//! *config/default.toml* if available. //! -//! * De esta manera podrás tener diferentes ajustes de configuración para diferentes entornos -//! de ejecución. Por ejemplo, para *devel.toml*, *staging.toml* o *production.toml*. O -//! también para *server1.toml* o *server2.toml*. Sólo uno será cargado. +//! * Useful for environment-specific configurations, ensuring that each environment +//! (e.g., development, staging, production) has its own settings without affecting others, +//! such as API keys, URLs, or performance-related adjustments. //! -//! * Normalmente estos archivos suelen ser idóneos para incluir contraseñas o configuración -//! sensible asociada al entorno correspondiente. Estos archivos no deberían ser publicados en -//! el repositorio Git por razones de seguridad. +//! 3. **config/local.{rm}.toml**, useful for local machine-specific configurations: //! -//! 3. **config/local.toml**, para añadir o sobrescribir ajustes de los archivos anteriores. +//! * This file allows you to add or override settings specific to the environment. For example, +//! `local.devel.toml` for development or `local.production.toml` for production tweaks. +//! +//! * It enables developers to tailor settings for their machines within a given environment and +//! is typically not shared or committed to version control systems. +//! +//! 4. **config/local.toml**, for general local settings across all environments, ideal for quick +//! adjustments or temporary values not tied to any specific environment. +//! +//! The configuration settings are merged in the order listed above, with later files overriding +//! earlier ones if there are conflicts. //! //! -//! # Cómo añadir ajustes de configuración +//! # Adding configuration settings //! -//! Para proporcionar a tu **módulo** sus propios ajustes de configuración, añade -//! [*serde*](https://docs.rs/serde) en las dependencias de tu archivo *Cargo.toml* habilitando la -//! característica `derive`: +//! To give your **module** its own configuration settings, add [*serde*](https://docs.rs/serde) as +//! a dependency in your *Cargo.toml* file with the `derive` feature enabled: //! //! ```toml //! [dependencies] //! serde = { version = "1.0", features = ["derive"] } //! ``` //! -//! Y luego inicializa con la macro [`config_defaults!`](crate::config_defaults) tus ajustes, usando -//! tipos seguros y asignando los valores predefinidos para la estructura asociada: +//! Then, use the [`include_config!`](crate::include_config) macro to initialize your settings with +//! type-safe structures and predefined values: //! //! ``` //! use pagetop::prelude::*; //! use serde::Deserialize; //! +//! include_config!(SETTINGS: Settings => [ +//! // [myapp] +//! "myapp.name" => "Value Name", +//! "myapp.width" => 900, +//! "myapp.height" => 320, +//! ]); +//! //! #[derive(Debug, Deserialize)] //! pub struct Settings { //! pub myapp: MyApp, @@ -70,31 +80,26 @@ //! pub width: u16, //! pub height: u16, //! } -//! -//! config_defaults!(SETTINGS: Settings => [ -//! // [myapp] -//! "myapp.name" => "Value Name", -//! "myapp.width" => 900, -//! "myapp.height" => 320, -//! ]); //! ``` //! -//! De hecho, así se declaran los ajustes globales de la configuración (ver [`SETTINGS`]). +//! This is how global configuration settings are declared (see [`SETTINGS`](crate::global::SETTINGS)). //! -//! Puedes usar la [sintaxis TOML](https://toml.io/en/v1.0.0#table) para añadir tu nueva sección -//! `[myapp]` en los archivos de configuración, del mismo modo que se añaden `[log]` o `[server]` en -//! los ajustes globales (ver [`Settings`]). +//! You can add a new `[myapp]` section in the configuration files using the +//! [TOML syntax](https://toml.io/en/v1.0.0#table), just like the `[log]` or `[server]` sections in +//! the global settings (see [`Settings`](crate::global::Settings)). //! -//! Se recomienda inicializar todos los ajustes con valores predefinidos, o utilizar la notación -//! `Option` si van a ser tratados en el código como opcionales. +//! It is recommended to initialize all settings with predefined values or use `Option` for +//! optional settings handled within the code. //! -//! Si no pueden inicializarse correctamente los ajustes de configuración, entonces la aplicación -//! ejecutará un panic! y detendrá la ejecución. +//! If configuration settings fail to initialize correctly, the application will panic and stop +//! execution. //! -//! Los ajustes de configuración siempre serán de sólo lectura. +//! Configuration settings are always read-only. //! //! -//! # Cómo usar tus nuevos ajustes de configuración +//! # Using your new configuration settings +//! +//! Access the settings directly in your code: //! //! ``` //! use pagetop::prelude::*; @@ -127,64 +132,58 @@ use crate::config::file::File; use std::sync::LazyLock; use std::env; +use std::path::Path; -/// Directorio donde se encuentran los archivos de configuración. -const CONFIG_DIR: &str = "config"; +/// Original values read from configuration files in `key = value` pairs. +pub static CONFIG_VALUES: LazyLock = LazyLock::new(|| { + // Identify the configuration directory. + let config_dir = env::var("CARGO_MANIFEST_DIR") + .map(|manifest_dir| { + let manifest_config = Path::new(&manifest_dir).join("config"); + if manifest_config.exists() { + manifest_config.to_string_lossy().to_string() + } else { + "config".to_string() + } + }) + .unwrap_or_else(|_| "config".to_string()); -/// Valores originales de la configuración en forma de pares `clave = valor` recogidos de los -/// archivos de configuración. + // Execution mode based on the environment variable PAGETOP_RUN_MODE, defaults to 'default'. + let rm = env::var("PAGETOP_RUN_MODE").unwrap_or_else(|_| "default".into()); -#[rustfmt::skip] -pub static CONFIG_DATA: LazyLock = LazyLock::new(|| { - // Modo de ejecución según la variable de entorno PAGETOP_RUN_MODE. Por defecto 'default'. - let run_mode = env::var("PAGETOP_RUN_MODE").unwrap_or_else(|_| "default".into()); + // Initialize config values. + let mut values = ConfigData::default(); - // Inicializa los ajustes. - let mut settings = ConfigData::default(); + // Merge (optional) configuration files and set the execution mode. + values + // First, add the common configuration for all environments. Defaults to 'common.toml'. + .merge(File::with_name(&concat_string!(config_dir, "/common.toml")).required(false)) + .expect("Failed to merge common configuration (common.toml)") + // Add the environment-specific configuration. Defaults to 'default.toml'. + .merge(File::with_name(&concat_string!(config_dir, "/", rm, ".toml")).required(false)) + .expect(&format!("Failed to merge {rm}.toml configuration")) + // Add reserved local configuration for the environment. Defaults to 'local.default.toml'. + .merge(File::with_name(&concat_string!(config_dir, "/local.", rm, ".toml")).required(false)) + .expect("Failed to merge reserved local environment configuration") + // Add common reserved local configuration. Defaults to 'local.toml'. + .merge(File::with_name(&concat_string!(config_dir, "/local.toml")).required(false)) + .expect("Failed to merge general reserved local configuration") + // Save the execution mode. + .set("app.run_mode", rm) + .expect("Failed to set application run mode"); - // Combina los archivos (opcionales) de configuración y asigna el modo de ejecución. - settings - // Primero añade la configuración común a todos los entornos. Por defecto 'common.toml'. - .merge( - File::with_name(&concat_string!(CONFIG_DIR, "/common.toml")) - .required(false) - ).unwrap() - // Añade la configuración específica del entorno. Por defecto 'default.toml'. - .merge( - File::with_name(&concat_string!(CONFIG_DIR, "/", run_mode, ".toml")) - .required(false) - ).unwrap() - // Añade la configuración local reservada del entorno. Por defecto 'local.default.toml'. - .merge( - File::with_name(&concat_string!(CONFIG_DIR, "/local.", run_mode, ".toml")) - .required(false), - ).unwrap() - // Añade la configuración local reservada general. Por defecto 'local.toml'. - .merge( - File::with_name(&concat_string!(CONFIG_DIR, "/local.toml")) - .required(false) - ).unwrap() - // Salvaguarda el modo de ejecución. - .set("app.run_mode", run_mode) - .unwrap(); - - settings + values }); #[macro_export] -/// Define un conjunto de ajustes de configuración usando tipos seguros y valores predefinidos. -/// -/// Detiene la aplicación con un panic! si no pueden asignarse los ajustes de configuración. -/// -/// Ver [`Cómo añadir ajustes de configuración`](config/index.html#cómo-añadir-ajustes-de-configuración). -macro_rules! config_defaults { +macro_rules! include_config { ( $SETTINGS:ident: $Settings:ty => [ $($key:literal => $value:literal),* $(,)? ] ) => { #[doc = concat!( "Assigned or predefined values for configuration settings associated to the ", "[`", stringify!($Settings), "`] type." )] pub static $SETTINGS: std::sync::LazyLock<$Settings> = std::sync::LazyLock::new(|| { - let mut settings = $crate::config::CONFIG_DATA.clone(); + let mut settings = $crate::config::CONFIG_VALUES.clone(); $( settings.set_default($key, $value).unwrap(); )* diff --git a/src/core.rs b/src/core.rs index 1fe95a5d..cd076eb0 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,6 +1,6 @@ //! Key types and functions for creating actions, components, packages, and themes. -use crate::global::TypeInfo; +use crate::util::TypeInfo; use std::any::Any; diff --git a/src/core/component/context.rs b/src/core/component/context.rs index aea73147..439afb05 100644 --- a/src/core/component/context.rs +++ b/src/core/component/context.rs @@ -3,11 +3,11 @@ use crate::concat_string; use crate::core::component::AnyOp; use crate::core::theme::all::{theme_by_short_name, DEFAULT_THEME}; use crate::core::theme::{ComponentsInRegions, ThemeRef}; -use crate::global::TypeInfo; use crate::html::{html, Markup}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; use crate::locale::{LanguageIdentifier, DEFAULT_LANGID}; use crate::service::HttpRequest; +use crate::util::TypeInfo; use std::collections::HashMap; use std::error::Error; diff --git a/src/core/component/definition.rs b/src/core/component/definition.rs index ee8f1e2f..af33c943 100644 --- a/src/core/component/definition.rs +++ b/src/core/component/definition.rs @@ -1,8 +1,8 @@ use crate::base::action; use crate::core::component::Context; use crate::core::AnyBase; -use crate::global::TypeInfo; use crate::html::{html, Markup, PrepareMarkup}; +use crate::util::TypeInfo; pub trait ComponentBase { fn render(&mut self, cx: &mut Context) -> Markup; diff --git a/src/global.rs b/src/global.rs index 9a6fd2bf..e538958b 100644 --- a/src/global.rs +++ b/src/global.rs @@ -1,115 +1,16 @@ -//! Global settings, functions and macro helpers. +//! Global settings. -use crate::{config_defaults, trace}; +use crate::include_config; use serde::Deserialize; -use std::io; -use std::path::PathBuf; - -// ************************************************************************************************* -// SETTINGS. -// ************************************************************************************************* - -#[derive(Debug, Deserialize)] -/// Configuration settings for global [`[app]`](App), [`[dev]`](Dev), [`[log]`](Log), and -/// [`[server]`](Server) sections (see [`SETTINGS`]). -pub struct Settings { - pub app: App, - pub dev: Dev, - pub log: Log, - pub server: Server, -} - -#[derive(Debug, Deserialize)] -/// Section `[app]` of the configuration settings. -/// -/// See [`Settings`]. -pub struct App { - /// El nombre de la aplicación. - /// Por defecto: *"My App"*. - pub name: String, - /// Una descripción breve de la aplicación. - /// Por defecto: *"Developed with the amazing PageTop framework."*. - pub description: String, - /// Tema predeterminado. - /// Por defecto: *"Default"*. - pub theme: String, - /// Idioma (localización) predeterminado. - /// Por defecto: *"en-US"*. - pub language: String, - /// Dirección predeterminada para el texto: *"ltr"* (de izquierda a derecha), *"rtl"* (de - /// derecha a izquierda) o *"auto"*. - /// Por defecto: *"ltr"*. - pub direction: String, - /// Rótulo de texto ASCII al arrancar: *"Off"*, *"Slant"*, *"Small"*, *"Speed"* o *"Starwars"*. - /// Por defecto: *"Slant"*. - pub startup_banner: String, - /// Por defecto: según variable de entorno `PAGETOP_RUN_MODE`, o *"default"* si no lo está. - pub run_mode: String, -} - -#[derive(Debug, Deserialize)] -/// Section `[dev]` of the configuration settings. -/// -/// See [`Settings`]. -pub struct Dev { - /// Los archivos estáticos requeridos por la aplicación se integran de manera predeterminada en - /// el binario ejecutable. Sin embargo, durante el desarrollo puede resultar útil servir estos - /// archivos desde su propio directorio para evitar recompilar cada vez que se modifican. En - /// este caso bastaría con indicar la ruta completa al directorio raíz del proyecto. - /// Por defecto: *""*. - pub pagetop_project_dir: String, -} - -#[derive(Debug, Deserialize)] -/// Section `[log]` of the configuration settings. -/// -/// See [`Settings`]. -pub struct Log { - /// Filtro, o combinación de filtros separados por coma, para la traza de ejecución: *"Error"*, - /// *"Warn"*, *"Info"*, *"Debug"* o *"Trace"*. - /// Por ejemplo: "Error,actix_server::builder=Info,tracing_actix_web=Debug". - /// Por defecto: *"Info"*. - pub tracing: String, - /// Muestra la traza en el terminal (*"Stdout"*) o queda registrada en archivos con rotación - /// *"Daily"*, *"Hourly"*, *"Minutely"* o *"Endless"*. - /// Por defecto: *"Stdout"*. - pub rolling: String, - /// Directorio para los archivos de traza (si `rolling` != *"Stdout"*). - /// Por defecto: *"log"*. - pub path: String, - /// Prefijo para los archivos de traza (si `rolling` != *"Stdout"*). - /// Por defecto: *"tracing.log"*. - pub prefix: String, - /// Presentación de las trazas. Puede ser *"Full"*, *"Compact"*, *"Pretty"* o *"Json"*. - /// Por defecto: *"Full"*. - pub format: String, -} - -#[derive(Debug, Deserialize)] -/// Section `[server]` of the configuration settings. -/// -/// See [`Settings`]. -pub struct Server { - /// Dirección del servidor web. - /// Por defecto: *"localhost"*. - pub bind_address: String, - /// Puerto del servidor web. - /// Por defecto: *8088*. - pub bind_port: u16, - /// Duración en segundos para la sesión (0 indica "hasta que se cierre el navegador"). - /// Por defecto: *604800* (7 días). - pub session_lifetime: i64, -} - -config_defaults!(SETTINGS: Settings => [ +include_config!(SETTINGS: Settings => [ // [app] "app.name" => "My App", "app.description" => "Developed with the amazing PageTop framework.", - "app.theme" => "Default", + "app.theme" => "", "app.language" => "en-US", - "app.direction" => "ltr", + "app.text_direction" => "ltr", "app.startup_banner" => "Slant", // [dev] @@ -128,161 +29,93 @@ config_defaults!(SETTINGS: Settings => [ "server.session_lifetime" => 604_800, ]); -// ************************************************************************************************* -// FUNCTIONS HELPERS. -// ************************************************************************************************* - -pub enum TypeInfo { - FullName, - ShortName, - NameFrom(isize), - NameTo(isize), - PartialName(isize, isize), +#[derive(Debug, Deserialize)] +/// Configuration settings for the global [`[app]`](App), [`[dev]`](Dev), [`[log]`](Log), and +/// [`[server]`](Server) sections (see [`SETTINGS`]). +pub struct Settings { + pub app: App, + pub dev: Dev, + pub log: Log, + pub server: Server, } -impl TypeInfo { - pub fn of(&self) -> &'static str { - let type_name = std::any::type_name::(); - match self { - TypeInfo::FullName => type_name, - TypeInfo::ShortName => Self::partial(type_name, -1, None), - TypeInfo::NameFrom(start) => Self::partial(type_name, *start, None), - TypeInfo::NameTo(end) => Self::partial(type_name, 0, Some(*end)), - TypeInfo::PartialName(start, end) => Self::partial(type_name, *start, Some(*end)), - } - } - - fn partial(type_name: &'static str, start: isize, end: Option) -> &'static str { - let maxlen = type_name.len(); - let mut segments = Vec::new(); - let mut segment_start = 0; // Start position of the current segment. - let mut angle_brackets = 0; // Counter for tracking '<' and '>'. - let mut previous_char = '\0'; // Initializes to a null character, no previous character. - - for (idx, c) in type_name.char_indices() { - match c { - ':' if angle_brackets == 0 => { - if previous_char == ':' { - if segment_start < idx - 1 { - segments.push((segment_start, idx - 1)); // Do not include last '::'. - } - segment_start = idx + 1; // Next segment starts after '::'. - } - } - '<' => angle_brackets += 1, - '>' => angle_brackets -= 1, - _ => {} - } - previous_char = c; - } - - // Include the last segment if there's any. - if segment_start < maxlen { - segments.push((segment_start, maxlen)); - } - - // Calculates the start position. - let start_pos = segments - .get(if start >= 0 { - start as usize - } else { - segments.len() - start.unsigned_abs() - }) - .map_or(0, |&(s, _)| s); - - // Calculates the end position. - let end_pos = segments - .get(if let Some(end) = end { - if end >= 0 { - end as usize - } else { - segments.len() - end.unsigned_abs() - } - } else { - segments.len() - 1 - }) - .map_or(maxlen, |&(_, e)| e); - - // Returns the partial string based on the calculated positions. - &type_name[start_pos..end_pos] - } +#[derive(Debug, Deserialize)] +/// Section `[app]` of the configuration settings. +/// +/// See [`Settings`]. +pub struct App { + /// The name of the application. + /// Default: *"My App"*. + pub name: String, + /// A brief description of the application. + /// Default: *"Developed with the amazing PageTop framework."*. + pub description: String, + /// Default theme. + /// Default: *""*. + pub theme: String, + /// Default language (localization). + /// Default: *"en-US"*. + pub language: String, + /// Default text direction: *"ltr"* (left-to-right), *"rtl"* (right-to-left), or *"auto"*. + /// Default: *"ltr"*. + pub text_direction: String, + /// ASCII banner printed at startup: *"Off"*, *"Slant"*, *"Small"*, *"Speed"*, or *"Starwars"*. + /// Default: *"Slant"*. + pub startup_banner: String, + /// Default: according to the `PAGETOP_RUN_MODE` environment variable, or *"default"* if unset. + pub run_mode: String, } -/// Calculates the absolute directory given a root path and a relative path. +#[derive(Debug, Deserialize)] +/// Section `[dev]` of the configuration settings. /// -/// # Arguments -/// -/// * `root_path` - A string slice that holds the root path. -/// * `relative_path` - A string slice that holds the relative path. -/// -/// # Returns -/// -/// * `Ok` - If the operation is successful, returns the absolute directory as a `String`. -/// * `Err` - If an I/O error occurs, returns an `io::Error`. -/// -/// # Errors -/// -/// This function will return an error if: -/// - The root path or relative path are invalid. -/// - There is an issue with file system operations, such as reading the directory. -/// -/// # Examples -/// -/// ``` -/// let root = "/home/user"; -/// let relative = "documents"; -/// let abs_dir = absolute_dir(root, relative).unwrap(); -/// println!("{}", abs_dir); -/// ``` -pub fn absolute_dir( - root_path: impl Into, - relative_path: impl Into, -) -> Result { - let root_path = PathBuf::from(root_path.into()); - let full_path = root_path.join(relative_path.into()); - let absolute_dir = full_path.to_string_lossy().into(); - - if !full_path.is_absolute() { - let message = format!("Path \"{absolute_dir}\" is not absolute"); - trace::warn!(message); - return Err(io::Error::new(io::ErrorKind::InvalidInput, message)); - } - - if !full_path.exists() { - let message = format!("Path \"{absolute_dir}\" does not exist"); - trace::warn!(message); - return Err(io::Error::new(io::ErrorKind::NotFound, message)); - } - - if !full_path.is_dir() { - let message = format!("Path \"{absolute_dir}\" is not a directory"); - trace::warn!(message); - return Err(io::Error::new(io::ErrorKind::InvalidInput, message)); - } - - Ok(absolute_dir) +/// See [`Settings`]. +pub struct Dev { + /// Static files required by the application are integrated by default into the executable + /// binary. However, during development, it can be useful to serve these files from their own + /// directory to avoid recompilation every time they are modified. In this case, specify the + /// full path to the project's root directory. + /// Default: *""*. + pub pagetop_project_dir: String, } -// ************************************************************************************************* -// MACRO HELPERS. -// ************************************************************************************************* - -#[macro_export] -/// Macro para construir grupos de pares clave-valor. +#[derive(Debug, Deserialize)] +/// Section `[log]` of the configuration settings. /// -/// ```rust#ignore -/// let args = kv![ -/// "userName" => "Roberto", -/// "photoCount" => 3, -/// "userGender" => "male", -/// ]; -/// ``` -macro_rules! kv { - ( $($key:expr => $value:expr),* $(,)? ) => {{ - let mut a = std::collections::HashMap::new(); - $( - a.insert($key.into(), $value.into()); - )* - a - }}; +/// See [`Settings`]. +pub struct Log { + /// Filter, or a comma-separated combination of filters, for execution traces: *"Error"*, + /// *"Warn"*, *"Info"*, *"Debug"*, or *"Trace"*. + /// Example: "Error,actix_server::builder=Info,tracing_actix_web=Debug". + /// Default: *"Info"*. + pub tracing: String, + /// Displays traces in the terminal (*"Stdout"*) or logs them in files with rotation: *"Daily"*, + /// *"Hourly"*, *"Minutely"*, or *"Endless"*. + /// Default: *"Stdout"*. + pub rolling: String, + /// Directory for trace files (if `rolling` != *"Stdout"*). + /// Default: *"log"*. + pub path: String, + /// Prefix for trace files (if `rolling` != *"Stdout"*). + /// Default: *"tracing.log"*. + pub prefix: String, + /// Trace output format. Options are *"Full"*, *"Compact"*, *"Pretty"*, or *"Json"*. + /// Default: *"Full"*. + pub format: String, +} + +#[derive(Debug, Deserialize)] +/// Section `[server]` of the configuration settings. +/// +/// See [`Settings`]. +pub struct Server { + /// Web server bind address. + /// Default: *"localhost"*. + pub bind_address: String, + /// Web server bind port. + /// Default: *8088*. + pub bind_port: u16, + /// Session cookie duration in seconds (0 means "until the browser is closed"). + /// Default: *604800* (7 days). + pub session_lifetime: i64, } diff --git a/src/lib.rs b/src/lib.rs index 33ebd83c..ad7b24f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,9 +72,7 @@ #![cfg_attr(docsrs, feature(doc_cfg))] -// ************************************************************************************************* -// RE-EXPORTED MACROS AND DERIVES. -// ************************************************************************************************* +// RE-EXPORTED ************************************************************************************* pub use concat_string::concat_string; @@ -95,17 +93,16 @@ pub use std::any::TypeId; pub type Weight = i8; -// Global settings, functions and macro helpers. -pub mod global; - include_locales!(LOCALES_PAGETOP); -// ************************************************************************************************* -// PUBLIC API. -// ************************************************************************************************* +// API ********************************************************************************************* -// Retrieve and apply settings values from configuration files. +// Useful functions and macros. +pub mod util; +// Load configuration settings. pub mod config; +// Global settings. +pub mod global; // Application tracing and event logging. pub mod trace; // HTML in code. @@ -114,24 +111,17 @@ pub mod html; pub mod locale; // Date and time handling. pub mod datetime; - // Essential web framework. pub mod service; - // Key types and functions for creating actions, components, packages, and themes. pub mod core; - // Web request response variants. pub mod response; - // Base actions, components, packages, and themes. pub mod base; - // Prepare and run the application. pub mod app; -// ************************************************************************************************* -// The PageTop Prelude. -// ************************************************************************************************* +// The PageTop Prelude ***************************************************************************** pub mod prelude; diff --git a/src/prelude.rs b/src/prelude.rs index 932081ec..32026973 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -5,14 +5,14 @@ pub use crate::{concat_string, fn_builder, main, paste, test}; pub use crate::{AutoDefault, ComponentClasses}; // GLOBAL. -pub use crate::{global, HashMapResources, TypeId, Weight}; +pub use crate::{HashMapResources, TypeId, Weight}; // MACROS. // crate::global pub use crate::kv; // crate::config -pub use crate::config_defaults; +pub use crate::include_config; // crate::html pub use crate::html; // crate::locale @@ -24,6 +24,10 @@ pub use crate::actions; // API. +pub use crate::util; + +pub use crate::global; + pub use crate::trace; pub use crate::html::*; diff --git a/src/service.rs b/src/service.rs index 379472e3..1f67ca6f 100644 --- a/src/service.rs +++ b/src/service.rs @@ -42,7 +42,7 @@ macro_rules! static_files_service { let mut serve_embedded:bool = true; $( if !$root.is_empty() && !$relative.is_empty() { - if let Ok(absolute) = $crate::global::absolute_dir($root, $relative) { + if let Ok(absolute) = $crate::util::absolute_dir($root, $relative) { $scfg.service($crate::service::ActixFiles::new( $path, absolute, diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 00000000..a34ee599 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,161 @@ +//! Useful functions and macros. + +use crate::trace; + +use std::io; +use std::path::PathBuf; + +// USEFUL FUNCTIONS ******************************************************************************** + +pub enum TypeInfo { + FullName, + ShortName, + NameFrom(isize), + NameTo(isize), + PartialName(isize, isize), +} + +impl TypeInfo { + pub fn of(&self) -> &'static str { + let type_name = std::any::type_name::(); + match self { + TypeInfo::FullName => type_name, + TypeInfo::ShortName => Self::partial(type_name, -1, None), + TypeInfo::NameFrom(start) => Self::partial(type_name, *start, None), + TypeInfo::NameTo(end) => Self::partial(type_name, 0, Some(*end)), + TypeInfo::PartialName(start, end) => Self::partial(type_name, *start, Some(*end)), + } + } + + fn partial(type_name: &'static str, start: isize, end: Option) -> &'static str { + let maxlen = type_name.len(); + let mut segments = Vec::new(); + let mut segment_start = 0; // Start position of the current segment. + let mut angle_brackets = 0; // Counter for tracking '<' and '>'. + let mut previous_char = '\0'; // Initializes to a null character, no previous character. + + for (idx, c) in type_name.char_indices() { + match c { + ':' if angle_brackets == 0 => { + if previous_char == ':' { + if segment_start < idx - 1 { + segments.push((segment_start, idx - 1)); // Do not include last '::'. + } + segment_start = idx + 1; // Next segment starts after '::'. + } + } + '<' => angle_brackets += 1, + '>' => angle_brackets -= 1, + _ => {} + } + previous_char = c; + } + + // Include the last segment if there's any. + if segment_start < maxlen { + segments.push((segment_start, maxlen)); + } + + // Calculates the start position. + let start_pos = segments + .get(if start >= 0 { + start as usize + } else { + segments.len() - start.unsigned_abs() + }) + .map_or(0, |&(s, _)| s); + + // Calculates the end position. + let end_pos = segments + .get(if let Some(end) = end { + if end >= 0 { + end as usize + } else { + segments.len() - end.unsigned_abs() + } + } else { + segments.len() - 1 + }) + .map_or(maxlen, |&(_, e)| e); + + // Returns the partial string based on the calculated positions. + &type_name[start_pos..end_pos] + } +} + +/// Calculates the absolute directory given a root path and a relative path. +/// +/// # Arguments +/// +/// * `root_path` - A string slice that holds the root path. +/// * `relative_path` - A string slice that holds the relative path. +/// +/// # Returns +/// +/// * `Ok` - If the operation is successful, returns the absolute directory as a `String`. +/// * `Err` - If an I/O error occurs, returns an `io::Error`. +/// +/// # Errors +/// +/// This function will return an error if: +/// - The root path or relative path are invalid. +/// - There is an issue with file system operations, such as reading the directory. +/// +/// # Examples +/// +/// ``` +/// let root = "/home/user"; +/// let relative = "documents"; +/// let abs_dir = absolute_dir(root, relative).unwrap(); +/// println!("{}", abs_dir); +/// ``` +pub fn absolute_dir( + root_path: impl Into, + relative_path: impl Into, +) -> Result { + let root_path = PathBuf::from(root_path.into()); + let full_path = root_path.join(relative_path.into()); + let absolute_dir = full_path.to_string_lossy().into(); + + if !full_path.is_absolute() { + let message = format!("Path \"{absolute_dir}\" is not absolute"); + trace::warn!(message); + return Err(io::Error::new(io::ErrorKind::InvalidInput, message)); + } + + if !full_path.exists() { + let message = format!("Path \"{absolute_dir}\" does not exist"); + trace::warn!(message); + return Err(io::Error::new(io::ErrorKind::NotFound, message)); + } + + if !full_path.is_dir() { + let message = format!("Path \"{absolute_dir}\" is not a directory"); + trace::warn!(message); + return Err(io::Error::new(io::ErrorKind::InvalidInput, message)); + } + + Ok(absolute_dir) +} + +// USEFUL MACROS *********************************************************************************** + +#[macro_export] +/// Macro para construir grupos de pares clave-valor. +/// +/// ```rust#ignore +/// let args = kv![ +/// "userName" => "Roberto", +/// "photoCount" => 3, +/// "userGender" => "male", +/// ]; +/// ``` +macro_rules! kv { + ( $($key:expr => $value:expr),* $(,)? ) => {{ + let mut a = std::collections::HashMap::new(); + $( + a.insert($key.into(), $value.into()); + )* + a + }}; +}