♻️ Refactoring config parsing & theme integration

This commit is contained in:
Manuel Cillero 2024-11-16 18:52:02 +01:00
parent c26432d58c
commit cafa1d53a2
28 changed files with 716 additions and 589 deletions

View file

@ -42,6 +42,7 @@ actix-web-files = { package = "actix-files", version = "0.6" }
actix-web-static-files = "4.0"
actix-session = { version = "0.10", features = ["cookie-session"] }
fluent-templates = "0.11"
fluent-bundle = "0.15"
nom = "7.1"
substring = "1.4"
tracing = "0.1"

View file

@ -0,0 +1,37 @@
[app]
name = "My App"
description = "Developed with the amazing PageTop framework."
# Default theme.
theme = ""
# Default language (localization).
language = "en-US"
# Default text direction: "ltr", "rtl", or "auto".
text_direction = "ltr"
# Banner displayed at startup: "Off", "Slant", "Small", "Speed", or "Starwars".
startup_banner = "Slant"
[dev]
# During development, serve static files from the project's root directory to
# avoid recompilation.
pagetop_project_dir = ""
[log]
# Execution trace level: "Error", "Warn", "Info", "Debug", or "Trace".
# Example: tracing = "Error,actix_server::builder=Info,tracing_actix_web=Debug"
tracing = "Info"
# In terminal ("Stdout") or files "Daily", "Hourly", "Minutely", or "Endless".
rolling = "Stdout"
# Directory for trace files (if rolling != "Stdout").
path = "log"
# Prefix for trace files (if rolling != "Stdout").
prefix = "tracing.log"
# Traces format: "Full", "Compact", "Pretty", or "Json".
format = "Full"
[server]
# Web server config.
bind_address = "localhost"
bind_port = 8088
# If cookies are used, specify the session cookie duration (in seconds). A value
# of 0 means "until the browser is closed". Default: one week.
session_lifetime = 604800

View file

@ -1,40 +0,0 @@
[app]
name = "My App"
description = "Developed with the amazing PageTop framework."
# Default theme.
theme = "Default"
# Default language (localization).
language = "en-US"
# Default text direction: "ltr", "rtl", or "auto".
direction = "ltr"
# Startup banner: "Off", "Slant", "Small", "Speed", or "Starwars".
startup_banner = "Slant"
[dev]
# Static files required by the app 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 recompiling every time they are modified. In
# this case, just indicate the full path to the project's root directory.
pagetop_project_dir = ""
[log]
# Execution trace: "Error", "Warn", "Info", "Debug", or "Trace".
# For example: "Error,actix_server::builder=Info,tracing_actix_web=Debug".
tracing = "Info"
# In terminal ("Stdout") or files "Daily", "Hourly", "Minutely", or "Endless".
rolling = "Stdout"
# Directory for trace files (if rolling != "Stdout").
path = "log"
# Prefix for trace files (if rolling != "Stdout").
prefix = "tracing.log"
# Traces format: "Full", "Compact", "Pretty", or "Json".
format = "Full"
[server]
# Web server config.
bind_address = "localhost"
bind_port = 8088
# Session cookie duration (in seconds), i.e., the time from when the session is
# created until the cookie expires. A value of 0 indicates "until the browser is
# closed". By default, it is one week.
session_lifetime = 604800

View file

@ -1,9 +1,6 @@
[app]
#theme = "Basic"
#theme = "Chassis"
theme = "Inception"
#theme = "Bootsier"
#theme = "Bulmix"
language = "es-ES"
[log]

View file

