♻️ Restructure dirs/files to fix examples build
This commit is contained in:
parent
92268ca653
commit
73ba80eff7
53 changed files with 47 additions and 43 deletions
|
|
@ -1,49 +0,0 @@
|
|||
[package]
|
||||
name = "pagetop"
|
||||
version = "0.0.56"
|
||||
edition = "2021"
|
||||
|
||||
description = """\
|
||||
An opinionated web framework to build modular Server-Side Rendering web solutions.\
|
||||
"""
|
||||
categories = ["web-programming", "gui", "development-tools", "asynchronous"]
|
||||
keywords = ["pagetop", "web", "framework", "frontend", "ssr"]
|
||||
readme = "../../README.md"
|
||||
|
||||
homepage = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[lib]
|
||||
name = "pagetop"
|
||||
|
||||
[dependencies]
|
||||
colored = "2.1.0"
|
||||
concat-string = "1.0.1"
|
||||
figlet-rs = "0.1.5"
|
||||
itoa = "1.0.11"
|
||||
nom = "7.1.3"
|
||||
paste = "1.0.15"
|
||||
substring = "1.4.5"
|
||||
terminal_size = "0.4.0"
|
||||
toml = "0.8.19"
|
||||
|
||||
tracing = "0.1.40"
|
||||
tracing-appender = "0.2.3"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["json", "env-filter"] }
|
||||
tracing-actix-web = "0.7.15"
|
||||
|
||||
fluent-bundle = "0.15.3"
|
||||
fluent-templates = "0.11.0"
|
||||
unic-langid = { version = "0.9.5", features = ["macros"] }
|
||||
|
||||
actix-web = "4.9.0"
|
||||
actix-web-files = { package = "actix-files", version = "0.6.6" }
|
||||
actix-web-static-files = "4.0.1"
|
||||
actix-session = { version = "0.10.1", features = ["cookie-session"] }
|
||||
|
||||
serde.workspace = true
|
||||
static-files.workspace = true
|
||||
|
||||
pagetop-macros.workspace = true
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
//! Prepare and run an application created with **Pagetop**.
|
||||
|
||||
mod figfont;
|
||||
|
||||
use crate::core::{package, package::PackageRef};
|
||||
use crate::{global, locale, service, trace};
|
||||
|
||||
use actix_session::config::{BrowserSession, PersistentSession, SessionLifecycle};
|
||||
use actix_session::storage::CookieSessionStore;
|
||||
use actix_session::SessionMiddleware;
|
||||
|
||||
use substring::Substring;
|
||||
|
||||
use std::io::Error;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub struct Application;
|
||||
|
||||
impl Default for Application {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Application {
|
||||
/// Creates a new application instance without any package.
|
||||
pub fn new() -> Self {
|
||||
Self::internal_prepare(None)
|
||||
}
|
||||
|
||||
/// Prepares an application instance with a specific package.
|
||||
pub fn prepare(root_package: PackageRef) -> Self {
|
||||
Self::internal_prepare(Some(root_package))
|
||||
}
|
||||
|
||||
// Internal method to prepare the application, optionally with a package.
|
||||
fn internal_prepare(root_package: Option<PackageRef>) -> Self {
|
||||
// On startup, show the application banner.
|
||||
Self::show_banner();
|
||||
|
||||
// Starts logging and event tracing.
|
||||
LazyLock::force(&trace::TRACING);
|
||||
|
||||
// Validates the default language identifier.
|
||||
LazyLock::force(&locale::DEFAULT_LANGID);
|
||||
|
||||
// Registers the application's packages.
|
||||
package::all::register_packages(root_package);
|
||||
|
||||
// Registers package actions.
|
||||
package::all::register_actions();
|
||||
|
||||
// Initializes the packages.
|
||||
package::all::init_packages();
|
||||
|
||||
Self
|
||||
}
|
||||
|
||||
// Displays the application banner based on the configuration.
|
||||
fn show_banner() {
|
||||
use colored::Colorize;
|
||||
use terminal_size::{terminal_size, Width};
|
||||
|
||||
if global::SETTINGS.app.startup_banner.to_lowercase() != "off" {
|
||||
// Application name, formatted for the terminal width if necessary.
|
||||
let mut app_ff = "".to_string();
|
||||
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}");
|
||||
}
|
||||
|
||||
// Application description.
|
||||
if !global::SETTINGS.app.description.is_empty() {
|
||||
println!("{}", global::SETTINGS.app.description.cyan());
|
||||
};
|
||||
|
||||
// PageTop version.
|
||||
println!(
|
||||
"{} {}\n",
|
||||
"Powered by PageTop".yellow(),
|
||||
env!("CARGO_PKG_VERSION").yellow()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts the web server.
|
||||
pub fn run(self) -> Result<service::Server, Error> {
|
||||
// Generate the cookie key.
|
||||
let secret_key = service::cookie::Key::generate();
|
||||
|
||||
// Prepares the web server.
|
||||
Ok(service::HttpServer::new(move || {
|
||||
Self::service_app()
|
||||
.wrap(tracing_actix_web::TracingLogger::default())
|
||||
.wrap(
|
||||
SessionMiddleware::builder(CookieSessionStore::default(), secret_key.clone())
|
||||
.session_lifecycle(match global::SETTINGS.server.session_lifetime {
|
||||
0 => SessionLifecycle::BrowserSession(BrowserSession::default()),
|
||||
_ => SessionLifecycle::PersistentSession(
|
||||
PersistentSession::default().session_ttl(
|
||||
service::cookie::time::Duration::seconds(
|
||||
global::SETTINGS.server.session_lifetime,
|
||||
),
|
||||
),
|
||||
),
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
})
|
||||
.bind(format!(
|
||||
"{}:{}",
|
||||
&global::SETTINGS.server.bind_address,
|
||||
&global::SETTINGS.server.bind_port
|
||||
))?
|
||||
.run())
|
||||
}
|
||||
|
||||
/// Method for testing, returns a service application instance.
|
||||
pub fn test(
|
||||
self,
|
||||
) -> service::App<
|
||||
impl service::Factory<
|
||||
service::Request,
|
||||
Config = (),
|
||||
Response = service::Response<service::BoxBody>,
|
||||
Error = service::Error,
|
||||
InitError = (),
|
||||
>,
|
||||
> {
|
||||
Self::service_app()
|
||||
}
|
||||
|
||||
// Configures the service application.
|
||||
fn service_app() -> service::App<
|
||||
impl service::Factory<
|
||||
service::Request,
|
||||
Config = (),
|
||||
Response = service::Response<service::BoxBody>,
|
||||
Error = service::Error,
|
||||
InitError = (),
|
||||
>,
|
||||
> {
|
||||
service::App::new().configure(package::all::configure_services)
|
||||
// .default_service(service::web::route().to(service_not_found))
|
||||
}
|
||||
}
|
||||
/*
|
||||
async fn service_not_found(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
|
||||
Err(ErrorPage::NotFound(request))
|
||||
}
|
||||
*/
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
use crate::global;
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use figlet_rs::FIGfont;
|
||||
|
||||
pub static FIGFONT: LazyLock<FIGfont> = LazyLock::new(|| {
|
||||
let slant = include_str!("slant.flf");
|
||||
let small = include_str!("small.flf");
|
||||
let speed = include_str!("speed.flf");
|
||||
let starwars = include_str!("starwars.flf");
|
||||
|
||||
FIGfont::from_content(
|
||||
match global::SETTINGS.app.startup_banner.to_lowercase().as_str() {
|
||||
"off" => slant,
|
||||
"slant" => slant,
|
||||
"small" => small,
|
||||
"speed" => speed,
|
||||
"starwars" => starwars,
|
||||
_ => {
|
||||
println!(
|
||||
"\n FIGfont \"{}\" not found for banner. Using \"Slant\". Check settings files.",
|
||||
global::SETTINGS.app.startup_banner,
|
||||
);
|
||||
slant
|
||||
}
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,719 +0,0 @@
|
|||
flf2a$ 7 6 22 15 4
|
||||
starwars.flf by Ryan Youck (youck@cs.uregina.ca) Dec 25/1994
|
||||
I am not responsible for use of this font
|
||||
Based on Big.flf by Glenn Chappell
|
||||
|
||||
$ $@
|
||||
$ $@
|
||||
$ $@
|
||||
$ $@
|
||||
$ $@
|
||||
$ $@
|
||||
$ $@@
|
||||
__ $@
|
||||
| |$@
|
||||
| |$@
|
||||
| |$@
|
||||
|__|$@
|
||||
(__)$@
|
||||
$@@
|
||||
_ _ @
|
||||
( | )@
|
||||
V V @
|
||||
$ @
|
||||
$ @
|
||||
$ @
|
||||
@@
|
||||
_ _ @
|
||||
_| || |_$@
|
||||
|_ __ _|@
|
||||
_| || |_ @
|
||||
|_ __ _|@
|
||||
|_||_| $@
|
||||
@@
|
||||
__,--,_.@
|
||||
/ |@
|
||||
| (----`@
|
||||
\ \ $@
|
||||
.----) | $@
|
||||
|_ __/ $@
|
||||
'--' $@@
|
||||
_ ___$ @
|
||||
/ \ / /$ @
|
||||
( o ) / / $ @
|
||||
\_/ / / _$ @
|
||||
/ / / \ @
|
||||
/ / ( o )@
|
||||
/__/ \_/ @@
|
||||
@
|
||||
___ @
|
||||
( _ ) $@
|
||||
/ _ \/\@
|
||||
| (_> <@
|
||||
\___/\/@
|
||||
$@@
|
||||
__ @
|
||||
(_ )@
|
||||
|/ @
|
||||
$ @
|
||||
$ @
|
||||
$ @
|
||||
@@
|
||||
___@
|
||||
/ /@
|
||||
| |$@
|
||||
| |$@
|
||||
| |$@
|
||||
| |$@
|
||||
\__\@@
|
||||
___ @
|
||||
\ \ @
|
||||
| |@
|
||||
| |@
|
||||
| |@
|
||||
| |@
|
||||
/__/ @@
|
||||
_ @
|
||||
/\| |/\ @
|
||||
\ ` ' /$@
|
||||
|_ _|@
|
||||
/ , . \$@
|
||||
\/|_|\/ @
|
||||
@@
|
||||
@
|
||||
_ @
|
||||
_| |_$@
|
||||
|_ _|@
|
||||
|_| $@
|
||||
$ @
|
||||
@@
|
||||
@
|
||||
@
|
||||
$ @
|
||||
$ @
|
||||
__ @
|
||||
(_ )@
|
||||
|/ @@
|
||||
@
|
||||
@
|
||||
______ @
|
||||
|______|@
|
||||
$ @
|
||||
$ @
|
||||
@@
|
||||
@
|
||||
@
|
||||
@
|
||||
$ @
|
||||
__ @
|
||||
(__)@
|
||||
@@
|
||||
___@
|
||||
/ /@
|
||||
/ / @
|
||||
/ /$ @
|
||||
/ /$ @
|
||||
/__/$ @
|
||||
@@
|
||||
___ $@
|
||||
/ _ \ $@
|
||||
| | | |$@
|
||||
| | | |$@
|
||||
| |_| |$@
|
||||
\___/ $@
|
||||
$@@
|
||||
__ $@
|
||||
/_ |$@
|
||||
| |$@
|
||||
| |$@
|
||||
| |$@
|
||||
|_|$@
|
||||
$@@
|
||||
___ $@
|
||||
|__ \ $@
|
||||
$) |$@
|
||||
/ / $@
|
||||
/ /_ $@
|
||||
|____|$@
|
||||
$@@
|
||||
____ $@
|
||||
|___ \ $@
|
||||
__) |$@
|
||||
|__ < $@
|
||||
___) |$@
|
||||
|____/ $@
|
||||
$@@
|
||||
_ _ $@
|
||||
| || | $@
|
||||
| || |_ $@
|
||||
|__ _|$@
|
||||
| | $@
|
||||
|_| $@
|
||||
$@@
|
||||
_____ $@
|
||||
| ____|$@
|
||||
| |__ $@
|
||||
|___ \ $@
|
||||
___) |$@
|
||||
|____/ $@
|
||||
$@@
|
||||
__ $@
|
||||
/ / $@
|
||||
/ /_ $@
|
||||
| '_ \ $@
|
||||
| (_) |$@
|
||||
\___/ $@
|
||||
$@@
|
||||
______ $@
|
||||
|____ |$@
|
||||
$/ / $@
|
||||
/ / $@
|
||||
/ / $@
|
||||
/_/ $@
|
||||
$@@
|
||||
___ $@
|
||||
/ _ \ $@
|
||||
| (_) |$@
|
||||
> _ < $@
|
||||
| (_) |$@
|
||||
\___/ $@
|
||||
$@@
|
||||
___ $@
|
||||
/ _ \ $@
|
||||
| (_) |$@
|
||||
\__, |$@
|
||||
/ / $@
|
||||
/_/ $@
|
||||
$@@
|
||||
@
|
||||
_ @
|
||||
(_)@
|
||||
$ @
|
||||
_ @
|
||||
(_)@
|
||||
@@
|
||||
@
|
||||
_ @
|
||||
(_)@
|
||||
$ @
|
||||
_ @
|
||||
( )@
|
||||
|/ @@
|
||||
___@
|
||||
/ /@
|
||||
/ /$@
|
||||
< <$ @
|
||||
\ \$@
|
||||
\__\@
|
||||
@@
|
||||
@
|
||||
______ @
|
||||
|______|@
|
||||
______ @
|
||||
|______|@
|
||||
@
|
||||
@@
|
||||
___ @
|
||||
\ \$ @
|
||||
\ \ @
|
||||
> >@
|
||||
/ / @
|
||||
/__/$ @
|
||||
@@
|
||||
______ $@
|
||||
| \ $@
|
||||
`----) |$@
|
||||
/ / $@
|
||||
|__| $@
|
||||
__ $@
|
||||
(__) $@@
|
||||
____ @
|
||||
/ __ \ @
|
||||
/ / _` |@
|
||||
| | (_| |@
|
||||
\ \__,_|@
|
||||
\____/ @
|
||||
@@
|
||||
___ $ @
|
||||
/ \ $ @
|
||||
/ ^ \$ @
|
||||
/ /_\ \$ @
|
||||
/ _____ \$ @
|
||||
/__/ \__\$@
|
||||
$@@
|
||||
.______ $@
|
||||
| _ \ $@
|
||||
| |_) |$@
|
||||
| _ < $@
|
||||
| |_) |$@
|
||||
|______/ $@
|
||||
$@@
|
||||
______$@
|
||||
/ |@
|
||||
| ,----'@
|
||||
| | $@
|
||||
| `----.@
|
||||
\______|@
|
||||
$@@
|
||||
_______ $@
|
||||
| \$@
|
||||
| .--. |@
|
||||
| | | |@
|
||||
| '--' |@
|
||||
|_______/$@
|
||||
$@@
|
||||
_______ @
|
||||
| ____|@
|
||||
| |__ $@
|
||||
| __| $@
|
||||
| |____ @
|
||||
|_______|@
|
||||
@@
|
||||
_______ @
|
||||
| ____|@
|
||||
| |__ $@
|
||||
| __| $@
|
||||
| | $ @
|
||||
|__| @
|
||||
@@
|
||||
_______ @
|
||||
/ _____|@
|
||||
| | __ $@
|
||||
| | |_ |$@
|
||||
| |__| |$@
|
||||
\______|$@
|
||||
$@@
|
||||
__ __ $@
|
||||
| | | |$@
|
||||
| |__| |$@
|
||||
| __ |$@
|
||||
| | | |$@
|
||||
|__| |__|$@
|
||||
$@@
|
||||
__ $@
|
||||
| |$@
|
||||
| |$@
|
||||
| |$@
|
||||
| |$@
|
||||
|__|$@
|
||||
$@@
|
||||
__ $@
|
||||
| |$@
|
||||
| |$@
|
||||
.--. | |$@
|
||||
| `--' |$@
|
||||
\______/ $@
|
||||
$@@
|
||||
__ ___$@
|
||||
| |/ /$@
|
||||
| ' / $@
|
||||
| < $@
|
||||
| . \ $@
|
||||
|__|\__\$@
|
||||
$@@
|
||||
__ $@
|
||||
| | $@
|
||||
| | $@
|
||||
| | $@
|
||||
| `----.@
|
||||
|_______|@
|
||||
$@@
|
||||
.___ ___.$@
|
||||
| \/ |$@
|
||||
| \ / |$@
|
||||
| |\/| |$@
|
||||
| | | |$@
|
||||
|__| |__|$@
|
||||
$@@
|
||||
.__ __.$@
|
||||
| \ | |$@
|
||||
| \| |$@
|
||||
| . ` |$@
|
||||
| |\ |$@
|
||||
|__| \__|$@
|
||||
$@@
|
||||
______ $@
|
||||
/ __ \ $@
|
||||
| | | |$@
|
||||
| | | |$@
|
||||
| `--' |$@
|
||||
\______/ $@
|
||||
$@@
|
||||
.______ $@
|
||||
| _ \ $@
|
||||
| |_) |$@
|
||||
| ___/ $@
|
||||
| | $ @
|
||||
| _| $ @
|
||||
$ @@
|
||||
______ $ @
|
||||
/ __ \ $ @
|
||||
| | | | $ @
|
||||
| | | | $ @
|
||||
| `--' '--. @
|
||||
\_____\_____\@
|
||||
$ @@
|
||||
.______ $ @
|
||||
| _ \ $ @
|
||||
| |_) | $ @
|
||||
| / $ @
|
||||
| |\ \----.@
|
||||
| _| `._____|@
|
||||
$@@
|
||||
_______.@
|
||||
/ |@
|
||||
| (----`@
|
||||
\ \ $@
|
||||
.----) | $@
|
||||
|_______/ $@
|
||||
$@@
|
||||
.___________.@
|
||||
| |@
|
||||
`---| |----`@
|
||||
| | $ @
|
||||
| | $ @
|
||||
|__| $ @
|
||||
$ @@
|
||||
__ __ $@
|
||||
| | | |$@
|
||||
| | | |$@
|
||||
| | | |$@
|
||||
| `--' |$@
|
||||
\______/ $@
|
||||
$@@
|
||||
____ ____$@
|
||||
\ \ / /$@
|
||||
\ \/ /$ @
|
||||
\ /$ @
|
||||
\ /$ @
|
||||
\__/$ @
|
||||
$ @@
|
||||
____ __ ____$@
|
||||
\ \ / \ / /$@
|
||||
\ \/ \/ /$ @
|
||||
\ /$ @
|
||||
\ /\ /$ @
|
||||
\__/ \__/$ @
|
||||
$ @@
|
||||
___ ___$@
|
||||
\ \ / /$@
|
||||
\ V / $@
|
||||
> < $@
|
||||
/ . \ $@
|
||||
/__/ \__\$@
|
||||
$@@
|
||||
____ ____$@
|
||||
\ \ / /$@
|
||||
\ \/ /$ @
|
||||
\_ _/$ @
|
||||
| |$ @
|
||||
|__|$ @
|
||||
$ @@
|
||||
________ $@
|
||||
| / $@
|
||||
`---/ / $@
|
||||
/ / $@
|
||||
/ /----.@
|
||||
/________|@
|
||||
$@@
|
||||
____ @
|
||||
| |@
|
||||
| |-`@
|
||||
| | $@
|
||||
| | $@
|
||||
| |-.@
|
||||
|____|@@
|
||||
___ @
|
||||
\ \ $ @
|
||||
\ \$ @
|
||||
\ \$ @
|
||||
\ \$@
|
||||
\__\@
|
||||
@@
|
||||
____ @
|
||||
| |@
|
||||
`-| |@
|
||||
| |@
|
||||
| |@
|
||||
.-| |@
|
||||
|____|@@
|
||||
___ @
|
||||
/ \ @
|
||||
/--^--\@
|
||||
$@
|
||||
$@
|
||||
$@
|
||||
$@@
|
||||
@
|
||||
@
|
||||
@
|
||||
$ @
|
||||
$ @
|
||||
______ @
|
||||
|______|@@
|
||||
__ @
|
||||
( _)@
|
||||
\| @
|
||||
$ @
|
||||
$ @
|
||||
$ @
|
||||
@@
|
||||
___ $ @
|
||||
/ \ $ @
|
||||
/ ^ \$ @
|
||||
/ /_\ \$ @
|
||||
/ _____ \$ @
|
||||
/__/ \__\$@
|
||||
$@@
|
||||
.______ $@
|
||||
| _ \ $@
|
||||
| |_) |$@
|
||||
| _ < $@
|
||||
| |_) |$@
|
||||
|______/ $@
|
||||
$@@
|
||||
______$@
|
||||
/ |@
|
||||
| ,----'@
|
||||
| | $@
|
||||
| `----.@
|
||||
\______|@
|
||||
$@@
|
||||
_______ $@
|
||||
| \$@
|
||||
| .--. |@
|
||||
| | | |@
|
||||
| '--' |@
|
||||
|_______/$@
|
||||
$@@
|
||||
_______ @
|
||||
| ____|@
|
||||
| |__ $@
|
||||
| __| $@
|
||||
| |____ @
|
||||
|_______|@
|
||||
@@
|
||||
_______ @
|
||||
| ____|@
|
||||
| |__ $@
|
||||
| __| $@
|
||||
| | $ @
|
||||
|__| @
|
||||
@@
|
||||
_______ @
|
||||
/ _____|@
|
||||
| | __ $@
|
||||
| | |_ |$@
|
||||
| |__| |$@
|
||||
\______|$@
|
||||
$@@
|
||||
__ __ $@
|
||||
| | | |$@
|
||||
| |__| |$@
|
||||
| __ |$@
|
||||
| | | |$@
|
||||
|__| |__|$@
|
||||
$@@
|
||||
__ $@
|
||||
| |$@
|
||||
| |$@
|
||||
| |$@
|
||||
| |$@
|
||||
|__|$@
|
||||
$@@
|
||||
__ $@
|
||||
| |$@
|
||||
| |$@
|
||||
.--. | |$@
|
||||
| `--' |$@
|
||||
\______/ $@
|
||||
$@@
|
||||
__ ___$@
|
||||
| |/ /$@
|
||||
| ' / $@
|
||||
| < $@
|
||||
| . \ $@
|
||||
|__|\__\$@
|
||||
$@@
|
||||
__ $@
|
||||
| | $@
|
||||
| | $@
|
||||
| | $@
|
||||
| `----.@
|
||||
|_______|@
|
||||
$@@
|
||||
.___ ___.$@
|
||||
| \/ |$@
|
||||
| \ / |$@
|
||||
| |\/| |$@
|
||||
| | | |$@
|
||||
|__| |__|$@
|
||||
$@@
|
||||
.__ __.$@
|
||||
| \ | |$@
|
||||
| \| |$@
|
||||
| . ` |$@
|
||||
| |\ |$@
|
||||
|__| \__|$@
|
||||
$@@
|
||||
______ $@
|
||||
/ __ \ $@
|
||||
| | | |$@
|
||||
| | | |$@
|
||||
| `--' |$@
|
||||
\______/ $@
|
||||
$@@
|
||||
.______ $@
|
||||
| _ \ $@
|
||||
| |_) |$@
|
||||
| ___/ $@
|
||||
| | $ @
|
||||
| _| $ @
|
||||
$ @@
|
||||
______ $ @
|
||||
/ __ \ $ @
|
||||
| | | | $ @
|
||||
| | | | $ @
|
||||
| `--' '--. @
|
||||
\_____\_____\@
|
||||
$ @@
|
||||
.______ $ @
|
||||
| _ \ $ @
|
||||
| |_) | $ @
|
||||
| / $ @
|
||||
| |\ \----.@
|
||||
| _| `._____|@
|
||||
$@@
|
||||
_______.@
|
||||
/ |@
|
||||
| (----`@
|
||||
\ \ $@
|
||||
.----) | $@
|
||||
|_______/ $@
|
||||
$@@
|
||||
.___________.@
|
||||
| |@
|
||||
`---| |----`@
|
||||
| | $ @
|
||||
| | $ @
|
||||
|__| $ @
|
||||
$ @@
|
||||
__ __ $@
|
||||
| | | |$@
|
||||
| | | |$@
|
||||
| | | |$@
|
||||
| `--' |$@
|
||||
\______/ $@
|
||||
$@@
|
||||
____ ____$@
|
||||
\ \ / /$@
|
||||
\ \/ /$ @
|
||||
\ /$ @
|
||||
\ /$ @
|
||||
\__/$ @
|
||||
$ @@
|
||||
____ __ ____$@
|
||||
\ \ / \ / /$@
|
||||
\ \/ \/ /$ @
|
||||
\ /$ @
|
||||
\ /\ /$ @
|
||||
\__/ \__/$ @
|
||||
$ @@
|
||||
___ ___$@
|
||||
\ \ / /$@
|
||||
\ V / $@
|
||||
> < $@
|
||||
/ . \ $@
|
||||
/__/ \__\$@
|
||||
$@@
|
||||
____ ____$@
|
||||
\ \ / /$@
|
||||
\ \/ /$ @
|
||||
\_ _/$ @
|
||||
| |$ @
|
||||
|__|$ @
|
||||
$ @@
|
||||
________ $@
|
||||
| / $@
|
||||
`---/ / $@
|
||||
/ / $@
|
||||
/ /----.@
|
||||
/________|@
|
||||
$@@
|
||||
___@
|
||||
/ /@
|
||||
| |$@
|
||||
/ /$ @
|
||||
\ \$ @
|
||||
| |$@
|
||||
\__\@@
|
||||
__ $@
|
||||
| |$@
|
||||
| |$@
|
||||
| |$@
|
||||
| |$@
|
||||
| |$@
|
||||
|__|$@@
|
||||
___ @
|
||||
\ \$ @
|
||||
| | @
|
||||
\ \@
|
||||
/ /@
|
||||
| | @
|
||||
/__/$ @@
|
||||
__ _ @
|
||||
/ \/ |@
|
||||
|_/\__/ @
|
||||
$ @
|
||||
$ @
|
||||
$ @
|
||||
@@
|
||||
_ _ @
|
||||
(_)_(_) @
|
||||
/ \ @
|
||||
/ _ \ @
|
||||
/ ___ \ @
|
||||
/_/ \_\@
|
||||
@@
|
||||
_ _ @
|
||||
(_)_(_)@
|
||||
/ _ \ @
|
||||
| | | |@
|
||||
| |_| |@
|
||||
\___/ @
|
||||
@@
|
||||
_ _ @
|
||||
(_) (_)@
|
||||
| | | |@
|
||||
| | | |@
|
||||
| |_| |@
|
||||
\___/ @
|
||||
@@
|
||||
_ _ @
|
||||
(_) (_)@
|
||||
__ _ @
|
||||
/ _` |@
|
||||
| (_| |@
|
||||
\__,_|@
|
||||
@@
|
||||
_ _ @
|
||||
(_) (_)@
|
||||
___ @
|
||||
/ _ \ @
|
||||
| (_) |@
|
||||
\___/ @
|
||||
@@
|
||||
_ _ @
|
||||
(_) (_)@
|
||||
_ _ @
|
||||
| | | |@
|
||||
| |_| |@
|
||||
\__,_|@
|
||||
@@
|
||||
___ @
|
||||
/ _ \ @
|
||||
| | ) |@
|
||||
| |< < @
|
||||
| | ) |@
|
||||
| ||_/ @
|
||||
|_| @@
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
//! Key types and functions for creating actions, packages, and themes.
|
||||
|
||||
use crate::util::TypeInfo;
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
||||
#[allow(clippy::inline_always)]
|
||||
impl<T: Any> AnyBase for T {
|
||||
#[inline(always)]
|
||||
fn type_name(&self) -> &'static str {
|
||||
TypeInfo::FullName.of::<T>()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn short_name(&self) -> &'static str {
|
||||
TypeInfo::ShortName.of::<T>()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn as_any_ref(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
T: 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
|
||||
T: 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
|
||||
T: AnyBase,
|
||||
{
|
||||
self.as_any_mut().downcast_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + AnyBase> AnyTo for T {}
|
||||
|
||||
// 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;
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
mod definition;
|
||||
pub use definition::{ActionBase, ActionBox, ActionKey, ActionTrait};
|
||||
|
||||
mod list;
|
||||
use list::ActionsList;
|
||||
|
||||
mod all;
|
||||
pub(crate) use all::add_action;
|
||||
pub use all::dispatch_actions;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! actions {
|
||||
() => {
|
||||
Vec::<ActionBox>::new()
|
||||
};
|
||||
( $($action:expr),+ $(,)? ) => {{
|
||||
vec![$(Box::new($action),)+]
|
||||
}};
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
use crate::core::action::{ActionBox, ActionKey, ActionTrait, ActionsList};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{LazyLock, RwLock};
|
||||
|
||||
// Registered actions.
|
||||
static ACTIONS: LazyLock<RwLock<HashMap<ActionKey, ActionsList>>> =
|
||||
LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
pub fn add_action(action: ActionBox) {
|
||||
let key = action.key();
|
||||
let mut actions = ACTIONS.write().unwrap();
|
||||
if let Some(list) = actions.get_mut(&key) {
|
||||
list.add(action);
|
||||
} else {
|
||||
let mut list = ActionsList::new();
|
||||
list.add(action);
|
||||
actions.insert(key, list);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatch_actions<A, B, F>(key: &ActionKey, f: F)
|
||||
where
|
||||
A: ActionTrait,
|
||||
F: FnMut(&A) -> B,
|
||||
{
|
||||
if let Some(list) = ACTIONS.read().unwrap().get(key) {
|
||||
list.iter_map(f);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
use crate::core::AnyBase;
|
||||
use crate::{TypeId, Weight};
|
||||
|
||||
pub type ActionBox = Box<dyn ActionTrait>;
|
||||
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
pub struct ActionKey {
|
||||
action_type_id: TypeId,
|
||||
theme_type_id: Option<TypeId>,
|
||||
referer_type_id: Option<TypeId>,
|
||||
referer_id: Option<String>,
|
||||
}
|
||||
|
||||
impl ActionKey {
|
||||
pub fn new(
|
||||
action_type_id: TypeId,
|
||||
theme_type_id: Option<TypeId>,
|
||||
referer_type_id: Option<TypeId>,
|
||||
referer_id: Option<String>,
|
||||
) -> Self {
|
||||
ActionKey {
|
||||
action_type_id,
|
||||
theme_type_id,
|
||||
referer_type_id,
|
||||
referer_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ActionBase {
|
||||
fn key(&self) -> ActionKey;
|
||||
}
|
||||
|
||||
pub trait ActionTrait: ActionBase + AnyBase + Send + Sync {
|
||||
fn theme_type_id(&self) -> Option<TypeId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn referer_type_id(&self) -> Option<TypeId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn referer_id(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn weight(&self) -> Weight {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: ActionTrait> ActionBase for A {
|
||||
fn key(&self) -> ActionKey {
|
||||
ActionKey {
|
||||
action_type_id: self.type_id(),
|
||||
theme_type_id: self.theme_type_id(),
|
||||
referer_type_id: self.referer_type_id(),
|
||||
referer_id: self.referer_id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
use crate::core::action::{ActionBox, ActionTrait};
|
||||
use crate::core::AnyTo;
|
||||
use crate::trace;
|
||||
use crate::AutoDefault;
|
||||
|
||||
use std::sync::RwLock;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub struct ActionsList(RwLock<Vec<ActionBox>>);
|
||||
|
||||
impl ActionsList {
|
||||
pub fn new() -> Self {
|
||||
ActionsList::default()
|
||||
}
|
||||
|
||||
pub fn add(&mut self, action: ActionBox) {
|
||||
let mut list = self.0.write().unwrap();
|
||||
list.push(action);
|
||||
list.sort_by_key(|a| a.weight());
|
||||
}
|
||||
|
||||
pub fn iter_map<A, B, F>(&self, mut f: F)
|
||||
where
|
||||
Self: Sized,
|
||||
A: ActionTrait,
|
||||
F: FnMut(&A) -> B,
|
||||
{
|
||||
let _: Vec<_> = self
|
||||
.0
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|a| {
|
||||
if let Some(action) = (**a).downcast_ref::<A>() {
|
||||
f(action);
|
||||
} else {
|
||||
trace::error!("Failed to downcast action of type {}", (**a).type_name());
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
mod definition;
|
||||
pub use definition::{PackageRef, PackageTrait};
|
||||
|
||||
pub(crate) mod all;
|
||||
pub(crate) mod welcome;
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
use crate::core::action::add_action;
|
||||
use crate::core::package::{welcome, PackageRef};
|
||||
use crate::core::theme::all::THEMES;
|
||||
use crate::{service, trace};
|
||||
|
||||
use std::sync::{LazyLock, RwLock};
|
||||
|
||||
// PACKAGES ****************************************************************************************
|
||||
|
||||
static ENABLED_PACKAGES: LazyLock<RwLock<Vec<PackageRef>>> =
|
||||
LazyLock::new(|| RwLock::new(Vec::new()));
|
||||
|
||||
static DROPPED_PACKAGES: LazyLock<RwLock<Vec<PackageRef>>> =
|
||||
LazyLock::new(|| RwLock::new(Vec::new()));
|
||||
|
||||
// REGISTER PACKAGES *******************************************************************************
|
||||
|
||||
pub fn register_packages(root_package: Option<PackageRef>) {
|
||||
// Initialize a list for packages to be enabled.
|
||||
let mut enabled_list: Vec<PackageRef> = Vec::new();
|
||||
|
||||
// If a root package is provided, add it to the enabled list.
|
||||
if let Some(package) = root_package {
|
||||
add_to_enabled(&mut enabled_list, package);
|
||||
}
|
||||
// Reverse the order to ensure packages are sorted from none to most dependencies.
|
||||
enabled_list.reverse();
|
||||
// Save the final list of enabled packages.
|
||||
ENABLED_PACKAGES.write().unwrap().append(&mut enabled_list);
|
||||
|
||||
// Initialize a list for packages to be dropped.
|
||||
let mut dropped_list: Vec<PackageRef> = Vec::new();
|
||||
// If a root package is provided, analyze its dropped list.
|
||||
if let Some(package) = root_package {
|
||||
add_to_dropped(&mut dropped_list, package);
|
||||
}
|
||||
// Save the final list of dropped packages.
|
||||
DROPPED_PACKAGES.write().unwrap().append(&mut dropped_list);
|
||||
}
|
||||
|
||||
fn add_to_enabled(list: &mut Vec<PackageRef>, package: PackageRef) {
|
||||
// Check if the package is not already in the enabled list to avoid duplicates.
|
||||
if !list.iter().any(|p| p.type_id() == package.type_id()) {
|
||||
// Add the package to the enabled list.
|
||||
list.push(package);
|
||||
|
||||
// Reverse dependencies to add them in correct order (dependencies first).
|
||||
let mut dependencies = package.dependencies();
|
||||
dependencies.reverse();
|
||||
for d in &dependencies {
|
||||
add_to_enabled(list, *d);
|
||||
}
|
||||
|
||||
// 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.
|
||||
if !registered_themes
|
||||
.iter()
|
||||
.any(|t| t.type_id() == theme.type_id())
|
||||
{
|
||||
registered_themes.push(theme);
|
||||
trace::debug!("Enabling \"{}\" theme", theme.short_name());
|
||||
}
|
||||
} else {
|
||||
trace::debug!("Enabling \"{}\" package", package.short_name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_to_dropped(list: &mut Vec<PackageRef>, package: PackageRef) {
|
||||
// Iterate through packages recommended to be dropped.
|
||||
for d in &package.drop_packages() {
|
||||
// Check if the package is not already in the dropped list.
|
||||
if !list.iter().any(|p| p.type_id() == d.type_id()) {
|
||||
// Check if the package is currently enabled. If so, log a warning.
|
||||
if ENABLED_PACKAGES
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|p| p.type_id() == package.type_id())
|
||||
{
|
||||
trace::warn!(
|
||||
"Trying to drop \"{}\" package which is enabled",
|
||||
package.short_name()
|
||||
);
|
||||
} else {
|
||||
// If the package is not enabled, add it to the dropped list and log the action.
|
||||
list.push(*d);
|
||||
trace::debug!("Package \"{}\" dropped", d.short_name());
|
||||
// Recursively add the dependencies of the dropped package to the dropped list.
|
||||
// This ensures that all dependencies are also considered for dropping.
|
||||
for dependency in &package.dependencies() {
|
||||
add_to_dropped(list, *dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// REGISTER ACTIONS ********************************************************************************
|
||||
|
||||
pub fn register_actions() {
|
||||
for m in ENABLED_PACKAGES.read().unwrap().iter() {
|
||||
for a in m.actions().into_iter() {
|
||||
add_action(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// INIT PACKAGES ***********************************************************************************
|
||||
|
||||
pub fn init_packages() {
|
||||
trace::info!("Calling application bootstrap");
|
||||
for m in ENABLED_PACKAGES.read().unwrap().iter() {
|
||||
m.init();
|
||||
}
|
||||
}
|
||||
|
||||
// CONFIGURE SERVICES ******************************************************************************
|
||||
|
||||
pub fn configure_services(scfg: &mut service::web::ServiceConfig) {
|
||||
for m in ENABLED_PACKAGES.read().unwrap().iter() {
|
||||
m.configure_service(scfg);
|
||||
}
|
||||
// Default welcome homepage.
|
||||
scfg.route("/", service::web::get().to(welcome::homepage));
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
use crate::core::action::ActionBox;
|
||||
use crate::core::theme::ThemeRef;
|
||||
use crate::core::AnyBase;
|
||||
use crate::locale::L10n;
|
||||
use crate::{actions, service};
|
||||
|
||||
pub type PackageRef = &'static dyn PackageTrait;
|
||||
|
||||
/// Los paquetes deben implementar este *trait*.
|
||||
pub trait PackageTrait: AnyBase + Send + Sync {
|
||||
fn name(&self) -> L10n {
|
||||
L10n::n(self.short_name())
|
||||
}
|
||||
|
||||
fn description(&self) -> L10n {
|
||||
L10n::none()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Option<ThemeRef> {
|
||||
None
|
||||
}
|
||||
|
||||
fn dependencies(&self) -> Vec<PackageRef> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn drop_packages(&self) -> Vec<PackageRef> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn actions(&self) -> Vec<ActionBox> {
|
||||
actions![]
|
||||
}
|
||||
|
||||
fn init(&self) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {}
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
use crate::html::{html, Markup};
|
||||
use crate::locale::L10n;
|
||||
use crate::{concat_string, global};
|
||||
|
||||
pub async fn homepage() -> Markup {
|
||||
html! {
|
||||
head {
|
||||
meta charset="UTF-8" {}
|
||||
meta name="viewport" content="width=device-width, initial-scale=1" {}
|
||||
title { (concat_string!(
|
||||
&global::SETTINGS.app.name, " | ", L10n::l("welcome_page").to_string()
|
||||
)) }
|
||||
style { r#"
|
||||
body {
|
||||
background-color: #f3d060;
|
||||
font-size: 20px;
|
||||
}
|
||||
.wrapper {
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
}
|
||||
.container {
|
||||
padding: 0 16px;
|
||||
}
|
||||
.title {
|
||||
font-size: clamp(3rem, 10vw, 10rem);
|
||||
letter-spacing: -0.05em;
|
||||
line-height: 1.2;
|
||||
margin: 0;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: clamp(1.8rem, 2vw, 3rem);
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.2;
|
||||
margin: 0;
|
||||
}
|
||||
.powered {
|
||||
margin: .5em 0 1em;
|
||||
}
|
||||
.box-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
gap: 1.5em;
|
||||
}
|
||||
.box {
|
||||
flex: 1 1 280px;
|
||||
border: 3px solid #25282a;
|
||||
box-shadow: 5px 5px 0px #25282a;
|
||||
box-sizing: border-box;
|
||||
padding: 0 16px;
|
||||
}
|
||||
footer {
|
||||
margin-top: 5em;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #a5282c;
|
||||
}
|
||||
"# }
|
||||
}
|
||||
body style="font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;" {
|
||||
div class="wrapper" {
|
||||
div class="container" {
|
||||
h1 class="title" { (L10n::l("welcome_title").markup()) }
|
||||
|
||||
p class="subtitle" {
|
||||
(L10n::l("welcome_intro").with_arg("app", format!(
|
||||
"<span style=\"font-weight: bold;\">{}</span>",
|
||||
&global::SETTINGS.app.name
|
||||
)).markup())
|
||||
}
|
||||
p class="powered" {
|
||||
(L10n::l("welcome_powered").with_arg("pagetop", format!(
|
||||
"<a href=\"{}\" target=\"_blank\">{}</a>",
|
||||
"https://crates.io/crates/pagetop", "PageTop"
|
||||
)).markup())
|
||||
}
|
||||
|
||||
h2 { (L10n::l("welcome_page").markup()) }
|
||||
|
||||
div class="box-container" {
|
||||
section class="box" style="background-color: #5eb0e5;" {
|
||||
h3 {
|
||||
(L10n::l("welcome_subtitle")
|
||||
.with_arg("app", &global::SETTINGS.app.name)
|
||||
.markup())
|
||||
}
|
||||
p { (L10n::l("welcome_text1").markup()) }
|
||||
p { (L10n::l("welcome_text2").markup()) }
|
||||
}
|
||||
section class="box" style="background-color: #aee1cd;" {
|
||||
h3 {
|
||||
(L10n::l("welcome_pagetop_title").markup())
|
||||
}
|
||||
p { (L10n::l("welcome_pagetop_text1").markup()) }
|
||||
p { (L10n::l("welcome_pagetop_text2").markup()) }
|
||||
p { (L10n::l("welcome_pagetop_text3").markup()) }
|
||||
}
|
||||
section class="box" style="background-color: #ebebe3;" {
|
||||
h3 {
|
||||
(L10n::l("welcome_issues_title").markup())
|
||||
}
|
||||
p { (L10n::l("welcome_issues_text1").markup()) }
|
||||
p {
|
||||
(L10n::l("welcome_issues_text2")
|
||||
.with_arg("app", &global::SETTINGS.app.name)
|
||||
.markup())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer { "[" (L10n::l("welcome_have_fun").markup()) "]" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
mod definition;
|
||||
pub use definition::{ThemeRef, ThemeTrait};
|
||||
/*
|
||||
mod regions;
|
||||
pub(crate) use regions::ComponentsInRegions;
|
||||
pub use regions::InRegion;
|
||||
*/
|
||||
pub(crate) mod all;
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
use crate::core::theme::ThemeRef;
|
||||
|
||||
use std::sync::{LazyLock, RwLock};
|
||||
|
||||
// THEMES ******************************************************************************************
|
||||
|
||||
pub static THEMES: LazyLock<RwLock<Vec<ThemeRef>>> = LazyLock::new(|| RwLock::new(Vec::new()));
|
||||
|
||||
/* DEFAULT THEME ***********************************************************************************
|
||||
|
||||
pub struct NoTheme;
|
||||
|
||||
impl PackageTrait for NoTheme {
|
||||
fn theme(&self) -> Option<ThemeRef> {
|
||||
Some(&NoTheme)
|
||||
}
|
||||
}
|
||||
|
||||
impl ThemeTrait for NoTheme {
|
||||
}
|
||||
|
||||
pub static DEFAULT_THEME: LazyLock<ThemeRef> =
|
||||
LazyLock::new(|| match theme_by_short_name(&global::SETTINGS.app.theme) {
|
||||
Some(theme) => theme,
|
||||
None => &NoTheme,
|
||||
});
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
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())
|
||||
}
|
||||
})
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
//! Global settings.
|
||||
|
||||
use crate::static_config;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
static_config!(SETTINGS: Settings => [
|
||||
// [app]
|
||||
"app.name" => "My App",
|
||||
"app.description" => "Developed with the amazing PageTop framework.",
|
||||
"app.theme" => "",
|
||||
"app.language" => "en-US",
|
||||
"app.text_direction" => "ltr",
|
||||
"app.startup_banner" => "Slant",
|
||||
|
||||
// [dev]
|
||||
"dev.pagetop_project_dir" => "",
|
||||
|
||||
// [log]
|
||||
"log.tracing" => "Info",
|
||||
"log.rolling" => "Stdout",
|
||||
"log.path" => "log",
|
||||
"log.prefix" => "tracing.log",
|
||||
"log.format" => "Full",
|
||||
|
||||
// [server]
|
||||
"server.bind_address" => "localhost",
|
||||
"server.bind_port" => 8088,
|
||||
"server.session_lifetime" => 604_800,
|
||||
]);
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
/// Section `[dev]` of the configuration settings.
|
||||
///
|
||||
/// 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,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
/// Section `[log]` of the configuration settings.
|
||||
///
|
||||
/// 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,
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
//! HTML in code.
|
||||
|
||||
mod maud;
|
||||
pub use maud::{html, html_private, Markup, PreEscaped, DOCTYPE};
|
||||
|
|
@ -1,350 +0,0 @@
|
|||
//#![no_std]
|
||||
|
||||
//! A macro for writing HTML templates.
|
||||
//!
|
||||
//! This documentation only describes the runtime API. For a general
|
||||
//! guide, check out the [book] instead.
|
||||
//!
|
||||
//! [book]: https://maud.lambda.xyz/
|
||||
|
||||
//#![doc(html_root_url = "https://docs.rs/maud/0.25.0")]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::{borrow::Cow, boxed::Box, string::String};
|
||||
use core::fmt::{self, Arguments, Display, Write};
|
||||
|
||||
pub use pagetop_macros::html;
|
||||
|
||||
mod escape;
|
||||
|
||||
/// An adapter that escapes HTML special characters.
|
||||
///
|
||||
/// The following characters are escaped:
|
||||
///
|
||||
/// * `&` is escaped as `&`
|
||||
/// * `<` is escaped as `<`
|
||||
/// * `>` is escaped as `>`
|
||||
/// * `"` is escaped as `"`
|
||||
///
|
||||
/// All other characters are passed through unchanged.
|
||||
///
|
||||
/// **Note:** In versions prior to 0.13, the single quote (`'`) was
|
||||
/// escaped as well.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use maud::Escaper;
|
||||
/// use std::fmt::Write;
|
||||
/// let mut s = String::new();
|
||||
/// write!(Escaper::new(&mut s), "<script>launchMissiles()</script>").unwrap();
|
||||
/// assert_eq!(s, "<script>launchMissiles()</script>");
|
||||
/// ```
|
||||
pub struct Escaper<'a>(&'a mut String);
|
||||
|
||||
impl<'a> Escaper<'a> {
|
||||
/// Creates an `Escaper` from a `String`.
|
||||
pub fn new(buffer: &'a mut String) -> Escaper<'a> {
|
||||
Escaper(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Write for Escaper<'a> {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
escape::escape_to_string(s, self.0);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a type that can be rendered as HTML.
|
||||
///
|
||||
/// To implement this for your own type, override either the `.render()`
|
||||
/// or `.render_to()` methods; since each is defined in terms of the
|
||||
/// other, you only need to implement one of them. See the example below.
|
||||
///
|
||||
/// # Minimal implementation
|
||||
///
|
||||
/// An implementation of this trait must override at least one of
|
||||
/// `.render()` or `.render_to()`. Since the default definitions of
|
||||
/// these methods call each other, not doing this will result in
|
||||
/// infinite recursion.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use maud::{html, Markup, Render};
|
||||
///
|
||||
/// /// Provides a shorthand for linking to a CSS stylesheet.
|
||||
/// pub struct Stylesheet(&'static str);
|
||||
///
|
||||
/// impl Render for Stylesheet {
|
||||
/// fn render(&self) -> Markup {
|
||||
/// html! {
|
||||
/// link rel="stylesheet" type="text/css" href=(self.0);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Render {
|
||||
/// Renders `self` as a block of `Markup`.
|
||||
fn render(&self) -> Markup {
|
||||
let mut buffer = String::new();
|
||||
self.render_to(&mut buffer);
|
||||
PreEscaped(buffer)
|
||||
}
|
||||
|
||||
/// Appends a representation of `self` to the given buffer.
|
||||
///
|
||||
/// Its default implementation just calls `.render()`, but you may
|
||||
/// override it with something more efficient.
|
||||
///
|
||||
/// Note that no further escaping is performed on data written to
|
||||
/// the buffer. If you override this method, you must make sure that
|
||||
/// any data written is properly escaped, whether by hand or using
|
||||
/// the [`Escaper`](struct.Escaper.html) wrapper struct.
|
||||
fn render_to(&self, buffer: &mut String) {
|
||||
buffer.push_str(&self.render().into_string());
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for str {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
escape::escape_to_string(self, w);
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for String {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
str::render_to(self, w);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Render for Cow<'a, str> {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
str::render_to(self, w);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Render for Arguments<'a> {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
let _ = Escaper::new(w).write_fmt(*self);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Render + ?Sized> Render for &'a T {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
T::render_to(self, w);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Render + ?Sized> Render for &'a mut T {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
T::render_to(self, w);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Render + ?Sized> Render for Box<T> {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
T::render_to(self, w);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_render_with_display {
|
||||
($($ty:ty)*) => {
|
||||
$(
|
||||
impl Render for $ty {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
// TODO: remove the explicit arg when Rust 1.58 is released
|
||||
format_args!("{self}", self = self).render_to(w);
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_render_with_display! {
|
||||
char f32 f64
|
||||
}
|
||||
|
||||
macro_rules! impl_render_with_itoa {
|
||||
($($ty:ty)*) => {
|
||||
$(
|
||||
impl Render for $ty {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
w.push_str(itoa::Buffer::new().format(*self));
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_render_with_itoa! {
|
||||
i8 i16 i32 i64 i128 isize
|
||||
u8 u16 u32 u64 u128 usize
|
||||
}
|
||||
|
||||
/// Renders a value using its [`Display`] impl.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use maud::html;
|
||||
/// use std::net::Ipv4Addr;
|
||||
///
|
||||
/// let ip_address = Ipv4Addr::new(127, 0, 0, 1);
|
||||
///
|
||||
/// let markup = html! {
|
||||
/// "My IP address is: "
|
||||
/// (maud::display(ip_address))
|
||||
/// };
|
||||
///
|
||||
/// assert_eq!(markup.into_string(), "My IP address is: 127.0.0.1");
|
||||
/// ```
|
||||
pub fn display(value: impl Display) -> impl Render {
|
||||
struct DisplayWrapper<T>(T);
|
||||
|
||||
impl<T: Display> Render for DisplayWrapper<T> {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
format_args!("{0}", self.0).render_to(w);
|
||||
}
|
||||
}
|
||||
|
||||
DisplayWrapper(value)
|
||||
}
|
||||
|
||||
/// A wrapper that renders the inner value without escaping.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PreEscaped<T: AsRef<str>>(pub T);
|
||||
|
||||
impl<T: AsRef<str>> Render for PreEscaped<T> {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
w.push_str(self.0.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
/// A block of markup is a string that does not need to be escaped.
|
||||
///
|
||||
/// The `html!` macro expands to an expression of this type.
|
||||
pub type Markup = PreEscaped<String>;
|
||||
|
||||
impl Markup {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str> + Into<String>> PreEscaped<T> {
|
||||
/// Converts the inner value to a string.
|
||||
pub fn into_string(self) -> String {
|
||||
self.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str> + Into<String>> From<PreEscaped<T>> for String {
|
||||
fn from(value: PreEscaped<T>) -> String {
|
||||
value.into_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str> + Default> Default for PreEscaped<T> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// The literal string `<!DOCTYPE html>`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// A minimal web page:
|
||||
///
|
||||
/// ```rust
|
||||
/// use maud::{DOCTYPE, html};
|
||||
///
|
||||
/// let markup = html! {
|
||||
/// (DOCTYPE)
|
||||
/// html {
|
||||
/// head {
|
||||
/// meta charset="utf-8";
|
||||
/// title { "Test page" }
|
||||
/// }
|
||||
/// body {
|
||||
/// p { "Hello, world!" }
|
||||
/// }
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub const DOCTYPE: PreEscaped<&'static str> = PreEscaped("<!DOCTYPE html>");
|
||||
|
||||
mod actix_support {
|
||||
extern crate alloc;
|
||||
|
||||
use crate::html::PreEscaped;
|
||||
use actix_web::{http::header, HttpRequest, HttpResponse, Responder};
|
||||
use alloc::string::String;
|
||||
|
||||
impl Responder for PreEscaped<String> {
|
||||
type Body = String;
|
||||
|
||||
fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||
HttpResponse::Ok()
|
||||
.content_type(header::ContentType::html())
|
||||
.message_body(self.0)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod html_private {
|
||||
extern crate alloc;
|
||||
|
||||
use super::{display, Render};
|
||||
use alloc::string::String;
|
||||
use core::fmt::Display;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! render_to {
|
||||
($x:expr, $buffer:expr) => {{
|
||||
use $crate::html::html_private::*;
|
||||
match ChooseRenderOrDisplay($x) {
|
||||
x => (&&x).implements_render_or_display().render_to(x.0, $buffer),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub use render_to;
|
||||
|
||||
pub struct ChooseRenderOrDisplay<T>(pub T);
|
||||
|
||||
pub struct ViaRenderTag;
|
||||
pub struct ViaDisplayTag;
|
||||
|
||||
pub trait ViaRender {
|
||||
fn implements_render_or_display(&self) -> ViaRenderTag {
|
||||
ViaRenderTag
|
||||
}
|
||||
}
|
||||
pub trait ViaDisplay {
|
||||
fn implements_render_or_display(&self) -> ViaDisplayTag {
|
||||
ViaDisplayTag
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Render> ViaRender for &ChooseRenderOrDisplay<T> {}
|
||||
impl<T: Display> ViaDisplay for ChooseRenderOrDisplay<T> {}
|
||||
|
||||
impl ViaRenderTag {
|
||||
pub fn render_to<T: Render + ?Sized>(self, value: &T, buffer: &mut String) {
|
||||
value.render_to(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
impl ViaDisplayTag {
|
||||
pub fn render_to<T: Display + ?Sized>(self, value: &T, buffer: &mut String) {
|
||||
display(value).render_to(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// !!!!! PLEASE KEEP THIS IN SYNC WITH `maud_macros/src/escape.rs` !!!!!
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::string::String;
|
||||
|
||||
pub fn escape_to_string(input: &str, output: &mut String) {
|
||||
for b in input.bytes() {
|
||||
match b {
|
||||
b'&' => output.push_str("&"),
|
||||
b'<' => output.push_str("<"),
|
||||
b'>' => output.push_str(">"),
|
||||
b'"' => output.push_str("""),
|
||||
_ => unsafe { output.as_mut_vec().push(b) },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
extern crate alloc;
|
||||
|
||||
use super::escape_to_string;
|
||||
use alloc::string::String;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let mut s = String::new();
|
||||
escape_to_string("<script>launchMissiles()</script>", &mut s);
|
||||
assert_eq!(s, "<script>launchMissiles()</script>");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
//! <div align="center">
|
||||
//!
|
||||
//! <img src="https://raw.githubusercontent.com/manuelcillero/pagetop/main/static/banner.png" />
|
||||
//!
|
||||
//! <h1>PageTop</h1>
|
||||
//!
|
||||
//! <p>An opinionated web framework to build modular <em>Server-Side Rendering</em> web solutions.</p>
|
||||
//!
|
||||
//! [](https://github.com/manuelcillero/pagetop#-license)
|
||||
//! [](https://docs.rs/pagetop)
|
||||
//! [](https://crates.io/crates/pagetop)
|
||||
//! [](https://crates.io/crates/pagetop)
|
||||
//!
|
||||
//! <br>
|
||||
//! </div>
|
||||
//!
|
||||
//! The `PageTop` core API provides a comprehensive toolkit for extending its functionalities to
|
||||
//! specific requirements and application scenarios through actions, components, packages, and
|
||||
//! themes:
|
||||
//!
|
||||
//! * **Actions** serve as a mechanism to customize `PageTop`'s internal behavior by intercepting
|
||||
//! its execution flow.
|
||||
//! * **Components** encapsulate HTML, CSS, and JavaScript into functional, configurable, and
|
||||
//! well-defined units.
|
||||
//! * **Packages** extend or customize existing functionality by interacting with `PageTop` APIs
|
||||
//! or third-party package APIs.
|
||||
//! * **Themes** enable developers to alter the appearance of pages and components without
|
||||
//! affecting their functionality.
|
||||
//!
|
||||
//! # ⚡️ Quick start
|
||||
//!
|
||||
//! ```rust
|
||||
//! use pagetop::prelude::*;
|
||||
//!
|
||||
//! struct HelloWorld;
|
||||
//!
|
||||
//! impl PackageTrait for HelloWorld {
|
||||
//! fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
|
||||
//! scfg.route("/", service::web::get().to(hello_world));
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! async fn hello_world(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
|
||||
//! Page::new(request)
|
||||
//! .with_component(Html::with(html! { h1 { "Hello World!" } }))
|
||||
//! .render()
|
||||
//! }
|
||||
//!
|
||||
//! #[pagetop::main]
|
||||
//! async fn main() -> std::io::Result<()> {
|
||||
//! Application::prepare(&HelloWorld).run()?.await
|
||||
//! }
|
||||
//! ```
|
||||
//! This program implements a package named `HelloWorld` with one service that returns a web page
|
||||
//! that greets the world whenever it is accessed from the browser at `http://localhost:8088` (using
|
||||
//! the [default configuration settings](`config::Server`)). You can find this code in the `PageTop`
|
||||
//! [examples repository](https://github.com/manuelcillero/pagetop/tree/latest/examples).
|
||||
//!
|
||||
//! # 🧩 Dependency Management
|
||||
//!
|
||||
//! Projects leveraging `PageTop` will use `cargo` to resolve dependencies, similar to any other
|
||||
//! Rust project.
|
||||
//!
|
||||
//! Nevertheless, it’s crucial that each package explicitly declares its
|
||||
//! [dependencies](core::package::PackageTrait#method.dependencies), if any, to assist `PageTop` in
|
||||
//! structuring and initializing the application in a modular fashion.
|
||||
//!
|
||||
//! # 🚧 Warning
|
||||
//!
|
||||
//! **`PageTop`** framework is currently in active development. The API is unstable and subject to
|
||||
//! frequent changes. Production use is not recommended until version **0.1.0**.
|
||||
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
// RE-EXPORTED *************************************************************************************
|
||||
|
||||
pub use concat_string::concat_string;
|
||||
|
||||
/// Enables flexible identifier concatenation in macros, allowing new items with pasted identifiers.
|
||||
pub use paste::paste;
|
||||
|
||||
pub use pagetop_macros::{html, main, test, AutoDefault};
|
||||
|
||||
pub type StaticResources = std::collections::HashMap<&'static str, static_files::Resource>;
|
||||
|
||||
pub use std::any::TypeId;
|
||||
|
||||
pub type Weight = i8;
|
||||
|
||||
// API *********************************************************************************************
|
||||
|
||||
// Useful functions and macros.
|
||||
pub mod util;
|
||||
// Application tracing and event logging.
|
||||
pub mod trace;
|
||||
// HTML in code.
|
||||
pub mod html;
|
||||
// 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;
|
||||
|
||||
// The PageTop Prelude *****************************************************************************
|
||||
|
||||
pub mod prelude;
|
||||
|
|
@ -1,281 +0,0 @@
|
|||
//! Localization (L10n).
|
||||
//!
|
||||
//! 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 both readable and
|
||||
//! expressive, enabling complex natural language constructs like gender, plurals, and conjugations.
|
||||
//!
|
||||
//! # Fluent Resources
|
||||
//!
|
||||
//! 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/
|
||||
//! ├── common.ftl
|
||||
//! ├── en-US/
|
||||
//! │ ├── default.ftl
|
||||
//! │ └── main.ftl
|
||||
//! ├── es-ES/
|
||||
//! │ ├── default.ftl
|
||||
//! │ └── main.ftl
|
||||
//! ├── es-MX/
|
||||
//! │ ├── default.ftl
|
||||
//! │ └── main.ftl
|
||||
//! └── fr/
|
||||
//! ├── default.ftl
|
||||
//! └── main.ftl
|
||||
//! ```
|
||||
//!
|
||||
//! Example of a file *src/locale/en-US/main.ftl*:
|
||||
//!
|
||||
//! ```text
|
||||
//! hello-world = Hello world!
|
||||
//! hello-user = Hello, {$userName}!
|
||||
//! shared-photos =
|
||||
//! {$userName} {$photoCount ->
|
||||
//! [one] added a new photo
|
||||
//! *[other] added {$photoCount} new photos
|
||||
//! } of {$userGender ->
|
||||
//! [male] him and his family
|
||||
//! [female] her and her family
|
||||
//! *[other] the family
|
||||
//! }.
|
||||
//! ```
|
||||
//!
|
||||
//! Example of the equivalent file *src/locale/es-ES/main.ftl*:
|
||||
//!
|
||||
//! ```text
|
||||
//! hello-world = Hola mundo!
|
||||
//! hello-user = ¡Hola, {$userName}!
|
||||
//! shared-photos =
|
||||
//! {$userName} {$photoCount ->
|
||||
//! [one] ha añadido una nueva foto
|
||||
//! *[other] ha añadido {$photoCount} nuevas fotos
|
||||
//! } de {$userGender ->
|
||||
//! [male] él y su familia
|
||||
//! [female] ella y su familia
|
||||
//! *[other] la familia
|
||||
//! }.
|
||||
//! ```
|
||||
//!
|
||||
//! # How to apply localization in your code
|
||||
//!
|
||||
//! Once you have created your FTL resource directory, use the
|
||||
//! [`static_locales!`](crate::static_locales) macro to integrate them into your module or
|
||||
//! application. If your resources are located in the `"src/locale"` directory, simply declare:
|
||||
//!
|
||||
//! ```
|
||||
//! use pagetop::prelude::*;
|
||||
//!
|
||||
//! static_locales!(LOCALES_SAMPLE);
|
||||
//! ```
|
||||
//!
|
||||
//! But if they are in another directory, then you can use:
|
||||
//!
|
||||
//! ```
|
||||
//! use pagetop::prelude::*;
|
||||
//!
|
||||
//! static_locales!(LOCALES_SAMPLE in "path/to/locale");
|
||||
//! ```
|
||||
|
||||
use crate::html::{Markup, PreEscaped};
|
||||
use crate::{global, kv, AutoDefault};
|
||||
|
||||
pub use fluent_bundle::FluentValue;
|
||||
pub use fluent_templates;
|
||||
pub use unic_langid::LanguageIdentifier;
|
||||
|
||||
use fluent_templates::Loader;
|
||||
use fluent_templates::StaticLoader as Locales;
|
||||
|
||||
use unic_langid::langid;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
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"),
|
||||
"en-GB" => (langid!("en-GB"), "English (British)"),
|
||||
"en-US" => (langid!("en-US"), "English (United States)"),
|
||||
"es" => (langid!("es-ES"), "Spanish"),
|
||||
"es-ES" => (langid!("es-ES"), "Spanish (Spain)"),
|
||||
]
|
||||
});
|
||||
|
||||
pub static LANGID_FALLBACK: LazyLock<LanguageIdentifier> = LazyLock::new(|| langid!("en-US"));
|
||||
|
||||
/// Sets the application's default
|
||||
/// [Unicode Language Identifier](https://unicode.org/reports/tr35/tr35.html#Unicode_language_identifier)
|
||||
/// through `SETTINGS.app.language`.
|
||||
pub static DEFAULT_LANGID: 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();
|
||||
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, 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! {
|
||||
static $LOCALES = {
|
||||
locales: "src/locale",
|
||||
$( core_locales: $core_locales, )?
|
||||
fallback_language: "en-US",
|
||||
// Removes unicode isolating marks around arguments.
|
||||
customise: |bundle| bundle.set_use_isolating(false),
|
||||
};
|
||||
}
|
||||
};
|
||||
( $LOCALES:ident in $dir_locales:literal $(, $core_locales:literal)? ) => {
|
||||
$crate::locale::fluent_templates::static_loader! {
|
||||
static $LOCALES = {
|
||||
locales: $dir_locales,
|
||||
$( core_locales: $core_locales, )?
|
||||
fallback_language: "en-US",
|
||||
// Removes unicode isolating marks around arguments.
|
||||
customise: |bundle| bundle.set_use_isolating(false),
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static_locales!(LOCALES_PAGETOP);
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
enum L10nOp {
|
||||
#[default]
|
||||
None,
|
||||
Text(String),
|
||||
Translate(String),
|
||||
}
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub struct L10n {
|
||||
op: L10nOp,
|
||||
locales: Option<&'static Locales>,
|
||||
args: HashMap<String, FluentValue<'static>>,
|
||||
}
|
||||
|
||||
impl L10n {
|
||||
pub fn none() -> Self {
|
||||
L10n::default()
|
||||
}
|
||||
|
||||
pub fn n(text: impl Into<String>) -> Self {
|
||||
L10n {
|
||||
op: L10nOp::Text(text.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn l(key: impl Into<String>) -> Self {
|
||||
L10n {
|
||||
op: L10nOp::Translate(key.into()),
|
||||
locales: Some(&LOCALES_PAGETOP),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn t(key: impl Into<String>, locales: &'static Locales) -> Self {
|
||||
L10n {
|
||||
op: L10nOp::Translate(key.into()),
|
||||
locales: Some(locales),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_arg(mut self, arg: impl Into<String>, value: impl Into<String>) -> Self {
|
||||
self.args
|
||||
.insert(arg.into(), FluentValue::from(value.into()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn using(&self, langid: &LanguageIdentifier) -> Option<String> {
|
||||
match &self.op {
|
||||
L10nOp::None => None,
|
||||
L10nOp::Text(text) => Some(text.to_owned()),
|
||||
L10nOp::Translate(key) => match self.locales {
|
||||
Some(locales) => {
|
||||
if self.args.is_empty() {
|
||||
locales.try_lookup(langid, key)
|
||||
} else {
|
||||
locales.try_lookup_with_args(langid, key, &self.args)
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Escapes the content using the default language identifier.
|
||||
pub fn markup(&self) -> Markup {
|
||||
let content = self.using(&DEFAULT_LANGID).unwrap_or_default();
|
||||
PreEscaped(content)
|
||||
}
|
||||
|
||||
/// Escapes the content using the specified language identifier.
|
||||
pub fn escaped(&self, langid: &LanguageIdentifier) -> Markup {
|
||||
let content = self.using(langid).unwrap_or_default();
|
||||
PreEscaped(content)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for L10n {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &self.op {
|
||||
L10nOp::None => write!(f, ""),
|
||||
L10nOp::Text(text) => write!(f, "{text}"),
|
||||
L10nOp::Translate(key) => {
|
||||
if let Some(locales) = self.locales {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
if self.args.is_empty() {
|
||||
locales.lookup(
|
||||
match key.as_str() {
|
||||
LANGUAGE_SET_FAILURE => &LANGID_FALLBACK,
|
||||
_ => &DEFAULT_LANGID,
|
||||
},
|
||||
key,
|
||||
)
|
||||
} else {
|
||||
locales.lookup_with_args(
|
||||
match key.as_str() {
|
||||
LANGUAGE_SET_FAILURE => &LANGID_FALLBACK,
|
||||
_ => &DEFAULT_LANGID,
|
||||
},
|
||||
key,
|
||||
&self.args,
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
write!(f, "Unknown localization {key}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
welcome_title = Hello world!
|
||||
|
||||
welcome_intro = Verifying the proper operation of your { $app } installation.
|
||||
welcome_powered = A web solution powered by { $pagetop }.
|
||||
|
||||
welcome_page = Welcome Page
|
||||
welcome_subtitle = Are you a { $app } user?
|
||||
welcome_text1 = If you don't know what this page is about, this probably means that the site is either experiencing problems or is undergoing routine maintenance.
|
||||
welcome_text2 = If the issue persists, please contact your system administrator for assistance.
|
||||
|
||||
welcome_pagetop_title = About PageTop
|
||||
welcome_pagetop_text1 = If you can read this page, it means that the PageTop server is working properly, but has not yet been configured.
|
||||
welcome_pagetop_text2 = PageTop is a <a href="https://www.rust-lang.org" target="_blank">Rust</a>-based web development framework designed to create modular, extensible, and configurable web solutions.
|
||||
welcome_pagetop_text3 = For detailed information, please visit the <a href="https://docs.rs/pagetop/latest/pagetop" target="_blank">official technical documentation</a>.
|
||||
|
||||
welcome_issues_title = Reporting Issues
|
||||
welcome_issues_text1 = To report any issues with PageTop, please use <a href="https://github.com/manuelcillero/pagetop/issues" target="_blank">GitHub</a>. However, check the existing error reports to avoid duplicates.
|
||||
welcome_issues_text2 = For issues specific to { $app }, please refer to its official repository or support channel, rather than directly to PageTop.
|
||||
|
||||
welcome_have_fun = Write code. Break nothing. Repeat.
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
welcome_title = ¡Hola mundo!
|
||||
|
||||
welcome_intro = Verificando el funcionamiento de tu instalación de { $app }.
|
||||
welcome_powered = Una solución web con la potencia de { $pagetop }.
|
||||
|
||||
welcome_page = Página de Bienvenida
|
||||
welcome_subtitle = ¿Eres usuario de { $app }?
|
||||
welcome_text1 = Si no sabes de qué trata esta página probablemente significa que el sitio está experimentando problemas o está pasando por un mantenimiento de rutina.
|
||||
welcome_text2 = Si el problema persiste, por favor póngase en contacto con el administrador del sistema.
|
||||
|
||||
welcome_pagetop_title = Sobre PageTop
|
||||
welcome_pagetop_text1 = Si puedes leer esta página significa que el servidor PageTop funciona correctamente, pero aún no se ha configurado.
|
||||
welcome_pagetop_text2 = PageTop es un entorno de desarrollo web basado en <a href="https://www.rust-lang.org/es" target="_blank">Rust</a>, diseñado para crear soluciones web modulares, extensibles y configurables.
|
||||
welcome_pagetop_text3 = Para más información visita la <a href="https://docs.rs/pagetop/latest/pagetop" target="_blank">documentación técnica oficial</a>.
|
||||
|
||||
welcome_issues_title = Informando Problemas
|
||||
welcome_issues_text1 = Para comunicar cualquier problema con PageTop utiliza <a href="https://github.com/manuelcillero/pagetop/issues" target="_blank">GitHub</a>. No obstante, comprueba los informes de errores ya existentes para evitar duplicados.
|
||||
welcome_issues_text2 = Si son fallos específicos de { $app }, por favor acude a su repositorio oficial o canal de soporte, y no al de PageTop directamente.
|
||||
|
||||
welcome_have_fun = Escribe código. No rompas nada. Repite.
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
//! The `PageTop` Prelude.
|
||||
|
||||
// RE-EXPORTED.
|
||||
|
||||
pub use crate::{concat_string, html, main, paste, test};
|
||||
|
||||
pub use crate::{AutoDefault, StaticResources, TypeId, Weight};
|
||||
|
||||
// MACROS.
|
||||
|
||||
// crate::util
|
||||
pub use crate::{kv, static_config};
|
||||
// crate::locale
|
||||
pub use crate::static_locales;
|
||||
// crate::service
|
||||
pub use crate::{static_files, static_files_service};
|
||||
// crate::core::action
|
||||
pub use crate::actions;
|
||||
|
||||
// API.
|
||||
|
||||
pub use crate::util;
|
||||
|
||||
pub use crate::trace;
|
||||
|
||||
pub use crate::html::*;
|
||||
|
||||
pub use crate::locale::*;
|
||||
|
||||
pub use crate::service;
|
||||
pub use crate::service::{HttpMessage, HttpRequest};
|
||||
|
||||
pub use crate::core::{AnyBase, AnyTo};
|
||||
|
||||
pub use crate::core::action::*;
|
||||
pub use crate::core::package::*;
|
||||
pub use crate::core::theme::*;
|
||||
|
||||
pub use crate::response::{json::*, redirect::*, ResponseError};
|
||||
|
||||
pub use crate::global;
|
||||
|
||||
pub use crate::app::Application;
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
//! Web request response variants.
|
||||
|
||||
pub use actix_web::ResponseError;
|
||||
|
||||
pub mod json;
|
||||
|
||||
pub mod redirect;
|
||||
|
|
@ -1 +0,0 @@
|
|||
pub use actix_web::web::Json;
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
//! Perform redirections in HTTP.
|
||||
//!
|
||||
//! **URL redirection**, also known as *URL forwarding*, is a technique to give more than one URL
|
||||
//! address to a web resource. HTTP has a response called ***HTTP redirect*** for this operation
|
||||
//! (see [Redirections in HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections)).
|
||||
//!
|
||||
//! There are several types of redirects, sorted into three categories:
|
||||
//!
|
||||
//! * **Permanent redirections**. These redirections are meant to last forever. They imply that
|
||||
//! the original URL should no longer be used, and replaced with the new one. Search engine
|
||||
//! robots, RSS readers, and other crawlers will update the original URL for the resource.
|
||||
//!
|
||||
//! * **Temporary redirections**. Sometimes the requested resource can't be accessed from its
|
||||
//! canonical location, but it can be accessed from another place. In this case, a temporary
|
||||
//! redirect can be used. Search engine robots and other crawlers don't memorize the new,
|
||||
//! temporary URL. Temporary redirections are also used when creating, updating, or deleting
|
||||
//! resources, to show temporary progress pages.
|
||||
//!
|
||||
//! * **Special redirections**.
|
||||
|
||||
use crate::service::HttpResponse;
|
||||
|
||||
pub struct Redirect;
|
||||
|
||||
impl Redirect {
|
||||
/// Permanent redirection. Status Code **301**. GET methods unchanged. Others may or may not be
|
||||
/// changed to GET. Typical for reorganization of a website.
|
||||
pub fn moved(redirect_to_url: &str) -> HttpResponse {
|
||||
HttpResponse::MovedPermanently()
|
||||
.append_header(("Location", redirect_to_url))
|
||||
.finish()
|
||||
}
|
||||
|
||||
/// Permanent redirection. Status Code **308**. Method and body not changed. Typical for
|
||||
/// reorganization of a website, with non-GET links/operations.
|
||||
pub fn permanent(redirect_to_url: &str) -> HttpResponse {
|
||||
HttpResponse::PermanentRedirect()
|
||||
.append_header(("Location", redirect_to_url))
|
||||
.finish()
|
||||
}
|
||||
|
||||
/// Temporary redirection. Status Code **302**. GET methods unchanged. Others may or may not be
|
||||
/// changed to GET. Used when the web page is temporarily unavailable for unforeseen reasons.
|
||||
pub fn found(redirect_to_url: &str) -> HttpResponse {
|
||||
HttpResponse::Found()
|
||||
.append_header(("Location", redirect_to_url))
|
||||
.finish()
|
||||
}
|
||||
|
||||
/// Temporary redirection. Status Code **303**. GET methods unchanged. Others changed to GET
|
||||
/// (body lost). Used to redirect after a PUT or a POST, so that refreshing the result page
|
||||
/// doesn't re-trigger the operation.
|
||||
pub fn see_other(redirect_to_url: &str) -> HttpResponse {
|
||||
HttpResponse::SeeOther()
|
||||
.append_header(("Location", redirect_to_url))
|
||||
.finish()
|
||||
}
|
||||
|
||||
/// Temporary redirection. Status Code **307**. Method and body not changed. The web page is
|
||||
/// temporarily unavailable for unforeseen reasons. Better than [`found()`](Self::found) when
|
||||
/// non-GET operations are available on the site.
|
||||
pub fn temporary(redirect_to_url: &str) -> HttpResponse {
|
||||
HttpResponse::TemporaryRedirect()
|
||||
.append_header(("Location", redirect_to_url))
|
||||
.finish()
|
||||
}
|
||||
|
||||
/// Special redirection. Status Code **304**. Redirects a page to the locally cached copy (that
|
||||
/// was stale). Sent for revalidated conditional requests. Indicates that the cached response is
|
||||
/// still fresh and can be used.
|
||||
pub fn not_modified(redirect_to_url: &str) -> HttpResponse {
|
||||
HttpResponse::NotModified()
|
||||
.append_header(("Location", redirect_to_url))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
//! Essential web framework ([actix-web](https://docs.rs/actix-web)).
|
||||
|
||||
pub use actix_session::Session;
|
||||
pub use actix_web::body::BoxBody;
|
||||
pub use actix_web::dev::Server;
|
||||
pub use actix_web::dev::ServiceFactory as Factory;
|
||||
pub use actix_web::dev::ServiceRequest as Request;
|
||||
pub use actix_web::dev::ServiceResponse as Response;
|
||||
pub use actix_web::{cookie, get, http, rt, test, web};
|
||||
pub use actix_web::{App, Error, HttpMessage, HttpRequest, HttpResponse, HttpServer, Responder};
|
||||
|
||||
pub use actix_web_files::Files as ActixFiles;
|
||||
pub use actix_web_static_files::ResourceFiles;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! static_files {
|
||||
( $bundle:ident ) => {
|
||||
$crate::paste! {
|
||||
mod [<static_files_ $bundle>] {
|
||||
include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs"));
|
||||
}
|
||||
}
|
||||
};
|
||||
( $bundle:ident => $STATIC:ident ) => {
|
||||
$crate::paste! {
|
||||
mod [<static_files_ $bundle>] {
|
||||
include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs"));
|
||||
}
|
||||
static $STATIC: std::sync::LazyLock<StaticResources> = std::sync::LazyLock::new(
|
||||
[<static_files_ $bundle>]::$bundle
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! static_files_service {
|
||||
( $scfg:ident, $bundle:ident => $path:expr $(, [$root:expr, $relative:expr])? ) => {{
|
||||
$crate::paste! {
|
||||
let span = $crate::trace::debug_span!("Configuring static files ", path = $path);
|
||||
let _ = span.in_scope(|| {
|
||||
let mut serve_embedded:bool = true;
|
||||
$(
|
||||
if !$root.is_empty() && !$relative.is_empty() {
|
||||
if let Ok(absolute) = $crate::util::absolute_dir($root, $relative) {
|
||||
$scfg.service($crate::service::ActixFiles::new(
|
||||
$path,
|
||||
absolute,
|
||||
).show_files_listing());
|
||||
serve_embedded = false
|
||||
}
|
||||
}
|
||||
)?
|
||||
if serve_embedded {
|
||||
$scfg.service($crate::service::ResourceFiles::new(
|
||||
$path,
|
||||
[<static_files_ $bundle>]::$bundle(),
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
//! Application tracing and event logging.
|
||||
//!
|
||||
//! `PageTop` collects application diagnostic information in a structured and event-based manner.
|
||||
//!
|
||||
//! In asynchronous systems, interpreting traditional log messages often becomes complicated.
|
||||
//! Individual tasks are multiplexed to the same thread, and associated events and log messages get
|
||||
//! intermingled, making it difficult to follow the logical sequence.
|
||||
//!
|
||||
//! `PageTop` uses [`tracing`](https://docs.rs/tracing) to allow **applications** and **modules** to
|
||||
//! log structured events with added information about *temporality* and *causality*. Unlike a log
|
||||
//! message, a span has a start and end time, can enter and exit the execution flow, and can exist
|
||||
//! within a nested tree of similar spans. Additionally, these spans are *structured*, with the
|
||||
//! ability to record data types and text messages.
|
||||
|
||||
use crate::global;
|
||||
|
||||
pub use tracing::{debug, error, info, trace, warn};
|
||||
pub use tracing::{debug_span, error_span, info_span, trace_span, warn_span};
|
||||
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
/// Application tracing and event logging.
|
||||
///
|
||||
/// To increase performance, a dedicated thread uses a non-blocking writer system that acts
|
||||
/// periodically instead of sending each trace or event instantly. If the program terminates
|
||||
/// abruptly (e.g., due to a panic! or a `std::process::exit`), some traces or events might not be
|
||||
/// sent.
|
||||
///
|
||||
/// Since traces or events logged shortly before an application crash are often important for
|
||||
/// diagnosing the cause of the failure, `Lazy<WorkerGuard>` ensures that all stored logs are sent
|
||||
/// before terminating execution.
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub(crate) static TRACING: LazyLock<WorkerGuard> = LazyLock::new(|| {
|
||||
let env_filter = EnvFilter::try_new(&global::SETTINGS.log.tracing)
|
||||
.unwrap_or_else(|_| EnvFilter::new("Info"));
|
||||
|
||||
let rolling = global::SETTINGS.log.rolling.to_lowercase();
|
||||
|
||||
let (non_blocking, guard) = match rolling.as_str() {
|
||||
"stdout" => tracing_appender::non_blocking(std::io::stdout()),
|
||||
_ => tracing_appender::non_blocking({
|
||||
let path = &global::SETTINGS.log.path;
|
||||
let prefix = &global::SETTINGS.log.prefix;
|
||||
match rolling.as_str() {
|
||||
"daily" => tracing_appender::rolling::daily(path, prefix),
|
||||
"hourly" => tracing_appender::rolling::hourly(path, prefix),
|
||||
"minutely" => tracing_appender::rolling::minutely(path, prefix),
|
||||
"endless" => tracing_appender::rolling::never(path, prefix),
|
||||
_ => {
|
||||
println!(
|
||||
"Rolling value \"{}\" not valid. Using \"daily\". Check the settings file.",
|
||||
global::SETTINGS.log.rolling,
|
||||
);
|
||||
tracing_appender::rolling::daily(path, prefix)
|
||||
}
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
let subscriber = tracing_subscriber::fmt()
|
||||
.with_env_filter(env_filter)
|
||||
.with_writer(non_blocking)
|
||||
.with_ansi(rolling.as_str() == "stdout");
|
||||
|
||||
match global::SETTINGS.log.format.to_lowercase().as_str() {
|
||||
"json" => subscriber.json().init(),
|
||||
"full" => subscriber.init(),
|
||||
"compact" => subscriber.compact().init(),
|
||||
"pretty" => subscriber.pretty().init(),
|
||||
_ => {
|
||||
println!(
|
||||
"Tracing format \"{}\" not valid. Using \"Full\". Check the settings file.",
|
||||
global::SETTINGS.log.format,
|
||||
);
|
||||
subscriber.init();
|
||||
}
|
||||
}
|
||||
|
||||
guard
|
||||
});
|
||||
|
|
@ -1,298 +0,0 @@
|
|||
//! Useful functions and macros.
|
||||
|
||||
pub mod config;
|
||||
|
||||
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),
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
//! Retrieve settings values from configuration files.
|
||||
|
||||
mod data;
|
||||
mod de;
|
||||
mod error;
|
||||
mod file;
|
||||
mod path;
|
||||
mod source;
|
||||
mod value;
|
||||
|
||||
use crate::concat_string;
|
||||
use crate::util::config::data::ConfigData;
|
||||
use crate::util::config::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 rm = 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(&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 the general 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");
|
||||
|
||||
settings
|
||||
});
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
use crate::util::config::error::*;
|
||||
use crate::util::config::path;
|
||||
use crate::util::config::source::Source;
|
||||
use crate::util::config::value::Value;
|
||||
|
||||
use serde::de::Deserialize;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum ConfigKind {
|
||||
// A mutable configuration. This is the default.
|
||||
Mutable {
|
||||
defaults: HashMap<path::Expression, Value>,
|
||||
overrides: HashMap<path::Expression, Value>,
|
||||
sources: Vec<Box<dyn Source + Send + Sync>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for ConfigKind {
|
||||
fn default() -> Self {
|
||||
ConfigKind::Mutable {
|
||||
defaults: HashMap::new(),
|
||||
overrides: HashMap::new(),
|
||||
sources: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A prioritized configuration repository. It maintains a set of configuration sources, fetches
|
||||
/// values to populate those, and provides them according to the source's priority.
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct ConfigData {
|
||||
kind: ConfigKind,
|
||||
/// Root of the cached configuration.
|
||||
pub cache: Value,
|
||||
}
|
||||
|
||||
impl ConfigData {
|
||||
/// Merge in a configuration property source.
|
||||
pub fn merge<T>(&mut self, source: T) -> Result<&mut ConfigData>
|
||||
where
|
||||
T: 'static,
|
||||
T: Source + Send + Sync,
|
||||
{
|
||||
match self.kind {
|
||||
ConfigKind::Mutable {
|
||||
ref mut sources, ..
|
||||
} => {
|
||||
sources.push(Box::new(source));
|
||||
}
|
||||
}
|
||||
|
||||
self.refresh()
|
||||
}
|
||||
|
||||
/// Refresh the configuration cache with fresh data from added sources.
|
||||
///
|
||||
/// Configuration is automatically refreshed after a mutation operation (`set`, `merge`,
|
||||
/// `set_default`, etc.).
|
||||
pub fn refresh(&mut self) -> Result<&mut ConfigData> {
|
||||
self.cache = match self.kind {
|
||||
// TODO: We need to actually merge in all the stuff.
|
||||
ConfigKind::Mutable {
|
||||
ref overrides,
|
||||
ref sources,
|
||||
ref defaults,
|
||||
} => {
|
||||
let mut cache: Value = HashMap::<String, Value>::new().into();
|
||||
|
||||
// Add defaults.
|
||||
for (key, val) in defaults {
|
||||
key.set(&mut cache, val.clone());
|
||||
}
|
||||
|
||||
// Add sources.
|
||||
sources.collect_to(&mut cache)?;
|
||||
|
||||
// Add overrides.
|
||||
for (key, val) in overrides {
|
||||
key.set(&mut cache, val.clone());
|
||||
}
|
||||
|
||||
cache
|
||||
}
|
||||
};
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn set_default<T>(&mut self, key: &str, value: T) -> Result<&mut ConfigData>
|
||||
where
|
||||
T: Into<Value>,
|
||||
{
|
||||
match self.kind {
|
||||
ConfigKind::Mutable {
|
||||
ref mut defaults, ..
|
||||
} => {
|
||||
defaults.insert(key.parse()?, value.into());
|
||||
}
|
||||
};
|
||||
|
||||
self.refresh()
|
||||
}
|
||||
|
||||
pub fn set<T>(&mut self, key: &str, value: T) -> Result<&mut ConfigData>
|
||||
where
|
||||
T: Into<Value>,
|
||||
{
|
||||
match self.kind {
|
||||
ConfigKind::Mutable {
|
||||
ref mut overrides, ..
|
||||
} => {
|
||||
overrides.insert(key.parse()?, value.into());
|
||||
}
|
||||
};
|
||||
|
||||
self.refresh()
|
||||
}
|
||||
|
||||
/// Attempt to deserialize the entire configuration into the requested type.
|
||||
pub fn try_into<'de, T: Deserialize<'de>>(self) -> Result<T> {
|
||||
T::deserialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for ConfigData {
|
||||
fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
|
||||
Box::new((*self).clone())
|
||||
}
|
||||
|
||||
fn collect(&self) -> Result<HashMap<String, Value>> {
|
||||
self.cache.clone().into_table()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,462 +0,0 @@
|
|||
use crate::util::config::data::ConfigData;
|
||||
use crate::util::config::error::*;
|
||||
use crate::util::config::value::{Table, Value, ValueKind};
|
||||
|
||||
use serde::de;
|
||||
use serde::forward_to_deserialize_any;
|
||||
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::iter::Enumerate;
|
||||
|
||||
impl<'de> de::Deserializer<'de> for Value {
|
||||
type Error = ConfigError;
|
||||
|
||||
#[inline]
|
||||
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor<'de>,
|
||||
{
|
||||
// Deserialize based on the underlying type.
|
||||
match self.kind {
|
||||
ValueKind::Nil => visitor.visit_unit(),
|
||||
ValueKind::Integer(i) => visitor.visit_i64(i),
|
||||
ValueKind::Boolean(b) => visitor.visit_bool(b),
|
||||
ValueKind::Float(f) => visitor.visit_f64(f),
|
||||
ValueKind::String(s) => visitor.visit_string(s),
|
||||
ValueKind::Array(values) => visitor.visit_seq(SeqAccess::new(values)),
|
||||
ValueKind::Table(map) => visitor.visit_map(MapAccess::new(map)),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_bool<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
visitor.visit_bool(self.into_bool()?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_i8<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
// FIXME: This should *fail* if the value does not fit in the requets integer type.
|
||||
visitor.visit_i8(self.into_int()? as i8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_i16<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
// FIXME: This should *fail* if the value does not fit in the requets integer type.
|
||||
visitor.visit_i16(self.into_int()? as i16)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_i32<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
// FIXME: This should *fail* if the value does not fit in the requets integer type.
|
||||
visitor.visit_i32(self.into_int()? as i32)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_i64<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
visitor.visit_i64(self.into_int()?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_u8<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
// FIXME: This should *fail* if the value does not fit in the requets integer type.
|
||||
visitor.visit_u8(self.into_int()? as u8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_u16<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
// FIXME: This should *fail* if the value does not fit in the requets integer type.
|
||||
visitor.visit_u16(self.into_int()? as u16)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_u32<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
// FIXME: This should *fail* if the value does not fit in the requets integer type.
|
||||
visitor.visit_u32(self.into_int()? as u32)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_u64<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
// FIXME: This should *fail* if the value does not fit in the requets integer type.
|
||||
visitor.visit_u64(self.into_int()? as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_f32<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
visitor.visit_f32(self.into_float()? as f32)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_f64<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
visitor.visit_f64(self.into_float()?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_str<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
visitor.visit_string(self.into_str()?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_string<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
visitor.visit_string(self.into_str()?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor<'de>,
|
||||
{
|
||||
// Match an explicit nil as None and everything else as Some.
|
||||
match self.kind {
|
||||
ValueKind::Nil => visitor.visit_none(),
|
||||
_ => visitor.visit_some(self),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_newtype_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor<'de>,
|
||||
{
|
||||
visitor.visit_newtype_struct(self)
|
||||
}
|
||||
|
||||
fn deserialize_enum<V>(
|
||||
self,
|
||||
name: &'static str,
|
||||
variants: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor<'de>,
|
||||
{
|
||||
visitor.visit_enum(EnumAccess {
|
||||
value: self,
|
||||
name,
|
||||
variants,
|
||||
})
|
||||
}
|
||||
|
||||
forward_to_deserialize_any! {
|
||||
char seq
|
||||
bytes byte_buf map struct unit
|
||||
identifier ignored_any unit_struct tuple_struct tuple
|
||||
}
|
||||
}
|
||||
|
||||
struct StrDeserializer<'a>(&'a str);
|
||||
|
||||
impl<'de, 'a> de::Deserializer<'de> for StrDeserializer<'a> {
|
||||
type Error = ConfigError;
|
||||
|
||||
#[inline]
|
||||
fn deserialize_any<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
visitor.visit_str(self.0)
|
||||
}
|
||||
|
||||
forward_to_deserialize_any! {
|
||||
bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
|
||||
bytes byte_buf map struct unit enum newtype_struct
|
||||
identifier ignored_any unit_struct tuple_struct tuple option
|
||||
}
|
||||
}
|
||||
|
||||
struct SeqAccess {
|
||||
elements: Enumerate<::std::vec::IntoIter<Value>>,
|
||||
}
|
||||
|
||||
impl SeqAccess {
|
||||
fn new(elements: Vec<Value>) -> Self {
|
||||
SeqAccess {
|
||||
elements: elements.into_iter().enumerate(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> de::SeqAccess<'de> for SeqAccess {
|
||||
type Error = ConfigError;
|
||||
|
||||
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
|
||||
where
|
||||
T: de::DeserializeSeed<'de>,
|
||||
{
|
||||
match self.elements.next() {
|
||||
Some((idx, value)) => seed
|
||||
.deserialize(value)
|
||||
.map(Some)
|
||||
.map_err(|e| e.prepend_index(idx)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> Option<usize> {
|
||||
match self.elements.size_hint() {
|
||||
(lower, Some(upper)) if lower == upper => Some(upper),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MapAccess {
|
||||
elements: VecDeque<(String, Value)>,
|
||||
}
|
||||
|
||||
impl MapAccess {
|
||||
fn new(table: HashMap<String, Value>) -> Self {
|
||||
MapAccess {
|
||||
elements: table.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> de::MapAccess<'de> for MapAccess {
|
||||
type Error = ConfigError;
|
||||
|
||||
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
|
||||
where
|
||||
K: de::DeserializeSeed<'de>,
|
||||
{
|
||||
if let Some((key_s, _)) = self.elements.front() {
|
||||
let key_de = Value::new(None, key_s as &str);
|
||||
let key = de::DeserializeSeed::deserialize(seed, key_de)?;
|
||||
|
||||
Ok(Some(key))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::DeserializeSeed<'de>,
|
||||
{
|
||||
let (key, value) = self.elements.pop_front().unwrap();
|
||||
de::DeserializeSeed::deserialize(seed, value).map_err(|e| e.prepend_key(key))
|
||||
}
|
||||
}
|
||||
|
||||
struct EnumAccess {
|
||||
value: Value,
|
||||
name: &'static str,
|
||||
variants: &'static [&'static str],
|
||||
}
|
||||
|
||||
impl EnumAccess {
|
||||
fn variant_deserializer(&self, name: &str) -> Result<StrDeserializer> {
|
||||
self.variants
|
||||
.iter()
|
||||
.find(|&&s| s == name)
|
||||
.map(|&s| StrDeserializer(s))
|
||||
.ok_or_else(|| self.no_constructor_error(name))
|
||||
}
|
||||
|
||||
fn table_deserializer(&self, table: &Table) -> Result<StrDeserializer> {
|
||||
if table.len() == 1 {
|
||||
self.variant_deserializer(table.iter().next().unwrap().0)
|
||||
} else {
|
||||
Err(self.structural_error())
|
||||
}
|
||||
}
|
||||
|
||||
fn no_constructor_error(&self, supposed_variant: &str) -> ConfigError {
|
||||
ConfigError::Message(format!(
|
||||
"enum {} does not have variant constructor {}",
|
||||
self.name, supposed_variant
|
||||
))
|
||||
}
|
||||
|
||||
fn structural_error(&self) -> ConfigError {
|
||||
ConfigError::Message(format!(
|
||||
"value of enum {} should be represented by either string or table with exactly one key",
|
||||
self.name
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> de::EnumAccess<'de> for EnumAccess {
|
||||
type Error = ConfigError;
|
||||
type Variant = Self;
|
||||
|
||||
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant)>
|
||||
where
|
||||
V: de::DeserializeSeed<'de>,
|
||||
{
|
||||
let value = {
|
||||
let deserializer = match self.value.kind {
|
||||
ValueKind::String(ref s) => self.variant_deserializer(s),
|
||||
ValueKind::Table(ref t) => self.table_deserializer(t),
|
||||
_ => Err(self.structural_error()),
|
||||
}?;
|
||||
seed.deserialize(deserializer)?
|
||||
};
|
||||
|
||||
Ok((value, self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> de::VariantAccess<'de> for EnumAccess {
|
||||
type Error = ConfigError;
|
||||
|
||||
fn unit_variant(self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value>
|
||||
where
|
||||
T: de::DeserializeSeed<'de>,
|
||||
{
|
||||
match self.value.kind {
|
||||
ValueKind::Table(t) => seed.deserialize(t.into_iter().next().unwrap().1),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor<'de>,
|
||||
{
|
||||
match self.value.kind {
|
||||
ValueKind::Table(t) => {
|
||||
de::Deserializer::deserialize_seq(t.into_iter().next().unwrap().1, visitor)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor<'de>,
|
||||
{
|
||||
match self.value.kind {
|
||||
ValueKind::Table(t) => {
|
||||
de::Deserializer::deserialize_map(t.into_iter().next().unwrap().1, visitor)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> de::Deserializer<'de> for ConfigData {
|
||||
type Error = ConfigError;
|
||||
|
||||
#[inline]
|
||||
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor<'de>,
|
||||
{
|
||||
// Deserialize based on the underlying type.
|
||||
match self.cache.kind {
|
||||
ValueKind::Nil => visitor.visit_unit(),
|
||||
ValueKind::Integer(i) => visitor.visit_i64(i),
|
||||
ValueKind::Boolean(b) => visitor.visit_bool(b),
|
||||
ValueKind::Float(f) => visitor.visit_f64(f),
|
||||
ValueKind::String(s) => visitor.visit_string(s),
|
||||
ValueKind::Array(values) => visitor.visit_seq(SeqAccess::new(values)),
|
||||
ValueKind::Table(map) => visitor.visit_map(MapAccess::new(map)),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_bool<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
visitor.visit_bool(self.cache.into_bool()?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_i8<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
// FIXME: This should *fail* if the value does not fit in the requets integer type.
|
||||
visitor.visit_i8(self.cache.into_int()? as i8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_i16<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
// FIXME: This should *fail* if the value does not fit in the requets integer type.
|
||||
visitor.visit_i16(self.cache.into_int()? as i16)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_i32<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
// FIXME: This should *fail* if the value does not fit in the requets integer type.
|
||||
visitor.visit_i32(self.cache.into_int()? as i32)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_i64<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
visitor.visit_i64(self.cache.into_int()?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_u8<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
// FIXME: This should *fail* if the value does not fit in the requets integer type.
|
||||
visitor.visit_u8(self.cache.into_int()? as u8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_u16<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
// FIXME: This should *fail* if the value does not fit in the requets integer type.
|
||||
visitor.visit_u16(self.cache.into_int()? as u16)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_u32<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
// FIXME: This should *fail* if the value does not fit in the requets integer type.
|
||||
visitor.visit_u32(self.cache.into_int()? as u32)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_u64<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
// FIXME: This should *fail* if the value does not fit in the requets integer type.
|
||||
visitor.visit_u64(self.cache.into_int()? as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_f32<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
visitor.visit_f32(self.cache.into_float()? as f32)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_f64<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
visitor.visit_f64(self.cache.into_float()?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_str<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
visitor.visit_string(self.cache.into_str()?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_string<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
visitor.visit_string(self.cache.into_str()?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor<'de>,
|
||||
{
|
||||
// Match an explicit nil as None and everything else as Some.
|
||||
match self.cache.kind {
|
||||
ValueKind::Nil => visitor.visit_none(),
|
||||
_ => visitor.visit_some(self),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_enum<V>(
|
||||
self,
|
||||
name: &'static str,
|
||||
variants: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor<'de>,
|
||||
{
|
||||
visitor.visit_enum(EnumAccess {
|
||||
value: self.cache,
|
||||
name,
|
||||
variants,
|
||||
})
|
||||
}
|
||||
|
||||
forward_to_deserialize_any! {
|
||||
char seq
|
||||
bytes byte_buf map struct unit newtype_struct
|
||||
identifier ignored_any unit_struct tuple_struct tuple
|
||||
}
|
||||
}
|
||||
|
|
@ -1,222 +0,0 @@
|
|||
use nom;
|
||||
use serde::de;
|
||||
use serde::ser;
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::result;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Unexpected {
|
||||
Bool(bool),
|
||||
Integer(i64),
|
||||
Float(f64),
|
||||
Str(String),
|
||||
Unit,
|
||||
Seq,
|
||||
Map,
|
||||
}
|
||||
|
||||
impl fmt::Display for Unexpected {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
|
||||
match *self {
|
||||
Unexpected::Bool(b) => write!(f, "boolean `{}`", b),
|
||||
Unexpected::Integer(i) => write!(f, "integer `{}`", i),
|
||||
Unexpected::Float(v) => write!(f, "floating point `{}`", v),
|
||||
Unexpected::Str(ref s) => write!(f, "string {:?}", s),
|
||||
Unexpected::Unit => write!(f, "unit value"),
|
||||
Unexpected::Seq => write!(f, "sequence"),
|
||||
Unexpected::Map => write!(f, "map"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents all possible errors that can occur when working with configuration.
|
||||
pub enum ConfigError {
|
||||
/// Configuration is frozen and no further mutations can be made.
|
||||
Frozen,
|
||||
|
||||
/// Configuration property was not found.
|
||||
NotFound(String),
|
||||
|
||||
/// Configuration path could not be parsed.
|
||||
PathParse(nom::error::ErrorKind),
|
||||
|
||||
/// Configuration could not be parsed from file.
|
||||
FileParse {
|
||||
/// The URI used to access the file (if not loaded from a string).
|
||||
/// Example: `/path/to/config.json`
|
||||
uri: Option<String>,
|
||||
|
||||
/// The captured error from attempting to parse the file in its desired format.
|
||||
/// This is the actual error object from the library used for the parsing.
|
||||
cause: Box<dyn Error + Send + Sync>,
|
||||
},
|
||||
|
||||
/// Value could not be converted into the requested type.
|
||||
Type {
|
||||
/// The URI that references the source that the value came from.
|
||||
/// Example: `/path/to/config.json` or `Environment` or `etcd://localhost`
|
||||
// TODO: Why is this called Origin but FileParse has a uri field?
|
||||
origin: Option<String>,
|
||||
|
||||
/// What we found when parsing the value.
|
||||
unexpected: Unexpected,
|
||||
|
||||
/// What was expected when parsing the value.
|
||||
expected: &'static str,
|
||||
|
||||
/// The key in the configuration hash of this value (if available where the error is
|
||||
/// generated).
|
||||
key: Option<String>,
|
||||
},
|
||||
|
||||
/// Custom message.
|
||||
Message(String),
|
||||
|
||||
/// Unadorned error from a foreign origin.
|
||||
Foreign(Box<dyn Error + Send + Sync>),
|
||||
}
|
||||
|
||||
impl ConfigError {
|
||||
// FIXME: pub(crate).
|
||||
#[doc(hidden)]
|
||||
pub fn invalid_type(
|
||||
origin: Option<String>,
|
||||
unexpected: Unexpected,
|
||||
expected: &'static str,
|
||||
) -> Self {
|
||||
ConfigError::Type {
|
||||
origin,
|
||||
unexpected,
|
||||
expected,
|
||||
key: None,
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: pub(crate).
|
||||
#[doc(hidden)]
|
||||
pub fn extend_with_key(self, key: &str) -> Self {
|
||||
match self {
|
||||
ConfigError::Type {
|
||||
origin,
|
||||
unexpected,
|
||||
expected,
|
||||
..
|
||||
} => ConfigError::Type {
|
||||
origin,
|
||||
unexpected,
|
||||
expected,
|
||||
key: Some(key.into()),
|
||||
},
|
||||
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
fn prepend(self, segment: String, add_dot: bool) -> Self {
|
||||
let concat = |key: Option<String>| {
|
||||
let key = key.unwrap_or_default();
|
||||
let dot = if add_dot && key.as_bytes().first().unwrap_or(&b'[') != &b'[' {
|
||||
"."
|
||||
} else {
|
||||
""
|
||||
};
|
||||
format!("{}{}{}", segment, dot, key)
|
||||
};
|
||||
match self {
|
||||
ConfigError::Type {
|
||||
origin,
|
||||
unexpected,
|
||||
expected,
|
||||
key,
|
||||
} => ConfigError::Type {
|
||||
origin,
|
||||
unexpected,
|
||||
expected,
|
||||
key: Some(concat(key)),
|
||||
},
|
||||
ConfigError::NotFound(key) => ConfigError::NotFound(concat(Some(key))),
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn prepend_key(self, key: String) -> Self {
|
||||
self.prepend(key, true)
|
||||
}
|
||||
|
||||
pub(crate) fn prepend_index(self, idx: usize) -> Self {
|
||||
self.prepend(format!("[{}]", idx), false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Alias for a `Result` with the error type set to `ConfigError`.
|
||||
pub type Result<T> = result::Result<T, ConfigError>;
|
||||
|
||||
// Forward Debug to Display for readable panic! messages.
|
||||
impl fmt::Debug for ConfigError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", *self)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ConfigError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
ConfigError::Frozen => write!(f, "configuration is frozen"),
|
||||
|
||||
ConfigError::PathParse(ref kind) => write!(f, "{}", kind.description()),
|
||||
|
||||
ConfigError::Message(ref s) => write!(f, "{}", s),
|
||||
|
||||
ConfigError::Foreign(ref cause) => write!(f, "{}", cause),
|
||||
|
||||
ConfigError::NotFound(ref key) => {
|
||||
write!(f, "configuration property {:?} not found", key)
|
||||
}
|
||||
|
||||
ConfigError::Type {
|
||||
ref origin,
|
||||
ref unexpected,
|
||||
expected,
|
||||
ref key,
|
||||
} => {
|
||||
write!(f, "invalid type: {}, expected {}", unexpected, expected)?;
|
||||
|
||||
if let Some(ref key) = *key {
|
||||
write!(f, " for key `{}`", key)?;
|
||||
}
|
||||
|
||||
if let Some(ref origin) = *origin {
|
||||
write!(f, " in {}", origin)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
ConfigError::FileParse { ref cause, ref uri } => {
|
||||
write!(f, "{}", cause)?;
|
||||
|
||||
if let Some(ref uri) = *uri {
|
||||
write!(f, " in {}", uri)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ConfigError {}
|
||||
|
||||
impl de::Error for ConfigError {
|
||||
fn custom<T: fmt::Display>(msg: T) -> Self {
|
||||
ConfigError::Message(msg.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Error for ConfigError {
|
||||
fn custom<T: fmt::Display>(msg: T) -> Self {
|
||||
ConfigError::Message(msg.to_string())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
mod source;
|
||||
mod toml;
|
||||
|
||||
use crate::util::config::error::*;
|
||||
use crate::util::config::source::Source;
|
||||
use crate::util::config::value::Value;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use self::source::FileSource;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct File<T>
|
||||
where
|
||||
T: FileSource,
|
||||
{
|
||||
source: T,
|
||||
/// A required File will error if it cannot be found.
|
||||
required: bool,
|
||||
}
|
||||
|
||||
impl File<source::FileSourceFile> {
|
||||
/// Given the basename of a file, will attempt to locate a file by setting its extension to a
|
||||
/// registered format.
|
||||
pub fn with_name(name: &str) -> Self {
|
||||
File {
|
||||
source: source::FileSourceFile::new(name.into()),
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Path> for File<source::FileSourceFile> {
|
||||
fn from(path: &'a Path) -> Self {
|
||||
File {
|
||||
source: source::FileSourceFile::new(path.to_path_buf()),
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PathBuf> for File<source::FileSourceFile> {
|
||||
fn from(path: PathBuf) -> Self {
|
||||
File {
|
||||
source: source::FileSourceFile::new(path),
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FileSource> File<T> {
|
||||
pub fn required(mut self, required: bool) -> Self {
|
||||
self.required = required;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FileSource> Source for File<T>
|
||||
where
|
||||
T: 'static,
|
||||
T: Sync + Send,
|
||||
{
|
||||
fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
|
||||
Box::new((*self).clone())
|
||||
}
|
||||
|
||||
fn collect(&self) -> Result<HashMap<String, Value>> {
|
||||
// Coerce the file contents to a string.
|
||||
let (uri, contents) = match self.source.resolve().map_err(ConfigError::Foreign) {
|
||||
Ok((uri, contents)) => (uri, contents),
|
||||
|
||||
Err(error) => {
|
||||
if !self.required {
|
||||
return Ok(HashMap::new());
|
||||
}
|
||||
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Parse the string using the given format.
|
||||
toml::parse(uri.as_ref(), &contents).map_err(|cause| ConfigError::FileParse { uri, cause })
|
||||
}
|
||||
}
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::fmt::Debug;
|
||||
use std::fs;
|
||||
use std::io::{self, Read};
|
||||
use std::iter::Iterator;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Describes where the file is sourced.
|
||||
pub trait FileSource: Debug + Clone {
|
||||
fn resolve(&self) -> Result<(Option<String>, String), Box<dyn Error + Send + Sync>>;
|
||||
}
|
||||
|
||||
/// Describes a file sourced from a file.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileSourceFile {
|
||||
/// Path of configuration file.
|
||||
name: PathBuf,
|
||||
}
|
||||
|
||||
impl FileSourceFile {
|
||||
pub fn new(name: PathBuf) -> FileSourceFile {
|
||||
FileSourceFile { name }
|
||||
}
|
||||
|
||||
fn find_file(&self) -> Result<PathBuf, Box<dyn Error + Send + Sync>> {
|
||||
// First check for an _exact_ match.
|
||||
let mut filename = env::current_dir()?.as_path().join(self.name.clone());
|
||||
if filename.is_file() {
|
||||
if ["toml"].contains(
|
||||
&filename
|
||||
.extension()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.as_ref(),
|
||||
) {
|
||||
return Ok(filename);
|
||||
}
|
||||
|
||||
Err(Box::new(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
format!(
|
||||
"configuration file \"{}\" is not of a registered file format",
|
||||
filename.to_string_lossy()
|
||||
),
|
||||
)))
|
||||
} else {
|
||||
filename.set_extension("toml");
|
||||
|
||||
if filename.is_file() {
|
||||
return Ok(filename);
|
||||
}
|
||||
|
||||
Err(Box::new(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
format!(
|
||||
"configuration file \"{}\" not found",
|
||||
self.name.to_string_lossy()
|
||||
),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileSource for FileSourceFile {
|
||||
fn resolve(&self) -> Result<(Option<String>, String), Box<dyn Error + Send + Sync>> {
|
||||
// Find file.
|
||||
let filename = self.find_file()?;
|
||||
|
||||
// Attempt to use a relative path for the URI.
|
||||
let base = env::current_dir()?;
|
||||
let uri = match path_relative_from(&filename, &base) {
|
||||
Some(value) => value,
|
||||
None => filename.clone(),
|
||||
};
|
||||
|
||||
// Read contents from file.
|
||||
let mut file = fs::File::open(filename)?;
|
||||
let mut text = String::new();
|
||||
file.read_to_string(&mut text)?;
|
||||
|
||||
Ok((Some(uri.to_string_lossy().into_owned()), text))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This should probably be a crate.
|
||||
// https://github.com/rust-lang/rust/blob/master/src/librustc_trans/back/rpath.rs#L128
|
||||
fn path_relative_from(path: &Path, base: &Path) -> Option<PathBuf> {
|
||||
use std::path::Component;
|
||||
|
||||
if path.is_absolute() != base.is_absolute() {
|
||||
if path.is_absolute() {
|
||||
Some(PathBuf::from(path))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
let mut ita = path.components();
|
||||
let mut itb = base.components();
|
||||
let mut comps: Vec<Component> = vec![];
|
||||
loop {
|
||||
match (ita.next(), itb.next()) {
|
||||
(None, None) => break,
|
||||
(Some(a), None) => {
|
||||
comps.push(a);
|
||||
comps.extend(ita.by_ref());
|
||||
break;
|
||||
}
|
||||
(None, _) => comps.push(Component::ParentDir),
|
||||
(Some(a), Some(b)) if comps.is_empty() && a == b => (),
|
||||
(Some(a), Some(Component::CurDir)) => comps.push(a),
|
||||
(Some(_), Some(Component::ParentDir)) => return None,
|
||||
(Some(a), Some(_)) => {
|
||||
comps.push(Component::ParentDir);
|
||||
for _ in itb {
|
||||
comps.push(Component::ParentDir);
|
||||
}
|
||||
comps.push(a);
|
||||
comps.extend(ita.by_ref());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(comps.iter().map(|c| c.as_os_str()).collect())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
use crate::util::config::value::{Value, ValueKind};
|
||||
|
||||
use toml;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
|
||||
pub fn parse(
|
||||
uri: Option<&String>,
|
||||
text: &str,
|
||||
) -> Result<HashMap<String, Value>, Box<dyn Error + Send + Sync>> {
|
||||
// Parse a TOML value from the provided text.
|
||||
// TODO: Have a proper error fire if the root of a file is ever not a Table
|
||||
let value = from_toml_value(uri, &toml::from_str(text)?);
|
||||
match value.kind {
|
||||
ValueKind::Table(map) => Ok(map),
|
||||
|
||||
_ => Ok(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_toml_value(uri: Option<&String>, value: &toml::Value) -> Value {
|
||||
match *value {
|
||||
toml::Value::String(ref value) => Value::new(uri, value.to_string()),
|
||||
toml::Value::Float(value) => Value::new(uri, value),
|
||||
toml::Value::Integer(value) => Value::new(uri, value),
|
||||
toml::Value::Boolean(value) => Value::new(uri, value),
|
||||
|
||||
toml::Value::Table(ref table) => {
|
||||
let mut m = HashMap::new();
|
||||
|
||||
for (key, value) in table {
|
||||
m.insert(key.clone(), from_toml_value(uri, value));
|
||||
}
|
||||
|
||||
Value::new(uri, m)
|
||||
}
|
||||
|
||||
toml::Value::Array(ref array) => {
|
||||
let mut l = Vec::new();
|
||||
|
||||
for value in array {
|
||||
l.push(from_toml_value(uri, value));
|
||||
}
|
||||
|
||||
Value::new(uri, l)
|
||||
}
|
||||
|
||||
toml::Value::Datetime(ref datetime) => Value::new(uri, datetime.to_string()),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
use crate::util::config::error::*;
|
||||
use crate::util::config::value::{Value, ValueKind};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
mod parser;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
pub enum Expression {
|
||||
Identifier(String),
|
||||
Child(Box<Expression>, String),
|
||||
Subscript(Box<Expression>, isize),
|
||||
}
|
||||
|
||||
impl FromStr for Expression {
|
||||
type Err = ConfigError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Expression> {
|
||||
parser::from_str(s).map_err(ConfigError::PathParse)
|
||||
}
|
||||
}
|
||||
|
||||
fn sindex_to_uindex(index: isize, len: usize) -> usize {
|
||||
if index >= 0 {
|
||||
index as usize
|
||||
} else {
|
||||
len - (index.unsigned_abs())
|
||||
}
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
pub fn get_mut_forcibly<'a>(&self, root: &'a mut Value) -> Option<&'a mut Value> {
|
||||
match *self {
|
||||
Expression::Identifier(ref id) => match root.kind {
|
||||
ValueKind::Table(ref mut map) => Some(
|
||||
map.entry(id.clone())
|
||||
.or_insert_with(|| Value::new(None, ValueKind::Nil)),
|
||||
),
|
||||
|
||||
_ => None,
|
||||
},
|
||||
|
||||
Expression::Child(ref expr, ref key) => match expr.get_mut_forcibly(root) {
|
||||
Some(value) => match value.kind {
|
||||
ValueKind::Table(ref mut map) => Some(
|
||||
map.entry(key.clone())
|
||||
.or_insert_with(|| Value::new(None, ValueKind::Nil)),
|
||||
),
|
||||
|
||||
_ => {
|
||||
*value = HashMap::<String, Value>::new().into();
|
||||
|
||||
if let ValueKind::Table(ref mut map) = value.kind {
|
||||
Some(
|
||||
map.entry(key.clone())
|
||||
.or_insert_with(|| Value::new(None, ValueKind::Nil)),
|
||||
)
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_ => None,
|
||||
},
|
||||
|
||||
Expression::Subscript(ref expr, index) => match expr.get_mut_forcibly(root) {
|
||||
Some(value) => {
|
||||
match value.kind {
|
||||
ValueKind::Array(_) => (),
|
||||
_ => *value = Vec::<Value>::new().into(),
|
||||
}
|
||||
|
||||
match value.kind {
|
||||
ValueKind::Array(ref mut array) => {
|
||||
let index = sindex_to_uindex(index, array.len());
|
||||
|
||||
if index >= array.len() {
|
||||
array.resize(index + 1, Value::new(None, ValueKind::Nil));
|
||||
}
|
||||
|
||||
Some(&mut array[index])
|
||||
}
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&self, root: &mut Value, value: Value) {
|
||||
match *self {
|
||||
Expression::Identifier(ref id) => {
|
||||
// Ensure that root is a table.
|
||||
match root.kind {
|
||||
ValueKind::Table(_) => {}
|
||||
|
||||
_ => {
|
||||
*root = HashMap::<String, Value>::new().into();
|
||||
}
|
||||
}
|
||||
|
||||
match value.kind {
|
||||
ValueKind::Table(ref incoming_map) => {
|
||||
// Pull out another table.
|
||||
let target = if let ValueKind::Table(ref mut map) = root.kind {
|
||||
map.entry(id.clone())
|
||||
.or_insert_with(|| HashMap::<String, Value>::new().into())
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
// Continue the deep merge.
|
||||
for (key, val) in incoming_map {
|
||||
Expression::Identifier(key.clone()).set(target, val.clone());
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
if let ValueKind::Table(ref mut map) = root.kind {
|
||||
// Just do a simple set.
|
||||
map.insert(id.clone(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Expression::Child(ref expr, ref key) => {
|
||||
if let Some(parent) = expr.get_mut_forcibly(root) {
|
||||
match parent.kind {
|
||||
ValueKind::Table(_) => {
|
||||
Expression::Identifier(key.clone()).set(parent, value);
|
||||
}
|
||||
|
||||
_ => {
|
||||
// Didn't find a table. Oh well. Make a table and do this anyway.
|
||||
*parent = HashMap::<String, Value>::new().into();
|
||||
|
||||
Expression::Identifier(key.clone()).set(parent, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Expression::Subscript(ref expr, index) => {
|
||||
if let Some(parent) = expr.get_mut_forcibly(root) {
|
||||
match parent.kind {
|
||||
ValueKind::Array(_) => (),
|
||||
_ => *parent = Vec::<Value>::new().into(),
|
||||
}
|
||||
|
||||
if let ValueKind::Array(ref mut array) = parent.kind {
|
||||
let uindex = sindex_to_uindex(index, array.len());
|
||||
if uindex >= array.len() {
|
||||
array.resize(uindex + 1, Value::new(None, ValueKind::Nil));
|
||||
}
|
||||
|
||||
array[uindex] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
use super::Expression;
|
||||
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::{is_a, tag},
|
||||
character::complete::{char, digit1, space0},
|
||||
combinator::{map, map_res, opt, recognize},
|
||||
error::ErrorKind,
|
||||
sequence::{delimited, pair, preceded},
|
||||
Err, IResult,
|
||||
};
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
fn raw_ident(i: &str) -> IResult<&str, String> {
|
||||
map(
|
||||
is_a(
|
||||
"abcdefghijklmnopqrstuvwxyz \
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ \
|
||||
0123456789 \
|
||||
_-",
|
||||
),
|
||||
|s: &str| s.to_string(),
|
||||
)(i)
|
||||
}
|
||||
|
||||
fn integer(i: &str) -> IResult<&str, isize> {
|
||||
map_res(
|
||||
delimited(space0, recognize(pair(opt(tag("-")), digit1)), space0),
|
||||
FromStr::from_str,
|
||||
)(i)
|
||||
}
|
||||
|
||||
fn ident(i: &str) -> IResult<&str, Expression> {
|
||||
map(raw_ident, Expression::Identifier)(i)
|
||||
}
|
||||
|
||||
fn postfix<'a>(expr: Expression) -> impl FnMut(&'a str) -> IResult<&'a str, Expression> {
|
||||
let e2 = expr.clone();
|
||||
let child = map(preceded(tag("."), raw_ident), move |id| {
|
||||
Expression::Child(Box::new(expr.clone()), id)
|
||||
});
|
||||
|
||||
let subscript = map(delimited(char('['), integer, char(']')), move |num| {
|
||||
Expression::Subscript(Box::new(e2.clone()), num)
|
||||
});
|
||||
|
||||
alt((child, subscript))
|
||||
}
|
||||
|
||||
pub fn from_str(input: &str) -> Result<Expression, ErrorKind> {
|
||||
match ident(input) {
|
||||
Ok((mut rem, mut expr)) => {
|
||||
while !rem.is_empty() {
|
||||
match postfix(expr)(rem) {
|
||||
Ok((rem_, expr_)) => {
|
||||
rem = rem_;
|
||||
expr = expr_;
|
||||
}
|
||||
|
||||
// Forward Incomplete and Error
|
||||
result => {
|
||||
return result.map(|(_, o)| o).map_err(to_error_kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
// Forward Incomplete and Error
|
||||
result => result.map(|(_, o)| o).map_err(to_error_kind),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_error_kind(e: Err<nom::error::Error<&str>>) -> ErrorKind {
|
||||
match e {
|
||||
Err::Incomplete(_) => ErrorKind::Complete,
|
||||
Err::Failure(e) | Err::Error(e) => e.code,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Expression::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_id() {
|
||||
let parsed: Expression = from_str("abcd").unwrap();
|
||||
assert_eq!(parsed, Identifier("abcd".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_id_dash() {
|
||||
let parsed: Expression = from_str("abcd-efgh").unwrap();
|
||||
assert_eq!(parsed, Identifier("abcd-efgh".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_child() {
|
||||
let parsed: Expression = from_str("abcd.efgh").unwrap();
|
||||
let expected = Child(Box::new(Identifier("abcd".into())), "efgh".into());
|
||||
|
||||
assert_eq!(parsed, expected);
|
||||
|
||||
let parsed: Expression = from_str("abcd.efgh.ijkl").unwrap();
|
||||
let expected = Child(
|
||||
Box::new(Child(Box::new(Identifier("abcd".into())), "efgh".into())),
|
||||
"ijkl".into(),
|
||||
);
|
||||
|
||||
assert_eq!(parsed, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_subscript() {
|
||||
let parsed: Expression = from_str("abcd[12]").unwrap();
|
||||
let expected = Subscript(Box::new(Identifier("abcd".into())), 12);
|
||||
|
||||
assert_eq!(parsed, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_subscript_neg() {
|
||||
let parsed: Expression = from_str("abcd[-1]").unwrap();
|
||||
let expected = Subscript(Box::new(Identifier("abcd".into())), -1);
|
||||
|
||||
assert_eq!(parsed, expected);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
use crate::util::config::error::*;
|
||||
use crate::util::config::path;
|
||||
use crate::util::config::value::{Value, ValueKind};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Describes a generic _source_ of configuration properties.
|
||||
pub trait Source: Debug {
|
||||
fn clone_into_box(&self) -> Box<dyn Source + Send + Sync>;
|
||||
|
||||
/// Collect all configuration properties available from this source and return a HashMap.
|
||||
fn collect(&self) -> Result<HashMap<String, Value>>;
|
||||
|
||||
fn collect_to(&self, cache: &mut Value) -> Result<()> {
|
||||
let props = match self.collect() {
|
||||
Ok(props) => props,
|
||||
Err(error) => {
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
|
||||
for (key, val) in &props {
|
||||
match path::Expression::from_str(key) {
|
||||
// Set using the path.
|
||||
Ok(expr) => expr.set(cache, val.clone()),
|
||||
|
||||
// Set diretly anyway.
|
||||
_ => path::Expression::Identifier(key.clone()).set(cache, val.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn Source + Send + Sync> {
|
||||
fn clone(&self) -> Box<dyn Source + Send + Sync> {
|
||||
self.clone_into_box()
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for Vec<Box<dyn Source + Send + Sync>> {
|
||||
fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
|
||||
Box::new((*self).clone())
|
||||
}
|
||||
|
||||
fn collect(&self) -> Result<HashMap<String, Value>> {
|
||||
let mut cache: Value = HashMap::<String, Value>::new().into();
|
||||
|
||||
for source in self {
|
||||
source.collect_to(&mut cache)?;
|
||||
}
|
||||
|
||||
if let ValueKind::Table(table) = cache.kind {
|
||||
Ok(table)
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Source for Vec<T>
|
||||
where
|
||||
T: Source + Sync + Send,
|
||||
T: Clone,
|
||||
T: 'static,
|
||||
{
|
||||
fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
|
||||
Box::new((*self).clone())
|
||||
}
|
||||
|
||||
fn collect(&self) -> Result<HashMap<String, Value>> {
|
||||
let mut cache: Value = HashMap::<String, Value>::new().into();
|
||||
|
||||
for source in self {
|
||||
source.collect_to(&mut cache)?;
|
||||
}
|
||||
|
||||
if let ValueKind::Table(table) = cache.kind {
|
||||
Ok(table)
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,545 +0,0 @@
|
|||
use crate::util::config::error::*;
|
||||
|
||||
use serde::de::{Deserialize, Deserializer, Visitor};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
|
||||
/// Underlying kind of the configuration value.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub enum ValueKind {
|
||||
#[default]
|
||||
Nil,
|
||||
Boolean(bool),
|
||||
Integer(i64),
|
||||
Float(f64),
|
||||
String(String),
|
||||
Table(Table),
|
||||
Array(Array),
|
||||
}
|
||||
|
||||
pub type Array = Vec<Value>;
|
||||
pub type Table = HashMap<String, Value>;
|
||||
|
||||
impl<T> From<Option<T>> for ValueKind
|
||||
where
|
||||
T: Into<ValueKind>,
|
||||
{
|
||||
fn from(value: Option<T>) -> Self {
|
||||
match value {
|
||||
Some(value) => value.into(),
|
||||
None => ValueKind::Nil,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ValueKind {
|
||||
fn from(value: String) -> Self {
|
||||
ValueKind::String(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for ValueKind {
|
||||
fn from(value: &'a str) -> Self {
|
||||
ValueKind::String(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for ValueKind {
|
||||
fn from(value: i64) -> Self {
|
||||
ValueKind::Integer(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for ValueKind {
|
||||
fn from(value: f64) -> Self {
|
||||
ValueKind::Float(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for ValueKind {
|
||||
fn from(value: bool) -> Self {
|
||||
ValueKind::Boolean(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<HashMap<String, T>> for ValueKind
|
||||
where
|
||||
T: Into<Value>,
|
||||
{
|
||||
fn from(values: HashMap<String, T>) -> Self {
|
||||
let mut r = HashMap::new();
|
||||
|
||||
for (k, v) in values {
|
||||
r.insert(k.clone(), v.into());
|
||||
}
|
||||
|
||||
ValueKind::Table(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Vec<T>> for ValueKind
|
||||
where
|
||||
T: Into<Value>,
|
||||
{
|
||||
fn from(values: Vec<T>) -> Self {
|
||||
let mut l = Vec::new();
|
||||
|
||||
for v in values {
|
||||
l.push(v.into());
|
||||
}
|
||||
|
||||
ValueKind::Array(l)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ValueKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
ValueKind::String(ref value) => write!(f, "{}", value),
|
||||
ValueKind::Boolean(value) => write!(f, "{}", value),
|
||||
ValueKind::Integer(value) => write!(f, "{}", value),
|
||||
ValueKind::Float(value) => write!(f, "{}", value),
|
||||
ValueKind::Nil => write!(f, "nil"),
|
||||
|
||||
// TODO: Figure out a nice Display for these
|
||||
ValueKind::Table(ref table) => write!(f, "{:?}", table),
|
||||
ValueKind::Array(ref array) => write!(f, "{:?}", array),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A configuration value.
|
||||
#[derive(Default, Debug, Clone, PartialEq)]
|
||||
pub struct Value {
|
||||
/// A description of the original location of the value.
|
||||
///
|
||||
/// A Value originating from a File might contain:
|
||||
/// ```text
|
||||
/// Settings.toml
|
||||
/// ```
|
||||
///
|
||||
/// A Value originating from the environment would contain:
|
||||
/// ```text
|
||||
/// the envrionment
|
||||
/// ```
|
||||
///
|
||||
/// A Value originating from a remote source might contain:
|
||||
/// ```text
|
||||
/// etcd+http://127.0.0.1:2379
|
||||
/// ```
|
||||
origin: Option<String>,
|
||||
|
||||
/// Underlying kind of the configuration value.
|
||||
pub kind: ValueKind,
|
||||
}
|
||||
|
||||
impl Value {
|
||||
/// Create a new value instance that will remember its source uri.
|
||||
pub fn new<V>(origin: Option<&String>, kind: V) -> Self
|
||||
where
|
||||
V: Into<ValueKind>,
|
||||
{
|
||||
Value {
|
||||
origin: origin.cloned(),
|
||||
kind: kind.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to deserialize this value into the requested type.
|
||||
pub fn try_into<'de, T: Deserialize<'de>>(self) -> Result<T> {
|
||||
T::deserialize(self)
|
||||
}
|
||||
|
||||
/// Returns `self` as a bool, if possible.
|
||||
// FIXME: Should this not be `try_into_*` ?
|
||||
pub fn into_bool(self) -> Result<bool> {
|
||||
match self.kind {
|
||||
ValueKind::Boolean(value) => Ok(value),
|
||||
ValueKind::Integer(value) => Ok(value != 0),
|
||||
ValueKind::Float(value) => Ok(value != 0.0),
|
||||
|
||||
ValueKind::String(ref value) => {
|
||||
match value.to_lowercase().as_ref() {
|
||||
"1" | "true" | "on" | "yes" => Ok(true),
|
||||
"0" | "false" | "off" | "no" => Ok(false),
|
||||
|
||||
// Unexpected string value
|
||||
s => Err(ConfigError::invalid_type(
|
||||
self.origin.clone(),
|
||||
Unexpected::Str(s.into()),
|
||||
"a boolean",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// Unexpected type
|
||||
ValueKind::Nil => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Unit,
|
||||
"a boolean",
|
||||
)),
|
||||
ValueKind::Table(_) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Map,
|
||||
"a boolean",
|
||||
)),
|
||||
ValueKind::Array(_) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Seq,
|
||||
"a boolean",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `self` into an i64, if possible.
|
||||
// FIXME: Should this not be `try_into_*` ?
|
||||
pub fn into_int(self) -> Result<i64> {
|
||||
match self.kind {
|
||||
ValueKind::Integer(value) => Ok(value),
|
||||
|
||||
ValueKind::String(ref s) => {
|
||||
match s.to_lowercase().as_ref() {
|
||||
"true" | "on" | "yes" => Ok(1),
|
||||
"false" | "off" | "no" => Ok(0),
|
||||
_ => {
|
||||
s.parse().map_err(|_| {
|
||||
// Unexpected string
|
||||
ConfigError::invalid_type(
|
||||
self.origin.clone(),
|
||||
Unexpected::Str(s.clone()),
|
||||
"an integer",
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::bool_to_int_with_if)]
|
||||
ValueKind::Boolean(value) => Ok(if value { 1 } else { 0 }),
|
||||
|
||||
ValueKind::Float(value) => Ok(value.round() as i64),
|
||||
|
||||
// Unexpected type
|
||||
ValueKind::Nil => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Unit,
|
||||
"an integer",
|
||||
)),
|
||||
|
||||
ValueKind::Table(_) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Map,
|
||||
"an integer",
|
||||
)),
|
||||
|
||||
ValueKind::Array(_) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Seq,
|
||||
"an integer",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `self` into a f64, if possible.
|
||||
// FIXME: Should this not be `try_into_*` ?
|
||||
pub fn into_float(self) -> Result<f64> {
|
||||
match self.kind {
|
||||
ValueKind::Float(value) => Ok(value),
|
||||
|
||||
ValueKind::String(ref s) => {
|
||||
match s.to_lowercase().as_ref() {
|
||||
"true" | "on" | "yes" => Ok(1.0),
|
||||
"false" | "off" | "no" => Ok(0.0),
|
||||
_ => {
|
||||
s.parse().map_err(|_| {
|
||||
// Unexpected string
|
||||
ConfigError::invalid_type(
|
||||
self.origin.clone(),
|
||||
Unexpected::Str(s.clone()),
|
||||
"a floating point",
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ValueKind::Integer(value) => Ok(value as f64),
|
||||
ValueKind::Boolean(value) => Ok(if value { 1.0 } else { 0.0 }),
|
||||
|
||||
// Unexpected type.
|
||||
ValueKind::Nil => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Unit,
|
||||
"a floating point",
|
||||
)),
|
||||
ValueKind::Table(_) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Map,
|
||||
"a floating point",
|
||||
)),
|
||||
ValueKind::Array(_) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Seq,
|
||||
"a floating point",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `self` into a str, if possible.
|
||||
// FIXME: Should this not be `try_into_*` ?
|
||||
pub fn into_str(self) -> Result<String> {
|
||||
match self.kind {
|
||||
ValueKind::String(value) => Ok(value),
|
||||
|
||||
ValueKind::Boolean(value) => Ok(value.to_string()),
|
||||
ValueKind::Integer(value) => Ok(value.to_string()),
|
||||
ValueKind::Float(value) => Ok(value.to_string()),
|
||||
|
||||
// Cannot convert.
|
||||
ValueKind::Nil => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Unit,
|
||||
"a string",
|
||||
)),
|
||||
ValueKind::Table(_) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Map,
|
||||
"a string",
|
||||
)),
|
||||
ValueKind::Array(_) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Seq,
|
||||
"a string",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `self` into an array, if possible.
|
||||
// FIXME: Should this not be `try_into_*` ?
|
||||
pub fn into_array(self) -> Result<Vec<Value>> {
|
||||
match self.kind {
|
||||
ValueKind::Array(value) => Ok(value),
|
||||
|
||||
// Cannot convert.
|
||||
ValueKind::Float(value) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Float(value),
|
||||
"an array",
|
||||
)),
|
||||
ValueKind::String(value) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Str(value),
|
||||
"an array",
|
||||
)),
|
||||
ValueKind::Integer(value) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Integer(value),
|
||||
"an array",
|
||||
)),
|
||||
ValueKind::Boolean(value) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Bool(value),
|
||||
"an array",
|
||||
)),
|
||||
ValueKind::Nil => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Unit,
|
||||
"an array",
|
||||
)),
|
||||
ValueKind::Table(_) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Map,
|
||||
"an array",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// If the `Value` is a Table, returns the associated Map.
|
||||
// FIXME: Should this not be `try_into_*` ?
|
||||
pub fn into_table(self) -> Result<HashMap<String, Value>> {
|
||||
match self.kind {
|
||||
ValueKind::Table(value) => Ok(value),
|
||||
|
||||
// Cannot convert.
|
||||
ValueKind::Float(value) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Float(value),
|
||||
"a map",
|
||||
)),
|
||||
ValueKind::String(value) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Str(value),
|
||||
"a map",
|
||||
)),
|
||||
ValueKind::Integer(value) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Integer(value),
|
||||
"a map",
|
||||
)),
|
||||
ValueKind::Boolean(value) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Bool(value),
|
||||
"a map",
|
||||
)),
|
||||
ValueKind::Nil => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Unit,
|
||||
"a map",
|
||||
)),
|
||||
ValueKind::Array(_) => Err(ConfigError::invalid_type(
|
||||
self.origin,
|
||||
Unexpected::Seq,
|
||||
"a map",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Value {
|
||||
#[inline]
|
||||
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Value, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct ValueVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ValueVisitor {
|
||||
type Value = Value;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("any valid configuration value")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_bool<E>(self, value: bool) -> ::std::result::Result<Value, E> {
|
||||
Ok(value.into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_i8<E>(self, value: i8) -> ::std::result::Result<Value, E> {
|
||||
Ok((value as i64).into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_i16<E>(self, value: i16) -> ::std::result::Result<Value, E> {
|
||||
Ok((value as i64).into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_i32<E>(self, value: i32) -> ::std::result::Result<Value, E> {
|
||||
Ok((value as i64).into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_i64<E>(self, value: i64) -> ::std::result::Result<Value, E> {
|
||||
Ok(value.into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_u8<E>(self, value: u8) -> ::std::result::Result<Value, E> {
|
||||
Ok((value as i64).into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_u16<E>(self, value: u16) -> ::std::result::Result<Value, E> {
|
||||
Ok((value as i64).into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_u32<E>(self, value: u32) -> ::std::result::Result<Value, E> {
|
||||
Ok((value as i64).into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_u64<E>(self, value: u64) -> ::std::result::Result<Value, E> {
|
||||
// FIXME: This is bad
|
||||
Ok((value as i64).into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_f64<E>(self, value: f64) -> ::std::result::Result<Value, E> {
|
||||
Ok(value.into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_str<E>(self, value: &str) -> ::std::result::Result<Value, E>
|
||||
where
|
||||
E: ::serde::de::Error,
|
||||
{
|
||||
self.visit_string(String::from(value))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_string<E>(self, value: String) -> ::std::result::Result<Value, E> {
|
||||
Ok(value.into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_none<E>(self) -> ::std::result::Result<Value, E> {
|
||||
Ok(Value::new(None, ValueKind::Nil))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_some<D>(self, deserializer: D) -> ::std::result::Result<Value, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Deserialize::deserialize(deserializer)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_unit<E>(self) -> ::std::result::Result<Value, E> {
|
||||
Ok(Value::new(None, ValueKind::Nil))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_seq<V>(self, mut visitor: V) -> ::std::result::Result<Value, V::Error>
|
||||
where
|
||||
V: ::serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let mut vec = Array::new();
|
||||
|
||||
while let Some(elem) = visitor.next_element()? {
|
||||
vec.push(elem);
|
||||
}
|
||||
|
||||
Ok(vec.into())
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut visitor: V) -> ::std::result::Result<Value, V::Error>
|
||||
where
|
||||
V: ::serde::de::MapAccess<'de>,
|
||||
{
|
||||
let mut values = Table::new();
|
||||
|
||||
while let Some((key, value)) = visitor.next_entry()? {
|
||||
values.insert(key, value);
|
||||
}
|
||||
|
||||
Ok(values.into())
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(ValueVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Value
|
||||
where
|
||||
T: Into<ValueKind>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Value {
|
||||
origin: None,
|
||||
kind: value.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Value {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.kind)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue