🧑‍💻 Añade macros para concatenar cadenas

This commit is contained in:
Manuel Cillero 2025-07-16 00:45:05 +02:00
parent 0df5f3f1c7
commit 74e497f170
4 changed files with 172 additions and 3 deletions

9
Cargo.lock generated
View file

@ -525,6 +525,12 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "concat-string"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7439becb5fafc780b6f4de382b1a7a3e70234afe783854a4702ee8adbb838609"
[[package]] [[package]]
name = "config" name = "config"
version = "0.15.13" version = "0.15.13"
@ -1407,13 +1413,14 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]] [[package]]
name = "pagetop" name = "pagetop"
version = "0.0.10" version = "0.0.11"
dependencies = [ dependencies = [
"actix-files", "actix-files",
"actix-web", "actix-web",
"actix-web-static-files", "actix-web-static-files",
"chrono", "chrono",
"colored", "colored",
"concat-string",
"config", "config",
"figlet-rs", "figlet-rs",
"fluent-templates", "fluent-templates",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "pagetop" name = "pagetop"
version = "0.0.10" version = "0.0.11"
edition = "2021" edition = "2021"
description = """\ description = """\
@ -17,6 +17,7 @@ authors.workspace = true
[dependencies] [dependencies]
chrono = "0.4.41" chrono = "0.4.41"
colored = "3.0.0" colored = "3.0.0"
concat-string = "1.0.1"
config = { version = "0.15.13", default-features = false, features = ["toml"] } config = { version = "0.15.13", default-features = false, features = ["toml"] }
figlet-rs = "0.1.5" figlet-rs = "0.1.5"
itoa = "1.0.15" itoa = "1.0.15"

View file

@ -9,7 +9,7 @@ pub use crate::{AutoDefault, StaticResources};
// MACROS. // MACROS.
// crate::util // crate::util
pub use crate::hm; pub use crate::{hm, join, join_opt, join_pair, join_strict};
// crate::config // crate::config
pub use crate::include_config; pub use crate::include_config;
// crate::locale // crate::locale

View file

@ -5,6 +5,7 @@ use crate::trace;
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
// FUNCIONES ÚTILES ********************************************************************************
/// Devuelve la ruta absoluta a un directorio existente. /// Devuelve la ruta absoluta a un directorio existente.
/// ///
@ -51,6 +52,9 @@ where
#[doc(hidden)] #[doc(hidden)]
pub use paste::paste; pub use paste::paste;
#[doc(hidden)]
pub use concat_string::concat_string;
#[macro_export] #[macro_export]
/// Macro para construir una colección de pares clave-valor. /// Macro para construir una colección de pares clave-valor.
/// ///
@ -73,3 +77,160 @@ macro_rules! hm {
a a
}}; }};
} }
/// Concatena eficientemente varios fragmentos en un [`String`].
///
/// Esta macro exporta [`concat_string!`](https://docs.rs/concat-string). Acepta cualquier número de
/// fragmentos que implementen [`AsRef<str>`] y construye un [`String`] con el tamaño óptimo, de
/// forma eficiente y evitando el uso de cadenas de formato que penalicen el rendimiento.
///
/// # Ejemplo
///
/// ```rust
/// use pagetop::prelude::*;
///
/// // Concatena todos los fragmentos directamente.
/// let result = join!("Hello", " ", "World");
/// assert_eq!(result, String::from("Hello World"));
///
/// // También funciona con valores vacíos.
/// let result_with_empty = join!("Hello", "", "World");
/// assert_eq!(result_with_empty, String::from("HelloWorld"));
///
/// // Un único fragmento devuelve el mismo valor.
/// let single_result = join!("Hello");
/// assert_eq!(single_result, String::from("Hello"));
/// ```
#[macro_export]
macro_rules! join {
($($arg:tt)*) => {
$crate::util::concat_string!($($arg)*)
};
}
/// Concatena los fragmentos no vacíos en un [`Option<String>`] con un separador opcional.
///
/// Esta macro acepta cualquier número de fragmentos que implementen [`AsRef<str>`] para concatenar
/// todos los fragmentos no vacíos usando opcionalmente un separador.
///
/// Si todos los fragmentos están vacíos, devuelve [`None`].
///
/// # Ejemplo
///
/// ```rust
/// use pagetop::prelude::*;
///
/// // Concatena los fragmentos no vacíos con un espacio como separador.
/// let result_with_separator = join_opt!(["Hello", "", "World"]; " ");
/// assert_eq!(result_with_separator, Some(String::from("Hello World")));
///
/// // Concatena los fragmentos no vacíos sin un separador.
/// let result_without_separator = join_opt!(["Hello", "", "World"]);
/// assert_eq!(result_without_separator, Some(String::from("HelloWorld")));
///
/// // Devuelve `None` si todos los fragmentos están vacíos.
/// let result_empty = join_opt!(["", "", ""]);
/// assert_eq!(result_empty, None);
/// ```
#[macro_export]
macro_rules! join_opt {
([$($arg:expr),* $(,)?]) => {{
let s = $crate::util::concat_string!($($arg),*);
(!s.is_empty()).then_some(s)
}};
([$($arg:expr),* $(,)?]; $separator:expr) => {{
let s = [$($arg),*]
.iter()
.filter(|&item| !item.is_empty())
.cloned()
.collect::<Vec<_>>()
.join($separator);
(!s.is_empty()).then_some(s)
}};
}
/// Concatena dos fragmentos en un [`String`] usando un separador.
///
/// Une los dos fragmentos, que deben implementar [`AsRef<str>`], usando el separador proporcionado.
/// Si uno de ellos está vacío, devuelve directamente el otro; y si ambos están vacíos devuelve un
/// [`String`] vacío.
///
/// # Ejemplo
///
/// ```rust
/// use pagetop::prelude::*;
///
/// let first = "Hello";
/// let separator = "-";
/// let second = "World";
///
/// // Concatena los dos fragmentos cuando ambos no están vacíos.
/// let result = join_pair!(first, separator, second);
/// assert_eq!(result, String::from("Hello-World"));
///
/// // Si el primer fragmento está vacío, devuelve el segundo.
/// let result_empty_first = join_pair!("", separator, second);
/// assert_eq!(result_empty_first, String::from("World"));
///
/// // Si el segundo fragmento está vacío, devuelve el primero.
/// let result_empty_second = join_pair!(first, separator, "");
/// assert_eq!(result_empty_second, String::from("Hello"));
///
/// // Si ambos fragmentos están vacíos, devuelve una cadena vacía.
/// let result_both_empty = join_pair!("", separator, "");
/// assert_eq!(result_both_empty, String::from(""));
/// ```
#[macro_export]
macro_rules! join_pair {
($first:expr, $separator:expr, $second:expr) => {{
if $first.is_empty() {
String::from($second)
} else if $second.is_empty() {
String::from($first)
} else {
$crate::util::concat_string!($first, $separator, $second)
}
}};
}
/// Concatena varios fragmentos en un [`Option<String>`] si ninguno está vacío.
///
/// Si alguno de los fragmentos, que deben implementar [`AsRef<str>`], está vacío, devuelve
/// [`None`]. Opcionalmente se puede indicar un separador entre los fragmentos concatenados.
///
/// # Ejemplo
///
/// ```rust
/// use pagetop::prelude::*;
///
/// // Concatena los fragmentos.
/// let result = join_strict!(["Hello", "World"]);
/// assert_eq!(result, Some(String::from("HelloWorld")));
///
/// // Concatena los fragmentos con un separador.
/// let result_with_separator = join_strict!(["Hello", "World"]; " ");
/// assert_eq!(result_with_separator, Some(String::from("Hello World")));
///
/// // Devuelve `None` si alguno de los fragmentos está vacío.
/// let result_with_empty = join_strict!(["Hello", "", "World"]);
/// assert_eq!(result_with_empty, None);
/// ```
#[macro_export]
macro_rules! join_strict {
([$($arg:expr),* $(,)?]) => {{
let fragments = [$($arg),*];
if fragments.iter().any(|&item| item.is_empty()) {
None
} else {
Some(fragments.concat())
}
}};
([$($arg:expr),* $(,)?]; $separator:expr) => {{
let fragments = [$($arg),*];
if fragments.iter().any(|&item| item.is_empty()) {
None
} else {
Some(fragments.join($separator))
}
}};
}