@ -1,196 +0,0 @@
//! Retrieve and apply settings values from configuration files.
//!
//! Carga la configuración de la aplicación en forma de pares `clave = valor` recogidos en archivos
//! [TOML](https://toml.io).
//!
//! 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.
//!
//!
//! # Cómo cargar los ajustes de configuración
//!
//! 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).
//!
//! `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):
//!
//! 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.
//!
//! 2. **config/{file}.toml**, donde *{file}* se define con la variable de entorno
//! `PAGETOP_RUN_MODE`:
//!
//! * Si no está definida se asumirá *default* por defecto y `PageTop` intentará cargar el
//! archivo *config/default.toml* si existe.
//!
//! * 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.
//!
//! * 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.toml**, para añadir o sobrescribir ajustes de los archivos anteriores.
//!
//!
//! # Cómo añadir ajustes de configuración
//!
//! 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`:
//!
//! ```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:
//!
//! ```
//! use pagetop::prelude::*;
//! use serde::Deserialize;
//!
//! #[derive(Debug, Deserialize)]
//! pub struct Settings {
//! pub myapp: MyApp,
//! }
//!
//! #[derive(Debug, Deserialize)]
//! pub struct MyApp {
//! pub name: String,
//! pub description: Option<String>,
//! 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`]).
//!
//! 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`]).
//!
//! Se recomienda inicializar todos los ajustes con valores predefinidos, o utilizar la notación
//! `Option<T>` si van a ser tratados en el código como opcionales.
//!
//! Si no pueden inicializarse correctamente los ajustes de configuración, entonces la aplicación
//! ejecutará un panic! y detendrá la ejecución.
//!
//! Los ajustes de configuración siempre serán de sólo lectura.
//!
//!
//! # Cómo usar tus nuevos ajustes de configuración
//!
//! ```
//! use pagetop::prelude::*;
//! use crate::config;
//!
//! fn global_settings() {
//! println!("App name: {}", &global::SETTINGS.app.name);
//! println!("App description: {}", &global::SETTINGS.app.description);
//! println!("Value of PAGETOP_RUN_MODE: {}", &global::SETTINGS.app.run_mode);
//! }
//!
//! fn package_settings() {
//! println!("{} - {:?}", &config::SETTINGS.myapp.name, &config::SETTINGS.myapp.description);
//! println!("{}", &config::SETTINGS.myapp.width);
//! }
//! ```
mod data;
mod de;
mod error;
mod file;
mod path;
mod source;
mod value;
use crate::config::data::ConfigData;
use crate::config::file::File;
use crate::join;
use std::sync::LazyLock;
use std::env;
use std::path::Path;
/// Original configuration values in `key = value` pairs gathered from configuration files.
pub static CONFIG_DATA: LazyLock<ConfigData> = 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());
// Execution mode based on the environment variable PAGETOP_RUN_MODE, defaults to 'default'.
let run_mode = env::var("PAGETOP_RUN_MODE").unwrap_or_else(|_| "default".into());
// Initialize settings.
let mut settings = ConfigData::default();
// Merge (optional) configuration files and set the execution mode.
settings
// First, add the common configuration for all environments. Defaults to 'common.toml'.
.merge(File::with_name(&join!(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(&join!(config_dir, "/", run_mode, ".toml")).required(false))
.expect(&format!("Failed to merge {run_mode}.toml configuration"))
// Add reserved local configuration for the environment. Defaults to 'local.default.toml'.
.merge(File::with_name(&join!(config_dir, "/local.", run_mode, ".toml")).required(false))
.expect("Failed to merge reserved local environment configuration")
// Add the general reserved local configuration. Defaults to 'local.toml'.
.merge(File::with_name(&join!(config_dir, "/local.toml")).required(false))
.expect("Failed to merge general reserved local configuration")
// Save the execution mode.
.set("app.run_mode", run_mode)
.expect("Failed to set application run mode");
settings
});
#[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 {
( $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();
$(
settings.set_default($key, $value).unwrap();
)*
match settings.try_into() {
Ok(s) => s,
Err(e) => panic!("Error parsing settings: {}", e),
}
});
};
}

View file

@ -1,17 +1,22 @@
//! Key types and functions for creating actions, components, packages, and themes.
//! Key types and functions for creating actions, packages, and themes.
use crate::global::TypeInfo;
use crate::util::TypeInfo;
use std::any::Any;
// Common definitions for core types.
/// A base trait that extends `Any` to provide metadata and dynamic type casting
/// capabilities.
pub trait AnyBase: Any {
/// Returns the full name of the type.
fn type_name(&self) -> &'static str;
/// Returns a short name for the type.
fn short_name(&self) -> &'static str;
/// Returns a reference to `dyn Any` for dynamic type casting.
fn as_any_ref(&self) -> &dyn Any;
/// Returns a mutable reference to `dyn Any` for dynamic type casting.
fn as_any_mut(&mut self) -> &mut dyn Any;
}
@ -38,7 +43,9 @@ impl<T: Any> AnyBase for T {
}
}
/// A trait for advanced dynamic type manipulation and downcasting.
pub trait AnyTo: AnyBase {
/// Checks if the type is of the specified type `T`.
#[inline]
fn is<T>(&self) -> bool
where
@ -47,6 +54,7 @@ pub trait AnyTo: AnyBase {
self.as_any_ref().is::<T>()
}
/// Attempts to downcast a reference to the specified type `T`.
#[inline]
fn downcast_ref<T>(&self) -> Option<&T>
where
@ -55,6 +63,7 @@ pub trait AnyTo: AnyBase {
self.as_any_ref().downcast_ref()
}
/// Attempts to downcast a mutable reference to the specified type `T`.
#[inline]
fn downcast_mut<T>(&mut self) -> Option<&mut T>
where
@ -66,8 +75,11 @@ pub trait AnyTo: AnyBase {
impl<T: ?Sized + AnyBase> AnyTo for T {}
// API to define functions that alter the behavior of PageTop core.
// API to define functions that modify the predefined behavior of the code.
pub mod action;
// API to add new features with packages.
pub mod package;
// API to add new layouts with themes.
pub mod theme;

View file

@ -1,5 +1,6 @@
use crate::core::action::add_action;
use crate::core::package::PackageRef;
use crate::core::theme::all::THEMES;
use crate::{service, trace};
use std::sync::{LazyLock, RwLock};
@ -60,7 +61,7 @@ fn add_to_enabled(list: &mut Vec<PackageRef>, package: PackageRef) {
add_to_enabled(list, *d);
}
/* Check if the package has an associated theme to register.
// Check if the package has an associated theme to register.
if let Some(theme) = package.theme() {
let mut registered_themes = THEMES.write().unwrap();
// Ensure the theme is not already registered to avoid duplicates.
@ -73,7 +74,7 @@ fn add_to_enabled(list: &mut Vec<PackageRef>, package: PackageRef) {
}
} else {
trace::debug!("Enabling \"{}\" package", package.short_name());
} */
}
}
}

View file

@ -1,4 +1,5 @@
use crate::core::action::ActionBox;
use crate::core::theme::ThemeRef;
use crate::core::AnyBase;
use crate::locale::L10n;
use crate::{actions, service};
@ -15,6 +16,10 @@ pub trait PackageTrait: AnyBase + Send + Sync {
L10n::none()
}
fn theme(&self) -> Option<ThemeRef> {
None
}
fn dependencies(&self) -> Vec<PackageRef> {
vec![]
}

View file

@ -0,0 +1,8 @@
mod definition;
pub use definition::{ThemeRef, ThemeTrait};
/*
mod regions;
pub(crate) use regions::ComponentsInRegions;
pub use regions::InRegion;
*/
pub(crate) mod all;

View file

@ -0,0 +1,32 @@
use crate::core::theme::ThemeRef;
//use crate::global;
use std::sync::{LazyLock, RwLock};
// THEMES ******************************************************************************************
pub static THEMES: LazyLock<RwLock<Vec<ThemeRef>>> = LazyLock::new(|| RwLock::new(Vec::new()));
// DEFAULT THEME ***********************************************************************************
/*
pub static THEME_DEFAULT: LazyLock<ThemeRef> =
LazyLock::new(|| match theme_by_short_name(&global::SETTINGS.app.theme) {
Some(theme) => theme,
None => &crate::base::theme::Inception,
});
*/
// THEME BY NAME ***********************************************************************************
/*
pub fn theme_by_short_name(short_name: &str) -> Option<ThemeRef> {
let short_name = short_name.to_lowercase();
match THEMES
.read()
.unwrap()
.iter()
.find(|t| t.short_name().to_lowercase() == short_name)
{
Some(theme) => Some(*theme),
_ => None,
}
}
*/

View file

@ -0,0 +1,104 @@
use crate::core::package::PackageTrait;
pub type ThemeRef = &'static dyn ThemeTrait;
/// Los temas deben implementar este "trait".
pub trait ThemeTrait: PackageTrait + Send + Sync {
/*
#[rustfmt::skip]
fn regions(&self) -> Vec<(&'static str, L10n)> {
vec![
("header", L10n::l("header")),
("pagetop", L10n::l("pagetop")),
("sidebar_left", L10n::l("sidebar_left")),
("content", L10n::l("content")),
("sidebar_right", L10n::l("sidebar_right")),
("footer", L10n::l("footer")),
]
}
#[allow(unused_variables)]
fn before_prepare_body(&self, page: &mut Page) {}
fn prepare_body(&self, page: &mut Page) -> PrepareMarkup {
let skip_to_id = page.body_skip_to().get().unwrap_or("content".to_owned());
PrepareMarkup::With(html! {
body id=[page.body_id().get()] class=[page.body_classes().get()] {
@if let Some(skip) = L10n::l("skip_to_content").using(page.context().langid()) {
div class="skip__to_content" {
a href=(concat_string!("#", skip_to_id)) { (skip) }
}
}
(flex::Container::new()
.with_id("body__wrapper")
.with_direction(flex::Direction::Column(BreakPoint::None))
.with_align(flex::Align::Center)
.add_item(flex::Item::region().with_id("header"))
.add_item(flex::Item::region().with_id("pagetop"))
.add_item(
flex::Item::with(
flex::Container::new()
.with_direction(flex::Direction::Row(BreakPoint::None))
.add_item(
flex::Item::region()
.with_id("sidebar_left")
.with_grow(flex::Grow::Is1),
)
.add_item(
flex::Item::region()
.with_id("content")
.with_grow(flex::Grow::Is3),
)
.add_item(
flex::Item::region()
.with_id("sidebar_right")
.with_grow(flex::Grow::Is1),
),
)
.with_id("flex__wrapper"),
)
.add_item(flex::Item::region().with_id("footer"))
.render(page.context()))
}
})
}
fn after_prepare_body(&self, page: &mut Page) {
page.set_assets(AssetsOp::SetFaviconIfNone(
Favicon::new().with_icon("/base/favicon.ico"),
));
}
fn prepare_head(&self, page: &mut Page) -> PrepareMarkup {
let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no";
PrepareMarkup::With(html! {
head {
meta charset="utf-8";
@if let Some(title) = page.title() {
title { (global::SETTINGS.app.name) (" - ") (title) }
} @else {
title { (global::SETTINGS.app.name) }
}
@if let Some(description) = page.description() {
meta name="description" content=(description);
}
meta name="viewport" content=(viewport);
@for (name, content) in page.metadata() {
meta name=(name) content=(content) {}
}
meta http-equiv="X-UA-Compatible" content="IE=edge";
@for (property, content) in page.properties() {
meta property=(property) content=(content) {}
}
(page.context().prepare_assets())
}
})
}
*/
}

View file

@ -1,115 +1,16 @@
//! Global settings, functions and macro helpers.
//! Global settings.
use crate::{config_defaults, trace};
use crate::static_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 => [
static_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<T: ?Sized>(&self) -> &'static str {
let type_name = std::any::type_name::<T>();
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<isize>) -> &'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<String>,
relative_path: impl Into<String>,
) -> Result<String, io::Error> {
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,
}

View file

@ -73,7 +73,7 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
// *************************************************************************************************
// RE-EXPORTED MACROS AND DERIVES.
// RE-EXPORTED.
// *************************************************************************************************
pub use concat_string::concat_string as join;
@ -83,43 +83,30 @@ pub use paste::paste;
pub use pagetop_macros::{main, test, AutoDefault};
// *************************************************************************************************
// GLOBAL.
// *************************************************************************************************
pub use static_files::Resource as StaticResource;
pub type HashMapResources = std::collections::HashMap<&'static str, StaticResource>;
pub type StaticResources = std::collections::HashMap<&'static str, static_files::Resource>;
pub use std::any::TypeId;
pub type Weight = i8;
// Global settings, functions and macro helpers.
pub mod global;
static_locales!(LOCALES_PAGETOP);
// *************************************************************************************************
// PUBLIC API.
// API.
// *************************************************************************************************
// Retrieve and apply settings values from configuration files.
pub mod config;
// Useful functions and macros.
pub mod util;
// Application tracing and event logging.
pub mod trace;
// Localization.
pub mod locale;
// 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;
// Global settings.
pub mod global;
// Prepare and run the application.
pub mod app;

View file

@ -1,21 +1,19 @@
//! Localization (L10n).
//!
//! PageTop uses the [Fluent](https://www.projectfluent.org/) set of specifications for application
//! localization.
//! PageTop uses the [Fluent](https://www.projectfluent.org/) specifications for application
//! localization, leveraging the [fluent-templates](https://docs.rs/fluent-templates/) crate to
//! integrate translation resources directly into the application binary.
//!
//! # Fluent Syntax (FTL)
//!
//! The format used to describe the translation resources used by Fluent is called
//! [FTL](https://www.projectfluent.org/fluent/guide/). FTL is designed to be easy to read while
//! simultaneously allowing the representation of complex natural language concepts to address
//! gender, plurals, conjugations, and others.
//! [FTL](https://www.projectfluent.org/fluent/guide/). FTL is designed to be both readable and
//! expressive, enabling complex natural language constructs like gender, plurals, and conjugations.
//!
//! # Fluent Resources
//!
//! PageTop utilizes [fluent-templates](https://docs.rs/fluent-templates/) to integrate localization
//! resources into the application binary. The following example groups files and subfolders from
//! *src/locale* that have a valid [Unicode Language Identifier](https://docs.rs/unic-langid/) and
//! assigns them to their corresponding identifier:
//! Localization resources are organized in the *src/locale* directory, with subdirectories for
//! each valid [Unicode Language Identifier](https://docs.rs/unic-langid/):
//!
//! ```text
//! src/locale/
@ -86,8 +84,9 @@
//! static_locales!(LOCALES_SAMPLE in "path/to/locale");
//! ```
use crate::{global, kv, AutoDefault, LOCALES_PAGETOP};
use crate::{global, kv, AutoDefault};
pub use fluent_bundle::FluentValue;
pub use fluent_templates;
pub use unic_langid::LanguageIdentifier;
@ -103,6 +102,8 @@ use std::fmt;
const LANGUAGE_SET_FAILURE: &str = "language_set_failure";
/// A mapping between language codes (e.g., "en-US") and their corresponding [`LanguageIdentifier`]
/// and human-readable names.
static LANGUAGES: LazyLock<HashMap<String, (LanguageIdentifier, &str)>> = LazyLock::new(|| {
kv![
"en" => (langid!("en-US"), "English"),
@ -118,28 +119,23 @@ pub static LANGID_FALLBACK: LazyLock<LanguageIdentifier> = LazyLock::new(|| lang
/// Sets the application's default
/// [Unicode Language Identifier](https://unicode.org/reports/tr35/tr35.html#Unicode_language_identifier)
/// through `SETTINGS.app.language`.
pub static LANGID_DEFAULT: LazyLock<&LanguageIdentifier> = LazyLock::new(|| {
langid_for(global::SETTINGS.app.language.as_str()).unwrap_or(&LANGID_FALLBACK)
});
pub static LANGID_DEFAULT: LazyLock<&LanguageIdentifier> =
LazyLock::new(|| langid_for(&global::SETTINGS.app.language).unwrap_or(&LANGID_FALLBACK));
pub fn langid_for(language: impl Into<String>) -> Result<&'static LanguageIdentifier, String> {
let language = language.into();
match LANGUAGES.get(language.as_str()) {
Some((langid, _)) => Ok(langid),
None => {
if language.is_empty() {
Ok(&LANGID_FALLBACK)
} else {
Err(format!(
"No langid for Unicode Language Identifier \"{language}\".",
))
}
}
if language.is_empty() {
return Ok(&LANGID_FALLBACK);
}
LANGUAGES
.get(&language)
.map(|(langid, _)| langid)
.ok_or_else(|| format!("No langid for Unicode Language Identifier \"{language}\"."))
}
#[macro_export]
/// Defines a set of localization elements and local translation texts.
/// Defines a set of localization elements and local translation texts, removing Unicode isolating
/// marks around arguments to improve readability and compatibility in certain rendering contexts.
macro_rules! static_locales {
( $LOCALES:ident $(, $core_locales:literal)? ) => {
$crate::locale::fluent_templates::static_loader! {
@ -165,6 +161,8 @@ macro_rules! static_locales {
};
}
static_locales!(LOCALES_PAGETOP);
#[derive(AutoDefault)]
enum L10nOp {
#[default]
@ -177,7 +175,7 @@ enum L10nOp {
pub struct L10n {
op: L10nOp,
locales: Option<&'static Locales>,
args: HashMap<String, String>,
args: HashMap<String, FluentValue<'static>>,
}
impl L10n {
@ -209,7 +207,8 @@ impl L10n {
}
pub fn with_arg(mut self, arg: impl Into<String>, value: impl Into<String>) -> Self {
self.args.insert(arg.into(), value.into());
self.args
.insert(arg.into(), FluentValue::from(value.into()));
self
}
@ -222,17 +221,7 @@ impl L10n {
if self.args.is_empty() {
locales.try_lookup(langid, key)
} else {
locales.try_lookup_with_args(
langid,
key,
&self
.args
.iter()
.fold(HashMap::new(), |mut args, (key, value)| {
args.insert(key.to_string(), value.to_owned().into());
args
}),
)
locales.try_lookup_with_args(langid, key, &self.args)
}
}
None => None,
@ -266,13 +255,7 @@ impl fmt::Display for L10n {
_ => &LANGID_DEFAULT,
},
key,
&self
.args
.iter()
.fold(HashMap::new(), |mut args, (key, value)| {
args.insert(key.to_string(), value.to_owned().into());
args
}),
&self.args,
)
}
)

View file

@ -1,17 +1,15 @@
//! The `PageTop` Prelude.
// RE-EXPORTED.
pub use crate::{join, main, paste, test, AutoDefault};
// GLOBAL.
pub use crate::{global, HashMapResources, TypeId, Weight};
pub use crate::{join, main, paste, test};
pub use crate::{AutoDefault, StaticResources, TypeId, Weight};
// MACROS.
// crate::global
pub use crate::kv;
// crate::config
pub use crate::config_defaults;
// crate::util
pub use crate::{kv, static_config};
// crate::locale
pub use crate::static_locales;
// crate::service
@ -21,6 +19,8 @@ pub use crate::actions;
// API.
pub use crate::util;
pub use crate::trace;
pub use crate::locale::*;
@ -35,4 +35,6 @@ pub use crate::core::package::*;
pub use crate::response::{json::*, redirect::*, ResponseError};
pub use crate::global;
pub use crate::app::Application;

View file

@ -26,7 +26,7 @@ macro_rules! static_files {
mod [<static_files_ $bundle>] {
include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs"));
}
static $STATIC: std::sync::LazyLock<HashMapResources> = std::sync::LazyLock::new(
static $STATIC: std::sync::LazyLock<StaticResources> = std::sync::LazyLock::new(
[<static_files_ $bundle>]::$bundle
);
}
@ -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,

View file

@ -0,0 +1,310 @@
//! Useful functions and macros.
pub mod config;
mod data;
mod de;
mod error;
mod file;
mod path;
mod source;
mod value;
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<T: ?Sized>(&self) -> &'static str {
let type_name = std::any::type_name::<T>();
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<isize>) -> &'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<String>,
relative_path: impl Into<String>,
) -> Result<String, io::Error> {
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
}};
}
#[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.
///
/// Carga la configuración de la aplicación en forma de pares `clave = valor` recogidos en archivos
/// [TOML](https://toml.io).
///
/// 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.
///
///
/// # Cómo cargar los ajustes de configuración
///
/// 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).
///
/// `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):
///
/// 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.
///
/// 2. **config/{file}.toml**, donde *{file}* se define con la variable de entorno
/// `PAGETOP_RUN_MODE`:
///
/// * Si no está definida se asumirá *default* por defecto y `PageTop` intentará cargar el
/// archivo *config/default.toml* si existe.
///
/// * 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.
///
/// * 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.toml**, para añadir o sobrescribir ajustes de los archivos anteriores.
///
///
/// # Cómo añadir ajustes de configuración
///
/// 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`:
///
/// ```toml
/// [dependencies]
/// serde = { version = "1.0", features = ["derive"] }
/// ```
///
/// Y luego inicializa con la macro [`static_config!`](crate::static_config) tus ajustes, usando
/// tipos seguros y asignando los valores predefinidos para la estructura asociada:
///
/// ```
/// use pagetop::prelude::*;
/// use serde::Deserialize;
///
/// #[derive(Debug, Deserialize)]
/// pub struct Settings {
/// pub myapp: MyApp,
/// }
///
/// #[derive(Debug, Deserialize)]
/// pub struct MyApp {
/// pub name: String,
/// pub description: Option<String>,
/// pub width: u16,
/// pub height: u16,
/// }
///
/// static_config!(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`]).
///
/// 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`]).
///
/// Se recomienda inicializar todos los ajustes con valores predefinidos, o utilizar la notación
/// `Option<T>` si van a ser tratados en el código como opcionales.
///
/// Si no pueden inicializarse correctamente los ajustes de configuración, entonces la aplicación
/// ejecutará un panic! y detendrá la ejecución.
///
/// Los ajustes de configuración siempre serán de sólo lectura.
///
///
/// # Cómo usar tus nuevos ajustes de configuración
///
/// ```
/// use pagetop::prelude::*;
/// use crate::config;
///
/// fn global_settings() {
/// println!("App name: {}", &global::SETTINGS.app.name);
/// println!("App description: {}", &global::SETTINGS.app.description);
/// println!("Value of PAGETOP_RUN_MODE: {}", &global::SETTINGS.app.run_mode);
/// }
///
/// fn package_settings() {
/// println!("{} - {:?}", &config::SETTINGS.myapp.name, &config::SETTINGS.myapp.description);
/// println!("{}", &config::SETTINGS.myapp.width);
/// }
/// ```
macro_rules! static_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::util::config::CONFIG_DATA.clone();
$(
settings.set_default($key, $value).unwrap();
)*
match settings.try_into() {
Ok(s) => s,
Err(e) => panic!("Error parsing settings: {}", e),
}
});
};
}

View file

@ -0,0 +1,51 @@
//! Retrieve settings values from configuration files.
use crate::join;
use crate::util::data::ConfigData;
use crate::util::file::File;
use std::sync::LazyLock;
use std::env;
use std::path::Path;
/// Original configuration values in `key = value` pairs gathered from configuration files.
pub static CONFIG_DATA: LazyLock<ConfigData> = 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());
// Execution mode based on the environment variable PAGETOP_RUN_MODE, defaults to 'default'.
let run_mode = env::var("PAGETOP_RUN_MODE").unwrap_or_else(|_| "default".into());
// Initialize settings.
let mut settings = ConfigData::default();
// Merge (optional) configuration files and set the execution mode.
settings
// First, add the common configuration for all environments. Defaults to 'common.toml'.
.merge(File::with_name(&join!(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(&join!(config_dir, "/", run_mode, ".toml")).required(false))
.expect(&format!("Failed to merge {run_mode}.toml configuration"))
// Add reserved local configuration for the environment. Defaults to 'local.default.toml'.
.merge(File::with_name(&join!(config_dir, "/local.", run_mode, ".toml")).required(false))
.expect("Failed to merge reserved local environment configuration")
// Add the general reserved local configuration. Defaults to 'local.toml'.
.merge(File::with_name(&join!(config_dir, "/local.toml")).required(false))
.expect("Failed to merge general reserved local configuration")
// Save the execution mode.
.set("app.run_mode", run_mode)
.expect("Failed to set application run mode");
settings
});

View file

@ -1,7 +1,7 @@
use crate::config::error::*;
use crate::config::path;
use crate::config::source::Source;
use crate::config::value::Value;
use crate::util::error::*;
use crate::util::path;
use crate::util::source::Source;
use crate::util::value::Value;
use serde::de::Deserialize;

View file

@ -1,6 +1,6 @@
use crate::config::data::ConfigData;
use crate::config::error::*;
use crate::config::value::{Table, Value, ValueKind};
use crate::util::data::ConfigData;
use crate::util::error::*;
use crate::util::value::{Table, Value, ValueKind};
use serde::de;
use serde::forward_to_deserialize_any;

View file

@ -1,9 +1,9 @@
mod source;
mod toml;
use crate::config::error::*;
use crate::config::source::Source;
use crate::config::value::Value;
use crate::util::error::*;
use crate::util::source::Source;
use crate::util::value::Value;
use std::collections::HashMap;
use std::path::{Path, PathBuf};

View file

@ -1,4 +1,4 @@
use crate::config::value::{Value, ValueKind};
use crate::util::value::{Value, ValueKind};
use toml;

View file

@ -1,5 +1,5 @@
use crate::config::error::*;
use crate::config::value::{Value, ValueKind};
use crate::util::error::*;
use crate::util::value::{Value, ValueKind};
use std::collections::HashMap;
use std::str::FromStr;

View file

@ -1,6 +1,6 @@
use crate::config::error::*;
use crate::config::path;
use crate::config::value::{Value, ValueKind};
use crate::util::error::*;
use crate::util::path;
use crate::util::value::{Value, ValueKind};
use std::collections::HashMap;
use std::fmt::Debug;

View file

@ -1,4 +1,4 @@
use crate::config::error::*;
use crate::util::error::*;
use serde::de::{Deserialize, Deserializer, Visitor};